@duro-app/ui 5.12.0 → 5.14.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 (270) hide show
  1. package/README.md +49 -0
  2. package/dist/Table-DnaoH-h5.js +1806 -0
  3. package/dist/Table-DnaoH-h5.js.map +1 -0
  4. package/dist/components/AppShell/native.css.d.ts +53 -0
  5. package/dist/components/AppShell/native.css.d.ts.map +1 -0
  6. package/dist/components/Button/Button.d.ts.map +1 -1
  7. package/dist/components/ColorMode/ColorMode.d.ts +2 -1
  8. package/dist/components/ColorMode/ColorMode.d.ts.map +1 -1
  9. package/dist/components/ColorMode/storage.d.ts +4 -0
  10. package/dist/components/ColorMode/storage.d.ts.map +1 -0
  11. package/dist/components/ColorMode/styles.css.d.ts +4 -0
  12. package/dist/components/ColorMode/styles.css.d.ts.map +1 -1
  13. package/dist/components/ColorMode/useSystemScheme.d.ts +7 -0
  14. package/dist/components/ColorMode/useSystemScheme.d.ts.map +1 -0
  15. package/dist/components/Combobox/Combobox.d.ts +7 -2
  16. package/dist/components/Combobox/Combobox.d.ts.map +1 -1
  17. package/dist/components/Combobox/comboboxState.d.ts +29 -0
  18. package/dist/components/Combobox/comboboxState.d.ts.map +1 -0
  19. package/dist/components/Combobox/native.css.d.ts +27 -0
  20. package/dist/components/Combobox/native.css.d.ts.map +1 -0
  21. package/dist/components/Combobox/useComboboxRoot.d.ts +6 -10
  22. package/dist/components/Combobox/useComboboxRoot.d.ts.map +1 -1
  23. package/dist/components/Drawer/Drawer.d.ts +12 -37
  24. package/dist/components/Drawer/Drawer.d.ts.map +1 -1
  25. package/dist/components/Drawer/Drawer.meta.d.ts.map +1 -1
  26. package/dist/components/Drawer/DrawerScroll.stories.d.ts.map +1 -1
  27. package/dist/components/Drawer/DrawerSnap.stories.d.ts +22 -0
  28. package/dist/components/Drawer/DrawerSnap.stories.d.ts.map +1 -0
  29. package/dist/components/Drawer/native.css.d.ts +59 -0
  30. package/dist/components/Drawer/native.css.d.ts.map +1 -0
  31. package/dist/components/Drawer/parts.css.d.ts +96 -0
  32. package/dist/components/Drawer/parts.css.d.ts.map +1 -0
  33. package/dist/components/Drawer/parts.d.ts +56 -0
  34. package/dist/components/Drawer/parts.d.ts.map +1 -0
  35. package/dist/components/Drawer/sheetPhysics.d.ts +56 -0
  36. package/dist/components/Drawer/sheetPhysics.d.ts.map +1 -0
  37. package/dist/components/Drawer/styles.css.d.ts +16 -57
  38. package/dist/components/Drawer/styles.css.d.ts.map +1 -1
  39. package/dist/components/Drawer/touchInput.d.ts +35 -0
  40. package/dist/components/Drawer/touchInput.d.ts.map +1 -0
  41. package/dist/components/Drawer/useDrawerRoot.d.ts +33 -0
  42. package/dist/components/Drawer/useDrawerRoot.d.ts.map +1 -0
  43. package/dist/components/Drawer/useSheetHeights.d.ts +7 -0
  44. package/dist/components/Drawer/useSheetHeights.d.ts.map +1 -0
  45. package/dist/components/Drawer/useSwipeDismiss.d.ts +13 -2
  46. package/dist/components/Drawer/useSwipeDismiss.d.ts.map +1 -1
  47. package/dist/components/Form/FormSubmitContext.d.ts +8 -0
  48. package/dist/components/Form/FormSubmitContext.d.ts.map +1 -0
  49. package/dist/components/Listbox/native.css.d.ts +84 -0
  50. package/dist/components/Listbox/native.css.d.ts.map +1 -0
  51. package/dist/components/Menu/Menu.d.ts +7 -1
  52. package/dist/components/Menu/Menu.d.ts.map +1 -1
  53. package/dist/components/Menu/native.css.d.ts +14 -0
  54. package/dist/components/Menu/native.css.d.ts.map +1 -0
  55. package/dist/components/Menu/useMenuOpen.d.ts +16 -0
  56. package/dist/components/Menu/useMenuOpen.d.ts.map +1 -0
  57. package/dist/components/Menu/useMenuRoot.d.ts +3 -6
  58. package/dist/components/Menu/useMenuRoot.d.ts.map +1 -1
  59. package/dist/components/Popover/native.css.d.ts +11 -0
  60. package/dist/components/Popover/native.css.d.ts.map +1 -0
  61. package/dist/components/Select/Select.d.ts +9 -2
  62. package/dist/components/Select/Select.d.ts.map +1 -1
  63. package/dist/components/Select/Select.stories.d.ts +11 -0
  64. package/dist/components/Select/Select.stories.d.ts.map +1 -1
  65. package/dist/components/Select/native.css.d.ts +11 -0
  66. package/dist/components/Select/native.css.d.ts.map +1 -0
  67. package/dist/components/Select/selectState.d.ts +21 -0
  68. package/dist/components/Select/selectState.d.ts.map +1 -0
  69. package/dist/components/Select/styles.css.d.ts +10 -0
  70. package/dist/components/Select/styles.css.d.ts.map +1 -1
  71. package/dist/components/Select/useSelectRoot.d.ts +6 -8
  72. package/dist/components/Select/useSelectRoot.d.ts.map +1 -1
  73. package/dist/components/SideNav/Chevron.d.ts +4 -0
  74. package/dist/components/SideNav/Chevron.d.ts.map +1 -0
  75. package/dist/components/SideNav/SideNav.d.ts.map +1 -1
  76. package/dist/components/SideNav/styles.css.d.ts +6 -0
  77. package/dist/components/SideNav/styles.css.d.ts.map +1 -1
  78. package/dist/components/SideNav/types.d.ts +6 -0
  79. package/dist/components/SideNav/types.d.ts.map +1 -0
  80. package/dist/components/SplitPane/SplitPane.d.ts +38 -0
  81. package/dist/components/SplitPane/SplitPane.d.ts.map +1 -0
  82. package/dist/components/SplitPane/SplitPane.meta.d.ts +3 -0
  83. package/dist/components/SplitPane/SplitPane.meta.d.ts.map +1 -0
  84. package/dist/components/SplitPane/SplitPane.stories.d.ts +17 -0
  85. package/dist/components/SplitPane/SplitPane.stories.d.ts.map +1 -0
  86. package/dist/components/SplitPane/styles.css.d.ts +78 -0
  87. package/dist/components/SplitPane/styles.css.d.ts.map +1 -0
  88. package/dist/components/Tabs/TabLayout.d.ts +7 -0
  89. package/dist/components/Tabs/TabLayout.d.ts.map +1 -0
  90. package/dist/components/Tabs/Tabs.d.ts.map +1 -1
  91. package/dist/components/Tabs/indicator.d.ts +28 -0
  92. package/dist/components/Tabs/indicator.d.ts.map +1 -0
  93. package/dist/components/Tabs/styles.css.d.ts +4 -0
  94. package/dist/components/Tabs/styles.css.d.ts.map +1 -1
  95. package/dist/components/Tabs/useTabIndicator.d.ts +4 -0
  96. package/dist/components/Tabs/useTabIndicator.d.ts.map +1 -0
  97. package/dist/components/Tabs/useTabKeys.d.ts +13 -0
  98. package/dist/components/Tabs/useTabKeys.d.ts.map +1 -0
  99. package/dist/components/Tag/RemoveGlyph.d.ts +4 -0
  100. package/dist/components/Tag/RemoveGlyph.d.ts.map +1 -0
  101. package/dist/components/Tag/Tag.d.ts +2 -24
  102. package/dist/components/Tag/Tag.d.ts.map +1 -1
  103. package/dist/components/Tag/styles.css.d.ts +8 -0
  104. package/dist/components/Tag/styles.css.d.ts.map +1 -1
  105. package/dist/components/Tag/types.d.ts +39 -0
  106. package/dist/components/Tag/types.d.ts.map +1 -0
  107. package/dist/components/ThemeProvider/ThemeOverrides.stories.d.ts +13 -0
  108. package/dist/components/ThemeProvider/ThemeOverrides.stories.d.ts.map +1 -0
  109. package/dist/components/ThemeProvider/ThemeProvider.d.ts +9 -2
  110. package/dist/components/ThemeProvider/ThemeProvider.d.ts.map +1 -1
  111. package/dist/components/ThemeProvider/overrideRule.d.ts +7 -0
  112. package/dist/components/ThemeProvider/overrideRule.d.ts.map +1 -0
  113. package/dist/components/ThemeProvider/overrides.d.ts +16 -0
  114. package/dist/components/ThemeProvider/overrides.d.ts.map +1 -0
  115. package/dist/components/Toast/ToastProvider.d.ts +2 -21
  116. package/dist/components/Toast/ToastProvider.d.ts.map +1 -1
  117. package/dist/components/Toast/native.css.d.ts +81 -0
  118. package/dist/components/Toast/native.css.d.ts.map +1 -0
  119. package/dist/components/Toast/store.d.ts +47 -0
  120. package/dist/components/Toast/store.d.ts.map +1 -0
  121. package/dist/components/Tooltip/TooltipHint.d.ts +9 -0
  122. package/dist/components/Tooltip/TooltipHint.d.ts.map +1 -0
  123. package/dist/components/Tree/Tree.d.ts.map +1 -1
  124. package/dist/components/Tree/styles.css.d.ts +6 -0
  125. package/dist/components/Tree/styles.css.d.ts.map +1 -1
  126. package/dist/components/Tree/useTreeKeyboard.d.ts +19 -0
  127. package/dist/components/Tree/useTreeKeyboard.d.ts.map +1 -0
  128. package/dist/components/VirtualList/VirtualList.d.ts +33 -0
  129. package/dist/components/VirtualList/VirtualList.d.ts.map +1 -0
  130. package/dist/components/VirtualList/VirtualList.meta.d.ts +3 -0
  131. package/dist/components/VirtualList/VirtualList.meta.d.ts.map +1 -0
  132. package/dist/components/VirtualList/VirtualList.stories.d.ts +15 -0
  133. package/dist/components/VirtualList/VirtualList.stories.d.ts.map +1 -0
  134. package/dist/components/VirtualList/styles.css.d.ts +28 -0
  135. package/dist/components/VirtualList/styles.css.d.ts.map +1 -0
  136. package/dist/index.css +1 -1
  137. package/dist/index.d.ts +4 -2
  138. package/dist/index.d.ts.map +1 -1
  139. package/dist/index.js +5090 -4381
  140. package/dist/index.js.map +1 -1
  141. package/dist/shared/ButtonHost.d.ts +11 -0
  142. package/dist/shared/ButtonHost.d.ts.map +1 -0
  143. package/dist/shared/pressableChild.d.ts +14 -0
  144. package/dist/shared/pressableChild.d.ts.map +1 -0
  145. package/dist/shared/textOf.d.ts +7 -0
  146. package/dist/shared/textOf.d.ts.map +1 -0
  147. package/dist/table.js +1 -1
  148. package/package.json +3 -2
  149. package/src/components/AppShell/AppShell.meta.ts +1 -1
  150. package/src/components/AppShell/AppShell.native.tsx +174 -0
  151. package/src/components/AppShell/native.css.ts +56 -0
  152. package/src/components/Button/Button.tsx +15 -4
  153. package/src/components/ColorMode/ColorMode.meta.ts +1 -1
  154. package/src/components/ColorMode/ColorMode.tsx +16 -37
  155. package/src/components/ColorMode/storage.native.ts +8 -0
  156. package/src/components/ColorMode/storage.ts +19 -0
  157. package/src/components/ColorMode/styles.css.ts +6 -0
  158. package/src/components/ColorMode/useSystemScheme.native.ts +6 -0
  159. package/src/components/ColorMode/useSystemScheme.ts +19 -0
  160. package/src/components/Combobox/Combobox.meta.ts +1 -1
  161. package/src/components/Combobox/Combobox.native.tsx +295 -0
  162. package/src/components/Combobox/Combobox.tsx +14 -8
  163. package/src/components/Combobox/comboboxState.ts +107 -0
  164. package/src/components/Combobox/native.css.ts +33 -0
  165. package/src/components/Combobox/useComboboxRoot.ts +17 -91
  166. package/src/components/Drawer/Drawer.meta.ts +2 -1
  167. package/src/components/Drawer/Drawer.native.tsx +491 -0
  168. package/src/components/Drawer/Drawer.tsx +65 -143
  169. package/src/components/Drawer/DrawerScroll.stories.tsx +4 -54
  170. package/src/components/Drawer/DrawerSnap.stories.tsx +225 -0
  171. package/src/components/Drawer/native.css.ts +62 -0
  172. package/src/components/Drawer/parts.css.ts +125 -0
  173. package/src/components/Drawer/parts.tsx +110 -0
  174. package/src/components/Drawer/sheetPhysics.ts +103 -0
  175. package/src/components/Drawer/styles.css.ts +18 -73
  176. package/src/components/Drawer/touchInput.ts +81 -0
  177. package/src/components/Drawer/useDrawerRoot.ts +90 -0
  178. package/src/components/Drawer/useSheetHeights.ts +42 -0
  179. package/src/components/Drawer/useSwipeDismiss.ts +146 -23
  180. package/src/components/Form/Form.meta.ts +1 -1
  181. package/src/components/Form/Form.native.tsx +120 -0
  182. package/src/components/Form/FormSubmitContext.ts +12 -0
  183. package/src/components/Listbox/Listbox.meta.ts +1 -1
  184. package/src/components/Listbox/Listbox.native.tsx +153 -0
  185. package/src/components/Listbox/native.css.ts +91 -0
  186. package/src/components/Menu/Menu.meta.ts +1 -1
  187. package/src/components/Menu/Menu.native.tsx +268 -0
  188. package/src/components/Menu/Menu.tsx +11 -2
  189. package/src/components/Menu/native.css.ts +18 -0
  190. package/src/components/Menu/useMenuOpen.ts +33 -0
  191. package/src/components/Menu/useMenuRoot.ts +7 -26
  192. package/src/components/Popover/Popover.meta.ts +1 -1
  193. package/src/components/Popover/Popover.native.tsx +205 -0
  194. package/src/components/Popover/native.css.ts +13 -0
  195. package/src/components/ScrollArea/ScrollArea.meta.ts +1 -1
  196. package/src/components/ScrollArea/ScrollArea.native.tsx +61 -0
  197. package/src/components/Select/Select.meta.ts +1 -1
  198. package/src/components/Select/Select.native.tsx +224 -0
  199. package/src/components/Select/Select.stories.tsx +49 -1
  200. package/src/components/Select/Select.tsx +20 -3
  201. package/src/components/Select/native.css.ts +13 -0
  202. package/src/components/Select/selectState.ts +36 -0
  203. package/src/components/Select/styles.css.ts +11 -0
  204. package/src/components/Select/useSelectRoot.ts +9 -30
  205. package/src/components/SideNav/Chevron.native.tsx +20 -0
  206. package/src/components/SideNav/Chevron.tsx +20 -0
  207. package/src/components/SideNav/SideNav.meta.ts +1 -1
  208. package/src/components/SideNav/SideNav.tsx +57 -41
  209. package/src/components/SideNav/styles.css.ts +8 -0
  210. package/src/components/SideNav/types.ts +6 -0
  211. package/src/components/SplitPane/SplitPane.meta.ts +40 -0
  212. package/src/components/SplitPane/SplitPane.native.tsx +47 -0
  213. package/src/components/SplitPane/SplitPane.stories.tsx +148 -0
  214. package/src/components/SplitPane/SplitPane.tsx +218 -0
  215. package/src/components/SplitPane/styles.css.ts +81 -0
  216. package/src/components/Tabs/TabLayout.native.tsx +9 -0
  217. package/src/components/Tabs/TabLayout.tsx +6 -0
  218. package/src/components/Tabs/Tabs.meta.ts +1 -1
  219. package/src/components/Tabs/Tabs.tsx +38 -136
  220. package/src/components/Tabs/indicator.ts +36 -0
  221. package/src/components/Tabs/styles.css.ts +7 -0
  222. package/src/components/Tabs/useTabIndicator.native.ts +35 -0
  223. package/src/components/Tabs/useTabIndicator.ts +49 -0
  224. package/src/components/Tabs/useTabKeys.native.ts +2 -0
  225. package/src/components/Tabs/useTabKeys.ts +99 -0
  226. package/src/components/Tag/RemoveGlyph.native.tsx +17 -0
  227. package/src/components/Tag/RemoveGlyph.tsx +10 -0
  228. package/src/components/Tag/Tag.meta.ts +1 -1
  229. package/src/components/Tag/Tag.native.tsx +70 -0
  230. package/src/components/Tag/Tag.tsx +9 -37
  231. package/src/components/Tag/styles.css.ts +9 -0
  232. package/src/components/Tag/types.ts +43 -0
  233. package/src/components/ThemeProvider/ThemeOverrides.stories.tsx +113 -0
  234. package/src/components/ThemeProvider/ThemeProvider.meta.ts +1 -1
  235. package/src/components/ThemeProvider/ThemeProvider.native.tsx +36 -7
  236. package/src/components/ThemeProvider/ThemeProvider.tsx +42 -6
  237. package/src/components/ThemeProvider/overrideRule.ts +39 -0
  238. package/src/components/ThemeProvider/overrides.ts +27 -0
  239. package/src/components/Toast/Toast.meta.ts +1 -1
  240. package/src/components/Toast/ToastProvider.native.tsx +226 -0
  241. package/src/components/Toast/ToastProvider.tsx +22 -84
  242. package/src/components/Toast/native.css.ts +78 -0
  243. package/src/components/Toast/store.ts +96 -0
  244. package/src/components/Toggle/Toggle.tsx +3 -3
  245. package/src/components/Tooltip/Tooltip.meta.ts +1 -1
  246. package/src/components/Tooltip/Tooltip.native.tsx +36 -0
  247. package/src/components/Tooltip/TooltipHint.ts +13 -0
  248. package/src/components/Tree/Tree.meta.ts +1 -1
  249. package/src/components/Tree/Tree.tsx +23 -135
  250. package/src/components/Tree/styles.css.ts +6 -0
  251. package/src/components/Tree/useTreeKeyboard.native.ts +5 -0
  252. package/src/components/Tree/useTreeKeyboard.ts +152 -0
  253. package/src/components/VirtualList/VirtualList.meta.ts +39 -0
  254. package/src/components/VirtualList/VirtualList.stories.tsx +95 -0
  255. package/src/components/VirtualList/VirtualList.tsx +103 -0
  256. package/src/components/VirtualList/styles.css.ts +31 -0
  257. package/src/form.native.ts +7 -0
  258. package/src/hooks/useContainerBelow.native.ts +30 -0
  259. package/src/index.native.ts +48 -0
  260. package/src/index.ts +9 -1
  261. package/src/shared/ButtonHost.native.tsx +70 -0
  262. package/src/shared/ButtonHost.tsx +12 -0
  263. package/src/shared/focusInput.native.ts +18 -0
  264. package/src/shared/portal/AnchoredOverlay.native.tsx +117 -0
  265. package/src/shared/pressableChild.ts +33 -0
  266. package/src/shared/textOf.ts +13 -0
  267. package/src/shared/useKeyboardHeight.native.ts +18 -0
  268. package/src/shared/useScreenReaderEnabled.native.ts +19 -0
  269. package/dist/Table-ColfpgKV.js +0 -1552
  270. package/dist/Table-ColfpgKV.js.map +0 -1
