@godxjp/ui 30.3.1 → 30.4.1

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.
@@ -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` 30.3.1.** If the project you are editing has a different
6
+ **This catalog describes `@godxjp/ui` 30.4.1.** 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.
@@ -47,7 +47,7 @@ Then ask it: `search_components`, `get_component`, `get_tokens`, `get_rule`, `li
47
47
  task is a task** — "build a settings page", "confirm a destructive delete", "a list page with
48
48
  filters" — start HERE, not at the components. Then fetch `patterns/<name>.json` for complete,
49
49
  copy-paste-ready code. A component index answers "does X exist"; it cannot answer "build Y".
50
- 1. `components-index.json` — 45 KB, all 171 components as name + group +
50
+ 1. `components-index.json` — 45 KB, all 172 components as name + group +
51
51
  tagline. Read this when you already know the SHAPE you need. Each entry may carry `absorbed`:
52
52
  names that **do not exist** and map to it — `Combobox`, `Autocomplete`, `CountrySelect` and
53
53
  `SearchSelect` are all `Select`. If you are about to hand-roll something, search this field
@@ -76,14 +76,14 @@
76
76
  "type": "React.ReactNode"
77
77
  },
78
78
  {
79
- "description": "Slot BELOW the draft row — a hint line, a token counter.",
79
+ "description": "Slot BELOW the draft row — a hint line, a token counter, OR (Ant Design X Sender NodeRender) a function that receives the real SubmitButton/CancelButton components, pre-wired, so they can be rendered in the footer instead of beside the draft box. Pair with actions={false} to hide the inline pair.",
80
80
  "name": "footer",
81
- "type": "React.ReactNode"
81
+ "type": "React.ReactNode | ((info: { components: ChatComposerActionComponents }) => React.ReactNode)"
82
82
  },
83
83
  {
84
- "description": "Extra trailing actions, rendered BEFORE the send/cancel action.",
84
+ "description": "Extra trailing actions, rendered BEFORE the send/cancel action — or false (Ant Design X Sender suffix semantics) to hide the ENTIRE inline trailing-action cell, built-in send/cancel button included, once footer has taken it over. Omitted renders exactly as before.",
85
85
  "name": "actions",
86
- "type": "React.ReactNode"
86
+ "type": "React.ReactNode | false"
87
87
  },
88
88
  {
89
89
  "defaultValue": "\"md\"",
@@ -148,11 +148,13 @@
148
148
  "DON'T render your own stop button beside the send button — set `loading` and the trailing action becomes cancel. Exactly one trailing action exists at a time (the picker trailing-action discipline).",
149
149
  "DO put a hint in `footer` (t('dataEntry.chatComposer.hintEnter') / 'hintShiftEnter') when you flip `submitType` — the keystroke contract is invisible otherwise. For submitType=\"modEnter\" use t('dataEntry.chatComposer.hintModEnter', { modifier: isApplePlatform() ? '⌘' : 'Ctrl' }) with isApplePlatform from @godxjp/ui/lib/utils — the same platform test the composer uses to pick metaKey vs ctrlKey.",
150
150
  "DO set `allowEmptySubmit` (not a hidden fake draft) when the composer also submits field changes from `header`/`footer`; your onSubmit receives \"\" and decides whether anything changed.",
151
- "DON'T size it with a className height: the box grows between --chat-composer-min-height and --chat-composer-max-height, both derived from the --control-height tier. Use `size`, or re-tune the two tokens in your theme."
151
+ "DON'T size it with a className height: the box grows between --chat-composer-min-height and --chat-composer-max-height, both derived from the --control-height tier. Use `size`, or re-tune the two tokens in your theme.",
152
+ "DO move the toolbar BELOW a full-width draft box with `actions={false}` + a function `footer` — the function receives `{ components: { SubmitButton, CancelButton } }`, both already wired to onSubmit/onCancel/disabled/the loading swap, so nothing about the send contract is reimplemented at the call site. Put the attach control and a keyboard hint on the START side of the footer row and `<SubmitButton />` on the END side (a `Flex justify=\"between\"`), exactly like the record-comment box case. Leaving `actions` unset keeps today's inline row — this is opt-in, not a breaking change."
152
153
  ],
