@multiplatform.one/frappe-ui 6.4.3 → 6.7.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 +2 -2
- package/dist/cjs/FrappeDashboard.native.js +2 -2
- 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 +9 -7
- package/dist/cjs/FrappeListView.native.js +9 -7
- package/dist/cjs/FrappeListView.native.js.map +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/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 +2 -13
- package/dist/cjs/fields/Date/index.native.js +2 -15
- 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/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 +3 -3
- package/dist/esm/FrappeDashboard.mjs.map +1 -1
- package/dist/esm/FrappeDashboard.native.js +3 -3
- 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 +11 -9
- package/dist/esm/FrappeListView.mjs.map +1 -1
- package/dist/esm/FrappeListView.native.js +11 -9
- package/dist/esm/FrappeListView.native.js.map +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/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 +1 -12
- package/dist/esm/fields/Date/index.mjs.map +1 -1
- package/dist/esm/fields/Date/index.native.js +1 -14
- 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/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/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 +3 -3
- package/dist/jsx/FrappeDashboard.mjs.map +1 -1
- package/dist/jsx/FrappeDashboard.native.js +2 -2
- 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 +11 -9
- package/dist/jsx/FrappeListView.mjs.map +1 -1
- package/dist/jsx/FrappeListView.native.js +9 -7
- package/dist/jsx/FrappeListView.native.js.map +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/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 +1 -12
- package/dist/jsx/fields/Date/index.mjs.map +1 -1
- package/dist/jsx/fields/Date/index.native.js +2 -15
- 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/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/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 +23 -23
- 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.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
|
@@ -32,7 +32,12 @@ interface CalEvent {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
const today = new Date();
|
|
35
|
-
|
|
35
|
+
// LOCAL parts, matching FrappeCalendar's own `datePart`: a `toISOString()` date
|
|
36
|
+
// is UTC, so east of Greenwich the fixture names yesterday for the hour after
|
|
37
|
+
// local midnight and no event lands on the view's "today".
|
|
38
|
+
const pad2 = (n: number) => String(n).padStart(2, "0");
|
|
39
|
+
const formatDate = (date: Date) =>
|
|
40
|
+
`${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())}`;
|
|
36
41
|
|
|
37
42
|
// Fixture doc colors come from the sanctioned categorical palette (chart
|
|
38
43
|
// palette precedent) instead of raw brand hexes — Frappe docs store one color
|
|
@@ -287,6 +292,72 @@ Live.parameters = {
|
|
|
287
292
|
},
|
|
288
293
|
};
|
|
289
294
|
|
|
295
|
+
// ---------------------------------------------------------------------------
|
|
296
|
+
// Frappe `Date` fields — date-only wire values, no time component
|
|
297
|
+
// ---------------------------------------------------------------------------
|
|
298
|
+
|
|
299
|
+
interface LeaveRequest {
|
|
300
|
+
name: string;
|
|
301
|
+
doctype: string;
|
|
302
|
+
subject: string;
|
|
303
|
+
from_date: string;
|
|
304
|
+
to_date: string;
|
|
305
|
+
color: string;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const dateOnlyFixtures: LeaveRequest[] = [
|
|
309
|
+
{
|
|
310
|
+
name: "LEAVE-001",
|
|
311
|
+
doctype: "LeaveApplication",
|
|
312
|
+
subject: "Annual leave",
|
|
313
|
+
from_date: formatDate(today),
|
|
314
|
+
to_date: formatDate(new Date(today.getTime() + 2 * 86400000)),
|
|
315
|
+
color: green,
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
name: "LEAVE-002",
|
|
319
|
+
doctype: "LeaveApplication",
|
|
320
|
+
subject: "Conference travel",
|
|
321
|
+
from_date: formatDate(new Date(today.getTime() + 4 * 86400000)),
|
|
322
|
+
to_date: formatDate(new Date(today.getTime() + 5 * 86400000)),
|
|
323
|
+
color: orange,
|
|
324
|
+
},
|
|
325
|
+
];
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Frappe `Date` fields arrive as bare "YYYY-MM-DD" — a calendar date carrying
|
|
329
|
+
* no timezone, the shape every other fixture story here omits by always
|
|
330
|
+
* sending a time. They parse to LOCAL midnight (`parseCalendarDate`); read as
|
|
331
|
+
* UTC they render a day early in every negative-offset zone, so this is the
|
|
332
|
+
* story that makes that class of regression visible.
|
|
333
|
+
*/
|
|
334
|
+
export const DateOnlyFields = () => (
|
|
335
|
+
<FrappeProvider
|
|
336
|
+
baseURL="https://demo.frappe.cloud"
|
|
337
|
+
fixtures={new InMemoryFixtureProvider({ LeaveApplication: dateOnlyFixtures })}
|
|
338
|
+
>
|
|
339
|
+
<FrappeCalendar<LeaveRequest>
|
|
340
|
+
doctype="LeaveApplication"
|
|
341
|
+
dateField="from_date"
|
|
342
|
+
endDateField="to_date"
|
|
343
|
+
titleField="subject"
|
|
344
|
+
colorField="color"
|
|
345
|
+
defaultView="month"
|
|
346
|
+
onEventClick={action("onEventClick")}
|
|
347
|
+
onDateClick={action("onDateClick")}
|
|
348
|
+
height={600}
|
|
349
|
+
/>
|
|
350
|
+
</FrappeProvider>
|
|
351
|
+
);
|
|
352
|
+
DateOnlyFields.parameters = {
|
|
353
|
+
docs: {
|
|
354
|
+
description: {
|
|
355
|
+
story:
|
|
356
|
+
"Date-only Frappe `Date` fields (no time component). The leave block starts on today's cell in every timezone — read as UTC midnight it would sit on yesterday west of Greenwich.",
|
|
357
|
+
},
|
|
358
|
+
},
|
|
359
|
+
};
|
|
360
|
+
|
|
290
361
|
// ---------------------------------------------------------------------------
|
|
291
362
|
// Named Frappe colors (P-15): palette references resolve to sanctioned ramps,
|
|
292
363
|
// literals pass through, unknown names fall back to the identity slot
|
package/src/FrappeCalendar.tsx
CHANGED
|
@@ -9,7 +9,7 @@ import { Spinner } from "@multiplatform.one/forms";
|
|
|
9
9
|
import React, { useMemo, useCallback, useState, type ReactNode } from "react";
|
|
10
10
|
import { Text, YStack, type YStackProps } from "tamagui";
|
|
11
11
|
import { useTranslation } from "react-i18next";
|
|
12
|
-
import { useChartPalette, useResolvedKnobs } from "@multiplatform.one/theme";
|
|
12
|
+
import { parseCalendarDate, useChartPalette, useResolvedKnobs } from "@multiplatform.one/theme";
|
|
13
13
|
import { FrappeConnectionIndicator } from "./FrappeConnectionIndicator";
|
|
14
14
|
import { useFrappeConfig } from "./FrappeProvider";
|
|
15
15
|
import { describeSyncError } from "./syncError";
|
|
@@ -57,16 +57,6 @@ export interface FrappeCalendarProps<TDoc extends { name: string; doctype: strin
|
|
|
57
57
|
firstDayOfWeek?: 0 | 1;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
/**
|
|
61
|
-
* Parse a date string from Frappe (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS).
|
|
62
|
-
*/
|
|
63
|
-
function parseDate(dateStr: string | Date | undefined): Date | null {
|
|
64
|
-
if (!dateStr) return null;
|
|
65
|
-
if (dateStr instanceof Date) return dateStr;
|
|
66
|
-
const parsed = new Date(dateStr.replace(" ", "T"));
|
|
67
|
-
return isNaN(parsed.getTime()) ? null : parsed;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
60
|
/**
|
|
71
61
|
* Format a moved date back into the same shape the document field used:
|
|
72
62
|
* Date stays Date, "YYYY-MM-DD HH:MM:SS" keeps its time part, plain
|
|
@@ -195,9 +185,9 @@ export function FrappeCalendar<TDoc extends { name: string; doctype: string }>({
|
|
|
195
185
|
const events = useMemo((): CalendarEvent[] => {
|
|
196
186
|
return docs
|
|
197
187
|
.map((doc) => {
|
|
198
|
-
const startDate =
|
|
188
|
+
const startDate = parseCalendarDate((doc as any)[dateField]);
|
|
199
189
|
if (!startDate) return null;
|
|
200
|
-
const endDate = endDateField ?
|
|
190
|
+
const endDate = endDateField ? parseCalendarDate((doc as any)[endDateField]) : undefined;
|
|
201
191
|
const title = titleField ? String((doc as any)[titleField] || doc.name) : doc.name;
|
|
202
192
|
const color = colorField
|
|
203
193
|
? resolveFrappeEventColor((doc as any)[colorField], chartPalette)
|
|
@@ -2,7 +2,7 @@ import { useFrappeConnection, type UseFrappeCollectionConfig } from "@multiplatf
|
|
|
2
2
|
import React from "react";
|
|
3
3
|
import { useTranslation } from "react-i18next";
|
|
4
4
|
import { Text, XStack, YStack } from "tamagui";
|
|
5
|
-
import { useResolvedKnobs } from "@multiplatform.one/theme";
|
|
5
|
+
import { radiusClassProps, useResolvedKnobs } from "@multiplatform.one/theme";
|
|
6
6
|
|
|
7
7
|
export interface FrappeConnectionIndicatorProps {
|
|
8
8
|
/** The same connection config the sibling collection hooks use. */
|
|
@@ -37,7 +37,13 @@ export function FrappeConnectionIndicator({ config }: FrappeConnectionIndicatorP
|
|
|
37
37
|
borderColor="$yellow7"
|
|
38
38
|
borderWidth={1}
|
|
39
39
|
>
|
|
40
|
-
<YStack
|
|
40
|
+
<YStack
|
|
41
|
+
{...radiusClassProps("R-PILL", "connection status dot")}
|
|
42
|
+
width={8}
|
|
43
|
+
height={8}
|
|
44
|
+
borderRadius={4}
|
|
45
|
+
backgroundColor="$yellow10"
|
|
46
|
+
/>
|
|
41
47
|
<Text fontSize="$2" color="$yellow11">
|
|
42
48
|
{t("Reconnecting…")}
|
|
43
49
|
</Text>
|
package/src/FrappeDashboard.tsx
CHANGED
|
@@ -16,6 +16,7 @@ import { useTranslation } from "react-i18next";
|
|
|
16
16
|
import { Spinner } from "@multiplatform.one/forms";
|
|
17
17
|
import { Button, Text, XStack, YStack, type YStackProps, H3 } from "tamagui";
|
|
18
18
|
import {
|
|
19
|
+
formatTimeOfDay,
|
|
19
20
|
MIN_PRESS_TARGET,
|
|
20
21
|
pressTargetHitSlop,
|
|
21
22
|
pressTargetStyle,
|
|
@@ -217,7 +218,7 @@ function LiveDashboardContent<TDoc extends { name: string; doctype: string }>({
|
|
|
217
218
|
{isLiveStale && <Spinner size="small" aria-label={t("Updating")} />}
|
|
218
219
|
<Text fontSize="$1" color="$color10">
|
|
219
220
|
{t("Last updated: {{time}}", {
|
|
220
|
-
time: (dataUpdatedAt ?? lastRefresh)
|
|
221
|
+
time: formatTimeOfDay(dataUpdatedAt ?? lastRefresh),
|
|
221
222
|
})}
|
|
222
223
|
</Text>
|
|
223
224
|
<Button
|
|
@@ -328,7 +329,7 @@ export function FrappeDashboard<TDoc extends { name: string; doctype: string }>(
|
|
|
328
329
|
<XStack {...knobProps.gap} alignItems="center">
|
|
329
330
|
{autoRefresh && (
|
|
330
331
|
<Text fontSize="$1" color="$color10">
|
|
331
|
-
{t("Last updated: {{time}}", { time: lastRefresh
|
|
332
|
+
{t("Last updated: {{time}}", { time: formatTimeOfDay(lastRefresh) })}
|
|
332
333
|
</Text>
|
|
333
334
|
)}
|
|
334
335
|
<Button
|
|
@@ -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
|
)}
|
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
|