@@ -0,0 +1,11 @@
1
+ import { ComponentProps } from 'react';
2
+ import { html } from 'react-strict-dom';
3
+ export type ButtonHostProps = ComponentProps<typeof html.button>;
4
+ /**
5
+ * The host element of Button and Toggle. On the web it is the <button>
6
+ * itself, with no component in between (a wrapper changed what a Form's
7
+ * submit focuses); the native variant (ButtonHost.native.tsx) can swap in an
8
+ * RN Pressable to carry a Tooltip's accessibility hint.
9
+ */
10
+ export declare const ButtonHost: typeof html.button;
11
+ //# sourceMappingURL=ButtonHost.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ButtonHost.d.ts","sourceRoot":"","sources":["../../src/shared/ButtonHost.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,OAAO,CAAA;AACzC,OAAO,EAAC,IAAI,EAAC,MAAM,kBAAkB,CAAA;AAErC,MAAM,MAAM,eAAe,GAAG,cAAc,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,CAAA;AAEhE;;;;;GAKG;AACH,eAAO,MAAM,UAAU,EAAE,OAAO,IAAI,CAAC,MAAoB,CAAA"}
@@ -0,0 +1,14 @@
1
+ import { ReactElement, ReactNode, Ref } from 'react';
2
+ interface PressableChildProps {
3
+ onClick?: () => void;
4
+ ref?: Ref<unknown>;
5
+ }
6
+ /**
7
+ * Native overlay triggers and closes: native presses do not bubble to a
8
+ * wrapper as web clicks do, so the one child (a Button) gets the handler
9
+ * itself, after its own. `ref` also receives the child's host, where focus
10
+ * returns when the overlay closes.
11
+ */
12
+ export declare function withPress(children: ReactNode, onPress: () => void, ref?: Ref<unknown>): ReactElement<PressableChildProps, string | import('react').JSXElementConstructor<any>> | null;
13
+ export {};
14
+ //# sourceMappingURL=pressableChild.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pressableChild.d.ts","sourceRoot":"","sources":["../../src/shared/pressableChild.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,YAAY,EACjB,KAAK,SAAS,EACd,KAAK,GAAG,EACT,MAAM,OAAO,CAAA;AAGd,UAAU,mBAAmB;IAC3B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,GAAG,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC,CAAA;CACnB;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,IAAI,EAAE,GAAG,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC,iGAWrF"}
@@ -0,0 +1,7 @@
1
+ import { ReactNode } from 'react';
2
+ /**
3
+ * The text a React node would render, read from its props: a native picker's
4
+ * option label, where there is no DOM `textContent` to read it from.
5
+ */
6
+ export declare function textOf(node: ReactNode): string;
7
+ //# sourceMappingURL=textOf.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"textOf.d.ts","sourceRoot":"","sources":["../../src/shared/textOf.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,SAAS,EAAC,MAAM,OAAO,CAAA;AAEpD;;;GAGG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,CAM9C"}
package/dist/table.js CHANGED
@@ -1,4 +1,4 @@
1
- import { M as F, m as gt, R as it, H as kt, n as pt, o as bt, p as ft, q as mt, b as ht, t as yt, r as St, v as Tt } from "./Table-ColfpgKV.js";
1
+ import { M as F, q as gt, R as it, H as kt, t as pt, v as bt, w as ft, x as mt, b as ht, g as yt, r as St, y as Tt } from "./Table-DnaoH-h5.js";
2
2
  import { jsx as u, jsxs as q } from "react/jsx-runtime";
