@ankhorage/zora 6.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 (126) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +2 -1
  3. package/dist/components/image/Image.d.ts +1 -5
  4. package/dist/components/image/Image.d.ts.map +1 -1
  5. package/dist/components/image/Image.js +79 -8
  6. package/dist/components/image/Image.js.map +1 -1
  7. package/dist/components/image/types.d.ts +4 -2
  8. package/dist/components/image/types.d.ts.map +1 -1
  9. package/dist/components/image/types.js.map +1 -1
  10. package/dist/features/uploader/adapters/inbound/Uploader.d.ts +5 -0
  11. package/dist/features/uploader/adapters/inbound/Uploader.d.ts.map +1 -0
  12. package/dist/features/uploader/adapters/inbound/Uploader.js +274 -0
  13. package/dist/features/uploader/adapters/inbound/Uploader.js.map +1 -0
  14. package/dist/features/uploader/adapters/outbound/pickDocumentOrFileAsync.d.ts +5 -0
  15. package/dist/features/uploader/adapters/outbound/pickDocumentOrFileAsync.d.ts.map +1 -0
  16. package/dist/features/uploader/adapters/outbound/pickDocumentOrFileAsync.js +38 -0
  17. package/dist/features/uploader/adapters/outbound/pickDocumentOrFileAsync.js.map +1 -0
  18. package/dist/features/uploader/adapters/outbound/pickImageOrVideoAsync.d.ts +5 -0
  19. package/dist/features/uploader/adapters/outbound/pickImageOrVideoAsync.d.ts.map +1 -0
  20. package/dist/features/uploader/adapters/outbound/pickImageOrVideoAsync.js +24 -0
  21. package/dist/features/uploader/adapters/outbound/pickImageOrVideoAsync.js.map +1 -0
  22. package/dist/features/uploader/application/ports/outbound/UploadPickerPort.d.ts +9 -0
  23. package/dist/features/uploader/application/ports/outbound/UploadPickerPort.d.ts.map +1 -0
  24. package/dist/features/uploader/application/ports/outbound/UploadPickerPort.js +2 -0
  25. package/dist/features/uploader/application/ports/outbound/UploadPickerPort.js.map +1 -0
  26. package/dist/features/uploader/application/use-cases/pickUploadAssetAsync.d.ts +5 -0
  27. package/dist/features/uploader/application/use-cases/pickUploadAssetAsync.d.ts.map +1 -0
  28. package/dist/features/uploader/application/use-cases/pickUploadAssetAsync.js +6 -0
  29. package/dist/features/uploader/application/use-cases/pickUploadAssetAsync.js.map +1 -0
  30. package/dist/features/uploader/application/use-cases/validateUploadAsset.d.ts +4 -0
  31. package/dist/features/uploader/application/use-cases/validateUploadAsset.d.ts.map +1 -0
  32. package/dist/features/uploader/application/use-cases/validateUploadAsset.js +77 -0
  33. package/dist/features/uploader/application/use-cases/validateUploadAsset.js.map +1 -0
  34. package/dist/features/uploader/composition/createUploadPicker.d.ts +4 -0
  35. package/dist/features/uploader/composition/createUploadPicker.d.ts.map +1 -0
  36. package/dist/features/uploader/composition/createUploadPicker.js +12 -0
  37. package/dist/features/uploader/composition/createUploadPicker.js.map +1 -0
  38. package/dist/features/uploader/meta.d.ts +10 -0
  39. package/dist/features/uploader/meta.d.ts.map +1 -0
  40. package/dist/features/uploader/meta.js +10 -0
  41. package/dist/features/uploader/meta.js.map +1 -0
  42. package/dist/features/uploader/public.d.ts +4 -0
  43. package/dist/features/uploader/public.d.ts.map +1 -0
  44. package/dist/features/uploader/public.js +3 -0
  45. package/dist/features/uploader/public.js.map +1 -0
  46. package/dist/index.d.ts +2 -4
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +1 -2
  49. package/dist/index.js.map +1 -1
  50. package/dist/metadata/componentMeta.d.ts.map +1 -1
  51. package/dist/metadata/componentMeta.js +2 -4
  52. package/dist/metadata/componentMeta.js.map +1 -1
  53. package/dist/registry.d.ts.map +1 -1
  54. package/dist/registry.js +2 -4
  55. package/dist/registry.js.map +1 -1
  56. package/dist/types/upload.d.ts +53 -0
  57. package/dist/types/upload.d.ts.map +1 -0
  58. package/dist/types/upload.js +2 -0
  59. package/dist/types/upload.js.map +1 -0
  60. package/package.json +11 -2
  61. package/src/components/image/Image.tsx +136 -8
  62. package/src/components/image/types.ts +6 -2
  63. package/src/features/uploader/adapters/inbound/Uploader.tsx +403 -0
  64. package/src/features/uploader/adapters/outbound/pickDocumentOrFileAsync.ts +50 -0
  65. package/src/features/uploader/adapters/outbound/pickImageOrVideoAsync.ts +30 -0
  66. package/src/features/uploader/application/ports/outbound/UploadPickerPort.ts +10 -0
  67. package/src/features/uploader/application/use-cases/pickUploadAssetAsync.test.ts +48 -0
  68. package/src/features/uploader/application/use-cases/pickUploadAssetAsync.ts +14 -0
  69. package/src/features/uploader/application/use-cases/validateUploadAsset.test.ts +51 -0
  70. package/src/features/uploader/application/use-cases/validateUploadAsset.ts +111 -0
  71. package/src/features/uploader/composition/createUploadPicker.ts +14 -0
  72. package/src/features/uploader/meta.ts +11 -0
  73. package/src/features/uploader/public.test.ts +34 -0
  74. package/src/features/uploader/public.ts +10 -0
  75. package/src/index.ts +9 -12
  76. package/src/metadata/componentMeta.ts +2 -4
  77. package/src/registry.test.ts +1 -1
  78. package/src/registry.ts +2 -4
  79. package/src/showcaseCoverage.test.ts +1 -2
  80. package/src/types/upload.ts +62 -0
  81. package/dist/patterns/image-preview/ImagePreview.d.ts +0 -9
  82. package/dist/patterns/image-preview/ImagePreview.d.ts.map +0 -1
  83. package/dist/patterns/image-preview/ImagePreview.js +0 -46
  84. package/dist/patterns/image-preview/ImagePreview.js.map +0 -1
  85. package/dist/patterns/image-preview/index.d.ts +0 -3
  86. package/dist/patterns/image-preview/index.d.ts.map +0 -1
  87. package/dist/patterns/image-preview/index.js +0 -2
  88. package/dist/patterns/image-preview/index.js.map +0 -1
  89. package/dist/patterns/image-preview/meta.d.ts +0 -9
  90. package/dist/patterns/image-preview/meta.d.ts.map +0 -1
  91. package/dist/patterns/image-preview/meta.js +0 -9
  92. package/dist/patterns/image-preview/meta.js.map +0 -1
  93. package/dist/patterns/image-preview/types.d.ts +0 -36
  94. package/dist/patterns/image-preview/types.d.ts.map +0 -1
  95. package/dist/patterns/image-preview/types.js +0 -2
  96. package/dist/patterns/image-preview/types.js.map +0 -1
  97. package/dist/patterns/image-upload-field/ImageUploadField.d.ts +0 -9
  98. package/dist/patterns/image-upload-field/ImageUploadField.d.ts.map +0 -1
  99. package/dist/patterns/image-upload-field/ImageUploadField.js +0 -222
  100. package/dist/patterns/image-upload-field/ImageUploadField.js.map +0 -1
  101. package/dist/patterns/image-upload-field/index.d.ts +0 -3
  102. package/dist/patterns/image-upload-field/index.d.ts.map +0 -1
  103. package/dist/patterns/image-upload-field/index.js +0 -2
  104. package/dist/patterns/image-upload-field/index.js.map +0 -1
  105. package/dist/patterns/image-upload-field/meta.d.ts +0 -9
  106. package/dist/patterns/image-upload-field/meta.d.ts.map +0 -1
  107. package/dist/patterns/image-upload-field/meta.js +0 -9
  108. package/dist/patterns/image-upload-field/meta.js.map +0 -1
  109. package/dist/patterns/image-upload-field/types.d.ts +0 -35
  110. package/dist/patterns/image-upload-field/types.d.ts.map +0 -1
  111. package/dist/patterns/image-upload-field/types.js +0 -2
  112. package/dist/patterns/image-upload-field/types.js.map +0 -1
  113. package/dist/patterns/image-upload-field/uploadFlow.d.ts +0 -18
  114. package/dist/patterns/image-upload-field/uploadFlow.d.ts.map +0 -1
  115. package/dist/patterns/image-upload-field/uploadFlow.js +0 -106
  116. package/dist/patterns/image-upload-field/uploadFlow.js.map +0 -1
  117. package/src/patterns/image-preview/ImagePreview.tsx +0 -82
  118. package/src/patterns/image-preview/index.ts +0 -2
  119. package/src/patterns/image-preview/meta.ts +0 -10
  120. package/src/patterns/image-preview/types.ts +0 -41
  121. package/src/patterns/image-upload-field/ImageUploadField.tsx +0 -319
  122. package/src/patterns/image-upload-field/index.ts +0 -2
  123. package/src/patterns/image-upload-field/meta.ts +0 -10
  124. package/src/patterns/image-upload-field/types.ts +0 -41
  125. package/src/patterns/image-upload-field/uploadFlow.test.ts +0 -117
  126. package/src/patterns/image-upload-field/uploadFlow.ts +0 -145
