@ankhorage/surface 4.2.1 → 4.4.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/CHANGELOG.md +12 -0
- package/README.md +4 -4
- package/dist/components/tabs/Tabs.js +2 -2
- package/dist/components/tabs/Tabs.js.map +1 -1
- package/dist/components/tabs/context.js +1 -1
- package/dist/components/tabs/context.js.map +1 -1
- package/dist/context/FontContext.d.ts.map +1 -1
- package/dist/context/FontContext.js +3 -3
- package/dist/context/FontContext.js.map +1 -1
- package/dist/context/TranslationContext.d.ts.map +1 -1
- package/dist/context/TranslationContext.js +3 -4
- package/dist/context/TranslationContext.js.map +1 -1
- package/dist/core/responsive/ResponsiveProvider.d.ts.map +1 -1
- package/dist/core/responsive/ResponsiveProvider.js +3 -3
- package/dist/core/responsive/ResponsiveProvider.js.map +1 -1
- package/dist/examples/DocsExamples.js +1 -1
- package/dist/examples/DocsExamples.js.map +1 -1
- package/dist/features/accordion/adapters/inbound/Accordion.d.ts +5 -0
- package/dist/features/accordion/adapters/inbound/Accordion.d.ts.map +1 -0
- package/dist/features/accordion/adapters/inbound/Accordion.js +46 -0
- package/dist/features/accordion/adapters/inbound/Accordion.js.map +1 -0
- package/dist/features/accordion/adapters/inbound/AccordionContent.d.ts +5 -0
- package/dist/features/accordion/adapters/inbound/AccordionContent.d.ts.map +1 -0
- package/dist/features/accordion/adapters/inbound/AccordionContent.js +16 -0
- package/dist/features/accordion/adapters/inbound/AccordionContent.js.map +1 -0
- package/dist/features/accordion/adapters/inbound/AccordionContext.d.ts +4 -0
- package/dist/features/accordion/adapters/inbound/AccordionContext.d.ts.map +1 -0
- package/dist/features/accordion/adapters/inbound/AccordionContext.js +3 -0
- package/dist/features/accordion/adapters/inbound/AccordionContext.js.map +1 -0
- package/dist/features/accordion/adapters/inbound/AccordionItem.d.ts +5 -0
- package/dist/features/accordion/adapters/inbound/AccordionItem.d.ts.map +1 -0
- package/dist/features/accordion/adapters/inbound/AccordionItem.js +28 -0
- package/dist/features/accordion/adapters/inbound/AccordionItem.js.map +1 -0
- package/dist/features/accordion/adapters/inbound/AccordionItemContext.d.ts +4 -0
- package/dist/features/accordion/adapters/inbound/AccordionItemContext.d.ts.map +1 -0
- package/dist/features/accordion/adapters/inbound/AccordionItemContext.js +3 -0
- package/dist/features/accordion/adapters/inbound/AccordionItemContext.js.map +1 -0
- package/dist/features/accordion/adapters/inbound/AccordionTrigger.d.ts +5 -0
- package/dist/features/accordion/adapters/inbound/AccordionTrigger.d.ts.map +1 -0
- package/dist/features/accordion/adapters/inbound/AccordionTrigger.js +17 -0
- package/dist/features/accordion/adapters/inbound/AccordionTrigger.js.map +1 -0
- package/dist/features/accordion/public.d.ts +6 -0
- package/dist/features/accordion/public.d.ts.map +1 -0
- package/dist/features/accordion/public.js +5 -0
- package/dist/features/accordion/public.js.map +1 -0
- package/dist/features/accordion/utils/resolveAccordionToggle.d.ts +11 -0
- package/dist/features/accordion/utils/resolveAccordionToggle.d.ts.map +1 -0
- package/dist/features/accordion/utils/resolveAccordionToggle.js +13 -0
- package/dist/features/accordion/utils/resolveAccordionToggle.js.map +1 -0
- package/dist/features/bottom-sheet/BottomSheetProvider.js +2 -2
- package/dist/features/bottom-sheet/BottomSheetProvider.js.map +1 -1
- package/dist/features/bottom-sheet/useBottomSheet.js +1 -1
- package/dist/features/bottom-sheet/useBottomSheet.js.map +1 -1
- package/dist/features/popover/adapters/inbound/Popover.d.ts +5 -0
- package/dist/features/popover/adapters/inbound/Popover.d.ts.map +1 -0
- package/dist/features/popover/adapters/inbound/Popover.js +31 -0
- package/dist/features/popover/adapters/inbound/Popover.js.map +1 -0
- package/dist/features/popover/adapters/inbound/PopoverOverlay.d.ts +17 -0
- package/dist/features/popover/adapters/inbound/PopoverOverlay.d.ts.map +1 -0
- package/dist/features/popover/adapters/inbound/PopoverOverlay.js +34 -0
- package/dist/features/popover/adapters/inbound/PopoverOverlay.js.map +1 -0
- package/dist/features/popover/adapters/inbound/usePopoverLayout.d.ts +21 -0
- package/dist/features/popover/adapters/inbound/usePopoverLayout.d.ts.map +1 -0
- package/dist/features/popover/adapters/inbound/usePopoverLayout.js +33 -0
- package/dist/features/popover/adapters/inbound/usePopoverLayout.js.map +1 -0
- package/dist/features/popover/public.d.ts +3 -0
- package/dist/features/popover/public.d.ts.map +1 -0
- package/dist/features/popover/public.js +2 -0
- package/dist/features/popover/public.js.map +1 -0
- package/dist/features/popover/utils/resolvePopoverPosition.d.ts +25 -0
- package/dist/features/popover/utils/resolvePopoverPosition.d.ts.map +1 -0
- package/dist/features/popover/utils/resolvePopoverPosition.js +23 -0
- package/dist/features/popover/utils/resolvePopoverPosition.js.map +1 -0
- package/dist/{components/toast → features/toast/adapters/inbound}/Toast.d.ts +2 -1
- package/dist/features/toast/adapters/inbound/Toast.d.ts.map +1 -0
- package/dist/{components/toast → features/toast/adapters/inbound}/Toast.js +37 -32
- package/dist/features/toast/adapters/inbound/Toast.js.map +1 -0
- package/dist/features/toast/adapters/inbound/ToastProvider.d.ts +5 -0
- package/dist/features/toast/adapters/inbound/ToastProvider.d.ts.map +1 -0
- package/dist/features/toast/adapters/inbound/ToastProvider.js +29 -0
- package/dist/features/toast/adapters/inbound/ToastProvider.js.map +1 -0
- package/dist/features/toast/adapters/inbound/useToast.d.ts +4 -0
- package/dist/features/toast/adapters/inbound/useToast.d.ts.map +1 -0
- package/dist/features/toast/adapters/inbound/useToast.js +11 -0
- package/dist/features/toast/adapters/inbound/useToast.js.map +1 -0
- package/dist/features/toast/composition/ToastContext.d.ts +4 -0
- package/dist/features/toast/composition/ToastContext.d.ts.map +1 -0
- package/dist/features/toast/composition/ToastContext.js +4 -0
- package/dist/features/toast/composition/ToastContext.js.map +1 -0
- package/dist/features/toast/composition/useToastRuntime.d.ts +11 -0
- package/dist/features/toast/composition/useToastRuntime.d.ts.map +1 -0
- package/dist/features/toast/composition/useToastRuntime.js +50 -0
- package/dist/features/toast/composition/useToastRuntime.js.map +1 -0
- package/dist/features/toast/public.d.ts +5 -0
- package/dist/features/toast/public.d.ts.map +1 -0
- package/dist/features/toast/public.js +4 -0
- package/dist/features/toast/public.js.map +1 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/overlay/OverlayProvider.d.ts +1 -0
- package/dist/internal/overlay/OverlayProvider.d.ts.map +1 -1
- package/dist/internal/overlay/OverlayProvider.js +9 -7
- package/dist/internal/overlay/OverlayProvider.js.map +1 -1
- package/dist/internal/overlay/useOverlayStack.js +1 -1
- package/dist/internal/overlay/useOverlayStack.js.map +1 -1
- package/dist/internal/resolvers/resolveOverlayZIndex.d.ts +1 -1
- package/dist/internal/resolvers/resolveOverlayZIndex.d.ts.map +1 -1
- package/dist/internal/resolvers/resolveOverlayZIndex.js +1 -0
- package/dist/internal/resolvers/resolveOverlayZIndex.js.map +1 -1
- package/dist/layout/AppBar.js +1 -1
- package/dist/layout/AppBar.js.map +1 -1
- package/dist/theme/ThemeContext.d.ts.map +1 -1
- package/dist/theme/ThemeContext.js +4 -5
- package/dist/theme/ThemeContext.js.map +1 -1
- package/dist/types/accordion.d.ts +54 -0
- package/dist/types/accordion.d.ts.map +1 -0
- package/dist/types/accordion.js +2 -0
- package/dist/types/accordion.js.map +1 -0
- package/dist/types/popover.d.ts +21 -0
- package/dist/types/popover.d.ts.map +1 -0
- package/dist/types/popover.js +2 -0
- package/dist/types/popover.js.map +1 -0
- package/dist/{components/toast/types.d.ts → types/toast.d.ts} +11 -3
- package/dist/types/toast.d.ts.map +1 -0
- package/dist/types/toast.js +2 -0
- package/dist/types/toast.js.map +1 -0
- package/package.json +1 -1
- package/src/components/tabs/Tabs.tsx +2 -2
- package/src/components/tabs/context.tsx +1 -1
- package/src/context/FontContext.tsx +3 -3
- package/src/context/TranslationContext.tsx +3 -4
- package/src/core/responsive/ResponsiveProvider.tsx +3 -3
- package/src/examples/DocsExamples.tsx +1 -1
- package/src/features/accordion/adapters/inbound/Accordion.tsx +83 -0
- package/src/features/accordion/adapters/inbound/AccordionContent.tsx +30 -0
- package/src/features/accordion/adapters/inbound/AccordionContext.ts +5 -0
- package/src/features/accordion/adapters/inbound/AccordionItem.tsx +42 -0
- package/src/features/accordion/adapters/inbound/AccordionItemContext.ts +5 -0
- package/src/features/accordion/adapters/inbound/AccordionTrigger.tsx +34 -0
- package/src/features/accordion/public.ts +13 -0
- package/src/features/accordion/utils/resolveAccordionToggle.test.ts +57 -0
- package/src/features/accordion/utils/resolveAccordionToggle.ts +28 -0
- package/src/features/bottom-sheet/BottomSheetProvider.tsx +2 -2
- package/src/features/bottom-sheet/useBottomSheet.ts +1 -1
- package/src/features/popover/adapters/inbound/Popover.tsx +49 -0
- package/src/features/popover/adapters/inbound/PopoverOverlay.tsx +72 -0
- package/src/features/popover/adapters/inbound/usePopoverLayout.ts +51 -0
- package/src/features/popover/public.ts +2 -0
- package/src/features/popover/utils/resolvePopoverPosition.test.ts +31 -0
- package/src/features/popover/utils/resolvePopoverPosition.ts +40 -0
- package/src/{components/toast → features/toast/adapters/inbound}/Toast.tsx +58 -54
- package/src/features/toast/adapters/inbound/ToastProvider.tsx +47 -0
- package/src/features/toast/adapters/inbound/useToast.test.tsx +18 -0
- package/src/features/toast/adapters/inbound/useToast.ts +15 -0
- package/src/features/toast/composition/ToastContext.ts +6 -0
- package/src/features/toast/composition/useToastRuntime.ts +76 -0
- package/src/features/toast/public.ts +10 -0
- package/src/index.test.ts +3 -1
- package/src/index.ts +29 -2
- package/src/internal/overlay/OverlayProvider.tsx +10 -8
- package/src/internal/overlay/useOverlayStack.ts +1 -1
- package/src/internal/resolvers/resolveOverlayZIndex.test.ts +2 -1
- package/src/internal/resolvers/resolveOverlayZIndex.ts +2 -1
- package/src/layout/AppBar.tsx +1 -1
- package/src/theme/ThemeContext.tsx +4 -5
- package/src/types/accordion.ts +69 -0
- package/src/types/popover.ts +25 -0
- package/src/{components/toast/types.ts → types/toast.ts} +12 -2
- package/dist/components/toast/Toast.d.ts.map +0 -1
- package/dist/components/toast/Toast.js.map +0 -1
- package/dist/components/toast/ToastProvider.d.ts +0 -13
- package/dist/components/toast/ToastProvider.d.ts.map +0 -1
- package/dist/components/toast/ToastProvider.js +0 -70
- package/dist/components/toast/ToastProvider.js.map +0 -1
- package/dist/components/toast/index.d.ts +0 -4
- package/dist/components/toast/index.d.ts.map +0 -1
- package/dist/components/toast/index.js +0 -3
- package/dist/components/toast/index.js.map +0 -1
- package/dist/components/toast/types.d.ts.map +0 -1
- package/dist/components/toast/types.js +0 -2
- package/dist/components/toast/types.js.map +0 -1
- package/src/components/toast/ToastProvider.tsx +0 -115
- package/src/components/toast/index.ts +0 -3
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { PopoverPlacement } from '../../../types/popover';
|
|
2
|
+
|
|
3
|
+
/*** Resolves a viewport-clamped anchored position for popover content. */
|
|
4
|
+
export function resolvePopoverPosition({
|
|
5
|
+
anchor,
|
|
6
|
+
contentSize,
|
|
7
|
+
offset,
|
|
8
|
+
placement,
|
|
9
|
+
viewport,
|
|
10
|
+
viewportPadding,
|
|
11
|
+
}: {
|
|
12
|
+
anchor: { height: number; width: number; x: number; y: number };
|
|
13
|
+
contentSize: { height: number; width: number };
|
|
14
|
+
offset: number;
|
|
15
|
+
placement: PopoverPlacement;
|
|
16
|
+
viewport: { height: number; width: number };
|
|
17
|
+
viewportPadding: number;
|
|
18
|
+
}): { left: number; top: number } {
|
|
19
|
+
const anchorCenterX = anchor.x + anchor.width / 2;
|
|
20
|
+
const rawLeft = placement.endsWith('-start')
|
|
21
|
+
? anchor.x
|
|
22
|
+
: placement.endsWith('-end')
|
|
23
|
+
? anchor.x + anchor.width - contentSize.width
|
|
24
|
+
: anchorCenterX - contentSize.width / 2;
|
|
25
|
+
const rawTop = placement.startsWith('top')
|
|
26
|
+
? anchor.y - contentSize.height - offset
|
|
27
|
+
: anchor.y + anchor.height + offset;
|
|
28
|
+
const maxLeft = Math.max(viewportPadding, viewport.width - contentSize.width - viewportPadding);
|
|
29
|
+
const maxTop = Math.max(viewportPadding, viewport.height - contentSize.height - viewportPadding);
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
left: clamp(rawLeft, viewportPadding, maxLeft),
|
|
33
|
+
top: clamp(rawTop, viewportPadding, maxTop),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/*** Clamps one numeric position into the available viewport range. */
|
|
38
|
+
function clamp(value: number, minimum: number, maximum: number): number {
|
|
39
|
+
return Math.min(Math.max(value, minimum), maximum);
|
|
40
|
+
}
|
|
@@ -1,45 +1,15 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Pressable } from 'react-native';
|
|
2
|
+
import { Pressable, StyleSheet, type ViewStyle } from 'react-native';
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
10
|
-
import
|
|
11
|
-
import type { ToastProps } from './types';
|
|
12
|
-
|
|
13
|
-
function resolveToastStatusColor(theme: SurfaceTheme, status: ToastProps['status']) {
|
|
14
|
-
switch (status) {
|
|
15
|
-
case 'success':
|
|
16
|
-
return theme.semantics.success.base;
|
|
17
|
-
case 'warning':
|
|
18
|
-
return theme.semantics.warning.base;
|
|
19
|
-
case 'error':
|
|
20
|
-
return theme.semantics.error.base;
|
|
21
|
-
case 'info':
|
|
22
|
-
return theme.semantics.info.base;
|
|
23
|
-
default:
|
|
24
|
-
return theme.semantics.action.primary.base;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function resolveToastStatusTextColor(status: ToastProps['status']): SurfaceColor {
|
|
29
|
-
switch (status) {
|
|
30
|
-
case 'success':
|
|
31
|
-
return 'success';
|
|
32
|
-
case 'warning':
|
|
33
|
-
return 'warning';
|
|
34
|
-
case 'error':
|
|
35
|
-
return 'error';
|
|
36
|
-
case 'info':
|
|
37
|
-
return 'info';
|
|
38
|
-
default:
|
|
39
|
-
return 'primary';
|
|
40
|
-
}
|
|
41
|
-
}
|
|
4
|
+
import type { SurfaceColor } from '../../../../surfaceColor';
|
|
5
|
+
import { useTheme } from '../../../../theme/ThemeContext';
|
|
6
|
+
import type { SurfaceTheme } from '../../../../theme/types';
|
|
7
|
+
import type { ToastProps } from '../../../../types/toast';
|
|
8
|
+
import { Box, Stack } from '../../../layout/public';
|
|
9
|
+
import { Surface } from '../../../surface/public';
|
|
10
|
+
import { Text } from '../../../typography/public';
|
|
42
11
|
|
|
12
|
+
/*** Renders one transient toast notification. */
|
|
43
13
|
export function Toast({
|
|
44
14
|
title,
|
|
45
15
|
description,
|
|
@@ -52,22 +22,14 @@ export function Toast({
|
|
|
52
22
|
const statusColor = resolveToastStatusColor(theme, status);
|
|
53
23
|
const statusTextColor = resolveToastStatusTextColor(status);
|
|
54
24
|
const passive = interactionPolicy === 'passive';
|
|
25
|
+
const statusStyle = React.useMemo<ViewStyle>(
|
|
26
|
+
() => ({ borderLeftColor: statusColor }),
|
|
27
|
+
[statusColor],
|
|
28
|
+
);
|
|
55
29
|
|
|
56
30
|
return (
|
|
57
|
-
<Surface
|
|
58
|
-
|
|
59
|
-
style={{
|
|
60
|
-
borderLeftColor: statusColor,
|
|
61
|
-
borderLeftWidth: 3,
|
|
62
|
-
minWidth: 280,
|
|
63
|
-
shadowOpacity: 0.14,
|
|
64
|
-
shadowRadius: 12,
|
|
65
|
-
shadowOffset: { width: 0, height: 6 },
|
|
66
|
-
}}
|
|
67
|
-
testID={testID}
|
|
68
|
-
variant="raised"
|
|
69
|
-
>
|
|
70
|
-
<Inline align="center" justify="space-between">
|
|
31
|
+
<Surface p="m" style={[styles.root, statusStyle]} testID={testID} variant="raised">
|
|
32
|
+
<Stack align="center" direction="row" justify="space-between">
|
|
71
33
|
<Box flex={1}>
|
|
72
34
|
{title ? (
|
|
73
35
|
<Text variant="label" weight="medium">
|
|
@@ -86,7 +48,49 @@ export function Toast({
|
|
|
86
48
|
<Text color={statusTextColor}>×</Text>
|
|
87
49
|
</Pressable>
|
|
88
50
|
) : null}
|
|
89
|
-
</
|
|
51
|
+
</Stack>
|
|
90
52
|
</Surface>
|
|
91
53
|
);
|
|
92
54
|
}
|
|
55
|
+
|
|
56
|
+
/*** Resolves the border color representing a toast status. */
|
|
57
|
+
function resolveToastStatusColor(theme: SurfaceTheme, status: ToastProps['status']) {
|
|
58
|
+
switch (status) {
|
|
59
|
+
case 'success':
|
|
60
|
+
return theme.semantics.success.base;
|
|
61
|
+
case 'warning':
|
|
62
|
+
return theme.semantics.warning.base;
|
|
63
|
+
case 'error':
|
|
64
|
+
return theme.semantics.error.base;
|
|
65
|
+
case 'info':
|
|
66
|
+
return theme.semantics.info.base;
|
|
67
|
+
default:
|
|
68
|
+
return theme.semantics.action.primary.base;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/*** Resolves the semantic text color representing a toast status. */
|
|
73
|
+
function resolveToastStatusTextColor(status: ToastProps['status']): SurfaceColor {
|
|
74
|
+
switch (status) {
|
|
75
|
+
case 'success':
|
|
76
|
+
return 'success';
|
|
77
|
+
case 'warning':
|
|
78
|
+
return 'warning';
|
|
79
|
+
case 'error':
|
|
80
|
+
return 'error';
|
|
81
|
+
case 'info':
|
|
82
|
+
return 'info';
|
|
83
|
+
default:
|
|
84
|
+
return 'primary';
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const styles = StyleSheet.create({
|
|
89
|
+
root: {
|
|
90
|
+
borderLeftWidth: 3,
|
|
91
|
+
minWidth: 280,
|
|
92
|
+
shadowOffset: { height: 6, width: 0 },
|
|
93
|
+
shadowOpacity: 0.14,
|
|
94
|
+
shadowRadius: 12,
|
|
95
|
+
},
|
|
96
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { StyleSheet, type ViewStyle } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { Portal } from '../../../../internal/overlay/Portal';
|
|
5
|
+
import { resolveOverlayAnimation } from '../../../../internal/resolvers';
|
|
6
|
+
import type { ToastProviderProps } from '../../../../types/toast';
|
|
7
|
+
import { Stack } from '../../../layout/public';
|
|
8
|
+
import { ToastContext } from '../../composition/ToastContext';
|
|
9
|
+
import { useToastRuntime } from '../../composition/useToastRuntime';
|
|
10
|
+
import { Toast } from './Toast';
|
|
11
|
+
|
|
12
|
+
/*** Provides the toast runtime context and renders its shared portal host. */
|
|
13
|
+
export function ToastProvider({ children, defaultDuration = 4000 }: ToastProviderProps) {
|
|
14
|
+
const animation = resolveOverlayAnimation('toast');
|
|
15
|
+
const { controller, toasts } = useToastRuntime(defaultDuration);
|
|
16
|
+
const animationStyle = React.useMemo<ViewStyle>(
|
|
17
|
+
() => ({ paddingTop: 16 + animation.offset }),
|
|
18
|
+
[animation.offset],
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<ToastContext value={controller}>
|
|
23
|
+
{children}
|
|
24
|
+
<Portal layer="toast" visible={toasts.length > 0}>
|
|
25
|
+
<Stack gap="s" pointerEvents="box-none" style={[styles.container, animationStyle]}>
|
|
26
|
+
{toasts.map((toast) => (
|
|
27
|
+
<Toast
|
|
28
|
+
description={toast.description}
|
|
29
|
+
key={toast.id}
|
|
30
|
+
onDismiss={() => controller.dismissToast(toast.id)}
|
|
31
|
+
status={toast.status}
|
|
32
|
+
testID={toast.testID}
|
|
33
|
+
title={toast.title}
|
|
34
|
+
/>
|
|
35
|
+
))}
|
|
36
|
+
</Stack>
|
|
37
|
+
</Portal>
|
|
38
|
+
</ToastContext>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const styles = StyleSheet.create({
|
|
43
|
+
container: {
|
|
44
|
+
alignItems: 'flex-end',
|
|
45
|
+
padding: 16,
|
|
46
|
+
},
|
|
47
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { renderToStaticMarkup } from 'react-dom/server';
|
|
4
|
+
|
|
5
|
+
import { useToast } from './useToast';
|
|
6
|
+
|
|
7
|
+
describe('useToast', () => {
|
|
8
|
+
it('requires ToastProvider', () => {
|
|
9
|
+
function Consumer() {
|
|
10
|
+
useToast();
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
expect(() => renderToStaticMarkup(<Consumer />)).toThrow(
|
|
15
|
+
'useToast must be used within <ToastProvider>.',
|
|
16
|
+
);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { use } from 'react';
|
|
2
|
+
|
|
3
|
+
import type { ToastController } from '../../../../types/toast';
|
|
4
|
+
import { ToastContext } from '../../composition/ToastContext';
|
|
5
|
+
|
|
6
|
+
/*** Returns the toast controller installed by ToastProvider. */
|
|
7
|
+
export function useToast(): ToastController {
|
|
8
|
+
const controller = use(ToastContext);
|
|
9
|
+
|
|
10
|
+
if (!controller) {
|
|
11
|
+
throw new Error('useToast must be used within <ToastProvider>.');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return controller;
|
|
15
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import type { ToastController, ToastOptions } from '../../../types/toast';
|
|
4
|
+
|
|
5
|
+
/*** Owns the toast queue, dismissal timers, and imperative controller state. */
|
|
6
|
+
export function useToastRuntime(defaultDuration: number) {
|
|
7
|
+
const [toasts, setToasts] = React.useState<ToastEntry[]>([]);
|
|
8
|
+
const timersRef = React.useRef(new Map<string, ReturnType<typeof setTimeout>>());
|
|
9
|
+
const counterRef = React.useRef(0);
|
|
10
|
+
|
|
11
|
+
const dismissToast = React.useCallback((id: string) => {
|
|
12
|
+
const timer = timersRef.current.get(id);
|
|
13
|
+
if (timer) {
|
|
14
|
+
clearTimeout(timer);
|
|
15
|
+
timersRef.current.delete(id);
|
|
16
|
+
}
|
|
17
|
+
setToasts((current) => current.filter((toast) => toast.id !== id));
|
|
18
|
+
}, []);
|
|
19
|
+
|
|
20
|
+
const showToast = React.useCallback((options: ToastOptions) => {
|
|
21
|
+
const id = options.id ?? `toast-${counterRef.current++}`;
|
|
22
|
+
setToasts((current) => [...current, { ...options, id }]);
|
|
23
|
+
return id;
|
|
24
|
+
}, []);
|
|
25
|
+
|
|
26
|
+
useToastTimers({ defaultDuration, dismissToast, timersRef, toasts });
|
|
27
|
+
|
|
28
|
+
const controller = React.useMemo<ToastController>(
|
|
29
|
+
() => ({ dismissToast, showToast }),
|
|
30
|
+
[dismissToast, showToast],
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
return { controller, toasts };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface ToastEntry extends ToastOptions {
|
|
37
|
+
id: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface UseToastTimersInput {
|
|
41
|
+
defaultDuration: number;
|
|
42
|
+
dismissToast: (id: string) => void;
|
|
43
|
+
timersRef: React.RefObject<Map<string, ReturnType<typeof setTimeout>>>;
|
|
44
|
+
toasts: readonly ToastEntry[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/*** Synchronizes toast lifetimes with timer side effects and cleans them up on unmount. */
|
|
48
|
+
function useToastTimers({ defaultDuration, dismissToast, timersRef, toasts }: UseToastTimersInput) {
|
|
49
|
+
React.useEffect(() => {
|
|
50
|
+
const timers = timersRef.current;
|
|
51
|
+
|
|
52
|
+
toasts.forEach((toast) => {
|
|
53
|
+
if (timers.has(toast.id)) return;
|
|
54
|
+
|
|
55
|
+
const timer = setTimeout(() => dismissToast(toast.id), toast.duration ?? defaultDuration);
|
|
56
|
+
timers.set(toast.id, timer);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const activeToastIds = new Set(toasts.map((toast) => toast.id));
|
|
60
|
+
timers.forEach((timer, id) => {
|
|
61
|
+
if (!activeToastIds.has(id)) {
|
|
62
|
+
clearTimeout(timer);
|
|
63
|
+
timers.delete(id);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}, [defaultDuration, dismissToast, timersRef, toasts]);
|
|
67
|
+
|
|
68
|
+
React.useEffect(() => {
|
|
69
|
+
const timers = timersRef.current;
|
|
70
|
+
|
|
71
|
+
return () => {
|
|
72
|
+
timers.forEach((timer) => clearTimeout(timer));
|
|
73
|
+
timers.clear();
|
|
74
|
+
};
|
|
75
|
+
}, [timersRef]);
|
|
76
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
ToastController,
|
|
3
|
+
ToastOptions,
|
|
4
|
+
ToastProps,
|
|
5
|
+
ToastProviderProps,
|
|
6
|
+
ToastStatus,
|
|
7
|
+
} from '../../types/toast';
|
|
8
|
+
export { Toast } from './adapters/inbound/Toast';
|
|
9
|
+
export { ToastProvider } from './adapters/inbound/ToastProvider';
|
|
10
|
+
export { useToast } from './adapters/inbound/useToast';
|
package/src/index.test.ts
CHANGED
|
@@ -28,7 +28,9 @@ const expectedFeatureRootExports = [
|
|
|
28
28
|
"export { Image } from './features/image/public';",
|
|
29
29
|
"export { KeyboardAvoidingView } from './features/keyboard-avoiding-view/public';",
|
|
30
30
|
"export { Box, Container, Divider, Grid, Stack } from './features/layout/public';",
|
|
31
|
+
"export { Popover } from './features/popover/public';",
|
|
31
32
|
"export { Surface } from './features/surface/public';",
|
|
33
|
+
"export { Toast, ToastProvider, useToast } from './features/toast/public';",
|
|
32
34
|
"export { Heading, Text } from './features/typography/public';",
|
|
33
35
|
] as const;
|
|
34
36
|
|
|
@@ -42,7 +44,6 @@ const expectedLegacyRootExports = [
|
|
|
42
44
|
"export { Switch } from './components/switch';",
|
|
43
45
|
"export { Tab, TabList, TabPanel, Tabs } from './components/tabs';",
|
|
44
46
|
"export { Textarea } from './components/textarea';",
|
|
45
|
-
"export { Toast, ToastProvider, useToast } from './components/toast';",
|
|
46
47
|
"export { Tooltip } from './components/tooltip';",
|
|
47
48
|
"export type { InteractionPolicy, InteractionPolicyProps } from './interactionPolicy';",
|
|
48
49
|
"export * from './core/responsive';",
|
|
@@ -63,6 +64,7 @@ describe('feature-owned root barrel contract', () => {
|
|
|
63
64
|
'./components/icon-button',
|
|
64
65
|
'./components/radio',
|
|
65
66
|
'./components/text-input',
|
|
67
|
+
'./components/toast',
|
|
66
68
|
'./primitives/heading',
|
|
67
69
|
'./primitives/icon',
|
|
68
70
|
'./primitives/image',
|
package/src/index.ts
CHANGED
|
@@ -16,8 +16,6 @@ export type { TabListProps, TabPanelProps, TabProps, TabsProps } from './compone
|
|
|
16
16
|
export { Tab, TabList, TabPanel, Tabs } from './components/tabs';
|
|
17
17
|
export type { TextareaProps } from './components/textarea';
|
|
18
18
|
export { Textarea } from './components/textarea';
|
|
19
|
-
export type { ToastOptions, ToastProps, ToastStatus } from './components/toast';
|
|
20
|
-
export { Toast, ToastProvider, useToast } from './components/toast';
|
|
21
19
|
export type { TooltipProps } from './components/tooltip';
|
|
22
20
|
export { Tooltip } from './components/tooltip';
|
|
23
21
|
export type { FontRuntime } from './context/FontContext';
|
|
@@ -25,6 +23,21 @@ export { FontProvider, useFontContext } from './context/FontContext';
|
|
|
25
23
|
export type { I18nInstance, TranslationRuntime, Translator } from './context/TranslationContext';
|
|
26
24
|
export { TranslationProvider, useTranslationContext } from './context/TranslationContext';
|
|
27
25
|
export * from './core/responsive';
|
|
26
|
+
export type {
|
|
27
|
+
AccordionContentProps,
|
|
28
|
+
AccordionItemProps,
|
|
29
|
+
AccordionMode,
|
|
30
|
+
AccordionMultipleProps,
|
|
31
|
+
AccordionProps,
|
|
32
|
+
AccordionSingleProps,
|
|
33
|
+
AccordionTriggerProps,
|
|
34
|
+
} from './features/accordion/public';
|
|
35
|
+
export {
|
|
36
|
+
Accordion,
|
|
37
|
+
AccordionContent,
|
|
38
|
+
AccordionItem,
|
|
39
|
+
AccordionTrigger,
|
|
40
|
+
} from './features/accordion/public';
|
|
28
41
|
export type { BadgeProps } from './features/badge/public';
|
|
29
42
|
export { Badge } from './features/badge/public';
|
|
30
43
|
export type { ButtonIconSpec, ButtonProps, IconButtonProps } from './features/button/public';
|
|
@@ -61,8 +74,22 @@ export type {
|
|
|
61
74
|
StackProps,
|
|
62
75
|
} from './features/layout/public';
|
|
63
76
|
export { Box, Container, Divider, Grid, Stack } from './features/layout/public';
|
|
77
|
+
export type {
|
|
78
|
+
PopoverAnchorRenderProps,
|
|
79
|
+
PopoverPlacement,
|
|
80
|
+
PopoverProps,
|
|
81
|
+
} from './features/popover/public';
|
|
82
|
+
export { Popover } from './features/popover/public';
|
|
64
83
|
export type { SurfaceProps, SurfaceVariant } from './features/surface/public';
|
|
65
84
|
export { Surface } from './features/surface/public';
|
|
85
|
+
export type {
|
|
86
|
+
ToastController,
|
|
87
|
+
ToastOptions,
|
|
88
|
+
ToastProps,
|
|
89
|
+
ToastProviderProps,
|
|
90
|
+
ToastStatus,
|
|
91
|
+
} from './features/toast/public';
|
|
92
|
+
export { Toast, ToastProvider, useToast } from './features/toast/public';
|
|
66
93
|
export type { HeadingLevel, HeadingProps, TextProps } from './features/typography/public';
|
|
67
94
|
export { Heading, Text } from './features/typography/public';
|
|
68
95
|
export type { InteractionPolicy, InteractionPolicyProps } from './interactionPolicy';
|
|
@@ -22,10 +22,7 @@ const styles = StyleSheet.create({
|
|
|
22
22
|
},
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
return { zIndex };
|
|
27
|
-
}
|
|
28
|
-
|
|
25
|
+
/*** Installs the shared overlay stack used by portal-based Surface features. */
|
|
29
26
|
export function OverlayProvider({ children }: { children: React.ReactNode }) {
|
|
30
27
|
const orderRef = React.useRef(0);
|
|
31
28
|
const [overlays, setOverlays] = React.useState<OverlayEntry[]>([]);
|
|
@@ -65,8 +62,8 @@ export function OverlayProvider({ children }: { children: React.ReactNode }) {
|
|
|
65
62
|
);
|
|
66
63
|
|
|
67
64
|
return (
|
|
68
|
-
<OverlayStackActionsContext
|
|
69
|
-
<OverlayStackContext
|
|
65
|
+
<OverlayStackActionsContext value={actions}>
|
|
66
|
+
<OverlayStackContext value={value}>
|
|
70
67
|
{children}
|
|
71
68
|
<View {...boxNonePointerEvents.props} style={[boxNonePointerEvents.style, styles.fill]}>
|
|
72
69
|
{overlays.map((overlay) => (
|
|
@@ -79,7 +76,12 @@ export function OverlayProvider({ children }: { children: React.ReactNode }) {
|
|
|
79
76
|
</View>
|
|
80
77
|
))}
|
|
81
78
|
</View>
|
|
82
|
-
</OverlayStackContext
|
|
83
|
-
</OverlayStackActionsContext
|
|
79
|
+
</OverlayStackContext>
|
|
80
|
+
</OverlayStackActionsContext>
|
|
84
81
|
);
|
|
85
82
|
}
|
|
83
|
+
|
|
84
|
+
/*** Resolves a per-entry z-index style for the overlay host. */
|
|
85
|
+
function overlayZIndex(zIndex: number): ViewStyle {
|
|
86
|
+
return { zIndex };
|
|
87
|
+
}
|
|
@@ -48,7 +48,7 @@ export const OverlayStackContext = React.createContext<OverlayStackRuntime | nul
|
|
|
48
48
|
export const OverlayStackActionsContext = React.createContext<OverlayStackActions | null>(null);
|
|
49
49
|
|
|
50
50
|
export function useOverlayStackActions(): OverlayStackActions | null {
|
|
51
|
-
return React.
|
|
51
|
+
return React.use(OverlayStackActionsContext);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
export function createOverlayEntry(
|
|
@@ -5,7 +5,8 @@ import { resolveOverlayZIndex } from './resolveOverlayZIndex';
|
|
|
5
5
|
describe('resolveOverlayZIndex', () => {
|
|
6
6
|
it('assigns higher base layers to later overlay types', () => {
|
|
7
7
|
expect(resolveOverlayZIndex('modal')).toBeLessThan(resolveOverlayZIndex('menu'));
|
|
8
|
-
expect(resolveOverlayZIndex('menu')).toBeLessThan(resolveOverlayZIndex('
|
|
8
|
+
expect(resolveOverlayZIndex('menu')).toBeLessThan(resolveOverlayZIndex('popover'));
|
|
9
|
+
expect(resolveOverlayZIndex('popover')).toBeLessThan(resolveOverlayZIndex('tooltip'));
|
|
9
10
|
expect(resolveOverlayZIndex('tooltip')).toBeLessThan(resolveOverlayZIndex('toast'));
|
|
10
11
|
});
|
|
11
12
|
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
export type OverlayLayer = 'modal' | 'drawer' | 'menu' | 'tooltip' | 'toast';
|
|
1
|
+
export type OverlayLayer = 'modal' | 'drawer' | 'menu' | 'popover' | 'tooltip' | 'toast';
|
|
2
2
|
|
|
3
3
|
const BASE_Z_INDEX: Record<OverlayLayer, number> = {
|
|
4
4
|
modal: 1000,
|
|
5
5
|
drawer: 1100,
|
|
6
6
|
menu: 1200,
|
|
7
|
+
popover: 1250,
|
|
7
8
|
tooltip: 1300,
|
|
8
9
|
toast: 1400,
|
|
9
10
|
};
|
package/src/layout/AppBar.tsx
CHANGED
|
@@ -26,7 +26,7 @@ export function AppBar({
|
|
|
26
26
|
...props
|
|
27
27
|
}: AppBarProps) {
|
|
28
28
|
const { theme } = useTheme();
|
|
29
|
-
const insets = React.
|
|
29
|
+
const insets = React.use(SafeAreaInsetsContext);
|
|
30
30
|
const topInset = safeAreaTop ? (insets?.top ?? 0) : 0;
|
|
31
31
|
const safeAreaStyle: ViewStyle | undefined =
|
|
32
32
|
topInset === 0 ? undefined : { paddingTop: topInset };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { createContext,
|
|
1
|
+
import React, { createContext, use, useMemo } from 'react';
|
|
2
2
|
|
|
3
3
|
import { useFontContext } from '../context/FontContext';
|
|
4
4
|
import { ResponsiveProvider } from '../core/responsive/ResponsiveProvider';
|
|
@@ -43,7 +43,6 @@ export const ThemeProvider = ({
|
|
|
43
43
|
const [mode, setMode] = React.useState<'light' | 'dark'>(initialMode);
|
|
44
44
|
const { activeFontId } = useFontContext();
|
|
45
45
|
|
|
46
|
-
// Keep state in sync with prop for real-time Studio updates
|
|
47
46
|
React.useEffect(() => {
|
|
48
47
|
if (initialConfig) {
|
|
49
48
|
setConfig((prev) => {
|
|
@@ -73,15 +72,15 @@ export const ThemeProvider = ({
|
|
|
73
72
|
|
|
74
73
|
return (
|
|
75
74
|
<ResponsiveProvider>
|
|
76
|
-
<ThemeContext
|
|
75
|
+
<ThemeContext value={value}>
|
|
77
76
|
<OverlayProvider>{children}</OverlayProvider>
|
|
78
|
-
</ThemeContext
|
|
77
|
+
</ThemeContext>
|
|
79
78
|
</ResponsiveProvider>
|
|
80
79
|
);
|
|
81
80
|
};
|
|
82
81
|
|
|
83
82
|
export const useTheme = () => {
|
|
84
|
-
return
|
|
83
|
+
return use(ThemeContext);
|
|
85
84
|
};
|
|
86
85
|
|
|
87
86
|
export const useThemeConfig = () => {
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
import type { PressableProps, ViewProps } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import type { InteractionPolicy, InteractionPolicyProps } from '../interactionPolicy';
|
|
5
|
+
|
|
6
|
+
export type AccordionMode = 'single' | 'multiple';
|
|
7
|
+
|
|
8
|
+
interface AccordionBaseProps extends Omit<ViewProps, 'children'>, InteractionPolicyProps {
|
|
9
|
+
children?: React.ReactNode;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface AccordionSingleProps extends AccordionBaseProps {
|
|
14
|
+
type?: 'single';
|
|
15
|
+
value?: string;
|
|
16
|
+
defaultValue?: string;
|
|
17
|
+
onValueChange?: (value: string | undefined) => void;
|
|
18
|
+
collapsible?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface AccordionMultipleProps extends AccordionBaseProps {
|
|
22
|
+
type: 'multiple';
|
|
23
|
+
value?: readonly string[];
|
|
24
|
+
defaultValue?: readonly string[];
|
|
25
|
+
onValueChange?: (value: readonly string[]) => void;
|
|
26
|
+
collapsible?: never;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type AccordionProps = AccordionSingleProps | AccordionMultipleProps;
|
|
30
|
+
|
|
31
|
+
export interface AccordionItemProps extends Omit<ViewProps, 'children'>, InteractionPolicyProps {
|
|
32
|
+
value: string;
|
|
33
|
+
children?: React.ReactNode;
|
|
34
|
+
disabled?: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface AccordionTriggerProps
|
|
38
|
+
extends
|
|
39
|
+
Omit<
|
|
40
|
+
PressableProps,
|
|
41
|
+
'accessibilityRole' | 'accessibilityState' | 'children' | 'disabled' | 'onPress'
|
|
42
|
+
>,
|
|
43
|
+
InteractionPolicyProps {
|
|
44
|
+
children?: PressableProps['children'];
|
|
45
|
+
disabled?: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface AccordionContentProps extends Omit<ViewProps, 'children'> {
|
|
49
|
+
children?: React.ReactNode;
|
|
50
|
+
forceMount?: boolean;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface AccordionContextValue {
|
|
54
|
+
disabled: boolean;
|
|
55
|
+
interactionPolicy: InteractionPolicy;
|
|
56
|
+
mode: AccordionMode;
|
|
57
|
+
openValues: readonly string[];
|
|
58
|
+
toggleValue: (value: string) => void;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface AccordionItemContextValue {
|
|
62
|
+
contentId: string;
|
|
63
|
+
disabled: boolean;
|
|
64
|
+
interactionPolicy: InteractionPolicy;
|
|
65
|
+
open: boolean;
|
|
66
|
+
toggle: () => void;
|
|
67
|
+
triggerId: string;
|
|
68
|
+
value: string;
|
|
69
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
|
|
3
|
+
import type { InteractionPolicy } from '../interactionPolicy';
|
|
4
|
+
|
|
5
|
+
export type PopoverPlacement =
|
|
6
|
+
'bottom-start' | 'bottom' | 'bottom-end' | 'top-start' | 'top' | 'top-end';
|
|
7
|
+
|
|
8
|
+
export interface PopoverAnchorRenderProps {
|
|
9
|
+
close: () => void;
|
|
10
|
+
open: boolean;
|
|
11
|
+
toggle: () => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface PopoverProps {
|
|
15
|
+
anchor: (controls: PopoverAnchorRenderProps) => React.ReactNode;
|
|
16
|
+
children?: React.ReactNode;
|
|
17
|
+
closeOnOutsidePress?: boolean;
|
|
18
|
+
defaultOpen?: boolean;
|
|
19
|
+
interactionPolicy?: InteractionPolicy;
|
|
20
|
+
offset?: number;
|
|
21
|
+
onOpenChange?: ((open: boolean) => void) | undefined;
|
|
22
|
+
open?: boolean;
|
|
23
|
+
placement?: PopoverPlacement;
|
|
24
|
+
testID?: string;
|
|
25
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
2
|
|
|
3
|
-
import type { InteractionPolicy } from '
|
|
4
|
-
import type { SurfaceStatusColor } from '
|
|
3
|
+
import type { InteractionPolicy } from '../interactionPolicy';
|
|
4
|
+
import type { SurfaceStatusColor } from '../surfaceColor';
|
|
5
5
|
|
|
6
6
|
export type ToastStatus = 'default' | SurfaceStatusColor;
|
|
7
7
|
|
|
@@ -18,3 +18,13 @@ export interface ToastOptions extends Omit<ToastProps, 'onDismiss'> {
|
|
|
18
18
|
duration?: number;
|
|
19
19
|
id?: string;
|
|
20
20
|
}
|
|
21
|
+
|
|
22
|
+
export interface ToastProviderProps {
|
|
23
|
+
children: React.ReactNode;
|
|
24
|
+
defaultDuration?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ToastController {
|
|
28
|
+
dismissToast: (id: string) => void;
|
|
29
|
+
showToast: (options: ToastOptions) => string;
|
|
30
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Toast.d.ts","sourceRoot":"","sources":["../../../src/components/toast/Toast.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAU1B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAgC1C,wBAAgB,KAAK,CAAC,EACpB,KAAK,EACL,WAAW,EACX,MAAkB,EAClB,SAAS,EACT,iBAA6B,EAC7B,MAAM,GACP,EAAE,UAAU,qBA0CZ"}
|