@godxjp/ui-mcp 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -191,6 +191,75 @@ import { Button } from "@godxjp/ui/general";
191
191
  storyPath: "layout/Inline.stories.tsx",
192
192
  rules: [2]
193
193
  },
194
+ {
195
+ name: "Flex",
196
+ group: "layout",
197
+ tagline: "Token-spaced flex primitive with explicit direction, alignment, justification, and wrapping controls.",
198
+ props: [
199
+ {
200
+ name: "direction",
201
+ type: '"row" | "col"',
202
+ defaultValue: '"col"',
203
+ description: "Main axis direction. Use row for horizontal runs, col for vertical stacks."
204
+ },
205
+ {
206
+ name: "gap",
207
+ type: '"xs" | "sm" | "md" | "lg" | "xl"',
208
+ defaultValue: '"md"',
209
+ description: "Token gap between children, shared with Stack and Inline."
210
+ },
211
+ {
212
+ name: "align",
213
+ type: '"start" | "center" | "end" | "stretch" | "baseline"',
214
+ description: "Cross-axis alignment, emitted as a data attribute for the layout CSS."
215
+ },
216
+ {
217
+ name: "justify",
218
+ type: '"start" | "center" | "end" | "between" | "around" | "evenly"',
219
+ description: "Main-axis distribution, emitted as a data attribute for the layout CSS."
220
+ },
221
+ {
222
+ name: "wrap",
223
+ type: "boolean",
224
+ defaultValue: "false",
225
+ description: "Allows children to wrap onto additional flex lines."
226
+ }
227
+ ],
228
+ usage: [
229
+ 'DO import from `@godxjp/ui/layout` and reach for Flex when the axis, alignment, justification, or wrap behavior is part of the component contract: `import { Flex } from "@godxjp/ui/layout"`.',
230
+ "DO keep spacing on the `gap` prop instead of raw `gap-*`, `space-*`, or padding utilities. Flex uses the same token scale as Stack and Inline, so spacing remains tied to the design system.",
231
+ 'DO use `direction="row"` with `wrap` for responsive control rows, chip clusters, and action groups that need more control than Inline exposes.',
232
+ 'DO use `direction="col"` for vertical groupings that need explicit `align` or `justify` behavior. If all you need is a vertical gap, Stack is the thinner alias.',
233
+ "DON'T override the axis with `className` after choosing a direction prop. Keep the layout intent in props so catalog guidance and data attributes stay accurate.",
234
+ "Flex is a plain div with React.HTMLAttributes<HTMLDivElement>; pass `id`, `role`, `aria-*`, `data-*`, and structural className values as needed, but do not use it as a semantic form or button wrapper."
235
+ ],
236
+ useCases: [
237
+ "Toolbar internals where controls should sit in a row, wrap on narrow widths, and stay vertically centered.",
238
+ "Card headers that need title content on the left and actions on the right via `justify='between'` without hand-rolling flex utility classes.",
239
+ "Empty-state or loading blocks that center content on both axes using `align='center'` and `justify='center'`.",
240
+ "Form sub-sections where a vertical group needs stretched children or centered helper content beyond what Stack exposes.",
241
+ "Badge, chip, or tag clusters where wrapping is required but the caller also needs a larger token gap than Inline's default.",
242
+ "Low-level layout composition inside custom components where Stack and Inline are too opinionated but raw flex classes would duplicate the primitive."
243
+ ],
244
+ related: [
245
+ "Stack \u2014 thin `direction='col'` alias of Flex. Use Stack for ordinary vertical block spacing; use Flex when you need align, justify, or wrap control.",
246
+ "Inline \u2014 thin `direction='row'` alias of Flex with wrapping and centered alignment. Use Inline for simple horizontal groups; use Flex for explicit axis/distribution control.",
247
+ "ResponsiveGrid \u2014 use for equal-width, multi-column tile layouts. Flex arranges children on one flex axis and does not provide column-count behavior.",
248
+ "PageContainer \u2014 page scaffold and padding context. Flex is an inner layout primitive used inside page sections, cards, dialogs, and toolbars."
249
+ ],
250
+ example: `import { Flex } from "@godxjp/ui/layout";
251
+ import { Button } from "@godxjp/ui/general";
252
+
253
+ <Flex direction="row" gap="sm" align="center" justify="between" wrap>
254
+ <SearchSummary />
255
+ <Flex direction="row" gap="xs" align="center" wrap>
256
+ <Button variant="outline">\u30EA\u30BB\u30C3\u30C8</Button>
257
+ <Button>\u9069\u7528</Button>
258
+ </Flex>
259
+ </Flex>`,
260
+ storyPath: "layout/Flex.stories.tsx",
261
+ rules: [2, 40]
262
+ },
194
263
  {
195
264
  name: "ResponsiveGrid",
196
265
  group: "layout",
@@ -811,7 +880,7 @@ function MyShell({ children }: { content: React.ReactNode }) {
811
880
  ],
812
881
  useCases: [
813
882
  'Primary form submission in a Dialog or Sheet (e.g. `<Button type="submit" disabled={form.processing}>\u4FDD\u5B58</Button>`) \u2014 the `disabled` prop greys it out and blocks pointer events, preventing double-submit during async operations.',
814
- 'Destructive confirmation inside a Dialog \u2014 pair `variant="destructive"` Button as the confirm action and `variant="outline"` as Cancel; never use `variant="default"` for a delete action.',
883
+ 'Destructive confirmation inside a Dialog \u2014 pair `tone="destructive"` Button as the confirm action and `variant="outline"` as Cancel; never use `variant="default"` for a delete action.',
815
884
  'Icon-only toolbar actions in a DataTable column (edit, delete, copy) using `size="icon-sm"` + `variant="ghost"` + a Lucide icon child \u2014 gives equal-width square targets that don\'t distort the row.',
816
885
  "Navigation links styled as buttons (e.g. 'New Invoice', 'Back to list') using `asChild` + Inertia `<Link>` \u2014 preserves SPA navigation while using the button's visual treatment.",
817
886
  "Async mutation trigger in an accounting workflow (e.g. 'Sync from MF', 'Export CSV') \u2014 disable on processing state; pair with `MutationFeedback` for error/retry UI rather than inline `try/catch` alerts.",
@@ -1212,7 +1281,11 @@ import { ResponsiveGrid } from "@godxjp/ui/layout";
1212
1281
  type: "React.ComponentType<{ className?: string }> | null",
1213
1282
  description: "Leading icon override. Pass null to suppress the auto status icon."
1214
1283
  },
1215
- { name: "children", type: "ReactNode", description: "Badge label. When omitted with status, Badge renders the translated lifecycle label or raw status." }
1284
+ {
1285
+ name: "children",
1286
+ type: "ReactNode",
1287
+ description: "Badge label. When omitted with status, Badge renders the translated lifecycle label or raw status."
1288
+ }
1216
1289
  ],
1217
1290
  usage: [
1218
1291
  "DO pick the correct variant semantically: `success` (approved/paid), `warning` (pending/overdue), `destructive` (rejected/error), `secondary` (neutral category), `outline` (subtle label), `default` (primary accent). Never force a colour just for aesthetics \u2014 agents and screen readers read the variant as intent.",
@@ -1224,7 +1297,7 @@ import { ResponsiveGrid } from "@godxjp/ui/layout";
1224
1297
  ],
1225
1298
  useCases: [
1226
1299
  'Category or tier labels on table rows \u2014 e.g. plan tier (`<Badge variant="secondary">Pro</Badge>`), document type (`<Badge variant="outline">Invoice</Badge>`), or locale tag (`<Badge variant="secondary">EN</Badge>`).',
1227
- 'Approval or review state in an accounting list where the value is not a lifecycle key in Badge\'s STATUS_MAP \u2014 e.g. a custom approval tier like `<Badge variant="success">\u627F\u8A8D\u6E08</Badge>` or `<Badge variant="warning">\u8981\u78BA\u8A8D</Badge>`.',
1300
+ 'Approval or review state in an accounting list where the value is not a lifecycle key in Badge\'s STATUS_MAP \u2014 e.g. a custom approval tier like `<Badge tone="success">\u627F\u8A8D\u6E08</Badge>` or `<Badge tone="warning">\u8981\u78BA\u8A8D</Badge>`.',
1228
1301
  "Inline count or highlight next to a heading or nav item \u2014 e.g. `<Badge variant=\"destructive\">3</Badge>` beside 'Overdue invoices' to draw attention to a non-zero count.",
1229
1302
  'Feature flags or experiment variant labels on admin records \u2014 e.g. `<Badge variant="outline">A/B</Badge>` alongside a campaign row to indicate it is in a split test.',
1230
1303
  "Read-only metadata chips inside a Descriptions.Item or Card header where a lifecycle icon would be visually heavy \u2014 e.g. currency code, payment method, or region tag."
@@ -1236,7 +1309,7 @@ import { ResponsiveGrid } from "@godxjp/ui/layout";
1236
1309
 
1237
1310
  <Badge variant="secondary">A/B</Badge>
1238
1311
  <Badge status="active">\u516C\u958B\u4E2D</Badge>
1239
- <Badge status="\u30D7\u30EC\u30DF\u30A2\u30E0" variant="success" icon={null}>\u30D7\u30EC\u30DF\u30A2\u30E0</Badge>`,
1312
+ <Badge status="\u30D7\u30EC\u30DF\u30A2\u30E0" tone="success" icon={null}>\u30D7\u30EC\u30DF\u30A2\u30E0</Badge>`,
1240
1313
  storyPath: "data-display/Badge.stories.tsx",
1241
1314
  rules: [35]
1242
1315
  },
@@ -1356,7 +1429,7 @@ import { ResponsiveGrid } from "@godxjp/ui/layout";
1356
1429
  "DON'T use Progress for editable numeric input or range selection \u2014 it has no callbacks, no interactivity, and no form `name` prop. Use Slider (bounded range input) or Input (free-form number) for data-entry scenarios."
1357
1430
  ],