@@ -0,0 +1,111 @@
1
+ import type { ValidateUploadAssetInput } from '../../../../types/upload';
2
+
3
+ /*** Validates a picked upload asset against MIME, extension, size, and caller rules. */
4
+ export function validateUploadAsset({
5
+ asset,
6
+ accept,
7
+ maxSizeBytes,
8
+ validate,
9
+ }: ValidateUploadAssetInput): string | undefined {
10
+ if (!isAccepted(asset.contentType, asset.fileName, accept)) {
11
+ return accept ? `File type not accepted. Expected ${accept}.` : 'File type not accepted.';
12
+ }
13
+
14
+ if (
15
+ maxSizeBytes !== undefined &&
16
+ Number.isFinite(maxSizeBytes) &&
17
+ maxSizeBytes > 0 &&
18
+ asset.sizeBytes !== undefined &&
19
+ Number.isFinite(asset.sizeBytes) &&
20
+ asset.sizeBytes > maxSizeBytes
21
+ ) {
22
+ return `File is too large (${formatBytes(asset.sizeBytes)}). Max ${formatBytes(maxSizeBytes)}.`;
23
+ }
24
+
25
+ return validate?.(asset);
26
+ }
27
+
28
+ /*** Returns whether available file metadata satisfies at least one accept token. */
29
+ function isAccepted(
30
+ contentType: string | undefined,
31
+ fileName: string | undefined,
32
+ accept: string | undefined,
33
+ ): boolean {
34
+ const tokens = parseAccept(accept);
35
+ if (tokens.length === 0) {
36
+ return true;
37
+ }
38
+
39
+ let hadSignal = false;
40
+
41
+ for (const token of tokens) {
42
+ const matches = matchesAcceptToken(token, contentType, fileName);
43
+ if (matches === null) {
44
+ continue;
45
+ }
46
+
47
+ hadSignal = true;
48
+ if (matches) {
49
+ return true;
50
+ }
51
+ }
52
+
53
+ return !hadSignal;
54
+ }
55
+
56
+ /*** Matches one MIME or extension accept token against available file metadata. */
57
+ function matchesAcceptToken(
58
+ token: string,
59
+ contentType: string | undefined,
60
+ fileName: string | undefined,
61
+ ): boolean | null {
62
+ const normalizedToken = token.toLowerCase();
63
+ const normalizedContentType = contentType?.toLowerCase();
64
+ const normalizedFileName = fileName?.toLowerCase();
65
+
66
+ if (normalizedToken.startsWith('.')) {
67
+ return normalizedFileName ? normalizedFileName.endsWith(normalizedToken) : null;
68
+ }
69
+
70
+ if (normalizedToken.endsWith('/*')) {
71
+ if (!normalizedContentType) {
72
+ return null;
73
+ }
74
+
75
+ return normalizedContentType.startsWith(normalizedToken.slice(0, -1));
76
+ }
77
+
78
+ return normalizedContentType ? normalizedContentType === normalizedToken : null;
79
+ }
80
+
81
+ /*** Parses a browser-style accept string into normalized non-empty tokens. */
82
+ function parseAccept(accept: string | undefined): readonly string[] {
83
+ if (!accept) {
84
+ return [];
85
+ }
86
+
87
+ return accept
88
+ .split(',')
89
+ .map((token) => token.trim())
90
+ .filter((token) => token.length > 0);
91
+ }
92
+
93
+ /*** Formats a byte count for concise upload validation feedback. */
94
+ function formatBytes(value: number): string {
95
+ if (!Number.isFinite(value) || value <= 0) {
96
+ return '0 B';
97
+ }
98
+
99
+ const units = ['B', 'KB', 'MB', 'GB'] as const;
100
+ let size = value;
101
+ let unitIndex = 0;
102
+
103
+ while (size >= 1024 && unitIndex < units.length - 1) {
104
+ size /= 1024;
105
+ unitIndex += 1;
106
+ }
107
+
108
+ const unit = units.at(unitIndex) ?? 'B';
109
+ const rounded = unitIndex === 0 ? Math.round(size) : Math.round(size * 10) / 10;
110
+ return `${rounded} ${unit}`;
111
+ }
@@ -0,0 +1,14 @@
1
+ import { pickDocumentOrFileAsync } from '../adapters/outbound/pickDocumentOrFileAsync';
2
+ import { pickImageOrVideoAsync } from '../adapters/outbound/pickImageOrVideoAsync';
3
+ import type { UploadPickerPort } from '../application/ports/outbound/UploadPickerPort';
4
+ import { pickUploadAssetAsync } from '../application/use-cases/pickUploadAssetAsync';
5
+
6
+ /*** Wires the Expo picker adapters into the generic upload picker use case. */
7
+ export function createUploadPicker(): UploadPickerPort {
8
+ const imageOrVideoPicker: UploadPickerPort = { pickAsync: pickImageOrVideoAsync };
9
+ const documentOrFilePicker: UploadPickerPort = { pickAsync: pickDocumentOrFileAsync };
10
+
11
+ return {
12
+ pickAsync: (input) => pickUploadAssetAsync(input, imageOrVideoPicker, documentOrFilePicker),
13
+ };
14
+ }
@@ -0,0 +1,11 @@
1
+ import type { ZoraComponentMeta } from '../../metadata';
2
+
3
+ export const uploaderMeta = {
4
+ name: 'Uploader',
5
+ category: 'pattern',
6
+ description: 'Picks and manages one image, video, document, or generic file upload.',
7
+ directManifestNode: false,
8
+ allowedChildren: [],
9
+ note: 'Upload transport callbacks remain runtime-owned and are not serialized into AppManifest.',
10
+ props: {},
11
+ } as const satisfies ZoraComponentMeta;
@@ -0,0 +1,34 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+
3
+ describe('ZORA Uploader public contract', () => {
4
+ test('publishes the generic uploader through root and explicit subpath APIs', async () => {
5
+ const packageJson = (await Bun.file('package.json').json()) as {
6
+ dependencies: Readonly<Record<string, string>>;
7
+ exports: Readonly<Record<string, unknown>>;
8
+ };
9
+ const [rootSource, publicSource] = await Promise.all([
10
+ Bun.file('src/index.ts').text(),
11
+ Bun.file('src/features/uploader/public.ts').text(),
12
+ ]);
13
+
14
+ expect(packageJson.exports['./uploader']).toBeDefined();
15
+ expect(packageJson.dependencies['expo-image-picker']).toMatch(/^~57\./u);
16
+ expect(packageJson.dependencies['expo-document-picker']).toMatch(/^~57\./u);
17
+ expect(rootSource).toContain("from './features/uploader/public'");
18
+ expect(publicSource).toContain('export { Uploader }');
19
+ expect(publicSource).toContain('export { validateUploadAsset }');
20
+ });
21
+
22
+ test('removes the superseded image preview and upload field APIs', async () => {
23
+ const [rootSource, registrySource, metadataSource] = await Promise.all([
24
+ Bun.file('src/index.ts').text(),
25
+ Bun.file('src/registry.ts').text(),
26
+ Bun.file('src/metadata/componentMeta.ts').text(),
27
+ ]);
28
+ const publicSurface = `${rootSource}\n${registrySource}\n${metadataSource}`;
29
+
30
+ expect(publicSurface).not.toContain('ImagePreview');
31
+ expect(publicSurface).not.toContain('ImageUploadField');
32
+ expect(publicSurface).toContain('Uploader');
33
+ });
34
+ });
@@ -0,0 +1,10 @@
1
+ export type {
2
+ UploadAsset,
3
+ UploadAssetBase,
4
+ UploaderProps,
5
+ UploadProgressContext,
6
+ UploadType,
7
+ ValidateUploadAssetInput,
8
+ } from '../../types/upload';
9
+ export { Uploader } from './adapters/inbound/Uploader';
10
+ export { validateUploadAsset } from './application/use-cases/validateUploadAsset';
package/src/index.ts CHANGED
@@ -146,6 +146,15 @@ export type { ToolbarActionProps, ToolbarPosition, ToolbarProps } from './compon
146
146
  export { Toolbar, ToolbarAction } from './components/toolbar';
