@multiplatform.one/frappe-ui 7.90.6 → 8.1.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 (266) hide show
  1. package/dist/cjs/FrappeListView.cjs +0 -2
  2. package/dist/cjs/FrappeListView.native.js +0 -2
  3. package/dist/cjs/FrappeListView.native.js.map +1 -1
  4. package/dist/cjs/desk/index.cjs +41 -0
  5. package/dist/cjs/desk/index.native.js +44 -0
  6. package/dist/cjs/desk/index.native.js.map +1 -0
  7. package/dist/cjs/fields/Link.cjs +0 -4
  8. package/dist/cjs/fields/Link.native.js +0 -4
  9. package/dist/cjs/fields/Link.native.js.map +1 -1
  10. package/dist/cjs/index.cjs +0 -2
  11. package/dist/cjs/index.gnome.cjs +203 -0
  12. package/dist/cjs/index.gnome.native.js +206 -0
  13. package/dist/cjs/index.gnome.native.js.map +1 -0
  14. package/dist/cjs/index.native.js +0 -2
  15. package/dist/cjs/index.native.js.map +1 -1
  16. package/dist/esm/FrappeListView.mjs +0 -2
  17. package/dist/esm/FrappeListView.mjs.map +1 -1
  18. package/dist/esm/FrappeListView.native.js +0 -2
  19. package/dist/esm/FrappeListView.native.js.map +1 -1
  20. package/dist/esm/desk/index.mjs +8 -0
  21. package/dist/esm/desk/index.mjs.map +1 -0
  22. package/dist/esm/desk/index.native.js +8 -0
  23. package/dist/esm/desk/index.native.js.map +1 -0
  24. package/dist/esm/fields/Link.mjs +0 -4
  25. package/dist/esm/fields/Link.mjs.map +1 -1
  26. package/dist/esm/fields/Link.native.js +0 -4
  27. package/dist/esm/fields/Link.native.js.map +1 -1
  28. package/dist/esm/index.gnome.mjs +59 -0
  29. package/dist/esm/index.gnome.mjs.map +1 -0
  30. package/dist/esm/index.gnome.native.js +59 -0
  31. package/dist/esm/index.gnome.native.js.map +1 -0
  32. package/dist/esm/index.mjs +1 -2
  33. package/dist/esm/index.mjs.map +1 -1
  34. package/dist/esm/index.native.js +1 -2
  35. package/dist/esm/index.native.js.map +1 -1
  36. package/dist/jsx/ChartCard.native.js.map +1 -1
  37. package/dist/jsx/FileBrowser/AttachLibrary.native.js.map +1 -1
  38. package/dist/jsx/FileBrowser/folderTree.native.js.map +1 -1
  39. package/dist/jsx/FileBrowser/index.native.js.map +1 -1
  40. package/dist/jsx/FileBrowser/types.native.js.map +1 -1
  41. package/dist/jsx/FileBrowser/viewPersistence.native.js.map +1 -1
  42. package/dist/jsx/FormSidebar.native.js.map +1 -1
  43. package/dist/jsx/FrappeAssignment.native.js.map +1 -1
  44. package/dist/jsx/FrappeCalendar.native.js.map +1 -1
  45. package/dist/jsx/FrappeChartCard.native.js.map +1 -1
  46. package/dist/jsx/FrappeConnectionIndicator.native.js.map +1 -1
  47. package/dist/jsx/FrappeDashboard.native.js.map +1 -1
  48. package/dist/jsx/FrappeForm.native.js +1 -1
  49. package/dist/jsx/FrappeForm.native.js.map +1 -1
  50. package/dist/jsx/FrappeFormActions.native.js.map +1 -1
  51. package/dist/jsx/FrappeFormConnections.native.js.map +1 -1
  52. package/dist/jsx/FrappeGantt.native.js.map +1 -1
  53. package/dist/jsx/FrappeImageView.native.js.map +1 -1
  54. package/dist/jsx/FrappeInsights.native.js.map +1 -1
  55. package/dist/jsx/FrappeKPICard.native.js.map +1 -1
  56. package/dist/jsx/FrappeKanban.native.js.map +1 -1
  57. package/dist/jsx/FrappeLikedBy.native.js.map +1 -1
  58. package/dist/jsx/FrappeList.native.js.map +1 -1
  59. package/dist/jsx/FrappeListView.mjs +0 -2
  60. package/dist/jsx/FrappeListView.mjs.map +1 -1
  61. package/dist/jsx/FrappeListView.native.js +0 -2
  62. package/dist/jsx/FrappeListView.native.js.map +1 -1
  63. package/dist/jsx/FrappeNotConfigured.native.js.map +1 -1
  64. package/dist/jsx/FrappeProvider.native.js.map +1 -1
  65. package/dist/jsx/FrappeReconnectIndicator.native.js.map +1 -1
  66. package/dist/jsx/FrappeRecordView.native.js.map +1 -1
  67. package/dist/jsx/FrappeReportBuilder.native.js.map +1 -1
  68. package/dist/jsx/FrappeReportView.native.js +3 -3
  69. package/dist/jsx/FrappeReportView.native.js.map +1 -1
  70. package/dist/jsx/FrappeRowsReader.native.js.map +1 -1
  71. package/dist/jsx/FrappeTable.native.js.map +1 -1
  72. package/dist/jsx/FrappeTreeView.native.js.map +1 -1
  73. package/dist/jsx/SyncErrorNotice.native.js.map +1 -1
  74. package/dist/jsx/commentActions.native.js.map +1 -1
  75. package/dist/jsx/components/attachments.native.js.map +1 -1
  76. package/dist/jsx/components/tags.native.js.map +1 -1
  77. package/dist/jsx/components/timeline.native.js.map +1 -1
  78. package/dist/jsx/dependsOn.native.js.map +1 -1
  79. package/dist/jsx/desk/DeskFormPage.native.js.map +1 -1
  80. package/dist/jsx/desk/DeskFormSidebar.native.js.map +1 -1
  81. package/dist/jsx/desk/DeskListPage.native.js.map +1 -1
  82. package/dist/jsx/desk/DeskShell.native.js.map +1 -1
  83. package/dist/jsx/desk/DeskSidebar.native.js.map +1 -1
  84. package/dist/jsx/desk/awesomebar.native.js.map +1 -1
  85. package/dist/jsx/desk/index.mjs +8 -0
  86. package/dist/jsx/desk/index.mjs.map +1 -0
  87. package/dist/jsx/desk/index.native.js +44 -0
  88. package/dist/jsx/desk/index.native.js.map +1 -0
  89. package/dist/jsx/fieldOverrides.native.js.map +1 -1
  90. package/dist/jsx/fieldRegistry.native.js +31 -31
  91. package/dist/jsx/fieldRegistry.native.js.map +1 -1
  92. package/dist/jsx/fields/Barcode/index.native.js.map +1 -1
  93. package/dist/jsx/fields/Check/index.native.js.map +1 -1
  94. package/dist/jsx/fields/CodeEditor/index.native.js.map +1 -1
  95. package/dist/jsx/fields/Color/index.native.js.map +1 -1
  96. package/dist/jsx/fields/Data/index.native.js.map +1 -1
  97. package/dist/jsx/fields/Date/catalog.native.js +1 -1
  98. package/dist/jsx/fields/Date/catalog.native.js.map +1 -1
  99. package/dist/jsx/fields/Date/index.native.js.map +1 -1
  100. package/dist/jsx/fields/Date/types.native.js.map +1 -1
  101. package/dist/jsx/fields/Date/wire.native.js.map +1 -1
  102. package/dist/jsx/fields/DateRange/index.native.js.map +1 -1
  103. package/dist/jsx/fields/Duration/index.native.js.map +1 -1
  104. package/dist/jsx/fields/DynamicLink.native.js.map +1 -1
  105. package/dist/jsx/fields/FileUploadField/index.native.js.map +1 -1
  106. package/dist/jsx/fields/Geolocation/index.native.js.map +1 -1
  107. package/dist/jsx/fields/HTML/index.native.js.map +1 -1
  108. package/dist/jsx/fields/Heading/index.native.js.map +1 -1
  109. package/dist/jsx/fields/Icon/index.native.js.map +1 -1
  110. package/dist/jsx/fields/Link.mjs +0 -4
  111. package/dist/jsx/fields/Link.mjs.map +1 -1
  112. package/dist/jsx/fields/Link.native.js +0 -4
  113. package/dist/jsx/fields/Link.native.js.map +1 -1
  114. package/dist/jsx/fields/LinkTree.native.js.map +1 -1
  115. package/dist/jsx/fields/LongText/index.native.js.map +1 -1
  116. package/dist/jsx/fields/MentionInput/index.native.js.map +1 -1
  117. package/dist/jsx/fields/Numeric/index.native.js.map +1 -1
  118. package/dist/jsx/fields/OTPInput/index.native.js.map +1 -1
  119. package/dist/jsx/fields/Password/index.native.js.map +1 -1
  120. package/dist/jsx/fields/Phone/index.native.js.map +1 -1
  121. package/dist/jsx/fields/Progress/index.native.js.map +1 -1
  122. package/dist/jsx/fields/RadioGroup/index.native.js.map +1 -1
  123. package/dist/jsx/fields/Rating/index.native.js.map +1 -1
  124. package/dist/jsx/fields/ReadOnly/index.native.js.map +1 -1
  125. package/dist/jsx/fields/Select/index.native.js.map +1 -1
  126. package/dist/jsx/fields/Signature/index.native.js.map +1 -1
  127. package/dist/jsx/fields/Slider/index.native.js.map +1 -1
  128. package/dist/jsx/fields/SmallText/index.native.js.map +1 -1
  129. package/dist/jsx/fields/Switch/index.native.js.map +1 -1
  130. package/dist/jsx/fields/TableMultiSelect.native.js.map +1 -1
  131. package/dist/jsx/fields/TextEditor/types.native.js.map +1 -1
  132. package/dist/jsx/fields/TimePicker/index.native.js.map +1 -1
  133. package/dist/jsx/fields/ToggleGroup/index.native.js.map +1 -1
  134. package/dist/jsx/fields/shared/EditorPreviewPanes.native.js.map +1 -1
  135. package/dist/jsx/filterFields.native.js.map +1 -1
  136. package/dist/jsx/formEngine/createPrefill.native.js.map +1 -1
  137. package/dist/jsx/formEngine/dirtyTracking.native.js.map +1 -1
  138. package/dist/jsx/formEngine/docLifecycle.native.js.map +1 -1
  139. package/dist/jsx/formEngine/docPermissions.native.js.map +1 -1
  140. package/dist/jsx/formEngine/doctypeMeta.native.js.map +1 -1
  141. package/dist/jsx/formEngine/fetchFrom.native.js.map +1 -1
  142. package/dist/jsx/formEngine/fieldPermissions.native.js.map +1 -1
  143. package/dist/jsx/formEngine/formActions.native.js.map +1 -1
  144. package/dist/jsx/formEngine/formLayout.native.js.map +1 -1
  145. package/dist/jsx/formEngine/index.native.js.map +1 -1
  146. package/dist/jsx/formEngine/useAmendedAs.native.js.map +1 -1
  147. package/dist/jsx/formEngine/useDocRights.native.js.map +1 -1
  148. package/dist/jsx/formEngine/useFormEngine.native.js.map +1 -1
  149. package/dist/jsx/formEngine/useSessionRoles.native.js.map +1 -1
  150. package/dist/jsx/frappeConnections.native.js.map +1 -1
  151. package/dist/jsx/frappeEventColor.native.js.map +1 -1
  152. package/dist/jsx/frappeNavigation.native.js.map +1 -1
  153. package/dist/jsx/frappeStoryTarget.native.js.map +1 -1
  154. package/dist/jsx/frappeUsers.native.js.map +1 -1
  155. package/dist/jsx/index.gnome.mjs +59 -0
  156. package/dist/jsx/index.gnome.mjs.map +1 -0
  157. package/dist/jsx/index.gnome.native.js +206 -0
  158. package/dist/jsx/index.gnome.native.js.map +1 -0
  159. package/dist/jsx/index.js +1 -2
  160. package/dist/jsx/index.js.map +1 -1
  161. package/dist/jsx/index.mjs +1 -2
  162. package/dist/jsx/index.mjs.map +1 -1
  163. package/dist/jsx/index.native.js +34 -36
  164. package/dist/jsx/index.native.js.map +1 -1
  165. package/dist/jsx/insights/InsightsFacetTags.native.js.map +1 -1
  166. package/dist/jsx/insights/InsightsKpiBand.native.js.map +1 -1
  167. package/dist/jsx/insights/InsightsLeaders.native.js.map +1 -1
  168. package/dist/jsx/insights/InsightsRecords.native.js.map +1 -1
  169. package/dist/jsx/insights/InsightsTable.native.js.map +1 -1
  170. package/dist/jsx/insights/insightsData.native.js.map +1 -1
  171. package/dist/jsx/insights/insightsRecord.native.js.map +1 -1
  172. package/dist/jsx/list/BandFilters.native.js.map +1 -1
  173. package/dist/jsx/list/FacetBadge.native.js.map +1 -1
  174. package/dist/jsx/list/FacetList.native.js.map +1 -1
  175. package/dist/jsx/list/FacetRail.native.js.map +1 -1
  176. package/dist/jsx/list/ImageWell.native.js.map +1 -1
  177. package/dist/jsx/list/ListControls.native.js.map +1 -1
  178. package/dist/jsx/list/ListControlsSheet.native.js.map +1 -1
  179. package/dist/jsx/list/ListFilterPanel.native.js.map +1 -1
  180. package/dist/jsx/list/ListFiltersButton.native.js.map +1 -1
  181. package/dist/jsx/list/ListSidebarToolbar.native.js.map +1 -1
  182. package/dist/jsx/list/ListToastHost.native.js.map +1 -1
  183. package/dist/jsx/list/ListToolsSheet.native.js.map +1 -1
  184. package/dist/jsx/list/RangeFilter.native.js.map +1 -1
  185. package/dist/jsx/list/ShufflePanel.native.js.map +1 -1
  186. package/dist/jsx/list/defaultProjection.native.js.map +1 -1
  187. package/dist/jsx/list/facetBadgeSteps.native.js.map +1 -1
  188. package/dist/jsx/list/facetReading.native.js.map +1 -1
  189. package/dist/jsx/list/favorites.native.js.map +1 -1
  190. package/dist/jsx/list/listColumns.native.js.map +1 -1
  191. package/dist/jsx/list/listControls.fixture.native.js.map +1 -1
  192. package/dist/jsx/list/listFields.native.js.map +1 -1
  193. package/dist/jsx/list/listFormat.native.js.map +1 -1
  194. package/dist/jsx/list/listState.native.js.map +1 -1
  195. package/dist/jsx/list/recordLook.native.js.map +1 -1
  196. package/dist/jsx/list/statusColors.native.js.map +1 -1
  197. package/dist/jsx/list/useListControls.native.js.map +1 -1
  198. package/dist/jsx/liveErrors.native.js.map +1 -1
  199. package/dist/jsx/measures.native.js.map +1 -1
  200. package/dist/jsx/record/RecordBars.native.js.map +1 -1
  201. package/dist/jsx/record/RecordGallery.native.js.map +1 -1
  202. package/dist/jsx/record/RecordHero.native.js.map +1 -1
  203. package/dist/jsx/record/RecordPage.native.js.map +1 -1
  204. package/dist/jsx/record/RecordPager.native.js.map +1 -1
  205. package/dist/jsx/record/RecordRows.native.js.map +1 -1
  206. package/dist/jsx/record/recordData.native.js.map +1 -1
  207. package/dist/jsx/record/recordSections.native.js.map +1 -1
  208. package/dist/jsx/recordShape.native.js.map +1 -1
  209. package/dist/jsx/report/ReportGrid.native.js.map +1 -1
  210. package/dist/jsx/report/ReportViewToolbar.native.js.map +1 -1
  211. package/dist/jsx/report/exportCsv.native.js.map +1 -1
  212. package/dist/jsx/report/queryReport.native.js.map +1 -1
  213. package/dist/jsx/report/queryReportColumns.native.js.map +1 -1
  214. package/dist/jsx/report/reportViewModel.native.js.map +1 -1
  215. package/dist/jsx/report/savedReports.native.js.map +1 -1
  216. package/dist/jsx/report/useFrappeReport.native.js.map +1 -1
  217. package/dist/jsx/report/useQueryReport.native.js.map +1 -1
  218. package/dist/jsx/resolveControl.native.js.map +1 -1
  219. package/dist/jsx/schemaForm/SchemaWizard.native.js.map +1 -1
  220. package/dist/jsx/schemaForm/applyFieldOrder.native.js.map +1 -1
  221. package/dist/jsx/schemaForm/index.native.js +1 -1
  222. package/dist/jsx/schemaForm/index.native.js.map +1 -1
  223. package/dist/jsx/sessionUser.native.js.map +1 -1
  224. package/dist/jsx/shared/devWarn.native.js.map +1 -1
  225. package/dist/jsx/shared/useKeyboardInset.native.js.map +1 -1
  226. package/dist/jsx/syncError.native.js.map +1 -1
  227. package/dist/jsx/useConnectionFilters.native.js.map +1 -1
  228. package/dist/jsx/useFrappeAggregate.native.js.map +1 -1
  229. package/dist/jsx/useFrappeFacets.native.js.map +1 -1
  230. package/dist/jsx/useFrappeForm.native.js.map +1 -1
  231. package/dist/jsx/useFrappePagination.native.js.map +1 -1
  232. package/dist/jsx/useFrappeRows.native.js.map +1 -1
  233. package/package.json +35 -15
  234. package/src/FrappeListView.tsx +0 -4
  235. package/src/desk/index.ts +18 -0
  236. package/src/fields/Link.tsx +0 -12
  237. package/src/formEngine/index.ts +2 -2
  238. package/src/index.gnome.ts +191 -0
  239. package/src/index.ts +2 -7
  240. package/types/FrappeListView.d.ts +0 -2
  241. package/types/FrappeListView.d.ts.map +1 -1
  242. package/types/desk/index.d.ts +12 -0
  243. package/types/desk/index.d.ts.map +1 -0
  244. package/types/fields/Link.d.ts +0 -8
  245. package/types/fields/Link.d.ts.map +1 -1
  246. package/types/formEngine/index.d.ts +2 -2
  247. package/types/index.d.ts +0 -1
  248. package/types/index.d.ts.map +1 -1
  249. package/types/index.gnome.d.ts +105 -0
  250. package/types/index.gnome.d.ts.map +1 -0
  251. package/dist/cjs/fields/FileUpload.cjs +0 -404
  252. package/dist/cjs/fields/FileUpload.native.js +0 -431
  253. package/dist/cjs/fields/FileUpload.native.js.map +0 -1
  254. package/dist/esm/fields/FileUpload.mjs +0 -379
  255. package/dist/esm/fields/FileUpload.mjs.map +0 -1
  256. package/dist/esm/fields/FileUpload.native.js +0 -403
  257. package/dist/esm/fields/FileUpload.native.js.map +0 -1
  258. package/dist/jsx/fields/FileUpload.mjs +0 -379
  259. package/dist/jsx/fields/FileUpload.mjs.map +0 -1
  260. package/dist/jsx/fields/FileUpload.native.js +0 -431
  261. package/dist/jsx/fields/FileUpload.native.js.map +0 -1
  262. package/src/fields/FileUpload.spec.tsx +0 -351
  263. package/src/fields/FileUpload.tsx +0 -472
  264. package/src/fields/FrappeFileUpload.stories.tsx +0 -139
  265. package/types/fields/FileUpload.d.ts +0 -27
  266. package/types/fields/FileUpload.d.ts.map +0 -1
