@duro-app/ui 5.11.0 → 5.13.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.
- package/dist/Table-Bms2ftt3.js +1694 -0
- package/dist/Table-Bms2ftt3.js.map +1 -0
- package/dist/compiler-runtime-BuugS4D0.js.map +1 -1
- package/dist/components/Alert/Alert.d.ts.map +1 -1
- package/dist/components/AppShell/native.css.d.ts +53 -0
- package/dist/components/AppShell/native.css.d.ts.map +1 -0
- package/dist/components/Button/Button.d.ts +2 -1
- package/dist/components/Button/Button.d.ts.map +1 -1
- package/dist/components/Button/styles.css.d.ts +1 -0
- package/dist/components/Button/styles.css.d.ts.map +1 -1
- package/dist/components/Callout/Callout.d.ts.map +1 -1
- package/dist/components/Callout/styles.css.d.ts +3 -0
- package/dist/components/Callout/styles.css.d.ts.map +1 -1
- package/dist/components/Checkbox/Checkbox.d.ts.map +1 -1
- package/dist/components/ConfirmDialog/ConfirmDialog.d.ts.map +1 -1
- package/dist/components/DetailPanel/DetailPanel.d.ts.map +1 -1
- package/dist/components/Dialog/Dialog.d.ts +2 -17
- package/dist/components/Dialog/Dialog.d.ts.map +1 -1
- package/dist/components/Dialog/Dialog.stories.d.ts +4 -0
- package/dist/components/Dialog/Dialog.stories.d.ts.map +1 -1
- package/dist/components/Dialog/native.css.d.ts +82 -0
- package/dist/components/Dialog/native.css.d.ts.map +1 -0
- package/dist/components/Dialog/parts.css.d.ts +82 -0
- package/dist/components/Dialog/parts.css.d.ts.map +1 -0
- package/dist/components/Dialog/parts.d.ts +35 -0
- package/dist/components/Dialog/parts.d.ts.map +1 -0
- package/dist/components/Dialog/styles.css.d.ts +1 -74
- package/dist/components/Dialog/styles.css.d.ts.map +1 -1
- package/dist/components/Drawer/Drawer.d.ts +7 -35
- package/dist/components/Drawer/Drawer.d.ts.map +1 -1
- package/dist/components/Drawer/Drawer.meta.d.ts.map +1 -1
- package/dist/components/Drawer/DrawerScroll.stories.d.ts.map +1 -1
- package/dist/components/Drawer/DrawerSnap.stories.d.ts +20 -0
- package/dist/components/Drawer/DrawerSnap.stories.d.ts.map +1 -0
- package/dist/components/Drawer/native.css.d.ts +55 -0
- package/dist/components/Drawer/native.css.d.ts.map +1 -0
- package/dist/components/Drawer/parts.css.d.ts +96 -0
- package/dist/components/Drawer/parts.css.d.ts.map +1 -0
- package/dist/components/Drawer/parts.d.ts +56 -0
- package/dist/components/Drawer/parts.d.ts.map +1 -0
- package/dist/components/Drawer/sheetPhysics.d.ts +56 -0
- package/dist/components/Drawer/sheetPhysics.d.ts.map +1 -0
- package/dist/components/Drawer/styles.css.d.ts +15 -58
- package/dist/components/Drawer/styles.css.d.ts.map +1 -1
- package/dist/components/Drawer/touchInput.d.ts +35 -0
- package/dist/components/Drawer/touchInput.d.ts.map +1 -0
- package/dist/components/Drawer/useDrawerRoot.d.ts +33 -0
- package/dist/components/Drawer/useDrawerRoot.d.ts.map +1 -0
- package/dist/components/Drawer/useSheetHeights.d.ts +7 -0
- package/dist/components/Drawer/useSheetHeights.d.ts.map +1 -0
- package/dist/components/Drawer/useSwipeDismiss.d.ts +13 -2
- package/dist/components/Drawer/useSwipeDismiss.d.ts.map +1 -1
- package/dist/components/Grid/styles.css.d.ts +1 -0
- package/dist/components/Grid/styles.css.d.ts.map +1 -1
- package/dist/components/Icon/Icon.d.ts +3 -9
- package/dist/components/Icon/Icon.d.ts.map +1 -1
- package/dist/components/Icon/geometry.d.ts +60 -0
- package/dist/components/Icon/geometry.d.ts.map +1 -0
- package/dist/components/Icon/types.d.ts +15 -0
- package/dist/components/Icon/types.d.ts.map +1 -0
- package/dist/components/Input/Input.d.ts +5 -1
- package/dist/components/Input/Input.d.ts.map +1 -1
- package/dist/components/Input/styles.css.d.ts +1 -0
- package/dist/components/Input/styles.css.d.ts.map +1 -1
- package/dist/components/LinkButton/styles.css.d.ts +1 -0
- package/dist/components/LinkButton/styles.css.d.ts.map +1 -1
- package/dist/components/Menu/Menu.d.ts +7 -1
- package/dist/components/Menu/Menu.d.ts.map +1 -1
- package/dist/components/Menu/native.css.d.ts +14 -0
- package/dist/components/Menu/native.css.d.ts.map +1 -0
- package/dist/components/Menu/useMenuOpen.d.ts +16 -0
- package/dist/components/Menu/useMenuOpen.d.ts.map +1 -0
- package/dist/components/Menu/useMenuRoot.d.ts +3 -6
- package/dist/components/Menu/useMenuRoot.d.ts.map +1 -1
- package/dist/components/Meter/styles.css.d.ts +1 -0
- package/dist/components/Meter/styles.css.d.ts.map +1 -1
- package/dist/components/PageShell/styles.css.d.ts +2 -0
- package/dist/components/PageShell/styles.css.d.ts.map +1 -1
- package/dist/components/Popover/native.css.d.ts +11 -0
- package/dist/components/Popover/native.css.d.ts.map +1 -0
- package/dist/components/RadioGroup/RadioGroup.d.ts +1 -17
- package/dist/components/RadioGroup/RadioGroup.d.ts.map +1 -1
- package/dist/components/RadioGroup/RadioGroup.stories.d.ts +3 -0
- package/dist/components/RadioGroup/RadioGroup.stories.d.ts.map +1 -1
- package/dist/components/RadioGroup/group.d.ts +31 -0
- package/dist/components/RadioGroup/group.d.ts.map +1 -0
- package/dist/components/RadioGroup/styles.css.d.ts +10 -0
- package/dist/components/RadioGroup/styles.css.d.ts.map +1 -1
- package/dist/components/ScrollArea/styles.css.d.ts +1 -0
- package/dist/components/ScrollArea/styles.css.d.ts.map +1 -1
- package/dist/components/SideNav/Chevron.d.ts +4 -0
- package/dist/components/SideNav/Chevron.d.ts.map +1 -0
- package/dist/components/SideNav/SideNav.d.ts.map +1 -1
- package/dist/components/SideNav/styles.css.d.ts +6 -0
- package/dist/components/SideNav/styles.css.d.ts.map +1 -1
- package/dist/components/SideNav/types.d.ts +6 -0
- package/dist/components/SideNav/types.d.ts.map +1 -0
- package/dist/components/Spinner/Spinner.d.ts +2 -6
- package/dist/components/Spinner/Spinner.d.ts.map +1 -1
- package/dist/components/Spinner/Spinner.types.d.ts +6 -0
- package/dist/components/Spinner/Spinner.types.d.ts.map +1 -0
- package/dist/components/Spinner/ring.css.d.ts +39 -0
- package/dist/components/Spinner/ring.css.d.ts.map +1 -0
- package/dist/components/Spinner/styles.css.d.ts +1 -28
- package/dist/components/Spinner/styles.css.d.ts.map +1 -1
- package/dist/components/SplitPane/SplitPane.d.ts +38 -0
- package/dist/components/SplitPane/SplitPane.d.ts.map +1 -0
- package/dist/components/SplitPane/SplitPane.meta.d.ts +3 -0
- package/dist/components/SplitPane/SplitPane.meta.d.ts.map +1 -0
- package/dist/components/SplitPane/SplitPane.stories.d.ts +17 -0
- package/dist/components/SplitPane/SplitPane.stories.d.ts.map +1 -0
- package/dist/components/SplitPane/styles.css.d.ts +78 -0
- package/dist/components/SplitPane/styles.css.d.ts.map +1 -0
- package/dist/components/StatusIcon/StatusIcon.d.ts.map +1 -1
- package/dist/components/StatusIcon/styles.css.d.ts +3 -0
- package/dist/components/StatusIcon/styles.css.d.ts.map +1 -1
- package/dist/components/Switch/Switch.d.ts +2 -18
- package/dist/components/Switch/Switch.d.ts.map +1 -1
- package/dist/components/Switch/Switch.types.d.ts +18 -0
- package/dist/components/Switch/Switch.types.d.ts.map +1 -0
- package/dist/components/TableOfContents/styles.css.d.ts +1 -0
- package/dist/components/TableOfContents/styles.css.d.ts.map +1 -1
- package/dist/components/Tabs/TabLayout.d.ts +7 -0
- package/dist/components/Tabs/TabLayout.d.ts.map +1 -0
- package/dist/components/Tabs/Tabs.d.ts.map +1 -1
- package/dist/components/Tabs/indicator.d.ts +28 -0
- package/dist/components/Tabs/indicator.d.ts.map +1 -0
- package/dist/components/Tabs/styles.css.d.ts +4 -0
- package/dist/components/Tabs/styles.css.d.ts.map +1 -1
- package/dist/components/Tabs/useTabIndicator.d.ts +4 -0
- package/dist/components/Tabs/useTabIndicator.d.ts.map +1 -0
- package/dist/components/Tabs/useTabKeys.d.ts +13 -0
- package/dist/components/Tabs/useTabKeys.d.ts.map +1 -0
- package/dist/components/Tag/RemoveGlyph.d.ts +4 -0
- package/dist/components/Tag/RemoveGlyph.d.ts.map +1 -0
- package/dist/components/Tag/Tag.d.ts +2 -24
- package/dist/components/Tag/Tag.d.ts.map +1 -1
- package/dist/components/Tag/styles.css.d.ts +8 -0
- package/dist/components/Tag/styles.css.d.ts.map +1 -1
- package/dist/components/Tag/types.d.ts +39 -0
- package/dist/components/Tag/types.d.ts.map +1 -0
- package/dist/components/Textarea/styles.css.d.ts +1 -0
- package/dist/components/Textarea/styles.css.d.ts.map +1 -1
- package/dist/components/Toast/ToastProvider.d.ts +2 -21
- package/dist/components/Toast/ToastProvider.d.ts.map +1 -1
- package/dist/components/Toast/native.css.d.ts +81 -0
- package/dist/components/Toast/native.css.d.ts.map +1 -0
- package/dist/components/Toast/store.d.ts +47 -0
- package/dist/components/Toast/store.d.ts.map +1 -0
- package/dist/components/Toggle/Toggle.d.ts.map +1 -1
- package/dist/components/Toggle/styles.css.d.ts +11 -2
- package/dist/components/Toggle/styles.css.d.ts.map +1 -1
- package/dist/components/Tooltip/TooltipHint.d.ts +9 -0
- package/dist/components/Tooltip/TooltipHint.d.ts.map +1 -0
- package/dist/components/Tree/Tree.d.ts.map +1 -1
- package/dist/components/Tree/styles.css.d.ts +6 -0
- package/dist/components/Tree/styles.css.d.ts.map +1 -1
- package/dist/components/Tree/useTreeKeyboard.d.ts +19 -0
- package/dist/components/Tree/useTreeKeyboard.d.ts.map +1 -0
- package/dist/components/VirtualList/VirtualList.d.ts +33 -0
- package/dist/components/VirtualList/VirtualList.d.ts.map +1 -0
- package/dist/components/VirtualList/VirtualList.meta.d.ts +3 -0
- package/dist/components/VirtualList/VirtualList.meta.d.ts.map +1 -0
- package/dist/components/VirtualList/VirtualList.stories.d.ts +15 -0
- package/dist/components/VirtualList/VirtualList.stories.d.ts.map +1 -0
- package/dist/components/VirtualList/styles.css.d.ts +28 -0
- package/dist/components/VirtualList/styles.css.d.ts.map +1 -0
- package/dist/components/VirtualTable/styles.css.d.ts +6 -0
- package/dist/components/VirtualTable/styles.css.d.ts.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6202 -4794
- package/dist/index.js.map +1 -1
- package/dist/shared/ButtonHost.d.ts +11 -0
- package/dist/shared/ButtonHost.d.ts.map +1 -0
- package/dist/shared/portal/hostRegistry.d.ts +33 -0
- package/dist/shared/portal/hostRegistry.d.ts.map +1 -0
- package/dist/shared/pressableChild.d.ts +14 -0
- package/dist/shared/pressableChild.d.ts.map +1 -0
- package/dist/shared/useOverlayOpenState.d.ts +40 -0
- package/dist/shared/useOverlayOpenState.d.ts.map +1 -0
- package/dist/table.js +141 -135
- package/dist/table.js.map +1 -1
- package/package.json +34 -4
- package/src/components/Alert/Alert.tsx +14 -2
- package/src/components/AppShell/AppShell.meta.ts +1 -1
- package/src/components/AppShell/AppShell.native.tsx +174 -0
- package/src/components/AppShell/native.css.ts +56 -0
- package/src/components/Button/Button.tsx +6 -5
- package/src/components/Button/styles.css.ts +2 -0
- package/src/components/Callout/Callout.tsx +17 -3
- package/src/components/Callout/styles.css.ts +5 -0
- package/src/components/Checkbox/Checkbox.native.tsx +67 -0
- package/src/components/Checkbox/Checkbox.tsx +1 -50
- package/src/components/ConfirmDialog/ConfirmDialog.tsx +3 -0
- package/src/components/DetailPanel/DetailPanel.tsx +9 -45
- package/src/components/Dialog/Dialog.native.tsx +296 -0
- package/src/components/Dialog/Dialog.stories.tsx +71 -0
- package/src/components/Dialog/Dialog.tsx +41 -130
- package/src/components/Dialog/native.css.ts +78 -0
- package/src/components/Dialog/parts.css.ts +101 -0
- package/src/components/Dialog/parts.tsx +61 -0
- package/src/components/Dialog/styles.css.ts +2 -89
- package/src/components/Drawer/Drawer.meta.ts +2 -1
- package/src/components/Drawer/Drawer.native.tsx +469 -0
- package/src/components/Drawer/Drawer.tsx +63 -179
- package/src/components/Drawer/DrawerScroll.stories.tsx +4 -54
- package/src/components/Drawer/DrawerSnap.stories.tsx +205 -0
- package/src/components/Drawer/native.css.ts +58 -0
- package/src/components/Drawer/parts.css.ts +125 -0
- package/src/components/Drawer/parts.tsx +110 -0
- package/src/components/Drawer/sheetPhysics.ts +103 -0
- package/src/components/Drawer/styles.css.ts +19 -74
- package/src/components/Drawer/touchInput.ts +81 -0
- package/src/components/Drawer/useDrawerRoot.ts +90 -0
- package/src/components/Drawer/useSheetHeights.ts +42 -0
- package/src/components/Drawer/useSwipeDismiss.ts +146 -23
- package/src/components/Grid/styles.css.ts +2 -0
- package/src/components/Icon/Icon.native.tsx +54 -0
- package/src/components/Icon/Icon.tsx +19 -430
- package/src/components/Icon/geometry.ts +456 -0
- package/src/components/Icon/types.ts +74 -0
- package/src/components/Input/Input.tsx +8 -0
- package/src/components/Input/styles.css.ts +2 -0
- package/src/components/LinkButton/styles.css.ts +2 -0
- package/src/components/Menu/Menu.meta.ts +1 -1
- package/src/components/Menu/Menu.native.tsx +268 -0
- package/src/components/Menu/Menu.tsx +11 -2
- package/src/components/Menu/native.css.ts +18 -0
- package/src/components/Menu/useMenuOpen.ts +33 -0
- package/src/components/Menu/useMenuRoot.ts +7 -26
- package/src/components/Meter/styles.css.ts +2 -0
- package/src/components/PageShell/styles.css.ts +4 -0
- package/src/components/Popover/Popover.meta.ts +1 -1
- package/src/components/Popover/Popover.native.tsx +205 -0
- package/src/components/Popover/native.css.ts +13 -0
- package/src/components/RadioGroup/RadioGroup.meta.ts +1 -1
- package/src/components/RadioGroup/RadioGroup.native.tsx +48 -0
- package/src/components/RadioGroup/RadioGroup.stories.tsx +64 -1
- package/src/components/RadioGroup/RadioGroup.tsx +8 -83
- package/src/components/RadioGroup/group.tsx +85 -0
- package/src/components/RadioGroup/styles.css.ts +13 -0
- package/src/components/ScrollArea/styles.css.ts +2 -0
- package/src/components/SideNav/Chevron.native.tsx +20 -0
- package/src/components/SideNav/Chevron.tsx +20 -0
- package/src/components/SideNav/SideNav.meta.ts +1 -1
- package/src/components/SideNav/SideNav.tsx +57 -41
- package/src/components/SideNav/styles.css.ts +8 -0
- package/src/components/SideNav/types.ts +6 -0
- package/src/components/Spinner/Spinner.native.tsx +37 -0
- package/src/components/Spinner/Spinner.tsx +6 -15
- package/src/components/Spinner/Spinner.types.ts +6 -0
- package/src/components/Spinner/ring.css.ts +45 -0
- package/src/components/Spinner/styles.css.ts +2 -32
- package/src/components/SplitPane/SplitPane.meta.ts +40 -0
- package/src/components/SplitPane/SplitPane.native.tsx +47 -0
- package/src/components/SplitPane/SplitPane.stories.tsx +148 -0
- package/src/components/SplitPane/SplitPane.tsx +218 -0
- package/src/components/SplitPane/styles.css.ts +81 -0
- package/src/components/StatusIcon/StatusIcon.tsx +13 -2
- package/src/components/StatusIcon/styles.css.ts +5 -0
- package/src/components/Switch/Switch.native.tsx +45 -0
- package/src/components/Switch/Switch.tsx +22 -44
- package/src/components/Switch/Switch.types.ts +18 -0
- package/src/components/TableOfContents/styles.css.ts +2 -0
- package/src/components/Tabs/TabLayout.native.tsx +9 -0
- package/src/components/Tabs/TabLayout.tsx +6 -0
- package/src/components/Tabs/Tabs.meta.ts +1 -1
- package/src/components/Tabs/Tabs.tsx +38 -136
- package/src/components/Tabs/indicator.ts +36 -0
- package/src/components/Tabs/styles.css.ts +7 -0
- package/src/components/Tabs/useTabIndicator.native.ts +35 -0
- package/src/components/Tabs/useTabIndicator.ts +49 -0
- package/src/components/Tabs/useTabKeys.native.ts +2 -0
- package/src/components/Tabs/useTabKeys.ts +99 -0
- package/src/components/Tag/RemoveGlyph.native.tsx +17 -0
- package/src/components/Tag/RemoveGlyph.tsx +10 -0
- package/src/components/Tag/Tag.meta.ts +1 -1
- package/src/components/Tag/Tag.native.tsx +70 -0
- package/src/components/Tag/Tag.tsx +9 -37
- package/src/components/Tag/styles.css.ts +9 -0
- package/src/components/Tag/types.ts +43 -0
- package/src/components/Textarea/styles.css.ts +2 -0
- package/src/components/ThemeProvider/ThemeProvider.native.tsx +70 -10
- package/src/components/Toast/Toast.meta.ts +1 -1
- package/src/components/Toast/ToastProvider.native.tsx +226 -0
- package/src/components/Toast/ToastProvider.tsx +22 -84
- package/src/components/Toast/native.css.ts +78 -0
- package/src/components/Toast/store.ts +96 -0
- package/src/components/Toggle/Toggle.tsx +5 -3
- package/src/components/Toggle/styles.css.ts +14 -2
- package/src/components/Tooltip/Tooltip.meta.ts +1 -1
- package/src/components/Tooltip/Tooltip.native.tsx +36 -0
- package/src/components/Tooltip/TooltipHint.ts +13 -0
- package/src/components/Tree/Tree.meta.ts +1 -1
- package/src/components/Tree/Tree.tsx +23 -135
- package/src/components/Tree/styles.css.ts +6 -0
- package/src/components/Tree/useTreeKeyboard.native.ts +5 -0
- package/src/components/Tree/useTreeKeyboard.ts +152 -0
- package/src/components/VirtualList/VirtualList.meta.ts +39 -0
- package/src/components/VirtualList/VirtualList.stories.tsx +95 -0
- package/src/components/VirtualList/VirtualList.tsx +103 -0
- package/src/components/VirtualList/styles.css.ts +31 -0
- package/src/components/VirtualTable/styles.css.ts +12 -0
- package/src/hooks/useContainerBelow.native.ts +30 -0
- package/src/index.native.ts +61 -1
- package/src/index.ts +8 -1
- package/src/native-guard/globals.d.ts +10 -0
- package/src/native-guard/react-dom.d.ts +3 -0
- package/src/shared/ButtonHost.native.tsx +70 -0
- package/src/shared/ButtonHost.tsx +12 -0
- package/src/shared/accessibilityFocus.native.ts +23 -0
- package/src/shared/portal/AnchoredOverlay.native.tsx +117 -0
- package/src/shared/portal/OverlayModal.native.tsx +51 -0
- package/src/shared/portal/PortalHost.native.tsx +102 -0
- package/src/shared/portal/hostRegistry.ts +76 -0
- package/src/shared/portal/themeScope.native.ts +18 -0
- package/src/shared/pressableChild.ts +33 -0
- package/src/shared/safeArea.native.ts +22 -0
- package/src/shared/useKeyboardHeight.native.ts +18 -0
- package/src/shared/useOverlayOpenState.ts +128 -0
- package/src/shared/useScreenReaderEnabled.native.ts +19 -0
- package/dist/Table-ColfpgKV.js +0 -1552
- package/dist/Table-ColfpgKV.js.map +0 -1
- package/dist/components/ThemeProvider/ThemeProvider.native.d.ts +0 -10
- package/dist/components/ThemeProvider/ThemeProvider.native.d.ts.map +0 -1
- package/dist/index.native.d.ts +0 -33
- package/dist/index.native.d.ts.map +0 -1
- package/dist/platform.native.d.ts +0 -2
- package/dist/platform.native.d.ts.map +0 -1
|
@@ -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,7 +1,7 @@
|
|
|
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
7
|
import {styles} from './styles.css'
|
|
@@ -24,7 +24,8 @@ interface ButtonProps {
|
|
|
24
24
|
'aria-controls'?: string
|
|
25
25
|
/**
|
|
26
26
|
* The underlying <button>, e.g. to anchor a Popover or return focus to it.
|
|
27
|
-
*
|
|
27
|
+
* On native it is react-strict-dom's element (a Pressable host), which
|
|
28
|
+
* measures with `measureInWindow` and takes accessibility focus.
|
|
28
29
|
*/
|
|
29
30
|
ref?: Ref<HTMLButtonElement>
|
|
30
31
|
children: ReactNode
|
|
@@ -50,8 +51,8 @@ export function Button({
|
|
|
50
51
|
}: ButtonProps) {
|
|
51
52
|
const grouped = useGroupedControl<HTMLButtonElement>({disabled})
|
|
52
53
|
return (
|
|
53
|
-
<
|
|
54
|
-
ref={isNative ?
|
|
54
|
+
<ButtonHost
|
|
55
|
+
ref={isNative ? ref : mergeRefs(ref, grouped.ref)}
|
|
55
56
|
tabIndex={grouped.tabIndex}
|
|
56
57
|
onFocus={grouped.onFocus}
|
|
57
58
|
type={type}
|
|
@@ -71,6 +72,6 @@ export function Button({
|
|
|
71
72
|
]}
|
|
72
73
|
>
|
|
73
74
|
{children}
|
|
74
|
-
</
|
|
75
|
+
</ButtonHost>
|
|
75
76
|
)
|
|
76
77
|
}
|
|
@@ -2,6 +2,8 @@ import type {ReactNode} from 'react'
|
|
|
2
2
|
import {html} from 'react-strict-dom'
|
|
3
3
|
import {Icon} from '../Icon'
|
|
4
4
|
import type {IconName} from '../Icon'
|
|
5
|
+
import type {ColorToken} from '@duro-app/tokens/keys'
|
|
6
|
+
import {isNative} from '../../platform'
|
|
5
7
|
import {styles} from './styles.css'
|
|
6
8
|
|
|
7
9
|
export type CalloutVariant = 'error' | 'success' | 'warning' | 'info'
|
|
@@ -13,6 +15,15 @@ const defaultIcons: Record<CalloutVariant, IconName> = {
|
|
|
13
15
|
error: 'x-circle-filled',
|
|
14
16
|
}
|
|
15
17
|
|
|
18
|
+
// On native an Icon cannot inherit the variant's text colour, so it is passed
|
|
19
|
+
// (the web keeps inheriting it).
|
|
20
|
+
const iconColor = {
|
|
21
|
+
error: 'errorText',
|
|
22
|
+
success: 'successText',
|
|
23
|
+
warning: 'warningText',
|
|
24
|
+
info: 'infoText',
|
|
25
|
+
} as const satisfies Record<CalloutVariant, ColorToken>
|
|
26
|
+
|
|
16
27
|
interface CalloutProps {
|
|
17
28
|
variant?: CalloutVariant
|
|
18
29
|
/** Built-in icon name, custom ReactNode, or false to hide. Defaults to variant icon. */
|
|
@@ -28,8 +39,9 @@ interface CalloutProps {
|
|
|
28
39
|
|
|
29
40
|
function resolveIcon(icon: CalloutProps['icon'], variant: CalloutVariant): ReactNode | null {
|
|
30
41
|
if (icon === false) return null
|
|
31
|
-
|
|
32
|
-
if (
|
|
42
|
+
const color = isNative ? iconColor[variant] : undefined
|
|
43
|
+
if (icon === undefined) return <Icon name={defaultIcons[variant]} size="xl" color={color} />
|
|
44
|
+
if (typeof icon === 'string') return <Icon name={icon as IconName} size="xl" color={color} />
|
|
33
45
|
return icon
|
|
34
46
|
}
|
|
35
47
|
|
|
@@ -45,7 +57,9 @@ export function Callout({variant = 'info', icon, align = 'center', children}: Ca
|
|
|
45
57
|
align === 'center' ? styles.alignCenter : styles.alignStart,
|
|
46
58
|
]}
|
|
47
59
|
>
|
|
48
|
-
{resolvedIcon &&
|
|
60
|
+
{resolvedIcon && (
|
|
61
|
+
<html.span style={[styles.icon, isNative && styles.nativeFlex]}>{resolvedIcon}</html.span>
|
|
62
|
+
)}
|
|
49
63
|
<html.div style={styles.content}>{children}</html.div>
|
|
50
64
|
</html.div>
|
|
51
65
|
)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import {type ReactNode, useState, useCallback} from 'react'
|
|
2
|
+
import {html} from 'react-strict-dom'
|
|
3
|
+
import {styles} from './styles.css'
|
|
4
|
+
|
|
5
|
+
interface CheckboxProps {
|
|
6
|
+
name?: string
|
|
7
|
+
value?: string
|
|
8
|
+
checked?: boolean
|
|
9
|
+
defaultChecked?: boolean
|
|
10
|
+
disabled?: boolean
|
|
11
|
+
/** Accessible label when no visible label (children) is provided. */
|
|
12
|
+
'aria-label'?: string
|
|
13
|
+
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void
|
|
14
|
+
children?: ReactNode
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function Checkbox({
|
|
18
|
+
name,
|
|
19
|
+
value,
|
|
20
|
+
checked: controlledChecked,
|
|
21
|
+
defaultChecked = false,
|
|
22
|
+
disabled = false,
|
|
23
|
+
'aria-label': ariaLabel,
|
|
24
|
+
onChange,
|
|
25
|
+
children,
|
|
26
|
+
}: CheckboxProps) {
|
|
27
|
+
const isControlled = controlledChecked !== undefined
|
|
28
|
+
const [internalChecked, setInternalChecked] = useState(defaultChecked)
|
|
29
|
+
const isChecked = isControlled ? controlledChecked : internalChecked
|
|
30
|
+
|
|
31
|
+
// RSD only makes <button> pressable and has no <input type=checkbox>, so the
|
|
32
|
+
// whole row is a button and we synthesize the change payload that consumers
|
|
33
|
+
// read (e.target.checked).
|
|
34
|
+
const handlePress = useCallback(() => {
|
|
35
|
+
if (disabled) return
|
|
36
|
+
const next = !isChecked
|
|
37
|
+
if (!isControlled) {
|
|
38
|
+
setInternalChecked(next)
|
|
39
|
+
}
|
|
40
|
+
onChange?.({
|
|
41
|
+
target: {checked: next, name, value},
|
|
42
|
+
} as unknown as React.ChangeEvent<HTMLInputElement>)
|
|
43
|
+
}, [disabled, isChecked, isControlled, onChange, name, value])
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<html.button
|
|
47
|
+
type="button"
|
|
48
|
+
onClick={handlePress}
|
|
49
|
+
disabled={disabled}
|
|
50
|
+
role="checkbox"
|
|
51
|
+
aria-checked={isChecked}
|
|
52
|
+
aria-label={!children ? ariaLabel : undefined}
|
|
53
|
+
style={[styles.root, styles.nativeFlex, styles.nativeButton, disabled && styles.rootDisabled]}
|
|
54
|
+
>
|
|
55
|
+
<html.div
|
|
56
|
+
style={[styles.box, styles.nativeFlex, isChecked ? styles.boxChecked : styles.boxUnchecked]}
|
|
57
|
+
>
|
|
58
|
+
{/* CSS-border checkmark (rotated right+bottom borders) — no SVG, no
|
|
59
|
+
react-native-svg dep, so nothing is added to the web bundle. */}
|
|
60
|
+
<html.div
|
|
61
|
+
style={[styles.checkmark, isChecked ? styles.checkmarkVisible : styles.checkmarkHidden]}
|
|
62
|
+
/>
|
|
63
|
+
</html.div>
|
|
64
|
+
{children && <html.span style={styles.labelText}>{children}</html.span>}
|
|
65
|
+
</html.button>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import {type ReactNode, useState, useCallback} from 'react'
|
|
2
2
|
import {html} from 'react-strict-dom'
|
|
3
|
-
import {isNative} from '../../platform'
|
|
4
3
|
import {styles} from './styles.css'
|
|
5
4
|
import {visuallyHidden} from '../../styles/visually-hidden.css'
|
|
6
5
|
|
|
@@ -30,7 +29,7 @@ export function Checkbox({
|
|
|
30
29
|
const [internalChecked, setInternalChecked] = useState(defaultChecked)
|
|
31
30
|
const isChecked = isControlled ? controlledChecked : internalChecked
|
|
32
31
|
|
|
33
|
-
//
|
|
32
|
+
// The real <input type=checkbox> drives toggling, a11y and form submit.
|
|
34
33
|
const handleChange = useCallback(
|
|
35
34
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
36
35
|
if (!isControlled) {
|
|
@@ -41,54 +40,6 @@ export function Checkbox({
|
|
|
41
40
|
[isControlled, onChange],
|
|
42
41
|
)
|
|
43
42
|
|
|
44
|
-
// Native: RSD only makes <button> pressable and has no <input type=checkbox>,
|
|
45
|
-
// so the whole row is a button and we synthesize the change payload that
|
|
46
|
-
// consumers read (e.target.checked).
|
|
47
|
-
const handlePress = useCallback(() => {
|
|
48
|
-
if (disabled) return
|
|
49
|
-
const next = !isChecked
|
|
50
|
-
if (!isControlled) {
|
|
51
|
-
setInternalChecked(next)
|
|
52
|
-
}
|
|
53
|
-
onChange?.({
|
|
54
|
-
target: {checked: next, name, value},
|
|
55
|
-
} as unknown as React.ChangeEvent<HTMLInputElement>)
|
|
56
|
-
}, [disabled, isChecked, isControlled, onChange, name, value])
|
|
57
|
-
|
|
58
|
-
if (isNative) {
|
|
59
|
-
return (
|
|
60
|
-
<html.button
|
|
61
|
-
type="button"
|
|
62
|
-
onClick={handlePress}
|
|
63
|
-
disabled={disabled}
|
|
64
|
-
role="checkbox"
|
|
65
|
-
aria-checked={isChecked}
|
|
66
|
-
aria-label={!children ? ariaLabel : undefined}
|
|
67
|
-
style={[
|
|
68
|
-
styles.root,
|
|
69
|
-
styles.nativeFlex,
|
|
70
|
-
styles.nativeButton,
|
|
71
|
-
disabled && styles.rootDisabled,
|
|
72
|
-
]}
|
|
73
|
-
>
|
|
74
|
-
<html.div
|
|
75
|
-
style={[
|
|
76
|
-
styles.box,
|
|
77
|
-
styles.nativeFlex,
|
|
78
|
-
isChecked ? styles.boxChecked : styles.boxUnchecked,
|
|
79
|
-
]}
|
|
80
|
-
>
|
|
81
|
-
{/* CSS-border checkmark (rotated right+bottom borders) — no SVG, no
|
|
82
|
-
react-native-svg dep, so nothing is added to the web bundle. */}
|
|
83
|
-
<html.div
|
|
84
|
-
style={[styles.checkmark, isChecked ? styles.checkmarkVisible : styles.checkmarkHidden]}
|
|
85
|
-
/>
|
|
86
|
-
</html.div>
|
|
87
|
-
{children && <html.span style={styles.labelText}>{children}</html.span>}
|
|
88
|
-
</html.button>
|
|
89
|
-
)
|
|
90
|
-
}
|
|
91
|
-
|
|
92
43
|
return (
|
|
93
44
|
<html.label style={[styles.root, disabled && styles.rootDisabled]}>
|
|
94
45
|
<html.input
|
|
@@ -74,6 +74,9 @@ export function ConfirmDialog({
|
|
|
74
74
|
value={typed}
|
|
75
75
|
onChange={(e) => setTyped(e.target.value)}
|
|
76
76
|
autoFocus
|
|
77
|
+
// The phrase must match exactly: no capitalising or correcting it.
|
|
78
|
+
autoCapitalize="none"
|
|
79
|
+
spellCheck={false}
|
|
77
80
|
aria-label="confirmation phrase"
|
|
78
81
|
/>
|
|
79
82
|
</Field.Root>
|
|
@@ -10,7 +10,8 @@ import {
|
|
|
10
10
|
useRef,
|
|
11
11
|
} from 'react'
|
|
12
12
|
import {html} from 'react-strict-dom'
|
|
13
|
-
import
|
|
13
|
+
import type {DurationToken} from '@duro-app/tokens/keys'
|
|
14
|
+
import {useOverlayOpenState} from '../../shared/useOverlayOpenState'
|
|
14
15
|
import {styles} from './styles.css'
|
|
15
16
|
|
|
16
17
|
// --- Types ---
|
|
@@ -71,34 +72,19 @@ function Root({
|
|
|
71
72
|
onOpenChange,
|
|
72
73
|
closeAnimationDuration = 'base',
|
|
73
74
|
}: RootProps) {
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
75
|
+
const {present, closing, requestOpen, requestClose} = useOverlayOpenState({
|
|
76
|
+
open: controlledOpen,
|
|
77
|
+
defaultOpen,
|
|
78
|
+
onOpenChange,
|
|
79
|
+
exitDuration: closeAnimationDuration,
|
|
80
|
+
closeNotice: 'at-once',
|
|
81
|
+
})
|
|
79
82
|
const panelRef = useRef<HTMLDivElement>(null)
|
|
80
83
|
const previousFocusRef = useRef<Element | null>(null)
|
|
81
84
|
|
|
82
85
|
const titleId = useId()
|
|
83
86
|
const [labelId, setLabelId] = useState<string | undefined>(undefined)
|
|
84
87
|
|
|
85
|
-
const isOpen = isControlled ? controlledOpen : internalOpen
|
|
86
|
-
|
|
87
|
-
// Sync presence with open state
|
|
88
|
-
useEffect(() => {
|
|
89
|
-
if (isOpen && !present) {
|
|
90
|
-
setPresent(true)
|
|
91
|
-
setClosing(false)
|
|
92
|
-
} else if (!isOpen && present && !closing) {
|
|
93
|
-
setClosing(true)
|
|
94
|
-
closingTimerRef.current = setTimeout(() => {
|
|
95
|
-
setClosing(false)
|
|
96
|
-
setPresent(false)
|
|
97
|
-
closingTimerRef.current = null
|
|
98
|
-
}, DURATION_MS[closeAnimationDuration])
|
|
99
|
-
}
|
|
100
|
-
}, [isOpen, present, closing, closeAnimationDuration])
|
|
101
|
-
|
|
102
88
|
// Focus management: move focus on open, restore on close
|
|
103
89
|
useEffect(() => {
|
|
104
90
|
if (present && !closing) {
|
|
@@ -131,28 +117,6 @@ function Root({
|
|
|
131
117
|
return () => document.removeEventListener('keydown', onKeyDown)
|
|
132
118
|
}, [present, closing]) // eslint-disable-line react-hooks/exhaustive-deps
|
|
133
119
|
|
|
134
|
-
// Clean up timer on unmount
|
|
135
|
-
useEffect(() => {
|
|
136
|
-
return () => {
|
|
137
|
-
if (closingTimerRef.current) clearTimeout(closingTimerRef.current)
|
|
138
|
-
}
|
|
139
|
-
}, [])
|
|
140
|
-
|
|
141
|
-
const requestOpen = useCallback(() => {
|
|
142
|
-
if (closingTimerRef.current) {
|
|
143
|
-
clearTimeout(closingTimerRef.current)
|
|
144
|
-
closingTimerRef.current = null
|
|
145
|
-
setClosing(false)
|
|
146
|
-
}
|
|
147
|
-
if (!isControlled) setInternalOpen(true)
|
|
148
|
-
onOpenChange?.(true)
|
|
149
|
-
}, [isControlled, onOpenChange])
|
|
150
|
-
|
|
151
|
-
const requestClose = useCallback(() => {
|
|
152
|
-
if (!isControlled) setInternalOpen(false)
|
|
153
|
-
onOpenChange?.(false)
|
|
154
|
-
}, [isControlled, onOpenChange])
|
|
155
|
-
|
|
156
120
|
const registerTitle = useCallback((id: string) => {
|
|
157
121
|
setLabelId(id)
|
|
158
122
|
}, [])
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Children,
|
|
3
|
+
cloneElement,
|
|
4
|
+
createContext,
|
|
5
|
+
isValidElement,
|
|
6
|
+
type ReactElement,
|
|
7
|
+
type ReactNode,
|
|
8
|
+
type Ref,
|
|
9
|
+
type RefObject,
|
|
10
|
+
useCallback,
|
|
11
|
+
useContext,
|
|
12
|
+
useEffect,
|
|
13
|
+
useId,
|
|
14
|
+
useRef,
|
|
15
|
+
useState,
|
|
16
|
+
} from 'react'
|
|
17
|
+
import {
|
|
18
|
+
KeyboardAvoidingView,
|
|
19
|
+
Platform,
|
|
20
|
+
ScrollView,
|
|
21
|
+
StyleSheet,
|
|
22
|
+
useWindowDimensions,
|
|
23
|
+
View,
|
|
24
|
+
} from 'react-native'
|
|
25
|
+
import {html} from 'react-strict-dom'
|
|
26
|
+
import type {DurationToken} from '@duro-app/tokens/keys'
|
|
27
|
+
import {mergeRefs} from '../../shared/mergeRefs'
|
|
28
|
+
import {ModalContext} from '../../shared/ModalContext'
|
|
29
|
+
import {OverlayModal} from '../../shared/portal/OverlayModal'
|
|
30
|
+
import {focusForAccessibility} from '../../shared/accessibilityFocus'
|
|
31
|
+
import {useOverlayOpenState} from '../../shared/useOverlayOpenState'
|
|
32
|
+
import {ControlContextBoundary} from '../Toolbar/ControlContextBoundary'
|
|
33
|
+
import {Icon} from '../Icon/Icon'
|
|
34
|
+
import {nativeStyles} from './native.css'
|
|
35
|
+
import {partStyles} from './parts.css'
|
|
36
|
+
import {
|
|
37
|
+
Description,
|
|
38
|
+
DialogContext,
|
|
39
|
+
Footer,
|
|
40
|
+
Header,
|
|
41
|
+
Title,
|
|
42
|
+
useDialog,
|
|
43
|
+
type DialogSize,
|
|
44
|
+
} from './parts'
|
|
45
|
+
|
|
46
|
+
export type {DialogSize}
|
|
47
|
+
|
|
48
|
+
const exitDurationMap = {
|
|
49
|
+
instant: nativeStyles.exitInstant,
|
|
50
|
+
minimal: nativeStyles.exitMinimal,
|
|
51
|
+
quick: nativeStyles.exitQuick,
|
|
52
|
+
fast: nativeStyles.exitFast,
|
|
53
|
+
brisk: nativeStyles.exitBrisk,
|
|
54
|
+
base: nativeStyles.exitBase,
|
|
55
|
+
slow: nativeStyles.exitSlow,
|
|
56
|
+
} as const satisfies Record<DurationToken, unknown>
|
|
57
|
+
|
|
58
|
+
/** Native-only wiring: where accessibility focus goes on open and returns on close. */
|
|
59
|
+
interface NativeDialogContextValue {
|
|
60
|
+
triggerRef: RefObject<unknown>
|
|
61
|
+
panelRef: RefObject<View | null>
|
|
62
|
+
closeOnEscape: boolean
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const NativeDialogContext = createContext<NativeDialogContextValue | null>(null)
|
|
66
|
+
|
|
67
|
+
function useNativeDialog() {
|
|
68
|
+
const ctx = useContext(NativeDialogContext)
|
|
69
|
+
if (!ctx) throw new Error('Dialog compound components must be used within Dialog.Root')
|
|
70
|
+
return ctx
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
interface RootProps {
|
|
74
|
+
children: ReactNode
|
|
75
|
+
open?: boolean
|
|
76
|
+
defaultOpen?: boolean
|
|
77
|
+
onOpenChange?: (open: boolean) => void
|
|
78
|
+
/** Whether pressing the backdrop closes the dialog. Default: true */
|
|
79
|
+
dismissable?: boolean
|
|
80
|
+
/** Whether Android's back button closes the dialog (Escape on the web). Default: true */
|
|
81
|
+
closeOnEscape?: boolean
|
|
82
|
+
/** Web only: on native, screen-reader focus moves to the dialog itself. */
|
|
83
|
+
initialFocus?: RefObject<HTMLElement | null>
|
|
84
|
+
closeAnimationDuration?: DurationToken
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Native Dialog (plan N0, Slice A): the same compound API, rendered in an
|
|
89
|
+
* OverlayModal. The backdrop press and Android's back button close it; the
|
|
90
|
+
* screen reader's focus moves into the panel on open and back to the trigger
|
|
91
|
+
* on close.
|
|
92
|
+
*/
|
|
93
|
+
function Root({
|
|
94
|
+
children,
|
|
95
|
+
open: controlledOpen,
|
|
96
|
+
defaultOpen = false,
|
|
97
|
+
onOpenChange,
|
|
98
|
+
dismissable = true,
|
|
99
|
+
closeOnEscape = true,
|
|
100
|
+
closeAnimationDuration = 'fast',
|
|
101
|
+
}: RootProps) {
|
|
102
|
+
const {present, closing, requestOpen, requestClose} = useOverlayOpenState({
|
|
103
|
+
open: controlledOpen,
|
|
104
|
+
defaultOpen,
|
|
105
|
+
onOpenChange,
|
|
106
|
+
exitDuration: closeAnimationDuration,
|
|
107
|
+
closeNotice: 'after-exit',
|
|
108
|
+
})
|
|
109
|
+
const titleId = useId()
|
|
110
|
+
const descriptionId = useId()
|
|
111
|
+
const popupRef = useRef<HTMLDivElement>(null)
|
|
112
|
+
const triggerRef = useRef<unknown>(null)
|
|
113
|
+
const panelRef = useRef<View>(null)
|
|
114
|
+
const registerClose = useCallback(() => () => {}, [])
|
|
115
|
+
|
|
116
|
+
// Back to the trigger once the dialog is gone.
|
|
117
|
+
const wasPresent = useRef(present)
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
if (wasPresent.current && !present) focusForAccessibility(triggerRef.current, 'Dialog')
|
|
120
|
+
wasPresent.current = present
|
|
121
|
+
}, [present])
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<DialogContext.Provider
|
|
125
|
+
value={{
|
|
126
|
+
open: present,
|
|
127
|
+
closing,
|
|
128
|
+
closeDuration: closeAnimationDuration,
|
|
129
|
+
dismissable,
|
|
130
|
+
requestOpen,
|
|
131
|
+
requestClose,
|
|
132
|
+
titleId,
|
|
133
|
+
descriptionId,
|
|
134
|
+
popupRef,
|
|
135
|
+
registerClose,
|
|
136
|
+
}}
|
|
137
|
+
>
|
|
138
|
+
<NativeDialogContext.Provider value={{triggerRef, panelRef, closeOnEscape}}>
|
|
139
|
+
{children}
|
|
140
|
+
</NativeDialogContext.Provider>
|
|
141
|
+
</DialogContext.Provider>
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
interface PressableChildProps {
|
|
146
|
+
onClick?: () => void
|
|
147
|
+
ref?: Ref<unknown>
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Opens the dialog from its one child (a Button). Native presses do not
|
|
152
|
+
* bubble to a wrapper as web clicks do, so the child gets the handler, and
|
|
153
|
+
* its ref is where focus returns on close.
|
|
154
|
+
*/
|
|
155
|
+
function Trigger({children}: {children: ReactNode}) {
|
|
156
|
+
const {requestOpen} = useDialog()
|
|
157
|
+
const {triggerRef} = useNativeDialog()
|
|
158
|
+
const child = Children.only(children)
|
|
159
|
+
if (!isValidElement<PressableChildProps>(child)) return null
|
|
160
|
+
const own = child.props.onClick
|
|
161
|
+
return cloneElement(child as ReactElement<PressableChildProps>, {
|
|
162
|
+
ref: mergeRefs(child.props.ref, triggerRef),
|
|
163
|
+
onClick: () => {
|
|
164
|
+
own?.()
|
|
165
|
+
requestOpen()
|
|
166
|
+
},
|
|
167
|
+
})
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
interface PortalProps {
|
|
171
|
+
children: ReactNode
|
|
172
|
+
size?: DialogSize
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function Portal({children, size = 'md'}: PortalProps) {
|
|
176
|
+
const {open, closing, closeDuration, dismissable, requestClose, titleId} = useDialog()
|
|
177
|
+
const {panelRef, closeOnEscape} = useNativeDialog()
|
|
178
|
+
// First frame hidden, then shown: the enter transition has a start state.
|
|
179
|
+
const [entered, setEntered] = useState(false)
|
|
180
|
+
useEffect(() => {
|
|
181
|
+
if (!open) {
|
|
182
|
+
setEntered(false)
|
|
183
|
+
return
|
|
184
|
+
}
|
|
185
|
+
const frame = requestAnimationFrame(() => setEntered(true))
|
|
186
|
+
return () => cancelAnimationFrame(frame)
|
|
187
|
+
}, [open])
|
|
188
|
+
|
|
189
|
+
// 85% of the window, as the web caps the panel at 85vh.
|
|
190
|
+
const maxPanelHeight = useWindowDimensions().height * 0.85
|
|
191
|
+
const shown = entered && !closing
|
|
192
|
+
const visibility = shown ? nativeStyles.shown : nativeStyles.hidden
|
|
193
|
+
return (
|
|
194
|
+
<OverlayModal
|
|
195
|
+
visible={open}
|
|
196
|
+
onRequestClose={() => {
|
|
197
|
+
if (closeOnEscape) requestClose()
|
|
198
|
+
}}
|
|
199
|
+
onShow={() => focusForAccessibility(panelRef.current, 'Dialog')}
|
|
200
|
+
>
|
|
201
|
+
<html.div
|
|
202
|
+
aria-hidden
|
|
203
|
+
onClick={dismissable ? requestClose : undefined}
|
|
204
|
+
style={[
|
|
205
|
+
nativeStyles.fill,
|
|
206
|
+
nativeStyles.backdrop,
|
|
207
|
+
shown ? nativeStyles.shown : nativeStyles.hidden,
|
|
208
|
+
closing && exitDurationMap[closeDuration],
|
|
209
|
+
]}
|
|
210
|
+
/>
|
|
211
|
+
<KeyboardAvoidingView
|
|
212
|
+
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
|
213
|
+
style={StyleSheet.absoluteFill}
|
|
214
|
+
pointerEvents="box-none"
|
|
215
|
+
>
|
|
216
|
+
<View style={localStyles.viewport} pointerEvents="box-none">
|
|
217
|
+
<html.div style={nativeStyles.inset}>
|
|
218
|
+
<View ref={panelRef} accessibilityViewIsModal style={localStyles.panelHost}>
|
|
219
|
+
<html.div
|
|
220
|
+
role="dialog"
|
|
221
|
+
aria-modal
|
|
222
|
+
aria-labelledby={titleId}
|
|
223
|
+
style={[
|
|
224
|
+
nativeStyles.popup,
|
|
225
|
+
nativeStyles.maxHeight(maxPanelHeight),
|
|
226
|
+
partStyles[size],
|
|
227
|
+
visibility,
|
|
228
|
+
closing && exitDurationMap[closeDuration],
|
|
229
|
+
]}
|
|
230
|
+
>
|
|
231
|
+
<ModalContext.Provider value={true}>
|
|
232
|
+
<ControlContextBoundary>{children}</ControlContextBoundary>
|
|
233
|
+
</ModalContext.Provider>
|
|
234
|
+
</html.div>
|
|
235
|
+
</View>
|
|
236
|
+
</html.div>
|
|
237
|
+
</View>
|
|
238
|
+
</KeyboardAvoidingView>
|
|
239
|
+
</OverlayModal>
|
|
240
|
+
)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Layout-only RN views: the centring viewport lets presses outside the panel
|
|
244
|
+
// through to the backdrop (`box-none`, which RSD styles cannot express), and
|
|
245
|
+
// the panel host spans the width so the panel's size cap applies.
|
|
246
|
+
const localStyles = StyleSheet.create({
|
|
247
|
+
viewport: {flex: 1, alignItems: 'center', justifyContent: 'center'},
|
|
248
|
+
panelHost: {width: '100%', alignItems: 'center'},
|
|
249
|
+
body: {flexGrow: 0, flexShrink: 1},
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
/** Native Body: a ScrollView, since a native div does not scroll its overflow. */
|
|
253
|
+
function Body({children}: {children: ReactNode}) {
|
|
254
|
+
return (
|
|
255
|
+
<ScrollView style={localStyles.body}>
|
|
256
|
+
<html.div style={nativeStyles.bodyContent}>{children}</html.div>
|
|
257
|
+
</ScrollView>
|
|
258
|
+
)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
interface CloseProps {
|
|
262
|
+
children?: ReactNode
|
|
263
|
+
'aria-label'?: string
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function Close({children, 'aria-label': ariaLabel = 'Close'}: CloseProps) {
|
|
267
|
+
const {requestClose} = useDialog()
|
|
268
|
+
if (children) {
|
|
269
|
+
const child = Children.only(children)
|
|
270
|
+
if (!isValidElement<PressableChildProps>(child)) return null
|
|
271
|
+
const own = child.props.onClick
|
|
272
|
+
return cloneElement(child as ReactElement<PressableChildProps>, {
|
|
273
|
+
onClick: () => {
|
|
274
|
+
own?.()
|
|
275
|
+
requestClose()
|
|
276
|
+
},
|
|
277
|
+
})
|
|
278
|
+
}
|
|
279
|
+
return (
|
|
280
|
+
<html.button onClick={requestClose} aria-label={ariaLabel} style={partStyles.closeButton}>
|
|
281
|
+
<Icon name="x" size="sm" color="textMuted" />
|
|
282
|
+
</html.button>
|
|
283
|
+
)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export const Dialog = {
|
|
287
|
+
Root,
|
|
288
|
+
Trigger,
|
|
289
|
+
Portal,
|
|
290
|
+
Header,
|
|
291
|
+
Title,
|
|
292
|
+
Description,
|
|
293
|
+
Body,
|
|
294
|
+
Footer,
|
|
295
|
+
Close,
|
|
296
|
+
}
|