style(storybook): unify form element story layouts

- Button: Sizes | Variants side by side, States as Brand / Standard Solid /
  Standard Outline matrices (3 / 2 / 1 columns), 8px button gap; the
  Interact row stays only in the verification "Brand States" Story
- Input / Textarea / Select: state name in the field Label slot, repeated
  field Label and group titles removed, aria-label keeps field meaning
  with the visible state name; Select uses the Input field grid
- Combobox / Date Picker: Sizes / States use the same state-name Label
  and Input grid; Usage unchanged
- Toggle: top Variants / States in 2 columns, 8px toggle gap, 단독 toggle
  keeps its own width
- Component code, props, tokens and verification Stories unchanged;
  component docs updated to match
This commit is contained in:
shkim4 committed 2026-11-12 17:11:56 +09:00
1 parent e8c16f5292
commit a7b56ff23e
14 files changed
+351 -187

No files matched your search

+24 -26
View File
@@ -1,7 +1,6 @@
import { useId, type ComponentProps, type ReactNode } from "react"
import type { Meta, StoryObj } from "@storybook/react-vite"
import { Textarea } from "@hm-design/ui"
import { StateBoard, type BoardState } from "@/components/story-board"
import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-page"
// Story structure follows Input: one representative "Textarea" Story shows every state on one page; the same specimens
@@ -49,19 +48,20 @@ function Specimen({
)
}
// Representative-page layout (Storybook presentation only). StateBoard's own auto-fill grid cannot give equal columns that
// fill a cell, so each specimen sits in its own single-column StateBoard (min larger than any column) inside this grid.
// Full class names are listed so Tailwind can see them.
const specimenColumns = {
4: "min-[1000px]:grid-cols-4",
} as const
function SpecimenGrid({ states, columns }: { states: BoardState[]; columns: keyof typeof specimenColumns }) {
// Representative-page layout (Storybook presentation only). 2026-11-12, same rule as Input: the state name sits in the field
// Label slot (no repeated "Description" under a separate group title); the textarea keeps a meaningful accessible name through
// aria-label, which also contains the visible state name (Label in Name). PC (iframe width 1000px and up): 4 equal columns;
// below that the columns shrink to fit (220px minimum, down to 1). The grid takes the common section indent (.sb-page-indent).
function FieldGrid({ fields }: { fields: { id: string; label: string; props: ComponentProps<typeof Specimen> }[] }) {
return (
<div className={`grid items-start gap-6 [grid-template-columns:repeat(auto-fill,minmax(min(100%,220px),1fr))] ${specimenColumns[columns]}`}>
{states.map((state) => (
<StateBoard key={state.id} min={10000} states={[state]} />
))}
<div className="sb-page-indent">
<div className="grid items-start gap-6 [grid-template-columns:repeat(auto-fill,minmax(min(100%,220px),1fr))] min-[1000px]:grid-cols-4">
{fields.map(({ id, label, props }) => (
<div key={id} className="min-w-0" data-overview-state={id}>
<Specimen label={label} aria-label={`Description (${label})`} {...props} />
</div>
))}
</div>
</div>
)
}
@@ -106,23 +106,21 @@ export const All: Story = {
{/* PC (iframe width 1000px and up): States in 4 equal columns, Validation's 2 specimens on the same column width.
Below that the columns shrink to fit (220px minimum, down to 1). Multiline and Resize live in their own hidden Stories. */}
<StorySection title="States">
<SpecimenGrid
columns={4}
states={[
{ id: "default", label: "Default", children: <Specimen placeholder={placeholder} /> },
{ id: "focus", label: "Focus", children: <Specimen placeholder={placeholder} /> },
{ id: "readonly", label: "Readonly", children: <Specimen readOnly defaultValue="Confirmed description" /> },
{ id: "disabled", label: "Disabled", children: <Specimen disabled defaultValue="Unavailable description" /> },
<FieldGrid
fields={[
{ id: "default", label: "Default", props: { placeholder } },
{ id: "focus", label: "Focus", props: { placeholder } },
{ id: "readonly", label: "Readonly", props: { readOnly: true, defaultValue: "Confirmed description" } },
{ id: "disabled", label: "Disabled", props: { disabled: true, defaultValue: "Unavailable description" } },
]}
/>
</StorySection>
<StorySection title="Validation">
{/* Same 4-column track as States, so the two specimens keep the States width instead of stretching across the page. */}
<SpecimenGrid
columns={4}
states={[
{ id: "error", label: "Error", children: <Specimen error defaultValue="Invalid value" /> },
{ id: "error-message", label: "Error Message", children: <Specimen error message="설명을 입력하세요." /> },
{/* Same 4-column track as States, so the two fields keep the States width instead of stretching across the page. */}
<FieldGrid
fields={[
{ id: "error", label: "Error", props: { error: true, defaultValue: "Invalid value" } },
{ id: "error-message", label: "Error Message", props: { error: true, message: "설명을 입력하세요." } },
]}
/>
</StorySection>