@cdx-ui/components 0.1.6 → 0.1.7
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/coverage.svg +1 -1
- package/lib/commonjs/components/Alert/index.js.map +1 -1
- package/lib/commonjs/components/Avatar/index.js +6 -4
- package/lib/commonjs/components/Avatar/index.js.map +1 -1
- package/lib/commonjs/components/Badge/index.js.map +1 -1
- package/lib/commonjs/components/Badge/styles.js +2 -2
- package/lib/commonjs/components/BottomSheet/index.js +106 -91
- package/lib/commonjs/components/BottomSheet/index.js.map +1 -1
- package/lib/commonjs/components/BottomSheet/styles.js +1 -12
- package/lib/commonjs/components/BottomSheet/styles.js.map +1 -1
- package/lib/commonjs/components/Button/index.js.map +1 -1
- package/lib/commonjs/components/Card/index.js.map +1 -1
- package/lib/commonjs/components/Dialog/index.js.map +1 -1
- package/lib/commonjs/components/Divider/index.js.map +1 -1
- package/lib/commonjs/components/Form/styles.js +1 -1
- package/lib/commonjs/components/Form/styles.js.map +1 -1
- package/lib/commonjs/components/FullScreenDialog/animation.js +46 -0
- package/lib/commonjs/components/FullScreenDialog/animation.js.map +1 -0
- package/lib/commonjs/components/FullScreenDialog/index.js +343 -0
- package/lib/commonjs/components/FullScreenDialog/index.js.map +1 -0
- package/lib/commonjs/components/FullScreenDialog/styles.js +14 -0
- package/lib/commonjs/components/FullScreenDialog/styles.js.map +1 -0
- package/lib/commonjs/components/IconButton/styles.js +7 -5
- package/lib/commonjs/components/IconButton/styles.js.map +1 -1
- package/lib/commonjs/components/InsuranceDisclosure/index.js.map +1 -1
- package/lib/commonjs/components/Link/index.js.map +1 -1
- package/lib/commonjs/components/ListItem/index.js +2 -0
- package/lib/commonjs/components/ListItem/index.js.map +1 -1
- package/lib/commonjs/components/ListItem/styles.js +6 -0
- package/lib/commonjs/components/ListItem/styles.js.map +1 -1
- package/lib/commonjs/components/ProgressSegmented/index.js.map +1 -1
- package/lib/commonjs/components/Radio/index.js.map +1 -1
- package/lib/commonjs/components/ScopedTheme/index.js +11 -3
- package/lib/commonjs/components/ScopedTheme/index.js.map +1 -1
- package/lib/commonjs/components/Skeleton/index.js.map +1 -1
- package/lib/commonjs/components/Stack/HStack.js.map +1 -1
- package/lib/commonjs/components/Stack/VStack.js.map +1 -1
- package/lib/commonjs/components/Textarea/index.js.map +1 -1
- package/lib/commonjs/components/Tile/index.js.map +1 -1
- package/lib/commonjs/components/Toast/index.js +40 -12
- package/lib/commonjs/components/Toast/index.js.map +1 -1
- package/lib/commonjs/components/Toast/styles.js +15 -4
- package/lib/commonjs/components/Toast/styles.js.map +1 -1
- package/lib/commonjs/components/index.js +12 -0
- package/lib/commonjs/components/index.js.map +1 -1
- package/lib/commonjs/components/picker/styles.js +6 -2
- package/lib/commonjs/components/picker/styles.js.map +1 -1
- package/lib/commonjs/components/sheet-header/index.js +97 -0
- package/lib/commonjs/components/sheet-header/index.js.map +1 -0
- package/lib/commonjs/components/sheet-header/styles.js +28 -0
- package/lib/commonjs/components/sheet-header/styles.js.map +1 -0
- package/lib/commonjs/figma/BottomSheet.figma.js +1 -1
- package/lib/commonjs/providers/toast/provider.js +44 -11
- package/lib/commonjs/providers/toast/provider.js.map +1 -1
- package/lib/module/components/Alert/index.js.map +1 -1
- package/lib/module/components/Avatar/index.js +6 -4
- package/lib/module/components/Avatar/index.js.map +1 -1
- package/lib/module/components/Badge/index.js.map +1 -1
- package/lib/module/components/Badge/styles.js +2 -2
- package/lib/module/components/BottomSheet/index.js +110 -95
- package/lib/module/components/BottomSheet/index.js.map +1 -1
- package/lib/module/components/BottomSheet/styles.js +0 -11
- package/lib/module/components/BottomSheet/styles.js.map +1 -1
- package/lib/module/components/Button/index.js.map +1 -1
- package/lib/module/components/Card/index.js.map +1 -1
- package/lib/module/components/Dialog/index.js.map +1 -1
- package/lib/module/components/Divider/index.js.map +1 -1
- package/lib/module/components/Form/styles.js +1 -1
- package/lib/module/components/Form/styles.js.map +1 -1
- package/lib/module/components/FullScreenDialog/animation.js +41 -0
- package/lib/module/components/FullScreenDialog/animation.js.map +1 -0
- package/lib/module/components/FullScreenDialog/index.js +338 -0
- package/lib/module/components/FullScreenDialog/index.js.map +1 -0
- package/lib/module/components/FullScreenDialog/styles.js +10 -0
- package/lib/module/components/FullScreenDialog/styles.js.map +1 -0
- package/lib/module/components/IconButton/styles.js +7 -4
- package/lib/module/components/IconButton/styles.js.map +1 -1
- package/lib/module/components/InsuranceDisclosure/index.js.map +1 -1
- package/lib/module/components/Link/index.js.map +1 -1
- package/lib/module/components/ListItem/index.js +2 -0
- package/lib/module/components/ListItem/index.js.map +1 -1
- package/lib/module/components/ListItem/styles.js +6 -0
- package/lib/module/components/ListItem/styles.js.map +1 -1
- package/lib/module/components/ProgressSegmented/index.js.map +1 -1
- package/lib/module/components/Radio/index.js.map +1 -1
- package/lib/module/components/ScopedTheme/index.js +11 -3
- package/lib/module/components/ScopedTheme/index.js.map +1 -1
- package/lib/module/components/Skeleton/index.js.map +1 -1
- package/lib/module/components/Stack/HStack.js.map +1 -1
- package/lib/module/components/Stack/VStack.js.map +1 -1
- package/lib/module/components/Textarea/index.js.map +1 -1
- package/lib/module/components/Tile/index.js.map +1 -1
- package/lib/module/components/Toast/index.js +43 -15
- package/lib/module/components/Toast/index.js.map +1 -1
- package/lib/module/components/Toast/styles.js +14 -3
- package/lib/module/components/Toast/styles.js.map +1 -1
- package/lib/module/components/index.js +1 -0
- package/lib/module/components/index.js.map +1 -1
- package/lib/module/components/picker/styles.js +6 -2
- package/lib/module/components/picker/styles.js.map +1 -1
- package/lib/module/components/sheet-header/index.js +94 -0
- package/lib/module/components/sheet-header/index.js.map +1 -0
- package/lib/module/components/sheet-header/styles.js +25 -0
- package/lib/module/components/sheet-header/styles.js.map +1 -0
- package/lib/module/figma/BottomSheet.figma.js +1 -1
- package/lib/module/providers/toast/provider.js +44 -11
- package/lib/module/providers/toast/provider.js.map +1 -1
- package/lib/typescript/components/Alert/index.d.ts +4 -1
- package/lib/typescript/components/Alert/index.d.ts.map +1 -1
- package/lib/typescript/components/Avatar/index.d.ts +10 -4
- package/lib/typescript/components/Avatar/index.d.ts.map +1 -1
- package/lib/typescript/components/Badge/index.d.ts +34 -0
- package/lib/typescript/components/Badge/index.d.ts.map +1 -1
- package/lib/typescript/components/BottomSheet/index.d.ts +12 -1
- package/lib/typescript/components/BottomSheet/index.d.ts.map +1 -1
- package/lib/typescript/components/BottomSheet/styles.d.ts +0 -5
- package/lib/typescript/components/BottomSheet/styles.d.ts.map +1 -1
- package/lib/typescript/components/Button/index.d.ts +9 -1
- package/lib/typescript/components/Button/index.d.ts.map +1 -1
- package/lib/typescript/components/Card/index.d.ts +14 -0
- package/lib/typescript/components/Card/index.d.ts.map +1 -1
- package/lib/typescript/components/Chip/index.d.ts +1 -1
- package/lib/typescript/components/CurrencyInput/index.d.ts +2 -2
- package/lib/typescript/components/CurrencyInput/index.d.ts.map +1 -1
- package/lib/typescript/components/Dialog/index.d.ts +10 -0
- package/lib/typescript/components/Dialog/index.d.ts.map +1 -1
- package/lib/typescript/components/Dialog/styles.d.ts +1 -0
- package/lib/typescript/components/Dialog/styles.d.ts.map +1 -1
- package/lib/typescript/components/Divider/index.d.ts +5 -0
- package/lib/typescript/components/Divider/index.d.ts.map +1 -1
- package/lib/typescript/components/Drawer/styles.d.ts +1 -1
- package/lib/typescript/components/Form/styles.d.ts.map +1 -1
- package/lib/typescript/components/FullScreenDialog/animation.d.ts +9 -0
- package/lib/typescript/components/FullScreenDialog/animation.d.ts.map +1 -0
- package/lib/typescript/components/FullScreenDialog/index.d.ts +85 -0
- package/lib/typescript/components/FullScreenDialog/index.d.ts.map +1 -0
- package/lib/typescript/components/FullScreenDialog/styles.d.ts +6 -0
- package/lib/typescript/components/FullScreenDialog/styles.d.ts.map +1 -0
- package/lib/typescript/components/IconButton/styles.d.ts +5 -0
- package/lib/typescript/components/IconButton/styles.d.ts.map +1 -1
- package/lib/typescript/components/Input/index.d.ts +1 -1
- package/lib/typescript/components/InsuranceDisclosure/index.d.ts +1 -0
- package/lib/typescript/components/InsuranceDisclosure/index.d.ts.map +1 -1
- package/lib/typescript/components/Link/index.d.ts +2 -0
- package/lib/typescript/components/Link/index.d.ts.map +1 -1
- package/lib/typescript/components/ListItem/index.d.ts +43 -4
- package/lib/typescript/components/ListItem/index.d.ts.map +1 -1
- package/lib/typescript/components/ListItem/styles.d.ts +2 -0
- package/lib/typescript/components/ListItem/styles.d.ts.map +1 -1
- package/lib/typescript/components/ProgressSegmented/index.d.ts +10 -3
- package/lib/typescript/components/ProgressSegmented/index.d.ts.map +1 -1
- package/lib/typescript/components/Radio/index.d.ts +1 -0
- package/lib/typescript/components/Radio/index.d.ts.map +1 -1
- package/lib/typescript/components/ScopedTheme/index.d.ts +11 -3
- package/lib/typescript/components/ScopedTheme/index.d.ts.map +1 -1
- package/lib/typescript/components/Skeleton/index.d.ts +5 -1
- package/lib/typescript/components/Skeleton/index.d.ts.map +1 -1
- package/lib/typescript/components/Stack/HStack.d.ts +6 -0
- package/lib/typescript/components/Stack/HStack.d.ts.map +1 -1
- package/lib/typescript/components/Stack/VStack.d.ts +6 -0
- package/lib/typescript/components/Stack/VStack.d.ts.map +1 -1
- package/lib/typescript/components/Switch/index.d.ts +1 -1
- package/lib/typescript/components/Tabs/index.d.ts +2 -2
- package/lib/typescript/components/Textarea/index.d.ts +1 -0
- package/lib/typescript/components/Textarea/index.d.ts.map +1 -1
- package/lib/typescript/components/Tile/index.d.ts +49 -0
- package/lib/typescript/components/Tile/index.d.ts.map +1 -1
- package/lib/typescript/components/Toast/index.d.ts +15 -0
- package/lib/typescript/components/Toast/index.d.ts.map +1 -1
- package/lib/typescript/components/Toast/styles.d.ts +10 -0
- package/lib/typescript/components/Toast/styles.d.ts.map +1 -1
- package/lib/typescript/components/Toast/types.d.ts +9 -0
- package/lib/typescript/components/Toast/types.d.ts.map +1 -1
- package/lib/typescript/components/index.d.ts +1 -0
- package/lib/typescript/components/index.d.ts.map +1 -1
- package/lib/typescript/components/picker/styles.d.ts +5 -2
- package/lib/typescript/components/picker/styles.d.ts.map +1 -1
- package/lib/typescript/components/sheet-header/index.d.ts +24 -0
- package/lib/typescript/components/sheet-header/index.d.ts.map +1 -0
- package/lib/typescript/components/sheet-header/styles.d.ts +12 -0
- package/lib/typescript/components/sheet-header/styles.d.ts.map +1 -0
- package/lib/typescript/providers/toast/provider.d.ts +1 -1
- package/lib/typescript/providers/toast/provider.d.ts.map +1 -1
- package/lib/typescript/providers/toast/types.d.ts +16 -4
- package/lib/typescript/providers/toast/types.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/components/Alert/index.tsx +4 -1
- package/src/components/Avatar/index.tsx +10 -4
- package/src/components/Badge/index.tsx +34 -0
- package/src/components/Badge/styles.ts +2 -2
- package/src/components/BottomSheet/index.tsx +156 -125
- package/src/components/BottomSheet/styles.ts +1 -22
- package/src/components/Button/index.tsx +9 -1
- package/src/components/Card/index.tsx +14 -0
- package/src/components/Chip/index.tsx +1 -1
- package/src/components/CurrencyInput/index.tsx +2 -2
- package/src/components/Dialog/index.tsx +10 -0
- package/src/components/Dialog/styles.ts +1 -0
- package/src/components/Divider/index.tsx +5 -0
- package/src/components/Form/styles.ts +1 -1
- package/src/components/FullScreenDialog/animation.ts +22 -0
- package/src/components/FullScreenDialog/index.tsx +484 -0
- package/src/components/FullScreenDialog/styles.ts +23 -0
- package/src/components/IconButton/styles.ts +7 -4
- package/src/components/Input/index.tsx +1 -1
- package/src/components/InsuranceDisclosure/index.tsx +1 -0
- package/src/components/Link/index.tsx +2 -0
- package/src/components/ListItem/index.tsx +48 -4
- package/src/components/ListItem/styles.ts +7 -0
- package/src/components/ProgressSegmented/index.tsx +10 -3
- package/src/components/Radio/index.tsx +1 -0
- package/src/components/ScopedTheme/index.tsx +11 -3
- package/src/components/Skeleton/index.tsx +5 -1
- package/src/components/Stack/HStack.tsx +6 -0
- package/src/components/Stack/VStack.tsx +6 -0
- package/src/components/Switch/index.tsx +1 -1
- package/src/components/Tabs/index.tsx +2 -2
- package/src/components/Textarea/index.tsx +1 -0
- package/src/components/Tile/index.tsx +49 -0
- package/src/components/Toast/index.tsx +55 -9
- package/src/components/Toast/styles.ts +14 -3
- package/src/components/Toast/types.ts +9 -0
- package/src/components/index.ts +1 -0
- package/src/components/picker/styles.ts +6 -2
- package/src/components/sheet-header/index.tsx +148 -0
- package/src/components/sheet-header/styles.ts +28 -0
- package/src/providers/toast/provider.tsx +47 -13
- package/src/providers/toast/types.ts +16 -4
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createContext,
|
|
3
|
+
forwardRef,
|
|
4
|
+
useCallback,
|
|
5
|
+
useContext,
|
|
6
|
+
useEffect,
|
|
7
|
+
useMemo,
|
|
8
|
+
useRef,
|
|
9
|
+
useState,
|
|
10
|
+
type ReactNode,
|
|
11
|
+
type RefObject,
|
|
12
|
+
} from 'react';
|
|
13
|
+
import {
|
|
14
|
+
AccessibilityInfo,
|
|
15
|
+
Platform,
|
|
16
|
+
useWindowDimensions,
|
|
17
|
+
View,
|
|
18
|
+
type ViewProps,
|
|
19
|
+
} from 'react-native';
|
|
20
|
+
import Animated from 'react-native-reanimated';
|
|
21
|
+
import { SafeAreaInsetsContext, type EdgeInsets } from 'react-native-safe-area-context';
|
|
22
|
+
import {
|
|
23
|
+
DialogContent as DialogContentPrimitive,
|
|
24
|
+
DialogPortal as DialogPortalPrimitive,
|
|
25
|
+
DialogRoot as DialogRootPrimitive,
|
|
26
|
+
DialogTrigger as DialogTriggerPrimitive,
|
|
27
|
+
MODAL_OVERLAY_Z_INDEX,
|
|
28
|
+
Slot,
|
|
29
|
+
dataAttributes,
|
|
30
|
+
mergeDataAttributes,
|
|
31
|
+
useDialog,
|
|
32
|
+
type IDialogContentProps,
|
|
33
|
+
type IDialogRootProps,
|
|
34
|
+
type IDialogTriggerProps,
|
|
35
|
+
} from '@cdx-ui/primitives';
|
|
36
|
+
import { cn, mergeRefs } from '@cdx-ui/utils';
|
|
37
|
+
import { useCombinedAnimationDisabledState, type AnimationRootDisableAll } from '../../animation';
|
|
38
|
+
import {
|
|
39
|
+
AnimationSettingsProvider,
|
|
40
|
+
useAnimationSettings,
|
|
41
|
+
} from '../../providers/animation-settings';
|
|
42
|
+
import { BottomSheet } from '../BottomSheet';
|
|
43
|
+
import { SheetHeader, type SheetHeaderTitleRef } from '../sheet-header';
|
|
44
|
+
import { WEB_TRANSITION_SAFETY_MS, getFullScreenDialogSurfaceMotion } from './animation';
|
|
45
|
+
import {
|
|
46
|
+
fullScreenDialogContentVariants,
|
|
47
|
+
fullScreenDialogHeaderVariants,
|
|
48
|
+
fullScreenDialogPopupVariants,
|
|
49
|
+
fullScreenDialogPortalVariants,
|
|
50
|
+
} from './styles';
|
|
51
|
+
|
|
52
|
+
const IS_WEB = Platform.OS === 'web';
|
|
53
|
+
|
|
54
|
+
type FullScreenDialogDataState = 'entering' | 'open' | 'closed';
|
|
55
|
+
|
|
56
|
+
const ZERO_INSETS: EdgeInsets = { top: 0, bottom: 0, left: 0, right: 0 };
|
|
57
|
+
|
|
58
|
+
function useSafeInsets(): EdgeInsets {
|
|
59
|
+
return useContext(SafeAreaInsetsContext) ?? ZERO_INSETS;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// The header's last title / back state for the current open session. Lives on the
|
|
63
|
+
// popup so a header that remounts per step still counts as a step change.
|
|
64
|
+
const StepFocusKeyContext = createContext<RefObject<string | undefined> | null>(null);
|
|
65
|
+
|
|
66
|
+
function focusTitle(node: SheetHeaderTitleRef | null): void {
|
|
67
|
+
if (!node) return;
|
|
68
|
+
if (IS_WEB) {
|
|
69
|
+
(node as unknown as HTMLElement).focus?.();
|
|
70
|
+
} else {
|
|
71
|
+
AccessibilityInfo.sendAccessibilityEvent(node, 'focus');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Moves focus to the header title when `stepKey` changes during an open session.
|
|
77
|
+
* The first key after open is only recorded, so `FocusScope` auto-focus decides
|
|
78
|
+
* initial focus. An undefined key (custom header content) never moves focus.
|
|
79
|
+
*/
|
|
80
|
+
function useStepChangeFocus(
|
|
81
|
+
stepKey: string | undefined,
|
|
82
|
+
titleRef: RefObject<SheetHeaderTitleRef | null>,
|
|
83
|
+
): void {
|
|
84
|
+
const lastKeyRef = useContext(StepFocusKeyContext);
|
|
85
|
+
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
if (!lastKeyRef || stepKey === undefined) return;
|
|
88
|
+
const previousKey = lastKeyRef.current;
|
|
89
|
+
lastKeyRef.current = stepKey;
|
|
90
|
+
if (previousKey === undefined || previousKey === stepKey) return;
|
|
91
|
+
|
|
92
|
+
if (IS_WEB) {
|
|
93
|
+
focusTitle(titleRef.current);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
// Native: wait a frame so the new title is in the accessibility tree.
|
|
97
|
+
let fired = false;
|
|
98
|
+
const frame = requestAnimationFrame(() => {
|
|
99
|
+
fired = true;
|
|
100
|
+
focusTitle(titleRef.current);
|
|
101
|
+
});
|
|
102
|
+
return () => {
|
|
103
|
+
if (fired) return;
|
|
104
|
+
cancelAnimationFrame(frame);
|
|
105
|
+
// Re-arm the pending move for the next run (StrictMode remounts, rapid changes).
|
|
106
|
+
lastKeyRef.current = previousKey;
|
|
107
|
+
};
|
|
108
|
+
}, [lastKeyRef, stepKey, titleRef]);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// =============================================================================
|
|
112
|
+
// STYLED ROOT
|
|
113
|
+
// =============================================================================
|
|
114
|
+
|
|
115
|
+
export interface FullScreenDialogRootProps
|
|
116
|
+
extends
|
|
117
|
+
Omit<ViewProps, 'children' | 'role'>,
|
|
118
|
+
Omit<
|
|
119
|
+
IDialogRootProps,
|
|
120
|
+
| 'closeOnBackdropPress'
|
|
121
|
+
| 'role'
|
|
122
|
+
| 'onOpenChange'
|
|
123
|
+
| 'defaultOpen'
|
|
124
|
+
| 'closeOnEscKey'
|
|
125
|
+
| 'children'
|
|
126
|
+
> {
|
|
127
|
+
/**
|
|
128
|
+
* Called with the next open state. Fires `false` for the header close button, Escape, and
|
|
129
|
+
* Android back while the header has no `onBack`.
|
|
130
|
+
*/
|
|
131
|
+
onOpenChange?: (open: boolean) => void;
|
|
132
|
+
/** Initial open state when uncontrolled. @default false */
|
|
133
|
+
defaultOpen?: boolean;
|
|
134
|
+
/**
|
|
135
|
+
* Close when Escape is pressed. Web only. Escape never steps back; Android back is routed
|
|
136
|
+
* by the header's `onBack` independently of this prop.
|
|
137
|
+
* @default true
|
|
138
|
+
*/
|
|
139
|
+
closeOnEscKey?: boolean;
|
|
140
|
+
/** `FullScreenDialog.Trigger` and `FullScreenDialog.Popup`. */
|
|
141
|
+
children: ReactNode;
|
|
142
|
+
/** Classes for the root wrapper around the trigger. */
|
|
143
|
+
className?: string;
|
|
144
|
+
/** Pass `'disable-all'` to turn off the popup's enter and exit motion. */
|
|
145
|
+
animation?: AnimationRootDisableAll;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const FullScreenDialogRoot = forwardRef<View, FullScreenDialogRootProps>(
|
|
149
|
+
({ animation, className, children, style, ...props }, ref) => {
|
|
150
|
+
const isAllAnimationsDisabled = useCombinedAnimationDisabledState(animation);
|
|
151
|
+
const animationSettingsContextValue = useMemo(
|
|
152
|
+
() => ({ isAllAnimationsDisabled }),
|
|
153
|
+
[isAllAnimationsDisabled],
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
return (
|
|
157
|
+
<AnimationSettingsProvider value={animationSettingsContextValue}>
|
|
158
|
+
<DialogRootPrimitive
|
|
159
|
+
ref={ref}
|
|
160
|
+
className={className}
|
|
161
|
+
style={style}
|
|
162
|
+
{...props}
|
|
163
|
+
{...mergeDataAttributes(props, dataAttributes({ slot: 'full-screen-dialog' }))}
|
|
164
|
+
>
|
|
165
|
+
{children}
|
|
166
|
+
</DialogRootPrimitive>
|
|
167
|
+
</AnimationSettingsProvider>
|
|
168
|
+
);
|
|
169
|
+
},
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
FullScreenDialogRoot.displayName = 'FullScreenDialog';
|
|
173
|
+
|
|
174
|
+
// =============================================================================
|
|
175
|
+
// STYLED TRIGGER
|
|
176
|
+
// =============================================================================
|
|
177
|
+
|
|
178
|
+
export interface FullScreenDialogTriggerProps extends Omit<IDialogTriggerProps, 'asChild'> {
|
|
179
|
+
/** Render the single child as the trigger instead of a `Pressable`. */
|
|
180
|
+
asChild?: boolean;
|
|
181
|
+
/** Classes for the trigger. */
|
|
182
|
+
className?: string;
|
|
183
|
+
/** Content that opens the dialog when pressed. */
|
|
184
|
+
children?: ReactNode;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const FullScreenDialogTrigger = forwardRef<View, FullScreenDialogTriggerProps>(
|
|
188
|
+
({ className, children, style, ...props }, ref) => (
|
|
189
|
+
<DialogTriggerPrimitive
|
|
190
|
+
ref={ref}
|
|
191
|
+
className={className}
|
|
192
|
+
style={style}
|
|
193
|
+
{...props}
|
|
194
|
+
{...mergeDataAttributes(props, dataAttributes({ slot: 'full-screen-dialog-trigger' }))}
|
|
195
|
+
>
|
|
196
|
+
{children}
|
|
197
|
+
</DialogTriggerPrimitive>
|
|
198
|
+
),
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
FullScreenDialogTrigger.displayName = 'FullScreenDialog.Trigger';
|
|
202
|
+
|
|
203
|
+
// =============================================================================
|
|
204
|
+
// STYLED POPUP (portal + full-viewport page in the app window)
|
|
205
|
+
// =============================================================================
|
|
206
|
+
|
|
207
|
+
export interface FullScreenDialogPopupProps extends Omit<
|
|
208
|
+
IDialogContentProps,
|
|
209
|
+
'asChild' | 'forceMount' | 'avoidKeyboard' | 'keyboardVerticalOffset'
|
|
210
|
+
> {
|
|
211
|
+
/** Classes for the page surface, merged after the defaults. */
|
|
212
|
+
className?: string;
|
|
213
|
+
/** `FullScreenDialog.Header` and `FullScreenDialog.Content`. Bottom sheets declared here present above the page. */
|
|
214
|
+
children?: ReactNode;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const FullScreenDialogPopup = forwardRef<View, FullScreenDialogPopupProps>(
|
|
218
|
+
({ className, children, style, ...props }, ref) => {
|
|
219
|
+
const { open } = useDialog();
|
|
220
|
+
const animationSettings = useAnimationSettings();
|
|
221
|
+
const { isAllAnimationsDisabled } = animationSettings;
|
|
222
|
+
const { height: windowHeight } = useWindowDimensions();
|
|
223
|
+
const surfaceMotion = useMemo(
|
|
224
|
+
() => getFullScreenDialogSurfaceMotion(windowHeight),
|
|
225
|
+
[windowHeight],
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
// Web enter/exit lifecycle mirrors `Dialog.Popup`: mount → 'entering' →
|
|
229
|
+
// (rAF) → 'open' → (close) → 'closed' → (transitionend) → unmount. The exit
|
|
230
|
+
// flag is derived during render so the popup stays mounted in the same
|
|
231
|
+
// render that sees `open` go false.
|
|
232
|
+
const popupRef = useRef<View | null>(null);
|
|
233
|
+
const prevOpenRef = useRef(open);
|
|
234
|
+
const [isExiting, setIsExiting] = useState(false);
|
|
235
|
+
const [webState, setWebState] = useState<FullScreenDialogDataState>(
|
|
236
|
+
open ? 'entering' : 'closed',
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
if (IS_WEB && prevOpenRef.current && !open && !isExiting) {
|
|
240
|
+
setIsExiting(true);
|
|
241
|
+
setWebState('closed');
|
|
242
|
+
}
|
|
243
|
+
if (IS_WEB && !prevOpenRef.current && open) {
|
|
244
|
+
setIsExiting(false);
|
|
245
|
+
setWebState('entering');
|
|
246
|
+
}
|
|
247
|
+
prevOpenRef.current = open;
|
|
248
|
+
|
|
249
|
+
const stepFocusKeyRef = useRef<string | undefined>(undefined);
|
|
250
|
+
useEffect(() => {
|
|
251
|
+
if (!open) stepFocusKeyRef.current = undefined;
|
|
252
|
+
}, [open]);
|
|
253
|
+
|
|
254
|
+
useEffect(() => {
|
|
255
|
+
if (!IS_WEB || !open) return;
|
|
256
|
+
const raf = requestAnimationFrame(() => {
|
|
257
|
+
setWebState('open');
|
|
258
|
+
});
|
|
259
|
+
return () => cancelAnimationFrame(raf);
|
|
260
|
+
}, [open]);
|
|
261
|
+
|
|
262
|
+
useEffect(() => {
|
|
263
|
+
if (!IS_WEB || !isExiting) return;
|
|
264
|
+
|
|
265
|
+
const node = popupRef.current as unknown as HTMLElement | null;
|
|
266
|
+
let done = false;
|
|
267
|
+
|
|
268
|
+
const finish = () => {
|
|
269
|
+
if (done) return;
|
|
270
|
+
done = true;
|
|
271
|
+
setIsExiting(false);
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const safetyTimer = setTimeout(finish, WEB_TRANSITION_SAFETY_MS);
|
|
275
|
+
|
|
276
|
+
if (!node || typeof node.addEventListener !== 'function') {
|
|
277
|
+
return () => {
|
|
278
|
+
clearTimeout(safetyTimer);
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const handler = (event: Event) => {
|
|
283
|
+
if (event.target !== node) return;
|
|
284
|
+
const propertyName = (event as unknown as { propertyName?: string }).propertyName ?? '';
|
|
285
|
+
if (propertyName === 'opacity' || propertyName === 'transform') {
|
|
286
|
+
finish();
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
node.addEventListener('transitionend', handler);
|
|
290
|
+
|
|
291
|
+
return () => {
|
|
292
|
+
clearTimeout(safetyTimer);
|
|
293
|
+
node.removeEventListener('transitionend', handler);
|
|
294
|
+
};
|
|
295
|
+
}, [isExiting]);
|
|
296
|
+
|
|
297
|
+
const shouldMount = IS_WEB ? open || isExiting : open;
|
|
298
|
+
|
|
299
|
+
if (!shouldMount) {
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const isNativeMotion = !IS_WEB && !isAllAnimationsDisabled;
|
|
304
|
+
|
|
305
|
+
return (
|
|
306
|
+
<DialogPortalPrimitive forceMount>
|
|
307
|
+
<AnimationSettingsProvider value={animationSettings}>
|
|
308
|
+
{/*
|
|
309
|
+
No `FullWindowOverlay`: the page lives in the app window so native
|
|
310
|
+
modals (Menu, Select, picker popover) present above it. Without the
|
|
311
|
+
overlay window, VoiceOver containment comes from this wrapper, a
|
|
312
|
+
native sibling of the app tree under the root `PortalHost`.
|
|
313
|
+
*/}
|
|
314
|
+
<Animated.View
|
|
315
|
+
className={fullScreenDialogPortalVariants()}
|
|
316
|
+
style={{ zIndex: MODAL_OVERLAY_Z_INDEX }}
|
|
317
|
+
entering={isNativeMotion ? surfaceMotion.entering : undefined}
|
|
318
|
+
exiting={isNativeMotion ? surfaceMotion.exiting : undefined}
|
|
319
|
+
{...(IS_WEB ? undefined : { accessibilityViewIsModal: true })}
|
|
320
|
+
>
|
|
321
|
+
<DialogContentPrimitive
|
|
322
|
+
ref={mergeRefs(ref, popupRef)}
|
|
323
|
+
forceMount
|
|
324
|
+
className={cn(fullScreenDialogPopupVariants(), className)}
|
|
325
|
+
style={style}
|
|
326
|
+
{...props}
|
|
327
|
+
{...mergeDataAttributes(
|
|
328
|
+
props,
|
|
329
|
+
dataAttributes({ slot: 'full-screen-dialog-popup' }),
|
|
330
|
+
IS_WEB ? dataAttributes({ state: webState }) : undefined,
|
|
331
|
+
)}
|
|
332
|
+
>
|
|
333
|
+
<StepFocusKeyContext.Provider value={stepFocusKeyRef}>
|
|
334
|
+
<BottomSheet.Provider>{children}</BottomSheet.Provider>
|
|
335
|
+
</StepFocusKeyContext.Provider>
|
|
336
|
+
</DialogContentPrimitive>
|
|
337
|
+
</Animated.View>
|
|
338
|
+
</AnimationSettingsProvider>
|
|
339
|
+
</DialogPortalPrimitive>
|
|
340
|
+
);
|
|
341
|
+
},
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
FullScreenDialogPopup.displayName = 'FullScreenDialog.Popup';
|
|
345
|
+
|
|
346
|
+
// =============================================================================
|
|
347
|
+
// STYLED HEADER (shared back / title / close row)
|
|
348
|
+
// =============================================================================
|
|
349
|
+
|
|
350
|
+
export interface FullScreenDialogHeaderProps {
|
|
351
|
+
/** Title text. Labels the dialog. Ignored when `children` is set. */
|
|
352
|
+
title?: string;
|
|
353
|
+
/** Secondary line under the title. Has no effect without `title`, or when `children` is set. */
|
|
354
|
+
subtitle?: string;
|
|
355
|
+
/**
|
|
356
|
+
* Custom header content. Replaces `title` and `subtitle`. The consumer then owns the
|
|
357
|
+
* dialog's accessible name and focus on step change: focus doesn't move to the header.
|
|
358
|
+
*/
|
|
359
|
+
children?: ReactNode;
|
|
360
|
+
/** Classes for the header region, which includes the top safe-area padding. */
|
|
361
|
+
className?: string;
|
|
362
|
+
/** Called by the close button. @default Closes the dialog (`onOpenChange(false)`). */
|
|
363
|
+
onClose?: () => void;
|
|
364
|
+
/** Accessible name for the close button. @default Close */
|
|
365
|
+
closeAccessibilityLabel?: string;
|
|
366
|
+
/**
|
|
367
|
+
* Shows the back button when set. Android system back calls it too, instead of
|
|
368
|
+
* closing the dialog. Escape on web always closes.
|
|
369
|
+
*/
|
|
370
|
+
onBack?: () => void;
|
|
371
|
+
/** Accessible name for the back button. @default Go back */
|
|
372
|
+
backAccessibilityLabel?: string;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const FullScreenDialogHeader = forwardRef<View, FullScreenDialogHeaderProps>(
|
|
376
|
+
(
|
|
377
|
+
{
|
|
378
|
+
title,
|
|
379
|
+
subtitle,
|
|
380
|
+
children,
|
|
381
|
+
className,
|
|
382
|
+
onClose,
|
|
383
|
+
closeAccessibilityLabel,
|
|
384
|
+
onBack,
|
|
385
|
+
backAccessibilityLabel,
|
|
386
|
+
},
|
|
387
|
+
ref,
|
|
388
|
+
) => {
|
|
389
|
+
const { nativeID, onOpenChange, backHandlerRef } = useDialog();
|
|
390
|
+
const insets = useSafeInsets();
|
|
391
|
+
const titleRef = useRef<SheetHeaderTitleRef | null>(null);
|
|
392
|
+
|
|
393
|
+
const hasTitle = children == null && title !== undefined && title !== '';
|
|
394
|
+
useStepChangeFocus(hasTitle ? `${onBack ? 'back' : 'root'}:${title}` : undefined, titleRef);
|
|
395
|
+
|
|
396
|
+
useEffect(() => {
|
|
397
|
+
backHandlerRef.current = onBack;
|
|
398
|
+
return () => {
|
|
399
|
+
backHandlerRef.current = undefined;
|
|
400
|
+
};
|
|
401
|
+
}, [backHandlerRef, onBack]);
|
|
402
|
+
|
|
403
|
+
const handleClose = useCallback(() => {
|
|
404
|
+
onOpenChange(false);
|
|
405
|
+
}, [onOpenChange]);
|
|
406
|
+
|
|
407
|
+
return (
|
|
408
|
+
<View
|
|
409
|
+
ref={ref}
|
|
410
|
+
className={cn(fullScreenDialogHeaderVariants(), className)}
|
|
411
|
+
style={{ paddingTop: insets.top }}
|
|
412
|
+
{...dataAttributes({ slot: 'full-screen-dialog-header' })}
|
|
413
|
+
>
|
|
414
|
+
<SheetHeader
|
|
415
|
+
title={title}
|
|
416
|
+
subtitle={subtitle}
|
|
417
|
+
titleNativeID={`${nativeID}-title`}
|
|
418
|
+
titleRef={titleRef}
|
|
419
|
+
titleFocusable
|
|
420
|
+
onClose={onClose ?? handleClose}
|
|
421
|
+
closeAccessibilityLabel={closeAccessibilityLabel}
|
|
422
|
+
onBack={onBack}
|
|
423
|
+
backAccessibilityLabel={backAccessibilityLabel}
|
|
424
|
+
>
|
|
425
|
+
{children}
|
|
426
|
+
</SheetHeader>
|
|
427
|
+
</View>
|
|
428
|
+
);
|
|
429
|
+
},
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
FullScreenDialogHeader.displayName = 'FullScreenDialog.Header';
|
|
433
|
+
|
|
434
|
+
// =============================================================================
|
|
435
|
+
// STYLED CONTENT
|
|
436
|
+
// =============================================================================
|
|
437
|
+
|
|
438
|
+
export interface FullScreenDialogContentProps extends ViewProps {
|
|
439
|
+
/** Render the single child as the content region instead of a `View`. */
|
|
440
|
+
asChild?: boolean;
|
|
441
|
+
/** Classes for the content region, merged after the defaults. */
|
|
442
|
+
className?: string;
|
|
443
|
+
/** Page body. Usually a `ScrollView`. */
|
|
444
|
+
children?: ReactNode;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const FullScreenDialogContent = forwardRef<View, FullScreenDialogContentProps>(
|
|
448
|
+
({ asChild, className, children, style, ...props }, ref) => {
|
|
449
|
+
const insets = useSafeInsets();
|
|
450
|
+
const Comp = asChild ? Slot : View;
|
|
451
|
+
|
|
452
|
+
return (
|
|
453
|
+
<Comp
|
|
454
|
+
ref={ref}
|
|
455
|
+
className={cn(fullScreenDialogContentVariants(), className)}
|
|
456
|
+
style={[{ paddingBottom: insets.bottom }, style]}
|
|
457
|
+
{...props}
|
|
458
|
+
{...mergeDataAttributes(props, dataAttributes({ slot: 'full-screen-dialog-content' }))}
|
|
459
|
+
>
|
|
460
|
+
{children}
|
|
461
|
+
</Comp>
|
|
462
|
+
);
|
|
463
|
+
},
|
|
464
|
+
);
|
|
465
|
+
|
|
466
|
+
FullScreenDialogContent.displayName = 'FullScreenDialog.Content';
|
|
467
|
+
|
|
468
|
+
// =============================================================================
|
|
469
|
+
// COMPOUND COMPONENT
|
|
470
|
+
// =============================================================================
|
|
471
|
+
|
|
472
|
+
type FullScreenDialogCompoundComponent = typeof FullScreenDialogRoot & {
|
|
473
|
+
Trigger: typeof FullScreenDialogTrigger;
|
|
474
|
+
Popup: typeof FullScreenDialogPopup;
|
|
475
|
+
Header: typeof FullScreenDialogHeader;
|
|
476
|
+
Content: typeof FullScreenDialogContent;
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
export const FullScreenDialog = Object.assign(FullScreenDialogRoot, {
|
|
480
|
+
Trigger: FullScreenDialogTrigger,
|
|
481
|
+
Popup: FullScreenDialogPopup,
|
|
482
|
+
Header: FullScreenDialogHeader,
|
|
483
|
+
Content: FullScreenDialogContent,
|
|
484
|
+
}) as FullScreenDialogCompoundComponent;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { cva } from '@cdx-ui/utils';
|
|
2
|
+
|
|
3
|
+
export const fullScreenDialogPortalVariants = cva([
|
|
4
|
+
'absolute inset-0',
|
|
5
|
+
'overflow-hidden',
|
|
6
|
+
'web:fixed',
|
|
7
|
+
]);
|
|
8
|
+
|
|
9
|
+
export const fullScreenDialogPopupVariants = cva([
|
|
10
|
+
'flex-1 flex-col',
|
|
11
|
+
'bg-surface-primary',
|
|
12
|
+
'overflow-hidden',
|
|
13
|
+
'web:transition-transform web:duration-300 web:ease-[cubic-bezier(0.32,0.72,0,1)]',
|
|
14
|
+
'web:data-[state=entering]:translate-y-full',
|
|
15
|
+
'web:data-[state=open]:translate-y-0',
|
|
16
|
+
'web:data-[state=closed]:translate-y-full web:data-[state=closed]:duration-[220ms]',
|
|
17
|
+
'web:motion-reduce:transition-none web:motion-reduce:duration-0',
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
/** Paints the surface behind the status bar inset above the header row. */
|
|
21
|
+
export const fullScreenDialogHeaderVariants = cva(['shrink-0', 'bg-surface-primary']);
|
|
22
|
+
|
|
23
|
+
export const fullScreenDialogContentVariants = cva(['flex-1 min-h-0']);
|
|
@@ -2,6 +2,12 @@ import { Platform } from 'react-native';
|
|
|
2
2
|
import { cva, type VariantProps } from '@cdx-ui/utils';
|
|
3
3
|
import { DISABLED_CURSOR, TRANSITION_COLORS } from '../../styles/constants';
|
|
4
4
|
|
|
5
|
+
/** Box classes per size. Shared with layout placeholders that must match an `IconButton`'s footprint. */
|
|
6
|
+
export const iconButtonSizeClassNames = {
|
|
7
|
+
default: 'h-11 w-11 min-w-11 shrink-0 p-2.5',
|
|
8
|
+
small: 'h-8 w-8 min-w-8 shrink-0 p-1.5',
|
|
9
|
+
} as const;
|
|
10
|
+
|
|
5
11
|
export const iconButtonRootVariants = cva(
|
|
6
12
|
[
|
|
7
13
|
'flex items-center justify-center',
|
|
@@ -24,10 +30,7 @@ export const iconButtonRootVariants = cva(
|
|
|
24
30
|
action: [],
|
|
25
31
|
danger: [],
|
|
26
32
|
},
|
|
27
|
-
size:
|
|
28
|
-
default: 'h-11 w-11 min-w-11 shrink-0 p-2.5',
|
|
29
|
-
small: 'h-8 w-8 min-w-8 shrink-0 p-1.5',
|
|
30
|
-
},
|
|
33
|
+
size: iconButtonSizeClassNames,
|
|
31
34
|
},
|
|
32
35
|
compoundVariants: [
|
|
33
36
|
// ── solid × color ───────────────────────────────────────────────────
|
|
@@ -21,6 +21,7 @@ type InsuranceDisclosureSharedProps = Omit<HStackProps, 'children'> & {
|
|
|
21
21
|
* Pass `framed` to restore chrome; padding is now `px-3 py-2`.
|
|
22
22
|
*/
|
|
23
23
|
framed?: boolean;
|
|
24
|
+
/** Disclosure copy rendered beside the logo. */
|
|
24
25
|
label?: string;
|
|
25
26
|
className?: string;
|
|
26
27
|
};
|
|
@@ -24,6 +24,7 @@ export { LinkProvider, type LinkConfig, type LinkAction } from '@cdx-ui/primitiv
|
|
|
24
24
|
// =============================================================================
|
|
25
25
|
|
|
26
26
|
export interface LinkProps extends ILinkProps {
|
|
27
|
+
/** Renders the consumer's child element in place of the default pressable host. */
|
|
27
28
|
asChild?: boolean;
|
|
28
29
|
className?: string;
|
|
29
30
|
children?: ReactNode;
|
|
@@ -52,6 +53,7 @@ LinkRoot.displayName = 'Link';
|
|
|
52
53
|
// =============================================================================
|
|
53
54
|
|
|
54
55
|
export interface LinkLabelProps extends TextProps {
|
|
56
|
+
/** Renders the consumer's child element in place of the default `Text` host. */
|
|
55
57
|
asChild?: boolean;
|
|
56
58
|
className?: string;
|
|
57
59
|
children?: ReactNode;
|