153
154
  "useCases": [
154
155
  "The message box of an AI assistant or support chat, under a ChatBubbleList feed.",
155
156
  "A comment composer on a record detail screen (prefix = attach Button, footer = character counter).",
157
+ "A record-detail comment box with a full-width textarea and a toolbar row BELOW it — attach on the start side, a keyboard hint in the middle, clear/send on the end side: `actions={false}` + `footer={({ components }) => <Flex justify=\"between\">…<components.SubmitButton /></Flex>}`.",
156
158
  "A long-form reply box where Enter must break the line: submitType=\"shiftEnter\".",
157
159
  "A comment bar on an issue/record where Enter breaks the line and ⌘/Ctrl+Enter posts, and a status change may be posted without text: submitType=\"modEnter\" + allowEmptySubmit.",
158
160
  "A streaming answer the user can stop: loading + onCancel."
@@ -0,0 +1,61 @@
1
+ {
2
+ "docPath": "layout/space-compact.tsx",
3
+ "example": "import { SpaceCompact } from \"@godxjp/ui/layout\";\nimport { FormField, NumberInput, Select } from \"@godxjp/ui/data-entry\";\n\n<FormField label=\"繰り返し間隔\">\n <SpaceCompact>\n <NumberInput aria-label=\"間隔の数\" min={1} defaultValue={2} />\n <Select\n aria-label=\"単位\"\n defaultValue=\"week\"\n options={[\n { value: \"day\", label: \"日\" },\n { value: \"week\", label: \"週\" },\n { value: \"month\", label: \"月\" },\n ]}\n />\n </SpaceCompact>\n</FormField>",
4
+ "group": "layout",
5
+ "importPath": "@godxjp/ui/layout",
6
+ "name": "SpaceCompact",
7
+ "props": [
8
+ {
9
+ "defaultValue": "\"horizontal\"",
10
+ "description": "Layout axis (antd `orientation`; the installed antd `direction` prop is itself @deprecated in favour of this, so only `orientation` is ported).",
11
+ "name": "orientation",
12
+ "type": "\"horizontal\" | \"vertical\""
13
+ },
14
+ {
15
+ "description": "antd's boolean spelling of orientation=\"vertical\". `orientation` wins when both are given.",
16
+ "name": "vertical",
17
+ "type": "boolean"
18
+ },
19
+ {
20
+ "defaultValue": "false",
21
+ "description": "The row fills its parent's inline size (antd `block`, renamed to match Button.fullWidth — the same rename, the same reason: this library's controlled vocabulary wins on values).",
22
+ "name": "fullWidth",
23
+ "type": "boolean"
24
+ },
25
+ {
26
+ "description": "Scoped control density for the whole row (antd Space.Compact `size`, small|middle|large — the ConfigProvider ambient-size cascade this library already owns under `density`; see `Form.density`). Each child still carries its OWN `size` prop.",
27
+ "name": "density",
28
+ "type": "\"compact\" | \"default\" | \"comfortable\""
29
+ },
30
+ {
31
+ "description": "DOM id of the row.",
32
+ "name": "id",
33
+ "type": "string"
34
+ }
35
+ ],
36
+ "related": [
37
+ "Flex `gap` — siblings that stay visually SEPARATE (a normal control row with spacing between items). SpaceCompact is the opposite case: controls that must read as ONE box.",
38
+ "ResponsiveGrid — equal-width form CELLS; SpaceCompact welds controls WITHIN one cell (or one row) into a single unit.",
39
+ "FormField — wraps the whole SpaceCompact with one label when the pair is one field.",
40
+ "NumberInput, Select — the two controls the shipping use case welds; either composes cleanly inside SpaceCompact because both already expose the radius knob it reads (`--input-radius-*`, `--control-trigger-radius-*`)."
41
+ ],
42
+ "rules": [
43
+ 2
44
+ ],
45
+ "storyPath": "layout/SpaceCompact.stories.tsx",
46
+ "tagline": "Weld a row (or column) of controls into one visual unit — antd Space.Compact. Inner corner radii are zeroed and the shared border collapses to one hairline; each child keeps its own focus ring and its own size.",
47
+ "usage": [
48
+ "DO reach for it when two or more controls must read as ONE field — a number + a unit (\"毎 [2] [週 ▾] ごと\"), a currency Select welded to an amount Input, a search field with an attached filter Select. `ResponsiveGrid columns={2}` puts them in separate cells (which collapses to ONE column on a narrow form card); SpaceCompact keeps them fused on one line regardless of the grid.",
49
+ "ONE label for the pair vs per-control: wrap the WHOLE `SpaceCompact` in a single `FormField label=\"…\"` when the pair reads as one field (\"繰り返し間隔\" over a number+unit row) — a role-less `<div>` cannot carry a cloned `aria-label`/`aria-labelledby` (axe `aria-allowed-attr`), so a NAMED SpaceCompact with no explicit `role` promotes itself to `role=\"group\"` automatically, the same contract `Flex` already honours for a range/年月 pair. Give each CHILD its own `aria-label` instead only when the two controls are independently meaningful outside the row (rare — prefer the one-label pattern for a welded control).",
50
+ "DON'T reach for `className=\"rounded-none\"` / `[&>*:not(:first-child)]` utilities to join controls yourself — `ui-audit`'s `no-utility-layout` rule blocks exactly that, and it is precisely the corner-radius seam this component owns.",
51
+ "DO set `size` on each CHILD individually, not on SpaceCompact — the row has no size prop of its own by design (see `density`); every control already owns its own `size` axis (`xs|sm|md|lg`).",
52
+ "DO use `fullWidth` inside a narrow form card so the joined row spans the field column, exactly like a lone Input would.",
53
+ "`fullWidth` sets NO per-child flex ratio — same as antd, whose Space.Compact block style only stretches the ROW (`display: flex; width: 100%`), never the children. Two fields (NumberInput+Select, Input+Select) split the row evenly because both already default to their own full width; a Button beside a field keeps its own content width and the field absorbs the rest (Select+Button, Input+Button) — the same split you get outside SpaceCompact.",
54
+ "DON'T expect corner-radius welding on `orientation=\"vertical\"` yet — the shared border still collapses, but each child keeps all four of its own corners rounded until a block-axis radius knob exists on the Input/trigger families (documented gap, not a silent one)."
55
+ ],
56
+ "useCases": [
57
+ "定期課題 (recurring issue) interval row: 毎 [NumberInput] [Select 週/月/日 ▾] ごと, fused on one line inside a FormField, inside a 2-column ResponsiveGrid form card that collapses to one column.",
58
+ "A currency amount: [Select 通貨 ▾][NumberInput 金額] welded so the currency reads as part of the amount field, not a separate control beside it.",
59
+ "A filtered search bar: [Select scope][SearchInput query] as one visual field, `fullWidth` inside a page toolbar."
60
+ ]
61
+ }
@@ -54,6 +54,11 @@
54
54
  "name": "Flex",
55
55
  "tagline": "Token-spaced flex primitive with explicit direction, alignment, justification, and wrapping controls."
56
56
  },
57
+ {
58
+ "group": "layout",
59
+ "name": "SpaceCompact",
60
+ "tagline": "Weld a row (or column) of controls into one visual unit — antd Space.Compact. Inner corner radii are zeroed and the shared border collapses to one hairline; each child keeps its own focus ring and its own size."
61
+ },
57
62
  {
58
63
  "group": "layout",
59
64
  "name": "ResponsiveGrid",
@@ -938,6 +938,67 @@
938
938
  "Low-level layout composition inside custom components where raw flex classes would duplicate the primitive."
939
939
  ]
940
940
  },
