diff --git a/docs/components/example/index.json b/docs/components/example/index.json index 4444b47fb..3b7537243 100644 --- a/docs/components/example/index.json +++ b/docs/components/example/index.json @@ -108,6 +108,7 @@ "inline-banner-with-icon": "import { InlineBanner } from \"seed-design/ui/inline-banner\";\nimport { IconILowercaseSerifCircleFill } from \"@daangn/react-monochrome-icon\";\n\nexport default function InlineBannerWithIcon() {\n return (\n }>\n 다른 사람과 예약된 물품이 있어요.\n \n );\n}", "inline-banner-with-link": "import { InlineBanner } from \"seed-design/ui/inline-banner\";\n\nexport default function InlineBannerWithLink() {\n return (\n {} }}>\n 다른 사람과 예약된 물품이 있어요.\n \n );\n}", "inline-banner-with-title-text": "import { InlineBanner } from \"seed-design/ui/inline-banner\";\n\nexport default function InlineBannerWithTitleText() {\n return (\n \n 다른 사람과 예약된 물품이 있어요.\n \n );\n}", + "multiline-text-field-preview": "import { useState } from \"react\";\nimport { MultilineTextField } from \"seed-design/ui/multiline-text-field\";\n\nexport default function TextFieldPreview() {\n const [value, setValue] = useState(\"\");\n\n return (\n
\n \n

현재 값: {value}

