@lotics/ui 12.1.1 → 13.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/src/badge.tsx CHANGED
@@ -9,15 +9,16 @@ interface BadgeProps {
9
9
  label?: string;
10
10
  color?: ColorName;
11
11
  /**
12
- * The status-indicator weight:
13
- * - "tonal" (default): a filled pill — a record's prominent STATUS field (a
14
- * register's Status column, a drawer header). For categorical status, not
15
- * a metric value: a percentage/amount is a number, never a chip.
16
- * - "dot": pill-lessa colored dot + label, the LIGHT indicator for
17
- * legends, secondary/inline status, or a metric's quality cue (a
18
- * healthy-vs-thin margin) where a filled chip would be too heavy.
19
- * (StatusGrid/StatusLegend keep their own dot their color is raw hex,
20
- * coupled to the grid cells' tint, a separate data-viz concern.)
12
+ * The status-indicator weight — DEFAULT to `dot`:
13
+ * - "dot": pill-less — a colored dot + label. The everyday status indicator:
14
+ * legends, register cells, inline/secondary status, a metric's quality cue.
15
+ * Light enough to sit anywhere without shouting. Reach for this by default.
16
+ * - "tonal": a filled pill — HEAVY, and easy to overuse into clutter. Reserve
17
+ * it for the ONE prominent status of a surface (a drawer header, a register's
18
+ * primary Status column). One per view, a deliberate choice not sprinkled
19
+ * through rows or option lists. If in doubt, use `dot`.
20
+ * (StatusGrid/StatusLegend keep their own dot raw hex coupled to the grid
21
+ * cells' tint, a separate data-viz concern.)
21
22
  */
22
23
  variant?: "tonal" | "dot";
23
24
  style?: StyleProp<ViewStyle>;
@@ -25,8 +26,23 @@ interface BadgeProps {
25
26
  userSelect?: "none" | "auto";
26
27
  }
27
28
 
29
+ /**
30
+ * A STATUS indicator — a lifecycle state, a risk level, a quality cue — that reads at a
31
+ * GLANCE. That is the ONLY thing a Badge is for; keep the vocabulary scarce so a colored
32
+ * chip always MEANS state.
33
+ *
34
+ * NOT for a type / category / attribute / count (a party's type, a document kind, a city,
35
+ * "3 files") — that is not a status, and a pill for it is noise. Render it as inline TEXT
36
+ * (a muted metadata line; a taller row is fine). If you're adding a Badge "to show more
37
+ * info" in a row or an option list, STOP — that's text, not a badge.
38
+ *
39
+ * Default `variant="dot"`; `tonal` is the heavy exception (see the prop doc).
40
+ */
41
+
28
42
  export function Badge(props: BadgeProps) {
29
- const { label, color, variant = "tonal", style, tooltip, userSelect = "none" } = props;
43
+ // Default `dot` (the light indicator) — `tonal` is the heavy pill you opt INTO
44
+ // deliberately, so a bare <Badge> never silently clutters a row with a filled chip.
45
+ const { label, color, variant = "dot", style, tooltip, userSelect = "none" } = props;
30
46
  const tooltipProps = useTooltip(tooltip);
31
47
  const hue = color && colors[color] ? colors[color] : null;
32
48
 
package/src/checklist.tsx CHANGED
@@ -21,7 +21,7 @@ import { ActionMenu, type ActionMenuItem } from "./action_menu";
21
21
  // <ChecklistRow control={<CheckCircle …/>} trailing={<InlineMemberSelect …/>}
22
22
  // menu={{ items: [{ key: "delete", label: "Delete task", danger: true, … }],
23
23
  // accessibilityLabel: "Task options: …" }}>
24
- // <InlineTextInput background="transparent" struck={done} … />
24
+ // <InlineTextInput variant="cell" struck={done} … />
25
25
  // </ChecklistRow>
26
26
  // <SuggestionChip label="Verify the tax ID" onAdd={materialize} onDismiss={dismiss} />
27
27
  // <CaptureRow … />
@@ -1,8 +1,10 @@
1
- import { StyleSheet, View } from "react-native";
1
+ import { Pressable, View } from "react-native";
2
2
  import { colors } from "./colors";
3
3
  import { Text } from "./text";
4
4
  import { Icon } from "./icon";
5
- import { CardSelectItem } from "./card_select_item";
5
+ import { Divider } from "./divider";
6
+ import { useFocusRing } from "./use_focus_ring";
7
+ import { FOCUS_RING } from "./control_surface";
6
8
 
7
9
  export interface ChoiceOption {
8
10
  label: string;
@@ -18,46 +20,55 @@ export interface ChoiceListProps {
18
20
  onSelect: (value: string) => void;
19
21
  }
20
22
 
23
+ /** One selectable option — a role-less-free `Pressable` (its own accessible name
24
+ * + selected state), hover wash + focus ring, no border. */
25
+ function ChoiceRow({ option, selected, onSelect }: { option: ChoiceOption; selected: boolean; onSelect: (value: string) => void }) {
26
+ const { focusVisible, focusProps } = useFocusRing();
27
+ return (
28
+ <Pressable
29
+ accessibilityRole="button"
30
+ accessibilityLabel={option.label}
31
+ accessibilityState={{ selected }}
32
+ onPress={() => onSelect(option.value)}
33
+ {...focusProps}
34
+ style={({ hovered, pressed }) => [
35
+ { flexDirection: "row", alignItems: "center", gap: 12, paddingVertical: 14, paddingHorizontal: 6, borderRadius: 8 },
36
+ hovered || pressed ? { backgroundColor: colors.zinc[50] } : null,
37
+ focusVisible ? { boxShadow: FOCUS_RING } : null,
38
+ ]}
39
+ >
40
+ <View style={{ flex: 1, gap: 3, minWidth: 0 }}>
41
+ <Text size="sm" weight={selected ? "semibold" : "medium"}>
42
+ {option.label}
43
+ </Text>
44
+ {option.description ? (
45
+ <Text size="xs" color="muted">
46
+ {option.description}
47
+ </Text>
48
+ ) : null}
49
+ </View>
50
+ {selected ? <Icon name="check" size={17} color={colors.zinc[900]} /> : null}
51
+ </Pressable>
52
+ );
53
+ }
54
+
21
55
  /**
22
- * A vertical set of selectable answer options — bordered cards with the global
23
- * focus/hover/selected ring (`CardSelectItem`), single-select and freely
24
- * switchable (pick a different option any time). The chosen card shows a check.
25
- * The agent's quick-reply surface (`Clarify` uses it) and any "pick one"
56
+ * A vertical set of selectable answer options — divider-separated rows (no
57
+ * bordered cards), each with its own focus ring + hover wash, single-select and
58
+ * freely switchable (pick a different option any time). The chosen row shows a
59
+ * check. The agent's quick-reply surface (`Clarify` uses it) and any "pick one"
26
60
  * question.
27
61
  */
28
62
  export function ChoiceList(props: ChoiceListProps) {
29
63
  const { options, value, onSelect } = props;
30
64
  return (
31
- <View style={{ gap: 8 }}>
32
- {options.map((o) => {
33
- const selected = value === o.value;
34
- return (
35
- <CardSelectItem
36
- key={o.value}
37
- selected={selected}
38
- onPress={() => onSelect(o.value)}
39
- accessibilityLabel={o.label}
40
- style={styles.item}
41
- >
42
- <View style={styles.text}>
43
- <Text size="sm" weight="medium">
44
- {o.label}
45
- </Text>
46
- {o.description ? (
47
- <Text size="xs" color="muted">
48
- {o.description}
49
- </Text>
50
- ) : null}
51
- </View>
52
- {selected ? <Icon name="check" size={16} color={colors.zinc[900]} /> : null}
53
- </CardSelectItem>
54
- );
55
- })}
65
+ <View>
66
+ {options.map((o, i) => (
67
+ <View key={o.value}>
68
+ {i > 0 ? <Divider /> : null}
69
+ <ChoiceRow option={o} selected={value === o.value} onSelect={onSelect} />
70
+ </View>
71
+ ))}
56
72
  </View>
57
73
  );
58
74
  }
59
-
60
- const styles = StyleSheet.create({
61
- item: { flexDirection: "row", alignItems: "center", gap: 10, paddingVertical: 11, paddingHorizontal: 14 },
62
- text: { flex: 1, gap: 2 },
63
- });
package/src/clarify.tsx CHANGED
@@ -1,5 +1,4 @@
1
- import { StyleSheet, View } from "react-native";
2
- import { colors } from "./colors";
1
+ import { View } from "react-native";
3
2
  import { Text } from "./text";
4
3
  import { ChoiceList, type ChoiceOption } from "./choice_list";
5
4
 
@@ -16,33 +15,24 @@ export interface ClarifyProps {
16
15
  }
17
16
 
18
17
  /**
19
- * The agent asks BACK — a question with selectable answer options (`ChoiceList`)
20
- * the human picks before the run continues, freely switchable until committed.
18
+ * The agent asks BACK — a question with selectable answer options the human picks
19
+ * before the run continues, freely switchable until committed. A borderless block
20
+ * (no card): the question, then divider-separated option rows (`ChoiceList`).
21
21
  * Human-in-the-loop input: when the agent is unsure, it clarifies instead of
22
- * guessing wrong. The run pauses on this and `onAnswer` resumes it. Pair with
23
- * `AgentRun` / `AgentProgress`.
22
+ * guessing wrong. Pair with `AgentRun` / `AgentProgress`.
24
23
  */
25
24
  export function Clarify(props: ClarifyProps) {
26
25
  return (
27
- <View style={styles.card}>
28
- <Text size="xs" color="muted" weight="medium">
29
- Question
30
- </Text>
31
- <Text size="sm" weight="medium">
32
- {props.question}
33
- </Text>
26
+ <View style={{ gap: 14 }}>
27
+ <View style={{ gap: 4 }}>
28
+ <Text size="xs" color="muted" weight="medium">
29
+ Question
30
+ </Text>
31
+ <Text size="sm" weight="medium">
32
+ {props.question}
33
+ </Text>
34
+ </View>
34
35
  <ChoiceList options={props.options} value={props.answer} onSelect={props.onAnswer} />
35
36
  </View>
36
37
  );
37
38
  }
38
-
39
- const styles = StyleSheet.create({
40
- card: {
41
- borderWidth: 1,
42
- borderColor: colors.border,
43
- backgroundColor: colors.white,
44
- borderRadius: 12,
45
- padding: 16,
46
- gap: 12,
47
- },
48
- });
@@ -111,6 +111,11 @@ export interface DetailRowProps {
111
111
  * hidden ⓘ gloss. Invalid STATE → `error` or a co-located `Callout`. Same
112
112
  * name, same meaning as `FormField.description`. */
113
113
  description?: string;
114
+ /** A consequence the user should WEIGH before acting — renders in the same
115
+ * annotation slot in amber (`Text color="warning"`), one tone between the
116
+ * muted `description` and the danger `error`, announced on appearance. Same
117
+ * name, same meaning as `FormField.warning`. */
118
+ warning?: string;
114
119
  /** Field-level failure, under the value in danger ink with `FormField`'s
115
120
  * alert semantics (announced on appearance). For CONSUMER-validated state —
116
121
  * the `Inline*` editors already render their own transient save errors;
@@ -136,13 +141,13 @@ export interface DetailRowProps {
136
141
  */
137
142
  export function DetailRow(props: DetailRowProps) {
138
143
  const table = useContext(DetailTableContext);
139
- const { label, children, trailing, labelSize = "sm", description, error, flat } = props;
144
+ const { label, children, trailing, labelSize = "sm", description, warning, error, flat } = props;
140
145
  const labelWidth = props.labelWidth ?? table?.labelWidth;
141
146
  const minHeight = props.minHeight ?? table?.minHeight ?? 28;
142
147
  const form = labelWidth != null;
143
- // Annotations (description / error) live UNDER THE VALUE — the row grows,
144
- // so the label + trailing center against the CONTROL LINE, not the block.
145
- const annotated = description != null || error != null;
148
+ // Annotations (description / warning / error) live UNDER THE VALUE — the row
149
+ // grows, so the label + trailing center against the CONTROL LINE, not the block.
150
+ const annotated = description != null || warning != null || error != null;
146
151
  // A FLAT value (no painted chip) centers its text in the control band,
147
152
  // leaving invisible slack below it — tuck the annotations up by that slack
148
153
  // so the perceived gap matches a chip row's. Horizontal mode only (stacked
@@ -155,9 +160,10 @@ export function DetailRow(props: DetailRowProps) {
155
160
  </Text>
156
161
  </View>
157
162
  );
158
- // Error first (adjacent to the control that failed, FormField's alert
159
- // semantics), then the guidance. Indented to the inline chip's OWN text
160
- // inset (paddingHorizontal 8), so annotations align with the value's text.
163
+ // Severity descending from the control: error first (adjacent to the control
164
+ // that failed, FormField's alert semantics), then warning, then the guidance.
165
+ // Indented to the inline chip's OWN text inset (paddingHorizontal 8), so
166
+ // annotations align with the value's text.
161
167
  const annotations = annotated ? (
162
168
  <>
163
169
  {error != null ? (
@@ -165,6 +171,11 @@ export function DetailRow(props: DetailRowProps) {
165
171
  {error}
166
172
  </Text>
167
173
  ) : null}
174
+ {warning != null ? (
175
+ <Text size="xs" color="warning" accessibilityRole="alert" aria-live="polite" style={styles.annotation}>
176
+ {warning}
177
+ </Text>
178
+ ) : null}
168
179
  {description != null ? (
169
180
  <Text size="xs" color="muted" style={styles.annotation}>
170
181
  {description}
@@ -184,12 +195,17 @@ export function DetailRow(props: DetailRowProps) {
184
195
  {label}
185
196
  </Text>
186
197
  {/* stacked wears the FORM grammar exactly: label · description ·
187
- control · error (the `FormField` order) */}
198
+ control · warning · error (the `FormField` order) */}
188
199
  {description != null ? <Text color="muted">{description}</Text> : null}
189
200
  <View style={[styles.stackedValueRow, { minHeight }]}>
190
201
  <View style={styles.value}>{children}</View>
191
202
  {trailing != null ? trailing : null}
192
203
  </View>
204
+ {warning != null ? (
205
+ <Text size="xs" color="warning" accessibilityRole="alert" aria-live="polite">
206
+ {warning}
207
+ </Text>
208
+ ) : null}
193
209
  {error != null ? (
194
210
  <Text size="xs" color="danger" accessibilityRole="alert" aria-live="polite">
195
211
  {error}
@@ -8,12 +8,13 @@ export interface FormDatePickerProps
8
8
  /** `FormField` wrapping a `DatePicker` — one labeled date field. (Omits `style`; for a grid cell
9
9
  * use a bare `FormField style={half}` around `DatePicker`.) */
10
10
  export function FormDatePicker(props: FormDatePickerProps) {
11
- const { label, description, optional, optionalLabel, error, ...datePickerProps } = props;
11
+ const { label, description, warning, optional, optionalLabel, error, ...datePickerProps } = props;
12
12
 
13
13
  return (
14
14
  <FormField
15
15
  label={label}
16
16
  description={description}
17
+ warning={warning}
17
18
  error={error}
18
19
  optional={optional}
19
20
  optionalLabel={optionalLabel}
@@ -8,6 +8,10 @@ export interface FormFieldProps {
8
8
  label: string;
9
9
  optional?: boolean;
10
10
  description?: string;
11
+ /** A consequence the user should WEIGH before acting — amber, one tone between
12
+ * the muted `description` and the danger `error`. Same name/meaning on
13
+ * `DetailRow`. */
14
+ warning?: string;
11
15
  error?: string;
12
16
  style?: StyleProp<ViewStyle>;
13
17
  /** Label shown next to `label` when `optional` is true. Defaults to the locale
@@ -18,12 +22,13 @@ export interface FormFieldProps {
18
22
  /**
19
23
  * Binding emitted by `FormField` to the single input it wraps. Descendants
20
24
  * that call `useFormField()` apply these props to the native input so screen
21
- * readers know the label, error, and description apply to it.
25
+ * readers know the label, description, warning, and error apply to it.
22
26
  */
23
27
  export interface FormFieldBinding {
24
28
  inputId: string;
25
29
  labelId: string;
26
30
  descriptionId: string | undefined;
31
+ warningId: string | undefined;
27
32
  errorId: string | undefined;
28
33
  invalid: boolean;
29
34
  }
@@ -33,7 +38,7 @@ const FormFieldContext = createContext<FormFieldBinding | null>(null);
33
38
  /**
34
39
  * Returns the association IDs set by the nearest enclosing `FormField`.
35
40
  * Inputs spread the returned props onto their underlying element so label,
36
- * description, and error are announced together. Returns `null` when the
41
+ * description, warning, and error are announced together. Returns `null` when the
37
42
  * input is used outside a `FormField` — callers should then provide their
38
43
  * own `accessibilityLabel`.
39
44
  */
@@ -42,18 +47,20 @@ export function useFormField(): FormFieldBinding | null {
42
47
  }
43
48
 
44
49
  export function FormField(props: FormFieldProps & { children: React.ReactNode }) {
45
- const { label, description, error, optional, style, children, optionalLabel } = props;
50
+ const { label, description, warning, error, optional, style, children, optionalLabel } = props;
46
51
  const resolvedOptionalLabel = optionalLabel ?? useLoticsLocale().formField.optional;
47
52
 
48
53
  const inputId = useId();
49
54
  const labelId = `${inputId}-label`;
50
55
  const descriptionId = description ? `${inputId}-description` : undefined;
56
+ const warningId = warning ? `${inputId}-warning` : undefined;
51
57
  const errorId = error ? `${inputId}-error` : undefined;
52
58
 
53
59
  const binding: FormFieldBinding = {
54
60
  inputId,
55
61
  labelId,
56
62
  descriptionId,
63
+ warningId,
57
64
  errorId,
58
65
  invalid: !!error,
59
66
  };
@@ -86,6 +93,19 @@ export function FormField(props: FormFieldProps & { children: React.ReactNode })
86
93
  )}
87
94
  <Spacer size={8} />
88
95
  <View>{children}</View>
96
+ {warning && (
97
+ <>
98
+ <Spacer size={8} />
99
+ {/*
100
+ Below the control, above the error — the three tones read
101
+ description · warning · error, ascending severity. `role="alert"`
102
+ + `aria-live="polite"` announce it politely on appearance.
103
+ */}
104
+ <Text nativeID={warningId} color="warning" accessibilityRole="alert" aria-live="polite">
105
+ {warning}
106
+ </Text>
107
+ </>
108
+ )}
89
109
  {error && (
90
110
  <>
91
111
  <Spacer size={8} />
@@ -5,12 +5,13 @@ import { Picker, PickerProps } from "./picker";
5
5
  export function FormPicker<T extends string>(
6
6
  props: PickerProps<T> & FormFieldProps,
7
7
  ) {
8
- const { label, description, optional, optionalLabel, error, ...pickerProps } = props;
8
+ const { label, description, warning, optional, optionalLabel, error, ...pickerProps } = props;
9
9
 
10
10
  return (
11
11
  <FormField
12
12
  label={label}
13
13
  description={description}
14
+ warning={warning}
14
15
  error={error}
15
16
  optional={optional}
16
17
  optionalLabel={optionalLabel}
@@ -7,10 +7,11 @@ import { Spacer } from "./spacer";
7
7
 
8
8
  export interface FormSwitchProps extends Omit<FormFieldProps, "optional">, SwitchProps {}
9
9
 
10
- /** A labeled on/off toggle — a `Switch` + clickable label + optional `description`/`error`. */
10
+ /** A labeled on/off toggle — a `Switch` + clickable label + optional `description`/`warning`/`error`. */
11
11
  export function FormSwitch(props: FormSwitchProps) {
12
- const { label, description, error, value, onChange } = props;
12
+ const { label, description, warning, error, value, onChange } = props;
13
13
  const labelId = useId();
14
+ const warningId = warning ? `${labelId}-warning` : undefined;
14
15
  const errorId = error ? `${labelId}-error` : undefined;
15
16
 
16
17
  return (
@@ -44,6 +45,19 @@ export function FormSwitch(props: FormSwitchProps) {
44
45
  </View>
45
46
  </View>
46
47
 
48
+ {!!warning && (
49
+ <>
50
+ <Spacer size={8} />
51
+ <Text
52
+ nativeID={warningId}
53
+ color="warning"
54
+ accessibilityRole="alert"
55
+ aria-live="polite"
56
+ >
57
+ {warning}
58
+ </Text>
59
+ </>
60
+ )}
47
61
  {!!error && (
48
62
  <>
49
63
  <Spacer size={8} />
@@ -4,13 +4,13 @@ import { TextInputField } from "./text_input_field";
4
4
 
5
5
  export interface FormTextInputProps extends Omit<FormFieldProps, "style">, TextInputProps {}
6
6
 
7
- /** `FormField` (label / description / error / optional) wrapping a `TextInputField` — one labeled
8
- * text field. For a 2-col grid cell, prefer a bare `FormField style={half}` around the input. */
7
+ /** `FormField` (label / description / warning / error / optional) wrapping a `TextInputField` — one
8
+ * labeled text field. For a 2-col grid cell, prefer a bare `FormField style={half}` around the input. */
9
9
  export function FormTextInput(props: FormTextInputProps) {
10
- const { label, description, optional, error, ...textInputProps } = props;
10
+ const { label, description, warning, optional, error, ...textInputProps } = props;
11
11
 
12
12
  return (
13
- <FormField label={label} description={description} error={error} optional={optional}>
13
+ <FormField label={label} description={description} warning={warning} error={error} optional={optional}>
14
14
  <TextInputField {...textInputProps} />
15
15
  </FormField>
16
16
  );
@@ -9,7 +9,8 @@ import { DateField } from "./date_field";
9
9
  import { isoHasTime, resolveDateCommit } from "./date_picker_value";
10
10
  import { formatDate } from "./format_date";
11
11
  import { ActivityIndicator } from "./activity_indicator";
12
- import { type InlineEditBackground, InlineEditView } from "./inline_edit";
12
+ import { type InlineEditVariant, InlineEditView } from "./inline_edit";
13
+ import { type TextColor } from "./text_utils";
13
14
  import { useLoticsLocale } from "./locale";
14
15
  import { getInteractionModality } from "./interaction_modality";
15
16
  import { shouldOpenOnFocus, shouldRestoreFocusOnClose } from "./inline_focus";
@@ -34,8 +35,11 @@ export interface InlineDatePickerProps {
34
35
  locale?: string;
35
36
  disabled?: boolean;
36
37
  accessibilityLabel?: string;
37
- /** Resting surface — "tint" (default, the zinc-50 chip) or "transparent". */
38
- background?: InlineEditBackground;
38
+ /** Form field (default) or grid cell see `InlineEditVariant`. */
39
+ variant?: InlineEditVariant;
40
+ /** Colour the resting date by urgency — overdue red, soon-due amber (the caller owns
41
+ * the rule; e.g. a task's `dueTone`). Applies to a set value only. */
42
+ tone?: TextColor;
39
43
  }
40
44
 
41
45
  /**
@@ -54,7 +58,7 @@ export interface InlineDatePickerProps {
54
58
  * The row never changes height in either mode.
55
59
  */
56
60
  export function InlineDatePicker(props: InlineDatePickerProps) {
57
- const { value, onSave, onClear, format = "date", optionalTime, placeholder, locale, disabled, accessibilityLabel , background } = props;
61
+ const { value, onSave, onClear, format = "date", optionalTime, placeholder, locale, disabled, accessibilityLabel , variant, tone } = props;
58
62
  const locales = useLoticsLocale();
59
63
  const inlineLabels = locales.inline;
60
64
  const dateLabels = locales.datePicker;
@@ -228,6 +232,11 @@ export function InlineDatePicker(props: InlineDatePickerProps) {
228
232
  ) : (
229
233
  <Icon name={format === "datetime" ? "calendar-clock" : "calendar"} size={18} color={colors.zinc[400]} />
230
234
  );
235
+ // The resting calendar glyph is a FORM affordance (marks an empty field as a date
236
+ // control). A `cell` lives in a grid where the column header already says "Due" and
237
+ // every cell edits — the repeated glyph is noise, so it reads value-first (the saving
238
+ // spinner still shows). The typed-edit field keeps its glyph — that's the open-picker button.
239
+ const restTrailing = variant === "cell" && !saving ? undefined : trailing;
231
240
 
232
241
  return (
233
242
  <View>
@@ -254,17 +263,18 @@ export function InlineDatePicker(props: InlineDatePickerProps) {
254
263
  ) : (
255
264
  <PopoverTrigger>
256
265
  <InlineEditView
257
- background={background}
266
+ variant={variant}
267
+ tone={tone}
258
268
  display={display}
259
269
  placeholder={placeholder}
260
270
  disabled={disabled}
261
271
  active={open && !disabled}
262
272
  accessibilityLabel={accessibilityLabel}
263
273
  onFocus={handleViewFocus}
264
- // A rest affordance (like the select's chevron): a calendar glyph marks
265
- // the field as a tappable date control even when empty no hover / pointer
266
- // cursor needed, so it reads as interactive on touch.
267
- trailing={trailing}
274
+ // A rest affordance (like the select's chevron): in a FORM a calendar glyph
275
+ // marks the field as a tappable date control even when empty; a grid CELL
276
+ // drops it (see restTrailing) to read value-first.
277
+ trailing={restTrailing}
268
278
  />
269
279
  </PopoverTrigger>
270
280
  )}