941
+ {
942
+ "docPath": "layout/space-compact.tsx",
943
+ "example": "import { SpaceCompact } from \"@godxjp/ui/layout\";\nimport { FormField, NumberInput, Select } from \"@godxjp/ui/data-entry\";\n\n<FormField label=\"繰り返し間隔\">\n <SpaceCompact>\n <NumberInput aria-label=\"間隔の数\" min={1} defaultValue={2} />\n <Select\n aria-label=\"単位\"\n defaultValue=\"week\"\n options={[\n { value: \"day\", label: \"日\" },\n { value: \"week\", label: \"週\" },\n { value: \"month\", label: \"月\" },\n ]}\n />\n </SpaceCompact>\n</FormField>",
944
+ "group": "layout",
945
+ "importPath": "@godxjp/ui/layout",
946
+ "name": "SpaceCompact",
947
+ "props": [
948
+ {
949
+ "defaultValue": "\"horizontal\"",
950
+ "description": "Layout axis (antd `orientation`; the installed antd `direction` prop is itself @deprecated in favour of this, so only `orientation` is ported).",
951
+ "name": "orientation",
952
+ "type": "\"horizontal\" | \"vertical\""
953
+ },
954
+ {
955
+ "description": "antd's boolean spelling of orientation=\"vertical\". `orientation` wins when both are given.",
956
+ "name": "vertical",
957
+ "type": "boolean"
958
+ },
959
+ {
960
+ "defaultValue": "false",
961
+ "description": "The row fills its parent's inline size (antd `block`, renamed to match Button.fullWidth — the same rename, the same reason: this library's controlled vocabulary wins on values).",
962
+ "name": "fullWidth",
963
+ "type": "boolean"
964
+ },
965
+ {
966
+ "description": "Scoped control density for the whole row (antd Space.Compact `size`, small|middle|large — the ConfigProvider ambient-size cascade this library already owns under `density`; see `Form.density`). Each child still carries its OWN `size` prop.",
967
+ "name": "density",
968
+ "type": "\"compact\" | \"default\" | \"comfortable\""
969
+ },
970
+ {
971
+ "description": "DOM id of the row.",
972
+ "name": "id",
973
+ "type": "string"
974
+ }
975
+ ],
976
+ "related": [
977
+ "Flex `gap` — siblings that stay visually SEPARATE (a normal control row with spacing between items). SpaceCompact is the opposite case: controls that must read as ONE box.",
978
+ "ResponsiveGrid — equal-width form CELLS; SpaceCompact welds controls WITHIN one cell (or one row) into a single unit.",
979
+ "FormField — wraps the whole SpaceCompact with one label when the pair is one field.",
980
+ "NumberInput, Select — the two controls the shipping use case welds; either composes cleanly inside SpaceCompact because both already expose the radius knob it reads (`--input-radius-*`, `--control-trigger-radius-*`)."
981
+ ],
982
+ "rules": [
983
+ 2
984
+ ],
985
+ "storyPath": "layout/SpaceCompact.stories.tsx",
986
+ "tagline": "Weld a row (or column) of controls into one visual unit — antd Space.Compact. Inner corner radii are zeroed and the shared border collapses to one hairline; each child keeps its own focus ring and its own size.",
987
+ "usage": [
988
+ "DO reach for it when two or more controls must read as ONE field — a number + a unit (\"毎 [2] [週 ▾] ごと\"), a currency Select welded to an amount Input, a search field with an attached filter Select. `ResponsiveGrid columns={2}` puts them in separate cells (which collapses to ONE column on a narrow form card); SpaceCompact keeps them fused on one line regardless of the grid.",
989
+ "ONE label for the pair vs per-control: wrap the WHOLE `SpaceCompact` in a single `FormField label=\"…\"` when the pair reads as one field (\"繰り返し間隔\" over a number+unit row) — a role-less `<div>` cannot carry a cloned `aria-label`/`aria-labelledby` (axe `aria-allowed-attr`), so a NAMED SpaceCompact with no explicit `role` promotes itself to `role=\"group\"` automatically, the same contract `Flex` already honours for a range/年月 pair. Give each CHILD its own `aria-label` instead only when the two controls are independently meaningful outside the row (rare — prefer the one-label pattern for a welded control).",
990
+ "DON'T reach for `className=\"rounded-none\"` / `[&>*:not(:first-child)]` utilities to join controls yourself — `ui-audit`'s `no-utility-layout` rule blocks exactly that, and it is precisely the corner-radius seam this component owns.",
991
+ "DO set `size` on each CHILD individually, not on SpaceCompact — the row has no size prop of its own by design (see `density`); every control already owns its own `size` axis (`xs|sm|md|lg`).",
992
+ "DO use `fullWidth` inside a narrow form card so the joined row spans the field column, exactly like a lone Input would.",
993
+ "`fullWidth` sets NO per-child flex ratio — same as antd, whose Space.Compact block style only stretches the ROW (`display: flex; width: 100%`), never the children. Two fields (NumberInput+Select, Input+Select) split the row evenly because both already default to their own full width; a Button beside a field keeps its own content width and the field absorbs the rest (Select+Button, Input+Button) — the same split you get outside SpaceCompact.",
994
+ "DON'T expect corner-radius welding on `orientation=\"vertical\"` yet — the shared border still collapses, but each child keeps all four of its own corners rounded until a block-axis radius knob exists on the Input/trigger families (documented gap, not a silent one)."
995
+ ],
996
+ "useCases": [
997
+ "定期課題 (recurring issue) interval row: 毎 [NumberInput] [Select 週/月/日 ▾] ごと, fused on one line inside a FormField, inside a 2-column ResponsiveGrid form card that collapses to one column.",
998
+ "A currency amount: [Select 通貨 ▾][NumberInput 金額] welded so the currency reads as part of the amount field, not a separate control beside it.",
999
+ "A filtered search bar: [Select scope][SearchInput query] as one visual field, `fullWidth` inside a page toolbar."
1000
+ ]
1001
+ },
941
1002
  {
942
1003
  "example": "import { ResponsiveGrid } from \"@godxjp/ui/layout\";\nimport { StatCard } from \"@godxjp/ui/data-display\";\n\n<ResponsiveGrid columns={4}>\n <StatCard label=\"総会員数\" value=\"12,400\" />\n <StatCard label=\"公開中クーポン\" value=\"8\" />\n <StatCard label=\"月間利用数\" value=\"3,210\" />\n <StatCard label=\"割引総額\" value=\"¥480,000\" />\n</ResponsiveGrid>",
943
1004
  "group": "layout",
@@ -14713,14 +14774,14 @@
14713
14774
  "type": "React.ReactNode"
14714
14775
  },
