@ankhorage/zora 14.0.1 → 15.0.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 +6 -0
- package/README.md +1 -1
- package/dist/features/app-bar/adapters/inbound/AppBar.d.ts.map +1 -1
- package/dist/features/app-bar/adapters/inbound/AppBar.js +28 -27
- package/dist/features/app-bar/adapters/inbound/AppBar.js.map +1 -1
- package/dist/features/app-bar/public.d.ts +1 -1
- package/dist/features/app-bar/public.d.ts.map +1 -1
- package/dist/features/app-bar/public.js.map +1 -1
- package/dist/features/bottom-sheet/public.d.ts +2 -2
- package/dist/features/bottom-sheet/public.d.ts.map +1 -1
- package/dist/features/bottom-sheet/public.js +1 -1
- package/dist/features/bottom-sheet/public.js.map +1 -1
- package/dist/features/breadcrumbs/adapters/inbound/Breadcrumbs.d.ts +5 -0
- package/dist/features/breadcrumbs/adapters/inbound/Breadcrumbs.d.ts.map +1 -0
- package/dist/features/breadcrumbs/adapters/inbound/Breadcrumbs.js +81 -0
- package/dist/features/breadcrumbs/adapters/inbound/Breadcrumbs.js.map +1 -0
- package/dist/features/breadcrumbs/breadcrumbsMeta.d.ts +119 -0
- package/dist/features/breadcrumbs/breadcrumbsMeta.d.ts.map +1 -0
- package/dist/features/breadcrumbs/breadcrumbsMeta.js +94 -0
- package/dist/features/breadcrumbs/breadcrumbsMeta.js.map +1 -0
- package/dist/features/breadcrumbs/public.d.ts +3 -0
- package/dist/features/breadcrumbs/public.d.ts.map +1 -0
- package/dist/features/breadcrumbs/public.js +2 -0
- package/dist/features/breadcrumbs/public.js.map +1 -0
- package/dist/features/button/utils/resolveButtonThemeRecipe.d.ts +1 -1
- package/dist/features/data-table/adapters/inbound/DataTable.d.ts.map +1 -1
- package/dist/features/data-table/adapters/inbound/DataTable.js +4 -2
- package/dist/features/data-table/adapters/inbound/DataTable.js.map +1 -1
- package/dist/features/form/formFieldMeta.d.ts +1 -1
- package/dist/features/form/formFieldMeta.js +1 -1
- package/dist/features/form/formFieldMeta.js.map +1 -1
- package/dist/features/form/radio/utils/resolveRadioGroupThemeRecipe.d.ts +1 -1
- package/dist/features/form/select/adapters/inbound/Select.d.ts +2 -0
- package/dist/features/form/select/adapters/inbound/Select.d.ts.map +1 -0
- package/dist/features/form/select/adapters/inbound/Select.js +2 -0
- package/dist/features/form/select/adapters/inbound/Select.js.map +1 -0
- package/dist/features/form/select/adapters/inbound/Select.native.d.ts +5 -0
- package/dist/features/form/select/adapters/inbound/Select.native.d.ts.map +1 -0
- package/dist/features/form/select/adapters/inbound/Select.native.js +42 -0
- package/dist/features/form/select/adapters/inbound/Select.native.js.map +1 -0
- package/dist/features/form/select/adapters/inbound/Select.web.d.ts +5 -0
- package/dist/features/form/select/adapters/inbound/Select.web.d.ts.map +1 -0
- package/dist/features/form/select/adapters/inbound/Select.web.js +31 -0
- package/dist/features/form/select/adapters/inbound/Select.web.js.map +1 -0
- package/dist/features/form/select/composition/SelectField.d.ts +10 -0
- package/dist/features/form/select/composition/SelectField.d.ts.map +1 -0
- package/dist/features/form/select/composition/SelectField.js +8 -0
- package/dist/features/form/select/composition/SelectField.js.map +1 -0
- package/dist/features/form/select/composition/SelectOptionRow.d.ts +12 -0
- package/dist/features/form/select/composition/SelectOptionRow.d.ts.map +1 -0
- package/dist/features/form/select/composition/SelectOptionRow.js +9 -0
- package/dist/features/form/select/composition/SelectOptionRow.js.map +1 -0
- package/dist/features/form/select/composition/SelectTrigger.d.ts +10 -0
- package/dist/features/form/select/composition/SelectTrigger.d.ts.map +1 -0
- package/dist/features/form/select/composition/SelectTrigger.js +9 -0
- package/dist/features/form/select/composition/SelectTrigger.js.map +1 -0
- package/dist/features/form/select/composition/useSelectController.d.ts +8 -0
- package/dist/features/form/select/composition/useSelectController.d.ts.map +1 -0
- package/dist/features/form/select/composition/useSelectController.js +15 -0
- package/dist/features/form/select/composition/useSelectController.js.map +1 -0
- package/dist/features/form/select/public.d.ts +3 -0
- package/dist/features/form/select/public.d.ts.map +1 -0
- package/dist/features/form/select/public.js +2 -0
- package/dist/features/form/select/public.js.map +1 -0
- package/dist/features/form/select/selectMeta.d.ts +195 -0
- package/dist/features/form/select/selectMeta.d.ts.map +1 -0
- package/dist/features/form/select/selectMeta.js +152 -0
- package/dist/features/form/select/selectMeta.js.map +1 -0
- package/dist/features/popover-menu/adapters/inbound/PopoverMenu.d.ts +5 -0
- package/dist/features/popover-menu/adapters/inbound/PopoverMenu.d.ts.map +1 -0
- package/dist/features/popover-menu/adapters/inbound/PopoverMenu.js +43 -0
- package/dist/features/popover-menu/adapters/inbound/PopoverMenu.js.map +1 -0
- package/dist/features/popover-menu/popoverMenuMeta.d.ts +9 -0
- package/dist/features/popover-menu/popoverMenuMeta.d.ts.map +1 -0
- package/dist/features/popover-menu/popoverMenuMeta.js +9 -0
- package/dist/features/popover-menu/popoverMenuMeta.js.map +1 -0
- package/dist/features/popover-menu/public.d.ts +3 -0
- package/dist/features/popover-menu/public.d.ts.map +1 -0
- package/dist/features/popover-menu/public.js +2 -0
- package/dist/features/popover-menu/public.js.map +1 -0
- package/dist/features/typography/utils/resolveHeadingThemeRecipe.d.ts +1 -1
- package/dist/features/typography/utils/resolveTextThemeRecipe.d.ts +1 -1
- package/dist/index.d.ts +4 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/metadata/bindableComponentMeta.d.ts +1 -2
- package/dist/metadata/bindableComponentMeta.d.ts.map +1 -1
- package/dist/metadata/bindableComponentMeta.js +1 -2
- package/dist/metadata/bindableComponentMeta.js.map +1 -1
- package/dist/metadata/componentMeta.d.ts.map +1 -1
- package/dist/metadata/componentMeta.js +4 -5
- package/dist/metadata/componentMeta.js.map +1 -1
- package/dist/patterns/theme-composer/ThemeComposer.js +1 -1
- package/dist/patterns/theme-composer/ThemeComposer.js.map +1 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +4 -5
- package/dist/registry.js.map +1 -1
- package/dist/types/app-bar.d.ts +6 -3
- package/dist/types/app-bar.d.ts.map +1 -1
- package/dist/types/app-bar.js.map +1 -1
- package/dist/{components/breadcrumbs/types.d.ts → types/breadcrumbs.d.ts} +8 -6
- package/dist/types/breadcrumbs.d.ts.map +1 -0
- package/dist/types/breadcrumbs.js +2 -0
- package/dist/types/breadcrumbs.js.map +1 -0
- package/dist/types/data-table.d.ts +2 -2
- package/dist/types/data-table.d.ts.map +1 -1
- package/dist/types/data-table.js.map +1 -1
- package/dist/types/popover-menu.d.ts +24 -0
- package/dist/types/popover-menu.d.ts.map +1 -0
- package/dist/types/popover-menu.js +2 -0
- package/dist/types/popover-menu.js.map +1 -0
- package/dist/types/select.d.ts +22 -0
- package/dist/types/select.d.ts.map +1 -0
- package/dist/types/select.js +2 -0
- package/dist/types/select.js.map +1 -0
- package/package.json +2 -4
- package/src/features/app-bar/adapters/inbound/AppBar.tsx +50 -42
- package/src/features/app-bar/public.ts +1 -1
- package/src/features/bottom-sheet/public.test.ts +10 -1
- package/src/features/bottom-sheet/public.ts +9 -1
- package/src/features/breadcrumbs/adapters/inbound/Breadcrumbs.tsx +179 -0
- package/src/features/breadcrumbs/breadcrumbsMeta.ts +95 -0
- package/src/features/breadcrumbs/public.test.ts +22 -0
- package/src/features/breadcrumbs/public.ts +6 -0
- package/src/features/data-table/adapters/inbound/DataTable.tsx +9 -5
- package/src/features/form/formFieldMeta.ts +1 -1
- package/src/features/form/select/adapters/inbound/Select.native.tsx +78 -0
- package/src/features/form/select/adapters/inbound/Select.tsx +1 -0
- package/src/features/form/select/adapters/inbound/Select.web.tsx +63 -0
- package/src/features/form/select/composition/SelectField.tsx +27 -0
- package/src/features/form/select/composition/SelectOptionRow.tsx +36 -0
- package/src/features/form/select/composition/SelectTrigger.tsx +32 -0
- package/src/features/form/select/composition/useSelectController.ts +21 -0
- package/src/features/form/select/public.test.ts +25 -0
- package/src/features/form/select/public.ts +2 -0
- package/src/features/form/select/selectMeta.ts +154 -0
- package/src/features/popover-menu/adapters/inbound/PopoverMenu.tsx +60 -0
- package/src/{components/breadcrumbs/meta.ts → features/popover-menu/popoverMenuMeta.ts} +3 -3
- package/src/features/popover-menu/public.test.ts +11 -0
- package/src/features/popover-menu/public.ts +6 -0
- package/src/index.ts +14 -7
- package/src/interactionPolicy.test.ts +19 -21
- package/src/metadata/bindableComponentMeta.test.ts +8 -0
- package/src/metadata/bindableComponentMeta.ts +1 -2
- package/src/metadata/componentMeta.test.ts +3 -0
- package/src/metadata/componentMeta.ts +4 -5
- package/src/nativePeerContract.test.ts +5 -19
- package/src/patterns/theme-composer/ThemeComposer.tsx +1 -1
- package/src/platformAcceptance.test.ts +1 -1
- package/src/registry.ts +4 -5
- package/src/theme/themeScopeStructure.test.ts +8 -3
- package/src/types/app-bar.ts +6 -3
- package/src/{components/breadcrumbs/types.ts → types/breadcrumbs.ts} +8 -5
- package/src/types/data-table.ts +2 -2
- package/src/types/popover-menu.ts +31 -0
- package/src/{components/select/types.ts → types/select.ts} +11 -3
- package/dist/components/breadcrumbs/Breadcrumbs.d.ts +0 -7
- package/dist/components/breadcrumbs/Breadcrumbs.d.ts.map +0 -1
- package/dist/components/breadcrumbs/Breadcrumbs.js +0 -80
- package/dist/components/breadcrumbs/Breadcrumbs.js.map +0 -1
- package/dist/components/breadcrumbs/index.d.ts +0 -3
- package/dist/components/breadcrumbs/index.d.ts.map +0 -1
- package/dist/components/breadcrumbs/index.js +0 -2
- package/dist/components/breadcrumbs/index.js.map +0 -1
- package/dist/components/breadcrumbs/meta.d.ts +0 -9
- package/dist/components/breadcrumbs/meta.d.ts.map +0 -1
- package/dist/components/breadcrumbs/meta.js +0 -9
- package/dist/components/breadcrumbs/meta.js.map +0 -1
- package/dist/components/breadcrumbs/types.d.ts.map +0 -1
- package/dist/components/breadcrumbs/types.js +0 -2
- package/dist/components/breadcrumbs/types.js.map +0 -1
- package/dist/components/menu/DropdownMenu.d.ts +0 -7
- package/dist/components/menu/DropdownMenu.d.ts.map +0 -1
- package/dist/components/menu/DropdownMenu.js +0 -9
- package/dist/components/menu/DropdownMenu.js.map +0 -1
- package/dist/components/menu/Menu.d.ts +0 -7
- package/dist/components/menu/Menu.d.ts.map +0 -1
- package/dist/components/menu/Menu.js +0 -46
- package/dist/components/menu/Menu.js.map +0 -1
- package/dist/components/menu/index.d.ts +0 -4
- package/dist/components/menu/index.d.ts.map +0 -1
- package/dist/components/menu/index.js +0 -3
- package/dist/components/menu/index.js.map +0 -1
- package/dist/components/menu/meta.d.ts +0 -17
- package/dist/components/menu/meta.d.ts.map +0 -1
- package/dist/components/menu/meta.js +0 -17
- package/dist/components/menu/meta.js.map +0 -1
- package/dist/components/menu/types.d.ts +0 -22
- package/dist/components/menu/types.d.ts.map +0 -1
- package/dist/components/menu/types.js +0 -2
- package/dist/components/menu/types.js.map +0 -1
- package/dist/components/select/Select.d.ts +0 -7
- package/dist/components/select/Select.d.ts.map +0 -1
- package/dist/components/select/Select.js +0 -32
- package/dist/components/select/Select.js.map +0 -1
- package/dist/components/select/index.d.ts +0 -3
- package/dist/components/select/index.d.ts.map +0 -1
- package/dist/components/select/index.js +0 -3
- package/dist/components/select/index.js.map +0 -1
- package/dist/components/select/meta.d.ts +0 -9
- package/dist/components/select/meta.d.ts.map +0 -1
- package/dist/components/select/meta.js +0 -9
- package/dist/components/select/meta.js.map +0 -1
- package/dist/components/select/types.d.ts +0 -14
- package/dist/components/select/types.d.ts.map +0 -1
- package/dist/components/select/types.js +0 -2
- package/dist/components/select/types.js.map +0 -1
- package/src/components/breadcrumbs/Breadcrumbs.tsx +0 -147
- package/src/components/breadcrumbs/index.ts +0 -2
- package/src/components/menu/DropdownMenu.tsx +0 -11
- package/src/components/menu/Menu.tsx +0 -59
- package/src/components/menu/index.ts +0 -3
- package/src/components/menu/meta.ts +0 -19
- package/src/components/menu/types.ts +0 -31
- package/src/components/select/Select.tsx +0 -67
- package/src/components/select/index.ts +0 -2
- package/src/components/select/meta.ts +0 -10
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { Inline } from '../../../../foundation';
|
|
4
|
+
import { resolveIconSize } from '../../../../internal/recipes';
|
|
5
|
+
import { useZoraTheme } from '../../../../theme/useZoraTheme';
|
|
6
|
+
import { withZoraThemeScope } from '../../../../theme/withZoraThemeScope';
|
|
7
|
+
import type {
|
|
8
|
+
BreadcrumbItem,
|
|
9
|
+
BreadcrumbPressEvent,
|
|
10
|
+
BreadcrumbsProps,
|
|
11
|
+
} from '../../../../types/breadcrumbs';
|
|
12
|
+
import { Button } from '../../../button/public';
|
|
13
|
+
import { Icon } from '../../../icon/public';
|
|
14
|
+
import { Text } from '../../../typography/public';
|
|
15
|
+
|
|
16
|
+
/*** Renders a breadcrumb trail whose final item represents the current location. */
|
|
17
|
+
export const Breadcrumbs = withZoraThemeScope(BreadcrumbsInner);
|
|
18
|
+
|
|
19
|
+
type BreadcrumbRenderItem =
|
|
20
|
+
| {
|
|
21
|
+
item: BreadcrumbItem;
|
|
22
|
+
type: 'item';
|
|
23
|
+
}
|
|
24
|
+
| {
|
|
25
|
+
type: 'ellipsis';
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/*** Connects serializable breadcrumb items to the component-level item press event. */
|
|
29
|
+
function BreadcrumbsInner({
|
|
30
|
+
themeId: _themeId,
|
|
31
|
+
mode: _mode,
|
|
32
|
+
interactionPolicy,
|
|
33
|
+
items,
|
|
34
|
+
separator = '/',
|
|
35
|
+
maxItems,
|
|
36
|
+
compact = false,
|
|
37
|
+
disabled = false,
|
|
38
|
+
onItemPress,
|
|
39
|
+
testID,
|
|
40
|
+
}: BreadcrumbsProps) {
|
|
41
|
+
const normalizedMaxItems = normalizeMaxItems(maxItems);
|
|
42
|
+
const renderItems = createRenderItems(items, normalizedMaxItems);
|
|
43
|
+
const currentItemId = items.at(-1)?.id;
|
|
44
|
+
|
|
45
|
+
if (items.length === 0) return null;
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<Inline align="center" gap={compact ? 'xs' : 's'} testID={testID} wrap="wrap">
|
|
49
|
+
{renderItems.map((renderItem, index) => {
|
|
50
|
+
const showSeparator = index !== renderItems.length - 1;
|
|
51
|
+
|
|
52
|
+
if (renderItem.type === 'ellipsis') {
|
|
53
|
+
return (
|
|
54
|
+
<React.Fragment key="ellipsis">
|
|
55
|
+
<Text emphasis="muted" variant={compact ? 'caption' : 'bodySmall'}>
|
|
56
|
+
…
|
|
57
|
+
</Text>
|
|
58
|
+
{showSeparator ? renderSeparator(separator) : null}
|
|
59
|
+
</React.Fragment>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<BreadcrumbItemView
|
|
65
|
+
compact={compact}
|
|
66
|
+
current={renderItem.item.id === currentItemId}
|
|
67
|
+
disabled={disabled}
|
|
68
|
+
interactionPolicy={interactionPolicy}
|
|
69
|
+
item={renderItem.item}
|
|
70
|
+
key={renderItem.item.id}
|
|
71
|
+
onItemPress={onItemPress}
|
|
72
|
+
separator={showSeparator ? separator : undefined}
|
|
73
|
+
testID={testID}
|
|
74
|
+
/>
|
|
75
|
+
);
|
|
76
|
+
})}
|
|
77
|
+
</Inline>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/*** Renders one breadcrumb item and its optional following separator. */
|
|
82
|
+
function BreadcrumbItemView({
|
|
83
|
+
compact,
|
|
84
|
+
current,
|
|
85
|
+
disabled,
|
|
86
|
+
interactionPolicy,
|
|
87
|
+
item,
|
|
88
|
+
onItemPress,
|
|
89
|
+
separator,
|
|
90
|
+
testID,
|
|
91
|
+
}: {
|
|
92
|
+
compact: boolean;
|
|
93
|
+
current: boolean;
|
|
94
|
+
disabled: boolean;
|
|
95
|
+
interactionPolicy: BreadcrumbsProps['interactionPolicy'];
|
|
96
|
+
item: BreadcrumbItem;
|
|
97
|
+
onItemPress: ((event: BreadcrumbPressEvent) => void) | undefined;
|
|
98
|
+
separator: string | undefined;
|
|
99
|
+
testID: string | undefined;
|
|
100
|
+
}) {
|
|
101
|
+
const interactive = !current && !disabled && !item.disabled && onItemPress !== undefined;
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<>
|
|
105
|
+
{interactive ? (
|
|
106
|
+
<Button
|
|
107
|
+
color="neutral"
|
|
108
|
+
interactionPolicy={interactionPolicy}
|
|
109
|
+
leadingIcon={item.icon}
|
|
110
|
+
onPress={() => onItemPress({ id: item.id })}
|
|
111
|
+
size={compact ? 's' : 'm'}
|
|
112
|
+
testID={testID ? `${testID}-item-${item.id}` : undefined}
|
|
113
|
+
variant="ghost"
|
|
114
|
+
>
|
|
115
|
+
{item.label}
|
|
116
|
+
</Button>
|
|
117
|
+
) : (
|
|
118
|
+
<BreadcrumbLabel compact={compact} item={item} />
|
|
119
|
+
)}
|
|
120
|
+
{separator === undefined ? null : renderSeparator(separator)}
|
|
121
|
+
</>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/*** Renders one non-interactive breadcrumb label. */
|
|
126
|
+
function BreadcrumbLabel({ item, compact }: { item: BreadcrumbItem; compact: boolean }) {
|
|
127
|
+
const { theme } = useZoraTheme();
|
|
128
|
+
|
|
129
|
+
return (
|
|
130
|
+
<Inline align="center" gap="xs" wrap="nowrap">
|
|
131
|
+
{item.icon ? (
|
|
132
|
+
<Icon
|
|
133
|
+
{...item.icon}
|
|
134
|
+
color={theme.semantics.content.muted}
|
|
135
|
+
size={resolveIconSize(compact ? 's' : 'm')}
|
|
136
|
+
/>
|
|
137
|
+
) : null}
|
|
138
|
+
<Text emphasis="muted" variant={compact ? 'caption' : 'bodySmall'}>
|
|
139
|
+
{item.label}
|
|
140
|
+
</Text>
|
|
141
|
+
</Inline>
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/*** Renders the configured separator with muted breadcrumb styling. */
|
|
146
|
+
function renderSeparator(separator: string): React.ReactNode {
|
|
147
|
+
return (
|
|
148
|
+
<Text emphasis="muted" variant="bodySmall">
|
|
149
|
+
{separator}
|
|
150
|
+
</Text>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/*** Normalizes an optional maximum breadcrumb item count. */
|
|
155
|
+
function normalizeMaxItems(value: number | undefined): number | undefined {
|
|
156
|
+
if (value === undefined || !Number.isFinite(value)) return undefined;
|
|
157
|
+
return Math.max(0, Math.trunc(value));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/*** Collapses middle breadcrumb items into one ellipsis when required. */
|
|
161
|
+
function createRenderItems(
|
|
162
|
+
items: readonly BreadcrumbItem[],
|
|
163
|
+
maxItems: number | undefined,
|
|
164
|
+
): readonly BreadcrumbRenderItem[] {
|
|
165
|
+
if (maxItems === undefined || maxItems === 0 || items.length <= maxItems || maxItems < 3) {
|
|
166
|
+
return items.map((item) => ({ item, type: 'item' }));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const firstItem = items.at(0);
|
|
170
|
+
if (firstItem === undefined) return [];
|
|
171
|
+
|
|
172
|
+
const trailingCount = maxItems - 2;
|
|
173
|
+
const trailingItems = items.slice(items.length - trailingCount);
|
|
174
|
+
return [
|
|
175
|
+
{ item: firstItem, type: 'item' },
|
|
176
|
+
{ type: 'ellipsis' },
|
|
177
|
+
...trailingItems.map((item) => ({ item, type: 'item' }) satisfies BreadcrumbRenderItem),
|
|
178
|
+
];
|
|
179
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { ZoraComponentMeta } from '../../metadata';
|
|
2
|
+
|
|
3
|
+
export const breadcrumbsMeta = {
|
|
4
|
+
name: 'Breadcrumbs',
|
|
5
|
+
category: 'component',
|
|
6
|
+
description: 'Hierarchical route context where the final item represents the current location.',
|
|
7
|
+
directManifestNode: true,
|
|
8
|
+
allowedChildren: [],
|
|
9
|
+
blueprint: {
|
|
10
|
+
label: 'Breadcrumbs',
|
|
11
|
+
defaultProps: {
|
|
12
|
+
items: [
|
|
13
|
+
{ id: 'settings', label: 'Settings' },
|
|
14
|
+
{ id: 'general', label: 'General' },
|
|
15
|
+
],
|
|
16
|
+
separator: '/',
|
|
17
|
+
compact: false,
|
|
18
|
+
disabled: false,
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
bindings: {
|
|
22
|
+
events: {
|
|
23
|
+
itemPress: {
|
|
24
|
+
label: 'Item press',
|
|
25
|
+
description: 'Runs when a non-current breadcrumb item is pressed.',
|
|
26
|
+
payload: {
|
|
27
|
+
eventType: 'breadcrumbs.itemPress',
|
|
28
|
+
fields: [{ path: 'id', type: 'string', label: 'Item id' }],
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
events: {
|
|
34
|
+
itemPress: {
|
|
35
|
+
label: 'Item press',
|
|
36
|
+
eventType: 'breadcrumbs.itemPress',
|
|
37
|
+
description: 'Emitted when a non-current breadcrumb item is pressed.',
|
|
38
|
+
payloadFields: [{ path: 'id', type: 'string', label: 'Item id' }],
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
props: {
|
|
42
|
+
items: {
|
|
43
|
+
type: 'array',
|
|
44
|
+
category: 'Content',
|
|
45
|
+
label: 'Items',
|
|
46
|
+
itemSchema: [
|
|
47
|
+
{
|
|
48
|
+
key: 'id',
|
|
49
|
+
schema: { type: 'string', category: 'Content', label: 'Id' },
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
key: 'label',
|
|
53
|
+
schema: { type: 'string', category: 'Content', label: 'Label' },
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
key: 'disabled',
|
|
57
|
+
schema: {
|
|
58
|
+
type: 'boolean',
|
|
59
|
+
category: 'State',
|
|
60
|
+
label: 'Disabled',
|
|
61
|
+
default: false,
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
authoring: { authority: 'instance' },
|
|
66
|
+
},
|
|
67
|
+
separator: {
|
|
68
|
+
type: 'string',
|
|
69
|
+
category: 'Content',
|
|
70
|
+
label: 'Separator',
|
|
71
|
+
default: '/',
|
|
72
|
+
authoring: { authority: 'instance' },
|
|
73
|
+
},
|
|
74
|
+
maxItems: {
|
|
75
|
+
type: 'number',
|
|
76
|
+
category: 'Layout',
|
|
77
|
+
label: 'Maximum items',
|
|
78
|
+
authoring: { authority: 'instance' },
|
|
79
|
+
},
|
|
80
|
+
compact: {
|
|
81
|
+
type: 'boolean',
|
|
82
|
+
category: 'Layout',
|
|
83
|
+
label: 'Compact',
|
|
84
|
+
default: false,
|
|
85
|
+
authoring: { authority: 'instance' },
|
|
86
|
+
},
|
|
87
|
+
disabled: {
|
|
88
|
+
type: 'boolean',
|
|
89
|
+
category: 'State',
|
|
90
|
+
label: 'Disabled',
|
|
91
|
+
default: false,
|
|
92
|
+
authoring: { authority: 'instance' },
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
} as const satisfies ZoraComponentMeta;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { expect, test } from 'bun:test';
|
|
2
|
+
|
|
3
|
+
import { breadcrumbsMeta } from './breadcrumbsMeta';
|
|
4
|
+
|
|
5
|
+
test('Breadcrumbs is a direct manifest node with serializable item press payload', () => {
|
|
6
|
+
expect(breadcrumbsMeta.directManifestNode).toBe(true);
|
|
7
|
+
expect(breadcrumbsMeta.events.itemPress.eventType).toBe('breadcrumbs.itemPress');
|
|
8
|
+
expect(breadcrumbsMeta.events.itemPress.payloadFields).toEqual([
|
|
9
|
+
{ path: 'id', type: 'string', label: 'Item id' },
|
|
10
|
+
]);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test('Breadcrumbs keeps current-location derivation and callbacks outside item data', async () => {
|
|
14
|
+
const [source, types] = await Promise.all([
|
|
15
|
+
Bun.file('src/features/breadcrumbs/adapters/inbound/Breadcrumbs.tsx').text(),
|
|
16
|
+
Bun.file('src/types/breadcrumbs.ts').text(),
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
expect(source).toContain('const currentItemId = items.at(-1)?.id;');
|
|
20
|
+
expect(source).toContain('onItemPress({ id: item.id })');
|
|
21
|
+
expect(types).not.toContain('onPress?:');
|
|
22
|
+
});
|
|
@@ -2,7 +2,6 @@ import type { InteractionPolicy } from '@ankhorage/surface';
|
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { ScrollView, type ViewStyle } from 'react-native';
|
|
4
4
|
|
|
5
|
-
import { DropdownMenu, type MenuAction } from '../../../../components/menu';
|
|
6
5
|
import { Show } from '../../../../foundation';
|
|
7
6
|
import { withZoraThemeScope } from '../../../../theme/withZoraThemeScope';
|
|
8
7
|
import type {
|
|
@@ -19,6 +18,7 @@ import { IconButton } from '../../../button/public';
|
|
|
19
18
|
import { Card } from '../../../card/public';
|
|
20
19
|
import { EmptyState } from '../../../empty-state/public';
|
|
21
20
|
import { Box, Stack } from '../../../layout/public';
|
|
21
|
+
import { PopoverMenu, type PopoverMenuAction } from '../../../popover-menu/public';
|
|
22
22
|
import { SkeletonList } from '../../../skeleton/public';
|
|
23
23
|
import { Text, type TextAlign } from '../../../typography/public';
|
|
24
24
|
import { resolveDataTableRowKey } from '../../utils/resolveDataTableRowKey';
|
|
@@ -139,10 +139,11 @@ function resolveNextSortDirection(
|
|
|
139
139
|
return current === 'asc' ? 'desc' : 'asc';
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
/*** Maps row-scoped DataTable actions into the shared PopoverMenu action contract. */
|
|
142
143
|
function mapRowActions<TRow extends object>(
|
|
143
144
|
row: TRow,
|
|
144
145
|
actions: readonly DataTableRowAction<TRow>[],
|
|
145
|
-
): readonly
|
|
146
|
+
): readonly PopoverMenuAction[] {
|
|
146
147
|
return actions.map((action) => ({
|
|
147
148
|
description: action.description,
|
|
148
149
|
disabled: action.disabled,
|
|
@@ -154,6 +155,7 @@ function mapRowActions<TRow extends object>(
|
|
|
154
155
|
}));
|
|
155
156
|
}
|
|
156
157
|
|
|
158
|
+
/*** Renders the row action trigger and anchored PopoverMenu when actions exist. */
|
|
157
159
|
function renderRowActions<TRow extends object>({
|
|
158
160
|
row,
|
|
159
161
|
rowIndex,
|
|
@@ -174,18 +176,20 @@ function renderRowActions<TRow extends object>({
|
|
|
174
176
|
}
|
|
175
177
|
|
|
176
178
|
return (
|
|
177
|
-
<
|
|
179
|
+
<PopoverMenu
|
|
178
180
|
actions={mapRowActions(row, actions)}
|
|
179
181
|
interactionPolicy={interactionPolicy}
|
|
180
182
|
testID={testID ? `${testID}-row-actions-${rowIndex}` : undefined}
|
|
181
|
-
trigger={
|
|
183
|
+
trigger={({ toggle }) => (
|
|
182
184
|
<IconButton
|
|
183
185
|
icon={{ name: 'ellipsis-horizontal' }}
|
|
186
|
+
interactionPolicy={interactionPolicy}
|
|
184
187
|
label="Row actions"
|
|
188
|
+
onPress={toggle}
|
|
185
189
|
size="s"
|
|
186
190
|
variant="ghost"
|
|
187
191
|
/>
|
|
188
|
-
}
|
|
192
|
+
)}
|
|
189
193
|
/>
|
|
190
194
|
);
|
|
191
195
|
}
|
|
@@ -3,7 +3,7 @@ export const formFieldMeta = {
|
|
|
3
3
|
name: 'FormField',
|
|
4
4
|
category: 'component',
|
|
5
5
|
directManifestNode: true,
|
|
6
|
-
allowedChildren: ['Checkbox', 'CheckboxGroup', 'RadioGroup', 'TextInput'],
|
|
6
|
+
allowedChildren: ['Checkbox', 'CheckboxGroup', 'RadioGroup', 'Select', 'TextInput'],
|
|
7
7
|
blueprint: { label: 'Form field', defaultProps: { label: 'Label' } },
|
|
8
8
|
bindings: {
|
|
9
9
|
props: {
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type ListRenderItemInfo, StyleSheet } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { withZoraThemeScope } from '../../../../../theme/withZoraThemeScope';
|
|
5
|
+
import type { SelectOption, SelectProps } from '../../../../../types/select';
|
|
6
|
+
import { BottomSheetFlatList, useBottomSheet } from '../../../../bottom-sheet/public';
|
|
7
|
+
import { SelectField } from '../../composition/SelectField';
|
|
8
|
+
import { SelectOptionRow } from '../../composition/SelectOptionRow';
|
|
9
|
+
import { SelectTrigger } from '../../composition/SelectTrigger';
|
|
10
|
+
import { useSelectController } from '../../composition/useSelectController';
|
|
11
|
+
|
|
12
|
+
const SELECT_SHEET_MAX_HEIGHT = 480;
|
|
13
|
+
|
|
14
|
+
/*** Renders Select with BottomSheetFlatList presentation on native platforms. */
|
|
15
|
+
export const Select = withZoraThemeScope(SelectInner);
|
|
16
|
+
|
|
17
|
+
/*** Connects shared Select state and controls to the native BottomSheet host. */
|
|
18
|
+
function SelectInner<TValue extends string = string>({
|
|
19
|
+
themeId: _themeId,
|
|
20
|
+
mode: _mode,
|
|
21
|
+
...props
|
|
22
|
+
}: SelectProps<TValue>) {
|
|
23
|
+
const { dismiss, present } = useBottomSheet();
|
|
24
|
+
const { select, selectedOption, value } = useSelectController(props);
|
|
25
|
+
const passive = props.interactionPolicy === 'passive';
|
|
26
|
+
const { disabled, interactionPolicy, options, readOnly, testID } = props;
|
|
27
|
+
const listData = React.useMemo(() => [...options], [options]);
|
|
28
|
+
|
|
29
|
+
const handleSelect = React.useCallback(
|
|
30
|
+
(nextValue: TValue) => {
|
|
31
|
+
select(nextValue);
|
|
32
|
+
dismiss();
|
|
33
|
+
},
|
|
34
|
+
[dismiss, select],
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const renderOption = React.useCallback(
|
|
38
|
+
({ item }: ListRenderItemInfo<SelectOption<TValue>>) => (
|
|
39
|
+
<SelectOptionRow
|
|
40
|
+
interactionPolicy={interactionPolicy}
|
|
41
|
+
onSelect={handleSelect}
|
|
42
|
+
option={item}
|
|
43
|
+
selected={value === item.value}
|
|
44
|
+
testID={testID}
|
|
45
|
+
/>
|
|
46
|
+
),
|
|
47
|
+
[handleSelect, interactionPolicy, testID, value],
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const openSelect = React.useCallback(() => {
|
|
51
|
+
if (disabled || readOnly || passive) return;
|
|
52
|
+
|
|
53
|
+
present({
|
|
54
|
+
contentMode: 'direct',
|
|
55
|
+
maxDynamicContentSize: SELECT_SHEET_MAX_HEIGHT,
|
|
56
|
+
content: (
|
|
57
|
+
<BottomSheetFlatList
|
|
58
|
+
contentContainerStyle={styles.listContent}
|
|
59
|
+
data={listData}
|
|
60
|
+
keyExtractor={(item) => item.value}
|
|
61
|
+
renderItem={renderOption}
|
|
62
|
+
/>
|
|
63
|
+
),
|
|
64
|
+
});
|
|
65
|
+
}, [disabled, listData, passive, present, readOnly, renderOption]);
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<SelectField props={props}>
|
|
69
|
+
<SelectTrigger displayLabel={selectedOption?.label} onPress={openSelect} props={props} />
|
|
70
|
+
</SelectField>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const styles = StyleSheet.create({
|
|
75
|
+
listContent: {
|
|
76
|
+
padding: 16,
|
|
77
|
+
},
|
|
78
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Select } from './Select.web';
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Popover } from '@ankhorage/surface';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
import { withZoraThemeScope } from '../../../../../theme/withZoraThemeScope';
|
|
5
|
+
import type { SelectProps } from '../../../../../types/select';
|
|
6
|
+
import { Stack } from '../../../../layout/public';
|
|
7
|
+
import { Surface } from '../../../../surface/public';
|
|
8
|
+
import { SelectField } from '../../composition/SelectField';
|
|
9
|
+
import { SelectOptionRow } from '../../composition/SelectOptionRow';
|
|
10
|
+
import { SelectTrigger } from '../../composition/SelectTrigger';
|
|
11
|
+
import { useSelectController } from '../../composition/useSelectController';
|
|
12
|
+
|
|
13
|
+
/*** Renders Select with anchored Popover presentation on web. */
|
|
14
|
+
export const Select = withZoraThemeScope(SelectInner);
|
|
15
|
+
|
|
16
|
+
/*** Connects shared Select state and controls to the web Popover host. */
|
|
17
|
+
function SelectInner<TValue extends string = string>({
|
|
18
|
+
themeId: _themeId,
|
|
19
|
+
mode: _mode,
|
|
20
|
+
...props
|
|
21
|
+
}: SelectProps<TValue>) {
|
|
22
|
+
const [open, setOpen] = React.useState(false);
|
|
23
|
+
const { select, selectedOption, value } = useSelectController(props);
|
|
24
|
+
const { interactionPolicy, options, testID } = props;
|
|
25
|
+
|
|
26
|
+
const selectAndClose = React.useCallback(
|
|
27
|
+
(nextValue: TValue) => {
|
|
28
|
+
select(nextValue);
|
|
29
|
+
setOpen(false);
|
|
30
|
+
},
|
|
31
|
+
[select],
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<SelectField props={props}>
|
|
36
|
+
<Popover
|
|
37
|
+
anchor={({ toggle }) => (
|
|
38
|
+
<SelectTrigger displayLabel={selectedOption?.label} onPress={toggle} props={props} />
|
|
39
|
+
)}
|
|
40
|
+
interactionPolicy={props.interactionPolicy}
|
|
41
|
+
onOpenChange={setOpen}
|
|
42
|
+
open={open}
|
|
43
|
+
placement="bottom-start"
|
|
44
|
+
testID={testID ? `${testID}-popover` : undefined}
|
|
45
|
+
>
|
|
46
|
+
<Surface variant="raised">
|
|
47
|
+
<Stack gap="xs" p="xs">
|
|
48
|
+
{options.map((option) => (
|
|
49
|
+
<SelectOptionRow
|
|
50
|
+
interactionPolicy={interactionPolicy}
|
|
51
|
+
key={option.value}
|
|
52
|
+
onSelect={selectAndClose}
|
|
53
|
+
option={option}
|
|
54
|
+
selected={value === option.value}
|
|
55
|
+
testID={testID}
|
|
56
|
+
/>
|
|
57
|
+
))}
|
|
58
|
+
</Stack>
|
|
59
|
+
</Surface>
|
|
60
|
+
</Popover>
|
|
61
|
+
</SelectField>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Field } from '@ankhorage/surface';
|
|
2
|
+
import type React from 'react';
|
|
3
|
+
|
|
4
|
+
import type { SelectProps } from '../../../../types/select';
|
|
5
|
+
|
|
6
|
+
/*** Wraps Select presentation in the canonical Surface form-field semantics. */
|
|
7
|
+
export function SelectField<TValue extends string>({ children, props }: SelectFieldProps<TValue>) {
|
|
8
|
+
return (
|
|
9
|
+
<Field
|
|
10
|
+
disabled={props.disabled}
|
|
11
|
+
errorText={props.errorText}
|
|
12
|
+
helperText={props.helperText}
|
|
13
|
+
invalid={props.invalid}
|
|
14
|
+
label={props.label}
|
|
15
|
+
readOnly={props.readOnly}
|
|
16
|
+
required={props.required}
|
|
17
|
+
testID={props.testID ? `${props.testID}-field` : undefined}
|
|
18
|
+
>
|
|
19
|
+
{children}
|
|
20
|
+
</Field>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface SelectFieldProps<TValue extends string> {
|
|
25
|
+
children: React.ReactNode;
|
|
26
|
+
props: SelectProps<TValue>;
|
|
27
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { SelectOption, SelectProps } from '../../../../types/select';
|
|
2
|
+
import { Button } from '../../../button/public';
|
|
3
|
+
|
|
4
|
+
const SELECTED_ICON = { name: 'checkmark' } as const;
|
|
5
|
+
|
|
6
|
+
/*** Renders one Select option with shared web/native selection semantics. */
|
|
7
|
+
export function SelectOptionRow<TValue extends string>({
|
|
8
|
+
interactionPolicy,
|
|
9
|
+
onSelect,
|
|
10
|
+
option,
|
|
11
|
+
selected,
|
|
12
|
+
testID,
|
|
13
|
+
}: SelectOptionRowProps<TValue>) {
|
|
14
|
+
return (
|
|
15
|
+
<Button
|
|
16
|
+
color={selected ? 'primary' : 'neutral'}
|
|
17
|
+
disabled={option.disabled}
|
|
18
|
+
fullWidth
|
|
19
|
+
interactionPolicy={interactionPolicy}
|
|
20
|
+
onPress={() => onSelect(option.value)}
|
|
21
|
+
testID={testID ? `${testID}-option-${option.value}` : undefined}
|
|
22
|
+
trailingIcon={selected ? SELECTED_ICON : undefined}
|
|
23
|
+
variant={selected ? 'soft' : 'ghost'}
|
|
24
|
+
>
|
|
25
|
+
{option.label}
|
|
26
|
+
</Button>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface SelectOptionRowProps<TValue extends string> {
|
|
31
|
+
interactionPolicy: SelectProps<TValue>['interactionPolicy'];
|
|
32
|
+
onSelect: (value: TValue) => void;
|
|
33
|
+
option: SelectOption<TValue>;
|
|
34
|
+
selected: boolean;
|
|
35
|
+
testID: string | undefined;
|
|
36
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { SelectProps } from '../../../../types/select';
|
|
2
|
+
import { Button } from '../../../button/public';
|
|
3
|
+
|
|
4
|
+
const SELECT_CHEVRON_ICON = { name: 'chevron-down' } as const;
|
|
5
|
+
|
|
6
|
+
/*** Renders the shared Select trigger with the resolved option label or placeholder. */
|
|
7
|
+
export function SelectTrigger<TValue extends string>({
|
|
8
|
+
displayLabel,
|
|
9
|
+
onPress,
|
|
10
|
+
props,
|
|
11
|
+
}: SelectTriggerProps<TValue>) {
|
|
12
|
+
return (
|
|
13
|
+
<Button
|
|
14
|
+
color={props.invalid ? 'danger' : 'neutral'}
|
|
15
|
+
disabled={props.disabled}
|
|
16
|
+
fullWidth
|
|
17
|
+
interactionPolicy={props.interactionPolicy}
|
|
18
|
+
onPress={props.readOnly ? undefined : onPress}
|
|
19
|
+
testID={props.testID ? `${props.testID}-trigger` : undefined}
|
|
20
|
+
trailingIcon={SELECT_CHEVRON_ICON}
|
|
21
|
+
variant="outline"
|
|
22
|
+
>
|
|
23
|
+
{displayLabel ?? props.placeholder ?? 'Select'}
|
|
24
|
+
</Button>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface SelectTriggerProps<TValue extends string> {
|
|
29
|
+
displayLabel: string | undefined;
|
|
30
|
+
onPress: () => void;
|
|
31
|
+
props: SelectProps<TValue>;
|
|
32
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import type { SelectProps } from '../../../../types/select';
|
|
4
|
+
|
|
5
|
+
/*** Resolves controlled or uncontrolled Select value state and the selected option. */
|
|
6
|
+
export function useSelectController<TValue extends string>(props: SelectProps<TValue>) {
|
|
7
|
+
const { defaultValue, onValueChange, options, value: controlledValue } = props;
|
|
8
|
+
const [localValue, setLocalValue] = React.useState<TValue | undefined>(defaultValue);
|
|
9
|
+
const value = controlledValue ?? localValue;
|
|
10
|
+
const selectedOption = options.find((option) => option.value === value);
|
|
11
|
+
|
|
12
|
+
const select = React.useCallback(
|
|
13
|
+
(nextValue: TValue) => {
|
|
14
|
+
if (controlledValue === undefined) setLocalValue(nextValue);
|
|
15
|
+
onValueChange?.(nextValue);
|
|
16
|
+
},
|
|
17
|
+
[controlledValue, onValueChange],
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
return { select, selectedOption, value } as const;
|
|
21
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { expect, test } from 'bun:test';
|
|
2
|
+
|
|
3
|
+
import { selectMeta } from './selectMeta';
|
|
4
|
+
|
|
5
|
+
test('Select is a direct manifest node with valueChange payload', () => {
|
|
6
|
+
expect(selectMeta.directManifestNode).toBe(true);
|
|
7
|
+
expect(selectMeta.events.valueChange.eventType).toBe('select.valueChange');
|
|
8
|
+
expect(selectMeta.events.valueChange.payloadFields).toEqual([
|
|
9
|
+
{ path: 'value', type: 'string', label: 'Value' },
|
|
10
|
+
]);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test('Select uses web Popover and native BottomSheetFlatList presentation', async () => {
|
|
14
|
+
const [neutral, web, native] = await Promise.all([
|
|
15
|
+
Bun.file('src/features/form/select/adapters/inbound/Select.tsx').text(),
|
|
16
|
+
Bun.file('src/features/form/select/adapters/inbound/Select.web.tsx').text(),
|
|
17
|
+
Bun.file('src/features/form/select/adapters/inbound/Select.native.tsx').text(),
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
expect(neutral).toContain("export { Select } from './Select.web';");
|
|
21
|
+
expect(web).toContain("import { Popover } from '@ankhorage/surface';");
|
|
22
|
+
expect(native).toContain('<BottomSheetFlatList');
|
|
23
|
+
expect(native).toContain("contentMode: 'direct'");
|
|
24
|
+
expect(`${neutral}\n${web}\n${native}`).not.toContain('@react-native-picker/picker');
|
|
25
|
+
});
|