@godxjp/ui 28.9.0 → 28.12.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 (244) hide show
  1. package/agent/START-HERE.md +193 -0
  2. package/agent/anti-ai-tells.json +158 -0
  3. package/agent/components/Accordion.json +60 -0
  4. package/agent/components/AccountChip.json +59 -0
  5. package/agent/components/Actions.json +78 -0
  6. package/agent/components/Activity.json +80 -0
  7. package/agent/components/Affix.json +78 -0
  8. package/agent/components/Alert.json +65 -0
  9. package/agent/components/AlertDialog.json +109 -0
  10. package/agent/components/AlertDialogRoot.json +52 -0
  11. package/agent/components/Anchor.json +119 -0
  12. package/agent/components/AppLauncher.json +95 -0
  13. package/agent/components/AppProvider.json +105 -0
  14. package/agent/components/AppSettingPicker.json +88 -0
  15. package/agent/components/AppSettingToggle.json +70 -0
  16. package/agent/components/AppShell.json +159 -0
  17. package/agent/components/AreaChart.json +105 -0
  18. package/agent/components/AspectRatio.json +38 -0
  19. package/agent/components/Attachments.json +77 -0
  20. package/agent/components/AuthAccountSummary.json +64 -0
  21. package/agent/components/AuthDivider.json +35 -0
  22. package/agent/components/AuthFooter.json +48 -0
  23. package/agent/components/AuthIdentity.json +42 -0
  24. package/agent/components/AuthShell.json +108 -0
  25. package/agent/components/AuthStack.json +21 -0
  26. package/agent/components/Avatar.json +89 -0
  27. package/agent/components/Badge.json +96 -0
  28. package/agent/components/Banner.json +48 -0
  29. package/agent/components/BarChart.json +108 -0
  30. package/agent/components/BranchScopePicker.json +89 -0
  31. package/agent/components/Breadcrumb.json +54 -0
  32. package/agent/components/Button.json +133 -0
  33. package/agent/components/Calendar.json +259 -0
  34. package/agent/components/Callout.json +46 -0
  35. package/agent/components/Card.json +112 -0
  36. package/agent/components/CardBar.json +49 -0
  37. package/agent/components/CardContent.json +51 -0
  38. package/agent/components/Carousel.json +50 -0
  39. package/agent/components/Cascader.json +209 -0
  40. package/agent/components/CenteredShell.json +66 -0
  41. package/agent/components/ChatBubble.json +100 -0
  42. package/agent/components/ChatBubbleList.json +64 -0
  43. package/agent/components/ChatComposer.json +160 -0
  44. package/agent/components/ChatSuggestion.json +86 -0
  45. package/agent/components/Checkbox.json +68 -0
  46. package/agent/components/CheckboxGroup.json +96 -0
  47. package/agent/components/CodeBlock.json +64 -0
  48. package/agent/components/Collapsible.json +74 -0
  49. package/agent/components/ColorPicker.json +87 -0
  50. package/agent/components/Command.json +168 -0
  51. package/agent/components/CommandPalette.json +84 -0
  52. package/agent/components/CompactBarTrend.json +101 -0
  53. package/agent/components/Conversations.json +82 -0
  54. package/agent/components/CredentialReveal.json +93 -0
  55. package/agent/components/DataState.json +79 -0
  56. package/agent/components/DataTable.json +268 -0
  57. package/agent/components/DatePicker.json +275 -0
  58. package/agent/components/Descriptions.json +67 -0
  59. package/agent/components/Dialog.json +78 -0
  60. package/agent/components/DraggablePanel.json +106 -0
  61. package/agent/components/DropdownMenu.json +102 -0
  62. package/agent/components/EmptyState.json +83 -0
  63. package/agent/components/ErrorSurface.json +128 -0
  64. package/agent/components/FeatureList.json +43 -0
  65. package/agent/components/Field.json +64 -0
  66. package/agent/components/FilterBar.json +99 -0
  67. package/agent/components/Flex.json +153 -0
  68. package/agent/components/FloatButton.json +91 -0
  69. package/agent/components/Form.json +87 -0
  70. package/agent/components/FormErrors.json +51 -0
  71. package/agent/components/FormField.json +137 -0
  72. package/agent/components/FormFieldArray.json +39 -0
  73. package/agent/components/FormFieldControl.json +129 -0
  74. package/agent/components/FormRoot.json +122 -0
  75. package/agent/components/Heading.json +61 -0
  76. package/agent/components/HoverCard.json +55 -0
  77. package/agent/components/Icon.json +60 -0
  78. package/agent/components/InfiniteQueryState.json +58 -0
  79. package/agent/components/Input.json +122 -0
  80. package/agent/components/InputOTP.json +106 -0
  81. package/agent/components/Label.json +43 -0
  82. package/agent/components/LegalDocumentShell.json +102 -0
  83. package/agent/components/Legend.json +42 -0
  84. package/agent/components/LineChart.json +103 -0
  85. package/agent/components/Link.json +41 -0
  86. package/agent/components/ListRow.json +92 -0
  87. package/agent/components/Logo.json +85 -0
  88. package/agent/components/Marquee.json +91 -0
  89. package/agent/components/Masonry.json +82 -0
  90. package/agent/components/MasterDetail.json +95 -0
  91. package/agent/components/MegaMenu.json +120 -0
  92. package/agent/components/MobileShell.json +73 -0
  93. package/agent/components/NavList.json +63 -0
  94. package/agent/components/NumberInput.json +158 -0
  95. package/agent/components/OrgSwitcher.json +89 -0
  96. package/agent/components/OverlayPortalProvider.json +42 -0
  97. package/agent/components/PageContainer.json +181 -0
  98. package/agent/components/Pagination.json +132 -0
  99. package/agent/components/Paragraph.json +40 -0
  100. package/agent/components/PasswordInput.json +79 -0
  101. package/agent/components/PasswordStrength.json +51 -0
  102. package/agent/components/PermissionMatrix.json +81 -0
  103. package/agent/components/PieChart.json +99 -0
  104. package/agent/components/Popover.json +110 -0
  105. package/agent/components/PrefetchLink.json +65 -0
  106. package/agent/components/Progress.json +79 -0
  107. package/agent/components/Prose.json +57 -0
  108. package/agent/components/QrCode.json +62 -0
  109. package/agent/components/Radio.json +98 -0
  110. package/agent/components/RadioGroup.json +91 -0
  111. package/agent/components/RangeTimeline.json +80 -0
  112. package/agent/components/Rating.json +92 -0
  113. package/agent/components/ResizablePanel.json +69 -0
  114. package/agent/components/ResponsiveGrid.json +77 -0
  115. package/agent/components/Reveal.json +70 -0
  116. package/agent/components/ScrollArea.json +104 -0
  117. package/agent/components/SearchInput.json +98 -0
  118. package/agent/components/Segmented.json +96 -0
  119. package/agent/components/Select.json +397 -0
  120. package/agent/components/Separator.json +86 -0
  121. package/agent/components/ServiceCatalogCta.json +46 -0
  122. package/agent/components/ServiceLauncherCard.json +93 -0
  123. package/agent/components/ServiceRolePanel.json +83 -0
  124. package/agent/components/Sheet.json +85 -0
  125. package/agent/components/Sidebar.json +118 -0
  126. package/agent/components/Skeleton.json +57 -0
  127. package/agent/components/SkeletonArticle.json +71 -0
  128. package/agent/components/SkeletonAvatar.json +50 -0
  129. package/agent/components/SkeletonButton.json +57 -0
  130. package/agent/components/SkeletonForm.json +52 -0
  131. package/agent/components/SkeletonImage.json +37 -0
  132. package/agent/components/SkeletonInput.json +51 -0
  133. package/agent/components/SkeletonNode.json +42 -0
  134. package/agent/components/SkeletonRows.json +49 -0
  135. package/agent/components/SkeletonTable.json +45 -0
  136. package/agent/components/Slider.json +160 -0
  137. package/agent/components/SplitPane.json +66 -0
  138. package/agent/components/StatCard.json +83 -0
  139. package/agent/components/Steps.json +95 -0
  140. package/agent/components/Swatch.json +41 -0
  141. package/agent/components/Switch.json +81 -0
  142. package/agent/components/Table.json +112 -0
  143. package/agent/components/Tabs.json +158 -0
  144. package/agent/components/TagInput.json +105 -0
  145. package/agent/components/Text.json +201 -0
  146. package/agent/components/Textarea.json +126 -0
  147. package/agent/components/ThoughtChain.json +76 -0
  148. package/agent/components/Thumbnail.json +70 -0
  149. package/agent/components/TimePicker.json +200 -0
  150. package/agent/components/TimeRangePicker.json +90 -0
  151. package/agent/components/Timeline.json +47 -0
  152. package/agent/components/TimelineGrid.json +92 -0
  153. package/agent/components/Title.json +67 -0
  154. package/agent/components/Toaster.json +42 -0
  155. package/agent/components/Toggle.json +90 -0
  156. package/agent/components/ToggleGroup.json +102 -0
  157. package/agent/components/Toolbar.json +120 -0
  158. package/agent/components/Tooltip.json +110 -0
  159. package/agent/components/Topbar.json +83 -0
  160. package/agent/components/TopbarItem.json +79 -0
  161. package/agent/components/Transfer.json +141 -0
  162. package/agent/components/Tree.json +185 -0
  163. package/agent/components/TreeSelect.json +232 -0
  164. package/agent/components/TwoFactorSetup.json +79 -0
  165. package/agent/components/Typography.json +42 -0
  166. package/agent/components/Upload.json +221 -0
  167. package/agent/components/UploadCropDialog.json +60 -0
  168. package/agent/components/VisuallyHidden.json +20 -0
  169. package/agent/components/Welcome.json +65 -0
  170. package/agent/components/formatDate.json +46 -0
  171. package/agent/components/inertiaUpload.json +32 -0
  172. package/agent/components/useZodForm.json +39 -0
  173. package/agent/components-index.json +884 -0
  174. package/agent/components.json +15515 -0
  175. package/agent/index.json +56 -0
  176. package/agent/llms.txt +32 -0
  177. package/agent/patterns/account-recovery-settings.json +19 -0
  178. package/agent/patterns/async-data-state.json +20 -0
  179. package/agent/patterns/auth-recovery-panels.json +29 -0
  180. package/agent/patterns/badge-coloring.json +14 -0
  181. package/agent/patterns/common-fixes.json +16 -0
  182. package/agent/patterns/confirm-destructive.json +11 -0
  183. package/agent/patterns/data-table-page.json +18 -0
  184. package/agent/patterns/deferred-loading.json +12 -0
  185. package/agent/patterns/error-pages.json +28 -0
  186. package/agent/patterns/inertia-detail-page.json +13 -0
  187. package/agent/patterns/inertia-list-page.json +15 -0
  188. package/agent/patterns/inertia-persistent-layout.json +14 -0
  189. package/agent/patterns/organization-memberships.json +19 -0
  190. package/agent/patterns/page-sections.json +18 -0
  191. package/agent/patterns/settings-page-responsive.json +18 -0
  192. package/agent/patterns/settings-section-rows.json +23 -0
  193. package/agent/patterns/signup-form.json +13 -0
  194. package/agent/patterns/topbar-account-chip.json +18 -0
  195. package/agent/patterns/transactional-email.json +22 -0
  196. package/agent/patterns-index.json +323 -0
  197. package/agent/patterns.json +342 -0
  198. package/agent/rules.json +237 -0
  199. package/agent/tokens.json +8427 -0
  200. package/agent/vocabulary.json +198 -0
  201. package/dist/components/data-display/service-launcher-card.d.ts +19 -0
  202. package/dist/components/data-display/service-launcher-card.js +14 -1
  203. package/dist/components/data-entry/attachments.js +77 -33
  204. package/dist/components/data-entry/input.js +8 -1
  205. package/dist/components/layout/flex.d.ts +2 -2
  206. package/dist/components/layout/flex.js +2 -0
  207. package/dist/components/ui/avatar.d.ts +1 -18
  208. package/dist/components/ui/avatar.js +1 -36
  209. package/dist/components/ui/tag-input.d.ts +10 -0
  210. package/dist/components/ui/tag-input.js +35 -2
  211. package/dist/contracts/measurement.json +1 -1
  212. package/dist/i18n/messages/en.json +190 -1
  213. package/dist/i18n/messages/ja.json +188 -1
  214. package/dist/i18n/messages/vi.json +188 -1
  215. package/dist/lib/image-loading-status.d.ts +25 -0
  216. package/dist/lib/image-loading-status.js +41 -0
  217. package/dist/props/components/data-entry.prop.d.ts +21 -2
  218. package/dist/props/components/layout.prop.d.ts +42 -0
  219. package/dist/props/registry.d.ts +14 -1
  220. package/dist/props/registry.js +18 -1
  221. package/dist/styles/card-layout.css +10 -4
  222. package/dist/styles/control.css +33 -4
  223. package/dist/styles/data-display-layout.css +1 -1
  224. package/dist/styles/data-entry-layout.css +245 -2
  225. package/dist/styles/layout.css +17 -0
  226. package/dist/styles/navigation-layout.css +3 -1
  227. package/dist/styles/shell-layout.css +2 -0
  228. package/dist/styles/table-layout.css +50 -9
  229. package/dist/tokens/components/attachments.css +18 -9
  230. package/dist/tokens/components/segmented.css +7 -3
  231. package/dist/tokens/components/table.css +2 -1
  232. package/docs/COMPOSITION-VS-COMPONENT.md +41 -0
  233. package/docs/DESIGN-AUTHORITY.md +52 -0
  234. package/docs/DEVELOPMENT.md +81 -6
  235. package/docs/assets/service-mark-rose.svg +6 -0
  236. package/docs/assets/service-mark-teal.svg +5 -0
  237. package/docs/data-display/service-launcher-card.tsx +232 -92
  238. package/docs/data-entry/tag-input.tsx +37 -0
  239. package/docs/layout/flex.tsx +40 -0
  240. package/docs/roadmap/website-components.md +34 -0
  241. package/docs/showcase/marketing-page.tsx +54 -45
  242. package/docs/showcase/table-pagination.tsx +99 -18
  243. package/docs/showcase/theme-customization.tsx +25 -2
  244. package/package.json +8 -5
