@lotics/ui 5.7.0 → 5.9.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.
Files changed (80) hide show
  1. package/AGENTS.md +102 -42
  2. package/examples/tpl_approvals.tsx +10 -10
  3. package/examples/tpl_convert.tsx +11 -23
  4. package/examples/tpl_crm_desk.tsx +3 -3
  5. package/examples/tpl_dieline.tsx +7 -6
  6. package/examples/tpl_directory.tsx +3 -3
  7. package/examples/tpl_dossier.tsx +7 -7
  8. package/examples/tpl_draft.tsx +4 -4
  9. package/examples/tpl_extract.tsx +7 -7
  10. package/examples/tpl_inventory.tsx +3 -3
  11. package/examples/tpl_lookup.tsx +4 -3
  12. package/examples/tpl_match.tsx +1 -1
  13. package/examples/tpl_order.tsx +1 -1
  14. package/examples/tpl_ratedesk.tsx +12 -12
  15. package/examples/tpl_record.tsx +1 -1
  16. package/examples/tpl_report.tsx +3 -3
  17. package/examples/tpl_stock.tsx +3 -3
  18. package/examples/tpl_triage.tsx +1 -1
  19. package/package.json +12 -4
  20. package/src/agent_progress.tsx +14 -12
  21. package/src/agent_run.tsx +253 -66
  22. package/src/back_button.tsx +3 -2
  23. package/src/badge.tsx +2 -1
  24. package/src/button.tsx +11 -32
  25. package/src/button_colors.ts +23 -0
  26. package/src/calendar/calendar_view.tsx +2 -2
  27. package/src/card_select_item.tsx +2 -1
  28. package/src/change_review.tsx +5 -6
  29. package/src/{pill_button.tsx → chip.tsx} +7 -7
  30. package/src/chip_group.tsx +2 -2
  31. package/src/column_filter.tsx +4 -4
  32. package/src/combobox.tsx +2 -9
  33. package/src/composer.tsx +4 -3
  34. package/src/control_surface.ts +15 -15
  35. package/src/date_field.tsx +2 -1
  36. package/src/date_filter.tsx +2 -1
  37. package/src/date_range_filter_field.tsx +2 -1
  38. package/src/dialog.tsx +2 -2
  39. package/src/discrepancy.tsx +1 -1
  40. package/src/drawer.tsx +1 -2
  41. package/src/file_gallery_modal.tsx +220 -133
  42. package/src/file_grid.tsx +184 -0
  43. package/src/file_preview.web.tsx +329 -6
  44. package/src/file_preview_types.ts +40 -0
  45. package/src/file_row.tsx +114 -0
  46. package/src/file_rows.tsx +105 -0
  47. package/src/file_thumbnail.tsx +121 -79
  48. package/src/file_thumbnail_grid.tsx +130 -55
  49. package/src/{filter_pill.tsx → filter_chip.tsx} +8 -8
  50. package/src/finding.tsx +1 -1
  51. package/src/gantt/gantt_view.tsx +2 -2
  52. package/src/icon_button.tsx +49 -14
  53. package/src/image_gallery.tsx +0 -4
  54. package/src/inline_edit.tsx +2 -2
  55. package/src/link_button.tsx +2 -1
  56. package/src/menu_button.tsx +2 -1
  57. package/src/menu_list_item.tsx +2 -1
  58. package/src/number_input.tsx +2 -1
  59. package/src/pdfjs_worker.d.ts +5 -0
  60. package/src/picker.tsx +3 -3
  61. package/src/popover_header.tsx +2 -2
  62. package/src/radio_picker.tsx +2 -1
  63. package/src/range_slider.tsx +1 -1
  64. package/src/review_card.tsx +2 -2
  65. package/src/scan_field.tsx +2 -1
  66. package/src/scored_option.tsx +0 -1
  67. package/src/search_input.tsx +3 -2
  68. package/src/segmented_control.tsx +2 -1
  69. package/src/status_badge.tsx +2 -1
  70. package/src/stepper.tsx +3 -2
  71. package/src/switch_button.tsx +2 -1
  72. package/src/switcher.tsx +3 -2
  73. package/src/tabs.tsx +2 -1
  74. package/src/tag_input.tsx +2 -1
  75. package/src/text_input_field.tsx +2 -1
  76. package/src/time_field.tsx +2 -1
  77. package/src/time_picker.tsx +2 -1
  78. package/src/triage_row.tsx +3 -3
  79. package/src/uploading_thumbnail.tsx +212 -0
  80. package/src/hover_action.tsx +0 -54
