@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,67 @@
1
+ {
2
+ "example": "import { Title } from \"@godxjp/ui/general\";\n\n<Title level={2}>請求書一覧</Title>\n<Title level={3} editable={{ onChange: rename }}>{projectName}</Title>",
3
+ "group": "general",
4
+ "importPath": "@godxjp/ui/general",
5
+ "name": "Title",
6
+ "props": [
7
+ {
8
+ "defaultValue": "1",
9
+ "description": "Sets BOTH the --heading-h{1..5} size token and the semantic <h1>..<h5>. Level 5 reads --heading-h5, which is bound to the existing --font-size-2xs step rather than being a new number. A value outside 1..5 falls back to h1, the way antd does — an <h7> from a runtime value would have no heading semantics at all. NOTE the default is antd's 1, while `Heading` defaults to 2.",
10
+ "name": "level",
11
+ "type": "1 | 2 | 3 | 4 | 5"
12
+ },
13
+ {
14
+ "description": "Override the rendered element — a visual h2 that is a real <h1>.",
15
+ "name": "as",
16
+ "type": "\"h1\" | \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"div\""
17
+ },
18
+ {
19
+ "defaultValue": "\"default\"",
20
+ "description": "Semantic foreground colour. Outranks antd's `type`.",
21
+ "name": "tone",
22
+ "type": "\"default\" | \"muted\" | \"primary\" | \"success\" | \"warning\" | \"destructive\" | \"info\" | \"inherit\""
23
+ },
24
+ {
25
+ "description": "Logical text alignment.",
26
+ "name": "align",
27
+ "type": "\"start\" | \"center\" | \"end\""
28
+ },
29
+ {
30
+ "description": "Single-line ellipsis. `ellipsis` outranks it when both are passed.",
31
+ "name": "truncate",
32
+ "type": "boolean"
33
+ },
34
+ {
35
+ "defaultValue": "\"medium\"",
36
+ "description": "Font weight, from the three-weight canon.",
37
+ "name": "weight",
38
+ "type": "\"regular\" | \"medium\" | \"semibold\" | \"bold\""
39
+ },
40
+ {
41
+ "description": "Truncation. `true` is one line; `rows` clamps to N. `expandable` adds an expand control, `symbol` relabels it, `onEllipsis` fires when the measured overflow flips.",
42
+ "name": "ellipsis",
43
+ "type": "boolean | { rows, expandable, suffix, symbol, defaultExpanded, expanded, onExpand, onEllipsis, tooltip }"
44
+ }
45
+ ],
46
+ "related": [
47
+ "Heading",
48
+ "Typography",
49
+ "Text",
50
+ "CardTitle"
51
+ ],
52
+ "rules": [
53
+ 2,
54
+ 23
55
+ ],
56
+ "storyPath": "general/typography.tsx",
57
+ "tagline": "antd Typography.Title — a heading with five levels and the antd block behaviours (copyable, editable, ellipsis, the decorations). A SIBLING of `Heading`, which is this library's own four-level heading and is unchanged.",
58
+ "usage": [
59
+ "DO use `Title` when you want antd's spelling, a fifth level, or a heading that is copyable / editable.",
60
+ "DO use `Heading` for everything else. It is the four-level heading the rest of this library is written in, and `CardTitle` / `PageContainer` already render one for you.",
61
+ "DON'T set `level` to pick a SIZE. It sets the semantic element too, so skipping from h1 to h4 for looks breaks the document outline — override the element with `as` instead."
62
+ ],
63
+ "useCases": [
64
+ "A project name a user can rename in place: `<Title level={2} editable={{ onChange: rename }}>{name}</Title>`.",
65
+ "A dense sub-label below the body step, where `Heading` has no level left: `<Title level={5}>内訳</Title>`."
66
+ ]
67
+ }
@@ -0,0 +1,42 @@
1
+ {
2
+ "example": "// app root — mount once\nimport { Toaster } from \"@godxjp/ui/feedback\";\n<>{children}<Toaster richColors /></>\n\n// anywhere — import toast from \"sonner\"\nimport { toast } from \"sonner\";\ntoast.success(\"クーポンを公開しました\");\ntoast.error(\"保存に失敗しました\");",
3
+ "group": "feedback",
4
+ "importPath": "@godxjp/ui/feedback",
5
+ "name": "Toaster",
6
+ "props": [
7
+ {
8
+ "defaultValue": "\"bottom-right\"",
9
+ "description": "Toast stack anchor.",
10
+ "name": "position",
11
+ "type": "\"top-right\" | \"top-center\" | \"bottom-right\" | \"…\""
12
+ },
13
+ {
14
+ "description": "Enable Sonner rich variant colours.",
15
+ "name": "richColors",
16
+ "type": "boolean"
17
+ }
18
+ ],
19
+ "related": [
20
+ "Alert — use for persistent, inline feedback that must stay visible (validation summaries, page-level warnings, destructive notices). Unlike Toaster, Alert does not auto-dismiss and lives in the document flow.",
21
+ "AlertMutationFeedback — use when you have a TanStack `useMutation` result and want an inline error + retry UI inside a form or card. Renders nothing on success/idle; pairs naturally with a `toast.success` in `onSuccess`.",
22
+ "Dialog — use when the user must make a conscious decision (confirm delete, resolve conflict) before proceeding. Toaster toasts are fire-and-forget; Dialog blocks until the user responds."
23
+ ],
24
+ "rules": [],
25
+ "storyPath": "feedback/Toaster.stories.tsx",
26
+ "tagline": "Mount once at app root to enable toasts. IMPORTANT: trigger toasts via `import { toast } from \"sonner\"` — NOT from @godxjp/ui.",
27
+ "usage": [
28
+ "DO: Mount exactly ONE `<Toaster richColors />` at the app root (e.g. inside your layout or AppShell children). Multiple mounts create duplicate toast stacks — there is no provider context, only DOM portals.",
29
+ "DO: Import `toast` from `\"sonner\"` directly (not from `@godxjp/ui`) to fire toasts anywhere: `toast.success(…)`, `toast.error(…)`, `toast.warning(…)`, `toast.info(…)`, `toast.loading(…)`, `toast.promise(…)`.",
30
+ "DON'T: Try to import a `toast` helper from `@godxjp/ui/feedback` — it does not exist. The component re-exports only the `Toaster` mount; the imperative API lives in the `sonner` package.",
31
+ "DO: Let the wrapper handle theming — it uses `useDocumentTheme()` to sync with the document `dark` class and `prefers-color-scheme` automatically. Never pass a hardcoded `theme` prop unless you are deliberately overriding.",
32
+ "DON'T: Use `Toaster` for persistent errors or blocking confirmations. Toasts auto-dismiss; they are not a substitute for `Alert` (inline persistent warnings) or `Dialog` (decisions requiring user input).",
33
+ "DO: Pass `position` to relocate the stack if a persistent sidebar/footer would obscure the default `bottom-right`. The wrapper already sets a safe `mobileOffset`; don't add redundant mobile offsets unless your layout differs."
34
+ ],
35
+ "useCases": [
36
+ "After a successful form save (invoice, journal entry, vendor record) — show `toast.success(\"保存しました\")` to confirm without blocking navigation.",
37
+ "After a background job is enqueued (e.g. bulk sync or export) — show `toast.info(\"エクスポートを開始しました\")` then later update with `toast.promise()` to track completion.",
38
+ "Mutation error fallback when the error is transient and retrying is the right UX — show `toast.error(message)` instead of replacing page content; reserve `AlertMutationFeedback` for inline, persistent error display inside a form.",
39
+ "Soft destructive action confirmation outcome — e.g. \"削除しました\" after an item is removed, paired with an undo action via `toast(\"…\", { action: { label: '元に戻す', onClick: undo } })`.",
40
+ "OAuth / session expiry warnings — surface a brief `toast.warning(\"セッションの有効期限が近づいています\")` without interrupting the user's current form state."
41
+ ]
42
+ }
@@ -0,0 +1,90 @@
1
+ {
2
+ "example": "import { Toggle } from \"@godxjp/ui/data-entry\";\n\n<Toggle aria-label=\"Bold\">B</Toggle>\n\n// A counted filter chip — one control, one accessible name (\"Unread, 12 items\").\n// Drive the state with the usual Radix pair: pressed / defaultPressed / onPressedChange.\n// variant=\"soft\" + shape=\"pill\" is the antd Tag.CheckableTag shape: it has a RESTING fill, so\n// the chip is legible before it is pressed.\n<Toggle\n variant=\"soft\"\n shape=\"pill\"\n onPressedChange={setUnreadOnly}\n count={12}\n countLabel={t(\"common.items\")}\n>\n {t(\"inbox.unread\")}\n</Toggle>",
3
+ "group": "data-entry",
4
+ "importPath": "@godxjp/ui/data-entry",
5
+ "name": "Toggle",
6
+ "props": [
7
+ {
8
+ "description": "Controlled pressed state.",
9
+ "name": "pressed",
10
+ "type": "boolean"
11
+ },
12
+ {
13
+ "description": "Pressed-state callback.",
14
+ "name": "onPressedChange",
15
+ "type": "(pressed: boolean) => void"
16
+ },
17
+ {
18
+ "defaultValue": "\"default\"",
19
+ "description": "Visual style. `default` is a 1px TRANSPARENT border (a toolbar affordance — no resting surface at all), `outline` a hairline on --background, and `soft` (gh#734) the REST FILL a chip needs: hsl(var(--secondary)), byte-identical to the fill `Badge variant=\"secondary\"` and `Button variant=\"secondary\"` already paint, so a chip, a badge and a button on one row are one family. `soft` is antd Tag's resting surface and is what makes a FILTER CHIP legible before it is pressed — with `default` or `outline` the chip reads as transparent, which is the reported defect. Hover is the opaque --secondary-hover (not a translucent tint, which would shift between a page and a Card); pressed still wins over both.",
20
+ "name": "variant",
21
+ "type": "\"default\" | \"outline\" | \"soft\""
22
+ },
23
+ {
24
+ "defaultValue": "\"md\"",
25
+ "description": "Control size on the --control-height tier: xs 24px · sm 28px · md 32px · lg 36px. Pick the step the ROW already has — an xs chip is what lets a 24px-dense row carry a pressed/segmented control instead of someone hand-rolling one out of Buttons (gh#716). Every step clears the WCAG 2.2 SC 2.5.8 24px target floor, xs exactly.",
26
+ "name": "size",
27
+ "type": "\"xs\" | \"sm\" | \"md\" | \"lg\""
28
+ },
29
+ {
30
+ "defaultValue": "\"default\"",
31
+ "description": "Corner shape, the SAME three values and the same two radius tokens `Button` and `Badge` use, so `shape=\"pill\"` means one thing across the library (gh#734). `pill` + `variant=\"soft\"` is the antd-Tag filter chip. On a ToggleGroup it propagates to every item through context, because a chip row is a row of pills, not one pill among squares.",
32
+ "name": "shape",
33
+ "type": "\"default\" | \"pill\" | \"sharp\""
34
+ },
35
+ {
36
+ "description": "Optional numeric count rendered as a borderless counter pill after the label — the SAME vocabulary Button defines (count/overflowCount/showZero), so a counted filter tab and a counted PRESSED chip read identically. This is what makes a faceted filter chip ('Open 42', selected or not) or a reaction chip ONE control: one button[aria-pressed], one tab stop, one focus ring, one accessible name. Formatted with Intl.NumberFormat in the active locale — never String(n), never a hand-rolled separator. NEVER nest a Badge inside a Toggle for this (a Badge is a status chip with its own surface: it double-borders the chip and puts two boxes where there is one control), and never hand-write a <span> counter inside a Toggle (that re-derives the Intl formatting, the cap and the pill's type ramp page-side).",
37
+ "name": "count",
38
+ "type": "number"
39
+ },
40
+ {
41
+ "defaultValue": "99",
42
+ "description": "Cap for `count` — above it the pill shows `{overflowCount}+` (e.g. 99+). The cap itself is locale-formatted too (vi: 1.000+). Same default and semantics as Button.",
43
+ "name": "overflowCount",
44
+ "type": "number"
45
+ },
46
+ {
47
+ "defaultValue": "true",
48
+ "description": "Render the pill when `count` is 0. Same default as Button. Set false to hide an empty facet's pill while keeping the chip.",
49
+ "name": "showZero",
50
+ "type": "boolean"
51
+ },
52
+ {
53
+ "description": "Localized description of what the count MEANS, folded into the accessible name so the control never announces as a bare number. The name always comes out as '<label>, <count> <unit>' — from contents for a text chip ('Unread, 12 items'), or folded into `aria-label` for an icon/emoji chip ('thumbs up, 3 reactions'). REQUIRED in practice whenever the visible label is an icon or emoji. Pass a t()-resolved string; the library does not own this wording.",
54
+ "name": "countLabel",
55
+ "type": "string"
56
+ }
57
+ ],
58
+ "related": [
59
+ "ToggleGroup",
60
+ "Button",
61
+ "Badge — the REMOVABLE chip (`onRemove` = antd Tag `closable`). Toggle is the SELECTABLE chip. Between them they cover antd's Tag family, which is why no `Tag` component exists here."
62
+ ],
63
+ "rules": [
64
+ 3,
65
+ 13,
66
+ 45
67
+ ],
68
+ "storyPath": "data-entry/Toggle.stories.tsx",
69
+ "tagline": "Radix Toggle wrapper with default/outline variants and tokenized sizes.",
70
+ "usage": [
71
+ "DO provide an accessible label when the toggle only contains an icon.",
72
+ "DON'T use Toggle for multi-option selection; use ToggleGroup.",
73
+ "DO reach for `count` for a faceted filter chip, a counted segmented toggle or a reaction chip — Toggle is the primitive that owns BOTH the count and the pressed state.",
74
+ "DON'T write `<Button count={n} aria-pressed>` for this: buttonVariants has no pressed branch, so the state paints nothing and the chip is indistinguishable from an unpressed one.",
75
+ "DON'T nest a Badge (or a hand-written span) inside a Toggle to show a count — use `count`.",
76
+ "DO pass `countLabel` whenever the label is an icon or an emoji, so the chip does not announce as a bare number.",
77
+ "DON'T wrap the chip in a live region to announce count changes — Toggle deliberately does not, because a count driven by other people is not this control's status.",
78
+ "DO build a FILTER CHIP (antd `Tag.CheckableTag`) as `<Toggle variant=\"soft\" shape=\"pill\" count countLabel>` — that IS the chip: a resting --secondary fill, a pill corner, a real pressed state and the counter pill, in one control with one tab stop. There is no separate `Tag` / `CheckableTag` export and there will not be one (gh#734).",
79
+ "DON'T substitute `<Button variant=\"secondary\" shape=\"pill\" aria-pressed>` for it: Button has no pressed branch, so the chip paints identically selected and unselected (WCAG 1.4.1).",
80
+ "DO reach for `Badge onRemove` instead when the chip is REMOVABLE rather than selectable — an applied-filter chip that draws its own × is antd `Tag closable`, and this library put that on Badge, not on Toggle."
81
+ ],
82
+ "useCases": [
83
+ "Bold/italic toolbar buttons",
84
+ "Pinned filter toggles",
85
+ "Compact view mode buttons",
86
+ "Faceted filter chips with facet sizes (Open 42 / Closed 118)",
87
+ "A tag-filter panel — `variant=\"soft\" shape=\"pill\"` chips carrying tag counts (antd Tag.CheckableTag)",
88
+ "Reaction chips (emoji + how many reacted + whether YOU did)"
89
+ ]
90
+ }
@@ -0,0 +1,102 @@
1
+ {
2
+ "example": "import { ToggleGroup, ToggleGroupItem } from \"@godxjp/ui/data-entry\";\n\n// size/variant are set ONCE on the group and reach every item.\n<ToggleGroup type=\"single\" size=\"lg\" variant=\"outline\" defaultValue=\"left\" aria-label=\"Alignment\">\n <ToggleGroupItem value=\"left\">Left</ToggleGroupItem>\n <ToggleGroupItem value=\"center\">Center</ToggleGroupItem>\n</ToggleGroup>",
3
+ "group": "data-entry",
4
+ "importPath": "@godxjp/ui/data-entry",
5
+ "name": "ToggleGroup",
6
+ "props": [
7
+ {
8
+ "description": "Selection mode.",
9
+ "name": "type",
10
+ "required": true,
11
+ "type": "\"single\" | \"multiple\""
12
+ },
13
+ {
14
+ "description": "Controlled selected value(s).",
15
+ "name": "value",
16
+ "type": "string | string[]"
17
+ },
18
+ {
19
+ "description": "Uncontrolled initial value(s).",
20
+ "name": "defaultValue",
21
+ "type": "string | string[]"
22
+ },
23
+ {
24
+ "description": "Selection callback.",
25
+ "name": "onValueChange",
26
+ "type": "(value: string | string[]) => void"
27
+ },
28
+ {
29
+ "defaultValue": "\"default\"",
30
+ "description": "Visual style, PROVIDED TO EVERY ITEM via context — set it once on the group, not on each ToggleGroupItem. An explicit `variant` on an item still wins. The default is applied per item by toggleVariants, so an unset group emits no `data-variant` at all. `soft` (gh#734) is the chip fill: use it for a tag-filter panel, where `default`/`outline` leave the unselected chips reading as transparent.",
31
+ "name": "variant",
32
+ "type": "\"default\" | \"outline\" | \"soft\""
33
+ },
34
+ {
35
+ "defaultValue": "\"default\"",
36
+ "description": "Corner shape, PROVIDED TO EVERY ITEM via context exactly as `variant`/`size` are — a chip row is a row of pills, so the decision belongs to the row (gh#734). An explicit `shape` on an item still wins. Same three values and same radius tokens as Button and Badge.",
37
+ "name": "shape",
38
+ "type": "\"default\" | \"pill\" | \"sharp\""
39
+ },
40
+ {
41
+ "defaultValue": "\"md\"",
42
+ "description": "Control size, PROVIDED TO EVERY ITEM via context — set it once on the group. An explicit `size` on an item still wins. Heights come from the --control-height tier (xs 24px · sm 28px · md 32px · lg 36px). Pick the step the ROW already has: xs is the one that fits a 24px-dense row (gh#716).",
43
+ "name": "size",
44
+ "type": "\"xs\" | \"sm\" | \"md\" | \"lg\""
45
+ },
46
+ {
47
+ "description": "Disables the whole group; individual items also accept `disabled`.",
48
+ "name": "disabled",
49
+ "type": "boolean"
50
+ },
51
+ {
52
+ "defaultValue": "false",
53
+ "description": "May the group end up with NOTHING selected? Omitted it may — pressing the selected item again clears it and reports `\"\"`, which is what a tag-filter row wants. THIS PROP DECIDES THE ARIA ROLE of a type=single group (gh#744), because emptiness is the one thing the two candidate roles disagree about: ARIA has no press-again-to-deselect for a radio, so a radiogroup the user just emptied is a state a screen reader cannot read out. Omitted → `role=\"group\"` + `aria-pressed` per item (no `aria-orientation`, which `group` does not take), arrow keys move FOCUS and Space/Enter presses. Set → `role=\"radiogroup\"` + `role=\"radio\"` / `aria-checked`, the selected item is the single tab stop, arrow keys move the SELECTION as APG requires, and pressing the selected item again keeps it. The name is React Aria's own (`useToggleGroupState`); neither antd nor Radix names the capability, and Radix's own single group emits radio roles while still allowing empty — the divergence is recorded in docs/DESIGN-AUTHORITY.md. On type=multiple it only keeps the last item selected; the roles do not move.",
54
+ "name": "disallowEmptySelection",
55
+ "type": "boolean"
56
+ },
57
+ {
58
+ "defaultValue": "false",
59
+ "description": "Let the row break onto further lines instead of running past its rail (gh#741) — the SAME name, boolean shape and `data-wrap` attribute `Flex` carries. OPT-IN, and the same default for every `variant`, both decided by measurement: a twelve-chip soft/pill xs row in a 320px rail is scrollWidth 644 > clientWidth 320 and 171.5px tall without it (chips squeezed to min-content, labels broken over up to seven lines) and scrollWidth 320 = clientWidth 320, 108px, 4 lines with it — while forcing wrap on all 18 groups of the docs page changed NONE of the 17 that already fit, at a 320px and a 1358px rail. So a default of true could only reflow rows that overflow today, silently, on an upgrade; and `variant=\"soft\"` gets no different default because forcing wrap changed nothing across default/outline/soft either — paint is not the axis that decides, content width against the rail is. The wrapped lines keep the group's single `gap`, so both axes measure the same 4px, and `flex-wrap` reorders nothing: arrow keys still walk the items in DOM order across lines, in LTR and in RTL.",
60
+ "name": "wrap",
61
+ "type": "boolean"
62
+ },
63
+ {
64
+ "description": "The counter-pill vocabulary is available PER ITEM, because variant/size are a group decision but the number is per-item data. Same vocabulary and same rendering as Toggle and Button: Intl.NumberFormat on the active locale, `{overflowCount}+` above the cap, and the count folded into that item's own accessible name. This is the faceted filter chip row (Open 42 / Closed 118) and the reaction row.",
65
+ "name": "ToggleGroupItem count / overflowCount / showZero / countLabel",
66
+ "type": "number | number | boolean | string"
67
+ }
68
+ ],
69
+ "related": [
70
+ "Segmented — the single-select sibling with a shared connected track. ToggleGroup is the generic multi/single toggle set; Segmented is the one-of-N control.",
71
+ "Toggle",
72
+ "RadioGroup"
73
+ ],
74
+ "rules": [
75
+ 3,
76
+ 13
77
+ ],
78
+ "storyPath": "data-entry/ToggleGroup.stories.tsx",
79
+ "subParts": [
80
+ "ToggleGroupItem"
81
+ ],
82
+ "tagline": "Single or multiple toggle selection on react-aria-components. On type=single the ARIA role follows `disallowEmptySelection`: omitted it is a `group` of `aria-pressed` buttons that MAY be all-off, set it is a `radiogroup` of `radio`s (gh#744).",
83
+ "usage": [
84
+ "DO choose type='single' for mutually exclusive toolbar modes.",
85
+ "DO add `disallowEmptySelection` to a type='single' group that is a SETTING — a view density, a sort order, a fiscal period. It is what makes the group a real radiogroup (role, aria-checked, one tab stop, arrow keys that move the selection) and it stops the second press from clearing the value, so you no longer need the `onValueChange={(v) => { if (v) setX(v) }}` guard that used to paper over it (gh#744).",
86
+ "DON'T add `disallowEmptySelection` to a FILTER row. Clearing a chip by pressing it again is what the user expects there, and without the prop the group says so honestly: `role=\"group\"` + `aria-pressed`, a set of buttons that may all be off.",
87
+ "DO choose type='multiple' for independent formatting toggles.",
88
+ "DO set `variant`/`size`/`shape` ONCE on the ToggleGroup — they propagate to every ToggleGroupItem through context. Repeating them on each item is redundant (it still works, and an explicit item prop overrides the group).",
89
+ "DO build a tag-filter panel as `<ToggleGroup type=\"multiple\" variant=\"soft\" shape=\"pill\" size=\"xs\">` with one counted `ToggleGroupItem` per tag — that is the whole antd `Tag.CheckableTag` row, one tab stop per chip, no `Tag` component needed (gh#734).",
90
+ "DO set `size`/`variant` on an individual ToggleGroupItem only when that ONE item must differ from the group.",
91
+ "DO add `wrap` to a TAG FILTER ROW — `<ToggleGroup type=\"multiple\" variant=\"soft\" shape=\"pill\" size=\"xs\" wrap>` is the whole folder-tag panel however many tags the folder has. This is what replaces the hand-built `<ul>` of individual `<Toggle>`s a row wider than its rail used to force (gh#741): that list loses exactly what the group owns — the shared `variant`/`size`/`shape` context, ONE `value`/`onValueChange`, and the group's arrow-key traversal, which keeps walking the chips in DOM order across the wrapped lines.",
92
+ "DON'T set `wrap` on a 3–4 item segmented group. Measured at a 320px rail it is one 32px line either way, so the prop buys nothing and only adds a way for a toolbar to reflow.",
93
+ "DON'T pass size='default' — it is not a member of the `xs | sm | md | lg` union. Omit `size` for the md default.",
94
+ "DO give the group an accessible name (`aria-label`) — it renders a `group` of toggle buttons (single, the default), a `radiogroup` (single + `disallowEmptySelection`) or a `toolbar` of toggle buttons (multiple), and all three need a name."
95
+ ],
96
+ "useCases": [
97
+ "Text alignment selector",
98
+ "Formatting toolbar",
99
+ "View density switcher",
100
+ "Tag filter row (wrap)"
101
+ ]
102
+ }
@@ -0,0 +1,120 @@
1
+ {
2
+ "docPath": "navigation/toolbar",
3
+ "example": "import { Toolbar, ToolbarGroup } from \"@godxjp/ui/navigation\";\nimport { SearchInput, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"@godxjp/ui/data-entry\";\n\n<Toolbar sticky hasActiveFilters={hasFilters} onClear={clearAll}>\n <SearchInput placeholder=\"氏名・メールで検索\" value={q} onSearch={setQ} />\n <ToolbarGroup label=\"ステータス\">\n <Select value={status} onValueChange={setStatus}>\n <SelectTrigger aria-label=\"ステータス\"><SelectValue /></SelectTrigger>\n <SelectContent>\n <SelectItem value=\"all\">すべて</SelectItem>\n <SelectItem value=\"active\">有効</SelectItem>\n </SelectContent>\n </Select>\n </ToolbarGroup>\n</Toolbar>",
4
+ "group": "navigation",
5
+ "importPath": "@godxjp/ui/navigation",
6
+ "name": "Toolbar",
7
+ "props": [
8
+ {
9
+ "description": "Filter controls. Place SearchInput directly; wrap each labelled Select/DatePicker in a ToolbarGroup.",
10
+ "name": "children",
11
+ "required": true,
12
+ "type": "ReactNode"
13
+ },
14
+ {
15
+ "description": "Clear-all handler. When provided AND hasActiveFilters is true, Toolbar renders the trailing 'Clear filters' button.",
16
+ "name": "onClear",
17
+ "type": "() => void"
18
+ },
19
+ {
20
+ "defaultValue": "true",
21
+ "description": "Whether any filter is applied — gates the clear-all button visibility.",
22
+ "name": "hasActiveFilters",
23
+ "type": "boolean"
24
+ },
25
+ {
26
+ "defaultValue": "false",
27
+ "description": "Pin the strip to the top of its scroll container while the list scrolls beneath it. Opt-in; tune offset/fill via the --filter-bar-sticky-offset / --filter-bar-sticky-background theme knobs.",
28
+ "name": "sticky",
29
+ "type": "boolean"
30
+ },
31
+ {
32
+ "defaultValue": "'wrap'",
33
+ "description": "Responsive overflow strategy. 'wrap' stacks the groups into one column below 640px, then wraps onto extra rows. 'scroll' keeps ONE bounded row at >=640px that scrolls inline (groups never shrink; the clear-all button stays sticky at the inline end) — use it when many filters with long JA/EN/VI labels would otherwise push the table below the fold. Below 640px 'scroll' still stacks, so a 390px viewport never hides a filter behind an invisible horizontal scroll.",
34
+ "name": "overflow",
35
+ "type": "'wrap' | 'scroll'"
36
+ },
37
+ {
38
+ "description": "Typed model — canonical search slot rendered FIRST in the strip with the token-owned consistent width (--filter-bar-search-width, full-width below 640px). Controlled triad value/defaultValue/onValueChange plus SearchInput's debounced onSearch. ANY model prop (search/filters/chips/onChipRemove/actions/resultCount/loading/disabled/error) switches the bar to the canonical model layout; with none of them the bar stays the plain children-composition toolbar unchanged.",
39
+ "name": "search",
40
+ "type": "FilterBarSearchProp"
41
+ },
42
+ {
43
+ "description": "Typed model — labelled domain-neutral Select filters rendered after the search slot in array order. Each: { value (stable identity), label (rendered as the control's REAL <label htmlFor> — WCAG 2.5.3), options, selected/defaultSelected/onSelectedChange, placeholder, disabled }. Width is token-owned (--filter-bar-filter-width).",
44
+ "name": "filters",
45
+ "type": "FilterBarFilterProp[]"
46
+ },
47
+ {
48
+ "description": "Typed model — applied-filter chips as pure consumer data ({ value, label, disabled }), rendered in a labelled row under the strip. Lifecycle: ADD = include the chip, REMOVE = onChipRemove(value) via each chip's × button, CLEAR-ALL = onClear. Keep label a string so the remove button's accessible name can quote it.",
49
+ "name": "chips",
50
+ "type": "FilterBarChipProp[]"
51
+ },
52
+ {
53
+ "description": "Remove ONE chip by its value. Required for chips to render their × remove button (each gets a localized 'Remove filter: {label}' accessible name).",
54
+ "name": "onChipRemove",
55
+ "type": "(value: string) => void"
56
+ },
57
+ {
58
+ "description": "Typed model — trailing action slot (e.g. the primary 'Add' Button) parked at the inline end of the strip, after the reset button in DOM/keyboard order.",
59
+ "name": "actions",
60
+ "type": "ReactNode"
61
+ },
62
+ {
63
+ "description": "Typed model — localized, CLDR-pluralized result count ('{count} results' / '{count} 件の結果' / '{count} kết quả') announced politely via a role='status' line under the strip. 0 renders '0 results' as the visible empty state.",
64
+ "name": "resultCount",
65
+ "type": "number"
66
+ },
67
+ {
68
+ "description": "Typed model — marks the strip aria-busy (and sets data-loading on the root) while results are being (re)fetched.",
69
+ "name": "loading",
70
+ "type": "boolean"
71
+ },
72
+ {
73
+ "description": "Typed model — disables every model-rendered control: search, filter Selects, the reset button and the chip remove buttons. Custom children stay consumer-owned.",
74
+ "name": "disabled",
75
+ "type": "boolean"
76
+ },
77
+ {
78
+ "description": "Typed model — consumer error content announced via role='alert' in the meta line, replacing the result count while present.",
79
+ "name": "error",
80
+ "type": "ReactNode"
81
+ },
82
+ {
83
+ "description": "Extra classes on the root element.",
84
+ "name": "className",
85
+ "type": "string"
86
+ }
87
+ ],
88
+ "related": [
89
+ "ToolbarGroup — the labelled wrapper for each individual filter control inside a Toolbar (SearchInput is placed directly, without a group).",
90
+ "DataTable.Toolbar — the in-table strip for column/density/bulk-action controls; Toolbar (this) is the page-level filter strip that sits ABOVE the table.",
91
+ "SearchInput — the debounced free-text control placed as the first child of Toolbar.",
92
+ "Badge — compose Badge + a sibling icon Button to render each active-filter chip; Badge itself is a non-interactive leaf."
93
+ ],
94
+ "rules": [
95
+ 23,
96
+ 44,
97
+ 45,
98
+ 46
99
+ ],
100
+ "storyPath": "navigation/toolbar.tsx",
101
+ "subParts": [
102
+ "ToolbarGroup"
103
+ ],
104
+ "tagline": "List-page filter strip (the framework FilterBar) — SearchInput + labelled ToolbarGroup filter slots + a clear-all affordance, optionally sticky.",
105
+ "usage": [
106
+ "DO prefer the typed model on a canonical list page — `search`/`filters`/`chips`/`onChipRemove`/`actions`/`resultCount` (+ `loading`/`disabled`/`error`) make the bar own layout, widths, chip lifecycle and keyboard order through --filter-bar-* tokens while ALL state stays consumer data. Children composition remains fully supported and unchanged when no model prop is passed.",
107
+ "DO place Toolbar ABOVE the table Card, never inside a `CardContent flush`. Put SearchInput as a direct child (it self-labels) and wrap every other control in a ToolbarGroup with a `label`.",
108
+ "DO drive the clear-all button with `hasActiveFilters` + `onClear` — it only renders when both are truthy. The strip collapses to a single stacked column below 640px automatically.",
109
+ "DO set `sticky` for long list pages so the filters stay reachable while scrolling; if a topbar sits above the list, raise `--filter-bar-sticky-offset` so the strip parks below it.",
110
+ "DO pass `controlId` on a ToolbarGroup that wraps ONE control and give that control the same `id` — the visible caption then becomes the control's real `<label htmlFor>` (WCAG 2.5.3 label-in-name). Without it the caption only names the group wrapper and the control itself is NAMELESS (axe: select-name / button-name), so you must fall back to an `aria-label` that repeats the caption.",
111
+ "DO switch to `overflow='scroll'` when a list page carries more filters than fit one row — one bounded row that scrolls inline keeps the table above the fold, where the default `wrap` would grow a 3-row strip with long JA/EN/VI labels. Never re-implement the geometry in the page: no page-local flex/grid/width rules on a filter strip.",
112
+ "DON'T build active-filter chips by nesting a Button inside a Badge (invalid markup + broken focus). Render each chip as a Badge label with a SIBLING icon Button (`aria-label` = 'clear <filter>'); a ghost `size='sm'` Button clears all.",
113
+ "DON'T hand-roll a debounced search box or a raw `<select>` — compose SearchInput and Select. Toolbar is layout + clear-all only; the controls own their own state and a11y."
114
+ ],
115
+ "useCases": [
116
+ "Master list screens (members, organizations, subscriptions, invoices) that need free-text search plus a few dropdown filters above a DataTable.",
117
+ "Sticky filter strip over a tall list where the filters must remain visible as the user scrolls the results.",
118
+ "Filtered views that surface applied conditions as removable chips (clear-one via each chip's × Button, clear-all via `onClear`)."
119
+ ]
120
+ }
@@ -0,0 +1,110 @@
1
+ {
2
+ "example": "import {\n Tooltip,\n TooltipTrigger,\n TooltipContent,\n} from \"@godxjp/ui/feedback\";\nimport { Button } from \"@godxjp/ui/general\";\nimport { Trash2Icon } from \"lucide-react\";\n\n// Icon-only button with tooltip label\nexport function DeleteAction() {\n return (\n <Tooltip>\n <TooltipTrigger asChild>\n <Button variant=\"ghost\" size=\"icon\" aria-label=\"Delete invoice\">\n <Trash2Icon className=\"size-4\" />\n </Button>\n </TooltipTrigger>\n <TooltipContent side=\"top\">Delete invoice</TooltipContent>\n </Tooltip>\n );\n}\n\n// Controlled tooltip (e.g. force-open for a tutorial)\nexport function ControlledExample() {\n const [open, setOpen] = React.useState(false);\n return (\n <Tooltip open={open} onOpenChange={setOpen}>\n <TooltipTrigger asChild>\n <Button variant=\"outline\" onMouseEnter={() => setOpen(true)}>\n Hover me\n </Button>\n </TooltipTrigger>\n <TooltipContent side=\"bottom\" sideOffset={8}>\n This is a controlled tooltip\n </TooltipContent>\n </Tooltip>\n );\n}",
3
+ "group": "feedback",
4
+ "importPath": "@godxjp/ui/feedback",
5
+ "name": "Tooltip",
6
+ "props": [
7
+ {
8
+ "defaultValue": "200",
9
+ "description": "Milliseconds of hover before the tooltip opens. Accepted on both Tooltip (per-instance) and TooltipProvider (subtree-wide override).",
10
+ "name": "delayDuration",
11
+ "type": "number"
12
+ },
13
+ {
14
+ "description": "Controlled open state. Pair with onOpenChange to drive the tooltip programmatically.",
15
+ "name": "open",
16
+ "type": "boolean"
17
+ },
18
+ {
19
+ "description": "Callback fired when the open state changes. Required when using controlled 'open'.",
20
+ "name": "onOpenChange",
21
+ "type": "(open: boolean) => void"
22
+ },
23
+ {
24
+ "defaultValue": "false",
25
+ "description": "Uncontrolled initial open state.",
26
+ "name": "defaultOpen",
27
+ "type": "boolean"
28
+ },
29
+ {
30
+ "defaultValue": "false",
31
+ "description": "When true, the tooltip closes as soon as the pointer leaves the trigger (content is not hoverable). Passed through to Radix Root.",
32
+ "name": "disableHoverableContent",
33
+ "type": "boolean"
34
+ },
35
+ {
36
+ "defaultValue": "\"top\"",
37
+ "description": "On TooltipContent — preferred side for the tooltip panel. Radix flips automatically when there is not enough space.",
38
+ "name": "side",
39
+ "type": "'top' | 'right' | 'bottom' | 'left'"
40
+ },
41
+ {
42
+ "defaultValue": "6",
43
+ "description": "On TooltipContent — pixel gap between the trigger edge and the tooltip panel.",
44
+ "name": "sideOffset",
45
+ "type": "number"
46
+ },
47
+ {
48
+ "defaultValue": "\"center\"",
49
+ "description": "On TooltipContent — alignment relative to the trigger along the cross-axis.",
50
+ "name": "align",
51
+ "type": "'start' | 'center' | 'end'"
52
+ },
53
+ {
54
+ "defaultValue": "0",
55
+ "description": "On TooltipContent — pixel offset applied to the align position.",
56
+ "name": "alignOffset",
57
+ "type": "number"
58
+ },
59
+ {
60
+ "description": "On TooltipContent — extra Tailwind classes merged with the built-in panel styles (z-50, max-w-xs, rounded-md, shadow-md, text-xs).",
61
+ "name": "className",
62
+ "type": "string"
63
+ },
64
+ {
65
+ "description": "On TooltipContent — the text or JSX rendered inside the floating panel.",
66
+ "name": "children",
67
+ "required": true,
68
+ "type": "React.ReactNode"
69
+ },
70
+ {
71
+ "defaultValue": "false",
72
+ "description": "On TooltipTrigger — merges props onto the immediate child instead of wrapping with a <button>. Use when the trigger is already an interactive element.",
73
+ "name": "asChild",
74
+ "type": "boolean"
75
+ }
76
+ ],
77
+ "related": [
78
+ "Popover — use Popover (also @godxjp/ui/feedback) when the floating panel needs interactive content (forms, links, action menus) rather than read-only text. Tooltip is read-only; Popover is interactive.",
79
+ "HoverCard — for rich preview cards (user profiles, link previews) that appear on hover with more complex layout. Tooltip is for short text hints only.",
80
+ "Badge / StatusChip — for persistent, always-visible short labels inline with text; not hover-triggered. Use Tooltip when the hint should be hidden until hovered."
81
+ ],
82
+ "rules": [
83
+ 3,
84
+ 6,
85
+ 23,
86
+ 39
87
+ ],
88
+ "storyPath": "feedback/Tooltip.stories.tsx",
89
+ "subParts": [
90
+ "TooltipContent",
91
+ "TooltipProvider",
92
+ "TooltipTrigger"
93
+ ],
94
+ "tagline": "Radix-based hover/focus tooltip — self-providing, no app-level TooltipProvider required; compose Tooltip > TooltipTrigger > TooltipContent every time.",
95
+ "usage": [
96
+ "DO compose the full three-part structure every time: <Tooltip> wraps <TooltipTrigger> (the element that triggers the tip) and <TooltipContent> (the floating panel). Omitting any part silently produces nothing.",
97
+ "DO NOT add an app-level <TooltipProvider> — every <Tooltip> self-provides its own Radix Provider. Only add <TooltipProvider> at a subtree root when you need a shared delayDuration different from the default 200ms across many tooltips.",
98
+ "DO use asChild on TooltipTrigger when the trigger is already a Button, IconButton, or other interactive element — this avoids nesting a <button> inside a <button>, which is invalid HTML and breaks keyboard focus.",
99
+ "DON'T put non-interactive elements (plain <div>, <span>) as the direct TooltipTrigger child without asChild — Radix needs a focusable element for keyboard accessibility. Wrap the target in a <span tabIndex={0}> or use a Button.",
100
+ "For controlled usage (e.g. programmatic show/hide or testing), pass open + onOpenChange to <Tooltip>. For typical hover/focus behaviour, leave both unset (uncontrolled).",
101
+ "TooltipContent renders inside a Radix Portal appended to document.body — z-index and overflow:hidden on ancestors do NOT clip it. Use className to extend max width beyond the built-in max-w-xs if long text is expected."
102
+ ],
103
+ "useCases": [
104
+ "Explaining an icon-only Button action (e.g. a trash icon, a copy-to-clipboard icon) in a DataTable action column — show the label on hover without cluttering the row.",
105
+ "Annotating a truncated cell value in a DataTable (e.g. a long account name clipped with text-ellipsis) — reveal the full text on hover without a modal.",
106
+ "Providing contextual help for a form field label or an info icon next to an accounting term (e.g. 'AR Balance' with a short definition).",
107
+ "Surfacing keyboard shortcut hints next to toolbar buttons (e.g. '⌘K — open search') without adding visible text to the UI.",
108
+ "NOT for a disabled control's reason. A disabled Button gets `pointer-events: none` (control.css) and leaves the tab order, so a tooltip on one can never open — measured in Chromium: the same trigger showed 1 tooltip on hover while enabled and 0 while disabled, and `.focus()` on it left activeElement as BODY, so keyboard and touch get nothing either. Render the reason as VISIBLE TEXT before the action instead — that is what ServiceLauncherCard's `disabledReason` does, and it puts the explanation ahead of the control in DOM order (WCAG 1.3.2). See docs/CONSUMER-RULES.md."
109
+ ]
110
+ }