@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,45 @@
|
|
|
1
|
+
import {css} from 'react-strict-dom'
|
|
2
|
+
import {colors} from '@duro-app/tokens/tokens/colors.css'
|
|
3
|
+
import {sizes} from '@duro-app/tokens/tokens/sizes.css'
|
|
4
|
+
import {borders} from '@duro-app/tokens/tokens/borders.css'
|
|
5
|
+
|
|
6
|
+
/** The ring both platforms draw; each turns it its own way. */
|
|
7
|
+
export const ring = css.create({
|
|
8
|
+
root: {
|
|
9
|
+
display: 'inline-flex',
|
|
10
|
+
alignItems: 'center',
|
|
11
|
+
justifyContent: 'center',
|
|
12
|
+
},
|
|
13
|
+
// RSD-native only honors display:'flex' (not 'inline-flex').
|
|
14
|
+
nativeRoot: {
|
|
15
|
+
display: 'flex',
|
|
16
|
+
},
|
|
17
|
+
ring: {
|
|
18
|
+
borderRadius: '50%',
|
|
19
|
+
borderStyle: 'solid',
|
|
20
|
+
borderColor: colors.border,
|
|
21
|
+
borderTopColor: colors.accent,
|
|
22
|
+
},
|
|
23
|
+
sm: {
|
|
24
|
+
width: sizes.spinnerSm,
|
|
25
|
+
height: sizes.spinnerSm,
|
|
26
|
+
borderWidth: borders.strong,
|
|
27
|
+
},
|
|
28
|
+
md: {
|
|
29
|
+
width: sizes.spinnerMd,
|
|
30
|
+
height: sizes.spinnerMd,
|
|
31
|
+
borderWidth: borders.strong,
|
|
32
|
+
},
|
|
33
|
+
lg: {
|
|
34
|
+
width: sizes.spinnerLg,
|
|
35
|
+
height: sizes.spinnerLg,
|
|
36
|
+
borderWidth: borders.accent,
|
|
37
|
+
},
|
|
38
|
+
// Layered on visuallyHidden.base.
|
|
39
|
+
srOnly: {
|
|
40
|
+
clip: 'rect(0, 0, 0, 0)',
|
|
41
|
+
},
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
/** One turn, in ms: the web animation's 0.6s. */
|
|
45
|
+
export const TURN_MS = 600
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import {css} from 'react-strict-dom'
|
|
2
|
-
import {colors} from '@duro-app/tokens/tokens/colors.css'
|
|
3
|
-
import {sizes} from '@duro-app/tokens/tokens/sizes.css'
|
|
4
2
|
import {easing} from '@duro-app/tokens/tokens/motion.css'
|
|
5
|
-
import {borders} from '@duro-app/tokens/tokens/borders.css'
|
|
6
3
|
|
|
7
4
|
const spin = css.keyframes({
|
|
8
5
|
'0%': {transform: 'rotate(0deg)'},
|
|
@@ -10,38 +7,11 @@ const spin = css.keyframes({
|
|
|
10
7
|
})
|
|
11
8
|
|
|
12
9
|
export const styles = css.create({
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
alignItems: 'center',
|
|
16
|
-
justifyContent: 'center',
|
|
17
|
-
},
|
|
18
|
-
spinner: {
|
|
19
|
-
borderRadius: '50%',
|
|
20
|
-
borderStyle: 'solid',
|
|
21
|
-
borderColor: colors.border,
|
|
22
|
-
borderTopColor: colors.accent,
|
|
10
|
+
// Web turns the ring with a CSS animation (one turn per TURN_MS).
|
|
11
|
+
spinning: {
|
|
23
12
|
animationName: spin,
|
|
24
13
|
animationDuration: '0.6s',
|
|
25
14
|
animationTimingFunction: easing.linear,
|
|
26
15
|
animationIterationCount: 'infinite',
|
|
27
16
|
},
|
|
28
|
-
sm: {
|
|
29
|
-
width: sizes.spinnerSm,
|
|
30
|
-
height: sizes.spinnerSm,
|
|
31
|
-
borderWidth: borders.strong,
|
|
32
|
-
},
|
|
33
|
-
md: {
|
|
34
|
-
width: sizes.spinnerMd,
|
|
35
|
-
height: sizes.spinnerMd,
|
|
36
|
-
borderWidth: borders.strong,
|
|
37
|
-
},
|
|
38
|
-
lg: {
|
|
39
|
-
width: sizes.spinnerLg,
|
|
40
|
-
height: sizes.spinnerLg,
|
|
41
|
-
borderWidth: borders.accent,
|
|
42
|
-
},
|
|
43
|
-
// Layered on visuallyHidden.base.
|
|
44
|
-
srOnly: {
|
|
45
|
-
clip: 'rect(0, 0, 0, 0)',
|
|
46
|
-
},
|
|
47
17
|
})
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type {ComponentMeta} from '../component-meta'
|
|
2
|
+
|
|
3
|
+
export const meta: ComponentMeta = {
|
|
4
|
+
description:
|
|
5
|
+
'Two panes side by side with a resizable separator: the first Pane is sized by the Handle (pointer drag, arrow keys, Shift for larger steps, Home and End), the second takes the rest. Min, max and starting widths are size tokens. Below collapseBelow, measured on its own width, the panes stack in one column and the handle goes; on native they always stack. The Handle is a hairline that thickens to the accent on hover, keyboard focus or drag.',
|
|
6
|
+
whenToUse: [
|
|
7
|
+
'A list beside its detail where people need more room on either side (a session list and its transcript)',
|
|
8
|
+
'An editor beside its preview',
|
|
9
|
+
],
|
|
10
|
+
whenNotToUse: [
|
|
11
|
+
'A fixed list/detail layout nobody resizes — use Grid layout="split"',
|
|
12
|
+
'Navigation beside content — use AppShell or the page-with-sidenav recipe',
|
|
13
|
+
'A short-lived inspection over a list — use DetailPanel',
|
|
14
|
+
],
|
|
15
|
+
anatomy: {
|
|
16
|
+
required: ['Root', 'Pane'],
|
|
17
|
+
optional: ['Handle'],
|
|
18
|
+
},
|
|
19
|
+
relatedTo: [
|
|
20
|
+
{
|
|
21
|
+
component: 'Grid',
|
|
22
|
+
kind: 'contrast',
|
|
23
|
+
relationship: 'Grid layout="split" is a fixed split; SplitPane lets people resize it',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
component: 'DetailPanel',
|
|
27
|
+
kind: 'contrast',
|
|
28
|
+
relationship: 'DetailPanel overlays the list; SplitPane keeps both side by side',
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
example: `<SplitPane.Root aria-label="Resize the session list" minSize="sidebarW" maxSize="panelLg">
|
|
32
|
+
<SplitPane.Pane>
|
|
33
|
+
<List.Root aria-label="Sessions">…</List.Root>
|
|
34
|
+
</SplitPane.Pane>
|
|
35
|
+
<SplitPane.Handle />
|
|
36
|
+
<SplitPane.Pane>
|
|
37
|
+
<Panel.Root>…</Panel.Root>
|
|
38
|
+
</SplitPane.Pane>
|
|
39
|
+
</SplitPane.Root>`,
|
|
40
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type {ReactNode} from 'react'
|
|
2
|
+
import {html} from 'react-strict-dom'
|
|
3
|
+
import type {SizeToken} from '@duro-app/tokens/keys'
|
|
4
|
+
import type {Breakpoint} from '@duro-app/tokens/tokens/breakpoints.css'
|
|
5
|
+
import {styles} from './styles.css'
|
|
6
|
+
|
|
7
|
+
interface RootProps {
|
|
8
|
+
children: ReactNode
|
|
9
|
+
/** Web only: on a phone the panes stack. */
|
|
10
|
+
defaultSize?: SizeToken
|
|
11
|
+
/** Web only. */
|
|
12
|
+
size?: number
|
|
13
|
+
/** Web only. */
|
|
14
|
+
onSizeChange?: (px: number) => void
|
|
15
|
+
/** Web only. */
|
|
16
|
+
minSize?: SizeToken
|
|
17
|
+
/** Web only. */
|
|
18
|
+
maxSize?: SizeToken
|
|
19
|
+
/** Web only: a phone is below every breakpoint, so native always stacks. */
|
|
20
|
+
collapseBelow?: Breakpoint
|
|
21
|
+
/** The handle's accessible name (web only: native has no handle). */
|
|
22
|
+
'aria-label': string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Native SplitPane (plan N0, Slice B): a phone shows one pane at a time's
|
|
27
|
+
* worth of width, so the panes stack in one column and there is no handle —
|
|
28
|
+
* the shape the web takes below `collapseBelow`.
|
|
29
|
+
*/
|
|
30
|
+
function Root({children}: RootProps) {
|
|
31
|
+
return <html.div style={[styles.root, styles.rootStacked]}>{children}</html.div>
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function Pane({children}: {children: ReactNode}) {
|
|
35
|
+
return <html.div style={styles.stackedPane}>{children}</html.div>
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** No handle on native. */
|
|
39
|
+
function Handle() {
|
|
40
|
+
return null
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const SplitPane = {
|
|
44
|
+
Root,
|
|
45
|
+
Pane,
|
|
46
|
+
Handle,
|
|
47
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import type {Meta, StoryObj} from '@storybook/react'
|
|
2
|
+
import {expect, fn, userEvent, waitFor} from 'storybook/test'
|
|
3
|
+
import {css, html} from 'react-strict-dom'
|
|
4
|
+
import {SIZES_PX, SPACING_PX, type SizeToken} from '@duro-app/tokens/keys'
|
|
5
|
+
import type {Breakpoint} from '@duro-app/tokens/tokens/breakpoints.css'
|
|
6
|
+
import {SplitPane} from './SplitPane'
|
|
7
|
+
import {setViewport} from '../Drawer/touchInput'
|
|
8
|
+
import {List} from '../List/List'
|
|
9
|
+
import {Panel} from '../Panel/Panel'
|
|
10
|
+
import {Heading} from '../Heading/Heading'
|
|
11
|
+
import {Text} from '../Text/Text'
|
|
12
|
+
|
|
13
|
+
const meta: Meta = {
|
|
14
|
+
title: 'Components/SplitPane',
|
|
15
|
+
parameters: {layout: 'fullscreen'},
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default meta
|
|
19
|
+
type Story = StoryObj
|
|
20
|
+
|
|
21
|
+
const SESSIONS = ['Refactor auth', 'Flaky e2e', 'Release notes', 'Docs pass', 'Perf trace']
|
|
22
|
+
|
|
23
|
+
// A SplitPane fills its parent: the story gives it the viewport's height.
|
|
24
|
+
const frame = css.create({fill: {height: '100vh'}})
|
|
25
|
+
|
|
26
|
+
interface SplitArgs {
|
|
27
|
+
defaultSize: SizeToken
|
|
28
|
+
minSize: SizeToken
|
|
29
|
+
maxSize: SizeToken
|
|
30
|
+
collapseBelow: Breakpoint
|
|
31
|
+
onSizeChange?: (px: number) => void
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const DEFAULT_ARGS: SplitArgs = {
|
|
35
|
+
defaultSize: 'sidebarW',
|
|
36
|
+
minSize: 'sidebarW',
|
|
37
|
+
maxSize: 'panelLg',
|
|
38
|
+
collapseBelow: 'sm',
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Width tokens that make sense for a pane, smallest first.
|
|
42
|
+
const PANE_SIZES: SizeToken[] = ['sidebarW', 'panelSm', 'panelMd', 'panelLg']
|
|
43
|
+
|
|
44
|
+
/** The picked B0 direction (docs/mockups/split-pane/Hairline.dc.html). */
|
|
45
|
+
function Split(props: Partial<SplitArgs>) {
|
|
46
|
+
const {defaultSize, minSize, maxSize, collapseBelow, onSizeChange} = {...DEFAULT_ARGS, ...props}
|
|
47
|
+
return (
|
|
48
|
+
<html.div style={frame.fill}>
|
|
49
|
+
<SplitPane.Root
|
|
50
|
+
aria-label="Resize the session list"
|
|
51
|
+
defaultSize={defaultSize}
|
|
52
|
+
minSize={minSize}
|
|
53
|
+
maxSize={maxSize}
|
|
54
|
+
collapseBelow={collapseBelow}
|
|
55
|
+
onSizeChange={onSizeChange}
|
|
56
|
+
>
|
|
57
|
+
<SplitPane.Pane>
|
|
58
|
+
<List.Root aria-label="Sessions">
|
|
59
|
+
{SESSIONS.map((s) => (
|
|
60
|
+
<List.Item key={s}>
|
|
61
|
+
<List.Text>{s}</List.Text>
|
|
62
|
+
</List.Item>
|
|
63
|
+
))}
|
|
64
|
+
</List.Root>
|
|
65
|
+
</SplitPane.Pane>
|
|
66
|
+
<SplitPane.Handle />
|
|
67
|
+
<SplitPane.Pane>
|
|
68
|
+
<Panel.Root>
|
|
69
|
+
<Panel.Header>
|
|
70
|
+
<Heading level={2}>Refactor auth</Heading>
|
|
71
|
+
</Panel.Header>
|
|
72
|
+
<Panel.Body>
|
|
73
|
+
<Text>The transcript of the selected session.</Text>
|
|
74
|
+
</Panel.Body>
|
|
75
|
+
</Panel.Root>
|
|
76
|
+
</SplitPane.Pane>
|
|
77
|
+
</SplitPane.Root>
|
|
78
|
+
</html.div>
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export const Default: StoryObj<SplitArgs> = {
|
|
83
|
+
args: {...DEFAULT_ARGS, onSizeChange: fn()},
|
|
84
|
+
argTypes: {
|
|
85
|
+
defaultSize: {
|
|
86
|
+
control: 'select',
|
|
87
|
+
options: PANE_SIZES,
|
|
88
|
+
description: 'Starting width of the first pane',
|
|
89
|
+
},
|
|
90
|
+
minSize: {control: 'select', options: PANE_SIZES, description: 'Narrowest the first pane goes'},
|
|
91
|
+
maxSize: {control: 'select', options: PANE_SIZES, description: 'Widest the first pane goes'},
|
|
92
|
+
collapseBelow: {
|
|
93
|
+
control: 'select',
|
|
94
|
+
options: ['xs', 'sm', 'md', 'lg', 'xl'],
|
|
95
|
+
description: "Below this breakpoint (the split's own width) the panes stack",
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
// Keyed by the starting width: it seeds state on mount, so a change remounts the split.
|
|
99
|
+
render: (args) => <Split key={args.defaultSize} {...args} />,
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const separator = () => document.querySelector<HTMLElement>('[role="separator"]')
|
|
103
|
+
|
|
104
|
+
export const KeyboardMovesTheSeparator: Story = {
|
|
105
|
+
render: () => <Split />,
|
|
106
|
+
beforeEach: async () => {
|
|
107
|
+
await setViewport(1200, 800)
|
|
108
|
+
},
|
|
109
|
+
play: async () => {
|
|
110
|
+
await waitFor(() => expect(separator()).not.toBeNull())
|
|
111
|
+
const handle = separator()!
|
|
112
|
+
const primary = document.getElementById(handle.getAttribute('aria-controls')!)!
|
|
113
|
+
await expect(handle).toHaveAttribute('aria-valuenow', String(SIZES_PX.sidebarW))
|
|
114
|
+
await expect(handle).toHaveAttribute('aria-valuemin', String(SIZES_PX.sidebarW))
|
|
115
|
+
await expect(handle).toHaveAttribute('aria-valuemax', String(SIZES_PX.panelLg))
|
|
116
|
+
|
|
117
|
+
handle.focus()
|
|
118
|
+
await userEvent.keyboard('{ArrowRight}')
|
|
119
|
+
const after = SIZES_PX.sidebarW + SPACING_PX.md
|
|
120
|
+
await expect(handle).toHaveAttribute('aria-valuenow', String(after))
|
|
121
|
+
await waitFor(() => expect(primary.getBoundingClientRect().width).toBeCloseTo(after, 0))
|
|
122
|
+
|
|
123
|
+
await userEvent.keyboard('{End}')
|
|
124
|
+
await expect(handle).toHaveAttribute('aria-valuenow', String(SIZES_PX.panelLg))
|
|
125
|
+
// the bound holds
|
|
126
|
+
await userEvent.keyboard('{ArrowRight}')
|
|
127
|
+
await expect(handle).toHaveAttribute('aria-valuenow', String(SIZES_PX.panelLg))
|
|
128
|
+
|
|
129
|
+
await userEvent.keyboard('{Home}')
|
|
130
|
+
await expect(handle).toHaveAttribute('aria-valuenow', String(SIZES_PX.sidebarW))
|
|
131
|
+
},
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export const NarrowStacksInOneColumn: Story = {
|
|
135
|
+
render: () => <Split />,
|
|
136
|
+
beforeEach: async () => {
|
|
137
|
+
await setViewport(390, 844)
|
|
138
|
+
},
|
|
139
|
+
play: async ({canvas}) => {
|
|
140
|
+
await waitFor(() => expect(separator()).toBeNull())
|
|
141
|
+
const list = canvas.getByRole('list', {name: 'Sessions'})
|
|
142
|
+
const heading = canvas.getByRole('heading', {name: 'Refactor auth'})
|
|
143
|
+
// one column: the detail sits below the list
|
|
144
|
+
await expect(heading.getBoundingClientRect().top).toBeGreaterThan(
|
|
145
|
+
list.getBoundingClientRect().bottom - 1,
|
|
146
|
+
)
|
|
147
|
+
},
|
|
148
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Children,
|
|
3
|
+
createContext,
|
|
4
|
+
isValidElement,
|
|
5
|
+
type ReactNode,
|
|
6
|
+
useCallback,
|
|
7
|
+
useContext,
|
|
8
|
+
useEffect,
|
|
9
|
+
useId,
|
|
10
|
+
useRef,
|
|
11
|
+
useState,
|
|
12
|
+
} from 'react'
|
|
13
|
+
import {html} from 'react-strict-dom'
|
|
14
|
+
import {SIZES_PX, SPACING_PX, type SizeToken} from '@duro-app/tokens/keys'
|
|
15
|
+
import type {Breakpoint} from '@duro-app/tokens/tokens/breakpoints.css'
|
|
16
|
+
import {useContainerBelow} from '../../hooks/useContainerBelow'
|
|
17
|
+
import {styles} from './styles.css'
|
|
18
|
+
|
|
19
|
+
interface SplitPaneContextValue {
|
|
20
|
+
size: number
|
|
21
|
+
min: number
|
|
22
|
+
max: number
|
|
23
|
+
setSize: (px: number) => void
|
|
24
|
+
stacked: boolean
|
|
25
|
+
primaryId: string
|
|
26
|
+
label: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const SplitPaneContext = createContext<SplitPaneContextValue | null>(null)
|
|
30
|
+
|
|
31
|
+
function useSplitPane(part: string) {
|
|
32
|
+
const ctx = useContext(SplitPaneContext)
|
|
33
|
+
if (!ctx) throw new Error(`SplitPane.${part} must be used within SplitPane.Root`)
|
|
34
|
+
return ctx
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Which Pane this is, in order: the first is the one the handle sizes. */
|
|
38
|
+
const PaneIndexContext = createContext(0)
|
|
39
|
+
|
|
40
|
+
/** One arrow press moves the handle this far; with Shift, `STEP_LARGE`. */
|
|
41
|
+
const STEP = SPACING_PX.md
|
|
42
|
+
const STEP_LARGE = SPACING_PX.xxl
|
|
43
|
+
|
|
44
|
+
const clamp = (px: number, min: number, max: number) => Math.min(max, Math.max(min, px))
|
|
45
|
+
|
|
46
|
+
interface RootProps {
|
|
47
|
+
children: ReactNode
|
|
48
|
+
/** The primary (first) pane's starting width, a size token. Default: `sidebarW` */
|
|
49
|
+
defaultSize?: SizeToken
|
|
50
|
+
/** Controlled width of the primary pane, in px (from `onSizeChange`) */
|
|
51
|
+
size?: number
|
|
52
|
+
/** Called with the primary pane's new width while the handle moves */
|
|
53
|
+
onSizeChange?: (px: number) => void
|
|
54
|
+
/** The primary pane's narrowest width, a size token. Default: `sidebarW` */
|
|
55
|
+
minSize?: SizeToken
|
|
56
|
+
/** The primary pane's widest width, a size token. Default: `panelLg` */
|
|
57
|
+
maxSize?: SizeToken
|
|
58
|
+
/** Below this breakpoint, measured on the split's own width, the panes stack. Default: `sm` */
|
|
59
|
+
collapseBelow?: Breakpoint
|
|
60
|
+
/** The handle's accessible name ("Resize list"). */
|
|
61
|
+
'aria-label': string
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Two panes side by side with a resizable separator between them: a list
|
|
66
|
+
* and its detail, an editor and its preview. The first Pane is sized by the
|
|
67
|
+
* handle (pointer drag, arrow keys, Home and End); the second takes the rest.
|
|
68
|
+
* Below `collapseBelow` the panes stack in one column and the handle goes.
|
|
69
|
+
*/
|
|
70
|
+
function Root({
|
|
71
|
+
children,
|
|
72
|
+
defaultSize = 'sidebarW',
|
|
73
|
+
size: controlledSize,
|
|
74
|
+
onSizeChange,
|
|
75
|
+
minSize = 'sidebarW',
|
|
76
|
+
maxSize = 'panelLg',
|
|
77
|
+
collapseBelow = 'sm',
|
|
78
|
+
'aria-label': label,
|
|
79
|
+
}: RootProps) {
|
|
80
|
+
const min = SIZES_PX[minSize]
|
|
81
|
+
const max = Math.max(min, SIZES_PX[maxSize])
|
|
82
|
+
const [inner, setInner] = useState(() => clamp(SIZES_PX[defaultSize], min, max))
|
|
83
|
+
const size = clamp(controlledSize ?? inner, min, max)
|
|
84
|
+
const setSize = useCallback(
|
|
85
|
+
(px: number) => {
|
|
86
|
+
const next = clamp(Math.round(px), min, max)
|
|
87
|
+
if (controlledSize === undefined) setInner(next)
|
|
88
|
+
onSizeChange?.(next)
|
|
89
|
+
},
|
|
90
|
+
[controlledSize, onSizeChange, min, max],
|
|
91
|
+
)
|
|
92
|
+
const rootRef = useRef<HTMLDivElement>(null)
|
|
93
|
+
const stacked = useContainerBelow(rootRef, collapseBelow)
|
|
94
|
+
const primaryId = useId()
|
|
95
|
+
|
|
96
|
+
let paneIndex = 0
|
|
97
|
+
const items = Children.map(children, (child) => {
|
|
98
|
+
if (isValidElement(child) && child.type === Pane) {
|
|
99
|
+
const index = paneIndex++
|
|
100
|
+
return <PaneIndexContext.Provider value={index}>{child}</PaneIndexContext.Provider>
|
|
101
|
+
}
|
|
102
|
+
return child
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<SplitPaneContext.Provider value={{size, min, max, setSize, stacked, primaryId, label}}>
|
|
107
|
+
<html.div ref={rootRef} style={[styles.root, stacked && styles.rootStacked]}>
|
|
108
|
+
{items}
|
|
109
|
+
</html.div>
|
|
110
|
+
</SplitPaneContext.Provider>
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function Pane({children}: {children: ReactNode}) {
|
|
115
|
+
const {size, stacked, primaryId} = useSplitPane('Pane')
|
|
116
|
+
const primary = useContext(PaneIndexContext) === 0
|
|
117
|
+
return (
|
|
118
|
+
<html.div
|
|
119
|
+
id={primary ? primaryId : undefined}
|
|
120
|
+
style={[
|
|
121
|
+
primary ? [styles.primary, !stacked && styles.primarySize(size)] : styles.secondary,
|
|
122
|
+
stacked && styles.stackedPane,
|
|
123
|
+
]}
|
|
124
|
+
>
|
|
125
|
+
{children}
|
|
126
|
+
</html.div>
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function Handle() {
|
|
131
|
+
const {size, min, max, setSize, stacked, primaryId, label} = useSplitPane('Handle')
|
|
132
|
+
const [hovered, setHovered] = useState(false)
|
|
133
|
+
const [keyboardFocus, setKeyboardFocus] = useState(false)
|
|
134
|
+
const [dragging, setDragging] = useState(false)
|
|
135
|
+
const drag = useRef<{startX: number; startSize: number} | null>(null)
|
|
136
|
+
const handleRef = useRef<HTMLDivElement>(null)
|
|
137
|
+
|
|
138
|
+
// Arrow keys move the handle, Shift by larger steps; Home and End go to
|
|
139
|
+
// the bounds. A DOM listener, for the full KeyboardEvent.
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
const el = handleRef.current
|
|
142
|
+
if (!el) return
|
|
143
|
+
function onKeyDown(e: KeyboardEvent) {
|
|
144
|
+
const step = e.shiftKey ? STEP_LARGE : STEP
|
|
145
|
+
const next =
|
|
146
|
+
e.key === 'ArrowLeft'
|
|
147
|
+
? size - step
|
|
148
|
+
: e.key === 'ArrowRight'
|
|
149
|
+
? size + step
|
|
150
|
+
: e.key === 'Home'
|
|
151
|
+
? min
|
|
152
|
+
: e.key === 'End'
|
|
153
|
+
? max
|
|
154
|
+
: null
|
|
155
|
+
if (next === null) return
|
|
156
|
+
e.preventDefault()
|
|
157
|
+
setSize(next)
|
|
158
|
+
}
|
|
159
|
+
el.addEventListener('keydown', onKeyDown)
|
|
160
|
+
return () => el.removeEventListener('keydown', onKeyDown)
|
|
161
|
+
}, [size, min, max, setSize, stacked])
|
|
162
|
+
|
|
163
|
+
if (stacked) return null
|
|
164
|
+
const active = hovered || keyboardFocus || dragging
|
|
165
|
+
|
|
166
|
+
return (
|
|
167
|
+
<html.div
|
|
168
|
+
ref={handleRef}
|
|
169
|
+
role="separator"
|
|
170
|
+
tabIndex={0}
|
|
171
|
+
aria-label={label}
|
|
172
|
+
aria-orientation="vertical"
|
|
173
|
+
aria-controls={primaryId}
|
|
174
|
+
aria-valuenow={size}
|
|
175
|
+
aria-valuemin={min}
|
|
176
|
+
aria-valuemax={max}
|
|
177
|
+
style={styles.handle}
|
|
178
|
+
onFocus={(e: {currentTarget: HTMLElement}) =>
|
|
179
|
+
setKeyboardFocus(e.currentTarget.matches(':focus-visible'))
|
|
180
|
+
}
|
|
181
|
+
onBlur={() => setKeyboardFocus(false)}
|
|
182
|
+
onPointerEnter={() => setHovered(true)}
|
|
183
|
+
onPointerLeave={() => setHovered(false)}
|
|
184
|
+
onPointerDown={(e: {
|
|
185
|
+
button: number
|
|
186
|
+
clientX: number
|
|
187
|
+
pointerId: number
|
|
188
|
+
currentTarget: HTMLElement
|
|
189
|
+
}) => {
|
|
190
|
+
if (e.button !== 0) return
|
|
191
|
+
e.currentTarget.setPointerCapture(e.pointerId)
|
|
192
|
+
drag.current = {startX: e.clientX, startSize: size}
|
|
193
|
+
setDragging(true)
|
|
194
|
+
}}
|
|
195
|
+
onPointerMove={(e: {clientX: number}) => {
|
|
196
|
+
if (!drag.current) return
|
|
197
|
+
setSize(drag.current.startSize + e.clientX - drag.current.startX)
|
|
198
|
+
}}
|
|
199
|
+
onPointerUp={() => {
|
|
200
|
+
drag.current = null
|
|
201
|
+
setDragging(false)
|
|
202
|
+
}}
|
|
203
|
+
onPointerCancel={() => {
|
|
204
|
+
drag.current = null
|
|
205
|
+
setDragging(false)
|
|
206
|
+
}}
|
|
207
|
+
>
|
|
208
|
+
<html.span aria-hidden style={[styles.line, active && styles.lineActive]} />
|
|
209
|
+
<html.span aria-hidden style={[styles.grip, active && styles.gripShown]} />
|
|
210
|
+
</html.div>
|
|
211
|
+
)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export const SplitPane = {
|
|
215
|
+
Root,
|
|
216
|
+
Pane,
|
|
217
|
+
Handle,
|
|
218
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import {css} from 'react-strict-dom'
|
|
2
|
+
import {colors} from '@duro-app/tokens/tokens/colors.css'
|
|
3
|
+
import {spacing, radii} from '@duro-app/tokens/tokens/spacing.css'
|
|
4
|
+
import {duration, easing} from '@duro-app/tokens/tokens/motion.css'
|
|
5
|
+
import {borders} from '@duro-app/tokens/tokens/borders.css'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* SplitPane, the picked B0 direction (docs/mockups/split-pane/Hairline.dc.html):
|
|
9
|
+
* a hairline between the panes inside a wider hit area; hover, keyboard
|
|
10
|
+
* focus or a drag thicken it to the accent and show a grip.
|
|
11
|
+
*/
|
|
12
|
+
export const styles = css.create({
|
|
13
|
+
root: {
|
|
14
|
+
display: 'flex',
|
|
15
|
+
flexDirection: 'row',
|
|
16
|
+
alignItems: 'stretch',
|
|
17
|
+
minWidth: 0,
|
|
18
|
+
minHeight: 0,
|
|
19
|
+
height: '100%',
|
|
20
|
+
},
|
|
21
|
+
// Below `collapseBelow`: one column, the panes stacked, no handle.
|
|
22
|
+
rootStacked: {
|
|
23
|
+
flexDirection: 'column',
|
|
24
|
+
height: 'auto',
|
|
25
|
+
},
|
|
26
|
+
primary: {
|
|
27
|
+
flexShrink: 0,
|
|
28
|
+
minWidth: 0,
|
|
29
|
+
overflow: 'auto',
|
|
30
|
+
},
|
|
31
|
+
primarySize: (px: number) => ({width: px}),
|
|
32
|
+
secondary: {
|
|
33
|
+
flexGrow: 1,
|
|
34
|
+
flexBasis: 0,
|
|
35
|
+
minWidth: 0,
|
|
36
|
+
overflow: 'auto',
|
|
37
|
+
},
|
|
38
|
+
stackedPane: {
|
|
39
|
+
width: '100%',
|
|
40
|
+
overflow: 'visible',
|
|
41
|
+
},
|
|
42
|
+
handle: {
|
|
43
|
+
position: 'relative',
|
|
44
|
+
flexShrink: 0,
|
|
45
|
+
display: 'flex',
|
|
46
|
+
justifyContent: 'center',
|
|
47
|
+
width: spacing.sm,
|
|
48
|
+
cursor: 'col-resize',
|
|
49
|
+
touchAction: 'none',
|
|
50
|
+
userSelect: 'none',
|
|
51
|
+
outlineStyle: 'none',
|
|
52
|
+
},
|
|
53
|
+
line: {
|
|
54
|
+
width: borders.hairline,
|
|
55
|
+
height: '100%',
|
|
56
|
+
backgroundColor: colors.border,
|
|
57
|
+
transitionProperty: 'background-color, width',
|
|
58
|
+
transitionDuration: duration.fast,
|
|
59
|
+
transitionTimingFunction: easing.standard,
|
|
60
|
+
},
|
|
61
|
+
lineActive: {
|
|
62
|
+
width: borders.strong,
|
|
63
|
+
backgroundColor: colors.accent,
|
|
64
|
+
},
|
|
65
|
+
grip: {
|
|
66
|
+
position: 'absolute',
|
|
67
|
+
top: '50%',
|
|
68
|
+
left: '50%',
|
|
69
|
+
transform: 'translate(-50%, -50%)',
|
|
70
|
+
width: spacing.xs,
|
|
71
|
+
height: spacing.xl,
|
|
72
|
+
borderRadius: radii.full,
|
|
73
|
+
backgroundColor: colors.textMuted,
|
|
74
|
+
opacity: 0,
|
|
75
|
+
transitionProperty: 'opacity',
|
|
76
|
+
transitionDuration: duration.fast,
|
|
77
|
+
},
|
|
78
|
+
gripShown: {
|
|
79
|
+
opacity: 1,
|
|
80
|
+
},
|
|
81
|
+
})
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import {html} from 'react-strict-dom'
|
|
2
2
|
import {Icon} from '../Icon'
|
|
3
3
|
import type {IconName, IconSize} from '../Icon'
|
|
4
|
+
import type {ColorToken} from '@duro-app/tokens/keys'
|
|
5
|
+
import {isNative} from '../../platform'
|
|
4
6
|
import {styles} from './styles.css'
|
|
5
7
|
|
|
6
8
|
export type StatusIconName = Extract<
|
|
@@ -9,6 +11,15 @@ export type StatusIconName = Extract<
|
|
|
9
11
|
>
|
|
10
12
|
export type StatusIconVariant = 'error' | 'success' | 'warning' | 'info' | 'muted'
|
|
11
13
|
|
|
14
|
+
// On native an Icon cannot inherit the variant's colour, so it is passed.
|
|
15
|
+
const iconColor = {
|
|
16
|
+
error: 'error',
|
|
17
|
+
success: 'success',
|
|
18
|
+
warning: 'warning',
|
|
19
|
+
info: 'info',
|
|
20
|
+
muted: 'textMuted',
|
|
21
|
+
} as const satisfies Record<StatusIconVariant, ColorToken>
|
|
22
|
+
|
|
12
23
|
interface StatusIconProps {
|
|
13
24
|
name: StatusIconName
|
|
14
25
|
size?: IconSize
|
|
@@ -17,8 +28,8 @@ interface StatusIconProps {
|
|
|
17
28
|
|
|
18
29
|
export function StatusIcon({name, size = 'xxl', variant = 'muted'}: StatusIconProps) {
|
|
19
30
|
return (
|
|
20
|
-
<html.div style={[styles.root, styles[variant]]}>
|
|
21
|
-
<Icon name={name} size={size} />
|
|
31
|
+
<html.div style={[styles.root, isNative && styles.nativeFlex, styles[variant]]}>
|
|
32
|
+
<Icon name={name} size={size} color={isNative ? iconColor[variant] : undefined} />
|
|
22
33
|
</html.div>
|
|
23
34
|
)
|
|
24
35
|
}
|