@godxjp/ui-mcp 33.4.1 → 33.5.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 +6 -6
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -966,13 +966,13 @@ const q = useInfiniteQuery<{ items: Activity[]; cursor?: string }>({
|
|
|
966
966
|
|
|
967
967
|
<InfiniteQueryState query={q} skeleton={<SkeletonRows />} flatten={flattenItemPages} isEmpty={(it) => it.length === 0}>
|
|
968
968
|
{(items) => items.map((a) => <ActivityRow key={a.id} activity={a} />)}
|
|
969
|
-
</InfiniteQueryState>`,storyPath:"query/infinite-query-state.tsx",rules:[]},{name:"Form",group:"data-entry",tagline:"Ant-style layout container \u2014 renders <form> and pushes layout (vertical/horizontal), labelWidth/controlWidth, label alignment, responsive collapse, and multi-column grid down to every FormField (overridable per field).",props:[{name:"layout",type:'"vertical" | "horizontal" | "inline"',defaultValue:'"vertical"',description:"Label position relative to control; applied to all FormFields."},{name:"labelWidth",type:"number | string",description:"Label column width in horizontal layout (number\u2192px). e.g. 120 or '8rem'."},{name:"controlWidth",type:"number | string",description:"Cap the control width (number\u2192px). Omit to fill the column."},{name:"labelAlign",type:'"start" | "end"',defaultValue:'"end"',description:"Horizontal alignment of the label within its column."},{name:"collapseBelow",type:'"sm" | "md" | "lg" | "xl" | false',defaultValue:'"md"',description:"Breakpoint below which horizontal collapses to vertical (mobile-first). false = always horizontal."},{name:"columns",type:"number | { sm?: number; md?: number; lg?: number }",description:"Lay fields out in a responsive grid (reuses ResponsiveGrid; 1 col on small)."},{name:"errors",type:"Partial<Record<string, string | string[]>>",description:"Server validation error bag (e.g. Inertia's form.errors). Each FormField name='\u2026' inside resolves its own message from the bag and CLAIMS its key; <FormErrors /> renders the unclaimed remainder (errors on hidden/derived fields). Works in both <form> and asChild modes."},{name:"density",type:'"compact" | "default" | "comfortable"',description:"Apply a density to controls inside the form."},{name:"disabled",type:"boolean",description:"Disable controls inside FormField, including nested native controls."},{name:"requiredMark",type:'boolean | "optional"',description:"Show required marks, hide them, or mark optional fields."}],usage:["DO set `layout`, `labelWidth`, `controlWidth` ONCE on `<Form>` \u2014 every `<FormField>` inside inherits them. Override a single field by passing the same prop on that `<FormField>` (Form \u2192 FormField priority).","DO rely on mobile-first collapse: `layout='horizontal'` automatically stacks to vertical below `collapseBelow` (default `md`). Pass `collapseBelow={false}` only when a field MUST stay label-beside-control even on phones.","DO use `columns` for multi-field forms (e.g. `columns={2}`) \u2014 it reuses ResponsiveGrid (1 column on small screens, more on md/lg). Span a wide field across columns with `<FormField colSpan={2}>`.","ROW RHYTHM IS THE SAME ON EVERY PATH: a grid of FormFields hands its row spacing to the grid's own `row-gap` (--form-grid-row-gap, defaulting to --form-field-row-gap) instead of the per-field margin a stacked Form uses. So `columns={1}` \u2014 and any `columns={n}` form once a narrow container collapses it to one column \u2014 is pixel-identical to a Form with no `columns`, and a hand-written `<ResponsiveGrid columns={2}>` of FormFields inside a `CardContent` (what a form with several titled Card sections has to write) matches `columns={2}` exactly. Retune --form-field-row-gap to move every path together; --form-grid-row-gap / --form-grid-column-gap to retune the grid rows / gutter alone. Never add margin to a FormField to space a grid row: a per-item margin inside a grid misaligns row 1 and double-counts the track gap.","ACTION ROW in a horizontal form (gh#1271): the submit/cancel buttons go in `<FormField label={false}><Flex gap=\"sm\" wrap>\u2026buttons\u2026</Flex></FormField>` as the LAST field. A label-less field renders no label cell and its control is pinned to the control column (gh#709), so the first button's left edge is the inputs' left edge, it follows `labelWidth`, and it collapses with the fields below `collapseBelow`; `wrap` lets the row break onto a second line on a phone. Requires `labelWidth` on the Form \u2014 unset, each label column is as wide as its own text and nothing can align with it. DON'T use `staticText` for buttons (it is the read-only VALUE slot), DON'T push the row with a hand-written padding, and DON'T float it to the far end of the card, away from the column the eye just went down. This is the row for a form INSIDE a card or section; a form that is the whole page puts its primary + cancel in the PageContainer footer instead (docs/general/button/examples/form-actions.tsx).","DON'T hand-roll a `<form>` + Flex stack for spacing \u2014 `<Form>` provides the vertical rhythm and the layout context FormField reads. Wire react-hook-form by spreading `onSubmit={handleSubmit(...)}` onto `<Form>`.","SERVER ERROR BAG: pass `errors={form.errors}` (Inertia) ONCE on `<Form>`, give each field a `name`, and put `<FormErrors />` at the top of the form. A named field resolves its message from the bag automatically (no per-field `error={errors.x}`), and FormErrors catches validation errors on hidden/derived keys (`action_mode`, `page`, a source-record id) that no visible field could display \u2014 without it those submits fail SILENTLY.","SIBLING FORMS: an edit screen split into several Card+Form sections shares ONE bag by wrapping the region in `<FormErrorsProvider errors={form.errors}>` instead of passing `errors` to each Form \u2014 the section Forms (without their own `errors`) join the shared registry, and one `<FormErrors />` anywhere in the region renders the unclaimed remainder."],useCases:["A settings page form where every label sits in a fixed 120px column to the left of its control (horizontal), collapsing to stacked labels on mobile.","A two-column entity-edit form (`columns={2}`) where the address field spans both columns (`colSpan={2}`).","A compact filter form (`layout='horizontal' density='compact'`) above a DataTable.","An Inertia edit screen passing `errors={form.errors}` so every `FormField name='\u2026'` self-binds its server validation message and `<FormErrors />` surfaces the hidden-field remainder."],related:["FormField \u2014 the per-field wrapper (label + control + helper/error) that reads Form's layout context; use one per control inside a Form.","FormErrors \u2014 renders the error-bag entries no mounted FormField claims (validation errors on hidden/derived fields); place inside a `Form errors={\u2026}`.","ResponsiveGrid \u2014 Form `columns` reuses it; use ResponsiveGrid directly for non-form card grids."],example:`import { Form, FormField, Input } from "@godxjp/ui/data-entry";
|
|
969
|
+
</InfiniteQueryState>`,storyPath:"query/infinite-query-state.tsx",rules:[]},{name:"Form",group:"data-entry",tagline:"Ant-style layout container \u2014 renders <form> and pushes layout (vertical/horizontal), labelWidth/controlWidth, label alignment, responsive collapse, and multi-column grid down to every FormField (overridable per field).",props:[{name:"layout",type:'"vertical" | "horizontal" | "inline"',defaultValue:'"vertical"',description:"Label position relative to control; applied to all FormFields."},{name:"labelWidth",type:"number | string",description:"Label column width in horizontal layout (number\u2192px). e.g. 120 or '8rem'."},{name:"controlWidth",type:"number | string",description:"Cap the control width (number\u2192px). Omit to fill the column. Caps the control only \u2014 the helper and error still span the field column."},{name:"labelAlign",type:'"start" | "end"',defaultValue:'"end"',description:"Horizontal alignment of the label within its column."},{name:"collapseBelow",type:'"sm" | "md" | "lg" | "xl" | false',defaultValue:'"md"',description:"Breakpoint below which horizontal collapses to vertical (mobile-first). false = always horizontal."},{name:"columns",type:"number | { sm?: number; md?: number; lg?: number }",description:"Lay fields out in a responsive grid (reuses ResponsiveGrid; 1 col on small)."},{name:"errors",type:"Partial<Record<string, string | string[]>>",description:"Server validation error bag (e.g. Inertia's form.errors). Each FormField name='\u2026' inside resolves its own message from the bag and CLAIMS its key; <FormErrors /> renders the unclaimed remainder (errors on hidden/derived fields). Works in both <form> and asChild modes."},{name:"density",type:'"compact" | "default" | "comfortable"',description:"Apply a density to controls inside the form."},{name:"disabled",type:"boolean",description:"Disable controls inside FormField, including nested native controls."},{name:"requiredMark",type:'boolean | "optional"',description:"Show required marks, hide them, or mark optional fields."}],usage:["DO set `layout`, `labelWidth`, `controlWidth` ONCE on `<Form>` \u2014 every `<FormField>` inside inherits them. Override a single field by passing the same prop on that `<FormField>` (Form \u2192 FormField priority).","DO rely on mobile-first collapse: `layout='horizontal'` automatically stacks to vertical below `collapseBelow` (default `md`). Pass `collapseBelow={false}` only when a field MUST stay label-beside-control even on phones.","DO use `columns` for multi-field forms (e.g. `columns={2}`) \u2014 it reuses ResponsiveGrid (1 column on small screens, more on md/lg). Span a wide field across columns with `<FormField colSpan={2}>`.","ROW RHYTHM IS THE SAME ON EVERY PATH: a grid of FormFields hands its row spacing to the grid's own `row-gap` (--form-grid-row-gap, defaulting to --form-field-row-gap) instead of the per-field margin a stacked Form uses. So `columns={1}` \u2014 and any `columns={n}` form once a narrow container collapses it to one column \u2014 is pixel-identical to a Form with no `columns`, and a hand-written `<ResponsiveGrid columns={2}>` of FormFields inside a `CardContent` (what a form with several titled Card sections has to write) matches `columns={2}` exactly. Retune --form-field-row-gap to move every path together; --form-grid-row-gap / --form-grid-column-gap to retune the grid rows / gutter alone. Never add margin to a FormField to space a grid row: a per-item margin inside a grid misaligns row 1 and double-counts the track gap.","ACTION ROW in a horizontal form (gh#1271): the submit/cancel buttons go in `<FormField label={false}><Flex gap=\"sm\" wrap>\u2026buttons\u2026</Flex></FormField>` as the LAST field. A label-less field renders no label cell and its control is pinned to the control column (gh#709), so the first button's left edge is the inputs' left edge, it follows `labelWidth`, and it collapses with the fields below `collapseBelow`; `wrap` lets the row break onto a second line on a phone. Requires `labelWidth` on the Form \u2014 unset, each label column is as wide as its own text and nothing can align with it. DON'T use `staticText` for buttons (it is the read-only VALUE slot), DON'T push the row with a hand-written padding, and DON'T float it to the far end of the card, away from the column the eye just went down. This is the row for a form INSIDE a card or section; a form that is the whole page puts its primary + cancel in the PageContainer footer instead (docs/general/button/examples/form-actions.tsx).","DON'T hand-roll a `<form>` + Flex stack for spacing \u2014 `<Form>` provides the vertical rhythm and the layout context FormField reads. Wire react-hook-form by spreading `onSubmit={handleSubmit(...)}` onto `<Form>`.","SERVER ERROR BAG: pass `errors={form.errors}` (Inertia) ONCE on `<Form>`, give each field a `name`, and put `<FormErrors />` at the top of the form. A named field resolves its message from the bag automatically (no per-field `error={errors.x}`), and FormErrors catches validation errors on hidden/derived keys (`action_mode`, `page`, a source-record id) that no visible field could display \u2014 without it those submits fail SILENTLY.","SIBLING FORMS: an edit screen split into several Card+Form sections shares ONE bag by wrapping the region in `<FormErrorsProvider errors={form.errors}>` instead of passing `errors` to each Form \u2014 the section Forms (without their own `errors`) join the shared registry, and one `<FormErrors />` anywhere in the region renders the unclaimed remainder."],useCases:["A settings page form where every label sits in a fixed 120px column to the left of its control (horizontal), collapsing to stacked labels on mobile.","A two-column entity-edit form (`columns={2}`) where the address field spans both columns (`colSpan={2}`).","A compact filter form (`layout='horizontal' density='compact'`) above a DataTable.","An Inertia edit screen passing `errors={form.errors}` so every `FormField name='\u2026'` self-binds its server validation message and `<FormErrors />` surfaces the hidden-field remainder."],related:["FormField \u2014 the per-field wrapper (label + control + helper/error) that reads Form's layout context; use one per control inside a Form.","FormErrors \u2014 renders the error-bag entries no mounted FormField claims (validation errors on hidden/derived fields); place inside a `Form errors={\u2026}`.","ResponsiveGrid \u2014 Form `columns` reuses it; use ResponsiveGrid directly for non-form card grids."],example:`import { Form, FormField, Input } from "@godxjp/ui/data-entry";
|
|
970
970
|
|
|
971
971
|
<Form layout="horizontal" labelWidth={120} columns={2} onSubmit={onSubmit}>
|
|
972
972
|
<FormField id="first" label="\u59D3"><Input id="first" /></FormField>
|
|
973
973
|
<FormField id="last" label="\u540D"><Input id="last" /></FormField>
|
|
974
974
|
<FormField id="address" label="\u4F4F\u6240" colSpan={2}><Input id="address" /></FormField>
|
|
975
|
-
</Form>`,storyPath:"data-entry/form.tsx",rules:[23,24]},{name:"FormField",group:"data-entry",tagline:"Wraps a control with label, helper, and error; injects the accessible name (aria-labelledby), description (aria-describedby) and validation (aria-errormessage/aria-invalid/aria-required) contract onto the child, which forwards it to its real semantic focus target. Reads the parent Form's layout (vertical/horizontal) \u2014 overridable per field.",props:[{name:"field",type:"string",description:"T\xEAn tr\u01B0\u1EDDng c\u1EE7a form \u2014 d\xF9ng khi `id` kh\xF4ng \u0111\u1EE7 \u0111\u1EC3 n\u1ED1i control v\u1EDBi error/aria."},{name:"labelAddon",type:"ReactNode",description:"N\u1ED9i dung ph\u1EE5 c\u1EA1nh nh\xE3n: g\u1EE3i \xFD, badge b\u1EAFt bu\u1ED9c, n\xFAt tr\u1EE3 gi\xFAp, action ch\u1EEF ng\u1EAFn. N\u1EB1m TRONG h\xE0ng nh\xE3n n\xEAn kh\xF4ng ph\xE1 nh\u1ECBp tr\u01B0\u1EDDng. \u1EDE layout horizontal/inline h\xE0ng nh\xE3n xu\u1ED1ng d\xF2ng: addon kh\xF4ng v\u1EEBa c\u1EA1nh nh\xE3n th\xEC r\u01A1i xu\u1ED1ng d\xF2ng ri\xEAng d\u01B0\u1EDBi nh\xE3n, trong c\u1ED9t nh\xE3n, kh\xF4ng tr\xE0n sang c\u1ED9t control."},{name:"id",type:"string",required:!0,description:"Forwarded to Label htmlFor + builds helper/error ids."},{name:"name",type:"string",description:"Error-bag key of this field. When the surrounding Form carries `errors`, the field resolves its message from `errors[name]` automatically (an explicit `error` prop wins) and CLAIMS the key so <FormErrors /> does not repeat it. NOT injected into the child \u2014 pass `name` on the control itself for native form submission."},{name:"label",type:"ReactNode",required:!0,description:"Field label above the control. `false` renders no label row; in a horizontal form the content still sits in the control column \u2014 the action row (gh#1271)."},{name:"required",type:"boolean",defaultValue:"false",description:"Red asterisk + aria-required on the child."},{name:"helper",type:"string",description:"Muted hint shown when there is no error."},{name:"helperPlacement",type:'"before" | "after"',defaultValue:'"after"',description:"Which side of the control the helper sits on. `before` puts it between the label and the input, for a hint the reader needs before answering (a bilingual form's second line, a unit or format note). Paint only \u2014 the helper keeps its id and stays on aria-describedby. Prefer it over stuffing a second line into `labelAddon` (a label-row slot for a chip, help button or short action) or into a ReactNode `label` (which loses the string-label aria fallbacks)."},{name:"error",type:"string",description:"Destructive error message (role=alert); overrides helper."},{name:"layout",type:'"vertical" | "horizontal" | "inline"',description:"Override the parent Form's layout for this field only."},{name:"labelWidth",type:"number | string",description:"Override the Form's label width for this field."},{name:"controlWidth",type:"number | string",description:"Override the Form's control width for this field."},{name:"colSpan",type:"number",description:"Span N columns when inside a `columns` Form grid."},{name:"children",type:"ReactNode",description:"The single interactive control to render. Mutually exclusive with `staticText` \u2014 pass exactly one of the two."},{name:"staticText",type:"ReactNode",description:"Read-only VALUE instead of an interactive control \u2014 renders as plain text styled to match `Descriptions.Item`'s value typography byte-for-byte (`text-sm break-all`), skipping all of FormField's id/aria-* control wiring (there is nothing to label). Mutually exclusive with `children`. Use this to put a read-only field (name, email \u2014 anything immutable) on the SAME `<Form>` as editable fields, so it inherits the exact same layout/labelAlign/row-gap automatically instead of reaching for a separate `Descriptions` block that needs its own props reconciled to match."},{name:"validateStatus",type:'"success" | "warning" | "error" | "validating"',description:"Validation state; an actual error takes priority. Warnings do not mark values invalid."},{name:"hasFeedback",type:"boolean",description:"Show a localized accessible validation status beside its feedback icon."},{name:"feedback",type:"React.ReactNode",description:"Custom feedback content; the localized status remains available."}],usage:["DO pass the same string to both `id` on `<FormField>` and `id` on the child control \u2014 the component wires `<Label htmlFor={id}>`, and builds `{id}-helper` / `{id}-error` ids for `aria-describedby`. If the ids diverge the label click and screen-reader announcements break.",'NO LABEL: `label={false}` renders no label row and reserves no label space; in a horizontal form the content still sits in the CONTROL column (gh#709). That is the canonical action row under a horizontal form (gh#1271): `<FormField label={false}><Flex gap="sm" wrap><Button type="submit">Save</Button><Button variant="outline">Cancel</Button></Flex></FormField>`. A label-less field around a CONTROL must name the control itself (a `<Checkbox>label</Checkbox>`, an `aria-label`).',"DO pass a SINGLE React element as `children`. FormField calls `React.cloneElement` on it to inject `aria-describedby`, `aria-required`, and `aria-invalid` \u2014 if you pass a fragment or multiple nodes, cloneElement silently skips the injection and a11y attributes are lost.","COMPOSITE CHILD: when the single child is a layout wrapper \u2014 a `Flex` holding a range from/to pair or a \u5E74/\u6708 input+select combo \u2014 the label still reaches every control inside. FormField publishes its label through FieldNameContext and each control's semantic focus target (Input's `<input>`, Select's `role=combobox` trigger, and everything composed on them) adopts it as a LAST-RESORT accessible name; a control's own `aria-label`/`aria-labelledby` always wins, so set a per-control `aria-label` (e.g. \u958B\u59CB\u65E5/\u7D42\u4E86\u65E5) when the two halves should announce distinct names. The wrapper itself renders as a named `role='group'` (see Flex).","DO reach for `staticText` (not `children` with a bare string/span) for a read-only field mixed into an otherwise-editable Form \u2014 e.g. an immutable name/email row above an editable role Select on the same Members-edit card. It renders with the exact typography `Descriptions.Item`'s value uses, and \u2014 because it IS a FormField reading the same Form context \u2014 it lines up with every other field's label column, `labelAlign`, and row-to-row gap automatically. A bare string as `children` instead triggers the dev-mode 'expected a single React element child' warning and has no typography contract at all.","WIDTH: a FormField FILLS its container in vertical/horizontal layout \u2014 like the conventional Form.Item (vertical \u2192 width:100%). It works full-width inside `<Form>`, a `ResponsiveGrid` cell, a bare `<Flex direction='col'>`, or a plain block; you do NOT need to wrap it in a grid to get full width. `layout='inline'` is the only content-width exception (compact, side-by-side). To narrow just the control (keeping the label row full-width), set `controlWidth` \u2014 never constrain the FormField itself.","DO use the `error` prop (not a hand-rolled `<p>`) for validation messages \u2014 it renders with `role='alert'` and `text-destructive` styling and overrides `helper` automatically. Never render an error paragraph alongside FormField.","DO use `labelAddon` (a ReactNode rendered after the label text, in the label row) for supplementary controls such as a tooltip trigger, a 'copy' icon button or a short text action ('Assign to myself'); in a horizontal/inline layout the label row wraps, so an addon that does not fit beside the label drops under it inside the label column rather than overlapping the control \u2014 never insert such controls as siblings outside FormField, which breaks layout.",'DON\'T wrap `Switch` in FormField \u2014 use `Field` instead. Field owns the inline row: `label`, `labelAddon`, `description`, and `error` (which wires aria-invalid / aria-errormessage / aria-describedby onto the control, gh#812). It renders no hidden input of its own \u2014 `Switch name="\u2026"` is what submits natively \u2014 and it has no `required`, `helper` or layout knobs.',"DON'T use FormField for checkbox-beside-label or radio-beside-label patterns \u2014 use `Field` (single checkbox/radio with description) or `CheckboxGroup` / `RadioGroup` (multiple options), which have their own integrated labelling.","CONTRACT (which element owns each ARIA relationship): every data-entry control accepts and FORWARDS the injected props to its real semantic focus target, not a wrapper div \u2014 Input/Textarea/NumberInput \u2192 the `<input>/<textarea>`; Select/Cascader/TreeSelect \u2192 the `role=combobox` trigger (with aria-expanded + aria-haspopup + aria-controls per the WAI-ARIA APG combobox pattern); DatePicker/TimePicker \u2192 the typeable `role=combobox` input (aria-haspopup=dialog); ColorPicker \u2192 the `<input type=color>` swatch; SearchInput \u2192 the `role=searchbox` input. GROUP controls own the relationship on their container: RadioGroup \u2192 `role=radiogroup` (full validation incl. aria-invalid/-errormessage/-required); CheckboxGroup, `DatePicker range` (two inputs), and Transfer \u2192 `role=group` \u2014 per ARIA 1.2 a group is not a widget, so the error id is folded into aria-describedby instead of aria-invalid/-errormessage. Upload forwards the label/description onto its native `<input type=file>`; its visible dropzone/button keeps its own action label. This forwarding is implemented once in `src/lib/field-a11y.ts` (`pickFieldA11y` / `pickGroupFieldA11y` / `resolveFieldA11y`) \u2014 do not reinvent it per control.","FIELD IDENTITY / AUTOMATION: FormField also injects a `data-field` \u2014 the field's stable MACHINE key, resolved as `field` \u2192 `name` \u2192 `id` \u2014 onto the same semantic focus target the ARIA relationships land on, and onto every option of a RadioGroup/CheckboxGroup. Use it (not a generated id, and never the visible Japanese label) as the selector in e2e tests and screen automation. It reaches NESTED controls too: when the direct child is a layout wrapper (a Flex holding a from/to pair, a \u5E74/\u6708 combo, a value beside a \u300C\u4E0D\u660E\u300D checkbox) cloneElement stops on that wrapper, so FormField also publishes the field through context and each control inside resolves its own key from its OWN `id` \u2014 which is what keeps `search_billing_date_from` and `..._to` distinct instead of collapsing onto one shared key. A nested control with NO id of its own deliberately gets nothing: a fabricated key is worse than a missing one, because automation binds to it and breaks silently. Two companion pieces: a `Select`'s trigger also carries `data-value` = the selected CODE (the trigger shows the option LABEL, and Radix keeps the value in an aria-hidden 1x1px native `<select>`), and each RadioGroup/CheckboxGroup option gets a deterministic `{groupId}-{optionValue}` id instead of a per-mount `React.useId()` token. Nothing here is opt-in and no DOM structure changed. A `data-field` written on the control itself always wins.","NATIVE `name` IS OPT-IN: FormField emits the same key as a real `name` attribute ONLY when the app set `<AppProvider emitFieldNames>`. It is off by default because `name` decides what a native `<form>` submit sends \u2014 turning it on globally in a shared package would make every consumer start posting new keys to its backend on an upgrade. Turn it on in apps that need native form posts or a screen-automation contract; a `name` written on the control itself always wins.","NATIVE FORM PARTICIPATION: pass `name` to a control for HTML form submission \u2014 Input/Textarea/NumberInput/Select submit natively; a searchable Select submits via a hidden input; DatePicker/TimePicker emit ISO strings (`yyyy-MM-dd` / 24h `HH:mm`); the range pickers emit `${name}_from` / `${name}_to`. `required`/`readOnly`/`disabled` map to the underlying control. Cascader/TreeSelect/Transfer submit named values via hidden inputs; Upload appends staged local files to FormData when named. Disabled controls are excluded.","ERROR TIMING & RECOVERY: pass `error` only after a field is dirty or the form is submitted (don't show errors on pristine mount). The error node renders with `role='alert'` so it is announced live the moment it appears; clearing `error` (e.g. after the user corrects the value or a server round-trip succeeds) removes aria-invalid and restores the helper. On submit, focus the first invalid control and/or render an error summary that links to each field by `id`."],useCases:["Labelling a text `Input` or `Textarea` in an invoice-entry form, showing a red asterisk for required fields and surfacing server validation errors returned from a Laravel FormRequest.","Wrapping a `Select` or `DatePicker` inside a multi-field filter panel where each control needs a visible label, helper hint (e.g. 'YYYY/MM/DD'), and inline error state.","Adding a `labelAddon` tooltip button next to a 'Tax rate' label in an accounting form to explain when different rates apply, without breaking the label\u2013control association.","Enclosing a `DatePicker range` or `TimePicker` in an admin settings page where the field needs a label, a muted hint ('Inclusive of start and end date'), and conditional error display.","Wrapping a `Select` (with `showSearch`) control for vendor/account lookup in a journal-entry form where the `id` must be kept consistent for programmatic focus management.","Providing structured error feedback for a `Cascader` or `TreeSelect` in a multi-level category assignment screen, replacing ad-hoc error rendering with the standardised `role='alert'` pattern."],related:["Label \u2014 the bare label component. Use directly only when you are building a fully custom layout that cannot accept FormField's stack wrapper, and you will manage aria-describedby/aria-invalid yourself. FormField is always preferred for standard form controls.",'Field \u2014 the inline row for a boolean control: label and control side by side, with `labelAddon`, `description` and `error` (gh#812 added the last two slots; `error` wires aria-invalid / aria-errormessage / aria-describedby onto the control, so a validated boolean no longer has to be hand-wired). It still renders NO hidden `<input name>` \u2014 `Switch name="\u2026"` submits itself \u2014 and it has no `required`, `helper`, error-bag `name` binding or layout/width knobs. Still: never wrap a bare `Switch` in FormField.',"Field \u2014 pairs a single checkbox or radio with a label and optional description in a horizontal layout (control beside text). Use Field instead of FormField when the control and its label sit side-by-side rather than stacked.","CheckboxGroup / RadioGroup \u2014 for groups of options where FormField is not needed per-item; the group component handles its own legend/label and option layout."],example:`import { FormField, Input } from "@godxjp/ui/data-entry";
|
|
975
|
+
</Form>`,storyPath:"data-entry/form.tsx",rules:[23,24]},{name:"FormField",group:"data-entry",tagline:"Wraps a control with label, helper, and error; injects the accessible name (aria-labelledby), description (aria-describedby) and validation (aria-errormessage/aria-invalid/aria-required) contract onto the child, which forwards it to its real semantic focus target. Reads the parent Form's layout (vertical/horizontal) \u2014 overridable per field.",props:[{name:"field",type:"string",description:"T\xEAn tr\u01B0\u1EDDng c\u1EE7a form \u2014 d\xF9ng khi `id` kh\xF4ng \u0111\u1EE7 \u0111\u1EC3 n\u1ED1i control v\u1EDBi error/aria."},{name:"labelAddon",type:"ReactNode",description:"N\u1ED9i dung ph\u1EE5 c\u1EA1nh nh\xE3n: g\u1EE3i \xFD, badge b\u1EAFt bu\u1ED9c, n\xFAt tr\u1EE3 gi\xFAp, action ch\u1EEF ng\u1EAFn. N\u1EB1m TRONG h\xE0ng nh\xE3n n\xEAn kh\xF4ng ph\xE1 nh\u1ECBp tr\u01B0\u1EDDng. \u1EDE layout horizontal/inline h\xE0ng nh\xE3n xu\u1ED1ng d\xF2ng: addon kh\xF4ng v\u1EEBa c\u1EA1nh nh\xE3n th\xEC r\u01A1i xu\u1ED1ng d\xF2ng ri\xEAng d\u01B0\u1EDBi nh\xE3n, trong c\u1ED9t nh\xE3n, kh\xF4ng tr\xE0n sang c\u1ED9t control."},{name:"id",type:"string",required:!0,description:"Forwarded to Label htmlFor + builds helper/error ids."},{name:"name",type:"string",description:"Error-bag key of this field. When the surrounding Form carries `errors`, the field resolves its message from `errors[name]` automatically (an explicit `error` prop wins) and CLAIMS the key so <FormErrors /> does not repeat it. NOT injected into the child \u2014 pass `name` on the control itself for native form submission."},{name:"label",type:"ReactNode",required:!0,description:"Field label above the control. `false` renders no label row; in a horizontal form the content still sits in the control column \u2014 the action row (gh#1271)."},{name:"required",type:"boolean",defaultValue:"false",description:"Red asterisk + aria-required on the child."},{name:"helper",type:"string",description:"Muted hint shown when there is no error."},{name:"helperPlacement",type:'"before" | "after"',defaultValue:'"after"',description:"Which side of the control the helper sits on. `before` puts it between the label and the input, for a hint the reader needs before answering (a bilingual form's second line, a unit or format note). Paint only \u2014 the helper keeps its id and stays on aria-describedby. Prefer it over stuffing a second line into `labelAddon` (a label-row slot for a chip, help button or short action) or into a ReactNode `label` (which loses the string-label aria fallbacks)."},{name:"error",type:"string",description:"Destructive error message (role=alert); overrides helper."},{name:"layout",type:'"vertical" | "horizontal" | "inline"',description:"Override the parent Form's layout for this field only."},{name:"labelWidth",type:"number | string",description:"Override the Form's label width for this field."},{name:"controlWidth",type:"number | string",description:"Override the Form's control width for this field. Caps the control only \u2014 the helper and error still span the field column."},{name:"colSpan",type:"number",description:"Span N columns when inside a `columns` Form grid."},{name:"children",type:"ReactNode",description:"The single interactive control to render. Mutually exclusive with `staticText` \u2014 pass exactly one of the two."},{name:"staticText",type:"ReactNode",description:"Read-only VALUE instead of an interactive control \u2014 renders as plain text styled to match `Descriptions.Item`'s value typography byte-for-byte (`text-sm break-all`), skipping all of FormField's id/aria-* control wiring (there is nothing to label). Mutually exclusive with `children`. Use this to put a read-only field (name, email \u2014 anything immutable) on the SAME `<Form>` as editable fields, so it inherits the exact same layout/labelAlign/row-gap automatically instead of reaching for a separate `Descriptions` block that needs its own props reconciled to match."},{name:"validateStatus",type:'"success" | "warning" | "error" | "validating"',description:"Validation state; an actual error takes priority. Warnings do not mark values invalid."},{name:"hasFeedback",type:"boolean",description:"Show a localized accessible validation status beside its feedback icon."},{name:"feedback",type:"React.ReactNode",description:"Custom feedback content; the localized status remains available."}],usage:["DO pass the same string to both `id` on `<FormField>` and `id` on the child control \u2014 the component wires `<Label htmlFor={id}>`, and builds `{id}-helper` / `{id}-error` ids for `aria-describedby`. If the ids diverge the label click and screen-reader announcements break.",'NO LABEL: `label={false}` renders no label row and reserves no label space; in a horizontal form the content still sits in the CONTROL column (gh#709). That is the canonical action row under a horizontal form (gh#1271): `<FormField label={false}><Flex gap="sm" wrap><Button type="submit">Save</Button><Button variant="outline">Cancel</Button></Flex></FormField>`. A label-less field around a CONTROL must name the control itself (a `<Checkbox>label</Checkbox>`, an `aria-label`).',"DO pass a SINGLE React element as `children`. FormField calls `React.cloneElement` on it to inject `aria-describedby`, `aria-required`, and `aria-invalid` \u2014 if you pass a fragment or multiple nodes, cloneElement silently skips the injection and a11y attributes are lost.","COMPOSITE CHILD: when the single child is a layout wrapper \u2014 a `Flex` holding a range from/to pair or a \u5E74/\u6708 input+select combo \u2014 the label still reaches every control inside. FormField publishes its label through FieldNameContext and each control's semantic focus target (Input's `<input>`, Select's `role=combobox` trigger, and everything composed on them) adopts it as a LAST-RESORT accessible name; a control's own `aria-label`/`aria-labelledby` always wins, so set a per-control `aria-label` (e.g. \u958B\u59CB\u65E5/\u7D42\u4E86\u65E5) when the two halves should announce distinct names. The wrapper itself renders as a named `role='group'` (see Flex).","DO reach for `staticText` (not `children` with a bare string/span) for a read-only field mixed into an otherwise-editable Form \u2014 e.g. an immutable name/email row above an editable role Select on the same Members-edit card. It renders with the exact typography `Descriptions.Item`'s value uses, and \u2014 because it IS a FormField reading the same Form context \u2014 it lines up with every other field's label column, `labelAlign`, and row-to-row gap automatically. A bare string as `children` instead triggers the dev-mode 'expected a single React element child' warning and has no typography contract at all.","WIDTH: a FormField FILLS its container in vertical/horizontal layout \u2014 like the conventional Form.Item (vertical \u2192 width:100%). It works full-width inside `<Form>`, a `ResponsiveGrid` cell, a bare `<Flex direction='col'>`, or a plain block; you do NOT need to wrap it in a grid to get full width. `layout='inline'` is the only content-width exception (compact, side-by-side). To narrow just the control (keeping the label row full-width), set `controlWidth` \u2014 never constrain the FormField itself.","DO use the `error` prop (not a hand-rolled `<p>`) for validation messages \u2014 it renders with `role='alert'` and `text-destructive` styling and overrides `helper` automatically. Never render an error paragraph alongside FormField.","DO use `labelAddon` (a ReactNode rendered after the label text, in the label row) for supplementary controls such as a tooltip trigger, a 'copy' icon button or a short text action ('Assign to myself'); in a horizontal/inline layout the label row wraps, so an addon that does not fit beside the label drops under it inside the label column rather than overlapping the control \u2014 never insert such controls as siblings outside FormField, which breaks layout.",'DON\'T wrap `Switch` in FormField \u2014 use `Field` instead. Field owns the inline row: `label`, `labelAddon`, `description`, and `error` (which wires aria-invalid / aria-errormessage / aria-describedby onto the control, gh#812). It renders no hidden input of its own \u2014 `Switch name="\u2026"` is what submits natively \u2014 and it has no `required`, `helper` or layout knobs.',"DON'T use FormField for checkbox-beside-label or radio-beside-label patterns \u2014 use `Field` (single checkbox/radio with description) or `CheckboxGroup` / `RadioGroup` (multiple options), which have their own integrated labelling.","CONTRACT (which element owns each ARIA relationship): every data-entry control accepts and FORWARDS the injected props to its real semantic focus target, not a wrapper div \u2014 Input/Textarea/NumberInput \u2192 the `<input>/<textarea>`; Select/Cascader/TreeSelect \u2192 the `role=combobox` trigger (with aria-expanded + aria-haspopup + aria-controls per the WAI-ARIA APG combobox pattern); DatePicker/TimePicker \u2192 the typeable `role=combobox` input (aria-haspopup=dialog); ColorPicker \u2192 the `<input type=color>` swatch; SearchInput \u2192 the `role=searchbox` input. GROUP controls own the relationship on their container: RadioGroup \u2192 `role=radiogroup` (full validation incl. aria-invalid/-errormessage/-required); CheckboxGroup, `DatePicker range` (two inputs), and Transfer \u2192 `role=group` \u2014 per ARIA 1.2 a group is not a widget, so the error id is folded into aria-describedby instead of aria-invalid/-errormessage. Upload forwards the label/description onto its native `<input type=file>`; its visible dropzone/button keeps its own action label. This forwarding is implemented once in `src/lib/field-a11y.ts` (`pickFieldA11y` / `pickGroupFieldA11y` / `resolveFieldA11y`) \u2014 do not reinvent it per control.","FIELD IDENTITY / AUTOMATION: FormField also injects a `data-field` \u2014 the field's stable MACHINE key, resolved as `field` \u2192 `name` \u2192 `id` \u2014 onto the same semantic focus target the ARIA relationships land on, and onto every option of a RadioGroup/CheckboxGroup. Use it (not a generated id, and never the visible Japanese label) as the selector in e2e tests and screen automation. It reaches NESTED controls too: when the direct child is a layout wrapper (a Flex holding a from/to pair, a \u5E74/\u6708 combo, a value beside a \u300C\u4E0D\u660E\u300D checkbox) cloneElement stops on that wrapper, so FormField also publishes the field through context and each control inside resolves its own key from its OWN `id` \u2014 which is what keeps `search_billing_date_from` and `..._to` distinct instead of collapsing onto one shared key. A nested control with NO id of its own deliberately gets nothing: a fabricated key is worse than a missing one, because automation binds to it and breaks silently. Two companion pieces: a `Select`'s trigger also carries `data-value` = the selected CODE (the trigger shows the option LABEL, and Radix keeps the value in an aria-hidden 1x1px native `<select>`), and each RadioGroup/CheckboxGroup option gets a deterministic `{groupId}-{optionValue}` id instead of a per-mount `React.useId()` token. Nothing here is opt-in and no DOM structure changed. A `data-field` written on the control itself always wins.","NATIVE `name` IS OPT-IN: FormField emits the same key as a real `name` attribute ONLY when the app set `<AppProvider emitFieldNames>`. It is off by default because `name` decides what a native `<form>` submit sends \u2014 turning it on globally in a shared package would make every consumer start posting new keys to its backend on an upgrade. Turn it on in apps that need native form posts or a screen-automation contract; a `name` written on the control itself always wins.","NATIVE FORM PARTICIPATION: pass `name` to a control for HTML form submission \u2014 Input/Textarea/NumberInput/Select submit natively; a searchable Select submits via a hidden input; DatePicker/TimePicker emit ISO strings (`yyyy-MM-dd` / 24h `HH:mm`); the range pickers emit `${name}_from` / `${name}_to`. `required`/`readOnly`/`disabled` map to the underlying control. Cascader/TreeSelect/Transfer submit named values via hidden inputs; Upload appends staged local files to FormData when named. Disabled controls are excluded.","ERROR TIMING & RECOVERY: pass `error` only after a field is dirty or the form is submitted (don't show errors on pristine mount). The error node renders with `role='alert'` so it is announced live the moment it appears; clearing `error` (e.g. after the user corrects the value or a server round-trip succeeds) removes aria-invalid and restores the helper. On submit, focus the first invalid control and/or render an error summary that links to each field by `id`."],useCases:["Labelling a text `Input` or `Textarea` in an invoice-entry form, showing a red asterisk for required fields and surfacing server validation errors returned from a Laravel FormRequest.","Wrapping a `Select` or `DatePicker` inside a multi-field filter panel where each control needs a visible label, helper hint (e.g. 'YYYY/MM/DD'), and inline error state.","Adding a `labelAddon` tooltip button next to a 'Tax rate' label in an accounting form to explain when different rates apply, without breaking the label\u2013control association.","Enclosing a `DatePicker range` or `TimePicker` in an admin settings page where the field needs a label, a muted hint ('Inclusive of start and end date'), and conditional error display.","Wrapping a `Select` (with `showSearch`) control for vendor/account lookup in a journal-entry form where the `id` must be kept consistent for programmatic focus management.","Providing structured error feedback for a `Cascader` or `TreeSelect` in a multi-level category assignment screen, replacing ad-hoc error rendering with the standardised `role='alert'` pattern."],related:["Label \u2014 the bare label component. Use directly only when you are building a fully custom layout that cannot accept FormField's stack wrapper, and you will manage aria-describedby/aria-invalid yourself. FormField is always preferred for standard form controls.",'Field \u2014 the inline row for a boolean control: label and control side by side, with `labelAddon`, `description` and `error` (gh#812 added the last two slots; `error` wires aria-invalid / aria-errormessage / aria-describedby onto the control, so a validated boolean no longer has to be hand-wired). It still renders NO hidden `<input name>` \u2014 `Switch name="\u2026"` submits itself \u2014 and it has no `required`, `helper`, error-bag `name` binding or layout/width knobs. Still: never wrap a bare `Switch` in FormField.',"Field \u2014 pairs a single checkbox or radio with a label and optional description in a horizontal layout (control beside text). Use Field instead of FormField when the control and its label sit side-by-side rather than stacked.","CheckboxGroup / RadioGroup \u2014 for groups of options where FormField is not needed per-item; the group component handles its own legend/label and option layout."],example:`import { FormField, Input } from "@godxjp/ui/data-entry";
|
|
976
976
|
|
|
977
977
|
<FormField id="coupon-name" label="\u30AF\u30FC\u30DD\u30F3\u540D" required error={errors.name} helper="\u6700\u592750\u6587\u5B57">
|
|
978
978
|
<Input id="coupon-name" placeholder="\u6625\u306E\u82B1\u7C89\u75C7\u5BFE\u7B5615%OFF" value={name} onValueChange={(v) => setName(v)} />
|
|
@@ -1125,7 +1125,7 @@ export function PrioritySelect({ value, onValueChange }) {
|
|
|
1125
1125
|
<Textarea id="notes" rows={4} placeholder="\u81EA\u7531\u8A18\u8FF0" value={notes} onChange={(e) => setNotes(e.target.value)} />
|
|
1126
1126
|
|
|
1127
1127
|
// Chat / comment composer \u2014 grows with its content, scrolls past 8 rows, collapses on send.
|
|
1128
|
-
<Textarea autoGrow minRows={1} maxRows={8} value={draft} onChange={(e) => setDraft(e.target.value)} placeholder="\u30E1\u30C3\u30BB\u30FC\u30B8\u3092\u5165\u529B..." />`,storyPath:"data-entry/textarea.tsx",rules:[]},{name:"TranslatableInput",group:"data-entry",tagline:"ONE text field holding a value per locale (`{ ja, en, vi }`), switched by locale tabs above it (gh#1336). For user content stored per locale \u2014 a product name, a category label, a menu description (Astrotomic / Spatie `*_translations`). Locales, first tab and fallback come from AppProvider; an empty locale shows the fallback locale's text as its placeholder; a locale with an error is marked on its tab. Every other `Input` prop applies to the one control.",props:[{name:"value",type:"TranslatableValue",description:'Controlled per-locale values \u2014 `Partial<Record<string, string>>` keyed by BCP-47 tag. A missing key or `""` is an empty locale.'},{name:"defaultValue",type:"TranslatableValue",description:"Uncontrolled initial per-locale values."},{name:"onValueChange",type:"(value: TranslatableValue) => void",description:"The WHOLE map after an edit: only the active locale's key changes, every other locale is carried over untouched. The \u2715 of `allowClear` empties the active locale only."},{name:"locales",type:"readonly string[]",description:"The BCP-47 tags the field edits, in tab order. Default: every locale registered with the app (`getRegisteredLocales()` \u2014 built-in `vi`/`en`/`ja` plus `registerLocale`). Tabs are named by `Intl.DisplayNames` in the reader's UI language; the tags stay the keys."},{name:"defaultLocale",type:"string",description:"The locale tab selected first. Default: the AppProvider's active locale when it is in `locales`, else the fallback locale."},{name:"fallbackLocale",type:"string",description:"The locale whose text an EMPTY locale shows as its placeholder. Default: the AppProvider's `fallbackLocale`, or the first of `locales` when that is not one of them."},{name:"errors",type:"Partial<Record<string, ReactNode>>",description:"Per-locale validation messages. Each locale that has one gets an error mark on its tab \u2014 a glyph plus screen-reader text (\xABVietnamese, has an error\xBB), never colour alone. The ACTIVE locale's message sets `aria-invalid` and is shown in the enclosing FormField's own error line (wired by `aria-errormessage`); without a FormField it is shown under the field."},{name:"size",type:'"xs" | "sm" | "md" | "lg"',defaultValue:'"md"',description:"The field's control tier, as on Input. The tab strip uses the Tabs `sm` tier (`md` for an `lg` field)."},{name:"placeholder",type:"string",description:"Shown while the active locale is empty and there is no fallback text to show (the fallback locale itself, or an empty fallback)."},{name:"disabled",type:"boolean",description:"Disables the control; the locale tabs stay operable so every locale can still be read."},{name:"id",type:"string",description:"Lands on the control, so a FormField label click focuses it."},{name:"className",type:"string",description:"Root (strip + field) class override."}],usage:["DO wrap it in FormField: the label names the control together with the active locale (\xAB\u5546\u54C1\u540D English\xBB) and names the tab strip (\xAB\u5546\u54C1\u540D Language\xBB), `required`/`helper` reach the control, and the active locale's error renders in FormField's error line.","DO pass the per-locale error map from your validator as `errors` (e.g. Laravel `name.ja` \u2192 `errors.ja`). Don't pass the same message to FormField `error` as well: FormField's own `error` wins over the reported one, and it would mark the field invalid on every locale.","DO set `fallbackLocale` to the content's SOURCE language (often `ja`) when it differs from the AppProvider's UI fallback \u2014 the placeholder of an untranslated locale then shows the source text to translate from.","DON'T build a local wrapper of Tabs + Input for this: the tab\u2194panel ARIA, the lang attribute, the error marks and the FormField wiring are what this component exists for.","DON'T use it for a choice of language that IS the form's value (a user's preferred language): that is `AppSettingPicker kind=\"locale\"` or a Select.","The locale switcher is the WAI-ARIA APG tabs pattern (tablist \u2192 one tabpanel holding the field), not a Segmented radio group: the locale is not form state \u2014 every locale is submitted together \u2014 the tab only chooses which locale the one field shows."],useCases:["Product / menu item name in ja, en and vi on an admin edit form","Category or section label stored per locale (Astrotomic `*_translations`)","Translated description or recipe text (TranslatableTextarea)"],related:["TranslatableTextarea \u2014 the same contract over a Textarea.","Input \u2014 the control it composes; every Input prop applies.","FormField \u2014 the label / helper / error wiring it reports into.","Tabs \u2014 the switcher it composes (strip-only, `controls` \u2192 the field's panel).",'AppSettingPicker kind="locale" \u2014 chooses the UI language, not per-locale content.'],example:`import { FormField, TranslatableInput, TranslatableTextarea, type TranslatableValue } from "@godxjp/ui/data-entry";
|
|
1128
|
+
<Textarea autoGrow minRows={1} maxRows={8} value={draft} onChange={(e) => setDraft(e.target.value)} placeholder="\u30E1\u30C3\u30BB\u30FC\u30B8\u3092\u5165\u529B..." />`,storyPath:"data-entry/textarea.tsx",rules:[]},{name:"TranslatableInput",group:"data-entry",tagline:"ONE text field holding a value per locale (`{ ja, en, vi }`), switched by locale tabs above it (gh#1336). For user content stored per locale \u2014 a product name, a category label, a menu description (Astrotomic / Spatie `*_translations`). Locales, first tab and fallback come from AppProvider; an empty locale shows the fallback locale's text as its placeholder; a locale with an error is marked on its tab. Every other `Input` prop applies to the one control.",props:[{name:"value",type:"TranslatableValue",description:'Controlled per-locale values \u2014 `Partial<Record<string, string>>` keyed by BCP-47 tag. A missing key or `""` is an empty locale.'},{name:"defaultValue",type:"TranslatableValue",description:"Uncontrolled initial per-locale values."},{name:"onValueChange",type:"(value: TranslatableValue) => void",description:"The WHOLE map after an edit: only the active locale's key changes, every other locale is carried over untouched. The \u2715 of `allowClear` empties the active locale only."},{name:"locales",type:"readonly string[]",description:"The BCP-47 tags the field edits, in tab order. Default: AppProvider `contentLocales` when set (gh#1347 \u2014 state the content languages once at the root instead of on every field), else every locale registered with the app (`getRegisteredLocales()` \u2014 built-in `vi`/`en`/`ja` plus `registerLocale`). Tabs are named by `Intl.DisplayNames` in the reader's UI language; the tags stay the keys."},{name:"defaultLocale",type:"string",description:"The locale tab selected first. Default: the first of AppProvider `contentLocales` (the source language) when set and in `locales`, else the AppProvider's active locale when it is in `locales`, else the fallback locale."},{name:"fallbackLocale",type:"string",description:"The locale whose text an EMPTY locale shows as its placeholder. Default: the AppProvider's `fallbackLocale`, or the first of `locales` when that is not one of them."},{name:"errors",type:"Partial<Record<string, ReactNode>>",description:"Per-locale validation messages. Each locale that has one gets an error mark on its tab \u2014 a glyph plus screen-reader text (\xABVietnamese, has an error\xBB), never colour alone. The ACTIVE locale's message sets `aria-invalid` and is shown in the enclosing FormField's own error line (wired by `aria-errormessage`); without a FormField it is shown under the field."},{name:"size",type:'"xs" | "sm" | "md" | "lg"',defaultValue:'"md"',description:"The field's control tier, as on Input. The tab strip uses the Tabs `sm` tier (`md` for an `lg` field)."},{name:"placeholder",type:"string",description:"Shown while the active locale is empty and there is no fallback text to show (the fallback locale itself, or an empty fallback)."},{name:"disabled",type:"boolean",description:"Disables the control; the locale tabs stay operable so every locale can still be read."},{name:"id",type:"string",description:"Lands on the control, so a FormField label click focuses it."},{name:"className",type:"string",description:"Root (strip + field) class override."}],usage:['DO set the app\'s content languages ONCE on the root provider \u2014 `<AppProvider contentLocales={["ja", "en", "vi"]}>` \u2014 when every translatable field edits the same list; each field then needs no `locales` and opens on the source language (the first). Pass `locales` on a field only where it differs.',"DO wrap it in FormField: the label names the control together with the active locale (\xAB\u5546\u54C1\u540D English\xBB) and names the tab strip (\xAB\u5546\u54C1\u540D Language\xBB), `required`/`helper` reach the control, and the active locale's error renders in FormField's error line.","DO pass the per-locale error map from your validator as `errors` (e.g. Laravel `name.ja` \u2192 `errors.ja`). Don't pass the same message to FormField `error` as well: FormField's own `error` wins over the reported one, and it would mark the field invalid on every locale.","DO set `fallbackLocale` to the content's SOURCE language (often `ja`) when it differs from the AppProvider's UI fallback \u2014 the placeholder of an untranslated locale then shows the source text to translate from.","DON'T build a local wrapper of Tabs + Input for this: the tab\u2194panel ARIA, the lang attribute, the error marks and the FormField wiring are what this component exists for.","DON'T use it for a choice of language that IS the form's value (a user's preferred language): that is `AppSettingPicker kind=\"locale\"` or a Select.","The locale switcher is the WAI-ARIA APG tabs pattern (tablist \u2192 one tabpanel holding the field), not a Segmented radio group: the locale is not form state \u2014 every locale is submitted together \u2014 the tab only chooses which locale the one field shows."],useCases:["Product / menu item name in ja, en and vi on an admin edit form","Category or section label stored per locale (Astrotomic `*_translations`)","Translated description or recipe text (TranslatableTextarea)"],related:["TranslatableTextarea \u2014 the same contract over a Textarea.","Input \u2014 the control it composes; every Input prop applies.","FormField \u2014 the label / helper / error wiring it reports into.","Tabs \u2014 the switcher it composes (strip-only, `controls` \u2192 the field's panel).",'AppSettingPicker kind="locale" \u2014 chooses the UI language, not per-locale content.'],example:`import { FormField, TranslatableInput, TranslatableTextarea, type TranslatableValue } from "@godxjp/ui/data-entry";
|
|
1129
1129
|
|
|
1130
1130
|
const [productName, setProductName] = React.useState<TranslatableValue>({ ja: "\u91A4\u6CB9\u30E9\u30FC\u30E1\u30F3" });
|
|
1131
1131
|
const [description, setDescription] = React.useState<TranslatableValue>({});
|
|
@@ -1142,7 +1142,7 @@ const [description, setDescription] = React.useState<TranslatableValue>({});
|
|
|
1142
1142
|
|
|
1143
1143
|
<FormField label="\u8AAC\u660E">
|
|
1144
1144
|
<TranslatableTextarea locales={["ja", "en", "vi"]} value={description} onValueChange={setDescription} rows={3} />
|
|
1145
|
-
</FormField>`,storyPath:"data-entry/translatable-input.tsx",rules:[]},{name:"TranslatableTextarea",group:"data-entry",tagline:"TranslatableInput over a Textarea (gh#1336): one multi-line field holding a value per locale, switched by locale tabs. Same locale, fallback-placeholder, error-mark and FormField contract; every Textarea prop (rows, autoGrow, count\u2026) applies to the one control.",props:[{name:"value",type:"TranslatableValue",description:"Controlled per-locale values, keyed by BCP-47 tag."},{name:"defaultValue",type:"TranslatableValue",description:"Uncontrolled initial per-locale values."},{name:"onValueChange",type:"(value: TranslatableValue) => void",description:"The whole map after an edit; only the active locale's key changes."},{name:"locales",type:"readonly string[]",description:"The BCP-47 tags the field edits, in tab order. Default: the app's registered locales."},{name:"defaultLocale",type:"string",description:"The locale tab selected first. Default: the AppProvider's active locale."},{name:"fallbackLocale",type:"string",description:"The locale whose text an empty locale shows as its placeholder. Default: the AppProvider's `fallbackLocale`."},{name:"errors",type:"Partial<Record<string, ReactNode>>",description:"Per-locale messages: tab marks for each, `aria-invalid` + FormField's error line for the active one."},{name:"size",type:'"sm" | "md" | "lg"',defaultValue:'"md"',description:"The field's control tier, as on Textarea."},{name:"placeholder",type:"string",description:"Shown while the active locale is empty and there is no fallback text."},{name:"disabled",type:"boolean",description:"Disables the control; the locale tabs stay operable."},{name:"id",type:"string",description:"Lands on the control."},{name:"className",type:"string",description:"Root class override."}],usage:["DO use it for per-locale multi-line content (a description, a recipe); for a single line use TranslatableInput.","DO wrap it in FormField, exactly as TranslatableInput."],useCases:["Menu item description in ja, en and vi","Recipe or notice body stored per locale"],related:["TranslatableInput \u2014 the single-line form and the full contract.","Textarea \u2014 the control it composes."],example:`import { FormField, TranslatableTextarea } from "@godxjp/ui/data-entry";
|
|
1145
|
+
</FormField>`,storyPath:"data-entry/translatable-input.tsx",rules:[]},{name:"TranslatableTextarea",group:"data-entry",tagline:"TranslatableInput over a Textarea (gh#1336): one multi-line field holding a value per locale, switched by locale tabs. Same locale, fallback-placeholder, error-mark and FormField contract; every Textarea prop (rows, autoGrow, count\u2026) applies to the one control.",props:[{name:"value",type:"TranslatableValue",description:"Controlled per-locale values, keyed by BCP-47 tag."},{name:"defaultValue",type:"TranslatableValue",description:"Uncontrolled initial per-locale values."},{name:"onValueChange",type:"(value: TranslatableValue) => void",description:"The whole map after an edit; only the active locale's key changes."},{name:"locales",type:"readonly string[]",description:"The BCP-47 tags the field edits, in tab order. Default: AppProvider `contentLocales` when set, else the app's registered locales."},{name:"defaultLocale",type:"string",description:"The locale tab selected first. Default: the first of AppProvider `contentLocales` when set and in `locales`, else the AppProvider's active locale."},{name:"fallbackLocale",type:"string",description:"The locale whose text an empty locale shows as its placeholder. Default: the AppProvider's `fallbackLocale`."},{name:"errors",type:"Partial<Record<string, ReactNode>>",description:"Per-locale messages: tab marks for each, `aria-invalid` + FormField's error line for the active one."},{name:"size",type:'"sm" | "md" | "lg"',defaultValue:'"md"',description:"The field's control tier, as on Textarea."},{name:"placeholder",type:"string",description:"Shown while the active locale is empty and there is no fallback text."},{name:"disabled",type:"boolean",description:"Disables the control; the locale tabs stay operable."},{name:"id",type:"string",description:"Lands on the control."},{name:"className",type:"string",description:"Root class override."}],usage:["DO use it for per-locale multi-line content (a description, a recipe); for a single line use TranslatableInput.","DO wrap it in FormField, exactly as TranslatableInput."],useCases:["Menu item description in ja, en and vi","Recipe or notice body stored per locale"],related:["TranslatableInput \u2014 the single-line form and the full contract.","Textarea \u2014 the control it composes."],example:`import { FormField, TranslatableTextarea } from "@godxjp/ui/data-entry";
|
|
1146
1146
|
|
|
1147
1147
|
<FormField label="\u8AAC\u660E">
|
|
1148
1148
|
<TranslatableTextarea locales={["ja", "en", "vi"]} value={description} onValueChange={setDescription} autoGrow minRows={3} />
|
|
@@ -1316,7 +1316,7 @@ import { FormField, Input, Select, DatePicker } from "@godxjp/ui/data-entry";
|
|
|
1316
1316
|
<FormField label="\u56FD\u7C4D" id="q-nat"><Select id="q-nat" options={nationalities} value={draft.nationality} onValueChange={(v) => set({ nationality: v })} /></FormField>
|
|
1317
1317
|
<FormField label="\u5165\u56FD\u65E5" id="q-entry"><DatePicker id="q-entry" range /></FormField>
|
|
1318
1318
|
<FormField label="\u8077\u7A2E" id="q-occ"><Select id="q-occ" options={occupations} value={draft.occupation} onValueChange={(v) => set({ occupation: v })} /></FormField>
|
|
1319
|
-
</SearchForm>`,docPath:"navigation/search-form",storyPath:"navigation/search-form.tsx",rules:[23,51]},{name:"AppProvider",subParts:["AppPresetContext"],group:"providers",importPath:"@godxjp/ui/app",tagline:"Root locale/timezone/date-time context \u2014 wrap the app ONCE. All pickers + formatDate read from it. Import from @godxjp/ui/app.",props:[{name:"defaultLocale",type:"string (BCP-47)",description:"Initial locale when nothing is stored. Resolution, first match wins: this prop (when registered \u2014 `de-AT` is served by a registered `de`) \u2192 `<html lang>` (client only) \u2192 the preset's `defaultLocale` \u2192 `en`. Never `navigator.language` (the server cannot see it, so SSR would hydrate differently). An SSR host passes the request's locale here. v32: the default was `vi`."},{name:"preset",type:"AppPreset",description:'Product defaults the host opts into \u2014 `import { godxPreset } from "@godxjp/ui/themes/godx"` (pair it with `@godxjp/ui/themes/godx.css` after the base stylesheet; a shadow root that embeds `@godxjp/ui/styles/core` takes the font-free `@godxjp/ui/themes/godx-tokens.css` instead, since `@font-face` must live in the document). Its `defaultLocale` fills only what the props leave unset \u2014 it pins NO `timeZone` since 32.4.1 (gh#1282: it pinned `Asia/Ho_Chi_Minh` and every app showed Vietnam time to users in Japan; pass `defaultTimezone` for a fixed zone), its `brandMark` is what an `AuthIdentity` with no `brand` draws, and its `name` is written to `<html data-preset>`. Omit for the neutral defaults (ink primary, `en`, no product mark).'},{name:"defaultTimezone",type:'string | "browser" | "system"',defaultValue:'"browser"',description:"Initial IANA timezone."},{name:"defaultDateFormat",type:'"iso" | "ymd" | "dmy" | "mdy" | "locale"',defaultValue:'"locale"',description:"Initial date display format."},{name:"defaultTimeFormat",type:'"24h" | "12h" | "locale"',defaultValue:'"locale"',description:"Initial clock format."},{name:"theme",type:'"light" | "dark" | "system"',defaultValue:'"light"',description:'Theme axis \u2192 <html data-theme>. Equal alias of the legacy .dark class. "system" defers to prefers-color-scheme and is followed LIVE (matchMedia listener), so <html data-theme> flips with the OS; the persisted value stays "system" (the CHOICE), never the resolved light/dark. Persisted; change via setTheme / <AppSettingPicker kind="theme"> / <Segmented>.'},{name:"brand",type:'"brand" | "crm" | "logistics" | "partner" | "slate" | "dxs" | null',defaultValue:"null",description:'Brand-palette axis \u2192 <html data-brand> (sets --primary/--ring/--accent). OPT-IN: null keeps the --primary your own theme.css defines. "dxs" is THE CANONICAL DXS PRESET and is more than a tint: it also binds the canonical hosted-identity surface contract (36px auth controls, 22.5rem auth card measure, 16px page inset / 15px below 30rem), so a DXS surface needs ZERO page CSS for auth geometry, density, insets, logo colour, divider or footer. Stylesheet-only apps (no provider) import "@godxjp/ui/theme/dxs.canonical.css" instead \u2014 same contract, guarded against drift by a test.'},{name:"density",type:'"compact" | "default" | "comfortable"',defaultValue:'"default"',description:"Density axis \u2192 <html data-density>. A named preset of the global --scaling factor (compact .92 / default 1 / comfortable 1.08): every size token rescales in proportion app-wide. PageContainer density= overrides locally."},{name:"scaling",type:"number | null",defaultValue:"null",description:"Continuous global size multiplier \u2192 inline --scaling on <html> (Radix model). Scales spacing, control/table/checkbox/switch heights, radius in proportion. null defers to the density preset; a number (e.g. 0.95) overrides it. Type is NOT scaled (separate fontSize axis)."},{name:"fontSize",type:'"sm" | "default" | "lg"',defaultValue:'"default"',description:"Base type-size axis \u2192 <html data-font-size>. A preset sets --font-size-base and the whole golden scale rescales. Orthogonal to --scaling."},{name:"persist",type:"boolean | readonly AppPreferenceAxis[]",defaultValue:"true",description:'Which viewer preferences survive a reload: `true` every axis, `false` none, or a LIST \u2014 `persist={["theme", "density", "fontSize"]}`. The list exists because THE AXES DO NOT SHARE AN OWNER. theme/brand/density/fontSize/scaling are the VIEWER\'s and belong in this browser; locale/timezone/timeFormat/dateFormat are frequently the SERVER\'s, resolved per request from a cookie, an account row or a header \u2014 and a stored copy then WINS over the value the server just sent, because storage is read after the props. Given one all-or-nothing flag, that consumer sets `persist={false}` and loses the viewer\'s theme along with it (measured: a hosted Inertia app whose theme toggle reset on every full page load). Naming the axes keeps both owners.'},{name:"emitFieldNames",type:"boolean",defaultValue:"false",description:"OFF by default and deliberately opt-in: `name` decides what a native <form> submit sends, so switching it on for every consumer of a shared package would make apps start posting new keys to their backend on a library upgrade. Turn it on for apps that need native form posts or a screen-automation (RPA) contract on their controls. The inert `data-field` companion attribute is emitted regardless; a `name` written on the control itself always wins. NOT persisted to localStorage: it is the app's configuration, not a user choice."}],usage:["`AppPresetContext` is the React context AppProvider's `preset` prop fills; read it with `useAppPreset()` (the active `AppPreset`, or undefined for the neutral defaults). Don't provide it yourself \u2014 pass `preset` to AppProvider.",'DO drive the four theme axes (theme / brand / density / fontSize) from AppProvider props ONLY \u2014 they are written to <html data-*> and read by every component via tokens. Never hand-set --font-size-base or .ui-density-* in app CSS; that bypasses persistence + the runtime switchers. For runtime switching mount `<AppSettingPicker kind="density" | "fontSize" | "theme" | "brand" >` or call setDensity/setFontSize/setTheme/setBrand from useAppContext().','DO mount AppProvider ONCE at the application root (e.g. in app.tsx or the Inertia layout), wrapping ALL children \u2014 every `AppSettingPicker` (kind="locale" / "timezone" / "dateFormat" / "timeFormat"), every formatDate call, and the Toaster all rely on the single context it provides. Nesting two AppProviders creates split contexts; inner pickers silently read the wrong one.',"DO NOT omit AppProvider and then try to use AppSettingPicker or formatDate standalone \u2014 useAppContext() throws 'useAppContext must be used within <AppProvider>' at runtime. The only exception is using those pickers in fully controlled mode (value + onChange) which reads useOptionalAppContext() and returns null safely.","DO use `persist={false}` on AppProvider for isolated tests and standalone settings forms where localStorage must not be read or written. With the default `persist={true}` the provider reads localStorage key `godxjp.app` on mount (after first render), so initial state may differ between SSR and client.",'DO NOT reach for `persist={false}` because the SERVER owns the locale \u2014 pass the axes the BROWSER owns instead: `persist={["theme", "brand", "density", "fontSize", "scaling"]}`. Storage is read after the props, so a stored `locale` overrides the one the server just resolved; turning persistence off wholesale fixes that and silently takes the viewer\'s theme with it, which is how a hosted app shipped a theme toggle that reset on every reload.',"DO set `defaultTimezone='system'` together with `systemTimezone={serverTimezone}` when your backend knows the legal entity's canonical timezone (e.g. 'Asia/Ho_Chi_Minh'). Use `defaultTimezone='browser'` (the default) only when you want the user's browser clock. Do NOT pass a raw IANA string to `defaultTimezone` if the user may be in a different zone \u2014 use the named aliases.","DO wire `onLocaleChange`, `onTimezoneChange`, `onTimeFormatChange`, `onDateFormatChange` to persist changes server-side (e.g. patch user profile via Inertia router) in addition to the automatic localStorage write. These callbacks fire after state is set, so the new value is already reflected in context.","DO set `emitFieldNames` on AppProvider when the app is driven by screen automation (RPA) or posts native forms \u2014 every control under a FormField then carries a real `name` taken from the field key, and legacy automation that addressed controls by `name` keeps working after a rewrite. Leave it off (the default) otherwise: it changes what a native submit sends. The `data-field` attribute is emitted either way, so e2e selectors do not depend on this flag.",'DO restrict the timezone dropdown by passing `timezoneOptions={APP_TIMEZONE_PRESET}` (an exported constant) to AppProvider \u2014 every `AppSettingPicker kind="timezone"` inherits this restricted list from context. Without it the picker offers the full IANA list (~600 entries).'],useCases:["App bootstrap in a multi-locale SaaS admin (ja/en/vi) \u2014 mount AppProvider at the root with the tenant's preferred locale and IANA timezone so every DataTable date column, every formatDate call, and every picker renders consistently in the user's locale without any per-component configuration.",'User settings page \u2014 render `<AppSettingPicker kind="locale" />`, `kind="timezone"`, `kind="dateFormat"` and `kind="timeFormat"` inside the existing AppProvider; each reads and writes context automatically. Wire `onLocaleChange` to an Inertia form submit to persist the change to the server profile.',"Server-rendered Inertia app with SSR hydration \u2014 pass `defaultTimezone='system'` and `systemTimezone={sharedProps.timezone}` (injected via HandleInertiaRequests) so the initial render is timezone-deterministic and avoids hydration mismatches caused by browser-timezone detection.","Multi-entity accounting dashboard \u2014 use `timezoneOptions` to restrict the picker to the legal entity's permissible zones (e.g. Southeast Asian IANA ids only), preventing users from accidentally switching to an out-of-scope timezone that would misrepresent transaction timestamps.","Isolated preview / Storybook story \u2014 wrap a single component in `<AppProvider persist={false} defaultLocale='en'>` to give it a stable context without polluting localStorage between stories.","Test harness \u2014 wrap the component under test in `<AppProvider persist={false} defaultLocale='ja' defaultDateFormat='iso'>` to assert locale-sensitive formatting output deterministically, independent of whatever the browser or stored preferences report."],related:['AppSettingPicker \u2014 `kind="locale"` is the language selector that reads/writes AppProvider locale context automatically when used with no value props. Prefer it over calling setLocale from useAppContext() directly in UI.','AppSettingPicker `kind="timezone"` \u2014 the timezone selector; inherits `timezoneOptions` from AppProvider context. Every kind needs AppProvider in the tree unless value + onValueChange are passed.',"formatDate \u2014 the MANDATORY date/time formatter that reads locale, timezone, timeFormat, and dateFormat from AppProvider context. Do NOT call date-fns or Intl.DateTimeFormat directly; formatDate is the single source of truth for display.","AppShell \u2014 the authenticated layout (sidebar + topbar + main). It does NOT mount AppProvider: mount AppProvider once at the application root, ABOVE AppShell (and above any CenteredShell/MobileShell), so every shell and page reads the same context.","OverlayPortalProvider \u2014 the other root-level provider, and the one to reach for when this library is mounted inside a SHADOW ROOT. Every overlay here (Popover, Dialog, Sheet, Tooltip, DropdownMenu, HoverCard) portals to `document.body` by default, which is correct on an ordinary page and wrong in a shadow root: the panel lands outside the tree carrying the stylesheet and renders with none of it \u2014 measured on an embedded bar as border 0, radius 0, a transparent background, and the anchor maths 40px off. `<OverlayPortalProvider container={shadowRoot}>` moves all of them at once; a per-component prop cannot, because a component that owns its own overlay (AppLauncher) is unreachable from the outside."],example:`import { AppProvider } from "@godxjp/ui/app";
|
|
1319
|
+
</SearchForm>`,docPath:"navigation/search-form",storyPath:"navigation/search-form.tsx",rules:[23,51]},{name:"AppProvider",subParts:["AppPresetContext"],group:"providers",importPath:"@godxjp/ui/app",tagline:"Root locale/timezone/date-time context \u2014 wrap the app ONCE. All pickers + formatDate read from it. Import from @godxjp/ui/app.",props:[{name:"defaultLocale",type:"string (BCP-47)",description:"Initial locale when nothing is stored. Resolution, first match wins: this prop (when registered \u2014 `de-AT` is served by a registered `de`) \u2192 `<html lang>` (client only) \u2192 the preset's `defaultLocale` \u2192 `en`. Never `navigator.language` (the server cannot see it, so SSR would hydrate differently). An SSR host passes the request's locale here. v32: the default was `vi`."},{name:"preset",type:"AppPreset",description:'Product defaults the host opts into \u2014 `import { godxPreset } from "@godxjp/ui/themes/godx"` (pair it with `@godxjp/ui/themes/godx.css` after the base stylesheet; a shadow root that embeds `@godxjp/ui/styles/core` takes the font-free `@godxjp/ui/themes/godx-tokens.css` instead, since `@font-face` must live in the document). Its `defaultLocale` fills only what the props leave unset \u2014 it pins NO `timeZone` since 32.4.1 (gh#1282: it pinned `Asia/Ho_Chi_Minh` and every app showed Vietnam time to users in Japan; pass `defaultTimezone` for a fixed zone), its `brandMark` is what an `AuthIdentity` with no `brand` draws, and its `name` is written to `<html data-preset>`. Omit for the neutral defaults (ink primary, `en`, no product mark).'},{name:"defaultTimezone",type:'string | "browser" | "system"',defaultValue:'"browser"',description:"Initial IANA timezone."},{name:"defaultDateFormat",type:'"iso" | "ymd" | "dmy" | "mdy" | "locale"',defaultValue:'"locale"',description:"Initial date display format."},{name:"defaultTimeFormat",type:'"24h" | "12h" | "locale"',defaultValue:'"locale"',description:"Initial clock format."},{name:"theme",type:'"light" | "dark" | "system"',defaultValue:'"light"',description:'Theme axis \u2192 <html data-theme>. Equal alias of the legacy .dark class. "system" defers to prefers-color-scheme and is followed LIVE (matchMedia listener), so <html data-theme> flips with the OS; the persisted value stays "system" (the CHOICE), never the resolved light/dark. Persisted; change via setTheme / <AppSettingPicker kind="theme"> / <Segmented>.'},{name:"brand",type:'"brand" | "crm" | "logistics" | "partner" | "slate" | "dxs" | null',defaultValue:"null",description:'Brand-palette axis \u2192 <html data-brand> (sets --primary/--ring/--accent). OPT-IN: null keeps the --primary your own theme.css defines. "dxs" is THE CANONICAL DXS PRESET and is more than a tint: it also binds the canonical hosted-identity surface contract (36px auth controls, 22.5rem auth card measure, 16px page inset / 15px below 30rem), so a DXS surface needs ZERO page CSS for auth geometry, density, insets, logo colour, divider or footer. Stylesheet-only apps (no provider) import "@godxjp/ui/theme/dxs.canonical.css" instead \u2014 same contract, guarded against drift by a test.'},{name:"density",type:'"compact" | "default" | "comfortable"',defaultValue:'"default"',description:"Density axis \u2192 <html data-density>. A named preset of the global --scaling factor (compact .92 / default 1 / comfortable 1.08): every size token rescales in proportion app-wide. PageContainer density= overrides locally."},{name:"scaling",type:"number | null",defaultValue:"null",description:"Continuous global size multiplier \u2192 inline --scaling on <html> (Radix model). Scales spacing, control/table/checkbox/switch heights, radius in proportion. null defers to the density preset; a number (e.g. 0.95) overrides it. Type is NOT scaled (separate fontSize axis)."},{name:"fontSize",type:'"sm" | "default" | "lg"',defaultValue:'"default"',description:"Base type-size axis \u2192 <html data-font-size>. A preset sets --font-size-base and the whole golden scale rescales. Orthogonal to --scaling."},{name:"persist",type:"boolean | readonly AppPreferenceAxis[]",defaultValue:"true",description:'Which viewer preferences survive a reload: `true` every axis, `false` none, or a LIST \u2014 `persist={["theme", "density", "fontSize"]}`. The list exists because THE AXES DO NOT SHARE AN OWNER. theme/brand/density/fontSize/scaling are the VIEWER\'s and belong in this browser; locale/timezone/timeFormat/dateFormat are frequently the SERVER\'s, resolved per request from a cookie, an account row or a header \u2014 and a stored copy then WINS over the value the server just sent, because storage is read after the props. Given one all-or-nothing flag, that consumer sets `persist={false}` and loses the viewer\'s theme along with it (measured: a hosted Inertia app whose theme toggle reset on every full page load). Naming the axes keeps both owners.'},{name:"contentLocales",type:"readonly string[]",description:'The languages the app stores USER CONTENT in, in order, source language first \u2014 e.g. `["ja", "en", "vi"]` for a Japanese-first catalogue (gh#1347). A different fact from the UI locale (`defaultLocale`) and from the registered UI catalogs: a Japanese UI can edit content in three languages, and content may be stored in a language the UI has no catalog for. Every `TranslatableInput` / `TranslatableTextarea` without its own `locales` takes its tabs from this list and opens on its first entry; a field\'s `locales` / `defaultLocale` still override. Omit (or `[]`) to keep the field default (`getRegisteredLocales()`, first tab = the UI locale). App configuration: not persisted, no setter. Read it from `useAppContext().contentLocales` (`undefined` when unset).'},{name:"emitFieldNames",type:"boolean",defaultValue:"false",description:"OFF by default and deliberately opt-in: `name` decides what a native <form> submit sends, so switching it on for every consumer of a shared package would make apps start posting new keys to their backend on a library upgrade. Turn it on for apps that need native form posts or a screen-automation (RPA) contract on their controls. The inert `data-field` companion attribute is emitted regardless; a `name` written on the control itself always wins. NOT persisted to localStorage: it is the app's configuration, not a user choice."}],usage:["`AppPresetContext` is the React context AppProvider's `preset` prop fills; read it with `useAppPreset()` (the active `AppPreset`, or undefined for the neutral defaults). Don't provide it yourself \u2014 pass `preset` to AppProvider.",'DO drive the four theme axes (theme / brand / density / fontSize) from AppProvider props ONLY \u2014 they are written to <html data-*> and read by every component via tokens. Never hand-set --font-size-base or .ui-density-* in app CSS; that bypasses persistence + the runtime switchers. For runtime switching mount `<AppSettingPicker kind="density" | "fontSize" | "theme" | "brand" >` or call setDensity/setFontSize/setTheme/setBrand from useAppContext().','DO mount AppProvider ONCE at the application root (e.g. in app.tsx or the Inertia layout), wrapping ALL children \u2014 every `AppSettingPicker` (kind="locale" / "timezone" / "dateFormat" / "timeFormat"), every formatDate call, and the Toaster all rely on the single context it provides. Nesting two AppProviders creates split contexts; inner pickers silently read the wrong one.',"DO NOT omit AppProvider and then try to use AppSettingPicker or formatDate standalone \u2014 useAppContext() throws 'useAppContext must be used within <AppProvider>' at runtime. The only exception is using those pickers in fully controlled mode (value + onChange) which reads useOptionalAppContext() and returns null safely.","DO use `persist={false}` on AppProvider for isolated tests and standalone settings forms where localStorage must not be read or written. With the default `persist={true}` the provider reads localStorage key `godxjp.app` on mount (after first render), so initial state may differ between SSR and client.",'DO NOT reach for `persist={false}` because the SERVER owns the locale \u2014 pass the axes the BROWSER owns instead: `persist={["theme", "brand", "density", "fontSize", "scaling"]}`. Storage is read after the props, so a stored `locale` overrides the one the server just resolved; turning persistence off wholesale fixes that and silently takes the viewer\'s theme with it, which is how a hosted app shipped a theme toggle that reset on every reload.',"DO set `defaultTimezone='system'` together with `systemTimezone={serverTimezone}` when your backend knows the legal entity's canonical timezone (e.g. 'Asia/Ho_Chi_Minh'). Use `defaultTimezone='browser'` (the default) only when you want the user's browser clock. Do NOT pass a raw IANA string to `defaultTimezone` if the user may be in a different zone \u2014 use the named aliases.","DO wire `onLocaleChange`, `onTimezoneChange`, `onTimeFormatChange`, `onDateFormatChange` to persist changes server-side (e.g. patch user profile via Inertia router) in addition to the automatic localStorage write. These callbacks fire after state is set, so the new value is already reflected in context.","DO set `emitFieldNames` on AppProvider when the app is driven by screen automation (RPA) or posts native forms \u2014 every control under a FormField then carries a real `name` taken from the field key, and legacy automation that addressed controls by `name` keeps working after a rewrite. Leave it off (the default) otherwise: it changes what a native submit sends. The `data-field` attribute is emitted either way, so e2e selectors do not depend on this flag.",'DO pass `contentLocales` when the app edits translatable content (`TranslatableInput` / `TranslatableTextarea`) \u2014 `contentLocales={["ja", "en", "vi"]}` \u2014 so the tab order and the source-language first tab are stated once, not repeated as `locales` on every field. It is NOT the UI language list: leave `defaultLocale` / `registerLocale` to the UI.','DO restrict the timezone dropdown by passing `timezoneOptions={APP_TIMEZONE_PRESET}` (an exported constant) to AppProvider \u2014 every `AppSettingPicker kind="timezone"` inherits this restricted list from context. Without it the picker offers the full IANA list (~600 entries).'],useCases:["App bootstrap in a multi-locale SaaS admin (ja/en/vi) \u2014 mount AppProvider at the root with the tenant's preferred locale and IANA timezone so every DataTable date column, every formatDate call, and every picker renders consistently in the user's locale without any per-component configuration.",'User settings page \u2014 render `<AppSettingPicker kind="locale" />`, `kind="timezone"`, `kind="dateFormat"` and `kind="timeFormat"` inside the existing AppProvider; each reads and writes context automatically. Wire `onLocaleChange` to an Inertia form submit to persist the change to the server profile.',"Server-rendered Inertia app with SSR hydration \u2014 pass `defaultTimezone='system'` and `systemTimezone={sharedProps.timezone}` (injected via HandleInertiaRequests) so the initial render is timezone-deterministic and avoids hydration mismatches caused by browser-timezone detection.","Multi-entity accounting dashboard \u2014 use `timezoneOptions` to restrict the picker to the legal entity's permissible zones (e.g. Southeast Asian IANA ids only), preventing users from accidentally switching to an out-of-scope timezone that would misrepresent transaction timestamps.","Isolated preview / Storybook story \u2014 wrap a single component in `<AppProvider persist={false} defaultLocale='en'>` to give it a stable context without polluting localStorage between stories.","Test harness \u2014 wrap the component under test in `<AppProvider persist={false} defaultLocale='ja' defaultDateFormat='iso'>` to assert locale-sensitive formatting output deterministically, independent of whatever the browser or stored preferences report."],related:['AppSettingPicker \u2014 `kind="locale"` is the language selector that reads/writes AppProvider locale context automatically when used with no value props. Prefer it over calling setLocale from useAppContext() directly in UI.','AppSettingPicker `kind="timezone"` \u2014 the timezone selector; inherits `timezoneOptions` from AppProvider context. Every kind needs AppProvider in the tree unless value + onValueChange are passed.',"formatDate \u2014 the MANDATORY date/time formatter that reads locale, timezone, timeFormat, and dateFormat from AppProvider context. Do NOT call date-fns or Intl.DateTimeFormat directly; formatDate is the single source of truth for display.","AppShell \u2014 the authenticated layout (sidebar + topbar + main). It does NOT mount AppProvider: mount AppProvider once at the application root, ABOVE AppShell (and above any CenteredShell/MobileShell), so every shell and page reads the same context.","OverlayPortalProvider \u2014 the other root-level provider, and the one to reach for when this library is mounted inside a SHADOW ROOT. Every overlay here (Popover, Dialog, Sheet, Tooltip, DropdownMenu, HoverCard) portals to `document.body` by default, which is correct on an ordinary page and wrong in a shadow root: the panel lands outside the tree carrying the stylesheet and renders with none of it \u2014 measured on an embedded bar as border 0, radius 0, a transparent background, and the anchor maths 40px off. `<OverlayPortalProvider container={shadowRoot}>` moves all of them at once; a per-component prop cannot, because a component that owns its own overlay (AppLauncher) is unreachable from the outside."],example:`import { AppProvider } from "@godxjp/ui/app";
|
|
1320
1320
|
|
|
1321
1321
|
<AppProvider defaultLocale="ja" defaultTimezone="Asia/Tokyo" defaultDateFormat="iso" defaultTimeFormat="24h">
|
|
1322
1322
|
{children}
|
|
@@ -7950,7 +7950,7 @@ export default function Demo() {
|
|
|
7950
7950
|
</PageContainer>
|
|
7951
7951
|
);
|
|
7952
7952
|
}
|
|
7953
|
-
`};var p={name:"@godxjp/ui-mcp",version:"33.4.1",godxUiCompatibility:"33.4.x",description:"Model Context Protocol server for @godxjp/ui \u2014 gives Claude Code / Codex CLI / Cursor / any MCP-aware agent live access to the component catalog, prop vocabulary, design tokens, 45 cardinal rules, copy-paste-ready patterns, 12 design / taste skills synthesised from Leonxlnx/taste-skill, 20+ anti-AI-tell patterns, and a 50-check redesign audit \u2014 token-efficient (list \u2192 drill-down).",type:"module",main:"./dist/index.js",module:"./dist/index.js",types:"./dist/index.d.ts",bin:{"godx-ui-mcp":"./dist/index.js"},files:["dist","README.md"],publishConfig:{registry:"https://registry.npmjs.org/",access:"public"},repository:{type:"git",url:"git+https://github.com/godx-jp/godxjp-ui.git",directory:"mcp"},homepage:"https://github.com/godx-jp/godxjp-ui/tree/main/mcp#readme",license:"Apache-2.0",scripts:{build:"tsup",dev:"tsup --watch",start:"node dist/index.js",inspect:"npx @modelcontextprotocol/inspector node dist/index.js","type-check":"tsc --noEmit",test:"vitest run",prepublishOnly:"npm run build"},dependencies:{"@modelcontextprotocol/sdk":"^1.29.0",zod:"^4.4.3"},devDependencies:{"@types/node":"^22.10.0",tsup:"^8.5.1",typescript:"^6.0.3",vitest:"^4.1.6"},keywords:["mcp","model-context-protocol","godxjp","ui","design-system","react","claude","cursor"],author:"GoDX (https://godx.jp)",bugs:{url:"https://github.com/godx-jp/godxjp-ui/issues"}};var O=[{name:"list_skills",description:"List every design/taste skill bundled by this MCP (id + name + whenToUse + section ids). Use FIRST to discover skills; then `get_skill_section` to drill in.",inputSchema:{type:"object",properties:{}}},{name:"list_primitives",description:'List every @godxjp/ui primitive/composite/shell (group + tagline per entry). Optionally filter by group. Then `get_component` for one\'s full API. Core only by default \u2014 pass tier="lab" for the opt-in @godxjp/ui/lab components.',inputSchema:{type:"object",properties:{tier:{type:"string",enum:["core","lab","all"],description:'Default "core". "lab" lists the opt-in `@godxjp/ui/lab` components (niche, kept and tested, not in the default context); "all" lists both.'},group:{type:"string",enum:["general","layout","data-display","data-entry","feedback","navigation","composites","shell","providers"]}}}},{name:"list_utilities",description:'List every NON-component public export of @godxjp/ui \u2014 hooks, helper functions and constants (cn, formatDate, formatCurrency, toast, useDebouncedValue, buttonVariants, CHART_COLORS, SHOW_PARENT \u2026). Reach for this before hand-writing a className merger, a date/money formatter, a debounce hook or a chart palette: two products in this org each re-implemented `cn` because it could not be found. Optionally filter by kind. Then `get_component name="<name>"` for its signature, usage and example.',inputSchema:{type:"object",properties:{kind:{type:"string",enum:["hook","function","value"],description:"hook = only legal inside a component body; function = callable anywhere; value = a constant to read."}}}},{name:"list_patterns",description:"List every canonical copy-paste code pattern (signup-form, settings-page, data-table-page, async-data-state, confirm-destructive, \u2026); common aliases resolve too. Use before `get_pattern`.",inputSchema:{type:"object",properties:{}}},{name:"list_anti_ai_tells",description:"List every AI-tell pattern to AVOID (optionally by category). Use to self-audit a design before shipping; then `get_anti_ai_tell` for the fix.",inputSchema:{type:"object",properties:{category:{type:"string",enum:["visual","layout","copy","interaction","imagery","structure"]}}}},{name:"list_redesign_checks",description:"List the redesign audit checklist (50+ checks; optionally by category). Use when auditing an existing project; then `get_redesign_check` for a symptom's fix.",inputSchema:{type:"object",properties:{category:{type:"string",enum:["typography","color-surface","layout","interactivity","content","components","iconography","code-quality","omissions"]}}}},{name:"list_audit_rules",description:"List the LOCAL static ui-audit rules (scripts/ui-audit.mjs) to run BEFORE any visual review \u2014 each cites the standard it enforces (WCAG/WAI-ARIA/Intl/ISO/IANA/CSS-Logical) + a fix + the run command. Optionally by category.",inputSchema:{type:"object",properties:{category:{type:"string",enum:["tokens","composition","api","a11y","i18n","rtl","copy"]}}}},{name:"list_visual_checks",description:"List the RUNTIME visual-audit checks (scripts/visual-audit.mjs \u2014 Playwright + axe-core) to run against the RUNNING app: contrast/ARIA (axe), target size, rendered-accent chroma, DOM emoji, banner layout. Needs a browser (vs list_audit_rules, static). Optionally by category.",inputSchema:{type:"object",properties:{category:{type:"string",enum:["a11y","color","i18n","layout"]}}}},{name:"get_anti_ai_tell",description:"Fetch ONE anti-AI-tell \u2014 full body + concrete fix. Use after `list_anti_ai_tells`.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Exact tell name from list_anti_ai_tells."}},required:["name"]}},{name:"get_redesign_check",description:"Fetch redesign check(s) matching a symptom snippet. Returns full fix + UI note. Use after `list_redesign_checks`.",inputSchema:{type:"object",properties:{symptom:{type:"string",description:"Fragment of the symptom text (e.g. 'Inter everywhere' / '100vh')."}},required:["symptom"]}},{name:"get_skill_section",description:"Fetch ONE section of ONE skill \u2014 token-efficient. E.g. `skill='soft', section='double-bezel'`. Use after `list_skills` narrowed the relevant skill + section.",inputSchema:{type:"object",properties:{skill:{type:"string",description:"Skill id (e.g. 'soft', 'minimalist', 'taste')."},section:{type:"string",description:"Section id within that skill."}},required:["skill","section"]}},{name:"get_component",description:"Full guide for one @godxjp/ui component \u2014 import path, props/types/defaults, HOW to use it (DO/DON'T), WHEN to reach for it (use cases), related components (don't reinvent/confuse), a copy-paste example, story path, and cardinal rules. Use this before hand-rolling anything. Design-token knobs are listed compactly (name+default); pass `verbose:true` for what each token controls.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Component name (e.g. 'Button', 'DataTable')."},verbose:{type:"boolean",description:"Include the full design-token table with a 'what it controls' description per token. Default false (compact token+default only) to save context."}},required:["name"]}},{name:"get_pattern",description:"Full code snippet for one canonical pattern \u2014 copy-paste-ready.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Pattern slug (use list_patterns first)."}},required:["name"]}},{name:"get_rule",description:"Read one cardinal rule from CLAUDE.md (by number) OR all if no number.",inputSchema:{type:"object",properties:{number:{type:"number",description:"Rule number (1-N)."}}}},{name:"get_vocab",description:"Read shared prop-vocabulary type (`SizeProp`, `StatusProp`, `ColorProp`, `LoadingProp`, etc.) OR all if no name.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Vocab type name."}}}},{name:"get_tokens",description:"Read design tokens, optionally filtered by tier category (primitive / semantic / component).",inputSchema:{type:"object",properties:{category:{type:"string",enum:["primitive","semantic","component"]}}}},{name:"list_consumer_skills",description:"List the design skills relevant to an app-dev BUILDING WITH @godxjp/ui (audience consumer/both). Hides core library-maintenance skills. START HERE if you import @godxjp/ui and want guidance (design-to-page, compose-a-screen, taste, \u2026). Returns id + name + whenToUse + section ids.",inputSchema:{type:"object",properties:{}}},{name:"get_consumer_skill",description:"Fetch ONE section of ONE consumer-facing skill. Same as get_skill_section but refuses core-only skills (steers app-devs away from library-maintenance material). Use after list_consumer_skills / route_consumer_task.",inputSchema:{type:"object",properties:{skill:{type:"string",description:"Consumer skill id (e.g. 'design-to-page', 'compose-a-screen')."},section:{type:"string",description:"Section id within that skill."}},required:["skill"]}},{name:"route_consumer_task",description:"Natural-language task \u2192 consumer skill+section pointer. Like route_task but only points to consumer-facing skills (never core library-maintenance). Use FIRST when you're building an app with @godxjp/ui.",inputSchema:{type:"object",properties:{task:{type:"string",description:"Describe what you want to build."}},required:["task"]}},{name:"draft_bug_report",description:"When @godxjp/ui ITSELF is at fault (missing token, a primitive lacking the controlled-vocabulary prop, a real a11y/behaviour bug, a wrong catalog example) and you cannot follow a rule \u2014 DON'T fake a workaround. This drafts a detailed GitHub issue body + a copy-paste `gh issue create` command so you can report it. Prints the command only; never runs gh.",inputSchema:{type:"object",properties:{summary:{type:"string",description:"One-line title of the bug / blocked rule."},repro:{type:"string",description:"Minimal steps or code to reproduce."},expected:{type:"string",description:"What SHOULD happen (per the rule/spec)."},actual:{type:"string",description:"What actually happens."},component:{type:"string",description:"Affected component name, if any (links to get_component)."},rule:{type:"number",description:"Cardinal rule number that can't be followed, if any."},version:{type:"string",description:"Installed @godxjp/ui version (e.g. '12.1.0')."},env:{type:"string",description:"Environment (browser/OS/framework), if relevant."}},required:["summary"]}},{name:"check_compatibility",description:"Report whether the @godxjp/ui version installed in the target project matches THIS catalog (which describes one release train). A mismatched minor means the props/tokens/patterns may describe a build they never installed (#140). Pass the installed version (`npm ls @godxjp/ui`); call it at the START of a consumer session.",inputSchema:{type:"object",properties:{version:{type:"string",description:"Installed @godxjp/ui version in the target project, e.g. '16.10.0'. Omit to just read the catalog's own version + compatible range."}}}},{name:"route_task",description:"Natural-language task \u2192 skill+section pointer (e.g. 'design a premium agency hero' \u2192 soft/vibe-archetypes). Use FIRST when you don't know which skill applies.",inputSchema:{type:"object",properties:{task:{type:"string",description:"Describe what you want to build."}},required:["task"]}},{name:"suggest_primitive",description:"Use case \u2192 primitive recommendation. E.g. 'confirm a destructive delete' \u2192 DangerZone pattern + Dialog suggestion.",inputSchema:{type:"object",properties:{use_case:{type:"string"}},required:["use_case"]}},{name:"search_components",description:'Fuzzy-search primitives by name / tagline / prop. Returns ranked matches. Core only by default; pass tier="lab" or "all" to include the opt-in @godxjp/ui/lab components.',inputSchema:{type:"object",properties:{query:{type:"string"},tier:{type:"string",enum:["core","lab","all"],description:'Default "core". "lab" lists the opt-in `@godxjp/ui/lab` components (niche, kept and tested, not in the default context); "all" lists both.'}},required:["query"]}},{name:"get_frame_coverage",description:"Verified preview-contract coverage for a component (issue #163). Answers 'is this state actually PROVEN?' \u2014 returns, per contract dimension (variants, tones, sizes, shapes, density, controlled/uncontrolled ownership, disabled/read-only/loading/empty/error/success, async retry/cancel/offline, responsive viewport matrix, RTL, long/localized content, keyboard/focus, accessible name/description/error, reduced motion / coarse touch), whether an EXECUTED case proves it (covered), whether nothing proves it (UNTESTED), or whether it cannot exist (not-applicable, with a reason). UNTESTED IS NOT A PASS: never infer that a component supports a state because an example renders. Call this before telling a user a component 'supports' anything. Omit `name` for the repo-wide summary and the tracked known gaps.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Public export name (e.g. 'Button', 'DataTable', 'CardFooter'). Omit for the repo-wide coverage summary."}}}},{name:"get_design",description:"DESIGN.md \u2014 the visual spec for screens built with @godxjp/ui: real tokens (YAML front matter), colour scarcity rules, typography, layout & density, elevation, components, ADMIN SCREEN ANATOMY (list / detail / form / settings / dashboard / approval queue), do's and don'ts, responsive, iteration guide, known gaps. Read before composing a screen. Pass `section` for one section; omit it for the whole document (~12 KB).",inputSchema:{type:"object",properties:{section:{type:"string",description:"Section id or heading word, e.g. 'tokens', 'colors', 'admin-screen-anatomy', 'dos-and-donts', 'iteration-guide'. Unknown ids list the valid ones."}}}},{name:"get_exemplar",description:"A reference screen to match for an admin archetype: its anatomy notes from DESIGN.md, the full source of the showcase built from real @godxjp/ui primitives, and its live preview URL for screenshot comparison. Archetypes: "+E.map(e=>e.archetype).join(", ")+". Omit `archetype` to list them.",inputSchema:{type:"object",properties:{archetype:{type:"string",description:E.map(e=>e.archetype).join(" | ")+" (aliases: "+E.flatMap(e=>e.aliases??[]).join(", ")+")."}}}},{name:"lint_jsx",description:"Heuristic check of a JSX snippet for common violations \u2014 raw `<button>` / `<input>`, `color='error'` on Tag/Badge, missing aria-label, missing source.code override on stories with cell renderers (rule 34), etc.",inputSchema:{type:"object",properties:{jsx:{type:"string"}},required:["jsx"]}}],Ze=new Set(O.map(e=>e.name));function et(e=X()){let t=p.godxUiCompatibility??p.version,a=`@godxjp/ui-mcp ${p.version} (catalog for @godxjp/ui ${t})`;if(!e)return a;let n=e.source==="node_modules"?"read from node_modules at answer time":"from GODX_UI_VERSION at launch \u2014 node_modules/@godxjp/ui not resolved";return`${a} \u2014 installed @godxjp/ui ${e.version} (${n})`}function tt(e=X()){let t=e?V(e.version):null,a=V(p.version);return!e||!t||!a?null:dt(t,a)>0?`\u26A0\uFE0F SERVER OLDER THAN INSTALLED PACKAGE: this server is @godxjp/ui-mcp ${p.version}, the project has @godxjp/ui ${e.version} installed \u2014 this catalog is BEHIND the package and may be missing props and components that exist. Do not conclude from this answer that a prop is unavailable. Restart the session so the server relaunches on the installed version (run \`npx @godxjp/ui sync-rules\` first if the project's .mcp.json still pins an older @godxjp/ui-mcp).`:t.major===a.major?null:`\u26A0\uFE0F MAJOR MISMATCH: this server is @godxjp/ui-mcp ${p.version}, the project has @godxjp/ui ${e.version} installed \u2014 this catalog may describe components that do not exist in the installed package. Pin the MCP to @godxjp/ui-mcp@${e.version} (\`npx @godxjp/ui sync-rules\` updates the project's .mcp.json; a registration outside the project needs \`claude mcp remove <key>\`), then restart the agent.`}async function G(e,t){let a=await at(e,t);if(!Ze.has(e))return a;let n=X(),o=tt(n);return`${et(n)}
|
|
7953
|
+
`};var p={name:"@godxjp/ui-mcp",version:"33.5.0",godxUiCompatibility:"33.5.x",description:"Model Context Protocol server for @godxjp/ui \u2014 gives Claude Code / Codex CLI / Cursor / any MCP-aware agent live access to the component catalog, prop vocabulary, design tokens, 45 cardinal rules, copy-paste-ready patterns, 12 design / taste skills synthesised from Leonxlnx/taste-skill, 20+ anti-AI-tell patterns, and a 50-check redesign audit \u2014 token-efficient (list \u2192 drill-down).",type:"module",main:"./dist/index.js",module:"./dist/index.js",types:"./dist/index.d.ts",bin:{"godx-ui-mcp":"./dist/index.js"},files:["dist","README.md"],publishConfig:{registry:"https://registry.npmjs.org/",access:"public"},repository:{type:"git",url:"git+https://github.com/godx-jp/godxjp-ui.git",directory:"mcp"},homepage:"https://github.com/godx-jp/godxjp-ui/tree/main/mcp#readme",license:"Apache-2.0",scripts:{build:"tsup",dev:"tsup --watch",start:"node dist/index.js",inspect:"npx @modelcontextprotocol/inspector node dist/index.js","type-check":"tsc --noEmit",test:"vitest run",prepublishOnly:"npm run build"},dependencies:{"@modelcontextprotocol/sdk":"^1.29.0",zod:"^4.4.3"},devDependencies:{"@types/node":"^22.10.0",tsup:"^8.5.1",typescript:"^6.0.3",vitest:"^4.1.6"},keywords:["mcp","model-context-protocol","godxjp","ui","design-system","react","claude","cursor"],author:"GoDX (https://godx.jp)",bugs:{url:"https://github.com/godx-jp/godxjp-ui/issues"}};var O=[{name:"list_skills",description:"List every design/taste skill bundled by this MCP (id + name + whenToUse + section ids). Use FIRST to discover skills; then `get_skill_section` to drill in.",inputSchema:{type:"object",properties:{}}},{name:"list_primitives",description:'List every @godxjp/ui primitive/composite/shell (group + tagline per entry). Optionally filter by group. Then `get_component` for one\'s full API. Core only by default \u2014 pass tier="lab" for the opt-in @godxjp/ui/lab components.',inputSchema:{type:"object",properties:{tier:{type:"string",enum:["core","lab","all"],description:'Default "core". "lab" lists the opt-in `@godxjp/ui/lab` components (niche, kept and tested, not in the default context); "all" lists both.'},group:{type:"string",enum:["general","layout","data-display","data-entry","feedback","navigation","composites","shell","providers"]}}}},{name:"list_utilities",description:'List every NON-component public export of @godxjp/ui \u2014 hooks, helper functions and constants (cn, formatDate, formatCurrency, toast, useDebouncedValue, buttonVariants, CHART_COLORS, SHOW_PARENT \u2026). Reach for this before hand-writing a className merger, a date/money formatter, a debounce hook or a chart palette: two products in this org each re-implemented `cn` because it could not be found. Optionally filter by kind. Then `get_component name="<name>"` for its signature, usage and example.',inputSchema:{type:"object",properties:{kind:{type:"string",enum:["hook","function","value"],description:"hook = only legal inside a component body; function = callable anywhere; value = a constant to read."}}}},{name:"list_patterns",description:"List every canonical copy-paste code pattern (signup-form, settings-page, data-table-page, async-data-state, confirm-destructive, \u2026); common aliases resolve too. Use before `get_pattern`.",inputSchema:{type:"object",properties:{}}},{name:"list_anti_ai_tells",description:"List every AI-tell pattern to AVOID (optionally by category). Use to self-audit a design before shipping; then `get_anti_ai_tell` for the fix.",inputSchema:{type:"object",properties:{category:{type:"string",enum:["visual","layout","copy","interaction","imagery","structure"]}}}},{name:"list_redesign_checks",description:"List the redesign audit checklist (50+ checks; optionally by category). Use when auditing an existing project; then `get_redesign_check` for a symptom's fix.",inputSchema:{type:"object",properties:{category:{type:"string",enum:["typography","color-surface","layout","interactivity","content","components","iconography","code-quality","omissions"]}}}},{name:"list_audit_rules",description:"List the LOCAL static ui-audit rules (scripts/ui-audit.mjs) to run BEFORE any visual review \u2014 each cites the standard it enforces (WCAG/WAI-ARIA/Intl/ISO/IANA/CSS-Logical) + a fix + the run command. Optionally by category.",inputSchema:{type:"object",properties:{category:{type:"string",enum:["tokens","composition","api","a11y","i18n","rtl","copy"]}}}},{name:"list_visual_checks",description:"List the RUNTIME visual-audit checks (scripts/visual-audit.mjs \u2014 Playwright + axe-core) to run against the RUNNING app: contrast/ARIA (axe), target size, rendered-accent chroma, DOM emoji, banner layout. Needs a browser (vs list_audit_rules, static). Optionally by category.",inputSchema:{type:"object",properties:{category:{type:"string",enum:["a11y","color","i18n","layout"]}}}},{name:"get_anti_ai_tell",description:"Fetch ONE anti-AI-tell \u2014 full body + concrete fix. Use after `list_anti_ai_tells`.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Exact tell name from list_anti_ai_tells."}},required:["name"]}},{name:"get_redesign_check",description:"Fetch redesign check(s) matching a symptom snippet. Returns full fix + UI note. Use after `list_redesign_checks`.",inputSchema:{type:"object",properties:{symptom:{type:"string",description:"Fragment of the symptom text (e.g. 'Inter everywhere' / '100vh')."}},required:["symptom"]}},{name:"get_skill_section",description:"Fetch ONE section of ONE skill \u2014 token-efficient. E.g. `skill='soft', section='double-bezel'`. Use after `list_skills` narrowed the relevant skill + section.",inputSchema:{type:"object",properties:{skill:{type:"string",description:"Skill id (e.g. 'soft', 'minimalist', 'taste')."},section:{type:"string",description:"Section id within that skill."}},required:["skill","section"]}},{name:"get_component",description:"Full guide for one @godxjp/ui component \u2014 import path, props/types/defaults, HOW to use it (DO/DON'T), WHEN to reach for it (use cases), related components (don't reinvent/confuse), a copy-paste example, story path, and cardinal rules. Use this before hand-rolling anything. Design-token knobs are listed compactly (name+default); pass `verbose:true` for what each token controls.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Component name (e.g. 'Button', 'DataTable')."},verbose:{type:"boolean",description:"Include the full design-token table with a 'what it controls' description per token. Default false (compact token+default only) to save context."}},required:["name"]}},{name:"get_pattern",description:"Full code snippet for one canonical pattern \u2014 copy-paste-ready.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Pattern slug (use list_patterns first)."}},required:["name"]}},{name:"get_rule",description:"Read one cardinal rule from CLAUDE.md (by number) OR all if no number.",inputSchema:{type:"object",properties:{number:{type:"number",description:"Rule number (1-N)."}}}},{name:"get_vocab",description:"Read shared prop-vocabulary type (`SizeProp`, `StatusProp`, `ColorProp`, `LoadingProp`, etc.) OR all if no name.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Vocab type name."}}}},{name:"get_tokens",description:"Read design tokens, optionally filtered by tier category (primitive / semantic / component).",inputSchema:{type:"object",properties:{category:{type:"string",enum:["primitive","semantic","component"]}}}},{name:"list_consumer_skills",description:"List the design skills relevant to an app-dev BUILDING WITH @godxjp/ui (audience consumer/both). Hides core library-maintenance skills. START HERE if you import @godxjp/ui and want guidance (design-to-page, compose-a-screen, taste, \u2026). Returns id + name + whenToUse + section ids.",inputSchema:{type:"object",properties:{}}},{name:"get_consumer_skill",description:"Fetch ONE section of ONE consumer-facing skill. Same as get_skill_section but refuses core-only skills (steers app-devs away from library-maintenance material). Use after list_consumer_skills / route_consumer_task.",inputSchema:{type:"object",properties:{skill:{type:"string",description:"Consumer skill id (e.g. 'design-to-page', 'compose-a-screen')."},section:{type:"string",description:"Section id within that skill."}},required:["skill"]}},{name:"route_consumer_task",description:"Natural-language task \u2192 consumer skill+section pointer. Like route_task but only points to consumer-facing skills (never core library-maintenance). Use FIRST when you're building an app with @godxjp/ui.",inputSchema:{type:"object",properties:{task:{type:"string",description:"Describe what you want to build."}},required:["task"]}},{name:"draft_bug_report",description:"When @godxjp/ui ITSELF is at fault (missing token, a primitive lacking the controlled-vocabulary prop, a real a11y/behaviour bug, a wrong catalog example) and you cannot follow a rule \u2014 DON'T fake a workaround. This drafts a detailed GitHub issue body + a copy-paste `gh issue create` command so you can report it. Prints the command only; never runs gh.",inputSchema:{type:"object",properties:{summary:{type:"string",description:"One-line title of the bug / blocked rule."},repro:{type:"string",description:"Minimal steps or code to reproduce."},expected:{type:"string",description:"What SHOULD happen (per the rule/spec)."},actual:{type:"string",description:"What actually happens."},component:{type:"string",description:"Affected component name, if any (links to get_component)."},rule:{type:"number",description:"Cardinal rule number that can't be followed, if any."},version:{type:"string",description:"Installed @godxjp/ui version (e.g. '12.1.0')."},env:{type:"string",description:"Environment (browser/OS/framework), if relevant."}},required:["summary"]}},{name:"check_compatibility",description:"Report whether the @godxjp/ui version installed in the target project matches THIS catalog (which describes one release train). A mismatched minor means the props/tokens/patterns may describe a build they never installed (#140). Pass the installed version (`npm ls @godxjp/ui`); call it at the START of a consumer session.",inputSchema:{type:"object",properties:{version:{type:"string",description:"Installed @godxjp/ui version in the target project, e.g. '16.10.0'. Omit to just read the catalog's own version + compatible range."}}}},{name:"route_task",description:"Natural-language task \u2192 skill+section pointer (e.g. 'design a premium agency hero' \u2192 soft/vibe-archetypes). Use FIRST when you don't know which skill applies.",inputSchema:{type:"object",properties:{task:{type:"string",description:"Describe what you want to build."}},required:["task"]}},{name:"suggest_primitive",description:"Use case \u2192 primitive recommendation. E.g. 'confirm a destructive delete' \u2192 DangerZone pattern + Dialog suggestion.",inputSchema:{type:"object",properties:{use_case:{type:"string"}},required:["use_case"]}},{name:"search_components",description:'Fuzzy-search primitives by name / tagline / prop. Returns ranked matches. Core only by default; pass tier="lab" or "all" to include the opt-in @godxjp/ui/lab components.',inputSchema:{type:"object",properties:{query:{type:"string"},tier:{type:"string",enum:["core","lab","all"],description:'Default "core". "lab" lists the opt-in `@godxjp/ui/lab` components (niche, kept and tested, not in the default context); "all" lists both.'}},required:["query"]}},{name:"get_frame_coverage",description:"Verified preview-contract coverage for a component (issue #163). Answers 'is this state actually PROVEN?' \u2014 returns, per contract dimension (variants, tones, sizes, shapes, density, controlled/uncontrolled ownership, disabled/read-only/loading/empty/error/success, async retry/cancel/offline, responsive viewport matrix, RTL, long/localized content, keyboard/focus, accessible name/description/error, reduced motion / coarse touch), whether an EXECUTED case proves it (covered), whether nothing proves it (UNTESTED), or whether it cannot exist (not-applicable, with a reason). UNTESTED IS NOT A PASS: never infer that a component supports a state because an example renders. Call this before telling a user a component 'supports' anything. Omit `name` for the repo-wide summary and the tracked known gaps.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Public export name (e.g. 'Button', 'DataTable', 'CardFooter'). Omit for the repo-wide coverage summary."}}}},{name:"get_design",description:"DESIGN.md \u2014 the visual spec for screens built with @godxjp/ui: real tokens (YAML front matter), colour scarcity rules, typography, layout & density, elevation, components, ADMIN SCREEN ANATOMY (list / detail / form / settings / dashboard / approval queue), do's and don'ts, responsive, iteration guide, known gaps. Read before composing a screen. Pass `section` for one section; omit it for the whole document (~12 KB).",inputSchema:{type:"object",properties:{section:{type:"string",description:"Section id or heading word, e.g. 'tokens', 'colors', 'admin-screen-anatomy', 'dos-and-donts', 'iteration-guide'. Unknown ids list the valid ones."}}}},{name:"get_exemplar",description:"A reference screen to match for an admin archetype: its anatomy notes from DESIGN.md, the full source of the showcase built from real @godxjp/ui primitives, and its live preview URL for screenshot comparison. Archetypes: "+E.map(e=>e.archetype).join(", ")+". Omit `archetype` to list them.",inputSchema:{type:"object",properties:{archetype:{type:"string",description:E.map(e=>e.archetype).join(" | ")+" (aliases: "+E.flatMap(e=>e.aliases??[]).join(", ")+")."}}}},{name:"lint_jsx",description:"Heuristic check of a JSX snippet for common violations \u2014 raw `<button>` / `<input>`, `color='error'` on Tag/Badge, missing aria-label, missing source.code override on stories with cell renderers (rule 34), etc.",inputSchema:{type:"object",properties:{jsx:{type:"string"}},required:["jsx"]}}],Ze=new Set(O.map(e=>e.name));function et(e=X()){let t=p.godxUiCompatibility??p.version,a=`@godxjp/ui-mcp ${p.version} (catalog for @godxjp/ui ${t})`;if(!e)return a;let n=e.source==="node_modules"?"read from node_modules at answer time":"from GODX_UI_VERSION at launch \u2014 node_modules/@godxjp/ui not resolved";return`${a} \u2014 installed @godxjp/ui ${e.version} (${n})`}function tt(e=X()){let t=e?V(e.version):null,a=V(p.version);return!e||!t||!a?null:dt(t,a)>0?`\u26A0\uFE0F SERVER OLDER THAN INSTALLED PACKAGE: this server is @godxjp/ui-mcp ${p.version}, the project has @godxjp/ui ${e.version} installed \u2014 this catalog is BEHIND the package and may be missing props and components that exist. Do not conclude from this answer that a prop is unavailable. Restart the session so the server relaunches on the installed version (run \`npx @godxjp/ui sync-rules\` first if the project's .mcp.json still pins an older @godxjp/ui-mcp).`:t.major===a.major?null:`\u26A0\uFE0F MAJOR MISMATCH: this server is @godxjp/ui-mcp ${p.version}, the project has @godxjp/ui ${e.version} installed \u2014 this catalog may describe components that do not exist in the installed package. Pin the MCP to @godxjp/ui-mcp@${e.version} (\`npx @godxjp/ui sync-rules\` updates the project's .mcp.json; a registration outside the project needs \`claude mcp remove <key>\`), then restart the agent.`}async function G(e,t){let a=await at(e,t);if(!Ze.has(e))return a;let n=X(),o=tt(n);return`${et(n)}
|
|
7954
7954
|
${o?`${o}
|
|
7955
7955
|
`:""}
|
|
7956
7956
|
${a}`}async function at(e,t){switch(e){case"list_skills":return it();case"list_primitives":return Pe(t.group,t.tier);case"list_utilities":return Tt(t.kind);case"list_patterns":return ut();case"list_anti_ai_tells":return bt(t.category);case"list_redesign_checks":return vt(t.category);case"list_audit_rules":return gt(t.category);case"list_visual_checks":return mt(t.category);case"get_anti_ai_tell":return ft(String(t.name??""));case"get_redesign_check":return yt(String(t.symptom??""));case"get_skill_section":return Le(String(t.skill??""),String(t.section??""));case"get_component":return St(String(t.name??""),t.verbose===!0);case"get_pattern":return Dt(String(t.name??""));case"get_rule":return Et(typeof t.number=="number"?t.number:void 0);case"get_vocab":return Ot(t.name==null?void 0:String(t.name));case"get_tokens":return Nt(t.category);case"list_consumer_skills":return rt();case"get_consumer_skill":return st(String(t.skill??""),String(t.section??""));case"route_consumer_task":return Oe(String(t.task??""),{consumerOnly:!0});case"draft_bug_report":return lt(t);case"check_compatibility":return ze(t.version==null?void 0:String(t.version));case"route_task":return Oe(String(t.task??""));case"suggest_primitive":return Rt(String(t.use_case??""));case"search_components":return It(String(t.query??""),t.tier);case"get_frame_coverage":return At(t.name===void 0?void 0:String(t.name));case"get_design":return nt(t.section==null?void 0:String(t.section));case"get_exemplar":return ot(t.archetype==null?"":String(t.archetype));case"lint_jsx":return zt(String(t.jsx??""));default:return`Unknown tool: ${e}`}}function nt(e){if(e===void 0||e.trim()==="")return U;let t=ke(e);if(t)return t.body;let a=j().map(n=>`- \`${n.id}\` \u2014 ${n.title}`).join(`
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@godxjp/ui-mcp",
|
|
3
|
-
"version": "33.
|
|
4
|
-
"godxUiCompatibility": "33.
|
|
3
|
+
"version": "33.5.0",
|
|
4
|
+
"godxUiCompatibility": "33.5.x",
|
|
5
5
|
"description": "Model Context Protocol server for @godxjp/ui \u2014 gives Claude Code / Codex CLI / Cursor / any MCP-aware agent live access to the component catalog, prop vocabulary, design tokens, 45 cardinal rules, copy-paste-ready patterns, 12 design / taste skills synthesised from Leonxlnx/taste-skill, 20+ anti-AI-tell patterns, and a 50-check redesign audit \u2014 token-efficient (list \u2192 drill-down).",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/index.js",
|