Skip to content

Commit

Permalink
feat: add textInput component (#77)
Browse files Browse the repository at this point in the history
Signed-off-by: Lukas.J.Han <lukas.j.han@gmail.com>
  • Loading branch information
lukasjhan authored Aug 20, 2024
1 parent 0d7e8e4 commit f90594f
Show file tree
Hide file tree
Showing 3 changed files with 266 additions and 1 deletion.
85 changes: 85 additions & 0 deletions packages/core/lib/components/TextInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React, { forwardRef } from 'react';
import { Label } from './Label';

type TextInputProps = {
id: string;
title?: string;
description?: string;
helpText?: string;
error?: string;
length?: 'x-short' | 'short' | 'middle' | 'long' | 'full';
} & React.InputHTMLAttributes<HTMLInputElement>;

export const TextInput = forwardRef<HTMLInputElement, TextInputProps>(
(
{
title,
description,
helpText,
error,
id,
placeholder,
length = 'middle',
...props
},
ref
) => {
const inputId = id;
const helperTextId = `${inputId}-help`;
const errorId = `${inputId}-error`;

const lengthClasses = {
'x-short': 'w-16',
short: 'w-32',
middle: 'w-64',
long: 'w-128',
full: 'w-full',
}[length];

return (
<div className="flex flex-col gap-1 justify-center">
{title && (
<Label htmlFor={inputId} weight="bold">
{title}
</Label>
)}
{description && (
<Label size={'s'} color={'gray-50'}>
{description}
</Label>
)}
<div className="relative">
<input
ref={ref}
id={inputId}
type="text"
className={`
${lengthClasses} px-4 py-3 text-gray-70 border rounded-2 focus:border-primary
focus:outline-none focus:ring-1 focus:ring-primary mt-3
${error ? 'border-danger' : 'border-gray-50'}
transition duration-150 ease-in-out
`}
placeholder={placeholder}
aria-describedby={error ? errorId : helperTextId}
aria-invalid={error ? 'true' : 'false'}
{...props}
/>
</div>
{error ? (
<Label id={errorId} size={'s'} color={'danger'} className="mt-1">
{error}
</Label>
) : helpText ? (
<Label
id={helperTextId}
size={'s'}
color={'gray-50'}
className="mt-1"
>
{helpText}
</Label>
) : null}
</div>
);
}
);
3 changes: 2 additions & 1 deletion packages/core/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { LinkButton } from './components/LinkButton';
import { Tag } from './components/Tag';
import { Spinner } from './components/Spinner';
import { Badge } from './components/Badge';
import { TextInput } from './components/TextInput';

export { Display, Heading, Title, Body, Detail, Label, Link, colors };
export { Button, LinkButton, Tag, Spinner, Badge };
export { Button, LinkButton, Tag, Spinner, Badge, TextInput };
179 changes: 179 additions & 0 deletions stories/core/TextInput.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import type { Meta, StoryObj } from '@storybook/react';
import { fn } from '@storybook/test';
import { TextInput } from '../../packages/core/lib';

const meta = {
title: 'Components/TextInput',
component: TextInput,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
length: {
control: { type: 'select' },
options: ['x-short', 'short', 'middle', 'long', 'full'],
},
title: {
control: {
type: 'text',
},
},
description: {
control: {
type: 'text',
},
},
helpText: {
control: {
type: 'text',
},
},
error: {
control: {
type: 'text',
},
},
onChange: { action: 'changed' },
maxLength: { control: { type: 'number' } },
},
} satisfies Meta<typeof TextInput>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Default: Story = {
args: {
id: 'text-input',
title: '이름',
description: '입력하신 이름이 저장됩니다.',
placeholder: '이름을 입력하세요',
onChange: (e: React.ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value);
},
length: 'long',
maxLength: 20,
},
};

export const Error: Story = {
args: {
id: 'text-input2',
title: '이름',
description: '입력하신 이름이 저장됩니다.',
placeholder: '이름을 입력하세요',
onChange: (e: React.ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value);
},
length: 'long',
maxLength: 20,
error: '이름을 입력하세요',
},
};

export const HelpText: Story = {
args: {
id: 'text-input3',
title: '이름',
description: '입력하신 이름이 저장됩니다.',
placeholder: '이름을 입력하세요',
onChange: (e: React.ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value);
},
length: 'long',
maxLength: 20,
helpText: '성과 이름을 한번에 입력하세요.',
},
};

export const XShort: Story = {
args: {
id: 'text-input4',
title: '이름',
description: '입력하신 이름이 저장됩니다.',
onChange: (e: React.ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value);
},
length: 'x-short',
maxLength: 20,
},
};

export const Short: Story = {
args: {
id: 'text-input5',
title: '이름',
description: '입력하신 이름이 저장됩니다.',
onChange: (e: React.ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value);
},
length: 'short',
maxLength: 20,
},
};

export const Middle: Story = {
args: {
id: 'text-input6',
title: '이름',
description: '입력하신 이름이 저장됩니다.',
onChange: (e: React.ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value);
},
length: 'middle',
maxLength: 20,
},
};

export const Long: Story = {
args: {
id: 'text-input7',
title: '이름',
description: '입력하신 이름이 저장됩니다.',
placeholder: '이름을 입력하세요',
onChange: (e: React.ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value);
},
length: 'long',
maxLength: 20,
},
};

export const Full: Story = {
args: {
id: 'text-input8',
title: '이름',
description: '입력하신 이름이 저장됩니다.',
placeholder: '이름을 입력하세요',
onChange: (e: React.ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value);
},
length: 'full',
maxLength: 20,
},
};

export const NoTitle: Story = {
args: {
id: 'text-input9',
description: '입력하신 이름이 저장됩니다.',
placeholder: '이름을 입력하세요',
onChange: (e: React.ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value);
},
length: 'long',
maxLength: 20,
},
};

export const NoDescription: Story = {
args: {
id: 'text-input10',
placeholder: '이름을 입력하세요',
onChange: (e: React.ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value);
},
length: 'long',
maxLength: 20,
},
};

0 comments on commit f90594f

Please sign in to comment.