@ratio-ui/core 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/README.md +2 -0
  2. package/dist/catalogue.json +2829 -72
  3. package/dist/components/alert/Alert.d.ts +18 -0
  4. package/dist/components/alert/Alert.js +67 -0
  5. package/dist/components/alert/index.d.ts +2 -0
  6. package/dist/components/alert/index.js +1 -0
  7. package/dist/components/alert-dialog/AlertDialog.d.ts +48 -0
  8. package/dist/components/alert-dialog/AlertDialog.js +76 -0
  9. package/dist/components/alert-dialog/index.d.ts +2 -0
  10. package/dist/components/alert-dialog/index.js +1 -0
  11. package/dist/components/app-shell/AppShell.d.ts +22 -0
  12. package/dist/components/app-shell/AppShell.js +39 -0
  13. package/dist/components/app-shell/context.js +5 -0
  14. package/dist/components/app-shell/index.d.ts +2 -0
  15. package/dist/components/app-shell/index.js +1 -0
  16. package/dist/components/avatar/Avatar.d.ts +22 -0
  17. package/dist/components/avatar/Avatar.js +38 -0
  18. package/dist/components/avatar/index.d.ts +2 -0
  19. package/dist/components/avatar/index.js +1 -0
  20. package/dist/components/button/Button.d.ts +37 -0
  21. package/dist/components/button/Button.js +41 -0
  22. package/dist/components/button/index.d.ts +2 -0
  23. package/dist/components/button/index.js +1 -0
  24. package/dist/components/calendar/Calendar.d.ts +20 -0
  25. package/dist/components/calendar/Calendar.js +119 -0
  26. package/dist/components/calendar/index.d.ts +2 -0
  27. package/dist/components/calendar/index.js +1 -0
  28. package/dist/components/card/Card.d.ts +33 -0
  29. package/dist/components/card/Card.js +53 -0
  30. package/dist/components/card/index.d.ts +2 -0
  31. package/dist/components/card/index.js +1 -0
  32. package/dist/components/checkbox/Checkbox.d.ts +10 -0
  33. package/dist/components/checkbox/Checkbox.js +28 -0
  34. package/dist/components/checkbox/index.d.ts +2 -0
  35. package/dist/components/checkbox/index.js +1 -0
  36. package/dist/components/checkbox-list/CheckboxList.d.ts +36 -0
  37. package/dist/components/checkbox-list/CheckboxList.js +93 -0
  38. package/dist/components/checkbox-list/index.d.ts +2 -0
  39. package/dist/components/checkbox-list/index.js +1 -0
  40. package/dist/components/collapsible/Collapsible.d.ts +24 -0
  41. package/dist/components/collapsible/Collapsible.js +58 -0
  42. package/dist/components/collapsible/index.d.ts +2 -0
  43. package/dist/components/collapsible/index.js +1 -0
  44. package/dist/components/data-table/DataTable.d.ts +65 -0
  45. package/dist/components/data-table/DataTable.js +141 -0
  46. package/dist/components/data-table/index.d.ts +2 -0
  47. package/dist/components/data-table/index.js +1 -0
  48. package/dist/components/date-picker/DatePicker.d.ts +33 -0
  49. package/dist/components/date-picker/DatePicker.js +104 -0
  50. package/dist/components/date-picker/index.d.ts +2 -0
  51. package/dist/components/date-picker/index.js +1 -0
  52. package/dist/components/date-range-picker/DateRangePicker.d.ts +39 -0
  53. package/dist/components/date-range-picker/DateRangePicker.js +142 -0
  54. package/dist/components/date-range-picker/index.d.ts +2 -0
  55. package/dist/components/date-range-picker/index.js +1 -0
  56. package/dist/components/definition/Definition.d.ts +14 -0
  57. package/dist/components/definition/Definition.js +29 -0
  58. package/dist/components/definition/index.d.ts +2 -0
  59. package/dist/components/definition/index.js +1 -0
  60. package/dist/components/dialog/Dialog.d.ts +44 -0
  61. package/dist/components/dialog/Dialog.js +79 -0
  62. package/dist/components/dialog/index.d.ts +2 -0
  63. package/dist/components/dialog/index.js +1 -0
  64. package/dist/components/dropdown-menu/DropdownMenu.d.ts +73 -0
  65. package/dist/components/dropdown-menu/DropdownMenu.js +116 -0
  66. package/dist/components/dropdown-menu/index.d.ts +2 -0
  67. package/dist/components/dropdown-menu/index.js +1 -0
  68. package/dist/components/empty-state/EmptyState.d.ts +15 -0
  69. package/dist/components/empty-state/EmptyState.js +30 -0
  70. package/dist/components/empty-state/index.d.ts +2 -0
  71. package/dist/components/empty-state/index.js +1 -0
  72. package/dist/components/field/Field.d.ts +35 -0
  73. package/dist/components/field/Field.js +67 -0
  74. package/dist/components/field/Label.d.ts +8 -0
  75. package/dist/components/field/Label.js +13 -0
  76. package/dist/components/field/index.d.ts +3 -0
  77. package/dist/components/field/index.js +3 -0
  78. package/dist/components/filter-tabs/FilterTabs.d.ts +28 -0
  79. package/dist/components/filter-tabs/FilterTabs.js +37 -0
  80. package/dist/components/filter-tabs/index.d.ts +2 -0
  81. package/dist/components/filter-tabs/index.js +1 -0
  82. package/dist/components/global-search/GlobalSearch.d.ts +51 -0
  83. package/dist/components/global-search/GlobalSearch.js +144 -0
  84. package/dist/components/global-search/index.d.ts +2 -0
  85. package/dist/components/global-search/index.js +1 -0
  86. package/dist/components/icon/registry.d.ts +3 -2
  87. package/dist/components/icon/registry.js +57 -1
  88. package/dist/components/input/Input.d.ts +19 -0
  89. package/dist/components/input/Input.js +26 -0
  90. package/dist/components/input/index.d.ts +2 -0
  91. package/dist/components/input/index.js +1 -0
  92. package/dist/components/input-group/InputGroup.d.ts +42 -0
  93. package/dist/components/input-group/InputGroup.js +83 -0
  94. package/dist/components/input-group/index.d.ts +2 -0
  95. package/dist/components/input-group/index.js +1 -0
  96. package/dist/components/kbd/Kbd.d.ts +17 -0
  97. package/dist/components/kbd/Kbd.js +21 -0
  98. package/dist/components/kbd/index.d.ts +2 -0
  99. package/dist/components/kbd/index.js +1 -0
  100. package/dist/components/link/Link.d.ts +22 -0
  101. package/dist/components/link/Link.js +31 -0
  102. package/dist/components/link/index.d.ts +2 -0
  103. package/dist/components/link/index.js +1 -0
  104. package/dist/components/link-provider/LinkProvider.d.ts +29 -0
  105. package/dist/components/link-provider/LinkProvider.js +15 -0
  106. package/dist/components/link-provider/RatioLink.js +18 -0
  107. package/dist/components/link-provider/context.js +4 -0
  108. package/dist/components/link-provider/index.d.ts +2 -0
  109. package/dist/components/link-provider/index.js +1 -0
  110. package/dist/components/media-grid/MediaGrid.d.ts +49 -0
  111. package/dist/components/media-grid/MediaGrid.js +303 -0
  112. package/dist/components/media-grid/index.d.ts +2 -0
  113. package/dist/components/media-grid/index.js +1 -0
  114. package/dist/components/metric-strip/MetricStrip.d.ts +23 -0
  115. package/dist/components/metric-strip/MetricStrip.js +39 -0
  116. package/dist/components/metric-strip/index.d.ts +2 -0
  117. package/dist/components/metric-strip/index.js +1 -0
  118. package/dist/components/money-input/MoneyInput.d.ts +30 -0
  119. package/dist/components/money-input/MoneyInput.js +72 -0
  120. package/dist/components/money-input/index.d.ts +2 -0
  121. package/dist/components/money-input/index.js +1 -0
  122. package/dist/components/page-header/PageHeader.d.ts +37 -0
  123. package/dist/components/page-header/PageHeader.js +62 -0
  124. package/dist/components/page-header/index.d.ts +2 -0
  125. package/dist/components/page-header/index.js +1 -0
  126. package/dist/components/pagination/Pagination.d.ts +18 -0
  127. package/dist/components/pagination/Pagination.js +59 -0
  128. package/dist/components/pagination/index.d.ts +2 -0
  129. package/dist/components/pagination/index.js +1 -0
  130. package/dist/components/popover/Popover.d.ts +27 -0
  131. package/dist/components/popover/Popover.js +47 -0
  132. package/dist/components/popover/index.d.ts +2 -0
  133. package/dist/components/popover/index.js +1 -0
  134. package/dist/components/save-bar/SaveBar.d.ts +27 -0
  135. package/dist/components/save-bar/SaveBar.js +37 -0
  136. package/dist/components/save-bar/index.d.ts +2 -0
  137. package/dist/components/save-bar/index.js +1 -0
  138. package/dist/components/search-field/SearchField.d.ts +23 -0
  139. package/dist/components/search-field/SearchField.js +66 -0
  140. package/dist/components/search-field/index.d.ts +2 -0
  141. package/dist/components/search-field/index.js +1 -0
  142. package/dist/components/select/Select.d.ts +40 -0
  143. package/dist/components/select/Select.js +84 -0
  144. package/dist/components/select/index.d.ts +2 -0
  145. package/dist/components/select/index.js +1 -0
  146. package/dist/components/side-nav/SideNav.d.ts +64 -0
  147. package/dist/components/side-nav/SideNav.js +194 -0
  148. package/dist/components/side-nav/index.d.ts +2 -0
  149. package/dist/components/side-nav/index.js +1 -0
  150. package/dist/components/skeleton/Skeleton.d.ts +13 -0
  151. package/dist/components/skeleton/Skeleton.js +21 -0
  152. package/dist/components/skeleton/index.d.ts +2 -0
  153. package/dist/components/skeleton/index.js +1 -0
  154. package/dist/components/spinner/Spinner.d.ts +11 -0
  155. package/dist/components/spinner/Spinner.js +16 -0
  156. package/dist/components/spinner/index.d.ts +2 -0
  157. package/dist/components/spinner/index.js +1 -0
  158. package/dist/components/status-pill/StatusPill.d.ts +14 -0
  159. package/dist/components/status-pill/StatusPill.js +19 -0
  160. package/dist/components/status-pill/index.d.ts +2 -0
  161. package/dist/components/status-pill/index.js +1 -0
  162. package/dist/components/summary-chips/SummaryChips.d.ts +17 -0
  163. package/dist/components/summary-chips/SummaryChips.js +19 -0
  164. package/dist/components/summary-chips/index.d.ts +2 -0
  165. package/dist/components/summary-chips/index.js +1 -0
  166. package/dist/components/switch/Switch.d.ts +10 -0
  167. package/dist/components/switch/Switch.js +20 -0
  168. package/dist/components/switch/index.d.ts +2 -0
  169. package/dist/components/switch/index.js +1 -0
  170. package/dist/components/table/Table.d.ts +46 -0
  171. package/dist/components/table/Table.js +87 -0
  172. package/dist/components/table/index.d.ts +2 -0
  173. package/dist/components/table/index.js +1 -0
  174. package/dist/components/table-loader/TableLoader.d.ts +31 -0
  175. package/dist/components/table-loader/TableLoader.js +35 -0
  176. package/dist/components/table-loader/index.d.ts +2 -0
  177. package/dist/components/table-loader/index.js +1 -0
  178. package/dist/components/tabs/Tabs.d.ts +23 -0
  179. package/dist/components/tabs/Tabs.js +41 -0
  180. package/dist/components/tabs/index.d.ts +2 -0
  181. package/dist/components/tabs/index.js +1 -0
  182. package/dist/components/tag-input/TagInput.d.ts +44 -0
  183. package/dist/components/tag-input/TagInput.js +102 -0
  184. package/dist/components/tag-input/index.d.ts +2 -0
  185. package/dist/components/tag-input/index.js +1 -0
  186. package/dist/components/textarea/Textarea.d.ts +15 -0
  187. package/dist/components/textarea/Textarea.js +21 -0
  188. package/dist/components/textarea/index.d.ts +2 -0
  189. package/dist/components/textarea/index.js +1 -0
  190. package/dist/components/toaster/Toaster.d.ts +32 -0
  191. package/dist/components/toaster/Toaster.js +89 -0
  192. package/dist/components/toaster/index.d.ts +2 -0
  193. package/dist/components/toaster/index.js +1 -0
  194. package/dist/components/tooltip/Tooltip.d.ts +18 -0
  195. package/dist/components/tooltip/Tooltip.js +35 -0
  196. package/dist/components/tooltip/index.d.ts +2 -0
  197. package/dist/components/tooltip/index.js +1 -0
  198. package/dist/components/top-bar/StoreSwitcher.d.ts +26 -0
  199. package/dist/components/top-bar/StoreSwitcher.js +48 -0
  200. package/dist/components/top-bar/TopBar.d.ts +24 -0
  201. package/dist/components/top-bar/TopBar.js +53 -0
  202. package/dist/components/top-bar/index.d.ts +3 -0
  203. package/dist/components/top-bar/index.js +3 -0
  204. package/dist/components/unit-input/UnitInput.d.ts +33 -0
  205. package/dist/components/unit-input/UnitInput.js +47 -0
  206. package/dist/components/unit-input/index.d.ts +2 -0
  207. package/dist/components/unit-input/index.js +1 -0
  208. package/dist/index.d.ts +102 -1
  209. package/dist/index.js +54 -2
  210. package/dist/lib/cn.js +38 -27
  211. package/dist/lib/format.d.ts +92 -0
  212. package/dist/lib/format.js +222 -0
  213. package/dist/styles.css +1 -1
  214. package/package.json +205 -3