\n
\n );\n}", "segmented-control-disabled": "import { SegmentedControl, Segment } from \"seed-design/ui/segmented-control\";\n\nexport default function SegmentedControlPreview() {\n return (\n \n Hot\n New\n \n );\n}", "segmented-control-fixed-width": "import { SegmentedControl, Segment } from \"seed-design/ui/segmented-control\";\n\nexport default function SegmentedControlFixedWidth() {\n return (\n \n New\n Hot\n \n );\n}", "segmented-control-long-label-fixed-width": "import { SegmentedControl, Segment } from \"seed-design/ui/segmented-control\";\n\nexport default function SegmentedControlLongLabelFixedWidth() {\n return (\n \n 가격 높은 순\n 할인율 높은 순\n 인기 많은 순\n \n );\n}", diff --git a/docs/components/example/multiline-text-field-preview.tsx b/docs/components/example/multiline-text-field-preview.tsx new file mode 100644 index 000000000..d3f13d655 --- /dev/null +++ b/docs/components/example/multiline-text-field-preview.tsx @@ -0,0 +1,15 @@ +"use client"; + +import { useState } from "react"; +import { MultilineTextField } from "seed-design/ui/multiline-text-field"; + +export default function TextFieldPreview() { + const [value, setValue] = useState(""); + + return ( +
+ +

현재 값: {value}

+
+ ); +} diff --git a/docs/content/docs/react/components/text-fields/meta.json b/docs/content/docs/react/components/text-fields/meta.json new file mode 100644 index 000000000..31698d2e2 --- /dev/null +++ b/docs/content/docs/react/components/text-fields/meta.json @@ -0,0 +1,5 @@ +{ + "title": "Text Fields", + "pages": ["..."], + "defaultOpen": true +} diff --git a/docs/content/docs/react/components/text-fields/multiline-text-field.mdx b/docs/content/docs/react/components/text-fields/multiline-text-field.mdx new file mode 100644 index 000000000..abb22e183 --- /dev/null +++ b/docs/content/docs/react/components/text-fields/multiline-text-field.mdx @@ -0,0 +1,61 @@ +--- +title: Multiline Text Field +description: 사용자가 입력할 수 있는 텍스트를 받는 컴포넌트에요. 여러 줄의 텍스트를 입력할 수 있고 높이가 자동으로 조절돼요. +--- + + + +## 설치 + + + +## Props + + + +## 예제 + +### Status + +#### Enabled + + + +#### Disabled + + + +#### Read Only + + + +### Customizable Parts + +#### Required Indicator + + + +#### Optional Indicator + + + +#### Grapheme Count + + + +### Size + +#### XLarge + + + +#### Large + + + +#### Medium + + diff --git a/docs/content/docs/react/components/text-field.mdx b/docs/content/docs/react/components/text-fields/text-field.mdx similarity index 100% rename from docs/content/docs/react/components/text-field.mdx rename to docs/content/docs/react/components/text-fields/text-field.mdx diff --git a/docs/public/__registry__/ui/index.json b/docs/public/__registry__/ui/index.json index 6ca94c07f..5308d6e24 100644 --- a/docs/public/__registry__/ui/index.json +++ b/docs/public/__registry__/ui/index.json @@ -195,5 +195,15 @@ "files": [ "ui:text-field.tsx" ] + }, + { + "name": "multiline-text-field", + "dependencies": [ + "@seed-design/react-text-field", + "@daangn/react-monochrome-icon" + ], + "files": [ + "ui:multiline-text-field.tsx" + ] } ] \ No newline at end of file diff --git a/docs/public/__registry__/ui/multiline-text-field.json b/docs/public/__registry__/ui/multiline-text-field.json new file mode 100644 index 000000000..6c9d8bc47 --- /dev/null +++ b/docs/public/__registry__/ui/multiline-text-field.json @@ -0,0 +1,14 @@ +{ + "name": "multiline-text-field", + "dependencies": [ + "@seed-design/react-text-field", + "@daangn/react-monochrome-icon" + ], + "registries": [ + { + "name": "multiline-text-field.tsx", + "type": "ui", + "content": "\"use client\";\n\nimport \"@seed-design/stylesheet/textField.css\";\n\nimport * as React from \"react\";\nimport clsx from \"clsx\";\nimport {\n textField,\n type TextFieldVariantProps,\n} from \"@seed-design/recipe/textField\";\nimport { IconExclamationmarkCircleFill } from \"@daangn/react-monochrome-icon\";\nimport {\n useTextField,\n type UseTextFieldProps,\n} from \"@seed-design/react-text-field\";\nimport type { Assign } from \"../util/types\";\n\nexport interface MultilineTextFieldProps\n extends UseTextFieldProps,\n TextFieldVariantProps {\n label?: string;\n requiredIndicator?: string;\n optionalIndicator?: string;\n\n description?: string;\n errorMessage?: string;\n\n maxGraphemeCount?: number;\n hideGraphemeCount?: boolean;\n}\n\ntype ReactMultilineTextFieldProps = Assign<\n Omit<\n React.TextareaHTMLAttributes,\n \"children\" | \"maxLength\"\n >,\n MultilineTextFieldProps\n>;\n\nexport const MultilineTextField = React.forwardRef<\n HTMLTextAreaElement,\n ReactMultilineTextFieldProps\n>(\n (\n {\n size = \"medium\",\n label,\n requiredIndicator,\n optionalIndicator,\n hideGraphemeCount,\n ...restProps\n },\n ref,\n ) => {\n const {\n rootProps: { className: rootClassName, ...rootProps },\n inputProps: { className: inputClassName, ...inputProps },\n labelProps: { className: labelClassName, ...labelProps },\n descriptionProps,\n errorMessageProps,\n stateProps,\n restProps: restInternalProps,\n isInvalid,\n isRequired,\n graphemes,\n } = useTextField({ ...restProps });\n\n const { description, errorMessage, maxGraphemeCount } = restProps;\n\n const classNames = textField({ size });\n\n const indicator = isRequired ? requiredIndicator : optionalIndicator;\n\n const renderDescription = !isInvalid && description;\n const renderErrorMessage = isInvalid && !!errorMessage;\n const renderCharacterCount = !hideGraphemeCount && maxGraphemeCount;\n\n return (\n \n {label && (\n // XXX\n // biome-ignore lint/a11y/noLabelWithoutControl: \n \n )}\n \n {(renderDescription || renderErrorMessage || renderCharacterCount) && (\n
\n {renderDescription && (\n
\n {description}\n
\n )}\n {renderErrorMessage && (\n
\n \n
{errorMessage}
\n
\n )}\n {renderCharacterCount && (\n
\n \n {graphemes.length}\n \n \n /{maxGraphemeCount}\n \n
\n )}\n
\n )}\n \n );\n },\n);\nMultilineTextField.displayName = \"MultilineTextField\";\n" + } + ] +} \ No newline at end of file diff --git a/docs/registry/registry-ui.ts b/docs/registry/registry-ui.ts index 02077b886..aa488c729 100644 --- a/docs/registry/registry-ui.ts +++ b/docs/registry/registry-ui.ts @@ -127,4 +127,12 @@ export const registryUI: RegistryUI = [ ], files: ["ui:text-field.tsx"], }, + { + name: "multiline-text-field", + dependencies: [ + "@seed-design/react-text-field", + "@daangn/react-monochrome-icon", + ], + files: ["ui:multiline-text-field.tsx"], + }, ]; diff --git a/docs/registry/ui/multiline-text-field.tsx b/docs/registry/ui/multiline-text-field.tsx new file mode 100644 index 000000000..865425373 --- /dev/null +++ b/docs/registry/ui/multiline-text-field.tsx @@ -0,0 +1,139 @@ +"use client"; + +import "@seed-design/stylesheet/textField.css"; + +import * as React from "react"; +import clsx from "clsx"; +import { + textField, + type TextFieldVariantProps, +} from "@seed-design/recipe/textField"; +import { IconExclamationmarkCircleFill } from "@daangn/react-monochrome-icon"; +import { + useTextField, + type UseTextFieldProps, +} from "@seed-design/react-text-field"; +import type { Assign } from "../util/types"; + +export interface MultilineTextFieldProps + extends UseTextFieldProps, + TextFieldVariantProps { + label?: string; + requiredIndicator?: string; + optionalIndicator?: string; + + description?: string; + errorMessage?: string; + + maxGraphemeCount?: number; + hideGraphemeCount?: boolean; +} + +type ReactMultilineTextFieldProps = Assign< + Omit< + React.TextareaHTMLAttributes, + "children" | "maxLength" + >, + MultilineTextFieldProps +>; + +export const MultilineTextField = React.forwardRef< + HTMLTextAreaElement, + ReactMultilineTextFieldProps +>( + ( + { + size = "medium", + label, + requiredIndicator, + optionalIndicator, + hideGraphemeCount, + ...restProps + }, + ref, + ) => { + const { + rootProps: { className: rootClassName, ...rootProps }, + textareaProps: { className: textareaClassName, ...textareaProps }, + labelProps: { className: labelClassName, ...labelProps }, + descriptionProps, + errorMessageProps, + stateProps, + restProps: restInternalProps, + isInvalid, + isRequired, + graphemes, + } = useTextField({ elementType: "textarea", ...restProps }); + + const { description, errorMessage, maxGraphemeCount } = restProps; + + const classNames = textField({ size }); + + const indicator = isRequired ? requiredIndicator : optionalIndicator; + + const renderDescription = !isInvalid && description; + const renderErrorMessage = isInvalid && !!errorMessage; + const renderCharacterCount = !hideGraphemeCount && maxGraphemeCount; + + return ( +
+ {label && ( + // XXX + // biome-ignore lint/a11y/noLabelWithoutControl: + + )} +
+