3
3
  import { c as H, m as p, d as b } from "./compiler-runtime-BuugS4D0.js";
4
4
  import { flexRender as E, useReactTable as rt, getPaginationRowModel as Ct, getFilteredRowModel as vt, getSortedRowModel as at, getCoreRowModel as st } from "@tanstack/react-table";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duro-app/ui",
3
- "version": "5.12.0",
3
+ "version": "5.14.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -39,6 +39,7 @@
39
39
  "types": "./dist/table.d.ts"
40
40
  },
41
41
  "./form": {
42
+ "react-native": "./src/form.native.ts",
42
43
  "source": "./src/form.ts",
43
44
  "import": "./dist/form.js",
44
45
  "types": "./dist/form.d.ts"
@@ -90,7 +91,7 @@
90
91
  },
91
92
  "dependencies": {
92
93
  "@tanstack/react-virtual": "^3.14.6",
93
- "@duro-app/tokens": "^5.12.0"
94
+ "@duro-app/tokens": "^5.14.0"
94
95
  },
95
96
  "devDependencies": {
96
97
  "@babel/preset-typescript": "^7.28.0",
@@ -2,7 +2,7 @@ import type {ComponentMeta} from '../component-meta'
2
2
 
3
3
  export const meta: ComponentMeta = {
4
4
  description:
5
- "An application's frame: a navigation rail (a labelled nav, the brand at its top, an optional footer at its foot) beside the main content (main), with an optional Header. Below collapseBelow (sm default, md or lg), measured on the shell's own width, the rail is hidden and one sticky bar (a banner, appBarH tall) holds a Menu button that opens the same navigation in a left Drawer, the brand, and the Header's content when there is one — a shell without a Header still has its navigation on a phone. Above it the Header is a row that scrolls with the page and the rail stays sticky. CSS makes the switch: the server renders one HTML for every width with the brand in both places, and display: none hides the inactive copy, so nothing moves after hydration. A skip link (the first tab stop) focuses Main; an Aside inside Main sticks below the bar. Labels come from props. Compound component — Root is required.",
5
+ "An application's frame: a navigation rail (a labelled nav, the brand at its top, an optional footer at its foot) beside the main content (main), with an optional Header. Below collapseBelow (sm default, md or lg), measured on the shell's own width, the rail is hidden and one sticky bar (a banner, appBarH tall) holds a Menu button that opens the same navigation in a left Drawer, the brand, and the Header's content when there is one — a shell without a Header still has its navigation on a phone. Above it the Header is a row that scrolls with the page and the rail stays sticky. CSS makes the switch: the server renders one HTML for every width with the brand in both places, and display: none hides the inactive copy, so nothing moves after hydration. A skip link (the first tab stop) focuses Main; an Aside inside Main sticks below the bar. Labels come from props. Compound component — Root is required. On React Native it is always the narrow shape: the bar below the status bar, Main under it, and the Rail in a left Drawer; there is no skip link.",
6
6
  whenToUse: [
7
7
  'The frame of an app with a persistent navigation rail: an admin, a docs site, a dashboard',
8
8
  'A layout whose navigation must collapse into a drawer on a phone',
@@ -0,0 +1,174 @@
1
+ import {
2
+ Children,
3
+ createContext,
4
+ isValidElement,
5
+ type ReactNode,
6
+ type Ref,
7
+ useCallback,
8
+ useContext,
9
+ useMemo,
10
+ useState,
11
+ } from 'react'
12
+ import {html} from 'react-strict-dom'
13
+ import {Button} from '../Button/Button'
14
+ import {Drawer} from '../Drawer/Drawer'
15
+ import {Icon} from '../Icon'
16
+ import {useSafeAreaInsets} from '../../shared/safeArea'
17
+ import {nativeStyles} from './native.css'
18
+
19
+ export type AppShellCollapse = 'sm' | 'md' | 'lg'
20
+
21
+ interface AppShellContextValue {
22
+ open: boolean
23
+ setOpen: (open: boolean) => void
24
+ closeLabel: string
25
+ }
26
+
27
+ const AppShellContext = createContext<AppShellContextValue | null>(null)
28
+
29
+ // True only where Root placed a Header itself, as on the web.
30
+ const HeaderSlotContext = createContext(false)
31
+
32
+ function useAppShell(): AppShellContextValue {
33
+ const ctx = useContext(AppShellContext)
34
+ if (!ctx) throw new Error('AppShell compound components must be used within AppShell.Root')
35
+ return ctx
36
+ }
37
+
38
+ interface RootProps {
39
+ /** The button that opens the navigation ("Menu"), from your text catalog. */
40
+ menuLabel: string
41
+ /** The accessible name of the drawer's close button ("Close navigation"). */
42
+ closeLabel: string
43
+ /** Web only: native has no skip link (no keyboard tab order to skip). */
44
+ skipLabel: string
45
+ /** The app's name or logo, shown in the bar beside the Menu button. */
46
+ brand?: ReactNode
47
+ /** Web only: a phone is below every breakpoint, so native always shows the bar and the drawer. */
48
+ collapseBelow?: AppShellCollapse
49
+ /** `AppShell.Rail`, `AppShell.Main` and an optional `AppShell.Header`, as direct children. */
50
+ children: ReactNode
51
+ }
52
+
53
+ /**
54
+ * Native AppShell (plan N0, Slice B): the narrow shape the web shows below
55
+ * `collapseBelow`, always — the bar on top (Menu button, brand, the Header's
56
+ * content) below the top safe-area inset, Main under it, and the Rail's
57
+ * navigation in a left Drawer. The same parts and props as the web.
58
+ */
59
+ function Root({menuLabel, closeLabel, brand, children}: RootProps) {
60
+ const [open, setOpen] = useState(false)
61
+ const insets = useSafeAreaInsets()
62
+
63
+ let header: ReactNode = null
64
+ const rest: ReactNode[] = []
65
+ Children.forEach(children, (child) => {
66
+ if (isValidElement<HeaderProps>(child) && child.type === Header) {
67
+ header = child
68
+ return
69
+ }
70
+ rest.push(child)
71
+ })
72
+
73
+ const value = useMemo(() => ({open, setOpen, closeLabel}), [open, closeLabel])
74
+
75
+ return (
76
+ <AppShellContext.Provider value={value}>
77
+ <html.div style={nativeStyles.shell}>
78
+ <html.header style={[nativeStyles.bar, nativeStyles.barTop(insets.top)]}>
79
+ <Button
80
+ variant="secondary"
81
+ size="small"
82
+ aria-expanded={open}
83
+ onClick={() => setOpen(true)}
84
+ >
85
+ <Icon name="menu" size="sm" />
86
+ {menuLabel}
87
+ </Button>
88
+ {brand}
89
+ <HeaderSlotContext.Provider value={true}>{header}</HeaderSlotContext.Provider>
90
+ </html.header>
91
+ {rest}
92
+ </html.div>
93
+ </AppShellContext.Provider>
94
+ )
95
+ }
96
+
97
+ export interface AppShellRailState {
98
+ /** Closes the drawer. Call it after any selection in the navigation. */
99
+ close: () => void
100
+ }
101
+
102
+ type RailContent = ReactNode | ((state: AppShellRailState) => ReactNode)
103
+
104
+ interface RailProps {
105
+ /** Names the navigation and the drawer ("Navigation"); required. */
106
+ 'aria-label': string
107
+ /** The navigation (a SideNav, a Tree), shown in the drawer; a function receives `close`. */
108
+ children: RailContent
109
+ /** What sits at the end of the drawer; a function receives `close`. */
110
+ footer?: RailContent
111
+ }
112
+
113
+ function Rail({'aria-label': ariaLabel, children, footer}: RailProps) {
114
+ const {open, setOpen, closeLabel} = useAppShell()
115
+ const close = useCallback(() => setOpen(false), [setOpen])
116
+ const content = typeof children === 'function' ? children({close}) : children
117
+ const foot = typeof footer === 'function' ? footer({close}) : footer
118
+
119
+ return (
120
+ <Drawer.Root anchor="left" open={open} onOpenChange={setOpen}>
121
+ <Drawer.Portal size="sm">
122
+ <Drawer.Header>
123
+ <Drawer.Title>{ariaLabel}</Drawer.Title>
124
+ <Drawer.Close aria-label={closeLabel} />
125
+ </Drawer.Header>
126
+ <Drawer.Body>
127
+ <html.nav aria-label={ariaLabel}>
128
+ {content}
129
+ {foot != null && <html.div style={nativeStyles.drawerFooter}>{foot}</html.div>}
130
+ </html.nav>
131
+ </Drawer.Body>
132
+ </Drawer.Portal>
133
+ </Drawer.Root>
134
+ )
135
+ }
136
+
137
+ interface HeaderProps {
138
+ /** The header's content: a search field, the account menu. It sits in the bar. */
139
+ children?: ReactNode
140
+ }
141
+
142
+ function Header({children}: HeaderProps) {
143
+ useAppShell()
144
+ if (!useContext(HeaderSlotContext)) {
145
+ throw new Error(
146
+ 'AppShell.Header must be a direct child of AppShell.Root (not inside a fragment or another component)',
147
+ )
148
+ }
149
+ return <html.div style={nativeStyles.header}>{children}</html.div>
150
+ }
151
+
152
+ interface MainProps {
153
+ children: ReactNode
154
+ /** Web only: the skip link's target. */
155
+ id?: string
156
+ /** The main region's host, e.g. to hand `useContainerBelow`. */
157
+ ref?: Ref<HTMLElement>
158
+ }
159
+
160
+ function Main({children, ref}: MainProps) {
161
+ useAppShell()
162
+ return (
163
+ <html.main ref={ref} style={nativeStyles.main}>
164
+ {children}
165
+ </html.main>
166
+ )
167
+ }
168
+
169
+ export const AppShell = {
170
+ Root,
171
+ Rail,
172
+ Header,
173
+ Main,
174
+ }
@@ -0,0 +1,56 @@
1
+ import {css} from 'react-strict-dom'
2
+ import {colors} from '@duro-app/tokens/tokens/colors.css'
3
+ import {spacing} from '@duro-app/tokens/tokens/spacing.css'
4
+ import {sizes} from '@duro-app/tokens/tokens/sizes.css'
5
+ import {borders} from '@duro-app/tokens/tokens/borders.css'
6
+
7
+ /**
8
+ * The native AppShell: a phone is below every `collapseBelow`, so it is
9
+ * always the narrow shape — the bar on top, Main below, the rail in a Drawer.
10
+ */
11
+ export const nativeStyles = css.create({
12
+ shell: {
13
+ flexGrow: 1,
14
+ display: 'flex',
15
+ flexDirection: 'column',
16
+ color: colors.text,
17
+ backgroundColor: colors.bg,
18
+ },
19
+ bar: {
20
+ display: 'flex',
21
+ flexDirection: 'row',
22
+ alignItems: 'center',
23
+ gap: spacing.sm,
24
+ minHeight: sizes.appBarH,
25
+ paddingLeft: spacing.md,
26
+ paddingRight: spacing.md,
27
+ backgroundColor: colors.bgCard,
28
+ borderBottomWidth: borders.hairline,
29
+ borderBottomStyle: 'solid',
30
+ borderBottomColor: colors.border,
31
+ },
32
+ // The bar sits below the status bar: the top safe-area inset is its padding.
33
+ barTop: (top: number) => ({paddingTop: top}),
34
+ header: {
35
+ display: 'flex',
36
+ flexDirection: 'row',
37
+ alignItems: 'center',
38
+ gap: spacing.sm,
39
+ flexGrow: 1,
40
+ flexShrink: 1,
41
+ },
42
+ main: {
43
+ flexGrow: 1,
44
+ flexShrink: 1,
45
+ paddingTop: spacing.lg,
46
+ paddingLeft: spacing.md,
47
+ paddingRight: spacing.md,
48
+ },
49
+ drawerFooter: {
50
+ marginTop: spacing.md,
51
+ paddingTop: spacing.md,
52
+ borderTopWidth: borders.hairline,
53
+ borderTopStyle: 'solid',
54
+ borderTopColor: colors.border,
55
+ },
56
+ })
@@ -1,9 +1,10 @@
1
1
  import type {ReactNode, Ref} from 'react'
2
- import {html} from 'react-strict-dom'
3
2
  import {isNative} from '../../platform'
4
3
  import {mergeRefs} from '../../shared/mergeRefs'
4
+ import {ButtonHost} from '../../shared/ButtonHost'
5
5
  import type {ControlSize} from '../../shared/types'
6
6
  import {useGroupedControl} from '../ButtonGroup/useGroupedControl'
7
+ import {useFormSubmit} from '../Form/FormSubmitContext'
7
8
  import {styles} from './styles.css'
8
9
 
9
10
  export type ButtonVariant = 'primary' | 'secondary' | 'inverseSecondary' | 'link' | 'danger'
@@ -50,14 +51,24 @@ export function Button({
50
51
  children,
51
52
  }: ButtonProps) {
52
53
  const grouped = useGroupedControl<HTMLButtonElement>({disabled})
54
+ // Native Form only (no DOM submit event); the web Form provides none.
55
+ const formSubmit = useFormSubmit()
56
+ const submit = type === 'submit' ? formSubmit : null
53
57
  return (
54
- <html.button
58
+ <ButtonHost
55
59
  ref={isNative ? ref : mergeRefs(ref, grouped.ref)}
56
60
  tabIndex={grouped.tabIndex}
57
61
  onFocus={grouped.onFocus}
58
62
  type={type}
59
63
  disabled={disabled}
60
- onClick={onClick}
64
+ onClick={
65
+ submit
66
+ ? () => {
67
+ onClick?.()
68
+ submit()
69
+ }
70
+ : onClick
71
+ }
61
72
  aria-label={ariaLabel}
62
73
  aria-expanded={ariaExpanded}
63
74
  aria-controls={ariaControls}
@@ -72,6 +83,6 @@ export function Button({
72
83
  ]}
73
84
  >
74
85
  {children}
75
- </html.button>
86
+ </ButtonHost>
76
87
  )
77
88
  }
@@ -2,7 +2,7 @@ import type {ComponentMeta} from '../component-meta'
2
2
 
3
3
  export const meta: ComponentMeta = {
4
4
  description:
5
- 'Color-mode controller + toggle. ColorModeProvider owns the preference (system/light/dark/high-contrast), resolves "system" against the OS scheme, persists it to localStorage, and applies the resolved theme via ThemeProvider. ColorModeToggle is an icon button that cycles the preference; useColorMode() reads it.',
5
+ 'Color-mode controller + toggle. ColorModeProvider owns the preference (system/light/dark/high-contrast), resolves "system" against the OS scheme, persists it to localStorage, and applies the resolved theme via ThemeProvider. ColorModeToggle is an icon button that cycles the preference; useColorMode() reads it. On React Native "system" follows the OS appearance (useColorScheme) and nothing is stored: persist the preference yourself through preference and onPreferenceChange.',
6
6
  whenToUse: [
7
7
  'App-level light/dark (and high-contrast) switching that persists across reloads',
8
8
  'Respecting the OS prefers-color-scheme with a user override',
@@ -12,6 +12,8 @@ import {isNative} from '../../platform'
12
12
  import {Icon, type IconName, type IconSize} from '../Icon/Icon'
13
13
  import {ThemeProvider, type ThemeName} from '../ThemeProvider/ThemeProvider'
14
14
  import {styles} from './styles.css'
15
+ import {useSystemScheme} from './useSystemScheme'
16
+ import {readStoredRaw, writeStoredRaw} from './storage'
15
17
 
16
18
  /**
17
19
  * The user's *preference*. `'system'` follows the OS `prefers-color-scheme`;
@@ -62,31 +64,13 @@ export function useColorMode(): ColorModeContextValue {
62
64
  return ctx
63
65
  }
64
66
 
65
- const canUseDom =
66
- !isNative && typeof window !== 'undefined' && typeof window.matchMedia === 'function'
67
-
68
67
  function isPreference(value: unknown): value is ColorModePreference {
69
68
  return typeof value === 'string' && (PREFERENCES as readonly string[]).includes(value)
70
69
  }
71
70
 
72
71
  function readStored(key: string | null): ColorModePreference | null {
73
- if (!key || isNative || typeof window === 'undefined') return null
74
- try {
75
- const raw = window.localStorage.getItem(key)
76
- return isPreference(raw) ? raw : null
77
- } catch {
78
- // Private mode / blocked storage — degrade to no persistence.
79
- return null
80
- }
81
- }
82
-
83
- function writeStored(key: string | null, value: ColorModePreference): void {
84
- if (!key || isNative || typeof window === 'undefined') return
85
- try {
86
- window.localStorage.setItem(key, value)
87
- } catch {
88
- /* ignore */
89
- }
72
+ const raw = readStoredRaw(key)
73
+ return isPreference(raw) ? raw : null
90
74
  }
91
75
 
92
76
  interface ColorModeProviderProps {
@@ -100,7 +84,8 @@ interface ColorModeProviderProps {
100
84
  onPreferenceChange?: (preference: ColorModePreference) => void
101
85
  /** Cycle order for `cycle()` / `<ColorModeToggle>`. Default `['light', 'dark']`. */
102
86
  modes?: readonly ColorModePreference[]
103
- /** localStorage key for persistence; `null` disables it. Default `'duro-color-mode'`. */
87
+ /** localStorage key for persistence; `null` disables it. Default `'duro-color-mode'`.
88
+ * Native has no localStorage: persist through `preference` and `onPreferenceChange`. */
104
89
  storageKey?: string | null
105
90
  /** Wrap children in the DS `ThemeProvider` with the resolved theme. Default `true`.
106
91
  * Set `false` to drive your own ThemeProvider from `useColorMode().theme`. */
@@ -136,26 +121,15 @@ export function ColorModeProvider({
136
121
  const setPreference = useCallback(
137
122
  (next: ColorModePreference) => {
138
123
  if (controlled === undefined) setInternal(next)
139
- writeStored(storageKey, next)
124
+ writeStoredRaw(storageKey, next)
140
125
  onPreferenceChange?.(next)
141
126
  },
142
127
  [controlled, storageKey, onPreferenceChange],
143
128
  )
144
129
 
145
- // Track the OS scheme so `'system'` stays live. The initial value MUST be the
146
- // SSR-stable default ('dark', what the server renders without a DOM) so the
147
- // first client render matches the server — reading matchMedia in the
148
- // initializer would diverge (server 'dark' vs client's real scheme) and trip
149
- // a hydration mismatch. The effect reconciles to the real scheme after mount.
150
- const [systemTheme, setSystemTheme] = useState<'light' | 'dark'>('dark')
151
- useEffect(() => {
152
- if (!canUseDom) return
153
- const mq = window.matchMedia('(prefers-color-scheme: dark)')
154
- const onChange = () => setSystemTheme(mq.matches ? 'dark' : 'light')
155
- onChange()
156
- mq.addEventListener('change', onChange)
157
- return () => mq.removeEventListener('change', onChange)
158
- }, [])
130
+ // Track the OS scheme so `'system'` stays live (web: prefers-color-scheme,
131
+ // after mount so hydration matches; native: RN Appearance).
132
+ const systemTheme = useSystemScheme()
159
133
 
160
134
  const theme: ThemeName = preference === 'system' ? systemTheme : preference
161
135
 
@@ -196,7 +170,12 @@ export function ColorModeToggle({size = 'md', 'aria-label': ariaLabel}: ColorMod
196
170
  const label = ariaLabel ?? `Color mode: ${LABEL[preference]}. Switch to ${LABEL[next]}.`
197
171
 
198
172
  return (
199
- <html.button type="button" aria-label={label} onClick={cycle} style={styles.button}>
173
+ <html.button
174
+ type="button"
175
+ aria-label={label}
176
+ onClick={cycle}
177
+ style={[styles.button, isNative && styles.buttonNative]}
178
+ >
200
179
  <Icon name={ICON[preference]} size={size} />
201
180
  </html.button>
202
181
  )
@@ -0,0 +1,8 @@
1
+ // Native has no localStorage: an app persists the preference itself, through
2
+ // ColorModeProvider's `preference` and `onPreferenceChange`.
3
+
4
+ export function readStoredRaw(_key: string | null): string | null {
5
+ return null
6
+ }
7
+
8
+ export function writeStoredRaw(_key: string | null, _value: string): void {}
@@ -0,0 +1,19 @@
1
+ /** The stored preference, as a raw string; null without storage or a value. */
2
+ export function readStoredRaw(key: string | null): string | null {
3
+ if (!key || typeof window === 'undefined') return null
4
+ try {
5
+ return window.localStorage.getItem(key)
6
+ } catch {
7
+ // Private mode / blocked storage — degrade to no persistence.
8
+ return null
9
+ }
10
+ }
11
+
12
+ export function writeStoredRaw(key: string | null, value: string): void {
13
+ if (!key || typeof window === 'undefined') return
14
+ try {
15
+ window.localStorage.setItem(key, value)
16
+ } catch {
17
+ /* ignore */
18
+ }
19
+ }
@@ -31,4 +31,10 @@ export const styles = css.create({
31
31
  outlineColor: colors.accent,
32
32
  outlineOffset: borders.focusOffset,
33
33
  },
34
+ // RSD on native lays out only display: flex (not inline-flex), and a colour
35
+ // transition there ends near-black: keep the layout, drop the colour easing.
36
+ buttonNative: {
37
+ display: 'flex',
38
+ transitionProperty: 'opacity',
39
+ },
34
40
  })
@@ -0,0 +1,6 @@
1
+ import {useColorScheme} from 'react-native'
2
+
3
+ /** The OS colour scheme, live, from RN Appearance; no answer reads as dark. */
4
+ export function useSystemScheme(): 'light' | 'dark' {
5
+ return useColorScheme() === 'light' ? 'light' : 'dark'
6
+ }
@@ -0,0 +1,19 @@
1
+ import {useEffect, useState} from 'react'
2
+
3
+ /**
4
+ * The OS colour scheme, live. The first render answers `'dark'`, what the
5
+ * server renders without a DOM, so hydration matches; the effect then reads
6
+ * `prefers-color-scheme` and follows its changes.
7
+ */
8
+ export function useSystemScheme(): 'light' | 'dark' {
9
+ const [scheme, setScheme] = useState<'light' | 'dark'>('dark')
10
+ useEffect(() => {
11
+ if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return
12
+ const mq = window.matchMedia('(prefers-color-scheme: dark)')
13
+ const onChange = () => setScheme(mq.matches ? 'dark' : 'light')
14
+ onChange()
15
+ mq.addEventListener('change', onChange)
16
+ return () => mq.removeEventListener('change', onChange)
17
+ }, [])
18
+ return scheme
19
+ }
@@ -2,7 +2,7 @@ import type {ComponentMeta} from '../component-meta'
2
2
 
3
3
  export const meta: ComponentMeta = {
4
4
  description:
5
- 'Searchable dropdown for selecting a value from a filterable list. Combines a text input with a popup listbox. Compound component — Root is required.',
5
+ "Searchable dropdown for selecting a value from a filterable list. Combines a text input with a popup listbox. Compound component — Root is required. Combobox.Popup takes label (the list's accessible name). On React Native the field opens a bottom sheet at its large detent, above the keyboard, titled by label with a Cancel text button (cancelLabel): a search field is pinned under the header and focused, the list below filters as you type, Combobox.Empty shows when nothing matches, and a press picks an option and closes the sheet. The sheet's search text starts empty each time; Combobox.Trigger renders nothing on native.",
6
6
  whenToUse: [
7
7
  'Large option lists where users benefit from typing to filter (e.g. country, city, user picker)',
8
8
  'Autocomplete or typeahead scenarios',