14715
14776
  {
14716
- "description": "Slot BELOW the draft row — a hint line, a token counter.",
14777
+ "description": "Slot BELOW the draft row — a hint line, a token counter, OR (Ant Design X Sender NodeRender) a function that receives the real SubmitButton/CancelButton components, pre-wired, so they can be rendered in the footer instead of beside the draft box. Pair with actions={false} to hide the inline pair.",
14717
14778
  "name": "footer",
14718
- "type": "React.ReactNode"
14779
+ "type": "React.ReactNode | ((info: { components: ChatComposerActionComponents }) => React.ReactNode)"
14719
14780
  },
14720
14781
  {
14721
- "description": "Extra trailing actions, rendered BEFORE the send/cancel action.",
14782
+ "description": "Extra trailing actions, rendered BEFORE the send/cancel action — or false (Ant Design X Sender suffix semantics) to hide the ENTIRE inline trailing-action cell, built-in send/cancel button included, once footer has taken it over. Omitted renders exactly as before.",
14722
14783
  "name": "actions",
14723
- "type": "React.ReactNode"
14784
+ "type": "React.ReactNode | false"
14724
14785
  },
14725
14786
  {
14726
14787
  "defaultValue": "\"md\"",
@@ -14785,11 +14846,13 @@
14785
14846
  "DON'T render your own stop button beside the send button — set `loading` and the trailing action becomes cancel. Exactly one trailing action exists at a time (the picker trailing-action discipline).",
14786
14847
  "DO put a hint in `footer` (t('dataEntry.chatComposer.hintEnter') / 'hintShiftEnter') when you flip `submitType` — the keystroke contract is invisible otherwise. For submitType=\"modEnter\" use t('dataEntry.chatComposer.hintModEnter', { modifier: isApplePlatform() ? '⌘' : 'Ctrl' }) with isApplePlatform from @godxjp/ui/lib/utils — the same platform test the composer uses to pick metaKey vs ctrlKey.",
14787
14848
  "DO set `allowEmptySubmit` (not a hidden fake draft) when the composer also submits field changes from `header`/`footer`; your onSubmit receives \"\" and decides whether anything changed.",
14788
- "DON'T size it with a className height: the box grows between --chat-composer-min-height and --chat-composer-max-height, both derived from the --control-height tier. Use `size`, or re-tune the two tokens in your theme."
14849
+ "DON'T size it with a className height: the box grows between --chat-composer-min-height and --chat-composer-max-height, both derived from the --control-height tier. Use `size`, or re-tune the two tokens in your theme.",
14850
+ "DO move the toolbar BELOW a full-width draft box with `actions={false}` + a function `footer` — the function receives `{ components: { SubmitButton, CancelButton } }`, both already wired to onSubmit/onCancel/disabled/the loading swap, so nothing about the send contract is reimplemented at the call site. Put the attach control and a keyboard hint on the START side of the footer row and `<SubmitButton />` on the END side (a `Flex justify=\"between\"`), exactly like the record-comment box case. Leaving `actions` unset keeps today's inline row — this is opt-in, not a breaking change."
14789
14851
  ],