147
147
  export { ZORA_CORE_PLUGIN } from './corePlugin';
148
148
  export { ZORA_CORE_PLUGIN_METADATA } from './corePluginMetadata';
149
+ export type {
150
+ UploadAsset,
151
+ UploadAssetBase,
152
+ UploaderProps,
153
+ UploadProgressContext,
154
+ UploadType,
155
+ ValidateUploadAssetInput,
156
+ } from './features/uploader/public';
157
+ export { Uploader, validateUploadAsset } from './features/uploader/public';
149
158
  export type {
150
159
  BoxProps,
151
160
  CenterProps,
@@ -284,18 +293,6 @@ export type { FilterBarProps } from './patterns/filter-bar';
284
293
  export { FilterBar } from './patterns/filter-bar';
285
294
  export type { HeroAction, HeroAlign, HeroLayout, HeroProps, HeroTone } from './patterns/hero';
286
295
  export { Hero } from './patterns/hero';
287
- export type {
288
- ImagePreviewProps,
289
- ZoraImageAsset,
290
- ZoraImageMetadata,
291
- } from './patterns/image-preview';
292
- export { ImagePreview } from './patterns/image-preview';
293
- export type {
294
- ImageUploadFieldProps,
295
- ImageUploadProgressContext,
296
- ZoraPickedImage,
297
- } from './patterns/image-upload-field';
298
- export { ImageUploadField } from './patterns/image-upload-field';
299
296
  export type { InspectorFieldProps } from './patterns/inspector-field';
300
297
  export { InspectorField } from './patterns/inspector-field';
301
298
  export type {
@@ -41,6 +41,7 @@ import { textareaMeta } from '../components/textarea/meta';
41
41
  import { timePickerMeta } from '../components/time-picker/meta';
42
42
  import { toastMeta, toastProviderMeta } from '../components/toast/meta';
43
43
  import { toolbarActionMeta, toolbarMeta } from '../components/toolbar/meta';
44
+ import { uploaderMeta } from '../features/uploader/meta';
44
45
  import { foundationMetas } from '../foundation/meta';
45
46
  import { appShellMeta } from '../layout/app-shell/meta';
46
47
  import { screenMeta } from '../layout/screen/meta';
@@ -64,8 +65,6 @@ import { disclosureSectionMeta } from '../patterns/disclosure-section/meta';
64
65
  import { emptyStateMeta } from '../patterns/empty-state/meta';
65
66
  import { filterBarMeta } from '../patterns/filter-bar/meta';
66
67
  import { heroMeta } from '../patterns/hero/meta';
67
- import { imagePreviewMeta } from '../patterns/image-preview/meta';
68
- import { imageUploadFieldMeta } from '../patterns/image-upload-field/meta';
69
68
  import { inspectorFieldMeta } from '../patterns/inspector-field/meta';
70
69
  import { listMeta, listRowMeta, listSectionMeta } from '../patterns/list/meta';
71
70
  import { messageBubbleMeta } from '../patterns/message-bubble/meta';
@@ -143,6 +142,7 @@ export const ZORA_COMPONENT_META: ZoraComponentMetaRegistry = {
143
142
  ToastProvider: toastProviderMeta,
144
143
  Toolbar: toolbarMeta,
145
144
  ToolbarAction: toolbarActionMeta,
145
+ Uploader: uploaderMeta,
146
146
  ThemeModeToggle: themeModeToggleMeta,
147
147
  AppShell: appShellMeta,
148
148
  Screen: screenMeta,
@@ -164,8 +164,6 @@ export const ZORA_COMPONENT_META: ZoraComponentMetaRegistry = {
164
164
  EmptyState: emptyStateMeta,
165
165
  FilterBar: filterBarMeta,
166
166
  Hero: heroMeta,
167
- ImagePreview: imagePreviewMeta,
168
- ImageUploadField: imageUploadFieldMeta,
169
167
  InspectorField: inspectorFieldMeta,
170
168
  List: listMeta,
171
169
  ListRow: listRowMeta,
@@ -55,7 +55,7 @@ async function listPublicConcreteComponentExports(): Promise<readonly string[]>
55
55
  const source = await Bun.file('src/index.ts').text();
56
56
  const componentExports = Array.from(
57
57
  source.matchAll(
58
- /export\s+\{([^}]+)\}\s+from '\.\/(components\/[^']+|foundation|layout\/[^']+|patterns\/[^']+)';/g,
58
+ /export\s+\{([^}]+)\}\s+from '\.\/(components\/[^']+|features\/[^']+|foundation|layout\/[^']+|patterns\/[^']+)';/g,
59
59
  ),
60
60
  )