1358
1431
  useCases: [
1359
- 'Budget utilisation in an accounting dashboard \u2014 show how much of a monthly budget has been consumed, switching to `variant="warning"` when the figure crosses 80%.',
1432
+ 'Budget utilisation in an accounting dashboard \u2014 show how much of a monthly budget has been consumed, switching to `tone="warning"` when the figure crosses 80%.',
1360
1433
  'Invoice payment progress \u2014 display the proportion of an invoice total that has been settled (e.g. partial payments), with a label like `"\xA545,000 / \xA560,000 \u652F\u6255\u6E08"` computed before passing `value`.',
1361
1434
  "Storage or quota indicator in an admin panel \u2014 visualise disk usage, API quota, or seat licence consumption against a fixed limit.",
1362
1435
  "Sync / import job completion feedback \u2014 surface the completion percentage of a long-running background job (polling the server) without giving the user an interactive control.",
@@ -1675,7 +1748,7 @@ import { ResponsiveGrid } from "@godxjp/ui/layout";
1675
1748
  example: `import { FormField, Input } from "@godxjp/ui/data-entry";
1676
1749
 
1677
1750
  <FormField id="coupon-name" label="\u30AF\u30FC\u30DD\u30F3\u540D" required error={errors.name} helper="\u6700\u592750\u6587\u5B57">
1678
- <Input id="coupon-name" placeholder="\u6625\u306E\u82B1\u7C89\u75C7\u5BFE\u7B5615%OFF" value={name} onChange={(e) => setName(e.target.value)} />
1751
+ <Input id="coupon-name" placeholder="\u6625\u306E\u82B1\u7C89\u75C7\u5BFE\u7B5615%OFF" value={name} onValueChange={(e) => setName(e.target.value)} />
1679
1752
  </FormField>`,
1680
1753
  storyPath: "data-entry/FormField.stories.tsx",
1681
1754
  rules: [23]
@@ -1718,7 +1791,7 @@ import { ResponsiveGrid } from "@godxjp/ui/layout";
1718
1791
  ],
1719
1792
  example: `import { Input } from "@godxjp/ui/data-entry";
1720
1793
 
1721
- <Input id="qty" type="number" placeholder="\u4F8B: 500" value={value} onChange={(e) => setValue(e.target.value)} />`,
1794
+ <Input id="qty" type="number" placeholder="\u4F8B: 500" value={value} onValueChange={(e) => setValue(e.target.value)} />`,
1722
1795
  storyPath: "data-entry/Input.stories.tsx",
1723
1796
  rules: []
1724
1797
  },
@@ -1916,7 +1989,7 @@ export function StatusSelect({ value, onChange }) {
1916
1989
  return (
1917
1990
  <Select
1918
1991
  value={value}
1919
- onChange={onChange}
1992
+ onValueChange={onChange}
1920
1993
  options={[
1921
1994
  { value: "draft", label: "Draft" },
1922
1995
  { value: "sent", label: "Sent" },
@@ -1935,7 +2008,7 @@ export function CurrencySelect({ value, onChange }) {
1935
2008
  return (
1936
2009
  <Select
1937
2010
  value={value}
1938
- onChange={onChange}
2011
+ onValueChange={onChange}
1939
2012
  showSearch
1940
2013
  options={[
1941
2014
  { value: "JPY", label: "Japanese Yen", group: "Asia" },
@@ -1961,7 +2034,7 @@ export function AccountSelect({ value, onChange, selectedLabel }) {
1961
2034
  return (
1962
2035
  <Select
1963
2036
  value={value}
1964
- onChange={onChange}
2037
+ onValueChange={onChange}
1965
2038
  loadOptions={loadOptions}
1966
2039
  selectedLabel={selectedLabel}
1967
2040
  placeholder="Search accounts\u2026"
@@ -2084,7 +2157,7 @@ export function PrioritySelect({ value, onValueChange }) {
2084
2157
  ],
2085
2158
  example: `import { Textarea } from "@godxjp/ui/data-entry";
2086
2159
 
2087
- <Textarea id="notes" rows={4} placeholder="\u81EA\u7531\u8A18\u8FF0" value={notes} onChange={(e) => setNotes(e.target.value)} />`,
2160
+ <Textarea id="notes" rows={4} placeholder="\u81EA\u7531\u8A18\u8FF0" value={notes} onValueChange={(e) => setNotes(e.target.value)} />`,
2088
2161
  storyPath: "data-entry/Textarea.stories.tsx",
2089
2162
  rules: []
2090
2163
  },
@@ -2320,7 +2393,7 @@ export function InvoiceDueDateField() {
2320
2393
  id="due-date"
2321
2394
  name="due_date"
2322
2395
  value={dueDate}
2323
- onChange={setDueDate}
2396
+ onValueChange={setDueDate}
2324
2397
  fromDate={new Date()}
2325
2398
  placeholder="yyyy-mm-dd"
2326
2399
  />
@@ -2465,7 +2538,7 @@ import { Button } from "@godxjp/ui/general";
2465
2538
  }
2466
2539
  ],
2467
2540
  usage: [
2468
- 'DO compose with sub-parts in order: wrap text content in `<Alert.Content>` (or bare `<AlertContent>`), then `<Alert.Title>` + `<Alert.Description>` inside it, then `<Alert.Actions>` for any retry/CTA buttons. Example: `<Alert variant="destructive"><Alert.Content><Alert.Title>Error</Alert.Title><Alert.Description>{msg}</Alert.Description></Alert.Content><Alert.Actions><Button \u2026/></Alert.Actions></Alert>`.',
2541
+ 'DO compose with sub-parts in order: wrap text content in `<Alert.Content>` (or bare `<AlertContent>`), then `<Alert.Title>` + `<Alert.Description>` inside it, then `<Alert.Actions>` for any retry/CTA buttons. Example: `<Alert tone="destructive"><Alert.Content><Alert.Title>Error</Alert.Title><Alert.Description>{msg}</Alert.Description></Alert.Content><Alert.Actions><Button \u2026/></Alert.Actions></Alert>`.',
2469
2542
  "DO use `Alert.QueryError` (alias `AlertQueryError`) for TanStack Query / API failure surfaces \u2014 it already renders humanError(error), an i18n title, and an optional Retry button. Never hand-roll that pattern.",
2470
2543
  'DON\'T pass raw action elements directly as top-level children of `<Alert>` without wrapping them in `<Alert.Actions>` \u2014 the layout slot only activates correctly via the `data-slot="alert-actions"` wrapper.',
2471
2544
  'DON\'T hand-roll a dismiss \u2715 button \u2014 pass `onDismiss` to `<Alert>` and the component renders its own accessible dismiss button with `aria-label="Dismiss"`. The `onDismiss` handler may return a Promise.',
@@ -2473,12 +2546,12 @@ import { Button } from "@godxjp/ui/general";
2473
2546
  "DO NOT use `Alert` for transient ephemeral feedback (e.g. 'saved successfully'). Use `toast()` from sonner + `<Toaster>` for that. `Alert` is for persistent, page-scoped banners that stay visible until the user acts or dismisses."
2474
2547
  ],
2475
2548
  useCases: [
2476
- 'Page-level error banner after a form submission fails server-side validation \u2014 `variant="destructive"` with `Alert.Title` summarising the error and `Alert.Description` listing field issues, paired with `onDismiss` so the user can clear it.',
2549
+ 'Page-level error banner after a form submission fails server-side validation \u2014 `tone="destructive"` with `Alert.Title` summarising the error and `Alert.Description` listing field issues, paired with `onDismiss` so the user can clear it.',
2477
2550
  "Inline warning at the top of an accounting invoice list when the OAuth token for the MF sync is about to expire \u2014 `variant=\"warning\"` with an `Alert.Actions` containing a 'Reconnect' Button.",
2478
- 'Success confirmation banner rendered after a bulk-import job completes and the user returns to the list page \u2014 `variant="success"` with `Alert.Description` showing the record count imported.',
2551
+ 'Success confirmation banner rendered after a bulk-import job completes and the user returns to the list page \u2014 `tone="success"` with `Alert.Description` showing the record count imported.',
2479
2552
  "TanStack Query data-fetch failure inside a Card body \u2014 use `<Alert.QueryError error={error} onRetry={refetch} />` instead of writing a custom error state.",
2480
2553
  "Informational notice at the top of a settings page when a feature is in beta or requires a plan upgrade \u2014 `variant=\"default\"` (Info icon) with a short description and an `Alert.Actions` 'Learn more' link.",
2481
- 'Dismissible billing-overdue notice at the top of the dashboard \u2014 `variant="destructive"` with `onDismiss` that sets a session flag so it does not reappear until the next login.'
2554
+ 'Dismissible billing-overdue notice at the top of the dashboard \u2014 `tone="destructive"` with `onDismiss` that sets a session flag so it does not reappear until the next login.'
2482
2555
  ],
2483
2556
  related: [
2484
2557
  "Toaster \u2014 use for transient, auto-dismissing feedback ('Record saved', 'Deleted'). Alert is for persistent page-scoped banners; Toaster is for fire-and-forget notifications triggered by toast() from sonner.",
@@ -2488,7 +2561,7 @@ import { Button } from "@godxjp/ui/general";
2488
2561
  ],
2489
2562
  example: `import { Alert, AlertTitle, AlertDescription } from "@godxjp/ui/feedback";
2490
2563
 
2491
- <Alert variant="warning">
2564
+ <Alert tone="warning">
2492
2565
  <AlertTitle>3 \u4EF6\u306E\u6253\u523B\u6F0F\u308C\u304C\u3042\u308A\u307E\u3059</AlertTitle>
2493
2566
  <AlertDescription>\u672C\u65E5\u4E2D\u306B\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002</AlertDescription>
2494
2567
  </Alert>`,
@@ -2809,7 +2882,7 @@ import { SearchInput, Select, SelectTrigger, SelectValue, SelectContent, SelectI
2809
2882
  ],