14790
14852
  "useCases": [
14791
14853
  "The message box of an AI assistant or support chat, under a ChatBubbleList feed.",
14792
14854
  "A comment composer on a record detail screen (prefix = attach Button, footer = character counter).",
14855
+ "A record-detail comment box with a full-width textarea and a toolbar row BELOW it — attach on the start side, a keyboard hint in the middle, clear/send on the end side: `actions={false}` + `footer={({ components }) => <Flex justify=\"between\">…<components.SubmitButton /></Flex>}`.",
14793
14856
  "A long-form reply box where Enter must break the line: submitType=\"shiftEnter\".",
14794
14857
  "A comment bar on an issue/record where Enter breaks the line and ⌘/Ctrl+Enter posts, and a status change may be posted without text: submitType=\"modEnter\" + allowEmptySubmit.",
14795
14858
  "A streaming answer the user can stop: loading + onCancel."
package/agent/index.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "counts": {
3
3
  "anti-ai-tells": 26,
4
- "components": 171,
4
+ "components": 172,
5
5
  "patterns": 20,
6
6
  "rules": 50,
7
7
  "tokens": 2071,
@@ -10,7 +10,7 @@
10
10
  "files": [
11
11
  {
12
12
  "file": "components-index.json",
13
- "note": "45 KB — name + group + tagline for all 171. FETCH THIS FIRST, then fetch only the components you chose.",
13
+ "note": "45 KB — name + group + tagline for all 172. FETCH THIS FIRST, then fetch only the components you chose.",
14
14
  "url": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json"
15
15
  },
16
16
  {
@@ -48,7 +48,7 @@
48
48
  "note": "Pin to the tag that matches the @godxjp/ui version you installed. A catalog newer than your package describes props you do not have; older, and it hides props you do.",
49
49
  "read": {
50
50
  "live": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/index.json",
51
- "pinned": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/v30.3.1/agent/index.json"
51
+ "pinned": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/v30.4.1/agent/index.json"
52
52
  },
53
53
  "source": "mcp/src/data — the same data @godxjp/ui-mcp serves — plus the foundation and semantic token tiers, read from src/tokens/*.css",
54
54
  "start": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/START-HERE.md",
@@ -62,5 +62,5 @@
62
62
  "foundation": "the seeds a consumer is invited to set — --primary, --background, --radius",
63
63
  "semantic": "named roles that follow the seeds — --ring, --text-link, --overlay-background"
64
64
  },
65
- "version": "30.3.1"
65
+ "version": "30.4.1"
66
66
  }
package/agent/llms.txt CHANGED
@@ -1,10 +1,10 @@
1
1
  # @godxjp/ui
2
2
 
3
- > A Japanese-enterprise React design system: 171 components, 2071 design tokens,
4
- > 50 cardinal rules. This file is the entry point for AI agents. Catalog version 30.3.1.
3
+ > A Japanese-enterprise React design system: 172 components, 2071 design tokens,
4
+ > 50 cardinal rules. This file is the entry point for AI agents. Catalog version 30.4.1.
5
5
 
6
6
  If your client can run a process, do not read these files — run the MCP server instead
7
- (`npx @godxjp/ui-mcp@30.3.1`). It is searchable and version-locked. These files exist for agents
7
+ (`npx @godxjp/ui-mcp@30.4.1`). It is searchable and version-locked. These files exist for agents
8
8
  that can only fetch URLs.
9
9
 
10
10
  ## Start
@@ -15,7 +15,7 @@ that can only fetch URLs.
15
15
  ## Catalog
16
16
 
17
17
  - [patterns-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/patterns-index.json): 20 whole-task patterns (name, tagline, tags). Start here when the task is a TASK — "build a settings page" — then fetch `patterns/<name>.json` for complete code.
18
- - [components-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json): 45 KB — all 171 components as name, group, tagline, plus `absorbed`: the names that do NOT exist and map to it (`Combobox` → `Select`).
18
+ - [components-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json): 45 KB — all 172 components as name, group, tagline, plus `absorbed`: the names that do NOT exist and map to it (`Combobox` → `Select`).
19
19
  - [components/&lt;Name&gt;.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components/Select.json): one file per component (1 KB–34 KB, median 6 KB). Read the index, then fetch only the ones you chose — this is the selective route, and the reason you do not need the blob.
20
20
  - [components.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components.json): 1.2 MB — every entry in one file. Most URL fetchers truncate a response this size without saying so; prefer the per-component files.
21
21
  - [tokens.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/tokens.json): every design token with its value, the reason it exists, and its `tier` — 211 `foundation` seeds (`--primary`, `--background`, `--radius`: set these when you are handed a brand), 103 `semantic` roles, 1757 `component` knobs.
@@ -26,7 +26,7 @@ that can only fetch URLs.
26
26
  ## Pinning
27
27
 
28
28
  Every URL above tracks `main`. To pin to the release a project actually installed, swap `main` for
29
- the tag: `.../godx-jp/godxjp-ui/v30.3.1/agent/...`. A catalog that does not match the installed
29
+ the tag: `.../godx-jp/godxjp-ui/v30.4.1/agent/...`. A catalog that does not match the installed
30
30
  package describes props that are absent, or hides props that are present, and says nothing either way.
31
31
 
32
32
  Pinned catalogs only exist for releases whose tag actually contains `agent/`. If `…/v<version>/agent/index.json` returns 404, that release predates this catalog: read `…/main/…` instead and compare `index.json` → `version` against the package you have, so you at least know which way it drifted.
@@ -1,5 +1,5 @@
1
1
  import * as React from "react";
2
- export type { ChatComposerProp, ChatComposerProp as ChatComposerProps, ChatComposerSubmitTypeProp, } from "../../props/components/data-entry.prop.js";
2
+ export type { ChatComposerProp, ChatComposerProp as ChatComposerProps, ChatComposerSubmitTypeProp, ChatComposerActionComponents, ChatComposerFooterProp, } from "../../props/components/data-entry.prop.js";
3
3
  /**
4
4
  * ChatComposer — the message input of a conversation (Ant Design X `Sender`).
5
5
  *
@@ -37,8 +37,8 @@ export declare const ChatComposer: React.ForwardRefExoticComponent<Omit<React.HT
37
37
  readOnly?: boolean;
38
38
  header?: React.ReactNode;
39
39
  prefix?: React.ReactNode;
40
- footer?: React.ReactNode;
41
- actions?: React.ReactNode;
40
+ footer?: import("./chat-composer.js").ChatComposerFooterProp;
41
+ actions?: React.ReactNode | false;
42
42
  size?: import("../../props/index.js").SizeProp;
43
43
  maxLength?: number;
44
44
  status?: import("../../props/index.js").ControlStatusProp;
@@ -85,6 +85,50 @@ const ChatComposer = React.forwardRef(
85
85
  const sendName = submitLabel ?? t("dataEntry.chatComposer.send");
86
86
  const cancelName = cancelLabel ?? t("dataEntry.chatComposer.cancel");
87
87
  const actionSize = size === "xs" ? "icon-xs" : size === "sm" ? "icon-sm" : size === "lg" ? "icon-lg" : "icon";
88
+ const liveRef = React.useRef({ actionSize, sendName, cancelName, submit, onCancel, canSubmit, disabled });
89
+ liveRef.current = { actionSize, sendName, cancelName, submit, onCancel, canSubmit, disabled };
90
+ const SubmitButton = React.useMemo(
91
+ () => function SubmitButton2(buttonProps) {
92
+ const live = liveRef.current;
93
+ return /* @__PURE__ */ jsx(
94
+ Button,
95
+ {
96
+ type: "button",
97
+ size: live.actionSize,
98
+ "aria-label": live.sendName,
99
+ onClick: live.submit,
100
+ disabled: !live.canSubmit,
101
+ ...buttonProps,
102
+ children: buttonProps.children ?? /* @__PURE__ */ jsx(SendHorizontal, { "aria-hidden": "true" })
103
+ }
104
+ );
105
+ },
106
+ []
107
+ );
108
+ const CancelButton = React.useMemo(
109
+ () => function CancelButton2(buttonProps) {
110
+ const live = liveRef.current;
111
+ return /* @__PURE__ */ jsx(
112
+ Button,
113
+ {
114
+ type: "button",
115
+ size: live.actionSize,
116
+ variant: "secondary",
117
+ "aria-label": live.cancelName,
118
+ onClick: live.onCancel,
119
+ disabled: live.disabled,
120
+ ...buttonProps,
121
+ children: buttonProps.children ?? /* @__PURE__ */ jsx(Square, { "aria-hidden": "true" })
122
+ }
123
+ );
124
+ },
125
+ []
126
+ );
127
+ const actionComponents = React.useMemo(
128
+ () => ({ SubmitButton, CancelButton }),
129
+ [SubmitButton, CancelButton]
130
+ );
131
+ const footerContent = typeof footer === "function" ? footer({ components: actionComponents }) : footer;
88
132
  return /* @__PURE__ */ jsxs(
89
133
  "div",
90
134
  {
@@ -132,33 +176,12 @@ const ChatComposer = React.forwardRef(
132
176
  ...identity
133
177
  }
134
178
  ),
135
- /* @__PURE__ */ jsxs("div", { "data-slot": "chat-composer-actions", className: "ui-chat-composer-actions", children: [
179
+ actions === false ? null : /* @__PURE__ */ jsxs("div", { "data-slot": "chat-composer-actions", className: "ui-chat-composer-actions", children: [
136
180
  actions,
137
- loading ? /* @__PURE__ */ jsx(
138
- Button,
139
- {
140
- type: "button",
141
- size: actionSize,
142
- variant: "secondary",
143
- "aria-label": cancelName,
144
- onClick: onCancel,
145
- disabled,
146
- children: /* @__PURE__ */ jsx(Square, { "aria-hidden": "true" })
147
- }
148
- ) : /* @__PURE__ */ jsx(
149
- Button,
150
- {
151
- type: "button",
152
- size: actionSize,
153
- "aria-label": sendName,
154
- onClick: submit,
155
- disabled: !canSubmit,
156
- children: /* @__PURE__ */ jsx(SendHorizontal, { "aria-hidden": "true" })
157
- }
158
- )
181
+ loading ? /* @__PURE__ */ jsx(CancelButton, {}) : /* @__PURE__ */ jsx(SubmitButton, {})
159
182
  ] })
160
183
  ] }),
161
- footer ? /* @__PURE__ */ jsx("div", { "data-slot": "chat-composer-footer", className: "ui-chat-composer-footer", children: footer }) : null
184
+ footerContent ? /* @__PURE__ */ jsx("div", { "data-slot": "chat-composer-footer", className: "ui-chat-composer-footer", children: footerContent }) : null
162
185
  ]
