@godxjp/ui-mcp 26.4.0 → 27.0.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.
Files changed (2) hide show
  1. package/dist/index.js +4 -5
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -899,7 +899,7 @@ const form = useForm({ customer_nm: "", action_mode: "regist" });
899
899
  aria-label="\u6570\u91CF"
900
900
  />`},{name:"SearchInput",group:"data-entry",tagline:"Debounced search box with a clear button. Fires onSearch (NOT onChange) after the debounce. Controlled (value) or uncontrolled (defaultValue).",props:[{name:"status",type:'"error" | "warning"',description:"Validation state the field paints \u2014 Ant Design `status`. `error` also reports `aria-invalid`, so the red boundary and what a screen reader hears are one fact; `warning` paints only, because a warning is not a validity failure. antd's `success`/`validating` are not implemented: antd only draws them together with its `hasFeedback` icon slot, which FormField owns here."},{name:"variant",type:'"outlined" | "filled" | "borderless"',defaultValue:'"outlined"',description:"Chrome level \u2014 Ant Design `variant`. `outlined` is the historical field; `filled` swaps the boundary for a tinted surface (dense forms); `borderless` drops both, for a field inside a box that already draws one. antd's fourth member `underlined` is deliberately absent \u2014 a single bottom rule is a Material convention and SmartHR, the JP authority here, draws every field as a full box."},{name:"onSearch",type:"(q: string) => void",description:"Called after a changed query settles. Never fires for the initial value on mount or callback-only rerenders. Optional when filtering uses onValueChange."},{name:"value",type:"string",description:"Controlled value."},{name:"defaultValue",type:"string",defaultValue:'""',description:"Initial uncontrolled value."},{name:"placeholder",type:"string",description:"Input placeholder."},{name:"debounce",type:"number",defaultValue:"250",description:"Debounce delay (ms)."},{name:"id",type:"string",description:"Input id; pair with `label` or an external `<label htmlFor>`."},{name:"label",type:"React.ReactNode",description:"Optional visible label rendered above the search box (falls back to an sr-only label)."},{name:"onValueChange",type:"(value: string) => void",description:"Fires on EVERY keystroke (immediate) \u2014 required to keep a controlled `value` responsive."},{name:"ariaLabel",type:"string",description:"Accessible search name when there is no visible label."},{name:"disabled",type:"boolean",defaultValue:"false",description:"Disable search input and clearing."}],usage:["DO: listen to `onSearch`, not `onChange`. The component debounces internally (default 250 ms) and fires `onSearch(q)` after the delay \u2014 never wire your filter logic to `onChange` on SearchInput because it does not expose one.","DO: choose controlled vs uncontrolled deliberately. Pass `value` + `onValueChange` together for controlled mode; optionally add `onSearch` for debounced effects (e.g. when search state lives in a URL param or shared parent). For local-only ephemeral search pass only `defaultValue` + `onSearch` \u2014 omitting `value` puts the component in uncontrolled mode.","DO: supply an `ariaLabel` (or visible `label`) when no adjacent label exists. Without either prop, SearchInput falls back to the i18n key `common.search` rendered as a visually-hidden `<Label>` \u2014 still accessible, but providing a context-specific string (e.g. `ariaLabel='\u8ACB\u6C42\u66F8\u3092\u691C\u7D22'`) is more descriptive for screen readers.","DON'T: use SearchInput inside a `<form>` expecting native form submission. The component has no `name` prop and does not emit a form field value \u2014 it is a filter-trigger widget. For a form search field, use a plain `Input` inside `FormField`.","DON'T: hand-roll a debounced input when you need a search box. SearchInput ships the debounce, clear button (\xD7), search icon, and accessible label \u2014 recreating these with a raw `<Input>` adds code and misses the UX contract.","DON'T: place SearchInput inside a `ToolbarGroup` wrapper \u2014 `ToolbarGroup` is for Select/DatePicker controls with a label chip. SearchInput goes directly as a child of `Toolbar` (or standalone above a table), not wrapped in `ToolbarGroup`."],useCases:["List-page filter bar: placed as the first child of `Toolbar` (before any `ToolbarGroup` children) to drive text-based filtering of a `DataTable`. The `onSearch` callback updates a query param or state variable that the table's data fetch reads.","Inline client-side search over a small in-memory list (e.g. a sidebar nav list, a transfer panel, a settings category list) where results narrow immediately as the user types without a server round-trip \u2014 use uncontrolled mode (`defaultValue`) so no state is needed in the parent.","URL-synced search: controlled mode where `value` comes from `useSearchParams()` and `onSearch` pushes to the URL, enabling deep-linkable, bookmarkable filtered views on invoice/transaction/customer index pages.","Panel or dialog search: filtering a long dropdown list, a tree, or a multi-item selection panel that does not use the built-in `Command` palette \u2014 SearchInput provides the search box while the parent renders the filtered result set.","Toolbar search on a data-heavy accounting page (e.g. journal-entry search, partner lookup in a subledger view) where the 250 ms debounce prevents a flood of API calls on every keystroke without requiring the developer to implement debounce logic."],related:["Input \u2014 use `Input` (inside `FormField`) when the search field is part of a submitted form and needs a `name` attribute, or when you need full `onChange` control without any debounce or clear button. SearchInput is the right pick when the field only triggers filtering, not form submission.","Toolbar \u2014 SearchInput is almost always placed as a direct child of `Toolbar`, which provides the surrounding strip, clear-all button, and active-filter state. Do not use SearchInput as a standalone header widget when a full filter strip (with selects etc.) already exists \u2014 compose them together.","Command \u2014 use `Command` + `CommandInput` when you need a keyboard-navigable command palette or combobox list with grouped items and keyboard selection. `Command` is only meaningful when paired with `CommandList`; SearchInput is the right pick for a plain filter box with no item-selection behavior.","Select (with showSearch) \u2014 when users must pick a value from a list AND search to narrow it, use `<Select options={...} showSearch>` (which has its own built-in search input). SearchInput is for filtering an external data set, not for value selection from an option list."],example:`import { SearchInput } from "@godxjp/ui/data-entry";
901
901
 
