@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,6 +1,8 @@
1
- import type { ImageProps as SurfaceImageProps } from '@ankhorage/surface';
1
+ import type { ImageProps as SurfaceImageProps, SurfaceImageSource } from '@ankhorage/surface';
2
2
  import type { ZoraBaseProps } from '../../theme/ZoraBaseProps';
3
+ import type { UploadAsset } from '../../types/upload';
3
4
  export type { ImageFit, SurfaceImageSource } from '@ankhorage/surface';
4
- export interface ImageProps extends ZoraBaseProps, Omit<SurfaceImageProps, 'mode' | 'themeId'> {
5
+ export interface ImageProps extends ZoraBaseProps, Omit<SurfaceImageProps, 'mode' | 'source' | 'themeId'> {
6
+ source?: SurfaceImageSource | UploadAsset | null;
5
7
  }
6
8
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/image/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,IAAI,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE1E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE/D,YAAY,EAAE,QAAQ,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAEvE,MAAM,WAAW,UAAW,SAAQ,aAAa,EAAE,IAAI,CAAC,iBAAiB,EAAE,MAAM,GAAG,SAAS,CAAC;CAAG"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/image/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,IAAI,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAE9F,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEtD,YAAY,EAAE,QAAQ,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAEvE,MAAM,WAAW,UACf,SAAQ,aAAa,EAAE,IAAI,CAAC,iBAAiB,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;IAC7E,MAAM,CAAC,EAAE,kBAAkB,GAAG,WAAW,GAAG,IAAI,CAAC;CAClD"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/components/image/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { ImageProps as SurfaceImageProps } from '@ankhorage/surface';\n\nimport type { ZoraBaseProps } from '../../theme/ZoraBaseProps';\n\nexport type { ImageFit, SurfaceImageSource } from '@ankhorage/surface';\n\nexport interface ImageProps extends ZoraBaseProps, Omit<SurfaceImageProps, 'mode' | 'themeId'> {}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/components/image/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { ImageProps as SurfaceImageProps, SurfaceImageSource } from '@ankhorage/surface';\n\nimport type { ZoraBaseProps } from '../../theme/ZoraBaseProps';\nimport type { UploadAsset } from '../../types/upload';\n\nexport type { ImageFit, SurfaceImageSource } from '@ankhorage/surface';\n\nexport interface ImageProps\n extends ZoraBaseProps, Omit<SurfaceImageProps, 'mode' | 'source' | 'themeId'> {\n source?: SurfaceImageSource | UploadAsset | null;\n}\n"]}
@@ -3,7 +3,7 @@ import type { TextAlign, TextVariant, TextWeight } from './types';
3
3
  export declare function resolveTextThemeRecipe(fields: Readonly<Record<string, ThemeRecipeOverrideValue>>): {
4
4
  variant: TextVariant | undefined;
5
5
  color: "primary" | "secondary" | "tertiary" | "quaternary" | "neutral" | "error" | "success" | "warning" | "info" | "danger" | undefined;
6
- emphasis: "default" | "muted" | "subtle" | "inverse" | undefined;
6
+ emphasis: "default" | "subtle" | "muted" | "inverse" | undefined;
7
7
  align: TextAlign | undefined;
8
8
  weight: TextWeight | undefined;
9
9
  italic: boolean | undefined;
@@ -1,9 +1,7 @@
1
1
  import React from 'react';
2
2
  import type { TimePickerProps } from './types';
3
3
  /***
4
- * Time input control with wheel selection and formatted display value.
5
- *
6
-
4
+ * Time input control with bottom-sheet selection and formatted display value.
7
5
  */
8
6
  export declare const TimePicker: (props: TimePickerProps) => React.ReactElement | null;
9
7
  //# sourceMappingURL=TimePicker.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"TimePicker.d.ts","sourceRoot":"","sources":["../../../src/components/time-picker/TimePicker.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAC;AAQ1B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AA+J/C;;;;GAIG;AACH,eAAO,MAAM,UAAU,uDAAsC,CAAC"}
1
+ {"version":3,"file":"TimePicker.d.ts","sourceRoot":"","sources":["../../../src/components/time-picker/TimePicker.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAC;AAQ1B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAwP/C;;GAEG;AACH,eAAO,MAAM,UAAU,uDAAsC,CAAC"}
@@ -1,13 +1,14 @@
1
1
  import { Field } from '@ankhorage/surface';
2
2
  import React from 'react';
3
- import { ScrollView } from 'react-native';
3
+ import { ScrollView, StyleSheet } from 'react-native';
4
+ import { useBottomSheet } from '../../features/bottom-sheet/public';
4
5
  import { Stack } from '../../foundation';
5
6
  import { withZoraThemeScope } from '../../theme/withZoraThemeScope';
6
- import { ActionSheet, ActionSheetItem } from '../action-sheet';
7
7
  import { Button } from '../button';
8
8
  import { Text } from '../text';
9
9
  const MINUTES_PER_DAY = 24 * 60;
10
10
  const DEFAULT_STEP_MINUTES = 30;
11
+ /*** Render the field label and optional supporting description. */
11
12
  function renderLabel(label, description) {
12
13
  return (<Stack gap="xs">
13
14
  <Text variant="label" weight="semiBold">
@@ -18,80 +19,102 @@ function renderLabel(label, description) {
18
19
  </Text>) : null}
19
20
  </Stack>);
20
21
  }
22
+ /*** Parse one HH:mm value to minutes since midnight. */
21
23
  function parseTimeToMinutes(value) {
22
- if (!value) {
24
+ if (!value)
23
25
  return undefined;
24
- }
25
26
  const match = /^(\d{2}):(\d{2})$/.exec(value);
26
- if (!match) {
27
+ if (!match)
27
28
  return undefined;
28
- }
29
29
  const hours = Number(match[1]);
30
30
  const minutes = Number(match[2]);
31
- if (hours < 0 || hours > 23 || minutes < 0 || minutes > 59) {
31
+ if (hours < 0 || hours > 23 || minutes < 0 || minutes > 59)
32
32
  return undefined;
33
- }
34
33
  return hours * 60 + minutes;
35
34
  }
35
+ /*** Format minutes since midnight as HH:mm. */
36
36
  function formatMinutes(value) {
37
37
  const hours = Math.floor(value / 60);
38
38
  const minutes = value % 60;
39
39
  return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`;
40
40
  }
41
+ /*** Normalize the configured time step to a positive whole minute. */
41
42
  function resolveStepMinutes(stepMinutes) {
42
43
  if (stepMinutes === undefined || !Number.isFinite(stepMinutes) || stepMinutes <= 0) {
43
44
  return DEFAULT_STEP_MINUTES;
44
45
  }
45
46
  return Math.max(1, Math.floor(stepMinutes));
46
47
  }
48
+ /*** Generate selectable HH:mm values within the configured range. */
47
49
  function generateTimeOptions({ minTime, maxTime, stepMinutes, }) {
48
50
  const step = resolveStepMinutes(stepMinutes);
49
51
  const min = parseTimeToMinutes(minTime) ?? 0;
50
52
  const max = parseTimeToMinutes(maxTime) ?? MINUTES_PER_DAY - 1;
51
53
  const options = [];
52
54
  for (let minute = 0; minute < MINUTES_PER_DAY; minute += step) {
53
- if (minute >= min && minute <= max) {
55
+ if (minute >= min && minute <= max)
54
56
  options.push(formatMinutes(minute));
55
- }
56
57
  }
57
58
  return options;
58
59
  }
59
60
  function TimePickerInner({ themeId: _themeId, mode: _mode, value, onValueChange, label, description, error, placeholder = 'Choose time', minTime, maxTime, stepMinutes, disabled = false, required = false, formatTime, testID, interactionPolicy, }) {
61
+ const { dismiss, present } = useBottomSheet();
60
62
  const passive = interactionPolicy === 'passive';
61
- const [visible, setVisible] = React.useState(false);
62
- const options = React.useMemo(() => generateTimeOptions({ maxTime, minTime, stepMinutes }), [maxTime, minTime, stepMinutes]);
63
63
  const displayValue = value ? (formatTime ? formatTime(value) : value) : placeholder;
64
+ /*** Present the time options through the shared BottomSheet controller. */
64
65
  function openPicker() {
65
66
  if (passive)
66
67
  return;
67
- setVisible(true);
68
+ present({
69
+ content: (<TimePickerSheet description={description} formatTime={formatTime} interactionPolicy={interactionPolicy} label={label} maxTime={maxTime} minTime={minTime} onDismiss={dismiss} onSelect={(time) => {
70
+ onValueChange?.(time);
71
+ dismiss();
72
+ }} stepMinutes={stepMinutes} testID={testID} value={value}/>),
73
+ });
68
74
  }
69
75
  return (<Field disabled={disabled} errorText={error} invalid={Boolean(error)} label={label ? renderLabel(label, description) : undefined} required={required} testID={testID}>
70
76
  <Button disabled={disabled} interactionPolicy={interactionPolicy} onPress={openPicker} testID={testID ? `${testID}-trigger` : undefined} trailingIcon={{ name: 'chevron-down-outline' }} variant="outline">
71
77
  {displayValue}
72
78
  </Button>
73
- <ActionSheet description={description} interactionPolicy={interactionPolicy} onDismiss={() => {
74
- if (passive)
75
- return;
76
- setVisible(false);
77
- }} testID={testID ? `${testID}-sheet` : undefined} title={label ?? 'Choose time'} visible={visible}>
78
- <ScrollView style={{ maxHeight: 360 }}>
79
- <Stack gap="xxs">
80
- {options.map((option) => (<ActionSheetItem interactionPolicy={interactionPolicy} key={option} label={formatTime ? formatTime(option) : option} onPress={() => {
81
- if (passive)
82
- return;
83
- onValueChange?.(option);
84
- setVisible(false);
85
- }} selected={option === value} testID={testID ? `${testID}-option-${option}` : undefined}/>))}
86
- </Stack>
87
- </ScrollView>
88
- </ActionSheet>
89
79
  </Field>);
90
80
  }
81
+ /*** Render time selection content inside the shared BottomSheet host. */
82
+ function TimePickerSheet({ description, formatTime, interactionPolicy, label, maxTime, minTime, onDismiss, onSelect, stepMinutes, testID, value, }) {
83
+ const options = React.useMemo(() => generateTimeOptions({ maxTime, minTime, stepMinutes }), [maxTime, minTime, stepMinutes]);
84
+ return (<Stack gap="m" p="m" testID={testID ? `${testID}-sheet` : undefined}>
85
+ <PickerSheetHeader description={description} title={label ?? 'Choose time'}/>
86
+ <ScrollView style={styles.options}>
87
+ <Stack gap="xxs">
88
+ {options.map((option) => (<TimePickerOption formatTime={formatTime} interactionPolicy={interactionPolicy} key={option} onSelect={onSelect} option={option} selected={option === value} testID={testID}/>))}
89
+ </Stack>
90
+ </ScrollView>
91
+ <Button fullWidth interactionPolicy={interactionPolicy} onPress={onDismiss} variant="ghost">
92
+ Cancel
93
+ </Button>
94
+ </Stack>);
95
+ }
96
+ /*** Render the shared title block used by the time sheet. */
97
+ function PickerSheetHeader({ description, title, }) {
98
+ return (<Stack gap="xxs">
99
+ <Text align="center" variant="label" weight="semiBold">
100
+ {title}
101
+ </Text>
102
+ {description ? (<Text align="center" emphasis="muted" variant="bodySmall">
103
+ {description}
104
+ </Text>) : null}
105
+ </Stack>);
106
+ }
107
+ /*** Render one accessible selectable time option. */
108
+ function TimePickerOption({ formatTime, interactionPolicy, onSelect, option, selected, testID, }) {
109
+ return (<Button accessibilityState={{ selected }} fullWidth interactionPolicy={interactionPolicy} onPress={() => onSelect(option)} testID={testID ? `${testID}-option-${option}` : undefined} variant={selected ? 'soft' : 'ghost'}>
110
+ {formatTime ? formatTime(option) : option}
111
+ </Button>);
112
+ }
113
+ const styles = StyleSheet.create({
114
+ options: { maxHeight: 360 },
115
+ });
91
116
  /***
92
- * Time input control with wheel selection and formatted display value.
93
- *
94
-
117
+ * Time input control with bottom-sheet selection and formatted display value.
95
118
  */
96
119
  export const TimePicker = withZoraThemeScope(TimePickerInner);
97
120
  //# sourceMappingURL=TimePicker.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"TimePicker.js","sourceRoot":"","sources":["../../../src/components/time-picker/TimePicker.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Field } from '@ankhorage/surface';\nimport React from 'react';\nimport { ScrollView } from 'react-native';\n\nimport { Stack } from '../../foundation';\nimport { withZoraThemeScope } from '../../theme/withZoraThemeScope';\nimport { ActionSheet, ActionSheetItem } from '../action-sheet';\nimport { Button } from '../button';\nimport { Text } from '../text';\nimport type { TimePickerProps } from './types';\n\nconst MINUTES_PER_DAY = 24 * 60;\nconst DEFAULT_STEP_MINUTES = 30;\n\nfunction renderLabel(label: React.ReactNode, description: React.ReactNode | undefined) {\n return (\n <Stack gap=\"xs\">\n <Text variant=\"label\" weight=\"semiBold\">\n {label}\n </Text>\n {description ? (\n <Text emphasis=\"muted\" variant=\"bodySmall\">\n {description}\n </Text>\n ) : null}\n </Stack>\n );\n}\n\nfunction parseTimeToMinutes(value: string | undefined): number | undefined {\n if (!value) {\n return undefined;\n }\n\n const match = /^(\\d{2}):(\\d{2})$/.exec(value);\n if (!match) {\n return undefined;\n }\n\n const hours = Number(match[1]);\n const minutes = Number(match[2]);\n if (hours < 0 || hours > 23 || minutes < 0 || minutes > 59) {\n return undefined;\n }\n\n return hours * 60 + minutes;\n}\n\nfunction formatMinutes(value: number): string {\n const hours = Math.floor(value / 60);\n const minutes = value % 60;\n return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`;\n}\n\nfunction resolveStepMinutes(stepMinutes: number | undefined): number {\n if (stepMinutes === undefined || !Number.isFinite(stepMinutes) || stepMinutes <= 0) {\n return DEFAULT_STEP_MINUTES;\n }\n\n return Math.max(1, Math.floor(stepMinutes));\n}\n\nfunction generateTimeOptions({\n minTime,\n maxTime,\n stepMinutes,\n}: Pick<TimePickerProps, 'maxTime' | 'minTime' | 'stepMinutes'>): string[] {\n const step = resolveStepMinutes(stepMinutes);\n const min = parseTimeToMinutes(minTime) ?? 0;\n const max = parseTimeToMinutes(maxTime) ?? MINUTES_PER_DAY - 1;\n const options: string[] = [];\n\n for (let minute = 0; minute < MINUTES_PER_DAY; minute += step) {\n if (minute >= min && minute <= max) {\n options.push(formatMinutes(minute));\n }\n }\n\n return options;\n}\n\nfunction TimePickerInner({\n themeId: _themeId,\n mode: _mode,\n value,\n onValueChange,\n label,\n description,\n error,\n placeholder = 'Choose time',\n minTime,\n maxTime,\n stepMinutes,\n disabled = false,\n required = false,\n formatTime,\n testID,\n interactionPolicy,\n}: TimePickerProps) {\n const passive = interactionPolicy === 'passive';\n const [visible, setVisible] = React.useState(false);\n const options = React.useMemo(\n () => generateTimeOptions({ maxTime, minTime, stepMinutes }),\n [maxTime, minTime, stepMinutes],\n );\n const displayValue = value ? (formatTime ? formatTime(value) : value) : placeholder;\n\n function openPicker() {\n if (passive) return;\n\n setVisible(true);\n }\n\n return (\n <Field\n disabled={disabled}\n errorText={error}\n invalid={Boolean(error)}\n label={label ? renderLabel(label, description) : undefined}\n required={required}\n testID={testID}\n >\n <Button\n disabled={disabled}\n interactionPolicy={interactionPolicy}\n onPress={openPicker}\n testID={testID ? `${testID}-trigger` : undefined}\n trailingIcon={{ name: 'chevron-down-outline' }}\n variant=\"outline\"\n >\n {displayValue}\n </Button>\n <ActionSheet\n description={description}\n interactionPolicy={interactionPolicy}\n onDismiss={() => {\n if (passive) return;\n\n setVisible(false);\n }}\n testID={testID ? `${testID}-sheet` : undefined}\n title={label ?? 'Choose time'}\n visible={visible}\n >\n <ScrollView style={{ maxHeight: 360 }}>\n <Stack gap=\"xxs\">\n {options.map((option) => (\n <ActionSheetItem\n interactionPolicy={interactionPolicy}\n key={option}\n label={formatTime ? formatTime(option) : option}\n onPress={() => {\n if (passive) return;\n\n onValueChange?.(option);\n setVisible(false);\n }}\n selected={option === value}\n testID={testID ? `${testID}-option-${option}` : undefined}\n />\n ))}\n </Stack>\n </ScrollView>\n </ActionSheet>\n </Field>\n );\n}\n\n/***\n * Time input control with wheel selection and formatted display value.\n *\n \n */\nexport const TimePicker = withZoraThemeScope(TimePickerInner);\n"]}
1
+ {"version":3,"file":"TimePicker.js","sourceRoot":"","sources":["../../../src/components/time-picker/TimePicker.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Field } from '@ankhorage/surface';\nimport React from 'react';\nimport { ScrollView, StyleSheet } from 'react-native';\n\nimport { useBottomSheet } from '../../features/bottom-sheet/public';\nimport { Stack } from '../../foundation';\nimport { withZoraThemeScope } from '../../theme/withZoraThemeScope';\nimport { Button } from '../button';\nimport { Text } from '../text';\nimport type { TimePickerProps } from './types';\n\nconst MINUTES_PER_DAY = 24 * 60;\nconst DEFAULT_STEP_MINUTES = 30;\n\n/*** Render the field label and optional supporting description. */\nfunction renderLabel(label: React.ReactNode, description: React.ReactNode | undefined) {\n return (\n <Stack gap=\"xs\">\n <Text variant=\"label\" weight=\"semiBold\">\n {label}\n </Text>\n {description ? (\n <Text emphasis=\"muted\" variant=\"bodySmall\">\n {description}\n </Text>\n ) : null}\n </Stack>\n );\n}\n\n/*** Parse one HH:mm value to minutes since midnight. */\nfunction parseTimeToMinutes(value: string | undefined): number | undefined {\n if (!value) return undefined;\n const match = /^(\\d{2}):(\\d{2})$/.exec(value);\n if (!match) return undefined;\n\n const hours = Number(match[1]);\n const minutes = Number(match[2]);\n if (hours < 0 || hours > 23 || minutes < 0 || minutes > 59) return undefined;\n return hours * 60 + minutes;\n}\n\n/*** Format minutes since midnight as HH:mm. */\nfunction formatMinutes(value: number): string {\n const hours = Math.floor(value / 60);\n const minutes = value % 60;\n return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`;\n}\n\n/*** Normalize the configured time step to a positive whole minute. */\nfunction resolveStepMinutes(stepMinutes: number | undefined): number {\n if (stepMinutes === undefined || !Number.isFinite(stepMinutes) || stepMinutes <= 0) {\n return DEFAULT_STEP_MINUTES;\n }\n return Math.max(1, Math.floor(stepMinutes));\n}\n\n/*** Generate selectable HH:mm values within the configured range. */\nfunction generateTimeOptions({\n minTime,\n maxTime,\n stepMinutes,\n}: Pick<TimePickerProps, 'maxTime' | 'minTime' | 'stepMinutes'>): string[] {\n const step = resolveStepMinutes(stepMinutes);\n const min = parseTimeToMinutes(minTime) ?? 0;\n const max = parseTimeToMinutes(maxTime) ?? MINUTES_PER_DAY - 1;\n const options: string[] = [];\n\n for (let minute = 0; minute < MINUTES_PER_DAY; minute += step) {\n if (minute >= min && minute <= max) options.push(formatMinutes(minute));\n }\n return options;\n}\n\nfunction TimePickerInner({\n themeId: _themeId,\n mode: _mode,\n value,\n onValueChange,\n label,\n description,\n error,\n placeholder = 'Choose time',\n minTime,\n maxTime,\n stepMinutes,\n disabled = false,\n required = false,\n formatTime,\n testID,\n interactionPolicy,\n}: TimePickerProps) {\n const { dismiss, present } = useBottomSheet();\n const passive = interactionPolicy === 'passive';\n const displayValue = value ? (formatTime ? formatTime(value) : value) : placeholder;\n\n /*** Present the time options through the shared BottomSheet controller. */\n function openPicker() {\n if (passive) return;\n present({\n content: (\n <TimePickerSheet\n description={description}\n formatTime={formatTime}\n interactionPolicy={interactionPolicy}\n label={label}\n maxTime={maxTime}\n minTime={minTime}\n onDismiss={dismiss}\n onSelect={(time) => {\n onValueChange?.(time);\n dismiss();\n }}\n stepMinutes={stepMinutes}\n testID={testID}\n value={value}\n />\n ),\n });\n }\n\n return (\n <Field\n disabled={disabled}\n errorText={error}\n invalid={Boolean(error)}\n label={label ? renderLabel(label, description) : undefined}\n required={required}\n testID={testID}\n >\n <Button\n disabled={disabled}\n interactionPolicy={interactionPolicy}\n onPress={openPicker}\n testID={testID ? `${testID}-trigger` : undefined}\n trailingIcon={{ name: 'chevron-down-outline' }}\n variant=\"outline\"\n >\n {displayValue}\n </Button>\n </Field>\n );\n}\n\ninterface TimePickerSheetProps extends Pick<\n TimePickerProps,\n | 'description'\n | 'formatTime'\n | 'interactionPolicy'\n | 'label'\n | 'maxTime'\n | 'minTime'\n | 'stepMinutes'\n | 'testID'\n | 'value'\n> {\n onDismiss: () => void;\n onSelect: (value: string) => void;\n}\n\n/*** Render time selection content inside the shared BottomSheet host. */\nfunction TimePickerSheet({\n description,\n formatTime,\n interactionPolicy,\n label,\n maxTime,\n minTime,\n onDismiss,\n onSelect,\n stepMinutes,\n testID,\n value,\n}: TimePickerSheetProps) {\n const options = React.useMemo(\n () => generateTimeOptions({ maxTime, minTime, stepMinutes }),\n [maxTime, minTime, stepMinutes],\n );\n\n return (\n <Stack gap=\"m\" p=\"m\" testID={testID ? `${testID}-sheet` : undefined}>\n <PickerSheetHeader description={description} title={label ?? 'Choose time'} />\n <ScrollView style={styles.options}>\n <Stack gap=\"xxs\">\n {options.map((option) => (\n <TimePickerOption\n formatTime={formatTime}\n interactionPolicy={interactionPolicy}\n key={option}\n onSelect={onSelect}\n option={option}\n selected={option === value}\n testID={testID}\n />\n ))}\n </Stack>\n </ScrollView>\n <Button fullWidth interactionPolicy={interactionPolicy} onPress={onDismiss} variant=\"ghost\">\n Cancel\n </Button>\n </Stack>\n );\n}\n\n/*** Render the shared title block used by the time sheet. */\nfunction PickerSheetHeader({\n description,\n title,\n}: {\n description: React.ReactNode | undefined;\n title: React.ReactNode;\n}) {\n return (\n <Stack gap=\"xxs\">\n <Text align=\"center\" variant=\"label\" weight=\"semiBold\">\n {title}\n </Text>\n {description ? (\n <Text align=\"center\" emphasis=\"muted\" variant=\"bodySmall\">\n {description}\n </Text>\n ) : null}\n </Stack>\n );\n}\n\n/*** Render one accessible selectable time option. */\nfunction TimePickerOption({\n formatTime,\n interactionPolicy,\n onSelect,\n option,\n selected,\n testID,\n}: Pick<TimePickerSheetProps, 'formatTime' | 'interactionPolicy' | 'testID'> & {\n onSelect: (value: string) => void;\n option: string;\n selected: boolean;\n}) {\n return (\n <Button\n accessibilityState={{ selected }}\n fullWidth\n interactionPolicy={interactionPolicy}\n onPress={() => onSelect(option)}\n testID={testID ? `${testID}-option-${option}` : undefined}\n variant={selected ? 'soft' : 'ghost'}\n >\n {formatTime ? formatTime(option) : option}\n </Button>\n );\n}\n\nconst styles = StyleSheet.create({\n options: { maxHeight: 360 },\n});\n\n/***\n * Time input control with bottom-sheet selection and formatted display value.\n */\nexport const TimePicker = withZoraThemeScope(TimePickerInner);\n"]}
@@ -3,7 +3,7 @@ export declare const timePickerMeta: {
3
3
  readonly category: "component";
4
4
  readonly directManifestNode: false;
5
5
  readonly allowedChildren: readonly [];
6
- readonly note: "Code-facing ActionSheet-backed time picker; not represented as a manifest node in v1.";
6
+ readonly note: "Code-facing BottomSheet-backed time picker; not represented as a manifest node in v1.";
7
7
  readonly props: {};
8
8
  };
9
9
  //# sourceMappingURL=meta.d.ts.map
@@ -3,7 +3,7 @@ export const timePickerMeta = {
3
3
  category: 'component',
4
4
  directManifestNode: false,
5
5
  allowedChildren: [],
6
- note: 'Code-facing ActionSheet-backed time picker; not represented as a manifest node in v1.',
6
+ note: 'Code-facing BottomSheet-backed time picker; not represented as a manifest node in v1.',
7
7
  props: {},
8
8
  };
9
9
  //# sourceMappingURL=meta.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"meta.js","sourceRoot":"","sources":["../../../src/components/time-picker/meta.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,IAAI,EAAE,YAAY;IAClB,QAAQ,EAAE,WAAW;IACrB,kBAAkB,EAAE,KAAK;IACzB,eAAe,EAAE,EAAE;IACnB,IAAI,EAAE,uFAAuF;IAC7F,KAAK,EAAE,EAAE;CAC2B,CAAC","sourcesContent":["import type { ZoraComponentMeta } from '../../metadata';\n\nexport const timePickerMeta = {\n name: 'TimePicker',\n category: 'component',\n directManifestNode: false,\n allowedChildren: [],\n note: 'Code-facing ActionSheet-backed time picker; not represented as a manifest node in v1.',\n props: {},\n} as const satisfies ZoraComponentMeta;\n"]}
1
+ {"version":3,"file":"meta.js","sourceRoot":"","sources":["../../../src/components/time-picker/meta.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,IAAI,EAAE,YAAY;IAClB,QAAQ,EAAE,WAAW;IACrB,kBAAkB,EAAE,KAAK;IACzB,eAAe,EAAE,EAAE;IACnB,IAAI,EAAE,uFAAuF;IAC7F,KAAK,EAAE,EAAE;CAC2B,CAAC","sourcesContent":["import type { ZoraComponentMeta } from '../../metadata';\n\nexport const timePickerMeta = {\n name: 'TimePicker',\n category: 'component',\n directManifestNode: false,\n allowedChildren: [],\n note: 'Code-facing BottomSheet-backed time picker; not represented as a manifest node in v1.',\n props: {},\n} as const satisfies ZoraComponentMeta;\n"]}
@@ -0,0 +1,3 @@
1
+ export type { BottomSheetController, BottomSheetPresentOptions, } from '@ankhorage/surface/bottom-sheet';
2
+ export { useBottomSheet } from '@ankhorage/surface/bottom-sheet';
3
+ //# sourceMappingURL=public.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../../src/features/bottom-sheet/public.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,qBAAqB,EACrB,yBAAyB,GAC1B,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { useBottomSheet } from '@ankhorage/surface/bottom-sheet';
2
+ //# sourceMappingURL=public.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"public.js","sourceRoot":"","sources":["../../../src/features/bottom-sheet/public.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC","sourcesContent":["export type {\n BottomSheetController,\n BottomSheetPresentOptions,\n} from '@ankhorage/surface/bottom-sheet';\nexport { useBottomSheet } from '@ankhorage/surface/bottom-sheet';\n"]}
@@ -0,0 +1,5 @@
1
+ import React from 'react';
2
+ import type { UploaderProps } from '../../../../types/upload';
3
+ /*** Picks, validates, uploads, previews, and removes one generic file asset. */
4
+ export declare const Uploader: (props: UploaderProps) => React.ReactElement | null;
5
+ //# sourceMappingURL=Uploader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Uploader.d.ts","sourceRoot":"","sources":["../../../../../src/features/uploader/adapters/inbound/Uploader.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAW1B,OAAO,KAAK,EAAe,aAAa,EAAc,MAAM,0BAA0B,CAAC;AAIvF,gFAAgF;AAChF,eAAO,MAAM,QAAQ,qDAAoC,CAAC"}
@@ -0,0 +1,274 @@
1
+ import React from 'react';
2
+ import { Button } from '../../../../components/button';
3
+ import { Icon } from '../../../../components/icon';
4
+ import { Image } from '../../../../components/image';
5
+ import { Modal } from '../../../../components/modal';
6
+ import { Progress } from '../../../../components/progress';
7
+ import { Text } from '../../../../components/text';
8
+ import { Box, Stack } from '../../../../foundation';
9
+ import { FormField } from '../../../../patterns/form-field';
10
+ import { withZoraThemeScope } from '../../../../theme/withZoraThemeScope';
11
+ import { validateUploadAsset } from '../../application/use-cases/validateUploadAsset';
12
+ import { createUploadPicker } from '../../composition/createUploadPicker';
13
+ /*** Picks, validates, uploads, previews, and removes one generic file asset. */
14
+ export const Uploader = withZoraThemeScope(UploaderInner);
15
+ /*** Owns the interactive state for the generic uploader inbound adapter. */
16
+ function UploaderInner({ themeId: _themeId, mode: _mode, testID, value, onChange, label, description, helperText, errorText, type = 'file', accept, maxSizeBytes, required, disabled = false, readOnly = false, validatePicked, onUpload, onRemove, aspectRatio = 1, interactionPolicy, }) {
17
+ const passive = interactionPolicy === 'passive';
18
+ const picker = React.useMemo(() => createUploadPicker(), []);
19
+ const [internalError, setInternalError] = React.useState();
20
+ const [uploading, setUploading] = React.useState(false);
21
+ const [removing, setRemoving] = React.useState(false);
22
+ const [progress, setProgress] = React.useState(null);
23
+ const [previewOpen, setPreviewOpen] = React.useState(false);
24
+ const mountedRef = React.useRef(null);
25
+ const resolvedAccept = resolveUploadAccept(type, accept);
26
+ const actionsDisabled = disabled || readOnly;
27
+ const effectiveError = errorText ?? internalError;
28
+ React.useEffect(() => {
29
+ mountedRef.current = true;
30
+ return () => {
31
+ mountedRef.current = false;
32
+ };
33
+ }, []);
34
+ const isMounted = React.useCallback(() => mountedRef.current === true, []);
35
+ const setProgressSafe = React.useCallback((next) => {
36
+ if (!isMounted()) {
37
+ return;
38
+ }
39
+ setProgress(clampProgress(next));
40
+ }, [isMounted]);
41
+ const handlePick = React.useCallback(async () => {
42
+ if (passive || actionsDisabled || uploading || removing) {
43
+ return;
44
+ }
45
+ setInternalError(undefined);
46
+ try {
47
+ const picked = await picker.pickAsync({ accept: resolvedAccept, type });
48
+ if (!picked || !isMounted()) {
49
+ return;
50
+ }
51
+ const validationError = validateUploadAsset({
52
+ accept: resolvedAccept,
53
+ asset: picked,
54
+ maxSizeBytes,
55
+ validate: validatePicked,
56
+ });
57
+ if (validationError) {
58
+ setInternalError(validationError);
59
+ return;
60
+ }
61
+ onChange(picked);
62
+ if (!onUpload) {
63
+ return;
64
+ }
65
+ setUploading(true);
66
+ setProgressSafe(0);
67
+ try {
68
+ const uploaded = await onUpload(picked, { setProgress: setProgressSafe });
69
+ if (!isMounted()) {
70
+ return;
71
+ }
72
+ onChange(uploaded);
73
+ setUploading(false);
74
+ setProgress(null);
75
+ }
76
+ catch (error) {
77
+ if (!isMounted()) {
78
+ return;
79
+ }
80
+ setInternalError(formatUnknownError(error));
81
+ setUploading(false);
82
+ setProgress(null);
83
+ }
84
+ }
85
+ catch (error) {
86
+ if (isMounted()) {
87
+ setInternalError(formatUnknownError(error));
88
+ }
89
+ }
90
+ }, [
91
+ actionsDisabled,
92
+ isMounted,
93
+ maxSizeBytes,
94
+ onChange,
95
+ onUpload,
96
+ passive,
97
+ picker,
98
+ removing,
99
+ resolvedAccept,
100
+ setProgressSafe,
101
+ type,
102
+ uploading,
103
+ validatePicked,
104
+ ]);
105
+ const handleRemove = React.useCallback(async () => {
106
+ if (passive || actionsDisabled || uploading || removing || !value) {
107
+ return;
108
+ }
109
+ setInternalError(undefined);
110
+ if (!onRemove) {
111
+ onChange(null);
112
+ return;
113
+ }
114
+ setRemoving(true);
115
+ try {
116
+ await onRemove(value);
117
+ if (isMounted()) {
118
+ onChange(null);
119
+ setRemoving(false);
120
+ }
121
+ }
122
+ catch (error) {
123
+ if (isMounted()) {
124
+ setInternalError(formatUnknownError(error));
125
+ setRemoving(false);
126
+ }
127
+ }
128
+ }, [actionsDisabled, isMounted, onChange, onRemove, passive, removing, uploading, value]);
129
+ const canPreviewImage = type === 'image' && resolveRenderableAssetUrl(value) !== null;
130
+ return (<>
131
+ <FormField description={description} disabled={disabled} errorText={effectiveError} helperText={helperText} interactionPolicy={interactionPolicy} invalid={Boolean(effectiveError)} label={label} readOnly={readOnly} required={required} testID={testID}>
132
+ <Stack gap="m">
133
+ <UploadPresentation asset={value} aspectRatio={aspectRatio} type={type}/>
134
+
135
+ {resolvedAccept || maxSizeBytes ? (<Stack gap="xs">
136
+ {resolvedAccept ? (<Text emphasis="muted" variant="caption">
137
+ Accepted: {resolvedAccept}
138
+ </Text>) : null}
139
+ {maxSizeBytes ? (<Text emphasis="muted" variant="caption">
140
+ Max size: {formatBytes(maxSizeBytes)}
141
+ </Text>) : null}
142
+ </Stack>) : null}
143
+
144
+ {uploading ? (<Stack gap="xs">
145
+ <Text emphasis="muted" variant="caption">
146
+ Uploading…
147
+ </Text>
148
+ {progress !== null ? <Progress max={1} value={progress}/> : null}
149
+ </Stack>) : null}
150
+
151
+ <Stack direction={{ base: 'column', md: 'row' }} gap="s">
152
+ <Button disabled={actionsDisabled || uploading || removing} interactionPolicy={interactionPolicy} onPress={() => {
153
+ void handlePick();
154
+ }}>
155
+ {value ? 'Replace file' : 'Select file'}
156
+ </Button>
157
+
158
+ {value ? (<Button color="danger" disabled={actionsDisabled || uploading || removing} interactionPolicy={interactionPolicy} loading={removing} variant="outline" onPress={() => {
159
+ void handleRemove();
160
+ }}>
161
+ Remove
162
+ </Button>) : null}
163
+
164
+ {canPreviewImage ? (<Button color="neutral" interactionPolicy={interactionPolicy} variant="soft" onPress={() => setPreviewOpen(true)}>
165
+ Preview
166
+ </Button>) : null}
167
+ </Stack>
168
+ </Stack>
169
+ </FormField>
170
+
171
+ {canPreviewImage ? (<Modal closeOnBackdrop interactionPolicy={interactionPolicy} onDismiss={() => setPreviewOpen(false)} title="Image preview" visible={previewOpen}>
172
+ <Stack gap="m">
173
+ <Image aspectRatio={aspectRatio} fit="contain" source={value}/>
174
+ <Stack direction="row" justify="flex-end">
175
+ <Button color="neutral" interactionPolicy={interactionPolicy} variant="soft" onPress={() => setPreviewOpen(false)}>
176
+ Close
177
+ </Button>
178
+ </Stack>
179
+ </Stack>
180
+ </Modal>) : null}
181
+ </>);
182
+ }
183
+ /*** Renders image previews or generic file metadata without exposing picker-specific assets. */
184
+ function UploadPresentation({ asset, aspectRatio, type, }) {
185
+ if (type === 'image') {
186
+ return <Image aspectRatio={aspectRatio} source={asset}/>;
187
+ }
188
+ return (<Box borderWidth={1} p="m" radius="m">
189
+ <Stack direction="row" gap="s">
190
+ <Icon name={resolveUploadIcon(type)} size={22}/>
191
+ <Stack gap="xs">
192
+ <Text variant="label" weight="semiBold">
193
+ {asset?.fileName ?? 'No file selected'}
194
+ </Text>
195
+ {asset?.contentType ? (<Text emphasis="muted" variant="caption">
196
+ {asset.contentType}
197
+ </Text>) : null}
198
+ </Stack>
199
+ </Stack>
200
+ </Box>);
201
+ }
202
+ /*** Resolves the native picker default while allowing accept to remain the precise restriction. */
203
+ function resolveUploadAccept(type, accept) {
204
+ if (accept) {
205
+ return accept;
206
+ }
207
+ if (type === 'image') {
208
+ return 'image/*';
209
+ }
210
+ if (type === 'video') {
211
+ return 'video/*';
212
+ }
213
+ return undefined;
214
+ }
215
+ /*** Resolves an upload asset URL when it can be rendered directly. */
216
+ function resolveRenderableAssetUrl(asset) {
217
+ if (!asset) {
218
+ return null;
219
+ }
220
+ switch (asset.kind) {
221
+ case 'local':
222
+ return asset.uri;
223
+ case 'url':
224
+ return asset.url;
225
+ case 'storage':
226
+ return asset.publicUrl ?? null;
227
+ }
228
+ }
229
+ /*** Resolves a generic icon for the selected upload mode. */
230
+ function resolveUploadIcon(type) {
231
+ switch (type) {
232
+ case 'video':
233
+ return 'videocam-outline';
234
+ case 'document':
235
+ return 'document-text-outline';
236
+ case 'file':
237
+ case 'image':
238
+ return 'attach-outline';
239
+ }
240
+ }
241
+ /*** Clamps progress callbacks into the normalized 0..1 range. */
242
+ function clampProgress(value) {
243
+ if (value === null || !Number.isFinite(value)) {
244
+ return null;
245
+ }
246
+ return Math.max(0, Math.min(1, value));
247
+ }
248
+ /*** Converts unknown picker/upload failures into user-facing text. */
249
+ function formatUnknownError(error) {
250
+ if (error instanceof Error && error.message) {
251
+ return error.message;
252
+ }
253
+ if (typeof error === 'string' && error.trim().length > 0) {
254
+ return error;
255
+ }
256
+ return 'Something went wrong.';
257
+ }
258
+ /*** Formats an upload byte limit for concise helper text. */
259
+ function formatBytes(value) {
260
+ if (!Number.isFinite(value) || value <= 0) {
261
+ return '0 B';
262
+ }
263
+ const units = ['B', 'KB', 'MB', 'GB'];
264
+ let size = value;
265
+ let unitIndex = 0;
266
+ while (size >= 1024 && unitIndex < units.length - 1) {
267
+ size /= 1024;
268
+ unitIndex += 1;
269
+ }
270
+ const unit = units.at(unitIndex) ?? 'B';
271
+ const rounded = unitIndex === 0 ? Math.round(size) : Math.round(size * 10) / 10;
272
+ return `${rounded} ${unit}`;
273
+ }
274
+ //# sourceMappingURL=Uploader.js.map