@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
@@ -163,8 +163,8 @@ export function GanttView<T = unknown>(props: GanttViewProps<T>) {
163
163
  const styles = StyleSheet.create({
164
164
  root: { flex: 1, backgroundColor: colors.white },
165
165
  toolbar: { flexDirection: "row", alignItems: "center", justifyContent: "space-between", paddingHorizontal: 14, paddingVertical: 10, borderBottomWidth: 1, borderBottomColor: colors.border },
166
- zoomSwitch: { flexDirection: "row", backgroundColor: colors.zinc[100], borderRadius: 8, padding: 2 },
167
- zoomBtn: { paddingHorizontal: 12, paddingVertical: 5, borderRadius: 6 },
166
+ zoomSwitch: { flexDirection: "row", backgroundColor: colors.zinc[100], borderRadius: 10, padding: 2 },
167
+ zoomBtn: { paddingHorizontal: 12, paddingVertical: 5, borderRadius: 8 },
168
168
  zoomBtnActive: { backgroundColor: colors.white },
169
169
  headerCell: { borderBottomWidth: 1, borderBottomColor: colors.border },
170
170
  labelRow: { flexDirection: "row", alignItems: "center", gap: 8, paddingHorizontal: 12, borderBottomWidth: 1, borderBottomColor: colors.zinc[100] },
@@ -1,7 +1,9 @@
1
- import { GestureResponderEvent, StyleProp, StyleSheet, View, ViewStyle } from "react-native";
1
+ import { ActivityIndicator, GestureResponderEvent, StyleProp, StyleSheet, View, ViewStyle } from "react-native";
2
2
  import { Icon, IconName } from "./icon";
3
3
  import { colors } from "./colors";
4
4
  import { PressableHighlight } from "./pressable_highlight";
5
+ import { type ButtonColor } from "./button";
6
+ import { getButtonIconColor } from "./button_colors";
5
7
  import { Ref, useCallback } from "react";
6
8
  import { TooltipSide } from "./tooltip";
7
9
 
@@ -9,16 +11,27 @@ interface IconButtonBase {
9
11
  ref?: Ref<View>;
10
12
  testID?: string;
11
13
  icon: IconName;
12
- color?: "none" | "primary" | "secondary" | "white";
13
- /** `md` (28px, 18px glyph) for toolbars; `sm` (24px, 14px glyph) for
14
- * inline affordances sitting next to body/sm text — e.g. the ⓘ in a
15
- * CardHeader. Both keep a 40px touch target via hitSlop. */
16
- size?: "md" | "sm";
14
+ /** Matches `Button`'s palette so an icon-only action migrates 1:1 — a filled
15
+ * disc for primary/secondary/danger, a ghost for muted/danger-secondary/none. */
16
+ color?: ButtonColor | "none" | "white";
17
+ /** `lg` (40px, 20px glyph) for a circular primary action at the system control
18
+ * height — a composer send, a drawer/popover back or close; `md` (28px, 18px
19
+ * glyph) for toolbars; `sm` (24px, 14px glyph) for inline affordances next to
20
+ * body/sm text (e.g. the ⓘ in a CardHeader). md/sm keep a 40px touch target via
21
+ * hitSlop; `lg` is already 40. */
22
+ size?: "lg" | "md" | "sm";
17
23
  iconColor?: string;
24
+ /** Floating variant: a white fill + hairline border + soft shadow, so the
25
+ * button reads as a distinct, tappable target sitting ON TOP of imagery or a
26
+ * colored surface (a tile's remove ✕, an overlay's retry). */
27
+ elevated?: boolean;
18
28
  tooltipSide?: TooltipSide;
19
29
  onPress?: (event: GestureResponderEvent) => void;
20
30
  style?: StyleProp<ViewStyle>;
21
31
  disabled?: boolean;
32
+ /** Show a spinner in place of the icon (and block press) — for an icon action
33
+ * that triggers async work, e.g. a delete. */
34
+ loading?: boolean;
22
35
  }
23
36
 
24
37
  /**
@@ -39,10 +52,12 @@ export function IconButton(props: IconButtonProps) {
39
52
  onPress,
40
53
  color = "none",
41
54
  size = "md",
55
+ elevated,
42
56
  tooltip,
43
57
  tooltipSide,
44
58
  accessibilityLabel,
45
59
  disabled,
60
+ loading,
46
61
  style,
47
62
  } = props;
48
63
 
@@ -53,6 +68,11 @@ export function IconButton(props: IconButtonProps) {
53
68
  [onPress],
54
69
  );
55
70
 
71
+ const disabledOrLoading = disabled || loading;
72
+ const tint =
73
+ iconColor ??
74
+ (color === "none" ? colors.zinc[700] : color === "white" ? colors.white : getButtonIconColor(color));
75
+
56
76
  return (
57
77
  <PressableHighlight
58
78
  ref={ref}
@@ -61,18 +81,18 @@ export function IconButton(props: IconButtonProps) {
61
81
  tooltipSide={tooltipSide}
62
82
  accessibilityRole="button"
63
83
  accessibilityLabel={accessibilityLabel ?? tooltip}
64
- style={[styles.button, styles[size], styles[color], disabled && styles.disabled, style]}
84
+ style={[styles.button, styles[size], styles[color], elevated && styles.elevated, disabledOrLoading && styles.disabled, style]}
65
85
  onPress={handlePress}
66
- disabled={disabled}
86
+ disabled={disabledOrLoading}
67
87
  // 40px touch target regardless of visual size (28 + 2×6 / 24 + 2×8)
68
88
  // without shifting surrounding layouts.
69
- hitSlop={size === "sm" ? 8 : 6}
89
+ hitSlop={size === "lg" ? 0 : size === "sm" ? 8 : 6}
70
90
  >
71
- <Icon
72
- size={size === "sm" ? 14 : 18}
73
- name={icon}
74
- color={iconColor || (color === "white" || color === "primary" ? colors.white : colors.zinc[700])}
75
- />
91
+ {loading ? (
92
+ <ActivityIndicator size="small" color={tint} />
93
+ ) : (
94
+ <Icon size={size === "sm" ? 14 : size === "lg" ? 20 : 18} name={icon} color={tint} />
95
+ )}
76
96
  </PressableHighlight>
77
97
  );
78
98
  }
@@ -85,6 +105,10 @@ const styles = StyleSheet.create({
85
105
  justifyContent: "center",
86
106
  borderRadius: 999,
87
107
  },
108
+ lg: {
109
+ width: 40,
110
+ height: 40,
111
+ },
88
112
  md: {
89
113
  width: 28,
90
114
  height: 28,
@@ -103,5 +127,16 @@ const styles = StyleSheet.create({
103
127
  secondary: {
104
128
  backgroundColor: colors.zinc[100],
105
129
  },
130
+ danger: {
131
+ backgroundColor: colors.red[100],
132
+ },
133
+ muted: {},
134
+ "danger-secondary": {},
106
135
  white: {},
136
+ elevated: {
137
+ backgroundColor: colors.white,
138
+ borderWidth: 1,
139
+ borderColor: colors.zinc[200],
140
+ ...({ boxShadow: "0 1px 3px rgba(0, 0, 0, 0.18)" } as object),
141
+ },
107
142
  });
@@ -36,8 +36,6 @@ export interface ImageGalleryProps {
36
36
  emptyText?: string;
37
37
  /** Optional max width. Omit to fill the container. */
38
38
  maxWidth?: number;
39
- /** Hint shown in the full-screen zoom modal. Pass a translated string. */
40
- captionHint?: string;
41
39
  /** "bottom" stacks thumbnails under the main image; "right" puts the main
42
40
  * image (~60% of the width) and the thumbnail grid side by side. */
43
41
  thumbnailPosition?: "bottom" | "right";
@@ -55,7 +53,6 @@ export function ImageGallery({
55
53
  loading = false,
56
54
  emptyText = "No images.",
57
55
  maxWidth,
58
- captionHint = "ESC to close · ←/→ to navigate",
59
56
  thumbnailPosition = "bottom",
60
57
  mainAspectRatio = 4 / 3,
61
58
  mainFraction = 0.6,
@@ -157,7 +154,6 @@ export function ImageGallery({
157
154
  files={images}
158
155
  activeIndex={zoomIdx}
159
156
  onIndexChange={setZoomIdx}
160
- captionHint={captionHint}
161
157
  rotation={rotation}
162
158
  rotatable={rotatable}
163
159
  />
@@ -5,7 +5,7 @@ import { IconButton } from "./icon_button";
5
5
  import { ActivityIndicator } from "./activity_indicator";
6
6
  import { PressableHighlight } from "./pressable_highlight";
7
7
  import { colors } from "./colors";
8
- import { ACTIVE_RING } from "./control_surface";
8
+ import { ACTIVE_RING, CONTROL_RADIUS } from "./control_surface";
9
9
  import { fontFamilyRegular, getInputTextStyle } from "./text_utils";
10
10
 
11
11
  /** The kit's standard control height (TextInputField, NumberInput, Picker, …).
@@ -221,7 +221,7 @@ const viewTextStyle = [getInputTextStyle(), { flex: 1, fontFamily: fontFamilyReg
221
221
  const styles = StyleSheet.create({
222
222
  view: {
223
223
  minHeight: INLINE_CONTROL_HEIGHT,
224
- borderRadius: 8,
224
+ borderRadius: CONTROL_RADIUS,
225
225
  borderWidth: 1,
226
226
  borderColor: "transparent",
227
227
  paddingHorizontal: 8,
@@ -1,6 +1,7 @@
1
1
  import { StyleSheet } from "react-native";
2
2
  import { Text } from "./text";
3
3
  import { PressableHighlight } from "./pressable_highlight";
4
+ import { CONTROL_RADIUS } from "./control_surface";
4
5
 
5
6
  export interface LinkButtonProps {
6
7
  title: string;
@@ -44,7 +45,7 @@ const styles = StyleSheet.create({
44
45
  paddingVertical: 6,
45
46
  paddingHorizontal: 8,
46
47
  marginHorizontal: -8,
47
- borderRadius: 8,
48
+ borderRadius: CONTROL_RADIUS,
48
49
  alignSelf: "flex-start",
49
50
  },
50
51
  });
@@ -2,6 +2,7 @@ import { Ref } from "react";
2
2
  import { StyleProp, StyleSheet, View, ViewStyle } 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, IconName } from "./icon";
6
7
  import { PressableHighlight } from "./pressable_highlight";
7
8
  export interface MenuButtonProps {
@@ -126,7 +127,7 @@ const styles = StyleSheet.create({
126
127
  gap: 8,
127
128
  height: 40,
128
129
  paddingHorizontal: 8,
129
- borderRadius: 8,
130
+ borderRadius: CONTROL_RADIUS,
130
131
  },
131
132
  textContainer: {
132
133
  flex: 1,
@@ -2,6 +2,7 @@ import { Ref } from "react";
2
2
  import { StyleProp, StyleSheet, View, ViewStyle } 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, IconName } from "./icon";
6
7
  import { PressableHighlight } from "./pressable_highlight";
7
8
  import type { TextColor } from "./text_utils";
@@ -111,7 +112,7 @@ const styles = StyleSheet.create({
111
112
  gap: 8,
112
113
  paddingHorizontal: 8,
113
114
  paddingVertical: 8,
114
- borderRadius: 8,
115
+ borderRadius: CONTROL_RADIUS,
115
116
  },
116
117
  textContainer: {
117
118
  flex: 1,
@@ -1,5 +1,6 @@
1
1
  import type { KeyboardEvent } from "react";
2
2
  import { colors } from "./colors";
3
+ import { CONTROL_RADIUS } from "./control_surface";
3
4
  import { fontFamilyRegular, inputTextStyleWeb } from "./text_utils";
4
5
  import { useFormField } from "./form_field";
5
6
 
@@ -50,7 +51,7 @@ export function NumberInput(props: NumberInputProps) {
50
51
  height: 40,
51
52
  paddingLeft: 8,
52
53
  paddingRight: 8,
53
- borderRadius: 8,
54
+ borderRadius: CONTROL_RADIUS,
54
55
  borderWidth: 1,
55
56
  borderStyle: "solid",
56
57
  borderColor: colors.border,
@@ -0,0 +1,5 @@
1
+ // The pdf.js worker entry is imported only for its side effect: it assigns
2
+ // `globalThis.pdfjsWorker`, which lets pdf.js run on the main thread (no Web
3
+ // Worker, no worker-URL resolution) — portable across Vite (apps) and Metro
4
+ // (frontend). The file ships no types; declare it as a side-effect module.
5
+ declare module "pdfjs-dist/build/pdf.worker.min.mjs";
package/src/picker.tsx CHANGED
@@ -2,7 +2,7 @@ import { Picker as RNPicker } from "@react-native-picker/picker";
2
2
  import { StyleSheet, View, Pressable, StyleProp, ViewStyle, TextStyle } from "react-native";
3
3
  import { useState, useCallback, useMemo, useEffect, useRef } from "react";
4
4
  import { colors } from "./colors";
5
- import { ACTIVE_RING } from "./control_surface";
5
+ import { ACTIVE_RING, CONTROL_RADIUS } from "./control_surface";
6
6
  import { Text } from "./text";
7
7
  import { fontFamilyRegular, getInputTextStyle } from "./text_utils";
8
8
  import { Icon } from "./icon";
@@ -310,7 +310,7 @@ const styles = StyleSheet.create({
310
310
  height: 40,
311
311
  paddingHorizontal: 6,
312
312
  paddingRight: 28,
313
- borderRadius: 8,
313
+ borderRadius: CONTROL_RADIUS,
314
314
  borderWidth: 1,
315
315
  borderColor: colors.border,
316
316
  fontFamily: fontFamilyRegular,
@@ -324,7 +324,7 @@ const styles = StyleSheet.create({
324
324
  paddingHorizontal: 6,
325
325
  borderWidth: 1,
326
326
  borderColor: colors.border,
327
- borderRadius: 8,
327
+ borderRadius: CONTROL_RADIUS,
328
328
  cursor: "auto",
329
329
  height: 40,
330
330
  },
@@ -1,6 +1,6 @@
1
1
  import { ReactNode } from "react";
2
2
  import { View, StyleSheet } from "react-native";
3
- import { Button } from "./button";
3
+ import { IconButton } from "./icon_button";
4
4
  import { Text } from "./text";
5
5
  import { usePopoverNav } from "./popover_nav";
6
6
 
@@ -18,7 +18,7 @@ export function PopoverHeader(props: PopoverHeaderProps) {
18
18
  return (
19
19
  <View style={styles.container}>
20
20
  {canGoBack && (
21
- <Button icon="chevron-left" color="secondary" accessibilityLabel={backLabel} onPress={goBack} />
21
+ <IconButton icon="chevron-left" size="lg" color="secondary" accessibilityLabel={backLabel} onPress={goBack} />
22
22
  )}
23
23
  <Text size="sm" weight="medium" style={{ flex: 1 }}>
24
24
  {title}
@@ -1,6 +1,7 @@
1
1
  import { useCallback, useRef } from "react";
2
2
  import { View } 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 { PressableHighlight } from "./pressable_highlight";
6
7
  import { Icon } from "./icon";
@@ -120,7 +121,7 @@ function RadioOption<T extends string | number | symbol>(
120
121
  flexDirection: "row",
121
122
  alignItems: "center",
122
123
  padding: compact ? 8 : 12,
123
- borderRadius: 8,
124
+ borderRadius: CONTROL_RADIUS,
124
125
  gap: compact ? 8 : 16,
125
126
  }}
126
127
  onPress={handlePress}
@@ -24,7 +24,7 @@ export interface RangeSliderProps {
24
24
  type Thumb = "low" | "high";
25
25
 
26
26
  /**
27
- * A short human summary of a range selection for a `FilterPill` preview —
27
+ * A short human summary of a range selection for a `FilterChip` preview —
28
28
  * "5tr–10tr", or "≥ 5tr" / "≤ 10tr" when one end sits at its bound, and
29
29
  * `undefined` at the full range (no active filter). Mirrors `columnFilterSummary`.
30
30
  */
@@ -79,7 +79,7 @@ export function ReviewCard(props: ReviewCardProps) {
79
79
  <Text size="sm" weight="medium" color="muted" numberOfLines={1}>
80
80
  {accepted ? props.acceptedLabel ?? "Accepted" : props.dismissedLabel ?? "Dismissed"}
81
81
  </Text>
82
- {props.onUndo ? <Button title="Undo" color="muted" shape="rounded" onPress={props.onUndo} /> : null}
82
+ {props.onUndo ? <Button title="Undo" color="muted" onPress={props.onUndo} /> : null}
83
83
  </View>
84
84
  );
85
85
  }
@@ -132,7 +132,7 @@ export function ReviewCard(props: ReviewCardProps) {
132
132
  <View style={styles.footer}>
133
133
  {actions.map((a, i) => {
134
134
  const kind = a.kind ?? (i === actions.length - 1 ? "primary" : "muted");
135
- return <Button key={a.label} title={a.label} color={kind} shape="rounded" onPress={a.onPress} />;
135
+ return <Button key={a.label} title={a.label} color={kind} onPress={a.onPress} />;
136
136
  })}
137
137
  </View>
138
138
  ) : null}
@@ -1,5 +1,6 @@
1
1
  import { StyleSheet, View } from "react-native";
2
2
  import { colors, solid } from "./colors";
3
+ import { CONTROL_RADIUS } from "./control_surface";
3
4
  import { Text } from "./text";
4
5
  import { TextInputField } from "./text_input_field";
5
6
 
@@ -52,7 +53,7 @@ export function ScanField(props: ScanFieldProps) {
52
53
 
53
54
  const styles = StyleSheet.create({
54
55
  field: {
55
- borderRadius: 10,
56
+ borderRadius: CONTROL_RADIUS,
56
57
  backgroundColor: colors.white,
57
58
  },
58
59
  });
@@ -94,7 +94,6 @@ export function ScoredOption(props: ScoredOptionProps) {
94
94
  title={selected ? "Selected" : selectLabel ?? "Choose"}
95
95
  icon={selected ? "check" : undefined}
96
96
  color={selected ? "secondary" : "primary"}
97
- shape="rounded"
98
97
  onPress={onSelect}
99
98
  />
100
99
  </View>
@@ -9,6 +9,7 @@ import {
9
9
  type ViewStyle,
10
10
  } from "react-native";
11
11
  import { colors } from "./colors";
12
+ import { CONTROL_RADIUS } from "./control_surface";
12
13
  import { TextInputField } from "./text_input_field";
13
14
 
14
15
  type SearchInputProps = Omit<
@@ -57,7 +58,7 @@ export function SearchInput(props: SearchInputProps) {
57
58
 
58
59
  const styles = StyleSheet.create({
59
60
  // Search reads as a view-control by its leading search glyph + white fill —
60
- // NOT by a distinct shape. It shares the rounded-8 radius of every other
61
+ // NOT by a distinct shape. It shares the kit-wide `CONTROL_RADIUS` of every other
61
62
  // input/picker (TextInputField, Combobox, the date filter), so a toolbar of
62
63
  // controls reads as one consistent band. The white fill still pops against
63
64
  // the zinc-50 canvas where most toolbars live; a zinc-300 border (one step up
@@ -65,7 +66,7 @@ const styles = StyleSheet.create({
65
66
  // the fill alone would vanish. 40px (TextInputField default) — the system
66
67
  // control height every band aligns to.
67
68
  search: {
68
- borderRadius: 8,
69
+ borderRadius: CONTROL_RADIUS,
69
70
  backgroundColor: colors.white,
70
71
  // A thin 1px resting border (TextInputField's default width) — interaction
71
72
  // emphasis comes from the input's own focus outline, so it stays quiet at
@@ -3,6 +3,7 @@ import { StyleSheet, View, type StyleProp, type ViewStyle } from "react-native";
3
3
  import { PressableHighlight } from "./pressable_highlight";
4
4
  import { Text } from "./text";
5
5
  import { colors, withAlpha } from "./colors";
6
+ import { CONTROL_RADIUS } from "./control_surface";
6
7
 
7
8
  export interface SegmentOption<T extends string> {
8
9
  label: string;
@@ -183,7 +184,7 @@ const styles = StyleSheet.create({
183
184
  height: 40,
184
185
  gap: 2,
185
186
  padding: 3,
186
- borderRadius: 10,
187
+ borderRadius: CONTROL_RADIUS,
187
188
  backgroundColor: colors.zinc["100"],
188
189
  },
189
190
  trackDisabled: {
@@ -2,6 +2,7 @@ import { useEffect, useRef } from "react";
2
2
  import { View, StyleSheet, Animated, Easing } from "react-native";
3
3
  import { Text } from "./text";
4
4
  import { colors } from "./colors";
5
+ import { CONTROL_RADIUS } from "./control_surface";
5
6
 
6
7
  interface StatusBadgeProps {
7
8
  enabled: boolean;
@@ -69,7 +70,7 @@ const styles = StyleSheet.create({
69
70
  gap: 8,
70
71
  paddingVertical: 4,
71
72
  paddingHorizontal: 8,
72
- borderRadius: 8,
73
+ borderRadius: CONTROL_RADIUS,
73
74
  },
74
75
  dotWrapper: {
75
76
  width: 10,
package/src/stepper.tsx CHANGED
@@ -155,8 +155,9 @@ export function Step(props: StepProps) {
155
155
 
156
156
  // The node — status drives fill, never size. Monochrome: reached fills with ink,
157
157
  // the current step is a ring around white (pulsing when live), what's ahead is a
158
- // faint grey ring, the terminal is a checked fill.
159
- function Marker({ status, color, live }: { status: StepStatus; color: string; live: boolean }) {
158
+ // faint grey ring, the terminal is a checked fill. Exported so `AgentRun` reuses
159
+ // the exact same dot in its flat activity rows.
160
+ export function Marker({ status, color, live }: { status: StepStatus; color: string; live: boolean }) {
160
161
  if (status === "done") {
161
162
  // A passed step: filled in the accent ink with a white check.
162
163
  return (
@@ -3,6 +3,7 @@ import { Text } from "./text";
3
3
  import { Switch, type SwitchProps } from "./switch";
4
4
  import { PressableHighlight } from "./pressable_highlight";
5
5
  import { Icon, type IconName } from "./icon";
6
+ import { CONTROL_RADIUS } from "./control_surface";
6
7
 
7
8
  export interface SwitchButtonProps extends SwitchProps {
8
9
  icon?: IconName;
@@ -22,7 +23,7 @@ export function SwitchButton(props: SwitchButtonProps) {
22
23
  paddingVertical: 4,
23
24
  paddingLeft: 8,
24
25
  paddingRight: 6,
25
- borderRadius: 8,
26
+ borderRadius: CONTROL_RADIUS,
26
27
  }}
27
28
  onPress={() => onChange?.(!value)}
28
29
  tooltip={tooltip}
package/src/switcher.tsx CHANGED
@@ -7,6 +7,7 @@ import { Popover, PopoverContent, PopoverTrigger } from "./popover";
7
7
  import { Stack } from "./stack";
8
8
  import { Text } from "./text";
9
9
  import { colors } from "./colors";
10
+ import { CONTROL_HEIGHT, CONTROL_RADIUS } from "./control_surface";
10
11
 
11
12
  export interface SwitcherItem {
12
13
  id: string;
@@ -100,9 +101,9 @@ const styles = StyleSheet.create({
100
101
  flexDirection: "row",
101
102
  alignItems: "center",
102
103
  gap: 6,
104
+ height: CONTROL_HEIGHT,
103
105
  paddingHorizontal: 10,
104
- paddingVertical: 6,
105
- borderRadius: 8,
106
+ borderRadius: CONTROL_RADIUS,
106
107
  },
107
108
  popoverBody: {
108
109
  minWidth: 220,
package/src/tabs.tsx CHANGED
@@ -3,6 +3,7 @@ import { View } from "react-native";
3
3
  import { PressableHighlight } from "./pressable_highlight";
4
4
  import { Text } from "./text";
5
5
  import { colors } from "./colors";
6
+ import { CONTROL_RADIUS } from "./control_surface";
6
7
 
7
8
  export interface TabOption<T extends string> {
8
9
  label: string;
@@ -138,7 +139,7 @@ function TabButton<T extends string>(props: TabButtonProps<T>) {
138
139
  style={{
139
140
  paddingVertical: 8,
140
141
  paddingHorizontal: 16,
141
- borderRadius: 8,
142
+ borderRadius: CONTROL_RADIUS,
142
143
  backgroundColor: pressed
143
144
  ? colors.zinc["100"]
144
145
  : hovered
package/src/tag_input.tsx CHANGED
@@ -1,6 +1,7 @@
1
1
  import { useMemo, useRef, useState } from "react";
2
2
  import { Pressable, ScrollView, StyleSheet, View, type StyleProp, type 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 { Icon } from "./icon";
6
7
  import { TextInputField } from "./text_input_field";
@@ -161,7 +162,7 @@ const styles = StyleSheet.create({
161
162
  paddingVertical: 5,
162
163
  borderWidth: 1,
163
164
  borderColor: colors.border,
164
- borderRadius: 8,
165
+ borderRadius: CONTROL_RADIUS,
165
166
  backgroundColor: colors.background,
166
167
  },
167
168
  chip: {
@@ -5,6 +5,7 @@ import {
5
5
  View,
6
6
  } from "react-native";
7
7
  import { colors } from "./colors";
8
+ import { CONTROL_RADIUS } from "./control_surface";
8
9
  import { Ref, useCallback } from "react";
9
10
  import { Icon, IconName } from "./icon";
10
11
  import { IconButton } from "./icon_button";
@@ -163,7 +164,7 @@ export function TextInputField(props: TextInputFieldProps) {
163
164
 
164
165
  const styles = StyleSheet.create({
165
166
  input: {
166
- borderRadius: 8,
167
+ borderRadius: CONTROL_RADIUS,
167
168
  borderWidth: 1,
168
169
  borderColor: colors.border,
169
170
  height: 40,
@@ -1,6 +1,7 @@
1
1
  import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
2
  import { Pressable, ScrollView, StyleSheet, View } 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 { Icon } from "./icon";
6
7
  import { Popover, PopoverContent } from "./popover";
@@ -266,7 +267,7 @@ const styles = StyleSheet.create({
266
267
  height: 40,
267
268
  paddingHorizontal: 8,
268
269
  gap: 2,
269
- borderRadius: 8,
270
+ borderRadius: CONTROL_RADIUS,
270
271
  borderWidth: 1,
271
272
  borderColor: colors.border,
272
273
  backgroundColor: colors.background,
@@ -1,6 +1,7 @@
1
1
  import type { KeyboardEvent } from "react";
2
2
  import { colors } from "@lotics/ui/colors";
3
3
  import { fontFamilyRegular, inputTextStyleWeb } from "@lotics/ui/text_utils";
4
+ import { CONTROL_RADIUS } from "./control_surface";
4
5
  export interface TimePickerProps {
5
6
  value?: string;
6
7
  onValueChange: (value: string) => void;
@@ -31,7 +32,7 @@ export function TimePicker(props: TimePickerProps) {
31
32
  height: 40,
32
33
  paddingLeft: 8,
33
34
  paddingRight: 8,
34
- borderRadius: 8,
35
+ borderRadius: CONTROL_RADIUS,
35
36
  borderWidth: 1,
36
37
  borderStyle: "solid",
37
38
  borderColor: colors.border,
@@ -74,9 +74,9 @@ export function TriageRow(props: TriageRowProps) {
74
74
  </Text>
75
75
  ) : (
76
76
  <View style={styles.footer}>
77
- {props.onOverride ? <Button title="Reclassify" color="muted" shape="rounded" onPress={props.onOverride} /> : null}
78
- {props.onDismiss ? <Button title="Dismiss" color="muted" shape="rounded" onPress={props.onDismiss} /> : null}
79
- {props.onAccept ? <Button title="Accept" color="primary" shape="rounded" onPress={props.onAccept} /> : null}
77
+ {props.onOverride ? <Button title="Reclassify" color="muted" onPress={props.onOverride} /> : null}
78
+ {props.onDismiss ? <Button title="Dismiss" color="muted" onPress={props.onDismiss} /> : null}
79
+ {props.onAccept ? <Button title="Accept" color="primary" onPress={props.onAccept} /> : null}
80
80
  </View>
81
81
  )}
82
82
  </View>