902
- <SearchInput placeholder="\u30AF\u30FC\u30DD\u30F3\u540D\u30FBID\u3067\u691C\u7D22" value={search} onSearch={setSearch} />`,storyPath:"data-entry/SearchInput.stories.tsx",rules:[23]},{name:"Select",subParts:["SelectContent","SelectGroup","SelectItem","SelectLabel","SelectScrollDownButton","SelectScrollUpButton","SelectSeparator","SelectTrigger","SelectValue"],group:"data-entry",tagline:"Polymorphic single-select: pass options/loadOptions for the data-driven (Ant-style) API, or compose sub-parts manually \u2014 never use a raw <select>.",props:[{name:"mode",type:'"multiple" | "tags"',description:"Select several options; value/defaultValue become string arrays. `tags` additionally ACCEPTS what was typed (a value that is not in the list), which is antd's own split between the two."},{name:"maxCount",type:"number",description:"Maximum selected options; selected items remain removable."},{name:"maxTagCount",type:"number",description:"Collapse extra selected labels."},{name:"options",type:"(SearchSelectOptionProp | { label: string; options: SearchSelectOptionProp[] })[]",description:"Static option list. Passing this (or loadOptions) switches Select from the compound API to the data-driven API. An entry may be one of antd's nested GROUPS ({ label, options }) instead of a row; its heading becomes the same `group` a flat row carries. Rows in a foreign shape ({id,name}) go through fieldNames. Each option has { value, label, sublabel?, icon?, group?, disabled? }. `icon` (avatar / flag / lucide node) renders before the label in the rows AND on the trigger once selected. group buckets the option under an optgroup-style heading."},{name:"loadOptions",type:"(params: SearchSelectLoadParamsProp) => Promise<SearchSelectLoadResultProp>",description:"Async remote fetcher. Receives { query, page } (1-based). Must return { options, hasMore? }. Implies showSearch=true automatically."},{name:"showSearch",type:'boolean | { filterOption?: boolean | ((input, option) => boolean); optionFilterProp?: "label" | "value" | "sublabel"; filterSort?; searchValue?: string; onSearch?: (value: string) => void; autoClearSearchValue?: boolean }',defaultValue:"true when loadOptions is set or mode is multiple/tags, false otherwise",description:"Toggle the searchable combobox mode vs the plain listbox. The OBJECT form is antd's and configures the search in one place (passing it also turns search ON). NOTE the argument order: `showSearch.filterOption(input, option)` is antd's, while the long-standing top-level `filterOption(option, query)` keeps this library's; `filterOption: false` keeps every row, for a list the server already filtered."},{name:"value",type:"string",defaultValue:'""',description:"Controlled selected value (data-driven API). Pass an empty string to represent no selection."},{name:"defaultValue",type:"string",description:"Uncontrolled initial value (data-driven API). The trigger shows the matching option's label at rest \u2014 including in searchable (showSearch) mode \u2014 so an edit form pre-filled from server data renders the label, not the placeholder. Selected option is marked by a background tint (no check icon)."},{name:"onValueChange",type:"(value: string, option?: SearchSelectOptionProp) => void",description:'Change handler for the data-driven API. Receives the new value string and the matching option object. The signature follows `mode` / `labelInValue`, and a bare `(value, option) => \u2026` is inferred for each (no annotation needed under `strict`, gh#679): single `string` / `SelectOption | undefined`; `mode="multiple" | "tags"` `string[]` / `SelectOption[] | undefined`; `labelInValue` the `{ value, label }` shapes.'},{name:"renderOption",type:"(option: SearchSelectOptionProp) => React.ReactNode",description:"Custom per-option renderer for the dropdown ROWS. Defaults to label + optional sublabel. Does not change the trigger \u2014 use `labelRender` for that."},{name:"labelRender",type:"(selected: { value: string; label: React.ReactNode; option?: SearchSelectOptionProp }) => React.ReactNode",description:"Custom renderer for the SELECTED value shown on the TRIGGER (`labelRender`) \u2014 avatar + name + role badge, etc. `option` is undefined for an async preset whose page hasn't loaded. Only used while a value is selected; the placeholder still shows when empty."},{name:"selectedLabel",type:"string",description:"Display label for the current value when its option is not in the loaded page (async). Prevents a flash of the raw id."},{name:"selectedIcon",type:"React.ReactNode",description:"Leading icon shown on the trigger for the current value when its option isn't loaded yet (async preset) \u2014 the trigger counterpart of `selectedLabel`, so an edit form pre-filled from the server shows the avatar/flag at rest."},{name:"placeholder",type:"string",description:"Placeholder shown in the trigger when no value is selected."},{name:"searchPlaceholder",type:"string",description:"Placeholder inside the search input (combobox mode only)."},{name:"emptyMessage",type:"string",description:"Message rendered when the filtered list is empty."},{name:"loadingMessage",type:"string",description:"Message rendered while loadOptions is resolving."},{name:"errorMessage",type:"string",description:"Message rendered when an async loadOptions REJECTS \u2014 a distinct state from empty/loading. Defaults to a localized 'Couldn\u2019t load options'. The panel shows this instead of a blank surface or a misleading 'no results'."},{name:"clearable",type:"boolean",defaultValue:"true",description:"Show a clear row when a value is selected (data-driven API). Set to false for required fields."},{name:"clearLabel",type:"string",description:"Label for the clear row (data-driven combobox mode)."},{name:"disabled",type:"boolean",description:"Disables the entire select."},{name:"readOnly",type:"boolean",defaultValue:"false",description:"Searchable mode only (showSearch/loadOptions). Value is shown (and the clear affordance hidden) but the popover cannot be opened \u2014 no new pick, no search. Mirrors the Input/NumberInput readOnly contract: stays focusable and still submits its value, unlike disabled."},{name:"size",type:'"xs" | "sm" | "md" | "lg"',description:"Searchable mode only. Height tier forwarded to the SearchSelect trigger Button. For the compound API use SelectTrigger's own size prop instead (below)."},{name:"width",type:'"full" | "auto" | "bounded"',defaultValue:'"full"',description:"Trigger width on the data-driven API (options / loadOptions), searchable or not \u2014 the same axis SelectTrigger carries on the compound API. `full` is what a form field wants; `auto` is what a filter bar wants, so two Selects share one row instead of stacking (CONSUMER-RULES rule 5); `bounded` holds one width from --control-bounded-width for a trigger whose value varies in length. Never wrap a Select in a fixed-width Flex to get this."},{name:"open",type:"boolean",description:"Searchable mode only. Controlled popover open state (uncontrolled by default). Pair with onOpenChange."},{name:"onOpenChange",type:"(open: boolean) => void",description:"Searchable mode only. Fires on every open/close attempt \u2014 including ones ignored because open is externally pinned \u2014 so a controlled consumer stays in sync."},{name:"search",type:"string",description:"Searchable mode only. Controlled search-box query (uncontrolled by default). Pair with onSearchChange."},{name:"onSearchChange",type:"(query: string) => void",description:"Searchable mode only. Fires on every keystroke in the search box."},{name:"filterOption",type:"(option: SearchSelectOptionProp, query: string) => boolean",description:"Searchable mode only, static options (ignored with loadOptions, which owns its own server-side filtering). Overrides the default label/value substring filter. Only consulted while the query is non-empty."},{name:"renderError",type:"(params: { message: string; retry: () => void }) => React.ReactNode",description:"Searchable mode only. Custom error slot, overriding the default errorMessage row. retry() reloads from the first page."},{name:"renderLoadMore",type:"(params: { hasMore: boolean; loading: boolean; loadMore: () => void }) => React.ReactNode",description:"Searchable mode only. Custom 'load more' affordance appended below the list while another page is available \u2014 pairs with (does not replace) the built-in scroll-triggered pagination."},{name:"name",type:"string",description:"Form field name. Submits the selected value via a hidden input (data-driven API). Required for uncontrolled form submission."},{name:"id",type:"string",description:"HTML id for the trigger element. Wire to a <label htmlFor> for a11y."},{name:"className",type:"string",description:"Additional CSS classes applied to the trigger."},{name:"data-testid",type:"string",description:"Test id on the trigger. Option items get ${data-testid}-option-${value} automatically."},{name:"SelectTrigger size",type:'"sm" | "md"',defaultValue:'"md"',description:"Compound API only. Size variant on the SelectTrigger sub-component."},{name:"SelectTrigger width",type:'"full" | "auto"',defaultValue:'"full"',description:"Compound API only. `full` fills the field column (inside FormField). `auto` sizes the trigger to its label \u2014 use it in a PageContainer `extra` slot, a toolbar or a footer row, where a full-width trigger swallows the row and truncates its siblings."},{name:"SelectTrigger showIndicator",type:"boolean",defaultValue:"true",description:"Compound API only. Set false to omit the built-in chevron disclosure indicator from the DOM entirely (not a CSS hide) \u2014 for specialized triggers (icon-only, etc.) that render their own affordance, so no consumer descendant CSS is needed."},{name:"status",type:'"error" | "warning"',description:"antd `status`. `error` recolours the control AND sets aria-invalid (a colour-only error fails WCAG 2.2 SC 1.4.1); `warning` recolours only. An aria-invalid injected by FormField always wins."},{name:"variant",type:'"outlined" | "filled" | "borderless"',description:"antd `variant` \u2014 the control surface. Default `outlined`. Drawn from --control-{surface,filled,borderless}-* tokens, so a theme retunes all three at once."},{name:"loading",type:"boolean",description:"antd `loading` \u2014 the FIELD is in flight: the trailing chevron becomes a spinner and the trigger reports aria-busy. Distinct from the list-level spinner an async loadOptions shows inside the popup."},{name:"defaultOpen",type:"boolean",description:"antd `defaultOpen` \u2014 uncontrolled initial popup state."},{name:"allowClear",type:"boolean | { clearIcon?: React.ReactNode; label?: string }",description:"antd `allowClear`. The object form replaces the \u2715 icon and/or its accessible label. Beats `clearable` when both are given."},{name:"onClear",type:"() => void",description:"antd `onClear` \u2014 fires after the value is cleared through the \u2715."},{name:"notFoundContent",type:"React.ReactNode",description:"antd `notFoundContent` \u2014 the node shown when the popup has nothing to list. Outranks the string-only emptyMessage."},{name:"autoClearSearchValue",type:"boolean",description:"antd `autoClearSearchValue` (default true) \u2014 clear the search box after a pick / on close. Set false to resume the same filtered list on the next open."},{name:"filterSort",type:"(a: SearchSelectOptionProp, b: SearchSelectOptionProp, info: { searchValue: string }) => number",description:"antd `filterSort` \u2014 orders what filterOption kept. Static options only; with loadOptions the server owns the order. Never mutates the caller's array."},{name:"optionRender",type:"(option: SearchSelectOptionProp, info: { index: number }) => React.ReactNode",description:"antd `optionRender` \u2014 per-option renderer in antd's own (option, { index }) shape. Outranks the older renderOption."},{name:"menuItemSelectedIcon",type:"React.ReactNode",description:"antd `menuItemSelectedIcon` \u2014 a decorative mark on the picked row. Off by default: the picked row is already marked by fill + weight, which costs no width."},{name:"popupMatchSelectWidth",type:"boolean | number",description:"antd `popupMatchSelectWidth`. true (default) pins the popup to the trigger width, false lets it hug its rows, a number pins it to that many pixels."},{name:"fieldNames",type:"{ label?: string; value?: string; options?: string; groupLabel?: string; disabled?: string }",description:"antd `fieldNames` \u2014 read rows in a FOREIGN shape ({id,name,children}) without copying them into a second array first. The same spelling Cascader and TreeSelect take. A foreign row needs a cast at the call site, exactly as theirs does."},{name:"labelInValue",type:"boolean",description:"antd `labelInValue` \u2014 value and onValueChange speak {value,label} instead of a bare string (an array of them in multiple/tags). It earns its place on the async EDIT form: a record holding {value:'52',label:'\u6771\u4EAC\u672C\u793E'} renders the pick immediately, with no options page loaded and no flash of the raw id."},{name:"prefix",type:"React.ReactNode",description:"antd `prefix` \u2014 a node pinned BEFORE the value on the trigger (a currency mark, an icon). Deliberately not aria-hidden: for role=combobox the trigger's text is the VALUE, and a prefix that is part of the value belongs in it. The NAME still comes from the label."},{name:"suffixIcon",type:"React.ReactNode",description:"antd `suffixIcon` \u2014 replaces the trailing chevron; `null` removes the indicator entirely (antd's own replacement for the deprecated showArrow). While a value is clearable the \u2715 owns that seat, as in antd."},{name:"placement",type:'"bottomStart" | "bottomEnd" | "topStart" | "topEnd"',description:"antd `placement`, spelled on the LOGICAL inline axis (antd's bottomLeft/topRight cannot mirror for an RTL layout). Absent = below, start-aligned, with collision flipping \u2014 what a picker wants."},{name:"popupRender",type:"(originNode: React.ReactNode) => React.ReactNode",description:"antd `popupRender` \u2014 wrap the popup's own node to add a footer, a 'create' action or a hint line. It must still render originNode: dropping it leaves a popup with no options."},{name:"listHeight",type:"number",description:"antd `listHeight` \u2014 the option list's max height in px for THIS instance. It overrides the --select-content-max-height token rather than hard-coding a height, so the token stays the default everywhere else."},{name:"onPopupScroll",type:"(event: React.UIEvent<HTMLElement>) => void",description:"antd `onPopupScroll` \u2014 fires on the option list's own scroll. It runs BESIDE the built-in infinite scroll (loadOptions paging), never instead of it."},{name:"optionFilterProp",type:'"label" | "value" | "sublabel"',description:"antd `optionFilterProp` \u2014 which field the default filter matches while searching. Unset matches BOTH label and value (this library's long-standing behaviour); antd's own default is value alone."},{name:"tokenSeparators",type:"string[]",description:"antd `tokenSeparators` (multiple/tags) \u2014 characters that commit what has been typed. Typing or PASTING 'a,b,c' commits three values in ONE onValueChange. A pasted run is read off the clipboard, so a '\\n' separator works even though a single-line input strips newlines."},{name:"maxTagTextLength",type:"number",description:"antd `maxTagTextLength` (multiple/tags) \u2014 cut each chip's TEXT to this many characters (an ellipsis marks the cut). The value keeps its whole label."},{name:"tagRender",type:"(props: { value: string; label: React.ReactNode; onClose: () => void; index: number; disabled: boolean }) => React.ReactNode",description:"antd `tagRender` (multiple/tags) \u2014 render one chip yourself. Exactly the shape TagInput's tagRender takes. The onClose handed in is the same remover the built-in \u2715 calls, so a custom chip cannot end up unremovable; supplying tagRender withdraws the built-in \u2715."},{name:"onSelect / onDeselect",type:"(value: string, option: SearchSelectOptionProp) => void",description:"antd `onSelect` / `onDeselect` \u2014 fires as one option JOINS or LEAVES the selection, beside onValueChange (which reports the whole value)."},{name:"maxTagPlaceholder",type:"React.ReactNode | ((omitted: { value: string; label: React.ReactNode }[]) => React.ReactNode)",description:"antd `maxTagPlaceholder` \u2014 the node standing in for the values maxTagCount hid. A function receives the omitted values, so '+3 \u4EF6' or a tooltip listing them is possible."}],usage:["DO use the data-driven API (options/loadOptions) for straightforward selects \u2014 it handles grouping, search, async, and custom rendering automatically. Only reach for the compound API when you need to inject arbitrary content into the trigger or listbox.","DO pass name= on the data-driven Select so the value is submitted with a native form or Inertia useForm. Without name= the value is React-only and will not appear in form data.","READING THE SELECTED CODE FROM THE DOM: the trigger publishes `data-value` = the selected VALUE, alongside the `data-field` key it inherits from FormField. Use that in e2e tests and screen automation \u2014 the trigger's visible text is the option LABEL (\u6771\u4EAC\u672C\u793E), and the only other place the code lives is the aria-hidden, 1px-clipped native <select> react-aria renders so a native submit (and browser autofill) carries the value. `data-value` is absent while nothing is selected, and it tracks uncontrolled picks too.","DO use loadOptions + selectedLabel together for async selects: selectedLabel prevents a flash of the raw id string while the first page loads.","DO name the control with FormField, aria-label, or a <label htmlFor> pointing at the trigger id \u2014 all three work. (An earlier version of this entry said htmlFor does NOT name the trigger. That was wrong: the trigger is a <button>, which is a labelable element, so <label for> does name it \u2014 src/components/data-entry/__tests__/select-rac.test.tsx pins it on both the old Radix base and the react-aria one, because 17 godx-task files name their Selects exactly that way.) What role=combobox does NOT do is take a name from its own content, so the visible value is the VALUE, never the name \u2014 a Select with no label of any kind is anonymous. Wrapping in <FormField label=\u2026> stays the route that also wires helper, error and required. This holds for BOTH APIs; anything set directly on SelectTrigger wins.","DO treat loading / no-options / error / disabled as DISTINCT states. A data-driven Select never opens a blank popover: a static options=[] list auto-disables the trigger (opening it would show nothing), while an async loadOptions shows a loading row, then either the options, a localized empty affordance (override with emptyMessage), or an error affordance if the fetch rejects (override with errorMessage). Disable the Select when there is nothing to pick AND no async loader; keep it enabled (it opens to load/search) whenever loadOptions is set.","DON'T mix the two APIs: once you pass options or loadOptions, Select is data-driven \u2014 all compound sub-parts (SelectTrigger, SelectContent, SelectItem) are rendered internally. Do not wrap them manually.","DON'T use a raw <select> element. Select is the one control for all single-select use cases. The only allowed raw <select> is a hidden aria-hidden sr-only element kept as an e2e hook paired with a visible Select.","COMPOUND API sub-parts (when NOT using options/loadOptions): Select \u2192 SelectTrigger (contains SelectValue) \u2192 SelectContent \u2192 SelectItem. Optionally wrap items in SelectGroup + SelectLabel for headings, or add SelectSeparator between sections.","DO reach for open/onOpenChange (searchable mode) to drive the popover from outside \u2014 e.g. opening it programmatically after a validation error \u2014 and search/onSearchChange to seed or read the query text. Both fall back to internal state when omitted; onOpenChange/onSearchChange still fire either way so a controlled consumer stays in sync.","DO use readOnly (searchable mode) for a value that must stay visible and submittable but not editable in this view \u2014 it differs from disabled: the control stays focusable and its value still submits. clearable is ignored while readOnly.","DO use filterOption (searchable mode, static options) when the default label/value substring match isn't right \u2014 e.g. filtering by a hidden code field. It is NOT consulted when loadOptions is set (that fetcher owns its own filtering).","DO use renderError + renderLoadMore (searchable mode) to replace the default error row with a branded retry affordance, or to pair a manual 'load more' button with (not instead of) the built-in scroll-triggered pagination.","DO set SelectTrigger showIndicator={false} (compound API) on a specialized trigger \u2014 icon-only, or one with its own affordance \u2014 instead of hiding [data-slot=select-chevron] with consumer CSS."],useCases:["Status filter on an invoice list \u2014 pass options=[{value:'draft',label:'Draft'},{value:'paid',label:'Paid'}] with onChange to drive a query param; no search needed so omit showSearch.","Legal-entity switcher \u2014 static options list with showSearch=true for client-side filtering when there are many entities; use selectedLabel to show the entity name before the full list loads.","Account category picker backed by an API \u2014 pass loadOptions to stream pages of accounts as the user types; use renderOption to show account code + name side by side; pass selectedLabel so the trigger shows the name on first render.","Grouped currency picker \u2014 set option.group='Asia' / 'Europe' on each option; the plain (non-search) data-driven mode renders SelectGroup headings automatically.","Form field in an accounting entry \u2014 use the compound API when the trigger must show a currency flag icon alongside the SelectValue; wire SelectTrigger size='sm' for dense table rows.","Required department select in a HR form \u2014 pass clearable=false so the user cannot clear the field once set; pair with name='department_id' for Inertia useForm submission.","Async account picker whose API can fail \u2014 pass loadOptions plus errorMessage so a rejected fetch shows a clear error affordance in the panel (not a blank surface or a false 'no results'); the loading and empty states are handled automatically."],related:["Segmented \u2014 the same choice when the option set is small and worth showing at once. Select hides its options behind a trigger; Segmented lays them out, which reads better for 2-4 mutually exclusive options.","SearchSelect \u2014 the combobox engine Select delegates to when showSearch=true or loadOptions is set. Prefer Select with showSearch instead of reaching for SearchSelect directly (SearchSelect is now deprecated as a public API).","TreeSelect \u2014 use when options are hierarchical (parent/child tree). Not a drop-in for Select; has expand/collapse and a separate treeData prop.","Select with showSearch \u2014 use Select (with the `showSearch` prop) for typeahead/autocomplete lookup patterns instead of the removed Autocomplete component.","RadioGroup \u2014 use instead of Select when there are 2-4 mutually exclusive choices that must all be visible at once without opening a popover.","Combobox (if present) \u2014 compound cmdk-powered combobox for free-text + suggestion; Select is for strict value lists only."],example:`import {
902
+ <SearchInput placeholder="\u30AF\u30FC\u30DD\u30F3\u540D\u30FBID\u3067\u691C\u7D22" value={search} onSearch={setSearch} />`,storyPath:"data-entry/SearchInput.stories.tsx",rules:[23]},{name:"Select",subParts:["SelectContent","SelectGroup","SelectItem","SelectLabel","SelectScrollDownButton","SelectScrollUpButton","SelectSeparator","SelectTrigger","SelectValue"],group:"data-entry",tagline:"Polymorphic single-select: pass options/loadOptions for the data-driven (Ant-style) API, or compose sub-parts manually \u2014 never use a raw <select>.",props:[{name:"mode",type:'"multiple" | "tags"',description:"Select several options; value/defaultValue become string arrays. `tags` additionally ACCEPTS what was typed (a value that is not in the list), which is antd's own split between the two."},{name:"maxCount",type:"number",description:"Maximum selected options; selected items remain removable."},{name:"maxTagCount",type:"number",description:"Collapse extra selected labels."},{name:"options",type:"(SearchSelectOptionProp | { label: string; options: SearchSelectOptionProp[] })[]",description:"Static option list. Passing this (or loadOptions) switches Select from the compound API to the data-driven API. An entry may be one of antd's nested GROUPS ({ label, options }) instead of a row; its heading becomes the same `group` a flat row carries. Rows in a foreign shape ({id,name}) go through fieldNames. Each option has { value, label, sublabel?, icon?, group?, disabled? }. `icon` (avatar / flag / lucide node) renders before the label in the rows AND on the trigger once selected. group buckets the option under an optgroup-style heading."},{name:"loadOptions",type:"(params: SearchSelectLoadParamsProp) => Promise<SearchSelectLoadResultProp>",description:"Async remote fetcher. Receives { query, page } (1-based). Must return { options, hasMore? }. Implies showSearch=true automatically."},{name:"showSearch",type:'boolean | { filterOption?: boolean | ((input, option) => boolean); optionFilterProp?: "label" | "value" | "sublabel"; filterSort?; searchValue?: string; onSearch?: (value: string) => void; autoClearSearchValue?: boolean }',defaultValue:"true when loadOptions is set or mode is multiple/tags, false otherwise",description:"Toggle the searchable combobox mode vs the plain listbox. The OBJECT form is antd's and configures the search in one place (passing it also turns search ON). NOTE the argument order: `showSearch.filterOption(input, option)` is antd's, while the long-standing top-level `filterOption(option, query)` keeps this library's; `filterOption: false` keeps every row, for a list the server already filtered."},{name:"value",type:"string",defaultValue:'""',description:"Controlled selected value (data-driven API). Pass an empty string to represent no selection."},{name:"defaultValue",type:"string",description:"Uncontrolled initial value (data-driven API). The trigger shows the matching option's label at rest \u2014 including in searchable (showSearch) mode \u2014 so an edit form pre-filled from server data renders the label, not the placeholder. Selected option is marked by a background tint (no check icon)."},{name:"onValueChange",type:"(value: string, option?: SearchSelectOptionProp) => void",description:'Change handler for the data-driven API. Receives the new value string and the matching option object. The signature follows `mode` / `labelInValue`, and a bare `(value, option) => \u2026` is inferred for each (no annotation needed under `strict`, gh#679): single `string` / `SelectOption | undefined`; `mode="multiple" | "tags"` `string[]` / `SelectOption[] | undefined`; `labelInValue` the `{ value, label }` shapes.'},{name:"renderOption",type:"(option: SearchSelectOptionProp) => React.ReactNode",description:"Custom per-option renderer for the dropdown ROWS. Defaults to label + optional sublabel. Does not change the trigger \u2014 use `labelRender` for that."},{name:"labelRender",type:"(selected: { value: string; label: React.ReactNode; option?: SearchSelectOptionProp }) => React.ReactNode",description:"Custom renderer for the SELECTED value shown on the TRIGGER (`labelRender`) \u2014 avatar + name + role badge, etc. `option` is undefined for an async preset whose page hasn't loaded. Only used while a value is selected; the placeholder still shows when empty."},{name:"selectedLabel",type:"string",description:"Display label for the current value when its option is not in the loaded page (async). Prevents a flash of the raw id."},{name:"selectedIcon",type:"React.ReactNode",description:"Leading icon shown on the trigger for the current value when its option isn't loaded yet (async preset) \u2014 the trigger counterpart of `selectedLabel`, so an edit form pre-filled from the server shows the avatar/flag at rest."},{name:"placeholder",type:"string",description:"Placeholder shown in the trigger when no value is selected."},{name:"searchPlaceholder",type:"string",description:"Placeholder inside the search input (combobox mode only)."},{name:"emptyMessage",type:"string",description:"Message rendered when the filtered list is empty."},{name:"loadingMessage",type:"string",description:"Message rendered while loadOptions is resolving."},{name:"errorMessage",type:"string",description:"Message rendered when an async loadOptions REJECTS \u2014 a distinct state from empty/loading. Defaults to a localized 'Couldn\u2019t load options'. The panel shows this instead of a blank surface or a misleading 'no results'."},{name:"clearable",type:"boolean",defaultValue:"false",description:"Show the clear \u2715 when a value is selected (data-driven API). Off by default, as antd `allowClear` \u2014 a required select is never one click from empty. Pass it (or `allowClear`) on an optional field whose empty state is a valid answer."},{name:"clearLabel",type:"string",description:"Label for the clear row (data-driven combobox mode)."},{name:"disabled",type:"boolean",description:"Disables the entire select."},{name:"readOnly",type:"boolean",defaultValue:"false",description:"Searchable mode only (showSearch/loadOptions). Value is shown (and the clear affordance hidden) but the popover cannot be opened \u2014 no new pick, no search. Mirrors the Input/NumberInput readOnly contract: stays focusable and still submits its value, unlike disabled."},{name:"size",type:'"xs" | "sm" | "md" | "lg"',description:"Searchable mode only. Height tier forwarded to the SearchSelect trigger Button. For the compound API use SelectTrigger's own size prop instead (below)."},{name:"width",type:'"full" | "auto" | "bounded"',defaultValue:'"full"',description:"Trigger width on the data-driven API (options / loadOptions), searchable or not \u2014 the same axis SelectTrigger carries on the compound API. `full` is what a form field wants; `auto` is what a filter bar wants, so two Selects share one row instead of stacking (CONSUMER-RULES rule 5); `bounded` holds one width from --control-bounded-width for a trigger whose value varies in length. Never wrap a Select in a fixed-width Flex to get this."},{name:"open",type:"boolean",description:"Searchable mode only. Controlled popover open state (uncontrolled by default). Pair with onOpenChange."},{name:"onOpenChange",type:"(open: boolean) => void",description:"Searchable mode only. Fires on every open/close attempt \u2014 including ones ignored because open is externally pinned \u2014 so a controlled consumer stays in sync."},{name:"search",type:"string",description:"Searchable mode only. Controlled search-box query (uncontrolled by default). Pair with onSearchChange."},{name:"onSearchChange",type:"(query: string) => void",description:"Searchable mode only. Fires on every keystroke in the search box."},{name:"filterOption",type:"(option: SearchSelectOptionProp, query: string) => boolean",description:"Searchable mode only, static options (ignored with loadOptions, which owns its own server-side filtering). Overrides the default label/value substring filter. Only consulted while the query is non-empty."},{name:"renderError",type:"(params: { message: string; retry: () => void }) => React.ReactNode",description:"Searchable mode only. Custom error slot, overriding the default errorMessage row. retry() reloads from the first page."},{name:"renderLoadMore",type:"(params: { hasMore: boolean; loading: boolean; loadMore: () => void }) => React.ReactNode",description:"Searchable mode only. Custom 'load more' affordance appended below the list while another page is available \u2014 pairs with (does not replace) the built-in scroll-triggered pagination."},{name:"name",type:"string",description:"Form field name. Submits the selected value via a hidden input (data-driven API). Required for uncontrolled form submission."},{name:"id",type:"string",description:"HTML id for the trigger element. Wire to a <label htmlFor> for a11y."},{name:"className",type:"string",description:"Additional CSS classes applied to the trigger."},{name:"data-testid",type:"string",description:"Test id on the trigger. Option items get ${data-testid}-option-${value} automatically."},{name:"SelectTrigger size",type:'"sm" | "md"',defaultValue:'"md"',description:"Compound API only. Size variant on the SelectTrigger sub-component."},{name:"SelectTrigger width",type:'"full" | "auto"',defaultValue:'"full"',description:"Compound API only. `full` fills the field column (inside FormField). `auto` sizes the trigger to its label \u2014 use it in a PageContainer `extra` slot, a toolbar or a footer row, where a full-width trigger swallows the row and truncates its siblings."},{name:"SelectTrigger showIndicator",type:"boolean",defaultValue:"true",description:"Compound API only. Set false to omit the built-in chevron disclosure indicator from the DOM entirely (not a CSS hide) \u2014 for specialized triggers (icon-only, etc.) that render their own affordance, so no consumer descendant CSS is needed."},{name:"status",type:'"error" | "warning"',description:"antd `status`. `error` recolours the control AND sets aria-invalid (a colour-only error fails WCAG 2.2 SC 1.4.1); `warning` recolours only. An aria-invalid injected by FormField always wins."},{name:"variant",type:'"outlined" | "filled" | "borderless"',description:"antd `variant` \u2014 the control surface. Default `outlined`. Drawn from --control-{surface,filled,borderless}-* tokens, so a theme retunes all three at once."},{name:"loading",type:"boolean",description:"antd `loading` \u2014 the FIELD is in flight: the trailing chevron becomes a spinner and the trigger reports aria-busy. Distinct from the list-level spinner an async loadOptions shows inside the popup."},{name:"defaultOpen",type:"boolean",description:"antd `defaultOpen` \u2014 uncontrolled initial popup state."},{name:"allowClear",type:"boolean | { clearIcon?: React.ReactNode; label?: string }",defaultValue:"false",description:"antd `allowClear` \u2014 the clear \u2715 on the trigger while a value is selected. Default false, as in antd. The object form replaces the \u2715 icon and/or its accessible label. Beats `clearable` when both are given."},{name:"onClear",type:"() => void",description:"antd `onClear` \u2014 fires after the value is cleared through the \u2715."},{name:"notFoundContent",type:"React.ReactNode",description:"antd `notFoundContent` \u2014 the node shown when the popup has nothing to list. Outranks the string-only emptyMessage."},{name:"autoClearSearchValue",type:"boolean",description:"antd `autoClearSearchValue` (default true) \u2014 clear the search box after a pick / on close. Set false to resume the same filtered list on the next open."},{name:"filterSort",type:"(a: SearchSelectOptionProp, b: SearchSelectOptionProp, info: { searchValue: string }) => number",description:"antd `filterSort` \u2014 orders what filterOption kept. Static options only; with loadOptions the server owns the order. Never mutates the caller's array."},{name:"optionRender",type:"(option: SearchSelectOptionProp, info: { index: number }) => React.ReactNode",description:"antd `optionRender` \u2014 per-option renderer in antd's own (option, { index }) shape. Outranks the older renderOption."},{name:"menuItemSelectedIcon",type:"React.ReactNode",description:"antd `menuItemSelectedIcon` \u2014 a decorative mark on the picked row. Off by default: the picked row is already marked by fill + weight, which costs no width."},{name:"popupMatchSelectWidth",type:"boolean | number",description:"antd `popupMatchSelectWidth`. true (default) pins the popup to the trigger width, false lets it hug its rows, a number pins it to that many pixels."},{name:"fieldNames",type:"{ label?: string; value?: string; options?: string; groupLabel?: string; disabled?: string }",description:"antd `fieldNames` \u2014 read rows in a FOREIGN shape ({id,name,children}) without copying them into a second array first. The same spelling Cascader and TreeSelect take. A foreign row needs a cast at the call site, exactly as theirs does."},{name:"labelInValue",type:"boolean",description:"antd `labelInValue` \u2014 value and onValueChange speak {value,label} instead of a bare string (an array of them in multiple/tags). It earns its place on the async EDIT form: a record holding {value:'52',label:'\u6771\u4EAC\u672C\u793E'} renders the pick immediately, with no options page loaded and no flash of the raw id."},{name:"prefix",type:"React.ReactNode",description:"antd `prefix` \u2014 a node pinned BEFORE the value on the trigger (a currency mark, an icon). Deliberately not aria-hidden: for role=combobox the trigger's text is the VALUE, and a prefix that is part of the value belongs in it. The NAME still comes from the label."},{name:"suffixIcon",type:"React.ReactNode",description:"antd `suffixIcon` \u2014 replaces the trailing chevron; `null` removes the indicator entirely (antd's own replacement for the deprecated showArrow). While a value is clearable the \u2715 owns that seat, as in antd."},{name:"placement",type:'"bottomStart" | "bottomEnd" | "topStart" | "topEnd"',description:"antd `placement`, spelled on the LOGICAL inline axis (antd's bottomLeft/topRight cannot mirror for an RTL layout). Absent = below, start-aligned, with collision flipping \u2014 what a picker wants."},{name:"popupRender",type:"(originNode: React.ReactNode) => React.ReactNode",description:"antd `popupRender` \u2014 wrap the popup's own node to add a footer, a 'create' action or a hint line. It must still render originNode: dropping it leaves a popup with no options."},{name:"listHeight",type:"number",description:"antd `listHeight` \u2014 the option list's max height in px for THIS instance. It overrides the --select-content-max-height token rather than hard-coding a height, so the token stays the default everywhere else."},{name:"onPopupScroll",type:"(event: React.UIEvent<HTMLElement>) => void",description:"antd `onPopupScroll` \u2014 fires on the option list's own scroll. It runs BESIDE the built-in infinite scroll (loadOptions paging), never instead of it."},{name:"optionFilterProp",type:'"label" | "value" | "sublabel"',description:"antd `optionFilterProp` \u2014 which field the default filter matches while searching. Unset matches BOTH label and value (this library's long-standing behaviour); antd's own default is value alone."},{name:"tokenSeparators",type:"string[]",description:"antd `tokenSeparators` (multiple/tags) \u2014 characters that commit what has been typed. Typing or PASTING 'a,b,c' commits three values in ONE onValueChange. A pasted run is read off the clipboard, so a '\\n' separator works even though a single-line input strips newlines."},{name:"maxTagTextLength",type:"number",description:"antd `maxTagTextLength` (multiple/tags) \u2014 cut each chip's TEXT to this many characters (an ellipsis marks the cut). The value keeps its whole label."},{name:"tagRender",type:"(props: { value: string; label: React.ReactNode; onClose: () => void; index: number; disabled: boolean }) => React.ReactNode",description:"antd `tagRender` (multiple/tags) \u2014 render one chip yourself. Exactly the shape TagInput's tagRender takes. The onClose handed in is the same remover the built-in \u2715 calls, so a custom chip cannot end up unremovable; supplying tagRender withdraws the built-in \u2715."},{name:"onSelect / onDeselect",type:"(value: string, option: SearchSelectOptionProp) => void",description:"antd `onSelect` / `onDeselect` \u2014 fires as one option JOINS or LEAVES the selection, beside onValueChange (which reports the whole value)."},{name:"maxTagPlaceholder",type:"React.ReactNode | ((omitted: { value: string; label: React.ReactNode }[]) => React.ReactNode)",description:"antd `maxTagPlaceholder` \u2014 the node standing in for the values maxTagCount hid. A function receives the omitted values, so '+3 \u4EF6' or a tooltip listing them is possible."}],usage:["DO use the data-driven API (options/loadOptions) for straightforward selects \u2014 it handles grouping, search, async, and custom rendering automatically. Only reach for the compound API when you need to inject arbitrary content into the trigger or listbox.","DO pass name= on the data-driven Select so the value is submitted with a native form or Inertia useForm. Without name= the value is React-only and will not appear in form data.","READING THE SELECTED CODE FROM THE DOM: the trigger publishes `data-value` = the selected VALUE, alongside the `data-field` key it inherits from FormField. Use that in e2e tests and screen automation \u2014 the trigger's visible text is the option LABEL (\u6771\u4EAC\u672C\u793E), and the only other place the code lives is the aria-hidden, 1px-clipped native <select> react-aria renders so a native submit (and browser autofill) carries the value. `data-value` is absent while nothing is selected, and it tracks uncontrolled picks too.","DO use loadOptions + selectedLabel together for async selects: selectedLabel prevents a flash of the raw id string while the first page loads.","DO name the control with FormField, aria-label, or a <label htmlFor> pointing at the trigger id \u2014 all three work. (An earlier version of this entry said htmlFor does NOT name the trigger. That was wrong: the trigger is a <button>, which is a labelable element, so <label for> does name it \u2014 src/components/data-entry/__tests__/select-rac.test.tsx pins it on both the old Radix base and the react-aria one, because 17 godx-task files name their Selects exactly that way.) What role=combobox does NOT do is take a name from its own content, so the visible value is the VALUE, never the name \u2014 a Select with no label of any kind is anonymous. Wrapping in <FormField label=\u2026> stays the route that also wires helper, error and required. This holds for BOTH APIs; anything set directly on SelectTrigger wins.","DO treat loading / no-options / error / disabled as DISTINCT states. A data-driven Select never opens a blank popover: a static options=[] list auto-disables the trigger (opening it would show nothing), while an async loadOptions shows a loading row, then either the options, a localized empty affordance (override with emptyMessage), or an error affordance if the fetch rejects (override with errorMessage). Disable the Select when there is nothing to pick AND no async loader; keep it enabled (it opens to load/search) whenever loadOptions is set.","DON'T mix the two APIs: once you pass options or loadOptions, Select is data-driven \u2014 all compound sub-parts (SelectTrigger, SelectContent, SelectItem) are rendered internally. Do not wrap them manually.","DON'T use a raw <select> element. Select is the one control for all single-select use cases. The only allowed raw <select> is a hidden aria-hidden sr-only element kept as an e2e hook paired with a visible Select.","COMPOUND API sub-parts (when NOT using options/loadOptions): Select \u2192 SelectTrigger (contains SelectValue) \u2192 SelectContent \u2192 SelectItem. Optionally wrap items in SelectGroup + SelectLabel for headings, or add SelectSeparator between sections.","DO reach for open/onOpenChange (searchable mode) to drive the popover from outside \u2014 e.g. opening it programmatically after a validation error \u2014 and search/onSearchChange to seed or read the query text. Both fall back to internal state when omitted; onOpenChange/onSearchChange still fire either way so a controlled consumer stays in sync.","DO use readOnly (searchable mode) for a value that must stay visible and submittable but not editable in this view \u2014 it differs from disabled: the control stays focusable and its value still submits. allowClear / clearable is ignored while readOnly.","DO use filterOption (searchable mode, static options) when the default label/value substring match isn't right \u2014 e.g. filtering by a hidden code field. It is NOT consulted when loadOptions is set (that fetcher owns its own filtering).","DO use renderError + renderLoadMore (searchable mode) to replace the default error row with a branded retry affordance, or to pair a manual 'load more' button with (not instead of) the built-in scroll-triggered pagination.","DO set SelectTrigger showIndicator={false} (compound API) on a specialized trigger \u2014 icon-only, or one with its own affordance \u2014 instead of hiding [data-slot=select-chevron] with consumer CSS."],useCases:["Status filter on an invoice list \u2014 pass options=[{value:'draft',label:'Draft'},{value:'paid',label:'Paid'}] with onChange to drive a query param; no search needed so omit showSearch.","Legal-entity switcher \u2014 static options list with showSearch=true for client-side filtering when there are many entities; use selectedLabel to show the entity name before the full list loads.","Account category picker backed by an API \u2014 pass loadOptions to stream pages of accounts as the user types; use renderOption to show account code + name side by side; pass selectedLabel so the trigger shows the name on first render.","Grouped currency picker \u2014 set option.group='Asia' / 'Europe' on each option; the plain (non-search) data-driven mode renders SelectGroup headings automatically.","Form field in an accounting entry \u2014 use the compound API when the trigger must show a currency flag icon alongside the SelectValue; wire SelectTrigger size='sm' for dense table rows.","Required department select in a HR form \u2014 leave allowClear off (the default) so the user cannot clear the field once set; pair with name='department_id' for Inertia useForm submission. An OPTIONAL filter select passes allowClear so the user can return to 'no filter'.","Async account picker whose API can fail \u2014 pass loadOptions plus errorMessage so a rejected fetch shows a clear error affordance in the panel (not a blank surface or a false 'no results'); the loading and empty states are handled automatically."],related:["Segmented \u2014 the same choice when the option set is small and worth showing at once. Select hides its options behind a trigger; Segmented lays them out, which reads better for 2-4 mutually exclusive options.","SearchSelect \u2014 the combobox engine Select delegates to when showSearch=true or loadOptions is set. Prefer Select with showSearch instead of reaching for SearchSelect directly (SearchSelect is now deprecated as a public API).","TreeSelect \u2014 use when options are hierarchical (parent/child tree). Not a drop-in for Select; has expand/collapse and a separate treeData prop.","Select with showSearch \u2014 use Select (with the `showSearch` prop) for typeahead/autocomplete lookup patterns instead of the removed Autocomplete component.","RadioGroup \u2014 use instead of Select when there are 2-4 mutually exclusive choices that must all be visible at once without opening a popover.","Combobox (if present) \u2014 compound cmdk-powered combobox for free-text + suggestion; Select is for strict value lists only."],example:`import {
903
903
  Select,
904
904
  SelectContent,
905
905
  SelectGroup,
@@ -947,7 +947,6 @@ export function CurrencySelect({ value, onChange }) {
947
947
  ]}
948
948
  placeholder="Select currency"
949
949
  searchPlaceholder="Search currencies\u2026"
950
- clearable={false}
951
950
  name="currency"
952
951
  />
953
952
  );