@@ -0,0 +1,232 @@
1
+ {
2
+ "example": "import { useState } from \"react\";\nimport { FormField, TreeSelect } from \"@godxjp/ui/data-entry\";\n\nconst accountTree = [\n {\n value: \"assets\",\n label: \"Assets\",\n content: [\n { value: \"current-assets\", label: \"Current Assets\", content: [\n { value: \"cash\", label: \"Cash\" },\n { value: \"ar\", label: \"Accounts Receivable\" },\n ],\n },\n { value: \"fixed-assets\", label: \"Fixed Assets\", content: [\n { value: \"equipment\", label: \"Equipment\" },\n ],\n },\n ],\n },\n {\n value: \"liabilities\",\n label: \"Liabilities\",\n content: [\n { value: \"ap\", label: \"Accounts Payable\" },\n ],\n },\n];\n\n// Single-select (returns string | undefined)\nexport function AccountPicker() {\n const [account, setAccount] = useState<string | undefined>();\n return (\n <FormField id=\"account-picker\" label=\"GL Account\">\n <TreeSelect\n id=\"account-picker\"\n treeData={accountTree}\n value={account}\n onValueChange={(v) => setAccount(v as string | undefined)}\n showSearch\n treeDefaultExpandAll\n placeholder=\"Select account…\"\n allowClear\n />\n </FormField>\n );\n}\n\n// Multi-select with checkboxes + cascade + SHOW_PARENT display\nexport function DepartmentFilter() {\n const [selected, setSelected] = useState<string[]>([]);\n return (\n <TreeSelect\n id=\"dept-filter\"\n treeData={accountTree}\n value={selected}\n onValueChange={(v) => setSelected(v as string[])}\n treeCheckable\n showCheckedStrategy={TreeSelect.SHOW_PARENT}\n showSearch\n placeholder=\"Filter by department…\"\n />\n );\n}",
3
+ "group": "data-entry",
4
+ "importPath": "@godxjp/ui/data-entry",
5
+ "name": "TreeSelect",
6
+ "props": [
7
+ {
8
+ "description": "Native form field name; repeated values for multiple selections.",
9
+ "name": "name",
10
+ "type": "string"
11
+ },
12
+ {
13
+ "description": "Prevent edits while preserving value.",
14
+ "name": "readOnly",
15
+ "type": "boolean"
16
+ },
17
+ {
18
+ "description": "The tree data. Each node: `{ value: string; label: ReactNode; disabled?: boolean; disableCheckbox?: boolean; isLeaf?: boolean; children?: TreeOptionProp[] }`. Use `fieldNames` to remap custom keys.",
19
+ "name": "treeData",
20
+ "required": true,
21
+ "type": "TreeOptionProp[]"
22
+ },
23
+ {
24
+ "description": "Controlled selected value(s). Pass `string` in single mode, `string[]` in multi/checkable mode. When undefined the component is uncontrolled.",
25
+ "name": "value",
26
+ "type": "string | string[]"
27
+ },
28
+ {
29
+ "description": "Initial value for uncontrolled usage. Ignored once `value` is provided.",
30
+ "name": "defaultValue",
31
+ "type": "string | string[]"
32
+ },
33
+ {
34
+ "description": "Called on selection change. Returns `string` in single mode, `string[]` in multi/checkable mode, or `undefined` when cleared.",
35
+ "name": "onValueChange",
36
+ "type": "(value: string | string[] | undefined) => void"
37
+ },
38
+ {
39
+ "defaultValue": "false",
40
+ "description": "Enable multi-select without checkboxes. When true, `onValueChange` always fires with `string[]`.",
41
+ "name": "multiple",
42
+ "type": "boolean"
43
+ },
44
+ {
45
+ "defaultValue": "false",
46
+ "description": "Render Checkbox controls beside each node. Implies multi-select; cascade-selects all descendants by default unless `treeCheckStrictly` is set.",
47
+ "name": "treeCheckable",
48
+ "type": "boolean"
49
+ },
50
+ {
51
+ "defaultValue": "false",
52
+ "description": "When true (only with `treeCheckable`), parent and child selections are independent — checking a parent does NOT auto-check its children.",
53
+ "name": "treeCheckStrictly",
54
+ "type": "boolean"
55
+ },
56
+ {
57
+ "defaultValue": "\"SHOW_CHILD\"",
58
+ "description": "Controls which values appear in the trigger label when checkboxes are used. `SHOW_CHILD` (default) — show only leaf nodes selected; `SHOW_PARENT` — show nearest ancestor when all children selected; `SHOW_ALL` — show every checked node. Use the exported constants `TreeSelect.SHOW_CHILD`, `TreeSelect.SHOW_PARENT`, `TreeSelect.SHOW_ALL` instead of raw strings.",
59
+ "name": "showCheckedStrategy",
60
+ "type": "\"SHOW_CHILD\" | \"SHOW_PARENT\" | \"SHOW_ALL\""
61
+ },
62
+ {
63
+ "defaultValue": "false",
64
+ "description": "Show a labelled SearchInput at the top of the dropdown. Filters visible tree nodes by label text; the trigger combobox controls the tree.",
65
+ "name": "showSearch",
66
+ "type": "boolean"
67
+ },
68
+ {
69
+ "defaultValue": "false",
70
+ "description": "Expand all nodes when the dropdown first opens. Initialised once; does not re-expand on re-render.",
71
+ "name": "treeDefaultExpandAll",
72
+ "type": "boolean"
73
+ },
74
+ {
75
+ "description": "Trigger button placeholder text when nothing is selected. Defaults to the i18n key `dataEntry.treeSelect.placeholder`.",
76
+ "name": "placeholder",
77
+ "type": "string"
78
+ },
79
+ {
80
+ "defaultValue": "false",
81
+ "description": "Disables the trigger button and all interactions.",
82
+ "name": "disabled",
83
+ "type": "boolean"
84
+ },
85
+ {
86
+ "defaultValue": "false",
87
+ "description": "Show an `X` icon in the trigger to clear the selection. Off by default, as antd TreeSelect; pass `allowClear` on an optional field.",
88
+ "name": "allowClear",
89
+ "type": "boolean"
90
+ },
91
+ {
92
+ "description": "Additional Tailwind classes applied to the trigger Button.",
93
+ "name": "className",
94
+ "type": "string"
95
+ },
96
+ {
97
+ "description": "HTML `id` placed on the trigger Button — use this to associate a `<label htmlFor>` for accessibility.",
98
+ "name": "id",
99
+ "type": "string"
100
+ },
101
+ {
102
+ "description": "Accessible name for the combobox trigger when no visible label is available.",
103
+ "name": "aria-label",
104
+ "type": "string"
105
+ },
106
+ {
107
+ "description": "ID of the element containing the current validation error message.",
108
+ "name": "aria-errormessage",
109
+ "type": "string"
110
+ },
111
+ {
112
+ "description": "Marks the semantic combobox trigger invalid for assistive technology.",
113
+ "name": "aria-invalid",
114
+ "type": "boolean | 'true' | 'false'"
115
+ },
116
+ {
117
+ "description": "Marks the semantic combobox trigger required for assistive technology.",
118
+ "name": "aria-required",
119
+ "type": "boolean | 'true' | 'false'"
120
+ },
121
+ {
122
+ "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`.",
123
+ "name": "fieldNames",
124
+ "type": "{ label?: string; value?: string; children?: string }"
125
+ },
126
+ {
127
+ "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.",
128
+ "name": "status",
129
+ "type": "\"error\" | \"warning\""
130
+ },
131
+ {
132
+ "description": "antd `variant` — the control surface. Default `outlined`. Drawn from --control-{surface,filled,borderless}-* tokens, so a theme retunes all three at once.",
133
+ "name": "variant",
134
+ "type": "\"outlined\" | \"filled\" | \"borderless\""
135
+ },
136
+ {
137
+ "description": "antd `loading` — 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.",
138
+ "name": "loading",
139
+ "type": "boolean"
140
+ },
141
+ {
142
+ "description": "antd `size` — height tier on the shared --control-height ladder.",
143
+ "name": "size",
144
+ "type": "\"xs\" | \"sm\" | \"md\" | \"lg\""
145
+ },
146
+ {
147
+ "description": "antd `open` — controlled panel state. The consumer is the authority: nothing internal closes a pinned panel.",
148
+ "name": "open",
149
+ "type": "boolean"
150
+ },
151
+ {
152
+ "description": "antd `defaultOpen` — uncontrolled initial panel state.",
153
+ "name": "defaultOpen",
154
+ "type": "boolean"
155
+ },
156
+ {
157
+ "description": "antd `onOpenChange` — fires for both controlled and uncontrolled panels.",
158
+ "name": "onOpenChange",
159
+ "type": "(open: boolean) => void"
160
+ },
161
+ {
162
+ "description": "antd `maxTagCount` — how many selected values stay visible before the rest collapse into the overflow node. antd's `\"responsive\"` is not supported (see the parity PR).",
163
+ "name": "maxTagCount",
164
+ "type": "number"
165
+ },
166
+ {
167
+ "description": "antd `maxTagPlaceholder` — the node standing in for what maxTagCount hid. Defaults to a localized `+N`.",
168
+ "name": "maxTagPlaceholder",
169
+ "type": "React.ReactNode | ((omitted: { value: string; label: React.ReactNode }[]) => React.ReactNode)"
170
+ },
171
+ {
172
+ "description": "antd `notFoundContent` — the node shown when the popup has nothing to list. Outranks the string-only emptyMessage.",
173
+ "name": "notFoundContent",
174
+ "type": "React.ReactNode"
175
+ },
176
+ {
177
+ "description": "antd `autoClearSearchValue` (default true) — clear the search box after a pick / on close. Set false to resume the same filtered list on the next open.",
178
+ "name": "autoClearSearchValue",
179
+ "type": "boolean"
180
+ },
181
+ {
182
+ "description": "antd `loadData` — 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).",
183
+ "name": "loadData",
184
+ "type": "(node: TreeOptionProp) => void | Promise<void>"
185
+ },
186
+ {
187
+ "description": "antd `treeTitleRender` — owns a node's title only; the checkbox, expander and row ARIA stay with the component.",
188
+ "name": "treeTitleRender",
189
+ "type": "(node: TreeOptionProp) => React.ReactNode"
190
+ },
191
+ {
192
+ "description": "Controlled search query (antd `showSearch.searchValue`).",
193
+ "name": "search",
194
+ "type": "string"
195
+ },
196
+ {
197
+ "description": "Search query change (antd `showSearch.onSearch`).",
198
+ "name": "onSearchChange",
199
+ "type": "(query: string) => void"
200
+ }
201
+ ],
202
+ "related": [
203
+ "Select — flat single/multi picker; use when data has no parent-child hierarchy. Pick TreeSelect as soon as items have `children`.",
204
+ "Cascader — 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→Region→City). Pick TreeSelect when the user may select any node at any level or needs checkboxes.",
205
+ "Checkbox / CheckboxGroup — 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.",
206
+ "Command / CommandInput — low-level search primitive; TreeSelect already embeds this internally. Do NOT compose your own tree dropdown out of Command — use TreeSelect."
207
+ ],
208
+ "rules": [
209
+ 3,
210
+ 6,
211
+ 13,
212
+ 23
213
+ ],
214
+ "storyPath": "data-entry/TreeSelect.stories.tsx",
215
+ "tagline": "Hierarchical tree picker in a Popover (single or multi-select with checkboxes) — `onValueChange` receives `string` in single mode and `string[]` in multi/checkable mode; never use a raw `<select>` for tree-structured data.",
216
+ "usage": [
217
+ "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'>` — not a native `<select>` — so an explicit label is required.",
218
+ "DO use `treeCheckable` (+ optionally `showCheckedStrategy`) for selecting multiple nodes with parent–child cascade; use `multiple` only when you want multi-select WITHOUT the checkbox cascade behaviour.",
219
+ "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`.",
220
+ "DON'T pass `value` and `defaultValue` simultaneously — pick controlled (`value` + `onValueChange`) OR uncontrolled (`defaultValue` only). Mixing them causes the component to silently prefer the controlled path.",
221
+ "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.",
222
+ "DON'T use a raw `<select>` or a flat `Select` component for hierarchical/nested data — TreeSelect is the correct primitive. If hierarchy is irrelevant and data is flat, use `Select` instead."
223
+ ],
224
+ "useCases": [
225
+ "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.",
226
+ "Multi-select department or cost-centre filter where selecting a parent division should auto-select all child departments (treeCheckable + SHOW_PARENT).",
227
+ "Category assignment on invoice line items where categories have up to 3 levels of nesting and users can assign a parent or a leaf.",
228
+ "Permission scope selector where roles are structured in a tree and selecting a parent role should cascade to all child scopes (treeCheckable + treeCheckStrictly=false).",
229
+ "Location picker (Country > Prefecture > City) in a form where only leaf-level cities are valid selections (single mode, no checkboxes).",
230
+ "Large GL hierarchy browser with showSearch enabled so users can type to filter thousands of account codes instead of manually expanding nodes."
231
+ ]
232
+ }
@@ -0,0 +1,79 @@
1
+ {
2
+ "example": "import { TwoFactorSetup } from \"@godxjp/ui/feedback\";\n\n<TwoFactorSetup\n open={open}\n onOpenChange={setOpen}\n qrValue={enrollment.qr}\n manualKey={enrollment.secret}\n code={code}\n onCodeChange={setCode}\n onConfirm={verify}\n recoveryCodes={recoveryCodes}\n onAcknowledge={finish}\n labels={labels}\n/>",
3
+ "group": "feedback",
4
+ "importPath": "@godxjp/ui/feedback",
5
+ "name": "TwoFactorSetup",
6
+ "props": [
7
+ {
8
+ "description": "Controlled dialog state.",
9
+ "name": "open",
10
+ "required": true,
11
+ "type": "boolean"
12
+ },
13
+ {
14
+ "description": "Dialog state callback.",
15
+ "name": "onOpenChange",
16
+ "required": true,
17
+ "type": "(open: boolean) => void"
18
+ },
19
+ {
20
+ "description": "Consumer-provided enrollment secret.",
21
+ "name": "manualKey",
22
+ "required": true,
23
+ "type": "string"
24
+ },
25
+ {
26
+ "description": "Optional QR payload for the same enrollment secret.",
27
+ "name": "qrValue",
28
+ "type": "string"
29
+ },
30
+ {
31
+ "description": "Current verification code.",
32
+ "name": "code",
33
+ "required": true,
34
+ "type": "string"
35
+ },
36
+ {
37
+ "description": "Verification-code callback.",
38
+ "name": "onCodeChange",
39
+ "required": true,
40
+ "type": "(code: string) => void"
41
+ },
42
+ {
43
+ "description": "Consumer-owned verification action.",
44
+ "name": "onConfirm",
45
+ "required": true,
46
+ "type": "() => void"
47
+ },
48
+ {
49
+ "description": "Codes shown only after successful enrollment.",
50
+ "name": "recoveryCodes",
51
+ "type": "string[]"
52
+ },
53
+ {
54
+ "description": "Recovery-code acknowledgement action.",
55
+ "name": "onAcknowledge",
56
+ "required": true,
57
+ "type": "() => void"
58
+ },
59
+ {
60
+ "description": "All localized copy.",
61
+ "name": "labels",
62
+ "required": true,
63
+ "type": "TwoFactorSetupLabels"
64
+ },
65
+ {
66
+ "defaultValue": "false",
67
+ "description": "Disables actions while the consumer request runs.",
68
+ "name": "pending",
69
+ "type": "boolean"
70
+ }
71
+ ],
72
+ "rules": [],
73
+ "storyPath": "feedback/TwoFactorSetup.stories.tsx",
74
+ "tagline": "Canonical two-factor enrollment dialog for QR/manual-key verification and recovery-code acknowledgement.",
75
+ "usage": [
76
+ "Keep enrollment, verification, persistence, and secret lifecycle in the consumer; this component is presentational.",
77
+ "Only pass recovery codes after verification succeeds, and clear them when the dialog lifecycle ends."
78
+ ]
79
+ }
@@ -0,0 +1,42 @@
1
+ {
2
+ "example": "import { Typography } from \"@godxjp/ui/general\";\n\n<Typography>\n <Typography.Title level={3}>リリースノート</Typography.Title>\n <Typography.Paragraph>請求書の一括ダウンロードに対応しました。</Typography.Paragraph>\n <Typography.Link href=\"/changelog\">変更履歴</Typography.Link>\n</Typography>",
3
+ "group": "general",
4
+ "importPath": "@godxjp/ui/general",
5
+ "name": "Typography",
6
+ "props": [
7
+ {
8
+ "defaultValue": "\"article\"",
9
+ "description": "Rendered element.",
10
+ "name": "as",
11
+ "type": "string"
12
+ },
13
+ {
14
+ "description": "antd's private alias for `as`. Accepted so antd code pastes in unchanged; `as` wins when both are passed.",
15
+ "name": "component",
16
+ "type": "string"
17
+ }
18
+ ],
19
+ "related": [
20
+ "Text",
21
+ "Title",
22
+ "Paragraph",
23
+ "Link",
24
+ "Heading",
25
+ "Prose"
26
+ ],
27
+ "rules": [
28
+ 2,
29
+ 23
30
+ ],
31
+ "storyPath": "general/typography.tsx",
32
+ "tagline": "antd's prose container — a plain <article> that Title / Paragraph / Text / Link sit inside, and the compound root so `<Typography.Text>` from an antd codebase compiles here unchanged.",
33
+ "usage": [
34
+ "DO reach for it when you have a RUN of prose — a heading, some paragraphs, a link — rather than one label. A single caption is just `<Text>`.",
35
+ "DO use the compound spelling when porting from antd: `Typography.Text`, `Typography.Title`, `Typography.Paragraph` and `Typography.Link` ARE the flat `Text` / `Title` / `Paragraph` / `Link` exports, not poorer copies of them.",
36
+ "DON'T use it as a layout box. It carries type, not spacing — sections are spaced by `PageContainer` and groups by `Flex` / `ResponsiveGrid`."
37
+ ],
38
+ "useCases": [
39
+ "A release-note body: a `Title`, two `Paragraph`s and a `Link`, wrapped so the block rhythm is owned in one place.",
40
+ "Pasting an antd screen in unchanged, `Typography.Paragraph` and all."
41
+ ]
42
+ }
@@ -0,0 +1,221 @@
1
+ {
2
+ "example": "import { useState } from \"react\";\nimport { Upload, type UploadFileItem, collectUploadCommitActions } from \"@godxjp/ui/data-entry\";\n\n// Example: avatar picker with server upload\nexport function AvatarUploadForm() {\n const [items, setItems] = useState<UploadFileItem[]>([]);\n\n async function handleUpload(file: File, _item: UploadFileItem) {\n const fd = new FormData();\n fd.append(\"file\", file);\n const res = await fetch(\"/api/media/upload\", { method: \"POST\", body: fd });\n const { mediaId, previewUrl } = await res.json();\n return { mediaId, previewUrl };\n }\n\n function handleSubmit(e: React.FormEvent) {\n e.preventDefault();\n const { deleteMediaIds, promoteMediaIds } = collectUploadCommitActions(items);\n // Send to your API — never send raw File objects\n console.log({ deleteMediaIds, promoteMediaIds });\n }\n\n return (\n <form onSubmit={handleSubmit}>\n <Upload\n variant=\"avatar-crop\"\n value={items}\n onValueChange={setItems}\n onUpload={handleUpload}\n maxSizeBytes={5 * 1024 * 1024}\n />\n <button type=\"submit\">Save Profile</button>\n </form>\n );\n}\n\n// Example: multi-file dropzone\nexport function DocumentUploadDropzone() {\n const [items, setItems] = useState<UploadFileItem[]>([]);\n\n return (\n <Upload\n variant=\"dropzone\"\n value={items}\n onValueChange={setItems}\n accept=\".pdf,.xlsx\"\n maxCount={10}\n maxSizeBytes={20 * 1024 * 1024}\n onUpload={async (file) => {\n const res = await fetch(\"/api/media/upload\", {\n method: \"POST\",\n body: Object.assign(new FormData(), { file }),\n });\n return res.json();\n }}\n />\n );\n}",
3
+ "group": "data-entry",
4
+ "importPath": "@godxjp/ui/data-entry",
5
+ "name": "Upload",
6
+ "props": [
7
+ {
8
+ "defaultValue": "\"dropzone\"",
9
+ "description": "Controls the visual rendering mode. dropzone = large dashed drop area + file list; button = compact outline button + file list; picture-card = grid of 96×96 image thumbnails; picture = single image preview with change/remove actions; avatar = circular single-image picker; avatar-crop = avatar with an in-dialog crop step before the item is staged.",
10
+ "name": "variant",
11
+ "type": "\"dropzone\" | \"button\" | \"picture-card\" | \"picture\" | \"avatar\" | \"avatar-crop\""
12
+ },
13
+ {
14
+ "defaultValue": "\"picture\" for variant=\"picture\", otherwise \"text\"",
15
+ "description": "HOW THE CHOSEN FILES ARE LISTED — antd's listType, and an axis of its own: variant decides how files are PICKED, listType decides how the picked ones are DRAWN. text = name, size and actions (the classic dropzone/button row). picture = a leading box on every row: the thumbnail when the item has a previewUrl, otherwise the glyph for its file kind (image / pdf / archive / text / generic), both boxes the same size so a mixed list keeps one row height. The glyph is decorative (aria-hidden) and carries its kind as data-file-kind for theming. antd's picture-card is variant='picture-card' here — the tile grid IS the picker there, so it is not offered as a listType.",
16
+ "name": "listType",
17
+ "type": "\"text\" | \"picture\""
18
+ },
19
+ {
20
+ "description": "Controlled list of file items. When provided the component is controlled — you own the state. Omit to run uncontrolled.",
21
+ "name": "value",
22
+ "type": "UploadFileItem[]"
23
+ },
24
+ {
25
+ "description": "Initial list of file items for uncontrolled usage. Ignored once value is provided.",
26
+ "name": "defaultValue",
27
+ "type": "UploadFileItem[]"
28
+ },
29
+ {
30
+ "description": "MIME / extension accept string passed to the hidden <input type=\"file\">. avatar/avatar-crop/picture/picture-card default to \"image/*\"; dropzone and button default to unrestricted.",
31
+ "name": "accept",
32
+ "type": "string"
33
+ },
34
+ {
35
+ "description": "Allow multi-file selection. Auto-derived: false when maxCount is 1 (or when variant is avatar/avatar-crop/picture); otherwise true.",
36
+ "name": "multiple",
37
+ "type": "boolean"
38
+ },
39
+ {
40
+ "description": "Hard upper bound on the number of items. avatar/avatar-crop/picture auto-default to 1. Once the limit is reached the add button is hidden (picture-card) or additions are rejected; maxCount=1 replaces the current item.",
41
+ "name": "maxCount",
42
+ "type": "number"
43
+ },
44
+ {
45
+ "description": "Files larger than this limit are rejected with localized feedback and onReject.",
46
+ "name": "maxSizeBytes",
47
+ "type": "number"
48
+ },
49
+ {
50
+ "description": "Disables all interactive surfaces (drop zone, buttons). Visual opacity + pointer-events-none applied.",
51
+ "name": "disabled",
52
+ "type": "boolean"
53
+ },
54
+ {
55
+ "defaultValue": "true",
56
+ "description": "Show the remove/delete control on each item. Set false to make uploads permanent within the session.",
57
+ "name": "removable",
58
+ "type": "boolean"
59
+ },
60
+ {
61
+ "description": "Called immediately after a file is picked (before form submit). Transitions the item to status='uploading', then 'done' on resolve or 'error' on reject. Wire this to your media-service issue/PUT/complete cycle. If omitted files stay in status='idle' and the raw File object remains in item.file.",
62
+ "name": "onUpload",
63
+ "type": "(file: File, item: UploadFileItem, context: UploadRequestContext) => Promise<UploadResult>"
64
+ },
65
+ {
66
+ "description": "Extra CSS class applied to the outer wrapper div.",
67
+ "name": "className",
68
+ "type": "string"
69
+ },
70
+ {
71
+ "description": "Custom button label for variant='button'. Falls back to the i18n 'Upload file' string.",
72
+ "name": "children",
73
+ "type": "React.ReactNode"
74
+ },
75
+ {
76
+ "description": "Fires with the current file list.",
77
+ "name": "onValueChange",
78
+ "type": "(items: UploadFileItemProp[]) => void"
79
+ },
80
+ {
81
+ "description": "`variant=\"button\"` only — size of the visible trigger, forwarded to Button. An icon size renders it icon-only and moves the label to `aria-label`: a 32px square beside other icon buttons rather than a 147px labelled one that outweighs them.",
82
+ "name": "triggerSize",
83
+ "type": "\"default\" | \"md\" | \"xs\" | \"sm\" | \"lg\" | \"icon\" | \"icon-xs\" | \"icon-sm\" | \"icon-lg\""
84
+ },
85
+ {
86
+ "defaultValue": "\"outline\"",
87
+ "description": "`variant=\"button\"` only — visual weight of the visible trigger, forwarded to Button. Default `outline` suits a standalone form field. Pass `ghost` when the trigger sits in a toolbar row beside other icon buttons — inside a chat composer, say — where a bordered square reads as the odd one out.",
88
+ "name": "triggerVariant",
89
+ "type": "\"default\" | \"destructive\" | \"outline\" | \"dashed\" | \"secondary\" | \"ghost\" | \"link\" | \"bare\""
90
+ },
91
+ {
92
+ "defaultValue": "the lucide upload arrow",
93
+ "description": "`variant=\"button\"` only — the glyph the trigger draws. Pass the COMPONENT (`triggerIcon={Plus}`), not an element, exactly as `Icon`'s `as` takes it; every lucide icon qualifies. It exists because the glyph was hard-coded and `triggerVariant` only moves emphasis, so a \"create new\" action that HAPPENS to upload could not carry a plus and had to announce itself as an upload (gh#734). The library still owns the class, the label spacing and the `aria-hidden`, so a swapped glyph renders at the same `--upload-row-icon-size` and never reaches the accessible name — the trigger's metrics cannot drift with the icon.",
94
+ "name": "triggerIcon",
95
+ "type": "React.ComponentType<React.SVGProps<SVGSVGElement> & React.RefAttributes<SVGSVGElement>>"
96
+ },
97
+ {
98
+ "description": "Displays existing files, blocks changes, preserves staged form data.",
99
+ "name": "readOnly",
100
+ "type": "boolean"
101
+ },
102
+ {
103
+ "description": "Select a folder; each item preserves relativePath.",
104
+ "name": "directory",
105
+ "type": "boolean"
106
+ },
107
+ {
108
+ "description": "Paste clipboard files while focus is inside this Upload; text paste is untouched.",
109
+ "name": "pastable",
110
+ "type": "boolean"
111
+ },
112
+ {
113
+ "description": "Defaults true. Disable native dialog activation for drop-only surfaces.",
114
+ "name": "openFileDialogOnClick",
115
+ "type": "boolean"
116
+ },
117
+ {
118
+ "description": "Multipart file field name; named staged files also join native FormData.",
119
+ "name": "name",
120
+ "type": "string"
121
+ },
122
+ {
123
+ "description": "Multipart upload URL; onUpload takes precedence.",
124
+ "name": "action",
125
+ "type": "string | ((file: File) => string | Promise<string>)"
126
+ },
127
+ {
128
+ "description": "Request method, default POST.",
129
+ "name": "method",
130
+ "type": "\"POST\" | \"PUT\" | \"PATCH\""
131
+ },
132
+ {
133
+ "description": "Request headers such as CSRF tokens; multipart boundaries remain browser-owned.",
134
+ "name": "headers",
135
+ "type": "Record<string, string>"
136
+ },
137
+ {
138
+ "description": "Additional multipart fields, optionally resolved per file.",
139
+ "name": "data",
140
+ "type": "Record<string, string | Blob> | ((file: File) => Record<string, string | Blob> | Promise<Record<string, string | Blob>>)"
141
+ },
142
+ {
143
+ "description": "Send credentials with the default transport.",
144
+ "name": "withCredentials",
145
+ "type": "boolean"
146
+ },
147
+ {
148
+ "description": "Validate or transform before upload; false stages manually; UPLOAD_LIST_IGNORE excludes. Rejections are reported.",
149
+ "name": "beforeUpload",
150
+ "type": "(file: File, files: File[]) => boolean | File | Blob | typeof UPLOAD_LIST_IGNORE | Promise<boolean | File | Blob | typeof UPLOAD_LIST_IGNORE>"
151
+ },
152
+ {
153
+ "description": "Reports accept, size, count, or preflight rejection.",
154
+ "name": "onReject",
155
+ "type": "(rejection: UploadRejection) => void"
156
+ },
157
+ {
158
+ "description": "Returning false or rejecting vetoes removal. Accepted removal aborts active upload.",
159
+ "name": "onRemove",
160
+ "type": "(item: UploadFileItem) => boolean | void | Promise<boolean | void>"
161
+ },
162
+ {
163
+ "description": "Preview action callback.",
164
+ "name": "onPreview",
165
+ "type": "(item: UploadFileItem) => void"
166
+ },
167
+ {
168
+ "description": "Download action callback.",
169
+ "name": "onDownload",
170
+ "type": "(item: UploadFileItem) => void"
171
+ },
172
+ {
173
+ "description": "Asynchronously generate a custom thumbnail.",
174
+ "name": "previewFile",
175
+ "type": "(file: File) => Promise<string>"
176
+ },
177
+ {
178
+ "description": "Observe drop events.",
179
+ "name": "onDrop",
180
+ "type": "React.DragEventHandler<HTMLElement>"
181
+ },
182
+ {
183
+ "description": "Show the default file list, default true.",
184
+ "name": "showUploadList",
185
+ "type": "boolean"
186
+ },
187
+ {
188
+ "description": "Customize a file row while preserving its default actions.",
189
+ "name": "itemRender",
190
+ "type": "(node: React.ReactElement, item: UploadFileItem, items: UploadFileItem[], actions: UploadItemActions) => React.ReactNode"
191
+ }
192
+ ],
193
+ "related": [
194
+ "Input (type='file') — never hand-roll a raw file input; use Upload instead. Upload provides drag-drop, preview, upload lifecycle, and soft-delete draft.",
195
+ "Avatar (display-only) — the godx-ui Avatar component renders a user's existing image; use Upload variant='avatar' or 'avatar-crop' when you need the user to change it.",
196
+ "DataTable — unrelated to Upload but both appear together in bulk-import flows: Upload (button variant) triggers the import, DataTable shows the result."
197
+ ],
198
+ "rules": [
199
+ 3,
200
+ 23
201
+ ],
202
+ "storyPath": "data-entry/Upload.stories.tsx",
203
+ "tagline": "Drag-and-drop / button / avatar / picture file uploader in six variants — wire onUpload to your media-service and call collectUploadCommitActions on form submit; supports multipart forms and custom media storage.",
204
+ "usage": [
205
+ "DO provide onUpload to auto-upload on pick. The callback must return { mediaId, previewUrl? } — the component transitions item.status through uploading → done/error automatically. Without onUpload the File object sits in item.file until you manually process it.",
206
+ "DO call collectUploadCommitActions(items) on form submit to get { deleteMediaIds, promoteMediaIds } for your media-service. For multipart endpoints submit File objects via FormData; never submit blob URLs as persisted media.",
207
+ "DO use createUploadItem(file) to build UploadFileItem objects when pre-populating value from server data (e.g. edit forms). Set status='done' and mediaId on existing server media so the draft/undo machinery tracks them correctly.",
208
+ "For native multipart or Inertia Form submissions, set name: staged local files are appended during the formdata event. Completed media uploads use collectUploadCommitActions instead.",
209
+ "Avatar/picture variants (maxCount=1) use internal soft-delete draft logic: removing an item marks it pendingDelete so the user can undo before committing. On form submit, collectUploadCommitActions converts pendingDelete → deleteMediaIds and done mediaIds → promoteMediaIds.",
210
+ "For avatar-crop: a crop dialog opens after pick. The cropped Blob is staged as a new UploadFileItem. The original file never enters the list — only the cropped version is passed to onUpload.",
211
+ "For a drawer or panel listing MIXED attachments (.png beside .json and .txt), keep variant='dropzone' and set listType='picture': the image rows draw their previewUrl as a thumbnail and every other row draws the glyph for its kind, on one row height. Do NOT switch to variant='picture' to get thumbnails — that variant also changes the picker, the default accept to image/* and maxCount to 1."
212
+ ],
213
+ "useCases": [
214
+ "Profile / user avatar editor: use variant='avatar-crop' so users can crop the image before upload; wire onUpload to your media-service; call collectUploadCommitActions on profile form submit to promote or delete.",
215
+ "Invoice / document attachment list: use variant='dropzone' with accept='.pdf,.xlsx' and maxSizeBytes to let accountants drag-drop supporting documents; show the file list with status indicators below the drop zone.",
216
+ "Product gallery (multiple images): use variant='picture-card' with maxCount to display a grid of thumbnails; each item gets an individual remove ✕ button; collectUploadCommitActions on product save.",
217
+ "Single cover-image picker on a content form: use variant='picture' with maxCount=1 to show a preview rectangle with change/remove controls and undo-delete support.",
218
+ "CSV / bulk-import button in an admin table header: use variant='button' with accept='.csv' and custom children label ('Import CSV') to keep the UI compact; process item.file in the onChange handler.",
219
+ "Inline document replacement on an accounting record (replace, not append): use variant='avatar' (single-slot logic) or picture; onUpload returns the new mediaId; collectUploadCommitActions delivers replacesMediaId → deleteMediaIds."
220
+ ]
221
+ }
@@ -0,0 +1,60 @@
1
+ {
2
+ "example": "{`import { useState } from \"react\";\nimport { UploadCropDialog } from \"@godxjp/ui/upload\"; // internal — prefer Upload variant=\"avatar-crop\" instead\n\nexport function AvatarField() {\n const [cropFile, setCropFile] = useState<File | null>(null);\n\n const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const file = e.target.files?.[0] ?? null;\n setCropFile(file);\n e.target.value = \"\"; // reset so re-selecting same file fires onChange\n };\n\n const handleConfirm = (cropped: File) => {\n // cropped is always image/jpeg 256×256\n const form = new FormData();\n form.append(\"avatar\", cropped);\n fetch(\"/api/avatar\", { method: \"POST\", body: form });\n };\n\n return (\n <>\n <input type=\"file\" accept=\"image/*\" onValueChange={handleFileChange} />\n <UploadCropDialog\n open={cropFile !== null}\n onOpenChange={(open) => { if (!open) setCropFile(null); }}\n file={cropFile}\n onConfirm={handleConfirm}\n />\n </>\n );\n}`}",
3
+ "group": "data-entry",
4
+ "importPath": "@godxjp/ui/data-entry",
5
+ "name": "UploadCropDialog",
6
+ "props": [
7
+ {
8
+ "description": "Controls dialog visibility. Drive this with useState; the component never auto-opens.",
9
+ "name": "open",
10
+ "required": true,
11
+ "type": "boolean"
12
+ },
13
+ {
14
+ "description": "Called when the dialog requests close (Cancel button or overlay click). Set your open state to false here.",
15
+ "name": "onOpenChange",
16
+ "required": true,
17
+ "type": "(open: boolean) => void"
18
+ },
19
+ {
20
+ "description": "The raw File object to crop. An object URL is created internally and revoked on cleanup. Pass null when no file is selected (dialog renders empty).",
21
+ "name": "file",
22
+ "required": true,
23
+ "type": "File | null"
24
+ },
25
+ {
26
+ "description": "Called with the cropped result — always a JPEG File (256x256, quality 0.92) regardless of the original format. The dialog closes itself after calling this.",
27
+ "name": "onConfirm",
28
+ "required": true,
29
+ "type": "(cropped: File) => void"
30
+ }
31
+ ],
32
+ "related": [
33
+ "Upload (variant='avatar-crop') — the preferred way to get crop-on-upload for avatars; it wraps UploadCropDialog automatically. Use UploadCropDialog directly only when you need a custom file-picking trigger or a non-avatar crop flow.",
34
+ "Upload (variant='avatar') — same circular avatar UI without the crop step; the file is used as-is.",
35
+ "Upload (variant='picture') — rectangular single-image upload without crop; no dialog.",
36
+ "Upload (variant='picture-card') — multi-image grid upload without crop."
37
+ ],
38
+ "rules": [
39
+ 3,
40
+ 13,
41
+ 23
42
+ ],
43
+ "storyPath": "data-entry/UploadCropDialog.stories.tsx",
44
+ "tagline": "Modal crop dialog for a single image file — always controlled (open + file + onConfirm required); do NOT use standalone when Upload variant=\"avatar-crop\" already embeds it.",
45
+ "usage": [
46
+ "DO pass a File object selected by the user (e.g. from an <input type='file'> or drag-drop handler) to `file`; the dialog creates and revokes its own object URL — never create one yourself before passing.",
47
+ "DO close the dialog in onOpenChange: `onOpenChange={(open) => !open && setCropFile(null)}` — always clear cropFile state on close to avoid a stale image on the next open.",
48
+ "DO handle the cropped File in onConfirm and then upload or store it; the output is always image/jpeg 256×256 named `<originalName>.jpg`.",
49
+ "DON'T use UploadCropDialog directly if you are already using `<Upload variant='avatar-crop'>` — that variant already embeds UploadCropDialog internally. Using both will double-mount the dialog.",
50
+ "DON'T try to control the zoom slider from outside — scale state is fully internal; the user controls zoom in-dialog via a Slider (1–2.5×, step 0.05).",
51
+ "DON'T submit the dialog's output File as a form field directly; pass it to your upload handler (e.g. onUpload prop on Upload, or a manual FormData POST), since File objects cannot survive a standard HTML form serialisation."
52
+ ],
53
+ "useCases": [
54
+ "Avatar / profile photo upload flow: show a file picker, pass the chosen File to UploadCropDialog, upload the cropped 256×256 JPEG to the server on confirm.",
55
+ "Admin user management: let admins set or replace a team member's avatar with consistent square crop instead of accepting arbitrary-shaped originals.",
56
+ "Legal-entity logo upload in an accounting app: enforce a square, web-ready JPEG from any source image before storing it as the entity's icon.",
57
+ "Any single-image form field that needs browser-side crop before upload — avoids a round-trip to a server-side image processor.",
58
+ "Building a custom avatar picker UI on top of the godx-ui Upload primitives when the built-in variant='avatar-crop' does not fit your layout."
59
+ ]
60
+ }