@stll/ui 0.68.0 → 0.69.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/dist/calendar/day-calendar.d.ts +22 -6
- package/dist/calendar/day-calendar.js +97 -26
- package/dist/calendar/resource-calendar.js +7 -9
- package/dist/calendar/topic-colors.d.ts +3 -0
- package/dist/calendar/topic-colors.js +19 -0
- package/dist/components/button.js +4 -4
- package/dist/components/composer-tokens.d.ts +6 -2
- package/dist/components/composer-tokens.js +6 -2
- package/dist/components/composer.d.ts +13 -0
- package/dist/components/composer.js +19 -3
- package/dist/components/court-filter.js +4 -4
- package/dist/components/date-picker-popover.js +3 -2
- package/dist/components/document-identity-badge.js +9 -4
- package/dist/components/file-input.js +3 -2
- package/dist/components/identity-text.js +4 -2
- package/dist/components/input-group.js +3 -2
- package/dist/components/landing.js +6 -5
- package/dist/components/list.d.ts +11 -5
- package/dist/components/list.js +28 -9
- package/dist/components/loader.js +4 -2
- package/dist/components/outline-rail.js +6 -4
- package/dist/components/run-state-mark.d.ts +8 -0
- package/dist/components/run-state-mark.js +30 -0
- package/dist/components/run-state-mark.native.d.ts +9 -0
- package/dist/components/run-state-mark.native.js +98 -0
- package/dist/components/run-state-mark.shared.d.ts +53 -0
- package/dist/components/run-state-mark.shared.js +45 -0
- package/dist/components/status-mark.js +6 -6
- package/dist/components/status-mark.shared.d.ts +12 -0
- package/dist/components/status-mark.shared.js +14 -0
- package/dist/components/table.d.ts +1 -1
- package/dist/components/table.js +13 -7
- package/dist/components/text.native.d.ts +3 -1
- package/dist/components/text.native.js +4 -2
- package/dist/components/truncated-text.d.ts +14 -3
- package/dist/components/truncated-text.js +21 -5
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/inspector/chrome.d.ts +3 -3
- package/dist/inspector/chrome.js +28 -17
- package/dist/kanban/column-band-header.js +3 -2
- package/dist/kanban/column-header.js +3 -2
- package/dist/kanban/sticky-lane.js +3 -2
- package/dist/lib/matter-colors.js +2 -5
- package/dist/lib/stable-hash.d.ts +2 -0
- package/dist/lib/stable-hash.js +8 -0
- package/dist/review/review-comment-card.js +7 -6
- package/dist/review/review-decision-actions.d.ts +8 -1
- package/dist/review/review-decision-actions.js +13 -4
- package/dist/styles/theme.css +24 -0
- package/package.json +11 -3
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { DayCalendarGranularity, DayCalendarRange } from "./day-calendar.logic.js";
|
|
2
2
|
import { ReactElement, ReactNode } from "react";
|
|
3
3
|
/**
|
|
4
|
-
* A block paints in its matter's colour; in a topic's
|
|
5
|
-
* from the
|
|
6
|
-
* edge; or in the neutral
|
|
4
|
+
* A block paints in its matter's colour, with the matter's edge; in a topic's
|
|
5
|
+
* colour, from the separate low-chroma topic set keyed by a stable topic key
|
|
6
|
+
* and without an edge, so it never reads as a matter; or in the neutral
|
|
7
|
+
* surface tone.
|
|
7
8
|
*/
|
|
8
9
|
export type DayCalendarTone = {
|
|
9
10
|
color: string | null;
|
|
@@ -21,22 +22,37 @@ export type DayCalendarTone = {
|
|
|
21
22
|
* button the caller acts on.
|
|
22
23
|
*/
|
|
23
24
|
export type DayCalendarEntryState = "draft" | "final" | "suggestion";
|
|
25
|
+
/**
|
|
26
|
+
* What a block shows on hover, for example the apps a `mixed` block merged.
|
|
27
|
+
* `actions` holds controls: it is a popover that also opens on focus or
|
|
28
|
+
* press and stays open while the pointer is on it, so its block is a button.
|
|
29
|
+
*/
|
|
30
|
+
export type DayCalendarBlockTooltip = {
|
|
31
|
+
content: ReactNode;
|
|
32
|
+
type: "actions";
|
|
33
|
+
} | {
|
|
34
|
+
content: ReactNode;
|
|
35
|
+
type: "text";
|
|
36
|
+
};
|
|
24
37
|
type DayCalendarBlockFields = DayCalendarRange & {
|
|
25
38
|
accessibleLabel: string;
|
|
26
39
|
caption?: ReactNode;
|
|
27
40
|
id: string;
|
|
28
41
|
title: ReactNode;
|
|
29
42
|
tone: DayCalendarTone;
|
|
30
|
-
|
|
31
|
-
tooltip?: ReactNode;
|
|
43
|
+
tooltip?: DayCalendarBlockTooltip;
|
|
32
44
|
};
|
|
33
45
|
export type DayCalendarBlock = (DayCalendarBlockFields & {
|
|
34
46
|
kind: "entry";
|
|
35
47
|
state: Exclude<DayCalendarEntryState, "suggestion">;
|
|
36
|
-
}) | (DayCalendarBlockFields & {
|
|
48
|
+
}) | (Omit<DayCalendarBlockFields, "tooltip"> & {
|
|
37
49
|
kind: "entry";
|
|
38
50
|
onActivate: () => void;
|
|
39
51
|
state: "suggestion";
|
|
52
|
+
/** A suggestion is already a button; its hover only describes it. */
|
|
53
|
+
tooltip?: Extract<DayCalendarBlockTooltip, {
|
|
54
|
+
type: "text";
|
|
55
|
+
}>;
|
|
40
56
|
}) | (DayCalendarBlockFields & {
|
|
41
57
|
kind: "mixed";
|
|
42
58
|
}) | (DayCalendarBlockFields & {
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { Popover, PopoverContent as PopoverPopup, PopoverTrigger } from "../components/popover.js";
|
|
1
2
|
import { Tooltip, TooltipContent as TooltipPopup, TooltipTrigger } from "../components/tooltip.js";
|
|
2
|
-
import {
|
|
3
|
+
import { TruncatedText } from "../components/truncated-text.js";
|
|
4
|
+
import { resolveMatterColor } from "../lib/matter-colors.js";
|
|
3
5
|
import { cn } from "../lib/utils.js";
|
|
4
6
|
import { DAY_CALENDAR_SLOT_HEIGHT_PX, boundsHeight, extendRange, getDayBounds, getHourTicks, isRepeatedWallTime, layoutOverlaps, moveRange, offsetToTime, rangeFromDrag, slotAt, timeToOffset } from "./day-calendar.logic.js";
|
|
7
|
+
import { getTopicColor } from "./topic-colors.js";
|
|
5
8
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
9
|
import { useState } from "react";
|
|
7
10
|
import { panic } from "better-result";
|
|
@@ -81,7 +84,7 @@ const DayCalendar = ({ ariaLabel, columns, date, granularity, hours, locale, now
|
|
|
81
84
|
const nowVisible = nowMs >= bounds.startMs && nowMs < bounds.endMs;
|
|
82
85
|
const timeAt = (event) => offsetToTime(event.clientY - event.currentTarget.getBoundingClientRect().top, grid);
|
|
83
86
|
const handlePointerDown = (columnId, event) => {
|
|
84
|
-
if (event.button !== 0) return;
|
|
87
|
+
if (event.button !== 0 || !isWithinColumn(event)) return;
|
|
85
88
|
event.currentTarget.setPointerCapture(event.pointerId);
|
|
86
89
|
const anchorMs = timeAt(event);
|
|
87
90
|
setDraft({
|
|
@@ -127,6 +130,7 @@ const DayCalendar = ({ ariaLabel, columns, date, granularity, hours, locale, now
|
|
|
127
130
|
return slotAt(nowVisible ? nowMs : bounds.startMs, grid);
|
|
128
131
|
};
|
|
129
132
|
const handleKeyDown = (columnId, event) => {
|
|
133
|
+
if (event.target !== event.currentTarget) return;
|
|
130
134
|
const current = draft?.columnId === columnId ? draft : null;
|
|
131
135
|
switch (event.key) {
|
|
132
136
|
case "ArrowDown":
|
|
@@ -186,11 +190,11 @@ const DayCalendar = ({ ariaLabel, columns, date, granularity, hours, locale, now
|
|
|
186
190
|
columns.map((column, index) => /* @__PURE__ */ jsxs("header", {
|
|
187
191
|
className: cn("bg-card border-border text-muted-foreground sticky top-0 z-30 flex min-w-0 items-center gap-2 border-s border-b px-3 py-2.5 text-xs font-medium", index === columns.length - 1 && "rounded-se-xl"),
|
|
188
192
|
"data-slot": "day-calendar-column-header",
|
|
189
|
-
children: [/* @__PURE__ */ jsx(
|
|
190
|
-
className: "
|
|
193
|
+
children: [/* @__PURE__ */ jsx(TruncatedText, {
|
|
194
|
+
className: "shrink-0",
|
|
191
195
|
children: column.label
|
|
192
|
-
}), column.badge === void 0 ? null : /* @__PURE__ */ jsx(
|
|
193
|
-
className: "bg-muted text-muted-foreground text-2xs
|
|
196
|
+
}), column.badge === void 0 ? null : /* @__PURE__ */ jsx(TruncatedText, {
|
|
197
|
+
className: "bg-muted text-muted-foreground text-2xs rounded-full px-2 py-0.5",
|
|
194
198
|
children: column.badge
|
|
195
199
|
})]
|
|
196
200
|
}, column.id)),
|
|
@@ -306,6 +310,11 @@ const ColumnBlocks = ({ blocks, grid }) => {
|
|
|
306
310
|
}, block.id);
|
|
307
311
|
});
|
|
308
312
|
};
|
|
313
|
+
/** A block's popover is portalled out of the column, yet its events still
|
|
314
|
+
* bubble to the column through React. */
|
|
315
|
+
function isWithinColumn({ currentTarget, target }) {
|
|
316
|
+
return target instanceof Node && currentTarget.contains(target);
|
|
317
|
+
}
|
|
309
318
|
/** Below this height a block puts its title and caption on one line. */
|
|
310
319
|
const TWO_LINE_BLOCK_MIN_PX = 40;
|
|
311
320
|
/** A brief block paints at least this tall, unless the grid ends sooner. */
|
|
@@ -329,12 +338,15 @@ const DayCalendarBlockView = ({ block, grid, lane, range }) => {
|
|
|
329
338
|
insetBlockStart: top,
|
|
330
339
|
insetInlineStart: `${String(lane.lane * 100 / lane.laneCount)}%`
|
|
331
340
|
};
|
|
332
|
-
const
|
|
333
|
-
const
|
|
334
|
-
|
|
341
|
+
const oneLine = blockSize < TWO_LINE_BLOCK_MIN_PX;
|
|
342
|
+
const className = cn("text-foreground @container flex h-full w-full min-w-0 overflow-hidden rounded-md px-2.5 text-start text-xs", oneLine ? "flex-row items-center gap-2" : "flex-col gap-0.5 py-1", BLOCK_KIND_CLASSES[block.kind], block.kind === "entry" && ENTRY_STATE_CLASSES[block.state], block.tone.type === "matter" && "border-s-[3px]", block.tone.type === "neutral" && "bg-muted");
|
|
343
|
+
const content = blockSize < 16 ? null : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(TruncatedText, {
|
|
344
|
+
className: cn("font-medium", oneLine ? "shrink" : "shrink-0"),
|
|
345
|
+
hint: false,
|
|
335
346
|
children: block.title
|
|
336
|
-
}), block.caption === void 0 ? null : /* @__PURE__ */ jsx(
|
|
337
|
-
className: "text-muted-foreground
|
|
347
|
+
}), block.caption === void 0 ? null : /* @__PURE__ */ jsx(TruncatedText, {
|
|
348
|
+
className: cn("text-muted-foreground tabular-nums", oneLine && "shrink-[999]"),
|
|
349
|
+
hint: false,
|
|
338
350
|
children: block.caption
|
|
339
351
|
})] });
|
|
340
352
|
const shared = {
|
|
@@ -345,9 +357,68 @@ const DayCalendarBlockView = ({ block, grid, lane, range }) => {
|
|
|
345
357
|
"data-slot": "day-calendar-block",
|
|
346
358
|
style: blockSurfaceStyle(block)
|
|
347
359
|
};
|
|
360
|
+
return /* @__PURE__ */ jsx("div", {
|
|
361
|
+
className: cn("absolute px-1.5 py-px", block.tooltip === void 0 && !(block.kind === "entry" && block.state === "suggestion") && "pointer-events-none"),
|
|
362
|
+
style: placement,
|
|
363
|
+
children: /* @__PURE__ */ jsx(BlockSurface, {
|
|
364
|
+
block,
|
|
365
|
+
content,
|
|
366
|
+
shared
|
|
367
|
+
})
|
|
368
|
+
});
|
|
369
|
+
};
|
|
370
|
+
/** A block whose hover holds actions answers hover, focus and an open
|
|
371
|
+
* popover like a suggestion does. */
|
|
372
|
+
const ACTIONS_BLOCK_CLASSES = "focus-visible:ring-ring data-popup-open:ring-ring/50 hover:ring-ring/50 outline-none hover:ring-1 focus-visible:ring-2 data-popup-open:ring-1";
|
|
373
|
+
/** A block's actions, opened by hover, focus or press. Once something in
|
|
374
|
+
* them is pressed they stay open until dismissed: a press can move the
|
|
375
|
+
* layout under a pointer that never moved (a notice above the calendar
|
|
376
|
+
* goes away), and a hover popover would close as if the pointer had left,
|
|
377
|
+
* taking the action's confirmation with it. */
|
|
378
|
+
const ActionsPopover = ({ children, trigger }) => {
|
|
379
|
+
const [open, setOpen] = useState(false);
|
|
380
|
+
const [held, setHeld] = useState(false);
|
|
381
|
+
return /* @__PURE__ */ jsxs(Popover, {
|
|
382
|
+
onOpenChange: (next, { reason }) => {
|
|
383
|
+
if (!next && held && reason === "trigger-hover") return;
|
|
384
|
+
setOpen(next);
|
|
385
|
+
if (!next) setHeld(false);
|
|
386
|
+
},
|
|
387
|
+
open,
|
|
388
|
+
children: [/* @__PURE__ */ jsx(PopoverTrigger, {
|
|
389
|
+
delay: 300,
|
|
390
|
+
openOnHover: true,
|
|
391
|
+
render: trigger
|
|
392
|
+
}), /* @__PURE__ */ jsx(PopoverPopup, {
|
|
393
|
+
className: "max-w-72",
|
|
394
|
+
onPointerDown: () => setHeld(true),
|
|
395
|
+
side: "top",
|
|
396
|
+
tooltipStyle: true,
|
|
397
|
+
children
|
|
398
|
+
})]
|
|
399
|
+
});
|
|
400
|
+
};
|
|
401
|
+
/** A block whose hover holds actions is a button that opens them; a
|
|
402
|
+
* suggestion is a button the caller acts on; any other block is read. */
|
|
403
|
+
const BlockSurface = ({ block, content, shared }) => {
|
|
404
|
+
const state = block.kind === "entry" ? block.state : void 0;
|
|
405
|
+
if (block.tooltip?.type === "actions") {
|
|
406
|
+
const trigger = /* @__PURE__ */ jsx("button", {
|
|
407
|
+
...shared,
|
|
408
|
+
className: cn(shared.className, ACTIONS_BLOCK_CLASSES),
|
|
409
|
+
"data-state": state,
|
|
410
|
+
onPointerDown: (event) => event.stopPropagation(),
|
|
411
|
+
type: "button",
|
|
412
|
+
children: content
|
|
413
|
+
});
|
|
414
|
+
return /* @__PURE__ */ jsx(ActionsPopover, {
|
|
415
|
+
trigger,
|
|
416
|
+
children: block.tooltip.content
|
|
417
|
+
});
|
|
418
|
+
}
|
|
348
419
|
const surface = block.kind === "entry" && block.state === "suggestion" ? /* @__PURE__ */ jsx("button", {
|
|
349
420
|
...shared,
|
|
350
|
-
"data-state":
|
|
421
|
+
"data-state": state,
|
|
351
422
|
onClick: block.onActivate,
|
|
352
423
|
onKeyDown: (event) => event.stopPropagation(),
|
|
353
424
|
onPointerDown: (event) => event.stopPropagation(),
|
|
@@ -355,37 +426,37 @@ const DayCalendarBlockView = ({ block, grid, lane, range }) => {
|
|
|
355
426
|
children: content
|
|
356
427
|
}) : /* @__PURE__ */ jsx("article", {
|
|
357
428
|
...shared,
|
|
358
|
-
"data-state":
|
|
429
|
+
"data-state": state,
|
|
359
430
|
children: content
|
|
360
431
|
});
|
|
361
|
-
return /* @__PURE__ */ jsx(
|
|
362
|
-
className:
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
className: "max-w-72",
|
|
366
|
-
children: block.tooltip
|
|
367
|
-
})] })
|
|
368
|
-
});
|
|
432
|
+
return block.tooltip === void 0 ? surface : /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, { render: surface }), /* @__PURE__ */ jsx(TooltipPopup, {
|
|
433
|
+
className: "max-w-72",
|
|
434
|
+
children: block.tooltip.content
|
|
435
|
+
})] });
|
|
369
436
|
};
|
|
370
437
|
/** A suggestion's tint is half an entry's, so proposed time reads lighter. */
|
|
371
438
|
const TONE_TINT_PERCENT = {
|
|
372
439
|
entry: 16,
|
|
373
440
|
suggestion: 8
|
|
374
441
|
};
|
|
375
|
-
/** A topic is
|
|
376
|
-
*
|
|
377
|
-
const TOPIC_TINT_PERCENT =
|
|
442
|
+
/** A topic swatch is already muted, so it mixes in stronger than a matter's
|
|
443
|
+
* to stay visible; the result is still far less saturated. */
|
|
444
|
+
const TOPIC_TINT_PERCENT = 30;
|
|
378
445
|
function blockSurfaceStyle(block) {
|
|
379
446
|
const tone = block.tone;
|
|
380
447
|
const suggestion = block.kind === "entry" && block.state === "suggestion";
|
|
381
448
|
switch (tone.type) {
|
|
382
449
|
case "neutral": return { backgroundColor: suggestion ? "transparent" : void 0 };
|
|
383
|
-
case "topic": return {
|
|
450
|
+
case "topic": return {
|
|
451
|
+
"--day-calendar-tone": getTopicColor(tone.key),
|
|
452
|
+
backgroundColor: `color-mix(in srgb, var(--day-calendar-tone) ${String(TOPIC_TINT_PERCENT)}%, var(--card))`
|
|
453
|
+
};
|
|
384
454
|
case "matter": {
|
|
385
455
|
const color = resolveMatterColor(tone.matterId, tone.color);
|
|
386
456
|
const tint = suggestion ? TONE_TINT_PERCENT.suggestion : TONE_TINT_PERCENT.entry;
|
|
387
457
|
return {
|
|
388
|
-
|
|
458
|
+
"--day-calendar-tone": color,
|
|
459
|
+
backgroundColor: `color-mix(in srgb, var(--day-calendar-tone) ${String(tint)}%, var(--card))`,
|
|
389
460
|
borderColor: suggestion ? color : void 0,
|
|
390
461
|
borderInlineStartColor: color
|
|
391
462
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { TruncatedText } from "../components/truncated-text.js";
|
|
1
2
|
import { cn } from "../lib/utils.js";
|
|
2
3
|
import { CalendarCell, CalendarEntryButton, CalendarEntrySurface, CalendarHeaderCell, CalendarHeaderRow } from "./primitives.js";
|
|
3
4
|
import { assertConsecutiveCalendarDates, layoutResourceCalendarEntries, nextCalendarDate } from "./resource-calendar.logic.js";
|
|
@@ -99,11 +100,11 @@ const ResourceCalendar = ({ ariaLabel, columns, empty, entries, onSelectEntry, r
|
|
|
99
100
|
className: "bg-card sticky start-0 z-20 border-b-0 px-4 py-3",
|
|
100
101
|
id: resourceHeaderId,
|
|
101
102
|
role: "rowheader",
|
|
102
|
-
children: [/* @__PURE__ */ jsx(
|
|
103
|
-
className: "
|
|
103
|
+
children: [/* @__PURE__ */ jsx(TruncatedText, {
|
|
104
|
+
className: "text-sm font-semibold",
|
|
104
105
|
children: resource.label
|
|
105
|
-
}), resource.meta === void 0 ? null : /* @__PURE__ */ jsx(
|
|
106
|
-
className: "text-muted-foreground mt-1
|
|
106
|
+
}), resource.meta === void 0 ? null : /* @__PURE__ */ jsx(TruncatedText, {
|
|
107
|
+
className: "text-muted-foreground mt-1 text-xs",
|
|
107
108
|
children: resource.meta
|
|
108
109
|
})]
|
|
109
110
|
}), columns.map((column, columnIndex) => {
|
|
@@ -146,11 +147,8 @@ const ResourceCalendarEntryView = ({ entry, labelledBy, onSelectEntry, placement
|
|
|
146
147
|
insetInlineStart: 0,
|
|
147
148
|
inlineSize: `calc(${String(placement.span)} * 100%)`
|
|
148
149
|
};
|
|
149
|
-
const content = /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(
|
|
150
|
-
className: "
|
|
151
|
-
children: entry.label
|
|
152
|
-
}), entry.meta === void 0 ? null : /* @__PURE__ */ jsx("span", {
|
|
153
|
-
className: "block truncate opacity-75",
|
|
150
|
+
const content = /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(TruncatedText, { children: entry.label }), entry.meta === void 0 ? null : /* @__PURE__ */ jsx(TruncatedText, {
|
|
151
|
+
className: "opacity-75",
|
|
154
152
|
children: entry.meta
|
|
155
153
|
})] });
|
|
156
154
|
return /* @__PURE__ */ jsx("div", {
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare const TOPIC_SWATCHES: readonly ["--topic-sand", "--topic-sage", "--topic-mist", "--topic-heather", "--topic-clay", "--topic-slate"];
|
|
2
|
+
export declare const getTopicSwatch: (key: string) => "--topic-clay" | "--topic-heather" | "--topic-mist" | "--topic-sage" | "--topic-sand" | "--topic-slate";
|
|
3
|
+
export declare const getTopicColor: (key: string) => string;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { stableHash } from "../lib/stable-hash.js";
|
|
2
|
+
// src/calendar/topic-colors.ts
|
|
3
|
+
/**
|
|
4
|
+
* Swatches for time no matter claimed yet, such as one document's or window's
|
|
5
|
+
* stretch of a day. A separate low-chroma set (`--topic-*` in theme.css), so
|
|
6
|
+
* a topic is never painted in a colour a matter can have.
|
|
7
|
+
*/
|
|
8
|
+
const DEFAULT_TOPIC_SWATCH = "--topic-sand";
|
|
9
|
+
const TOPIC_SWATCHES = [
|
|
10
|
+
DEFAULT_TOPIC_SWATCH,
|
|
11
|
+
"--topic-sage",
|
|
12
|
+
"--topic-mist",
|
|
13
|
+
"--topic-heather",
|
|
14
|
+
"--topic-clay",
|
|
15
|
+
"--topic-slate"
|
|
16
|
+
];
|
|
17
|
+
const getTopicSwatch = (key) => TOPIC_SWATCHES[stableHash(key) % TOPIC_SWATCHES.length] ?? DEFAULT_TOPIC_SWATCH;
|
|
18
|
+
const getTopicColor = (key) => `var(${getTopicSwatch(key)})`;
|
|
19
|
+
export { TOPIC_SWATCHES, getTopicColor, getTopicSwatch };
|
|
@@ -6,6 +6,7 @@ import { cn } from "../lib/utils.js";
|
|
|
6
6
|
import { BUTTON_PLAIN_VARIANT, BUTTON_VARIANT_TOKENS } from "./button.shared.js";
|
|
7
7
|
import { Loader } from "./loader.js";
|
|
8
8
|
import { renderTooltipTrigger } from "./tooltip-trigger-helper.js";
|
|
9
|
+
import { TruncatedText } from "./truncated-text.js";
|
|
9
10
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
10
11
|
import { Fragment as Fragment$1, isValidElement } from "react";
|
|
11
12
|
import { mergeProps } from "@base-ui/react/merge-props";
|
|
@@ -156,13 +157,12 @@ const BUTTON_SIZE_CONTENT = {
|
|
|
156
157
|
* A button never grows past its container (`max-w-full` on every variant).
|
|
157
158
|
* A one-line text label is cut to fit with an ellipsis; the button's
|
|
158
159
|
* accessible name keeps the whole text, and `tooltip` can show it. The label
|
|
159
|
-
* is a
|
|
160
|
+
* is a `TruncatedText` without its own hint: a tooltip root in every button's
|
|
160
161
|
* label would cost every screen with many buttons. `layout="wrap"` lets a
|
|
161
162
|
* label take several lines instead. An inline button is a word in a sentence
|
|
162
163
|
* and a plain button lays out its own content, so neither has a one-line
|
|
163
164
|
* label to cut.
|
|
164
165
|
*/
|
|
165
|
-
const BUTTON_LABEL_CLASS = "block min-w-0 truncate";
|
|
166
166
|
const cutsLabel = ({ layout, size, variant }) => layout === void 0 && variant !== "plain" && variant !== "link" && size !== "inline" && BUTTON_SIZE_CONTENT[size ?? BUTTON_DEFAULT_SIZE] === BUTTON_CONTENT.text;
|
|
167
167
|
/**
|
|
168
168
|
* Each run of adjacent text among the children, cut to the button's width.
|
|
@@ -178,9 +178,9 @@ const containedLabel = (children) => {
|
|
|
178
178
|
let run = [];
|
|
179
179
|
const closeRun = () => {
|
|
180
180
|
if (run.length > 0) {
|
|
181
|
-
parts.push(/* @__PURE__ */ jsx(
|
|
182
|
-
className: BUTTON_LABEL_CLASS,
|
|
181
|
+
parts.push(/* @__PURE__ */ jsx(TruncatedText, {
|
|
183
182
|
"data-slot": "button-label",
|
|
183
|
+
hint: false,
|
|
184
184
|
children: run
|
|
185
185
|
}, `label:${String(parts.length)}`));
|
|
186
186
|
run = [];
|
|
@@ -49,8 +49,12 @@ export declare const COMPOSER_TEXT_CLASS = "text-sm leading-5";
|
|
|
49
49
|
export declare const COMPOSER_LARGE_TEXT_WELL_CLASS = "ps-3 pe-3 pt-2 pb-1";
|
|
50
50
|
/** The `large` editable element's stature: three lines before it grows. */
|
|
51
51
|
export declare const COMPOSER_LARGE_EDITOR_CLASS = "min-h-15";
|
|
52
|
-
/**
|
|
53
|
-
|
|
52
|
+
/**
|
|
53
|
+
* The placeholder painted over an empty editor, in either stature.
|
|
54
|
+
* `ComposerPlaceholder` cuts its text through TruncatedText; a row that wears
|
|
55
|
+
* this class around other content cuts its own text the same way.
|
|
56
|
+
*/
|
|
57
|
+
export declare const COMPOSER_PLACEHOLDER_CLASS = "text-foreground-placeholder pointer-events-none absolute text-sm";
|
|
54
58
|
/** The `large` action row under the text well: leading controls, trailing send. */
|
|
55
59
|
export declare const COMPOSER_LARGE_ACTION_ROW_CLASS = "flex items-center justify-end gap-0.5 px-1.5 pb-1.5";
|
|
56
60
|
/** The leading group of the action row (the (+) menu, or a scope control). */
|
|
@@ -50,8 +50,12 @@ const COMPOSER_TEXT_CLASS = "text-sm leading-5";
|
|
|
50
50
|
const COMPOSER_LARGE_TEXT_WELL_CLASS = "ps-3 pe-3 pt-2 pb-1";
|
|
51
51
|
/** The `large` editable element's stature: three lines before it grows. */
|
|
52
52
|
const COMPOSER_LARGE_EDITOR_CLASS = "min-h-15";
|
|
53
|
-
/**
|
|
54
|
-
|
|
53
|
+
/**
|
|
54
|
+
* The placeholder painted over an empty editor, in either stature.
|
|
55
|
+
* `ComposerPlaceholder` cuts its text through TruncatedText; a row that wears
|
|
56
|
+
* this class around other content cuts its own text the same way.
|
|
57
|
+
*/
|
|
58
|
+
const COMPOSER_PLACEHOLDER_CLASS = "text-foreground-placeholder pointer-events-none absolute text-sm";
|
|
55
59
|
/** The `large` action row under the text well: leading controls, trailing send. */
|
|
56
60
|
const COMPOSER_LARGE_ACTION_ROW_CLASS = "flex items-center justify-end gap-0.5 px-1.5 pb-1.5";
|
|
57
61
|
/** The leading group of the action row (the (+) menu, or a scope control). */
|
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
import { COMPOSER_BOX_ANONYMIZED_CLASS, COMPOSER_BOX_CLASS, COMPOSER_BOX_FOCUS_CLASS, COMPOSER_COMPACT_ROW_CLASS, COMPOSER_COMPACT_TEXT_CELL_CLASS, COMPOSER_CONTROL_BUTTON_SIZE, COMPOSER_LARGE_ACTION_ROW_CLASS, COMPOSER_LARGE_EDITOR_CLASS, COMPOSER_LARGE_TEXT_WELL_CLASS, COMPOSER_LEADING_GROUP_CLASS, COMPOSER_PICKER_TRIGGER_CLASS, COMPOSER_PLACEHOLDER_CLASS, COMPOSER_SEND_BUTTON_CLASS, COMPOSER_TEXT_CLASS } from "./composer-tokens.js";
|
|
2
2
|
import { ReactNode } from "react";
|
|
3
|
+
/** Where the placeholder sits over the editor, in each stature. */
|
|
4
|
+
declare const COMPOSER_PLACEHOLDER_POSITION_CLASS: {
|
|
5
|
+
readonly compact: "start-0 end-0 top-1/2 -translate-y-1/2";
|
|
6
|
+
readonly large: "start-3 end-3 top-2";
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* The placeholder painted over an empty editor, cut to one line. It takes no
|
|
10
|
+
* pointer and is hidden from assistive tech, so it shows no hint.
|
|
11
|
+
*/
|
|
12
|
+
export declare const ComposerPlaceholder: ({ children, stature }: {
|
|
13
|
+
children: ReactNode;
|
|
14
|
+
stature: keyof typeof COMPOSER_PLACEHOLDER_POSITION_CLASS;
|
|
15
|
+
}) => import("react").JSX.Element;
|
|
3
16
|
type ComposerStatusRowProps = {
|
|
4
17
|
/** Left cluster: matter picker, web-search / anonymize toggles, ... */
|
|
5
18
|
start?: ReactNode | undefined;
|
|
@@ -3,8 +3,24 @@ import { ChevronDownIcon } from "../icons.js";
|
|
|
3
3
|
import { cn } from "../lib/utils.js";
|
|
4
4
|
import { COMPOSER_BOX_ANONYMIZED_CLASS, COMPOSER_BOX_CLASS, COMPOSER_BOX_FOCUS_CLASS, COMPOSER_COMPACT_ROW_CLASS, COMPOSER_COMPACT_TEXT_CELL_CLASS, COMPOSER_CONTROL_BUTTON_SIZE, COMPOSER_LARGE_ACTION_ROW_CLASS, COMPOSER_LARGE_EDITOR_CLASS, COMPOSER_LARGE_TEXT_WELL_CLASS, COMPOSER_LEADING_GROUP_CLASS, COMPOSER_PICKER_TRIGGER_CLASS, COMPOSER_PLACEHOLDER_CLASS, COMPOSER_SEND_BUTTON_CLASS, COMPOSER_TEXT_CLASS } from "./composer-tokens.js";
|
|
5
5
|
import { DropdownMenu as Menu, DropdownMenuContent as MenuPopup, DropdownMenuRadioGroup as MenuRadioGroup, DropdownMenuRadioItem as MenuRadioItem, DropdownMenuTrigger as MenuTrigger } from "./menu.js";
|
|
6
|
+
import { TruncatedText } from "./truncated-text.js";
|
|
6
7
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
8
|
// src/components/composer.tsx
|
|
9
|
+
/** Where the placeholder sits over the editor, in each stature. */
|
|
10
|
+
const COMPOSER_PLACEHOLDER_POSITION_CLASS = {
|
|
11
|
+
compact: "start-0 end-0 top-1/2 -translate-y-1/2",
|
|
12
|
+
large: "start-3 end-3 top-2"
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* The placeholder painted over an empty editor, cut to one line. It takes no
|
|
16
|
+
* pointer and is hidden from assistive tech, so it shows no hint.
|
|
17
|
+
*/
|
|
18
|
+
const ComposerPlaceholder = ({ children, stature }) => /* @__PURE__ */ jsx(TruncatedText, {
|
|
19
|
+
"aria-hidden": "true",
|
|
20
|
+
className: cn(COMPOSER_PLACEHOLDER_CLASS, COMPOSER_PLACEHOLDER_POSITION_CLASS[stature]),
|
|
21
|
+
hint: false,
|
|
22
|
+
children
|
|
23
|
+
});
|
|
8
24
|
/**
|
|
9
25
|
* The slim status row rendered beneath a composer box: a `text-xs` row with
|
|
10
26
|
* a start cluster and an end slot pinned to the far edge. Shared by every
|
|
@@ -54,8 +70,8 @@ const ComposerPicker = ({ ariaLabel, icon, onChange, options, value }) => {
|
|
|
54
70
|
className: "flex size-3 shrink-0 items-center justify-center [&>svg]:size-3",
|
|
55
71
|
children: icon
|
|
56
72
|
}),
|
|
57
|
-
/* @__PURE__ */ jsx(
|
|
58
|
-
|
|
73
|
+
/* @__PURE__ */ jsx(TruncatedText, {
|
|
74
|
+
hint: false,
|
|
59
75
|
children: current?.label ?? value
|
|
60
76
|
}),
|
|
61
77
|
/* @__PURE__ */ jsx(ChevronDownIcon, {
|
|
@@ -76,4 +92,4 @@ const ComposerPicker = ({ ariaLabel, icon, onChange, options, value }) => {
|
|
|
76
92
|
})
|
|
77
93
|
})] });
|
|
78
94
|
};
|
|
79
|
-
export { COMPOSER_BOX_ANONYMIZED_CLASS, COMPOSER_BOX_CLASS, COMPOSER_BOX_FOCUS_CLASS, COMPOSER_COMPACT_ROW_CLASS, COMPOSER_COMPACT_TEXT_CELL_CLASS, COMPOSER_CONTROL_BUTTON_SIZE, COMPOSER_LARGE_ACTION_ROW_CLASS, COMPOSER_LARGE_EDITOR_CLASS, COMPOSER_LARGE_TEXT_WELL_CLASS, COMPOSER_LEADING_GROUP_CLASS, COMPOSER_PICKER_TRIGGER_CLASS, COMPOSER_PLACEHOLDER_CLASS, COMPOSER_SEND_BUTTON_CLASS, COMPOSER_TEXT_CLASS, ComposerPicker, ComposerStatusRow };
|
|
95
|
+
export { COMPOSER_BOX_ANONYMIZED_CLASS, COMPOSER_BOX_CLASS, COMPOSER_BOX_FOCUS_CLASS, COMPOSER_COMPACT_ROW_CLASS, COMPOSER_COMPACT_TEXT_CELL_CLASS, COMPOSER_CONTROL_BUTTON_SIZE, COMPOSER_LARGE_ACTION_ROW_CLASS, COMPOSER_LARGE_EDITOR_CLASS, COMPOSER_LARGE_TEXT_WELL_CLASS, COMPOSER_LEADING_GROUP_CLASS, COMPOSER_PICKER_TRIGGER_CLASS, COMPOSER_PLACEHOLDER_CLASS, COMPOSER_SEND_BUTTON_CLASS, COMPOSER_TEXT_CLASS, ComposerPicker, ComposerPlaceholder, ComposerStatusRow };
|
|
@@ -7,6 +7,7 @@ import { DirectionalIcon } from "./directional-icon.js";
|
|
|
7
7
|
import { Field, FieldLabel } from "./field.js";
|
|
8
8
|
import { ScrollArea } from "./scroll-area.js";
|
|
9
9
|
import { SearchField } from "./search-field.js";
|
|
10
|
+
import { TruncatedText } from "./truncated-text.js";
|
|
10
11
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
11
12
|
import { useId, useState } from "react";
|
|
12
13
|
// src/components/court-filter.tsx
|
|
@@ -56,10 +57,9 @@ const SingleCourtGroup = ({ group, options }) => {
|
|
|
56
57
|
if (checked) options.onChange(void 0);
|
|
57
58
|
}
|
|
58
59
|
}),
|
|
59
|
-
/* @__PURE__ */ jsx(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
title: label ?? value,
|
|
60
|
+
/* @__PURE__ */ jsx(TruncatedText, {
|
|
61
|
+
className: cn("text-foreground flex-1", checked && "font-medium"),
|
|
62
|
+
render: /* @__PURE__ */ jsx(BidiText, { as: "span" }),
|
|
63
63
|
children: label ?? value
|
|
64
64
|
}),
|
|
65
65
|
badgeText !== void 0 && /* @__PURE__ */ jsx("bdi", {
|
|
@@ -9,6 +9,7 @@ import { DirectionalIcon } from "./directional-icon.js";
|
|
|
9
9
|
import { Input } from "./input.js";
|
|
10
10
|
import { Popover, PopoverContent as PopoverPopup, PopoverTrigger } from "./popover.js";
|
|
11
11
|
import { TimeField } from "./time-field.js";
|
|
12
|
+
import { TruncatedText } from "./truncated-text.js";
|
|
12
13
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
13
14
|
import { useCallback, useId, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
14
15
|
import { Result, panic } from "better-result";
|
|
@@ -465,8 +466,8 @@ const DatePickerPopoverContent = (props) => {
|
|
|
465
466
|
}),
|
|
466
467
|
children: [
|
|
467
468
|
showIcon && /* @__PURE__ */ jsx(CalendarIcon, { className: "size-3.5 shrink-0" }),
|
|
468
|
-
/* @__PURE__ */ jsx(
|
|
469
|
-
className: "
|
|
469
|
+
/* @__PURE__ */ jsx(TruncatedText, {
|
|
470
|
+
className: "flex-1 text-start",
|
|
470
471
|
id: labelledBy ? displayValueId : void 0,
|
|
471
472
|
children: displayLabel
|
|
472
473
|
}),
|
|
@@ -3,6 +3,7 @@ import { cn } from "../lib/utils.js";
|
|
|
3
3
|
import { CourtBadge } from "./court-badge.js";
|
|
4
4
|
import { COURT_TIER_WEIGHT, caseNumberIdentityLabels, statuteIdentityLabels } from "./document-identity-badge.logic.js";
|
|
5
5
|
import { Tooltip, TooltipContent as TooltipPopup, TooltipTrigger } from "./tooltip.js";
|
|
6
|
+
import { TruncatedText } from "./truncated-text.js";
|
|
6
7
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
8
|
import { panic } from "better-result";
|
|
8
9
|
// src/components/document-identity-badge.tsx
|
|
@@ -58,11 +59,15 @@ function referenceLabelMark(labels, kind) {
|
|
|
58
59
|
"data-slot": "document-identity-badge",
|
|
59
60
|
"data-kind": kind,
|
|
60
61
|
dir: "ltr",
|
|
61
|
-
children: [/* @__PURE__ */ jsx(
|
|
62
|
-
className:
|
|
62
|
+
children: [/* @__PURE__ */ jsx(TruncatedText, {
|
|
63
|
+
className: classes.short,
|
|
64
|
+
hint: false,
|
|
65
|
+
render: /* @__PURE__ */ jsx("bdi", {}),
|
|
63
66
|
children: labels.short
|
|
64
|
-
}), /* @__PURE__ */ jsx(
|
|
65
|
-
className:
|
|
67
|
+
}), /* @__PURE__ */ jsx(TruncatedText, {
|
|
68
|
+
className: classes.long,
|
|
69
|
+
hint: false,
|
|
70
|
+
render: /* @__PURE__ */ jsx("bdi", {}),
|
|
66
71
|
children: labels.long
|
|
67
72
|
})]
|
|
68
73
|
});
|
|
@@ -3,6 +3,7 @@ import { UploadIcon } from "../icons.js";
|
|
|
3
3
|
import { openFilePicker } from "../lib/file-picker.js";
|
|
4
4
|
import { cn } from "../lib/utils.js";
|
|
5
5
|
import { Button } from "./button.js";
|
|
6
|
+
import { TruncatedText } from "./truncated-text.js";
|
|
6
7
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
8
|
import { useId } from "react";
|
|
8
9
|
// src/components/file-input.tsx
|
|
@@ -23,8 +24,8 @@ const FileInput = ({ accept, "aria-labelledby": labelledBy, className, disabled,
|
|
|
23
24
|
type: "button",
|
|
24
25
|
variant: "outline",
|
|
25
26
|
children: [/* @__PURE__ */ jsx(UploadIcon, { "aria-hidden": "true" }), chooseLabel]
|
|
26
|
-
}), /* @__PURE__ */ jsx(
|
|
27
|
-
className: "text-muted-foreground
|
|
27
|
+
}), /* @__PURE__ */ jsx(TruncatedText, {
|
|
28
|
+
className: "text-muted-foreground text-sm",
|
|
28
29
|
"data-slot": "file-input-name",
|
|
29
30
|
children: file ? /* @__PURE__ */ jsx("bdi", { children: file.name }) : emptyLabel
|
|
30
31
|
})]
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { cn } from "../lib/utils.js";
|
|
3
3
|
import { withoutDirectionalControls } from "./bidi-text.js";
|
|
4
4
|
import { Tooltip, TooltipContent as TooltipPopup, TooltipTrigger } from "./tooltip.js";
|
|
5
|
+
import { TruncatedText } from "./truncated-text.js";
|
|
5
6
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
7
|
import { useCallback, useId, useState } from "react";
|
|
7
8
|
// src/components/identity-text.tsx
|
|
@@ -51,9 +52,10 @@ const IdentityText = ({ children, className }) => {
|
|
|
51
52
|
setOpen(true);
|
|
52
53
|
}
|
|
53
54
|
},
|
|
54
|
-
render: /* @__PURE__ */ jsx(
|
|
55
|
-
className: cn("focus-visible:ring-ring
|
|
55
|
+
render: /* @__PURE__ */ jsx(TruncatedText, {
|
|
56
|
+
className: cn("focus-visible:ring-ring outline-none [unicode-bidi:isolate] focus-visible:ring-2", className),
|
|
56
57
|
dir: "auto",
|
|
58
|
+
hint: false,
|
|
57
59
|
ref: textRef,
|
|
58
60
|
role: overflow && !hasFocusOwner ? "button" : void 0,
|
|
59
61
|
tabIndex: overflow && !hasFocusOwner ? 0 : void 0
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { cn } from "../lib/utils.js";
|
|
3
3
|
import { Input } from "./input.js";
|
|
4
4
|
import { Textarea } from "./textarea.js";
|
|
5
|
+
import { TruncatedText } from "./truncated-text.js";
|
|
5
6
|
import { jsx } from "react/jsx-runtime";
|
|
6
7
|
import { cva } from "class-variance-authority";
|
|
7
8
|
// src/components/input-group.tsx
|
|
@@ -35,8 +36,8 @@ const InputGroupAddon = ({ className, align = "inline-start", ...props }) => /*
|
|
|
35
36
|
},
|
|
36
37
|
...props
|
|
37
38
|
});
|
|
38
|
-
const InputGroupText = ({ className, ...props }) => /* @__PURE__ */ jsx(
|
|
39
|
-
className: cn("text-muted-foreground flex items-center gap-2
|
|
39
|
+
const InputGroupText = ({ className, ...props }) => /* @__PURE__ */ jsx(TruncatedText, {
|
|
40
|
+
className: cn("text-muted-foreground flex items-center gap-2 leading-none [&_svg]:pointer-events-none [&_svg]:-mx-0.5 in-[[data-slot=input-group]:has([data-slot=input-control],[data-slot=textarea-control])]:[&_svg:not([class*='size-'])]:size-4.5 sm:in-[[data-slot=input-group]:has([data-slot=input-control],[data-slot=textarea-control])]:[&_svg:not([class*='size-'])]:size-4", className),
|
|
40
41
|
...props
|
|
41
42
|
});
|
|
42
43
|
const InputGroupInput = ({ className, ...props }) => /* @__PURE__ */ jsx(Input, {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { cn } from "../lib/utils.js";
|
|
3
3
|
import { BidiText } from "./bidi-text.js";
|
|
4
4
|
import { ScrollArea } from "./scroll-area.js";
|
|
5
|
+
import { TruncatedText } from "./truncated-text.js";
|
|
5
6
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
7
|
// src/components/landing.tsx
|
|
7
8
|
const LandingLayout = ({ actions, hero, children, footer }) => /* @__PURE__ */ jsxs("div", {
|
|
@@ -88,12 +89,12 @@ const LandingItemText = ({ icon, iconTone = "muted", meta, title }) => /* @__PUR
|
|
|
88
89
|
children: icon
|
|
89
90
|
}), /* @__PURE__ */ jsxs("span", {
|
|
90
91
|
className: "min-w-0 flex-1",
|
|
91
|
-
children: [/* @__PURE__ */ jsx(
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
children: [/* @__PURE__ */ jsx(TruncatedText, {
|
|
93
|
+
className: "text-foreground text-sm font-medium",
|
|
94
|
+
render: /* @__PURE__ */ jsx(BidiText, { as: "span" }),
|
|
94
95
|
children: title
|
|
95
|
-
}), meta !== void 0 && meta !== null ? /* @__PURE__ */ jsx(
|
|
96
|
-
className: "text-muted-foreground
|
|
96
|
+
}), meta !== void 0 && meta !== null ? /* @__PURE__ */ jsx(TruncatedText, {
|
|
97
|
+
className: "text-muted-foreground text-xs",
|
|
97
98
|
children: meta
|
|
98
99
|
}) : null]
|
|
99
100
|
})]
|