@lotics/ui 27.15.0 → 27.15.2
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/docs/catalog.md +2 -0
- package/docs/composition.md +13 -3
- package/docs/data_entry.md +7 -5
- package/package.json +1 -1
- package/src/date_stamp.tsx +34 -11
package/docs/catalog.md
CHANGED
|
@@ -1056,6 +1056,8 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1056
1056
|
empty — and a ladder of fields is a form with invisible borders. Reach for `InlineDatePicker`
|
|
1057
1057
|
when a person is expected to ENTER the date, and this when the system already knows it.
|
|
1058
1058
|
Empty renders a thin rule, not a placeholder; read-only (omit `onChange`) renders text alone.
|
|
1059
|
+
`onChange` fires on CLOSE, not on each pick — one commit per visit, and a dismissed panel
|
|
1060
|
+
still writes whatever was picked in it, which is what makes a single tap enough.
|
|
1059
1061
|
Worked screen: `examples/tpl_record.tsx` § Progress.
|
|
1060
1062
|
- **`check_circle`** — `CheckCircle`: the completion ring — `state="none" | "partial" | "done"`,
|
|
1061
1063
|
springing to a filled check when done, distinct from the square checkbox; the
|
package/docs/composition.md
CHANGED
|
@@ -471,9 +471,19 @@ All view controls are 40px tall (`CONTROL_HEIGHT`), `sm` labels, in ONE wrapping
|
|
|
471
471
|
|
|
472
472
|
## Commit & feedback surfaces
|
|
473
473
|
|
|
474
|
-
- **
|
|
475
|
-
|
|
476
|
-
|
|
474
|
+
- **A form's COMMIT sits with its fields, never in the footer.** The button submitting a set of
|
|
475
|
+
inputs goes directly beneath the last one it commits, `alignSelf="flex-start"` — the same left
|
|
476
|
+
edge the user just filled (data_entry.md's form-action alignment law, which holds inside an
|
|
477
|
+
overlay too). A footer commit is separated from what it commits by the width of the surface and
|
|
478
|
+
reads as belonging to the dialog rather than the fields. A **`Reset` travels with the `Save` it
|
|
479
|
+
undoes** — same draft, same row, secondary to the commit's left; splitting the pair across two
|
|
480
|
+
surfaces breaks one decision in half. A dialog whose only actions are a commit and its reset has
|
|
481
|
+
**no footer at all**.
|
|
482
|
+
- **The footer carries SURFACE actions**: `Cancel`/`Close`/`Done` (dismissal), `Back`
|
|
483
|
+
(navigation) — verbs about the overlay, with no field group to sit beside. `PopoverFooter`/
|
|
484
|
+
`DialogFooter` default `align="end"`; `DrawerFooter` is right-aligned by construction. Primary
|
|
485
|
+
at the right edge, secondary to its left. A confirm dialog (no fields) is all surface: its
|
|
486
|
+
buttons ARE the dialog, and they belong in the footer.
|
|
477
487
|
- **Empty result**: `<EmptyState message hint? icon? action?>` — never a bare muted Text.
|
|
478
488
|
- **Inline status**: `<Callout tone="info|success|warning|error|neutral">` — compound (like
|
|
479
489
|
Card): compose `CalloutTitle`/`CalloutText`/`CalloutActions` inside. `Callout` is INLINE;
|
package/docs/data_entry.md
CHANGED
|
@@ -36,11 +36,13 @@ shows in the resting row too; the `data` generic types option payloads). A DEPEN
|
|
|
36
36
|
renders only while its parent value makes it real — never a disabled ghost row. Worked rows:
|
|
37
37
|
`tpl_record`'s Classification group.
|
|
38
38
|
|
|
39
|
-
**The form-action alignment law.**
|
|
40
|
-
|
|
41
|
-
the
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
**The form-action alignment law.** A label-left form's action row rides the form's OWN GRID —
|
|
40
|
+
an empty-label `DetailRow` puts the CTA (and what it produces) exactly on the CONTROL COLUMN,
|
|
41
|
+
the same left edge the user just filled, and inherits stacked mode on narrow containers. A
|
|
42
|
+
right-floated button aligns to nothing. Worked: `tpl_record`'s Delivery receipt.
|
|
43
|
+
|
|
44
|
+
**This holds inside an overlay too.** A dialog's commit sits under the last field it commits,
|
|
45
|
+
not in the footer — see [composition.md §Commit & feedback surfaces](./composition.md).
|
|
44
46
|
|
|
45
47
|
### A dialog is a draft — use `FormField`, never an `Inline*` editor
|
|
46
48
|
|
package/package.json
CHANGED
package/src/date_stamp.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState } from "react";
|
|
1
|
+
import { useRef, useState } from "react";
|
|
2
2
|
import { Pressable, View } from "react-native";
|
|
3
3
|
import { Text } from "./text";
|
|
4
4
|
import { Icon } from "./icon";
|
|
@@ -50,6 +50,10 @@ export function DateStamp(props: DateStampProps) {
|
|
|
50
50
|
const [hovered, setHovered] = useState(false);
|
|
51
51
|
const [open, setOpen] = useState(false);
|
|
52
52
|
const [draft, setDraft] = useState<string | null>(value);
|
|
53
|
+
// The panel emits its pick and asks to close in the SAME event, so a commit
|
|
54
|
+
// that read `draft` off render state would still see the value the pick just
|
|
55
|
+
// replaced. This is what the close path reads instead.
|
|
56
|
+
const picked = useRef<string | null>(value);
|
|
53
57
|
|
|
54
58
|
const shown = value ? formatDate(value, { locale: tag }) : "";
|
|
55
59
|
|
|
@@ -57,21 +61,34 @@ export function DateStamp(props: DateStampProps) {
|
|
|
57
61
|
// so a caller can drop this into a column without a conditional.
|
|
58
62
|
if (!onChange) return shown ? <Text size="xs" color="muted">{shown}</Text> : null;
|
|
59
63
|
|
|
64
|
+
// Commit on CLOSE, matching `InlineDatePicker`: a single pick and the panel's
|
|
65
|
+
// Today button both auto-close, so committing on CHANGE instead would fire
|
|
66
|
+
// twice and a dismissed panel would still write.
|
|
67
|
+
//
|
|
68
|
+
// It has to be THIS function rather than `onOpenChange`, and every close has to
|
|
69
|
+
// route through it: a day press closes the panel via `onRequestClose`, which
|
|
70
|
+
// never reaches `onOpenChange`, so a commit living there is skipped by the one
|
|
71
|
+
// path this component exists for — picking a date. The popover shuts, the
|
|
72
|
+
// correction is discarded, and nothing says so. A stamp that silently drops the
|
|
73
|
+
// correction is worse than a read-only one.
|
|
74
|
+
const commit = () => {
|
|
75
|
+
setOpen(false);
|
|
76
|
+
if (picked.current !== value) onChange(picked.current ?? "");
|
|
77
|
+
};
|
|
78
|
+
const reopen = () => {
|
|
79
|
+
picked.current = value;
|
|
80
|
+
setDraft(value);
|
|
81
|
+
setOpen(true);
|
|
82
|
+
};
|
|
83
|
+
|
|
60
84
|
return (
|
|
61
85
|
<Popover
|
|
62
86
|
open={open}
|
|
63
|
-
onOpenChange={(next) => {
|
|
64
|
-
setOpen(next);
|
|
65
|
-
// Commit on CLOSE, matching `InlineDatePicker`: a single pick and the
|
|
66
|
-
// panel's Today button both auto-close through here, so committing on
|
|
67
|
-
// change instead would fire twice and a dismissed panel would still write.
|
|
68
|
-
if (!next && draft !== value) onChange(draft ?? "");
|
|
69
|
-
if (next) setDraft(value);
|
|
70
|
-
}}
|
|
87
|
+
onOpenChange={(next) => { if (next) reopen(); else commit(); }}
|
|
71
88
|
>
|
|
72
89
|
<PopoverTrigger>
|
|
73
90
|
<Pressable
|
|
74
|
-
onPress={
|
|
91
|
+
onPress={reopen}
|
|
75
92
|
onHoverIn={() => setHovered(true)}
|
|
76
93
|
onHoverOut={() => setHovered(false)}
|
|
77
94
|
accessibilityRole="button"
|
|
@@ -114,7 +131,13 @@ export function DateStamp(props: DateStampProps) {
|
|
|
114
131
|
</Pressable>
|
|
115
132
|
</PopoverTrigger>
|
|
116
133
|
<PopoverContent>
|
|
117
|
-
<DatePickerPanel
|
|
134
|
+
<DatePickerPanel
|
|
135
|
+
value={draft}
|
|
136
|
+
onValueChange={(iso) => { picked.current = iso; setDraft(iso); }}
|
|
137
|
+
format="date"
|
|
138
|
+
locale={tag}
|
|
139
|
+
onRequestClose={commit}
|
|
140
|
+
/>
|
|
118
141
|
</PopoverContent>
|
|
119
142
|
</Popover>
|
|
120
143
|
);
|