@ankhorage/zora 5.0.0 → 7.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.
Files changed (185) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +14 -10
  3. package/dist/components/button/resolveButtonThemeRecipe.d.ts +1 -1
  4. package/dist/components/date-picker/DatePicker.d.ts.map +1 -1
  5. package/dist/components/date-picker/DatePicker.js +92 -86
  6. package/dist/components/date-picker/DatePicker.js.map +1 -1
  7. package/dist/components/date-picker/meta.d.ts +1 -1
  8. package/dist/components/date-picker/meta.js +1 -1
  9. package/dist/components/date-picker/meta.js.map +1 -1
  10. package/dist/components/heading/resolveHeadingThemeRecipe.d.ts +1 -1
  11. package/dist/components/image/Image.d.ts +1 -5
  12. package/dist/components/image/Image.d.ts.map +1 -1
  13. package/dist/components/image/Image.js +79 -8
  14. package/dist/components/image/Image.js.map +1 -1
  15. package/dist/components/image/types.d.ts +4 -2
  16. package/dist/components/image/types.d.ts.map +1 -1
  17. package/dist/components/image/types.js.map +1 -1
  18. package/dist/components/text/resolveTextThemeRecipe.d.ts +1 -1
  19. package/dist/components/time-picker/TimePicker.d.ts +1 -3
  20. package/dist/components/time-picker/TimePicker.d.ts.map +1 -1
  21. package/dist/components/time-picker/TimePicker.js +55 -32
  22. package/dist/components/time-picker/TimePicker.js.map +1 -1
  23. package/dist/components/time-picker/meta.d.ts +1 -1
  24. package/dist/components/time-picker/meta.js +1 -1
  25. package/dist/components/time-picker/meta.js.map +1 -1
  26. package/dist/features/bottom-sheet/public.d.ts +3 -0
  27. package/dist/features/bottom-sheet/public.d.ts.map +1 -0
  28. package/dist/features/bottom-sheet/public.js +2 -0
  29. package/dist/features/bottom-sheet/public.js.map +1 -0
  30. package/dist/features/uploader/adapters/inbound/Uploader.d.ts +5 -0
  31. package/dist/features/uploader/adapters/inbound/Uploader.d.ts.map +1 -0
  32. package/dist/features/uploader/adapters/inbound/Uploader.js +274 -0
  33. package/dist/features/uploader/adapters/inbound/Uploader.js.map +1 -0
  34. package/dist/features/uploader/adapters/outbound/pickDocumentOrFileAsync.d.ts +5 -0
  35. package/dist/features/uploader/adapters/outbound/pickDocumentOrFileAsync.d.ts.map +1 -0
  36. package/dist/features/uploader/adapters/outbound/pickDocumentOrFileAsync.js +38 -0
  37. package/dist/features/uploader/adapters/outbound/pickDocumentOrFileAsync.js.map +1 -0
  38. package/dist/features/uploader/adapters/outbound/pickImageOrVideoAsync.d.ts +5 -0
  39. package/dist/features/uploader/adapters/outbound/pickImageOrVideoAsync.d.ts.map +1 -0
  40. package/dist/features/uploader/adapters/outbound/pickImageOrVideoAsync.js +24 -0
  41. package/dist/features/uploader/adapters/outbound/pickImageOrVideoAsync.js.map +1 -0
  42. package/dist/features/uploader/application/ports/outbound/UploadPickerPort.d.ts +9 -0
  43. package/dist/features/uploader/application/ports/outbound/UploadPickerPort.d.ts.map +1 -0
  44. package/dist/features/uploader/application/ports/outbound/UploadPickerPort.js +2 -0
  45. package/dist/features/uploader/application/ports/outbound/UploadPickerPort.js.map +1 -0
  46. package/dist/features/uploader/application/use-cases/pickUploadAssetAsync.d.ts +5 -0
  47. package/dist/features/uploader/application/use-cases/pickUploadAssetAsync.d.ts.map +1 -0
  48. package/dist/features/uploader/application/use-cases/pickUploadAssetAsync.js +6 -0
  49. package/dist/features/uploader/application/use-cases/pickUploadAssetAsync.js.map +1 -0
  50. package/dist/features/uploader/application/use-cases/validateUploadAsset.d.ts +4 -0
  51. package/dist/features/uploader/application/use-cases/validateUploadAsset.d.ts.map +1 -0
  52. package/dist/features/uploader/application/use-cases/validateUploadAsset.js +77 -0
  53. package/dist/features/uploader/application/use-cases/validateUploadAsset.js.map +1 -0
  54. package/dist/features/uploader/composition/createUploadPicker.d.ts +4 -0
  55. package/dist/features/uploader/composition/createUploadPicker.d.ts.map +1 -0
  56. package/dist/features/uploader/composition/createUploadPicker.js +12 -0
  57. package/dist/features/uploader/composition/createUploadPicker.js.map +1 -0
  58. package/dist/features/uploader/meta.d.ts +10 -0
  59. package/dist/features/uploader/meta.d.ts.map +1 -0
  60. package/dist/features/uploader/meta.js +10 -0
  61. package/dist/features/uploader/meta.js.map +1 -0
  62. package/dist/features/uploader/public.d.ts +4 -0
  63. package/dist/features/uploader/public.d.ts.map +1 -0
  64. package/dist/features/uploader/public.js +3 -0
  65. package/dist/features/uploader/public.js.map +1 -0
  66. package/dist/index.d.ts +2 -6
  67. package/dist/index.d.ts.map +1 -1
  68. package/dist/index.js +1 -3
  69. package/dist/index.js.map +1 -1
  70. package/dist/metadata/componentMeta.d.ts.map +1 -1
  71. package/dist/metadata/componentMeta.js +2 -7
  72. package/dist/metadata/componentMeta.js.map +1 -1
  73. package/dist/registry.d.ts.map +1 -1
  74. package/dist/registry.js +2 -7
  75. package/dist/registry.js.map +1 -1
  76. package/dist/theme/ZoraProvider.d.ts +1 -1
  77. package/dist/theme/ZoraProvider.d.ts.map +1 -1
  78. package/dist/theme/ZoraProvider.js +3 -2
  79. package/dist/theme/ZoraProvider.js.map +1 -1
  80. package/dist/theme/useZoraTheme.d.ts +1 -1
  81. package/dist/types/upload.d.ts +53 -0
  82. package/dist/types/upload.d.ts.map +1 -0
  83. package/dist/types/upload.js +2 -0
  84. package/dist/types/upload.js.map +1 -0
  85. package/package.json +25 -3
  86. package/src/components/date-picker/DatePicker.tsx +197 -129
  87. package/src/components/date-picker/meta.ts +1 -1
  88. package/src/components/image/Image.tsx +136 -8
  89. package/src/components/image/types.ts +6 -2
  90. package/src/components/time-picker/TimePicker.tsx +147 -60
  91. package/src/components/time-picker/meta.ts +1 -1
  92. package/src/expo57Migration.test.ts +56 -0
  93. package/src/features/bottom-sheet/public.test.ts +39 -0
  94. package/src/features/bottom-sheet/public.ts +5 -0
  95. package/src/features/uploader/adapters/inbound/Uploader.tsx +403 -0
  96. package/src/features/uploader/adapters/outbound/pickDocumentOrFileAsync.ts +50 -0
  97. package/src/features/uploader/adapters/outbound/pickImageOrVideoAsync.ts +30 -0
  98. package/src/features/uploader/application/ports/outbound/UploadPickerPort.ts +10 -0
  99. package/src/features/uploader/application/use-cases/pickUploadAssetAsync.test.ts +48 -0
  100. package/src/features/uploader/application/use-cases/pickUploadAssetAsync.ts +14 -0
  101. package/src/features/uploader/application/use-cases/validateUploadAsset.test.ts +51 -0
  102. package/src/features/uploader/application/use-cases/validateUploadAsset.ts +111 -0
  103. package/src/features/uploader/composition/createUploadPicker.ts +14 -0
  104. package/src/features/uploader/meta.ts +11 -0
  105. package/src/features/uploader/public.test.ts +34 -0
  106. package/src/features/uploader/public.ts +10 -0
  107. package/src/index.ts +9 -14
  108. package/src/metadata/componentMeta.ts +2 -7
  109. package/src/platformAcceptance.test.ts +1 -1
  110. package/src/registry.test.ts +1 -1
  111. package/src/registry.ts +2 -7
  112. package/src/showcaseCoverage.test.ts +1 -2
  113. package/src/theme/ZoraProvider.tsx +3 -2
  114. package/src/types/upload.ts +62 -0
  115. package/dist/components/action-sheet/ActionSheet.d.ts +0 -7
  116. package/dist/components/action-sheet/ActionSheet.d.ts.map +0 -1
  117. package/dist/components/action-sheet/ActionSheet.js +0 -11
  118. package/dist/components/action-sheet/ActionSheet.js.map +0 -1
  119. package/dist/components/action-sheet/ActionSheetItem.d.ts +0 -7
  120. package/dist/components/action-sheet/ActionSheetItem.d.ts.map +0 -1
  121. package/dist/components/action-sheet/ActionSheetItem.js +0 -17
  122. package/dist/components/action-sheet/ActionSheetItem.js.map +0 -1
  123. package/dist/components/action-sheet/index.d.ts +0 -4
  124. package/dist/components/action-sheet/index.d.ts.map +0 -1
  125. package/dist/components/action-sheet/index.js +0 -3
  126. package/dist/components/action-sheet/index.js.map +0 -1
  127. package/dist/components/action-sheet/meta.d.ts +0 -17
  128. package/dist/components/action-sheet/meta.d.ts.map +0 -1
  129. package/dist/components/action-sheet/meta.js +0 -17
  130. package/dist/components/action-sheet/meta.js.map +0 -1
  131. package/dist/components/action-sheet/types.d.ts +0 -13
  132. package/dist/components/action-sheet/types.d.ts.map +0 -1
  133. package/dist/components/action-sheet/types.js +0 -2
  134. package/dist/components/action-sheet/types.js.map +0 -1
  135. package/dist/patterns/image-preview/ImagePreview.d.ts +0 -9
  136. package/dist/patterns/image-preview/ImagePreview.d.ts.map +0 -1
  137. package/dist/patterns/image-preview/ImagePreview.js +0 -46
  138. package/dist/patterns/image-preview/ImagePreview.js.map +0 -1
  139. package/dist/patterns/image-preview/index.d.ts +0 -3
  140. package/dist/patterns/image-preview/index.d.ts.map +0 -1
  141. package/dist/patterns/image-preview/index.js +0 -2
  142. package/dist/patterns/image-preview/index.js.map +0 -1
  143. package/dist/patterns/image-preview/meta.d.ts +0 -9
  144. package/dist/patterns/image-preview/meta.d.ts.map +0 -1
  145. package/dist/patterns/image-preview/meta.js +0 -9
  146. package/dist/patterns/image-preview/meta.js.map +0 -1
  147. package/dist/patterns/image-preview/types.d.ts +0 -36
  148. package/dist/patterns/image-preview/types.d.ts.map +0 -1
  149. package/dist/patterns/image-preview/types.js +0 -2
  150. package/dist/patterns/image-preview/types.js.map +0 -1
  151. package/dist/patterns/image-upload-field/ImageUploadField.d.ts +0 -9
  152. package/dist/patterns/image-upload-field/ImageUploadField.d.ts.map +0 -1
  153. package/dist/patterns/image-upload-field/ImageUploadField.js +0 -222
  154. package/dist/patterns/image-upload-field/ImageUploadField.js.map +0 -1
  155. package/dist/patterns/image-upload-field/index.d.ts +0 -3
  156. package/dist/patterns/image-upload-field/index.d.ts.map +0 -1
  157. package/dist/patterns/image-upload-field/index.js +0 -2
  158. package/dist/patterns/image-upload-field/index.js.map +0 -1
  159. package/dist/patterns/image-upload-field/meta.d.ts +0 -9
  160. package/dist/patterns/image-upload-field/meta.d.ts.map +0 -1
  161. package/dist/patterns/image-upload-field/meta.js +0 -9
  162. package/dist/patterns/image-upload-field/meta.js.map +0 -1
  163. package/dist/patterns/image-upload-field/types.d.ts +0 -35
  164. package/dist/patterns/image-upload-field/types.d.ts.map +0 -1
  165. package/dist/patterns/image-upload-field/types.js +0 -2
  166. package/dist/patterns/image-upload-field/types.js.map +0 -1
  167. package/dist/patterns/image-upload-field/uploadFlow.d.ts +0 -18
  168. package/dist/patterns/image-upload-field/uploadFlow.d.ts.map +0 -1
  169. package/dist/patterns/image-upload-field/uploadFlow.js +0 -106
  170. package/dist/patterns/image-upload-field/uploadFlow.js.map +0 -1
  171. package/src/components/action-sheet/ActionSheet.tsx +0 -14
  172. package/src/components/action-sheet/ActionSheetItem.tsx +0 -30
  173. package/src/components/action-sheet/index.ts +0 -3
  174. package/src/components/action-sheet/meta.ts +0 -19
  175. package/src/components/action-sheet/types.ts +0 -23
  176. package/src/patterns/image-preview/ImagePreview.tsx +0 -82
  177. package/src/patterns/image-preview/index.ts +0 -2
  178. package/src/patterns/image-preview/meta.ts +0 -10
  179. package/src/patterns/image-preview/types.ts +0 -41
  180. package/src/patterns/image-upload-field/ImageUploadField.tsx +0 -319
  181. package/src/patterns/image-upload-field/index.ts +0 -2
  182. package/src/patterns/image-upload-field/meta.ts +0 -10
  183. package/src/patterns/image-upload-field/types.ts +0 -41
  184. package/src/patterns/image-upload-field/uploadFlow.test.ts +0 -117
  185. package/src/patterns/image-upload-field/uploadFlow.ts +0 -145
