@tangle-network/ui 11.45.0 → 11.46.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/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # @tangle-network/ui
2
2
 
3
+ ## 11.46.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 7924be7: SegmentedControl (row): the selected segment is a raised thumb on one quiet well instead of a tint inside an inset ring, the track uses the hairline border token, and the control is 36px tall so it sits inside a 44px toolbar with air above and below.
8
+
9
+ ### Patch Changes
10
+
11
+ - 9bd1487: RunRowShell shows its description (the file, command or query) at every width, truncated, instead of hiding it below 640px, where a transcript read as a column of bare verbs ("Read", "Read", "Shell").
12
+
3
13
  ## 11.45.0
4
14
 
5
15
  ### Minor Changes
@@ -11,10 +11,13 @@ import * as React from "react";
11
11
  * console warning fires when both are omitted.
12
12
  *
13
13
  * Design rules baked into the default variant:
14
- * - The row track is a field well (`--well`, `--field-border`), so the
15
- * control reads as one inset group on whatever plane it sits on.
16
- * - Only the SELECTED segment shows a surface colour + accent text: the
17
- * shared selected state (`ixSelected`: tint plus an inset ring). A tab
14
+ * - The row track is one quiet well (`--well`, the hairline `border`), so
15
+ * the control reads as one inset group on whatever plane it sits on, and
16
+ * it is compact (36px) so it sits inside a 44px toolbar with air above
17
+ * and below instead of touching its edges.
18
+ * - Only the SELECTED segment shows a surface: a raised thumb (the card
19
+ * colour with a hairline and a soft shadow) with accent text. No ring is
20
+ * drawn around the track's segments, so the group never stacks rings. A tab
18
21
  * shows it as an underline instead (`ixTabUnderline`).
19
22
  * - Unselected segments have NO background — they're plain-text labels
20
23
  * that darken on hover. This keeps the selected segment as the
@@ -7,6 +7,8 @@ import * as React from "react";
7
7
  import { jsx, jsxs } from "react/jsx-runtime";
8
8
  import { ChevronLeft, ChevronRight } from "lucide-react";
9
9
  // src/primitives/segmented-control.tsx
10
+ /** The row variant's selected segment: a raised thumb on the well, not a tint inside a ring. */
11
+ const rowThumb = "aria-checked:bg-card aria-checked:border-border aria-checked:shadow-[0_1px_2px_rgb(0_0_0/0.08)] hover:aria-checked:bg-card";
10
12
  /** Size of the edge chevron, in px. The selected option is kept this far
11
13
  * (plus the strip's own padding) from an edge so the chevron never covers it. */
12
14
  const SCROLL_CUE_SIZE = 32;