@@ -3,7 +3,7 @@ import { Text } from "./text";
3
3
  import { TextInputField } from "./text_input_field";
4
4
  import { NumberInput } from "./number_input";
5
5
  import { PickerMenu } from "./picker_menu";
6
- import { FilterPill } from "./filter_pill";
6
+ import { FilterChip } from "./filter_chip";
7
7
  import type { PickerOption } from "./picker";
8
8
 
9
9
  /** A column the picker can filter on. `type` selects the control + operators. */
@@ -97,7 +97,7 @@ export interface ColumnFilterProps {
97
97
  }
98
98
 
99
99
  /**
100
- * A toolbar-style filter pill: the same `PillButton` the table toolbar uses, so
100
+ * A toolbar-style filter pill: the same `Chip` the table toolbar uses, so
101
101
  * a picker's filters read identically. Inactive → "Label ⌄"; active →
102
102
  * "Label: summary" with an X to clear. Pressing the pill opens a type-aware
103
103
  * editor (text contains / number range / multi-select). Controlled — the
@@ -109,7 +109,7 @@ export function ColumnFilter(props: ColumnFilterProps) {
109
109
  const active = isColumnFilterActive(value);
110
110
 
111
111
  return (
112
- <FilterPill
112
+ <FilterChip
113
113
  label={column.label}
114
114
  summary={active ? columnFilterSummary(column, value) : undefined}
115
115
  onClear={() => onChange(undefined)}
@@ -150,7 +150,7 @@ export function ColumnFilter(props: ColumnFilterProps) {
150
150
  onValueChange={(selected) => onChange({ kind: "select", selected })}
151
151
  />
152
152
  )}
153
- </FilterPill>
153
+ </FilterChip>
154
154
  );
155
155
  }
156
156
 
package/src/combobox.tsx CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  } from "react-native";
10
10
  import { useCallback, useEffect, useId, useMemo, useRef, useState, type ReactNode } from "react";
11
11
  import { colors } from "./colors";
12
- import { ACTIVE_RING, inputPillStyle } from "./control_surface";
12
+ import { ACTIVE_RING, CONTROL_RADIUS } from "./control_surface";
13
13
  import { Text } from "./text";
14
14
  import { Icon, type IconName } from "./icon";
15
15
  import { TextInputField } from "./text_input_field";
@@ -70,11 +70,6 @@ export interface ComboboxProps<T extends string = string, D = unknown> {
70
70
  * picker, not a free-text search box. Opt IN to the search-box look explicitly
71
71
  * with `icon="search"` (leading glyph, no chevron). Multi-select shows neither. */
72
72
  icon?: IconName;
73
- /** Render the input on the white toolbar-pill surface (rounded-full, white
74
- * fill, zinc-300 border) — the same surface SearchInput/ChipGroup/FilterPill
75
- * wear — so a combobox used as a toolbar filter reads as a view-control on the
76
- * zinc-50 canvas, not a form field. Single-select only. */
77
- pill?: boolean;
78
73
  placeholder?: string;
79
74
  recentsLabel?: string;
80
75
  emptyText?: string;