61
61
  .flatMap((match) => match[1].split(',').map((item) => item.trim()))
package/src/registry.ts CHANGED
@@ -39,6 +39,7 @@ import { Textarea } from './components/textarea';
39
39
  import { TimePicker } from './components/time-picker';
40
40
  import { Toast } from './components/toast';
41
41
  import { Toolbar, ToolbarAction } from './components/toolbar';
42
+ import { Uploader } from './features/uploader/public';
42
43
  import {
43
44
  Box,
44
45
  Center,
@@ -73,8 +74,6 @@ import { DisclosureSection } from './patterns/disclosure-section';
73
74
  import { EmptyState } from './patterns/empty-state';
74
75
  import { FilterBar } from './patterns/filter-bar';
75
76
  import { Hero } from './patterns/hero';
76
- import { ImagePreview } from './patterns/image-preview';
77
- import { ImageUploadField } from './patterns/image-upload-field';
78
77
  import { InspectorField } from './patterns/inspector-field';
79
78
  import { List, ListRow, ListSection } from './patterns/list';
80
79
  import { MessageBubble } from './patterns/message-bubble';
@@ -170,6 +169,7 @@ const _ZORA_COMPONENT_REGISTRY = {
170
169
  Toast,
171
170
  Toolbar,
172
171
  ToolbarAction,
172
+ Uploader,
173
173
  ThemeModeToggle,
174
174
  Box,
175
175
  Center,
@@ -201,8 +201,6 @@ const _ZORA_COMPONENT_REGISTRY = {
201
201
  EmptyState,
202
202
  FilterBar,
203
203
  Hero,
204
- ImagePreview,
205
- ImageUploadField,
206
204
  InspectorField,
207
205
  List,
208
206
  ListRow,
@@ -65,6 +65,7 @@ const REQUIRED_SHOWCASE_COVERAGE = {
65
65
  'Stack',
66
66
  'Surface',
67
67
  ],
68
+ features: ['Uploader'],
68
69
  layouts: [
69
70
  'AppShell',
70
71
  'Screen',
@@ -90,8 +91,6 @@ const REQUIRED_SHOWCASE_COVERAGE = {
90
91
  'ListRow',
91
92
  'ListSection',
92
93
  'InspectorField',
93
- 'ImagePreview',
94
- 'ImageUploadField',
95
94
  'Notice',
96
95
  'Panel',
97
96
  'ReaderSurface',
@@ -0,0 +1,62 @@
1
+ import type React from 'react';
2
+
3
+ import type { ZoraBaseProps } from '../theme/ZoraBaseProps';
4
+
5
+ export type UploadType = 'image' | 'video' | 'document' | 'file';
6
+
7
+ export interface UploadAssetBase {
8
+ fileName?: string;
9
+ sizeBytes?: number;
10
+ contentType?: string;
11
+ width?: number;
12
+ height?: number;
13
+ alt?: string;
14
+ createdAt?: string;
15
+ }
16
+
17
+ export type UploadAsset =
18
+ | (UploadAssetBase & {
19
+ kind: 'local';
20
+ uri: string;
21
+ })
22
+ | (UploadAssetBase & {
23
+ kind: 'url';
24
+ url: string;
25
+ })
26
+ | (UploadAssetBase & {
27
+ kind: 'storage';
28
+ storageId?: string;
29
+ bucket: string;
30
+ path: string;
31
+ publicUrl?: string;
32
+ });
33
+
34
+ export interface UploadProgressContext {
35
+ setProgress: (progress: number | null) => void;
36
+ }
37
+
38
+ export interface ValidateUploadAssetInput {
39
+ asset: UploadAsset;
40
+ accept?: string;
41
+ maxSizeBytes?: number;
42
+ validate?: (asset: UploadAsset) => string | undefined;
43
+ }
44
+
45
+ export interface UploaderProps extends ZoraBaseProps {
46
+ value: UploadAsset | null;
47
+ onChange: (next: UploadAsset | null) => void;
48
+ label: React.ReactNode;
49
+ description?: React.ReactNode;
50
+ helperText?: React.ReactNode;
51
+ errorText?: React.ReactNode;
52
+ type?: UploadType;
53
+ accept?: string;
54
+ maxSizeBytes?: number;
55
+ required?: boolean;
56
+ disabled?: boolean;
57
+ readOnly?: boolean;
58
+ validatePicked?: (asset: UploadAsset) => string | undefined;
59
+ onUpload?: (asset: UploadAsset, context: UploadProgressContext) => Promise<UploadAsset>;
60
+ onRemove?: (current: UploadAsset) => void | Promise<void>;
61
+ aspectRatio?: number;
62
+ }
@@ -1,9 +0,0 @@
1
- import React from 'react';
2
- import type { ImagePreviewProps } from './types';
3
- /***
4
- * Preview pattern for showing an image with optional metadata and actions.
5
- *
6
-
7
- */
8
- export declare const ImagePreview: (props: ImagePreviewProps) => React.ReactElement | null;
9
- //# sourceMappingURL=ImagePreview.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ImagePreview.d.ts","sourceRoot":"","sources":["../../../src/patterns/image-preview/ImagePreview.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAO1B,OAAO,KAAK,EAAE,iBAAiB,EAAkB,MAAM,SAAS,CAAC;AAqEjE;;;;GAIG;AACH,eAAO,MAAM,YAAY,yDAAwC,CAAC"}
@@ -1,46 +0,0 @@
1
- import React from 'react';
2
- import { Image } from '../../components/image';
3
- import { Text } from '../../components/text';
4
- import { Box, Stack } from '../../foundation';
5
- import { useZoraTheme } from '../../theme/useZoraTheme';
6
- import { withZoraThemeScope } from '../../theme/withZoraThemeScope';
7
- function resolveRenderableUrl(asset) {
8
- if (!asset)
9
- return null;
10
- if (asset.kind === 'url') {
11
- return asset.url;
12
- }
13
- return asset.publicUrl ?? null;
14
- }
15
- function resolveSafeAspectRatio(value) {
16
- if (!value || !Number.isFinite(value) || value <= 0) {
17
- return 1;
18
- }
19
- return value;
20
- }
21
- function ImagePreviewInner({ themeId: _themeId, mode: _mode, testID, asset, aspectRatio, fit = 'cover', emptyTitle = 'No image', emptyDescription = 'Select an image to preview it here.', interactionPolicy: _interactionPolicy, }) {
22
- const { theme } = useZoraTheme();
23
- const renderableUrl = resolveRenderableUrl(asset);
24
- const resolvedAspectRatio = resolveSafeAspectRatio(aspectRatio);
25
- return (<Box bg={theme.semantics.neutral.surface} borderColor={theme.semantics.neutral.divider} borderWidth={1} radius="l" testID={testID} style={{ overflow: 'hidden' }}>
26
- {renderableUrl ? (<Box style={{ aspectRatio: resolvedAspectRatio, width: '100%' }}>
27
- <Image alt={asset?.alt} fit={fit} source={renderableUrl} style={{ height: '100%', width: '100%' }}/>
28
- </Box>) : (<Box p="l">
29
- <Stack gap="xs">
30
- <Text variant="label" weight="semiBold">
31
- {emptyTitle}
32
- </Text>
33
- <Text emphasis="muted" variant="bodySmall">
34
- {emptyDescription}
35
- </Text>
36
- </Stack>
37
- </Box>)}
38
- </Box>);
39
- }
40
- /***
41
- * Preview pattern for showing an image with optional metadata and actions.
42
- *
43
-
44
- */
45
- export const ImagePreview = withZoraThemeScope(ImagePreviewInner);
46
- //# sourceMappingURL=ImagePreview.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ImagePreview.js","sourceRoot":"","sources":["../../../src/patterns/image-preview/ImagePreview.tsx"],"names":[],"mappings":"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","sourcesContent":["import React from 'react';\n\nimport { Image } from '../../components/image';\nimport { Text } from '../../components/text';\nimport { Box, Stack } from '../../foundation';\nimport { useZoraTheme } from '../../theme/useZoraTheme';\nimport { withZoraThemeScope } from '../../theme/withZoraThemeScope';\nimport type { ImagePreviewProps, ZoraImageAsset } from './types';\n\nfunction resolveRenderableUrl(asset: ZoraImageAsset | null | undefined): string | null {\n if (!asset) return null;\n\n if (asset.kind === 'url') {\n return asset.url;\n }\n\n return asset.publicUrl ?? null;\n}\n\nfunction resolveSafeAspectRatio(value: number | undefined): number {\n if (!value || !Number.isFinite(value) || value <= 0) {\n return 1;\n }\n\n return value;\n}\n\nfunction ImagePreviewInner({\n themeId: _themeId,\n mode: _mode,\n testID,\n asset,\n aspectRatio,\n fit = 'cover',\n emptyTitle = 'No image',\n emptyDescription = 'Select an image to preview it here.',\n interactionPolicy: _interactionPolicy,\n}: ImagePreviewProps) {\n const { theme } = useZoraTheme();\n const renderableUrl = resolveRenderableUrl(asset);\n const resolvedAspectRatio = resolveSafeAspectRatio(aspectRatio);\n\n return (\n <Box\n bg={theme.semantics.neutral.surface}\n borderColor={theme.semantics.neutral.divider}\n borderWidth={1}\n radius=\"l\"\n testID={testID}\n style={{ overflow: 'hidden' }}\n >\n {renderableUrl ? (\n <Box style={{ aspectRatio: resolvedAspectRatio, width: '100%' }}>\n <Image\n alt={asset?.alt}\n fit={fit}\n source={renderableUrl}\n style={{ height: '100%', width: '100%' }}\n />\n </Box>\n ) : (\n <Box p=\"l\">\n <Stack gap=\"xs\">\n <Text variant=\"label\" weight=\"semiBold\">\n {emptyTitle}\n </Text>\n <Text emphasis=\"muted\" variant=\"bodySmall\">\n {emptyDescription}\n </Text>\n </Stack>\n </Box>\n )}\n </Box>\n );\n}\n\n/***\n * Preview pattern for showing an image with optional metadata and actions.\n *\n \n */\nexport const ImagePreview = withZoraThemeScope(ImagePreviewInner);\n"]}
@@ -1,3 +0,0 @@
1
- export { ImagePreview } from './ImagePreview';
2
- export type { ImagePreviewProps, ZoraImageAsset, ZoraImageMetadata } from './types';
3
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/patterns/image-preview/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,YAAY,EAAE,iBAAiB,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC"}
@@ -1,2 +0,0 @@
1
- export { ImagePreview } from './ImagePreview';
2
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/patterns/image-preview/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC","sourcesContent":["export { ImagePreview } from './ImagePreview';\nexport type { ImagePreviewProps, ZoraImageAsset, ZoraImageMetadata } from './types';\n"]}
@@ -1,9 +0,0 @@
1
- export declare const imagePreviewMeta: {
2
- readonly name: "ImagePreview";
3
- readonly category: "pattern";
4
- readonly directManifestNode: false;
5
- readonly allowedChildren: readonly [];
6
- readonly note: "Media pattern; not represented as a manifest node in v1.";
7
- readonly props: {};
8
- };
9
- //# sourceMappingURL=meta.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"meta.d.ts","sourceRoot":"","sources":["../../../src/patterns/image-preview/meta.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,gBAAgB;;;;;;;CAOS,CAAC"}
@@ -1,9 +0,0 @@
1
- export const imagePreviewMeta = {
2
- name: 'ImagePreview',
3
- category: 'pattern',
4
- directManifestNode: false,
5
- allowedChildren: [],
6
- note: 'Media pattern; not represented as a manifest node in v1.',
7
- props: {},
8
- };
9
- //# sourceMappingURL=meta.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"meta.js","sourceRoot":"","sources":["../../../src/patterns/image-preview/meta.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,IAAI,EAAE,cAAc;IACpB,QAAQ,EAAE,SAAS;IACnB,kBAAkB,EAAE,KAAK;IACzB,eAAe,EAAE,EAAE;IACnB,IAAI,EAAE,0DAA0D;IAChE,KAAK,EAAE,EAAE;CAC2B,CAAC","sourcesContent":["import type { ZoraComponentMeta } from '../../metadata';\n\nexport const imagePreviewMeta = {\n name: 'ImagePreview',\n category: 'pattern',\n directManifestNode: false,\n allowedChildren: [],\n note: 'Media pattern; not represented as a manifest node in v1.',\n props: {},\n} as const satisfies ZoraComponentMeta;\n"]}
@@ -1,36 +0,0 @@
1
- import type React from 'react';
2
- import type { ImageFit } from '../../components/image';
3
- import type { ZoraBaseProps } from '../../theme/ZoraBaseProps';
4
- export interface ZoraImageMetadata {
5
- fileName?: string;
6
- sizeBytes?: number;
7
- createdAt?: string;
8
- }
9
- export type ZoraImageAsset = {
10
- kind: 'url';
11
- url: string;
12
- alt?: string;
13
- width?: number;
14
- height?: number;
15
- contentType?: string;
16
- metadata?: ZoraImageMetadata;
17
- } | {
18
- kind: 'storage';
19
- storageId?: string;
20
- bucket: string;
21
- path: string;
22
- publicUrl?: string;
23
- alt?: string;
24
- width?: number;
25
- height?: number;
26
- contentType?: string;
27
- metadata?: ZoraImageMetadata;
28
- };
29
- export interface ImagePreviewProps extends ZoraBaseProps {
30
- asset?: ZoraImageAsset | null;
31
- aspectRatio?: number;
32
- fit?: ImageFit;
33
- emptyTitle?: React.ReactNode;
34
- emptyDescription?: React.ReactNode;
35
- }
36
- //# sourceMappingURL=types.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/patterns/image-preview/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE/D,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,cAAc,GACtB;IACE,IAAI,EAAE,KAAK,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;CAC9B,GACD;IACE,IAAI,EAAE,SAAS,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;CAC9B,CAAC;AAEN,MAAM,WAAW,iBAAkB,SAAQ,aAAa;IACtD,KAAK,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,CAAC,EAAE,QAAQ,CAAC;IACf,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC7B,gBAAgB,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CACpC"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=types.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/patterns/image-preview/types.ts"],"names":[],"mappings":"","sourcesContent":["import type React from 'react';\n\nimport type { ImageFit } from '../../components/image';\nimport type { ZoraBaseProps } from '../../theme/ZoraBaseProps';\n\nexport interface ZoraImageMetadata {\n fileName?: string;\n sizeBytes?: number;\n createdAt?: string;\n}\n\nexport type ZoraImageAsset =\n | {\n kind: 'url';\n url: string;\n alt?: string;\n width?: number;\n height?: number;\n contentType?: string;\n metadata?: ZoraImageMetadata;\n }\n | {\n kind: 'storage';\n storageId?: string;\n bucket: string;\n path: string;\n publicUrl?: string;\n alt?: string;\n width?: number;\n height?: number;\n contentType?: string;\n metadata?: ZoraImageMetadata;\n };\n\nexport interface ImagePreviewProps extends ZoraBaseProps {\n asset?: ZoraImageAsset | null;\n aspectRatio?: number;\n fit?: ImageFit;\n emptyTitle?: React.ReactNode;\n emptyDescription?: React.ReactNode;\n}\n"]}
@@ -1,9 +0,0 @@
1
- import React from 'react';
2
- import type { ImageUploadFieldProps } from './types';
3
- /***
4
- * Form field pattern for picking, previewing, and uploading an image.
5
- *
6
-
7
- */
8
- export declare const ImageUploadField: (props: ImageUploadFieldProps) => React.ReactElement | null;
9
- //# sourceMappingURL=ImageUploadField.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ImageUploadField.d.ts","sourceRoot":"","sources":["../../../src/patterns/image-upload-field/ImageUploadField.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAU1B,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AA+SrD;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,6DAA4C,CAAC"}