163
186
  }
164
187
  );
@@ -2,6 +2,8 @@ export { PageContainer } from "./page-container.js";
2
2
  export type { PageContainerProp, PageContainerProps, BreadcrumbItem, BreadcrumbItemProp, } from "./page-container.js";
3
3
  export { Flex } from "./flex.js";
4
4
  export type { FlexAlignProp, FlexDirectionProp, FlexJustifyProp, FlexProp, FlexProps, } from "./flex.js";
5
+ export { SpaceCompact } from "./space-compact.js";
6
+ export type { SpaceCompactProp, SpaceCompactProps } from "./space-compact.js";
5
7
  export { ResizablePanel, ResizablePanelGroup, ResizableHandle } from "./resizable.js";
6
8
  export { NavList } from "./nav-list.js";
7
9
  export type { NavListProp, NavListProps } from "./nav-list.js";
@@ -1,5 +1,6 @@
1
1
  import { PageContainer } from "./page-container.js";
2
2
  import { Flex } from "./flex.js";
3
+ import { SpaceCompact } from "./space-compact.js";
3
4
  import { ResizablePanel, ResizablePanelGroup, ResizableHandle } from "./resizable.js";
4
5
  import { NavList } from "./nav-list.js";
5
6
  import { AppShell } from "./app-shell.js";
