Skip to content

Commit

Permalink
refactor(ui): TextField 컴포넌트, FieldBox를 사용한 형태로 리팩토링. (#195)
Browse files Browse the repository at this point in the history
* refactor: FieldBox 기반의 컴포넌트로 리팩토링

* docs: TextField Storybook 문서 수정

* fix: FieldBox.Label Optional prop으로 수정

* cs

* feat: add bottomAddon prop.
  • Loading branch information
Brokyeom authored Nov 2, 2024
1 parent 62d2aa0 commit 37b6b9b
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 25 deletions.
5 changes: 5 additions & 0 deletions .changeset/tiny-days-hammer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sopt-makers/ui': patch
---

TextField 컴포넌트를 FieldBox 기반으로 수정합니다.
18 changes: 9 additions & 9 deletions apps/docs/src/stories/TextField.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,22 @@ const useTextField = (props: TextFieldProps) => {

const handleTextChange = (e: ChangeEvent<HTMLInputElement>) => {
setText(e.target.value);
}
};

return <TextField<string> {...props} value={text} onChange={handleTextChange} />
}
return <TextField<string> {...props} value={text} onChange={handleTextChange} />;
};

export default {
title: 'Components/Input/TextField',
component: useTextField,
tags: ['autodocs'],
args: {
style: { width: '335px' }
style: { width: '335px' },
},
argTypes: {
style: { control: false }
}
}
style: { control: false },
},
};

export const Default: StoryObj<TextFieldProps> = {
args: {
Expand All @@ -52,7 +52,7 @@ export const NoLabel: StoryObj<TextFieldProps> = {
placeholder: 'Placeholder...',
isError: false,
errorMessage: 'error message',
required: true,
required: false,
readOnly: false,
disabled: false,
},
Expand Down Expand Up @@ -136,4 +136,4 @@ export const Error: StoryObj<TextFieldProps> = {
readOnly: false,
disabled: false,
},
};
};
8 changes: 4 additions & 4 deletions packages/ui/FieldBox/components/Label.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import { forwardRef } from 'react';
import { requiredMarkStyle, TopAddonDescriptionStyle, TopAddonLabelStyle } from '../style.css';

export interface FieldBoxLabelProps extends HTMLAttributes<HTMLDivElement> {
label: string;
description: string;
required: boolean;
label?: string;
description?: string;
required?: boolean;
}

export const FieldBoxLabel = forwardRef<HTMLDivElement, FieldBoxLabelProps>((props, forwardedRef) => {
const { required, label, description } = props;
const { required = false, label, description } = props;

return (
<div aria-label={label} aria-required={required} ref={forwardedRef}>
Expand Down
51 changes: 39 additions & 12 deletions packages/ui/Input/TextField.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { type InputHTMLAttributes } from 'react';
import type { ReactNode, InputHTMLAttributes } from 'react';
import { FieldBox } from 'index';
import * as S from './style.css';
import AlertCircleIcon from './icons/AlertCircleIcon';

interface TextFieldProps<T> extends Omit<InputHTMLAttributes<HTMLInputElement>, 'value'> {
className?: string;
topAddon?: ReactNode;
bottomAddon?: ReactNode;
labelText?: string;
descriptionText?: string;
required?: boolean;
errorMessage?: string;
value: T;
// isError -> validationFn 순서로 적용
Expand All @@ -14,23 +17,47 @@ interface TextFieldProps<T> extends Omit<InputHTMLAttributes<HTMLInputElement>,
}

function TextField<T extends string | number>(props: TextFieldProps<T>) {
const { className, labelText, descriptionText, errorMessage, value, isError, validationFn, ...inputProps } = props;
const {
className,
topAddon,
bottomAddon,
labelText,
descriptionText,
required,
errorMessage,
value,
isError,
validationFn,
...inputProps
} = props;

const hasError = () => {
if (inputProps.disabled || inputProps.readOnly) return false;
if (isError !== undefined) return isError;
if (validationFn && !validationFn(value)) return true;
return false;
}
};

const required = inputProps.required ? <span className={S.required}>*</span> : null;
const description = descriptionText ? <p className={S.description}>{descriptionText}</p> : null;
const input = <input {...inputProps} className={`${S.input} ${hasError() ? S.inputError : ''}`} value={value} />;

return <div className={className}>
{labelText ? <label className={S.label}><span>{labelText}{required}</span>{description}{input}</label> : <div className={S.inputWrap}>{description}{input}</div>}
{hasError() ? <div className={S.inputBottom}><div className={S.errorMessage}><AlertCircleIcon /><p>{errorMessage ?? 'error'}</p></div></div> : null}
</div>
return (
<FieldBox
bottomAddon={
<FieldBox.BottomAddon
leftAddon={hasError() && errorMessage ? <FieldBox.ErrorMessage message={errorMessage} /> : null}
rightAddon={bottomAddon}
/>
}
className={className}
topAddon={
labelText || descriptionText ? (
<FieldBox.Label description={descriptionText} label={labelText} required={required} />
) : (
topAddon
)
}
>
<input {...inputProps} className={`${S.input} ${hasError() ? S.inputError : ''}`} value={value} />
</FieldBox>
);
}

export default TextField;

0 comments on commit 37b6b9b

Please sign in to comment.