@@ -128,7 +123,6 @@ export function Combobox<T extends string = string, D = unknown>(props: Combobox
128
123
  loading = false,
129
124
  searchDebounceMs = 200,
130
125
  icon,
131
- pill = false,
132
126
  placeholder,
133
127
  recentsLabel = "Recent",
134
128
  emptyText = "No results",
@@ -346,7 +340,6 @@ export function Combobox<T extends string = string, D = unknown>(props: Combobox
346
340
  autoCorrect={false}
347
341
  style={[
348
342
  multi ? styles.multiInput : showChevron ? styles.selectInput : undefined,
349
- !multi && pill ? inputPillStyle : undefined,
350
343
  // Single-select wears the open ring on the input (which holds the
351
344
  // border); multi wears it on the wrapper above (the input is borderless).
352
345
  // Gate on `showList`, not `open`: an empty query with no options has
@@ -471,7 +464,7 @@ const styles = StyleSheet.create({
471
464
  minHeight: 40,
472
465
  borderWidth: 1,
473
466
  borderColor: colors.border,
474
- borderRadius: 8,
467
+ borderRadius: CONTROL_RADIUS,
475
468
  },
476
469
  multiInput: {
477
470
  flexGrow: 1,
package/src/composer.tsx CHANGED
@@ -1,6 +1,6 @@
1
1
  import React, { type ReactNode, type Ref, useCallback, useEffect, useState } from "react";
2
2
  import { View, TextInput as RNTextInput, ScrollView, StyleSheet, type TextInputProps } from "react-native";
3
- import { Button } from "./button";
3
+ import { IconButton } from "./icon_button";
4
4
  import { colors } from "./colors";
5
5
  import { fontFamilyRegular, getInputTextStyle } from "./text_utils";
6
6
  import { useAutoGrowHeight } from "./use_auto_grow_height";
@@ -156,10 +156,11 @@ export function Composer(props: ComposerProps) {
156
156
 
157
157
  const sendButton =
158
158
  disabled && onStop ? (
159
- <Button onPress={onStop} color="primary" icon="square" tooltip={stopLabel} accessibilityLabel={stopLabel} />
159
+ <IconButton onPress={onStop} size="lg" color="primary" icon="square" tooltip={stopLabel} accessibilityLabel={stopLabel} />
160
160
  ) : (
161
- <Button
161
+ <IconButton
162
162
  onPress={handleSend}
163
+ size="lg"
163
164
  color="primary"
164
165
  disabled={disabled || effectiveSendDisabled}
165
166
  icon="arrow-up"
@@ -1,10 +1,21 @@
1
- import { type TextStyle, type ViewStyle } from "react-native";
1
+ import { type ViewStyle } from "react-native";
2
2
  import { colors } from "./colors";
3
3
 
4
4
  /** The system control height — every band control (chips, pills, search,
5
5
  * buttons) aligns to it so a toolbar row reads as one band. */
6
6
  export const CONTROL_HEIGHT = 40;
7
7
 
8
+ /** The system control radius — every interactive control surface (Button, the
9
+ * inputs/selects/pickers, SearchInput, MenuButton, Tabs, the SegmentedControl
10
+ * track, and ChipGroup/FilterChip/Chip via the surfaces below) wears it,
11
+ * so the whole control vocabulary reads as one family. Containers are
12
+ * deliberately larger (Card 16, the review card 12); a nested element inside a
13
+ * padded control (a segmented thumb, a combobox chip) takes `CONTROL_RADIUS −
14
+ * padding` so its corner stays CONCENTRIC with the parent's — set it equal and
15
+ * the inner corner bulges past the outer curve. Round-by-function controls
16
+ * (avatars, dots, switches, sliders, progress, icon buttons) stay full. */
17
+ export const CONTROL_RADIUS = 10;
18
+
8
19
  /** The active/open edge for an interactive control — a 2px zinc-900 ring flush
9
20
  * against the box (offset 0), MIRRORING the keyboard `:focus-visible` outline
10
21
  * (index.css). A mouse-opened popover trigger (Picker / Combobox / InlineSelect /
@@ -13,17 +24,6 @@ export const CONTROL_HEIGHT = 40;
13
24
  * beside the kit's focus outline and selected-pill ring. */
14
25
  export const ACTIVE_RING = `0 0 0 2px ${colors.zinc[900]}`;
15
26
 
16
- /** THE white toolbar-input surface — a rounded-full white pill with a one-step-up
17
- * zinc-300 border. A search/select control (SearchInput, and Combobox via `pill`)
18
- * wears it so it reads as a view-control beside ChipGroup/FilterPill on the
19
- * zinc-50 canvas (where the fill pops), not as a form field that vanishes into a
20
- * white card. One definition so the two controls never drift. */
21
- export const inputPillStyle: TextStyle = {
22
- borderRadius: 999,
23
- backgroundColor: colors.white,
24
- borderColor: colors.zinc[300],
25
- };
26
-
27
27
  /** THE review-card surface — the bordered white card every "AI proposes, you
28
28
  * decide" surface wears (`ReviewCard`, and the whole-set `ChangeReview`), so the
29
29
  * review family reads as ONE contract instead of look-alikes drifting apart. One
@@ -41,7 +41,7 @@ export const reviewResolvedStyle: ViewStyle = { backgroundColor: colors.zinc[50]
41
41
 
42
42
  /**
43
43
  * THE pill-surface contract — the single definition of the bordered, white,
44
- * rounded interactive surface shared by `ChipGroup` chips and `PillButton` (and
44
+ * rounded interactive surface shared by `ChipGroup` chips and `Chip` (and
45
45
  * anything composed on them). Returns the style for the CURRENT pressable state,
46
46
  * so the hover (white → zinc-100) and press (zinc-200) washes live in ONE place
47
47
  * instead of drifting per control — and any control built on it hovers without
@@ -49,7 +49,7 @@ export const reviewResolvedStyle: ViewStyle = { backgroundColor: colors.zinc[50]
49
49
  * crisp 2px dark edge, no layout shift). Each control adds its own padding/layout
50
50
  * on top.
51
51
  */
52
- export function pillSurfaceStyle(
52
+ export function chipSurfaceStyle(
53
53
  state: { hovered?: boolean; pressed?: boolean },
54
54
  opts?: { selected?: boolean },
55
55
  ): ViewStyle {
@@ -57,7 +57,7 @@ export function pillSurfaceStyle(
57
57
  return {
58
58
  height: CONTROL_HEIGHT,
59
59
  justifyContent: "center",
60
- borderRadius: 999,
60
+ borderRadius: CONTROL_RADIUS,
61
61
  borderWidth: 1,
62
62
  borderColor: selected ? colors.zinc[900] : colors.border,
63
63
  backgroundColor: state.pressed ? colors.zinc[200] : state.hovered ? colors.zinc[100] : colors.white,
@@ -1,6 +1,7 @@
1
1
  import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
2
  import { StyleProp, StyleSheet, View, ViewStyle } from "react-native";
3
3
  import { colors } from "./colors";
4
+ import { CONTROL_RADIUS } from "./control_surface";
4
5
  import { Text } from "./text";
5
6
  import { DateSegments } from "./date_segments_field";
6
7
  import { SegmentLabels, dateSegmentsConfig } from "./date_segments";
@@ -144,7 +145,7 @@ const styles = StyleSheet.create({
144
145
  height: 40,
145
146
  paddingHorizontal: 8,
146
147
  gap: 4,
147
- borderRadius: 8,
148
+ borderRadius: CONTROL_RADIUS,
148
149
  borderWidth: 1,
149
150
  borderColor: colors.border,
150
151
  backgroundColor: colors.background,
@@ -2,6 +2,7 @@ import React, { useCallback, useMemo, useRef, useState } from "react";
2
2
  import { ScrollView, StyleSheet, View } from "react-native";
3
3
  import { Text } from "./text";
4
4
  import { colors } from "./colors";
5
+ import { CONTROL_RADIUS } from "./control_surface";
5
6
  import { MenuButton } from "./menu_button";
6
7
  import { Calendar, CalendarRangeValue, CalendarRef } from "./date_calendar";
7
8
  import { TimeField } from "./time_field";
@@ -336,7 +337,7 @@ export function DateFilter(props: DateFilterProps) {
336
337
  const styles = StyleSheet.create({
337
338
  container: {
338
339
  backgroundColor: colors.white,
339
- borderRadius: 8,
340
+ borderRadius: CONTROL_RADIUS,
340
341
  gap: 16,
341
342
  },
342
343
  presetsSidebar: {
@@ -2,6 +2,7 @@ import React, { useMemo, useState } from "react";
2
2
  import { View } from "react-native";
3
3
  import { Text } from "./text";
4
4
  import { colors } from "./colors";
5
+ import { CONTROL_RADIUS } from "./control_surface";
5
6
  import { Icon } from "./icon";
6
7
  import { Button } from "./button";
7
8
  import { PressableHighlight } from "./pressable_highlight";
@@ -130,7 +131,7 @@ export function DateRangeFilterField(props: DateRangeFilterFieldProps) {
130
131
  paddingHorizontal: 12,
131
132
  borderWidth: 1,
132
133
  borderColor: colors.zinc[300],
133
- borderRadius: 8,
134
+ borderRadius: CONTROL_RADIUS,
134
135
  backgroundColor: colors.white,
135
136
  }}
136
137
  >
package/src/dialog.tsx CHANGED
@@ -3,7 +3,7 @@ import { Animated, Modal, StyleSheet, View, ScrollView } from "react-native";
3
3
  import { useScreenSize } from "@lotics/ui/use_screen_size";
4
4
  import { PortalHost } from "@lotics/ui/portal";
5
5
  import { colors } from "@lotics/ui/colors";
6
- import { Button } from "@lotics/ui/button";
6
+ import { IconButton } from "@lotics/ui/icon_button";
7
7
  import { Text } from "@lotics/ui/text";
8
8
  import { BackButton } from "@lotics/ui/back_button";
9
9
  import { useOverlayScope } from "@lotics/ui/overlay_scope";
@@ -163,7 +163,7 @@ export function Dialog(props: DialogProps) {
163
163
  <PortalHost>
164
164
  <View testID={testID} style={[styles.dialogContainer, { borderRadius }]}>
165
165
  <View style={[styles.closeButtonContainer, { paddingHorizontal: screenSize.small ? 16 : 24 }]}>
166
- <Button icon="x" accessibilityLabel="Close" onPress={handleClose} />
166
+ <IconButton icon="x" size="lg" accessibilityLabel="Close" onPress={handleClose} />
167
167
  </View>
168
168
  <View style={styles.container}>{children}</View>
169
169
  </View>
@@ -88,7 +88,7 @@ export function Discrepancy(props: DiscrepancyProps) {
88
88
 
89
89
  {onFlag ? (
90
90
  <View style={styles.footer}>
91
- <Button title="Flag for review" color="muted" shape="rounded" onPress={onFlag} />
91
+ <Button title="Flag for review" color="muted" onPress={onFlag} />
92
92
  </View>
93
93
  ) : null}
94
94
  </>
package/src/drawer.tsx CHANGED
@@ -3,7 +3,6 @@ import { Modal, Pressable, StyleSheet, View } from "react-native";
3
3
  import { useScreenSize } from "@lotics/ui/use_screen_size";
4
4
  import { PortalHost } from "@lotics/ui/portal";
5
5
  import { colors } from "@lotics/ui/colors";
6
- import { Button } from "@lotics/ui/button";
7
6
  import { IconButton } from "@lotics/ui/icon_button";
8
7
  import { Text } from "@lotics/ui/text";
9
8
  import { useOverlayScope } from "@lotics/ui/overlay_scope";
@@ -112,7 +111,7 @@ export function Drawer(props: DrawerProps) {
112
111
  />
113
112
  </View>
114
113
  ) : null}
115
- <Button icon="x" accessibilityLabel="Close" onPress={handleClose} />
114
+ <IconButton icon="x" size="lg" accessibilityLabel="Close" onPress={handleClose} />
116
115
  </View>
117
116
  <View testID={testID} style={styles.body}>
118
117
  {children}