@@ -1,106 +0,0 @@
1
- function parseAccept(accept) {
2
- if (!accept)
3
- return [];
4
- return accept
5
- .split(',')
6
- .map((entry) => entry.trim())
7
- .filter((entry) => entry.length > 0);
8
- }
9
- function matchesAcceptToken({ token, contentType, fileName, }) {
10
- const normalizedToken = token.toLowerCase();
11
- const normalizedContentType = contentType?.toLowerCase();
12
- const normalizedFileName = fileName?.toLowerCase();
13
- if (normalizedToken.startsWith('.')) {
14
- if (!normalizedFileName)
15
- return null;
16
- return normalizedFileName.endsWith(normalizedToken);
17
- }
18
- if (normalizedToken.endsWith('/*')) {
19
- if (!normalizedContentType)
20
- return null;
21
- const prefix = normalizedToken.slice(0, Math.max(0, normalizedToken.length - 1));
22
- return normalizedContentType.startsWith(prefix);
23
- }
24
- if (!normalizedContentType)
25
- return null;
26
- return normalizedContentType === normalizedToken;
27
- }
28
- export function isAccepted({ accept, contentType, fileName, }) {
29
- const tokens = parseAccept(accept);
30
- if (tokens.length === 0)
31
- return true;
32
- let hadSignal = false;
33
- for (const token of tokens) {
34
- const matches = matchesAcceptToken({ token, contentType, fileName });
35
- if (matches === null) {
36
- continue;
37
- }
38
- hadSignal = true;
39
- if (matches)
40
- return true;
41
- }
42
- // If we cannot validate due to missing metadata, do not block.
43
- return !hadSignal;
44
- }
45
- function formatBytes(value) {
46
- if (!Number.isFinite(value) || value <= 0)
47
- return '0 B';
48
- const units = ['B', 'KB', 'MB', 'GB'];
49
- let size = value;
50
- let unitIndex = 0;
51
- while (size >= 1024 && unitIndex < units.length - 1) {
52
- size /= 1024;
53
- unitIndex += 1;
54
- }
55
- const unit = units.at(unitIndex) ?? 'B';
56
- const rounded = unitIndex === 0 ? Math.round(size) : Math.round(size * 10) / 10;
57
- return `${rounded} ${unit}`;
58
- }
59
- export function formatAcceptHint(accept) {
60
- const tokens = parseAccept(accept);
61
- if (tokens.length === 0)
62
- return null;
63
- return `Accepted: ${tokens.join(', ')}`;
64
- }
65
- export function formatMaxSizeHint(maxSizeBytes) {
66
- if (maxSizeBytes === undefined)
67
- return null;
68
- if (!Number.isFinite(maxSizeBytes) || maxSizeBytes <= 0)
69
- return null;
70
- return `Max size: ${formatBytes(maxSizeBytes)}`;
71
- }
72
- export function createOptimisticAssetFromPicked(picked) {
73
- return {
74
- kind: 'url',
75
- url: picked.uri,
76
- width: picked.width,
77
- height: picked.height,
78
- contentType: picked.contentType,
79
- metadata: {
80
- fileName: picked.fileName,
81
- sizeBytes: picked.sizeBytes,
82
- },
83
- };
84
- }
85
- export function resolveRenderableUrl(asset) {
86
- if (!asset)
87
- return null;
88
- if (asset.kind === 'url')
89
- return asset.url;
90
- return asset.publicUrl ?? null;
91
- }
92
- export function validatePickedImage({ picked, accept, maxSizeBytes, validatePicked, }) {
93
- if (!isAccepted({ accept, contentType: picked.contentType, fileName: picked.fileName })) {
94
- return accept ? `File type not accepted. Expected ${accept}.` : 'File type not accepted.';
95
- }
96
- if (maxSizeBytes !== undefined &&
97
- Number.isFinite(maxSizeBytes) &&
98
- maxSizeBytes > 0 &&
99
- picked.sizeBytes !== undefined &&
100
- Number.isFinite(picked.sizeBytes) &&
101
- picked.sizeBytes > maxSizeBytes) {
102
- return `File is too large (${formatBytes(picked.sizeBytes)}). Max ${formatBytes(maxSizeBytes)}.`;
103
- }
104
- return validatePicked?.(picked);
105
- }
106
- //# sourceMappingURL=uploadFlow.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"uploadFlow.js","sourceRoot":"","sources":["../../../src/patterns/image-upload-field/uploadFlow.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ZoraImageAsset } from '../image-preview';\nimport type { ZoraPickedImage } from './types';\n\nfunction parseAccept(accept: string | undefined): readonly string[] {\n if (!accept) return [];\n\n return accept\n .split(',')\n .map((entry) => entry.trim())\n .filter((entry) => entry.length > 0);\n}\n\nfunction matchesAcceptToken({\n token,\n contentType,\n fileName,\n}: {\n token: string;\n contentType: string | undefined;\n fileName: string | undefined;\n}): boolean | null {\n const normalizedToken = token.toLowerCase();\n const normalizedContentType = contentType?.toLowerCase();\n const normalizedFileName = fileName?.toLowerCase();\n\n if (normalizedToken.startsWith('.')) {\n if (!normalizedFileName) return null;\n return normalizedFileName.endsWith(normalizedToken);\n }\n\n if (normalizedToken.endsWith('/*')) {\n if (!normalizedContentType) return null;\n const prefix = normalizedToken.slice(0, Math.max(0, normalizedToken.length - 1));\n return normalizedContentType.startsWith(prefix);\n }\n\n if (!normalizedContentType) return null;\n return normalizedContentType === normalizedToken;\n}\n\nexport function isAccepted({\n accept,\n contentType,\n fileName,\n}: {\n accept: string | undefined;\n contentType: string | undefined;\n fileName: string | undefined;\n}): boolean {\n const tokens = parseAccept(accept);\n if (tokens.length === 0) return true;\n\n let hadSignal = false;\n\n for (const token of tokens) {\n const matches = matchesAcceptToken({ token, contentType, fileName });\n if (matches === null) {\n continue;\n }\n\n hadSignal = true;\n if (matches) return true;\n }\n\n // If we cannot validate due to missing metadata, do not block.\n return !hadSignal;\n}\n\nfunction formatBytes(value: number): string {\n if (!Number.isFinite(value) || value <= 0) return '0 B';\n\n const units = ['B', 'KB', 'MB', 'GB'] as const;\n let size = value;\n let unitIndex = 0;\n\n while (size >= 1024 && unitIndex < units.length - 1) {\n size /= 1024;\n unitIndex += 1;\n }\n\n const unit = units.at(unitIndex) ?? 'B';\n const rounded = unitIndex === 0 ? Math.round(size) : Math.round(size * 10) / 10;\n return `${rounded} ${unit}`;\n}\n\nexport function formatAcceptHint(accept: string | undefined): string | null {\n const tokens = parseAccept(accept);\n if (tokens.length === 0) return null;\n return `Accepted: ${tokens.join(', ')}`;\n}\n\nexport function formatMaxSizeHint(maxSizeBytes: number | undefined): string | null {\n if (maxSizeBytes === undefined) return null;\n if (!Number.isFinite(maxSizeBytes) || maxSizeBytes <= 0) return null;\n return `Max size: ${formatBytes(maxSizeBytes)}`;\n}\n\nexport function createOptimisticAssetFromPicked(picked: ZoraPickedImage): ZoraImageAsset {\n return {\n kind: 'url',\n url: picked.uri,\n width: picked.width,\n height: picked.height,\n contentType: picked.contentType,\n metadata: {\n fileName: picked.fileName,\n sizeBytes: picked.sizeBytes,\n },\n };\n}\n\nexport function resolveRenderableUrl(asset: ZoraImageAsset | null): string | null {\n if (!asset) return null;\n if (asset.kind === 'url') return asset.url;\n return asset.publicUrl ?? null;\n}\n\nexport function validatePickedImage({\n picked,\n accept,\n maxSizeBytes,\n validatePicked,\n}: {\n picked: ZoraPickedImage;\n accept: string | undefined;\n maxSizeBytes: number | undefined;\n validatePicked: ((picked: ZoraPickedImage) => string | undefined) | undefined;\n}): string | undefined {\n if (!isAccepted({ accept, contentType: picked.contentType, fileName: picked.fileName })) {\n return accept ? `File type not accepted. Expected ${accept}.` : 'File type not accepted.';\n }\n\n if (\n maxSizeBytes !== undefined &&\n Number.isFinite(maxSizeBytes) &&\n maxSizeBytes > 0 &&\n picked.sizeBytes !== undefined &&\n Number.isFinite(picked.sizeBytes) &&\n picked.sizeBytes > maxSizeBytes\n ) {\n return `File is too large (${formatBytes(picked.sizeBytes)}). Max ${formatBytes(maxSizeBytes)}.`;\n }\n\n return validatePicked?.(picked);\n}\n"]}
@@ -1,14 +0,0 @@
1
- import { ActionSheet as SurfaceActionSheet } from '@ankhorage/surface';
2
- import React from 'react';
3
-
4
- import { withZoraThemeScope } from '../../theme/withZoraThemeScope';
5
- import type { ActionSheetProps } from './types';
6
-
7
- function ActionSheetInner({ mode: _mode, themeId: _themeId, ...props }: ActionSheetProps) {
8
- return <SurfaceActionSheet {...props} />;
9
- }
10
-
11
- /***
12
- * Presents a modal bottom sheet with a list of actions.
13
- */
14
- export const ActionSheet = withZoraThemeScope(ActionSheetInner);
@@ -1,30 +0,0 @@
1
- import { ActionSheetItem as SurfaceActionSheetItem } from '@ankhorage/surface';
2
- import React from 'react';
3
-
4
- import { resolveIconSize } from '../../internal/recipes';
5
- import { useZoraTheme } from '../../theme/useZoraTheme';
6
- import { withZoraThemeScope } from '../../theme/withZoraThemeScope';
7
- import { Icon } from '../icon';
8
- import type { ActionSheetItemProps } from './types';
9
-
10
- function ActionSheetItemInner({
11
- themeId: _themeId,
12
- mode: _mode,
13
- icon,
14
- leading,
15
- ...props
16
- }: ActionSheetItemProps) {
17
- const { theme } = useZoraTheme();
18
- const resolvedLeading =
19
- leading ??
20
- (icon ? (
21
- <Icon {...icon} color={theme.semantics.content.muted} size={resolveIconSize('s')} />
22
- ) : undefined);
23
-
24
- return <SurfaceActionSheetItem {...props} leading={resolvedLeading} />;
25
- }
26
-
27
- /***
28
- * Renders a single selectable action row within an `ActionSheet`.
29
- */
30
- export const ActionSheetItem = withZoraThemeScope(ActionSheetItemInner);
@@ -1,3 +0,0 @@
1
- export { ActionSheet } from './ActionSheet';
2
- export { ActionSheetItem } from './ActionSheetItem';
3
- export type { ActionSheetItemProps, ActionSheetProps } from './types';
@@ -1,19 +0,0 @@
1
- import type { ZoraComponentMeta } from '../../metadata';
2
-
3
- export const actionSheetMeta = {
4
- name: 'ActionSheet',
5
- category: 'component',
6
- directManifestNode: false,
7
- allowedChildren: [],
8
- note: 'Surface-backed contextual action sheet; not represented as a manifest node in v1.',
9
- props: {},
10
- } as const satisfies ZoraComponentMeta;
11
-
12
- export const actionSheetItemMeta = {
13
- name: 'ActionSheetItem',
14
- category: 'component',
15
- directManifestNode: false,
16
- allowedChildren: [],
17
- note: 'Surface-backed contextual action sheet row; not represented as a manifest node in v1.',
18
- props: {},
19
- } as const satisfies ZoraComponentMeta;
@@ -1,23 +0,0 @@
1
- import type {
2
- ActionSheetItemProps as SurfaceActionSheetItemProps,
3
- ActionSheetProps as SurfaceActionSheetProps,
4
- ButtonIconSpec,
5
- } from '@ankhorage/surface';
6
- import type React from 'react';
7
-
8
- import type { ZoraColor } from '../../internal/recipes';
9
- import type { ZoraBaseProps } from '../../theme/ZoraBaseProps';
10
-
11
- export interface ActionSheetProps
12
- extends ZoraBaseProps, Omit<SurfaceActionSheetProps, 'children' | 'mode' | 'themeId'> {
13
- children?: React.ReactNode;
14
- }
15
-
16
- export interface ActionSheetItemProps
17
- extends
18
- ZoraBaseProps,
19
- Omit<SurfaceActionSheetItemProps, 'color' | 'leading' | 'mode' | 'themeId'> {
20
- color?: ZoraColor;
21
- icon?: ButtonIconSpec;
22
- leading?: React.ReactNode;
23
- }
@@ -1,82 +0,0 @@
1
- import React from 'react';
2
-
3
- import { Image } from '../../components/image';
4
- import { Text } from '../../components/text';
5
- import { Box, Stack } from '../../foundation';
6
- import { useZoraTheme } from '../../theme/useZoraTheme';
7
- import { withZoraThemeScope } from '../../theme/withZoraThemeScope';
8
- import type { ImagePreviewProps, ZoraImageAsset } from './types';
9
-
10
- function resolveRenderableUrl(asset: ZoraImageAsset | null | undefined): string | null {
11
- if (!asset) return null;
12
-
13
- if (asset.kind === 'url') {
14
- return asset.url;
15
- }
16
-
17
- return asset.publicUrl ?? null;
18
- }
19
-
20
- function resolveSafeAspectRatio(value: number | undefined): number {
21
- if (!value || !Number.isFinite(value) || value <= 0) {
22
- return 1;
23
- }
24
-
25
- return value;
26
- }
27
-
28
- function ImagePreviewInner({
29
- themeId: _themeId,
30
- mode: _mode,
31
- testID,
32
- asset,
33
- aspectRatio,
34
- fit = 'cover',
35
- emptyTitle = 'No image',
36
- emptyDescription = 'Select an image to preview it here.',
37
- interactionPolicy: _interactionPolicy,
38
- }: ImagePreviewProps) {
39
- const { theme } = useZoraTheme();
40
- const renderableUrl = resolveRenderableUrl(asset);
41
- const resolvedAspectRatio = resolveSafeAspectRatio(aspectRatio);
42
-
43
- return (
44
- <Box
45
- bg={theme.semantics.neutral.surface}
46
- borderColor={theme.semantics.neutral.divider}
47
- borderWidth={1}
48
- radius="l"
49
- testID={testID}
50
- style={{ overflow: 'hidden' }}
51
- >
52
- {renderableUrl ? (
53
- <Box style={{ aspectRatio: resolvedAspectRatio, width: '100%' }}>
54
- <Image
55
- alt={asset?.alt}
56
- fit={fit}
57
- source={renderableUrl}
58
- style={{ height: '100%', width: '100%' }}
59
- />
60
- </Box>
61
- ) : (
62
- <Box p="l">
63
- <Stack gap="xs">
64
- <Text variant="label" weight="semiBold">
65
- {emptyTitle}
66
- </Text>
67
- <Text emphasis="muted" variant="bodySmall">
68
- {emptyDescription}
69
- </Text>
70
- </Stack>
71
- </Box>
72
- )}
73
- </Box>
74
- );
75
- }
76
-
77
- /***
78
- * Preview pattern for showing an image with optional metadata and actions.
79
- *
80
-
81
- */
82
- export const ImagePreview = withZoraThemeScope(ImagePreviewInner);
@@ -1,2 +0,0 @@
1
- export { ImagePreview } from './ImagePreview';
2
- export type { ImagePreviewProps, ZoraImageAsset, ZoraImageMetadata } from './types';
@@ -1,10 +0,0 @@
1
- import type { ZoraComponentMeta } from '../../metadata';
2
-
3
- export const imagePreviewMeta = {
4
- name: 'ImagePreview',
5
- category: 'pattern',
6
- directManifestNode: false,
7
- allowedChildren: [],
8
- note: 'Media pattern; not represented as a manifest node in v1.',
9
- props: {},
10
- } as const satisfies ZoraComponentMeta;
@@ -1,41 +0,0 @@
1
- import type React from 'react';
2
-
3
- import type { ImageFit } from '../../components/image';
4
- import type { ZoraBaseProps } from '../../theme/ZoraBaseProps';
5
-
6
- export interface ZoraImageMetadata {
7
- fileName?: string;
8
- sizeBytes?: number;
9
- createdAt?: string;
10
- }
11
-
12
- export type ZoraImageAsset =
13
- | {
14
- kind: 'url';
15
- url: string;
16
- alt?: string;
17
- width?: number;
18
- height?: number;
19
- contentType?: string;
20
- metadata?: ZoraImageMetadata;
21
- }
22
- | {
23
- kind: 'storage';
24
- storageId?: string;
25
- bucket: string;
26
- path: string;
27
- publicUrl?: string;
28
- alt?: string;
29
- width?: number;
30
- height?: number;
31
- contentType?: string;
32
- metadata?: ZoraImageMetadata;
33
- };
34
-
35
- export interface ImagePreviewProps extends ZoraBaseProps {
36
- asset?: ZoraImageAsset | null;
37
- aspectRatio?: number;
38
- fit?: ImageFit;
39
- emptyTitle?: React.ReactNode;
40
- emptyDescription?: React.ReactNode;
41
- }
@@ -1,319 +0,0 @@
1
- import React from 'react';
2
-
3
- import { Button } from '../../components/button';
4
- import { Modal } from '../../components/modal';
5
- import { Progress } from '../../components/progress';
6
- import { Text } from '../../components/text';
7
- import { Box, Stack } from '../../foundation';
8
- import { withZoraThemeScope } from '../../theme/withZoraThemeScope';
9
- import { FormField } from '../form-field';
10
- import { ImagePreview } from '../image-preview';
11
- import type { ImageUploadFieldProps } from './types';
12
- import {
13
- createOptimisticAssetFromPicked,
14
- formatAcceptHint,
15
- formatMaxSizeHint,
16
- resolveRenderableUrl,
17
- validatePickedImage,
18
- } from './uploadFlow';
19
-
20
- function clampProgress(value: number | null): number | null {
21
- if (value === null) return null;
22
- if (!Number.isFinite(value)) return null;
23
- return Math.max(0, Math.min(1, value));
24
- }
25
-
26
- function formatUnknownError(error: unknown): string {
27
- if (error instanceof Error && error.message) return error.message;
28
- if (typeof error === 'string' && error.trim().length > 0) return error;
29
- return 'Something went wrong.';
30
- }
31
-
32
- function ImageUploadFieldInner({
33
- themeId: _themeId,
34
- mode: _mode,
35
- testID,
36
- value,
37
- onChange,
38
- label,
39
- description,
40
- helperText,
41
- errorText,
42
- required,
43
- disabled = false,
44
- readOnly = false,
45
- accept,
46
- maxSizeBytes,
47
- validatePicked,
48
- onPick,
49
- onUpload,
50
- onRemove,
51
- aspectRatio = 1,
52
- previewTitle = 'Image preview',
53
- previewDescription,
54
- interactionPolicy,
55
- }: ImageUploadFieldProps) {
56
- const passive = interactionPolicy === 'passive';
57
- const [internalError, setInternalError] = React.useState<string | undefined>(undefined);
58
- const [uploading, setUploading] = React.useState(false);
59
- const [removing, setRemoving] = React.useState(false);
60
- const [progress, setProgress] = React.useState<number | null>(null);
61
- const [previewOpen, setPreviewOpen] = React.useState(false);
62
- const isMountedRef = React.useRef(true);
63
-
64
- React.useEffect(() => {
65
- return () => {
66
- isMountedRef.current = false;
67
- };
68
- }, []);
69
-
70
- const renderableUrl = resolveRenderableUrl(value);
71
- const isRenderable = renderableUrl !== null;
72
- const actionsDisabled = disabled || readOnly;
73
-
74
- const effectiveError = errorText ?? internalError;
75
- const invalid = Boolean(effectiveError);
76
-
77
- const acceptHint = formatAcceptHint(accept);
78
- const maxSizeHint = formatMaxSizeHint(maxSizeBytes);
79
-
80
- const setProgressSafe = React.useCallback((next: number | null) => {
81
- if (!isMountedRef.current) return;
82
- setProgress(clampProgress(next));
83
- }, []);
84
-
85
- const clearTransientState = React.useCallback(() => {
86
- if (!isMountedRef.current) return;
87
- setUploading(false);
88
- setRemoving(false);
89
- setProgress(null);
90
- }, []);
91
-
92
- const handleReplace = React.useCallback(async () => {
93
- if (passive || actionsDisabled || uploading || removing) return;
94
-
95
- setInternalError(undefined);
96
- let picked;
97
- try {
98
- picked = await onPick();
99
- } catch (error) {
100
- if (!isMountedRef.current) return;
101
- setInternalError(formatUnknownError(error));
102
- return;
103
- }
104
- if (!picked) return;
105
-
106
- const validationError = validatePickedImage({
107
- picked,
108
- accept,
109
- maxSizeBytes,
110
- validatePicked,
111
- });
112
-
113
- if (validationError) {
114
- setInternalError(validationError);
115
- return;
116
- }
117
-
118
- if (!onUpload) {
119
- onChange(createOptimisticAssetFromPicked(picked));
120
- return;
121
- }
122
-
123
- const optimisticAsset = createOptimisticAssetFromPicked(picked);
124
- onChange(optimisticAsset);
125
- setUploading(true);
126
- setProgressSafe(0);
127
-
128
- try {
129
- const uploaded = await onUpload(picked, { setProgress: setProgressSafe });
130
- if (!isMountedRef.current) return;
131
- onChange(uploaded);
132
- setInternalError(undefined);
133
- clearTransientState();
134
- } catch (error) {
135
- if (!isMountedRef.current) return;
136
- setInternalError(formatUnknownError(error));
137
- setUploading(false);
138
- setProgress(null);
139
- }
140
- }, [
141
- accept,
142
- actionsDisabled,
143
- clearTransientState,
144
- maxSizeBytes,
145
- onChange,
146
- onPick,
147
- onUpload,
148
- passive,
149
- removing,
150
- setProgressSafe,
151
- uploading,
152
- validatePicked,
153
- ]);
154
-
155
- const handleRemove = React.useCallback(async () => {
156
- if (passive || actionsDisabled || uploading || removing) return;
157
- if (!value) {
158
- onChange(null);
159
- return;
160
- }
161
-
162
- setInternalError(undefined);
163
-
164
- if (!onRemove) {
165
- onChange(null);
166
- return;
167
- }
168
-
169
- setRemoving(true);
170
- try {
171
- await onRemove(value);
172
- if (!isMountedRef.current) return;
173
- onChange(null);
174
- setRemoving(false);
175
- } catch (error) {
176
- if (!isMountedRef.current) return;
177
- setInternalError(formatUnknownError(error));
178
- setRemoving(false);
179
- }
180
- }, [actionsDisabled, onChange, onRemove, passive, removing, uploading, value]);
181
-
182
- const handlePreview = React.useCallback(() => {
183
- if (passive || !isRenderable) return;
184
- setPreviewOpen(true);
185
- }, [isRenderable, passive]);
186
-
187
- const closePreview = React.useCallback(() => {
188
- if (passive) return;
189
- setPreviewOpen(false);
190
- }, [passive]);
191
-
192
- return (
193
- <>
194
- <FormField
195
- description={description}
196
- disabled={disabled}
197
- errorText={effectiveError}
198
- helperText={helperText}
199
- interactionPolicy={interactionPolicy}
200
- invalid={invalid}
201
- label={label}
202
- readOnly={readOnly}
203
- required={required}
204
- testID={testID}
205
- >
206
- <Stack gap="m">
207
- <ImagePreview
208
- aspectRatio={aspectRatio}
209
- asset={value}
210
- emptyDescription={actionsDisabled ? 'No image available.' : undefined}
211
- />
212
-
213
- {acceptHint || maxSizeHint ? (
214
- <Stack gap="xs">
215
- {acceptHint ? (
216
- <Text emphasis="muted" variant="caption">
217
- {acceptHint}
218
- </Text>
219
- ) : null}
220
- {maxSizeHint ? (
221
- <Text emphasis="muted" variant="caption">
222
- {maxSizeHint}
223
- </Text>
224
- ) : null}
225
- </Stack>
226
- ) : null}
227
-
228
- {uploading ? (
229
- <Stack gap="xs">
230
- <Text emphasis="muted" variant="caption">
231
- Uploading…
232
- </Text>
233
- {progress !== null ? <Progress max={1} value={progress} /> : null}
234
- </Stack>
235
- ) : null}
236
-
237
- <Stack direction={{ base: 'column', md: 'row' }} gap="s">
238
- <Button
239
- disabled={actionsDisabled || uploading || removing}
240
- interactionPolicy={interactionPolicy}
241
- onPress={() => {
242
- void handleReplace();
243
- }}
244
- >
245
- {value ? 'Replace image' : 'Select image'}
246
- </Button>
247
-
248
- {value ? (
249
- <Button
250
- disabled={actionsDisabled || uploading || removing}
251
- variant="outline"
252
- loading={removing}
253
- color="danger"
254
- interactionPolicy={interactionPolicy}
255
- onPress={() => {
256
- void handleRemove();
257
- }}
258
- >
259
- Remove
260
- </Button>
261
- ) : null}
262
-
263
- {isRenderable ? (
264
- <Button
265
- disabled={false}
266
- interactionPolicy={interactionPolicy}
267
- variant="soft"
268
- color="neutral"
269
- onPress={handlePreview}
270
- >
271
- Preview
272
- </Button>
273
- ) : null}
274
- </Stack>
275
-
276
- {!isRenderable && value?.kind === 'storage' && value.publicUrl === undefined ? (
277
- <Box>
278
- <Text emphasis="muted" variant="caption">
279
- This image is stored, but no preview URL is available yet.
280
- </Text>
281
- </Box>
282
- ) : null}
283
- </Stack>
284
- </FormField>
285
-
286
- {isRenderable ? (
287
- <Modal
288
- closeOnBackdrop
289
- description={previewDescription}
290
- interactionPolicy={interactionPolicy}
291
- onDismiss={closePreview}
292
- title={previewTitle}
293
- visible={previewOpen}
294
- >
295
- <Stack gap="m">
296
- <ImagePreview asset={value} aspectRatio={aspectRatio} />
297
- <Stack direction="row" justify="flex-end">
298
- <Button
299
- interactionPolicy={interactionPolicy}
300
- variant="soft"
301
- color="neutral"
302
- onPress={closePreview}
303
- >
304
- Close
305
- </Button>
306
- </Stack>
307
- </Stack>
308
- </Modal>
309
- ) : null}
310
- </>
311
- );
312
- }
313
-
314
- /***
315
- * Form field pattern for picking, previewing, and uploading an image.
316
- *
317
-
318
- */
319
- export const ImageUploadField = withZoraThemeScope(ImageUploadFieldInner);
@@ -1,2 +0,0 @@
1
- export { ImageUploadField } from './ImageUploadField';
2
- export type { ImageUploadFieldProps, ImageUploadProgressContext, ZoraPickedImage } from './types';