@olympusoss/canvas 4.0.0 → 5.0.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 (297) hide show
  1. package/README.md +108 -0
  2. package/package.json +14 -3
  3. package/src/atoms/avatar/avatar.md +185 -0
  4. package/src/atoms/avatar/avatar.styles.ts +48 -0
  5. package/src/atoms/avatar/avatar.tsx +99 -0
  6. package/src/atoms/badge/badge.md +237 -0
  7. package/src/atoms/badge/badge.styles.ts +79 -0
  8. package/src/atoms/badge/badge.tsx +86 -0
  9. package/src/atoms/breadcrumb/breadcrumb.md +233 -0
  10. package/src/atoms/breadcrumb/breadcrumb.styles.ts +40 -0
  11. package/src/atoms/breadcrumb/breadcrumb.tsx +130 -0
  12. package/src/atoms/button/button.android.tsx +6 -0
  13. package/src/atoms/button/button.ios.tsx +6 -0
  14. package/src/atoms/button/button.md +184 -0
  15. package/src/atoms/button/button.shared.tsx +79 -0
  16. package/src/atoms/button/button.styles.ts +152 -0
  17. package/src/atoms/button/button.tsx +6 -0
  18. package/src/atoms/button-group/button-group.android.tsx +6 -0
  19. package/src/atoms/button-group/button-group.ios.tsx +6 -0
  20. package/src/atoms/button-group/button-group.md +120 -0
  21. package/src/atoms/button-group/button-group.shared.tsx +398 -0
  22. package/src/atoms/button-group/button-group.styles.ts +483 -0
  23. package/src/atoms/button-group/button-group.tsx +6 -0
  24. package/src/atoms/checkbox/checkbox.android.tsx +6 -0
  25. package/src/atoms/checkbox/checkbox.ios.tsx +6 -0
  26. package/src/atoms/checkbox/checkbox.md +150 -0
  27. package/src/atoms/checkbox/checkbox.shared.tsx +103 -0
  28. package/src/atoms/checkbox/checkbox.styles.ts +106 -0
  29. package/src/atoms/checkbox/checkbox.tsx +6 -0
  30. package/src/atoms/combobox/combobox.android.tsx +6 -0
  31. package/src/atoms/combobox/combobox.ios.tsx +6 -0
  32. package/src/atoms/combobox/combobox.md +213 -0
  33. package/src/atoms/combobox/combobox.shared.tsx +160 -0
  34. package/src/atoms/combobox/combobox.styles.ts +270 -0
  35. package/src/atoms/combobox/combobox.tsx +6 -0
  36. package/src/atoms/divider/divider.md +140 -0
  37. package/src/atoms/divider/divider.styles.ts +35 -0
  38. package/src/atoms/divider/divider.tsx +67 -0
  39. package/src/atoms/dropdown/dropdown.android.tsx +6 -0
  40. package/src/atoms/dropdown/dropdown.ios.tsx +6 -0
  41. package/src/atoms/dropdown/dropdown.md +221 -0
  42. package/src/atoms/dropdown/dropdown.shared.tsx +190 -0
  43. package/src/atoms/dropdown/dropdown.styles.ts +233 -0
  44. package/src/atoms/dropdown/dropdown.tsx +6 -0
  45. package/src/atoms/icon/icon.md +131 -0
  46. package/src/atoms/icon/icon.styles.ts +30 -0
  47. package/src/atoms/icon/icon.tsx +328 -0
  48. package/src/atoms/index.ts +24 -0
  49. package/src/atoms/input/input.android.tsx +6 -0
  50. package/src/atoms/input/input.ios.tsx +6 -0
  51. package/src/atoms/input/input.md +118 -0
  52. package/src/atoms/input/input.shared.tsx +203 -0
  53. package/src/atoms/input/input.styles.ts +286 -0
  54. package/src/atoms/input/input.tsx +6 -0
  55. package/src/atoms/kbd/kbd.md +91 -0
  56. package/src/atoms/kbd/kbd.styles.ts +33 -0
  57. package/src/atoms/kbd/kbd.tsx +27 -0
  58. package/src/atoms/listbox/listbox.md +177 -0
  59. package/src/atoms/listbox/listbox.styles.ts +60 -0
  60. package/src/atoms/listbox/listbox.tsx +113 -0
  61. package/src/atoms/pagination/pagination.android.tsx +6 -0
  62. package/src/atoms/pagination/pagination.ios.tsx +6 -0
  63. package/src/atoms/pagination/pagination.md +133 -0
  64. package/src/atoms/pagination/pagination.shared.tsx +289 -0
  65. package/src/atoms/pagination/pagination.styles.ts +245 -0
  66. package/src/atoms/pagination/pagination.tsx +6 -0
  67. package/src/atoms/popover/popover.android.tsx +8 -0
  68. package/src/atoms/popover/popover.ios.tsx +6 -0
  69. package/src/atoms/popover/popover.md +87 -0
  70. package/src/atoms/popover/popover.shared.tsx +124 -0
  71. package/src/atoms/popover/popover.styles.ts +144 -0
  72. package/src/atoms/popover/popover.tsx +6 -0
  73. package/src/atoms/radio/radio.android.tsx +6 -0
  74. package/src/atoms/radio/radio.ios.tsx +6 -0
  75. package/src/atoms/radio/radio.md +173 -0
  76. package/src/atoms/radio/radio.shared.tsx +98 -0
  77. package/src/atoms/radio/radio.styles.ts +109 -0
  78. package/src/atoms/radio/radio.tsx +6 -0
  79. package/src/atoms/select/select.android.tsx +6 -0
  80. package/src/atoms/select/select.ios.tsx +6 -0
  81. package/src/atoms/select/select.md +156 -0
  82. package/src/atoms/select/select.shared.tsx +143 -0
  83. package/src/atoms/select/select.styles.ts +310 -0
  84. package/src/atoms/select/select.tsx +6 -0
  85. package/src/atoms/skeleton/skeleton.md +135 -0
  86. package/src/atoms/skeleton/skeleton.styles.ts +117 -0
  87. package/src/atoms/skeleton/skeleton.tsx +145 -0
  88. package/src/atoms/spinner/spinner.android.tsx +7 -0
  89. package/src/atoms/spinner/spinner.ios.tsx +7 -0
  90. package/src/atoms/spinner/spinner.md +94 -0
  91. package/src/atoms/spinner/spinner.shared.tsx +92 -0
  92. package/src/atoms/spinner/spinner.styles.tsx +115 -0
  93. package/src/atoms/spinner/spinner.tsx +7 -0
  94. package/src/atoms/switch/switch.android.tsx +6 -0
  95. package/src/atoms/switch/switch.ios.tsx +6 -0
  96. package/src/atoms/switch/switch.md +91 -0
  97. package/src/atoms/switch/switch.shared.tsx +97 -0
  98. package/src/atoms/switch/switch.styles.ts +79 -0
  99. package/src/atoms/switch/switch.tsx +6 -0
  100. package/src/atoms/textarea/textarea.android.tsx +6 -0
  101. package/src/atoms/textarea/textarea.ios.tsx +6 -0
  102. package/src/atoms/textarea/textarea.md +140 -0
  103. package/src/atoms/textarea/textarea.shared.tsx +74 -0
  104. package/src/atoms/textarea/textarea.styles.ts +116 -0
  105. package/src/atoms/textarea/textarea.tsx +6 -0
  106. package/src/atoms/tooltip/tooltip.android.tsx +6 -0
  107. package/src/atoms/tooltip/tooltip.ios.tsx +7 -0
  108. package/src/atoms/tooltip/tooltip.md +122 -0
  109. package/src/atoms/tooltip/tooltip.shared.tsx +113 -0
  110. package/src/atoms/tooltip/tooltip.styles.ts +113 -0
  111. package/src/atoms/tooltip/tooltip.tsx +6 -0
  112. package/src/atoms/typography/typography.md +330 -0
  113. package/src/atoms/typography/typography.styles.ts +95 -0
  114. package/src/atoms/typography/typography.tsx +76 -0
  115. package/src/index.ts +12 -2
  116. package/src/molecules/action-panels/action-panels.md +133 -0
  117. package/src/molecules/action-panels/action-panels.styles.ts +39 -0
  118. package/src/molecules/action-panels/action-panels.tsx +113 -0
  119. package/src/molecules/alert/alert.md +119 -0
  120. package/src/molecules/alert/alert.styles.ts +88 -0
  121. package/src/molecules/alert/alert.tsx +74 -0
  122. package/src/molecules/alert-dialog/alert-dialog.android.tsx +6 -0
  123. package/src/molecules/alert-dialog/alert-dialog.ios.tsx +6 -0
  124. package/src/molecules/alert-dialog/alert-dialog.md +177 -0
  125. package/src/molecules/alert-dialog/alert-dialog.shared.tsx +187 -0
  126. package/src/molecules/alert-dialog/alert-dialog.styles.ts +248 -0
  127. package/src/molecules/alert-dialog/alert-dialog.tsx +6 -0
  128. package/src/molecules/card/card.md +190 -0
  129. package/src/molecules/card/card.styles.ts +67 -0
  130. package/src/molecules/card/card.tsx +176 -0
  131. package/src/molecules/code-block/code-block.md +159 -0
  132. package/src/molecules/code-block/code-block.styles.ts +167 -0
  133. package/src/molecules/code-block/code-block.tsx +176 -0
  134. package/src/molecules/description-lists/description-lists.md +129 -0
  135. package/src/molecules/description-lists/description-lists.styles.ts +102 -0
  136. package/src/molecules/description-lists/description-lists.tsx +133 -0
  137. package/src/molecules/empty-state/empty-state.md +218 -0
  138. package/src/molecules/empty-state/empty-state.styles.ts +63 -0
  139. package/src/molecules/empty-state/empty-state.tsx +77 -0
  140. package/src/molecules/feeds/feeds.md +102 -0
  141. package/src/molecules/feeds/feeds.styles.ts +120 -0
  142. package/src/molecules/feeds/feeds.tsx +167 -0
  143. package/src/molecules/field/field.md +117 -0
  144. package/src/molecules/field/field.styles.ts +85 -0
  145. package/src/molecules/field/field.tsx +175 -0
  146. package/src/molecules/fieldset/fieldset.md +141 -0
  147. package/src/molecules/fieldset/fieldset.styles.ts +79 -0
  148. package/src/molecules/fieldset/fieldset.tsx +182 -0
  149. package/src/molecules/form/form.md +137 -0
  150. package/src/molecules/form/form.styles.ts +39 -0
  151. package/src/molecules/form/form.tsx +246 -0
  152. package/src/molecules/grid-lists/grid-lists.md +114 -0
  153. package/src/molecules/grid-lists/grid-lists.styles.ts +79 -0
  154. package/src/molecules/grid-lists/grid-lists.tsx +157 -0
  155. package/src/molecules/index.ts +16 -0
  156. package/src/molecules/media-objects/media-objects.md +87 -0
  157. package/src/molecules/media-objects/media-objects.styles.ts +94 -0
  158. package/src/molecules/media-objects/media-objects.tsx +128 -0
  159. package/src/molecules/stacked-lists/stacked-lists.md +116 -0
  160. package/src/molecules/stacked-lists/stacked-lists.styles.ts +111 -0
  161. package/src/molecules/stacked-lists/stacked-lists.tsx +195 -0
  162. package/src/molecules/stats/stats.md +166 -0
  163. package/src/molecules/stats/stats.styles.ts +91 -0
  164. package/src/molecules/stats/stats.tsx +88 -0
  165. package/src/organisms/calendar/calendar.android.tsx +6 -0
  166. package/src/organisms/calendar/calendar.ios.tsx +6 -0
  167. package/src/organisms/calendar/calendar.md +114 -0
  168. package/src/organisms/calendar/calendar.shared.tsx +146 -0
  169. package/src/organisms/calendar/calendar.styles.ts +315 -0
  170. package/src/organisms/calendar/calendar.tsx +6 -0
  171. package/src/organisms/charts/charts.md +326 -0
  172. package/src/organisms/charts/charts.styles.ts +135 -0
  173. package/src/organisms/charts/charts.tsx +124 -0
  174. package/src/organisms/command/command.md +117 -0
  175. package/src/organisms/command/command.styles.ts +179 -0
  176. package/src/organisms/command/command.tsx +164 -0
  177. package/src/organisms/data-table/data-table.md +182 -0
  178. package/src/organisms/data-table/data-table.styles.ts +103 -0
  179. package/src/organisms/data-table/data-table.tsx +105 -0
  180. package/src/organisms/dialog/dialog.android.tsx +6 -0
  181. package/src/organisms/dialog/dialog.ios.tsx +6 -0
  182. package/src/organisms/dialog/dialog.md +271 -0
  183. package/src/organisms/dialog/dialog.shared.tsx +230 -0
  184. package/src/organisms/dialog/dialog.styles.ts +272 -0
  185. package/src/organisms/dialog/dialog.tsx +6 -0
  186. package/src/organisms/filter-panel/filter-panel.md +116 -0
  187. package/src/organisms/filter-panel/filter-panel.styles.ts +83 -0
  188. package/src/organisms/filter-panel/filter-panel.tsx +91 -0
  189. package/src/organisms/index.ts +13 -0
  190. package/src/organisms/navbars/navbars.android.tsx +6 -0
  191. package/src/organisms/navbars/navbars.ios.tsx +6 -0
  192. package/src/organisms/navbars/navbars.md +144 -0
  193. package/src/organisms/navbars/navbars.shared.tsx +137 -0
  194. package/src/organisms/navbars/navbars.styles.ts +251 -0
  195. package/src/organisms/navbars/navbars.tsx +6 -0
  196. package/src/organisms/overlays/overlays.android.tsx +6 -0
  197. package/src/organisms/overlays/overlays.ios.tsx +6 -0
  198. package/src/organisms/overlays/overlays.md +123 -0
  199. package/src/organisms/overlays/overlays.shared.tsx +175 -0
  200. package/src/organisms/overlays/overlays.styles.ts +309 -0
  201. package/src/organisms/overlays/overlays.tsx +6 -0
  202. package/src/organisms/row-menu/row-menu.android.tsx +6 -0
  203. package/src/organisms/row-menu/row-menu.ios.tsx +6 -0
  204. package/src/organisms/row-menu/row-menu.md +102 -0
  205. package/src/organisms/row-menu/row-menu.shared.tsx +105 -0
  206. package/src/organisms/row-menu/row-menu.styles.ts +262 -0
  207. package/src/organisms/row-menu/row-menu.tsx +6 -0
  208. package/src/organisms/sidebar/sidebar.android.tsx +6 -0
  209. package/src/organisms/sidebar/sidebar.ios.tsx +6 -0
  210. package/src/organisms/sidebar/sidebar.md +188 -0
  211. package/src/organisms/sidebar/sidebar.shared.tsx +167 -0
  212. package/src/organisms/sidebar/sidebar.styles.ts +262 -0
  213. package/src/organisms/sidebar/sidebar.tsx +6 -0
  214. package/src/organisms/stepper/stepper.android.tsx +6 -0
  215. package/src/organisms/stepper/stepper.ios.tsx +6 -0
  216. package/src/organisms/stepper/stepper.md +150 -0
  217. package/src/organisms/stepper/stepper.shared.tsx +158 -0
  218. package/src/organisms/stepper/stepper.styles.ts +280 -0
  219. package/src/organisms/stepper/stepper.tsx +6 -0
  220. package/src/organisms/tabs/tabs.android.tsx +6 -0
  221. package/src/organisms/tabs/tabs.ios.tsx +6 -0
  222. package/src/organisms/tabs/tabs.md +127 -0
  223. package/src/organisms/tabs/tabs.shared.tsx +281 -0
  224. package/src/organisms/tabs/tabs.styles.ts +398 -0
  225. package/src/organisms/tabs/tabs.tsx +6 -0
  226. package/src/style/color.ts +17 -0
  227. package/src/style/index.ts +14 -0
  228. package/src/style/primitives.ts +26 -0
  229. package/src/style/responsive.ts +45 -0
  230. package/src/style/shadow.ts +21 -0
  231. package/src/style/theme.tsx +56 -0
  232. package/src/style/tokens.ts +487 -0
  233. package/src/theme.ts +21 -0
  234. package/styles/canvas.css +128 -67
  235. package/tsconfig.json +4 -2
  236. package/src/cn.ts +0 -3
  237. package/styles/base.css +0 -17
  238. package/styles/components/alert.css +0 -66
  239. package/styles/components/app-shell.css +0 -46
  240. package/styles/components/avatar.css +0 -15
  241. package/styles/components/badge.css +0 -83
  242. package/styles/components/breadcrumb.css +0 -35
  243. package/styles/components/button-group.css +0 -23
  244. package/styles/components/button.css +0 -107
  245. package/styles/components/calendar.css +0 -73
  246. package/styles/components/card.css +0 -58
  247. package/styles/components/checkbox.css +0 -55
  248. package/styles/components/code-block.css +0 -18
  249. package/styles/components/combobox.css +0 -75
  250. package/styles/components/command.css +0 -94
  251. package/styles/components/data-table.css +0 -142
  252. package/styles/components/dialog.css +0 -72
  253. package/styles/components/dropdown.css +0 -54
  254. package/styles/components/empty-state.css +0 -17
  255. package/styles/components/field.css +0 -27
  256. package/styles/components/filter-panel.css +0 -58
  257. package/styles/components/form.css +0 -27
  258. package/styles/components/icon.css +0 -8
  259. package/styles/components/input-group.css +0 -45
  260. package/styles/components/input.css +0 -56
  261. package/styles/components/kbd.css +0 -15
  262. package/styles/components/page-header.css +0 -52
  263. package/styles/components/pagination.css +0 -48
  264. package/styles/components/popover.css +0 -14
  265. package/styles/components/radio.css +0 -28
  266. package/styles/components/row-menu.css +0 -69
  267. package/styles/components/section-card.css +0 -49
  268. package/styles/components/select.css +0 -57
  269. package/styles/components/separator.css +0 -32
  270. package/styles/components/sheet.css +0 -70
  271. package/styles/components/sidebar.css +0 -146
  272. package/styles/components/skeleton.css +0 -32
  273. package/styles/components/spinner.css +0 -26
  274. package/styles/components/stat-card.css +0 -71
  275. package/styles/components/stepper.css +0 -63
  276. package/styles/components/switch.css +0 -45
  277. package/styles/components/tabs.css +0 -40
  278. package/styles/components/textarea.css +0 -31
  279. package/styles/components/toast.css +0 -95
  280. package/styles/components/tooltip.css +0 -53
  281. package/styles/components/topbar.css +0 -24
  282. package/styles/components/typography.css +0 -105
  283. package/styles/patterns/backdrops.css +0 -35
  284. package/styles/patterns/density.css +0 -66
  285. package/styles/patterns/focus.css +0 -38
  286. package/styles/patterns/glass.css +0 -85
  287. package/styles/patterns/high-contrast.css +0 -70
  288. package/styles/patterns/reduced-motion.css +0 -12
  289. package/styles/patterns/scrollbar.css +0 -10
  290. package/styles/reset.css +0 -89
  291. package/styles/tokens/colors.css +0 -106
  292. package/styles/tokens/motion.css +0 -33
  293. package/styles/tokens/radius.css +0 -10
  294. package/styles/tokens/shadows.css +0 -35
  295. package/styles/tokens/spacing.css +0 -19
  296. package/styles/tokens/typography.css +0 -6
  297. package/styles/tokens/z-index.css +0 -12