@@ -123,7 +125,7 @@ function SegmentedControl({ value, onValueChange, options, variant = "row", clas
123
125
  ref: stripRef,
124
126
  onKeyDown: handleKeyDown,
125
127
  onScroll: measureOverflow,
126
- className: cn("relative flex gap-1", variant === "row" && "flex-wrap items-center rounded-lg border border-[var(--field-border)] bg-[var(--well)] p-1", variant === "tabs" && "flex-nowrap items-end border-b border-border pb-0 overflow-x-auto", className),
128
+ className: cn("relative flex gap-1", variant === "row" && "flex-wrap items-center rounded-lg border border-border bg-[var(--well)] p-0.5", variant === "tabs" && "flex-nowrap items-end border-b border-border pb-0 overflow-x-auto", className),
127
129
  children: [
128
130
  overflow.start && /* @__PURE__ */ jsx(ScrollCue, {
129
131
  side: "start",
@@ -146,10 +148,10 @@ function SegmentedControl({ value, onValueChange, options, variant = "row", clas
146
148
  if (!active) onValueChange(option.value);
147
149
  },
148
150
  className: cn("relative inline-flex items-center gap-2 whitespace-nowrap text-sm font-medium transition-[color,background-color,border-color,box-shadow]", variant === "row" && [
149
- "rounded-md border border-transparent px-3 py-1.5",
151
+ "rounded-md border border-transparent px-3 py-1",
150
152
  "focus-visible:outline-1 focus-visible:outline-[var(--focus-border)] focus-visible:ring-4 focus-visible:ring-[var(--focus-halo)] focus-visible:outline-offset-0",
151
153
  "hover:bg-[var(--ix-hover-bg)] active:bg-[var(--ix-sel-bg)]",
152
- "aria-pressed:bg-[var(--ix-sel-bg)] aria-pressed:text-[var(--accent-text)] aria-pressed:shadow-[inset_0_0_0_1px_var(--ix-sel-ring)] aria-checked:bg-[var(--ix-sel-bg)] aria-checked:text-[var(--accent-text)] aria-checked:shadow-[inset_0_0_0_1px_var(--ix-sel-ring)] aria-[current=page]:bg-[var(--ix-sel-bg)] aria-[current=page]:text-[var(--accent-text)] aria-[current=page]:shadow-[inset_0_0_0_1px_var(--ix-sel-ring)] data-[state=on]:bg-[var(--ix-sel-bg)] data-[state=on]:shadow-[inset_0_0_0_1px_var(--ix-sel-ring)] data-[state=active]:bg-[var(--ix-sel-bg)] data-[state=active]:text-[var(--accent-text)] data-[state=active]:shadow-[inset_0_0_0_1px_var(--ix-sel-ring)]"
154
+ rowThumb
153
155
  ], variant === "tabs" && [
154
156
  "shrink-0 rounded-none border-b-2 border-transparent -mb-px px-4 py-2",
155
157
  "pointer-coarse:min-h-11 pointer-coarse:min-w-11",
@@ -76,7 +76,7 @@ function RunRowShell({ icon, title, description, descriptionMono = false, status
76
76
  children: title
77
77
  }),
78
78
  description ? /* @__PURE__ */ jsx("span", {
79
- className: cn("hidden min-w-0 flex-1 truncate text-sm text-muted-foreground transition-colors group-hover:text-foreground sm:inline", descriptionMono && "font-mono"),
79
+ className: cn("min-w-0 flex-1 truncate text-sm text-muted-foreground transition-colors group-hover:text-foreground", descriptionMono && "font-mono"),
80
80
  children: description
81
81
  }) : null,
82
82
  /* @__PURE__ */ jsxs("span", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/ui",
3
- "version": "11.45.0",
3
+ "version": "11.46.0",
4
4
  "description": "Generic React UI components for Tangle products — primitives, chat, run, files, editor, markdown.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -191,15 +191,17 @@ describe("each primitive renders its interaction helper", () => {
191
191
  expect(classes(screen.getByText(/Guests, staff/))).toContain("line-clamp-2");
192
192
  });
193
193
 
194
- it("SegmentedControl row: a well track, soft items, the selected state on aria-checked", () => {
194
+ it("SegmentedControl row: a quiet well track, soft items, a raised thumb on aria-checked and no ring", () => {
195
195
  const options = [{ value: "a", label: "A–Z" }, { value: "b", label: "Recent" }];
196
196
  render(<SegmentedControl aria-label="Sort" value="a" onValueChange={() => {}} options={options} />);
197
197
  const track = screen.getByRole("radiogroup");
198
198
  expect(classes(track)).toContain("bg-[var(--well)]");
199
- expect(classes(track)).toContain("border-[var(--field-border)]");
199
+ expect(classes(track)).toContain("border-border");
200
200
  expect(classes(track)).not.toContain("bg-card");
201
201
  for (const radio of screen.getAllByRole("radio")) {
202
- expectHelpers(radio, ixControlSoft, ixSelected);
202
+ expectHelpers(radio, ixControlSoft);
203
+ expect(classes(radio)).toContain("aria-checked:bg-card");
204
+ expect([...classes(radio)].some((token) => token.includes("--ix-sel-ring"))).toBe(false);
203
205
  for (const token of FOCUS_RING) expect(classes(radio)).toContain(token);
204
206
  expect(classes(radio)).not.toContain("bg-[var(--accent-surface)]");
205
207
  }
@@ -271,9 +271,10 @@ describe("SegmentedControl", () => {
271
271
  const radiogroup = screen.getByRole("radiogroup")
272
272
  expect(radiogroup.className).toContain("flex-wrap")
273
273
  expect(radiogroup.className).toContain("rounded-lg")
274
- // The row track is a field well, so it steps off whatever plane holds it.
274
+ // The row track is one quiet well: the hairline border, not a field's strong border.
275
275
  expect(radiogroup.className).toContain("bg-[var(--well)]")
276
- expect(radiogroup.className).toContain("border-[var(--field-border)]")
276
+ expect(radiogroup.className).toContain("border-border")
277
+ expect(radiogroup.className).not.toContain("border-[var(--field-border)]")
277
278
  })
278
279
 
279
280
  it("applies tabs variant classes with nowrap", () => {
@@ -6,9 +6,13 @@ import { ChevronLeft, ChevronRight } from "lucide-react"
6
6
  import * as React from "react"
7
7
  import { touchTarget } from "../lib/control-presentation"
8
8
  import { focusRing, focusRingInset } from "../lib/focus"
9
- import { ixControlSoft, ixSelected, ixTabUnderline } from "../lib/interaction"
9
+ import { ixControlSoft, ixTabUnderline } from "../lib/interaction"
10
10
  import { cn } from "../lib/utils"
11
11
 
12
+ /** The row variant's selected segment: a raised thumb on the well, not a tint inside a ring. */
13
+ const rowThumb =
14
+ "aria-checked:bg-card aria-checked:border-border aria-checked:shadow-[0_1px_2px_rgb(0_0_0/0.08)] hover:aria-checked:bg-card"
15
+
12
16
  /**
13
17
  * Visually lightweight segmented control for single-value selection.
14
18
  *
@@ -21,10 +25,13 @@ import { cn } from "../lib/utils"
21
25
  * console warning fires when both are omitted.
22
26
  *
23
27
  * Design rules baked into the default variant:
24
- * - The row track is a field well (`--well`, `--field-border`), so the
25
- * control reads as one inset group on whatever plane it sits on.
26
- * - Only the SELECTED segment shows a surface colour + accent text: the
27
- * shared selected state (`ixSelected`: tint plus an inset ring). A tab
28
+ * - The row track is one quiet well (`--well`, the hairline `border`), so
29
+ * the control reads as one inset group on whatever plane it sits on, and
30
+ * it is compact (36px) so it sits inside a 44px toolbar with air above
31
+ * and below instead of touching its edges.
32
+ * - Only the SELECTED segment shows a surface: a raised thumb (the card
33
+ * colour with a hairline and a soft shadow) with accent text. No ring is
34
+ * drawn around the track's segments, so the group never stacks rings. A tab
28
35
  * shows it as an underline instead (`ixTabUnderline`).
29
36
  * - Unselected segments have NO background — they're plain-text labels
30
37
  * that darken on hover. This keeps the selected segment as the
@@ -224,7 +231,7 @@ export function SegmentedControl<T extends string = string>({
224
231
  className={cn(
225
232
  "relative flex gap-1",
226
233
  variant === "row" &&
227
- "flex-wrap items-center rounded-lg border border-[var(--field-border)] bg-[var(--well)] p-1",
234
+ "flex-wrap items-center rounded-lg border border-border bg-[var(--well)] p-0.5",
228
235
  variant === "tabs" &&
229
236
  "flex-nowrap items-end border-b border-border pb-0 overflow-x-auto",
230
237
  className,
@@ -251,7 +258,7 @@ export function SegmentedControl<T extends string = string>({
251
258
  }}
252
259
  className={cn(
253
260
  "relative inline-flex items-center gap-2 whitespace-nowrap text-sm font-medium transition-[color,background-color,border-color,box-shadow]",
254
- variant === "row" && ["rounded-md border border-transparent px-3 py-1.5", focusRing, ixControlSoft, ixSelected],
261
+ variant === "row" && ["rounded-md border border-transparent px-3 py-1", focusRing, ixControlSoft, rowThumb],
255
262
  // The strip scrolls horizontally, which would clip an outer ring.
256
263
  // The border-b-2 slot stays transparent: the underline is ixTabUnderline's inset shadow.
257
264
  variant === "tabs" && ["shrink-0 rounded-none border-b-2 border-transparent -mb-px px-4 py-2", touchTarget, focusRingInset, ixTabUnderline],
@@ -166,7 +166,7 @@ export function RunRowShell({
166
166
  {description ? (
167
167
  <span
168
168
  className={cn(
169
- "hidden min-w-0 flex-1 truncate text-sm text-muted-foreground transition-colors group-hover:text-foreground sm:inline",
169
+ "min-w-0 flex-1 truncate text-sm text-muted-foreground transition-colors group-hover:text-foreground",
170
170
  descriptionMono && "font-mono",
171
171
  )}
172
172
  >