From f90594fafe8907ea9077a14178fd723e1290323e Mon Sep 17 00:00:00 2001 From: "Lukas.J.Han" Date: Tue, 20 Aug 2024 20:37:54 +0900 Subject: [PATCH] feat: add textInput component (#77) Signed-off-by: Lukas.J.Han --- packages/core/lib/components/TextInput.tsx | 85 ++++++++++ packages/core/lib/index.ts | 3 +- stories/core/TextInput.stories.ts | 179 +++++++++++++++++++++ 3 files changed, 266 insertions(+), 1 deletion(-) create mode 100644 packages/core/lib/components/TextInput.tsx create mode 100644 stories/core/TextInput.stories.ts diff --git a/packages/core/lib/components/TextInput.tsx b/packages/core/lib/components/TextInput.tsx new file mode 100644 index 0000000..1b1877f --- /dev/null +++ b/packages/core/lib/components/TextInput.tsx @@ -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; + +export const TextInput = forwardRef( + ( + { + 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 ( +
+ {title && ( + + )} + {description && ( + + )} +
+ +
+ {error ? ( + + ) : helpText ? ( + + ) : null} +
+ ); + } +); diff --git a/packages/core/lib/index.ts b/packages/core/lib/index.ts index 1f89622..b56d28e 100644 --- a/packages/core/lib/index.ts +++ b/packages/core/lib/index.ts @@ -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 }; diff --git a/stories/core/TextInput.stories.ts b/stories/core/TextInput.stories.ts new file mode 100644 index 0000000..81db334 --- /dev/null +++ b/stories/core/TextInput.stories.ts @@ -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; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + id: 'text-input', + title: '이름', + description: '입력하신 이름이 저장됩니다.', + placeholder: '이름을 입력하세요', + onChange: (e: React.ChangeEvent) => { + console.log(e.target.value); + }, + length: 'long', + maxLength: 20, + }, +}; + +export const Error: Story = { + args: { + id: 'text-input2', + title: '이름', + description: '입력하신 이름이 저장됩니다.', + placeholder: '이름을 입력하세요', + onChange: (e: React.ChangeEvent) => { + 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) => { + console.log(e.target.value); + }, + length: 'long', + maxLength: 20, + helpText: '성과 이름을 한번에 입력하세요.', + }, +}; + +export const XShort: Story = { + args: { + id: 'text-input4', + title: '이름', + description: '입력하신 이름이 저장됩니다.', + onChange: (e: React.ChangeEvent) => { + console.log(e.target.value); + }, + length: 'x-short', + maxLength: 20, + }, +}; + +export const Short: Story = { + args: { + id: 'text-input5', + title: '이름', + description: '입력하신 이름이 저장됩니다.', + onChange: (e: React.ChangeEvent) => { + console.log(e.target.value); + }, + length: 'short', + maxLength: 20, + }, +}; + +export const Middle: Story = { + args: { + id: 'text-input6', + title: '이름', + description: '입력하신 이름이 저장됩니다.', + onChange: (e: React.ChangeEvent) => { + console.log(e.target.value); + }, + length: 'middle', + maxLength: 20, + }, +}; + +export const Long: Story = { + args: { + id: 'text-input7', + title: '이름', + description: '입력하신 이름이 저장됩니다.', + placeholder: '이름을 입력하세요', + onChange: (e: React.ChangeEvent) => { + console.log(e.target.value); + }, + length: 'long', + maxLength: 20, + }, +}; + +export const Full: Story = { + args: { + id: 'text-input8', + title: '이름', + description: '입력하신 이름이 저장됩니다.', + placeholder: '이름을 입력하세요', + onChange: (e: React.ChangeEvent) => { + console.log(e.target.value); + }, + length: 'full', + maxLength: 20, + }, +}; + +export const NoTitle: Story = { + args: { + id: 'text-input9', + description: '입력하신 이름이 저장됩니다.', + placeholder: '이름을 입력하세요', + onChange: (e: React.ChangeEvent) => { + console.log(e.target.value); + }, + length: 'long', + maxLength: 20, + }, +}; + +export const NoDescription: Story = { + args: { + id: 'text-input10', + placeholder: '이름을 입력하세요', + onChange: (e: React.ChangeEvent) => { + console.log(e.target.value); + }, + length: 'long', + maxLength: 20, + }, +};