@lotics/ui 4.4.0 → 4.5.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 +98 -28
- package/examples/tpl_assistant.tsx +6 -6
- package/examples/tpl_briefing.tsx +121 -0
- package/examples/tpl_compare.tsx +133 -0
- package/examples/tpl_crosscheck.tsx +120 -0
- package/examples/tpl_dieline.tsx +218 -55
- package/examples/tpl_draft.tsx +7 -9
- package/examples/tpl_extract.tsx +91 -92
- package/examples/tpl_lookup.tsx +288 -0
- package/examples/tpl_match.tsx +123 -0
- package/examples/tpl_triage.tsx +112 -0
- package/package.json +9 -2
- package/src/agent_progress.tsx +35 -23
- package/src/agent_run.tsx +60 -80
- package/src/change_review.tsx +190 -110
- package/src/choice_list.tsx +63 -0
- package/src/clarify.tsx +19 -39
- package/src/confidence.tsx +30 -8
- package/src/discrepancy.tsx +114 -0
- package/src/finding.tsx +104 -0
- package/src/match_row.tsx +133 -0
- package/src/prompt_field.tsx +47 -89
- package/src/record_review.tsx +149 -0
- package/src/scored_option.tsx +139 -0
- package/src/sources.tsx +38 -21
- package/src/spec_list.tsx +81 -0
- package/src/suggestion.tsx +35 -45
- package/src/triage_row.tsx +99 -0
- package/src/assisted_field.tsx +0 -93
package/examples/tpl_dieline.tsx
CHANGED
|
@@ -1,28 +1,31 @@
|
|
|
1
|
-
import { useEffect, useState } from "react";
|
|
2
|
-
import {
|
|
3
|
-
import { colors
|
|
1
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
import { PanResponder, StyleSheet, View, type ViewStyle } from "react-native";
|
|
3
|
+
import { colors } from "@lotics/ui/colors";
|
|
4
4
|
import { Text } from "@lotics/ui/text";
|
|
5
5
|
import { Button } from "@lotics/ui/button";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { KPIStrip } from "@lotics/ui/kpi_strip";
|
|
6
|
+
import { IconButton } from "@lotics/ui/icon_button";
|
|
7
|
+
import { PressableHighlight } from "@lotics/ui/pressable_highlight";
|
|
9
8
|
import { DotsIndicator } from "@lotics/ui/dots_indicator";
|
|
9
|
+
import { InlineTextInput } from "@lotics/ui/inline_text_input";
|
|
10
10
|
import { AgentProgress } from "@lotics/ui/agent_progress";
|
|
11
11
|
import { PromptField } from "@lotics/ui/prompt_field";
|
|
12
12
|
import { type AgentRunStep } from "@lotics/ui/agent_run";
|
|
13
13
|
|
|
14
14
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
15
15
|
// Template · AI design canvas (the carton "dieline" app) — the FLAGSHIP shape:
|
|
16
|
-
// the
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
16
|
+
// the page IS the design. The dieline fills the centre, a FLOATING composer sits
|
|
17
|
+
// at the bottom, and a pinned PARAMS PANEL (the RecordReview field-card, here in
|
|
18
|
+
// live-edit mode) floats at the centre-right. Empty → drop a photo in the
|
|
19
|
+
// composer → the centre processes while the composer MORPHS into a progress pill
|
|
20
|
+
// (AgentProgress) → the dieline reveals, the params panel appears, the composer
|
|
21
|
+
// returns. Two ways to change it: prompt the agent ("5 mm taller"), or edit a
|
|
22
|
+
// param directly — either re-flows the design in place. The geometry is
|
|
23
|
+
// deterministic (the app owns it); the agent only proposes parameters. The panel
|
|
24
|
+
// carries the single Download. All mock — the recipe, not a CAD engine.
|
|
23
25
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
24
26
|
|
|
25
27
|
type ScriptStep = Omit<AgentRunStep, "status">;
|
|
28
|
+
type Dims = { L: number; W: number; H: number };
|
|
26
29
|
|
|
27
30
|
const RECOGNIZE: ScriptStep[] = [
|
|
28
31
|
{ id: "r1", label: "Reading the photo", detail: "A single brown carton on a white sweep, card left for scale." },
|
|
@@ -37,12 +40,43 @@ const editScript = (prompt: string): ScriptStep[] => [
|
|
|
37
40
|
{ id: "e2", label: "Recomputing the dieline", kind: "tool" },
|
|
38
41
|
];
|
|
39
42
|
|
|
43
|
+
const ZOOM_MIN = 0.4;
|
|
44
|
+
const ZOOM_MAX = 2.5;
|
|
45
|
+
const ZOOM_STEP = 0.25;
|
|
46
|
+
|
|
47
|
+
// `grab`/`grabbing` are web-only cursors outside RN's typed `CursorValue` union,
|
|
48
|
+
// so this needs a boundary cast (the kit casts `cursor` the same way).
|
|
49
|
+
const grabCursor = (grabbing: boolean): ViewStyle => ({ cursor: grabbing ? "grabbing" : "grab" } as unknown as ViewStyle);
|
|
50
|
+
|
|
40
51
|
export function TplDieline() {
|
|
41
52
|
const [phase, setPhase] = useState<"empty" | "working" | "done">("empty");
|
|
42
53
|
const [designReady, setDesignReady] = useState(false);
|
|
43
|
-
const [dims, setDims] = useState({ L: 320, W: 230, H: 150 });
|
|
54
|
+
const [dims, setDims] = useState<Dims>({ L: 320, W: 230, H: 150 });
|
|
55
|
+
const [zoom, setZoom] = useState(1);
|
|
56
|
+
const [pan, setPan] = useState({ x: 0, y: 0 });
|
|
57
|
+
const [dragging, setDragging] = useState(false);
|
|
44
58
|
const [prompt, setPrompt] = useState("");
|
|
45
59
|
|
|
60
|
+
// Drag-to-pan — like a design tool: grab anywhere on the canvas and move it.
|
|
61
|
+
// The committed pan is read at grant via a ref (the responder is built once).
|
|
62
|
+
const panRef = useRef(pan);
|
|
63
|
+
panRef.current = pan;
|
|
64
|
+
const grantRef = useRef({ x: 0, y: 0 });
|
|
65
|
+
const panResponder = useMemo(
|
|
66
|
+
() =>
|
|
67
|
+
PanResponder.create({
|
|
68
|
+
onMoveShouldSetPanResponder: (_e, g) => Math.abs(g.dx) > 3 || Math.abs(g.dy) > 3,
|
|
69
|
+
onPanResponderGrant: () => {
|
|
70
|
+
grantRef.current = panRef.current;
|
|
71
|
+
setDragging(true);
|
|
72
|
+
},
|
|
73
|
+
onPanResponderMove: (_e, g) => setPan({ x: grantRef.current.x + g.dx, y: grantRef.current.y + g.dy }),
|
|
74
|
+
onPanResponderRelease: () => setDragging(false),
|
|
75
|
+
onPanResponderTerminate: () => setDragging(false),
|
|
76
|
+
}),
|
|
77
|
+
[],
|
|
78
|
+
);
|
|
79
|
+
|
|
46
80
|
// Streaming engine — reveal a run's steps over time, then fire onDone.
|
|
47
81
|
const [run, setRun] = useState<{ script: ScriptStep[]; onDone: () => void } | null>(null);
|
|
48
82
|
const [revealed, setRevealed] = useState(0);
|
|
@@ -98,53 +132,68 @@ export function TplDieline() {
|
|
|
98
132
|
});
|
|
99
133
|
};
|
|
100
134
|
|
|
135
|
+
const editParam = (key: keyof Dims, value: number) => setDims((d) => ({ ...d, [key]: value }));
|
|
136
|
+
|
|
101
137
|
const newSession = () => {
|
|
102
138
|
setRun(null);
|
|
103
139
|
setRevealed(0);
|
|
104
140
|
setDesignReady(false);
|
|
105
141
|
setDims({ L: 320, W: 230, H: 150 });
|
|
142
|
+
resetView();
|
|
106
143
|
setPhase("empty");
|
|
107
144
|
};
|
|
108
145
|
|
|
146
|
+
const zoomBy = (d: number) => setZoom((z) => Math.min(ZOOM_MAX, Math.max(ZOOM_MIN, Math.round((z + d) * 100) / 100)));
|
|
147
|
+
const resetView = () => {
|
|
148
|
+
setZoom(1);
|
|
149
|
+
setPan({ x: 0, y: 0 });
|
|
150
|
+
};
|
|
151
|
+
|
|
109
152
|
return (
|
|
110
153
|
<View style={{ flex: 1, backgroundColor: colors.zinc[50] }}>
|
|
111
154
|
{/* slim top bar */}
|
|
112
155
|
<View style={{ flexDirection: "row", alignItems: "center", gap: 12, paddingHorizontal: 20, paddingVertical: 12, borderBottomWidth: 1, borderBottomColor: colors.zinc[200], backgroundColor: colors.background }}>
|
|
113
156
|
<Text size="sm" weight="semibold" style={{ flex: 1 }}>Carton design</Text>
|
|
114
|
-
{
|
|
115
|
-
{phase !== "empty" ? <Button title="New" color="muted" shape="rounded" icon="rotate-ccw" onPress={newSession} /> : null}
|
|
157
|
+
{phase !== "empty" ? <Button title="New" color="secondary" shape="rounded" icon="plus" onPress={newSession} /> : null}
|
|
116
158
|
</View>
|
|
117
159
|
|
|
118
|
-
{/* canvas —
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
<
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
160
|
+
{/* canvas region — a pannable + zoomable surface (DRAG to pan, the zoom pill
|
|
161
|
+
to scale). The design sits CENTRED at rest and zoom scales it about that
|
|
162
|
+
centre; panel, zoom pill and composer float ON TOP, never shifting it. */}
|
|
163
|
+
<View
|
|
164
|
+
style={[{ flex: 1, overflow: "hidden" }, designReady ? grabCursor(dragging) : null]}
|
|
165
|
+
{...(designReady ? panResponder.panHandlers : {})}
|
|
166
|
+
>
|
|
167
|
+
<View style={{ flex: 1, alignItems: "center", justifyContent: "center", padding: 48 }}>
|
|
168
|
+
{phase === "empty" ? (
|
|
169
|
+
<EmptyFrame />
|
|
170
|
+
) : phase === "working" && !designReady ? (
|
|
171
|
+
<BuildingFrame />
|
|
172
|
+
) : (
|
|
173
|
+
<View style={{ opacity: phase === "working" ? 0.45 : 1, transform: [{ translateX: pan.x }, { translateY: pan.y }, { scale: zoom }] }}>
|
|
174
|
+
<DielinePreview L={dims.L} W={dims.W} H={dims.H} />
|
|
175
|
+
</View>
|
|
176
|
+
)}
|
|
177
|
+
</View>
|
|
178
|
+
|
|
179
|
+
{designReady ? (
|
|
180
|
+
<>
|
|
181
|
+
<View style={[StyleSheet.absoluteFill, { pointerEvents: "none", alignItems: "flex-end", justifyContent: "center", paddingRight: 24 }]}>
|
|
182
|
+
<View style={{ pointerEvents: "auto" }}>
|
|
183
|
+
<ParamsPanel dims={dims} onEditParam={editParam} onDownload={() => {}} />
|
|
184
|
+
</View>
|
|
136
185
|
</View>
|
|
137
|
-
<View style={{
|
|
138
|
-
<
|
|
139
|
-
<Button title="Download PDF" color="secondary" shape="rounded" icon="file-down" onPress={() => {}} />
|
|
186
|
+
<View style={{ position: "absolute", left: 24, bottom: 24 }}>
|
|
187
|
+
<ZoomBar zoom={zoom} onZoomOut={() => zoomBy(-ZOOM_STEP)} onZoomIn={() => zoomBy(ZOOM_STEP)} onReset={resetView} />
|
|
140
188
|
</View>
|
|
141
|
-
|
|
142
|
-
)}
|
|
143
|
-
</
|
|
189
|
+
</>
|
|
190
|
+
) : null}
|
|
191
|
+
</View>
|
|
144
192
|
|
|
145
|
-
{/* floating composer — morphs into the progress pill while the agent runs
|
|
146
|
-
|
|
147
|
-
|
|
193
|
+
{/* floating composer — morphs into the progress pill while the agent runs.
|
|
194
|
+
box-none so its full-width band doesn't intercept the zoom bar / canvas. */}
|
|
195
|
+
<View style={{ position: "absolute", left: 0, right: 0, bottom: 24, alignItems: "center", paddingHorizontal: 16, pointerEvents: "none" }}>
|
|
196
|
+
<View style={{ width: "100%", maxWidth: 620, pointerEvents: "auto" }}>
|
|
148
197
|
{phase === "working" ? (
|
|
149
198
|
<AgentProgress steps={liveSteps} state="streaming" />
|
|
150
199
|
) : (
|
|
@@ -162,12 +211,108 @@ export function TplDieline() {
|
|
|
162
211
|
);
|
|
163
212
|
}
|
|
164
213
|
|
|
165
|
-
|
|
214
|
+
// The pinned right-rail — the RecordReview field-card in live-edit mode: each
|
|
215
|
+
// parameter is click-to-edit and re-flows the design on save; one primary
|
|
216
|
+
// Download. Floats over the canvas, so it carries the composer's lift.
|
|
217
|
+
const PARAMS: { key: keyof Dims; label: string }[] = [
|
|
218
|
+
{ key: "L", label: "Length" },
|
|
219
|
+
{ key: "W", label: "Width" },
|
|
220
|
+
{ key: "H", label: "Height" },
|
|
221
|
+
];
|
|
222
|
+
|
|
223
|
+
function ParamsPanel({ dims, onEditParam, onDownload }: { dims: Dims; onEditParam: (key: keyof Dims, value: number) => void; onDownload: () => void }) {
|
|
224
|
+
const [open, setOpen] = useState(true);
|
|
166
225
|
return (
|
|
167
|
-
<View style={
|
|
168
|
-
<View style={
|
|
169
|
-
<
|
|
226
|
+
<View style={[panel.card, open ? panel.cardOpen : panel.cardMin]}>
|
|
227
|
+
<View style={panel.header}>
|
|
228
|
+
<View style={{ flex: 1, gap: 1 }}>
|
|
229
|
+
<Text size="sm" weight="semibold">Parameters</Text>
|
|
230
|
+
{open ? <Text size="xs" color="muted">FEFCO 0201 · B-flute</Text> : null}
|
|
231
|
+
</View>
|
|
232
|
+
<IconButton icon={open ? "minus" : "plus"} accessibilityLabel={open ? "Minimize parameters" : "Expand parameters"} onPress={() => setOpen((o) => !o)} />
|
|
170
233
|
</View>
|
|
234
|
+
|
|
235
|
+
{open ? (
|
|
236
|
+
<>
|
|
237
|
+
<View style={panel.fields}>
|
|
238
|
+
{PARAMS.map((p) => (
|
|
239
|
+
<View key={p.key} style={panel.row}>
|
|
240
|
+
<Text size="sm" color="muted" style={panel.label}>{p.label}</Text>
|
|
241
|
+
<View style={panel.value}>
|
|
242
|
+
<View style={{ flex: 1 }}>
|
|
243
|
+
<InlineTextInput
|
|
244
|
+
value={String(dims[p.key])}
|
|
245
|
+
accessibilityLabel={`Edit ${p.label}`}
|
|
246
|
+
onSave={(next) => {
|
|
247
|
+
const n = Math.round(Number(next.replace(/[^\d.]/g, "")));
|
|
248
|
+
if (Number.isFinite(n) && n > 0) onEditParam(p.key, n);
|
|
249
|
+
}}
|
|
250
|
+
/>
|
|
251
|
+
</View>
|
|
252
|
+
<Text size="sm" color="muted">mm</Text>
|
|
253
|
+
</View>
|
|
254
|
+
</View>
|
|
255
|
+
))}
|
|
256
|
+
</View>
|
|
257
|
+
|
|
258
|
+
<Button title="Download" color="primary" shape="rounded" icon="file-down" alignSelf="stretch" onPress={onDownload} />
|
|
259
|
+
</>
|
|
260
|
+
) : null}
|
|
261
|
+
</View>
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const panel = StyleSheet.create({
|
|
266
|
+
card: {
|
|
267
|
+
borderWidth: 1,
|
|
268
|
+
borderColor: colors.border,
|
|
269
|
+
backgroundColor: colors.white,
|
|
270
|
+
borderRadius: 12,
|
|
271
|
+
padding: 16,
|
|
272
|
+
gap: 14,
|
|
273
|
+
boxShadow: "0 4px 14px rgba(24,24,27,0.08)",
|
|
274
|
+
},
|
|
275
|
+
cardOpen: { width: 264 },
|
|
276
|
+
cardMin: { paddingVertical: 8, paddingHorizontal: 12 },
|
|
277
|
+
header: { flexDirection: "row", alignItems: "center", gap: 10 },
|
|
278
|
+
fields: { gap: 2 },
|
|
279
|
+
row: { flexDirection: "row", alignItems: "center", gap: 12, minHeight: 34 },
|
|
280
|
+
label: { width: 70 },
|
|
281
|
+
value: { flex: 1, flexDirection: "row", alignItems: "center", gap: 6 },
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
// The canvas zoom control — out / percentage (tap to reset) / in, in a floating
|
|
285
|
+
// pill that carries the same lift as the composer and params panel.
|
|
286
|
+
function ZoomBar({ zoom, onZoomOut, onZoomIn, onReset }: { zoom: number; onZoomOut: () => void; onZoomIn: () => void; onReset: () => void }) {
|
|
287
|
+
return (
|
|
288
|
+
<View style={zoombar.bar}>
|
|
289
|
+
<IconButton icon="minus" accessibilityLabel="Zoom out" onPress={onZoomOut} />
|
|
290
|
+
<PressableHighlight accessibilityRole="button" accessibilityLabel="Reset zoom to 100%" onPress={onReset} style={zoombar.pct}>
|
|
291
|
+
<Text size="xs" weight="medium" tabular>{Math.round(zoom * 100)}%</Text>
|
|
292
|
+
</PressableHighlight>
|
|
293
|
+
<IconButton icon="plus" accessibilityLabel="Zoom in" onPress={onZoomIn} />
|
|
294
|
+
</View>
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const zoombar = StyleSheet.create({
|
|
299
|
+
bar: {
|
|
300
|
+
flexDirection: "row",
|
|
301
|
+
alignItems: "center",
|
|
302
|
+
gap: 2,
|
|
303
|
+
padding: 4,
|
|
304
|
+
borderWidth: 1,
|
|
305
|
+
borderColor: colors.border,
|
|
306
|
+
backgroundColor: colors.white,
|
|
307
|
+
borderRadius: 12,
|
|
308
|
+
boxShadow: "0 4px 14px rgba(24,24,27,0.08)",
|
|
309
|
+
},
|
|
310
|
+
pct: { minWidth: 50, height: 28, alignItems: "center", justifyContent: "center", borderRadius: 8, paddingHorizontal: 6 },
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
function EmptyFrame() {
|
|
314
|
+
return (
|
|
315
|
+
<View style={frame.box}>
|
|
171
316
|
<View style={{ alignItems: "center", gap: 2 }}>
|
|
172
317
|
<Text size="md" weight="medium">Your dieline appears here</Text>
|
|
173
318
|
<Text size="sm" color="muted">Attach a photo of the carton in the composer below.</Text>
|
|
@@ -178,20 +323,38 @@ function EmptyFrame() {
|
|
|
178
323
|
|
|
179
324
|
function BuildingFrame() {
|
|
180
325
|
return (
|
|
181
|
-
<View style={
|
|
182
|
-
<DotsIndicator size={9} color={
|
|
326
|
+
<View style={[frame.box, { backgroundColor: colors.zinc[50] }]}>
|
|
327
|
+
<DotsIndicator size={9} color={colors.zinc[900]} />
|
|
183
328
|
<Text size="sm" color="muted">Designing your dieline…</Text>
|
|
184
329
|
</View>
|
|
185
330
|
);
|
|
186
331
|
}
|
|
187
332
|
|
|
333
|
+
const frame = StyleSheet.create({
|
|
334
|
+
box: {
|
|
335
|
+
width: 640,
|
|
336
|
+
maxWidth: "100%",
|
|
337
|
+
height: 360,
|
|
338
|
+
borderRadius: 16,
|
|
339
|
+
borderWidth: 1.5,
|
|
340
|
+
borderColor: colors.zinc[300],
|
|
341
|
+
borderStyle: "dashed",
|
|
342
|
+
backgroundColor: colors.background,
|
|
343
|
+
alignItems: "center",
|
|
344
|
+
justifyContent: "center",
|
|
345
|
+
gap: 12,
|
|
346
|
+
},
|
|
347
|
+
});
|
|
348
|
+
|
|
188
349
|
// A schematic RSC dieline, View-based, scaled from the dimensions. Solid edges =
|
|
189
|
-
// cut; dashed = crease. Re-flows whenever a dimension changes.
|
|
190
|
-
|
|
350
|
+
// cut; dashed = crease. Re-flows whenever a dimension changes. Sized to fill the
|
|
351
|
+
// canvas (TARGET wide), so the design reads as the hero.
|
|
352
|
+
const TARGET = 780;
|
|
353
|
+
function DielinePreview({ L, W, H }: Dims) {
|
|
191
354
|
const TAB = 40;
|
|
192
355
|
const blankW = 2 * (L + W) + TAB;
|
|
193
356
|
const flap = W / 2;
|
|
194
|
-
const scale =
|
|
357
|
+
const scale = TARGET / blankW;
|
|
195
358
|
const px = (mm: number) => Math.max(2, mm * scale);
|
|
196
359
|
const walls = [
|
|
197
360
|
{ w: W, label: "Side" },
|
|
@@ -204,11 +367,11 @@ function DielinePreview({ L, W, H }: { L: number; W: number; H: number }) {
|
|
|
204
367
|
const crease = { borderColor: colors.zinc[300], borderStyle: "dashed" as const };
|
|
205
368
|
|
|
206
369
|
return (
|
|
207
|
-
<View style={{ alignItems: "flex-start", gap:
|
|
370
|
+
<View style={{ alignItems: "flex-start", gap: 12 }}>
|
|
208
371
|
<View style={{ borderWidth: 1.5, borderColor: colors.zinc[500], backgroundColor: colors.background }}>
|
|
209
372
|
<View style={{ flexDirection: "row", height: flapH }}>
|
|
210
373
|
{walls.map((p, i) => (
|
|
211
|
-
<View key={`tf-${i}`} style={{ width: px(p.w), borderLeftWidth: i === 0 ? 0 : 1, ...crease, alignItems: "center", justifyContent: "center", backgroundColor:
|
|
374
|
+
<View key={`tf-${i}`} style={{ width: px(p.w), borderLeftWidth: i === 0 ? 0 : 1, ...crease, alignItems: "center", justifyContent: "center", backgroundColor: colors.zinc[100] }}>
|
|
212
375
|
<Text size="xs" color="muted">flap</Text>
|
|
213
376
|
</View>
|
|
214
377
|
))}
|
|
@@ -227,7 +390,7 @@ function DielinePreview({ L, W, H }: { L: number; W: number; H: number }) {
|
|
|
227
390
|
</View>
|
|
228
391
|
<View style={{ flexDirection: "row", height: flapH }}>
|
|
229
392
|
{walls.map((p, i) => (
|
|
230
|
-
<View key={`bf-${i}`} style={{ width: px(p.w), borderLeftWidth: i === 0 ? 0 : 1, ...crease, alignItems: "center", justifyContent: "center", backgroundColor:
|
|
393
|
+
<View key={`bf-${i}`} style={{ width: px(p.w), borderLeftWidth: i === 0 ? 0 : 1, ...crease, alignItems: "center", justifyContent: "center", backgroundColor: colors.zinc[100] }}>
|
|
231
394
|
<Text size="xs" color="muted">flap</Text>
|
|
232
395
|
</View>
|
|
233
396
|
))}
|
package/examples/tpl_draft.tsx
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { useEffect, useState } from "react";
|
|
2
2
|
import { ScrollView, View } from "react-native";
|
|
3
|
-
import { colors
|
|
3
|
+
import { colors } from "@lotics/ui/colors";
|
|
4
4
|
import { Text } from "@lotics/ui/text";
|
|
5
5
|
import { Button } from "@lotics/ui/button";
|
|
6
|
-
import { Icon } from "@lotics/ui/icon";
|
|
7
6
|
import { Card, CardBody, CardFooter, CardHeader, CardHeaderTitle } from "@lotics/ui/card";
|
|
8
7
|
import { SegmentedControl } from "@lotics/ui/segmented_control";
|
|
9
8
|
import { TextInputField } from "@lotics/ui/text_input_field";
|
|
@@ -107,7 +106,7 @@ export function TplDraft() {
|
|
|
107
106
|
<CardBody>
|
|
108
107
|
<View style={{ flexDirection: "row", flexWrap: "wrap", gap: 16 }}>
|
|
109
108
|
<View style={{ gap: 6 }}>
|
|
110
|
-
<Text size="xs" color="muted"
|
|
109
|
+
<Text size="xs" color="muted" weight="medium">Tone</Text>
|
|
111
110
|
<SegmentedControl
|
|
112
111
|
accessibilityLabel="Tone"
|
|
113
112
|
options={[{ label: "Friendly", value: "friendly" }, { label: "Formal", value: "formal" }]}
|
|
@@ -117,7 +116,7 @@ export function TplDraft() {
|
|
|
117
116
|
/>
|
|
118
117
|
</View>
|
|
119
118
|
<View style={{ gap: 6 }}>
|
|
120
|
-
<Text size="xs" color="muted"
|
|
119
|
+
<Text size="xs" color="muted" weight="medium">Length</Text>
|
|
121
120
|
<SegmentedControl
|
|
122
121
|
accessibilityLabel="Length"
|
|
123
122
|
options={[{ label: "Short", value: "short" }, { label: "Detailed", value: "detailed" }]}
|
|
@@ -130,16 +129,15 @@ export function TplDraft() {
|
|
|
130
129
|
|
|
131
130
|
{phase === "idle" ? (
|
|
132
131
|
<View style={{ alignItems: "flex-start", paddingTop: 4 }}>
|
|
133
|
-
<Button title="Draft a reply" color="primary" shape="rounded"
|
|
132
|
+
<Button title="Draft a reply" color="primary" shape="rounded" onPress={startDraft} />
|
|
134
133
|
</View>
|
|
135
134
|
) : null}
|
|
136
135
|
|
|
137
136
|
{phase === "drafting" ? (
|
|
138
|
-
<View style={{ borderWidth: 1, borderColor:
|
|
137
|
+
<View style={{ borderWidth: 1, borderColor: colors.zinc[200], borderRadius: 10, padding: 14, gap: 10, backgroundColor: colors.zinc[50] }}>
|
|
139
138
|
<View style={{ flexDirection: "row", alignItems: "center", gap: 8 }}>
|
|
140
|
-
<
|
|
141
|
-
<
|
|
142
|
-
<DotsIndicator size={5} color={solid("violet")} />
|
|
139
|
+
<Text size="xs" color="muted" weight="medium" style={{ flex: 1 }}>Drafting</Text>
|
|
140
|
+
<DotsIndicator size={5} color={colors.zinc[900]} />
|
|
143
141
|
</View>
|
|
144
142
|
<Text size="sm">{streamed}</Text>
|
|
145
143
|
</View>
|