@@ -1257,7 +1256,7 @@ export function CutoffTimeForm() {
1257
1256
  <Button type="submit">Save</Button>
1258
1257
  </form>
1259
1258
  );
1260
- }`,storyPath:"data-entry/TimePicker.stories.tsx",rules:[3,6,13,23]},{name:"Cascader",group:"data-entry",tagline:"Multi-level hierarchical path picker (Popover + cascading columns); value is always a string[] path, never a flat ID \u2014 passing a bare string breaks it.",props:[{name:"name",type:"string",description:"Native form field name; repeated values for multiple paths."},{name:"readOnly",type:"boolean",description:"Prevent edits while preserving the displayed value."},{name:"options",type:"TreeOptionProp[]",required:!0,description:"The hierarchical option tree. Each node has { value: string; label: ReactNode; disabled?: boolean; isLeaf?: boolean; children?: TreeOptionProp[] }. Normalised internally via fieldNames."},{name:"value",type:"string[] | string[][]",description:"Controlled value. Single mode: string[] path (e.g. ['vn','hcm','q1']). Multiple mode: string[][] array of paths."},{name:"defaultValue",type:"string[] | string[][]",description:"Initial value for uncontrolled mode. Same shape as value."},{name:"onValueChange",type:"(value: string[] | string[][], selectedOptions?: TreeOptionProp[] | TreeOptionProp[][]) => void",description:"Fires when selection changes. First arg is the selected path(s); second is the matching node objects. On clear, called with []."},{name:"multiple",type:"boolean",defaultValue:"false",description:"Enable multi-path selection. Renders checkboxes in columns and search results. Panel stays open on each pick. value/defaultValue become string[][]."},{name:"changeOnSelect",type:"boolean",defaultValue:"false",description:"When true, clicking any node (including branch nodes with children) commits that path immediately instead of waiting for a leaf selection."},{name:"showSearch",type:"boolean",defaultValue:"false",description:"Renders a CommandInput at the top of the popover. Filters to matching leaf paths across the whole tree when a query is typed; reverts to cascade columns when the query is cleared."},{name:"placeholder",type:"string",description:"Trigger button placeholder text when no value is selected. Defaults to the i18n key dataEntry.cascader.placeholder."},{name:"disabled",type:"boolean",defaultValue:"false",description:"Disables the trigger button and prevents the popover from opening."},{name:"expandTrigger",type:'"click" | "hover"',defaultValue:'"click"',description:"How child columns are expanded. 'hover' expands on mouseenter and collapses back on mouseleave."},{name:"fieldNames",type:"TreeFieldNamesProp",description:"Remap custom data keys: { label?: string; value?: string; children?: string }. Use when your data uses e.g. 'name' and 'id' instead of 'label' and 'value'."},{name:"allowClear",type:"boolean",defaultValue:"true",description:"Shows an X icon on the trigger when a value is selected. Clicking it calls onChange([]) and resets to placeholder."},{name:"className",type:"string",description:"Extra Tailwind classes applied to the trigger button."},{name:"id",type:"string",description:"HTML id forwarded to the trigger button. Use to associate a <label htmlFor>."},{name:"status",type:'"error" | "warning"',description:"antd `status`. `error` recolours the control AND sets aria-invalid (a colour-only error fails WCAG 2.2 SC 1.4.1); `warning` recolours only. An aria-invalid injected by FormField always wins."},{name:"variant",type:'"outlined" | "filled" | "borderless"',description:"antd `variant` \u2014 the control surface. Default `outlined`. Drawn from --control-{surface,filled,borderless}-* tokens, so a theme retunes all three at once."},{name:"loading",type:"boolean",description:"antd `loading` \u2014 the FIELD is in flight: the trailing chevron becomes a spinner and the trigger reports aria-busy. Distinct from the list-level spinner an async loadOptions shows inside the popup."},{name:"size",type:'"xs" | "sm" | "md" | "lg"',description:"antd `size` \u2014 height tier on the shared --control-height ladder."},{name:"open",type:"boolean",description:"antd `open` \u2014 controlled panel state. The consumer is the authority: nothing internal closes a pinned panel."},{name:"defaultOpen",type:"boolean",description:"antd `defaultOpen` \u2014 uncontrolled initial panel state."},{name:"onOpenChange",type:"(open: boolean) => void",description:"antd `onOpenChange` \u2014 fires for both controlled and uncontrolled panels."},{name:"maxTagCount",type:"number",description:'antd `maxTagCount` \u2014 how many selected values stay visible before the rest collapse into the overflow node. antd\'s `"responsive"` is not supported (see the parity PR).'},{name:"maxTagPlaceholder",type:"React.ReactNode | ((omitted: { value: string; label: React.ReactNode }[]) => React.ReactNode)",description:"antd `maxTagPlaceholder` \u2014 the node standing in for what maxTagCount hid. Defaults to a localized `+N`."},{name:"notFoundContent",type:"React.ReactNode",description:"antd `notFoundContent` \u2014 the node shown when the popup has nothing to list. Outranks the string-only emptyMessage."},{name:"autoClearSearchValue",type:"boolean",description:"antd `autoClearSearchValue` (default true) \u2014 clear the search box after a pick / on close. Set false to resume the same filtered list on the next open."},{name:"showCheckedStrategy",type:'"SHOW_CHILD" | "SHOW_PARENT"',description:"antd `showCheckedStrategy` (multiple only). SHOW_PARENT collapses a fully-checked branch into the branch itself; SHOW_CHILD (default) lists the leaves."},{name:"loadData",type:"(selectedOptions: TreeOptionProp[]) => void | Promise<void>",description:"antd `loadData` \u2014 lazy children. Called ONCE per branch that has no children and isLeaf !== true, the first time it is expanded; push the fetched children into options."},{name:"displayRender",type:"(labels: string[], selectedOptions?: TreeOptionProp[]) => React.ReactNode",description:"antd `displayRender` \u2014 owns the trigger label built from the selected path."},{name:"optionRender",type:"(option: TreeOptionProp) => React.ReactNode",description:"antd `optionRender` \u2014 owns a column row's body. The checkbox, check mark and chevron stay with the component."},{name:"search",type:"string",description:"Controlled search query (antd `showSearch.searchValue`)."},{name:"onSearchChange",type:"(query: string) => void",description:"Search query change (antd `showSearch.onSearch`)."}],usage:["DO pass a string[] path as value in single mode (e.g. ['country','region','city']). DON'T pass a flat string ID \u2014 the component treats value as an ordered path array and will render nothing if you pass a bare string.","DO use value + onChange together for controlled mode, or defaultValue alone for uncontrolled. DON'T mix both \u2014 providing value without onChange makes the field read-only (the internal state won't update).","DO set multiple={true} and pass value as string[][] (array of paths) for multi-selection. onChange receives string[][] in that mode. Mixing single-mode shape with multiple={true} silently produces no selection.","DON'T hand-roll a search input next to Cascader. Use showSearch={true} \u2014 it adds a built-in CommandInput that filters leaf paths across the full tree and reverts to cascade columns when cleared.","DO use fieldNames to remap data keys ({label:'name', value:'id', children:'nodes'}) rather than pre-transforming your API data. This keeps options in their original shape.","For form submission, Cascader has no 'name' prop. Wrap in a controlled pattern and store the path array in your form state (useForm/useState). For Inertia useForm, keep the field as an array (e.g. data.categoryPath = ['a','b','c'])."],useCases:["Geographic drilldown (Country \u2192 Prefecture \u2192 City) for address or branch-office pickers in accounting or logistics forms.","Expense category selection (e.g. Operating Expenses \u2192 Marketing \u2192 Digital Ads) where the full classification path is required for the general ledger.","Product taxonomy navigation (Department \u2192 Category \u2192 Sub-category) in inventory or invoice line-item entry.","Organisational unit picker (Company \u2192 Division \u2192 Department) in budget allocation or approval-routing configurations.","Multi-region filter in a report or dashboard filter bar, using multiple={true} to allow selecting several leaf locations at once.","Any deeply nested classification where the relationship between levels is meaningful and must be captured \u2014 not just the leaf value."],related:["TreeSelect \u2014 use when the hierarchy is a collapsible tree (expand/collapse nodes) rather than side-by-side columns, and when a single flat value string (node key) is sufficient instead of a full ancestor path. TreeSelect also supports treeCheckable for multi-select.","Select \u2014 use for a flat (non-hierarchical) list of options. Cascader is only needed when items have meaningful parent\u2013child levels.","Transfer \u2014 use when the user needs to shuttle multiple items between two panels; not for hierarchical path selection."],example:`{\`import { Cascader } from "@godxjp/ui/data-entry";
1259
+ }`,storyPath:"data-entry/TimePicker.stories.tsx",rules:[3,6,13,23]},{name:"Cascader",group:"data-entry",tagline:"Multi-level hierarchical path picker (Popover + cascading columns); value is always a string[] path, never a flat ID \u2014 passing a bare string breaks it.",props:[{name:"name",type:"string",description:"Native form field name; repeated values for multiple paths."},{name:"readOnly",type:"boolean",description:"Prevent edits while preserving the displayed value."},{name:"options",type:"TreeOptionProp[]",required:!0,description:"The hierarchical option tree. Each node has { value: string; label: ReactNode; disabled?: boolean; isLeaf?: boolean; children?: TreeOptionProp[] }. Normalised internally via fieldNames."},{name:"value",type:"string[] | string[][]",description:"Controlled value. Single mode: string[] path (e.g. ['vn','hcm','q1']). Multiple mode: string[][] array of paths."},{name:"defaultValue",type:"string[] | string[][]",description:"Initial value for uncontrolled mode. Same shape as value."},{name:"onValueChange",type:"(value: string[] | string[][], selectedOptions?: TreeOptionProp[] | TreeOptionProp[][]) => void",description:"Fires when selection changes. First arg is the selected path(s); second is the matching node objects. On clear, called with []."},{name:"multiple",type:"boolean",defaultValue:"false",description:"Enable multi-path selection. Renders checkboxes in columns and search results. Panel stays open on each pick. value/defaultValue become string[][]."},{name:"changeOnSelect",type:"boolean",defaultValue:"false",description:"When true, clicking any node (including branch nodes with children) commits that path immediately instead of waiting for a leaf selection."},{name:"showSearch",type:"boolean",defaultValue:"false",description:"Renders a CommandInput at the top of the popover. Filters to matching leaf paths across the whole tree when a query is typed; reverts to cascade columns when the query is cleared."},{name:"placeholder",type:"string",description:"Trigger button placeholder text when no value is selected. Defaults to the i18n key dataEntry.cascader.placeholder."},{name:"disabled",type:"boolean",defaultValue:"false",description:"Disables the trigger button and prevents the popover from opening."},{name:"expandTrigger",type:'"click" | "hover"',defaultValue:'"click"',description:"How child columns are expanded. 'hover' expands on mouseenter and collapses back on mouseleave."},{name:"fieldNames",type:"TreeFieldNamesProp",description:"Remap custom data keys: { label?: string; value?: string; children?: string }. Use when your data uses e.g. 'name' and 'id' instead of 'label' and 'value'."},{name:"allowClear",type:"boolean",defaultValue:"true",description:"Shows an X icon on the trigger when a value is selected. Clicking it calls onChange([]) and resets to placeholder. On by default, as antd Cascader; pass `false` on a required field."},{name:"className",type:"string",description:"Extra Tailwind classes applied to the trigger button."},{name:"id",type:"string",description:"HTML id forwarded to the trigger button. Use to associate a <label htmlFor>."},{name:"status",type:'"error" | "warning"',description:"antd `status`. `error` recolours the control AND sets aria-invalid (a colour-only error fails WCAG 2.2 SC 1.4.1); `warning` recolours only. An aria-invalid injected by FormField always wins."},{name:"variant",type:'"outlined" | "filled" | "borderless"',description:"antd `variant` \u2014 the control surface. Default `outlined`. Drawn from --control-{surface,filled,borderless}-* tokens, so a theme retunes all three at once."},{name:"loading",type:"boolean",description:"antd `loading` \u2014 the FIELD is in flight: the trailing chevron becomes a spinner and the trigger reports aria-busy. Distinct from the list-level spinner an async loadOptions shows inside the popup."},{name:"size",type:'"xs" | "sm" | "md" | "lg"',description:"antd `size` \u2014 height tier on the shared --control-height ladder."},{name:"open",type:"boolean",description:"antd `open` \u2014 controlled panel state. The consumer is the authority: nothing internal closes a pinned panel."},{name:"defaultOpen",type:"boolean",description:"antd `defaultOpen` \u2014 uncontrolled initial panel state."},{name:"onOpenChange",type:"(open: boolean) => void",description:"antd `onOpenChange` \u2014 fires for both controlled and uncontrolled panels."},{name:"maxTagCount",type:"number",description:'antd `maxTagCount` \u2014 how many selected values stay visible before the rest collapse into the overflow node. antd\'s `"responsive"` is not supported (see the parity PR).'},{name:"maxTagPlaceholder",type:"React.ReactNode | ((omitted: { value: string; label: React.ReactNode }[]) => React.ReactNode)",description:"antd `maxTagPlaceholder` \u2014 the node standing in for what maxTagCount hid. Defaults to a localized `+N`."},{name:"notFoundContent",type:"React.ReactNode",description:"antd `notFoundContent` \u2014 the node shown when the popup has nothing to list. Outranks the string-only emptyMessage."},{name:"autoClearSearchValue",type:"boolean",description:"antd `autoClearSearchValue` (default true) \u2014 clear the search box after a pick / on close. Set false to resume the same filtered list on the next open."},{name:"showCheckedStrategy",type:'"SHOW_CHILD" | "SHOW_PARENT"',description:"antd `showCheckedStrategy` (multiple only). SHOW_PARENT collapses a fully-checked branch into the branch itself; SHOW_CHILD (default) lists the leaves."},{name:"loadData",type:"(selectedOptions: TreeOptionProp[]) => void | Promise<void>",description:"antd `loadData` \u2014 lazy children. Called ONCE per branch that has no children and isLeaf !== true, the first time it is expanded; push the fetched children into options."},{name:"displayRender",type:"(labels: string[], selectedOptions?: TreeOptionProp[]) => React.ReactNode",description:"antd `displayRender` \u2014 owns the trigger label built from the selected path."},{name:"optionRender",type:"(option: TreeOptionProp) => React.ReactNode",description:"antd `optionRender` \u2014 owns a column row's body. The checkbox, check mark and chevron stay with the component."},{name:"search",type:"string",description:"Controlled search query (antd `showSearch.searchValue`)."},{name:"onSearchChange",type:"(query: string) => void",description:"Search query change (antd `showSearch.onSearch`)."}],usage:["DO pass a string[] path as value in single mode (e.g. ['country','region','city']). DON'T pass a flat string ID \u2014 the component treats value as an ordered path array and will render nothing if you pass a bare string.","DO use value + onChange together for controlled mode, or defaultValue alone for uncontrolled. DON'T mix both \u2014 providing value without onChange makes the field read-only (the internal state won't update).","DO set multiple={true} and pass value as string[][] (array of paths) for multi-selection. onChange receives string[][] in that mode. Mixing single-mode shape with multiple={true} silently produces no selection.","DON'T hand-roll a search input next to Cascader. Use showSearch={true} \u2014 it adds a built-in CommandInput that filters leaf paths across the full tree and reverts to cascade columns when cleared.","DO use fieldNames to remap data keys ({label:'name', value:'id', children:'nodes'}) rather than pre-transforming your API data. This keeps options in their original shape.","For form submission, Cascader has no 'name' prop. Wrap in a controlled pattern and store the path array in your form state (useForm/useState). For Inertia useForm, keep the field as an array (e.g. data.categoryPath = ['a','b','c'])."],useCases:["Geographic drilldown (Country \u2192 Prefecture \u2192 City) for address or branch-office pickers in accounting or logistics forms.","Expense category selection (e.g. Operating Expenses \u2192 Marketing \u2192 Digital Ads) where the full classification path is required for the general ledger.","Product taxonomy navigation (Department \u2192 Category \u2192 Sub-category) in inventory or invoice line-item entry.","Organisational unit picker (Company \u2192 Division \u2192 Department) in budget allocation or approval-routing configurations.","Multi-region filter in a report or dashboard filter bar, using multiple={true} to allow selecting several leaf locations at once.","Any deeply nested classification where the relationship between levels is meaningful and must be captured \u2014 not just the leaf value."],related:["TreeSelect \u2014 use when the hierarchy is a collapsible tree (expand/collapse nodes) rather than side-by-side columns, and when a single flat value string (node key) is sufficient instead of a full ancestor path. TreeSelect also supports treeCheckable for multi-select.","Select \u2014 use for a flat (non-hierarchical) list of options. Cascader is only needed when items have meaningful parent\u2013child levels.","Transfer \u2014 use when the user needs to shuttle multiple items between two panels; not for hierarchical path selection."],example:`{\`import { Cascader } from "@godxjp/ui/data-entry";
1261
1260
 
1262
1261
  const REGIONS = [
1263
1262
  {
@@ -1333,7 +1332,7 @@ function MultiRegionPicker() {
1333
1332
  changeOnSelect
1334
1333
  onValueChange={(v) => console.log("path", v)}
1335
1334
  />
1336
- \`}`,storyPath:"data-entry/Cascader.stories.tsx",rules:[3,6,23,31]},{name:"TreeSelect",group:"data-entry",tagline:"Hierarchical tree picker in a Popover (single or multi-select with checkboxes) \u2014 `onValueChange` receives `string` in single mode and `string[]` in multi/checkable mode; never use a raw `<select>` for tree-structured data.",props:[{name:"name",type:"string",description:"Native form field name; repeated values for multiple selections."},{name:"readOnly",type:"boolean",description:"Prevent edits while preserving value."},{name:"treeData",type:"TreeOptionProp[]",required:!0,description:"The tree data. Each node: `{ value: string; label: ReactNode; disabled?: boolean; disableCheckbox?: boolean; isLeaf?: boolean; children?: TreeOptionProp[] }`. Use `fieldNames` to remap custom keys."},{name:"value",type:"string | string[]",description:"Controlled selected value(s). Pass `string` in single mode, `string[]` in multi/checkable mode. When undefined the component is uncontrolled."},{name:"defaultValue",type:"string | string[]",description:"Initial value for uncontrolled usage. Ignored once `value` is provided."},{name:"onValueChange",type:"(value: string | string[] | undefined) => void",description:"Called on selection change. Returns `string` in single mode, `string[]` in multi/checkable mode, or `undefined` when cleared."},{name:"multiple",type:"boolean",defaultValue:"false",description:"Enable multi-select without checkboxes. When true, `onValueChange` always fires with `string[]`."},{name:"treeCheckable",type:"boolean",defaultValue:"false",description:"Render Checkbox controls beside each node. Implies multi-select; cascade-selects all descendants by default unless `treeCheckStrictly` is set."},{name:"treeCheckStrictly",type:"boolean",defaultValue:"false",description:"When true (only with `treeCheckable`), parent and child selections are independent \u2014 checking a parent does NOT auto-check its children."},{name:"showCheckedStrategy",type:'"SHOW_CHILD" | "SHOW_PARENT" | "SHOW_ALL"',defaultValue:'"SHOW_CHILD"',description:"Controls which values appear in the trigger label when checkboxes are used. `SHOW_CHILD` (default) \u2014 show only leaf nodes selected; `SHOW_PARENT` \u2014 show nearest ancestor when all children selected; `SHOW_ALL` \u2014 show every checked node. Use the exported constants `TreeSelect.SHOW_CHILD`, `TreeSelect.SHOW_PARENT`, `TreeSelect.SHOW_ALL` instead of raw strings."},{name:"showSearch",type:"boolean",defaultValue:"false",description:"Show a labelled SearchInput at the top of the dropdown. Filters visible tree nodes by label text; the trigger combobox controls the tree."},{name:"treeDefaultExpandAll",type:"boolean",defaultValue:"false",description:"Expand all nodes when the dropdown first opens. Initialised once; does not re-expand on re-render."},{name:"placeholder",type:"string",description:"Trigger button placeholder text when nothing is selected. Defaults to the i18n key `dataEntry.treeSelect.placeholder`."},{name:"disabled",type:"boolean",defaultValue:"false",description:"Disables the trigger button and all interactions."},{name:"allowClear",type:"boolean",defaultValue:"true",description:"Show an `X` icon in the trigger to clear the selection. Set to `false` to make selection mandatory."},{name:"className",type:"string",description:"Additional Tailwind classes applied to the trigger Button."},{name:"id",type:"string",description:"HTML `id` placed on the trigger Button \u2014 use this to associate a `<label htmlFor>` for accessibility."},{name:"aria-label",type:"string",description:"Accessible name for the combobox trigger when no visible label is available."},{name:"aria-errormessage",type:"string",description:"ID of the element containing the current validation error message."},{name:"aria-invalid",type:"boolean | 'true' | 'false'",description:"Marks the semantic combobox trigger invalid for assistive technology."},{name:"aria-required",type:"boolean | 'true' | 'false'",description:"Marks the semantic combobox trigger required for assistive technology."},{name:"fieldNames",type:"{ label?: string; value?: string; children?: string }",description:"Remap data object keys. Example: `{ label: 'name', value: 'id', children: 'items' }` so you don't have to transform your API response before passing it to `treeData`."},{name:"status",type:'"error" | "warning"',description:"antd `status`. `error` recolours the control AND sets aria-invalid (a colour-only error fails WCAG 2.2 SC 1.4.1); `warning` recolours only. An aria-invalid injected by FormField always wins."},{name:"variant",type:'"outlined" | "filled" | "borderless"',description:"antd `variant` \u2014 the control surface. Default `outlined`. Drawn from --control-{surface,filled,borderless}-* tokens, so a theme retunes all three at once."},{name:"loading",type:"boolean",description:"antd `loading` \u2014 the FIELD is in flight: the trailing chevron becomes a spinner and the trigger reports aria-busy. Distinct from the list-level spinner an async loadOptions shows inside the popup."},{name:"size",type:'"xs" | "sm" | "md" | "lg"',description:"antd `size` \u2014 height tier on the shared --control-height ladder."},{name:"open",type:"boolean",description:"antd `open` \u2014 controlled panel state. The consumer is the authority: nothing internal closes a pinned panel."},{name:"defaultOpen",type:"boolean",description:"antd `defaultOpen` \u2014 uncontrolled initial panel state."},{name:"onOpenChange",type:"(open: boolean) => void",description:"antd `onOpenChange` \u2014 fires for both controlled and uncontrolled panels."},{name:"maxTagCount",type:"number",description:'antd `maxTagCount` \u2014 how many selected values stay visible before the rest collapse into the overflow node. antd\'s `"responsive"` is not supported (see the parity PR).'},{name:"maxTagPlaceholder",type:"React.ReactNode | ((omitted: { value: string; label: React.ReactNode }[]) => React.ReactNode)",description:"antd `maxTagPlaceholder` \u2014 the node standing in for what maxTagCount hid. Defaults to a localized `+N`."},{name:"notFoundContent",type:"React.ReactNode",description:"antd `notFoundContent` \u2014 the node shown when the popup has nothing to list. Outranks the string-only emptyMessage."},{name:"autoClearSearchValue",type:"boolean",description:"antd `autoClearSearchValue` (default true) \u2014 clear the search box after a pick / on close. Set false to resume the same filtered list on the next open."},{name:"loadData",type:"(node: TreeOptionProp) => void | Promise<void>",description:"antd `loadData` \u2014 lazy children. Called ONCE per node that has no children and isLeaf !== true, the first time it is expanded; push the fetched children into treeData. Such a node still reads as expandable (aria-expanded + a working expander)."},{name:"treeTitleRender",type:"(node: TreeOptionProp) => React.ReactNode",description:"antd `treeTitleRender` \u2014 owns a node's title only; the checkbox, expander and row ARIA stay with the component."},{name:"search",type:"string",description:"Controlled search query (antd `showSearch.searchValue`)."},{name:"onSearchChange",type:"(query: string) => void",description:"Search query change (antd `showSearch.onSearch`)."}],usage:["DO pair with a `<label htmlFor={id}>` and pass the matching `id` prop so screen readers announce the control correctly. The underlying trigger is a `<Button role='combobox'>` \u2014 not a native `<select>` \u2014 so an explicit label is required.","DO use `treeCheckable` (+ optionally `showCheckedStrategy`) for selecting multiple nodes with parent\u2013child cascade; use `multiple` only when you want multi-select WITHOUT the checkbox cascade behaviour.","DO use the static constants `TreeSelect.SHOW_CHILD`, `TreeSelect.SHOW_PARENT`, `TreeSelect.SHOW_ALL` (or the named exports `SHOW_CHILD`/`SHOW_PARENT`/`SHOW_ALL` from the same import path) instead of raw string literals for `showCheckedStrategy`.","DON'T pass `value` and `defaultValue` simultaneously \u2014 pick controlled (`value` + `onValueChange`) OR uncontrolled (`defaultValue` only). Mixing them causes the component to silently prefer the controlled path.","DON'T hand-roll `onValueChange` type narrowing: in single mode the callback receives `string | undefined`; in multi/checkable mode it receives `string[]`. Branch on `multiple || treeCheckable` if you need to handle both shapes in the same handler.","DON'T use a raw `<select>` or a flat `Select` component for hierarchical/nested data \u2014 TreeSelect is the correct primitive. If hierarchy is irrelevant and data is flat, use `Select` instead."],useCases:["Chart-of-accounts picker in an accounting app where accounts belong to groups (Assets > Current Assets > Cash) and the user must select one leaf account.","Multi-select department or cost-centre filter where selecting a parent division should auto-select all child departments (treeCheckable + SHOW_PARENT).","Category assignment on invoice line items where categories have up to 3 levels of nesting and users can assign a parent or a leaf.","Permission scope selector where roles are structured in a tree and selecting a parent role should cascade to all child scopes (treeCheckable + treeCheckStrictly=false).","Location picker (Country > Prefecture > City) in a form where only leaf-level cities are valid selections (single mode, no checkboxes).","Large GL hierarchy browser with showSearch enabled so users can type to filter thousands of account codes instead of manually expanding nodes."],related:["Select \u2014 flat single/multi picker; use when data has no parent-child hierarchy. Pick TreeSelect as soon as items have `children`.","Cascader \u2014 also renders tree data but in a multi-column panel where the user drills down column by column; pick Cascader for strict path selection (select a full path Country\u2192Region\u2192City). Pick TreeSelect when the user may select any node at any level or needs checkboxes.","Checkbox / CheckboxGroup \u2014 use for a small, always-visible flat list of options. Use TreeSelect when options are hierarchical or the list is long enough to warrant a dropdown.","Command / CommandInput \u2014 low-level search primitive; TreeSelect already embeds this internally. Do NOT compose your own tree dropdown out of Command \u2014 use TreeSelect."],example:`import { useState } from "react";
1335
+ \`}`,storyPath:"data-entry/Cascader.stories.tsx",rules:[3,6,23,31]},{name:"TreeSelect",group:"data-entry",tagline:"Hierarchical tree picker in a Popover (single or multi-select with checkboxes) \u2014 `onValueChange` receives `string` in single mode and `string[]` in multi/checkable mode; never use a raw `<select>` for tree-structured data.",props:[{name:"name",type:"string",description:"Native form field name; repeated values for multiple selections."},{name:"readOnly",type:"boolean",description:"Prevent edits while preserving value."},{name:"treeData",type:"TreeOptionProp[]",required:!0,description:"The tree data. Each node: `{ value: string; label: ReactNode; disabled?: boolean; disableCheckbox?: boolean; isLeaf?: boolean; children?: TreeOptionProp[] }`. Use `fieldNames` to remap custom keys."},{name:"value",type:"string | string[]",description:"Controlled selected value(s). Pass `string` in single mode, `string[]` in multi/checkable mode. When undefined the component is uncontrolled."},{name:"defaultValue",type:"string | string[]",description:"Initial value for uncontrolled usage. Ignored once `value` is provided."},{name:"onValueChange",type:"(value: string | string[] | undefined) => void",description:"Called on selection change. Returns `string` in single mode, `string[]` in multi/checkable mode, or `undefined` when cleared."},{name:"multiple",type:"boolean",defaultValue:"false",description:"Enable multi-select without checkboxes. When true, `onValueChange` always fires with `string[]`."},{name:"treeCheckable",type:"boolean",defaultValue:"false",description:"Render Checkbox controls beside each node. Implies multi-select; cascade-selects all descendants by default unless `treeCheckStrictly` is set."},{name:"treeCheckStrictly",type:"boolean",defaultValue:"false",description:"When true (only with `treeCheckable`), parent and child selections are independent \u2014 checking a parent does NOT auto-check its children."},{name:"showCheckedStrategy",type:'"SHOW_CHILD" | "SHOW_PARENT" | "SHOW_ALL"',defaultValue:'"SHOW_CHILD"',description:"Controls which values appear in the trigger label when checkboxes are used. `SHOW_CHILD` (default) \u2014 show only leaf nodes selected; `SHOW_PARENT` \u2014 show nearest ancestor when all children selected; `SHOW_ALL` \u2014 show every checked node. Use the exported constants `TreeSelect.SHOW_CHILD`, `TreeSelect.SHOW_PARENT`, `TreeSelect.SHOW_ALL` instead of raw strings."},{name:"showSearch",type:"boolean",defaultValue:"false",description:"Show a labelled SearchInput at the top of the dropdown. Filters visible tree nodes by label text; the trigger combobox controls the tree."},{name:"treeDefaultExpandAll",type:"boolean",defaultValue:"false",description:"Expand all nodes when the dropdown first opens. Initialised once; does not re-expand on re-render."},{name:"placeholder",type:"string",description:"Trigger button placeholder text when nothing is selected. Defaults to the i18n key `dataEntry.treeSelect.placeholder`."},{name:"disabled",type:"boolean",defaultValue:"false",description:"Disables the trigger button and all interactions."},{name:"allowClear",type:"boolean",defaultValue:"false",description:"Show an `X` icon in the trigger to clear the selection. Off by default, as antd TreeSelect; pass `allowClear` on an optional field."},{name:"className",type:"string",description:"Additional Tailwind classes applied to the trigger Button."},{name:"id",type:"string",description:"HTML `id` placed on the trigger Button \u2014 use this to associate a `<label htmlFor>` for accessibility."},{name:"aria-label",type:"string",description:"Accessible name for the combobox trigger when no visible label is available."},{name:"aria-errormessage",type:"string",description:"ID of the element containing the current validation error message."},{name:"aria-invalid",type:"boolean | 'true' | 'false'",description:"Marks the semantic combobox trigger invalid for assistive technology."},{name:"aria-required",type:"boolean | 'true' | 'false'",description:"Marks the semantic combobox trigger required for assistive technology."},{name:"fieldNames",type:"{ label?: string; value?: string; children?: string }",description:"Remap data object keys. Example: `{ label: 'name', value: 'id', children: 'items' }` so you don't have to transform your API response before passing it to `treeData`."},{name:"status",type:'"error" | "warning"',description:"antd `status`. `error` recolours the control AND sets aria-invalid (a colour-only error fails WCAG 2.2 SC 1.4.1); `warning` recolours only. An aria-invalid injected by FormField always wins."},{name:"variant",type:'"outlined" | "filled" | "borderless"',description:"antd `variant` \u2014 the control surface. Default `outlined`. Drawn from --control-{surface,filled,borderless}-* tokens, so a theme retunes all three at once."},{name:"loading",type:"boolean",description:"antd `loading` \u2014 the FIELD is in flight: the trailing chevron becomes a spinner and the trigger reports aria-busy. Distinct from the list-level spinner an async loadOptions shows inside the popup."},{name:"size",type:'"xs" | "sm" | "md" | "lg"',description:"antd `size` \u2014 height tier on the shared --control-height ladder."},{name:"open",type:"boolean",description:"antd `open` \u2014 controlled panel state. The consumer is the authority: nothing internal closes a pinned panel."},{name:"defaultOpen",type:"boolean",description:"antd `defaultOpen` \u2014 uncontrolled initial panel state."},{name:"onOpenChange",type:"(open: boolean) => void",description:"antd `onOpenChange` \u2014 fires for both controlled and uncontrolled panels."},{name:"maxTagCount",type:"number",description:'antd `maxTagCount` \u2014 how many selected values stay visible before the rest collapse into the overflow node. antd\'s `"responsive"` is not supported (see the parity PR).'},{name:"maxTagPlaceholder",type:"React.ReactNode | ((omitted: { value: string; label: React.ReactNode }[]) => React.ReactNode)",description:"antd `maxTagPlaceholder` \u2014 the node standing in for what maxTagCount hid. Defaults to a localized `+N`."},{name:"notFoundContent",type:"React.ReactNode",description:"antd `notFoundContent` \u2014 the node shown when the popup has nothing to list. Outranks the string-only emptyMessage."},{name:"autoClearSearchValue",type:"boolean",description:"antd `autoClearSearchValue` (default true) \u2014 clear the search box after a pick / on close. Set false to resume the same filtered list on the next open."},{name:"loadData",type:"(node: TreeOptionProp) => void | Promise<void>",description:"antd `loadData` \u2014 lazy children. Called ONCE per node that has no children and isLeaf !== true, the first time it is expanded; push the fetched children into treeData. Such a node still reads as expandable (aria-expanded + a working expander)."},{name:"treeTitleRender",type:"(node: TreeOptionProp) => React.ReactNode",description:"antd `treeTitleRender` \u2014 owns a node's title only; the checkbox, expander and row ARIA stay with the component."},{name:"search",type:"string",description:"Controlled search query (antd `showSearch.searchValue`)."},{name:"onSearchChange",type:"(query: string) => void",description:"Search query change (antd `showSearch.onSearch`)."}],usage:["DO pair with a `<label htmlFor={id}>` and pass the matching `id` prop so screen readers announce the control correctly. The underlying trigger is a `<Button role='combobox'>` \u2014 not a native `<select>` \u2014 so an explicit label is required.","DO use `treeCheckable` (+ optionally `showCheckedStrategy`) for selecting multiple nodes with parent\u2013child cascade; use `multiple` only when you want multi-select WITHOUT the checkbox cascade behaviour.","DO use the static constants `TreeSelect.SHOW_CHILD`, `TreeSelect.SHOW_PARENT`, `TreeSelect.SHOW_ALL` (or the named exports `SHOW_CHILD`/`SHOW_PARENT`/`SHOW_ALL` from the same import path) instead of raw string literals for `showCheckedStrategy`.","DON'T pass `value` and `defaultValue` simultaneously \u2014 pick controlled (`value` + `onValueChange`) OR uncontrolled (`defaultValue` only). Mixing them causes the component to silently prefer the controlled path.","DON'T hand-roll `onValueChange` type narrowing: in single mode the callback receives `string | undefined`; in multi/checkable mode it receives `string[]`. Branch on `multiple || treeCheckable` if you need to handle both shapes in the same handler.","DON'T use a raw `<select>` or a flat `Select` component for hierarchical/nested data \u2014 TreeSelect is the correct primitive. If hierarchy is irrelevant and data is flat, use `Select` instead."],useCases:["Chart-of-accounts picker in an accounting app where accounts belong to groups (Assets > Current Assets > Cash) and the user must select one leaf account.","Multi-select department or cost-centre filter where selecting a parent division should auto-select all child departments (treeCheckable + SHOW_PARENT).","Category assignment on invoice line items where categories have up to 3 levels of nesting and users can assign a parent or a leaf.","Permission scope selector where roles are structured in a tree and selecting a parent role should cascade to all child scopes (treeCheckable + treeCheckStrictly=false).","Location picker (Country > Prefecture > City) in a form where only leaf-level cities are valid selections (single mode, no checkboxes).","Large GL hierarchy browser with showSearch enabled so users can type to filter thousands of account codes instead of manually expanding nodes."],related:["Select \u2014 flat single/multi picker; use when data has no parent-child hierarchy. Pick TreeSelect as soon as items have `children`.","Cascader \u2014 also renders tree data but in a multi-column panel where the user drills down column by column; pick Cascader for strict path selection (select a full path Country\u2192Region\u2192City). Pick TreeSelect when the user may select any node at any level or needs checkboxes.","Checkbox / CheckboxGroup \u2014 use for a small, always-visible flat list of options. Use TreeSelect when options are hierarchical or the list is long enough to warrant a dropdown.","Command / CommandInput \u2014 low-level search primitive; TreeSelect already embeds this internally. Do NOT compose your own tree dropdown out of Command \u2014 use TreeSelect."],example:`import { useState } from "react";
1337
1336
  import { FormField, TreeSelect } from "@godxjp/ui/data-entry";
1338
1337
 
1339
1338
  const accountTree = [
@@ -4430,7 +4429,7 @@ A block with no reason is IGNORED and the finding stands. An unclosed block runs
4430
4429
  The class-shaped rules (gap-*/p-*/m-*, bg-<palette>-*, w-[\u2026], pr-*, dark:*) only read class
4431
4430
  expressions \u2014 a className/class attribute, a class-named binding (\`baseClass\`, \`statusStyles\`,
4432
4431
  \`badgeVariants\`) or a cn()/clsx()/cva() call \u2014 so prose that merely spells a utility is not a
4433
- finding and needs no suppression.`,$=[{id:"dialog-needs-body",severity:"error",category:"composition",standard:null,fix:"Wrap a Dialog/Sheet's middle in <DialogBody>/<SheetBody>. The scroll lives on the body \u2014 the content box is `overflow: hidden` with no max-height \u2014 so an overlay without one clips long content at BOTH ends and takes the footer's buttons with it, leaving Escape as the only way out. It only shows on real data, never on demo data (gh#617)."},{id:"no-utility-spacing",severity:"error",category:"composition",standard:null,fix:"Remove gap-*/p-*/m-* from your own markup; space siblings with <Flex gap> / <ResponsiveGrid>. Page sections are spaced by <PageContainer> (docs/CONSUMER-RULES.md \xA73)."},{id:"no-utility-layout",severity:"error",category:"composition",standard:null,fix:'Replace className="flex \u2026" / "grid \u2026" with <Flex> (row), <Flex direction="col"> (stack) or <ResponsiveGrid columns>.'},{id:"no-hand-rolled-surface",severity:"warn",category:"composition",standard:null,fix:"A rounded+border/bg div is a fake surface \u2014 use Card, Badge, Avatar, ListRow, Descriptions or EmptyState so height, padding and radius come from tokens. A read-only sample of a colour a USER chose is Swatch, which takes that value as a prop (gh#527)."},{id:"sibling-cards-need-flex",severity:"warn",category:"composition",standard:null,fix:'Wrap adjacent <Card>s in <Flex direction="col" gap="lg"> or <ResponsiveGrid>; direct children of PageContainer are spaced by the page already.'},{id:"no-raw-palette-color",severity:"error",category:"tokens",standard:null,fix:"Use semantic tokens (bg-primary, text-muted-foreground), never raw palette (bg-blue-500)."},{id:"no-arbitrary-hex",severity:"error",category:"tokens",standard:null,fix:"No hardcoded hex in className; read design-system color tokens."},{id:"no-arbitrary-spacing",severity:"error",category:"tokens",standard:null,fix:"No p-[13px]/gap-[7px]; use the token scale / <Flex gap> / <PageContainer>."},{id:"no-arbitrary-size",severity:"error",category:"tokens",standard:null,fix:"No w-[37px]/h-[260px]; use token sizes or a sizing prop (min-w-[\u2026] allowed)."},{id:"no-arbitrary-typography",severity:"error",category:"tokens",standard:null,fix:"No text-[20px]/leading-[1.7]; use the golden-ratio type-scale tokens."},{id:"no-arbitrary-radius",severity:"error",category:"tokens",standard:null,fix:"No rounded-[6px]; use rounded-sm/md/lg radius tokens."},{id:"no-off-scale-token-value",severity:"warn",category:"tokens",standard:null,fix:"A design-system knob you override takes a step (style={{ '--card-space-inset': 'var(--space-4)' }}) or a calc() from one (calc(var(--space-4) + 2px)), not a raw 13px. Only axes that HAVE a scale count: space/padding/gap/margin, font-size, radius, icon-size (width/height/size/offset have none yet, so a number there is fine). A value that is genuinely off the grid keeps its literal and says why in place, with a /* scale-exempt: 6px status dot, below --space-1 */ comment on that line or the one above."},{id:"no-dark-color-override",severity:"warn",category:"tokens",standard:null,fix:"Drop dark: color overrides \u2014 semantic tokens already adapt."},{id:"raw-white-black",severity:"warn",category:"tokens",standard:null,fix:"Prefer semantic tokens (text-primary-foreground, bg-background) over raw white/black."},{id:"no-domain-tracking-token",severity:"error",category:"tokens",standard:null,fix:"No package-tracking/domain tokens; use semantic tokens or app theme overrides."},{id:"no-space-xy",severity:"error",category:"tokens",standard:null,fix:"Use <Flex gap> instead of space-x/y-*."},{id:"no-raw-select",severity:"error",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use <Select> from @godxjp/ui, not a raw <select>."},{id:"no-raw-table",severity:"error",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use the <Table>/<DataTable> family, not a raw <table>."},{id:"no-raw-input",severity:"error",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use <Input> from @godxjp/ui, not a raw <input>."},{id:"no-raw-textarea",severity:"warn",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use <Textarea> from @godxjp/ui, not a raw <textarea>."},{id:"no-raw-button",severity:"error",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use <Button> from @godxjp/ui, not a raw <button>."},{id:"card-manual-padding",severity:"error",category:"composition",standard:null,fix:"Wrap the body in <CardContent>; don't hand-roll padding on <Card>."},{id:"card-needs-content",severity:"error",category:"composition",standard:null,fix:"<Card> body must be in <CardContent> (no padding otherwise); flush only for a full-bleed table."},{id:"bare-control-needs-formfield",severity:"warn",category:"composition",standard:"WCAG 2.2 SC 1.3.1 \xB7 3.3.2 \xB7 @godxjp/ui FormField (cardinal rule 227)",fix:"Wrap a labelled control in <FormField label=\u2026> \u2014 it owns label\u2194control id wiring, aria/error, AND the field rhythm; never pair a bare <Label> with an <Input>."},{id:"manual-field-error",severity:"warn",category:"composition",standard:"WCAG 2.2 SC 3.3.1",fix:"Use <FormField error=\u2026>, not a hand-rolled <p class='text-destructive'>."},{id:"manual-field-helper",severity:"warn",category:"composition",standard:null,fix:"Use <FormField helper=\u2026>, not a hand-rolled helper <p>."},{id:"status-tone-not-variant",severity:"error",category:"api",standard:null,fix:"Badge/Tag/StatCard status uses tone, not variant (variant is structural)."},{id:"value-callback-on-value-change",severity:"error",category:"api",standard:null,fix:"Abstract value components use onValueChange, not onChange."},{id:"icon-button-needs-name",severity:"warn",category:"a11y",standard:"WCAG 2.2 SC 4.1.2 \xB7 1.1.1 \xB7 WAI-ARIA 1.2",fix:"Add aria-label={t('\u2026')} to <Button size='icon'>; the glyph is aria-hidden."},{id:"img-needs-alt",severity:"warn",category:"a11y",standard:"WCAG 2.2 SC 1.1.1 \xB7 HTML Living Standard",fix:"Add alt to every <img> (alt='' if decorative); prefer <Avatar>/<AspectRatio>."},{id:"no-positive-tabindex",severity:"warn",category:"a11y",standard:"WCAG 2.2 SC 2.4.3 \xB7 WAI-ARIA APG",fix:"Use tabIndex 0 or -1 only; never positive \u2014 it breaks focus order."},{id:"hand-rolled-close-glyph",severity:"warn",category:"a11y",standard:"WAI-ARIA 1.2 (dialog) \xB7 WCAG 2.2 SC 4.1.2",fix:"Pass onDismiss to <Alert>, or use <Dialog>/<Sheet>'s built-in labelled close \u2014 not a bare \u2715."},{id:"no-emoji-in-ui",severity:"warn",category:"i18n",standard:"Unicode UTS #51 \xB7 WCAG 2.2 SC 1.1.1",fix:"No emoji in product UI; quiet i18n copy + Lucide icon + Badge tone."},{id:"no-emoji-flag",severity:"warn",category:"i18n",standard:"ISO 3166-1 \xB7 ECMA-402 Intl.DisplayNames \xB7 Unicode UTS #51",fix:"Derive country names from Intl.DisplayNames; no emoji flags."},{id:"hardcoded-currency",severity:"warn",category:"i18n",standard:"ISO 4217 \xB7 ECMA-402 Intl.NumberFormat",fix:"Format money with Intl.NumberFormat({ style: 'currency', currency }), not \xA5{amount}."},{id:"raw-intl-date",severity:"warn",category:"i18n",standard:"ISO 8601 \xB7 IANA tz \xB7 ECMA-402 Intl.DateTimeFormat",fix:"Use formatDate from @godxjp/ui/datetime, not hand-built or locale-default dates."},{id:"no-physical-direction",severity:"warn",category:"rtl",standard:"W3C CSS Logical Properties L1 \xB7 WCAG 2.2 (1.3.2)",fix:"Use logical utilities (ms-/me-/ps-/pe-, start-/end-, text-start/end, border-s/e, rounded-s/e)."},{id:"no-em-dash-in-copy",severity:"warn",category:"copy",standard:"@godxjp/ui reference-design typography",fix:"No em-dash (\u2014) in copy; use a middot \xB7 or two calm sentences."}];function J(e){return e?$.filter(t=>t.category===e):$}var ee="node node_modules/@godxjp/ui/scripts/visual-audit.mjs <baseUrl> [route \u2026] (optional peers, TESTED range: playwright >=1.55 <2 [1.61.1] + @axe-core/playwright >=4.10 <5 [4.12.1] + axe-core >=4.10 <5 [4.12.1] + a chromium via `playwright install chromium`; --strict for a CI gate, --format json ALWAYS emits valid JSON with a status of ok|partial|error separating infra errors[] from product findings[], --rules to print this catalog)",Z=[{id:"css-layers-missing",severity:"error",category:"layout",standard:"@godxjp/ui styles contract (styles / styles/core are the only entries)",fix:"Import `@godxjp/ui/styles` (or `styles/core` without fonts); never cherry-pick *-layout.css \u2014 a missing layer renders naked menus and unsized Select rows."},{id:"control-height-mismatch",severity:"error",category:"layout",standard:"@godxjp/ui control tier (--control-height) \xB7 Nielsen consistency heuristic",fix:"Every control in one row must share --control-height; replace hand-rolled pills with Avatar/Button/Badge, never restyle a control's height."},{id:"sibling-card-gap",severity:"error",category:"layout",standard:"@godxjp/ui spacing scale (docs/SPACING.md)",fix:'Adjacent Cards need one space step between them \u2014 <Flex direction="col" gap>, <ResponsiveGrid>, or direct children of PageContainer.'},{id:"row-content-starved",severity:"warn",category:"layout",standard:"WCAG 2.2 SC 1.4.10 reflow",fix:`A sibling (a w-full SelectTrigger) takes the row's width and truncates its neighbours \u2014 give the Select width="auto" or move it out of the row.`},{id:"axe-violations",severity:"warn",category:"a11y",standard:"WCAG 2.2 A/AA \xB7 WAI-ARIA 1.2 (axe-core engine)",fix:"Fix each axe node \u2014 contrast (1.4.3), name/role/value (4.1.2), ARIA, landmarks. Runs on the REAL DOM, catching what static analysis cannot."},{id:"target-size-min",severity:"warn",category:"a11y",standard:"WCAG 2.2 SC 2.5.8 (24\xD724 AA) \xB7 2.5.5 (44\xD744 AAA)",fix:"Interactive targets must be \u226524\xD724 CSS px; size from the --control-height tier."},{id:"oversaturated-accent",severity:"warn",category:"color",standard:"@godxjp/ui reference-design \u6E0B\u307F (OKLCH chroma \u2264 0.18)",fix:"Desaturate brand/primary surfaces (OKLCH chroma \u2264 0.18); read --primary tokens, no raw vivid bars."},{id:"emoji-rendered",severity:"warn",category:"i18n",standard:"Unicode UTS #51 \xB7 WCAG 2.2 SC 1.1.1",fix:"Remove emoji from rendered product text; quiet i18n copy + Lucide icon + Badge tone."},{id:"alert-controls-misplaced",severity:"warn",category:"layout",standard:"@godxjp/ui Alert anatomy \xB7 WAI-ARIA 1.2 \xB7 WCAG 2.2 SC 4.1.2",fix:"Use <Alert>: one leading tone icon, <Alert.Actions> trailing-right normal width, onDismiss \xD7 top-right, one horizontal row."}];function te(e){return e?Z.filter(t=>t.category===e):Z}var p={name:"@godxjp/ui-mcp",version:"26.4.0",godxUiCompatibility:"26.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 oe=[{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.",inputSchema:{type:"object",properties:{group:{type:"string",enum:["general","layout","data-display","data-entry","feedback","navigation","composites","shell","providers"]}}}},{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.",inputSchema:{type:"object",properties:{query:{type:"string"}},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:"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"]}}];async function ne(e,t){switch(e){case"list_skills":return ve();case"list_primitives":return ce(t.group);case"list_patterns":return xe();case"list_anti_ai_tells":return Ce(t.category);case"list_redesign_checks":return De(t.category);case"list_audit_rules":return Te(t.category);case"list_visual_checks":return Se(t.category);case"get_anti_ai_tell":return Ae(String(t.name??""));case"get_redesign_check":return Oe(String(t.symptom??""));case"get_skill_section":return he(String(t.skill??""),String(t.section??""));case"get_component":return Re(String(t.name??""),t.verbose===!0);case"get_pattern":return Pe(String(t.name??""));case"get_rule":return Le(typeof t.number=="number"?t.number:void 0);case"get_vocab":return Fe(t.name==null?void 0:String(t.name));case"get_tokens":return Me(t.category);case"list_consumer_skills":return ye();case"get_consumer_skill":return we(String(t.skill??""),String(t.section??""));case"route_consumer_task":return ae(String(t.task??""),{consumerOnly:!0});case"draft_bug_report":return ke(t);case"check_compatibility":return de(t.version==null?void 0:String(t.version));case"route_task":return ae(String(t.task??""));case"suggest_primitive":return Be(String(t.use_case??""));case"search_components":return Ue(String(t.query??""));case"get_frame_coverage":return ze(t.name===void 0?void 0:String(t.name));case"lint_jsx":return He(String(t.jsx??""));default:return`Unknown tool: ${e}`}}function ve(){let e=`# Available skills (${S.length})
4432
+ finding and needs no suppression.`,$=[{id:"dialog-needs-body",severity:"error",category:"composition",standard:null,fix:"Wrap a Dialog/Sheet's middle in <DialogBody>/<SheetBody>. The scroll lives on the body \u2014 the content box is `overflow: hidden` with no max-height \u2014 so an overlay without one clips long content at BOTH ends and takes the footer's buttons with it, leaving Escape as the only way out. It only shows on real data, never on demo data (gh#617)."},{id:"no-utility-spacing",severity:"error",category:"composition",standard:null,fix:"Remove gap-*/p-*/m-* from your own markup; space siblings with <Flex gap> / <ResponsiveGrid>. Page sections are spaced by <PageContainer> (docs/CONSUMER-RULES.md \xA73)."},{id:"no-utility-layout",severity:"error",category:"composition",standard:null,fix:'Replace className="flex \u2026" / "grid \u2026" with <Flex> (row), <Flex direction="col"> (stack) or <ResponsiveGrid columns>.'},{id:"no-hand-rolled-surface",severity:"warn",category:"composition",standard:null,fix:"A rounded+border/bg div is a fake surface \u2014 use Card, Badge, Avatar, ListRow, Descriptions or EmptyState so height, padding and radius come from tokens. A read-only sample of a colour a USER chose is Swatch, which takes that value as a prop (gh#527)."},{id:"sibling-cards-need-flex",severity:"warn",category:"composition",standard:null,fix:'Wrap adjacent <Card>s in <Flex direction="col" gap="lg"> or <ResponsiveGrid>; direct children of PageContainer are spaced by the page already.'},{id:"no-raw-palette-color",severity:"error",category:"tokens",standard:null,fix:"Use semantic tokens (bg-primary, text-muted-foreground), never raw palette (bg-blue-500)."},{id:"no-arbitrary-hex",severity:"error",category:"tokens",standard:null,fix:"No hardcoded hex in className; read design-system color tokens."},{id:"no-arbitrary-spacing",severity:"error",category:"tokens",standard:null,fix:"No p-[13px]/gap-[7px]; use the token scale / <Flex gap> / <PageContainer>."},{id:"no-arbitrary-size",severity:"error",category:"tokens",standard:null,fix:"No w-[37px]/h-[260px]; use token sizes or a sizing prop (min-w-[\u2026] allowed)."},{id:"no-arbitrary-typography",severity:"error",category:"tokens",standard:null,fix:"No text-[20px]/leading-[1.7]; use the golden-ratio type-scale tokens."},{id:"no-arbitrary-radius",severity:"error",category:"tokens",standard:null,fix:"No rounded-[6px]; use rounded-sm/md/lg radius tokens."},{id:"no-off-scale-token-value",severity:"warn",category:"tokens",standard:null,fix:"A design-system knob you override takes a step (style={{ '--card-space-inset': 'var(--space-4)' }}) or a calc() from one (calc(var(--space-4) + 2px)), not a raw 13px. Only axes that HAVE a scale count: space/padding/gap/margin, font-size, radius, icon-size (width/height/size/offset have none yet, so a number there is fine). A value that is genuinely off the grid keeps its literal and says why in place, with a /* scale-exempt: 6px status dot, below --space-1 */ comment on that line or the one above."},{id:"no-dark-color-override",severity:"warn",category:"tokens",standard:null,fix:"Drop dark: color overrides \u2014 semantic tokens already adapt."},{id:"raw-white-black",severity:"warn",category:"tokens",standard:null,fix:"Prefer semantic tokens (text-primary-foreground, bg-background) over raw white/black."},{id:"no-domain-tracking-token",severity:"error",category:"tokens",standard:null,fix:"No package-tracking/domain tokens; use semantic tokens or app theme overrides."},{id:"no-space-xy",severity:"error",category:"tokens",standard:null,fix:"Use <Flex gap> instead of space-x/y-*."},{id:"no-raw-select",severity:"error",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use <Select> from @godxjp/ui, not a raw <select>."},{id:"no-raw-table",severity:"error",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use the <Table>/<DataTable> family, not a raw <table>."},{id:"no-raw-input",severity:"error",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use <Input> from @godxjp/ui, not a raw <input>."},{id:"no-raw-textarea",severity:"warn",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use <Textarea> from @godxjp/ui, not a raw <textarea>."},{id:"no-raw-button",severity:"error",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use <Button> from @godxjp/ui, not a raw <button>."},{id:"card-manual-padding",severity:"error",category:"composition",standard:null,fix:"Wrap the body in <CardContent>; don't hand-roll padding on <Card>."},{id:"card-needs-content",severity:"error",category:"composition",standard:null,fix:"<Card> body must be in <CardContent> (no padding otherwise); flush only for a full-bleed table."},{id:"bare-control-needs-formfield",severity:"warn",category:"composition",standard:"WCAG 2.2 SC 1.3.1 \xB7 3.3.2 \xB7 @godxjp/ui FormField (cardinal rule 227)",fix:"Wrap a labelled control in <FormField label=\u2026> \u2014 it owns label\u2194control id wiring, aria/error, AND the field rhythm; never pair a bare <Label> with an <Input>."},{id:"manual-field-error",severity:"warn",category:"composition",standard:"WCAG 2.2 SC 3.3.1",fix:"Use <FormField error=\u2026>, not a hand-rolled <p class='text-destructive'>."},{id:"manual-field-helper",severity:"warn",category:"composition",standard:null,fix:"Use <FormField helper=\u2026>, not a hand-rolled helper <p>."},{id:"status-tone-not-variant",severity:"error",category:"api",standard:null,fix:"Badge/Tag/StatCard status uses tone, not variant (variant is structural)."},{id:"value-callback-on-value-change",severity:"error",category:"api",standard:null,fix:"Abstract value components use onValueChange, not onChange."},{id:"icon-button-needs-name",severity:"warn",category:"a11y",standard:"WCAG 2.2 SC 4.1.2 \xB7 1.1.1 \xB7 WAI-ARIA 1.2",fix:"Add aria-label={t('\u2026')} to <Button size='icon'>; the glyph is aria-hidden."},{id:"img-needs-alt",severity:"warn",category:"a11y",standard:"WCAG 2.2 SC 1.1.1 \xB7 HTML Living Standard",fix:"Add alt to every <img> (alt='' if decorative); prefer <Avatar>/<AspectRatio>."},{id:"no-positive-tabindex",severity:"warn",category:"a11y",standard:"WCAG 2.2 SC 2.4.3 \xB7 WAI-ARIA APG",fix:"Use tabIndex 0 or -1 only; never positive \u2014 it breaks focus order."},{id:"hand-rolled-close-glyph",severity:"warn",category:"a11y",standard:"WAI-ARIA 1.2 (dialog) \xB7 WCAG 2.2 SC 4.1.2",fix:"Pass onDismiss to <Alert>, or use <Dialog>/<Sheet>'s built-in labelled close \u2014 not a bare \u2715."},{id:"no-emoji-in-ui",severity:"warn",category:"i18n",standard:"Unicode UTS #51 \xB7 WCAG 2.2 SC 1.1.1",fix:"No emoji in product UI; quiet i18n copy + Lucide icon + Badge tone."},{id:"no-emoji-flag",severity:"warn",category:"i18n",standard:"ISO 3166-1 \xB7 ECMA-402 Intl.DisplayNames \xB7 Unicode UTS #51",fix:"Derive country names from Intl.DisplayNames; no emoji flags."},{id:"hardcoded-currency",severity:"warn",category:"i18n",standard:"ISO 4217 \xB7 ECMA-402 Intl.NumberFormat",fix:"Format money with Intl.NumberFormat({ style: 'currency', currency }), not \xA5{amount}."},{id:"raw-intl-date",severity:"warn",category:"i18n",standard:"ISO 8601 \xB7 IANA tz \xB7 ECMA-402 Intl.DateTimeFormat",fix:"Use formatDate from @godxjp/ui/datetime, not hand-built or locale-default dates."},{id:"no-physical-direction",severity:"warn",category:"rtl",standard:"W3C CSS Logical Properties L1 \xB7 WCAG 2.2 (1.3.2)",fix:"Use logical utilities (ms-/me-/ps-/pe-, start-/end-, text-start/end, border-s/e, rounded-s/e)."},{id:"no-em-dash-in-copy",severity:"warn",category:"copy",standard:"@godxjp/ui reference-design typography",fix:"No em-dash (\u2014) in copy; use a middot \xB7 or two calm sentences."}];function J(e){return e?$.filter(t=>t.category===e):$}var ee="node node_modules/@godxjp/ui/scripts/visual-audit.mjs <baseUrl> [route \u2026] (optional peers, TESTED range: playwright >=1.55 <2 [1.61.1] + @axe-core/playwright >=4.10 <5 [4.12.1] + axe-core >=4.10 <5 [4.12.1] + a chromium via `playwright install chromium`; --strict for a CI gate, --format json ALWAYS emits valid JSON with a status of ok|partial|error separating infra errors[] from product findings[], --rules to print this catalog)",Z=[{id:"css-layers-missing",severity:"error",category:"layout",standard:"@godxjp/ui styles contract (styles / styles/core are the only entries)",fix:"Import `@godxjp/ui/styles` (or `styles/core` without fonts); never cherry-pick *-layout.css \u2014 a missing layer renders naked menus and unsized Select rows."},{id:"control-height-mismatch",severity:"error",category:"layout",standard:"@godxjp/ui control tier (--control-height) \xB7 Nielsen consistency heuristic",fix:"Every control in one row must share --control-height; replace hand-rolled pills with Avatar/Button/Badge, never restyle a control's height."},{id:"sibling-card-gap",severity:"error",category:"layout",standard:"@godxjp/ui spacing scale (docs/SPACING.md)",fix:'Adjacent Cards need one space step between them \u2014 <Flex direction="col" gap>, <ResponsiveGrid>, or direct children of PageContainer.'},{id:"row-content-starved",severity:"warn",category:"layout",standard:"WCAG 2.2 SC 1.4.10 reflow",fix:`A sibling (a w-full SelectTrigger) takes the row's width and truncates its neighbours \u2014 give the Select width="auto" or move it out of the row.`},{id:"axe-violations",severity:"warn",category:"a11y",standard:"WCAG 2.2 A/AA \xB7 WAI-ARIA 1.2 (axe-core engine)",fix:"Fix each axe node \u2014 contrast (1.4.3), name/role/value (4.1.2), ARIA, landmarks. Runs on the REAL DOM, catching what static analysis cannot."},{id:"target-size-min",severity:"warn",category:"a11y",standard:"WCAG 2.2 SC 2.5.8 (24\xD724 AA) \xB7 2.5.5 (44\xD744 AAA)",fix:"Interactive targets must be \u226524\xD724 CSS px; size from the --control-height tier."},{id:"oversaturated-accent",severity:"warn",category:"color",standard:"@godxjp/ui reference-design \u6E0B\u307F (OKLCH chroma \u2264 0.18)",fix:"Desaturate brand/primary surfaces (OKLCH chroma \u2264 0.18); read --primary tokens, no raw vivid bars."},{id:"emoji-rendered",severity:"warn",category:"i18n",standard:"Unicode UTS #51 \xB7 WCAG 2.2 SC 1.1.1",fix:"Remove emoji from rendered product text; quiet i18n copy + Lucide icon + Badge tone."},{id:"alert-controls-misplaced",severity:"warn",category:"layout",standard:"@godxjp/ui Alert anatomy \xB7 WAI-ARIA 1.2 \xB7 WCAG 2.2 SC 4.1.2",fix:"Use <Alert>: one leading tone icon, <Alert.Actions> trailing-right normal width, onDismiss \xD7 top-right, one horizontal row."}];function te(e){return e?Z.filter(t=>t.category===e):Z}var p={name:"@godxjp/ui-mcp",version:"27.0.0",godxUiCompatibility:"27.0.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 oe=[{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.",inputSchema:{type:"object",properties:{group:{type:"string",enum:["general","layout","data-display","data-entry","feedback","navigation","composites","shell","providers"]}}}},{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.",inputSchema:{type:"object",properties:{query:{type:"string"}},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:"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"]}}];async function ne(e,t){switch(e){case"list_skills":return ve();case"list_primitives":return ce(t.group);case"list_patterns":return xe();case"list_anti_ai_tells":return Ce(t.category);case"list_redesign_checks":return De(t.category);case"list_audit_rules":return Te(t.category);case"list_visual_checks":return Se(t.category);case"get_anti_ai_tell":return Ae(String(t.name??""));case"get_redesign_check":return Oe(String(t.symptom??""));case"get_skill_section":return he(String(t.skill??""),String(t.section??""));case"get_component":return Re(String(t.name??""),t.verbose===!0);case"get_pattern":return Pe(String(t.name??""));case"get_rule":return Le(typeof t.number=="number"?t.number:void 0);case"get_vocab":return Fe(t.name==null?void 0:String(t.name));case"get_tokens":return Me(t.category);case"list_consumer_skills":return ye();case"get_consumer_skill":return we(String(t.skill??""),String(t.section??""));case"route_consumer_task":return ae(String(t.task??""),{consumerOnly:!0});case"draft_bug_report":return ke(t);case"check_compatibility":return de(t.version==null?void 0:String(t.version));case"route_task":return ae(String(t.task??""));case"suggest_primitive":return Be(String(t.use_case??""));case"search_components":return Ue(String(t.query??""));case"get_frame_coverage":return ze(t.name===void 0?void 0:String(t.name));case"lint_jsx":return He(String(t.jsx??""));default:return`Unknown tool: ${e}`}}function ve(){let e=`# Available skills (${S.length})
4434
4433
 
4435
4434
  `;e+="Each is tagged `[audience]` \u2014 `core` = building @godxjp/ui itself, `consumer` = building an app with it, `both`. App-devs: use `list_consumer_skills` to hide core material.\n\n",e+='Use `get_skill_section skill="..." section="..."` to drill in.\n\n';for(let t of S)e+=`## ${t.id} \u2014 ${t.name} \`[${t.audience}]\`
4436
4435
  `,e+=`**When to use:** ${t.whenToUse}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@godxjp/ui-mcp",
3
- "version": "26.4.0",
4
- "godxUiCompatibility": "26.4.x",
3
+ "version": "27.0.0",
4
+ "godxUiCompatibility": "27.0.x",
5
5
  "description": "Model Context Protocol server for @godxjp/ui — 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 — token-efficient (list → drill-down).",
6
6
  "type": "module",
7
7
  "main": "./dist/index.js",