@@ -1,132 +1,2642 @@
1
1
  {
2
- "version": "0.1.1",
2
+ "version": "0.3.0",
3
3
  "components": [
4
+ {
5
+ "name": "Alert",
6
+ "import": "import { Alert } from '@ratio-ui/core'",
7
+ "description": "A message that stays in the page: a tone glyph, an optional title and description, and an optional action.",
8
+ "props": [
9
+ {
10
+ "name": "tone",
11
+ "type": "\"critical\" | \"info\" | \"success\" | \"warning\"",
12
+ "required": false,
13
+ "default": "info",
14
+ "description": "The tone. Defaults to info."
15
+ },
16
+ {
17
+ "name": "title",
18
+ "type": "ReactNode",
19
+ "required": false,
20
+ "description": "An optional bold first line."
21
+ },
22
+ {
23
+ "name": "description",
24
+ "type": "ReactNode",
25
+ "required": false,
26
+ "description": "An optional second line."
27
+ },
28
+ {
29
+ "name": "action",
30
+ "type": "ReactNode",
31
+ "required": false,
32
+ "description": "An optional action on the right, usually one Button."
33
+ },
34
+ {
35
+ "name": "ref",
36
+ "type": "Ref<HTMLDivElement>",
37
+ "required": false,
38
+ "description": "Receives the rendered div."
39
+ }
40
+ ],
41
+ "usage": {
42
+ "whenToUse": "- A message that sits in the page and stays until the cause is gone: a banner above a form, a notice in a card\n- Four tones: info, success, warning, critical (question 1 tokens: no fill, strong border, warning glyph)",
43
+ "whenNotToUse": "- A message that disappears by itself: use Toaster\n- A confirmation before a destructive step: use AlertDialog\n- A field error: say it in one clause under the field",
44
+ "example": "```tsx\n<Alert\n tone=\"critical\"\n title=\"Payment failed\"\n description=\"Try another method.\"\n action={<Button>Retry</Button>}\n/>\n```\n\nInfo and success have role status (polite); warning and critical have role alert (assertive)."
45
+ }
46
+ },
47
+ {
48
+ "name": "AlertDialog",
49
+ "import": "import { AlertDialog } from '@ratio-ui/core'",
50
+ "description": "The root of a confirmation. It holds the open state and renders nothing itself. Pass `open` and `onOpenChange` to control it, or `defaultOpen` to leave it uncontrolled. Escape closes it; a click outside does not.",
51
+ "props": [],
52
+ "usage": {
53
+ "whenToUse": "- A confirm step before deleting something, or before a money action such as a refund\n- An answer that must be given: Escape closes it, a click outside does not\n- A short question that fits in 480px: the panel is fixed at that width, with a ring outline and no scrim",
54
+ "whenNotToUse": "- A form or any input: use Dialog\n- A message after an action: use toast()\n- A reversible action: do it and offer undo instead",
55
+ "example": "```tsx\n<AlertDialog>\n <AlertDialogTrigger>Delete product</AlertDialogTrigger>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Delete this product?</AlertDialogTitle>\n <AlertDialogDescription>It leaves the storefront at once.</AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>Cancel</AlertDialogCancel>\n <AlertDialogAction>Delete</AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n</AlertDialog>\n```\n\n- `AlertDialogAction` is the Button's destructive variant and closes the dialog after `onClick` runs. Pass `variant=\"main\"` for a non-destructive confirmation such as a refund. For an async action that must stay open until it finishes, control the dialog with `open` and `onOpenChange`.\n- `AlertDialogCancel` is the secondary Button and closes the dialog.\n- Name the action with the verb (\"Delete\", \"Refund\"), never \"OK\". Put the amount in the title for money actions, formatted with the format helpers."
56
+ },
57
+ "parts": [
58
+ {
59
+ "name": "AlertDialogAction",
60
+ "import": "import { AlertDialogAction } from '@ratio-ui/core'",
61
+ "description": "The confirming button. It is the Button's destructive variant and closes the dialog after its onClick runs.",
62
+ "props": [
63
+ {
64
+ "name": "variant",
65
+ "type": "\"destructive\" | \"main\"",
66
+ "required": false,
67
+ "default": "destructive",
68
+ "description": "The Button look. Defaults to destructive; use main for a confirmation that deletes nothing."
69
+ }
70
+ ]
71
+ },
72
+ {
73
+ "name": "AlertDialogCancel",
74
+ "import": "import { AlertDialogCancel } from '@ratio-ui/core'",
75
+ "description": "The secondary button that closes the dialog without doing anything.",
76
+ "props": []
77
+ },
78
+ {
79
+ "name": "AlertDialogContent",
80
+ "import": "import { AlertDialogContent } from '@ratio-ui/core'",
81
+ "description": "The panel: 480px wide, white, drawn with the ring outline and no shadow, with no scrim behind it. Put AlertDialogHeader and AlertDialogFooter inside.",
82
+ "props": [
83
+ {
84
+ "name": "render",
85
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, DialogPopupState>",
86
+ "required": false,
87
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
88
+ }
89
+ ]
90
+ },
91
+ {
92
+ "name": "AlertDialogDescription",
93
+ "import": "import { AlertDialogDescription } from '@ratio-ui/core'",
94
+ "description": "What will happen, in subdued body text.",
95
+ "props": [
96
+ {
97
+ "name": "render",
98
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, DialogDescriptionState>",
99
+ "required": false,
100
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
101
+ }
102
+ ]
103
+ },
104
+ {
105
+ "name": "AlertDialogFooter",
106
+ "import": "import { AlertDialogFooter } from '@ratio-ui/core'",
107
+ "description": "The row of actions at the bottom, aligned to the end.",
108
+ "props": []
109
+ },
110
+ {
111
+ "name": "AlertDialogHeader",
112
+ "import": "import { AlertDialogHeader } from '@ratio-ui/core'",
113
+ "description": "The title and description, stacked.",
114
+ "props": []
115
+ },
116
+ {
117
+ "name": "AlertDialogTitle",
118
+ "import": "import { AlertDialogTitle } from '@ratio-ui/core'",
119
+ "description": "The question, in the b4 style. It names the dialog for assistive technology.",
120
+ "props": [
121
+ {
122
+ "name": "render",
123
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, DialogTitleState>",
124
+ "required": false,
125
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
126
+ }
127
+ ]
128
+ },
129
+ {
130
+ "name": "AlertDialogTrigger",
131
+ "import": "import { AlertDialogTrigger } from '@ratio-ui/core'",
132
+ "description": "The button that opens the confirmation.",
133
+ "props": [
134
+ {
135
+ "name": "render",
136
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, DialogTriggerState>",
137
+ "required": false,
138
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
139
+ }
140
+ ]
141
+ }
142
+ ]
143
+ },
144
+ {
145
+ "name": "AppShell",
146
+ "import": "import { AppShell } from '@ratio-ui/core'",
147
+ "description": "The frame of every page: the side nav at the left, the top bar above and the content below it. It fills the height of its container, so the app gives it a height and the content scrolls beside the side nav and under the top bar. The parts are slots, so the shell does not depend on their components. On a phone the side nav is a drawer: the menu button in TopBar opens it, and Escape or choosing a row closes it.",
148
+ "props": [
149
+ {
150
+ "name": "sideNav",
151
+ "type": "ReactNode",
152
+ "required": false,
153
+ "description": "The side navigation, for example SideNav. It sits at the left of the top bar and the content."
154
+ },
155
+ {
156
+ "name": "topBar",
157
+ "type": "ReactNode",
158
+ "required": false,
159
+ "description": "The top bar, for example TopBar. It spans the width beside the side nav."
160
+ },
161
+ {
162
+ "name": "navOpen",
163
+ "type": "boolean",
164
+ "required": false,
165
+ "description": "Whether the side navigation drawer is open on a phone. Set it with onNavOpenChange to control the drawer."
166
+ },
167
+ {
168
+ "name": "defaultNavOpen",
169
+ "type": "boolean",
170
+ "required": false,
171
+ "default": "false",
172
+ "description": "Whether the drawer starts open on a phone when navOpen is not set. Defaults to false."
173
+ },
174
+ {
175
+ "name": "onNavOpenChange",
176
+ "type": "((open: boolean) => void)",
177
+ "required": false,
178
+ "description": "Called when the drawer asks to open or close on a phone: the menu button in TopBar, Escape, or choosing a row."
179
+ },
180
+ {
181
+ "name": "children",
182
+ "type": "ReactNode",
183
+ "required": false,
184
+ "description": "The page content, centred in a column that is 1152px wide at most."
185
+ }
186
+ ],
187
+ "usage": {
188
+ "whenToUse": "- The frame of an admin page: side nav, top bar and content\n- Content that should be at most 1152px wide and centred",
189
+ "whenNotToUse": "- A page without the admin chrome, such as sign in\n- Spacing inside the content: use Stack",
190
+ "example": "<AppShell sideNav={<SideNav />} topBar={<TopBar />}>\n <PageHeader title=\"Products\" />\n</AppShell>"
191
+ }
192
+ },
193
+ {
194
+ "name": "Avatar",
195
+ "import": "import { Avatar } from '@ratio-ui/core'",
196
+ "description": "A picture or the initials of a person or a store: round for people, square for stores.",
197
+ "props": [
198
+ {
199
+ "name": "name",
200
+ "type": "string",
201
+ "required": true,
202
+ "description": "The full name of the person or store. It is the accessible label, and its first letters are the initials."
203
+ },
204
+ {
205
+ "name": "src",
206
+ "type": "string",
207
+ "required": false,
208
+ "description": "The address of a picture. The initials show until it loads and when it fails."
209
+ },
210
+ {
211
+ "name": "shape",
212
+ "type": "\"round\" | \"square\"",
213
+ "required": false,
214
+ "default": "round",
215
+ "description": "Round for a person, square for a store. Defaults to round."
216
+ },
217
+ {
218
+ "name": "size",
219
+ "type": "\"default\" | \"lg\" | \"sm\"",
220
+ "required": false,
221
+ "default": "default",
222
+ "description": "Defaults to default, which is 32px."
223
+ },
224
+ {
225
+ "name": "ref",
226
+ "type": "Ref<HTMLSpanElement>",
227
+ "required": false,
228
+ "description": "Receives the rendered element."
229
+ }
230
+ ],
231
+ "usage": {
232
+ "whenToUse": "- The identity of a person (round) or a store (square), in the top bar, account menu or a list\n- A picture with the initials as fallback while it loads or when it fails",
233
+ "whenNotToUse": "- A generic icon or logo that is not an identity: use Icon\n- A status marker: use StatusPill",
234
+ "example": "<Avatar name=\"My Store\" shape=\"square\" />\n<Avatar name=\"Kavya Singh\" />"
235
+ }
236
+ },
237
+ {
238
+ "name": "Button",
239
+ "import": "import { Button } from '@ratio-ui/core'",
240
+ "description": "The design system's 28px button in five looks: main, secondary, ghost, link and destructive. Hover and pressed change the fill only, disabled is 50% opacity and the colours fade in 120ms. Put an Icon beside the label, or use iconOnly with an aria-label. It has no size prop: 28px is the only height.",
241
+ "props": [
242
+ {
243
+ "name": "iconOnly",
244
+ "type": "boolean",
245
+ "required": false,
246
+ "default": "false",
247
+ "description": "Draws a 28px square for one icon. Needs aria-label, because there is no text to name the button."
248
+ },
249
+ {
250
+ "name": "children",
251
+ "type": "ReactNode",
252
+ "required": false,
253
+ "description": "The label, optionally with an Icon beside it. One Icon."
254
+ },
255
+ {
256
+ "name": "variant",
257
+ "type": "\"destructive\" | \"ghost\" | \"link\" | \"main\" | \"secondary\"",
258
+ "required": false,
259
+ "default": "main",
260
+ "description": "The look of the button. Defaults to main."
261
+ },
262
+ {
263
+ "name": "ref",
264
+ "type": "Ref<HTMLButtonElement>",
265
+ "required": false,
266
+ "description": "Gives the button back to the caller's ref."
267
+ },
268
+ {
269
+ "name": "aria-label",
270
+ "type": "string",
271
+ "required": true,
272
+ "description": "The accessible name of an icon-only button."
273
+ }
274
+ ],
275
+ "usage": {
276
+ "whenToUse": "- The main action of a view, once per page header or table toolbar: variant main\n- A second action beside it, such as Export: variant secondary\n- Quiet actions like Cancel: ghost. Navigation inside running text or a card: link\n- A delete or remove action: destructive, followed by a confirm step\n- A single icon action, such as add or close: iconOnly with an aria-label",
277
+ "whenNotToUse": "- A choice among options that filters a list: use FilterTabs\n- Going to another page from a menu: use the menu's own items\n- A different height: Button is 28px only, with no size prop\n- Styling: Button takes no `className` and no `style`",
278
+ "example": "<Stack gap={8}>\n <Button><Icon name=\"plus\" />Add customer</Button>\n <Button variant=\"secondary\">Export</Button>\n <Button iconOnly variant=\"ghost\" aria-label=\"Add\"><Icon name=\"plus\" /></Button>\n</Stack>"
279
+ }
280
+ },
281
+ {
282
+ "name": "Calendar",
283
+ "import": "import { Calendar } from '@ratio-ui/core'",
284
+ "description": "A month grid to pick a day or a range of days, drawn by the design system: 28px days with a 6px radius, today in the accent ink, the selected day filled with the accent, a range as accent ends on a tinted band, disabled days at 50% and the days of the months around it in the muted ink. Every day is named in the format module's date order (28/10/2026). Arrow keys move between days and the previous and next buttons between months. It has no fill of its own, so it sits in a popover or a container as drawn.",
285
+ "props": [],
286
+ "usage": {
287
+ "whenToUse": "- Picking one day, such as a \"Best before\" date, inside a popover or a form: `mode=\"single\"`\n- Picking a range of days, such as an analytics period: `mode=\"range\"`, with `numberOfMonths={2}` for a wide range\n- Limiting the days that can be picked: `disabled` (for example `{ before: today }`), `startMonth` and `endMonth`",
288
+ "whenNotToUse": "- A date field people type into: use DatePicker or DateRangePicker, which open this calendar in a popover\n- Showing a date as text: write it with the format module, never with a calendar\n- Styling: Calendar takes no `className`, `classNames`, `style`, `components` or `formatters`; dates are written in the format module's order (28/10/2026)",
289
+ "example": "const [day, setDay] = useState<Date | undefined>()\n\n<Calendar mode=\"single\" selected={day} onSelect={setDay} disabled={{ before: new Date() }} />"
290
+ }
291
+ },
292
+ {
293
+ "name": "Card",
294
+ "import": "import { Card } from '@ratio-ui/core'",
295
+ "description": "A container for a panel of content: a 1px hairline border on white, no shadow and 24px of padding. The design system has no cards, so this is only the hairline container.",
296
+ "props": [],
297
+ "usage": {
298
+ "whenToUse": "- A panel of related content that needs a boundary, such as a settings group or a metric with its notes\n- With a title in B4 and, optionally, a description, content and a footer of actions",
299
+ "whenNotToUse": "- Page content that needs no boundary: it sits directly on white\n- A menu or popup: those have no shadow either, but they are not cards",
300
+ "example": "<Card>\n <CardHeader>\n <CardTitle>Payout account</CardTitle>\n <CardDescription>Where settlements are paid</CardDescription>\n </CardHeader>\n <CardContent>HDFC ending 4021</CardContent>\n</Card>"
301
+ },
302
+ "parts": [
303
+ {
304
+ "name": "CardHeader",
305
+ "import": "import { CardHeader } from '@ratio-ui/core'",
306
+ "description": "The top of a card: its title and description, stacked.",
307
+ "props": []
308
+ },
309
+ {
310
+ "name": "CardTitle",
311
+ "import": "import { CardTitle } from '@ratio-ui/core'",
312
+ "description": "The title of a card, set in the B4 text style.",
313
+ "props": [
314
+ {
315
+ "name": "as",
316
+ "type": "\"div\" | \"h2\" | \"h3\" | \"h4\"",
317
+ "required": false,
318
+ "default": "h3",
319
+ "description": "The heading element. Defaults to h3."
320
+ }
321
+ ]
322
+ },
323
+ {
324
+ "name": "CardDescription",
325
+ "import": "import { CardDescription } from '@ratio-ui/core'",
326
+ "description": "A line under the title that says what the card holds.",
327
+ "props": []
328
+ },
329
+ {
330
+ "name": "CardContent",
331
+ "import": "import { CardContent } from '@ratio-ui/core'",
332
+ "description": "The body of a card.",
333
+ "props": []
334
+ },
335
+ {
336
+ "name": "CardFooter",
337
+ "import": "import { CardFooter } from '@ratio-ui/core'",
338
+ "description": "The foot of a card, a row for its actions.",
339
+ "props": []
340
+ }
341
+ ]
342
+ },
343
+ {
344
+ "name": "Checkbox",
345
+ "import": "import { Checkbox } from '@ratio-ui/core'",
346
+ "description": "A 14px box with a 4px radius, drawn with a hairline ring and filled in the primary colour when checked. Set indeterminate for a select-all that stands for some rows. An invisible 24px area around the box takes the tap.",
347
+ "props": [],
348
+ "usage": {
349
+ "whenToUse": "- A row selector in a table or list, one box per row\n- A select-all in a table header: set `indeterminate` while only some rows are selected\n- A yes or no option in a form, or a list of channels where several can be on",
350
+ "whenNotToUse": "- A setting that applies at once: use a switch\n- One choice among several: use radio buttons or FilterTabs\n- Styling: Checkbox takes no `className` and no `style`",
351
+ "example": "<Checkbox aria-label=\"Select row\" checked={selected} onCheckedChange={setSelected} />"
352
+ }
353
+ },
354
+ {
355
+ "name": "CheckboxList",
356
+ "import": "import { CheckboxList } from '@ratio-ui/core'",
357
+ "description": "A titled list of checkboxes with the count of chosen items in the heading (\"4 of 6\"), an optional description under each label and an optional select-all.",
358
+ "props": [
359
+ {
360
+ "name": "title",
361
+ "type": "string",
362
+ "required": true,
363
+ "description": "The heading. The count of chosen items sits beside it."
364
+ },
365
+ {
366
+ "name": "items",
367
+ "type": "CheckboxListItem[]",
368
+ "required": true,
369
+ "description": "The options."
370
+ },
371
+ {
372
+ "name": "value",
373
+ "type": "string[]",
374
+ "required": false,
375
+ "description": "The chosen values, when controlled."
376
+ },
377
+ {
378
+ "name": "defaultValue",
379
+ "type": "string[]",
380
+ "required": false,
381
+ "description": "The chosen values to start with, when not controlled."
382
+ },
383
+ {
384
+ "name": "onValueChange",
385
+ "type": "((value: string[]) => void)",
386
+ "required": false,
387
+ "description": "Called with the new list of chosen values."
388
+ },
389
+ {
390
+ "name": "selectAll",
391
+ "type": "boolean",
392
+ "required": false,
393
+ "description": "Adds a \"Select all\" box that is mixed while only some are chosen."
394
+ },
395
+ {
396
+ "name": "selectAllLabel",
397
+ "type": "string",
398
+ "required": false,
399
+ "default": "Select all",
400
+ "description": "The label of the select-all box. Defaults to \"Select all\"."
401
+ },
402
+ {
403
+ "name": "countLabel",
404
+ "type": "((chosen: string, total: string) => string)",
405
+ "required": false,
406
+ "default": "(c, t) => `${c} of ${t}`",
407
+ "description": "Writes the count in the heading from the chosen and total counts, already formatted. Defaults to \"4 of 6\"."
408
+ }
409
+ ],
410
+ "usage": {
411
+ "whenToUse": "- A titled list where several options can be on and the heading should say how many (\"Sales channels 4 of 6\")\n- Add `selectAll` for a long list; it shows a mixed box while only some are on",
412
+ "whenNotToUse": "- One choice among several: use radio buttons or FilterTabs\n- A single yes or no: use Checkbox\n- Styling: CheckboxList takes no `className` and no `style`",
413
+ "example": "<CheckboxList title=\"Sales channels\" items={channels} value={on} onValueChange={setOn} selectAll />"
414
+ }
415
+ },
416
+ {
417
+ "name": "Collapsible",
418
+ "import": "import { Collapsible } from '@ratio-ui/core'",
419
+ "description": "A section that opens and closes on its heading and, while closed, still shows its values as chips. The panel appears and disappears with no height animation.",
420
+ "props": [
421
+ {
422
+ "name": "title",
423
+ "type": "string",
424
+ "required": true,
425
+ "description": "The section heading, shown on the trigger."
426
+ },
427
+ {
428
+ "name": "summary",
429
+ "type": "readonly SummaryChip[]",
430
+ "required": false,
431
+ "description": "The values to show as chips while the section is closed."
432
+ },
433
+ {
434
+ "name": "open",
435
+ "type": "boolean",
436
+ "required": false,
437
+ "description": "Whether the section is open, when controlled."
438
+ },
439
+ {
440
+ "name": "defaultOpen",
441
+ "type": "boolean",
442
+ "required": false,
443
+ "default": "false",
444
+ "description": "Whether the section starts open, when uncontrolled. Defaults to closed."
445
+ },
446
+ {
447
+ "name": "onOpenChange",
448
+ "type": "((open: boolean) => void)",
449
+ "required": false,
450
+ "description": "Called when the section opens or closes."
451
+ },
452
+ {
453
+ "name": "disabled",
454
+ "type": "boolean",
455
+ "required": false,
456
+ "description": "Stops the section from opening or closing."
457
+ },
458
+ {
459
+ "name": "children",
460
+ "type": "ReactNode",
461
+ "required": true,
462
+ "description": "The section's content, shown when open."
463
+ },
464
+ {
465
+ "name": "ref",
466
+ "type": "Ref<HTMLDivElement>",
467
+ "required": false,
468
+ "description": "Receives the rendered root."
469
+ }
470
+ ],
471
+ "usage": {
472
+ "whenToUse": "- A form section that is rarely edited and can stay closed, such as the pricing details under the price\n- A closed section that must still show its values: pass `summary` and the chips show while it is closed",
473
+ "whenNotToUse": "- Content the person must see to finish the task: leave it open\n- Switching between peer views: use Tabs",
474
+ "example": "```tsx\n<Collapsible title=\"More pricing\" summary={[{ label: 'Cost price', value: '₹450.00' }, { label: 'HSN code', value: '6109' }]}>\n ...fields...\n</Collapsible>\n```\n\nThe panel appears and disappears at once; the library has no height animation."
475
+ }
476
+ },
477
+ {
478
+ "name": "DataTable",
479
+ "import": "import { DataTable } from '@ratio-ui/core'",
480
+ "description": "A data table on Table with TanStack Table v9: a selection column, server-side sorting and paging, alignment, one-line truncation, row links, and empty and loading slots. The parent owns the state (sort, page, selection) and passes it in. On phones it keeps the columns marked essential and hides the rest.",
481
+ "props": [
482
+ {
483
+ "name": "columns",
484
+ "type": "DataTableColumn<T>[]",
485
+ "required": true,
486
+ "description": "The columns, in order."
487
+ },
488
+ {
489
+ "name": "data",
490
+ "type": "T[]",
491
+ "required": true,
492
+ "description": "The rows of the page on show, already sorted by the parent."
493
+ },
494
+ {
495
+ "name": "getRowId",
496
+ "type": "(row: T) => string",
497
+ "required": true,
498
+ "description": "A stable id for a row."
499
+ },
500
+ {
501
+ "name": "selectedIds",
502
+ "type": "string[]",
503
+ "required": false,
504
+ "description": "Ids of the selected rows. Pass it with `onSelectedIdsChange` to show a checkbox column."
505
+ },
506
+ {
507
+ "name": "onSelectedIdsChange",
508
+ "type": "((ids: string[]) => void)",
509
+ "required": false,
510
+ "description": "Called with the ids that are selected after a change."
511
+ },
512
+ {
513
+ "name": "getRowLabel",
514
+ "type": "((row: T) => string)",
515
+ "required": false,
516
+ "description": "The label of one row's checkbox: \"RYZE - 2mg\", which the table prefixes with \"Select \". Defaults to \"Select row N\"."
517
+ },
518
+ {
519
+ "name": "sort",
520
+ "type": "DataTableSort | null",
521
+ "required": false,
522
+ "default": "null",
523
+ "description": "The sort the parent applied, or null for none."
524
+ },
525
+ {
526
+ "name": "onSortChange",
527
+ "type": "((sort: DataTableSort | null) => void)",
528
+ "required": false,
529
+ "description": "Called with the next sort, or null to clear it. The parent sorts and passes new data."
530
+ },
531
+ {
532
+ "name": "page",
533
+ "type": "number",
534
+ "required": false,
535
+ "description": "The page on show, counting from 1. Pass with `pageSize`, `total` and `onPageChange` to show the footer."
536
+ },
537
+ {
538
+ "name": "pageSize",
539
+ "type": "number",
540
+ "required": false,
541
+ "description": "How many results one page holds."
542
+ },
543
+ {
544
+ "name": "total",
545
+ "type": "number",
546
+ "required": false,
547
+ "description": "How many results there are in all, across every page."
548
+ },
549
+ {
550
+ "name": "onPageChange",
551
+ "type": "((page: number) => void)",
552
+ "required": false,
553
+ "description": "Called with the page to show."
554
+ },
555
+ {
556
+ "name": "rowHref",
557
+ "type": "((row: T) => string)",
558
+ "required": false,
559
+ "description": "Where a row leads. The first data cell of the row becomes a link to it. On a router, LinkProvider draws it with the app's own link."
560
+ },
561
+ {
562
+ "name": "empty",
563
+ "type": "ReactNode",
564
+ "required": false,
565
+ "description": "Shown in place of the rows when there are none and the table is not loading. Usually an EmptyState."
566
+ },
567
+ {
568
+ "name": "loading",
569
+ "type": "ReactNode",
570
+ "required": false,
571
+ "description": "Shown in place of the whole table while loading, usually a TableLoader. A falsy value shows the table."
572
+ }
573
+ ],
574
+ "usage": {
575
+ "whenToUse": "- A list of records with selection, server-side sorting and paging: products, variants, inventory\n- A first load: pass a TableLoader as `loading` and it replaces the table\n- Rows that link to a detail page, with an empty and a loading state\n- Phones: mark the columns a list needs as `essential` and the rest are hidden",
576
+ "whenNotToUse": "- Static content composed by hand: use Table\n- Client-side sorting or paging: the parent owns the sort and the page, and passes already sorted data\n- Styling: it takes no `className` and no `style`",
577
+ "example": "<DataTable\n aria-label=\"Products\"\n columns={[\n { id: 'name', header: 'Product', cell: (p) => p.name, essential: true, sortable: true, truncate: true },\n { id: 'stock', header: 'Stock', cell: (p) => p.stock, numeric: true },\n ]}\n data={products}\n getRowId={(p) => p.id}\n rowHref={(p) => `/products/${p.id}`}\n sort={sort}\n onSortChange={setSort}\n page={page}\n pageSize={20}\n total={84}\n onPageChange={setPage}\n empty=\"No products match this filter\"\n/>"
578
+ }
579
+ },
580
+ {
581
+ "name": "DatePicker",
582
+ "import": "import { DatePicker } from '@ratio-ui/core'",
583
+ "description": "A field that shows a day as DD/MM/YYYY, reads a typed date through the format module and opens a calendar in a popover from the button at its end. Text that is not a date is marked invalid when the field loses focus, and reports nothing. Under a name, a form submits the day as YYYY-MM-DD (empty when there is none), never the text on screen.",
584
+ "props": [
585
+ {
586
+ "name": "value",
587
+ "type": "Date | null",
588
+ "required": false,
589
+ "description": "The day, or null for an empty field. Pass it to control the field."
590
+ },
591
+ {
592
+ "name": "defaultValue",
593
+ "type": "Date | null",
594
+ "required": false,
595
+ "default": "null",
596
+ "description": "The starting day when the field is not controlled. Defaults to empty."
597
+ },
598
+ {
599
+ "name": "onValueChange",
600
+ "type": "((value: Date | null) => void)",
601
+ "required": false,
602
+ "description": "Called with the day when a typed date is complete or a day is picked, and with null when the text is cleared."
603
+ },
604
+ {
605
+ "name": "disabledDays",
606
+ "type": "Matcher | Matcher[]",
607
+ "required": false,
608
+ "description": "The days that cannot be picked in the calendar, for example { before: new Date() }."
609
+ },
610
+ {
611
+ "name": "startMonth",
612
+ "type": "Date",
613
+ "required": false,
614
+ "description": "The first month the calendar can show."
615
+ },
616
+ {
617
+ "name": "endMonth",
618
+ "type": "Date",
619
+ "required": false,
620
+ "description": "The last month the calendar can show."
621
+ },
622
+ {
623
+ "name": "size",
624
+ "type": "32 | 36",
625
+ "required": false,
626
+ "default": "36",
627
+ "description": "The height in px: 36 for forms, 32 for fields inside a table. Defaults to 36."
628
+ },
629
+ {
630
+ "name": "ref",
631
+ "type": "Ref<HTMLInputElement>",
632
+ "required": false,
633
+ "description": "Receives the input element."
634
+ }
635
+ ],
636
+ "usage": {
637
+ "whenToUse": "- One day a person types or picks, such as a \"Best before\" custom field: `value` and `onValueChange` take a `Date` (or null when empty)\n- A date inside a table: `size` 32\n- Limiting the days: `disabledDays` (for example `{ before: new Date() }`), `startMonth` and `endMonth`, which limit the calendar (a typed date is not checked against them)",
638
+ "whenNotToUse": "- A period of days, such as an analytics range: use DateRangePicker\n- A calendar shown on the page, not behind a field: use Calendar\n- Showing a date as text: write it with the format module (`formatDate`)\n- Custom classes or inline styles: DatePicker takes neither `className` nor `style`",
639
+ "example": "```tsx\nconst [bestBefore, setBestBefore] = useState<Date | null>(null)\n\n<DatePicker aria-label=\"Best before\" value={bestBefore} onValueChange={setBestBefore} />\n<DatePicker aria-label=\"Expiry\" size={32} aria-invalid aria-describedby=\"expiry-error\" />\n```\n\nThe field shows DD/MM/YYYY and reads typed dates through the format module (\"3/1/2026\" is 3 January). Text that is not a date turns the field invalid when it loses focus and is never reported.\n\nUnder a `name`, a form submits the committed day as `YYYY-MM-DD` (an empty string when the field is empty), not the text on screen: `<DatePicker name=\"bestBefore\" />` posts `bestBefore=2026-10-28`. A disabled field posts nothing."
640
+ }
641
+ },
642
+ {
643
+ "name": "DateRangePicker",
644
+ "import": "import { DateRangePicker } from '@ratio-ui/core'",
645
+ "description": "A field that shows a range as two DD/MM/YYYY days, reads a typed range through the format module and opens a calendar in a popover from the button at its end, for analytics periods. Pick the first day, then the last. Text that is not a range in order is marked invalid when the field loses focus, and reports nothing. Under a name, a form submits two fields, `name.from` and `name.to`, each as YYYY-MM-DD (empty when there is no range), never the text on screen.",
646
+ "props": [
647
+ {
648
+ "name": "value",
649
+ "type": "DateRangeValue | null",
650
+ "required": false,
651
+ "description": "The range, or null for an empty field. Pass it to control the field."
652
+ },
653
+ {
654
+ "name": "defaultValue",
655
+ "type": "DateRangeValue | null",
656
+ "required": false,
657
+ "default": "null",
658
+ "description": "The starting range when the field is not controlled. Defaults to empty."
659
+ },
660
+ {
661
+ "name": "onValueChange",
662
+ "type": "((value: DateRangeValue | null) => void)",
663
+ "required": false,
664
+ "description": "Called with the range when a typed range is complete or two days are picked, and with null when the text is cleared."
665
+ },
666
+ {
667
+ "name": "disabledDays",
668
+ "type": "Matcher | Matcher[]",
669
+ "required": false,
670
+ "description": "The days that cannot be picked in the calendar, for example { after: new Date() } for an analytics period."
671
+ },
672
+ {
673
+ "name": "startMonth",
674
+ "type": "Date",
675
+ "required": false,
676
+ "description": "The first month the calendar can show."
677
+ },
678
+ {
679
+ "name": "endMonth",
680
+ "type": "Date",
681
+ "required": false,
682
+ "description": "The last month the calendar can show."
683
+ },
684
+ {
685
+ "name": "size",
686
+ "type": "32 | 36",
687
+ "required": false,
688
+ "default": "36",
689
+ "description": "The height in px: 36 for forms, 32 for fields inside a table. Defaults to 36."
690
+ },
691
+ {
692
+ "name": "ref",
693
+ "type": "Ref<HTMLInputElement>",
694
+ "required": false,
695
+ "description": "Receives the input element."
696
+ }
697
+ ],
698
+ "usage": {
699
+ "whenToUse": "- A period of days a person types or picks, such as an analytics range: `value` and `onValueChange` take `{ from, to }` (or null when empty)\n- Limiting the days: `disabledDays` (for example `{ after: new Date() }` for reports), `startMonth` and `endMonth`, which limit the calendar (a typed range is not checked against them)\n- A range inside a table: `size` 32",
700
+ "whenNotToUse": "- One day: use DatePicker\n- A calendar shown on the page, not behind a field: use Calendar\n- Showing a range as text: write both days with the format module (`formatDate`)\n- Custom classes or inline styles: DateRangePicker takes neither `className` nor `style`",
701
+ "example": "```tsx\nconst [period, setPeriod] = useState<DateRangeValue | null>(null)\n\n<DateRangePicker aria-label=\"Period\" value={period} onValueChange={setPeriod} disabledDays={{ after: new Date() }} />\n```\n\nThe field shows `28/10/2026 – 03/11/2026`. Pick the first day, then the last (an earlier second day swaps them). A typed range with the end before the start, or one day only, turns the field invalid when it loses focus and is never reported.\n\nUnder a `name`, a form submits two fields, `name.from` and `name.to`, each the committed day as `YYYY-MM-DD` (empty strings when the field is empty), not the text on screen: `<DateRangePicker name=\"period\" />` posts `period.from=2026-10-28` and `period.to=2026-11-03`. A disabled field posts nothing."
702
+ }
703
+ },
704
+ {
705
+ "name": "Definition",
706
+ "import": "import { Definition } from '@ratio-ui/core'",
707
+ "description": "A term that explains itself. It is a span with the button role and a dotted underline, so it takes the font of the text around it, so keyboard users reach it with Tab and open it with Enter or Space, touch users open it with a tap, and mouse users with hover or click. It does not use the browser's own title tooltip, which keyboard and touch users cannot open. Escape closes it.",
708
+ "props": [
709
+ {
710
+ "name": "children",
711
+ "type": "ReactNode",
712
+ "required": true,
713
+ "description": "The term, shown in the text with a dotted underline."
714
+ },
715
+ {
716
+ "name": "definition",
717
+ "type": "ReactNode",
718
+ "required": true,
719
+ "description": "The explanation shown when the term is opened. Keep it to a sentence."
720
+ }
721
+ ],
722
+ "usage": {
723
+ "whenToUse": "- A term in a heading or label that needs a one-sentence explanation, such as a metric title\n- Where keyboard and touch users must be able to read the explanation: it opens with Tab and Enter, with a tap, and on hover",
724
+ "whenNotToUse": "- An icon-only hint: use Tooltip\n- Long or interactive content: use Popover\n- Text that the task cannot be finished without: show it on the screen",
725
+ "example": "<Definition definition=\"Gross merchandise value, before returns\">GMV</Definition>"
726
+ }
727
+ },
728
+ {
729
+ "name": "Dialog",
730
+ "import": "import { Dialog } from '@ratio-ui/core'",
731
+ "description": "The root of a dialog. It holds the open state and renders nothing itself. Pass `open` and `onOpenChange` to control it, or `defaultOpen` to leave it uncontrolled. The page behind is inert while it is open, with no scrim over it.",
732
+ "props": [],
733
+ "usage": {
734
+ "whenToUse": "- A short task that needs the user's full attention before going back to the page, such as adding a product or defining a field\n- A confirm step before a destructive action\n- Work that fits in 480px: the panel is fixed at that width and has no scrim",
735
+ "whenNotToUse": "- A message after an action: use toast()\n- A long form or a multi-step flow: give it a page\n- Help text on a control: use a Tooltip or Popover",
736
+ "example": "<Dialog>\n <DialogTrigger>Add product</DialogTrigger>\n <DialogContent>\n <DialogHeader>\n <DialogTitle>Add product</DialogTitle>\n <DialogDescription>Fill in the basics</DialogDescription>\n </DialogHeader>\n <DialogFooter>\n <DialogClose>Cancel</DialogClose>\n </DialogFooter>\n </DialogContent>\n</Dialog>"
737
+ },
738
+ "parts": [
739
+ {
740
+ "name": "DialogClose",
741
+ "import": "import { DialogClose } from '@ratio-ui/core'",
742
+ "description": "A button that closes the dialog, such as Cancel in the footer.",
743
+ "props": [
744
+ {
745
+ "name": "render",
746
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, DialogCloseState>",
747
+ "required": false,
748
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
749
+ }
750
+ ]
751
+ },
752
+ {
753
+ "name": "DialogContent",
754
+ "import": "import { DialogContent } from '@ratio-ui/core'",
755
+ "description": "The panel: 480px wide, white, drawn with the ring outline and no shadow, with no scrim behind it. Put DialogHeader, the form and DialogFooter inside.",
756
+ "props": [
757
+ {
758
+ "name": "showCloseButton",
759
+ "type": "boolean",
760
+ "required": false,
761
+ "default": "true",
762
+ "description": "Whether the corner close button shows. Defaults to true."
763
+ },
764
+ {
765
+ "name": "closeLabel",
766
+ "type": "string",
767
+ "required": false,
768
+ "default": "Close",
769
+ "description": "The accessible name of the corner close button. Defaults to Close."
770
+ },
771
+ {
772
+ "name": "render",
773
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, DialogPopupState>",
774
+ "required": false,
775
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
776
+ }
777
+ ]
778
+ },
779
+ {
780
+ "name": "DialogDescription",
781
+ "import": "import { DialogDescription } from '@ratio-ui/core'",
782
+ "description": "One line that says what the dialog is for, in subdued body text.",
783
+ "props": [
784
+ {
785
+ "name": "render",
786
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, DialogDescriptionState>",
787
+ "required": false,
788
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
789
+ }
790
+ ]
791
+ },
792
+ {
793
+ "name": "DialogFooter",
794
+ "import": "import { DialogFooter } from '@ratio-ui/core'",
795
+ "description": "The row of actions at the bottom, aligned to the end.",
796
+ "props": []
797
+ },
798
+ {
799
+ "name": "DialogHeader",
800
+ "import": "import { DialogHeader } from '@ratio-ui/core'",
801
+ "description": "The title and description, stacked.",
802
+ "props": []
803
+ },
804
+ {
805
+ "name": "DialogTitle",
806
+ "import": "import { DialogTitle } from '@ratio-ui/core'",
807
+ "description": "The dialog's name, in the b4 style. It names the dialog for assistive technology.",
808
+ "props": [
809
+ {
810
+ "name": "render",
811
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, DialogTitleState>",
812
+ "required": false,
813
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
814
+ }
815
+ ]
816
+ },
817
+ {
818
+ "name": "DialogTrigger",
819
+ "import": "import { DialogTrigger } from '@ratio-ui/core'",
820
+ "description": "The button that opens the dialog.",
821
+ "props": [
822
+ {
823
+ "name": "render",
824
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, DialogTriggerState>",
825
+ "required": false,
826
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
827
+ }
828
+ ]
829
+ }
830
+ ]
831
+ },
832
+ {
833
+ "name": "DropdownMenu",
834
+ "import": "import { DropdownMenu } from '@ratio-ui/core'",
835
+ "description": "The root of a menu of actions that opens from a trigger. Compose it with DropdownMenuTrigger and DropdownMenuContent.",
836
+ "props": [],
837
+ "usage": {
838
+ "whenToUse": "- A short list of actions behind one control: \"More actions\", a store menu, a \"…\" button\n- Actions that belong in groups, with a label per group, a hairline between groups, or a submenu\n- Items that show a key hint, a check (on or off) or one choice out of a few",
839
+ "whenNotToUse": "- Choosing a value for a form field: use a select\n- Content that is not a list of actions, such as notifications or a form: use Popover\n- A destructive action without a confirm step: the destructive item only colours it\n- Styling: the parts take no `className` and no `style`, and the menu has no shadow",
840
+ "example": "<DropdownMenu>\n <DropdownMenuTrigger>More actions</DropdownMenuTrigger>\n <DropdownMenuContent>\n <DropdownMenuGroup>\n <DropdownMenuLabel>Product</DropdownMenuLabel>\n <DropdownMenuItem>\n Edit\n <DropdownMenuShortcut>⌘E</DropdownMenuShortcut>\n </DropdownMenuItem>\n </DropdownMenuGroup>\n <DropdownMenuSeparator />\n <DropdownMenuItem variant=\"destructive\">Delete</DropdownMenuItem>\n </DropdownMenuContent>\n</DropdownMenu>"
841
+ },
842
+ "parts": [
843
+ {
844
+ "name": "DropdownMenuCheckboxItem",
845
+ "import": "import { DropdownMenuCheckboxItem } from '@ratio-ui/core'",
846
+ "description": "An item that toggles an option. A check shows at its start while it is on and keeps the menu open, so several options can be changed in one visit.",
847
+ "props": [
848
+ {
849
+ "name": "render",
850
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, MenuCheckboxItemState>",
851
+ "required": false,
852
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
853
+ }
854
+ ]
855
+ },
856
+ {
857
+ "name": "DropdownMenuContent",
858
+ "import": "import { DropdownMenuContent } from '@ratio-ui/core'",
859
+ "description": "The menu panel: a white popup with a hairline ring, 8px corners and no shadow. It opens in a portal.",
860
+ "props": [
861
+ {
862
+ "name": "side",
863
+ "type": "\"bottom\" | \"inline-end\" | \"inline-start\" | \"left\" | \"right\" | \"top\"",
864
+ "required": false,
865
+ "default": "bottom",
866
+ "description": "Which side of the trigger the menu opens on. Defaults to bottom."
867
+ },
868
+ {
869
+ "name": "align",
870
+ "type": "\"center\" | \"end\" | \"start\"",
871
+ "required": false,
872
+ "default": "start",
873
+ "description": "How the menu lines up with the trigger along that side. Defaults to start."
874
+ },
875
+ {
876
+ "name": "sideOffset",
877
+ "type": "number",
878
+ "required": false,
879
+ "default": "4",
880
+ "description": "The gap between the trigger and the menu in px. Defaults to 4."
881
+ },
882
+ {
883
+ "name": "alignOffset",
884
+ "type": "number",
885
+ "required": false,
886
+ "default": "0",
887
+ "description": "A shift along the side in px. Defaults to 0."
888
+ },
889
+ {
890
+ "name": "render",
891
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, MenuPopupState>",
892
+ "required": false,
893
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
894
+ }
895
+ ]
896
+ },
897
+ {
898
+ "name": "DropdownMenuGroup",
899
+ "import": "import { DropdownMenuGroup } from '@ratio-ui/core'",
900
+ "description": "Groups items under one DropdownMenuLabel, so assistive technology announces the label with the group.",
901
+ "props": [
902
+ {
903
+ "name": "render",
904
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, MenuGroupState>",
905
+ "required": false,
906
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
907
+ }
908
+ ]
909
+ },
910
+ {
911
+ "name": "DropdownMenuItem",
912
+ "import": "import { DropdownMenuItem } from '@ratio-ui/core'",
913
+ "description": "One action in the menu. It runs onClick and closes the menu; disabled is drawn at half opacity and cannot be chosen.",
914
+ "props": [
915
+ {
916
+ "name": "variant",
917
+ "type": "\"default\" | \"destructive\"",
918
+ "required": false,
919
+ "default": "default",
920
+ "description": "What the item means. destructive is drawn in the destructive token; a delete action also needs a confirm step. Defaults to default."
921
+ },
922
+ {
923
+ "name": "render",
924
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, MenuItemState>",
925
+ "required": false,
926
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
927
+ }
928
+ ]
929
+ },
930
+ {
931
+ "name": "DropdownMenuLabel",
932
+ "import": "import { DropdownMenuLabel } from '@ratio-ui/core'",
933
+ "description": "The caption of a group, in the design system's label style: 12px, uppercase, subdued. Place it inside DropdownMenuGroup.",
934
+ "props": [
935
+ {
936
+ "name": "render",
937
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, MenuGroupLabelState>",
938
+ "required": false,
939
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
940
+ }
941
+ ]
942
+ },
943
+ {
944
+ "name": "DropdownMenuRadioGroup",
945
+ "import": "import { DropdownMenuRadioGroup } from '@ratio-ui/core'",
946
+ "description": "Holds radio items and the one chosen value.",
947
+ "props": [
948
+ {
949
+ "name": "render",
950
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, MenuRadioGroupState>",
951
+ "required": false,
952
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
953
+ }
954
+ ]
955
+ },
956
+ {
957
+ "name": "DropdownMenuRadioItem",
958
+ "import": "import { DropdownMenuRadioItem } from '@ratio-ui/core'",
959
+ "description": "One choice of a DropdownMenuRadioGroup. A dot shows at its start while it is the chosen value.",
960
+ "props": [
961
+ {
962
+ "name": "render",
963
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, MenuRadioItemState>",
964
+ "required": false,
965
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
966
+ }
967
+ ]
968
+ },
969
+ {
970
+ "name": "DropdownMenuSeparator",
971
+ "import": "import { DropdownMenuSeparator } from '@ratio-ui/core'",
972
+ "description": "A hairline between groups of items.",
973
+ "props": [
974
+ {
975
+ "name": "render",
976
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, SeparatorState>",
977
+ "required": false,
978
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
979
+ }
980
+ ]
981
+ },
982
+ {
983
+ "name": "DropdownMenuShortcut",
984
+ "import": "import { DropdownMenuShortcut } from '@ratio-ui/core'",
985
+ "description": "A key hint pushed to the end of an item. It is a hint only: the host binds the key.",
986
+ "props": [
987
+ {
988
+ "name": "children",
989
+ "type": "ReactNode",
990
+ "required": true,
991
+ "description": "The key hint, such as ⌘E. It is text only and does not bind a key."
992
+ }
993
+ ]
994
+ },
995
+ {
996
+ "name": "DropdownMenuSub",
997
+ "import": "import { DropdownMenuSub } from '@ratio-ui/core'",
998
+ "description": "The root of a nested menu. Hold a DropdownMenuSubTrigger and a DropdownMenuSubContent.",
999
+ "props": []
1000
+ },
1001
+ {
1002
+ "name": "DropdownMenuSubContent",
1003
+ "import": "import { DropdownMenuSubContent } from '@ratio-ui/core'",
1004
+ "description": "The panel of a submenu. It opens to the side of its trigger.",
1005
+ "props": [
1006
+ {
1007
+ "name": "side",
1008
+ "type": "\"bottom\" | \"inline-end\" | \"inline-start\" | \"left\" | \"right\" | \"top\"",
1009
+ "required": false,
1010
+ "default": "right",
1011
+ "description": "Which side of the trigger the menu opens on. Defaults to bottom."
1012
+ },
1013
+ {
1014
+ "name": "align",
1015
+ "type": "\"center\" | \"end\" | \"start\"",
1016
+ "required": false,
1017
+ "description": "How the menu lines up with the trigger along that side. Defaults to start."
1018
+ },
1019
+ {
1020
+ "name": "sideOffset",
1021
+ "type": "number",
1022
+ "required": false,
1023
+ "default": "0",
1024
+ "description": "The gap between the trigger and the menu in px. Defaults to 4."
1025
+ },
1026
+ {
1027
+ "name": "alignOffset",
1028
+ "type": "number",
1029
+ "required": false,
1030
+ "default": "-6",
1031
+ "description": "A shift along the side in px. Defaults to 0."
1032
+ },
1033
+ {
1034
+ "name": "render",
1035
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, MenuPopupState>",
1036
+ "required": false,
1037
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
1038
+ }
1039
+ ]
1040
+ },
1041
+ {
1042
+ "name": "DropdownMenuSubTrigger",
1043
+ "import": "import { DropdownMenuSubTrigger } from '@ratio-ui/core'",
1044
+ "description": "The item that opens a submenu, on hover, click or ArrowRight. A chevron at its end marks it.",
1045
+ "props": [
1046
+ {
1047
+ "name": "render",
1048
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, MenuSubmenuTriggerState>",
1049
+ "required": false,
1050
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
1051
+ }
1052
+ ]
1053
+ },
1054
+ {
1055
+ "name": "DropdownMenuTrigger",
1056
+ "import": "import { DropdownMenuTrigger } from '@ratio-ui/core'",
1057
+ "description": "The control that opens the menu. It is a button, and carries aria-expanded and aria-haspopup.",
1058
+ "props": [
1059
+ {
1060
+ "name": "render",
1061
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, MenuTriggerState>",
1062
+ "required": false,
1063
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
1064
+ }
1065
+ ]
1066
+ }
1067
+ ]
1068
+ },
1069
+ {
1070
+ "name": "EmptyState",
1071
+ "import": "import { EmptyState } from '@ratio-ui/core'",
1072
+ "description": "What a list, table or panel shows when it has nothing to show: one clause and an optional action, centred in a hairline container.",
1073
+ "props": [
1074
+ {
1075
+ "name": "title",
1076
+ "type": "ReactNode",
1077
+ "required": true,
1078
+ "description": "One clause that says why nothing is shown, in sentence case with no period."
1079
+ },
1080
+ {
1081
+ "name": "description",
1082
+ "type": "ReactNode",
1083
+ "required": false,
1084
+ "description": "An optional second clause that says what to do next."
1085
+ },
1086
+ {
1087
+ "name": "action",
1088
+ "type": "ReactNode",
1089
+ "required": false,
1090
+ "description": "An optional action, usually one Button."
1091
+ }
1092
+ ],
1093
+ "usage": {
1094
+ "whenToUse": "- A list, table or panel with nothing to show, such as a filter that matches no products\n- With one action when the user can fix it, such as clearing the filter",
1095
+ "whenNotToUse": "- A load that is still running: use Skeleton\n- An error: say what failed in one clause, in the error colour, not here",
1096
+ "example": "<EmptyState title=\"No products match this filter\" action={<Button>Clear filter</Button>} />"
1097
+ }
1098
+ },
1099
+ {
1100
+ "name": "Field",
1101
+ "import": "import { Field } from '@ratio-ui/core'",
1102
+ "description": "A form row: label, control, hint and error. It wires the label to the control by id, links the hint and error with aria-describedby, and sets aria-invalid when there is an error. The control keeps an id it already has.",
1103
+ "props": [
1104
+ {
1105
+ "name": "label",
1106
+ "type": "ReactNode",
1107
+ "required": true,
1108
+ "description": "The label text."
1109
+ },
1110
+ {
1111
+ "name": "hint",
1112
+ "type": "ReactNode",
1113
+ "required": false,
1114
+ "description": "Help text under the control. An error replaces it."
1115
+ },
1116
+ {
1117
+ "name": "error",
1118
+ "type": "ReactNode",
1119
+ "required": false,
1120
+ "description": "The error message. It shows under the control beside the alert glyph and marks the control invalid."
1121
+ },
1122
+ {
1123
+ "name": "info",
1124
+ "type": "ReactNode",
1125
+ "required": false,
1126
+ "description": "An explanation of the field, opened from a small info icon beside the label by keyboard, touch or hover."
1127
+ },
1128
+ {
1129
+ "name": "required",
1130
+ "type": "boolean",
1131
+ "required": false,
1132
+ "description": "Shows a star after the label and marks the control aria-required."
1133
+ },
1134
+ {
1135
+ "name": "disabled",
1136
+ "type": "boolean",
1137
+ "required": false,
1138
+ "description": "Dims the label and disables the control."
1139
+ },
1140
+ {
1141
+ "name": "children",
1142
+ "type": "ReactElement<Record<string, unknown>, string | JSXElementConstructor<any>> | ((props: FieldControlProps) => ReactNode)",
1143
+ "required": true,
1144
+ "description": "The control: one element, such as Input, which gets an id, aria-describedby, aria-invalid, aria-required and disabled. When the control sits inside a wrapper such as InputGroup, pass a function instead and spread the props it receives onto the input itself."
1145
+ }
1146
+ ],
1147
+ "usage": {
1148
+ "whenToUse": "- Every form row: a label, one control, and optional hint, error, info tooltip and required mark\n- Wiring: Field sets the control's id, aria-describedby, aria-invalid, aria-required and disabled, so you do not",
1149
+ "whenNotToUse": "- A heading for a group of controls: use a fieldset with a legend\n- A label with no control beside it: use Label alone, with `htmlFor`\n- Long help that must be read to finish the task: put it on the screen as the hint",
1150
+ "example": "```tsx\n<Field label=\"Title\" hint=\"Shown in search results\" error={errors.title} required>\n <Input />\n</Field>\n<Field label=\"MRP\" info=\"Maximum retail price, with tax\">\n <Input inputMode=\"decimal\" />\n</Field>\n```\n\nFor an input inside an InputGroup, pass a function child and spread its props onto the input: `<Field label=\"MRP\">{(props) => <InputGroup>…<InputGroupInput {...props} /></InputGroup>}</Field>`.\n\nFor a Select, which renders no element of its own, spread the props onto its trigger: `<Field label=\"GST rate\">{(props) => <Select items={rates}><SelectTrigger {...props}><SelectValue /></SelectTrigger><SelectContent>…</SelectContent></Select>}</Field>`.\n\nThe error shows beside the alert glyph in the error ink (question 1) and replaces the hint. Label is the same label as a separate part."
1151
+ },
1152
+ "parts": [
1153
+ {
1154
+ "name": "Label",
1155
+ "import": "import { Label } from '@ratio-ui/core'",
1156
+ "description": "The text that names a control, in the 12px medium label type. Point htmlFor at the control's id; Field does this for you.",
1157
+ "props": [
1158
+ {
1159
+ "name": "ref",
1160
+ "type": "Ref<HTMLLabelElement>",
1161
+ "required": false,
1162
+ "description": "Receives the label element."
1163
+ }
1164
+ ]
1165
+ }
1166
+ ]
1167
+ },
1168
+ {
1169
+ "name": "FilterTabs",
1170
+ "import": "import { FilterTabs } from '@ratio-ui/core'",
1171
+ "description": "A single choice that filters a list, such as All, Active and Inactive above a table: the design system's Tab chips in a row with an 8px gap. One item is always chosen. For sections of a page use Tabs instead.",
1172
+ "props": [
1173
+ {
1174
+ "name": "items",
1175
+ "type": "FilterTabsItem[]",
1176
+ "required": true,
1177
+ "description": "The choices, in order."
1178
+ },
1179
+ {
1180
+ "name": "value",
1181
+ "type": "string",
1182
+ "required": false,
1183
+ "description": "The chosen value when controlled."
1184
+ },
1185
+ {
1186
+ "name": "defaultValue",
1187
+ "type": "string",
1188
+ "required": false,
1189
+ "description": "The chosen value at the start when uncontrolled. Defaults to the first item."
1190
+ },
1191
+ {
1192
+ "name": "onValueChange",
1193
+ "type": "((value: string) => void)",
1194
+ "required": false,
1195
+ "description": "Called with the new value when another item is chosen. Choosing the chosen item again does nothing."
1196
+ },
1197
+ {
1198
+ "name": "disabled",
1199
+ "type": "boolean",
1200
+ "required": false,
1201
+ "description": "Disables every item. Defaults to false."
1202
+ }
1203
+ ],
1204
+ "usage": {
1205
+ "whenToUse": "- A single choice that filters the list below it, such as All, Active and Inactive above the products table\n- A short set of choices, one chosen at all times, shown as the design system's chip tabs in a row",
1206
+ "whenNotToUse": "- Sections of a page with their own content: use Tabs\n- Choices that can be combined or cleared: use checkboxes or a menu\n- Many choices: the chips wrap onto more lines, so use a select",
1207
+ "example": "<FilterTabs\n aria-label=\"Status\"\n items={[\n { value: 'all', label: 'All' },\n { value: 'active', label: 'Active' },\n { value: 'draft', label: 'Draft' },\n ]}\n defaultValue=\"all\"\n onValueChange={setStatus}\n/>"
1208
+ }
1209
+ },
1210
+ {
1211
+ "name": "GlobalSearch",
1212
+ "import": "import { GlobalSearch } from '@ratio-ui/core'",
1213
+ "description": "The search in the top bar. Typing opens a panel of grouped results that the app supplies, an empty field shows recent items, and the arrow keys, Enter and Escape work the list. Cmd+K or Ctrl+K focuses the field from anywhere.",
1214
+ "props": [
1215
+ {
1216
+ "name": "label",
1217
+ "type": "string",
1218
+ "required": true,
1219
+ "description": "The accessible name of the field, such as \"Search the dashboard\"."
1220
+ },
1221
+ {
1222
+ "name": "value",
1223
+ "type": "string",
1224
+ "required": true,
1225
+ "description": "The text typed in the field."
1226
+ },
1227
+ {
1228
+ "name": "onValueChange",
1229
+ "type": "(value: string) => void",
1230
+ "required": true,
1231
+ "description": "Called on every change of the text. Debounce and fetch in the app."
1232
+ },
1233
+ {
1234
+ "name": "groups",
1235
+ "type": "GlobalSearchGroup[]",
1236
+ "required": true,
1237
+ "description": "The results for the current text, grouped. The app supplies them; nothing is filtered here."
1238
+ },
1239
+ {
1240
+ "name": "recent",
1241
+ "type": "GlobalSearchResult[]",
1242
+ "required": false,
1243
+ "default": "[]",
1244
+ "description": "Recent items, shown under \"Recent\" while the field is empty."
1245
+ },
1246
+ {
1247
+ "name": "onSelect",
1248
+ "type": "((result: GlobalSearchResult) => void)",
1249
+ "required": false,
1250
+ "description": "Called when a result is chosen by click or Enter."
1251
+ },
1252
+ {
1253
+ "name": "onShowMore",
1254
+ "type": "((group: GlobalSearchGroup) => void)",
1255
+ "required": false,
1256
+ "description": "Called when a \"+N more\" row is chosen, with its group."
1257
+ },
1258
+ {
1259
+ "name": "placeholder",
1260
+ "type": "string",
1261
+ "required": false,
1262
+ "default": "Search",
1263
+ "description": "The placeholder of the field. Defaults to \"Search\"."
1264
+ },
1265
+ {
1266
+ "name": "emptyText",
1267
+ "type": "string",
1268
+ "required": false,
1269
+ "default": "No results",
1270
+ "description": "The copy when the text matches nothing. Defaults to \"No results\"."
1271
+ }
1272
+ ],
1273
+ "usage": {
1274
+ "whenToUse": "- The search in the top bar that finds products, orders, customers and pages across the dashboard\n- Results that the app fetches and groups itself, with recent items while the field is empty",
1275
+ "whenNotToUse": "- Narrowing the rows of one listing: use SearchField\n- Picking a value for a form field: use a select",
1276
+ "example": "<GlobalSearch\n label=\"Search the dashboard\"\n value={query}\n onValueChange={setQuery}\n groups={[{ id: 'products', label: 'Products', total: 8, items: products }]}\n recent={recentItems}\n onSelect={(result) => navigate(result.id)}\n onShowMore={(group) => navigate(`/search?group=${group.id}&q=${query}`)}\n/>"
1277
+ }
1278
+ },
4
1279
  {
5
1280
  "name": "Icon",
6
1281
  "import": "import { Icon } from '@ratio-ui/core'",
7
1282
  "description": "Draws one icon of the registry: the design system's own glyphs, filled in a single colour, and Tabler's icons for the rest, drawn with a 1.5px line at every size. The icon is a square box of 8, 12 or 16px and takes the colour of its tone.",
8
1283
  "props": [
9
1284
  {
10
- "name": "name",
11
- "type": "\"alert-triangle\" | \"check\" | \"chevron-left\" | \"chevron-right\" | \"export\" | \"info-circle\" | \"nav-caret-down\" | \"nav-chevron\" | \"nav-home\" | \"nav-product\" | \"nav-product-2\" | \"nav-shortcut\" | \"plus\" | \"search\" | \"x\"",
1285
+ "name": "name",
1286
+ "type": "\"alert-triangle\" | \"calendar\" | \"check\" | \"chevron-left\" | \"chevron-right\" | \"export\" | \"import\" | \"info-circle\" | \"menu\" | \"more\" | \"nav-analytics\" | \"nav-apps\" | \"nav-caret-down\" | \"nav-chevron\" | \"nav-content\" | \"nav-customers\" | \"nav-discounts\" | \"nav-growth\" | \"nav-home\" | \"nav-markets\" | \"nav-orders\" | \"nav-product\" | \"nav-product-2\" | \"nav-sales-channels\" | \"nav-settings\" | \"nav-shortcut\" | \"plus\" | \"search\" | \"x\"",
1287
+ "required": true,
1288
+ "description": "Which icon to draw, by name."
1289
+ },
1290
+ {
1291
+ "name": "size",
1292
+ "type": "8 | 12 | 16",
1293
+ "required": false,
1294
+ "default": "12",
1295
+ "description": "The square box in px: 8, 12 or 16. Defaults to 12, the size inside controls."
1296
+ },
1297
+ {
1298
+ "name": "tone",
1299
+ "type": "\"current\" | \"icon\" | \"primary\" | \"subdued\"",
1300
+ "required": false,
1301
+ "default": "current",
1302
+ "description": "The colour. current, the default, takes the colour of the text around the icon; the other tones set their own."
1303
+ },
1304
+ {
1305
+ "name": "label",
1306
+ "type": "string",
1307
+ "required": false,
1308
+ "description": "The accessible name. With a label the icon is an image that assistive technology announces under that name. Without one it is decoration, hidden from assistive technology, and the text beside it has to say what it means."
1309
+ }
1310
+ ],
1311
+ "usage": {
1312
+ "whenToUse": "- A glyph beside a label inside a control: the plus on a main button, the magnifier in a search field, the export tray on a secondary button. Use size 12\n- The side navigation: the nav glyphs at size 16 (nav-home and nav-product are the design system's own; nav-orders, nav-customers, nav-growth, nav-discounts, nav-content, nav-markets, nav-analytics, nav-sales-channels, nav-apps and nav-settings are Tabler stand-ins for the dashboard's sections, question 7), and the disclosure caret (nav-chevron or nav-caret-down, one glyph under two names) at size 8\n- The date pickers' open button: calendar (Tabler) at size 12\n- Pagination: chevron-left and chevron-right at size 8\n- A mark that Tabler supplies: check, x, alert-triangle, info-circle, import, more (three dots) and menu (the phone menu button). They are drawn with a 1.5px line at every size, so they sit beside the design system's own glyphs without looking heavier\n- Meaning that lives only in the icon: pass label. The icon is then an image that assistive technology announces under that name. Without a label it is decoration and is hidden from assistive technology\n- Colour: tone current, the default, takes the colour of the text around the icon. icon is the design system's icon ink, subdued is its subdued text ink and primary is the accent\n- Chevron geometry (design question 9): the design system has no vector for its pagination chevrons, only a 1.5px CSS caret in an 8px box, so chevron-left and chevron-right stand in. Each is the outline of a line 1.5 units wide with round ends, two legs at 45 degrees, each 2.5 units across and 2.5 down, through (2.75, 1.5), (5.25, 4) and (2.75, 6.5) in an 8 unit box. They scale like every filled glyph, so draw them at size 8, and expect them to be replaced when the designer sends real vectors",
1313
+ "whenNotToUse": "- Decoration that adds no meaning. An icon next to text that already says the same thing is hidden from assistive technology, so leave the label off, and leave the icon out when it adds nothing\n- An icon the registry does not have. Components accept no className or style, and icons come from the registry only, so a new one is a registry entry: the design system's glyph first, Tabler for the rest\n- An icon-only control without a name. Pass label, or give the control its own accessible name\n- A size other than 8, 12 or 16, or a colour other than the four tones. There is no red, no destructive colour and no third accent: an error is marked with the alert-triangle glyph beside its message, not with a colour\n- Emoji, or unicode arrows and symbols, as icons. Inter's web subsets do not hold them and the design system bans them",
1314
+ "example": "import { Icon } from '@ratio-ui/core'\n\n// Beside text that names it: decoration, hidden from assistive technology\n<button type=\"button\">\n <Icon name=\"plus\" />\n Add product\n</button>\n\n// Alone: the label is the accessible name\n<Icon name=\"search\" tone=\"icon\" label=\"Search\" />\n\n// Pagination chevrons are drawn for an 8px box\n<Icon name=\"chevron-right\" size={8} />"
1315
+ }
1316
+ },
1317
+ {
1318
+ "name": "Inline",
1319
+ "import": "import { Inline } from '@ratio-ui/core'",
1320
+ "description": "Lays its children out in a row with a fixed space between them. It takes no className and no style: the gap is a step of the design system's spacing scale, and the layout is the only styling it has.",
1321
+ "props": [
1322
+ {
1323
+ "name": "gap",
1324
+ "type": "0 | 1 | 2 | 4 | 6 | 7 | 8 | 11 | 12 | 14 | 24 | 32 | 80",
1325
+ "required": true,
1326
+ "description": "The space between children, a step of the design system's scale in px: 0, 1, 2, 4, 6, 7, 8, 11, 12, 14, 24, 32 or 80."
1327
+ },
1328
+ {
1329
+ "name": "align",
1330
+ "type": "\"baseline\" | \"center\" | \"end\" | \"start\" | \"stretch\"",
1331
+ "required": false,
1332
+ "default": "center",
1333
+ "description": "How children line up across the row, which is the vertical axis. Defaults to center."
1334
+ },
1335
+ {
1336
+ "name": "justify",
1337
+ "type": "\"between\" | \"center\" | \"end\" | \"start\"",
1338
+ "required": false,
1339
+ "default": "start",
1340
+ "description": "How children spread along the row, which is the horizontal axis. Defaults to start."
1341
+ },
1342
+ {
1343
+ "name": "wrap",
1344
+ "type": "boolean",
1345
+ "required": false,
1346
+ "default": "false",
1347
+ "description": "Whether children move to a new line when the row is full. Defaults to false, which keeps one line."
1348
+ },
1349
+ {
1350
+ "name": "as",
1351
+ "type": "\"aside\" | \"div\" | \"fieldset\" | \"footer\" | \"form\" | \"header\" | \"li\" | \"main\" | \"nav\" | \"ol\" | \"section\" | \"ul\"",
1352
+ "required": false,
1353
+ "default": "div",
1354
+ "description": "The element to render. Defaults to div; use a list, landmark or form element where the content is one."
1355
+ },
1356
+ {
1357
+ "name": "ref",
1358
+ "type": "Ref<HTMLElement>",
1359
+ "required": false,
1360
+ "description": "Receives the rendered element."
1361
+ }
1362
+ ],
1363
+ "usage": {
1364
+ "whenToUse": "- Children side by side with a fixed space between them: a toolbar, a row of tags, a label next to a value\n- Children that spread along the row (`justify`: start, center, end or between) or move to the next line when the row is full (`wrap`)\n- A gap taken from the design system's spacing scale: 0, 1, 2, 4, 6, 7, 8, 11, 12, 14, 24, 32 or 80 px\n- A row that is also a list or a landmark: `as` renders `ul`, `ol`, `li`, `nav`, `header`, `footer`, `main`, `aside`, `section`, `form` or `fieldset` instead of `div`",
1365
+ "whenNotToUse": "- Children one under another: use Stack\n- Columns that have to line up from one row to the next: Inline lays out one row at a time, and wave 0 has no grid\n- Space around one child or at the edge of a container: Inline spaces siblings only, and has no padding or margin\n- Styling: Inline takes no `className` and no `style`\n- A list without markers: `as=\"ul\"` and `as=\"ol\"` leave the browser's list markers as they are",
1366
+ "example": "<Inline gap={8} justify=\"between\" wrap>\n <div>Orders</div>\n <Inline gap={4}>\n <div>Export</div>\n <div>Filter</div>\n </Inline>\n</Inline>"
1367
+ }
1368
+ },
1369
+ {
1370
+ "name": "Input",
1371
+ "import": "import { Input } from '@ratio-ui/core'",
1372
+ "description": "A single-line text field in the design system's look: 8px radius, a 1px ring that turns blue on focus, 14px body type. It takes every attribute of an input (type, inputMode, value, onChange). For a prefix, suffix, icon or shortcut hint, put an InputGroupInput in an InputGroup.",
1373
+ "props": [
1374
+ {
1375
+ "name": "size",
1376
+ "type": "32 | 36",
1377
+ "required": false,
1378
+ "default": "36",
1379
+ "description": "The height in px: 36 for forms and search, 32 for fields inside a table. Defaults to 36."
1380
+ },
1381
+ {
1382
+ "name": "ref",
1383
+ "type": "Ref<HTMLInputElement>",
1384
+ "required": false,
1385
+ "description": "Receives the input element."
1386
+ }
1387
+ ],
1388
+ "usage": {
1389
+ "whenToUse": "- A single-line text field in a form, a settings page or a table cell: `size` is 36 for forms and 32 for fields inside a table\n- Any input type the browser supports, such as text, email, number or password: Input takes every attribute of an input\n- A field with an error: set `aria-invalid` and the ring changes, and put the error message beside it in text",
1390
+ "whenNotToUse": "- A field with a prefix, suffix, icon or shortcut hint, such as a rupee price, a weight in kg or a search field: use InputGroup\n- Several lines of text: use a textarea\n- A choice from a fixed list: use a select\n- Custom classes or inline styles: Input takes no `className` and no `style`",
1391
+ "example": "<Input aria-label=\"Product title\" placeholder=\"Short sleeve t-shirt\" />\n<Input aria-label=\"Variant stock\" size={32} inputMode=\"numeric\" />"
1392
+ }
1393
+ },
1394
+ {
1395
+ "name": "InputGroup",
1396
+ "import": "import { InputGroup } from '@ratio-ui/core'",
1397
+ "description": "A field with a prefix, suffix, icon or shortcut hint beside the input, inside one ring. Put an InputGroupInput in it and an InputGroupAddon on either side, for example a rupee sign before a price or kg after a weight.",
1398
+ "props": [
1399
+ {
1400
+ "name": "size",
1401
+ "type": "32 | 36",
1402
+ "required": false,
1403
+ "default": "36",
1404
+ "description": "The height in px: 36 for forms and search, 32 for fields inside a table. Defaults to 36."
1405
+ },
1406
+ {
1407
+ "name": "ref",
1408
+ "type": "Ref<HTMLDivElement>",
1409
+ "required": false,
1410
+ "description": "Receives the group element."
1411
+ }
1412
+ ],
1413
+ "usage": {
1414
+ "whenToUse": "- A field with something beside the input, all inside one ring: a rupee sign before a price, kg after a weight, a search icon, or a shortcut hint such as ⌘K\n- A table search field or the top bar search: an icon at the start and a shortcut hint at the end\n- A compact field in a table: `size` 32, as for Input",
1415
+ "whenNotToUse": "- A plain field with nothing beside the input: use Input\n- A button inside the field: put it next to the group, because an addon holds text and icons only\n- Custom classes or inline styles: none of the parts take `className` or `style`",
1416
+ "example": "<InputGroup>\n <InputGroupAddon>\n <InputGroupText>₹</InputGroupText>\n </InputGroupAddon>\n <InputGroupInput aria-label=\"Selling price\" inputMode=\"decimal\" />\n</InputGroup>\n<InputGroup>\n <InputGroupAddon><Icon name=\"search\" /></InputGroupAddon>\n <InputGroupInput aria-label=\"Search\" placeholder=\"Search by name, email, etc.\" />\n <InputGroupAddon align=\"end\"><InputGroupShortcut>⌘K</InputGroupShortcut></InputGroupAddon>\n</InputGroup>"
1417
+ },
1418
+ "parts": [
1419
+ {
1420
+ "name": "InputGroupAddon",
1421
+ "import": "import { InputGroupAddon } from '@ratio-ui/core'",
1422
+ "description": "Holds an icon, a prefix, a suffix or a shortcut hint on one side of the input. Clicking it, off a button, moves focus to the input.",
1423
+ "props": [
1424
+ {
1425
+ "name": "align",
1426
+ "type": "\"end\" | \"start\"",
1427
+ "required": false,
1428
+ "default": "start",
1429
+ "description": "The side of the input: start before it, end after it. Defaults to start."
1430
+ }
1431
+ ]
1432
+ },
1433
+ {
1434
+ "name": "InputGroupInput",
1435
+ "import": "import { InputGroupInput } from '@ratio-ui/core'",
1436
+ "description": "The input of an InputGroup. It draws no ring: the group does. Mark it invalid with aria-invalid.",
1437
+ "props": [
1438
+ {
1439
+ "name": "ref",
1440
+ "type": "Ref<HTMLInputElement>",
1441
+ "required": false,
1442
+ "description": "Receives the input element."
1443
+ }
1444
+ ]
1445
+ },
1446
+ {
1447
+ "name": "InputGroupShortcut",
1448
+ "import": "import { InputGroupShortcut } from '@ratio-ui/core'",
1449
+ "description": "A shortcut hint such as ⌘K inside an end addon, drawn as a small keycap.",
1450
+ "props": []
1451
+ },
1452
+ {
1453
+ "name": "InputGroupText",
1454
+ "import": "import { InputGroupText } from '@ratio-ui/core'",
1455
+ "description": "A text prefix or suffix inside an addon, such as a currency sign or a unit, in the muted colour.",
1456
+ "props": []
1457
+ }
1458
+ ]
1459
+ },
1460
+ {
1461
+ "name": "Kbd",
1462
+ "import": "import { Kbd } from '@ratio-ui/core'",
1463
+ "description": "A key hint, 20px high with a 4px radius, such as ⌘K or esc.",
1464
+ "props": [
1465
+ {
1466
+ "name": "ref",
1467
+ "type": "Ref<HTMLElement>",
1468
+ "required": false,
1469
+ "description": "Receives the rendered element."
1470
+ }
1471
+ ],
1472
+ "usage": {
1473
+ "whenToUse": "- A shortcut hint beside a control or in a menu: ⌘K, ↵, esc\n- Several keys of one shortcut, inside KbdGroup",
1474
+ "whenNotToUse": "- A button that triggers the action: Kbd only shows the key and is not interactive\n- Long text: a key is one symbol or a short name",
1475
+ "example": "<KbdGroup>\n <Kbd>⌘</Kbd>\n <Kbd>K</Kbd>\n</KbdGroup>"
1476
+ },
1477
+ "parts": [
1478
+ {
1479
+ "name": "KbdGroup",
1480
+ "import": "import { KbdGroup } from '@ratio-ui/core'",
1481
+ "description": "Puts the keys of one shortcut in a row with a 4px gap.",
1482
+ "props": []
1483
+ }
1484
+ ]
1485
+ },
1486
+ {
1487
+ "name": "Link",
1488
+ "import": "import { Link } from '@ratio-ui/core'",
1489
+ "description": "A text link to another page, such as \"← Products\" or \"View all variants →\". It is drawn by the app's LinkProvider, so a router navigates without a reload; without one it is a plain <a href>. The arrows are decoration and are hidden from screen readers.",
1490
+ "props": [
1491
+ {
1492
+ "name": "href",
1493
+ "type": "string",
1494
+ "required": true,
1495
+ "description": "Where the link goes."
1496
+ },
1497
+ {
1498
+ "name": "children",
1499
+ "type": "ReactNode",
1500
+ "required": true,
1501
+ "description": "The link text."
1502
+ },
1503
+ {
1504
+ "name": "back",
1505
+ "type": "boolean",
1506
+ "required": false,
1507
+ "default": "false",
1508
+ "description": "Shows a back arrow before the text, as in \"← Products\". Screen readers skip it."
1509
+ },
1510
+ {
1511
+ "name": "forward",
1512
+ "type": "boolean",
1513
+ "required": false,
1514
+ "default": "false",
1515
+ "description": "Shows a forward arrow after the text, as in \"View all variants →\". Screen readers skip it."
1516
+ },
1517
+ {
1518
+ "name": "ref",
1519
+ "type": "Ref<HTMLAnchorElement>",
1520
+ "required": false,
1521
+ "description": "Receives the anchor element."
1522
+ }
1523
+ ],
1524
+ "usage": {
1525
+ "whenToUse": "- A text link to another page: \"← Products\", \"View all variants →\"\n- A back link in a detail page's body (the page header has its own back link)",
1526
+ "whenNotToUse": "- An action that changes something: use Button\n- A whole table row: use DataTable's `rowHref`\n- A navigation row in the side navigation: use SideNav",
1527
+ "example": "<Link href=\"/products\" back>Products</Link>\n<Link href=\"/products/blue-shirt/variants\" forward>View all variants</Link>"
1528
+ }
1529
+ },
1530
+ {
1531
+ "name": "LinkProvider",
1532
+ "import": "import { LinkProvider } from '@ratio-ui/core'",
1533
+ "description": "Draws every link of Ratio's components with the app's own router link, so a click navigates without a page reload: SideNav rows with an href, DataTable row links and the PageHeader back link. Wrap the app once, at its root. Without it those links are plain <a href> elements, which reload the page.",
1534
+ "props": [
1535
+ {
1536
+ "name": "render",
1537
+ "type": "LinkRender",
1538
+ "required": true,
1539
+ "description": "Draws every link Ratio renders. The function receives href, the styling class, the content and attributes such as aria-current and onClick, all to spread onto the element: `({ href, ...props }) => <RouterLink to={href} {...props} />`."
1540
+ },
1541
+ {
1542
+ "name": "children",
1543
+ "type": "ReactNode",
1544
+ "required": true,
1545
+ "description": "The app, or the part of it that the router serves."
1546
+ }
1547
+ ],
1548
+ "usage": {
1549
+ "whenToUse": "- An app on a client-side router (React Router, Next.js, TanStack Router): wrap it once, at its root\n- So that SideNav rows with an href, DataTable row links and the PageHeader back link navigate without a page reload",
1550
+ "whenNotToUse": "- An app whose pages load from the server: the plain links already work\n- A link in your own markup: use your router's link directly",
1551
+ "example": "import { Link as RouterLink } from 'react-router-dom'\n\n<LinkProvider render={({ href, ...props }) => <RouterLink to={href} {...props} />}>\n <App />\n</LinkProvider>"
1552
+ }
1553
+ },
1554
+ {
1555
+ "name": "MediaGrid",
1556
+ "import": "import { MediaGrid } from '@ratio-ui/core'",
1557
+ "description": "The media of a product: a row of square thumbs with an add tile that opens the file picker and takes dropped files. Thumbs reorder by drag (pointer, touch or keyboard) and by the Move left, Move right and Make cover buttons, so no drag is needed. The first image is the cover and carries a badge. Uploads show a thin progress bar and failures an error under the thumb. Every move, cover change and removal is announced to screen readers.",
1558
+ "props": [
1559
+ {
1560
+ "name": "items",
1561
+ "type": "MediaGridItem[]",
1562
+ "required": true,
1563
+ "description": "The images in order. The first one is the cover."
1564
+ },
1565
+ {
1566
+ "name": "onAdd",
1567
+ "type": "(files: File[]) => void",
1568
+ "required": true,
1569
+ "description": "Called with the files chosen in the add tile or dropped on the grid."
1570
+ },
1571
+ {
1572
+ "name": "onReorder",
1573
+ "type": "(ids: string[]) => void",
1574
+ "required": true,
1575
+ "description": "Called with every id in the new order after a drag, Move left, Move right or Make cover."
1576
+ },
1577
+ {
1578
+ "name": "onRemove",
1579
+ "type": "(id: string) => void",
1580
+ "required": true,
1581
+ "description": "Called with the id of the item to take off the grid."
1582
+ },
1583
+ {
1584
+ "name": "onMakeCover",
1585
+ "type": "((id: string) => void)",
1586
+ "required": false,
1587
+ "description": "Called with the id after Make cover has moved it first through onReorder, for an app that saves the cover on its own."
1588
+ },
1589
+ {
1590
+ "name": "accept",
1591
+ "type": "Accept",
1592
+ "required": false,
1593
+ "default": "{ 'image/*': [] }",
1594
+ "description": "The file types the add tile takes, as react-dropzone's accept map. Defaults to every image type."
1595
+ },
1596
+ {
1597
+ "name": "disabled",
1598
+ "type": "boolean",
1599
+ "required": false,
1600
+ "default": "false",
1601
+ "description": "Stops adding, dragging and every button."
1602
+ },
1603
+ {
1604
+ "name": "addLabel",
1605
+ "type": "string",
1606
+ "required": false,
1607
+ "default": "Add media",
1608
+ "description": "The accessible name of the add tile. Defaults to \"Add media\"."
1609
+ },
1610
+ {
1611
+ "name": "id",
1612
+ "type": "string",
1613
+ "required": false,
1614
+ "description": "The id of the grid."
1615
+ },
1616
+ {
1617
+ "name": "aria-label",
1618
+ "type": "string",
1619
+ "required": false,
1620
+ "description": "The accessible name of the grid. Defaults to \"Media\" when there is no aria-labelledby."
1621
+ },
1622
+ {
1623
+ "name": "aria-labelledby",
1624
+ "type": "string",
1625
+ "required": false,
1626
+ "description": "The id of the visible label that names the grid, such as a Field label."
1627
+ },
1628
+ {
1629
+ "name": "aria-describedby",
1630
+ "type": "string",
1631
+ "required": false,
1632
+ "description": "The id of help text for the grid."
1633
+ }
1634
+ ],
1635
+ "usage": {
1636
+ "whenToUse": "- The images of a product or any ordered set of images, with an add tile that opens the file picker and takes dropped files\n- The app uploads: MediaGrid reports `onAdd(files)`, `onReorder(ids)`, `onRemove(id)` and `onMakeCover(id)`, and shows each item's `progress` (0 to 100) and `error`\n- The first image is the cover; Make cover moves an image first through `onReorder`, then calls `onMakeCover`\n- Images reorder by drag (pointer, touch or keyboard) and by the Move left and Move right buttons, so no drag is needed",
1637
+ "whenNotToUse": "- A single image such as a logo or an avatar: use Avatar or a single file input\n- Files that are not images in a visual order (documents, CSV imports): use a list\n- Remove has no confirm step, because the save bar's Discard brings an image back; confirm in `onRemove` with AlertDialog when the removal is final\n- Styling: MediaGrid takes no `className` and no `style`",
1638
+ "example": "<MediaGrid\n items={media}\n onAdd={(files) => upload(files)}\n onReorder={(ids) => setMedia(ids.map((id) => media.find((m) => m.id === id)!))}\n onRemove={(id) => setMedia(media.filter((m) => m.id !== id))}\n/>"
1639
+ }
1640
+ },
1641
+ {
1642
+ "name": "MetricStrip",
1643
+ "import": "import { MetricStrip } from '@ratio-ui/core'",
1644
+ "description": "A row of metrics in one hairline container: each segment is a title, optionally with a definition, over its value.",
1645
+ "props": [
1646
+ {
1647
+ "name": "segments",
1648
+ "type": "MetricSegment[]",
1649
+ "required": true,
1650
+ "description": "The metrics, left to right."
1651
+ },
1652
+ {
1653
+ "name": "label",
1654
+ "type": "string",
1655
+ "required": false,
1656
+ "description": "The accessible name of the strip."
1657
+ }
1658
+ ],
1659
+ "usage": {
1660
+ "whenToUse": "- A few headline numbers above a listing, such as the Products metrics\n- A metric whose name needs a one-sentence definition",
1661
+ "whenNotToUse": "- A chart or a trend: this is plain values\n- Many metrics or long values: use a table",
1662
+ "example": "<MetricStrip\n label=\"Product metrics\"\n segments={[\n {\n id: 'rate',\n title: 'Average sell-through rate',\n definition: 'Share of stock sold',\n value: '9.95%',\n },\n { id: 'days', title: 'Days of inventory remaining', value: null },\n { id: 'abc', title: 'ABC product analysis', value: 0, format: 'money' },\n ]}\n/>"
1663
+ }
1664
+ },
1665
+ {
1666
+ "name": "MoneyInput",
1667
+ "import": "import { MoneyInput } from '@ratio-ui/core'",
1668
+ "description": "A money field: the currency symbol before the amount, inside one ring. It takes and gives integer paise, shows the amount grouped as India groups it with two decimals (1,23,456.50) once the field loses focus, and reads a pasted amount written with the currency symbol or with either grouping. Under a name, a form submits paise.",
1669
+ "props": [
1670
+ {
1671
+ "name": "value",
1672
+ "type": "number | null",
1673
+ "required": false,
1674
+ "description": "The amount in integer paise (12345650 is 1,23,456 rupees and 50 paise), or null for an empty field. Pass it to control the field."
1675
+ },
1676
+ {
1677
+ "name": "defaultValue",
1678
+ "type": "number | null",
1679
+ "required": false,
1680
+ "default": "null",
1681
+ "description": "The starting amount in integer paise when the field is not controlled. Defaults to empty."
1682
+ },
1683
+ {
1684
+ "name": "onValueChange",
1685
+ "type": "((paise: number | null) => void)",
1686
+ "required": false,
1687
+ "description": "Called with the amount in integer paise as it is typed, pasted or cleared (null when empty)."
1688
+ },
1689
+ {
1690
+ "name": "min",
1691
+ "type": "number",
1692
+ "required": false,
1693
+ "description": "The smallest amount in integer paise; the field moves a smaller one up to it on blur. Leave it out to allow negatives."
1694
+ },
1695
+ {
1696
+ "name": "max",
1697
+ "type": "number",
1698
+ "required": false,
1699
+ "description": "The largest amount in integer paise; the field moves a larger one down to it on blur."
1700
+ },
1701
+ {
1702
+ "name": "size",
1703
+ "type": "32 | 36",
1704
+ "required": false,
1705
+ "default": "36",
1706
+ "description": "The height in px: 36 for forms, 32 for prices inside a table. Defaults to 36."
1707
+ },
1708
+ {
1709
+ "name": "ref",
1710
+ "type": "Ref<HTMLInputElement>",
1711
+ "required": false,
1712
+ "description": "Receives the input element."
1713
+ }
1714
+ ],
1715
+ "usage": {
1716
+ "whenToUse": "- Any amount of money a person types: MRP, selling price, cost price, variant prices\n- Values stored as integer paise: `value` and `onValueChange` take and give paise, and under a `name` a form submits paise\n- A price inside a table: `size` 32\n- Amounts people paste from a spreadsheet or another page: a pasted amount with the rupee sign or either grouping is read as paise",
1717
+ "whenNotToUse": "- Showing an amount that is not edited: write it with the format module (`formatPaise`) in text\n- A number that is not money, such as a weight or a length: use UnitInput\n- A count of items: use Input with `inputMode=\"numeric\"`\n- Custom classes or inline styles: MoneyInput takes neither `className` nor `style`",
1718
+ "example": "const [mrp, setMrp] = useState<number | null>(129900)\n\n<MoneyInput aria-label=\"MRP\" value={mrp} onValueChange={setMrp} min={0} />\n<MoneyInput aria-label=\"Price of L\" size={32} defaultValue={44900} name=\"price\" />\n<MoneyInput aria-label=\"Selling price\" aria-invalid aria-describedby=\"price-error\" />"
1719
+ }
1720
+ },
1721
+ {
1722
+ "name": "PageHeader",
1723
+ "import": "import { PageHeader } from '@ratio-ui/core'",
1724
+ "description": "The top of a page: an optional back link, then an icon, the title and a status pill on the left, and the actions with an overflow menu on the right. It wraps on a phone.",
1725
+ "props": [
1726
+ {
1727
+ "name": "title",
1728
+ "type": "string",
1729
+ "required": true,
1730
+ "description": "The page title, shown as the level 1 heading."
1731
+ },
1732
+ {
1733
+ "name": "icon",
1734
+ "type": "ReactNode",
1735
+ "required": false,
1736
+ "description": "An icon or logo shown before the title."
1737
+ },
1738
+ {
1739
+ "name": "status",
1740
+ "type": "{ label: string; tone?: StatusPillTone; }",
1741
+ "required": false,
1742
+ "description": "The state of the record, one word, beside the title."
1743
+ },
1744
+ {
1745
+ "name": "backHref",
1746
+ "type": "string",
1747
+ "required": false,
1748
+ "description": "Where the back link goes. The link shows only with this. On a router, LinkProvider draws it with the app's own link."
1749
+ },
1750
+ {
1751
+ "name": "backLabel",
1752
+ "type": "string",
1753
+ "required": false,
1754
+ "default": "Back",
1755
+ "description": "The text of the back link, such as the parent page's name. Defaults to Back."
1756
+ },
1757
+ {
1758
+ "name": "actions",
1759
+ "type": "ReactNode",
1760
+ "required": false,
1761
+ "description": "Buttons on the right, such as Export and Add product."
1762
+ },
1763
+ {
1764
+ "name": "menuItems",
1765
+ "type": "PageHeaderMenuItem[]",
1766
+ "required": false,
1767
+ "description": "Entries of the overflow menu. The More actions button shows only when there is at least one."
1768
+ }
1769
+ ],
1770
+ "usage": {
1771
+ "whenToUse": "- The top of a page: Products, Product detail\n- A title with an icon, a one-word status, a back link, actions and an overflow menu",
1772
+ "whenNotToUse": "- A heading inside a panel: use Text\n- More than one main action: keep one main Button, the rest secondary or in the overflow menu",
1773
+ "example": "<PageHeader\n title=\"Blue shirt\"\n status={{ label: 'Active', tone: 'success' }}\n backHref=\"/products\"\n backLabel=\"Products\"\n actions={<Button>Save</Button>}\n menuItems={[\n { label: 'Duplicate', onSelect: duplicate },\n { label: 'Delete', onSelect: remove, destructive: true },\n ]}\n/>"
1774
+ }
1775
+ },
1776
+ {
1777
+ "name": "Pagination",
1778
+ "import": "import { Pagination } from '@ratio-ui/core'",
1779
+ "description": "The footer under a table: Previous and Next arrows and the range on show, written \"1 – 20 of 84 results\". An arrow is dimmed to 50% and disabled (aria-disabled, still focusable) when there is no page that way. The parent owns the page: it passes `page` and updates it in `onPageChange`.",
1780
+ "props": [
1781
+ {
1782
+ "name": "page",
1783
+ "type": "number",
1784
+ "required": true,
1785
+ "description": "The page being shown, counting from 1."
1786
+ },
1787
+ {
1788
+ "name": "pageSize",
1789
+ "type": "number",
1790
+ "required": true,
1791
+ "description": "How many results one page holds."
1792
+ },
1793
+ {
1794
+ "name": "total",
1795
+ "type": "number",
1796
+ "required": true,
1797
+ "description": "How many results there are in all, across every page."
1798
+ },
1799
+ {
1800
+ "name": "onPageChange",
1801
+ "type": "(page: number) => void",
1802
+ "required": true,
1803
+ "description": "Called with the page to show when Previous or Next is pressed."
1804
+ }
1805
+ ],
1806
+ "usage": {
1807
+ "whenToUse": "- The footer under a table or list that has more than one page of results\n- When the parent knows the page, the page size and the total",
1808
+ "whenNotToUse": "- Numbered page links or jump to page: this is Previous and Next only\n- Infinite scroll or a load more button\n- Styling: Pagination takes no `className` and no `style`",
1809
+ "example": "const [page, setPage] = useState(1)\n\n<Pagination page={page} pageSize={20} total={84} onPageChange={setPage} />"
1810
+ }
1811
+ },
1812
+ {
1813
+ "name": "Popover",
1814
+ "import": "import { Popover } from '@ratio-ui/core'",
1815
+ "description": "A panel that opens from a trigger and holds more than text, such as a list of notifications. Put a PopoverTrigger and a PopoverContent inside it.",
1816
+ "props": [],
1817
+ "usage": {
1818
+ "whenToUse": "- A small panel that opens from a button and holds more than a sentence, such as the notifications list\n- Content that people read or act on, and that they close with Escape or by clicking away",
1819
+ "whenNotToUse": "- A one-line hint: use Tooltip, or Definition for a term\n- A choice of actions: use DropdownMenu\n- A task that needs the whole screen's attention: use Dialog",
1820
+ "example": "<Popover>\n <PopoverTrigger>Notifications</PopoverTrigger>\n <PopoverContent>\n <PopoverHeader>\n <PopoverTitle>Notifications</PopoverTitle>\n <PopoverDescription>No new notifications</PopoverDescription>\n </PopoverHeader>\n </PopoverContent>\n</Popover>"
1821
+ },
1822
+ "parts": [
1823
+ {
1824
+ "name": "PopoverContent",
1825
+ "import": "import { PopoverContent } from '@ratio-ui/core'",
1826
+ "description": "The popover panel: a white panel with a 1px outline and no shadow, 288px wide. It opens below the trigger by default.",
1827
+ "props": [
1828
+ {
1829
+ "name": "render",
1830
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, PopoverPopupState>",
1831
+ "required": false,
1832
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
1833
+ }
1834
+ ]
1835
+ },
1836
+ {
1837
+ "name": "PopoverDescription",
1838
+ "import": "import { PopoverDescription } from '@ratio-ui/core'",
1839
+ "description": "The supporting text under the title, in the muted colour.",
1840
+ "props": [
1841
+ {
1842
+ "name": "render",
1843
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, PopoverDescriptionState>",
1844
+ "required": false,
1845
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
1846
+ }
1847
+ ]
1848
+ },
1849
+ {
1850
+ "name": "PopoverHeader",
1851
+ "import": "import { PopoverHeader } from '@ratio-ui/core'",
1852
+ "description": "Groups a title and a description at the top of the panel.",
1853
+ "props": []
1854
+ },
1855
+ {
1856
+ "name": "PopoverTitle",
1857
+ "import": "import { PopoverTitle } from '@ratio-ui/core'",
1858
+ "description": "The title of the panel; it names the dialog for screen readers.",
1859
+ "props": [
1860
+ {
1861
+ "name": "render",
1862
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, PopoverTitleState>",
1863
+ "required": false,
1864
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
1865
+ }
1866
+ ]
1867
+ },
1868
+ {
1869
+ "name": "PopoverTrigger",
1870
+ "import": "import { PopoverTrigger } from '@ratio-ui/core'",
1871
+ "description": "The element that opens the popover on click or Enter. It renders a button unless render says otherwise.",
1872
+ "props": [
1873
+ {
1874
+ "name": "handle",
1875
+ "type": "PopoverHandle<unknown>",
1876
+ "required": false,
1877
+ "description": "A handle to associate the trigger with a popover."
1878
+ },
1879
+ {
1880
+ "name": "payload",
1881
+ "type": "unknown",
1882
+ "required": false,
1883
+ "description": "A payload to pass to the popover when it is opened."
1884
+ },
1885
+ {
1886
+ "name": "render",
1887
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, PopoverTriggerState>",
1888
+ "required": false,
1889
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
1890
+ },
1891
+ {
1892
+ "name": "delay",
1893
+ "type": "number",
1894
+ "required": false,
1895
+ "default": "300",
1896
+ "description": "How long to wait before the popover may be opened on hover. Specified in milliseconds.\n\nRequires the `openOnHover` prop."
1897
+ },
1898
+ {
1899
+ "name": "closeDelay",
1900
+ "type": "number",
1901
+ "required": false,
1902
+ "default": "0",
1903
+ "description": "How long to wait before closing the popover that was opened on hover. Specified in milliseconds.\n\nRequires the `openOnHover` prop."
1904
+ },
1905
+ {
1906
+ "name": "openOnHover",
1907
+ "type": "boolean",
1908
+ "required": false,
1909
+ "default": "false",
1910
+ "description": "Whether the popover should also open when the trigger is hovered."
1911
+ }
1912
+ ]
1913
+ }
1914
+ ]
1915
+ },
1916
+ {
1917
+ "name": "SaveBar",
1918
+ "import": "import { SaveBar } from '@ratio-ui/core'",
1919
+ "description": "The bar that appears under the top bar when a form is dirty: a message, Discard and Save. It renders in flow, so the page (AppShell) decides where it sits. While saving, Save is disabled; Discard only calls onDiscard.",
1920
+ "props": [
1921
+ {
1922
+ "name": "dirty",
1923
+ "type": "boolean",
1924
+ "required": true,
1925
+ "description": "Whether the form has unsaved changes. The bar renders nothing when false."
1926
+ },
1927
+ {
1928
+ "name": "saving",
1929
+ "type": "boolean",
1930
+ "required": false,
1931
+ "default": "false",
1932
+ "description": "True while the save is running: Save is disabled and reads the saving label."
1933
+ },
1934
+ {
1935
+ "name": "onSave",
1936
+ "type": "() => void",
1937
+ "required": true,
1938
+ "description": "Called by Save."
1939
+ },
1940
+ {
1941
+ "name": "onDiscard",
1942
+ "type": "() => void",
1943
+ "required": true,
1944
+ "description": "Called by Discard. The bar asks nothing itself: confirm in the caller if you need to."
1945
+ },
1946
+ {
1947
+ "name": "message",
1948
+ "type": "ReactNode",
1949
+ "required": false,
1950
+ "default": "Unsaved changes",
1951
+ "description": "The message. Defaults to \"Unsaved changes\"."
1952
+ },
1953
+ {
1954
+ "name": "saveLabel",
1955
+ "type": "ReactNode",
1956
+ "required": false,
1957
+ "default": "Save",
1958
+ "description": "The Save label. Defaults to \"Save\"."
1959
+ },
1960
+ {
1961
+ "name": "savingLabel",
1962
+ "type": "ReactNode",
1963
+ "required": false,
1964
+ "default": "Saving…",
1965
+ "description": "The Save label while saving. Defaults to \"Saving…\"."
1966
+ },
1967
+ {
1968
+ "name": "discardLabel",
1969
+ "type": "ReactNode",
1970
+ "required": false,
1971
+ "default": "Discard",
1972
+ "description": "The Discard label. Defaults to \"Discard\"."
1973
+ },
1974
+ {
1975
+ "name": "ref",
1976
+ "type": "Ref<HTMLDivElement>",
1977
+ "required": false,
1978
+ "description": "Receives the rendered div."
1979
+ }
1980
+ ],
1981
+ "usage": {
1982
+ "whenToUse": "- Under the top bar of a form page, when the form has unsaved changes: \"Unsaved changes\", Discard, Save\n- Drive `dirty` from the form's dirty state and `saving` from the pending save",
1983
+ "whenNotToUse": "- A confirmation: Discard only calls `onDiscard`; open an AlertDialog yourself if the loss needs confirming\n- Per-field saving: save on change or use the field's own action",
1984
+ "example": "```tsx\n<SaveBar dirty={form.isDirty} saving={form.isSaving} onSave={form.save} onDiscard={form.reset} />\n```\n\nIt renders in flow and nothing when not dirty; the app shell decides where it sits. While saving, Save is disabled."
1985
+ }
1986
+ },
1987
+ {
1988
+ "name": "SearchField",
1989
+ "import": "import { SearchField } from '@ratio-ui/core'",
1990
+ "description": "The search field above a listing: a search icon, the input and a clear button that appears once there is text. Escape clears it too. The field reports every change; the debounce stays in the app.",
1991
+ "props": [
1992
+ {
1993
+ "name": "label",
1994
+ "type": "string",
1995
+ "required": true,
1996
+ "description": "The accessible name of the field, such as \"Search products\". Required because the field has no visible label."
1997
+ },
1998
+ {
1999
+ "name": "value",
2000
+ "type": "string",
2001
+ "required": false,
2002
+ "description": "The text in the field. Pass it with onValueChange to control the field."
2003
+ },
2004
+ {
2005
+ "name": "defaultValue",
2006
+ "type": "string",
2007
+ "required": false,
2008
+ "default": "",
2009
+ "description": "The starting text of an uncontrolled field."
2010
+ },
2011
+ {
2012
+ "name": "onValueChange",
2013
+ "type": "((value: string) => void)",
2014
+ "required": false,
2015
+ "description": "Called with the new text on every change and when the field is cleared. It is not debounced: debounce in the app."
2016
+ },
2017
+ {
2018
+ "name": "size",
2019
+ "type": "32 | 36",
2020
+ "required": false,
2021
+ "default": "36",
2022
+ "description": "The height in px: 36 above a listing, 32 inside a table. Defaults to 36."
2023
+ },
2024
+ {
2025
+ "name": "ref",
2026
+ "type": "Ref<HTMLInputElement>",
2027
+ "required": false,
2028
+ "description": "Receives the input element."
2029
+ }
2030
+ ],
2031
+ "usage": {
2032
+ "whenToUse": "- The search above a listing or inside a table, where typing narrows the rows\n- A field that needs a clear button and Escape to clear",
2033
+ "whenNotToUse": "- A search across the whole product from the top bar: use GlobalSearch\n- A field that must wait before searching: debounce in the app, the field reports every keystroke",
2034
+ "example": "<SearchField label=\"Search products\" placeholder=\"Search by name, SKU\" value={query} onValueChange={setQuery} />"
2035
+ }
2036
+ },
2037
+ {
2038
+ "name": "Select",
2039
+ "import": "import { Select } from '@ratio-ui/core'",
2040
+ "description": "A choice from a fixed list. Pass items ({ label, value }[]) so the trigger shows the label of the chosen value. Put a SelectTrigger and a SelectContent inside it.",
2041
+ "props": [],
2042
+ "usage": {
2043
+ "whenToUse": "- A choice from a short, fixed list in a form, such as a GST rate (0, 5, 12, 18, 28%), a return window or a status\n- A field in a table cell: `size={32}` on the trigger\n- A form row with a label, hint, error or required mark: put the Select inside a Field with a function child (see below)",
2044
+ "whenNotToUse": "- A long list, or one the user searches: use a combobox\n- Free text or several values (tags): use Input or TagInput\n- A menu of actions: use DropdownMenu\n- Custom classes or inline styles: no part takes `className` or `style`",
2045
+ "example": "```tsx\nconst rates = [\n { label: '0%', value: '0' },\n { label: '18%', value: '18' },\n]\n\n<Select items={rates} defaultValue=\"18\">\n <SelectTrigger aria-label=\"GST rate\">\n <SelectValue placeholder=\"Choose a rate\" />\n </SelectTrigger>\n <SelectContent>\n <SelectGroup>\n <SelectLabel>Rates</SelectLabel>\n <SelectItem value=\"0\">0%</SelectItem>\n <SelectItem value=\"18\">18%</SelectItem>\n </SelectGroup>\n </SelectContent>\n</Select>\n```\n\nPass `items` to `Select` so the trigger shows the label, not the raw value."
2046
+ },
2047
+ "parts": [
2048
+ {
2049
+ "name": "SelectContent",
2050
+ "import": "import { SelectContent } from '@ratio-ui/core'",
2051
+ "description": "The list: the popover look (8px radius, outline, no shadow, 6px 0 padding), as wide as the trigger. It opens below the trigger.",
2052
+ "props": [
2053
+ {
2054
+ "name": "render",
2055
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, SelectPopupState>",
2056
+ "required": false,
2057
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
2058
+ }
2059
+ ]
2060
+ },
2061
+ {
2062
+ "name": "SelectGroup",
2063
+ "import": "import { SelectGroup } from '@ratio-ui/core'",
2064
+ "description": "Groups related options under a SelectLabel.",
2065
+ "props": [
2066
+ {
2067
+ "name": "render",
2068
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, SelectGroupState>",
2069
+ "required": false,
2070
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
2071
+ }
2072
+ ]
2073
+ },
2074
+ {
2075
+ "name": "SelectItem",
2076
+ "import": "import { SelectItem } from '@ratio-ui/core'",
2077
+ "description": "One option. The highlighted one has the accent background, the chosen one a check at its end, a disabled one is dimmed and cannot be chosen.",
2078
+ "props": [
2079
+ {
2080
+ "name": "render",
2081
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, SelectItemState>",
2082
+ "required": false,
2083
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
2084
+ }
2085
+ ]
2086
+ },
2087
+ {
2088
+ "name": "SelectLabel",
2089
+ "import": "import { SelectLabel } from '@ratio-ui/core'",
2090
+ "description": "The heading of a group, in the muted colour.",
2091
+ "props": [
2092
+ {
2093
+ "name": "render",
2094
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, SelectGroupLabelState>",
2095
+ "required": false,
2096
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
2097
+ }
2098
+ ]
2099
+ },
2100
+ {
2101
+ "name": "SelectSeparator",
2102
+ "import": "import { SelectSeparator } from '@ratio-ui/core'",
2103
+ "description": "A hairline between groups.",
2104
+ "props": [
2105
+ {
2106
+ "name": "render",
2107
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, SelectSeparatorState>",
2108
+ "required": false,
2109
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
2110
+ }
2111
+ ]
2112
+ },
2113
+ {
2114
+ "name": "SelectTrigger",
2115
+ "import": "import { SelectTrigger } from '@ratio-ui/core'",
2116
+ "description": "The closed field: the chosen value (or the placeholder) and a chevron at the right. Put a SelectValue inside it.",
2117
+ "props": [
2118
+ {
2119
+ "name": "render",
2120
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, SelectTriggerState>",
2121
+ "required": false,
2122
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
2123
+ },
2124
+ {
2125
+ "name": "size",
2126
+ "type": "32 | 36",
2127
+ "required": false,
2128
+ "default": "36",
2129
+ "description": "The height in px: 36 for forms, 32 inside a table. Defaults to 36."
2130
+ }
2131
+ ]
2132
+ },
2133
+ {
2134
+ "name": "SelectValue",
2135
+ "import": "import { SelectValue } from '@ratio-ui/core'",
2136
+ "description": "Shows the label of the chosen option inside the trigger, or the placeholder while nothing is chosen.",
2137
+ "props": [
2138
+ {
2139
+ "name": "render",
2140
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, SelectValueState>",
2141
+ "required": false,
2142
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
2143
+ }
2144
+ ]
2145
+ }
2146
+ ]
2147
+ },
2148
+ {
2149
+ "name": "SideNav",
2150
+ "import": "import { SideNav } from '@ratio-ui/core'",
2151
+ "description": "The 240px side navigation of the admin: an account switcher, blocks of rows with nested groups, counts and collapsible labels, and a footer pinned at the bottom. On a phone it becomes a drawer: inside AppShell the menu button in TopBar opens it and choosing a row closes it; used alone, the caller opens it with `open` and closes it in `onOpenChange`.",
2152
+ "props": [
2153
+ {
2154
+ "name": "label",
2155
+ "type": "string",
2156
+ "required": true,
2157
+ "description": "The accessible name of the navigation landmark."
2158
+ },
2159
+ {
2160
+ "name": "sections",
2161
+ "type": "SideNavSection[]",
2162
+ "required": true,
2163
+ "description": "The blocks of rows, top to bottom."
2164
+ },
2165
+ {
2166
+ "name": "active",
2167
+ "type": "string",
2168
+ "required": false,
2169
+ "description": "The id of the current row. It is marked and its group opens."
2170
+ },
2171
+ {
2172
+ "name": "onSelect",
2173
+ "type": "((id: string) => void)",
2174
+ "required": false,
2175
+ "description": "Called with the id of the row pressed."
2176
+ },
2177
+ {
2178
+ "name": "account",
2179
+ "type": "SideNavAccount",
2180
+ "required": false,
2181
+ "description": "The account switcher shown above the rows."
2182
+ },
2183
+ {
2184
+ "name": "footer",
2185
+ "type": "ReactNode",
2186
+ "required": false,
2187
+ "description": "Content pinned at the bottom, such as Settings."
2188
+ },
2189
+ {
2190
+ "name": "open",
2191
+ "type": "boolean",
2192
+ "required": false,
2193
+ "description": "On a phone, whether the drawer is open."
2194
+ },
2195
+ {
2196
+ "name": "onOpenChange",
2197
+ "type": "((open: boolean) => void)",
2198
+ "required": false,
2199
+ "description": "On a phone, called when the drawer asks to open or close."
2200
+ }
2201
+ ],
2202
+ "usage": {
2203
+ "whenToUse": "- The admin's left navigation: grouped links with nested items, counts and a pinned footer\n- A phone layout where the same navigation slides in as a drawer",
2204
+ "whenNotToUse": "- Switching between views inside one page: use Tabs or FilterTabs\n- A menu of actions: use DropdownMenu",
2205
+ "example": "<SideNav\n label=\"Main\"\n active=\"orders\"\n onSelect={setView}\n sections={[{ id: 'main', items: [{ id: 'home', label: 'Home', icon: 'nav-home' }, { id: 'orders', label: 'Orders', count: '12' }] }]}\n footer={<a href=\"/settings\">Settings</a>}\n/>"
2206
+ }
2207
+ },
2208
+ {
2209
+ "name": "Skeleton",
2210
+ "import": "import { Skeleton } from '@ratio-ui/core'",
2211
+ "description": "A static placeholder for content that is still loading on a first load. It is hidden from assistive technology: the container that holds it says it is busy. Its fill and animation come from the loading tokens, so it does not move.",
2212
+ "props": [
2213
+ {
2214
+ "name": "shape",
2215
+ "type": "\"block\" | \"circle\" | \"line\"",
2216
+ "required": false,
2217
+ "default": "line",
2218
+ "description": "The shape of the placeholder. Defaults to line. A line and a block fill the width of their parent."
2219
+ }
2220
+ ],
2221
+ "usage": {
2222
+ "whenToUse": "- A first load, where the shape of the content is known: a line for text, a block for a tile, a circle for an avatar\n- Under real headings in a table, so the columns stay put while the rows load",
2223
+ "whenNotToUse": "- A refetch of content already on screen: keep the content and show the thin progress bar\n- A wait with no known shape: use Spinner\n- A load that has failed or returned nothing: use EmptyState",
2224
+ "example": "<Skeleton shape=\"line\" />\n<Skeleton shape=\"circle\" />"
2225
+ }
2226
+ },
2227
+ {
2228
+ "name": "Spinner",
2229
+ "import": "import { Spinner } from '@ratio-ui/core'",
2230
+ "description": "A small ring for a wait with no known shape. It is static until the designer answers question 5: its motion is the --rt-spinner-animation token, none for now, so the ring is drawn from the progress colours and the status role says what it means.",
2231
+ "props": [
2232
+ {
2233
+ "name": "label",
2234
+ "type": "string",
2235
+ "required": false,
2236
+ "default": "Loading",
2237
+ "description": "The accessible name, announced to assistive technology. Defaults to Loading."
2238
+ }
2239
+ ],
2240
+ "usage": {
2241
+ "whenToUse": "- A short wait where the shape of the result is not known, such as a button that saves or a panel that opens",
2242
+ "whenNotToUse": "- A first load of a list or table: use Skeleton rows\n- A refetch of content on screen: use the thin progress bar",
2243
+ "example": "<Spinner label=\"Saving\" />"
2244
+ }
2245
+ },
2246
+ {
2247
+ "name": "Stack",
2248
+ "import": "import { Stack } from '@ratio-ui/core'",
2249
+ "description": "Lays its children out in a column with a fixed space between them. It takes no className and no style: the gap is a step of the design system's spacing scale, and the layout is the only styling it has.",
2250
+ "props": [
2251
+ {
2252
+ "name": "gap",
2253
+ "type": "0 | 1 | 2 | 4 | 6 | 7 | 8 | 11 | 12 | 14 | 24 | 32 | 80",
2254
+ "required": true,
2255
+ "description": "The space between children, a step of the design system's scale in px: 0, 1, 2, 4, 6, 7, 8, 11, 12, 14, 24, 32 or 80."
2256
+ },
2257
+ {
2258
+ "name": "align",
2259
+ "type": "\"center\" | \"end\" | \"start\" | \"stretch\"",
2260
+ "required": false,
2261
+ "default": "stretch",
2262
+ "description": "How children line up across the stack, which is the horizontal axis. Defaults to stretch, which fills the width."
2263
+ },
2264
+ {
2265
+ "name": "as",
2266
+ "type": "\"aside\" | \"div\" | \"fieldset\" | \"footer\" | \"form\" | \"header\" | \"li\" | \"main\" | \"nav\" | \"ol\" | \"section\" | \"ul\"",
2267
+ "required": false,
2268
+ "default": "div",
2269
+ "description": "The element to render. Defaults to div; use a list, landmark or form element where the content is one."
2270
+ },
2271
+ {
2272
+ "name": "ref",
2273
+ "type": "Ref<HTMLElement>",
2274
+ "required": false,
2275
+ "description": "Receives the rendered element."
2276
+ }
2277
+ ],
2278
+ "usage": {
2279
+ "whenToUse": "- Children one under another with a fixed space between them: form fields, the parts of a card, page sections\n- A gap taken from the design system's spacing scale: 0, 1, 2, 4, 6, 7, 8, 11, 12, 14, 24, 32 or 80 px\n- A column that is also a list or a landmark: `as` renders `ul`, `ol`, `li`, `nav`, `header`, `footer`, `main`, `aside`, `section`, `form` or `fieldset` instead of `div`",
2280
+ "whenNotToUse": "- Children side by side: use Inline\n- Space around one child or at the edge of a container: Stack spaces siblings only, and has no padding or margin\n- A gap that is not on the scale: 10 or 16 is a type error, so use the nearest step\n- Styling: Stack takes no `className` and no `style`\n- A list without markers: `as=\"ul\"` and `as=\"ol\"` leave the browser's list markers as they are",
2281
+ "example": "<Stack gap={12}>\n <div>Settlement summary</div>\n <Stack gap={4} align=\"start\">\n <div>Paid out every weekday</div>\n <div>Next payout on Monday</div>\n </Stack>\n</Stack>"
2282
+ }
2283
+ },
2284
+ {
2285
+ "name": "StatusPill",
2286
+ "import": "import { StatusPill } from '@ratio-ui/core'",
2287
+ "description": "A 20px pill with a 4px radius and a 1px border that names the state of a row, such as a product's status in a listing.",
2288
+ "props": [
2289
+ {
2290
+ "name": "tone",
2291
+ "type": "\"neutral\" | \"success\" | \"warning\"",
2292
+ "required": false,
2293
+ "default": "neutral",
2294
+ "description": "The colour of the pill. Defaults to neutral."
2295
+ },
2296
+ {
2297
+ "name": "ref",
2298
+ "type": "Ref<HTMLSpanElement>",
2299
+ "required": false,
2300
+ "description": "Receives the rendered span."
2301
+ }
2302
+ ],
2303
+ "usage": {
2304
+ "whenToUse": "- The state of a row or record in a listing: Draft, Active, Hidden, Low, Pending\n- A single-word status, with success for a good state and warning for one that needs a look",
2305
+ "whenNotToUse": "- A count or a tag that is not a status\n- A state that needs its own colour: the design system has two tones, and neutral is plain text until question 3 is answered\n- A button or a link: the pill is not interactive",
2306
+ "example": "<StatusPill tone=\"success\">Active</StatusPill>"
2307
+ }
2308
+ },
2309
+ {
2310
+ "name": "SummaryChips",
2311
+ "import": "import { SummaryChips } from '@ratio-ui/core'",
2312
+ "description": "A row of 20px chips that summarise values, such as the ones a collapsed section still shows.",
2313
+ "props": [
2314
+ {
2315
+ "name": "items",
2316
+ "type": "readonly SummaryChip[]",
12
2317
  "required": true,
13
- "description": "Which icon to draw, by name."
2318
+ "description": "The chips, in order. Nothing is rendered when empty."
14
2319
  },
15
2320
  {
16
- "name": "size",
17
- "type": "8 | 12 | 16",
2321
+ "name": "ref",
2322
+ "type": "Ref<HTMLUListElement>",
18
2323
  "required": false,
19
- "default": "12",
20
- "description": "The square box in px: 8, 12 or 16. Defaults to 12, the size inside controls."
2324
+ "description": "Receives the rendered list."
2325
+ }
2326
+ ],
2327
+ "usage": {
2328
+ "whenToUse": "- A collapsed section that still shows its values: \"Cost price · HSN code · Includes GST Yes\"\n- A short read-only summary of a record's settings",
2329
+ "whenNotToUse": "- Values the person can edit or remove: use a tag input\n- A status of a row: use StatusPill",
2330
+ "example": "<SummaryChips items={[{ label: 'Cost price', value: '₹450.00' }, { label: 'Includes GST', value: 'Yes' }]} />"
2331
+ }
2332
+ },
2333
+ {
2334
+ "name": "Switch",
2335
+ "import": "import { Switch } from '@ratio-ui/core'",
2336
+ "description": "An on/off switch, 32px by 18px. Off it is drawn with the control hairline; on it is filled in the primary colour. In forced-colours mode the thumb keeps a system-colour outline, and its side shows the state. The thumb jumps to the other end with no slide, and colours fade over 120ms.",
2337
+ "props": [],
2338
+ "usage": {
2339
+ "whenToUse": "- An on/off setting that takes effect as soon as it changes, such as \"Inventory tracked\" or \"Cash on delivery\"\n- Give it an `aria-label`, or a label that points to it",
2340
+ "whenNotToUse": "- A choice that is sent with a form and several others: use Checkbox\n- Custom classes or inline styles: Switch takes no `className` and no `style`",
2341
+ "example": "<Switch aria-label=\"Inventory tracked\" defaultChecked />"
2342
+ }
2343
+ },
2344
+ {
2345
+ "name": "Table",
2346
+ "import": "import { Table } from '@ratio-ui/core'",
2347
+ "description": "A data table in the design system's Listing look: a 36px heading row over 48px rows split by 1px hairlines, 12px uppercase headings and a fill on the row under the pointer. It scrolls sideways inside its own box when the columns do not fit. Compose it from TableHeader, TableBody, TableRow, TableHead and TableCell.",
2348
+ "props": [],
2349
+ "usage": {
2350
+ "whenToUse": "- Rows of records with the same columns: products, orders, customers\n- A list that needs headings, right-aligned numbers and a hover fill\n- Static content composed by hand; for sorting, selection and paging use DataTable",
2351
+ "whenNotToUse": "- Layout: a table is for data, so use Stack and Inline to arrange a page\n- A single record's fields: use a definition list or a card\n- Styling: the parts take no `className` and no `style`",
2352
+ "example": "<Table>\n <TableHeader>\n <TableRow>\n <TableHead>Product</TableHead>\n <TableHead numeric>Stock</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n <TableRow>\n <TableCell>RYZE - 2mg</TableCell>\n <TableCell numeric>1200</TableCell>\n </TableRow>\n </TableBody>\n</Table>"
2353
+ },
2354
+ "parts": [
2355
+ {
2356
+ "name": "TableBody",
2357
+ "import": "import { TableBody } from '@ratio-ui/core'",
2358
+ "description": "The data rows of a table. Its rows take the hover fill.",
2359
+ "props": []
21
2360
  },
22
2361
  {
23
- "name": "tone",
24
- "type": "\"current\" | \"icon\" | \"primary\" | \"subdued\"",
2362
+ "name": "TableCaption",
2363
+ "import": "import { TableCaption } from '@ratio-ui/core'",
2364
+ "description": "The table's title for assistive technology, shown under the table in the subdued colour.",
2365
+ "props": []
2366
+ },
2367
+ {
2368
+ "name": "TableCell",
2369
+ "import": "import { TableCell } from '@ratio-ui/core'",
2370
+ "description": "One cell. Text stays on one line; a numeric cell is right aligned with tabular figures so digits line up.",
2371
+ "props": [
2372
+ {
2373
+ "name": "numeric",
2374
+ "type": "boolean",
2375
+ "required": false,
2376
+ "default": "false",
2377
+ "description": "Aligns the cell to the right with tabular figures, for a number. Defaults to false, which aligns it to the left."
2378
+ }
2379
+ ]
2380
+ },
2381
+ {
2382
+ "name": "TableFooter",
2383
+ "import": "import { TableFooter } from '@ratio-ui/core'",
2384
+ "description": "The summary rows at the foot of a table, such as totals. Its rows take no hover fill.",
2385
+ "props": []
2386
+ },
2387
+ {
2388
+ "name": "TableHead",
2389
+ "import": "import { TableHead } from '@ratio-ui/core'",
2390
+ "description": "A column heading: 12px uppercase text in the subdued colour. It is a column header unless `scope` says otherwise.",
2391
+ "props": [
2392
+ {
2393
+ "name": "numeric",
2394
+ "type": "boolean",
2395
+ "required": false,
2396
+ "default": "false",
2397
+ "description": "Aligns the heading to the right, over a column of numbers. Defaults to false, which aligns it to the left."
2398
+ }
2399
+ ]
2400
+ },
2401
+ {
2402
+ "name": "TableHeader",
2403
+ "import": "import { TableHeader } from '@ratio-ui/core'",
2404
+ "description": "The heading rows of a table. Its rows take no hover fill.",
2405
+ "props": []
2406
+ },
2407
+ {
2408
+ "name": "TableRow",
2409
+ "import": "import { TableRow } from '@ratio-ui/core'",
2410
+ "description": "One row, 48px high (36px in the heading), with a hairline under it. A body row is filled when the pointer is over it or it is selected.",
2411
+ "props": [
2412
+ {
2413
+ "name": "selected",
2414
+ "type": "boolean",
2415
+ "required": false,
2416
+ "default": "false",
2417
+ "description": "Keeps the hover fill on the row to show it is chosen, for example when its checkbox is ticked. Defaults to false."
2418
+ }
2419
+ ]
2420
+ }
2421
+ ]
2422
+ },
2423
+ {
2424
+ "name": "TableLoader",
2425
+ "import": "import { TableLoader } from '@ratio-ui/core'",
2426
+ "description": "What a table shows on its first load: the real headings over static skeleton rows, and a count line such as \"72 of 1000 products\". The table is marked busy and the fake rows are hidden from screen readers. Use it for the first load only; later loads keep the old rows.",
2427
+ "props": [
2428
+ {
2429
+ "name": "columns",
2430
+ "type": "TableLoaderColumn[]",
2431
+ "required": true,
2432
+ "description": "The columns, in order."
2433
+ },
2434
+ {
2435
+ "name": "rows",
2436
+ "type": "number",
25
2437
  "required": false,
26
- "default": "current",
27
- "description": "The colour. current, the default, takes the colour of the text around the icon; the other tones set their own."
2438
+ "default": "5",
2439
+ "description": "How many placeholder rows to draw. Defaults to 5."
28
2440
  },
29
2441
  {
30
- "name": "label",
2442
+ "name": "shown",
2443
+ "type": "number",
2444
+ "required": true,
2445
+ "description": "How many results the first page will hold, for the count line."
2446
+ },
2447
+ {
2448
+ "name": "total",
2449
+ "type": "number",
2450
+ "required": true,
2451
+ "description": "How many results there are in all."
2452
+ },
2453
+ {
2454
+ "name": "noun",
31
2455
  "type": "string",
32
- "required": false,
33
- "description": "The accessible name. With a label the icon is an image that assistive technology announces under that name. Without one it is decoration, hidden from assistive technology, and the text beside it has to say what it means."
2456
+ "required": true,
2457
+ "description": "What the results are called, in the plural: \"products\"."
34
2458
  }
35
2459
  ],
36
2460
  "usage": {
37
- "whenToUse": "- A glyph beside a label inside a control: the plus on a main button, the magnifier in a search field, the export tray on a secondary button. Use size 12\n- The side navigation: the nav glyphs at size 16, and the disclosure caret (nav-chevron or nav-caret-down, one glyph under two names) at size 8\n- Pagination: chevron-left and chevron-right at size 8\n- A mark that Tabler supplies: check, x, alert-triangle and info-circle. They are drawn with a 1.5px line at every size, so they sit beside the design system's own glyphs without looking heavier\n- Meaning that lives only in the icon: pass label. The icon is then an image that assistive technology announces under that name. Without a label it is decoration and is hidden from assistive technology\n- Colour: tone current, the default, takes the colour of the text around the icon. icon is the design system's icon ink, subdued is its subdued text ink and primary is the accent\n- Chevron geometry (design question 9): the design system has no vector for its pagination chevrons, only a 1.5px CSS caret in an 8px box, so chevron-left and chevron-right stand in. Each is the outline of a line 1.5 units wide with round ends, two legs at 45 degrees, each 2.5 units across and 2.5 down, through (2.75, 1.5), (5.25, 4) and (2.75, 6.5) in an 8 unit box. They scale like every filled glyph, so draw them at size 8, and expect them to be replaced when the designer sends real vectors",
38
- "whenNotToUse": "- Decoration that adds no meaning. An icon next to text that already says the same thing is hidden from assistive technology, so leave the label off, and leave the icon out when it adds nothing\n- An icon the registry does not have. Components accept no className or style, and icons come from the registry only, so a new one is a registry entry: the design system's glyph first, Tabler for the rest\n- An icon-only control without a name. Pass label, or give the control its own accessible name\n- A size other than 8, 12 or 16, or a colour other than the four tones. There is no red, no destructive colour and no third accent: an error is marked with the alert-triangle glyph beside its message, not with a colour\n- Emoji, or unicode arrows and symbols, as icons. Inter's web subsets do not hold them and the design system bans them",
39
- "example": "import { Icon } from '@ratio-ui/core'\n\n// Beside text that names it: decoration, hidden from assistive technology\n<button type=\"button\">\n <Icon name=\"plus\" />\n Add product\n</button>\n\n// Alone: the label is the accessible name\n<Icon name=\"search\" tone=\"icon\" label=\"Search\" />\n\n// Pagination chevrons are drawn for an 8px box\n<Icon name=\"chevron-right\" size={8} />"
2461
+ "whenToUse": "- The first load of a list: it shows the real headings over static skeleton rows, with a count such as \"72 of 1000 products\"\n- When the column headings are known before the data arrives",
2462
+ "whenNotToUse": "- Later loads such as a new page or sort: keep the old rows and show them as busy\n- Content that is not a table: use Skeleton on its own\n- Styling: it takes no `className` and no `style`",
2463
+ "example": "<TableLoader\n aria-label=\"Products\"\n columns={[{ id: 'name', header: 'Product' }, { id: 'stock', header: 'Stock', numeric: true }]}\n shown={72}\n total={1000}\n noun=\"products\"\n/>"
40
2464
  }
41
2465
  },
42
2466
  {
43
- "name": "Inline",
44
- "import": "import { Inline } from '@ratio-ui/core'",
45
- "description": "Lays its children out in a row with a fixed space between them. It takes no className and no style: the gap is a step of the design system's spacing scale, and the layout is the only styling it has.",
2467
+ "name": "Tabs",
2468
+ "import": "import { Tabs } from '@ratio-ui/core'",
2469
+ "description": "Sections of one page, switched by an underlined row of tabs: ABC analysis, the sections of a settings page. Holds the selected value, uncontrolled with defaultValue or controlled with value and onValueChange. To filter a list use FilterTabs, which looks like the design system's chip tab.",
46
2470
  "props": [
47
2471
  {
48
- "name": "gap",
49
- "type": "0 | 1 | 2 | 4 | 6 | 7 | 8 | 11 | 12 | 14 | 24 | 32 | 80",
2472
+ "name": "render",
2473
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, TabsRootState>",
2474
+ "required": false,
2475
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
2476
+ }
2477
+ ],
2478
+ "usage": {
2479
+ "whenToUse": "- Sections of one page that share a heading, such as the steps of an ABC analysis or the parts of a settings page\n- Content that is shown one section at a time, with the section named by an underlined tab\n- A row of tabs whose panels sit on the same page: the tab list and the panels are tied by value",
2480
+ "whenNotToUse": "- A single choice that filters a list, such as All, Active and Inactive above a table: use FilterTabs\n- Moving to another page: use a link\n- Steps that must be done in order: use a wizard",
2481
+ "example": "<Tabs defaultValue=\"rules\">\n <TabsList aria-label=\"Sections\">\n <TabsTrigger value=\"rules\">Rules</TabsTrigger>\n <TabsTrigger value=\"history\">History</TabsTrigger>\n </TabsList>\n <TabsContent value=\"rules\">Rules</TabsContent>\n <TabsContent value=\"history\">History</TabsContent>\n</Tabs>"
2482
+ },
2483
+ "parts": [
2484
+ {
2485
+ "name": "TabsContent",
2486
+ "import": "import { TabsContent } from '@ratio-ui/core'",
2487
+ "description": "The panel of the tab with the same value; only the selected one is shown.",
2488
+ "props": [
2489
+ {
2490
+ "name": "render",
2491
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, TabsPanelState>",
2492
+ "required": false,
2493
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
2494
+ }
2495
+ ]
2496
+ },
2497
+ {
2498
+ "name": "TabsList",
2499
+ "import": "import { TabsList } from '@ratio-ui/core'",
2500
+ "description": "The row of tabs, with a hairline under it. Give it an aria-label that names the sections.",
2501
+ "props": [
2502
+ {
2503
+ "name": "render",
2504
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, TabsListState>",
2505
+ "required": false,
2506
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
2507
+ }
2508
+ ]
2509
+ },
2510
+ {
2511
+ "name": "TabsTrigger",
2512
+ "import": "import { TabsTrigger } from '@ratio-ui/core'",
2513
+ "description": "One tab: 36px tall, with a 2px blue underline and a blue semibold label when selected. A disabled tab is at 50%.",
2514
+ "props": [
2515
+ {
2516
+ "name": "render",
2517
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, TabsTabState>",
2518
+ "required": false,
2519
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
2520
+ }
2521
+ ]
2522
+ }
2523
+ ]
2524
+ },
2525
+ {
2526
+ "name": "TagInput",
2527
+ "import": "import { TagInput } from '@ratio-ui/core'",
2528
+ "description": "Several values as chips in one field in the input's look. The picker offers the existing values (typing filters them), a small field below adds a new one on Enter, and a chip goes with its remove button or with Backspace in the empty picker. Base UI's combobox cannot accept a value it does not list, which is why new values have their own field.",
2529
+ "props": [
2530
+ {
2531
+ "name": "options",
2532
+ "type": "readonly string[]",
50
2533
  "required": true,
51
- "description": "The space between children, a step of the design system's scale in px: 0, 1, 2, 4, 6, 7, 8, 11, 12, 14, 24, 32 or 80."
2534
+ "description": "The existing values the picker offers, such as S, M, L, XL and XXL."
52
2535
  },
53
2536
  {
54
- "name": "align",
55
- "type": "\"baseline\" | \"center\" | \"end\" | \"start\" | \"stretch\"",
2537
+ "name": "value",
2538
+ "type": "readonly string[]",
56
2539
  "required": false,
57
- "default": "center",
58
- "description": "How children line up across the row, which is the vertical axis. Defaults to center."
2540
+ "description": "The chosen values, in order, when the owner holds the state."
59
2541
  },
60
2542
  {
61
- "name": "justify",
62
- "type": "\"between\" | \"center\" | \"end\" | \"start\"",
2543
+ "name": "defaultValue",
2544
+ "type": "readonly string[]",
63
2545
  "required": false,
64
- "default": "start",
65
- "description": "How children spread along the row, which is the horizontal axis. Defaults to start."
2546
+ "description": "The values chosen at first, when the component holds the state."
66
2547
  },
67
2548
  {
68
- "name": "wrap",
2549
+ "name": "onValueChange",
2550
+ "type": "((value: string[]) => void)",
2551
+ "required": false,
2552
+ "description": "Called with every new list of values: after a pick, an added value or a removal."
2553
+ },
2554
+ {
2555
+ "name": "creatable",
69
2556
  "type": "boolean",
70
2557
  "required": false,
71
- "default": "false",
72
- "description": "Whether children move to a new line when the row is full. Defaults to false, which keeps one line."
2558
+ "default": "true",
2559
+ "description": "Shows a small field that adds a typed value on Enter. Defaults to true."
73
2560
  },
74
2561
  {
75
- "name": "as",
76
- "type": "\"aside\" | \"div\" | \"fieldset\" | \"footer\" | \"form\" | \"header\" | \"li\" | \"main\" | \"nav\" | \"ol\" | \"section\" | \"ul\"",
2562
+ "name": "addLabel",
2563
+ "type": "string",
77
2564
  "required": false,
78
- "default": "div",
79
- "description": "The element to render. Defaults to div; use a list, landmark or form element where the content is one."
2565
+ "default": "Add value",
2566
+ "description": "The placeholder and accessible name of the small field. Defaults to \"Add value\"."
80
2567
  },
81
2568
  {
82
- "name": "ref",
83
- "type": "Ref<HTMLElement>",
2569
+ "name": "placeholder",
2570
+ "type": "string",
84
2571
  "required": false,
85
- "description": "Receives the rendered element."
86
- }
87
- ],
88
- "usage": {
89
- "whenToUse": "- Children side by side with a fixed space between them: a toolbar, a row of tags, a label next to a value\n- Children that spread along the row (`justify`: start, center, end or between) or move to the next line when the row is full (`wrap`)\n- A gap taken from the design system's spacing scale: 0, 1, 2, 4, 6, 7, 8, 11, 12, 14, 24, 32 or 80 px\n- A row that is also a list or a landmark: `as` renders `ul`, `ol`, `li`, `nav`, `header`, `footer`, `main`, `aside`, `section`, `form` or `fieldset` instead of `div`",
90
- "whenNotToUse": "- Children one under another: use Stack\n- Columns that have to line up from one row to the next: Inline lays out one row at a time, and wave 0 has no grid\n- Space around one child or at the edge of a container: Inline spaces siblings only, and has no padding or margin\n- Styling: Inline takes no `className` and no `style`\n- A list without markers: `as=\"ul\"` and `as=\"ol\"` leave the browser's list markers as they are",
91
- "example": "<Inline gap={8} justify=\"between\" wrap>\n <div>Orders</div>\n <Inline gap={4}>\n <div>Export</div>\n <div>Filter</div>\n </Inline>\n</Inline>"
92
- }
93
- },
94
- {
95
- "name": "Stack",
96
- "import": "import { Stack } from '@ratio-ui/core'",
97
- "description": "Lays its children out in a column with a fixed space between them. It takes no className and no style: the gap is a step of the design system's spacing scale, and the layout is the only styling it has.",
98
- "props": [
2572
+ "description": "The placeholder of the picker, shown while nothing is chosen."
2573
+ },
99
2574
  {
100
- "name": "gap",
101
- "type": "0 | 1 | 2 | 4 | 6 | 7 | 8 | 11 | 12 | 14 | 24 | 32 | 80",
102
- "required": true,
103
- "description": "The space between children, a step of the design system's scale in px: 0, 1, 2, 4, 6, 7, 8, 11, 12, 14, 24, 32 or 80."
2575
+ "name": "emptyText",
2576
+ "type": "string",
2577
+ "required": false,
2578
+ "default": "No matching values",
2579
+ "description": "The line the list shows when no value matches the typed text. Defaults to \"No matching values\"."
104
2580
  },
105
2581
  {
106
- "name": "align",
107
- "type": "\"center\" | \"end\" | \"start\" | \"stretch\"",
2582
+ "name": "name",
2583
+ "type": "string",
108
2584
  "required": false,
109
- "default": "stretch",
110
- "description": "How children line up across the stack, which is the horizontal axis. Defaults to stretch, which fills the width."
2585
+ "description": "The form field name: one hidden input per chosen value is submitted under it."
111
2586
  },
112
2587
  {
113
- "name": "as",
114
- "type": "\"aside\" | \"div\" | \"fieldset\" | \"footer\" | \"form\" | \"header\" | \"li\" | \"main\" | \"nav\" | \"ol\" | \"section\" | \"ul\"",
2588
+ "name": "disabled",
2589
+ "type": "boolean",
115
2590
  "required": false,
116
- "default": "div",
117
- "description": "The element to render. Defaults to div; use a list, landmark or form element where the content is one."
2591
+ "description": "Dims the component and stops every change."
2592
+ },
2593
+ {
2594
+ "name": "id",
2595
+ "type": "string",
2596
+ "required": false,
2597
+ "description": "The id of the picker's input, which a label points to."
2598
+ },
2599
+ {
2600
+ "name": "aria-label",
2601
+ "type": "string",
2602
+ "required": false,
2603
+ "description": "The accessible name of the picker, when no label points to it."
2604
+ },
2605
+ {
2606
+ "name": "aria-labelledby",
2607
+ "type": "string",
2608
+ "required": false,
2609
+ "description": "The id of the element that names the picker."
2610
+ },
2611
+ {
2612
+ "name": "aria-describedby",
2613
+ "type": "string",
2614
+ "required": false,
2615
+ "description": "The ids of the hint or error that describe the component."
2616
+ },
2617
+ {
2618
+ "name": "aria-invalid",
2619
+ "type": "boolean | \"true\" | \"false\"",
2620
+ "required": false,
2621
+ "description": "Marks the values as invalid, which draws the error ring."
2622
+ },
2623
+ {
2624
+ "name": "aria-required",
2625
+ "type": "boolean | \"true\" | \"false\"",
2626
+ "required": false,
2627
+ "description": "Marks the values as required."
118
2628
  },
119
2629
  {
120
2630
  "name": "ref",
121
- "type": "Ref<HTMLElement>",
2631
+ "type": "Ref<HTMLInputElement>",
122
2632
  "required": false,
123
- "description": "Receives the rendered element."
2633
+ "description": "Receives the picker's input element."
124
2634
  }
125
2635
  ],
126
2636
  "usage": {
127
- "whenToUse": "- Children one under another with a fixed space between them: form fields, the parts of a card, page sections\n- A gap taken from the design system's spacing scale: 0, 1, 2, 4, 6, 7, 8, 11, 12, 14, 24, 32 or 80 px\n- A column that is also a list or a landmark: `as` renders `ul`, `ol`, `li`, `nav`, `header`, `footer`, `main`, `aside`, `section`, `form` or `fieldset` instead of `div`",
128
- "whenNotToUse": "- Children side by side: use Inline\n- Space around one child or at the edge of a container: Stack spaces siblings only, and has no padding or margin\n- A gap that is not on the scale: 10 or 16 is a type error, so use the nearest step\n- Styling: Stack takes no `className` and no `style`\n- A list without markers: `as=\"ul\"` and `as=\"ol\"` leave the browser's list markers as they are",
129
- "example": "<Stack gap={12}>\n <div>Settlement summary</div>\n <Stack gap={4} align=\"start\">\n <div>Paid out every weekday</div>\n <div>Next payout on Monday</div>\n </Stack>\n</Stack>"
2637
+ "whenToUse": "- Several values in one field, such as a product's tags or a variant option's values (S, M, L, XL, XXL)\n- Picking from existing values, with typing to filter them, and adding a new one in the small field below (Enter adds it)\n- Inside a Field: the label, hint, error and required mark land on the picker",
2638
+ "whenNotToUse": "- One value from a fixed list: use Select\n- A read-only summary of values: use SummaryChips\n- Only existing values and no new ones: keep TagInput and set `creatable={false}`\n- Custom classes or inline styles: TagInput takes no `className` and no `style`",
2639
+ "example": "```tsx\nconst [sizes, setSizes] = useState(['S', 'M'])\n\n<Field label=\"Size\" error={errors.size}>\n <TagInput options={['S', 'M', 'L', 'XL', 'XXL']} value={sizes} onValueChange={setSizes} placeholder=\"Choose sizes\" />\n</Field>\n```\n\nA chip goes with its remove button, or with Backspace in the empty picker. A new value is trimmed, takes the spelling of an existing value that differs only in letter case, and is not added twice. Base UI's combobox cannot accept a value it does not list, so new values have their own field."
130
2640
  }
131
2641
  },
132
2642
  {
@@ -174,6 +2684,253 @@
174
2684
  "whenNotToUse": "- Text in a button: buttons take the design system's two button styles, which are not Text variants\n- A colour that is not a tone: `color` is a type error, and so are `className` and `style`\n- A style that is not in the type scale: `variant=\"h2\"` is a type error, so pick the nearest of the 13\n- Space around text: use Stack or Inline\n- Several lines cut at a number of lines: `truncate` keeps one line",
175
2685
  "example": "<Text as=\"h1\" variant=\"h1\">Settlements</Text>\n<Text tone=\"subdued\">Paid out every weekday</Text>"
176
2686
  }
2687
+ },
2688
+ {
2689
+ "name": "Textarea",
2690
+ "import": "import { Textarea } from '@ratio-ui/core'",
2691
+ "description": "A multi-line text field in the input's look: 8px radius, 1px ring that turns blue on focus, 10px 12px padding, 88px tall (64px compact).",
2692
+ "props": [
2693
+ {
2694
+ "name": "size",
2695
+ "type": "64 | 88",
2696
+ "required": false,
2697
+ "default": "88",
2698
+ "description": "The height in px: 88 by default, 64 compact. The field can still be dragged taller."
2699
+ },
2700
+ {
2701
+ "name": "ref",
2702
+ "type": "Ref<HTMLTextAreaElement>",
2703
+ "required": false,
2704
+ "description": "Receives the textarea element."
2705
+ }
2706
+ ],
2707
+ "usage": {
2708
+ "whenToUse": "- A multi-line text field such as a product description: 88px tall by default, `size={64}` for a compact one\n- A field with an error: set `aria-invalid` and the ring changes, and put the error message beside it in text",
2709
+ "whenNotToUse": "- A single line of text: use Input\n- Custom classes or inline styles: Textarea takes no `className` and no `style`",
2710
+ "example": "<Textarea aria-label=\"Description\" placeholder=\"Describe the product\" />\n<Textarea aria-label=\"Note\" size={64} />"
2711
+ }
2712
+ },
2713
+ {
2714
+ "name": "Toaster",
2715
+ "import": "import { Toaster } from '@ratio-ui/core'",
2716
+ "description": "Mounts the toast viewport, bottom right, once for the app. Messages from toast() appear in it as plain panels with the ring outline, stacked, with no entrance animation.",
2717
+ "props": [
2718
+ {
2719
+ "name": "closeLabel",
2720
+ "type": "string",
2721
+ "required": false,
2722
+ "default": "Close notification",
2723
+ "description": "The accessible name of each close button. Defaults to Close notification."
2724
+ }
2725
+ ],
2726
+ "usage": {
2727
+ "whenToUse": "- Telling the user what happened after an action: a saved product, a started sync, a failed request\n- One of three kinds: `toast.success`, `toast.info` or `toast.error`; each is announced to screen readers",
2728
+ "whenNotToUse": "- A decision the user must make: use a Dialog\n- A message that has to stay on the page: use a banner\n- Field validation: show it on the field",
2729
+ "example": "<Toaster />\n\ntoast.success('Product saved')\ntoast.error('Could not save', { description: 'Try again' })"
2730
+ }
2731
+ },
2732
+ {
2733
+ "name": "Tooltip",
2734
+ "import": "import { Tooltip } from '@ratio-ui/core'",
2735
+ "description": "A short hint for a control or term, shown on hover and keyboard focus. Put a TooltipTrigger and a TooltipContent inside it.",
2736
+ "props": [],
2737
+ "usage": {
2738
+ "whenToUse": "- A short hint for an icon-only control or an \"i\" icon: it opens on hover and on keyboard focus\n- Extra detail that is not needed to finish the task, one short sentence\n- Wrap the app once in TooltipProvider so the tooltips share their open delay",
2739
+ "whenNotToUse": "- A term in running text that explains itself: use Definition, which keyboard and touch users can open\n- Anything with a link, button or form inside: use Popover\n- Information that touch users need: a tooltip does not open on touch, so show the text on the screen",
2740
+ "example": "<TooltipProvider>\n <Tooltip>\n <TooltipTrigger>Settlement</TooltipTrigger>\n <TooltipContent>Paid out every weekday</TooltipContent>\n </Tooltip>\n</TooltipProvider>"
2741
+ },
2742
+ "parts": [
2743
+ {
2744
+ "name": "TooltipContent",
2745
+ "import": "import { TooltipContent } from '@ratio-ui/core'",
2746
+ "description": "The tooltip text: a dark panel with light text, no shadow and no animation. It sits above the trigger by default.",
2747
+ "props": [
2748
+ {
2749
+ "name": "render",
2750
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, TooltipPopupState>",
2751
+ "required": false,
2752
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
2753
+ }
2754
+ ]
2755
+ },
2756
+ {
2757
+ "name": "TooltipProvider",
2758
+ "import": "import { TooltipProvider } from '@ratio-ui/core'",
2759
+ "description": "Shares the open delay between the tooltips inside it. Wrap the app, or a group of tooltips, once. The delay defaults to 0 ms.",
2760
+ "props": []
2761
+ },
2762
+ {
2763
+ "name": "TooltipTrigger",
2764
+ "import": "import { TooltipTrigger } from '@ratio-ui/core'",
2765
+ "description": "The element that opens the tooltip on hover and focus. It renders a button unless render says otherwise.",
2766
+ "props": [
2767
+ {
2768
+ "name": "render",
2769
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, TooltipTriggerState>",
2770
+ "required": false,
2771
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
2772
+ }
2773
+ ]
2774
+ }
2775
+ ]
2776
+ },
2777
+ {
2778
+ "name": "TopBar",
2779
+ "import": "import { TopBar } from '@ratio-ui/core'",
2780
+ "description": "The bar across the top of every page: logo, search, actions, the store switcher and the account. It is 60px high on a desktop and 52px on a phone, with a hairline under it. The parts are slots, so the bar does not depend on their components. Inside an AppShell with a side nav it shows a menu button on phones, which opens the side nav drawer.",
2781
+ "props": [
2782
+ {
2783
+ "name": "logo",
2784
+ "type": "ReactNode",
2785
+ "required": false,
2786
+ "description": "The logo, usually an image. When it is left out the bar shows the text wordmark instead (--rt-wordmark)."
2787
+ },
2788
+ {
2789
+ "name": "wordmarkLabel",
2790
+ "type": "string",
2791
+ "required": false,
2792
+ "default": "Ratio",
2793
+ "description": "The accessible name of the text wordmark, shown when there is no logo. Defaults to Ratio."
2794
+ },
2795
+ {
2796
+ "name": "menuLabel",
2797
+ "type": "string",
2798
+ "required": false,
2799
+ "default": "Open navigation menu",
2800
+ "description": "The accessible name of the phone menu button that opens the side navigation. Defaults to Open navigation menu."
2801
+ },
2802
+ {
2803
+ "name": "search",
2804
+ "type": "ReactNode",
2805
+ "required": false,
2806
+ "description": "The global search, for example GlobalSearch. It takes the free space in the middle."
2807
+ },
2808
+ {
2809
+ "name": "actions",
2810
+ "type": "ReactNode",
2811
+ "required": false,
2812
+ "description": "The buttons at the right, such as help and notifications."
2813
+ },
2814
+ {
2815
+ "name": "storeSwitcher",
2816
+ "type": "ReactNode",
2817
+ "required": false,
2818
+ "description": "The store switcher, for example StoreSwitcher. It sits at the right, after the actions."
2819
+ },
2820
+ {
2821
+ "name": "account",
2822
+ "type": "ReactNode",
2823
+ "required": false,
2824
+ "description": "The account at the far right, such as an Avatar or a menu of the signed-in person."
2825
+ }
2826
+ ],
2827
+ "usage": {
2828
+ "whenToUse": "- The bar across the top of the admin: logo, global search, actions, the store switcher and the account\n- Inside AppShell's `topBar` slot",
2829
+ "whenNotToUse": "- A heading for one page: use PageHeader\n- Navigation between sections: use SideNav",
2830
+ "example": "<TopBar\n search={<GlobalSearch />}\n actions={<Button variant=\"ghost\">Help</Button>}\n storeSwitcher={<StoreSwitcher stores={stores} storeId={storeId} onStoreChange={setStoreId} />}\n account={<Avatar name=\"Naman Gupta\" />}\n/>"
2831
+ },
2832
+ "parts": [
2833
+ {
2834
+ "name": "StoreSwitcher",
2835
+ "import": "import { StoreSwitcher } from '@ratio-ui/core'",
2836
+ "description": "The store switcher of TopBar: a square avatar, the store name and a chevron that open a menu of the stores the app passes. On a phone only the avatar shows. Pass it to TopBar's `storeSwitcher` slot.",
2837
+ "props": [
2838
+ {
2839
+ "name": "stores",
2840
+ "type": "StoreOption[]",
2841
+ "required": true,
2842
+ "description": "The stores the person can switch between, as the app passes them."
2843
+ },
2844
+ {
2845
+ "name": "storeId",
2846
+ "type": "string",
2847
+ "required": true,
2848
+ "description": "The id of the current store."
2849
+ },
2850
+ {
2851
+ "name": "onStoreChange",
2852
+ "type": "((id: string) => void)",
2853
+ "required": false,
2854
+ "description": "Called with the id of the store chosen."
2855
+ },
2856
+ {
2857
+ "name": "label",
2858
+ "type": "string",
2859
+ "required": false,
2860
+ "default": "Switch store",
2861
+ "description": "The caption of its menu. Defaults to Switch store. The button is named by the store name."
2862
+ }
2863
+ ]
2864
+ }
2865
+ ]
2866
+ },
2867
+ {
2868
+ "name": "UnitInput",
2869
+ "import": "import { UnitInput } from '@ratio-ui/core'",
2870
+ "description": "A number field with its unit after it, inside one ring: a weight in kg, a length in cm. It takes and gives a number, written without thousands separators as counts are, and keeps at most `decimals` decimals.",
2871
+ "props": [
2872
+ {
2873
+ "name": "unit",
2874
+ "type": "string",
2875
+ "required": true,
2876
+ "description": "The unit after the number, such as kg or cm. Screen readers read it as the field's description."
2877
+ },
2878
+ {
2879
+ "name": "value",
2880
+ "type": "number | null",
2881
+ "required": false,
2882
+ "description": "The number, or null for an empty field. Pass it to control the field."
2883
+ },
2884
+ {
2885
+ "name": "defaultValue",
2886
+ "type": "number | null",
2887
+ "required": false,
2888
+ "description": "The starting number when the field is not controlled. Defaults to empty."
2889
+ },
2890
+ {
2891
+ "name": "onValueChange",
2892
+ "type": "((value: number | null) => void)",
2893
+ "required": false,
2894
+ "description": "Called with the number as it is typed or cleared (null when empty), and with the rounded number on blur."
2895
+ },
2896
+ {
2897
+ "name": "decimals",
2898
+ "type": "number",
2899
+ "required": false,
2900
+ "default": "3",
2901
+ "description": "The most decimals the number keeps; the field rounds to them on blur. Defaults to 3 (grams in kg)."
2902
+ },
2903
+ {
2904
+ "name": "min",
2905
+ "type": "number",
2906
+ "required": false,
2907
+ "description": "The smallest number; the field moves a smaller one up to it on blur."
2908
+ },
2909
+ {
2910
+ "name": "max",
2911
+ "type": "number",
2912
+ "required": false,
2913
+ "description": "The largest number; the field moves a larger one down to it on blur."
2914
+ },
2915
+ {
2916
+ "name": "size",
2917
+ "type": "32 | 36",
2918
+ "required": false,
2919
+ "default": "36",
2920
+ "description": "The height in px: 36 for forms, 32 for fields inside a table. Defaults to 36."
2921
+ },
2922
+ {
2923
+ "name": "ref",
2924
+ "type": "Ref<HTMLInputElement>",
2925
+ "required": false,
2926
+ "description": "Receives the input element."
2927
+ }
2928
+ ],
2929
+ "usage": {
2930
+ "whenToUse": "- A number with a unit after it: weight in kg, length in cm, a lead time in days\n- Values stored as numbers: `value` and `onValueChange` take and give a number, null when empty\n- A field inside a table: `size` 32",
2931
+ "whenNotToUse": "- Money: use MoneyInput, which takes and gives paise\n- A unit before the number, or an icon: use InputGroup with an addon\n- Text that is not a number: use Input\n- Custom classes or inline styles: UnitInput takes neither `className` nor `style`",
2932
+ "example": "const [weight, setWeight] = useState<number | null>(0.24)\n\n<UnitInput aria-label=\"Weight\" unit=\"kg\" value={weight} onValueChange={setWeight} min={0} />\n<UnitInput aria-label=\"Length\" unit=\"cm\" decimals={0} defaultValue={30} name=\"length\" />"
2933
+ }
177
2934
  }
178
2935
  ]
179
2936
  }