@lotics/ui 6.2.0 → 6.3.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/AGENTS.md CHANGED
@@ -205,6 +205,10 @@ builds the new record and attaches it; existing matches attach directly. The cre
205
205
  BELOW matches by default (the keyboard highlight lands on the first MATCH, so Enter on a partial
206
206
  picks it, never a duplicate); pass `customOptionPlacement="top"` to pin it above. Use
207
207
  `reflectSelection={false}` and render the attached record below as a card with a Change action.
208
+ For input-level status that must stay visible while results scroll (validity feedback on the typed
209
+ value, a result count, a secondary "create" affordance), pass `footer={({ close }) => …}` — a pinned
210
+ row below the listbox, OUTSIDE keyboard option-nav; call `close()` from a footer action that hands
211
+ off elsewhere (so the popover dismisses cleanly instead of floating over what you navigated to).
208
212
 
209
213
  ### Line items — create → preview → edit (a composition, not a primitive)
210
214
  For a list of records you build then revise (invoice rows, repair lines, config entries), each
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/ui",
3
- "version": "6.2.0",
3
+ "version": "6.3.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./tokens": "./src/tokens.ts",
package/src/combobox.tsx CHANGED
@@ -67,6 +67,13 @@ export interface ComboboxProps<T extends string = string, D = unknown> {
67
67
  placeholder?: string;
68
68
  recentsLabel?: string;
69
69
  emptyText?: string;
70
+ /** A pinned row rendered at the bottom of the popover (below the results, outside
71
+ * the scroll area) whenever the popover is open. For input-level status that must
72
+ * stay visible while results scroll — validity feedback, a result count, a "create"
73
+ * affordance. It sits OUTSIDE the listbox, so its content is auxiliary (not an
74
+ * option) and never enters keyboard option-nav. Receives `close` to dismiss the
75
+ * popover — call it from an action that navigates elsewhere. */
76
+ footer?: (api: { close: () => void }) => ReactNode;
70
77
  accessibilityLabel?: string;
71
78
  disabled?: boolean;
72
79
  /** Show an in-input clear ✕ whenever the input has text (a reflected selection
@@ -111,6 +118,7 @@ export function Combobox<T extends string = string, D = unknown>(props: Combobox
111
118
  searchDebounceMs = 200,
112
119
  icon,
113
120
  placeholder,
121
+ footer,
114
122
  accessibilityLabel = "Results",
115
123
  disabled = false,
116
124
  clearable = false,
@@ -169,6 +177,14 @@ export function Combobox<T extends string = string, D = unknown>(props: Combobox
169
177
  [onValueChange, isServer, debouncedSearch, onSearchChange, reflectSelection],
170
178
  );
171
179
 
180
+ // Dismiss the popover from the footer — drop focus too, mirroring the popover's
181
+ // own self-close (so the input's keyboard focus ring doesn't linger after a
182
+ // footer action hands off elsewhere).
183
+ const close = useCallback(() => {
184
+ setOpen(false);
185
+ inputRef.current?.blur();
186
+ }, []);
187
+
172
188
  const list = useOptionList<T, false, D>({
173
189
  options,
174
190
  value: single?.value ?? null,
@@ -347,6 +363,7 @@ export function Combobox<T extends string = string, D = unknown>(props: Combobox
347
363
  })
348
364
  )}
349
365
  </ScrollView>
366
+ {footer ? <View style={styles.footer}>{footer({ close })}</View> : null}
350
367
  </View>
351
368
  </PopoverContent>
352
369
  </Popover>
@@ -386,4 +403,10 @@ const styles = StyleSheet.create({
386
403
  justifyContent: "center",
387
404
  paddingVertical: 16,
388
405
  },
406
+ footer: {
407
+ borderTopWidth: 1,
408
+ borderTopColor: colors.border,
409
+ paddingHorizontal: 8,
410
+ paddingVertical: 6,
411
+ },
389
412
  });