Stack a bar's readings, and stop widgets taking the phone sideways
A bar drew its nested reading on top of the outer one in --chart-5, which measures 2.53:1 against --primary and lost the 3:1 guideline for non-text. The readings now partition the fill end to end, up to three of them, in a token of their own: --primary-nested, the primary hue a few steps deeper, 3.14:1 light and 3.12:1 dark. It cannot also clear 3:1 against --muted — in dark those two are 5.82:1 apart and a colour 3:1 from both would need a 9:1 gap — so a segment is drawn inside a gutter of outer fill rather than ever bordering the track, which is what separates neighbours too, and what caps the count at three. A nested value larger than its outer used to spill onto the track; it is clamped. `inner` still reads as a single binding, so no dashboard needs migrating. On a phone, .widget-grid took its width from the widest thing any widget held — a truncating flex item still offers its whole unwrapped line as a min-content contribution — and a handful of widgets had no floor of their own: the uPlot legend is a table, a fieldset carries min-inline-size: min-content from the UA sheet, and buttons are whitespace-nowrap. Each is capped now. A widget's body scrolls rather than clipping, so long text stops painting over the title. Gauges and bars move between readings instead of jumping, and a segmented control slides one thumb rather than recolouring cells. The gauge arc is drawn whole and revealed by its dash, because `d` cannot be transitioned. UplotChart pushed new readings only when the point count changed, so once a rolling window was full a refetch left the old values on screen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Uq8mtNb97A7praJLyeEYgs
This commit is contained in:
@@ -28,6 +28,7 @@ import {
|
||||
SelectValue,
|
||||
} from "@/components/ui/select"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { MAX_SEGMENTS, type Segment, segmentsOf } from "./BarWidget"
|
||||
import { MAX_SERIES, refreshFor } from "./ChartWidget"
|
||||
import {
|
||||
CANVAS_PRESETS,
|
||||
@@ -227,6 +228,14 @@ export function WidgetPanel({
|
||||
|
||||
const series = seriesOf(widget)
|
||||
const setSeries = (next: Series[]) => set({ series: next })
|
||||
// A bar's nested readings. An empty row stands in for none, so an unnested
|
||||
// bar still offers the picker rather than only a button.
|
||||
const segments = segmentsOf(widget)
|
||||
const rows: Segment[] = segments.length ? segments : [{}]
|
||||
// Always written as a list; `inner_dtype` belonged to the single binding a
|
||||
// bar carried before it stacked, and goes with it.
|
||||
const setSegments = (next: Segment[]) =>
|
||||
set({ inner: next, inner_dtype: undefined })
|
||||
// The icon widget's mapping. Position is the row's identity, as with series.
|
||||
const rules = (cfg.rules ?? []) as {
|
||||
at?: unknown
|
||||
@@ -404,13 +413,54 @@ export function WidgetPanel({
|
||||
</div>
|
||||
|
||||
{widget.type === "bar" ? (
|
||||
<MessagePicker
|
||||
kind="bar"
|
||||
value={str(cfg.inner)}
|
||||
label="Nested bar"
|
||||
testId="widget-inner"
|
||||
onPick={(inner, inner_dtype) => set({ inner, inner_dtype })}
|
||||
/>
|
||||
<div className="grid gap-2">
|
||||
{rows.map((segment, index) => (
|
||||
<div
|
||||
// Position is the only identity a segment row has, as with series.
|
||||
key={`segment-${index}`}
|
||||
className="flex items-end gap-1.5"
|
||||
>
|
||||
<div className="min-w-0 flex-1">
|
||||
<MessagePicker
|
||||
kind="bar"
|
||||
value={segment.message ?? ""}
|
||||
label={index === 0 ? "Nested bar" : ""}
|
||||
testId={index === 0 ? "widget-inner" : undefined}
|
||||
onPick={(message, dtype) =>
|
||||
setSegments(
|
||||
rows.map((other, at) =>
|
||||
at === index ? { ...other, message, dtype } : other,
|
||||
),
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
className="text-muted-foreground"
|
||||
aria-label="Remove segment"
|
||||
onClick={() =>
|
||||
setSegments(rows.filter((_, at) => at !== index))
|
||||
}
|
||||
>
|
||||
<X />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
{rows.length < MAX_SEGMENTS ? (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-8 justify-self-start"
|
||||
onClick={() => setSegments([...rows, {}])}
|
||||
data-testid="add-segment"
|
||||
>
|
||||
<Plus />
|
||||
Add segment
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{widget.type === "chart" && !querying ? (
|
||||
|
||||
Reference in New Issue
Block a user