@@ -64,6 +65,7 @@ export {
64
65
  SidebarHeader,
65
66
  SidebarItem,
66
67
  SidebarSection,
68
+ SpaceCompact,
67
69
  SplitPane,
68
70
  Topbar,
69
71
  TopbarItem,
@@ -0,0 +1,65 @@
1
+ import { type JSX } from "react";
2
+ import type { SpaceCompactProp } from "../../props/components/layout.prop.js";
3
+ export type { SpaceCompactProp, SpaceCompactProp as SpaceCompactProps, } from "../../props/components/layout.prop.js";
4
+ /**
5
+ * SpaceCompact — antd `Space.Compact`: weld a row of controls into one visual unit.
6
+ *
7
+ * A plain `<div>`, matching antd's own DOM (no ARIA role by default — each child keeps its own
8
+ * accessible name; this is a visual join, not a semantic group, exactly like antd's
9
+ * `Space.Compact`). The corner radii and shared border seam are zeroed/collapsed in CSS — the
10
+ * "not expressible from primitives + tokens" case `docs/roadmap/parity-audit-layout-navigation-
11
+ * general.md` §4.1 describes: doing this at the call site would need `[&>*:not(:first-child)]`
12
+ * utilities, which `ui-audit`'s `no-utility-layout` rule blocks.
13
+ *
14
+ * ## ONE label for the pair (`FormField` wrapping a `SpaceCompact`)
15
+ *
16
+ * `FormField` lands its label contract on its single direct child (`docs/FORMS.md`). Cloned
17
+ * `aria-label`/`aria-labelledby` onto a role-less `<div>` would be inert (axe `aria-allowed-attr`
18
+ * disagrees, and no AT reads a name off a generic element) — so, like `Flex` (which `FormField`
19
+ * already lands on for a range/`年/月` pair), a NAMED `SpaceCompact` with no explicit `role`
20
+ * becomes `role="group"` and keeps only the aria a group allows: `aria-errormessage` folds
21
+ * into `aria-describedby`, and the widget-only `aria-required`/`aria-invalid` are dropped. Pass an
22
+ * explicit `role` to opt out and own the attribute set yourself.
23
+ *
24
+ * NOT quite "exactly like `Flex`" any more: since gh#916 `Flex` skips that promotion when its `as`
25
+ * renders an element that already has a role (`ul`, `ol`, `li`), because a name should improve
26
+ * such a role rather than replace it. This component always renders a `<div>`, which has no role
27
+ * to lose, so the promotion is unconditional here and the two agree on every case `Flex` still
28
+ * promotes.
29
+ *
30
+ * ## ONE WRAPPER PER CHILD, and why removing it re-breaks the headline case (gh#919)
31
+ *
32
+ * Every rule in `src/styles/layout.css` — the `flex` basis, the negative seam margin, the
33
+ * `position: relative` + `z-index` raise, and the `:not(:first-child)`/`:not(:last-child)` corner
34
+ * zeroing — used to key on `> *`, the DIRECT ELEMENT children. That silently assumed one child
35
+ * element == one flex item, and `Select` breaks the assumption on purpose: `.ui-select-root` is
36
+ * `display: contents` (`src/styles/control.css`) precisely so the TRIGGER is the box a flex row
37
+ * sees, and react-aria renders a `<template>` beside it. So `> *` matched a root that generates no
38
+ * box at all — flex, margin, position and z-index all landed on nothing, the trigger inherited
39
+ * none of them and kept its own `w-full`, and the `<template>` shifted first/last-child by one.
40
+ * Measured by the guinea-pig consumer on `NumberInput` + `Select`, the pattern in this component's
41
+ * own catalog entry: trigger 562px (the whole row), spinbutton 54px with its step buttons pushed
42
+ * outside the box.
43
+ *
44
+ * Wrapping each child in a box this component owns makes "one child, one flex item" true no matter
45
+ * what the child renders, which is the property every one of those rules needed in the first place.
46
+ * The radius knobs still reach the control because custom properties inherit — through the wrapper
47
+ * and through `display: contents` alike. antd solves the same problem the other way, by passing
48
+ * position down `SpaceCompactItemContext` for each child to apply to its own root; that needs every
49
+ * control to participate, which is the port `docs/roadmap/parity-audit-layout-navigation-general.md`
50
+ * §4.2 records as not done.
51
+ *
52
+ * ## `orientation="vertical"` — a written-down partial port
53
+ *
54
+ * Layout (column stacking, border collapse) works on both axes. Corner-zeroing currently only
55
+ * covers the INLINE seam (`orientation="horizontal"`, the default and the only axis the shipping
56
+ * use case needs): it reads the same `--input-radius-start`/`--input-radius-end` and
57
+ * `--control-trigger-radius-start`/`--control-trigger-radius-end` knobs the Input and trigger
58
+ * families already expose, and both pairs are inline-only. A BLOCK-axis pair does not exist on
59
+ * either family yet, so a vertical stack collapses its shared border but each child keeps all
60
+ * four of its own corners rounded. Follow-up, not a silent gap.
61
+ */
62
+ export declare function SpaceCompact({ children, orientation, vertical, fullWidth, density, className, ...props }: SpaceCompactProp): JSX.Element;
63
+ export declare namespace SpaceCompact {
64
+ var displayName: string;
65
+ }
@@ -0,0 +1,52 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { Children } from "react";
4
+ import { mergeAriaIds } from "../../lib/field-a11y.js";
5
+ import { cn } from "../../lib/utils.js";
6
+ function SpaceCompact({
7
+ children,
8
+ orientation,
9
+ vertical,
10
+ fullWidth,
11
+ density,
12
+ className,
13
+ ...props
14
+ }) {
15
+ const resolvedOrientation = orientation ?? (vertical ? "vertical" : "horizontal");
16
+ let domProps = props;
17
+ if (props.role === void 0 && (props["aria-label"] !== void 0 || props["aria-labelledby"] !== void 0)) {
18
+ const {
19
+ "aria-required": _ariaRequired,
20
+ "aria-invalid": _ariaInvalid,
21
+ "aria-errormessage": ariaErrorMessage,
22
+ ...allowed
23
+ } = props;
24
+ domProps = {
25
+ ...allowed,
26
+ role: "group",
27
+ "aria-describedby": mergeAriaIds(props["aria-describedby"], ariaErrorMessage)
28
+ };
29
+ }
30
+ return /* @__PURE__ */ jsx(
31
+ "div",
32
+ {
33
+ "data-slot": "space-compact",
34
+ "data-orientation": resolvedOrientation,
35
+ "data-full-width": fullWidth ? "true" : void 0,
36
+ className: cn("ui-space-compact", density && `ui-density-${density}`, className),
37
+ ...domProps,
38
+ children: Children.toArray(children).map((child, index) => /* @__PURE__ */ jsx(
39
+ "div",
40
+ {
41
+ "data-slot": "space-compact-item",
42
+ children: child
43
+ },
44
+ index
45
+ ))
46
+ }
47
+ );
48
+ }
49
+ SpaceCompact.displayName = "SpaceCompact";
50
+ export {
51
+ SpaceCompact
52
+ };
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$comment": "AUTO-GENERATED by scripts/gen-measurement-contract.mjs — do not edit. Read this instead of guessing: docs/MEASUREMENT-CONTRACT.md.",
3
- "version": "30.3.0",
3
+ "version": "30.4.1",
4
4
  "targetSize": {
5
5
  "standard": "WCAG 2.2 SC 2.5.8 Target Size (Minimum), level AA — 24×24 CSS px",
6
6
  "min": 24,
@@ -46,7 +46,7 @@ export declare const controlOpenRingClass = "ui-control-trigger";
46
46
  *
47
47
  * The clamp keeps it to one line, so a wrappable value cannot grow the control.
48
48
  */
49
- export declare const controlTriggerBaseClass = "ui-control ui-control-trigger flex items-center justify-between gap-2 whitespace-nowrap rounded-[var(--control-radius)] transition-[color,box-shadow] [&>[data-slot=select-value]]:line-clamp-1 [&>[data-slot=select-value]]:whitespace-normal [&>[data-slot=select-value]]:text-ellipsis";
49
+ export declare const controlTriggerBaseClass = "ui-control ui-control-trigger flex items-center justify-between gap-2 whitespace-nowrap rounded-s-[var(--control-trigger-radius-start,var(--control-radius))] rounded-e-[var(--control-trigger-radius-end,var(--control-radius))] transition-[color,box-shadow] [&>[data-slot=select-value]]:line-clamp-1 [&>[data-slot=select-value]]:whitespace-normal [&>[data-slot=select-value]]:text-ellipsis";
50
50
  /**
51
51
  * The trigger WITH the resting surface — `.ui-control-outlined-surface`, which paints the identical
52
52
  * border and fill from `--control-surface-border-color` / `--control-surface-background`.
@@ -55,9 +55,9 @@ export declare const controlTriggerBaseClass = "ui-control ui-control-trigger fl
55
55
  * outranks `@layer components`, so those two tokens could not reach it (gh#880). The painted result
56
56
  * is unchanged; what changed is that a theme can now reach it.
57
57
  */
58
- export declare const controlTriggerClass = "ui-control ui-control-trigger flex items-center justify-between gap-2 whitespace-nowrap rounded-[var(--control-radius)] transition-[color,box-shadow] [&>[data-slot=select-value]]:line-clamp-1 [&>[data-slot=select-value]]:whitespace-normal [&>[data-slot=select-value]]:text-ellipsis ui-control-outlined-surface";
58
+ export declare const controlTriggerClass = "ui-control ui-control-trigger flex items-center justify-between gap-2 whitespace-nowrap rounded-s-[var(--control-trigger-radius-start,var(--control-radius))] rounded-e-[var(--control-trigger-radius-end,var(--control-radius))] transition-[color,box-shadow] [&>[data-slot=select-value]]:line-clamp-1 [&>[data-slot=select-value]]:whitespace-normal [&>[data-slot=select-value]]:text-ellipsis ui-control-outlined-surface";
59
59
  /** `controlTriggerBaseClass` + the token-driven surface — the select-family trigger. */
60
- export declare const controlSurfaceTriggerClass = "ui-control ui-control-trigger flex items-center justify-between gap-2 whitespace-nowrap rounded-[var(--control-radius)] transition-[color,box-shadow] [&>[data-slot=select-value]]:line-clamp-1 [&>[data-slot=select-value]]:whitespace-normal [&>[data-slot=select-value]]:text-ellipsis ui-control-surface";
60
+ export declare const controlSurfaceTriggerClass = "ui-control ui-control-trigger flex items-center justify-between gap-2 whitespace-nowrap rounded-s-[var(--control-trigger-radius-start,var(--control-radius))] rounded-e-[var(--control-trigger-radius-end,var(--control-radius))] transition-[color,box-shadow] [&>[data-slot=select-value]]:line-clamp-1 [&>[data-slot=select-value]]:whitespace-normal [&>[data-slot=select-value]]:text-ellipsis ui-control-surface";
61
61
  export declare const controlIconClass = "size-[length:var(--control-height)] shrink-0";
62
62
  export declare const controlIconSmClass = "size-[calc(var(--control-height)-0.5rem)] shrink-0";
63
63
  /** Leading/affix icon inside an input row (search, command) — sized to `--control-icon-size`. */
@@ -2,7 +2,14 @@ const controlMultilineClass = "ui-control-multiline ui-control-outlined-surface
2
2
  const controlMultilineGhostClass = "ui-control-multiline data-[status=error]:border-destructive data-[status=warning]:border-warning w-full min-h-0 border-0 bg-transparent shadow-none placeholder:text-muted-foreground focus-visible:ring-0";
3
3
  const controlMultilineFilledClass = "ui-control-multiline ui-control--filled aria-invalid:border-destructive data-[status=error]:border-destructive data-[status=warning]:border-warning w-full rounded-[var(--control-radius)] ring-offset-background placeholder:text-muted-foreground";
4
4
  const controlOpenRingClass = "ui-control-trigger";
5
- const controlTriggerBaseClass = "ui-control ui-control-trigger flex items-center justify-between gap-2 whitespace-nowrap rounded-[var(--control-radius)] transition-[color,box-shadow] [&>[data-slot=select-value]]:line-clamp-1 [&>[data-slot=select-value]]:whitespace-normal [&>[data-slot=select-value]]:text-ellipsis";
5
+ const controlTriggerBaseClass = (
6
+ // PER-EDGE RADIUS THROUGH A KNOB, same repair as Input's `--input-radius-start`/`-end`
7
+ // (gh#841): a plain `rounded-[var(--control-radius)]` is a utility, and `@layer utilities`
8
+ // outranks `@layer components`, so a joined-control seam (SpaceCompact) can never zero one
9
+ // side of it from a components-layer rule. Unset, both knobs fall back to `--control-radius`
10
+ // exactly as before — no existing trigger changes.
11
+ "ui-control ui-control-trigger flex items-center justify-between gap-2 whitespace-nowrap rounded-s-[var(--control-trigger-radius-start,var(--control-radius))] rounded-e-[var(--control-trigger-radius-end,var(--control-radius))] transition-[color,box-shadow] [&>[data-slot=select-value]]:line-clamp-1 [&>[data-slot=select-value]]:whitespace-normal [&>[data-slot=select-value]]:text-ellipsis"
12
+ );
6
13
  const controlTriggerClass = `${controlTriggerBaseClass} ui-control-outlined-surface`;
7
14
  const controlSurfaceTriggerClass = `${controlTriggerBaseClass} ui-control-surface`;
8
15
  const controlIconClass = "size-[length:var(--control-height)] shrink-0";