@duro-app/ui 5.12.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/components/AppShell/native.css.d.ts +53 -0
- package/dist/components/AppShell/native.css.d.ts.map +1 -0
- 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 +13 -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/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/Popover/native.css.d.ts +11 -0
- package/dist/components/Popover/native.css.d.ts.map +1 -0
- 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/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/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/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/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/index.css +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4935 -4260
- 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/pressableChild.d.ts +14 -0
- package/dist/shared/pressableChild.d.ts.map +1 -0
- package/dist/table.js +1 -1
- package/package.json +2 -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 +3 -3
- package/src/components/Drawer/Drawer.meta.ts +2 -1
- package/src/components/Drawer/Drawer.native.tsx +469 -0
- package/src/components/Drawer/Drawer.tsx +58 -145
- 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 +15 -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/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/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/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/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/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/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 +3 -3
- 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/hooks/useContainerBelow.native.ts +30 -0
- package/src/index.native.ts +33 -0
- package/src/index.ts +8 -1
- package/src/shared/ButtonHost.native.tsx +70 -0
- package/src/shared/ButtonHost.tsx +12 -0
- package/src/shared/portal/AnchoredOverlay.native.tsx +117 -0
- package/src/shared/pressableChild.ts +33 -0
- package/src/shared/useKeyboardHeight.native.ts +18 -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/src/index.native.ts
CHANGED
|
@@ -47,10 +47,43 @@ export {
|
|
|
47
47
|
export {List, type ListSelectionMode} from './components/List/List'
|
|
48
48
|
export {RadioGroup} from './components/RadioGroup/RadioGroup'
|
|
49
49
|
export {Toggle, type ToggleSize} from './components/Toggle/Toggle'
|
|
50
|
+
export {Tag, type TagVariant} from './components/Tag/Tag'
|
|
51
|
+
export {SideNav} from './components/SideNav/SideNav'
|
|
52
|
+
export {Tabs} from './components/Tabs/Tabs'
|
|
53
|
+
export {Tree} from './components/Tree/Tree'
|
|
54
|
+
// Always stacked on native: a phone is below every collapseBelow.
|
|
55
|
+
export {SplitPane} from './components/SplitPane/SplitPane'
|
|
56
|
+
// Always the phone shape on native: the bar on top, the rail in a left Drawer.
|
|
57
|
+
export {
|
|
58
|
+
AppShell,
|
|
59
|
+
type AppShellCollapse,
|
|
60
|
+
type AppShellRailState,
|
|
61
|
+
} from './components/AppShell/AppShell'
|
|
50
62
|
|
|
51
63
|
// Overlays (5.12: rendered in an OverlayModal on native)
|
|
52
64
|
export {ConfirmDialog} from './components/ConfirmDialog/ConfirmDialog'
|
|
53
65
|
export {Dialog, type DialogSize} from './components/Dialog/Dialog'
|
|
66
|
+
// 5.13: the panel follows the finger on the UI thread (Gesture Handler + Reanimated)
|
|
67
|
+
export {
|
|
68
|
+
Drawer,
|
|
69
|
+
type DrawerAnchor,
|
|
70
|
+
type DrawerSize,
|
|
71
|
+
type SnapPoint,
|
|
72
|
+
} from './components/Drawer/Drawer'
|
|
73
|
+
export {Menu, type MenuTriggerVariant} from './components/Menu/Menu'
|
|
74
|
+
export {Popover, type PopoverHandle} from './components/Popover/Popover'
|
|
75
|
+
// Touch has no hover: a Tooltip's text is its trigger's accessibility hint.
|
|
76
|
+
export {Tooltip} from './components/Tooltip/Tooltip'
|
|
77
|
+
// Drawn through the portal host, above every open overlay and the keyboard.
|
|
78
|
+
export {
|
|
79
|
+
ToastProvider,
|
|
80
|
+
useToast,
|
|
81
|
+
type ToastVariant,
|
|
82
|
+
type ToastOptions,
|
|
83
|
+
type ToastAction,
|
|
84
|
+
type ToastContextValue,
|
|
85
|
+
type ToastProviderProps,
|
|
86
|
+
} from './components/Toast'
|
|
54
87
|
|
|
55
88
|
// Layout primitives
|
|
56
89
|
export {PageShell, type PageShellMaxWidth, type PageShellPadding} from './components/PageShell'
|
package/src/index.ts
CHANGED
|
@@ -49,7 +49,12 @@ export {
|
|
|
49
49
|
} from './components/DragDrop/DragDrop'
|
|
50
50
|
export {Dialog, type DialogSize} from './components/Dialog/Dialog'
|
|
51
51
|
export {DetailPanel, type DetailPanelSize} from './components/DetailPanel/DetailPanel'
|
|
52
|
-
export {
|
|
52
|
+
export {
|
|
53
|
+
Drawer,
|
|
54
|
+
type DrawerAnchor,
|
|
55
|
+
type DrawerSize,
|
|
56
|
+
type SnapPoint,
|
|
57
|
+
} from './components/Drawer/Drawer'
|
|
53
58
|
export {EmptyState} from './components/EmptyState/EmptyState'
|
|
54
59
|
export {Heading, type HeadingVariant} from './components/Heading/Heading'
|
|
55
60
|
export {List, type ListSelectionMode} from './components/List/List'
|
|
@@ -115,6 +120,8 @@ export {
|
|
|
115
120
|
type TimelineRowProps,
|
|
116
121
|
} from './components/Timeline/Timeline'
|
|
117
122
|
export {Tree} from './components/Tree/Tree'
|
|
123
|
+
export {SplitPane} from './components/SplitPane/SplitPane'
|
|
124
|
+
export {VirtualList, type VirtualListProps} from './components/VirtualList/VirtualList'
|
|
118
125
|
export {Textarea, type TextareaVariant} from './components/Textarea/Textarea'
|
|
119
126
|
export {
|
|
120
127
|
ThemeProvider,
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import {Children, type ComponentProps, type ReactNode, type Ref} from 'react'
|
|
2
|
+
import {Pressable} from 'react-native'
|
|
3
|
+
import {html} from 'react-strict-dom'
|
|
4
|
+
import {useTooltipHint} from '../components/Tooltip/TooltipHint'
|
|
5
|
+
|
|
6
|
+
export type ButtonHostProps = ComponentProps<typeof html.button>
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The host element of Button and Toggle on native. Inside a Tooltip it is an
|
|
10
|
+
* RN Pressable, which carries the Tooltip's text as its own accessibility hint
|
|
11
|
+
* (plan N0, Decision 5: RSD drops `accessibilityHint` on html.*); the visual
|
|
12
|
+
* styles move to a div inside it. Elsewhere it is RSD's button, as before.
|
|
13
|
+
*
|
|
14
|
+
* holds-until: react-strict-dom maps aria-description to accessibilityHint
|
|
15
|
+
* (https://github.com/react/react-strict-dom/issues/524); then html.button
|
|
16
|
+
* carries the hint itself and this swap goes.
|
|
17
|
+
*/
|
|
18
|
+
export function ButtonHost(props: ButtonHostProps) {
|
|
19
|
+
const hint = useTooltipHint()
|
|
20
|
+
const content = textInSpans(props.children)
|
|
21
|
+
if (!hint) {
|
|
22
|
+
return (
|
|
23
|
+
<html.button {...props} style={props.style}>
|
|
24
|
+
{content}
|
|
25
|
+
</html.button>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
const {
|
|
29
|
+
ref,
|
|
30
|
+
style,
|
|
31
|
+
disabled,
|
|
32
|
+
onClick,
|
|
33
|
+
'aria-label': label,
|
|
34
|
+
'aria-pressed': pressed,
|
|
35
|
+
'aria-expanded': expanded,
|
|
36
|
+
} = props
|
|
37
|
+
return (
|
|
38
|
+
<Pressable
|
|
39
|
+
ref={ref as Ref<never>}
|
|
40
|
+
onPress={disabled ? undefined : () => onClick?.(undefined as never)}
|
|
41
|
+
disabled={disabled}
|
|
42
|
+
accessibilityRole="button"
|
|
43
|
+
accessibilityLabel={label ?? undefined}
|
|
44
|
+
accessibilityHint={hint}
|
|
45
|
+
accessibilityState={{
|
|
46
|
+
disabled: !!disabled,
|
|
47
|
+
...(pressed !== undefined ? {checked: !!pressed} : {}),
|
|
48
|
+
...(expanded !== undefined ? {expanded: !!expanded} : {}),
|
|
49
|
+
}}
|
|
50
|
+
>
|
|
51
|
+
<html.div style={style}>{content}</html.div>
|
|
52
|
+
</Pressable>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* A label beside an icon (`<Icon/> Menu`): RSD wraps a button's text only
|
|
58
|
+
* when it is the sole child, and React Native refuses bare text in a view.
|
|
59
|
+
*/
|
|
60
|
+
function textInSpans(children: ReactNode): ReactNode {
|
|
61
|
+
const items = Children.toArray(children)
|
|
62
|
+
if (items.length < 2) return children
|
|
63
|
+
return items.map((child, i) =>
|
|
64
|
+
typeof child === 'string' || typeof child === 'number' ? (
|
|
65
|
+
<html.span key={i}>{child}</html.span>
|
|
66
|
+
) : (
|
|
67
|
+
child
|
|
68
|
+
),
|
|
69
|
+
)
|
|
70
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type {ComponentProps} from 'react'
|
|
2
|
+
import {html} from 'react-strict-dom'
|
|
3
|
+
|
|
4
|
+
export type ButtonHostProps = ComponentProps<typeof html.button>
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The host element of Button and Toggle. On the web it is the <button>
|
|
8
|
+
* itself, with no component in between (a wrapper changed what a Form's
|
|
9
|
+
* submit focuses); the native variant (ButtonHost.native.tsx) can swap in an
|
|
10
|
+
* RN Pressable to carry a Tooltip's accessibility hint.
|
|
11
|
+
*/
|
|
12
|
+
export const ButtonHost: typeof html.button = html.button
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import {type ReactNode, type RefObject, useEffect, useState} from 'react'
|
|
2
|
+
import {
|
|
3
|
+
type LayoutChangeEvent,
|
|
4
|
+
Pressable,
|
|
5
|
+
StyleSheet,
|
|
6
|
+
useWindowDimensions,
|
|
7
|
+
View,
|
|
8
|
+
} from 'react-native'
|
|
9
|
+
import {SPACING_PX} from '@duro-app/tokens/keys'
|
|
10
|
+
import {
|
|
11
|
+
computePopoverPosition,
|
|
12
|
+
type PopoverAlign,
|
|
13
|
+
type PopoverRect,
|
|
14
|
+
type PopoverSide,
|
|
15
|
+
type PopoverSize,
|
|
16
|
+
} from '../../components/Popover/position'
|
|
17
|
+
import {OverlayModal} from './OverlayModal'
|
|
18
|
+
|
|
19
|
+
interface Measurable {
|
|
20
|
+
measureInWindow?: (cb: (x: number, y: number, width: number, height: number) => void) => void
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface AnchoredOverlayProps {
|
|
24
|
+
visible: boolean
|
|
25
|
+
/** The trigger's host: it is measured in window coordinates when the overlay opens */
|
|
26
|
+
anchorRef: RefObject<unknown>
|
|
27
|
+
side: PopoverSide
|
|
28
|
+
align: PopoverAlign
|
|
29
|
+
/** A backdrop press or Android's back button */
|
|
30
|
+
onRequestClose: () => void
|
|
31
|
+
/** The Modal has been presented: move accessibility focus in */
|
|
32
|
+
onShow?: () => void
|
|
33
|
+
/** iOS: the Modal has finished dismissing */
|
|
34
|
+
onDismiss?: () => void
|
|
35
|
+
children: ReactNode
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* A popup placed against its trigger on native (plan N0, Decision 3): the
|
|
40
|
+
* trigger is measured with `measureInWindow`, the popup with `onLayout`, and
|
|
41
|
+
* `computePopoverPosition` places it exactly as on the web. The OverlayModal
|
|
42
|
+
* is transparent and translucent, so its coordinates are window coordinates.
|
|
43
|
+
* Until both are measured the popup is laid out but not shown.
|
|
44
|
+
*/
|
|
45
|
+
export function AnchoredOverlay({
|
|
46
|
+
visible,
|
|
47
|
+
anchorRef,
|
|
48
|
+
side,
|
|
49
|
+
align,
|
|
50
|
+
onRequestClose,
|
|
51
|
+
onShow,
|
|
52
|
+
onDismiss,
|
|
53
|
+
children,
|
|
54
|
+
}: AnchoredOverlayProps) {
|
|
55
|
+
const window = useWindowDimensions()
|
|
56
|
+
const [anchor, setAnchor] = useState<PopoverRect | null>(null)
|
|
57
|
+
const [size, setSize] = useState<PopoverSize | null>(null)
|
|
58
|
+
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
if (!visible) {
|
|
61
|
+
setAnchor(null)
|
|
62
|
+
setSize(null)
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
const host = anchorRef.current as Measurable | null
|
|
66
|
+
host?.measureInWindow?.((left, top, width, height) => setAnchor({top, left, width, height}))
|
|
67
|
+
}, [visible, anchorRef, window.width, window.height])
|
|
68
|
+
|
|
69
|
+
const onLayout = (e: LayoutChangeEvent) => {
|
|
70
|
+
const {width, height} = e.nativeEvent.layout
|
|
71
|
+
setSize((prev) => (prev?.width === width && prev.height === height ? prev : {width, height}))
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const position =
|
|
75
|
+
anchor && size
|
|
76
|
+
? computePopoverPosition({
|
|
77
|
+
anchor,
|
|
78
|
+
popup: size,
|
|
79
|
+
viewport: {width: window.width, height: window.height},
|
|
80
|
+
side,
|
|
81
|
+
align,
|
|
82
|
+
offset: SPACING_PX.xs,
|
|
83
|
+
})
|
|
84
|
+
: null
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<OverlayModal
|
|
88
|
+
visible={visible}
|
|
89
|
+
onRequestClose={onRequestClose}
|
|
90
|
+
onShow={onShow}
|
|
91
|
+
onDismiss={onDismiss}
|
|
92
|
+
>
|
|
93
|
+
<Pressable
|
|
94
|
+
style={StyleSheet.absoluteFill}
|
|
95
|
+
onPress={onRequestClose}
|
|
96
|
+
accessible={false}
|
|
97
|
+
importantForAccessibility="no"
|
|
98
|
+
/>
|
|
99
|
+
<View
|
|
100
|
+
onLayout={onLayout}
|
|
101
|
+
style={[
|
|
102
|
+
layout.popup,
|
|
103
|
+
{maxWidth: window.width - 2 * SPACING_PX.sm},
|
|
104
|
+
position ? {top: position.top, left: position.left} : layout.unplaced,
|
|
105
|
+
]}
|
|
106
|
+
>
|
|
107
|
+
{children}
|
|
108
|
+
</View>
|
|
109
|
+
</OverlayModal>
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Layout-only: where the popup sits, and hidden until it has been placed.
|
|
114
|
+
const layout = StyleSheet.create({
|
|
115
|
+
popup: {position: 'absolute'},
|
|
116
|
+
unplaced: {top: 0, left: 0, opacity: 0},
|
|
117
|
+
})
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Children,
|
|
3
|
+
cloneElement,
|
|
4
|
+
isValidElement,
|
|
5
|
+
type ReactElement,
|
|
6
|
+
type ReactNode,
|
|
7
|
+
type Ref,
|
|
8
|
+
} from 'react'
|
|
9
|
+
import {mergeRefs} from './mergeRefs'
|
|
10
|
+
|
|
11
|
+
interface PressableChildProps {
|
|
12
|
+
onClick?: () => void
|
|
13
|
+
ref?: Ref<unknown>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Native overlay triggers and closes: native presses do not bubble to a
|
|
18
|
+
* wrapper as web clicks do, so the one child (a Button) gets the handler
|
|
19
|
+
* itself, after its own. `ref` also receives the child's host, where focus
|
|
20
|
+
* returns when the overlay closes.
|
|
21
|
+
*/
|
|
22
|
+
export function withPress(children: ReactNode, onPress: () => void, ref?: Ref<unknown>) {
|
|
23
|
+
const child = Children.only(children)
|
|
24
|
+
if (!isValidElement<PressableChildProps>(child)) return null
|
|
25
|
+
const own = child.props.onClick
|
|
26
|
+
return cloneElement(child as ReactElement<PressableChildProps>, {
|
|
27
|
+
...(ref ? {ref: mergeRefs(child.props.ref, ref)} : {}),
|
|
28
|
+
onClick: () => {
|
|
29
|
+
own?.()
|
|
30
|
+
onPress()
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {useEffect, useState} from 'react'
|
|
2
|
+
import {Keyboard, Platform} from 'react-native'
|
|
3
|
+
|
|
4
|
+
/** The on-screen keyboard's height while it is shown, else 0. */
|
|
5
|
+
export function useKeyboardHeight(): number {
|
|
6
|
+
const [height, setHeight] = useState(0)
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
// iOS announces the keyboard before it moves, Android only after.
|
|
9
|
+
const show = Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow'
|
|
10
|
+
const hide = Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide'
|
|
11
|
+
const subs = [
|
|
12
|
+
Keyboard.addListener(show, (e) => setHeight(e.endCoordinates.height)),
|
|
13
|
+
Keyboard.addListener(hide, () => setHeight(0)),
|
|
14
|
+
]
|
|
15
|
+
return () => subs.forEach((s) => s.remove())
|
|
16
|
+
}, [])
|
|
17
|
+
return height
|
|
18
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {useEffect, useState} from 'react'
|
|
2
|
+
import {AccessibilityInfo} from 'react-native'
|
|
3
|
+
|
|
4
|
+
/** Whether VoiceOver or TalkBack is on, following changes while mounted. */
|
|
5
|
+
export function useScreenReaderEnabled(): boolean {
|
|
6
|
+
const [enabled, setEnabled] = useState(false)
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
let live = true
|
|
9
|
+
void AccessibilityInfo.isScreenReaderEnabled().then((on) => {
|
|
10
|
+
if (live) setEnabled(on)
|
|
11
|
+
})
|
|
12
|
+
const sub = AccessibilityInfo.addEventListener('screenReaderChanged', setEnabled)
|
|
13
|
+
return () => {
|
|
14
|
+
live = false
|
|
15
|
+
sub.remove()
|
|
16
|
+
}
|
|
17
|
+
}, [])
|
|
18
|
+
return enabled
|
|
19
|
+
}
|