@godxjp/ui 33.1.1 → 33.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/agent/START-HERE.md +7 -7
- package/agent/components/Command.json +9 -1
- package/agent/components/ResponsiveGrid.json +2 -2
- package/agent/components/SearchForm.json +115 -0
- package/agent/components/Toolbar.json +6 -3
- package/agent/components/TranslatableInput.json +92 -0
- package/agent/components/TranslatableTextarea.json +84 -0
- package/agent/components-index.json +20 -1
- package/agent/components.json +308 -6
- package/agent/index.json +8 -8
- package/agent/llms.txt +8 -8
- package/agent/patterns/condition-builder.json +31 -0
- package/agent/patterns/data-table-page.json +1 -1
- package/agent/patterns/filter-bar-layouts.json +30 -0
- package/agent/patterns/inertia-list-page.json +1 -1
- package/agent/patterns/list-filters.json +1 -1
- package/agent/patterns/search-form.json +27 -0
- package/agent/patterns-index.json +85 -0
- package/agent/patterns.json +91 -3
- package/agent/rules.json +1 -1
- package/agent/tokens.json +12 -0
- package/dist/components/data-entry/command.d.ts +22 -1
- package/dist/components/data-entry/command.js +14 -1
- package/dist/components/data-entry/form-field.js +10 -4
- package/dist/components/data-entry/index.d.ts +4 -2
- package/dist/components/data-entry/index.js +6 -1
- package/dist/components/data-entry/translatable-input.d.ts +29 -0
- package/dist/components/data-entry/translatable-input.js +184 -0
- package/dist/components/navigation/index.d.ts +2 -0
- package/dist/components/navigation/index.js +2 -0
- package/dist/components/navigation/search-form.d.ts +31 -0
- package/dist/components/navigation/search-form.js +112 -0
- package/dist/contracts/measurement.json +1 -1
- package/dist/i18n/messages/en.json +13 -0
- package/dist/i18n/messages/ja.json +13 -0
- package/dist/i18n/messages/vi.json +13 -0
- package/dist/lib/field-a11y.d.ts +15 -0
- package/dist/lib/field-a11y.js +13 -1
- package/dist/lib/intl-cache.d.ts +1 -0
- package/dist/lib/intl-cache.js +4 -0
- package/dist/props/components/data-entry.prop.d.ts +46 -0
- package/dist/props/components/index.d.ts +1 -1
- package/dist/props/components/layout.prop.d.ts +3 -1
- package/dist/props/components/navigation.prop.d.ts +43 -3
- package/dist/props/registry.d.ts +56 -0
- package/dist/props/registry.js +91 -0
- package/dist/styles/control.css +15 -0
- package/dist/styles/data-entry-layout.css +25 -0
- package/dist/styles/layers.json +29 -1
- package/dist/styles/layout.css +41 -0
- package/dist/styles.css +1 -1
- package/dist/tokens/components/data-entry.css +4 -0
- package/docs/CONSUMER-RULES.md +1 -1
- package/package.json +2 -2
- package/scripts/ui-audit.mjs +148 -3
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ npm i @godxjp/ui
|
|
|
18
18
|
|
|
19
19
|
<!-- agent-catalog-counts:start -->
|
|
20
20
|
|
|
21
|
-
The catalog —
|
|
21
|
+
The catalog — 166 components, 2230 tokens, 44 cardinal rules — is published in two forms from one
|
|
22
22
|
source. **Entry point for either: [`AGENTS.md`](AGENTS.md).**
|
|
23
23
|
<!-- agent-catalog-counts:end -->
|
|
24
24
|
|
|
@@ -53,12 +53,12 @@ static files straight from this public repo — no hosting, no deploy step:
|
|
|
53
53
|
| [`agent/llms.txt`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/llms.txt) | 3 KB | the [llms.txt](https://llmstxt.org/) entry point |
|
|
54
54
|
| [`agent/DESIGN.md`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/DESIGN.md) | 17 KB | the visual spec: tokens, colour scarcity, admin screen anatomy, do/don't |
|
|
55
55
|
| [`agent/index.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/index.json) | 3 KB | manifest: version, counts, every file's URL |
|
|
56
|
-
| [`agent/components-index.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json) |
|
|
56
|
+
| [`agent/components-index.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json) | 50 KB | all 166 as name + group + tagline — **fetch this first** |
|
|
57
57
|
| [`agent/components/<Name>.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components/Select.json) | 1 KB–37 KB | one file per component, each with its `importPath` — **this is the route to take** |
|
|
58
|
-
| [`agent/components.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components.json) | 1.
|
|
59
|
-
| [`agent/tokens.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/tokens.json) | 857 KB | all
|
|
58
|
+
| [`agent/components.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components.json) | 1.3 MB | all 166 entries in one file — most URL fetchers truncate this silently; prefer the per-component files |
|
|
59
|
+
| [`agent/tokens.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/tokens.json) | 857 KB | all 2230 design tokens, their values and why they exist |
|
|
60
60
|
| [`agent/vocabulary.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/vocabulary.json) | 7 KB | the controlled prop vocabulary |
|
|
61
|
-
| [`agent/rules.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/rules.json) |
|
|
61
|
+
| [`agent/rules.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/rules.json) | 23 KB | the 44 cardinal rules |
|
|
62
62
|
<!-- prettier-ignore-end -->
|
|
63
63
|
<!-- agent-catalog-files:end -->
|
|
64
64
|
|
package/agent/START-HERE.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
You are about to write code against a design system you did not author. This file is the whole
|
|
4
4
|
contract. Read it before you write JSX.
|
|
5
5
|
|
|
6
|
-
**This catalog describes `@godxjp/ui` 33.
|
|
6
|
+
**This catalog describes `@godxjp/ui` 33.3.0.** If the project you are editing has a different
|
|
7
7
|
version in its `package.json`, read the pinned catalog for THAT version instead
|
|
8
8
|
(`…/v<their-version>/agent/…`). A catalog newer than the installed package describes props that do
|
|
9
9
|
not exist yet; older, and it hides props that do. Neither failure announces itself.
|
|
@@ -48,11 +48,11 @@ inspect every screenshot at every width region by region (no top-N cap), and han
|
|
|
48
48
|
→ These files are for you. Read `DESIGN.md` (in this folder) first: tokens, scarcity rules and
|
|
49
49
|
screen anatomy, the visual target the rules below do not give you. Then fetch in this order:
|
|
50
50
|
|
|
51
|
-
0. `patterns-index.json` —
|
|
51
|
+
0. `patterns-index.json` — 28 whole-task patterns as name + tagline + tags. **If your
|
|
52
52
|
task is a task** — "build a settings page", "confirm a destructive delete", "a list page with
|
|
53
53
|
filters" — start HERE, not at the components. Then fetch `patterns/<name>.json` for complete,
|
|
54
54
|
copy-paste-ready code. A component index answers "does X exist"; it cannot answer "build Y".
|
|
55
|
-
1. `components-index.json` —
|
|
55
|
+
1. `components-index.json` — 50 KB, all 166 components as name + group +
|
|
56
56
|
tagline. Read this when you already know the SHAPE you need. Each entry may carry `absorbed`:
|
|
57
57
|
names that **do not exist** and map to it — `Combobox`, `Autocomplete`, `CountrySelect` and
|
|
58
58
|
`SearchSelect` are all `Select`. If you are about to hand-roll something, search this field
|
|
@@ -61,15 +61,15 @@ screen anatomy, the visual target the rules below do not give you. Then fetch in
|
|
|
61
61
|
its `importPath`, and its examples. Fetch only the handful you picked in step 1.
|
|
62
62
|
3. `rules.json` — 44 cardinal rules. The ones about raw HTML and hardcoded colour are not
|
|
63
63
|
style advice.
|
|
64
|
-
4. `tokens.json` —
|
|
64
|
+
4. `tokens.json` — 2230 design tokens, each tagged with its `tier`. **If you were handed a
|
|
65
65
|
brand, read the 216 `foundation` entries first** — `--primary`, `--background`,
|
|
66
66
|
`--radius`, `--font-size-base` are the handful everything else derives from. The
|
|
67
|
-
|
|
67
|
+
1901 `component` entries are per-part knobs; reach for one only when a role is
|
|
68
68
|
right everywhere except one component.
|
|
69
69
|
5. `anti-ai-tells.json` — 35 shapes that make generated UI look generated, each with the
|
|
70
70
|
fix. Read before you reach for a gradient hero or a wall of coloured chips.
|
|
71
71
|
|
|
72
|
-
**Do not fetch `components.json`.** It is 1.
|
|
72
|
+
**Do not fetch `components.json`.** It is 1.3 MB, and most URL fetchers truncate a
|
|
73
73
|
response that size and return the head without telling you. You get the first few entries, believe
|
|
74
74
|
you read the catalog, and answer the rest from memory — which is the failure this file exists to
|
|
75
75
|
prevent. The per-component files say the same thing without the cliff.
|
|
@@ -150,7 +150,7 @@ has stopped following the brand.
|
|
|
150
150
|
|---|---|---|---|
|
|
151
151
|
| `foundation` | 216 | the seeds — `--primary`, `--background`, `--foreground`, `--radius`, `--font-size-base`, `--shadow-color`. Everything below is derived from these | **yes — this is the main road.** The default `--primary` is a neutral placeholder: set it (`:root { --primary: <H> <S>% <L>%; }`, HSL components, no `hsl()` wrapper) or import a theme preset such as `@godxjp/ui/themes/godx.css` |
|
|
152
152
|
| `semantic` | 113 | named roles that follow the seeds — `--ring`, `--text-link`, `--primary-hover`, `--overlay-background` | only when the seed is right and ONE role must differ. That role then stops following a later brand change |
|
|
153
|
-
| `component` |
|
|
153
|
+
| `component` | 1901 | per-part knobs, `--{component}-{part}-{property}` | rarely. Most are declared `initial` with the real default at the call site — deliberate, so a scoped override re-resolves instead of freezing at `:root` |
|
|
154
154
|
|
|
155
155
|
A token whose `value` is `initial` is not empty and not broken: `initial` is the guaranteed-invalid
|
|
156
156
|
value, so the real default is computed where the element paints it. Set it and yours wins.
|
|
@@ -126,6 +126,12 @@
|
|
|
126
126
|
"description": "Renders automatically only when there are no matching results. Place inside CommandList.",
|
|
127
127
|
"name": "CommandEmpty",
|
|
128
128
|
"type": "React.ReactNode (children)"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"defaultValue": "true",
|
|
132
|
+
"description": "CommandSeparator is cmdk's Command.Separator (data-slot=\"command-separator\", ref forwarded, className merged): a hairline between two groups of one list. Default true — the OPPOSITE of cmdk, which drops the separator once the search is non-empty; a `+ Create \"<q>\"` group only exists while the user types, so the rule above it must stay. Pass false for cmdk's palette behaviour. Hidden automatically beside a group cmdk has hidden (no matching rows) and under `split` (which already rules group boundaries). aria-hidden: decorative, since a separator is not an allowed listbox child. Tokens: --command-item-divider-width / --command-item-divider-color (→ --stroke-hairline / --border), margin-block --command-group-padding.",
|
|
133
|
+
"name": "CommandSeparator.alwaysRender",
|
|
134
|
+
"type": "boolean"
|
|
129
135
|
}
|
|
130
136
|
],
|
|
131
137
|
"related": [
|
|
@@ -146,7 +152,8 @@
|
|
|
146
152
|
"CommandGroup",
|
|
147
153
|
"CommandInput",
|
|
148
154
|
"CommandItem",
|
|
149
|
-
"CommandList"
|
|
155
|
+
"CommandList",
|
|
156
|
+
"CommandSeparator"
|
|
150
157
|
],
|
|
151
158
|
"tagline": "Accessible, keyboard-navigable command palette / combobox list built on cmdk — always pair CommandInput inside its own wrapper div, never render items outside CommandList.",
|
|
152
159
|
"usage": [
|
|
@@ -155,6 +162,7 @@
|
|
|
155
162
|
"DO always provide a stable explicit value prop on CommandItem when the item's text content can change between renders — relying on inferred textContent with dynamic labels causes selection bugs.",
|
|
156
163
|
"DO include CommandEmpty inside CommandList to show a no-results message. It renders automatically only when the filtered count is zero; do not conditionally render it yourself.",
|
|
157
164
|
"DO use split for checkbox/option lists (a filter facet, a picker: Popover + PopoverContent flush + Command split) — rows get a divider between them and 0 list padding, so they read as one box. Keep the default (unsplit) for a command palette. Never re-create the look with page CSS (border on items, padding 0 on the group): retune --command-item-divider-color / --command-item-divider-width instead.",
|
|
165
|
+
"DO divide a result group from an action group with CommandSeparator — matches, then `<CommandSeparator />`, then `<CommandGroup forceMount><CommandItem forceMount value={`__create__${q}`}>+ Create \"{q}\"</CommandItem></CommandGroup>` rendered only while the query has no exact match. Give the create item an explicit value (never inferred from its changing text) and forceMount both it and its group so the filter cannot hide it. Never keep a local copy of the Command family for the divider — a compound must come from one cmdk instance.",
|
|
158
166
|
"DON'T use CommandInput as a standalone search input — it is only meaningful inside a Command root (the root manages shared filter state). For a standalone search field use SearchInput instead.",
|
|
159
167
|
"DON'T hand-roll keyboard navigation on a list of items; Command handles arrow keys, Enter, Escape, Home/End, and vim bindings. Adding your own keyDown handlers on top creates conflicts — use onSelect on CommandItem for selection logic."
|
|
160
168
|
],
|
|
@@ -21,9 +21,9 @@
|
|
|
21
21
|
"type": "\"rows\" | \"columns\""
|
|
22
22
|
},
|
|
23
23
|
{
|
|
24
|
-
"description": "antd `Row align`, spelled with Flex's FlexAlignProp values (antd `top` → \"start\"). \"stretch\" makes every cell as tall as the tallest one. Omitted, flow=\"columns\" keeps \"start\" and flow=\"rows\" keeps the grid's natural stretch.",
|
|
24
|
+
"description": "antd `Row align`, spelled with Flex's FlexAlignProp values (antd `top` → \"start\", `middle` → \"center\"). \"center\" puts cells of different heights on one centre line (a condition row's number and +/− buttons beside taller controls). \"stretch\" makes every cell as tall as the tallest one. Omitted, flow=\"columns\" keeps \"start\" and flow=\"rows\" keeps the grid's natural stretch.",
|
|
25
25
|
"name": "align",
|
|
26
|
-
"type": "\"start\" | \"stretch\""
|
|
26
|
+
"type": "\"start\" | \"center\" | \"stretch\""
|
|
27
27
|
},
|
|
28
28
|
{
|
|
29
29
|
"defaultValue": "4",
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
{
|
|
2
|
+
"absorbed": [
|
|
3
|
+
"QueryFilter",
|
|
4
|
+
"SearchPanel"
|
|
5
|
+
],
|
|
6
|
+
"docPath": "navigation/search-form",
|
|
7
|
+
"example": "import { SearchForm } from \"@godxjp/ui/navigation\";\nimport { FormField, Input, Select, DatePicker } from \"@godxjp/ui/data-entry\";\n\n<SearchForm\n controlWidth=\"100%\"\n onSubmit={(e) => { e.preventDefault(); setApplied(draft); }}\n onReset={() => { setDraft(EMPTY); setApplied(EMPTY); }}\n summary=\"国籍: ベトナム / 状態: 実習中\"\n resultCount={rows.length}\n>\n <FormField label=\"氏名\" id=\"q-name\"><Input id=\"q-name\" value={draft.name} onChange={(e) => set({ name: e.target.value })} /></FormField>\n <FormField label=\"国籍\" id=\"q-nat\"><Select id=\"q-nat\" options={nationalities} value={draft.nationality} onValueChange={(v) => set({ nationality: v })} /></FormField>\n <FormField label=\"入国日\" id=\"q-entry\"><DatePicker id=\"q-entry\" range /></FormField>\n <FormField label=\"職種\" id=\"q-occ\"><Select id=\"q-occ\" options={occupations} value={draft.occupation} onValueChange={(v) => set({ occupation: v })} /></FormField>\n</SearchForm>",
|
|
8
|
+
"group": "navigation",
|
|
9
|
+
"importPath": "@godxjp/ui/navigation",
|
|
10
|
+
"name": "SearchForm",
|
|
11
|
+
"props": [
|
|
12
|
+
{
|
|
13
|
+
"description": "One FormField per condition, in reading order. Each direct child is one grid cell; a range (「日付」 From〜To, 「金額」 「下限〜上限」) is ONE FormField holding both controls.",
|
|
14
|
+
"name": "children",
|
|
15
|
+
"required": true,
|
|
16
|
+
"type": "ReactNode"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"defaultValue": "{ base: 1, md: 2, lg: 3 }",
|
|
20
|
+
"description": "Grid columns per container breakpoint — one column on a phone, aligned columns above. Conditions never spread to the edges.",
|
|
21
|
+
"name": "columns",
|
|
22
|
+
"type": "ResponsiveGridColumnsProp"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"description": "Conditions shown while collapsed (antd `defaultFormItemsNumber`). Default: the widest `columns` step, i.e. the first row.",
|
|
26
|
+
"name": "visibleFields",
|
|
27
|
+
"type": "number"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"description": "Controlled: whether the conditions past `visibleFields` are shown.",
|
|
31
|
+
"name": "open",
|
|
32
|
+
"type": "boolean"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"defaultValue": "false",
|
|
36
|
+
"description": "Uncontrolled initial state — collapsed to the first row by default.",
|
|
37
|
+
"name": "defaultOpen",
|
|
38
|
+
"type": "boolean"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"description": "Fires when the 「詳細条件を開く」 / 「閉じる」 toggle is pressed.",
|
|
42
|
+
"name": "onOpenChange",
|
|
43
|
+
"type": "(open: boolean) => void"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"description": "Search (「検索」) — the native form submit. Call `preventDefault()` and copy the draft conditions into the applied query; the form never searches on each keystroke.",
|
|
47
|
+
"name": "onSubmit",
|
|
48
|
+
"type": "FormEventHandler<HTMLFormElement>"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"description": "Clear (「クリア」). The button renders only when this is set.",
|
|
52
|
+
"name": "onReset",
|
|
53
|
+
"type": "() => void"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"description": "The conditions the CURRENT results were searched with, shown after the localized 「検索条件」 label in the status line (e.g. 「国「籍」: 「ベトナム」 / 「状態」: 「実習」中」).",
|
|
57
|
+
"name": "summary",
|
|
58
|
+
"type": "ReactNode"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"description": "Localized, pluralized result count in the same `role=\"status\"` line.",
|
|
62
|
+
"name": "resultCount",
|
|
63
|
+
"type": "number"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"description": "Puts the Search button in its loading state while the query runs.",
|
|
67
|
+
"name": "loading",
|
|
68
|
+
"type": "boolean"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"description": "Overrides the localized Search (「検索」) text.",
|
|
72
|
+
"name": "searchLabel",
|
|
73
|
+
"type": "ReactNode"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"description": "Overrides the localized Clear (「クリア」) text.",
|
|
77
|
+
"name": "resetLabel",
|
|
78
|
+
"type": "ReactNode"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"description": "Accessible name of the `search` landmark (default: localized 'Search conditions').",
|
|
82
|
+
"name": "label",
|
|
83
|
+
"type": "string"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"description": "Passed to the underlying Form: `layout=\"vertical\"` (default) is label-top; `layout=\"horizontal\"` + `labelWidth` is the label-left table style of Japanese business systems. Set `controlWidth` once here so Selects fill their column.",
|
|
87
|
+
"name": "layout / labelWidth / controlWidth / labelAlign",
|
|
88
|
+
"type": "Form props"
|
|
89
|
+
}
|
|
90
|
+
],
|
|
91
|
+
"related": [
|
|
92
|
+
"Toolbar — FilterBar, the live filter strip; use it for a few filters applied as the user changes them.",
|
|
93
|
+
"Form — the layout container SearchForm renders (`role=\"search\"`); its `layout`, `labelWidth` and `controlWidth` pass through.",
|
|
94
|
+
"FormField — one per condition; a range is one FormField holding two controls.",
|
|
95
|
+
"DataTable — the results under the form."
|
|
96
|
+
],
|
|
97
|
+
"rules": [
|
|
98
|
+
23,
|
|
99
|
+
51
|
|
100
|
+
],
|
|
101
|
+
"storyPath": "navigation/search-form.tsx",
|
|
102
|
+
"tagline": "Submit-driven 「検索条件フォーム」 (Ant Design ProComponents QueryFilter): a `search` landmark holding a grid of labelled conditions aligned in columns, Search 「検索」 + Clear 「クリア」, the conditions past the first row behind ONE 「詳細条件を開く」 toggle that names how many are hidden, and a 「検索条件」 summary + result count in a role=\"status\" line after the search. Use it instead of a live FilterBar when the conditions are many, the query is heavy, or users expect an explicit 「検索」 button (rule #51 decision table).",
|
|
103
|
+
"usage": [
|
|
104
|
+
"DO pick the shape from rule #51's decision table: a few filters applied instantly → FilterBar; many conditions / a heavy query / users who expect an explicit 「検索」 button → SearchForm; arbitrary field × operator conditions saved as views → pattern `condition-builder`.",
|
|
105
|
+
"DO put one FormField per condition as a direct child. Ranges (「入国日」 From〜To, 「金額」 「下限〜上限」) are ONE FormField with both controls; 「部分一致」 / 「完全一致」 is a RadioGroup inside the name field.",
|
|
106
|
+
"DO keep a draft and an applied copy of the conditions: inputs edit the draft, onSubmit copies it to the applied query, onReset clears both. Pass the applied conditions as `summary` and the hit count as `resultCount`.",
|
|
107
|
+
"DO NOT wrap it in another Form, and do not rebuild it as a Form + ResponsiveGrid + Collapsible by hand — the collapse, the hidden-count toggle, the search landmark and the status line are what this component owns. Hidden conditions stay mounted, so a typed value survives collapsing.",
|
|
108
|
+
"A SearchForm (or any `<Form role=\"search\" onSubmit>`) above a table is NOT flagged by ui-audit `form-on-list-page` or `hand-rolled-filter-bar`; a submitting create form without role=\"search\" still is."
|
|
109
|
+
],
|
|
110
|
+
"useCases": [
|
|
111
|
+
"「検索条件フォーム」 on a Japanese business-system list (「基幹系」 / 「業務システム」): 6–12 conditions, 「検索」 and 「クリア」 buttons, 「詳細条件を開く」.",
|
|
112
|
+
"Search screens over a heavy query where searching on every keystroke would be too expensive.",
|
|
113
|
+
"Label-left (「表形式」) or label-top search panels above a DataTable, with the applied conditions summarized after the search."
|
|
114
|
+
]
|
|
115
|
+
}
|
|
@@ -101,7 +101,8 @@
|
|
|
101
101
|
"DataTable.Toolbar — the in-table strip for column/density/bulk-action controls; Toolbar (this) is the page-level filter strip that sits ABOVE the table.",
|
|
102
102
|
"SearchInput — the debounced free-text control placed as the first child of Toolbar.",
|
|
103
103
|
"Badge — compose Badge + a sibling icon Button to render each active-filter chip; Badge itself is a non-interactive leaf.",
|
|
104
|
-
"Sheet — holds the filters beyond the 2–4 inline ones, opened by ONE 'More filters' Button inside the bar (pattern `list-filters`, rule #51). Not a ScrollArea: a filter strip never scrolls sideways."
|
|
104
|
+
"Sheet — holds the filters beyond the 2–4 inline ones, opened by ONE 'More filters' Button inside the bar (pattern `list-filters`, rule #51). Not a ScrollArea: a filter strip never scrolls sideways.",
|
|
105
|
+
"SearchForm — the submit-driven 「検索条件フォーム」 for many conditions or an explicit 「検索」 button (rule #51 decision table); FilterBar is the live strip."
|
|
105
106
|
],
|
|
106
107
|
"rules": [
|
|
107
108
|
23,
|
|
@@ -116,7 +117,7 @@
|
|
|
116
117
|
"FilterBar",
|
|
117
118
|
"FilterBarGroup"
|
|
118
119
|
],
|
|
119
|
-
"tagline": "List-page filter strip — SearchInput + labelled ToolbarGroup filter slots (or the typed `search`/`filters`/`chips` model) + a clear-all affordance, optionally sticky. It WRAPS and never scrolls sideways; many filters → 2–4 inline + ONE 'More filters' Sheet (rule #51, pattern `list-filters`). `FilterBar`/`FilterBarGroup` are the same components under their alias names.",
|
|
120
|
+
"tagline": "List-page filter strip — SearchInput + labelled ToolbarGroup filter slots (or the typed `search`/`filters`/`chips` model) + a clear-all affordance, optionally sticky. It WRAPS and never scrolls sideways; many filters → 2–4 inline + ONE 'More filters' Sheet (rule #51, pattern `list-filters`). `FilterBar`/`FilterBarGroup` are the same components under their alias names. Eleven layouts (inline, Filter menu + chips, More-filters Sheet, saved views, two rows, actions + sticky, faceted sidebar, compact in a card, date range, phone, docked panel) → pattern `filter-bar-layouts`.",
|
|
120
121
|
"usage": [
|
|
121
122
|
"DO prefer the typed model on a canonical list page — `search`/`filters`/`chips`/`onChipRemove`/`actions`/`resultCount` (+ `loading`/`disabled`/`error`) make the bar own layout, widths, chip lifecycle and keyboard order through --filter-bar-* tokens while ALL state stays consumer data. Children composition remains fully supported and unchanged when no model prop is passed.",
|
|
122
123
|
"DO place Toolbar ABOVE the table Card, never inside a `CardContent flush`. Put SearchInput as a direct child (it self-labels) and wrap every other control in a ToolbarGroup with a `label`.",
|
|
@@ -124,6 +125,7 @@
|
|
|
124
125
|
"DO set `sticky` for long list pages so the filters stay reachable while scrolling; if a topbar sits above the list, raise `--filter-bar-sticky-offset` so the strip parks below it.",
|
|
125
126
|
"DO pass `controlId` on a ToolbarGroup that wraps ONE control and give that control the same `id` — the visible caption then becomes the control's real `<label htmlFor>` (WCAG 2.5.3 label-in-name). Without it the caption only names the group wrapper and the control itself is NAMELESS (axe: select-name / button-name), so you must fall back to an `aria-label` that repeats the caption.",
|
|
126
127
|
"DO let the strip WRAP — cardinal rule #51 (`get_rule 51`). It stacks below 640px and wraps onto extra rows above; it never scrolls sideways, and neither may anything you wrap it in (`ScrollArea orientation=\"horizontal\"`, an `overflow-x-auto` box): the `overflow` prop and its `scroll` strategy were removed in 33.0.0 because a sideways strip hid filters off-screen and clipped the first label. When the filters are many, keep the 2–4 most-used ones inline and put the rest behind ONE 'More filters' Button (`count` = hidden filters applied) that opens a Sheet holding them as FilterBarGroup + control; every applied filter, inline or hidden, is a chip in `chips` (the chips row wraps too). Copy-paste composition: pattern `list-filters` (`get_pattern list-filters`); there is no dedicated component. Never re-implement the geometry in the page: no page-local flex/grid/width/overflow rules on a filter strip.",
|
|
128
|
+
"DO keep filters PACKED at the start of the bar with its one gap. Never lay them out yourself in a Flex/div/Card/ResponsiveGrid of FormFields above the table, and never spread them with `justify=\"between\"|\"around\"|\"evenly\"` — the only far-end element of a filter bar is its `actions` slot (with the clear button). ui-audit `hand-rolled-filter-bar` flags both. For a layout other than one strip (saved views, a Filter menu with chips, a faceted sidebar, a docked side panel, a compact bar inside a table card, a date range with quick ranges, a phone Sheet), copy pattern `filter-bar-layouts` (docs page navigation/filter-bar-layouts).",
|
|
127
129
|
"DON'T build active-filter chips by nesting a Button inside a Badge (invalid markup + broken focus). Render each chip as a Badge label with a SIBLING icon Button (`aria-label` = 'clear <filter>'); a ghost `size='sm'` Button clears all.",
|
|
128
130
|
"DON'T hand-roll a debounced search box or a raw `<select>` — compose SearchInput and Select. Toolbar is layout + clear-all only; the controls own their own state and a11y.",
|
|
129
131
|
"Prefer the typed model for a canonical list page: pass `search` + `filters` + `chips` + `resultCount` and the bar owns the layout, widths, chip lifecycle, keyboard order (search → filters → children → reset → actions → chip removes) and responsive stacking through --filter-bar-* tokens. Everything stays consumer data — the bar renders state, never owns it.",
|
|
@@ -134,6 +136,7 @@
|
|
|
134
136
|
"useCases": [
|
|
135
137
|
"Master list screens (members, organizations, subscriptions, invoices) that need free-text search plus a few dropdown filters above a DataTable.",
|
|
136
138
|
"Sticky filter strip over a tall list where the filters must remain visible as the user scrolls the results.",
|
|
137
|
-
"Filtered views that surface applied conditions as removable chips (clear-one via each chip's × Button, clear-all via `onClear`)."
|
|
139
|
+
"Filtered views that surface applied conditions as removable chips (clear-one via each chip's × Button, clear-all via `onClear`).",
|
|
140
|
+
"Filter layout choices — saved views filter tabs above the bar, a search-first bar with one Filter menu, a faceted filter sidebar with per-facet counts, a docked filter panel beside the table, a compact table toolbar, a date range filter with quick ranges, a phone filter Sheet: pattern `filter-bar-layouts`."
|
|
138
141
|
]
|
|
139
142
|
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
{
|
|
2
|
+
"example": "import { FormField, TranslatableInput, TranslatableTextarea, type TranslatableValue } from \"@godxjp/ui/data-entry\";\n\nconst [productName, setProductName] = React.useState<TranslatableValue>({ ja: \"醤油ラーメン\" });\nconst [description, setDescription] = React.useState<TranslatableValue>({});\n\n<FormField label=\"商品名\" required>\n <TranslatableInput\n locales={[\"ja\", \"en\", \"vi\"]}\n fallbackLocale=\"ja\"\n value={productName}\n onValueChange={setProductName}\n errors={{ en: \"英語名を入力してください\" }}\n />\n</FormField>\n\n<FormField label=\"説明\">\n <TranslatableTextarea locales={[\"ja\", \"en\", \"vi\"]} value={description} onValueChange={setDescription} rows={3} />\n</FormField>",
|
|
3
|
+
"group": "data-entry",
|
|
4
|
+
"importPath": "@godxjp/ui/data-entry",
|
|
5
|
+
"name": "TranslatableInput",
|
|
6
|
+
"props": [
|
|
7
|
+
{
|
|
8
|
+
"description": "Controlled per-locale values — `Partial<Record<string, string>>` keyed by BCP-47 tag. A missing key or `\"\"` is an empty locale.",
|
|
9
|
+
"name": "value",
|
|
10
|
+
"type": "TranslatableValue"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"description": "Uncontrolled initial per-locale values.",
|
|
14
|
+
"name": "defaultValue",
|
|
15
|
+
"type": "TranslatableValue"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"description": "The WHOLE map after an edit: only the active locale's key changes, every other locale is carried over untouched. The ✕ of `allowClear` empties the active locale only.",
|
|
19
|
+
"name": "onValueChange",
|
|
20
|
+
"type": "(value: TranslatableValue) => void"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"description": "The BCP-47 tags the field edits, in tab order. Default: every locale registered with the app (`getRegisteredLocales()` — built-in `vi`/`en`/`ja` plus `registerLocale`). Tabs are named by `Intl.DisplayNames` in the reader's UI language; the tags stay the keys.",
|
|
24
|
+
"name": "locales",
|
|
25
|
+
"type": "readonly string[]"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"description": "The locale tab selected first. Default: the AppProvider's active locale when it is in `locales`, else the fallback locale.",
|
|
29
|
+
"name": "defaultLocale",
|
|
30
|
+
"type": "string"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"description": "The locale whose text an EMPTY locale shows as its placeholder. Default: the AppProvider's `fallbackLocale`, or the first of `locales` when that is not one of them.",
|
|
34
|
+
"name": "fallbackLocale",
|
|
35
|
+
"type": "string"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"description": "Per-locale validation messages. Each locale that has one gets an error mark on its tab — a glyph plus screen-reader text («Vietnamese, has an error»), never colour alone. The ACTIVE locale's message sets `aria-invalid` and is shown in the enclosing FormField's own error line (wired by `aria-errormessage`); without a FormField it is shown under the field.",
|
|
39
|
+
"name": "errors",
|
|
40
|
+
"type": "Partial<Record<string, ReactNode>>"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"defaultValue": "\"md\"",
|
|
44
|
+
"description": "The field's control tier, as on Input. The tab strip uses the Tabs `sm` tier (`md` for an `lg` field).",
|
|
45
|
+
"name": "size",
|
|
46
|
+
"type": "\"xs\" | \"sm\" | \"md\" | \"lg\""
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"description": "Shown while the active locale is empty and there is no fallback text to show (the fallback locale itself, or an empty fallback).",
|
|
50
|
+
"name": "placeholder",
|
|
51
|
+
"type": "string"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"description": "Disables the control; the locale tabs stay operable so every locale can still be read.",
|
|
55
|
+
"name": "disabled",
|
|
56
|
+
"type": "boolean"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"description": "Lands on the control, so a FormField label click focuses it.",
|
|
60
|
+
"name": "id",
|
|
61
|
+
"type": "string"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"description": "Root (strip + field) class override.",
|
|
65
|
+
"name": "className",
|
|
66
|
+
"type": "string"
|
|
67
|
+
}
|
|
68
|
+
],
|
|
69
|
+
"related": [
|
|
70
|
+
"TranslatableTextarea — the same contract over a Textarea.",
|
|
71
|
+
"Input — the control it composes; every Input prop applies.",
|
|
72
|
+
"FormField — the label / helper / error wiring it reports into.",
|
|
73
|
+
"Tabs — the switcher it composes (strip-only, `controls` → the field's panel).",
|
|
74
|
+
"AppSettingPicker kind=\"locale\" — chooses the UI language, not per-locale content."
|
|
75
|
+
],
|
|
76
|
+
"rules": [],
|
|
77
|
+
"storyPath": "data-entry/translatable-input.tsx",
|
|
78
|
+
"tagline": "ONE text field holding a value per locale (`{ ja, en, vi }`), switched by locale tabs above it (gh#1336). For user content stored per locale — a product name, a category label, a menu description (Astrotomic / Spatie `*_translations`). Locales, first tab and fallback come from AppProvider; an empty locale shows the fallback locale's text as its placeholder; a locale with an error is marked on its tab. Every other `Input` prop applies to the one control.",
|
|
79
|
+
"usage": [
|
|
80
|
+
"DO wrap it in FormField: the label names the control together with the active locale («商品名 English») and names the tab strip («商品名 Language»), `required`/`helper` reach the control, and the active locale's error renders in FormField's error line.",
|
|
81
|
+
"DO pass the per-locale error map from your validator as `errors` (e.g. Laravel `name.ja` → `errors.ja`). Don't pass the same message to FormField `error` as well: FormField's own `error` wins over the reported one, and it would mark the field invalid on every locale.",
|
|
82
|
+
"DO set `fallbackLocale` to the content's SOURCE language (often `ja`) when it differs from the AppProvider's UI fallback — the placeholder of an untranslated locale then shows the source text to translate from.",
|
|
83
|
+
"DON'T build a local wrapper of Tabs + Input for this: the tab↔panel ARIA, the lang attribute, the error marks and the FormField wiring are what this component exists for.",
|
|
84
|
+
"DON'T use it for a choice of language that IS the form's value (a user's preferred language): that is `AppSettingPicker kind=\"locale\"` or a Select.",
|
|
85
|
+
"The locale switcher is the WAI-ARIA APG tabs pattern (tablist → one tabpanel holding the field), not a Segmented radio group: the locale is not form state — every locale is submitted together — the tab only chooses which locale the one field shows."
|
|
86
|
+
],
|
|
87
|
+
"useCases": [
|
|
88
|
+
"Product / menu item name in ja, en and vi on an admin edit form",
|
|
89
|
+
"Category or section label stored per locale (Astrotomic `*_translations`)",
|
|
90
|
+
"Translated description or recipe text (TranslatableTextarea)"
|
|
91
|
+
]
|
|
92
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"example": "import { FormField, TranslatableTextarea } from \"@godxjp/ui/data-entry\";\n\n<FormField label=\"説明\">\n <TranslatableTextarea locales={[\"ja\", \"en\", \"vi\"]} value={description} onValueChange={setDescription} autoGrow minRows={3} />\n</FormField>",
|
|
3
|
+
"group": "data-entry",
|
|
4
|
+
"importPath": "@godxjp/ui/data-entry",
|
|
5
|
+
"name": "TranslatableTextarea",
|
|
6
|
+
"props": [
|
|
7
|
+
{
|
|
8
|
+
"description": "Controlled per-locale values, keyed by BCP-47 tag.",
|
|
9
|
+
"name": "value",
|
|
10
|
+
"type": "TranslatableValue"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"description": "Uncontrolled initial per-locale values.",
|
|
14
|
+
"name": "defaultValue",
|
|
15
|
+
"type": "TranslatableValue"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"description": "The whole map after an edit; only the active locale's key changes.",
|
|
19
|
+
"name": "onValueChange",
|
|
20
|
+
"type": "(value: TranslatableValue) => void"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"description": "The BCP-47 tags the field edits, in tab order. Default: the app's registered locales.",
|
|
24
|
+
"name": "locales",
|
|
25
|
+
"type": "readonly string[]"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"description": "The locale tab selected first. Default: the AppProvider's active locale.",
|
|
29
|
+
"name": "defaultLocale",
|
|
30
|
+
"type": "string"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"description": "The locale whose text an empty locale shows as its placeholder. Default: the AppProvider's `fallbackLocale`.",
|
|
34
|
+
"name": "fallbackLocale",
|
|
35
|
+
"type": "string"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"description": "Per-locale messages: tab marks for each, `aria-invalid` + FormField's error line for the active one.",
|
|
39
|
+
"name": "errors",
|
|
40
|
+
"type": "Partial<Record<string, ReactNode>>"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"defaultValue": "\"md\"",
|
|
44
|
+
"description": "The field's control tier, as on Textarea.",
|
|
45
|
+
"name": "size",
|
|
46
|
+
"type": "\"sm\" | \"md\" | \"lg\""
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"description": "Shown while the active locale is empty and there is no fallback text.",
|
|
50
|
+
"name": "placeholder",
|
|
51
|
+
"type": "string"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"description": "Disables the control; the locale tabs stay operable.",
|
|
55
|
+
"name": "disabled",
|
|
56
|
+
"type": "boolean"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"description": "Lands on the control.",
|
|
60
|
+
"name": "id",
|
|
61
|
+
"type": "string"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"description": "Root class override.",
|
|
65
|
+
"name": "className",
|
|
66
|
+
"type": "string"
|
|
67
|
+
}
|
|
68
|
+
],
|
|
69
|
+
"related": [
|
|
70
|
+
"TranslatableInput — the single-line form and the full contract.",
|
|
71
|
+
"Textarea — the control it composes."
|
|
72
|
+
],
|
|
73
|
+
"rules": [],
|
|
74
|
+
"storyPath": "data-entry/translatable-input.tsx",
|
|
75
|
+
"tagline": "TranslatableInput over a Textarea (gh#1336): one multi-line field holding a value per locale, switched by locale tabs. Same locale, fallback-placeholder, error-mark and FormField contract; every Textarea prop (rows, autoGrow, count…) applies to the one control.",
|
|
76
|
+
"usage": [
|
|
77
|
+
"DO use it for per-locale multi-line content (a description, a recipe); for a single line use TranslatableInput.",
|
|
78
|
+
"DO wrap it in FormField, exactly as TranslatableInput."
|
|
79
|
+
],
|
|
80
|
+
"useCases": [
|
|
81
|
+
"Menu item description in ja, en and vi",
|
|
82
|
+
"Recipe or notice body stored per locale"
|
|
83
|
+
]
|
|
84
|
+
}
|
|
@@ -408,6 +408,16 @@
|
|
|
408
408
|
"name": "Textarea",
|
|
409
409
|
"tagline": "Styled wrapper around native <textarea>. Pair with FormField for labelled fields. For a Markdown body (wiki page, issue description, mail) use the sibling package `@godxjp/editor` `MarkdownEditor` — this Textarea plus a formatting toolbar, preview through `@godxjp/markdown`, and paste/drop upload through a host `upload` function (gh#1109)."
|
|
410
410
|
},
|
|
411
|
+
{
|
|
412
|
+
"group": "data-entry",
|
|
413
|
+
"name": "TranslatableInput",
|
|
414
|
+
"tagline": "ONE text field holding a value per locale (`{ ja, en, vi }`), switched by locale tabs above it (gh#1336). For user content stored per locale — a product name, a category label, a menu description (Astrotomic / Spatie `*_translations`). Locales, first tab and fallback come from AppProvider; an empty locale shows the fallback locale's text as its placeholder; a locale with an error is marked on its tab. Every other `Input` prop applies to the one control."
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
"group": "data-entry",
|
|
418
|
+
"name": "TranslatableTextarea",
|
|
419
|
+
"tagline": "TranslatableInput over a Textarea (gh#1336): one multi-line field holding a value per locale, switched by locale tabs. Same locale, fallback-placeholder, error-mark and FormField contract; every Textarea prop (rows, autoGrow, count…) applies to the one control."
|
|
420
|
+
},
|
|
411
421
|
{
|
|
412
422
|
"group": "data-entry",
|
|
413
423
|
"name": "Label",
|
|
@@ -483,7 +493,16 @@
|
|
|
483
493
|
{
|
|
484
494
|
"group": "navigation",
|
|
485
495
|
"name": "Toolbar",
|
|
486
|
-
"tagline": "List-page filter strip — SearchInput + labelled ToolbarGroup filter slots (or the typed `search`/`filters`/`chips` model) + a clear-all affordance, optionally sticky. It WRAPS and never scrolls sideways; many filters → 2–4 inline + ONE 'More filters' Sheet (rule #51, pattern `list-filters`). `FilterBar`/`FilterBarGroup` are the same components under their alias names."
|
|
496
|
+
"tagline": "List-page filter strip — SearchInput + labelled ToolbarGroup filter slots (or the typed `search`/`filters`/`chips` model) + a clear-all affordance, optionally sticky. It WRAPS and never scrolls sideways; many filters → 2–4 inline + ONE 'More filters' Sheet (rule #51, pattern `list-filters`). `FilterBar`/`FilterBarGroup` are the same components under their alias names. Eleven layouts (inline, Filter menu + chips, More-filters Sheet, saved views, two rows, actions + sticky, faceted sidebar, compact in a card, date range, phone, docked panel) → pattern `filter-bar-layouts`."
|
|
497
|
+
},
|
|
498
|
+
{
|
|
499
|
+
"absorbed": [
|
|
500
|
+
"QueryFilter",
|
|
501
|
+
"SearchPanel"
|
|
502
|
+
],
|
|
503
|
+
"group": "navigation",
|
|
504
|
+
"name": "SearchForm",
|
|
505
|
+
"tagline": "Submit-driven 「検索条件フォーム」 (Ant Design ProComponents QueryFilter): a `search` landmark holding a grid of labelled conditions aligned in columns, Search 「検索」 + Clear 「クリア」, the conditions past the first row behind ONE 「詳細条件を開く」 toggle that names how many are hidden, and a 「検索条件」 summary + result count in a role=\"status\" line after the search. Use it instead of a live FilterBar when the conditions are many, the query is heavy, or users expect an explicit 「検索」 button (rule #51 decision table)."
|
|
487
506
|
},
|
|
488
507
|
{
|
|
489
508
|
"group": "providers",
|