2810
2883
  example: `import { Pagination } from "@godxjp/ui/navigation";
2811
2884
 
2812
- <Pagination current={page} total={filtered.length} pageSize={10} showTotal onChange={(p) => setPage(p)} />`,
2885
+ <Pagination current={page} total={filtered.length} pageSize={10} showTotal onValueChange={(p) => setPage(p)} />`,
2813
2886
  storyPath: "navigation/Pagination.stories.tsx",
2814
2887
  rules: [40]
2815
2888
  },
@@ -2855,7 +2928,7 @@ import { Button } from "@godxjp/ui/general";
2855
2928
  <DropdownMenuContent>
2856
2929
  <DropdownMenuItem>\u7DE8\u96C6</DropdownMenuItem>
2857
2930
  <DropdownMenuSeparator />
2858
- <DropdownMenuItem variant="destructive">\u524A\u9664</DropdownMenuItem>
2931
+ <DropdownMenuItem tone="destructive">\u524A\u9664</DropdownMenuItem>
2859
2932
  </DropdownMenuContent>
2860
2933
  </DropdownMenu>`,
2861
2934
  storyPath: "navigation/DropdownMenu.stories.tsx",
@@ -3110,7 +3183,7 @@ export function ShiftStartField() {
3110
3183
  id="shift-start"
3111
3184
  name="shift_start"
3112
3185
  value={startTime}
3113
- onChange={setStartTime}
3186
+ onValueChange={setStartTime}
3114
3187
  minuteStep={15}
3115
3188
  className="w-36"
3116
3189
  />
@@ -3230,7 +3303,7 @@ export function InvoicePeriodFilter() {
3230
3303
  id="invoice-period"
3231
3304
  name="period"
3232
3305
  value={range}
3233
- onChange={setRange}
3306
+ onValueChange={setRange}
3234
3307
  fromDate={new Date(2020, 0, 1)}
3235
3308
  toDate={new Date(2030, 11, 31)}
3236
3309
  />
@@ -3386,7 +3459,7 @@ function RegionPicker() {
3386
3459
  <Cascader
3387
3460
  options={REGIONS}
3388
3461
  value={path}
3389
- onChange={(v) => setPath(v as string[])}
3462
+ onValueChange={(v) => setPath(v as string[])}
3390
3463
  showSearch
3391
3464
  placeholder="Select region\u2026"
3392
3465
  />
@@ -3402,7 +3475,7 @@ function MultiRegionPicker() {
3402
3475
  options={REGIONS}
3403
3476
  multiple
3404
3477
  value={paths}
3405
- onChange={(v) => setPaths(v as string[][])}
3478
+ onValueChange={(v) => setPaths(v as string[][])}
3406
3479
  showSearch
3407
3480
  />
3408
3481
  );
@@ -3419,7 +3492,7 @@ function MultiRegionPicker() {
3419
3492
  <Cascader
3420
3493
  options={REGIONS}
3421
3494
  changeOnSelect
3422
- onChange={(v) => console.log("path", v)}
3495
+ onValueChange={(v) => console.log("path", v)}
3423
3496
  />
3424
3497
  \`}`,
3425
3498
  storyPath: "data-entry/Cascader.stories.tsx",
@@ -3582,7 +3655,7 @@ export function AccountPicker() {
3582
3655
  id="account-picker"
3583
3656
  treeData={accountTree}
3584
3657
  value={account}
3585
- onChange={(v) => setAccount(v as string | undefined)}
3658
+ onValueChange={(v) => setAccount(v as string | undefined)}
3586
3659
  showSearch
3587
3660
  treeDefaultExpandAll
3588
3661
  placeholder="Select account\u2026"
@@ -3600,7 +3673,7 @@ export function DepartmentFilter() {
3600
3673
  id="dept-filter"
3601
3674
  treeData={accountTree}
3602
3675
  value={selected}
3603
- onChange={(v) => setSelected(v as string[])}
3676
+ onValueChange={(v) => setSelected(v as string[])}
3604
3677
  treeCheckable
3605
3678
  showCheckedStrategy={TreeSelect.SHOW_PARENT}
3606
3679
  showSearch
@@ -3673,7 +3746,7 @@ export function DepartmentFilter() {
3673
3746
  }
3674
3747
  ],
3675
3748
  usage: [
3676
- "DO own `targetKeys` in state and update it inside `onChange`: `const [targetKeys, setTargetKeys] = useState<string[]>([]); onChange={(next) => setTargetKeys(next)}`.",
3749
+ "DO own `targetKeys` in state and update it inside `onChange`: `const [targetKeys, setTargetKeys] = useState<string[]>([]); onValueChange={(next) => setTargetKeys(next)}`.",
3677
3750
  "DO NOT hand-roll a two-panel checkbox picker \u2014 Transfer ships the full shuttle UX (select-all header, indeterminate state, search, move buttons, empty state) out of the box.",
3678
3751
  "DO enable `showSearch` for lists longer than ~10 items; the built-in SearchInput filters by both `title` and `description` text content, including ReactNode content via `reactNodeText`.",
3679
3752
  "DO use `oneWay={true}` for append-only flows (e.g. adding permissions to a role) where items must never be moved back.",
@@ -3712,7 +3785,7 @@ export function AccountMapping() {
3712
3785
  <Transfer
3713
3786
  dataSource={ALL_ACCOUNTS}
3714
3787
  targetKeys={targetKeys}
3715
- onChange={(nextKeys) => setTargetKeys(nextKeys)}
3788
+ onValueChange={(nextKeys) => setTargetKeys(nextKeys)}
3716
3789
  titles={["Available Accounts", "Mapped Accounts"]}
3717
3790
  showSearch
3718
3791
  />
@@ -3842,7 +3915,7 @@ export function AvatarUploadForm() {
3842
3915
  <Upload
3843
3916
  variant="avatar-crop"
3844
3917
  value={items}
3845
- onChange={setItems}
3918
+ onValueChange={setItems}
3846
3919
  onUpload={handleUpload}
3847
3920
  maxSizeBytes={5 * 1024 * 1024}
3848
3921
  />
@@ -3859,7 +3932,7 @@ export function DocumentUploadDropzone() {
3859
3932
  <Upload
3860
3933
  variant="dropzone"
3861
3934
  value={items}
3862
- onChange={setItems}
3935
+ onValueChange={setItems}
3863
3936
  accept=".pdf,.xlsx"
3864
3937
  maxCount={10}
3865
3938
  maxSizeBytes={20 * 1024 * 1024}
@@ -3948,7 +4021,7 @@ export function AvatarField() {
3948
4021
 
3949
4022
  return (
3950
4023
  <>
3951
- <input type="file" accept="image/*" onChange={handleFileChange} />
4024
+ <input type="file" accept="image/*" onValueChange={handleFileChange} />
3952
4025
  <UploadCropDialog
3953
4026
  open={cropFile !== null}
3954
4027
  onOpenChange={(open) => { if (!open) setCropFile(null); }}
@@ -4004,7 +4077,7 @@ export function AvatarField() {
4004
4077
  }
4005
4078
  ],
4006
4079
  usage: [
4007
- "DO wrap in FormField when a label or validation message is needed \u2014 pass the same id to both FormField and ColorPicker so htmlFor wires up correctly: `<FormField id='brand' label='Brand color'><ColorPicker id='brand' value={v} onChange={setV} /></FormField>`.",
4080
+ "DO wrap in FormField when a label or validation message is needed \u2014 pass the same id to both FormField and ColorPicker so htmlFor wires up correctly: `<FormField id='brand' label='Brand color'><ColorPicker id='brand' value={v} onValueChange={setV} /></FormField>`.",
4008
4081
  "DO use controlled mode (value + onChange) \u2014 there is no defaultValue/uncontrolled path; always supply value.",
4009
4082
  "DON'T pass an invalid or empty string to value \u2014 the component will flash the invalid color on the preview swatch. Always initialize state to a valid 3- or 6-digit hex (e.g. '#2563eb').",
4010
4083
  "The hex Input is a live draft field \u2014 onChange is NOT called until the user presses Enter or blurs; only then is the value validated and the parent notified. Do not rely on onChange firing on every keystroke.",
@@ -4034,7 +4107,7 @@ export function BrandColorField() {
4034
4107
  <ColorPicker
4035
4108
  id="brand-color"
4036
4109
  value={color}
4037
- onChange={setColor}
4110
+ onValueChange={setColor}
4038
4111
  />
4039
4112
  </FormField>
4040
4113
  );
@@ -4043,7 +4116,7 @@ export function BrandColorField() {
4043
4116
  // Compact swatch-only variant (no hex input)
4044
4117
  export function SwatchOnly() {
4045
4118
  const [color, setColor] = useState("#16a34a");
4046
- return <ColorPicker value={color} onChange={setColor} showHexInput={false} />;
4119
+ return <ColorPicker value={color} onValueChange={setColor} showHexInput={false} />;
4047
4120
  }
4048
4121
 
4049
4122
  // Disabled state
@@ -4976,7 +5049,7 @@ export function ControlledExample() {
4976
5049
  name="permissions"
4977
5050
  options={PERMISSIONS}
4978
5051
  value={selected}
4979
- onChange={setSelected}
5052
+ onValueChange={setSelected}
4980
5053
  />
4981
5054
  );
4982
5055
  }`,
@@ -5213,7 +5286,7 @@ function LegacyAccountPicker({ value, onChange }) {
5213
5286
  return (
5214
5287
  <SearchSelect
5215
5288
  value={value}
5216
- onChange={onChange}
5289
+ onValueChange={onChange}
5217
5290
  options={[
5218
5291
  { value: "acc-001", label: "Cash", sublabel: "Current assets", group: "Assets" },
5219
5292
  { value: "acc-002", label: "Accounts Receivable", group: "Assets" },
@@ -5237,7 +5310,7 @@ function LegacyVendorPicker({ value, currentVendorName, onChange }) {
5237
5310
  return (
5238
5311
  <SearchSelect
5239
5312
  value={value}
5240
- onChange={onChange}
5313
+ onValueChange={onChange}
5241
5314
  loadOptions={fetchVendors}
5242
5315
  selectedLabel={currentVendorName}
5243
5316
  placeholder="Select vendor"
@@ -5326,7 +5399,7 @@ import { Autocomplete } from "@godxjp/ui/data-entry";
5326
5399
 
5327
5400
  // \u2705 Replace with:
5328
5401
  // import { Select } from "@godxjp/ui/data-entry";
5329
- // <Select options={options} showSearch placeholder="Search\u2026" onChange={setValue} value={value} />
5402
+ // <Select options={options} showSearch placeholder="Search\u2026" onValueChange={setValue} value={value} />
5330
5403
 
5331
5404
  // Legacy usage (backward compat only):
5332
5405
  import { Autocomplete } from "@godxjp/ui/data-entry";
@@ -5840,10 +5913,10 @@ export function LegacyInvoiceHeader() {
5840
5913
  ],
5841
5914
  useCases: [
5842
5915
  "App shell / top-nav language switcher that persists the user's locale preference via AppProvider and localStorage without any extra state.",
5843
- "Settings page 'Language' field where locale is part of a form submitted to the backend \u2014 use controlled mode: value={form.locale} onChange={(v) => form.setLocale(v)}.",
5916
+ "Settings page 'Language' field where locale is part of a form submitted to the backend \u2014 use controlled mode: value={form.locale} onValueChange={(v) => form.setLocale(v)}.",
5844
5917
  "Onboarding wizard step that lets the user pick their language before the rest of the app is configured \u2014 mount with AppProvider persist={false} and a controlled value to keep state local to the wizard.",
5845
5918
  "Admin user-profile form where locale is one of several preferences (alongside timezone and date/time format) \u2014 pair with TimezonePicker, DateFormatPicker, TimeFormatPicker under the same AppProvider.",
5846
- "Storybook / test harness where AppProvider is not present \u2014 render in fully controlled mode: <LocalePicker value='en' onChange={fn} />.",
5919
+ "Storybook / test harness where AppProvider is not present \u2014 render in fully controlled mode: <LocalePicker value='en' onValueChange={fn} />.",
5847
5920
  "Localization QA tool that cycles through locales programmatically \u2014 drive via controlled value to switch the UI language without user interaction."
5848
5921
  ],
5849
5922
  related: [
@@ -5875,7 +5948,7 @@ export function LocaleField() {
5875
5948
  return (
5876
5949
  <div className="flex flex-col gap-1.5">
5877
5950
  <label htmlFor="locale-picker">Language</label>
5878
- <LocalePicker id="locale-picker" value={locale} onChange={setLocale} />
5951
+ <LocalePicker id="locale-picker" value={locale} onValueChange={setLocale} />
5879
5952
  </div>
5880
5953
  );
5881
5954
  }\`}`,
@@ -5916,7 +5989,7 @@ export function LocaleField() {
5916
5989
  ],
5917
5990
  usage: [
5918
5991
  "DO: Wrap with <AppProvider> and omit value/onChange \u2014 the picker reads and writes context automatically. This is the canonical zero-prop usage: <TimezonePicker />.",
5919
- "DO: Pass value + onChange for fully controlled standalone usage (e.g. a form field that posts the timezone string): <TimezonePicker value={tz} onChange={setTz} />. AppProvider is not required in this mode.",
5992
+ "DO: Pass value + onChange for fully controlled standalone usage (e.g. a form field that posts the timezone string): <TimezonePicker value={tz} onValueChange={setTz} />. AppProvider is not required in this mode.",
5920
5993
  "DON'T: Omit BOTH AppProvider context AND controlled props \u2014 the component throws at runtime: 'TimezonePicker requires <AppProvider> or controlled value + onChange'.",
5921
5994
  "DO: Pass options={['Asia/Tokyo', 'UTC']} to restrict the list. The current value is automatically prepended if it is missing from the list, so the picker never shows an empty/invalid selection.",
5922
5995
  "DON'T: Hand-roll a timezone <select> or a custom combobox \u2014 TimezonePicker already handles locale-aware labels (translated city + GMT offset), the full IANA list, and ARIA semantics.",
@@ -5945,7 +6018,7 @@ export function TimezoneField() {
5945
6018
  return (
5946
6019
  <TimezonePicker
5947
6020
  value={tz}
5948
- onChange={setTz}
6021
+ onValueChange={setTz}
5949
6022
  options={["Asia/Tokyo", "Asia/Ho_Chi_Minh", "UTC"]}
5950
6023
  />
5951
6024
  );
@@ -6053,7 +6126,7 @@ export function ExportDialog() {
6053
6126
  return (
6054
6127
  <div className="flex items-center gap-2">
6055
6128
  <label htmlFor="export-fmt">Export date format</label>
6056
- <DateFormatPicker id="export-fmt" value={fmt} onChange={setFmt} />
6129
+ <DateFormatPicker id="export-fmt" value={fmt} onValueChange={setFmt} />
6057
6130
  </div>
6058
6131
  );
6059
6132
  }\`}`,
@@ -6093,7 +6166,7 @@ export function ExportDialog() {
6093
6166
  ],
6094
6167
  usage: [
6095
6168
  "DO use inside <AppProvider> with no extra props to let it read/write the global time-format automatically: <AppProvider defaultTimeFormat='24h'><TimeFormatPicker /></AppProvider>",
6096
- "DO switch to fully controlled mode when you need to manage the value yourself \u2014 supply BOTH value and onChange, or the component will throw: <TimeFormatPicker value={fmt} onChange={setFmt} />",
6169
+ "DO switch to fully controlled mode when you need to manage the value yourself \u2014 supply BOTH value and onChange, or the component will throw: <TimeFormatPicker value={fmt} onValueChange={setFmt} />",
6097
6170
  "DON'T omit both AppProvider and controlled props \u2014 the component throws an Error at render time: 'TimeFormatPicker requires <AppProvider> or controlled value + onChange'. There is no silent fallback.",
6098
6171
  "DON'T hand-roll a time-format <select> \u2014 the locale-aware labels (e.g. '24 gi\u1EDD' for vi, '24-hour' for en) are generated internally from the i18n layer; reinventing this loses those translations.",
6099
6172
  "DO wire a <label htmlFor={id}> when using the id prop for accessibility; the SelectTrigger already sets aria-label from i18n but a visible label improves discoverability.",
@@ -6136,7 +6209,7 @@ export function SettingsForm() {
6136
6209
  return (
6137
6210
  <div>
6138
6211
  <label htmlFor="time-fmt">Time format</label>
6139
- <TimeFormatPicker id="time-fmt" value={fmt} onChange={setFmt} />
6212
+ <TimeFormatPicker id="time-fmt" value={fmt} onValueChange={setFmt} />
6140
6213
  </div>
6141
6214
  );
6142
6215
  }\`}
@@ -6449,7 +6522,11 @@ export function InvoiceListHeader() {
6449
6522
  group: "data-display",
6450
6523
  tagline: "Radix Avatar wrapper with image and fallback slots for users, teams, and entities.",
6451
6524
  props: [
6452
- { name: "children", type: "ReactNode", description: "Compose AvatarImage and AvatarFallback." },
6525
+ {
6526
+ name: "children",
6527
+ type: "ReactNode",
6528
+ description: "Compose AvatarImage and AvatarFallback."
6529
+ },
6453
6530
  { name: "className", type: "string", description: "Extra classes on the avatar root." }
6454
6531
  ],
6455
6532
  usage: [
@@ -6472,11 +6549,28 @@ export function InvoiceListHeader() {
6472
6549
  group: "layout",
6473
6550
  tagline: "Radix Separator wrapper for tokenized horizontal or vertical dividers.",
6474
6551
  props: [
6475
- { name: "orientation", type: '"horizontal" | "vertical"', defaultValue: '"horizontal"', description: "Divider direction." },
6476
- { name: "decorative", type: "boolean", defaultValue: "true", description: "Whether the separator is decorative for assistive tech." }
6552
+ {
6553
+ name: "orientation",
6554
+ type: '"horizontal" | "vertical"',
6555
+ defaultValue: '"horizontal"',
6556
+ description: "Divider direction."
6557
+ },
6558
+ {
6559
+ name: "decorative",
6560
+ type: "boolean",
6561
+ defaultValue: "true",
6562
+ description: "Whether the separator is decorative for assistive tech."
6563
+ }
6564
+ ],
6565
+ usage: [
6566
+ "DO use Separator for section dividers instead of raw border divs.",
6567
+ "DO set orientation='vertical' only when the parent gives it a stable height."
6568
+ ],
6569
+ useCases: [
6570
+ "Separating toolbar groups",
6571
+ "Dividing stacked page sections",
6572
+ "Vertical split between metadata groups"
6477
6573
  ],
6478
- usage: ["DO use Separator for section dividers instead of raw border divs.", "DO set orientation='vertical' only when the parent gives it a stable height."],
6479
- useCases: ["Separating toolbar groups", "Dividing stacked page sections", "Vertical split between metadata groups"],
6480
6574
  related: ["Stack \u2014 use for vertical spacing without a visible rule."],
6481
6575
  example: `import { Separator } from "@godxjp/ui/layout";
6482
6576
 
@@ -6491,8 +6585,15 @@ export function InvoiceListHeader() {
6491
6585
  props: [
6492
6586
  { name: "className", type: "string", description: "Size and layout classes for the block." }
6493
6587
  ],
6494
- usage: ["DO use Skeleton for a custom block when SkeletonRows/Table/Card do not match the final layout.", "DON'T use a spinner overlay for skeletonable page content."],
6495
- useCases: ["Single loading line", "Custom card media placeholder", "Inline metadata placeholder"],
6588
+ usage: [
6589
+ "DO use Skeleton for a custom block when SkeletonRows/Table/Card do not match the final layout.",
6590
+ "DON'T use a spinner overlay for skeletonable page content."
6591
+ ],
6592
+ useCases: [
6593
+ "Single loading line",
6594
+ "Custom card media placeholder",
6595
+ "Inline metadata placeholder"
6596
+ ],
6496
6597
  related: ["SkeletonRows", "SkeletonTable", "SkeletonCard"],
6497
6598
  example: `import { Skeleton } from "@godxjp/ui/feedback";
6498
6599
 
@@ -6506,11 +6607,28 @@ export function InvoiceListHeader() {
6506
6607
  tagline: "Radix Toggle wrapper with default/outline variants and tokenized sizes.",
6507
6608
  props: [
6508
6609
  { name: "pressed", type: "boolean", description: "Controlled pressed state." },
6509
- { name: "onPressedChange", type: "(pressed: boolean) => void", description: "Pressed-state callback." },
6510
- { name: "variant", type: '"default" | "outline"', defaultValue: '"default"', description: "Visual style." },
6511
- { name: "size", type: '"sm" | "default" | "lg"', defaultValue: '"default"', description: "Control size." }
6610
+ {
6611
+ name: "onPressedChange",
6612
+ type: "(pressed: boolean) => void",
6613
+ description: "Pressed-state callback."
6614
+ },
6615
+ {
6616
+ name: "variant",
6617
+ type: '"default" | "outline"',
6618
+ defaultValue: '"default"',
6619
+ description: "Visual style."
6620
+ },
6621
+ {
6622
+ name: "size",
6623
+ type: '"sm" | "default" | "lg"',
6624
+ defaultValue: '"default"',
6625
+ description: "Control size."
6626
+ }
6627
+ ],
6628
+ usage: [
6629
+ "DO provide an accessible label when the toggle only contains an icon.",
6630
+ "DON'T use Toggle for multi-option selection; use ToggleGroup."
6512
6631
  ],
6513
- usage: ["DO provide an accessible label when the toggle only contains an icon.", "DON'T use Toggle for multi-option selection; use ToggleGroup."],
6514
6632
  useCases: ["Bold/italic toolbar buttons", "Pinned filter toggles", "Compact view mode buttons"],
6515
6633
  related: ["ToggleGroup", "Button"],
6516
6634
  example: `import { Toggle } from "@godxjp/ui/data-entry";
@@ -6524,11 +6642,23 @@ export function InvoiceListHeader() {
6524
6642
  group: "data-entry",
6525
6643
  tagline: "Radix ToggleGroup wrapper for single or multiple toggle selection.",
6526
6644
  props: [
6527
- { name: "type", type: '"single" | "multiple"', required: true, description: "Selection mode." },
6645
+ {
6646
+ name: "type",
6647
+ type: '"single" | "multiple"',
6648
+ required: true,
6649
+ description: "Selection mode."
6650
+ },
6528
6651
  { name: "value", type: "string | string[]", description: "Controlled selected value(s)." },
6529
- { name: "onValueChange", type: "(value: string | string[]) => void", description: "Selection callback." }
6652
+ {
6653
+ name: "onValueChange",
6654
+ type: "(value: string | string[]) => void",
6655
+ description: "Selection callback."
6656
+ }
6657
+ ],
6658
+ usage: [
6659
+ "DO choose type='single' for mutually exclusive toolbar modes.",
6660
+ "DO choose type='multiple' for independent formatting toggles."
6530
6661
  ],
6531
- usage: ["DO choose type='single' for mutually exclusive toolbar modes.", "DO choose type='multiple' for independent formatting toggles."],
6532
6662
  useCases: ["Text alignment selector", "Formatting toolbar", "View density switcher"],
6533
6663
  related: ["Toggle", "RadioGroup"],
6534
6664
  example: `import { ToggleGroup, ToggleGroupItem } from "@godxjp/ui/data-entry";
@@ -6544,10 +6674,18 @@ export function InvoiceListHeader() {
6544
6674
  group: "layout",
6545
6675
  tagline: "Radix AspectRatio wrapper for stable media and preview frames.",
6546
6676
  props: [
6547
- { name: "ratio", type: "number", defaultValue: "16 / 9", description: "Width divided by height." },
6677
+ {
6678
+ name: "ratio",
6679
+ type: "number",
6680
+ defaultValue: "16 / 9",
6681
+ description: "Width divided by height."
6682
+ },
6548
6683
  { name: "children", type: "ReactNode", description: "Content constrained to the ratio." }
6549
6684
  ],
6550
- usage: ["DO use AspectRatio for media, maps, charts, or previews that must not jump during load.", "DON'T use it for unconstrained text content."],
6685
+ usage: [
6686
+ "DO use AspectRatio for media, maps, charts, or previews that must not jump during load.",
6687
+ "DON'T use it for unconstrained text content."
6688
+ ],
6551
6689
  useCases: ["Video embed frame", "Image preview slot", "Dashboard chart placeholder"],
6552
6690
  related: ["CardCover", "Skeleton"],
6553
6691
  example: `import { AspectRatio } from "@godxjp/ui/layout";
@@ -6555,6 +6693,229 @@ export function InvoiceListHeader() {
6555
6693
  <AspectRatio ratio={16 / 9}>...</AspectRatio>`,
6556
6694
  storyPath: "layout/AspectRatio.stories.tsx",
6557
6695
  rules: [2, 3]
6696
+ },
6697
+ {
6698
+ name: "Accordion",
6699
+ group: "data-display",
6700
+ tagline: "Radix accordion \u2014 vertically stacked, collapsible sections. Compose Accordion > AccordionItem > AccordionTrigger + AccordionContent.",
6701
+ props: [
6702
+ {
6703
+ name: "type",
6704
+ type: '"single" | "multiple"',
6705
+ required: true,
6706
+ description: "single = one open at a time; multiple = independent."
6707
+ },
6708
+ {
6709
+ name: "collapsible",
6710
+ type: "boolean",
6711
+ description: "When type=single, allow closing the open item."
6712
+ },
6713
+ { name: "value", type: "string | string[]", description: "Controlled open item(s)." },
6714
+ {
6715
+ name: "defaultValue",
6716
+ type: "string | string[]",
6717
+ description: "Uncontrolled initial open item(s)."
6718
+ },
6719
+ {
6720
+ name: "onValueChange",
6721
+ type: "(value: string | string[]) => void",
6722
+ description: "Open-state callback."
6723
+ }
6724
+ ],
6725
+ usage: [
6726
+ 'DO compose the full set: <Accordion type="single" collapsible><AccordionItem value="a"><AccordionTrigger/><AccordionContent/></AccordionItem></Accordion>.',
6727
+ "DO give each AccordionItem a unique `value`.",
6728
+ "DON'T use it for primary navigation \u2014 that's Sidebar/Tabs. Accordion is for collapsible content/FAQ."
6729
+ ],
6730
+ useCases: [
6731
+ "FAQ lists",
6732
+ "Grouped settings sections",
6733
+ "Collapsible detail panels on a record page",
6734
+ "Filter facet groups in a sidebar"
6735
+ ],
6736
+ related: [
6737
+ "Collapsible (single open/close region, no item set)",
6738
+ "Tabs (mutually-exclusive views, always one visible)"
6739
+ ],
6740
+ example: `import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from "@godxjp/ui/data-display";
6741
+
6742
+ <Accordion type="single" collapsible>
6743
+ <AccordionItem value="ship">
6744
+ <AccordionTrigger>\u914D\u9001\u306B\u3064\u3044\u3066</AccordionTrigger>
6745
+ <AccordionContent>3\u301C5\u55B6\u696D\u65E5\u3067\u304A\u5C4A\u3051\u3057\u307E\u3059\u3002</AccordionContent>
6746
+ </AccordionItem>
6747
+ </Accordion>`,
6748
+ storyPath: "data-display/Accordion.stories.tsx",
6749
+ rules: [3, 6]
6750
+ },
6751
+ {
6752
+ name: "HoverCard",
6753
+ group: "data-display",
6754
+ tagline: "Radix hover card \u2014 a rich popover shown on hover/focus of a trigger (for sighted-pointer affordances; not a replacement for Tooltip's short text).",
6755
+ props: [
6756
+ {
6757
+ name: "openDelay",
6758
+ type: "number",
6759
+ defaultValue: "700",
6760
+ description: "ms before opening on hover."
6761
+ },
6762
+ {
6763
+ name: "closeDelay",
6764
+ type: "number",
6765
+ defaultValue: "300",
6766
+ description: "ms before closing."
6767
+ },
6768
+ { name: "open", type: "boolean", description: "Controlled open state." },
6769
+ {
6770
+ name: "onOpenChange",
6771
+ type: "(open: boolean) => void",
6772
+ description: "Open-state callback."
6773
+ }
6774
+ ],
6775
+ usage: [
6776
+ "DO compose HoverCard > HoverCardTrigger > HoverCardContent.",
6777
+ "DO use for RICH preview content (a card, avatar + bio); for short plain-text hints use Tooltip.",
6778
+ "DON'T rely on it for essential info \u2014 hover isn't available on touch; provide the same content on click/tap elsewhere."
6779
+ ],
6780
+ useCases: [
6781
+ "User/profile preview on @mention hover",
6782
+ "Entity preview (customer/account) on a table cell",
6783
+ "Glossary term definitions",
6784
+ "Commit/PR preview links"
6785
+ ],
6786
+ related: [
6787
+ "Tooltip (short text label, not rich content)",
6788
+ "Popover (click-triggered, interactive content)"
6789
+ ],
6790
+ example: `import { HoverCard, HoverCardTrigger, HoverCardContent } from "@godxjp/ui/data-display";
6791
+
6792
+ <HoverCard>
6793
+ <HoverCardTrigger>@yamada</HoverCardTrigger>
6794
+ <HoverCardContent>\u5C71\u7530\u592A\u90CE \u2014 \u7D4C\u7406\u90E8</HoverCardContent>
6795
+ </HoverCard>`,
6796
+ storyPath: "data-display/HoverCard.stories.tsx",
6797
+ rules: [3, 6]
6798
+ },
6799
+ {
6800
+ name: "PasswordInput",
6801
+ group: "data-entry",
6802
+ tagline: "Input for passwords with a built-in show/hide eye toggle. Accepts all Input props except `type`.",
6803
+ props: [
6804
+ {
6805
+ name: "value",
6806
+ type: "string",
6807
+ description: "Controlled value (or use defaultValue/uncontrolled)."
6808
+ },
6809
+ { name: "name", type: "string", description: "Form field name for native submission." },
6810
+ { name: "placeholder", type: "string", description: "Placeholder text." },
6811
+ { name: "disabled", type: "boolean", description: "Disables the field + toggle." }
6812
+ ],
6813
+ usage: [
6814
+ 'DO use for any password / secret field instead of `<Input type="password">` so users get the show/hide affordance.',
6815
+ 'DO pass `name` + `autoComplete="current-password"|"new-password"` for correct form/password-manager behavior.',
6816
+ "DON'T add your own eye button \u2014 it's built in (and excluded from the tab order)."
6817
+ ],
6818
+ useCases: [
6819
+ "Login password field",
6820
+ "Sign-up / change-password forms (new-password)",
6821
+ "API key / secret entry in settings"
6822
+ ],
6823
+ related: [
6824
+ "Input (the base text field this wraps)",
6825
+ "FormField (label + error wrapper around it)"
6826
+ ],
6827
+ example: `import { PasswordInput } from "@godxjp/ui/data-entry";
6828
+
6829
+ <PasswordInput name="password" autoComplete="current-password" placeholder="\u30D1\u30B9\u30EF\u30FC\u30C9" />`,
6830
+ storyPath: "data-entry/PasswordInput.stories.tsx",
6831
+ rules: [3, 6]
6832
+ },
6833
+ {
6834
+ name: "Drawer",
6835
+ group: "feedback",
6836
+ tagline: "Bottom-sheet (vaul) \u2014 a draggable sheet that slides up from the screen edge. DISTINCT from Sheet (the side panel); use Drawer for mobile/touch bottom sheets.",
6837
+ props: [
6838
+ { name: "open", type: "boolean", description: "Controlled open state." },
6839
+ {
6840
+ name: "onOpenChange",
6841
+ type: "(open: boolean) => void",
6842
+ description: "Open-state callback."
6843
+ },
6844
+ {
6845
+ name: "shouldScaleBackground",
6846
+ type: "boolean",
6847
+ defaultValue: "true",
6848
+ description: "Scale the page behind the sheet (iOS-style)."
6849
+ }
6850
+ ],
6851
+ usage: [
6852
+ "DO compose Drawer > DrawerTrigger > DrawerContent (> DrawerHeader/DrawerTitle + body + DrawerFooter).",
6853
+ "DO use Drawer for mobile/touch bottom sheets; use Sheet for a desktop side panel and Dialog for a centered modal.",
6854
+ "DON'T confuse with Sheet \u2014 Sheet slides from a side edge, Drawer is the draggable bottom sheet."
6855
+ ],
6856
+ useCases: [
6857
+ "Mobile action sheet / menu",
6858
+ "Filter panel on small screens",
6859
+ "Quick-create form on touch",
6860
+ "Detail peek that drags up"
6861
+ ],
6862
+ related: [
6863
+ "Sheet (side panel, same Radix Dialog base, different placement)",
6864
+ "Dialog (centered modal)"
6865
+ ],
6866
+ example: `import { Drawer, DrawerTrigger, DrawerContent, DrawerHeader, DrawerTitle } from "@godxjp/ui/feedback";
6867
+
6868
+ <Drawer>
6869
+ <DrawerTrigger>\u7D5E\u308A\u8FBC\u307F</DrawerTrigger>
6870
+ <DrawerContent>
6871
+ <DrawerHeader><DrawerTitle>\u7D5E\u308A\u8FBC\u307F</DrawerTitle></DrawerHeader>
6872
+ {/* filters */}
6873
+ </DrawerContent>
6874
+ </Drawer>`,
6875
+ storyPath: "feedback/Drawer.stories.tsx",
6876
+ rules: [3, 6, 24]
6877
+ },
6878
+ {
6879
+ name: "InputOTP",
6880
+ group: "data-entry",
6881
+ tagline: "One-time-code / 2FA input (input-otp) \u2014 N single-character slots that behave as one field. Compose InputOTP > InputOTPGroup > InputOTPSlot.",
6882
+ props: [
6883
+ {
6884
+ name: "maxLength",
6885
+ type: "number",
6886
+ required: true,
6887
+ description: "Number of slots (e.g. 6)."
6888
+ },
6889
+ { name: "value", type: "string", description: "Controlled value." },
6890
+ {
6891
+ name: "onChange",
6892
+ type: "(value: string) => void",
6893
+ description: "Value callback (this is a true text input \u2014 onChange is the DOM-style value handler here)."
6894
+ },
6895
+ { name: "pattern", type: "string", description: "Allowed-char regex (e.g. digits only)." }
6896
+ ],
6897
+ usage: [
6898
+ "DO set `maxLength` to the code length and render that many InputOTPSlot with sequential `index`.",
6899
+ "DO wrap slots in InputOTPGroup; use InputOTPSeparator between groups (e.g. 3 + 3).",
6900
+ "DON'T build N separate Inputs \u2014 this is ONE field with paste, arrow-key, and caret handling built in."
6901
+ ],
6902
+ useCases: [
6903
+ "2FA / OTP verification code",
6904
+ "Email / SMS confirmation code",
6905
+ "PIN entry",
6906
+ "Invite / redemption code"
6907
+ ],
6908
+ related: ["Input (a normal single text field)", "PasswordInput (masked secret field)"],
6909
+ example: `import { InputOTP, InputOTPGroup, InputOTPSlot } from "@godxjp/ui/data-entry";
6910
+
6911
+ <InputOTP maxLength={6}>
6912
+ <InputOTPGroup>
6913
+ <InputOTPSlot index={0} /><InputOTPSlot index={1} /><InputOTPSlot index={2} />
6914
+ <InputOTPSlot index={3} /><InputOTPSlot index={4} /><InputOTPSlot index={5} />
6915
+ </InputOTPGroup>
6916
+ </InputOTP>`,
6917
+ storyPath: "data-entry/InputOTP.stories.tsx",
6918
+ rules: [3, 6]
6558
6919
  }
6559
6920
  ];
6560
6921
  function findComponent(name) {
@@ -6568,194 +6929,85 @@ function componentsByGroup(group) {
6568
6929
  // src/data/prop-vocabulary.ts
6569
6930
  var PROP_VOCABULARY = [
6570
6931
  {
6571
- name: "SizeProp",
6572
- concept: "Dimensional scale for most primitives.",
6573
- values: ["small", "default", "large"],
6574
- usedBy: [
6575
- "InputSize",
6576
- "CheckboxGroupSize",
6577
- "ColorPickerSize",
6578
- "MediaUploadSize",
6579
- "ProgressSize",
6580
- "RadioGroupSize",
6581
- "RateSize",
6582
- "TransferSize",
6583
- "SegmentedControlSize (subset)",
6584
- "SpaceSize (+ number)",
6585
- "FlexGap (+ number)",
6586
- "GridGap (+ number)",
6587
- "MasonryGap (+ number)"
6588
- ]
6589
- },
6590
- {
6591
- name: "SizeWithXSProp",
6592
- concept: 'Extension of `SizeProp` with `"x-small"` for compact icon-bar / table-row contexts.',
6593
- values: ["x-small", "small", "default", "large"],
6594
- usedBy: ["ButtonSize"]
6932
+ name: "ValueProp<T = string>",
6933
+ concept: "Abstract controlled value.",
6934
+ values: ["generic"],
6935
+ usedBy: ["CheckboxGroup", "Upload", "Cascader", "TreeSelect", "Tabs", "SearchSelect"]
6595
6936
  },
6596
6937
  {
6597
- name: "IconSizeProp",
6598
- concept: "Icon-symbol shaped primitives (visual axis = glyph size, not height).",
6599
- values: ["sm", "md", "lg"],
6600
- usedBy: ["SpinnerSize", "IconButtonSize"]
6938
+ name: "DefaultValueProp<T = string>",
6939
+ concept: "Abstract uncontrolled initial value.",
6940
+ values: ["generic"],
6941
+ usedBy: ["CheckboxGroup", "Upload", "Cascader", "TreeSelect", "Tabs"]
6601
6942
  },
6602
6943
  {
6603
- name: "StatusProp",
6604
- concept: "Form-field validation state.",
6605
- values: ["default", "error", "warning", "success"],
6606
- usedBy: ["InputStatus"],
6607
- notes: 'Form errors use `"error"` (not `"destructive"`) \u2014 different concern from destructive actions.'
6944
+ name: "OnValueChangeProp<T = string>",
6945
+ concept: "Callback for abstract value changes. DOM events continue to use onChange.",
6946
+ values: ["(value: T) => void"],
6947
+ usedBy: ["CheckboxGroup", "Upload", "Cascader", "TreeSelect", "Transfer", "settings pickers"]
6608
6948
  },
6609
6949
  {
6610
- name: "ToneProp",
6611
- concept: "Same values as StatusProp \u2014 name chosen when describing surface colouring rather than validation.",
6612
- values: ["default", "error", "warning", "success"],
6613
- usedBy: ["(alias of StatusProp)"]
6614
- },
6615
- {
6616
- name: "HelpToneProp",
6617
- concept: "Help-line / Alert colour ladder. Adds `info` + `warn` to StatusProp.",
6618
- values: ["default", "info", "warn", "error", "success"],
6619
- usedBy: ["FieldHelpTone"]
6620
- },
6621
- {
6622
- name: "OrientationProp",
6623
- concept: "Layout axis.",
6624
- values: ["horizontal", "vertical"],
6625
- usedBy: [
6626
- "AnchorOrientation",
6627
- "MenuOrientation",
6628
- "RadioGroupOrientation",
6629
- "CheckboxGroupOrientation",
6630
- "SegmentedControlOrientation",
6631
- "StepsOrientation",
6632
- "TabsOrientation"
6633
- ]
6950
+ name: "OpenProp / DefaultOpenProp / OnOpenChangeProp",
6951
+ concept: "Disclosure state.",
6952
+ values: ["boolean", "(open: boolean) => void"],
6953
+ usedBy: ["Dialog", "Sheet", "Popover"]
6634
6954
  },
6635
6955
  {
6636
- name: "DensityProp",
6637
- concept: "Internal row-height / padding scale (distinct from SizeProp \u2014 density rescales chrome, size rescales the primitive's own visual axis).",
6638
- values: ["compact", "default", "comfortable"],
6639
- usedBy: ["TreeDensity", "TableDensity"]
6640
- },
6641
- {
6642
- name: "SideProp",
6643
- concept: "Edge a floating panel docks against.",
6644
- values: ["top", "right", "bottom", "left"],
6645
- usedBy: ["SheetSide", "TabsPlacement", "TableStickySide (subset)"]
6646
- },
6647
- {
6648
- name: "PlacementProp",
6649
- concept: "Extension of SideProp with a centred anchor (Tabs placement, Tour spotlight).",
6650
- values: ["top", "right", "bottom", "left", "center"],
6651
- usedBy: ["TourPlacement"]
6652
- },
6653
- {
6654
- name: "PaddingProp",
6655
- concept: "Outer gutter scale for surface containers.",
6656
- values: ["tight", "default", "cozy", "none"],
6657
- usedBy: ["CardPadding", "PageHeaderPadding", "PageContentPadding"]
6956
+ name: "SizeProp",
6957
+ concept: "Shared public size names.",
6958
+ values: ["xs", "sm", "md", "lg"],
6959
+ usedBy: ["Button", "Steps", "Switch"],
6960
+ notes: "Component-specific subsets must be documented. Old alias small is sm."
6658
6961
  },
6659
6962
  {
6660
- name: "AlignProp",
6661
- concept: "CSS-flexbox cross-axis alignment ladder.",
6662
- values: ["start", "end", "center", "stretch", "baseline"],
6663
- usedBy: ["FlexAlign"]
6963
+ name: "ToneProp",
6964
+ concept: "Semantic status/color intent.",
6965
+ values: ["default", "success", "warning", "destructive", "info", "muted", "neutral"],
6966
+ usedBy: ["Badge", "Alert"],
6967
+ notes: "Status values belong in tone, not variant."
6664
6968
  },
6665
6969
  {
6666
- name: "ColorProp",
6667
- concept: "Full semantic palette \u2014 every CSS variable slot.",
6668
- values: ["default", "info", "success", "warning", "destructive", "attention", "primary", "secondary"],
6669
- usedBy: ["TagPresetColor (\u2212 secondary)", "TimelineColor (\u2212 secondary)", "SpinnerTone (+ muted)"],
6670
- notes: "Each value maps 1:1 to a CSS variable (`--info`, `--success`, `--warning`, `--destructive`, `--attention`, `--primary`, `--secondary`). The `data-accent` axis rebinds `--primary`'s hue without renaming the slot."
6970
+ name: "GapProp",
6971
+ concept: "Shared layout gap scale.",
6972
+ values: ["xs", "sm", "md", "lg", "xl"],
6973
+ usedBy: ["Stack", "Inline"],
6974
+ notes: "Inline uses an Exclude<GapProp, 'xl'> subset."
6671
6975
  },
6672
6976
  {
6673
- name: "FeedbackColorProp",
6674
- concept: "Subset of ColorProp accepted by feedback primitives (no brand `primary` since they are themselves informational).",
6675
- values: ["default", "info", "success", "warning", "destructive"],
6676
- usedBy: ["AlertColor", "ResultColor", "ProgressColor"]
6977
+ name: "TitleProp",
6978
+ concept: "Primary heading text.",
6979
+ values: ["React.ReactNode"],
6980
+ usedBy: ["PageContainer", "PageHeader", "EmptyState", "Dialog"]
6677
6981
  },
6678
6982
  {
6679
- name: "LoadingProp",
6680
- concept: "Loading-state union \u2014 shared across Form / FormField / data-entry primitives.",
6681
- values: ["true", "false", '{ kind: "spinner" }', '{ kind: "skeleton" }', "{ kind, label }"],
6682
- usedBy: ["FormProps.loading", "FormFieldProps.loading"],
6683
- notes: 'Cascade: `<Form loading>` sets a default for every nested `<FormField>`. Per-field `loading` overrides Form\'s. `true` \u2192 spinner (default). `{ kind: "skeleton" }` \u2192 use for INITIAL fetch state. UX nuance: skeleton on init, spinner on save.'
6983
+ name: "DensityProp",
6984
+ concept: "Page/subtree density.",
6985
+ values: ["compact", "default", "comfortable"],
6986
+ usedBy: ["PageContainer"]
6684
6987
  }
6685
6988
  ];
6686
6989
  function findVocab(name) {
6687
- const normalized = name.trim().toLowerCase().replace(/prop$/i, "");
6688
- return PROP_VOCABULARY.find(
6689
- (v) => v.name.toLowerCase().replace(/prop$/i, "") === normalized
6690
- );
6990
+ const normalized = name.trim().toLowerCase().replace(/prop(?:<.*>)?$/i, "");
6991
+ return PROP_VOCABULARY.find((v) => v.name.toLowerCase().replace(/prop(?:<.*>)?$/i, "") === normalized);
6691
6992
  }
6692
6993
 
6693
6994
  // src/data/tokens.ts
6694
6995
  var TOKENS = [
6695
- // Color semantic slots (rebound by `data-theme` + `data-accent`)
6696
- { name: "--background", category: "color", role: "Base surface", axis: "data-theme" },
6697
- { name: "--foreground", category: "color", role: "Base text", axis: "data-theme" },
6698
- { name: "--card", category: "color", role: "Card surface", axis: "data-theme" },
6699
- { name: "--popover", category: "color", role: "Popover / dropdown surface", axis: "data-theme" },
6700
- { name: "--popover-foreground", category: "color", role: "Popover text", axis: "data-theme" },
6701
- { name: "--primary", category: "color", role: "Brand accent (Buttons, links)", axis: "data-accent" },
6702
- { name: "--primary-foreground", category: "color", role: "Text on primary surface", axis: "data-accent" },
6703
- { name: "--secondary", category: "color", role: "Secondary surface / text dimming", axis: "data-theme" },
6704
- { name: "--accent", category: "color", role: "Hover / focus tint" },
6705
- { name: "--muted", category: "color", role: "Muted surface" },
6706
- { name: "--muted-foreground", category: "color", role: "Muted text" },
6707
- { name: "--border", category: "color", role: "Default border color", axis: "data-theme" },
6708
- { name: "--input", category: "color", role: "Input field border" },
6709
- { name: "--ring", category: "color", role: "Focus ring", axis: "data-accent" },
6710
- { name: "--success", category: "color", role: "Success semantic slot" },
6711
- { name: "--warning", category: "color", role: "Warning semantic slot" },
6712
- { name: "--destructive", category: "color", role: "Danger / destructive action slot" },
6713
- { name: "--info", category: "color", role: "Info / neutral notice slot" },
6714
- { name: "--attention", category: "color", role: "Attention / non-destructive alert slot" },
6715
- // Spacing — fixed scale
6716
- { name: "--spacing-1", category: "spacing", role: "4px", value: "0.25rem" },
6717
- { name: "--spacing-2", category: "spacing", role: "8px", value: "0.5rem" },
6718
- { name: "--spacing-3", category: "spacing", role: "12px", value: "0.75rem" },
6719
- { name: "--spacing-4", category: "spacing", role: "16px", value: "1rem" },
6720
- { name: "--spacing-5", category: "spacing", role: "20px", value: "1.25rem" },
6721
- { name: "--spacing-6", category: "spacing", role: "24px", value: "1.5rem" },
6722
- { name: "--spacing-8", category: "spacing", role: "32px", value: "2rem" },
6723
- // Typography — fixed scale
6724
- { name: "--text-2xs", category: "typography", role: "10px", value: "0.625rem" },
6725
- { name: "--text-xs", category: "typography", role: "12px", value: "0.75rem" },
6726
- { name: "--text-sm", category: "typography", role: "14px", value: "0.875rem" },
6727
- { name: "--text-base", category: "typography", role: "16px", value: "1rem" },
6728
- { name: "--text-lg", category: "typography", role: "18px", value: "1.125rem" },
6729
- { name: "--text-xl", category: "typography", role: "20px", value: "1.25rem" },
6730
- { name: "--text-2xl", category: "typography", role: "24px", value: "1.5rem" },
6731
- { name: "--font-mono", category: "typography", role: "Monospace stack" },
6732
- // Radius — fixed scale
6733
- { name: "--radius-sm", category: "radius", role: "Small (chips, inputs)", value: "0.25rem" },
6734
- { name: "--radius-md", category: "radius", role: "Medium (cards)", value: "0.5rem" },
6735
- { name: "--radius-lg", category: "radius", role: "Large (dialogs)", value: "0.75rem" },
6736
- { name: "--radius-full", category: "radius", role: "Pill / circle", value: "9999px" },
6737
- // Breakpoints — mobile-first min-widths
6738
- { name: "--breakpoint-xs", category: "breakpoint", role: "Mobile-first base (\u22650px)", value: "0" },
6739
- { name: "--breakpoint-sm", category: "breakpoint", role: "Phone landscape / tablet portrait", value: "640px" },
6740
- { name: "--breakpoint-md", category: "breakpoint", role: "Tablet landscape", value: "768px" },
6741
- { name: "--breakpoint-lg", category: "breakpoint", role: "Laptop", value: "1024px" },
6742
- { name: "--breakpoint-xl", category: "breakpoint", role: "Desktop", value: "1280px" },
6743
- { name: "--breakpoint-xxl", category: "breakpoint", role: "Wide desktop", value: "1536px" },
6744
- // Density — rebound by `data-density`
6745
- { name: "--density-element", category: "density", role: "Element height (Input/Button)", axis: "data-density" },
6746
- { name: "--density-element-sm", category: "density", role: "Small element", axis: "data-density" },
6747
- { name: "--density-element-lg", category: "density", role: "Large element", axis: "data-density" },
6748
- { name: "--density-card", category: "density", role: "Card padding", axis: "data-density" },
6749
- { name: "--density-page", category: "density", role: "Page (PageContent) padding", axis: "data-density" },
6750
- { name: "--density-section", category: "density", role: "Section padding (cozy variant)", axis: "data-density" },
6751
- { name: "--header-height", category: "density", role: "Topbar height", axis: "data-density" },
6752
- { name: "--sidebar-width", category: "density", role: "Sidebar width (expanded)", axis: "data-density" },
6753
- { name: "--sidebar-width-collapsed", category: "density", role: "Sidebar icon-only width", axis: "data-density" },
6754
- { name: "--touch-target-min", category: "density", role: "Mobile touch target (does NOT scale)", value: "44px" },
6755
- // Motion — fixed timings
6756
- { name: "--transition-base", category: "motion", role: "Standard transition duration", value: "200ms" },
6757
- { name: "--ease-out", category: "motion", role: "Out easing curve", value: "cubic-bezier(0, 0, 0.2, 1)" },
6758
- { name: "--ease-in-out", category: "motion", role: "In-out easing", value: "cubic-bezier(0.4, 0, 0.2, 1)" }
6996
+ { name: "--wa-*", category: "primitive", tier: "primitive", role: "Neutral decorative Japanese accent primitives for charts/tags/decoration only." },
6997
+ { name: "--chart-1..6", category: "primitive", tier: "primitive", role: "Neutral decorative chart primitives; @theme chart colors reference these tokens." },
6998
+ { name: "--space-0..12", category: "primitive", tier: "primitive", role: "Raw spacing scale." },
6999
+ { name: "--font-size-*", category: "primitive", tier: "primitive", role: "Raw typography scale." },
7000
+ { name: "--primary", category: "semantic", tier: "semantic", role: "Brand/action color role." },
7001
+ { name: "--success", category: "semantic", tier: "semantic", role: "Success status role." },
7002
+ { name: "--warning", category: "semantic", tier: "semantic", role: "Warning status role." },
7003
+ { name: "--destructive", category: "semantic", tier: "semantic", role: "Destructive/error status role." },
7004
+ { name: "--info", category: "semantic", tier: "semantic", role: "Information status role." },
7005
+ { name: "--attention", category: "semantic", tier: "semantic", role: "Attention status role." },
7006
+ { name: "--badge-space-*", category: "component", tier: "component", role: "Badge spacing." },
7007
+ { name: "--card-*", category: "component", tier: "component", role: "Card surface, border, spacing, and typography." },
7008
+ { name: "--control-*", category: "component", tier: "component", role: "Shared form control heights, padding, icons, and focus chrome." },
7009
+ { name: "--table-*", category: "component", tier: "component", role: "Table row/cell sizing." },
7010
+ { name: "--dialog-* / --alert-* / --skeleton-*", category: "component", tier: "component", role: "Feedback component sizing and spacing." }
6759
7011
  ];
6760
7012
  function tokensByCategory(category) {
6761
7013
  return TOKENS.filter((t) => t.category === category);
@@ -6839,7 +7091,7 @@ var PATTERNS = [
6839
7091
  // 2) Badge renders grey with a \u25CB (no colour) for localized/tier labels
6840
7092
  // Cause: it auto-maps only English lifecycle keys. (@godxjp/ui >= 6.1)
6841
7093
  // \u274C <Badge status="\u30D7\u30EC\u30DF\u30A2\u30E0" />
6842
- // \u2705 <Badge status="\u30D7\u30EC\u30DF\u30A2\u30E0" variant="success" icon={null} /> // tier \u2192 pill, no icon
7094
+ // \u2705 <Badge status="\u30D7\u30EC\u30DF\u30A2\u30E0" tone="success" icon={null} /> // tier \u2192 pill, no icon
6843
7095
  // \u2705 <Badge status="active">\u516C\u958B\u4E2D</Badge> // lifecycle \u2192 keep icon
6844
7096
 
6845
7097
  // 3) Table text collapses to one char per line, or a chip wraps
@@ -6990,11 +7242,11 @@ export function DeleteProjectDialog({ open, onOpenChange, slug }: { open: boolea
6990
7242
  <DialogDescription>\u3053\u306E\u64CD\u4F5C\u306F\u53D6\u308A\u6D88\u305B\u307E\u305B\u3093\u3002\u78BA\u8A8D\u306E\u305F\u3081\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u540D "{slug}" \u3068\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044\u3002</DialogDescription>
6991
7243
  </DialogHeader>
6992
7244
  <Stack gap="md">
6993
- <Input value={confirm} onChange={(e) => setConfirm(e.target.value)} placeholder={slug} />
7245
+ <Input value={confirm} onValueChange={(e) => setConfirm(e.target.value)} placeholder={slug} />
6994
7246
  </Stack>
6995
7247
  <DialogFooter>
6996
7248
  <Button variant="outline" onClick={() => onOpenChange(false)}>\u30AD\u30E3\u30F3\u30BB\u30EB</Button>
6997
- <Button variant="destructive" disabled={confirm !== slug} onClick={() => { toast.success("\u524A\u9664\u3057\u307E\u3057\u305F"); onOpenChange(false); }}>\u5B8C\u5168\u306B\u524A\u9664</Button>
7249
+ <Button tone="destructive" disabled={confirm !== slug} onClick={() => { toast.success("\u524A\u9664\u3057\u307E\u3057\u305F"); onOpenChange(false); }}>\u5B8C\u5168\u306B\u524A\u9664</Button>
6998
7250
  </DialogFooter>
6999
7251
  </DialogContent>
7000
7252
  </Dialog>
@@ -7060,7 +7312,7 @@ function Coupons({ coupons }: { coupons: Coupon[] }) {
7060
7312
  // ColumnDef = { key, header, render?, align?: "left"|"center"|"right", sortable?, width? }
7061
7313
  const columns: ColumnDef<Coupon>[] = [
7062
7314
  { key: "name", header: "\u30AF\u30FC\u30DD\u30F3\u540D", render: (c) => <span className="font-medium">{c.name}</span> },
7063
- { key: "scope", header: "\u30B9\u30B3\u30FC\u30D7", render: (c) => <Badge status={c.scope} variant="info" icon={null} /> },
7315
+ { key: "scope", header: "\u30B9\u30B3\u30FC\u30D7", render: (c) => <Badge status={c.scope} tone="info" icon={null} /> },
7064
7316
  { key: "status", header: "\u30B9\u30C6\u30FC\u30BF\u30B9", render: (c) => <Badge status={c.status} /> },
7065
7317
  { key: "valid", header: "\u6709\u52B9\u671F\u9593", render: (c) => \`\${formatDate(c.validFrom)} \u301C \${formatDate(c.validTo)}\` },
7066
7318
  { key: "usage", header: "\u5229\u7528\u6570", align: "right", render: (c) => c.usage.toLocaleString() },
@@ -7102,7 +7354,7 @@ function Coupons({ coupons }: { coupons: Coupon[] }) {
7102
7354
  </Card>
7103
7355
 
7104
7356
  {filtered.length > PAGE_SIZE && (
7105
- <Pagination current={page} total={filtered.length} pageSize={PAGE_SIZE} showTotal onChange={(p) => setPage(p)} />
7357
+ <Pagination current={page} total={filtered.length} pageSize={PAGE_SIZE} showTotal onValueChange={(p) => setPage(p)} />
7106
7358
  )}
7107
7359
  </Stack>
7108
7360
  </PageContainer>
@@ -7158,7 +7410,7 @@ function MemberShow({ id }: { id: string }) {
7158
7410
  {/* Descriptions is COMPOUND \u2014 value goes in children, not a prop */}
7159
7411
  <Descriptions columns={2}>
7160
7412
  <Descriptions.Item label="\u6C0F\u540D">{member.name}</Descriptions.Item>
7161
- <Descriptions.Item label="\u30E9\u30F3\u30AF"><Badge status={member.rank} variant="info" icon={null} /></Descriptions.Item>
7413
+ <Descriptions.Item label="\u30E9\u30F3\u30AF"><Badge status={member.rank} tone="info" icon={null} /></Descriptions.Item>
7162
7414
  <Descriptions.Item label="\u30B9\u30C6\u30FC\u30BF\u30B9"><Badge status={member.status} /></Descriptions.Item>
7163
7415
  <Descriptions.Item label="\u767B\u9332\u65E5">{formatDate(member.registeredAt)}</Descriptions.Item>
7164
7416
  </Descriptions>
@@ -7221,12 +7473,12 @@ const seeded = (n: number) => { const x = Math.sin((n + 1) * 99.71) * 1e4; retur
7221
7473
  <Badge status="active">\u516C\u958B\u4E2D</Badge> // green \u2713 \u516C\u958B\u4E2D
7222
7474
 
7223
7475
  // 2) Unknown label \u2014 set tone explicitly (no icon, since the key is unknown):
7224
- <Badge status="\u516C\u958B\u4E2D" variant="success" />
7476
+ <Badge status="\u516C\u958B\u4E2D" tone="success" />
7225
7477
 
7226
7478
  // 3) Tier / category \u2014 coloured pill, drop the misleading glyph with icon={null}:
7227
- <Badge status="\u30D7\u30EC\u30DF\u30A2\u30E0" variant="success" icon={null} />
7228
- <Badge status="\u30B4\u30FC\u30EB\u30C9" variant="warning" icon={null} />
7229
- <Badge status="\u6CD5\u4EBA\u5171\u901A" variant="info" icon={null} />
7479
+ <Badge status="\u30D7\u30EC\u30DF\u30A2\u30E0" tone="success" icon={null} />
7480
+ <Badge status="\u30B4\u30FC\u30EB\u30C9" tone="warning" icon={null} />
7481
+ <Badge status="\u6CD5\u4EBA\u5171\u901A" tone="info" icon={null} />
7230
7482
 
7231
7483
  // tone: "success" | "warning" | "destructive" | "info" | "neutral" (import type BadgeTone)
7232
7484
  // RULE: a chip never wraps \u2014 it is pinned white-space: nowrap, so it stays one line in
@@ -9075,11 +9327,10 @@ function getTokens(cat) {
9075
9327
  for (const [c, items] of Object.entries(grouped)) {
9076
9328
  out += `## ${c}
9077
9329
 
9078
- | Name | Role | Value | Axis |
9079
- |---|---|---|---|
9330
+ | Name | Role | Tier |
9331
+ |---|---|---|
9080
9332
  `;
9081
- for (const t of items)
9082
- out += `| \`${t.name}\` | ${t.role} | ${t.value ?? "\u2014"} | ${t.axis ?? "\u2014"} |
9333
+ for (const t of items) out += `| \`${t.name}\` | ${t.role} | ${t.tier} |
9083
9334
  `;
9084
9335
  out += "\n";
9085
9336
  }