From f90594fafe8907ea9077a14178fd723e1290323e Mon Sep 17 00:00:00 2001 From: "Lukas.J.Han" Date: Tue, 20 Aug 2024 20:37:54 +0900 Subject: [PATCH 1/3] 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, + }, +}; From 5f687459b047dd73e5c39bb261b2cab42ccce266 Mon Sep 17 00:00:00 2001 From: "Lukas.J.Han" Date: Tue, 20 Aug 2024 21:10:36 +0900 Subject: [PATCH 2/3] feat: add textArea component (#78) Signed-off-by: Lukas.J.Han --- packages/core/lib/components/TextArea.tsx | 79 ++++++++++++++ packages/core/lib/index.ts | 3 +- stories/core/TextArea.stories.ts | 123 ++++++++++++++++++++++ 3 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 packages/core/lib/components/TextArea.tsx create mode 100644 stories/core/TextArea.stories.ts diff --git a/packages/core/lib/components/TextArea.tsx b/packages/core/lib/components/TextArea.tsx new file mode 100644 index 0000000..ce149be --- /dev/null +++ b/packages/core/lib/components/TextArea.tsx @@ -0,0 +1,79 @@ +import React, { forwardRef, useState } from 'react'; +import { Label } from './Label'; + +type TextAreaProps = { + id: string; + title?: string; + description?: string; + size?: 'small' | 'medium' | 'large'; + maxLength?: number; +} & React.TextareaHTMLAttributes; + +export const TextArea = forwardRef( + ( + { + title, + description, + id, + placeholder, + size = 'medium', + maxLength, + onChange, + ...props + }, + ref + ) => { + const [charCount, setCharCount] = useState(0); + + const sizeClasses = { + small: 'h-24', + medium: 'h-32', + large: 'h-40', + }[size]; + + const handleChange = (e: React.ChangeEvent) => { + setCharCount(e.target.value.length); + if (onChange) { + onChange(e); + } + }; + + return ( +
+ {title && ( + + )} + {description && ( + + )} +
+