@ankhorage/zora 0.3.6 → 0.3.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.
Files changed (117) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +0 -90
  3. package/package.json +5 -1
  4. package/src/components/badge/Badge.tsx +19 -0
  5. package/src/components/badge/index.ts +2 -0
  6. package/src/components/badge/types.ts +14 -0
  7. package/src/components/button/Button.tsx +13 -0
  8. package/src/components/button/index.ts +2 -0
  9. package/src/components/button/types.ts +16 -0
  10. package/src/components/card/Card.tsx +65 -0
  11. package/src/components/card/index.ts +2 -0
  12. package/src/components/card/types.ts +15 -0
  13. package/src/components/drawer/Drawer.tsx +27 -0
  14. package/src/components/drawer/index.ts +2 -0
  15. package/src/components/drawer/types.ts +12 -0
  16. package/src/components/icon/Icon.tsx +8 -0
  17. package/src/components/icon/index.ts +2 -0
  18. package/src/components/icon-button/IconButton.tsx +27 -0
  19. package/src/components/icon-button/index.ts +2 -0
  20. package/src/components/icon-button/types.ts +15 -0
  21. package/src/components/input/Input.tsx +38 -0
  22. package/src/components/input/index.ts +2 -0
  23. package/src/components/input/types.ts +12 -0
  24. package/src/components/modal/Modal.tsx +37 -0
  25. package/src/components/modal/index.ts +2 -0
  26. package/src/components/modal/types.ts +15 -0
  27. package/src/components/select/Select.tsx +49 -0
  28. package/src/components/select/index.ts +2 -0
  29. package/src/components/select/types.ts +14 -0
  30. package/src/components/tabs/Tabs.tsx +103 -0
  31. package/src/components/tabs/index.ts +2 -0
  32. package/src/components/tabs/types.ts +25 -0
  33. package/src/components/textarea/Textarea.tsx +38 -0
  34. package/src/components/textarea/index.ts +2 -0
  35. package/src/components/textarea/types.ts +12 -0
  36. package/src/components/toolbar/Toolbar.tsx +38 -0
  37. package/src/components/toolbar/ToolbarAction.tsx +15 -0
  38. package/src/components/toolbar/index.ts +3 -0
  39. package/src/components/toolbar/types.ts +21 -0
  40. package/src/index.ts +72 -0
  41. package/src/internal/deepMerge.ts +23 -0
  42. package/src/internal/recipes.test.ts +46 -0
  43. package/src/internal/recipes.ts +92 -0
  44. package/src/layout/app-shell/AppShell.tsx +15 -0
  45. package/src/layout/app-shell/index.ts +2 -0
  46. package/src/layout/app-shell/types.ts +7 -0
  47. package/src/layout/auth-layout/AuthLayout.tsx +29 -0
  48. package/src/layout/auth-layout/index.ts +2 -0
  49. package/src/layout/auth-layout/types.ts +10 -0
  50. package/src/layout/page/Page.tsx +17 -0
  51. package/src/layout/page/index.ts +2 -0
  52. package/src/layout/page/types.ts +11 -0
  53. package/src/layout/page-header/PageHeader.tsx +41 -0
  54. package/src/layout/page-header/index.ts +2 -0
  55. package/src/layout/page-header/types.ts +10 -0
  56. package/src/layout/page-section/PageSection.tsx +14 -0
  57. package/src/layout/page-section/index.ts +2 -0
  58. package/src/layout/page-section/types.ts +9 -0
  59. package/src/layout/settings-layout/SettingsLayout.tsx +26 -0
  60. package/src/layout/settings-layout/index.ts +2 -0
  61. package/src/layout/settings-layout/types.ts +10 -0
  62. package/src/layout/sidebar-layout/SidebarLayout.tsx +23 -0
  63. package/src/layout/sidebar-layout/index.ts +2 -0
  64. package/src/layout/sidebar-layout/types.ts +10 -0
  65. package/src/layout/topbar-layout/TopbarLayout.tsx +14 -0
  66. package/src/layout/topbar-layout/index.ts +2 -0
  67. package/src/layout/topbar-layout/types.ts +8 -0
  68. package/src/patterns/collection-editor/CollectionEditor.tsx +100 -0
  69. package/src/patterns/collection-editor/index.ts +2 -0
  70. package/src/patterns/collection-editor/types.ts +25 -0
  71. package/src/patterns/confirm-dialog/ConfirmDialog.tsx +46 -0
  72. package/src/patterns/confirm-dialog/index.ts +2 -0
  73. package/src/patterns/confirm-dialog/types.ts +19 -0
  74. package/src/patterns/disclosure-section/DisclosureSection.tsx +61 -0
  75. package/src/patterns/disclosure-section/index.ts +2 -0
  76. package/src/patterns/disclosure-section/types.ts +15 -0
  77. package/src/patterns/empty-state/EmptyState.tsx +53 -0
  78. package/src/patterns/empty-state/index.ts +2 -0
  79. package/src/patterns/empty-state/types.ts +20 -0
  80. package/src/patterns/form-field/FormField.tsx +27 -0
  81. package/src/patterns/form-field/index.ts +2 -0
  82. package/src/patterns/form-field/types.ts +11 -0
  83. package/src/patterns/inspector-field/InspectorField.tsx +16 -0
  84. package/src/patterns/inspector-field/index.ts +2 -0
  85. package/src/patterns/inspector-field/types.ts +15 -0
  86. package/src/patterns/notice/Notice.tsx +30 -0
  87. package/src/patterns/notice/index.ts +2 -0
  88. package/src/patterns/notice/types.ts +12 -0
  89. package/src/patterns/panel/Panel.tsx +8 -0
  90. package/src/patterns/panel/index.ts +2 -0
  91. package/src/patterns/panel/types.ts +15 -0
  92. package/src/patterns/responsive-panel/ResponsivePanel.tsx +70 -0
  93. package/src/patterns/responsive-panel/index.ts +2 -0
  94. package/src/patterns/responsive-panel/types.ts +20 -0
  95. package/src/patterns/section-header/SectionHeader.tsx +39 -0
  96. package/src/patterns/section-header/index.ts +2 -0
  97. package/src/patterns/section-header/types.ts +9 -0
  98. package/src/patterns/settings-row/SettingsRow.tsx +40 -0
  99. package/src/patterns/settings-row/index.ts +2 -0
  100. package/src/patterns/settings-row/types.ts +27 -0
  101. package/src/patterns/switch-field/SwitchField.tsx +24 -0
  102. package/src/patterns/switch-field/index.ts +2 -0
  103. package/src/patterns/switch-field/types.ts +10 -0
  104. package/src/patterns/tile-grid/PaletteItem.tsx +49 -0
  105. package/src/patterns/tile-grid/TileGrid.tsx +44 -0
  106. package/src/patterns/tile-grid/index.ts +3 -0
  107. package/src/patterns/tile-grid/types.ts +20 -0
  108. package/src/patterns/tree-view/TreeItem.tsx +86 -0
  109. package/src/patterns/tree-view/TreeView.tsx +50 -0
  110. package/src/patterns/tree-view/index.ts +3 -0
  111. package/src/patterns/tree-view/types.ts +31 -0
  112. package/src/theme/ZoraProvider.tsx +22 -0
  113. package/src/theme/createZoraTheme.test.ts +25 -0
  114. package/src/theme/createZoraTheme.ts +10 -0
  115. package/src/theme/index.ts +6 -0
  116. package/src/theme/useZoraTheme.ts +5 -0
  117. package/src/theme/zoraTheme.ts +16 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.7
