@multiplatform.one/frappe-ui 6.4.2 → 6.6.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.
- package/dist/cjs/FormSidebar.cjs +2 -2
- package/dist/cjs/FormSidebar.native.js +2 -2
- package/dist/cjs/FormSidebar.native.js.map +1 -1
- package/dist/cjs/FrappeAssignment.cjs +8 -4
- package/dist/cjs/FrappeAssignment.native.js +8 -4
- package/dist/cjs/FrappeAssignment.native.js.map +1 -1
- package/dist/cjs/FrappeCalendar.cjs +2 -8
- package/dist/cjs/FrappeCalendar.native.js +2 -8
- package/dist/cjs/FrappeCalendar.native.js.map +1 -1
- package/dist/cjs/FrappeConnectionIndicator.cjs +1 -0
- package/dist/cjs/FrappeConnectionIndicator.native.js +1 -0
- package/dist/cjs/FrappeConnectionIndicator.native.js.map +1 -1
- package/dist/cjs/FrappeDashboard.cjs +4 -4
- package/dist/cjs/FrappeDashboard.native.js +4 -4
- package/dist/cjs/FrappeDashboard.native.js.map +1 -1
- package/dist/cjs/FrappeGantt.cjs +2 -8
- package/dist/cjs/FrappeGantt.native.js +3 -9
- package/dist/cjs/FrappeGantt.native.js.map +1 -1
- package/dist/cjs/FrappeKanban.cjs +32 -21
- package/dist/cjs/FrappeKanban.native.js +36 -23
- package/dist/cjs/FrappeKanban.native.js.map +1 -1
- package/dist/cjs/FrappeLikedBy.cjs +2 -1
- package/dist/cjs/FrappeLikedBy.native.js +2 -1
- package/dist/cjs/FrappeLikedBy.native.js.map +1 -1
- package/dist/cjs/FrappeList.cjs +0 -1
- package/dist/cjs/FrappeList.native.js +10 -2
- package/dist/cjs/FrappeList.native.js.map +1 -1
- package/dist/cjs/FrappeListView.cjs +10 -8
- package/dist/cjs/FrappeListView.native.js +10 -8
- package/dist/cjs/FrappeListView.native.js.map +1 -1
- package/dist/cjs/FrappeReportBuilder.cjs +1 -1
- package/dist/cjs/FrappeReportBuilder.native.js +1 -1
- package/dist/cjs/FrappeTable.cjs +4 -0
- package/dist/cjs/FrappeTable.native.js +10 -0
- package/dist/cjs/FrappeTable.native.js.map +1 -1
- package/dist/cjs/FrappeTreeView.cjs +1 -1
- package/dist/cjs/FrappeTreeView.native.js +1 -1
- package/dist/cjs/desk/DeskFormPage.cjs +1 -1
- package/dist/cjs/desk/DeskFormPage.native.js +1 -1
- package/dist/cjs/desk/DeskFormPage.native.js.map +1 -1
- package/dist/cjs/desk/DeskListPage.cjs +1 -1
- package/dist/cjs/desk/DeskListPage.native.js +1 -1
- package/dist/cjs/desk/DeskListPage.native.js.map +1 -1
- package/dist/cjs/fieldRegistry.native.js.map +1 -1
- package/dist/cjs/fields/Date/date.native.js +11 -48
- package/dist/cjs/fields/Date/date.native.js.map +1 -1
- package/dist/cjs/fields/Date/index.cjs +10 -3
- package/dist/cjs/fields/Date/index.native.js +15 -3
- package/dist/cjs/fields/Date/index.native.js.map +1 -1
- package/dist/cjs/fields/Date/wire.cjs +49 -0
- package/dist/cjs/fields/Date/wire.native.js +55 -0
- package/dist/cjs/fields/Date/wire.native.js.map +1 -0
- package/dist/cjs/fields/MarkdownEditor/index.native.js.map +1 -1
- package/dist/cjs/fields/ReadOnly/index.cjs +8 -36
- package/dist/cjs/fields/ReadOnly/index.native.js +12 -36
- package/dist/cjs/fields/ReadOnly/index.native.js.map +1 -1
- package/dist/cjs/fields/TableMultiSelect.cjs +7 -15
- package/dist/cjs/fields/TableMultiSelect.native.js +7 -15
- package/dist/cjs/fields/TableMultiSelect.native.js.map +1 -1
- package/dist/cjs/fields/TextEditor/index.cjs +11 -6
- package/dist/cjs/schemaForm/index.cjs +10 -2
- package/dist/cjs/schemaForm/index.native.js +10 -2
- package/dist/cjs/schemaForm/index.native.js.map +1 -1
- package/dist/esm/FormSidebar.mjs +2 -2
- package/dist/esm/FormSidebar.mjs.map +1 -1
- package/dist/esm/FormSidebar.native.js +2 -2
- package/dist/esm/FormSidebar.native.js.map +1 -1
- package/dist/esm/FrappeAssignment.mjs +6 -2
- package/dist/esm/FrappeAssignment.mjs.map +1 -1
- package/dist/esm/FrappeAssignment.native.js +6 -2
- package/dist/esm/FrappeAssignment.native.js.map +1 -1
- package/dist/esm/FrappeCalendar.mjs +3 -9
- package/dist/esm/FrappeCalendar.mjs.map +1 -1
- package/dist/esm/FrappeCalendar.native.js +3 -9
- package/dist/esm/FrappeCalendar.native.js.map +1 -1
- package/dist/esm/FrappeConnectionIndicator.mjs +2 -1
- package/dist/esm/FrappeConnectionIndicator.mjs.map +1 -1
- package/dist/esm/FrappeConnectionIndicator.native.js +2 -1
- package/dist/esm/FrappeConnectionIndicator.native.js.map +1 -1
- package/dist/esm/FrappeDashboard.mjs +5 -5
- package/dist/esm/FrappeDashboard.mjs.map +1 -1
- package/dist/esm/FrappeDashboard.native.js +5 -5
- package/dist/esm/FrappeDashboard.native.js.map +1 -1
- package/dist/esm/FrappeGantt.mjs +3 -9
- package/dist/esm/FrappeGantt.mjs.map +1 -1
- package/dist/esm/FrappeGantt.native.js +4 -10
- package/dist/esm/FrappeGantt.native.js.map +1 -1
- package/dist/esm/FrappeKanban.mjs +33 -22
- package/dist/esm/FrappeKanban.mjs.map +1 -1
- package/dist/esm/FrappeKanban.native.js +37 -24
- package/dist/esm/FrappeKanban.native.js.map +1 -1
- package/dist/esm/FrappeLikedBy.mjs +2 -1
- package/dist/esm/FrappeLikedBy.mjs.map +1 -1
- package/dist/esm/FrappeLikedBy.native.js +2 -1
- package/dist/esm/FrappeLikedBy.native.js.map +1 -1
- package/dist/esm/FrappeList.mjs +0 -1
- package/dist/esm/FrappeList.mjs.map +1 -1
- package/dist/esm/FrappeList.native.js +10 -2
- package/dist/esm/FrappeList.native.js.map +1 -1
- package/dist/esm/FrappeListView.mjs +12 -10
- package/dist/esm/FrappeListView.mjs.map +1 -1
- package/dist/esm/FrappeListView.native.js +12 -10
- package/dist/esm/FrappeListView.native.js.map +1 -1
- package/dist/esm/FrappeReportBuilder.mjs +1 -1
- package/dist/esm/FrappeReportBuilder.mjs.map +1 -1
- package/dist/esm/FrappeReportBuilder.native.js +1 -1
- package/dist/esm/FrappeTable.mjs +4 -0
- package/dist/esm/FrappeTable.mjs.map +1 -1
- package/dist/esm/FrappeTable.native.js +10 -0
- package/dist/esm/FrappeTable.native.js.map +1 -1
- package/dist/esm/FrappeTreeView.mjs +1 -1
- package/dist/esm/FrappeTreeView.mjs.map +1 -1
- package/dist/esm/FrappeTreeView.native.js +1 -1
- package/dist/esm/desk/DeskFormPage.mjs +1 -1
- package/dist/esm/desk/DeskFormPage.mjs.map +1 -1
- package/dist/esm/desk/DeskFormPage.native.js +1 -1
- package/dist/esm/desk/DeskFormPage.native.js.map +1 -1
- package/dist/esm/desk/DeskListPage.mjs +1 -1
- package/dist/esm/desk/DeskListPage.mjs.map +1 -1
- package/dist/esm/desk/DeskListPage.native.js +1 -1
- package/dist/esm/desk/DeskListPage.native.js.map +1 -1
- package/dist/esm/fieldRegistry.mjs.map +1 -1
- package/dist/esm/fields/Date/date.native.js +12 -49
- package/dist/esm/fields/Date/date.native.js.map +1 -1
- package/dist/esm/fields/Date/index.mjs +10 -3
- package/dist/esm/fields/Date/index.mjs.map +1 -1
- package/dist/esm/fields/Date/index.native.js +15 -3
- package/dist/esm/fields/Date/index.native.js.map +1 -1
- package/dist/esm/fields/Date/wire.mjs +23 -0
- package/dist/esm/fields/Date/wire.mjs.map +1 -0
- package/dist/esm/fields/Date/wire.native.js +26 -0
- package/dist/esm/fields/Date/wire.native.js.map +1 -0
- package/dist/esm/fields/MarkdownEditor/index.native.js.map +1 -1
- package/dist/esm/fields/ReadOnly/index.mjs +9 -37
- package/dist/esm/fields/ReadOnly/index.mjs.map +1 -1
- package/dist/esm/fields/ReadOnly/index.native.js +13 -37
- package/dist/esm/fields/ReadOnly/index.native.js.map +1 -1
- package/dist/esm/fields/TableMultiSelect.mjs +4 -12
- package/dist/esm/fields/TableMultiSelect.mjs.map +1 -1
- package/dist/esm/fields/TableMultiSelect.native.js +4 -12
- package/dist/esm/fields/TableMultiSelect.native.js.map +1 -1
- package/dist/esm/fields/TextEditor/index.mjs +11 -6
- package/dist/esm/fields/TextEditor/index.mjs.map +1 -1
- package/dist/esm/resolveControl.mjs.map +1 -1
- package/dist/esm/schemaForm/index.mjs +10 -2
- package/dist/esm/schemaForm/index.mjs.map +1 -1
- package/dist/esm/schemaForm/index.native.js +10 -2
- package/dist/esm/schemaForm/index.native.js.map +1 -1
- package/dist/jsx/FormSidebar.mjs +2 -2
- package/dist/jsx/FormSidebar.mjs.map +1 -1
- package/dist/jsx/FormSidebar.native.js +2 -2
- package/dist/jsx/FormSidebar.native.js.map +1 -1
- package/dist/jsx/FrappeAssignment.mjs +6 -2
- package/dist/jsx/FrappeAssignment.mjs.map +1 -1
- package/dist/jsx/FrappeAssignment.native.js +8 -4
- package/dist/jsx/FrappeAssignment.native.js.map +1 -1
- package/dist/jsx/FrappeCalendar.mjs +3 -9
- package/dist/jsx/FrappeCalendar.mjs.map +1 -1
- package/dist/jsx/FrappeCalendar.native.js +2 -8
- package/dist/jsx/FrappeCalendar.native.js.map +1 -1
- package/dist/jsx/FrappeConnectionIndicator.mjs +2 -1
- package/dist/jsx/FrappeConnectionIndicator.mjs.map +1 -1
- package/dist/jsx/FrappeConnectionIndicator.native.js +1 -0
- package/dist/jsx/FrappeConnectionIndicator.native.js.map +1 -1
- package/dist/jsx/FrappeDashboard.mjs +5 -5
- package/dist/jsx/FrappeDashboard.mjs.map +1 -1
- package/dist/jsx/FrappeDashboard.native.js +4 -4
- package/dist/jsx/FrappeDashboard.native.js.map +1 -1
- package/dist/jsx/FrappeGantt.mjs +3 -9
- package/dist/jsx/FrappeGantt.mjs.map +1 -1
- package/dist/jsx/FrappeGantt.native.js +3 -9
- package/dist/jsx/FrappeGantt.native.js.map +1 -1
- package/dist/jsx/FrappeKanban.mjs +33 -22
- package/dist/jsx/FrappeKanban.mjs.map +1 -1
- package/dist/jsx/FrappeKanban.native.js +36 -23
- package/dist/jsx/FrappeKanban.native.js.map +1 -1
- package/dist/jsx/FrappeLikedBy.mjs +2 -1
- package/dist/jsx/FrappeLikedBy.mjs.map +1 -1
- package/dist/jsx/FrappeLikedBy.native.js +2 -1
- package/dist/jsx/FrappeLikedBy.native.js.map +1 -1
- package/dist/jsx/FrappeList.mjs +0 -1
- package/dist/jsx/FrappeList.mjs.map +1 -1
- package/dist/jsx/FrappeList.native.js +10 -2
- package/dist/jsx/FrappeList.native.js.map +1 -1
- package/dist/jsx/FrappeListView.mjs +12 -10
- package/dist/jsx/FrappeListView.mjs.map +1 -1
- package/dist/jsx/FrappeListView.native.js +10 -8
- package/dist/jsx/FrappeListView.native.js.map +1 -1
- package/dist/jsx/FrappeReportBuilder.mjs +1 -1
- package/dist/jsx/FrappeReportBuilder.mjs.map +1 -1
- package/dist/jsx/FrappeReportBuilder.native.js +1 -1
- package/dist/jsx/FrappeTable.mjs +4 -0
- package/dist/jsx/FrappeTable.mjs.map +1 -1
- package/dist/jsx/FrappeTable.native.js +10 -0
- package/dist/jsx/FrappeTable.native.js.map +1 -1
- package/dist/jsx/FrappeTreeView.mjs +1 -1
- package/dist/jsx/FrappeTreeView.mjs.map +1 -1
- package/dist/jsx/FrappeTreeView.native.js +1 -1
- package/dist/jsx/desk/DeskFormPage.mjs +1 -1
- package/dist/jsx/desk/DeskFormPage.mjs.map +1 -1
- package/dist/jsx/desk/DeskFormPage.native.js +1 -1
- package/dist/jsx/desk/DeskFormPage.native.js.map +1 -1
- package/dist/jsx/desk/DeskListPage.mjs +1 -1
- package/dist/jsx/desk/DeskListPage.mjs.map +1 -1
- package/dist/jsx/desk/DeskListPage.native.js +1 -1
- package/dist/jsx/desk/DeskListPage.native.js.map +1 -1
- package/dist/jsx/fieldRegistry.mjs.map +1 -1
- package/dist/jsx/fields/Date/date.native.js +11 -48
- package/dist/jsx/fields/Date/date.native.js.map +1 -1
- package/dist/jsx/fields/Date/index.mjs +10 -3
- package/dist/jsx/fields/Date/index.mjs.map +1 -1
- package/dist/jsx/fields/Date/index.native.js +15 -3
- package/dist/jsx/fields/Date/index.native.js.map +1 -1
- package/dist/jsx/fields/Date/wire.mjs +23 -0
- package/dist/jsx/fields/Date/wire.mjs.map +1 -0
- package/dist/jsx/fields/Date/wire.native.js +55 -0
- package/dist/jsx/fields/Date/wire.native.js.map +1 -0
- package/dist/jsx/fields/MarkdownEditor/index.native.js.map +1 -1
- package/dist/jsx/fields/ReadOnly/index.mjs +9 -37
- package/dist/jsx/fields/ReadOnly/index.mjs.map +1 -1
- package/dist/jsx/fields/ReadOnly/index.native.js +12 -36
- package/dist/jsx/fields/ReadOnly/index.native.js.map +1 -1
- package/dist/jsx/fields/TableMultiSelect.mjs +4 -12
- package/dist/jsx/fields/TableMultiSelect.mjs.map +1 -1
- package/dist/jsx/fields/TableMultiSelect.native.js +7 -15
- package/dist/jsx/fields/TableMultiSelect.native.js.map +1 -1
- package/dist/jsx/fields/TextEditor/index.mjs +11 -6
- package/dist/jsx/fields/TextEditor/index.mjs.map +1 -1
- package/dist/jsx/resolveControl.mjs.map +1 -1
- package/dist/jsx/schemaForm/index.mjs +10 -2
- package/dist/jsx/schemaForm/index.mjs.map +1 -1
- package/dist/jsx/schemaForm/index.native.js +10 -2
- package/dist/jsx/schemaForm/index.native.js.map +1 -1
- package/package.json +22 -21
- package/src/FormSidebar.stories.tsx +6 -6
- package/src/FormSidebar.tsx +1 -2
- package/src/FrappeAssignment.spec.tsx +97 -0
- package/src/FrappeAssignment.tsx +6 -2
- package/src/FrappeCalendar.stories.tsx +72 -1
- package/src/FrappeCalendar.tsx +3 -13
- package/src/FrappeConnectionIndicator.tsx +8 -2
- package/src/FrappeDashboard.tsx +3 -2
- package/src/FrappeForm.stories.tsx +23 -9
- package/src/FrappeGantt.stories.tsx +6 -1
- package/src/FrappeGantt.tsx +3 -13
- package/src/FrappeKanban.tsx +41 -23
- package/src/FrappeLikedBy.tsx +2 -1
- package/src/FrappeList.tsx +7 -1
- package/src/FrappeListView.tsx +16 -22
- package/src/FrappeTable.stories.tsx +3 -5
- package/src/FrappeTable.tsx +10 -0
- package/src/components/timeline.stories.tsx +7 -2
- package/src/fields/Barcode/Barcode.stories.tsx +1 -1
- package/src/fields/CodeEditor/CodeEditor.stories.tsx +1 -1
- package/src/fields/Color/Color.stories.tsx +1 -1
- package/src/fields/Date/Date.spec.tsx +149 -0
- package/src/fields/Date/Date.stories.tsx +1 -1
- package/src/fields/Date/Datetime.stories.tsx +1 -1
- package/src/fields/Date/date.native.tsx +18 -65
- package/src/fields/Date/index.tsx +1 -25
- package/src/fields/Date/wire.spec.ts +172 -0
- package/src/fields/Date/wire.ts +68 -0
- package/src/fields/DateRange/DateRange.stories.tsx +1 -1
- package/src/fields/Duration/Duration.stories.tsx +1 -1
- package/src/fields/FileUploadField/FileUploadField.stories.tsx +15 -1
- package/src/fields/Geolocation/Geolocation.stories.tsx +12 -1
- package/src/fields/Link.stories.tsx +1 -1
- package/src/fields/LongText/LongText.stories.tsx +1 -1
- package/src/fields/MentionInput/MentionInput.stories.tsx +1 -1
- package/src/fields/OTPInput/OTPInput.stories.tsx +1 -1
- package/src/fields/Password/Password.stories.tsx +1 -1
- package/src/fields/Phone/Phone.stories.tsx +1 -1
- package/src/fields/Progress/Progress.stories.tsx +1 -1
- package/src/fields/RadioGroup/RadioGroup.stories.tsx +1 -1
- package/src/fields/Rating/Rating.stories.tsx +1 -1
- package/src/fields/ReadOnly/index.tsx +23 -33
- package/src/fields/Signature/Signature.stories.tsx +1 -1
- package/src/fields/Slider/Slider.stories.tsx +1 -1
- package/src/fields/SmallText/SmallText.stories.tsx +1 -1
- package/src/fields/Switch/Switch.stories.tsx +1 -1
- package/src/fields/TableMultiSelect.tsx +5 -18
- package/src/fields/TextEditor/TextEditor.stories.tsx +1 -1
- package/src/fields/TextEditor/index.tsx +8 -5
- package/src/fields/TimePicker/TimePicker.stories.tsx +1 -1
- package/src/fields/ToggleGroup/ToggleGroup.stories.tsx +1 -1
- package/src/schemaForm/index.tsx +19 -1
- package/types/FormSidebar.d.ts.map +1 -1
- package/types/FrappeAssignment.d.ts.map +1 -1
- package/types/FrappeCalendar.d.ts.map +1 -1
- package/types/FrappeConnectionIndicator.d.ts.map +1 -1
- package/types/FrappeDashboard.d.ts.map +1 -1
- package/types/FrappeGantt.d.ts.map +1 -1
- package/types/FrappeKanban.d.ts +3 -1
- package/types/FrappeKanban.d.ts.map +1 -1
- package/types/FrappeLikedBy.d.ts.map +1 -1
- package/types/FrappeList.d.ts.map +1 -1
- package/types/FrappeListView.d.ts.map +1 -1
- package/types/FrappeTable.d.ts.map +1 -1
- package/types/fields/Date/date.native.d.ts.map +1 -1
- package/types/fields/Date/index.d.ts.map +1 -1
- package/types/fields/Date/wire.d.ts +41 -0
- package/types/fields/Date/wire.d.ts.map +1 -0
- package/types/fields/ReadOnly/index.d.ts.map +1 -1
- package/types/fields/TableMultiSelect.d.ts.map +1 -1
- package/types/fields/TextEditor/index.d.ts.map +1 -1
- package/types/schemaForm/index.d.ts +9 -1
- package/types/schemaForm/index.d.ts.map +1 -1
|
@@ -2,6 +2,7 @@ import { InMemoryFixtureProvider } from "@multiplatform.one/frappe";
|
|
|
2
2
|
import { action } from "@multiplatform.one/storybook";
|
|
3
3
|
import { YStack } from "tamagui";
|
|
4
4
|
import { Data } from "./fields/Data";
|
|
5
|
+
import { RadioGroupField } from "./fields/RadioGroup";
|
|
5
6
|
import { Select } from "./fields/Select";
|
|
6
7
|
import { Check } from "./fields/Check";
|
|
7
8
|
import { Date } from "./fields/Date";
|
|
@@ -51,7 +52,8 @@ function contactMeta() {
|
|
|
51
52
|
fieldname: "status",
|
|
52
53
|
fieldtype: "Select",
|
|
53
54
|
label: "Status",
|
|
54
|
-
options:
|
|
55
|
+
// >5 options: shorter single-select lists trip `select-too-few-options` (DG-CTRL-01).
|
|
56
|
+
options: "Active\nLead\nProspect\nCustomer\nInactive\nArchived",
|
|
55
57
|
},
|
|
56
58
|
],
|
|
57
59
|
permissions: [{ role: "All", read: 1, write: 1, create: 1 }],
|
|
@@ -72,7 +74,8 @@ function salesOrderMeta() {
|
|
|
72
74
|
fieldname: "status",
|
|
73
75
|
fieldtype: "Select",
|
|
74
76
|
label: "Status",
|
|
75
|
-
options:
|
|
77
|
+
// >5 options: shorter single-select lists trip `select-too-few-options` (DG-CTRL-01).
|
|
78
|
+
options: "Draft\nSubmitted\nIn Progress\nOn Hold\nCompleted\nCancelled",
|
|
76
79
|
},
|
|
77
80
|
{ fieldname: "is_paid", fieldtype: "Check", label: "Payment Received" },
|
|
78
81
|
{ fieldname: "notes", fieldtype: "Small Text", label: "Notes" },
|
|
@@ -172,7 +175,7 @@ export const CreateForm = () => (
|
|
|
172
175
|
label="Status"
|
|
173
176
|
value={doc.status || ""}
|
|
174
177
|
onChange={(v) => setValue("status", v)}
|
|
175
|
-
options={["Active", "Lead", "Inactive"]}
|
|
178
|
+
options={["Active", "Lead", "Prospect", "Customer", "Inactive", "Archived"]}
|
|
176
179
|
/>
|
|
177
180
|
</YStack>
|
|
178
181
|
)}
|
|
@@ -227,7 +230,7 @@ export const EditForm = () => (
|
|
|
227
230
|
label="Status"
|
|
228
231
|
value={doc.status || ""}
|
|
229
232
|
onChange={(v) => setValue("status", v)}
|
|
230
|
-
options={["Active", "Lead", "Inactive"]}
|
|
233
|
+
options={["Active", "Lead", "Prospect", "Customer", "Inactive", "Archived"]}
|
|
231
234
|
/>
|
|
232
235
|
</YStack>
|
|
233
236
|
)}
|
|
@@ -359,7 +362,7 @@ export const ComplexForm = () => (
|
|
|
359
362
|
label="Status"
|
|
360
363
|
value={doc.status || ""}
|
|
361
364
|
onChange={(v) => setValue("status", v)}
|
|
362
|
-
options={["Draft", "Submitted", "Completed", "Cancelled"]}
|
|
365
|
+
options={["Draft", "Submitted", "In Progress", "On Hold", "Completed", "Cancelled"]}
|
|
363
366
|
/>
|
|
364
367
|
<Check
|
|
365
368
|
label="Payment Received"
|
|
@@ -438,18 +441,29 @@ export const Live = (args: { name?: string }) => (
|
|
|
438
441
|
error={errors.description}
|
|
439
442
|
disabled={disabled}
|
|
440
443
|
/>
|
|
441
|
-
|
|
444
|
+
{/* The real ToDo doctype pins 3-option enums; DG-CTRL-01 routes short
|
|
445
|
+
single choices to RadioGroup, so the live arm composes the honest
|
|
446
|
+
control instead of tripping `select-too-few-options` at rest. */}
|
|
447
|
+
<RadioGroupField
|
|
442
448
|
label="Status"
|
|
443
449
|
value={doc.status || ""}
|
|
444
450
|
onChange={(v) => setValue("status", v)}
|
|
445
|
-
options={[
|
|
451
|
+
options={[
|
|
452
|
+
{ label: "Open", value: "Open" },
|
|
453
|
+
{ label: "Closed", value: "Closed" },
|
|
454
|
+
{ label: "Cancelled", value: "Cancelled" },
|
|
455
|
+
]}
|
|
446
456
|
disabled={disabled}
|
|
447
457
|
/>
|
|
448
|
-
<
|
|
458
|
+
<RadioGroupField
|
|
449
459
|
label="Priority"
|
|
450
460
|
value={doc.priority || ""}
|
|
451
461
|
onChange={(v) => setValue("priority", v)}
|
|
452
|
-
options={[
|
|
462
|
+
options={[
|
|
463
|
+
{ label: "Low", value: "Low" },
|
|
464
|
+
{ label: "Medium", value: "Medium" },
|
|
465
|
+
{ label: "High", value: "High" },
|
|
466
|
+
]}
|
|
453
467
|
disabled={disabled}
|
|
454
468
|
/>
|
|
455
469
|
</YStack>
|
|
@@ -31,7 +31,12 @@ interface ProjectTask {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
const today = new Date();
|
|
34
|
-
|
|
34
|
+
// LOCAL parts, matching FrappeGantt's own `datePart`: a `toISOString()` date is
|
|
35
|
+
// UTC, so east of Greenwich the fixture names yesterday for the hour after
|
|
36
|
+
// local midnight and every bar sits a day off the view's "today" marker.
|
|
37
|
+
const pad2 = (n: number) => String(n).padStart(2, "0");
|
|
38
|
+
const formatDate = (date: Date) =>
|
|
39
|
+
`${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())}`;
|
|
35
40
|
const dayOffset = (days: number) => formatDate(new Date(today.getTime() + days * 86400000));
|
|
36
41
|
|
|
37
42
|
// Fixture doc colors come from the sanctioned categorical palette (LC-27
|
package/src/FrappeGantt.tsx
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
import React, { useCallback, useMemo, useState, type ReactNode } from "react";
|
|
9
9
|
import { Text, YStack, type YStackProps } from "tamagui";
|
|
10
10
|
import { useTranslation } from "react-i18next";
|
|
11
|
-
import { useChartPalette, useResolvedKnobs } from "@multiplatform.one/theme";
|
|
11
|
+
import { parseCalendarDate, useChartPalette, useResolvedKnobs } from "@multiplatform.one/theme";
|
|
12
12
|
import { FrappeConnectionIndicator } from "./FrappeConnectionIndicator";
|
|
13
13
|
import { useFrappeConfig } from "./FrappeProvider";
|
|
14
14
|
import { describeSyncError } from "./syncError";
|
|
@@ -56,16 +56,6 @@ export interface FrappeGanttProps<TDoc extends { name: string; doctype: string }
|
|
|
56
56
|
initialDate?: Date;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
/**
|
|
60
|
-
* Parse a date string from Frappe (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS).
|
|
61
|
-
*/
|
|
62
|
-
function parseDate(dateStr: string | Date | undefined): Date | null {
|
|
63
|
-
if (!dateStr) return null;
|
|
64
|
-
if (dateStr instanceof Date) return dateStr;
|
|
65
|
-
const parsed = new Date(dateStr.replace(" ", "T"));
|
|
66
|
-
return isNaN(parsed.getTime()) ? null : parsed;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
59
|
/**
|
|
70
60
|
* Format a moved date back into the same shape the document field used:
|
|
71
61
|
* Date stays Date, "YYYY-MM-DD HH:MM:SS" keeps its time part, plain
|
|
@@ -195,9 +185,9 @@ export function FrappeGantt<TDoc extends { name: string; doctype: string }>({
|
|
|
195
185
|
const tasks = useMemo((): GanttTask[] => {
|
|
196
186
|
return docs
|
|
197
187
|
.map((doc) => {
|
|
198
|
-
const start =
|
|
188
|
+
const start = parseCalendarDate((doc as any)[startField]);
|
|
199
189
|
if (!start) return null;
|
|
200
|
-
const end =
|
|
190
|
+
const end = parseCalendarDate((doc as any)[endField]) ?? start;
|
|
201
191
|
const title = String((doc as any)[titleField] || doc.name);
|
|
202
192
|
const color = colorField
|
|
203
193
|
? resolveFrappeEventColor((doc as any)[colorField], chartPalette)
|
package/src/FrappeKanban.tsx
CHANGED
|
@@ -4,10 +4,11 @@ import {
|
|
|
4
4
|
type KanbanColumn as GenericKanbanColumn,
|
|
5
5
|
type KanbanProps,
|
|
6
6
|
} from "@multiplatform.one/components";
|
|
7
|
+
import { Button } from "@multiplatform.one/forms";
|
|
7
8
|
import { PlusIcon } from "@phosphor-icons/react";
|
|
8
9
|
import React, { useMemo, useCallback, useState } from "react";
|
|
9
10
|
import { useTranslation } from "react-i18next";
|
|
10
|
-
import {
|
|
11
|
+
import { Text, XStack, YStack } from "tamagui";
|
|
11
12
|
|
|
12
13
|
import { useResolvedKnobs } from "@multiplatform.one/theme";
|
|
13
14
|
import { FrappeConnectionIndicator } from "./FrappeConnectionIndicator";
|
|
@@ -48,6 +49,8 @@ export interface FrappeKanbanProps<TDoc extends { name: string; doctype: string
|
|
|
48
49
|
renderCard?: (item: Partial<TDoc>) => React.ReactNode;
|
|
49
50
|
onCardClick?: (card: Partial<TDoc>) => void;
|
|
50
51
|
onCardMove?: (cardName: string, fromStatus: string, toStatus: string) => void | Promise<void>;
|
|
52
|
+
/** Disable card dragging (the board stays a read-only visualization). */
|
|
53
|
+
readOnly?: boolean;
|
|
51
54
|
enableCardCreation?: boolean;
|
|
52
55
|
onCreateCard?: (status: string) => void;
|
|
53
56
|
loading?: React.ReactNode;
|
|
@@ -75,10 +78,12 @@ export function FrappeKanban<TDoc extends { name: string; doctype: string }>({
|
|
|
75
78
|
renderCard,
|
|
76
79
|
onCardClick,
|
|
77
80
|
onCardMove,
|
|
81
|
+
readOnly = false,
|
|
78
82
|
enableCardCreation = false,
|
|
79
83
|
onCreateCard,
|
|
80
84
|
loading,
|
|
81
85
|
error,
|
|
86
|
+
renderColumnActions,
|
|
82
87
|
...kanbanProps
|
|
83
88
|
}: FrappeKanbanProps<TDoc>) {
|
|
84
89
|
const config = useFrappeConfig();
|
|
@@ -234,6 +239,37 @@ export function FrappeKanban<TDoc extends { name: string; doctype: string }>({
|
|
|
234
239
|
[onCardClick],
|
|
235
240
|
);
|
|
236
241
|
|
|
242
|
+
// Desk parity: the quick-add "+" is anchored IN its column header (never a
|
|
243
|
+
// detached button strip) and names its column in the accessible label. The
|
|
244
|
+
// synthetic "Unassigned" bucket has no field value to prefill, so it gets
|
|
245
|
+
// no add affordance. Consumer-provided column actions compose after it.
|
|
246
|
+
const effectiveRenderColumnActions = useMemo(() => {
|
|
247
|
+
const canQuickAdd = enableCardCreation && Boolean(onCreateCard);
|
|
248
|
+
if (!canQuickAdd && !renderColumnActions) return undefined;
|
|
249
|
+
return (column: GenericKanbanColumn<Partial<TDoc>>) => (
|
|
250
|
+
<XStack alignItems="center" gap="$1">
|
|
251
|
+
{canQuickAdd && column.id !== "__unassigned__" && (
|
|
252
|
+
<Button
|
|
253
|
+
size="$2"
|
|
254
|
+
circular
|
|
255
|
+
chromeless
|
|
256
|
+
icon={<PlusIcon size={14} />}
|
|
257
|
+
aria-label={t("Add card to {{column}}", { column: column.title })}
|
|
258
|
+
onPress={() => {
|
|
259
|
+
setActionError(null);
|
|
260
|
+
try {
|
|
261
|
+
onCreateCard?.(column.id);
|
|
262
|
+
} catch (err) {
|
|
263
|
+
setActionError(err instanceof Error ? err : new Error(String(err)));
|
|
264
|
+
}
|
|
265
|
+
}}
|
|
266
|
+
/>
|
|
267
|
+
)}
|
|
268
|
+
{renderColumnActions?.(column)}
|
|
269
|
+
</XStack>
|
|
270
|
+
);
|
|
271
|
+
}, [enableCardCreation, onCreateCard, renderColumnActions, t]);
|
|
272
|
+
|
|
237
273
|
// Custom loading slot only — default path keeps empty columns visible so
|
|
238
274
|
// fixture-less demos still render the board (Kanban owns isLoading chrome).
|
|
239
275
|
if (isLoading && cards.length === 0 && loading) {
|
|
@@ -271,33 +307,15 @@ export function FrappeKanban<TDoc extends { name: string; doctype: string }>({
|
|
|
271
307
|
</Text>
|
|
272
308
|
</YStack>
|
|
273
309
|
)}
|
|
274
|
-
{enableCardCreation && onCreateCard && (
|
|
275
|
-
<XStack {...knobProps.gap} flexWrap="wrap" paddingHorizontal="$2">
|
|
276
|
-
{columns.map((col) => (
|
|
277
|
-
<Button
|
|
278
|
-
key={col.value}
|
|
279
|
-
size="$2"
|
|
280
|
-
chromeless
|
|
281
|
-
icon={PlusIcon}
|
|
282
|
-
onPress={() => {
|
|
283
|
-
setActionError(null);
|
|
284
|
-
try {
|
|
285
|
-
onCreateCard(col.value);
|
|
286
|
-
} catch (err) {
|
|
287
|
-
setActionError(err instanceof Error ? err : new Error(String(err)));
|
|
288
|
-
}
|
|
289
|
-
}}
|
|
290
|
-
aria-label={t("Add card to {{column}}", { column: col.title })}
|
|
291
|
-
/>
|
|
292
|
-
))}
|
|
293
|
-
</XStack>
|
|
294
|
-
)}
|
|
295
310
|
<Kanban
|
|
296
311
|
{...knobProps.gap}
|
|
297
312
|
columns={genericColumns}
|
|
298
313
|
renderCard={effectiveRenderCard}
|
|
299
|
-
|
|
314
|
+
// The generic Kanban enables drag when a move handler exists —
|
|
315
|
+
// readOnly withholds it so the board stays a visualization.
|
|
316
|
+
onCardMove={readOnly ? undefined : handleCardMove}
|
|
300
317
|
onCardSelect={handleCardSelect}
|
|
318
|
+
renderColumnActions={effectiveRenderColumnActions}
|
|
301
319
|
isLoading={isLoading}
|
|
302
320
|
error={boardError}
|
|
303
321
|
getItemKey={(item, index) => item.name ?? index}
|
package/src/FrappeLikedBy.tsx
CHANGED
|
@@ -13,11 +13,12 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { AsyncBoundary, Avatar } from "@multiplatform.one/components";
|
|
15
15
|
import { HttpClient, useFrappeCollection, useLiveQuery } from "@multiplatform.one/frappe";
|
|
16
|
+
import { Button } from "@multiplatform.one/forms";
|
|
16
17
|
import { useResolvedKnobs } from "@multiplatform.one/theme";
|
|
17
18
|
import { HeartIcon } from "@phosphor-icons/react";
|
|
18
19
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
19
20
|
import { useTranslation } from "react-i18next";
|
|
20
|
-
import {
|
|
21
|
+
import { Text, XStack, YStack, isWeb, useTheme, type YStackProps } from "tamagui";
|
|
21
22
|
import { useFrappeConfig } from "./FrappeProvider";
|
|
22
23
|
import { FrappeReconnectIndicator } from "./FrappeReconnectIndicator";
|
|
23
24
|
import { useFrappeUsers } from "./frappeUsers";
|
package/src/FrappeList.tsx
CHANGED
|
@@ -94,8 +94,14 @@ export function FrappeList<TDoc extends { name: string; doctype: string }>({
|
|
|
94
94
|
return (
|
|
95
95
|
<YStack {...knobProps.gap} flex={1}>
|
|
96
96
|
<FrappeReconnectIndicator config={connectionConfig} />
|
|
97
|
+
{/*
|
|
98
|
+
No chrome fragment rides this passthrough (LC-55 FRAGMENT-LICENSE):
|
|
99
|
+
List forwards its stack props straight to AsyncBoundary, a state-switch
|
|
100
|
+
wrapper that declares no surface class and paints no fill, border, or
|
|
101
|
+
clip. Rows and the error/empty panels own their own chrome; the error
|
|
102
|
+
branch above renders a bare AsyncBoundary, so both paths now match.
|
|
103
|
+
*/}
|
|
97
104
|
<List
|
|
98
|
-
{...knobProps.borderRadius}
|
|
99
105
|
items={items}
|
|
100
106
|
isLoading={isLoading}
|
|
101
107
|
renderItem={renderItem}
|
package/src/FrappeListView.tsx
CHANGED
|
@@ -4,19 +4,9 @@ import { ArrowsClockwiseIcon, PlusIcon } from "@phosphor-icons/react";
|
|
|
4
4
|
import React, { useState, useMemo, useCallback, useEffect } from "react";
|
|
5
5
|
import { useTranslation } from "react-i18next";
|
|
6
6
|
import { SearchInput, Spinner } from "@multiplatform.one/forms";
|
|
7
|
-
import {
|
|
8
|
-
Button,
|
|
9
|
-
Image,
|
|
10
|
-
Text,
|
|
11
|
-
View,
|
|
12
|
-
XStack,
|
|
13
|
-
YStack,
|
|
14
|
-
isWeb,
|
|
15
|
-
useTheme,
|
|
16
|
-
type YStackProps,
|
|
17
|
-
} from "tamagui";
|
|
7
|
+
import { Button, Text, View, XStack, YStack, isWeb, useTheme, type YStackProps } from "tamagui";
|
|
18
8
|
import { SavedFilterDropdown, useSavedFilters, type ParsedFilter } from "@multiplatform.one/table";
|
|
19
|
-
import { ViewSwitcher } from "@multiplatform.one/components";
|
|
9
|
+
import { Avatar, ViewSwitcher } from "@multiplatform.one/components";
|
|
20
10
|
import {
|
|
21
11
|
MIN_PRESS_TARGET,
|
|
22
12
|
pressTargetHitSlop,
|
|
@@ -311,15 +301,13 @@ export function FrappeListView<TDoc extends { name: string; doctype: string }>({
|
|
|
311
301
|
return (
|
|
312
302
|
<XStack alignItems="center" gap="$3" paddingVertical="$2" paddingHorizontal="$3">
|
|
313
303
|
{imageUrl ? (
|
|
314
|
-
<
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
objectFit="cover"
|
|
322
|
-
/>
|
|
304
|
+
<Avatar circular size={40} backgroundColor="$color3">
|
|
305
|
+
<Avatar.Image
|
|
306
|
+
src={imageUrl}
|
|
307
|
+
alt={typeof title === "string" ? title : ""}
|
|
308
|
+
objectFit="cover"
|
|
309
|
+
/>
|
|
310
|
+
</Avatar>
|
|
323
311
|
) : null}
|
|
324
312
|
<YStack flex={1} gap="$0.5">
|
|
325
313
|
<Text fontSize="$4" fontWeight="500" numberOfLines={1}>
|
|
@@ -503,7 +491,13 @@ export function FrappeListView<TDoc extends { name: string; doctype: string }>({
|
|
|
503
491
|
{...refreshPressFloorProps}
|
|
504
492
|
/>
|
|
505
493
|
{onAdd && (
|
|
506
|
-
<Button
|
|
494
|
+
<Button
|
|
495
|
+
size="$3"
|
|
496
|
+
theme="accent"
|
|
497
|
+
icon={PlusIcon}
|
|
498
|
+
onPress={onAdd}
|
|
499
|
+
{...pressTargetStyle()}
|
|
500
|
+
>
|
|
507
501
|
{t("Add {{doctype}}", { doctype })}
|
|
508
502
|
</Button>
|
|
509
503
|
)}
|
|
@@ -86,7 +86,8 @@ function jsonResponse(body: unknown, status = 200): Response {
|
|
|
86
86
|
if (urlStr.startsWith(MOCK_HOST)) {
|
|
87
87
|
const url = new URL(urlStr);
|
|
88
88
|
const metaMatch = url.pathname.match(/^\/api\/resource\/DocType\/(.+)$/);
|
|
89
|
-
if (metaMatch)
|
|
89
|
+
if (metaMatch)
|
|
90
|
+
return jsonResponse({ data: customerMetaRaw(decodeURIComponent(metaMatch[1])) });
|
|
90
91
|
if (url.pathname === "/api/resource/DocType") {
|
|
91
92
|
return jsonResponse({ data: [{ name: "Customer", module: "Selling" }] });
|
|
92
93
|
}
|
|
@@ -382,10 +383,7 @@ export const Loading = () => (
|
|
|
382
383
|
|
|
383
384
|
/** Zero documents, no filters, no failure — "No Customer records found" (empty ≠ error). */
|
|
384
385
|
export const Empty = () => (
|
|
385
|
-
<FrappeProvider
|
|
386
|
-
baseURL={MOCK_HOST}
|
|
387
|
-
fixtures={new InMemoryFixtureProvider({ Customer: [] })}
|
|
388
|
-
>
|
|
386
|
+
<FrappeProvider baseURL={MOCK_HOST} fixtures={new InMemoryFixtureProvider({ Customer: [] })}>
|
|
389
387
|
<FrappeTable<Customer> doctype="Customer" columns={customerColumns} />
|
|
390
388
|
</FrappeProvider>
|
|
391
389
|
);
|
package/src/FrappeTable.tsx
CHANGED
|
@@ -556,6 +556,16 @@ export function FrappeTable<TDoc extends { name: string; doctype: string }>({
|
|
|
556
556
|
onPress={() => onRowClick(props.row.original)}
|
|
557
557
|
cursor="pointer"
|
|
558
558
|
hoverStyle={{ backgroundColor: "$color2" }}
|
|
559
|
+
// Cell containment (LC-06/LC-10): a bare View defaults to
|
|
560
|
+
// flexShrink 0 and hugs its content, so long cell content
|
|
561
|
+
// (e.g. a ToDo description) painted PAST the column bounds and
|
|
562
|
+
// under the neighbor cells (the td itself doesn't clip).
|
|
563
|
+
// Shrink with the cell, cap at its box, clip the excess — the
|
|
564
|
+
// inner one-line Texts then ellipsize in-column.
|
|
565
|
+
minWidth={0}
|
|
566
|
+
maxWidth="100%"
|
|
567
|
+
flexShrink={1}
|
|
568
|
+
overflow="hidden"
|
|
559
569
|
>
|
|
560
570
|
{/* raw strings crash native (text must be inside <Text>) */}
|
|
561
571
|
{typeof content === "string" || typeof content === "number" ? (
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { InMemoryFixtureProvider } from "@multiplatform.one/frappe";
|
|
2
2
|
import { action } from "@multiplatform.one/storybook";
|
|
3
|
+
import { formatAbsoluteDateTime } from "@multiplatform.one/theme";
|
|
3
4
|
import { FrappeProvider } from "../FrappeProvider";
|
|
4
5
|
import { Timeline, type FrappeComment, type FrappeVersion } from "./timeline";
|
|
5
6
|
|
|
@@ -138,14 +139,18 @@ export const ReadOnly = () => (
|
|
|
138
139
|
</FrappeProvider>
|
|
139
140
|
);
|
|
140
141
|
|
|
141
|
-
/**
|
|
142
|
+
/**
|
|
143
|
+
* The formatTimestamp eject overrides the per-class register defaults —
|
|
144
|
+
* demoed with the house absolute register pinned to always show the year
|
|
145
|
+
* (LC-12: even an ejected formatter speaks a human register, never raw ISO).
|
|
146
|
+
*/
|
|
142
147
|
export const AbsoluteTimestamps = () => (
|
|
143
148
|
<FrappeProvider baseURL="https://demo.frappe.cloud" fixtures={makeFixtures()}>
|
|
144
149
|
<Timeline
|
|
145
150
|
doctype="Task"
|
|
146
151
|
docname="TASK-001"
|
|
147
152
|
label="Activity"
|
|
148
|
-
formatTimestamp={(ts) =>
|
|
153
|
+
formatTimestamp={(ts) => formatAbsoluteDateTime(ts, { year: "always" })}
|
|
149
154
|
/>
|
|
150
155
|
</FrappeProvider>
|
|
151
156
|
);
|
|
@@ -46,7 +46,7 @@ export const States = (args: StoryProps) => (
|
|
|
46
46
|
<Controlled {...args} label="Disabled" disabled />
|
|
47
47
|
<Controlled {...args} label="Disabled + error" disabled error="Broken while disabled" />
|
|
48
48
|
<Controlled {...args} label="Required" required />
|
|
49
|
-
<Controlled {...args} label="Error" error="
|
|
49
|
+
<Controlled {...args} label="Error" error="Value does not match the required format" />
|
|
50
50
|
<Controlled {...args} label="Helper text" helperText="Some helpful context" />
|
|
51
51
|
</YStack>
|
|
52
52
|
);
|
|
@@ -118,7 +118,7 @@ ReadOnly.args = {
|
|
|
118
118
|
|
|
119
119
|
export const States = (args: StoryProps) => (
|
|
120
120
|
<YStack gap="$5">
|
|
121
|
-
<Controlled {...args} label="Error" error="Script
|
|
121
|
+
<Controlled {...args} label="Error" error="Script fails to parse at line 3" />
|
|
122
122
|
<Controlled {...args} label="Disabled" disabled />
|
|
123
123
|
<Controlled {...args} label="Compact" compact />
|
|
124
124
|
<CodeEditorField label="Skeleton" skeleton onChange={() => {}} />
|
|
@@ -52,7 +52,7 @@ export const AllStates = () => (
|
|
|
52
52
|
<ControlledColor label="Default" />
|
|
53
53
|
<ControlledColor label="With Value" value="#10B981" />
|
|
54
54
|
<ControlledColor label="Required" required />
|
|
55
|
-
<ControlledColor label="With Error" value="invalid" error="
|
|
55
|
+
<ControlledColor label="With Error" value="invalid" error="Color must be a recognized value" />
|
|
56
56
|
<ControlledColor label="Disabled" value="#F59E0B" disabled />
|
|
57
57
|
<ControlledColor label="No Presets" showPresets={false} />
|
|
58
58
|
<ControlledColor
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Date wrapper value contract (late-value display fix):
|
|
3
|
+
*
|
|
4
|
+
* The wrapper used to fold the controlled `value` into the forms DatePicker's
|
|
5
|
+
* mount-only `defaultValue` and DROP `onChange` — so a doc value arriving
|
|
6
|
+
* after first paint (create-mode engine seeding on /backoffice/todo/new)
|
|
7
|
+
* never displayed, and picks never reached the consumer doc. It now passes
|
|
8
|
+
* `value` through controlled and converts picked Dates back to the Frappe
|
|
9
|
+
* wire shape. Programmatic value application must not emit onChange (LC-14:
|
|
10
|
+
* a seeded value is not a user edit).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { describe, it, expect, vi } from "vitest";
|
|
14
|
+
|
|
15
|
+
// The first field-module import in a cold fork transforms the whole fields
|
|
16
|
+
// graph (same budget as tests/fieldRenderers.spec.tsx).
|
|
17
|
+
vi.setConfig({ testTimeout: 240_000 });
|
|
18
|
+
|
|
19
|
+
// Mock react-native-reanimated to prevent ESM module resolution failures
|
|
20
|
+
// (same shim as tests/fieldRenderers.spec.tsx).
|
|
21
|
+
vi.mock("react-native-reanimated", () => ({
|
|
22
|
+
default: {
|
|
23
|
+
useSharedValue: (init: any) => ({ value: init }),
|
|
24
|
+
useAnimatedStyle: () => ({}),
|
|
25
|
+
withTiming: (val: any) => val,
|
|
26
|
+
withSpring: (val: any) => val,
|
|
27
|
+
createAnimatedComponent: (component: any) => component,
|
|
28
|
+
runOnJS: (fn: any) => fn,
|
|
29
|
+
runOnUI: (fn: any) => fn,
|
|
30
|
+
useDerivedValue: (fn: () => any) => ({ value: fn() }),
|
|
31
|
+
useAnimatedRef: () => ({ current: null }),
|
|
32
|
+
Easing: { linear: (v: any) => v, ease: (v: any) => v, bezier: () => (v: any) => v },
|
|
33
|
+
FadeIn: { duration: () => ({}) },
|
|
34
|
+
FadeOut: { duration: () => ({}) },
|
|
35
|
+
Layout: { duration: () => ({}) },
|
|
36
|
+
Extrapolation: { CLAMP: "clamp" },
|
|
37
|
+
measure: () => null,
|
|
38
|
+
scrollTo: () => {},
|
|
39
|
+
makeMutable: (init: any) => ({ value: init }),
|
|
40
|
+
},
|
|
41
|
+
useSharedValue: (init: any) => ({ value: init }),
|
|
42
|
+
useAnimatedStyle: () => ({}),
|
|
43
|
+
withTiming: (val: any) => val,
|
|
44
|
+
withSpring: (val: any) => val,
|
|
45
|
+
createAnimatedComponent: (component: any) => component,
|
|
46
|
+
runOnJS: (fn: any) => fn,
|
|
47
|
+
runOnUI: (fn: any) => fn,
|
|
48
|
+
useDerivedValue: (fn: () => any) => ({ value: fn() }),
|
|
49
|
+
useAnimatedRef: () => ({ current: null }),
|
|
50
|
+
Easing: { linear: (v: any) => v, ease: (v: any) => v, bezier: () => (v: any) => v },
|
|
51
|
+
FadeIn: { duration: () => ({}) },
|
|
52
|
+
FadeOut: { duration: () => ({}) },
|
|
53
|
+
Layout: { duration: () => ({}) },
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
import { renderWithProviders } from "@multiplatform.one/test-utils";
|
|
57
|
+
import { act, fireEvent, waitFor } from "@testing-library/react";
|
|
58
|
+
import React from "react";
|
|
59
|
+
|
|
60
|
+
async function loadDateField() {
|
|
61
|
+
return (await import(".")).Date;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const getDisplayInput = (container: ParentNode) =>
|
|
65
|
+
container.querySelector("input") as HTMLInputElement | null;
|
|
66
|
+
|
|
67
|
+
/** Enabled day-cell buttons inside the (portaled) calendar popover. */
|
|
68
|
+
function getDayButton(root: ParentNode, day: number): HTMLButtonElement | undefined {
|
|
69
|
+
const allButtons = Array.from(
|
|
70
|
+
root.querySelectorAll('button, [role="button"]'),
|
|
71
|
+
) as HTMLButtonElement[];
|
|
72
|
+
return allButtons.find((btn) => {
|
|
73
|
+
const text = btn.textContent?.trim() ?? "";
|
|
74
|
+
const disabled =
|
|
75
|
+
btn.hasAttribute("disabled") ||
|
|
76
|
+
btn.getAttribute("aria-disabled") === "true" ||
|
|
77
|
+
btn.hasAttribute("data-disabled");
|
|
78
|
+
return text === String(day) && !disabled;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
describe("Date wrapper — late-arriving value display", () => {
|
|
83
|
+
it("a value arriving after mount displays, without onChange", async () => {
|
|
84
|
+
const DateField = await loadDateField();
|
|
85
|
+
const onChange = vi.fn();
|
|
86
|
+
const result = renderWithProviders(
|
|
87
|
+
<DateField label="Due Date" value="" onChange={onChange} type="date" />,
|
|
88
|
+
);
|
|
89
|
+
expect(getDisplayInput(result.container)?.value ?? "").toBe("");
|
|
90
|
+
|
|
91
|
+
// Late seed: the engine applies create defaults once meta loads.
|
|
92
|
+
result.rerender(
|
|
93
|
+
<DateField label="Due Date" value="2026-03-05" onChange={onChange} type="date" />,
|
|
94
|
+
);
|
|
95
|
+
await waitFor(() => {
|
|
96
|
+
expect(getDisplayInput(result.container)?.value).toBe("Mar 5, 2026");
|
|
97
|
+
});
|
|
98
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe("Date wrapper — pick emits the Frappe wire string", () => {
|
|
103
|
+
it("emits YYYY-MM-DD once for a Date field pick", async () => {
|
|
104
|
+
const DateField = await loadDateField();
|
|
105
|
+
const onChange = vi.fn();
|
|
106
|
+
const result = renderWithProviders(
|
|
107
|
+
<DateField label="Due Date" value="2026-03-05" onChange={onChange} type="date" />,
|
|
108
|
+
);
|
|
109
|
+
const trigger = getDisplayInput(result.container);
|
|
110
|
+
expect(trigger).toBeTruthy();
|
|
111
|
+
await act(async () => {
|
|
112
|
+
if (trigger) fireEvent.click(trigger);
|
|
113
|
+
});
|
|
114
|
+
await waitFor(() => {
|
|
115
|
+
expect(getDayButton(document.body, 15)).toBeTruthy();
|
|
116
|
+
});
|
|
117
|
+
await act(async () => {
|
|
118
|
+
fireEvent.click(getDayButton(document.body, 15)!);
|
|
119
|
+
});
|
|
120
|
+
expect(onChange).toHaveBeenCalledTimes(1);
|
|
121
|
+
expect(onChange).toHaveBeenCalledWith("2026-03-15");
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("preserves the existing time part for a Datetime field pick", async () => {
|
|
125
|
+
const DateField = await loadDateField();
|
|
126
|
+
const onChange = vi.fn();
|
|
127
|
+
const result = renderWithProviders(
|
|
128
|
+
<DateField
|
|
129
|
+
label="Starts On"
|
|
130
|
+
value="2026-03-05 14:30:00"
|
|
131
|
+
onChange={onChange}
|
|
132
|
+
type="datetime"
|
|
133
|
+
/>,
|
|
134
|
+
);
|
|
135
|
+
const trigger = getDisplayInput(result.container);
|
|
136
|
+
expect(trigger).toBeTruthy();
|
|
137
|
+
await act(async () => {
|
|
138
|
+
if (trigger) fireEvent.click(trigger);
|
|
139
|
+
});
|
|
140
|
+
await waitFor(() => {
|
|
141
|
+
expect(getDayButton(document.body, 20)).toBeTruthy();
|
|
142
|
+
});
|
|
143
|
+
await act(async () => {
|
|
144
|
+
fireEvent.click(getDayButton(document.body, 20)!);
|
|
145
|
+
});
|
|
146
|
+
expect(onChange).toHaveBeenCalledTimes(1);
|
|
147
|
+
expect(onChange).toHaveBeenCalledWith("2026-03-20 14:30:00");
|
|
148
|
+
});
|
|
149
|
+
});
|
|
@@ -126,7 +126,7 @@ export const AllStates = () => (
|
|
|
126
126
|
<ControlledDate label="Default (Empty)" type="date" />
|
|
127
127
|
<ControlledDate label="With Value" type="date" value="2026-06-15" />
|
|
128
128
|
<ControlledDate label="Required" type="date" required />
|
|
129
|
-
<ControlledDate label="With Error" type="date" error="
|
|
129
|
+
<ControlledDate label="With Error" type="date" error="A valid date is required" />
|
|
130
130
|
<ControlledDate label="Disabled" type="date" value="2026-01-01" disabled />
|
|
131
131
|
</YStack>
|
|
132
132
|
);
|
|
@@ -52,7 +52,7 @@ export const States = (args: StoryProps) => (
|
|
|
52
52
|
<Controlled {...args} label="Disabled" disabled />
|
|
53
53
|
<Controlled {...args} label="Disabled + error" disabled error="Broken while disabled" />
|
|
54
54
|
<Controlled {...args} label="Required" required />
|
|
55
|
-
<Controlled {...args} label="Error" error="
|
|
55
|
+
<Controlled {...args} label="Error" error="Value does not match the required format" />
|
|
56
56
|
<Controlled {...args} label="Helper text" helperText="Some helpful context" />
|
|
57
57
|
</YStack>
|
|
58
58
|
);
|