-
Notifications
You must be signed in to change notification settings - Fork 1
Feat(design-system): textarea 공통 컴포넌트 구현 #23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
7a1f926
60099c1
ef5e33e
5dc7397
bb8f977
504689c
b5cfb37
e7f1d4b
0f37078
42d0faf
9388016
b102c1e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| export { default as Button } from './button/Button'; | ||
| export { Switch } from './switch/switch'; | ||
| export { default as Input } from './input/Input'; | ||
| export { Textarea } from './textarea/Textarea'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,110 @@ | ||
| import type { Meta, StoryObj } from '@storybook/react-vite'; | ||
| import { within, userEvent, expect } from '@storybook/test'; | ||
| import { Textarea } from './Textarea'; | ||
|
|
||
| const meta: Meta<typeof Textarea> = { | ||
| title: 'Components/Textarea', | ||
| component: Textarea, | ||
| tags: ['autodocs'], | ||
| parameters: { | ||
| layout: 'centered', | ||
| docs: { | ||
| description: { | ||
| component: | ||
| '고정 크기 **h-[12rem] / w-[24.8rem]** · 기본 **500자** 제한 · 내용 초과 시 내부 스크롤이 나타나는 텍스트영역입니다. ' + | ||
| '`maxLength`로 글자수 제한을 변경할 수 있습니다.', | ||
| }, | ||
| }, | ||
| }, | ||
| argTypes: { | ||
| className: { table: { disable: true } }, | ||
| maxLength: { control: { type: 'number', min: 1 } }, | ||
| placeholder: { control: 'text' }, | ||
| defaultValue: { control: 'text' }, | ||
| }, | ||
| args: { | ||
| placeholder: '나중에 내가 꺼내줄 수 있게 살짝 적어줘!', | ||
| maxLength: 500, | ||
| }, | ||
| }; | ||
|
|
||
| export default meta; | ||
| type Story = StoryObj<typeof Textarea>; | ||
|
|
||
| export const Basic: Story = { | ||
| render: (args) => <Textarea {...args} />, | ||
| }; | ||
|
|
||
| export const WithMaxLength500: Story = { | ||
| name: 'MaxLength=500', | ||
| args: { maxLength: 500, placeholder: '최대 500자' }, | ||
| render: (args) => <Textarea {...args} />, | ||
| }; | ||
|
|
||
| export const ScrollableOverflow: Story = { | ||
| name: '넘치면 스크롤', | ||
| args: { | ||
| defaultValue: `계절이 지나가는 하늘에는 | ||
| 가을로 가득 차 있습니다. | ||
|
|
||
| 나는 아무 걱정도 없이 | ||
| 가을 속의 별들을 다 헤일 듯합니다. | ||
|
|
||
| 가슴속에 하나둘 새겨지는 별을 | ||
| 이제 다 못 헤는 것은 | ||
| 쉬이 아침이 오는 까닭이요, | ||
| 내일 밤이 남은 까닭이요, | ||
| 아직 나의 청춘이 다하지 않은 까닭입니다. | ||
|
|
||
| 별 하나에 추억과 | ||
| 별 하나에 사랑과 | ||
| 별 하나에 쓸쓸함과 | ||
| 별 하나에 동경과 | ||
| 별 하나에 시와 | ||
| 별 하나에 어머니, 어머니, | ||
|
|
||
| 어머님, 나는 별 하나에 아름다운 말 한마디씩 불러 봅니다. 소학교 때 책상을 같이 했던 아이들의 이름과, 패, 경, 옥, 이런 이국 소녀들의 이름과, 벌써 아기 어머니 된 계집애들의 이름과, 가난한 이웃 사람들의 이름과, 비둘기, 강아지, 토끼, 노새, 노루, '프랑시스 잠', '라이너 마리아 릴케' 이런 시인의 이름을 불러 봅니다. | ||
|
|
||
| 이네들은 너무나 멀리 있습니다. | ||
| 별이 아스라이 멀듯이. | ||
|
|
||
| 어머님, | ||
| 그리고 당신은 멀리 북간도에 계십니다. | ||
|
|
||
| 나는 무엇인지 그리워 | ||
| 이 많은 별빛이 내린 언덕 위에 | ||
| 내 이름자를 써 보고 | ||
| 흙으로 덮어 버리었습니다. | ||
|
|
||
| 딴은 밤을 새워 우는 벌레는 | ||
| 부끄러운 이름을 슬퍼하는 까닭입니다. | ||
|
|
||
| 그러나 겨울이 지나고 나의 별에도 봄이 오면 | ||
| 무덤 위에 파란 잔디가 피어나듯이 | ||
| 내 이름자 묻힌 언덕 위에도 | ||
| 자랑처럼 풀이 무성할 거외다. | ||
| `, | ||
| }, | ||
| render: (args) => <Textarea {...args} />, | ||
| play: async ({ canvasElement }) => { | ||
| const canvas = within(canvasElement); | ||
| const ta = (await canvas.findByRole('textbox')) as HTMLTextAreaElement; | ||
| await expect(ta.scrollHeight).toBeGreaterThan(ta.clientHeight); | ||
| }, | ||
| }; | ||
|
|
||
| export const PreventOverflowByMaxLength: Story = { | ||
| name: '입력 길이 제한 동작', | ||
| args: { maxLength: 50, placeholder: '최대 50자' }, | ||
| render: (args) => <Textarea {...args} />, | ||
| play: async ({ canvasElement, args }) => { | ||
| const limit = Number(args.maxLength ?? 500); | ||
| const canvas = within(canvasElement); | ||
| const ta = (await canvas.findByRole('textbox')) as HTMLTextAreaElement; | ||
|
|
||
| await userEvent.click(ta); | ||
| await userEvent.type(ta, 'a'.repeat(limit + 10)); | ||
|
|
||
| await expect(ta.value.length).toBe(limit); | ||
| }, | ||
| }; | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,32 @@ | ||||||||||||||||||||||||||||||||||||
| // Textarea.tsx | ||||||||||||||||||||||||||||||||||||
| import * as React from 'react'; | ||||||||||||||||||||||||||||||||||||
| import { cn } from '@/lib/utils'; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| export interface TextareaProps | ||||||||||||||||||||||||||||||||||||
| extends Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'maxLength'> { | ||||||||||||||||||||||||||||||||||||
| maxLength: number; | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| export function Textarea({ | ||||||||||||||||||||||||||||||||||||
| className, | ||||||||||||||||||||||||||||||||||||
| maxLength, | ||||||||||||||||||||||||||||||||||||
| style, | ||||||||||||||||||||||||||||||||||||
| ...props | ||||||||||||||||||||||||||||||||||||
| }: TextareaProps) { | ||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||
| <textarea | ||||||||||||||||||||||||||||||||||||
| data-slot="textarea" | ||||||||||||||||||||||||||||||||||||
| maxLength={maxLength} | ||||||||||||||||||||||||||||||||||||
| className={cn( | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+18
to
+20
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maxLength 상수화에 대한 의견은 좋은 것 같습니다! 근데 just 질문이 있는데, 혹시 이 maxLength를 별도로 받는 이유가 있을까요?? 이 length로 글자수 제한이나 입력창 높이가 달라지는 걸까요? figma상에서는 옛날 앱잼 UI때처럼
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 원래 textarea에서 고정으로 지정할까싶었지만 추후 확장성을 고려해서 사용처에서 내려주는게 좋다고 판단했습니다-! |
||||||||||||||||||||||||||||||||||||
| 'h-[12rem] w-full', | ||||||||||||||||||||||||||||||||||||
| 'resize-none overflow-y-auto', | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+21
to
+22
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion rows/height 충돌: 고정 height가 소비자 props(예: rows, style.height)를 무력화합니다
아래처럼 최소 높이로 완화하면 - 'h-[12rem] w-full',
+ 'min-h-[12rem] h-auto w-full',📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||
| 'body3-r border-gray200 bg-white-bg text-font-gray-3 rounded-[0.4rem] border px-[0.8rem] py-[1.2rem] pr-[1.4rem]', | ||||||||||||||||||||||||||||||||||||
| 'focus:border-input outline-none focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0', | ||||||||||||||||||||||||||||||||||||
| 'ds-scrollbar', | ||||||||||||||||||||||||||||||||||||
| className | ||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+21
to
+27
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 접근성: 포커스 인디케이터가 충분히 눈에 띄지 않을 수 있습니다 현재 클래스는 outline/ring을 제거하고 border 색만 바꾸는데, 이는 키보드 사용자에게 포커스가 잘 보이지 않을 수 있습니다. 최소 권장 수정(토큰 네이밍은 디자인 토큰에 맞춰 조정하세요): - 'focus:border-input outline-none focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0',
+ 'focus:border-input outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2',추가로 배경색 토큰이 있다면 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||
| style={{ scrollbarGutter: 'stable', ...style }} | ||||||||||||||||||||||||||||||||||||
| {...props} | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+28
to
+29
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오왕 scrollbarGutter는 스크롤바 커스텀해주는 속성인건가요?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아뇽-!! 스크롤바가 생길 때를 대비해 콘텐츠 옆에 공간을 미리 확보해서 스크롤바 등장/사라짐으로 인한 레이아웃 흔들림을 막아줍니다-! |
||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -238,3 +238,34 @@ | |||||||||||||||||||||
| @layer components { | ||||||||||||||||||||||
| /* 공통 컴포넌트 스타일 */ | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| @layer utilities { | ||||||||||||||||||||||
| :where(.ds-scrollbar) { | ||||||||||||||||||||||
| --ds-scroll-size: 0.4rem; | ||||||||||||||||||||||
| --ds-scroll-thumb: var(--color-gray100); | ||||||||||||||||||||||
| --ds-scroll-track: transparent; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| scrollbar-color: var(--ds-scroll-thumb) var(--ds-scroll-track); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| scrollbar-gutter: stable; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
Comment on lines
+250
to
+251
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Textarea의 stableScrollbarGutter 속성이 비활성화되지 않는 버그
컴포넌트 쪽 수정 제안(Textarea.tsx 내 style 속성만 교체): - style={{
- ...(stableScrollbarGutter ? { scrollbarGutter: 'stable' } : {}),
- ...style,
- }}
+ style={{
+ scrollbarGutter: stableScrollbarGutter ? 'stable' : 'auto',
+ ...style,
+ }}또는 CSS에서 기본값을 제거하고(아래 라인 삭제) 컴포넌트에서만 제어하는 것도 방법입니다. - scrollbar-gutter: stable;🤖 Prompt for AI Agents |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| :where(.ds-scrollbar)::-webkit-scrollbar, | ||||||||||||||||||||||
| :where(.ds-scrollbar)::-webkit-scrollbar:hover, | ||||||||||||||||||||||
| :where(.ds-scrollbar)::-webkit-scrollbar:active { | ||||||||||||||||||||||
| width: var(--ds-scroll-size); | ||||||||||||||||||||||
| height: var(--ds-scroll-size); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
Comment on lines
+253
to
+258
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 불필요/무효한 선택자 정리: ::-webkit-scrollbar:hover, :active
아래처럼 간소화: - :where(.ds-scrollbar)::-webkit-scrollbar,
- :where(.ds-scrollbar)::-webkit-scrollbar:hover,
- :where(.ds-scrollbar)::-webkit-scrollbar:active {
+ :where(.ds-scrollbar)::-webkit-scrollbar {
width: var(--ds-scroll-size);
height: var(--ds-scroll-size);
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| :where(.ds-scrollbar)::-webkit-scrollbar-track { | ||||||||||||||||||||||
| background: var(--ds-scroll-track); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| :where(.ds-scrollbar)::-webkit-scrollbar-thumb, | ||||||||||||||||||||||
| :where(.ds-scrollbar)::-webkit-scrollbar-thumb:hover, | ||||||||||||||||||||||
| :where(.ds-scrollbar)::-webkit-scrollbar-thumb:active { | ||||||||||||||||||||||
| background: var(--ds-scroll-thumb); | ||||||||||||||||||||||
| border-radius: 9999px; | ||||||||||||||||||||||
| transition: none; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
엌,, 삭막한 테스트용 글귀에 윤동주씨 시라니,,, 감성 좋아여어 짱