@@ -0,0 +1,177 @@
1
+ # Listboxes
2
+
3
+ A custom (non-native) select: single or multi-select, optional avatars or icons per option, and a checkmark on the chosen items. Reach for it when a native select can't show rich options; prefer a native select for simple short lists.
4
+
5
+ ## Usage
6
+
7
+ ```tsx
8
+ <Listbox
9
+ items={[
10
+ { label: "Backend", selected: true },
11
+ { label: "Frontend", selected: false },
12
+ { label: "Design", selected: false },
13
+ { label: "Platform", selected: false },
14
+ { label: "Security", selected: false }
15
+ ]}
16
+ bordered
17
+ />
18
+ ```
19
+
20
+ ## Variants
21
+
22
+ ### Mode - multi
23
+
24
+ ```tsx
25
+ <Listbox
26
+ items={[
27
+ { label: "Backend", selected: true },
28
+ { label: "Frontend", selected: false },
29
+ { label: "Design", selected: true },
30
+ { label: "Platform", selected: false },
31
+ { label: "Security", selected: false }
32
+ ]}
33
+ multi
34
+ bordered
35
+ />
36
+ ```
37
+
38
+ ### Size - sm
39
+
40
+ ```tsx
41
+ <Listbox
42
+ items={[
43
+ { label: "Backend", selected: true },
44
+ { label: "Frontend", selected: false },
45
+ { label: "Design", selected: false },
46
+ { label: "Platform", selected: false },
47
+ { label: "Security", selected: false }
48
+ ]}
49
+ bordered
50
+ small
51
+ />
52
+ ```
53
+
54
+ ### Size - lg
55
+
56
+ ```tsx
57
+ <Listbox
58
+ items={[
59
+ { label: "Backend", selected: true },
60
+ { label: "Frontend", selected: false },
61
+ { label: "Design", selected: false },
62
+ { label: "Platform", selected: false },
63
+ { label: "Security", selected: false }
64
+ ]}
65
+ bordered
66
+ large
67
+ />
68
+ ```
69
+
70
+ ### Avatars
71
+
72
+ ```tsx
73
+ <Listbox
74
+ items={[
75
+ { label: "Rachel Chen", detail: "rachel@acme.io", selected: true },
76
+ { label: "Ada Lovelace", detail: "ada@acme.io", selected: false },
77
+ { label: "Kevin Turner", detail: "kevin@acme.io", selected: false },
78
+ { label: "Linus Berg", detail: "linus@acme.io", selected: false }
79
+ ]}
80
+ bordered
81
+ />
82
+ ```
83
+
84
+ ### Disabled
85
+
86
+ ```tsx
87
+ <Listbox
88
+ items={[
89
+ { label: "Backend", selected: true },
90
+ { label: "Frontend", selected: false },
91
+ { label: "Design", selected: false },
92
+ { label: "Platform", selected: false },
93
+ { label: "Security", selected: false }
94
+ ]}
95
+ bordered
96
+ disabled
97
+ />
98
+ ```
99
+
100
+ ## Do & Don't
101
+
102
+ ### Prefer a native select for simple lists
103
+
104
+ **Do** — For short, plain lists a native select is lighter, accessible, and uses the platform picker on mobile.
105
+
106
+ ```tsx
107
+ <Select open value="Yes" options={["Yes", "No"]} style={{ width: 192 }} />
108
+ ```
109
+
110
+ **Don't** — A custom listbox for two short options is heavier than it needs to be and worse on mobile.
111
+
112
+ ```tsx
113
+ <Listbox bordered style={{ width: 192 }} items={[
114
+ { label: "Yes", selected: true },
115
+ { label: "No" }
116
+ ]} />
117
+ ```
118
+
119
+ ### single
120
+
121
+ **Do** — Show exactly one checkmark, mirror it in the trigger value, and close the panel on pick.
122
+
123
+ ```tsx
124
+ <Listbox bordered style={{ width: 224 }} items={[
125
+ { label: "Backend", selected: true },
126
+ { label: "Frontend" },
127
+ { label: "Design" },
128
+ { label: "Platform" }
129
+ ]} />
130
+ ```
131
+
132
+ **Don't** — Single-select with two checkmarks lies about state: only one option can be the value.
133
+
134
+ ```tsx
135
+ <Listbox bordered style={{ width: 224 }} items={[
136
+ { label: "Backend", selected: true },
137
+ { label: "Frontend", selected: true },
138
+ { label: "Design" },
139
+ { label: "Platform" }
140
+ ]} />
141
+ ```
142
+
143
+ ### multi
144
+
145
+ **Do** — Keep the panel open, toggle each option's own checkmark, and summarize the count in the trigger.
146
+
147
+ ```tsx
148
+ <View style={{ width: 224, gap: 4 }}>
149
+ <View style={{ flexDirection: "row", alignItems: "center", justifyContent: "space-between", borderRadius: 6, borderWidth: 1, borderColor: tokens.input, backgroundColor: tokens.background, paddingHorizontal: 12, height: 36 }}>
150
+ <Text style={{ fontSize: 14, lineHeight: 20, color: tokens.foreground }}>3 selected</Text>
151
+ <Text style={{ fontSize: 14, lineHeight: 20, color: tokens["muted-foreground"] }}>▾</Text>
152
+ </View>
153
+ <Listbox multi bordered items={[
154
+ { label: "Backend", selected: true },
155
+ { label: "Frontend", selected: true },
156
+ { label: "Design" },
157
+ { label: "Platform", selected: true }
158
+ ]} />
159
+ </View>
160
+ ```
161
+
162
+ **Don't** — Don't close on each pick or echo only the last choice: multi-select needs to keep all selections visible.
163
+
164
+ ```tsx
165
+ <View style={{ width: 224, gap: 4 }}>
166
+ <View style={{ flexDirection: "row", alignItems: "center", justifyContent: "space-between", borderRadius: 6, borderWidth: 1, borderColor: tokens.input, backgroundColor: tokens.background, paddingHorizontal: 12, height: 36 }}>
167
+ <Text style={{ fontSize: 14, lineHeight: 20, color: tokens.foreground }}>Backend</Text>
168
+ <Text style={{ fontSize: 14, lineHeight: 20, color: tokens["muted-foreground"] }}>▾</Text>
169
+ </View>
170
+ <Listbox multi bordered items={[
171
+ { label: "Backend", selected: true },
172
+ { label: "Frontend", selected: true },
173
+ { label: "Design" },
174
+ { label: "Platform", selected: true }
175
+ ]} />
176
+ </View>
177
+ ```
@@ -0,0 +1,60 @@
1
+ import { type ViewStyle, type TextStyle } from "react-native";
2
+ import { type ColorTokens } from "../../style/index.js";
3
+
4
+ // Co-located Listbox styles. Layout-only fragments are static objects; anything
5
+ // that reads a color is a function of the active tokens (so the bordered surface,
6
+ // the selected/press fill, and the label/detail colors follow light/dark and the
7
+ // glass surface via tokens.popover/accent). The component resolves its mode and
8
+ // size axes and spreads these.
9
+
10
+ export type Mode = "single" | "multi";
11
+ export type Size = "small" | "medium" | "large";
12
+
13
+ // A bordered container reads as a popover surface: rounded card, hairline border,
14
+ // popover fill (translucent under glass), and a 4px inset so rows don't touch the
15
+ // edge.
16
+ export function containerBordered(tokens: ColorTokens): ViewStyle {
17
+ return { borderRadius: 6, borderWidth: 1, borderColor: tokens.border, backgroundColor: tokens.popover, padding: 4 };
18
+ }
19
+
20
+ // Each row: a horizontal Pressable with a leading control, the label/detail
21
+ // stack, and (added by the component) a subtle press-state fill. Size adds the
22
+ // vertical padding.
23
+ export const rowBase: ViewStyle = { flexDirection: "row", alignItems: "center", gap: 8, borderRadius: 2 };
24
+
25
+ // Per-row padding by size: small reads like the legacy h-8 trigger, medium like
26
+ // h-9, large like h-10.
27
+ export const rowSize: Record<Size, ViewStyle> = {
28
+ small: { paddingHorizontal: 8, paddingVertical: 6 },
29
+ medium: { paddingHorizontal: 8, paddingVertical: 8 },
30
+ large: { paddingHorizontal: 8, paddingVertical: 10 },
31
+ };
32
+
33
+ // The accent fill used for a selected single-select row and the press state.
34
+ export function rowSelected(tokens: ColorTokens): ViewStyle {
35
+ return { backgroundColor: tokens.accent };
36
+ }
37
+
38
+ // Single-select checkmark column: a fixed-width gutter reserved on every row so
39
+ // labels stay aligned whether or not the row is selected.
40
+ export function checkmark(tokens: ColorTokens): TextStyle {
41
+ return { width: 16, fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens.foreground };
42
+ }
43
+
44
+ // The label/detail stack grows to fill the remaining row width.
45
+ export const textStack: ViewStyle = { flexGrow: 1, flexShrink: 1, flexBasis: "0%" };
46
+
47
+ // Label type per size (small is smaller; medium and large share the body size).
48
+ const LABEL_TYPE: Record<Size, TextStyle> = {
49
+ small: { fontSize: 12, lineHeight: 16 },
50
+ medium: { fontSize: 14, lineHeight: 20 },
51
+ large: { fontSize: 14, lineHeight: 20 },
52
+ };
53
+
54
+ export function label(tokens: ColorTokens, size: Size): TextStyle {
55
+ return { color: tokens.foreground, ...LABEL_TYPE[size] };
56
+ }
57
+
58
+ export function detail(tokens: ColorTokens): TextStyle {
59
+ return { fontSize: 12, lineHeight: 16, color: tokens["muted-foreground"] };
60
+ }
@@ -0,0 +1,113 @@
1
+ import { View, Pressable, Text, useTheme, type StyleProp, type ViewStyle } from "../../style/index.js";
2
+ import { Checkbox } from "../checkbox/checkbox.js";
3
+ import * as s from "./listbox.styles.js";
4
+ import { type Mode, type Size } from "./listbox.styles.js";
5
+
6
+ // An inline, selectable list of options rendered directly (not a popover). Each
7
+ // row is a Pressable. Two selection modes, mutually exclusive:
8
+ //
9
+ // 1. Single-select (default): the chosen row is filled with the accent and shows
10
+ // a leading checkmark ("✓"); at most one row is the value.
11
+ // 2. Multi-select (`multi`): every row carries a leading Checkbox reflecting its
12
+ // own selected state, and any number of rows may be selected at once.
13
+ //
14
+ // Boolean-prop API: one boolean per option, grouped by axis, first-match
15
+ // precedence within an axis (mirrors Button's intentOf). `multi` switches the
16
+ // selection mode; `bordered` wraps the list in a rounded popover-surface card.
17
+
18
+ export interface ListboxItem {
19
+ /** The option's primary text. */
20
+ label: string;
21
+ /** Optional secondary text shown under the label in a muted tone. */
22
+ detail?: string;
23
+ /** Whether this option is currently selected. */
24
+ selected?: boolean;
25
+ }
26
+
27
+ export interface ListboxProps {
28
+ /** The options to render, top to bottom. */
29
+ items: ListboxItem[];
30
+ /** Multi-select: each row gets a leading Checkbox instead of a single ✓. */
31
+ multi?: boolean;
32
+ /** Wrap the list in a rounded, bordered popover-surface card. */
33
+ bordered?: boolean;
34
+ // Size (pick one; default is medium).
35
+ /** Tighter rows with smaller text. */
36
+ small?: boolean;
37
+ /** Taller rows. */
38
+ large?: boolean;
39
+ /** Dim the list and block selection. */
40
+ disabled?: boolean;
41
+ /** Fired with the pressed option's index. */
42
+ onSelect?: (index: number) => void;
43
+ /** Escape hatch for layout/positioning composition (mainly width). */
44
+ style?: StyleProp<ViewStyle>;
45
+ }
46
+
47
+ // Selection mode precedence when more than one axis prop is passed: first match
48
+ // wins. Only `multi` competes here; the default is single-select.
49
+ function modeOf(p: ListboxProps): Mode {
50
+ if (p.multi) return "multi";
51
+ return "single";
52
+ }
53
+
54
+ // Size precedence when more than one is passed: first match wins (small over
55
+ // large). The default is medium.
56
+ function sizeOf(p: ListboxProps): Size {
57
+ if (p.small) return "small";
58
+ if (p.large) return "large";
59
+ return "medium";
60
+ }
61
+
62
+ export function Listbox(props: ListboxProps) {
63
+ const { items, bordered, disabled, onSelect, style } = props;
64
+ const mode = modeOf(props);
65
+ const size = sizeOf(props);
66
+ const { tokens } = useTheme();
67
+
68
+ const container: StyleProp<ViewStyle> = [
69
+ bordered ? s.containerBordered(tokens) : null,
70
+ disabled ? { opacity: 0.5 } : null,
71
+ style,
72
+ ];
73
+
74
+ return (
75
+ <View style={container} accessibilityRole="list">
76
+ {items.map((item, index) => {
77
+ const selected = !!item.selected;
78
+ // Single-select fills the chosen row; multi-select leaves the row plain
79
+ // and reflects state in the leading Checkbox instead.
80
+ const rowBase: StyleProp<ViewStyle> = [
81
+ s.rowBase,
82
+ s.rowSize[size],
83
+ mode === "single" && selected ? s.rowSelected(tokens) : null,
84
+ ];
85
+
86
+ return (
87
+ <Pressable
88
+ key={index}
89
+ // The press fill (the old `active:bg-accent`) is the accent, applied
90
+ // only when the list is enabled.
91
+ style={({ pressed }) => [rowBase, !disabled && pressed ? s.rowSelected(tokens) : null]}
92
+ onPress={disabled ? undefined : () => onSelect?.(index)}
93
+ disabled={disabled}
94
+ accessibilityRole={mode === "multi" ? "checkbox" : "menuitem"}
95
+ accessibilityState={{ selected, disabled: !!disabled }}
96
+ >
97
+ {mode === "multi" ? (
98
+ <Checkbox checked={selected} />
99
+ ) : (
100
+ // Reserve the checkmark column on every row so labels stay aligned
101
+ // whether or not the row is selected.
102
+ <Text style={s.checkmark(tokens)}>{selected ? "✓" : ""}</Text>
103
+ )}
104
+ <View style={s.textStack}>
105
+ <Text style={s.label(tokens, size)}>{item.label}</Text>
106
+ {item.detail != null ? <Text style={s.detail(tokens)}>{item.detail}</Text> : null}
107
+ </View>
108
+ </Pressable>
109
+ );
110
+ })}
111
+ </View>
112
+ );
113
+ }
@@ -0,0 +1,6 @@
1
+ import { createPagination } from "./pagination.shared.js";
2
+ import { androidSkin } from "./pagination.styles.js";
3
+
4
+ // Material 3 Pagination. Metro resolves this file on Android; the docs import it for preview.
5
+ export const Pagination = createPagination(androidSkin);
6
+ export type { PaginationProps } from "./pagination.shared.js";
@@ -0,0 +1,6 @@
1
+ import { createPagination } from "./pagination.shared.js";
2
+ import { iosSkin } from "./pagination.styles.js";
3
+
4
+ // iOS (HIG page controls) Pagination. Metro resolves this file on iOS; the docs import it for preview.
5
+ export const Pagination = createPagination(iosSkin);
6
+ export type { PaginationProps } from "./pagination.shared.js";
@@ -0,0 +1,133 @@
1
+ # Pagination
2
+
3
+ Page-of-N navigation for tables and lists.
4
+
5
+ ## Usage
6
+
7
+ ```tsx
8
+ <Pagination page={2} total={12} compact pageSize={10} pageSizes={[10, 25, 50]} />
9
+ ```
10
+
11
+ ## Variants
12
+
13
+ ### Variant - numbered
14
+
15
+ ```tsx
16
+ <Pagination page={2} total={12} pageSize={10} pageSizes={[10, 25, 50]} />
17
+ ```
18
+
19
+ ### Variant - with-size
20
+
21
+ ```tsx
22
+ <Pagination page={2} total={12} withSize pageSize={10} pageSizes={[10, 25, 50]} />
23
+ ```
24
+
25
+ ## Do & Don't
26
+
27
+ ### compact
28
+
29
+ **Do** — Pair the buttons with a "Showing X–Y of N" range so position and total are always visible.
30
+
31
+ ```tsx
32
+ <Pagination compact page={2} total={12} />
33
+ ```
34
+
35
+ **Don't** — Bare Previous/Next with no range label leaves the user unable to tell where they are or how much is left.
36
+
37
+ ```tsx
38
+ <View style={{ flexDirection: "row", alignItems: "center", justifyContent: "flex-end", gap: 4 }}>
39
+ <Pressable style={{ flexDirection: "row", alignItems: "center", justifyContent: "center", height: 36, minWidth: 36, paddingHorizontal: 10, borderRadius: 6, borderWidth: 1, borderColor: tokens.input, backgroundColor: tokens.background }} accessibilityRole="button" accessibilityLabel="Previous page">
40
+ <Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens.foreground }}>‹</Text>
41
+ </Pressable>
42
+ <Pressable style={{ flexDirection: "row", alignItems: "center", justifyContent: "center", height: 36, minWidth: 36, paddingHorizontal: 10, borderRadius: 6, borderWidth: 1, borderColor: tokens.input, backgroundColor: tokens.background }} accessibilityRole="button" accessibilityLabel="Next page">
43
+ <Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens.foreground }}>›</Text>
44
+ </Pressable>
45
+ </View>
46
+ ```
47
+
48
+ ### numbered
49
+
50
+ **Do** — Truncate the middle with an ellipsis; keep first, last, and a window around the current page.
51
+
52
+ ```tsx
53
+ <Pagination page={2} total={12} />
54
+ ```
55
+
56
+ **Don't** — Rendering every page number overflows and stops being scannable past a handful.
57
+
58
+ ```tsx
59
+ <View style={{ flexDirection: "row", alignItems: "center", gap: 4 }}>
60
+ <Pressable style={{ flexDirection: "row", alignItems: "center", justifyContent: "center", height: 36, minWidth: 36, paddingHorizontal: 10, borderRadius: 6, borderWidth: 1, borderColor: tokens.input, backgroundColor: tokens.background, opacity: 0.5 }} accessibilityRole="button" accessibilityLabel="Previous page">
61
+ <Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens.foreground }}>‹</Text>
62
+ </Pressable>
63
+ <Pressable style={{ flexDirection: "row", alignItems: "center", justifyContent: "center", height: 36, minWidth: 36, paddingHorizontal: 10, borderRadius: 6, borderWidth: 1, borderColor: tokens.primary, backgroundColor: tokens.primary }} accessibilityRole="button" accessibilityLabel="Page 1">
64
+ <Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens["primary-foreground"] }}>1</Text>
65
+ </Pressable>
66
+ <Pressable style={{ flexDirection: "row", alignItems: "center", justifyContent: "center", height: 36, minWidth: 36, paddingHorizontal: 10, borderRadius: 6, borderWidth: 1, borderColor: tokens.input, backgroundColor: tokens.background }} accessibilityRole="button" accessibilityLabel="Page 2">
67
+ <Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens.foreground }}>2</Text>
68
+ </Pressable>
69
+ <Pressable style={{ flexDirection: "row", alignItems: "center", justifyContent: "center", height: 36, minWidth: 36, paddingHorizontal: 10, borderRadius: 6, borderWidth: 1, borderColor: tokens.input, backgroundColor: tokens.background }} accessibilityRole="button" accessibilityLabel="Page 3">
70
+ <Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens.foreground }}>3</Text>
71
+ </Pressable>
72
+ <Pressable style={{ flexDirection: "row", alignItems: "center", justifyContent: "center", height: 36, minWidth: 36, paddingHorizontal: 10, borderRadius: 6, borderWidth: 1, borderColor: tokens.input, backgroundColor: tokens.background }} accessibilityRole="button" accessibilityLabel="Page 4">
73
+ <Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens.foreground }}>4</Text>
74
+ </Pressable>
75
+ <Pressable style={{ flexDirection: "row", alignItems: "center", justifyContent: "center", height: 36, minWidth: 36, paddingHorizontal: 10, borderRadius: 6, borderWidth: 1, borderColor: tokens.input, backgroundColor: tokens.background }} accessibilityRole="button" accessibilityLabel="Page 5">
76
+ <Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens.foreground }}>5</Text>
77
+ </Pressable>
78
+ <Pressable style={{ flexDirection: "row", alignItems: "center", justifyContent: "center", height: 36, minWidth: 36, paddingHorizontal: 10, borderRadius: 6, borderWidth: 1, borderColor: tokens.input, backgroundColor: tokens.background }} accessibilityRole="button" accessibilityLabel="Page 6">
79
+ <Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens.foreground }}>6</Text>
80
+ </Pressable>
81
+ <Pressable style={{ flexDirection: "row", alignItems: "center", justifyContent: "center", height: 36, minWidth: 36, paddingHorizontal: 10, borderRadius: 6, borderWidth: 1, borderColor: tokens.input, backgroundColor: tokens.background }} accessibilityRole="button" accessibilityLabel="Page 7">
82
+ <Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens.foreground }}>7</Text>
83
+ </Pressable>
84
+ <Pressable style={{ flexDirection: "row", alignItems: "center", justifyContent: "center", height: 36, minWidth: 36, paddingHorizontal: 10, borderRadius: 6, borderWidth: 1, borderColor: tokens.input, backgroundColor: tokens.background }} accessibilityRole="button" accessibilityLabel="Page 8">
85
+ <Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens.foreground }}>8</Text>
86
+ </Pressable>
87
+ <Pressable style={{ flexDirection: "row", alignItems: "center", justifyContent: "center", height: 36, minWidth: 36, paddingHorizontal: 10, borderRadius: 6, borderWidth: 1, borderColor: tokens.input, backgroundColor: tokens.background }} accessibilityRole="button" accessibilityLabel="Page 9">
88
+ <Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens.foreground }}>9</Text>
89
+ </Pressable>
90
+ <Pressable style={{ flexDirection: "row", alignItems: "center", justifyContent: "center", height: 36, minWidth: 36, paddingHorizontal: 10, borderRadius: 6, borderWidth: 1, borderColor: tokens.input, backgroundColor: tokens.background }} accessibilityRole="button" accessibilityLabel="Page 10">
91
+ <Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens.foreground }}>10</Text>
92
+ </Pressable>
93
+ <Pressable style={{ flexDirection: "row", alignItems: "center", justifyContent: "center", height: 36, minWidth: 36, paddingHorizontal: 10, borderRadius: 6, borderWidth: 1, borderColor: tokens.input, backgroundColor: tokens.background }} accessibilityRole="button" accessibilityLabel="Page 11">
94
+ <Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens.foreground }}>11</Text>
95
+ </Pressable>
96
+ <Pressable style={{ flexDirection: "row", alignItems: "center", justifyContent: "center", height: 36, minWidth: 36, paddingHorizontal: 10, borderRadius: 6, borderWidth: 1, borderColor: tokens.input, backgroundColor: tokens.background }} accessibilityRole="button" accessibilityLabel="Page 12">
97
+ <Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens.foreground }}>12</Text>
98
+ </Pressable>
99
+ <Pressable style={{ flexDirection: "row", alignItems: "center", justifyContent: "center", height: 36, minWidth: 36, paddingHorizontal: 10, borderRadius: 6, borderWidth: 1, borderColor: tokens.input, backgroundColor: tokens.background }} accessibilityRole="button" accessibilityLabel="Next page">
100
+ <Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens.foreground }}>›</Text>
101
+ </Pressable>
102
+ </View>
103
+ ```
104
+
105
+ ### with-size
106
+
107
+ **Do** — Show "Page X of N" beside the size selector and reset to page 1 when the size changes.
108
+
109
+ ```tsx
110
+ <Pagination withSize page={2} total={12} pageSize={10} pageSizes={[10, 25, 50]} />
111
+ ```
112
+
113
+ **Don't** — Offering a page-size selector without a page indicator hides which page the new size landed on.
114
+
115
+ ```tsx
116
+ <View style={{ flexDirection: "row", alignItems: "center", gap: 16 }}>
117
+ <View style={{ flexDirection: "row", alignItems: "center", gap: 8 }}>
118
+ <Text style={{ fontSize: 14, lineHeight: 20, color: tokens["muted-foreground"] }}>Rows per page</Text>
119
+ <Pressable style={{ flexDirection: "row", alignItems: "center", justifyContent: "space-between", gap: 4, height: 36, minWidth: 36, paddingHorizontal: 10, borderRadius: 6, borderWidth: 1, borderColor: tokens.input, backgroundColor: tokens.background }} accessibilityRole="button" accessibilityLabel="Rows per page">
120
+ <Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens.foreground }}>10</Text>
121
+ <Text style={{ fontSize: 14, lineHeight: 20, color: tokens["muted-foreground"] }}>▾</Text>
122
+ </Pressable>
123
+ </View>
124
+ <View style={{ flexDirection: "row", alignItems: "center", gap: 4 }}>
125
+ <Pressable style={{ flexDirection: "row", alignItems: "center", justifyContent: "center", height: 36, minWidth: 36, paddingHorizontal: 10, borderRadius: 6, borderWidth: 1, borderColor: tokens.input, backgroundColor: tokens.background, opacity: 0.5 }} accessibilityRole="button" accessibilityLabel="Previous page">
126
+ <Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens.foreground }}>‹</Text>
127
+ </Pressable>
128
+ <Pressable style={{ flexDirection: "row", alignItems: "center", justifyContent: "center", height: 36, minWidth: 36, paddingHorizontal: 10, borderRadius: 6, borderWidth: 1, borderColor: tokens.input, backgroundColor: tokens.background }} accessibilityRole="button" accessibilityLabel="Next page">
129
+ <Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens.foreground }}>›</Text>
130
+ </Pressable>
131
+ </View>
132
+ </View>
133
+ ```