@@ -1,472 +0,0 @@
1
- import { useFrappeCollection } from "@multiplatform.one/frappe";
2
- import { IconButtonGroup, Skeleton } from "@multiplatform.one/forms";
3
- import { FileIcon, UploadIcon } from "@phosphor-icons/react";
4
- import { sizeRecipeForToken, useResolvedKnobs, sizeTokenStep } from "@multiplatform.one/theme";
5
- import type React from "react";
6
- import { useCallback, useEffect, useRef, useState } from "react";
7
- import { useTranslation } from "react-i18next";
8
- import { Button, Image, Label, Text as TamaguiText, XStack, YStack } from "tamagui";
9
- import { useFrappeConfig } from "../FrappeProvider";
10
- import { Text, ActionButton, componentColors } from "@multiplatform.one/components";
11
-
12
- export type FrappeFileUploadHandler = (
13
- file: File,
14
- ctx: { onProgress: (percent: number) => void; signal: AbortSignal },
15
- ) => Promise<string>;
16
-
17
- export interface FileUploadProps {
18
- label?: string;
19
- value?: string;
20
- onChange: (fileUrl: string) => void;
21
- accept?: string;
22
- isImage?: boolean;
23
- disabled?: boolean;
24
- required?: boolean;
25
- maxSize?: number;
26
- baseURL?: string;
27
- auth?: any;
28
- helperText?: string;
29
- readOnly?: boolean;
30
- skeleton?: boolean;
31
- /**
32
- * Override the File-doctype transport. Same progress/abort contract as
33
- * forms FileUpload; resolve with the committed URL. Tests and custom
34
- * desks inject this; the default reads the file and inserts a File doc.
35
- */
36
- onUpload?: FrappeFileUploadHandler;
37
- }
38
-
39
- type TransferStatus = "uploading" | "failed";
40
-
41
- type TransferState = {
42
- file: File;
43
- status: TransferStatus;
44
- progress: number;
45
- problem?: string;
46
- action?: string;
47
- controller: AbortController;
48
- };
49
-
50
- function formatFileSize(bytes: number): string {
51
- if (bytes === 0) return "0 Bytes";
52
- const k = 1024;
53
- const sizes = ["Bytes", "KB", "MB", "GB"];
54
- const i = Math.floor(Math.log(bytes) / Math.log(k));
55
- return `${Number.parseFloat((bytes / k ** i).toFixed(2))} ${sizes[i]}`;
56
- }
57
-
58
- function readFileAsDataUrl(
59
- file: File,
60
- { onProgress, signal }: { onProgress: (percent: number) => void; signal: AbortSignal },
61
- ): Promise<string> {
62
- return new Promise((resolve, reject) => {
63
- const reader = new FileReader();
64
- const onAbort = () => {
65
- reader.abort();
66
- reject(new DOMException("Aborted", "AbortError"));
67
- };
68
- if (signal.aborted) {
69
- onAbort();
70
- return;
71
- }
72
- signal.addEventListener("abort", onAbort);
73
- reader.onprogress = (event) => {
74
- if (event.lengthComputable && event.total > 0) {
75
- onProgress(Math.round((event.loaded / event.total) * 100));
76
- }
77
- };
78
- reader.onload = () => {
79
- signal.removeEventListener("abort", onAbort);
80
- onProgress(100);
81
- resolve(reader.result as string);
82
- };
83
- reader.onerror = () => {
84
- signal.removeEventListener("abort", onAbort);
85
- reject(new Error("Failed to read file"));
86
- };
87
- reader.onabort = () => {
88
- signal.removeEventListener("abort", onAbort);
89
- reject(new DOMException("Aborted", "AbortError"));
90
- };
91
- reader.readAsDataURL(file);
92
- });
93
- }
94
-
95
- export function FileUpload({
96
- label,
97
- value,
98
- onChange,
99
- accept,
100
- isImage = false,
101
- disabled = false,
102
- required = false,
103
- maxSize = 5 * 1024 * 1024,
104
- baseURL,
105
- auth,
106
- helperText,
107
- readOnly = false,
108
- skeleton = false,
109
- onUpload,
110
- }: FileUploadProps) {
111
- const { t } = useTranslation();
112
- const { knobProps } = useResolvedKnobs();
113
- const small = sizeTokenStep(knobProps.sizeToken) < 0;
114
- const config = useFrappeConfig();
115
- const fileInputRef = useRef<HTMLInputElement>(null);
116
- const [transfer, setTransfer] = useState<TransferState | null>(null);
117
- const [error, setError] = useState<string | null>(null);
118
- const transferRef = useRef<TransferState | null>(null);
119
- const onUploadRef = useRef(onUpload);
120
- onUploadRef.current = onUpload;
121
- const isLocked = disabled || readOnly;
122
- const minHeight = small ? 40 : 56;
123
- const previewSize = small ? 120 : 200;
124
- const uploading = transfer?.status === "uploading";
125
-
126
- const finalBaseURL = baseURL ?? config?.baseURL ?? "";
127
- const finalConfig = finalBaseURL
128
- ? {
129
- baseURL: finalBaseURL,
130
- socketPort: config?.socketPort,
131
- siteName: config?.siteName,
132
- socketBaseURL: config?.socketBaseURL,
133
- auth: auth ?? config?.auth,
134
- syncOptions: config?.syncOptions,
135
- realtime: config?.realtime,
136
- fixtures: config?.fixtures,
137
- }
138
- : undefined;
139
-
140
- const fileCollection = useFrappeCollection<{
141
- name: string;
142
- doctype: string;
143
- file_name: string;
144
- file_url: string;
145
- is_private: 0 | 1;
146
- }>(finalConfig, "File", {
147
- fields: ["name", "file_name", "file_url", "is_private"],
148
- });
149
-
150
- useEffect(() => {
151
- return () => {
152
- transferRef.current?.controller.abort();
153
- };
154
- }, []);
155
-
156
- const defaultUpload = useCallback<FrappeFileUploadHandler>(
157
- async (file, ctx) => {
158
- const base64 = await readFileAsDataUrl(file, ctx);
159
- if (fileCollection) {
160
- const tx = fileCollection.insert({
161
- doctype: "File",
162
- name: `new-${Date.now()}`,
163
- file_name: file.name,
164
- file_url: base64,
165
- is_private: 0,
166
- });
167
- await tx.isPersisted.promise;
168
- }
169
- return base64;
170
- },
171
- [fileCollection],
172
- );
173
-
174
- const runTransfer = useCallback(
175
- async (file: File, controller: AbortController) => {
176
- const upload = onUploadRef.current ?? defaultUpload;
177
- try {
178
- const url = await upload(file, {
179
- onProgress: (percent) => {
180
- const n = Math.min(100, Math.max(0, Math.round(Number(percent) || 0)));
181
- setTransfer((prev) => (prev ? { ...prev, progress: n } : prev));
182
- },
183
- signal: controller.signal,
184
- });
185
- if (controller.signal.aborted) return;
186
- setTransfer(null);
187
- transferRef.current = null;
188
- setError(null);
189
- onChange(url);
190
- } catch (err) {
191
- if (
192
- controller.signal.aborted ||
193
- (err instanceof Error && err.name === "AbortError") ||
194
- (typeof DOMException !== "undefined" &&
195
- err instanceof DOMException &&
196
- err.name === "AbortError")
197
- ) {
198
- return;
199
- }
200
- const problem = err instanceof Error && err.message ? err.message : t("Upload failed");
201
- setTransfer((prev) =>
202
- prev
203
- ? {
204
- ...prev,
205
- status: "failed",
206
- problem,
207
- action: t("Try again or remove the file"),
208
- }
209
- : prev,
210
- );
211
- // Per-file failure stays on the row (DG-VAL-04). Do not clear `value`.
212
- setError(null);
213
- }
214
- },
215
- [defaultUpload, onChange, t],
216
- );
217
-
218
- const startTransfer = useCallback(
219
- (file: File) => {
220
- transferRef.current?.controller.abort();
221
- const controller = new AbortController();
222
- const next: TransferState = {
223
- file,
224
- status: "uploading",
225
- progress: 0,
226
- controller,
227
- };
228
- transferRef.current = next;
229
- setTransfer(next);
230
- setError(null);
231
- void runTransfer(file, controller);
232
- },
233
- [runTransfer],
234
- );
235
-
236
- const handleFileSelect = (event: React.ChangeEvent<HTMLInputElement>) => {
237
- if (isLocked) return;
238
- const file = event.target.files?.[0];
239
- if (!file) return;
240
-
241
- if (maxSize && file.size > maxSize) {
242
- setError(`File size exceeds ${Math.round(maxSize / 1024 / 1024)}MB limit`);
243
- return;
244
- }
245
-
246
- startTransfer(file);
247
- };
248
-
249
- const handleCancel = () => {
250
- transferRef.current?.controller.abort();
251
- transferRef.current = null;
252
- setTransfer(null);
253
- if (fileInputRef.current) fileInputRef.current.value = "";
254
- };
255
-
256
- const handleRetry = () => {
257
- if (!transfer) return;
258
- startTransfer(transfer.file);
259
- };
260
-
261
- const handleRemove = () => {
262
- if (isLocked) return;
263
- handleCancel();
264
- onChange("");
265
- if (fileInputRef.current) {
266
- fileInputRef.current.value = "";
267
- }
268
- };
269
-
270
- const handleRemoveFailed = () => {
271
- handleCancel();
272
- };
273
-
274
- const handleClick = () => {
275
- if (isLocked || uploading) return;
276
- fileInputRef.current?.click();
277
- };
278
-
279
- if (skeleton) {
280
- return (
281
- <YStack {...knobProps.gap}>
282
- {label && (
283
- <Skeleton
284
- variant="text"
285
- width="30%"
286
- height={sizeRecipeForToken(knobProps.sizeToken).fontSize}
287
- />
288
- )}
289
- <Skeleton variant="rounded" width="100%" height={minHeight} />
290
- </YStack>
291
- );
292
- }
293
-
294
- // LC-260 ICONS-OVER-WORDS: Replace and Remove are their symbols, floating
295
- // over an image on the LC-176 media plate.
296
- const fileActions =
297
- !readOnly && !transfer
298
- ? (overMedia: boolean) => (
299
- <>
300
- <ActionButton
301
- kind="replace"
302
- overMedia={overMedia}
303
- disabled={isLocked}
304
- onPress={handleClick}
305
- />
306
- <ActionButton
307
- kind="remove"
308
- overMedia={overMedia}
309
- disabled={isLocked}
310
- onPress={handleRemove}
311
- />
312
- </>
313
- )
314
- : undefined;
315
-
316
- return (
317
- <YStack {...knobProps.gap}>
318
- {label && (
319
- <Label {...knobProps.label}>
320
- {label}
321
- {required && <TamaguiText color="$red10"> *</TamaguiText>}
322
- </Label>
323
- )}
324
-
325
- <input
326
- ref={fileInputRef}
327
- type="file"
328
- accept={accept || (isImage ? "image/*" : undefined)}
329
- style={{ display: "none" }}
330
- onChange={handleFileSelect}
331
- disabled={isLocked || uploading}
332
- />
333
-
334
- {transfer ? (
335
- <YStack
336
- data-file-upload="transfer"
337
- data-transfer-status={transfer.status}
338
- {...knobProps.gap}
339
- {...knobProps.panelPadding}
340
- >
341
- <XStack gap="$2" alignItems="center">
342
- <FileIcon size={20} />
343
- <YStack flex={1} gap="$1">
344
- <Text numberOfLines={1}>{transfer.file.name}</Text>
345
- <Text numberOfLines={1} fontWeight="700" fontSize="$2">
346
- {formatFileSize(transfer.file.size)}
347
- </Text>
348
- </YStack>
349
- {transfer.status === "uploading" ? (
350
- <ActionButton
351
- kind="cancel"
352
- label={t("Cancel {{name}}", { name: transfer.file.name })}
353
- disabled={isLocked}
354
- onPress={handleCancel}
355
- />
356
- ) : null}
357
- </XStack>
358
- {transfer.status === "uploading" ? (
359
- <YStack
360
- data-file-upload="progress"
361
- role="progressbar"
362
- aria-valuemin={0}
363
- aria-valuemax={100}
364
- aria-valuenow={transfer.progress}
365
- aria-label={t("Uploading {{name}}", { name: transfer.file.name })}
366
- height={11}
367
- width="100%"
368
- borderRadius={1000}
369
- backgroundColor="$color4"
370
- overflow="hidden"
371
- >
372
- <YStack
373
- height="100%"
374
- width={`${transfer.progress}%`}
375
- backgroundColor={componentColors.indicator.mark}
376
- />
377
- </YStack>
378
- ) : null}
379
- {transfer.status === "failed" ? (
380
- <YStack {...knobProps.gap}>
381
- <Text color="$red11" fontSize="$2">
382
- {transfer.problem}
383
- </Text>
384
- <Text color="$color11" fontSize="$2">
385
- {transfer.action}
386
- </Text>
387
- <XStack {...knobProps.gap}>
388
- <ActionButton
389
- kind="retry"
390
- label={t("Retry {{name}}", { name: transfer.file.name })}
391
- disabled={isLocked}
392
- onPress={handleRetry}
393
- />
394
- <ActionButton
395
- kind="remove"
396
- label={t("Remove {{name}}", { name: transfer.file.name })}
397
- disabled={isLocked}
398
- onPress={handleRemoveFailed}
399
- />
400
- </XStack>
401
- </YStack>
402
- ) : null}
403
- </YStack>
404
- ) : null}
405
-
406
- {!value && !transfer ? (
407
- <Button
408
- size="$3"
409
- icon={UploadIcon}
410
- disabled={isLocked}
411
- onPress={handleClick}
412
- {...knobProps.borderRadius}
413
- >
414
- {t("Choose File")}
415
- </Button>
416
- ) : null}
417
-
418
- {value ? (
419
- isImage ? (
420
- <YStack position="relative" alignSelf="flex-start">
421
- <Image
422
- // src (string), not source: the RN source object leaks onto the
423
- // DOM <img> as source="[object Object]" on web
424
- src={value}
425
- alt={t("Uploaded file preview")}
426
- width={previewSize}
427
- height={previewSize}
428
- {...knobProps.borderRadius}
429
- objectFit="cover"
430
- />
431
- {fileActions ? (
432
- <XStack
433
- data-file-upload="preview-actions"
434
- position="absolute"
435
- top="$2"
436
- right="$2"
437
- zIndex={1}
438
- {...knobProps.gap}
439
- >
440
- <IconButtonGroup plate="media">{fileActions(true)}</IconButtonGroup>
441
- </XStack>
442
- ) : null}
443
- </YStack>
444
- ) : (
445
- <XStack
446
- gap="$2"
447
- alignItems="center"
448
- {...knobProps.panelPadding}
449
- {...{ backgroundColor: "$color2" }}
450
- >
451
- <FileIcon size={20} />
452
- <Text flex={1} numberOfLines={1}>
453
- {value.split("/").pop() || value}
454
- </Text>
455
- {fileActions?.(false)}
456
- </XStack>
457
- )
458
- ) : null}
459
-
460
- {helperText && !error && !transfer ? (
461
- <Text color="$color11" fontSize="$2">
462
- {helperText}
463
- </Text>
464
- ) : null}
465
- {error ? (
466
- <Text color="$red11" fontSize="$2">
467
- {error}
468
- </Text>
469
- ) : null}
470
- </YStack>
471
- );
472
- }
@@ -1,139 +0,0 @@
1
- import { InMemoryFixtureProvider } from "@multiplatform.one/frappe";
2
- import { action } from "@multiplatform.one/storybook";
3
- import { DataTable, type DataTableColumn } from "@multiplatform.one/table";
4
- import type { ReactNode } from "react";
5
- import { useState } from "react";
6
- import { YStack } from "tamagui";
7
- import { fieldtypeToTableField } from "../fieldRegistry";
8
- import { FrappeProvider } from "../FrappeProvider";
9
- import { FrappeStoryProvider } from "../frappeStoryTarget";
10
- import { FileUpload, type FileUploadProps } from "./FileUpload";
11
-
12
- // 1x1 transparent PNG
13
- const tinyPng =
14
- "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==";
15
-
16
- const fixtures = new InMemoryFixtureProvider({
17
- File: [
18
- {
19
- name: "FILE-001",
20
- doctype: "File",
21
- file_name: "existing.png",
22
- file_url: tinyPng,
23
- is_private: 0,
24
- },
25
- ],
26
- });
27
-
28
- export default {
29
- title: "FrappeUI/FileUpload",
30
- component: FileUpload,
31
- tags: ["!test"],
32
- parameters: {
33
- status: { type: "beta" },
34
- docs: {
35
- description: {
36
- component:
37
- "Frappe FileUpload (live-data uploader): uploads into the File doctype collection (fixture-backed here) and previews images.",
38
- },
39
- },
40
- },
41
- argTypes: {
42
- label: { control: "text" },
43
- value: { control: "text" },
44
- accept: { control: "text" },
45
- isImage: { control: "boolean" },
46
- disabled: { control: "boolean" },
47
- required: { control: "boolean" },
48
- maxSize: { control: "number" },
49
- },
50
- };
51
-
52
- type StoryProps = FileUploadProps & Record<string, unknown>;
53
-
54
- function Controlled(props: Omit<StoryProps, "onChange">) {
55
- const [value, setValue] = useState(props.value ?? "");
56
- return (
57
- <FileUpload
58
- {...(props as any)}
59
- value={value}
60
- onChange={(v: string) => {
61
- setValue(v);
62
- action("onChange")(v);
63
- }}
64
- />
65
- );
66
- }
67
-
68
- const withProvider = (node: ReactNode) => (
69
- <FrappeProvider baseURL="https://demo.frappe.cloud" fixtures={fixtures}>
70
- {node}
71
- </FrappeProvider>
72
- );
73
-
74
- export const Main = (args: StoryProps) => withProvider(<Controlled {...args} />);
75
- Main.args = { label: "Attachment" } as Partial<StoryProps>;
76
- Main.storyName = "Main";
77
-
78
- export const ImageWithPreview = (args: StoryProps) => withProvider(<Controlled {...args} />);
79
- ImageWithPreview.args = {
80
- label: "Image with existing value",
81
- isImage: true,
82
- value: tinyPng,
83
- } as Partial<StoryProps>;
84
-
85
- export const BrokenPreview = (args: StoryProps) => withProvider(<Controlled {...args} />);
86
- BrokenPreview.args = {
87
- label: "Broken image value",
88
- isImage: true,
89
- value: "https://invalid.example/broken.png",
90
- } as Partial<StoryProps>;
91
-
92
- export const TinyMaxSize = (args: StoryProps) => withProvider(<Controlled {...args} />);
93
- TinyMaxSize.args = {
94
- label: "Max 1 byte (any upload should error)",
95
- maxSize: 1,
96
- } as Partial<StoryProps>;
97
-
98
- export const States = (args: StoryProps) =>
99
- withProvider(
100
- <YStack gap="$4" maxWidth={420}>
101
- <Controlled {...args} label="Disabled" disabled />
102
- <Controlled {...args} label="Required" required />
103
- <Controlled
104
- {...args}
105
- label="error probe (no error prop on this adapter)"
106
- {...({ error: "This should show but cannot" } as any)}
107
- />
108
- </YStack>,
109
- );
110
- States.args = {} as Partial<StoryProps>;
111
-
112
- type FileUploadRow = { id: string; label: string; value: string };
113
-
114
- export const InTable = {
115
- name: "In table",
116
- render: function InTableRender() {
117
- const [data, setData] = useState<FileUploadRow[]>([
118
- { id: "1", label: "Alpha", value: tinyPng },
119
- { id: "2", label: "Beta", value: "report.pdf" },
120
- { id: "3", label: "Gamma", value: "" },
121
- ]);
122
- const columns: DataTableColumn<FileUploadRow>[] = [
123
- { accessorKey: "label", header: "Row" },
124
- { accessorKey: "value", header: "File", ...fieldtypeToTableField("Attach") },
125
- ];
126
- return (
127
- <FrappeStoryProvider fixtures={fixtures}>
128
- <YStack width="100%" height={320}>
129
- <DataTable
130
- data={data}
131
- columns={columns}
132
- enablePagination={false}
133
- onDataChange={setData}
134
- />
135
- </YStack>
136
- </FrappeStoryProvider>
137
- );
138
- },
139
- };
@@ -1,27 +0,0 @@
1
- export type FrappeFileUploadHandler = (file: File, ctx: {
2
- onProgress: (percent: number) => void;
3
- signal: AbortSignal;
4
- }) => Promise<string>;
5
- export interface FileUploadProps {
6
- label?: string;
7
- value?: string;
8
- onChange: (fileUrl: string) => void;
9
- accept?: string;
10
- isImage?: boolean;
11
- disabled?: boolean;
12
- required?: boolean;
13
- maxSize?: number;
14
- baseURL?: string;
15
- auth?: any;
16
- helperText?: string;
17
- readOnly?: boolean;
18
- skeleton?: boolean;
19
- /**
20
- * Override the File-doctype transport. Same progress/abort contract as
21
- * forms FileUpload; resolve with the committed URL. Tests and custom
22
- * desks inject this; the default reads the file and inserts a File doc.
23
- */
24
- onUpload?: FrappeFileUploadHandler;
25
- }
26
- export declare function FileUpload({ label, value, onChange, accept, isImage, disabled, required, maxSize, baseURL, auth, helperText, readOnly, skeleton, onUpload, }: FileUploadProps): import("react/jsx-runtime").JSX.Element;
27
- //# sourceMappingURL=FileUpload.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"FileUpload.d.ts","sourceRoot":"","sources":["../../src/fields/FileUpload.tsx"],"names":[],"mappings":"AAWA,MAAM,MAAM,uBAAuB,GAAG,CACpC,IAAI,EAAE,IAAI,EACV,GAAG,EAAE;IAAE,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAAC,MAAM,EAAE,WAAW,CAAA;CAAE,KAChE,OAAO,CAAC,MAAM,CAAC,CAAC;AAErB,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,uBAAuB,CAAC;CACpC;AA0DD,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,KAAK,EACL,QAAQ,EACR,MAAM,EACN,OAAe,EACf,QAAgB,EAChB,QAAgB,EAChB,OAAyB,EACzB,OAAO,EACP,IAAI,EACJ,UAAU,EACV,QAAgB,EAChB,QAAgB,EAChB,QAAQ,GACT,EAAE,eAAe,2CA0WjB"}