@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,70 @@
|
|
|
1
|
+
import {html} from 'react-strict-dom'
|
|
2
|
+
import {SIZES_PX} from '@duro-app/tokens/keys'
|
|
3
|
+
import {styles} from './styles.css'
|
|
4
|
+
import {RemoveGlyph} from './RemoveGlyph'
|
|
5
|
+
import {variantText, type TagProps} from './types'
|
|
6
|
+
|
|
7
|
+
export type {TagProps, TagSize, TagVariant} from './types'
|
|
8
|
+
|
|
9
|
+
const sizeMap = {
|
|
10
|
+
sm: styles.sizeSm,
|
|
11
|
+
md: styles.sizeMd,
|
|
12
|
+
} as const
|
|
13
|
+
|
|
14
|
+
const removableSizeMap = {
|
|
15
|
+
sm: styles.removableSm,
|
|
16
|
+
md: styles.removableMd,
|
|
17
|
+
} as const
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Native Tag (plan N0, Slice B): the label and, when removable, the remove
|
|
21
|
+
* button. TagGroup is not ported (its grid keyboard model is web only,
|
|
22
|
+
* Decision 6), so `value` is unused here; the remove button's name comes from
|
|
23
|
+
* a string label.
|
|
24
|
+
*/
|
|
25
|
+
export function Tag({
|
|
26
|
+
variant = 'default',
|
|
27
|
+
size = 'md',
|
|
28
|
+
removable = false,
|
|
29
|
+
onRemove,
|
|
30
|
+
disabled = false,
|
|
31
|
+
wrap = false,
|
|
32
|
+
children,
|
|
33
|
+
}: TagProps) {
|
|
34
|
+
const removeLabel = typeof children === 'string' ? `Remove ${children}` : 'Remove'
|
|
35
|
+
// A div, not the web's span: a native span is a Text, which would lay the
|
|
36
|
+
// remove button out inline, raised off the label's centre line.
|
|
37
|
+
return (
|
|
38
|
+
<html.div
|
|
39
|
+
style={[
|
|
40
|
+
styles.base,
|
|
41
|
+
styles.nativeFlex,
|
|
42
|
+
sizeMap[size],
|
|
43
|
+
removable && removableSizeMap[size],
|
|
44
|
+
styles[variant],
|
|
45
|
+
disabled && styles.disabled,
|
|
46
|
+
wrap && styles.wrap,
|
|
47
|
+
]}
|
|
48
|
+
>
|
|
49
|
+
<html.span>{children}</html.span>
|
|
50
|
+
{removable && (
|
|
51
|
+
<html.button
|
|
52
|
+
type="button"
|
|
53
|
+
aria-label={removeLabel}
|
|
54
|
+
disabled={disabled}
|
|
55
|
+
onClick={disabled ? undefined : onRemove}
|
|
56
|
+
style={[
|
|
57
|
+
styles.removeButton,
|
|
58
|
+
styles.nativeButtonFlex,
|
|
59
|
+
disabled && styles.removeButtonDisabled,
|
|
60
|
+
]}
|
|
61
|
+
>
|
|
62
|
+
<RemoveGlyph
|
|
63
|
+
size={size === 'sm' ? SIZES_PX.glyphXs : SIZES_PX.glyphSm}
|
|
64
|
+
color={variantText[variant]}
|
|
65
|
+
/>
|
|
66
|
+
</html.button>
|
|
67
|
+
)}
|
|
68
|
+
</html.div>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
@@ -1,31 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {useCallback, useRef} from 'react'
|
|
2
2
|
import {html} from 'react-strict-dom'
|
|
3
|
+
import {SIZES_PX} from '@duro-app/tokens/keys'
|
|
3
4
|
import {useTagGroup} from '../TagGroup/TagGroupContext'
|
|
4
5
|
import {styles} from './styles.css'
|
|
6
|
+
import {RemoveGlyph} from './RemoveGlyph'
|
|
7
|
+
import {variantText, type TagProps} from './types'
|
|
5
8
|
|
|
6
|
-
export type
|
|
7
|
-
export type TagSize = 'sm' | 'md'
|
|
8
|
-
|
|
9
|
-
interface TagProps {
|
|
10
|
-
/** Value identifier — required when used inside TagGroup */
|
|
11
|
-
value?: string
|
|
12
|
-
variant?: TagVariant
|
|
13
|
-
size?: TagSize
|
|
14
|
-
/** Show remove button. Auto-derived from TagGroup context when inside one. */
|
|
15
|
-
removable?: boolean
|
|
16
|
-
/** Called when remove button is clicked. Auto-wired to TagGroup when inside one. */
|
|
17
|
-
onRemove?: () => void
|
|
18
|
-
disabled?: boolean
|
|
19
|
-
/**
|
|
20
|
-
* Allow the label to wrap / break instead of staying on one line. Tags are
|
|
21
|
-
* `white-space: nowrap` by default (they read as pills); set this for long
|
|
22
|
-
* identifier-style values (e.g. `lldap.group.member.remove`) sitting in a
|
|
23
|
-
* constrained column, so they break within their cell instead of leaking
|
|
24
|
-
* into the next one.
|
|
25
|
-
*/
|
|
26
|
-
wrap?: boolean
|
|
27
|
-
children: ReactNode
|
|
28
|
-
}
|
|
9
|
+
export type {TagProps, TagSize, TagVariant} from './types'
|
|
29
10
|
|
|
30
11
|
const sizeMap = {
|
|
31
12
|
sm: styles.sizeSm,
|
|
@@ -112,19 +93,10 @@ export function Tag({
|
|
|
112
93
|
tabIndex={-1}
|
|
113
94
|
style={[styles.removeButton, disabled && styles.removeButtonDisabled]}
|
|
114
95
|
>
|
|
115
|
-
<
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
fill="none"
|
|
120
|
-
>
|
|
121
|
-
<path
|
|
122
|
-
d="M3 3l6 6M9 3l-6 6"
|
|
123
|
-
stroke="currentColor"
|
|
124
|
-
strokeWidth={1.5}
|
|
125
|
-
strokeLinecap="round"
|
|
126
|
-
/>
|
|
127
|
-
</svg>
|
|
96
|
+
<RemoveGlyph
|
|
97
|
+
size={size === 'sm' ? SIZES_PX.glyphXs : SIZES_PX.glyphSm}
|
|
98
|
+
color={variantText[variant]}
|
|
99
|
+
/>
|
|
128
100
|
</html.button>
|
|
129
101
|
</html.span>
|
|
130
102
|
)}
|
|
@@ -110,6 +110,15 @@ export const styles = css.create({
|
|
|
110
110
|
},
|
|
111
111
|
outlineWidth: 0,
|
|
112
112
|
},
|
|
113
|
+
// RSD on native lays out only display: flex, not the web's inline-flex.
|
|
114
|
+
nativeFlex: {
|
|
115
|
+
display: 'flex',
|
|
116
|
+
flexDirection: 'row',
|
|
117
|
+
alignSelf: 'flex-start',
|
|
118
|
+
},
|
|
119
|
+
nativeButtonFlex: {
|
|
120
|
+
display: 'flex',
|
|
121
|
+
},
|
|
113
122
|
removeButtonDisabled: {
|
|
114
123
|
cursor: 'not-allowed',
|
|
115
124
|
opacity: 0.4,
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type {ReactNode} from 'react'
|
|
2
|
+
import type {ColorToken} from '@duro-app/tokens/keys'
|
|
3
|
+
|
|
4
|
+
export type TagVariant = 'default' | 'success' | 'warning' | 'error' | 'info'
|
|
5
|
+
|
|
6
|
+
/** Each variant's text colour, which the remove cross draws in. */
|
|
7
|
+
export const variantText = {
|
|
8
|
+
default: 'text',
|
|
9
|
+
success: 'successText',
|
|
10
|
+
warning: 'warningText',
|
|
11
|
+
error: 'errorText',
|
|
12
|
+
info: 'infoText',
|
|
13
|
+
} as const satisfies Record<TagVariant, ColorToken>
|
|
14
|
+
|
|
15
|
+
export type TagSize = 'sm' | 'md'
|
|
16
|
+
|
|
17
|
+
export interface TagProps {
|
|
18
|
+
/** Value identifier — required when used inside TagGroup */
|
|
19
|
+
value?: string
|
|
20
|
+
variant?: TagVariant
|
|
21
|
+
size?: TagSize
|
|
22
|
+
/** Show remove button. Auto-derived from TagGroup context when inside one. */
|
|
23
|
+
removable?: boolean
|
|
24
|
+
/** Called when remove button is clicked. Auto-wired to TagGroup when inside one. */
|
|
25
|
+
onRemove?: () => void
|
|
26
|
+
disabled?: boolean
|
|
27
|
+
/**
|
|
28
|
+
* Allow the label to wrap / break instead of staying on one line. Tags are
|
|
29
|
+
* `white-space: nowrap` by default (they read as pills); set this for long
|
|
30
|
+
* identifier-style values (e.g. `lldap.group.member.remove`) sitting in a
|
|
31
|
+
* constrained column, so they break within their cell instead of leaking
|
|
32
|
+
* into the next one.
|
|
33
|
+
*/
|
|
34
|
+
wrap?: boolean
|
|
35
|
+
children: ReactNode
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface RemoveGlyphProps {
|
|
39
|
+
/** px, from the glyph size tokens */
|
|
40
|
+
size: number
|
|
41
|
+
/** Native only: the web inherits the tag's text colour. */
|
|
42
|
+
color: ColorToken
|
|
43
|
+
}
|
|
@@ -8,6 +8,8 @@ import {borders} from '@duro-app/tokens/tokens/borders.css'
|
|
|
8
8
|
export const styles = css.create({
|
|
9
9
|
base: {
|
|
10
10
|
width: '100%',
|
|
11
|
+
// Native has no border-box reset (the web's global-reset.css); declare it.
|
|
12
|
+
boxSizing: 'border-box',
|
|
11
13
|
paddingTop: spacing.sm,
|
|
12
14
|
paddingBottom: spacing.sm,
|
|
13
15
|
paddingLeft: spacing.md,
|
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
// Native (React Native / Expo) ThemeProvider — Metro resolves this over
|
|
2
2
|
// ThemeProvider.tsx via the platform extension. The web version uses
|
|
3
3
|
// `display:contents` + a fixed portal-mount, neither of which exists on RN, so
|
|
4
|
-
// this applies the theme
|
|
5
|
-
//
|
|
6
|
-
|
|
4
|
+
// this applies the theme to a plain flex-column container, and passive
|
|
5
|
+
// overlays (toasts) draw through the host registry instead (plan N0,
|
|
6
|
+
// Decision 2).
|
|
7
|
+
import {createContext, type ReactNode, useContext, useMemo, useState} from 'react'
|
|
7
8
|
import {css, html} from 'react-strict-dom'
|
|
9
|
+
import type {ColorToken} from '@duro-app/tokens/keys'
|
|
10
|
+
import {darkColors, highContrastColors, lightColors, type RawColors} from '@duro-app/tokens/raw'
|
|
8
11
|
import {colors} from '@duro-app/tokens/tokens/colors.css'
|
|
9
12
|
import {lightTheme, lightShadows} from '@duro-app/tokens/themes/light.css'
|
|
10
13
|
import {highContrastTheme, highContrastShadows} from '@duro-app/tokens/themes/high-contrast.css'
|
|
14
|
+
import {createHostRegistry, type HostRegistry} from '../../shared/portal/hostRegistry'
|
|
15
|
+
import {HostRegistryContext, PortalHost} from '../../shared/portal/PortalHost'
|
|
16
|
+
import {
|
|
17
|
+
ResolvedColorsContext,
|
|
18
|
+
ThemeScopeContext,
|
|
19
|
+
type ThemeScope,
|
|
20
|
+
} from '../../shared/portal/themeScope'
|
|
11
21
|
|
|
12
22
|
export type ThemeName = 'dark' | 'light' | 'high-contrast'
|
|
13
23
|
|
|
@@ -16,34 +26,84 @@ interface ThemeProviderProps {
|
|
|
16
26
|
children: ReactNode
|
|
17
27
|
}
|
|
18
28
|
|
|
19
|
-
|
|
29
|
+
// On the web, `dark` is the base vars and needs no theme. On native,
|
|
30
|
+
// createTheme is a runtime call, so dark gets an explicit one: a dark
|
|
31
|
+
// provider nested in a light one then draws dark, and its resolved colours
|
|
32
|
+
// (below) agree with what RSD draws.
|
|
33
|
+
const darkTheme = css.createTheme(colors, darkColors)
|
|
34
|
+
|
|
35
|
+
const themeMap: Record<ThemeName, readonly unknown[]> = {
|
|
36
|
+
dark: [darkTheme],
|
|
20
37
|
light: [lightTheme, lightShadows],
|
|
21
38
|
'high-contrast': [highContrastTheme, highContrastShadows],
|
|
22
39
|
}
|
|
23
40
|
|
|
41
|
+
const palettes: Record<ThemeName, RawColors> = {
|
|
42
|
+
dark: darkColors,
|
|
43
|
+
light: lightColors,
|
|
44
|
+
'high-contrast': highContrastColors,
|
|
45
|
+
}
|
|
46
|
+
|
|
24
47
|
const styles = css.create({
|
|
25
48
|
// No `display:contents` on RN. RSD uses web flex semantics on native, so
|
|
26
|
-
// declare flex + column explicitly
|
|
49
|
+
// declare flex + column explicitly.
|
|
27
50
|
root: {
|
|
28
51
|
display: 'flex',
|
|
29
52
|
flexDirection: 'column',
|
|
30
|
-
flex: 1,
|
|
31
53
|
color: colors.text,
|
|
32
54
|
},
|
|
55
|
+
// The app canvas: the web paints it on body from vars.css; native has no
|
|
56
|
+
// body, so the outermost provider paints it and fills its host (height,
|
|
57
|
+
// not flex: the host is an RN view, which RSD does not know is flex).
|
|
58
|
+
canvas: {
|
|
59
|
+
backgroundColor: colors.bg,
|
|
60
|
+
height: '100%',
|
|
61
|
+
},
|
|
33
62
|
})
|
|
34
63
|
|
|
35
64
|
type DivStyle = Parameters<typeof html.div>[0]['style']
|
|
36
65
|
|
|
37
66
|
// Parity with the web export — there's no portal mount on native, so this is
|
|
38
|
-
// always null.
|
|
67
|
+
// always null.
|
|
39
68
|
const PortalMountContext = createContext<HTMLElement | null>(null)
|
|
40
69
|
|
|
41
70
|
export function usePortalMount(): HTMLElement | null {
|
|
42
71
|
return useContext(PortalMountContext)
|
|
43
72
|
}
|
|
44
73
|
|
|
74
|
+
/**
|
|
75
|
+
* The nearest ThemeProvider's colours as real colour strings, for native hosts
|
|
76
|
+
* that cannot read a `var(--…)` reference: an Icon renderer, an animated
|
|
77
|
+
* backdrop, a Pressable. RSD styles keep using the `colors` tokens.
|
|
78
|
+
*/
|
|
79
|
+
export function useResolvedColors(): Record<ColorToken, string> {
|
|
80
|
+
return useContext(ResolvedColorsContext)
|
|
81
|
+
}
|
|
82
|
+
|
|
45
83
|
export function ThemeProvider({theme = 'dark', children}: ThemeProviderProps) {
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
|
|
84
|
+
const outerRegistry = useContext(HostRegistryContext)
|
|
85
|
+
const outerScope = useContext(ThemeScopeContext)
|
|
86
|
+
// The outermost provider owns the registry and draws the root host.
|
|
87
|
+
const [ownRegistry] = useState<HostRegistry | null>(() =>
|
|
88
|
+
outerRegistry ? null : createHostRegistry(),
|
|
89
|
+
)
|
|
90
|
+
const registry = outerRegistry ?? ownRegistry
|
|
91
|
+
const themeStyles = themeMap[theme]
|
|
92
|
+
const scope = useMemo<ThemeScope>(
|
|
93
|
+
() => ({styles: [...outerScope.styles, ...themeStyles]}),
|
|
94
|
+
[outerScope, themeStyles],
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<HostRegistryContext.Provider value={registry}>
|
|
99
|
+
<ThemeScopeContext.Provider value={scope}>
|
|
100
|
+
<ResolvedColorsContext.Provider value={palettes[theme]}>
|
|
101
|
+
<html.div style={[...themeStyles, styles.root, ownRegistry && styles.canvas] as DivStyle}>
|
|
102
|
+
{children}
|
|
103
|
+
{ownRegistry ? <PortalHost placement="base" /> : null}
|
|
104
|
+
</html.div>
|
|
105
|
+
</ResolvedColorsContext.Provider>
|
|
106
|
+
</ThemeScopeContext.Provider>
|
|
107
|
+
</HostRegistryContext.Provider>
|
|
108
|
+
)
|
|
49
109
|
}
|
|
@@ -2,7 +2,7 @@ import type {ComponentMeta} from '../component-meta'
|
|
|
2
2
|
|
|
3
3
|
export const meta: ComponentMeta = {
|
|
4
4
|
description:
|
|
5
|
-
'Transient, self-dismissing notification for the RESULT of an action (saved, sent, revoked, failed). Mount <ToastProvider> once at the app root and call useToast().toast() from anywhere. Toasts stack and portal above dialogs. Variants: success, info, warning, error. The countdown pauses while a toast is hovered or holds focus, and every toast has a close button (`dismissLabel` on the provider names it).',
|
|
5
|
+
'Transient, self-dismissing notification for the RESULT of an action (saved, sent, revoked, failed). Mount <ToastProvider> once at the app root and call useToast().toast() from anywhere. Toasts stack and portal above dialogs. Variants: success, info, warning, error. The countdown pauses while a toast is hovered or holds focus, and every toast has a close button (`dismissLabel` on the provider names it). On React Native the same API draws through the portal host above every open overlay and the keyboard, as a card with a status-coloured edge, its action and its close in one row; a sideways swipe dismisses, a press holds the countdown, and so does an active screen reader (WCAG 2.2.1).',
|
|
6
6
|
whenToUse: [
|
|
7
7
|
'Confirming an action succeeded (e.g. "Grant revoked")',
|
|
8
8
|
'Reporting an action failed, with the reason',
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createContext,
|
|
3
|
+
type ReactNode,
|
|
4
|
+
useCallback,
|
|
5
|
+
useContext,
|
|
6
|
+
useEffect,
|
|
7
|
+
useMemo,
|
|
8
|
+
useRef,
|
|
9
|
+
useState,
|
|
10
|
+
useSyncExternalStore,
|
|
11
|
+
} from 'react'
|
|
12
|
+
import {
|
|
13
|
+
AccessibilityInfo,
|
|
14
|
+
type LayoutChangeEvent,
|
|
15
|
+
Platform,
|
|
16
|
+
Pressable,
|
|
17
|
+
StyleSheet,
|
|
18
|
+
} from 'react-native'
|
|
19
|
+
import {Gesture, GestureDetector} from 'react-native-gesture-handler'
|
|
20
|
+
import Animated, {
|
|
21
|
+
Easing,
|
|
22
|
+
useAnimatedStyle,
|
|
23
|
+
useSharedValue,
|
|
24
|
+
withTiming,
|
|
25
|
+
} from 'react-native-reanimated'
|
|
26
|
+
import {scheduleOnRN} from 'react-native-worklets'
|
|
27
|
+
import {html} from 'react-strict-dom'
|
|
28
|
+
import {DURATION_MS, LAYERS, SPACING_PX} from '@duro-app/tokens/keys'
|
|
29
|
+
import {Icon} from '../Icon/Icon'
|
|
30
|
+
import {Portal} from '../../shared/portal/PortalHost'
|
|
31
|
+
import {useSafeAreaInsets} from '../../shared/safeArea'
|
|
32
|
+
import {useKeyboardHeight} from '../../shared/useKeyboardHeight'
|
|
33
|
+
import {useScreenReaderEnabled} from '../../shared/useScreenReaderEnabled'
|
|
34
|
+
import {DISMISS_DURATION, DRAG_SLOP, SNAP_DURATION, shouldDismissEdge} from '../Drawer/sheetPhysics'
|
|
35
|
+
import {nativeStyles} from './native.css'
|
|
36
|
+
import {createToastStore, type ToastContextValue, type ToastEntry, type ToastStore} from './store'
|
|
37
|
+
|
|
38
|
+
export type {ToastAction, ToastContextValue, ToastOptions, ToastVariant} from './store'
|
|
39
|
+
|
|
40
|
+
const ToastContext = createContext<ToastContextValue | null>(null)
|
|
41
|
+
|
|
42
|
+
export function useToast(): ToastContextValue {
|
|
43
|
+
const ctx = useContext(ToastContext)
|
|
44
|
+
if (!ctx) throw new Error('useToast must be used within a <ToastProvider>')
|
|
45
|
+
return ctx
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const easeOut = Easing.out(Easing.cubic)
|
|
49
|
+
|
|
50
|
+
function ToastItem({
|
|
51
|
+
entry,
|
|
52
|
+
store,
|
|
53
|
+
onDismiss,
|
|
54
|
+
dismissLabel,
|
|
55
|
+
screenReader,
|
|
56
|
+
}: {
|
|
57
|
+
entry: ToastEntry
|
|
58
|
+
store: ToastStore
|
|
59
|
+
onDismiss: (id: string) => void
|
|
60
|
+
dismissLabel: string
|
|
61
|
+
screenReader: boolean
|
|
62
|
+
}) {
|
|
63
|
+
const {id, variant, message, action, duration} = entry
|
|
64
|
+
const [pressed, setPressed] = useState(false)
|
|
65
|
+
// A press holds the toast as hover does on the web; a screen reader holds
|
|
66
|
+
// it for as long as it is on (WCAG 2.2.1).
|
|
67
|
+
const paused = pressed || screenReader
|
|
68
|
+
|
|
69
|
+
// The store keeps what is left, so a toast that moves to another host
|
|
70
|
+
// (a Dialog above it closed) carries on where it was.
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
if (duration <= 0 || paused) return
|
|
73
|
+
const started = Date.now()
|
|
74
|
+
const timer = setTimeout(() => onDismiss(id), Math.max(store.remaining(id), 0))
|
|
75
|
+
return () => {
|
|
76
|
+
clearTimeout(timer)
|
|
77
|
+
store.spend(id, Date.now() - started)
|
|
78
|
+
}
|
|
79
|
+
}, [id, duration, paused, onDismiss, store])
|
|
80
|
+
|
|
81
|
+
// iOS has no live regions: announce the message once. Android reads the
|
|
82
|
+
// live region itself, so announcing there too would read it twice.
|
|
83
|
+
const announced = useRef(false)
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
if (Platform.OS !== 'ios' || announced.current || typeof message !== 'string') return
|
|
86
|
+
announced.current = true
|
|
87
|
+
AccessibilityInfo.announceForAccessibility(message)
|
|
88
|
+
}, [message])
|
|
89
|
+
|
|
90
|
+
const x = useSharedValue(0)
|
|
91
|
+
const shown = useSharedValue(0)
|
|
92
|
+
const width = useSharedValue(1)
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
shown.value = withTiming(1, {duration: DURATION_MS.brisk, easing: easeOut})
|
|
95
|
+
}, [shown])
|
|
96
|
+
|
|
97
|
+
const pan = useMemo(
|
|
98
|
+
() =>
|
|
99
|
+
Gesture.Pan()
|
|
100
|
+
.activeOffsetX([-DRAG_SLOP, DRAG_SLOP])
|
|
101
|
+
.failOffsetY([-DRAG_SLOP, DRAG_SLOP])
|
|
102
|
+
.onUpdate((e) => {
|
|
103
|
+
x.value = e.translationX
|
|
104
|
+
})
|
|
105
|
+
.onEnd((e) => {
|
|
106
|
+
const distance = Math.abs(e.translationX)
|
|
107
|
+
if (shouldDismissEdge(distance, width.value, Math.abs(e.velocityX) / 1000)) {
|
|
108
|
+
const off = Math.sign(e.translationX || e.velocityX) * width.value
|
|
109
|
+
x.value = withTiming(off, {duration: DISMISS_DURATION}, (done) => {
|
|
110
|
+
if (done) scheduleOnRN(onDismiss, id)
|
|
111
|
+
})
|
|
112
|
+
} else {
|
|
113
|
+
x.value = withTiming(0, {duration: SNAP_DURATION, easing: easeOut})
|
|
114
|
+
}
|
|
115
|
+
}),
|
|
116
|
+
[id, onDismiss, x, width],
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
const motion = useAnimatedStyle(() => ({
|
|
120
|
+
opacity: shown.value * (1 - Math.min(1, Math.abs(x.value) / width.value)),
|
|
121
|
+
transform: [{translateX: x.value}, {translateY: (1 - shown.value) * SPACING_PX.sm}],
|
|
122
|
+
}))
|
|
123
|
+
|
|
124
|
+
const onLayout = (e: LayoutChangeEvent) => {
|
|
125
|
+
width.value = e.nativeEvent.layout.width
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<GestureDetector gesture={pan}>
|
|
130
|
+
<Animated.View onLayout={onLayout} style={[layout.row, motion]}>
|
|
131
|
+
<Pressable
|
|
132
|
+
onPressIn={() => setPressed(true)}
|
|
133
|
+
onPressOut={() => setPressed(false)}
|
|
134
|
+
accessible={false}
|
|
135
|
+
style={layout.row}
|
|
136
|
+
>
|
|
137
|
+
<html.div
|
|
138
|
+
role={variant === 'error' ? 'alert' : 'status'}
|
|
139
|
+
aria-live={variant === 'error' ? 'assertive' : 'polite'}
|
|
140
|
+
style={[nativeStyles.toast, nativeStyles[variant]]}
|
|
141
|
+
>
|
|
142
|
+
<html.div style={nativeStyles.message}>{message}</html.div>
|
|
143
|
+
{action ? (
|
|
144
|
+
<html.button
|
|
145
|
+
type="button"
|
|
146
|
+
style={nativeStyles.action}
|
|
147
|
+
onClick={() => {
|
|
148
|
+
action.onClick()
|
|
149
|
+
onDismiss(id)
|
|
150
|
+
}}
|
|
151
|
+
>
|
|
152
|
+
{action.label}
|
|
153
|
+
</html.button>
|
|
154
|
+
) : null}
|
|
155
|
+
<html.button
|
|
156
|
+
type="button"
|
|
157
|
+
aria-label={dismissLabel}
|
|
158
|
+
style={nativeStyles.close}
|
|
159
|
+
onClick={() => onDismiss(id)}
|
|
160
|
+
>
|
|
161
|
+
<Icon name="x" size="sm" color="textMuted" />
|
|
162
|
+
</html.button>
|
|
163
|
+
</html.div>
|
|
164
|
+
</Pressable>
|
|
165
|
+
</Animated.View>
|
|
166
|
+
</GestureDetector>
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Layout-only: each toast row spans the region and centres its card.
|
|
171
|
+
const layout = StyleSheet.create({row: {width: '100%', alignItems: 'center'}})
|
|
172
|
+
|
|
173
|
+
export interface ToastProviderProps {
|
|
174
|
+
/** Accessible name of each toast's close button, e.g. from the app's message catalog. */
|
|
175
|
+
dismissLabel?: string
|
|
176
|
+
children: ReactNode
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Native ToastProvider (plan N0, Slice B): the same store and countdown as
|
|
181
|
+
* the web, drawn through the portal host at `LAYERS.toast`, so a toast shows
|
|
182
|
+
* above the app, every open Duro overlay (a sheet included) and the
|
|
183
|
+
* keyboard. A sideways swipe or the close button dismisses it.
|
|
184
|
+
*/
|
|
185
|
+
export function ToastProvider({dismissLabel = 'Dismiss', children}: ToastProviderProps) {
|
|
186
|
+
const storeRef = useRef<ToastStore>(null)
|
|
187
|
+
if (storeRef.current === null) storeRef.current = createToastStore()
|
|
188
|
+
const store = storeRef.current
|
|
189
|
+
const entries = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot)
|
|
190
|
+
const insets = useSafeAreaInsets()
|
|
191
|
+
const keyboard = useKeyboardHeight()
|
|
192
|
+
const screenReader = useScreenReaderEnabled()
|
|
193
|
+
|
|
194
|
+
const value = useMemo<ToastContextValue>(
|
|
195
|
+
() => ({toast: (opts) => store.add(opts), dismiss: (id) => store.remove(id)}),
|
|
196
|
+
[store],
|
|
197
|
+
)
|
|
198
|
+
const dismiss = useCallback((id: string) => store.remove(id), [store])
|
|
199
|
+
const bottom = Math.max(insets.bottom, keyboard) + SPACING_PX.md
|
|
200
|
+
|
|
201
|
+
return (
|
|
202
|
+
<ToastContext.Provider value={value}>
|
|
203
|
+
{children}
|
|
204
|
+
{entries.length > 0 && (
|
|
205
|
+
<Portal layer={LAYERS.toast}>
|
|
206
|
+
<html.div
|
|
207
|
+
role="region"
|
|
208
|
+
aria-label="Notifications"
|
|
209
|
+
style={[nativeStyles.region, nativeStyles.regionBottom(bottom)]}
|
|
210
|
+
>
|
|
211
|
+
{entries.map((entry) => (
|
|
212
|
+
<ToastItem
|
|
213
|
+
key={entry.id}
|
|
214
|
+
entry={entry}
|
|
215
|
+
store={store}
|
|
216
|
+
onDismiss={dismiss}
|
|
217
|
+
dismissLabel={dismissLabel}
|
|
218
|
+
screenReader={screenReader}
|
|
219
|
+
/>
|
|
220
|
+
))}
|
|
221
|
+
</html.div>
|
|
222
|
+
</Portal>
|
|
223
|
+
)}
|
|
224
|
+
</ToastContext.Provider>
|
|
225
|
+
)
|
|
226
|
+
}
|