@lotics/ui 5.6.1 → 5.8.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 +44 -27
- package/examples/tpl_approvals.tsx +10 -10
- package/examples/tpl_crm_desk.tsx +3 -3
- package/examples/tpl_dieline.tsx +7 -6
- package/examples/tpl_directory.tsx +3 -3
- package/examples/tpl_dossier.tsx +7 -7
- package/examples/tpl_draft.tsx +4 -4
- package/examples/tpl_extract.tsx +7 -7
- package/examples/tpl_inventory.tsx +3 -3
- package/examples/tpl_lookup.tsx +4 -3
- package/examples/tpl_match.tsx +1 -1
- package/examples/tpl_ratedesk.tsx +11 -11
- package/examples/tpl_report.tsx +3 -3
- package/examples/tpl_stock.tsx +3 -3
- package/examples/tpl_triage.tsx +1 -1
- package/package.json +12 -3
- package/src/agent_progress.tsx +14 -12
- package/src/agent_run.tsx +248 -86
- package/src/back_button.tsx +3 -2
- package/src/badge.tsx +2 -1
- package/src/button.tsx +11 -32
- package/src/button_colors.ts +23 -0
- package/src/calendar/calendar_view.tsx +2 -2
- package/src/card_select_item.tsx +2 -1
- package/src/change_review.tsx +5 -6
- package/src/{pill_button.tsx → chip.tsx} +7 -7
- package/src/chip_group.tsx +2 -2
- package/src/column_filter.tsx +4 -4
- package/src/combobox.tsx +2 -9
- package/src/composer.tsx +4 -3
- package/src/control_surface.ts +15 -15
- package/src/date_field.tsx +2 -1
- package/src/date_filter.tsx +2 -1
- package/src/date_range_filter_field.tsx +2 -1
- package/src/dialog.tsx +2 -2
- package/src/discrepancy.tsx +1 -1
- package/src/drawer.tsx +1 -2
- package/src/{filter_pill.tsx → filter_chip.tsx} +8 -8
- package/src/finding.tsx +1 -1
- package/src/gantt/gantt_view.tsx +2 -2
- package/src/icon_button.tsx +38 -14
- package/src/inline_edit.tsx +2 -2
- package/src/link_button.tsx +2 -1
- package/src/markdown.css +209 -0
- package/src/markdown.tsx +17 -0
- package/src/markdown.web.tsx +69 -0
- package/src/menu_button.tsx +2 -1
- package/src/menu_list_item.tsx +2 -1
- package/src/number_input.tsx +2 -1
- package/src/picker.tsx +3 -3
- package/src/popover_header.tsx +2 -2
- package/src/primitives_purity.test.ts +13 -7
- package/src/radio_picker.tsx +2 -1
- package/src/range_slider.tsx +1 -1
- package/src/review_card.tsx +2 -2
- package/src/scan_field.tsx +2 -1
- package/src/scored_option.tsx +0 -1
- package/src/search_input.tsx +3 -2
- package/src/segmented_control.tsx +2 -1
- package/src/status_badge.tsx +2 -1
- package/src/stepper.tsx +3 -2
- package/src/switch_button.tsx +2 -1
- package/src/switcher.tsx +3 -2
- package/src/tabs.tsx +2 -1
- package/src/tag_input.tsx +2 -1
- package/src/text_input_field.tsx +2 -1
- package/src/time_field.tsx +2 -1
- package/src/time_picker.tsx +2 -1
- package/src/triage_row.tsx +3 -3
package/src/link_button.tsx
CHANGED
|
@@ -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:
|
|
48
|
+
borderRadius: CONTROL_RADIUS,
|
|
48
49
|
alignSelf: "flex-start",
|
|
49
50
|
},
|
|
50
51
|
});
|
package/src/markdown.css
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
.ui-markdown {
|
|
2
|
+
font-family: Inter_400Regular, "apple-system", "BlinkMacSystemFont", "Segoe UI",
|
|
3
|
+
"Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
|
|
4
|
+
"Helvetica Neue", sans-serif;
|
|
5
|
+
font-size: 14px;
|
|
6
|
+
line-height: 24px;
|
|
7
|
+
letter-spacing: -0.4px;
|
|
8
|
+
color: rgba(24, 24, 27, 1);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/* Headings */
|
|
12
|
+
|
|
13
|
+
.ui-markdown h1 {
|
|
14
|
+
font-family: Inter_600SemiBold, "apple-system", "BlinkMacSystemFont", "Segoe UI",
|
|
15
|
+
"Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
|
|
16
|
+
"Helvetica Neue", sans-serif;
|
|
17
|
+
font-size: 24px;
|
|
18
|
+
line-height: 30px;
|
|
19
|
+
font-weight: 600;
|
|
20
|
+
padding-top: 12px;
|
|
21
|
+
padding-bottom: 4px;
|
|
22
|
+
margin: 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.ui-markdown h2 {
|
|
26
|
+
font-family: Inter_600SemiBold, "apple-system", "BlinkMacSystemFont", "Segoe UI",
|
|
27
|
+
"Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
|
|
28
|
+
"Helvetica Neue", sans-serif;
|
|
29
|
+
font-size: 18px;
|
|
30
|
+
line-height: 24px;
|
|
31
|
+
font-weight: 600;
|
|
32
|
+
padding-top: 12px;
|
|
33
|
+
padding-bottom: 4px;
|
|
34
|
+
margin: 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.ui-markdown h3 {
|
|
38
|
+
font-family: Inter_600SemiBold, "apple-system", "BlinkMacSystemFont", "Segoe UI",
|
|
39
|
+
"Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
|
|
40
|
+
"Helvetica Neue", sans-serif;
|
|
41
|
+
font-size: 16px;
|
|
42
|
+
line-height: 22px;
|
|
43
|
+
font-weight: 600;
|
|
44
|
+
padding-top: 8px;
|
|
45
|
+
padding-bottom: 4px;
|
|
46
|
+
margin: 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.ui-markdown h4,
|
|
50
|
+
.ui-markdown h5,
|
|
51
|
+
.ui-markdown h6 {
|
|
52
|
+
font-family: Inter_500Medium, "apple-system", "BlinkMacSystemFont", "Segoe UI",
|
|
53
|
+
"Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
|
|
54
|
+
"Helvetica Neue", sans-serif;
|
|
55
|
+
font-size: 14px;
|
|
56
|
+
line-height: 20px;
|
|
57
|
+
font-weight: 500;
|
|
58
|
+
padding-top: 8px;
|
|
59
|
+
padding-bottom: 4px;
|
|
60
|
+
margin: 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* Block elements */
|
|
64
|
+
|
|
65
|
+
.ui-markdown p {
|
|
66
|
+
margin: 0;
|
|
67
|
+
padding: 4px 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.ui-markdown blockquote {
|
|
71
|
+
border-left: 3px solid rgba(228, 228, 231, 1);
|
|
72
|
+
margin: 4px 0;
|
|
73
|
+
padding-left: 12px;
|
|
74
|
+
color: rgba(113, 113, 122, 1);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.ui-markdown hr {
|
|
78
|
+
border: none;
|
|
79
|
+
border-top: 1px solid rgba(228, 228, 231, 1);
|
|
80
|
+
margin: 8px 0;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* Lists */
|
|
84
|
+
|
|
85
|
+
.ui-markdown ul,
|
|
86
|
+
.ui-markdown ol {
|
|
87
|
+
padding-left: 24px;
|
|
88
|
+
margin: 4px 0;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.ui-markdown li {
|
|
92
|
+
padding-top: 2px;
|
|
93
|
+
padding-bottom: 2px;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* Inline */
|
|
97
|
+
|
|
98
|
+
.ui-markdown strong {
|
|
99
|
+
font-family: Inter_500Medium, "apple-system", "BlinkMacSystemFont", "Segoe UI",
|
|
100
|
+
"Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
|
|
101
|
+
"Helvetica Neue", sans-serif;
|
|
102
|
+
font-weight: 500;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.ui-markdown a {
|
|
106
|
+
color: rgba(59, 130, 246, 1);
|
|
107
|
+
text-decoration: underline;
|
|
108
|
+
cursor: pointer;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.ui-markdown del {
|
|
112
|
+
color: rgba(113, 113, 122, 1);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/* Code */
|
|
116
|
+
|
|
117
|
+
.ui-markdown code {
|
|
118
|
+
padding: 1px 5px;
|
|
119
|
+
color: rgba(127, 29, 29, 1);
|
|
120
|
+
background-color: rgba(244, 244, 245, 1);
|
|
121
|
+
border-radius: 4px;
|
|
122
|
+
font-size: 13px;
|
|
123
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.ui-markdown pre {
|
|
127
|
+
background-color: rgba(244, 244, 245, 1);
|
|
128
|
+
padding: 12px;
|
|
129
|
+
border-radius: 8px;
|
|
130
|
+
margin: 4px 0;
|
|
131
|
+
overflow-x: auto;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.ui-markdown pre code {
|
|
135
|
+
padding: 0;
|
|
136
|
+
color: inherit;
|
|
137
|
+
background-color: transparent;
|
|
138
|
+
border-radius: 0;
|
|
139
|
+
font-size: 13px;
|
|
140
|
+
line-height: 20px;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/* Tables */
|
|
144
|
+
|
|
145
|
+
.ui-markdown table {
|
|
146
|
+
border-collapse: collapse;
|
|
147
|
+
margin: 4px 0;
|
|
148
|
+
width: max-content;
|
|
149
|
+
min-width: 100%;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.ui-markdown th,
|
|
153
|
+
.ui-markdown td {
|
|
154
|
+
border: 1px solid rgba(228, 228, 231, 1);
|
|
155
|
+
padding: 6px 12px;
|
|
156
|
+
text-align: left;
|
|
157
|
+
white-space: nowrap;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.ui-markdown th {
|
|
161
|
+
background-color: rgba(244, 244, 245, 1);
|
|
162
|
+
font-family: Inter_500Medium, "apple-system", "BlinkMacSystemFont", "Segoe UI",
|
|
163
|
+
"Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
|
|
164
|
+
"Helvetica Neue", sans-serif;
|
|
165
|
+
font-weight: 500;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/* Table copy button */
|
|
169
|
+
|
|
170
|
+
.ui-markdown-table-wrapper {
|
|
171
|
+
position: relative;
|
|
172
|
+
margin: 16px 0;
|
|
173
|
+
max-width: 100%;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.ui-markdown-table-scroll {
|
|
177
|
+
overflow-x: auto;
|
|
178
|
+
max-width: 100%;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.ui-markdown-table-wrapper table {
|
|
182
|
+
margin: 0;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.ui-markdown-table-copy {
|
|
186
|
+
position: absolute;
|
|
187
|
+
top: 1px;
|
|
188
|
+
right: 1px;
|
|
189
|
+
opacity: 0;
|
|
190
|
+
transition: opacity 0.15s ease-out;
|
|
191
|
+
background: rgba(244, 244, 245, 0.9);
|
|
192
|
+
border: 1px solid rgba(228, 228, 231, 1);
|
|
193
|
+
border-radius: 0 0 0 6px;
|
|
194
|
+
width: 28px;
|
|
195
|
+
height: 28px;
|
|
196
|
+
padding: 0;
|
|
197
|
+
cursor: pointer;
|
|
198
|
+
display: flex;
|
|
199
|
+
align-items: center;
|
|
200
|
+
justify-content: center;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.ui-markdown-table-wrapper:hover .ui-markdown-table-copy {
|
|
204
|
+
opacity: 1;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.ui-markdown-table-copy:hover {
|
|
208
|
+
background: rgba(228, 228, 231, 1);
|
|
209
|
+
}
|
package/src/markdown.tsx
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { View } from "react-native";
|
|
2
|
+
import { Text } from "./text";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Native fallback. The rich renderer (`markdown.web.tsx`) is react-markdown over
|
|
6
|
+
* the DOM (`<div>`/`<table>`/clipboard) — web-only. `@lotics/ui` also builds for
|
|
7
|
+
* native, so this renders the source as plain text. Rich markdown shows on web
|
|
8
|
+
* (custom-code apps + the frontend on web), which is where it's used; the web
|
|
9
|
+
* variant is picked via the conditional `exports` in package.json.
|
|
10
|
+
*/
|
|
11
|
+
export function Markdown({ children }: { children: string }) {
|
|
12
|
+
return (
|
|
13
|
+
<View>
|
|
14
|
+
<Text size="sm">{children}</Text>
|
|
15
|
+
</View>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import "./markdown.css";
|
|
2
|
+
import React, { useCallback, useRef, useState } from "react";
|
|
3
|
+
import { View } from "react-native";
|
|
4
|
+
import ReactMarkdown from "react-markdown";
|
|
5
|
+
import remarkGfm from "remark-gfm";
|
|
6
|
+
import { Icon } from "./icon";
|
|
7
|
+
import { colors } from "./colors";
|
|
8
|
+
|
|
9
|
+
export function Markdown({ children }: { children: string }) {
|
|
10
|
+
return (
|
|
11
|
+
<View>
|
|
12
|
+
<div className="ui-markdown">
|
|
13
|
+
<ReactMarkdown remarkPlugins={[remarkGfm]} components={markdownComponents}>
|
|
14
|
+
{children}
|
|
15
|
+
</ReactMarkdown>
|
|
16
|
+
</div>
|
|
17
|
+
</View>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const markdownComponents = {
|
|
22
|
+
table: CopyableTable,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function CopyableTable(props: React.ComponentPropsWithoutRef<"table"> & { node?: unknown }) {
|
|
26
|
+
const { node: _node, children, ...rest } = props;
|
|
27
|
+
const tableRef = useRef<HTMLTableElement>(null);
|
|
28
|
+
const [copied, setCopied] = useState(false);
|
|
29
|
+
|
|
30
|
+
const handleCopy = useCallback(async () => {
|
|
31
|
+
const table = tableRef.current;
|
|
32
|
+
if (!table) return;
|
|
33
|
+
|
|
34
|
+
const rows = table.querySelectorAll("tr");
|
|
35
|
+
const tsv = Array.from(rows)
|
|
36
|
+
.map((row) =>
|
|
37
|
+
Array.from(row.querySelectorAll("th, td"))
|
|
38
|
+
.map((cell) => (cell.textContent ?? "").replace(/\t/g, " ").replace(/\n/g, " ").trim())
|
|
39
|
+
.join("\t"),
|
|
40
|
+
)
|
|
41
|
+
.join("\n");
|
|
42
|
+
|
|
43
|
+
await navigator.clipboard.writeText(tsv);
|
|
44
|
+
setCopied(true);
|
|
45
|
+
setTimeout(() => setCopied(false), 2000);
|
|
46
|
+
}, []);
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<div className="ui-markdown-table-wrapper">
|
|
50
|
+
<div className="ui-markdown-table-scroll">
|
|
51
|
+
<table ref={tableRef} {...rest}>
|
|
52
|
+
{children}
|
|
53
|
+
</table>
|
|
54
|
+
</div>
|
|
55
|
+
<button
|
|
56
|
+
className="ui-markdown-table-copy"
|
|
57
|
+
onClick={handleCopy}
|
|
58
|
+
type="button"
|
|
59
|
+
title={copied ? "Copied!" : "Copy table"}
|
|
60
|
+
>
|
|
61
|
+
<Icon
|
|
62
|
+
name={copied ? "check" : "copy"}
|
|
63
|
+
size={14}
|
|
64
|
+
color={copied ? colors.green[600] : colors.zinc[500]}
|
|
65
|
+
/>
|
|
66
|
+
</button>
|
|
67
|
+
</div>
|
|
68
|
+
);
|
|
69
|
+
}
|
package/src/menu_button.tsx
CHANGED
|
@@ -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:
|
|
130
|
+
borderRadius: CONTROL_RADIUS,
|
|
130
131
|
},
|
|
131
132
|
textContainer: {
|
|
132
133
|
flex: 1,
|
package/src/menu_list_item.tsx
CHANGED
|
@@ -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:
|
|
115
|
+
borderRadius: CONTROL_RADIUS,
|
|
115
116
|
},
|
|
116
117
|
textContainer: {
|
|
117
118
|
flex: 1,
|
package/src/number_input.tsx
CHANGED
|
@@ -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:
|
|
54
|
+
borderRadius: CONTROL_RADIUS,
|
|
54
55
|
borderWidth: 1,
|
|
55
56
|
borderStyle: "solid",
|
|
56
57
|
borderColor: colors.border,
|
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:
|
|
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:
|
|
327
|
+
borderRadius: CONTROL_RADIUS,
|
|
328
328
|
cursor: "auto",
|
|
329
329
|
height: 40,
|
|
330
330
|
},
|
package/src/popover_header.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ReactNode } from "react";
|
|
2
2
|
import { View, StyleSheet } from "react-native";
|
|
3
|
-
import {
|
|
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
|
-
<
|
|
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}
|
|
@@ -8,10 +8,18 @@ import * as path from "node:path";
|
|
|
8
8
|
* `@lotics/ui` is the public, published, universal-rendering primitives
|
|
9
9
|
* package. Every file in `src/` is a primitive. It must not import any
|
|
10
10
|
* dependency that ties it to a specific Lotics runtime context: no chat
|
|
11
|
-
* SDK, no analytics, no i18n, no
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
11
|
+
* SDK, no analytics, no i18n, and no Lotics domain types (which live in
|
|
12
|
+
* `@lotics/shared`). Lotics-coupled UI is in the workspace-private
|
|
13
|
+
* `@lotics/ui-internal` package, which may import from here and from
|
|
14
|
+
* `@lotics/shared` freely.
|
|
15
|
+
*
|
|
16
|
+
* Markdown (react-markdown/remark) is the one heavy third-party dependency
|
|
17
|
+
* the kit carries on purpose — it's the single canonical renderer for chat,
|
|
18
|
+
* apps, and `AgentRun`. It's generic, not a Lotics coupling; it stays
|
|
19
|
+
* universal via the `markdown.web.tsx` (DOM) / `markdown.tsx` (native
|
|
20
|
+
* plain-text) split and is tree-shaken out of any consumer that doesn't
|
|
21
|
+
* import `./markdown`. So it's allowed — the guards below remain for the
|
|
22
|
+
* genuine context couplings.
|
|
15
23
|
*
|
|
16
24
|
* The boundary is enforced by this test — without it primitives drift
|
|
17
25
|
* back into the parent app's contexts over months.
|
|
@@ -22,8 +30,6 @@ const FORBIDDEN_PATTERNS = [
|
|
|
22
30
|
/from\s+["']@lingui\//,
|
|
23
31
|
/from\s+["']@ai-sdk\//,
|
|
24
32
|
/from\s+["']ai["']/,
|
|
25
|
-
/from\s+["']react-markdown/,
|
|
26
|
-
/from\s+["']remark-/,
|
|
27
33
|
/from\s+["']@lotics\/shared/,
|
|
28
34
|
];
|
|
29
35
|
|
|
@@ -38,7 +44,7 @@ function listTopLevelSourceFiles(): string[] {
|
|
|
38
44
|
}
|
|
39
45
|
|
|
40
46
|
describe("packages/ui primitives — purity", () => {
|
|
41
|
-
it("no primitive imports posthog, lingui, ai-sdk,
|
|
47
|
+
it("no primitive imports posthog, lingui, ai-sdk, or @lotics/shared", () => {
|
|
42
48
|
const violations: string[] = [];
|
|
43
49
|
for (const file of listTopLevelSourceFiles()) {
|
|
44
50
|
const src = fs.readFileSync(file, "utf-8");
|
package/src/radio_picker.tsx
CHANGED
|
@@ -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:
|
|
124
|
+
borderRadius: CONTROL_RADIUS,
|
|
124
125
|
gap: compact ? 8 : 16,
|
|
125
126
|
}}
|
|
126
127
|
onPress={handlePress}
|
package/src/range_slider.tsx
CHANGED
|
@@ -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 `
|
|
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
|
*/
|
package/src/review_card.tsx
CHANGED
|
@@ -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"
|
|
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}
|
|
135
|
+
return <Button key={a.label} title={a.label} color={kind} onPress={a.onPress} />;
|
|
136
136
|
})}
|
|
137
137
|
</View>
|
|
138
138
|
) : null}
|
package/src/scan_field.tsx
CHANGED
|
@@ -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:
|
|
56
|
+
borderRadius: CONTROL_RADIUS,
|
|
56
57
|
backgroundColor: colors.white,
|
|
57
58
|
},
|
|
58
59
|
});
|
package/src/scored_option.tsx
CHANGED
package/src/search_input.tsx
CHANGED
|
@@ -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
|
|
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:
|
|
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:
|
|
187
|
+
borderRadius: CONTROL_RADIUS,
|
|
187
188
|
backgroundColor: colors.zinc["100"],
|
|
188
189
|
},
|
|
189
190
|
trackDisabled: {
|
package/src/status_badge.tsx
CHANGED
|
@@ -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:
|
|
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
|
-
|
|
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 (
|
package/src/switch_button.tsx
CHANGED
|
@@ -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:
|
|
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
|
-
|
|
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:
|
|
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:
|
|
165
|
+
borderRadius: CONTROL_RADIUS,
|
|
165
166
|
backgroundColor: colors.background,
|
|
166
167
|
},
|
|
167
168
|
chip: {
|