4
+
5
+ ### Patch Changes
6
+
7
+ - e9d563b: Export src/ for better Metro debugging
8
+
3
9
  ## 0.3.6
4
10
 
5
11
  ### Patch Changes
package/README.md CHANGED
@@ -1158,96 +1158,6 @@ const zoraTheme: ThemeConfig = {
1158
1158
 
1159
1159
  </details>
1160
1160
 
1161
- ## Public Exports
1162
-
1163
- Add `AppShell` to the public exports:
1164
-
1165
- ```ts
1166
- export {
1167
- AppShell,
1168
- AuthLayout,
1169
- Badge,
1170
- Button,
1171
- Card,
1172
- CollectionEditor,
1173
- ConfirmDialog,
1174
- createZoraTheme,
1175
- DisclosureSection,
1176
- Drawer,
1177
- EmptyState,
1178
- FormField,
1179
- IconButton,
1180
- Input,
1181
- InspectorField,
1182
- Modal,
1183
- Notice,
1184
- Page,
1185
- PageHeader,
1186
- PageSection,
1187
- PaletteItem,
1188
- Panel,
1189
- ResponsivePanel,
1190
- SectionHeader,
1191
- Select,
1192
- SettingsLayout,
1193
- SettingsRow,
1194
- SidebarLayout,
1195
- SwitchField,
1196
- Tabs,
1197
- Textarea,
1198
- TileGrid,
1199
- Toolbar,
1200
- ToolbarAction,
1201
- TopbarLayout,
1202
- TreeView,
1203
- ZoraProvider,
1204
- zoraTheme,
1205
- };
1206
- ```
1207
-
1208
- ```ts
1209
- export type {
1210
- AppShellProps,
1211
- AuthLayoutProps,
1212
- BadgeProps,
1213
- ButtonProps,
1214
- CardProps,
1215
- CollectionEditorProps,
1216
- ConfirmDialogProps,
1217
- DisclosureSectionProps,
1218
- DrawerProps,
1219
- EmptyStateAction,
1220
- EmptyStateProps,
1221
- FormFieldProps,
1222
- IconButtonProps,
1223
- InputProps,
1224
- InspectorFieldProps,
1225
- ModalProps,
1226
- NoticeProps,
1227
- PageHeaderProps,
1228
- PageProps,
1229
- PageSectionProps,
1230
- PaletteItemProps,
1231
- PanelProps,
1232
- ResponsivePanelProps,
1233
- SectionHeaderProps,
1234
- SelectProps,
1235
- SettingsLayoutProps,
1236
- SettingsRowProps,
1237
- SidebarLayoutProps,
1238
- SwitchFieldProps,
1239
- TabsProps,
1240
- TextareaProps,
1241
- TileGridProps,
1242
- ToolbarProps,
1243
- ToolbarActionProps,
1244
- TopbarLayoutProps,
1245
- TreeViewProps,
1246
- ZoraProviderProps,
1247
- ZoraThemeOverride,
1248
- };
1249
- ```
1250
-
1251
1161
  ## Example App
1252
1162
 
1253
1163
  A complete Expo showcase lives in `examples/expo-showcase`. It renders the
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ankhorage/zora",
3
3
  "type": "module",
4
- "version": "0.3.6",
4
+ "version": "0.3.7",
5
5
  "description": "Opinionated React Native and React Native Web UI kit built on @ankhorage/surface.",
6
6
  "homepage": "https://github.com/ankhorage/zora#readme",
7
7
  "bugs": {
@@ -47,12 +47,16 @@
47
47
  },
48
48
  "files": [
49
49
  "dist",
50
+ "src",
51
+ "package.json",
50
52
  "README.md",
51
53
  "CHANGELOG.md",
52
54
  "LICENSE"
53
55
  ],
54
56
  "exports": {
55
57
  ".": {
58
+ "react-native": "./src/index.ts",
59
+ "browser": "./src/index.ts",
56
60
  "types": "./dist/index.d.ts",
57
61
  "import": "./dist/index.js",
58
62
  "default": "./dist/index.js"
@@ -0,0 +1,19 @@
1
+ import { Badge as SurfaceBadge } from '@ankhorage/surface';
2
+ import React from 'react';
3
+
4
+ import { resolveBadgeRecipe } from '../../internal/recipes';
5
+ import type { BadgeProps } from './types';
6
+
7
+ export function Badge({ children, tone, emphasis, size, ...props }: BadgeProps) {
8
+ const recipe = resolveBadgeRecipe({ tone, emphasis, size });
9
+
10
+ return (
11
+ <SurfaceBadge
12
+ {...props}
13
+ content={children}
14
+ size={recipe.size}
15
+ tone={recipe.tone}
16
+ variant={recipe.variant}
17
+ />
18
+ );
19
+ }
@@ -0,0 +1,2 @@
1
+ export { Badge } from './Badge';
2
+ export type { BadgeProps } from './types';
@@ -0,0 +1,14 @@
1
+ import type { BadgeProps as SurfaceBadgeProps } from '@ankhorage/surface';
2
+ import type React from 'react';
3
+
4
+ import type { ZoraBadgeEmphasis, ZoraControlSize, ZoraTone } from '../../internal/recipes';
5
+
6
+ export interface BadgeProps extends Omit<
7
+ SurfaceBadgeProps,
8
+ 'content' | 'size' | 'tone' | 'variant'
9
+ > {
10
+ children?: React.ReactNode;
11
+ tone?: ZoraTone;
12
+ emphasis?: ZoraBadgeEmphasis;
13
+ size?: ZoraControlSize;
14
+ }
@@ -0,0 +1,13 @@
1
+ import { Button as SurfaceButton } from '@ankhorage/surface';
2
+ import React from 'react';
3
+
4
+ import { resolveButtonRecipe } from '../../internal/recipes';
5
+ import type { ButtonProps } from './types';
6
+
7
+ export function Button({ tone, emphasis, size, ...props }: ButtonProps) {
8
+ const recipe = resolveButtonRecipe({ tone, emphasis, size });
9
+
10
+ return (
11
+ <SurfaceButton {...props} size={recipe.size} tone={recipe.tone} variant={recipe.variant} />
12
+ );
13
+ }
@@ -0,0 +1,2 @@
1
+ export { Button } from './Button';
2
+ export type { ButtonProps } from './types';
@@ -0,0 +1,16 @@
1
+ import type { ButtonIconSpec, ButtonProps as SurfaceButtonProps } from '@ankhorage/surface';
2
+ import type React from 'react';
3
+
4
+ import type { ZoraControlSize, ZoraEmphasis, ZoraTone } from '../../internal/recipes';
5
+
6
+ export interface ButtonProps extends Omit<
7
+ SurfaceButtonProps,
8
+ 'children' | 'size' | 'tone' | 'variant'
9
+ > {
10
+ children?: React.ReactNode;
11
+ tone?: ZoraTone;
12
+ emphasis?: ZoraEmphasis;
13
+ size?: ZoraControlSize;
14
+ leadingIcon?: ButtonIconSpec;
15
+ trailingIcon?: ButtonIconSpec;
16
+ }
@@ -0,0 +1,65 @@
1
+ import { Box, Card as SurfaceCard, Heading, Stack, Text } from '@ankhorage/surface';
2
+ import React from 'react';
3
+
4
+ import { resolveCardVariant } from '../../internal/recipes';
5
+ import type { CardProps } from './types';
6
+
7
+ export function Card({
8
+ children,
9
+ title,
10
+ description,
11
+ eyebrow,
12
+ actions,
13
+ footer,
14
+ tone = 'default',
15
+ compact = false,
16
+ onPress,
17
+ ...props
18
+ }: CardProps) {
19
+ const hasHeader = [eyebrow, title, description, actions].some((item) => item != null);
20
+ const hasFooter = footer !== undefined;
21
+ const gap = compact ? 's' : 'm';
22
+ const isInteractive = Boolean(onPress) && !actions;
23
+
24
+ return (
25
+ <SurfaceCard
26
+ {...props}
27
+ onPress={isInteractive ? onPress : undefined}
28
+ p={compact ? 'm' : 'l'}
29
+ radius="l"
30
+ variant={resolveCardVariant(tone)}
31
+ >
32
+ <Stack gap={gap}>
33
+ {hasHeader ? (
34
+ <Stack
35
+ align={{ base: 'flex-start', md: 'center' }}
36
+ direction={{ base: 'column', md: 'row' }}
37
+ gap="m"
38
+ justify="space-between"
39
+ >
40
+ <Box flex={1}>
41
+ <Stack gap="xs">
42
+ {eyebrow ? (
43
+ <Text tone="muted" variant="caption" weight="semiBold">
44
+ {eyebrow}
45
+ </Text>
46
+ ) : null}
47
+ {title ? <Heading level={compact ? 4 : 3}>{title}</Heading> : null}
48
+ {description ? (
49
+ <Text tone="muted" variant="bodySmall">
50
+ {description}
51
+ </Text>
52
+ ) : null}
53
+ </Stack>
54
+ </Box>
55
+ {actions ? <Box>{actions}</Box> : null}
56
+ </Stack>
57
+ ) : null}
58
+
59
+ {children ? <Box>{children}</Box> : null}
60
+
61
+ {hasFooter ? <Box pt="xs">{footer}</Box> : null}
62
+ </Stack>
63
+ </SurfaceCard>
64
+ );
65
+ }
@@ -0,0 +1,2 @@
1
+ export { Card } from './Card';
2
+ export type { CardProps } from './types';
@@ -0,0 +1,15 @@
1
+ import type { CardProps as SurfaceCardProps } from '@ankhorage/surface';
2
+ import type React from 'react';
3
+
4
+ import type { ZoraCardTone } from '../../internal/recipes';
5
+
6
+ export interface CardProps extends Omit<SurfaceCardProps, 'children' | 'p' | 'radius' | 'variant'> {
7
+ children?: React.ReactNode;
8
+ title?: React.ReactNode;
9
+ description?: React.ReactNode;
10
+ eyebrow?: React.ReactNode;
11
+ actions?: React.ReactNode;
12
+ footer?: React.ReactNode;
13
+ tone?: ZoraCardTone;
14
+ compact?: boolean;
15
+ }
@@ -0,0 +1,27 @@
1
+ import { Box, Drawer as SurfaceDrawer, Heading, Stack, Text } from '@ankhorage/surface';
2
+ import React from 'react';
3
+
4
+ import type { DrawerProps } from './types';
5
+
6
+ export function Drawer({ children, title, description, footer, ...props }: DrawerProps) {
7
+ const hasHeader = title != null || description != null;
8
+
9
+ return (
10
+ <SurfaceDrawer {...props}>
11
+ <Stack gap="m">
12
+ {hasHeader ? (
13
+ <Stack gap="xs">
14
+ {title ? <Heading level={3}>{title}</Heading> : null}
15
+ {description ? (
16
+ <Text tone="muted" variant="bodySmall">
17
+ {description}
18
+ </Text>
19
+ ) : null}
20
+ </Stack>
21
+ ) : null}
22
+ {children ? <Box flex={1}>{children}</Box> : null}
23
+ {footer ? <Box pt="xs">{footer}</Box> : null}
24
+ </Stack>
25
+ </SurfaceDrawer>
26
+ );
27
+ }
@@ -0,0 +1,2 @@
1
+ export { Drawer } from './Drawer';
2
+ export type { DrawerProps } from './types';
@@ -0,0 +1,12 @@
1
+ import type { DrawerProps as SurfaceDrawerProps } from '@ankhorage/surface';
2
+ import type React from 'react';
3
+
4
+ export interface DrawerProps extends Pick<
5
+ SurfaceDrawerProps,
6
+ 'closeOnBackdrop' | 'onDismiss' | 'position' | 'testID' | 'visible'
7
+ > {
8
+ children?: React.ReactNode;
9
+ title?: React.ReactNode;
10
+ description?: React.ReactNode;
11
+ footer?: React.ReactNode;
12
+ }
@@ -0,0 +1,8 @@
1
+ import { Icon as SurfaceIcon, type IconProps as SurfaceIconProps } from '@ankhorage/surface';
2
+ import React from 'react';
3
+
4
+ export type IconProps = SurfaceIconProps;
5
+
6
+ export function Icon(props: IconProps) {
7
+ return <SurfaceIcon {...props} />;
8
+ }
@@ -0,0 +1,2 @@
1
+ export type { IconProps } from './Icon';
2
+ export { Icon } from './Icon';
@@ -0,0 +1,27 @@
1
+ import { IconButton as SurfaceIconButton } from '@ankhorage/surface';
2
+ import React from 'react';
3
+
4
+ import { resolveButtonRecipe } from '../../internal/recipes';
5
+ import type { IconButtonProps } from './types';
6
+
7
+ export function IconButton({
8
+ icon,
9
+ label,
10
+ emphasis = 'ghost',
11
+ tone = 'neutral',
12
+ size = 'm',
13
+ ...props
14
+ }: IconButtonProps) {
15
+ const recipe = resolveButtonRecipe({ emphasis, tone, size });
16
+
17
+ return (
18
+ <SurfaceIconButton
19
+ {...props}
20
+ icon={icon}
21
+ accessibilityLabel={label}
22
+ size={recipe.size}
23
+ tone={recipe.tone}
24
+ variant={recipe.variant}
25
+ />
26
+ );
27
+ }
@@ -0,0 +1,2 @@
1
+ export * from './IconButton';
2
+ export * from './types';
@@ -0,0 +1,15 @@
1
+ import type { ButtonIconSpec } from '@ankhorage/surface';
2
+
3
+ import type { ZoraControlSize, ZoraEmphasis, ZoraTone } from '../../internal/recipes';
4
+
5
+ export interface IconButtonProps {
6
+ icon: ButtonIconSpec;
7
+ label: string;
8
+ emphasis?: ZoraEmphasis;
9
+ tone?: ZoraTone;
10
+ size?: ZoraControlSize;
11
+ disabled?: boolean;
12
+ loading?: boolean;
13
+ onPress?: () => void;
14
+ testID?: string;
15
+ }
@@ -0,0 +1,38 @@
1
+ import { Icon, TextInput as SurfaceTextInput, useTheme } from '@ankhorage/surface';
2
+ import React from 'react';
3
+
4
+ import { resolveIconSize } from '../../internal/recipes';
5
+ import type { InputProps } from './types';
6
+
7
+ export function Input({ size = 'l', leadingIcon, trailingIcon, ...props }: InputProps) {
8
+ const { theme } = useTheme();
9
+ const iconSize = resolveIconSize(size);
10
+ const iconColor = theme.semantics.content.muted;
11
+
12
+ return (
13
+ <SurfaceTextInput
14
+ {...props}
15
+ leadingAccessory={
16
+ leadingIcon ? (
17
+ <Icon
18
+ color={iconColor}
19
+ name={leadingIcon.name}
20
+ provider={leadingIcon.provider}
21
+ size={iconSize}
22
+ />
23
+ ) : undefined
24
+ }
25
+ size={size}
26
+ trailingAccessory={
27
+ trailingIcon ? (
28
+ <Icon
29
+ color={iconColor}
30
+ name={trailingIcon.name}
31
+ provider={trailingIcon.provider}
32
+ size={iconSize}
33
+ />
34
+ ) : undefined
35
+ }
36
+ />
37
+ );
38
+ }
@@ -0,0 +1,2 @@
1
+ export { Input } from './Input';
2
+ export type { InputProps } from './types';
@@ -0,0 +1,12 @@
1
+ import type { ButtonIconSpec, TextInputProps as SurfaceTextInputProps } from '@ankhorage/surface';
2
+
3
+ import type { ZoraControlSize } from '../../internal/recipes';
4
+
5
+ export interface InputProps extends Omit<
6
+ SurfaceTextInputProps,
7
+ 'leadingAccessory' | 'size' | 'trailingAccessory'
8
+ > {
9
+ size?: ZoraControlSize;
10
+ leadingIcon?: ButtonIconSpec;
11
+ trailingIcon?: ButtonIconSpec;
12
+ }
@@ -0,0 +1,37 @@
1
+ import { Box, Heading, Modal as SurfaceModal, Stack, Text } from '@ankhorage/surface';
2
+ import React from 'react';
3
+
4
+ import { resolveDialogWidth } from '../../internal/recipes';
5
+ import type { ModalProps } from './types';
6
+
7
+ export function Modal({
8
+ children,
9
+ title,
10
+ description,
11
+ footer,
12
+ width = 'default',
13
+ ...props
14
+ }: ModalProps) {
15
+ const hasHeader = title != null || description != null;
16
+
17
+ return (
18
+ <SurfaceModal {...props}>
19
+ <Box maxWidth={resolveDialogWidth(width)} style={{ alignSelf: 'center', width: '100%' }}>
20
+ <Stack gap="m">
21
+ {hasHeader ? (
22
+ <Stack gap="xs">
23
+ {title ? <Heading level={3}>{title}</Heading> : null}
24
+ {description ? (
25
+ <Text tone="muted" variant="bodySmall">
26
+ {description}
27
+ </Text>
28
+ ) : null}
29
+ </Stack>
30
+ ) : null}
31
+ {children ? <Box>{children}</Box> : null}
32
+ {footer ? <Box pt="xs">{footer}</Box> : null}
33
+ </Stack>
34
+ </Box>
35
+ </SurfaceModal>
36
+ );
37
+ }
@@ -0,0 +1,2 @@
1
+ export { Modal } from './Modal';
2
+ export type { ModalProps } from './types';
@@ -0,0 +1,15 @@
1
+ import type { ModalProps as SurfaceModalProps } from '@ankhorage/surface';
2
+ import type React from 'react';
3
+
4
+ import type { ZoraContentWidth } from '../../internal/recipes';
5
+
6
+ export interface ModalProps extends Pick<
7
+ SurfaceModalProps,
8
+ 'closeOnBackdrop' | 'onDismiss' | 'testID' | 'visible'
9
+ > {
10
+ children?: React.ReactNode;
11
+ title?: React.ReactNode;
12
+ description?: React.ReactNode;
13
+ footer?: React.ReactNode;
14
+ width?: ZoraContentWidth;
15
+ }
@@ -0,0 +1,49 @@
1
+ import { Box, useTheme } from '@ankhorage/surface';
2
+ import { Picker } from '@react-native-picker/picker';
3
+ import React from 'react';
4
+
5
+ import type { SelectProps } from './types';
6
+
7
+ export function Select<TValue extends string = string>({
8
+ value,
9
+ options,
10
+ onValueChange,
11
+ disabled,
12
+ invalid,
13
+ testID,
14
+ }: SelectProps<TValue>) {
15
+ const { theme } = useTheme();
16
+
17
+ return (
18
+ <Box
19
+ bg="surface"
20
+ borderColor={invalid ? theme.colors.error : theme.colors.border}
21
+ borderWidth={1}
22
+ opacity={disabled ? 0.5 : 1}
23
+ radius="m"
24
+ testID={testID}
25
+ >
26
+ <Picker
27
+ enabled={!disabled}
28
+ onValueChange={(itemValue) => onValueChange(itemValue)}
29
+ selectedValue={value}
30
+ style={{
31
+ height: 44,
32
+ width: '100%',
33
+ backgroundColor: 'transparent',
34
+ color: theme.colors.text,
35
+ borderWidth: 0,
36
+ }}
37
+ >
38
+ {options.map((option) => (
39
+ <Picker.Item
40
+ key={option.value}
41
+ enabled={!option.disabled}
42
+ label={option.label}
43
+ value={option.value}
44
+ />
45
+ ))}
46
+ </Picker>
47
+ </Box>
48
+ );
49
+ }
@@ -0,0 +1,2 @@
1
+ export * from './Select';
2
+ export * from './types';
@@ -0,0 +1,14 @@
1
+ export interface SelectOption<TValue extends string = string> {
2
+ value: TValue;
3
+ label: string;
4
+ disabled?: boolean;
5
+ }
6
+
7
+ export interface SelectProps<TValue extends string = string> {
8
+ value: TValue;
9
+ options: readonly SelectOption<TValue>[];
10
+ onValueChange: (value: TValue) => void;
11
+ disabled?: boolean;
12
+ invalid?: boolean;
13
+ testID?: string;
14
+ }