@heyramzi/ui 2.19.2 → 2.21.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/README.md +2 -0
- package/dist/charts/dashboard/data-table.js +1 -2
- package/dist/components/list-page/filter-section.js +1 -1
- package/dist/components/list-page/list-table.js +5 -6
- package/dist/components/list-page/queue-rail.js +2 -1
- package/dist/components/picker/trigger.js +3 -3
- package/dist/components/segmented/segmented.js +5 -5
- package/dist/components/work-parts/looks.js +1 -1
- package/dist/shadcn/dialog/index.js +3 -2
- package/dist/shadcn/dropdown-menu/menu-item.js +1 -1
- package/dist/shadcn/sheet/index.js +3 -2
- package/dist/shadcn/skeleton/index.js +3 -2
- package/dist/status/index.js +1 -0
- package/dist/work/kanban-drag.js +13 -7
- package/package.json +1 -1
- package/src/ai/confirmation/ConfirmationTitle.svelte +1 -4
- package/src/ai/loading-state/PixelLoader.svelte +1 -1
- package/src/animations/dropdown.css +19 -10
- package/src/animations/modal.css +7 -2
- package/src/charts/charts.css +3 -5
- package/src/charts/dashboard/dashboard-section.svelte +1 -1
- package/src/charts/dashboard/data-table.ts +1 -2
- package/src/charts/dashboard/stat-tile.svelte +1 -1
- package/src/charts/number-chart.css +8 -7
- package/src/charts/pop-value.svelte +68 -0
- package/src/charts/primitives/chart-tooltip.svelte +1 -1
- package/src/charts/reference-line.css +1 -1
- package/src/charts/stat-card.css +4 -4
- package/src/components/copy/copy-button.svelte +11 -10
- package/src/components/data-table/row-arrow.svelte +1 -1
- package/src/components/data-table/row-context-menu.svelte +1 -1
- package/src/components/data-table/sortable-head.svelte +2 -2
- package/src/components/data-table/table-skeleton.svelte +5 -5
- package/src/components/docs/doc-page.svelte +1 -3
- package/src/components/docs/on-this-page.svelte +4 -4
- package/src/components/field-row/field-row.svelte +1 -1
- package/src/components/list-page/detail-empty.svelte +3 -8
- package/src/components/list-page/detail-header.svelte +1 -1
- package/src/components/list-page/detail-row.svelte +2 -1
- package/src/components/list-page/empty-state.svelte +15 -7
- package/src/components/list-page/filter-item.svelte +2 -2
- package/src/components/list-page/filter-popover.svelte +1 -1
- package/src/components/list-page/filter-section.ts +1 -4
- package/src/components/list-page/grouped-list.svelte +37 -8
- package/src/components/list-page/list-page-shell.svelte +1 -1
- package/src/components/list-page/list-table.ts +6 -6
- package/src/components/list-page/master-detail.svelte +1 -1
- package/src/components/list-page/queue-rail.ts +2 -2
- package/src/components/list-page/rail-empty.svelte +1 -1
- package/src/components/list-page/rail-row.svelte +5 -5
- package/src/components/open-in/open-in.svelte +2 -1
- package/src/components/picker/trigger.ts +6 -5
- package/src/components/section-card/section-card.svelte +1 -1
- package/src/components/section-header/section-header.svelte +4 -2
- package/src/components/section-header/section-label.svelte +2 -2
- package/src/components/segmented/animated-tabs.svelte +1 -1
- package/src/components/segmented/section-runs.svelte +1 -1
- package/src/components/segmented/segment.svelte +4 -6
- package/src/components/segmented/segmented-track.svelte +18 -52
- package/src/components/segmented/segmented.ts +5 -5
- package/src/components/table-view/table-view.svelte +43 -24
- package/src/components/tags-input/tags-input.svelte +17 -31
- package/src/components/work-parts/looks.ts +1 -1
- package/src/editor/content.css +3 -3
- package/src/editor/editor.svelte +1 -1
- package/src/inbox/thread-inbox.svelte +2 -9
- package/src/panel/side-panel.svelte +9 -4
- package/src/record/info-list.svelte +7 -8
- package/src/record/project-list.svelte +1 -1
- package/src/record/record-demo.svelte +2 -4
- package/src/record/record-shell.svelte +1 -1
- package/src/record/record-stats.svelte +9 -3
- package/src/record/steps-rail.svelte +7 -11
- package/src/shadcn/accordion/accordion-trigger.svelte +1 -1
- package/src/shadcn/alert-dialog/alert-dialog-content.svelte +5 -1
- package/src/shadcn/alert-dialog/alert-dialog-footer.svelte +12 -2
- package/src/shadcn/alert-dialog/alert-dialog-header.svelte +1 -1
- package/src/shadcn/alert-dialog/alert-dialog-title.svelte +1 -1
- package/src/shadcn/badge/badge.svelte +3 -1
- package/src/shadcn/bubble/bubble-content.svelte +1 -1
- package/src/shadcn/bubble/bubble.svelte +1 -1
- package/src/shadcn/button/button.svelte +11 -5
- package/src/shadcn/calendar/calendar-month-select.svelte +1 -1
- package/src/shadcn/calendar/calendar-year-select.svelte +1 -1
- package/src/shadcn/calendar/calendar.svelte +1 -1
- package/src/shadcn/card/card.svelte +2 -1
- package/src/shadcn/checkbox/checkbox.svelte +1 -1
- package/src/shadcn/code-block/code-block-actions.svelte +1 -1
- package/src/shadcn/command/command-dialog.svelte +2 -2
- package/src/shadcn/command/command-group.svelte +1 -1
- package/src/shadcn/command/command-input.svelte +1 -1
- package/src/shadcn/command/command-item.svelte +1 -1
- package/src/shadcn/dialog/dialog-body.svelte +22 -0
- package/src/shadcn/dialog/dialog-content.svelte +19 -4
- package/src/shadcn/dialog/dialog-footer.svelte +19 -1
- package/src/shadcn/dialog/dialog-header.svelte +6 -1
- package/src/shadcn/dialog/dialog-title.svelte +2 -1
- package/src/shadcn/dialog/index.ts +3 -0
- package/src/shadcn/dropdown-menu/dropdown-menu-content.svelte +5 -1
- package/src/shadcn/dropdown-menu/dropdown-menu-sub-content.svelte +1 -1
- package/src/shadcn/dropdown-menu/menu-item.ts +1 -1
- package/src/shadcn/field/field-separator.svelte +1 -1
- package/src/shadcn/hover-card/hover-card-content.svelte +1 -1
- package/src/shadcn/input/input.svelte +2 -2
- package/src/shadcn/input-otp/input-otp-slot.svelte +1 -1
- package/src/shadcn/message-scroller/message-scroller-button.svelte +1 -1
- package/src/shadcn/popover/popover-content.svelte +1 -1
- package/src/shadcn/radio-group/radio-group-item.svelte +1 -1
- package/src/shadcn/scroll-area/scroll-area.svelte +1 -1
- package/src/shadcn/select/select-content.svelte +6 -2
- package/src/shadcn/select/select-item.svelte +1 -1
- package/src/shadcn/select/select-trigger.svelte +1 -1
- package/src/shadcn/sheet/index.ts +3 -0
- package/src/shadcn/sheet/sheet-body.svelte +22 -0
- package/src/shadcn/sheet/sheet-content.svelte +27 -5
- package/src/shadcn/sheet/sheet-footer.svelte +14 -2
- package/src/shadcn/sheet/sheet-header.svelte +1 -1
- package/src/shadcn/sheet/sheet-title.svelte +1 -1
- package/src/shadcn/sheet/sheet.css +11 -5
- package/src/shadcn/sidebar/sidebar-group-action.svelte +1 -1
- package/src/shadcn/sidebar/sidebar-group-label.svelte +1 -1
- package/src/shadcn/sidebar/sidebar-inset.svelte +1 -1
- package/src/shadcn/sidebar/sidebar-menu-action.svelte +1 -1
- package/src/shadcn/sidebar/sidebar-menu-button.svelte +2 -2
- package/src/shadcn/sidebar/sidebar-menu-sub-button.svelte +1 -1
- package/src/shadcn/sidebar/sidebar-menu.svelte +1 -1
- package/src/shadcn/sidebar/sidebar.svelte +1 -1
- package/src/shadcn/skeleton/index.ts +3 -0
- package/src/shadcn/skeleton/skeleton-delay.svelte +63 -0
- package/src/shadcn/skeleton/skeleton-delay.svelte.test.ts +57 -0
- package/src/shadcn/skeleton/skeleton.svelte +5 -1
- package/src/shadcn/slider/slider.svelte +1 -1
- package/src/shadcn/switch/switch.svelte +2 -2
- package/src/shadcn/table/table-row.svelte +1 -1
- package/src/shadcn/tabs/tabs-list.svelte +1 -1
- package/src/shadcn/tabs/tabs-trigger.svelte +2 -1
- package/src/shadcn/textarea/textarea.svelte +1 -1
- package/src/shadcn/toggle/toggle.svelte +2 -2
- package/src/shadcn/toggle-group/toggle-group.svelte +1 -1
- package/src/shadcn/tooltip/tooltip-content.svelte +1 -1
- package/src/shadcn/trace/trace-collapsible-trigger.svelte +1 -1
- package/src/shadcn/trace/trace.svelte +1 -1
- package/src/status/README.md +1 -1
- package/src/status/index.ts +1 -0
- package/src/status/status-badge.svelte +21 -7
- package/src/status/status-dot.svelte +6 -11
- package/src/status/status-pill.svelte +50 -0
- package/src/thread/thread.svelte +54 -136
- package/src/tokens/craft.css +88 -30
- package/src/tokens/palette.css +16 -9
- package/src/work/demo.svelte +48 -42
- package/src/work/detail/detail-demo.svelte +33 -24
- package/src/work/detail/task-chip.svelte +4 -4
- package/src/work/detail/task-detail.svelte +41 -108
- package/src/work/detail/task-property.svelte +7 -13
- package/src/work/detail/task-thread.svelte +48 -119
- package/src/work/docs/doc-editor-page.svelte +5 -4
- package/src/work/docs/doc-menu.svelte +9 -4
- package/src/work/docs/docs-demo.svelte +4 -4
- package/src/work/docs/edited-chip.svelte +11 -4
- package/src/work/gantt/gantt-bar.svelte +18 -14
- package/src/work/gantt/gantt-grid.svelte +7 -7
- package/src/work/gantt/gantt-group-row.svelte +7 -10
- package/src/work/gantt/gantt-header.svelte +3 -1
- package/src/work/gantt/gantt-task-row.svelte +19 -9
- package/src/work/gantt/gantt.css +4 -5
- package/src/work/inbox/inbox-card.svelte +32 -10
- package/src/work/inbox/inbox-draft.svelte +2 -2
- package/src/work/kanban-column.svelte +7 -15
- package/src/work/kanban-drag.ts +13 -8
- package/src/work/task-gantt.svelte +2 -1
- package/src/work/task-list.svelte +54 -53
- package/src/work/time/time-chip.svelte +16 -19
- package/src/work/time/time-dot.svelte +2 -1
- package/src/work/time/time-ring.svelte +2 -3
- package/src/work/time/time-tracker.svelte +72 -166
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
bind:ref
|
|
18
18
|
data-slot="trace-collapsible-trigger"
|
|
19
19
|
class={cn(
|
|
20
|
-
"gap-1 rounded-md focus-visible:ring-
|
|
20
|
+
"gap-1 rounded-md focus-visible:shadow-(--ring-focus) group/trace-collapsible-trigger flex w-fit max-w-full min-w-0 cursor-pointer items-center text-left text-muted-foreground transition-colors outline-none hover:text-foreground has-[[data-status=error]]:text-destructive",
|
|
21
21
|
className
|
|
22
22
|
)}
|
|
23
23
|
{...restProps}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
bind:this={ref}
|
|
15
15
|
data-slot="trace"
|
|
16
16
|
class={cn(
|
|
17
|
-
"gap-2 *:has-[>[data-slot=trace-line]]:rounded-md *:has-[>[data-slot=trace-line]]:focus-visible:ring-
|
|
17
|
+
"gap-2 *:has-[>[data-slot=trace-line]]:rounded-md *:has-[>[data-slot=trace-line]]:focus-visible:shadow-(--ring-focus) flex w-full min-w-0 flex-col *:has-[>[data-slot=trace-line]]:flex *:has-[>[data-slot=trace-line]]:w-fit *:has-[>[data-slot=trace-line]]:max-w-full *:has-[>[data-slot=trace-line]]:min-w-0 *:has-[>[data-slot=trace-line]]:text-left *:has-[>[data-slot=trace-line]]:outline-none",
|
|
18
18
|
className
|
|
19
19
|
)}
|
|
20
20
|
{...restProps}
|
package/src/status/README.md
CHANGED
|
@@ -15,4 +15,4 @@ Status badge, dot and icon from one map. `defineStatuses` builds the map, and th
|
|
|
15
15
|
<StatusBadge {statuses} status="sent" />
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
Exports: `StatusBadge`, `StatusDot`, `StatusIcon`, `defineStatuses` and the types `StatusDef`, `StatusDotVariant`, `StatusId`, `StatusMap`, `Statuses`. The old subpaths `tasks/status`, `tasks/status-dot` and `tasks/statuses` resolve to the same files.
|
|
18
|
+
Exports: `StatusBadge`, `StatusPill` (the tinted pill StatusBadge draws, for an organism that has its own icon and label), `StatusDot`, `StatusIcon`, `defineStatuses` and the types `StatusDef`, `StatusDotVariant`, `StatusId`, `StatusMap`, `Statuses`. The old subpaths `tasks/status`, `tasks/status-dot` and `tasks/statuses` resolve to the same files.
|
package/src/status/index.ts
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
import type { Snippet } from "svelte";
|
|
3
3
|
import type { HTMLAttributes } from "svelte/elements";
|
|
4
4
|
import { cn } from "../lib/utils.js";
|
|
5
|
-
import
|
|
5
|
+
import StatusPill from "./status-pill.svelte";
|
|
6
|
+
import StatusIcon from "./status-icon.svelte";
|
|
6
7
|
import { Spinner } from "../shadcn/spinner/index.js";
|
|
7
8
|
import { TONE_COLOR, type Tone } from "../lib/tone.js";
|
|
8
9
|
import type { Statuses } from "./status-types.js";
|
|
@@ -44,22 +45,35 @@
|
|
|
44
45
|
let resolvedLabel = $derived(
|
|
45
46
|
label ?? (statuses && status ? statuses.label(status) : (status ?? "")),
|
|
46
47
|
);
|
|
48
|
+
// Every registry status: StatusIcon draws the variant's shape when the map has none (DESIGN.md, Patterns).
|
|
49
|
+
let hasIcon = $derived(Boolean(statuses && status && statuses.has(status)));
|
|
47
50
|
</script>
|
|
48
51
|
|
|
49
|
-
|
|
50
|
-
|
|
52
|
+
<!-- The pill draws the tint; this picks what goes before the label. A registry icon wins,
|
|
53
|
+
so the state reads without its colour. -->
|
|
54
|
+
{#snippet lead()}
|
|
55
|
+
{#if busy}
|
|
56
|
+
<Spinner class="size-3" aria-hidden="true" />
|
|
57
|
+
{:else if hasIcon && statuses && status}
|
|
58
|
+
<StatusIcon {statuses} {status} size={12} animate={false} />
|
|
59
|
+
{:else if dot}
|
|
51
60
|
<span
|
|
52
61
|
class="size-1.5 shrink-0 rounded-full"
|
|
53
62
|
style:background-color={TONE_COLOR[resolvedTone]}
|
|
54
63
|
aria-hidden="true"
|
|
55
64
|
></span>
|
|
56
65
|
{/if}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
66
|
+
{/snippet}
|
|
67
|
+
|
|
68
|
+
<StatusPill
|
|
69
|
+
tone={resolvedTone}
|
|
70
|
+
icon={busy || hasIcon || dot ? lead : undefined}
|
|
71
|
+
class={cn(capitalize && "capitalize", className)}
|
|
72
|
+
{...restProps}
|
|
73
|
+
>
|
|
60
74
|
{#if children}
|
|
61
75
|
{@render children()}
|
|
62
76
|
{:else}
|
|
63
77
|
{resolvedLabel}
|
|
64
78
|
{/if}
|
|
65
|
-
</
|
|
79
|
+
</StatusPill>
|
|
@@ -151,7 +151,7 @@
|
|
|
151
151
|
|
|
152
152
|
.sd-fill {
|
|
153
153
|
transform-origin: 7px 7px;
|
|
154
|
-
animation: sd-fill-in var(--duration-slow
|
|
154
|
+
animation: sd-fill-in var(--duration-slow) var(--ease-smooth-out) forwards;
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
@keyframes sd-fill-in {
|
|
@@ -167,7 +167,7 @@
|
|
|
167
167
|
|
|
168
168
|
.sd-pop {
|
|
169
169
|
transform-origin: 7px 7px;
|
|
170
|
-
animation: sd-pop-in var(--duration-medium
|
|
170
|
+
animation: sd-pop-in var(--duration-medium) var(--ease-smooth-out) forwards;
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
@keyframes sd-pop-in {
|
|
@@ -175,10 +175,6 @@
|
|
|
175
175
|
transform: scale(0.4);
|
|
176
176
|
opacity: 0;
|
|
177
177
|
}
|
|
178
|
-
60% {
|
|
179
|
-
transform: scale(1.08);
|
|
180
|
-
opacity: 1;
|
|
181
|
-
}
|
|
182
178
|
to {
|
|
183
179
|
transform: scale(1);
|
|
184
180
|
opacity: 1;
|
|
@@ -188,19 +184,18 @@
|
|
|
188
184
|
.sd-check {
|
|
189
185
|
stroke-dasharray: 12;
|
|
190
186
|
stroke-dashoffset: 12;
|
|
191
|
-
animation: sd-trace var(--duration-medium
|
|
192
|
-
var(--duration-quick, 120ms) forwards;
|
|
187
|
+
animation: sd-trace var(--duration-medium) var(--ease-smooth-out) var(--duration-quick) forwards;
|
|
193
188
|
}
|
|
194
189
|
|
|
195
190
|
.sd-cross {
|
|
196
191
|
stroke-dasharray: 6;
|
|
197
192
|
stroke-dashoffset: 6;
|
|
198
|
-
animation: sd-trace calc(var(--duration-fast
|
|
199
|
-
|
|
193
|
+
animation: sd-trace calc(var(--duration-fast) / 2) var(--ease-smooth-out) var(--duration-quick)
|
|
194
|
+
forwards;
|
|
200
195
|
}
|
|
201
196
|
|
|
202
197
|
.sd-cross-second {
|
|
203
|
-
animation-delay: calc(var(--duration-quick
|
|
198
|
+
animation-delay: calc(var(--duration-quick) + var(--duration-fast) / 2);
|
|
204
199
|
}
|
|
205
200
|
|
|
206
201
|
@keyframes sd-trace {
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import type { Tone } from "../lib/tone.js";
|
|
3
|
+
|
|
4
|
+
// A quiet tint and no border: the icon and the text carry the tone, the fill only groups
|
|
5
|
+
// them. Neutral reads off the foreground, not `bg-muted`, because muted sits 0.005 L from
|
|
6
|
+
// the canvas and vanishes there (tokens board, "what fights the new scale").
|
|
7
|
+
export const PILL_TINT: Record<Tone, string> = {
|
|
8
|
+
neutral: "bg-foreground/6 text-muted-foreground",
|
|
9
|
+
accent:
|
|
10
|
+
"bg-[color-mix(in_oklab,var(--tone-accent,var(--color-primary))_12%,transparent)] text-[color:var(--tone-accent,var(--color-primary))]",
|
|
11
|
+
info: "bg-info/12 text-info-800 dark:text-info-400",
|
|
12
|
+
success: "bg-success/12 text-success-800 dark:text-success-400",
|
|
13
|
+
warning: "bg-warning/14 text-warning-800 dark:text-warning-400",
|
|
14
|
+
destructive: "bg-destructive/10 text-destructive-ink",
|
|
15
|
+
};
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<script lang="ts">
|
|
19
|
+
import type { Snippet } from "svelte";
|
|
20
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
21
|
+
import { cn } from "../lib/utils.js";
|
|
22
|
+
|
|
23
|
+
interface Props extends Omit<HTMLAttributes<HTMLSpanElement>, "class" | "children"> {
|
|
24
|
+
tone?: Tone;
|
|
25
|
+
/** What sits before the label: a Phosphor icon, a `StatusIcon`, a dot or a spinner. */
|
|
26
|
+
icon?: Snippet;
|
|
27
|
+
children?: Snippet;
|
|
28
|
+
class?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let { tone = "neutral", icon, children, class: className, ...restProps }: Props = $props();
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<!-- A pill means status or a count (DESIGN.md rule 15), so this is the one rounded-full chip. -->
|
|
35
|
+
<span
|
|
36
|
+
data-slot="status-pill"
|
|
37
|
+
data-tone={tone}
|
|
38
|
+
class={cn(
|
|
39
|
+
"inline-flex h-5 max-w-full shrink-0 items-center gap-1 overflow-hidden rounded-full px-2 text-xs font-medium whitespace-nowrap",
|
|
40
|
+
"transition-[background-color,color] duration-quick ease-smooth-out motion-reduce:transition-none",
|
|
41
|
+
"[&>svg]:size-3 [&>svg]:shrink-0",
|
|
42
|
+
icon && "pl-1.5",
|
|
43
|
+
PILL_TINT[tone],
|
|
44
|
+
className,
|
|
45
|
+
)}
|
|
46
|
+
{...restProps}
|
|
47
|
+
>
|
|
48
|
+
{@render icon?.()}
|
|
49
|
+
<span class="min-w-0 truncate">{@render children?.()}</span>
|
|
50
|
+
</span>
|
package/src/thread/thread.svelte
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
import { AgentAvatar } from "../agents/index.js";
|
|
7
7
|
import RichRenderer from "../editor/renderer.svelte";
|
|
8
8
|
import { Skeleton } from "../shadcn/skeleton/index.js";
|
|
9
|
+
import { Button } from "../shadcn/button/index.js";
|
|
10
|
+
import { TagChip } from "../components/chip/index.js";
|
|
9
11
|
import ThreadBody from "./thread-body.svelte";
|
|
10
12
|
import ThreadEvents from "./thread-events.svelte";
|
|
11
13
|
import {
|
|
@@ -152,20 +154,15 @@
|
|
|
152
154
|
class="block max-h-60 min-h-16 w-full resize-none border-0 bg-transparent px-3 py-2 text-sm leading-relaxed outline-none [field-sizing:content] placeholder:text-muted-foreground"
|
|
153
155
|
></textarea>
|
|
154
156
|
<div class="flex justify-end px-2 pb-2">
|
|
155
|
-
<
|
|
156
|
-
type="button"
|
|
157
|
-
class="thread-button thread-primary"
|
|
158
|
-
onclick={onSend}
|
|
159
|
-
disabled={!value.trim() || sending}
|
|
160
|
-
>
|
|
157
|
+
<Button size="sm" onclick={onSend} disabled={!value.trim() || sending}>
|
|
161
158
|
{sending ? labels.sending : isReply ? labels.reply : labels.send}
|
|
162
|
-
</
|
|
159
|
+
</Button>
|
|
163
160
|
</div>
|
|
164
161
|
</div>
|
|
165
162
|
{/snippet}
|
|
166
163
|
|
|
167
164
|
{#snippet row(comment: ThreadComment, isRoot: boolean)}
|
|
168
|
-
<div class="group/row relative flex gap-3">
|
|
165
|
+
<div class="group/row hover-row relative flex gap-3">
|
|
169
166
|
<span class="relative size-6 shrink-0">
|
|
170
167
|
{#if avatar}
|
|
171
168
|
{@render avatar(comment)}
|
|
@@ -185,7 +182,7 @@
|
|
|
185
182
|
|
|
186
183
|
<div class="min-w-0 flex-1">
|
|
187
184
|
<div class="flex min-h-6 flex-wrap items-center gap-x-2">
|
|
188
|
-
<span class="truncate text-
|
|
185
|
+
<span class="truncate text-(length:--text-control) font-medium">{comment.author.name}</span>
|
|
189
186
|
{#if comment.author.kind === "agent"}
|
|
190
187
|
<span class="sr-only">{labels.agent}</span>
|
|
191
188
|
{/if}
|
|
@@ -197,35 +194,40 @@
|
|
|
197
194
|
{#if anchorChip}
|
|
198
195
|
{@render anchorChip(comment)}
|
|
199
196
|
{:else if onAnchor}
|
|
200
|
-
<button
|
|
201
|
-
|
|
202
|
-
|
|
197
|
+
<button
|
|
198
|
+
type="button"
|
|
199
|
+
class="rounded-sm outline-none focus-visible:shadow-[var(--ring-focus)]"
|
|
200
|
+
onclick={() => onAnchor(comment)}
|
|
201
|
+
><TagChip
|
|
202
|
+
>{#snippet icon()}<MapPin aria-hidden="true" />{/snippet}{labels.pinned}</TagChip
|
|
203
|
+
></button
|
|
204
|
+
>
|
|
203
205
|
{:else}
|
|
204
|
-
<
|
|
205
|
-
|
|
206
|
+
<TagChip
|
|
207
|
+
>{#snippet icon()}<MapPin aria-hidden="true" />{/snippet}{labels.pinned}</TagChip
|
|
206
208
|
>
|
|
207
209
|
{/if}
|
|
208
210
|
{/if}
|
|
209
211
|
{#if editingId !== comment.id}
|
|
210
|
-
<span
|
|
211
|
-
class="ms-auto flex gap-0.5 opacity-0 transition-opacity group-hover/row:opacity-100 focus-within:opacity-100 [@media(hover:none)]:opacity-100"
|
|
212
|
-
>
|
|
212
|
+
<span class="row-action ms-auto flex gap-0.5">
|
|
213
213
|
{#if isRoot && onResolve}
|
|
214
|
-
<
|
|
215
|
-
|
|
216
|
-
|
|
214
|
+
<Button
|
|
215
|
+
variant="ghost"
|
|
216
|
+
size="sm"
|
|
217
|
+
icon
|
|
217
218
|
aria-label={comment.resolved ? labels.unresolve : labels.resolve}
|
|
218
219
|
aria-pressed={comment.resolved ?? false}
|
|
219
220
|
onclick={() => onResolve(comment, !comment.resolved)}
|
|
220
221
|
>
|
|
221
222
|
<Check class="size-3.5" aria-hidden="true" />
|
|
222
|
-
</
|
|
223
|
+
</Button>
|
|
223
224
|
{/if}
|
|
224
225
|
{#if canEdit(comment)}
|
|
225
226
|
{#if onEdit && !comment.content}
|
|
226
|
-
<
|
|
227
|
-
|
|
228
|
-
|
|
227
|
+
<Button
|
|
228
|
+
variant="ghost"
|
|
229
|
+
size="sm"
|
|
230
|
+
icon
|
|
229
231
|
aria-label={labels.edit}
|
|
230
232
|
onclick={() => {
|
|
231
233
|
editingId = comment.id;
|
|
@@ -233,17 +235,18 @@
|
|
|
233
235
|
}}
|
|
234
236
|
>
|
|
235
237
|
<PencilSimple class="size-3.5" aria-hidden="true" />
|
|
236
|
-
</
|
|
238
|
+
</Button>
|
|
237
239
|
{/if}
|
|
238
240
|
{#if onDelete}
|
|
239
|
-
<
|
|
240
|
-
|
|
241
|
-
|
|
241
|
+
<Button
|
|
242
|
+
variant="ghost"
|
|
243
|
+
size="sm"
|
|
244
|
+
icon
|
|
242
245
|
aria-label={labels.delete}
|
|
243
246
|
onclick={() => onDelete?.(comment)}
|
|
244
247
|
>
|
|
245
248
|
<Trash class="size-3.5" aria-hidden="true" />
|
|
246
|
-
</
|
|
249
|
+
</Button>
|
|
247
250
|
{/if}
|
|
248
251
|
{/if}
|
|
249
252
|
</span>
|
|
@@ -255,20 +258,20 @@
|
|
|
255
258
|
bind:value={editDraft}
|
|
256
259
|
rows={3}
|
|
257
260
|
aria-label={labels.edit}
|
|
258
|
-
class="mt-1 w-full resize-y rounded-md border border-input bg-transparent px-2.5 py-2 text-sm leading-relaxed outline-none focus-visible:border-ring focus-visible:ring-
|
|
261
|
+
class="mt-1 w-full resize-y rounded-md border border-input bg-transparent px-2.5 py-2 text-sm leading-relaxed outline-none focus-visible:border-ring focus-visible:shadow-(--ring-focus)"
|
|
259
262
|
></textarea>
|
|
260
263
|
<div class="mt-2 flex justify-end gap-1.5">
|
|
261
|
-
<
|
|
262
|
-
>{labels.cancel}</
|
|
263
|
-
>
|
|
264
|
-
<button
|
|
265
|
-
type="button"
|
|
266
|
-
class="thread-button thread-primary"
|
|
267
|
-
onclick={() => saveEdit(comment)}>{labels.save}</button
|
|
264
|
+
<Button variant="ghost" size="sm" onclick={() => (editingId = null)}
|
|
265
|
+
>{labels.cancel}</Button
|
|
268
266
|
>
|
|
267
|
+
<Button size="sm" onclick={() => saveEdit(comment)}>{labels.save}</Button>
|
|
269
268
|
</div>
|
|
270
269
|
{:else if comment.content}
|
|
271
|
-
<div
|
|
270
|
+
<div
|
|
271
|
+
class="text-sm leading-relaxed [--content-gap:0.5em] [--content-leading:1.625] [--content-size:var(--text-sm)]"
|
|
272
|
+
>
|
|
273
|
+
<RichRenderer content={comment.content} />
|
|
274
|
+
</div>
|
|
272
275
|
{:else}
|
|
273
276
|
<ThreadBody text={comment.body} more={labels.showMore} less={labels.showLess} />
|
|
274
277
|
{/if}
|
|
@@ -280,16 +283,18 @@
|
|
|
280
283
|
|
|
281
284
|
<section aria-label={labels.title} class={cn("flex flex-col gap-3", className)}>
|
|
282
285
|
<div class={cn("flex h-7 items-center justify-between", !heading && resolved === 0 && "sr-only")}>
|
|
283
|
-
<h2 class={cn("text-xs font-medium text-muted-foreground", !heading && "sr-only")}>
|
|
286
|
+
<h2 class={cn("text-xs font-medium text-muted-foreground", !heading && "sr-only")}>
|
|
287
|
+
{labels.title}
|
|
288
|
+
</h2>
|
|
284
289
|
{#if resolved > 0}
|
|
285
|
-
<
|
|
286
|
-
|
|
287
|
-
|
|
290
|
+
<Button
|
|
291
|
+
variant="ghost"
|
|
292
|
+
size="sm"
|
|
288
293
|
aria-expanded={showResolved}
|
|
289
294
|
onclick={() => (showResolved = !showResolved)}
|
|
290
295
|
>
|
|
291
296
|
{showResolved ? labels.hideResolved : fillCount(labels.showResolved, resolved)}
|
|
292
|
-
</
|
|
297
|
+
</Button>
|
|
293
298
|
{/if}
|
|
294
299
|
</div>
|
|
295
300
|
|
|
@@ -339,16 +344,17 @@
|
|
|
339
344
|
)}
|
|
340
345
|
</div>
|
|
341
346
|
{:else}
|
|
342
|
-
<
|
|
343
|
-
|
|
344
|
-
|
|
347
|
+
<Button
|
|
348
|
+
variant="ghost"
|
|
349
|
+
size="sm"
|
|
350
|
+
class="ms-9 w-fit"
|
|
345
351
|
onclick={() => {
|
|
346
352
|
replyTo = node.comment.id;
|
|
347
353
|
replyDraft = "";
|
|
348
354
|
}}
|
|
349
355
|
>
|
|
350
356
|
<ArrowBendDownRight class="size-3.5" aria-hidden="true" />{labels.reply}
|
|
351
|
-
</
|
|
357
|
+
</Button>
|
|
352
358
|
{/if}
|
|
353
359
|
{/if}
|
|
354
360
|
</li>
|
|
@@ -356,7 +362,7 @@
|
|
|
356
362
|
{/each}
|
|
357
363
|
</ol>
|
|
358
364
|
{:else if !onPost}
|
|
359
|
-
<p class="text-
|
|
365
|
+
<p class="text-(length:--text-control) text-muted-foreground">{labels.empty}</p>
|
|
360
366
|
{/if}
|
|
361
367
|
|
|
362
368
|
{@render footer?.()}
|
|
@@ -365,91 +371,3 @@
|
|
|
365
371
|
{@render composer(draft, labels.placeholder, sendRoot, (next) => (draft = next), false)}
|
|
366
372
|
{/if}
|
|
367
373
|
</section>
|
|
368
|
-
|
|
369
|
-
<style>
|
|
370
|
-
.thread-icon {
|
|
371
|
-
display: grid;
|
|
372
|
-
place-items: center;
|
|
373
|
-
width: 1.75rem;
|
|
374
|
-
height: 1.75rem;
|
|
375
|
-
border-radius: var(--radius-md, 0.5rem);
|
|
376
|
-
color: var(--color-muted-foreground);
|
|
377
|
-
outline: none;
|
|
378
|
-
transition: background-color var(--duration-quick, 150ms) var(--ease-smooth-out, ease-out);
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
.thread-chip {
|
|
382
|
-
display: inline-flex;
|
|
383
|
-
align-items: center;
|
|
384
|
-
gap: 0.25rem;
|
|
385
|
-
height: 1.25rem;
|
|
386
|
-
padding-inline: 0.375rem;
|
|
387
|
-
border-radius: var(--radius-sm, 0.25rem);
|
|
388
|
-
background: var(--color-muted);
|
|
389
|
-
color: var(--color-muted-foreground);
|
|
390
|
-
font-size: 0.75rem;
|
|
391
|
-
outline: none;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
button.thread-chip:focus-visible {
|
|
395
|
-
box-shadow: 0 0 0 3px color-mix(in oklab, var(--color-ring) 50%, transparent);
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
@media (hover: hover) and (pointer: fine) {
|
|
399
|
-
.thread-icon:hover {
|
|
400
|
-
background: var(--color-muted);
|
|
401
|
-
color: var(--color-foreground);
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
.thread-icon:focus-visible,
|
|
406
|
-
.thread-button:focus-visible {
|
|
407
|
-
box-shadow: 0 0 0 3px color-mix(in oklab, var(--color-ring) 50%, transparent);
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
.thread-button {
|
|
411
|
-
height: 1.75rem;
|
|
412
|
-
padding-inline: 0.75rem;
|
|
413
|
-
border-radius: var(--radius-md, 0.5rem);
|
|
414
|
-
font-size: 0.8125rem;
|
|
415
|
-
font-weight: 500;
|
|
416
|
-
outline: none;
|
|
417
|
-
transition: background-color var(--duration-quick, 150ms) var(--ease-smooth-out, ease-out);
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
@media (hover: hover) and (pointer: fine) {
|
|
421
|
-
.thread-button:hover:not(:disabled) {
|
|
422
|
-
background: var(--color-muted);
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
.thread-button:disabled {
|
|
427
|
-
opacity: 0.5;
|
|
428
|
-
pointer-events: none;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
.thread-primary {
|
|
432
|
-
background: var(--color-primary);
|
|
433
|
-
color: var(--color-primary-foreground);
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
/* An empty draft reads as an outlined button, not a faded fill. */
|
|
437
|
-
.thread-primary:disabled {
|
|
438
|
-
opacity: 1;
|
|
439
|
-
background: transparent;
|
|
440
|
-
box-shadow: inset 0 0 0 1px var(--color-border);
|
|
441
|
-
color: var(--color-muted-foreground);
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
@media (hover: hover) and (pointer: fine) {
|
|
445
|
-
.thread-primary:hover:not(:disabled) {
|
|
446
|
-
background: color-mix(in oklab, var(--color-primary) 90%, transparent);
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
@media (any-pointer: coarse) {
|
|
451
|
-
.thread-button {
|
|
452
|
-
height: 2.5rem;
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
</style>
|
package/src/tokens/craft.css
CHANGED
|
@@ -1,25 +1,31 @@
|
|
|
1
1
|
/* ── Radius, by role not by size ─────────────────────────────
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
Tailwind's names, so `rounded-lg` is the control radius. Five steps, 4px
|
|
3
|
+
apart; xs and md fold into sm. */
|
|
4
4
|
@theme {
|
|
5
|
-
--radius-
|
|
6
|
-
--radius-
|
|
7
|
-
--radius-md:
|
|
8
|
-
--radius-lg:
|
|
9
|
-
--radius-xl:
|
|
10
|
-
--radius-2xl:
|
|
11
|
-
--radius-3xl:
|
|
5
|
+
--radius-sm: 6px; /* chip, badge, tag, inline code */
|
|
6
|
+
--radius-xs: var(--radius-sm); /* folded, was 4px */
|
|
7
|
+
--radius-md: var(--radius-sm); /* folded, was 7px */
|
|
8
|
+
--radius-lg: 10px; /* control: button, input, select, menu item */
|
|
9
|
+
--radius-xl: 14px; /* card, panel, stage */
|
|
10
|
+
--radius-2xl: 18px; /* overlay: dialog, sheet, popover, command palette */
|
|
11
|
+
--radius-3xl: 22px; /* shell panel, the inset page */
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
/* ── Type, by role ───────────────────────────────────────────
|
|
15
|
-
Tailwind's own `text-xs` to `text-base` cover the rest.
|
|
15
|
+
Tailwind's own `text-xs` to `text-base` cover the rest. Control text sits
|
|
16
|
+
between them: 13px at weight 500 is Linear's property row, measured 31 Aug
|
|
17
|
+
2026 on a 16px root, where 14px reads looser. No line height of its own, so
|
|
18
|
+
it inherits like the `text-[13px]` it replaces. Write it
|
|
19
|
+
`text-(length:--text-control)`: tailwind-merge takes a bare `text-control`
|
|
20
|
+
for a colour and drops it beside `text-muted-foreground`. */
|
|
16
21
|
@theme {
|
|
22
|
+
--text-control: 0.8125rem; /* a picker trigger, a property value */
|
|
17
23
|
--text-2xs: 0.6875rem; /* a count, a kbd hint, a meta line under a tile; the floor */
|
|
18
24
|
}
|
|
19
25
|
|
|
20
26
|
/* ── Elevation ───────────────────────────────────────────────
|
|
21
|
-
|
|
22
|
-
|
|
27
|
+
`--elev-card` and its hover carry their own hairline, so a box that also
|
|
28
|
+
draws `border` gets a 2px edge. The other steps carry none. */
|
|
23
29
|
:root {
|
|
24
30
|
/* Resting on the page. A button, a chip, a row. */
|
|
25
31
|
--elev-btn:
|
|
@@ -28,21 +34,28 @@
|
|
|
28
34
|
|
|
29
35
|
/* Sitting on the page. A card, a stage, a panel. */
|
|
30
36
|
--elev-card:
|
|
31
|
-
0 1px 2px
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
var(--elev-hairline), 0 1px 2px color-mix(in oklab, var(--elev-tint) 4%, transparent);
|
|
38
|
+
|
|
39
|
+
/* A hovered card: the edge darkens 3% toward the text and a soft shadow lifts it.
|
|
40
|
+
Pair it with a 1px rise, as `.t-lift` does. */
|
|
41
|
+
--elev-card-hover:
|
|
42
|
+
0 0 0 1px
|
|
43
|
+
color-mix(
|
|
44
|
+
in oklab,
|
|
45
|
+
var(--color-border, var(--border)),
|
|
46
|
+
var(--color-foreground, var(--foreground)) 3%
|
|
47
|
+
),
|
|
48
|
+
0 6px 16px -4px color-mix(in oklab, var(--elev-tint) 8%, transparent);
|
|
49
|
+
|
|
50
|
+
/* Lifted off the page. A popover, a menu, a hovered bordered card. */
|
|
36
51
|
--elev-raised:
|
|
37
|
-
0
|
|
38
|
-
0
|
|
39
|
-
0 20px 40px -12px color-mix(in oklab, var(--elev-tint) 12%, transparent);
|
|
52
|
+
0 2px 4px -1px color-mix(in oklab, var(--elev-tint) 5%, transparent),
|
|
53
|
+
0 8px 20px -6px color-mix(in oklab, var(--elev-tint) 10%, transparent);
|
|
40
54
|
|
|
41
55
|
/* Floating over a dimmed page. A dialog, a sheet, a command palette. */
|
|
42
56
|
--elev-overlay:
|
|
43
|
-
0
|
|
44
|
-
0
|
|
45
|
-
0 40px 72px -20px color-mix(in oklab, var(--elev-tint) 20%, transparent);
|
|
57
|
+
0 4px 8px -2px color-mix(in oklab, var(--elev-tint) 6%, transparent),
|
|
58
|
+
0 24px 48px -12px color-mix(in oklab, var(--elev-tint) 16%, transparent);
|
|
46
59
|
|
|
47
60
|
/* A hairline separates a surface from what's behind it; a rim is light on the
|
|
48
61
|
top edge, which makes a filled control read as a key. */
|
|
@@ -58,8 +71,11 @@
|
|
|
58
71
|
.dark {
|
|
59
72
|
--elev-btn: 0 1px 2px color-mix(in oklab, black 30%, transparent);
|
|
60
73
|
--elev-card:
|
|
61
|
-
0 1px
|
|
62
|
-
0
|
|
74
|
+
0 0 0 1px color-mix(in oklab, white 10%, transparent),
|
|
75
|
+
0 1px 2px color-mix(in oklab, black 24%, transparent);
|
|
76
|
+
--elev-card-hover:
|
|
77
|
+
0 0 0 1px color-mix(in oklab, white 16%, transparent),
|
|
78
|
+
0 8px 20px -6px color-mix(in oklab, black 40%, transparent);
|
|
63
79
|
--elev-raised:
|
|
64
80
|
0 2px 6px color-mix(in oklab, black 30%, transparent),
|
|
65
81
|
0 10px 24px -6px color-mix(in oklab, black 38%, transparent);
|
|
@@ -77,10 +93,12 @@
|
|
|
77
93
|
then a soft halo. */
|
|
78
94
|
:root {
|
|
79
95
|
--ring-color: var(--color-ring, var(--ring));
|
|
80
|
-
--ring-halo: color-mix(in oklab, var(--ring-color)
|
|
81
|
-
/* The
|
|
82
|
-
|
|
83
|
-
--ring-focus: 0 0 0 1px var(--ring-color), 0 0 0
|
|
96
|
+
--ring-halo: color-mix(in oklab, var(--ring-color) 10%, transparent);
|
|
97
|
+
/* The 1px edge carries the 3:1 contrast alone; a 5px 16% halo read as a thick grey band
|
|
98
|
+
(Ramzi, 24 Oct 2026), so it's 2px at 10%. */
|
|
99
|
+
--ring-focus: 0 0 0 1px var(--ring-color), 0 0 0 3px var(--ring-halo);
|
|
100
|
+
/* Rows inside a clipped list focus inward, where an outer ring would be cut off. */
|
|
101
|
+
--ring-focus-inset: inset 0 0 0 1px var(--ring-color), inset 0 0 0 3px var(--ring-halo);
|
|
84
102
|
}
|
|
85
103
|
|
|
86
104
|
/* ── Motion ──────────────────────────────────────────────────
|
|
@@ -124,6 +142,7 @@
|
|
|
124
142
|
--shadow-hairline: var(--elev-hairline);
|
|
125
143
|
--shadow-btn: var(--elev-btn);
|
|
126
144
|
--shadow-card: var(--elev-card);
|
|
145
|
+
--shadow-card-hover: var(--elev-card-hover);
|
|
127
146
|
--shadow-raised: var(--elev-raised);
|
|
128
147
|
--shadow-overlay: var(--elev-overlay);
|
|
129
148
|
--shadow-field: var(--elev-field);
|
|
@@ -132,7 +151,7 @@
|
|
|
132
151
|
because the two never appear apart and utilities cannot be stacked. */
|
|
133
152
|
--shadow-key: var(--elev-rim), var(--elev-btn);
|
|
134
153
|
/* The composed pair, for a surface that draws no border of its own. */
|
|
135
|
-
--shadow-edge-card: var(--elev-
|
|
154
|
+
--shadow-edge-card: var(--elev-card); /* the card step carries its own hairline */
|
|
136
155
|
--shadow-edge-raised: var(--elev-hairline), var(--elev-raised);
|
|
137
156
|
--shadow-edge-overlay: var(--elev-hairline), var(--elev-overlay);
|
|
138
157
|
|
|
@@ -149,3 +168,42 @@
|
|
|
149
168
|
--transition-duration-overlay: var(--duration-overlay);
|
|
150
169
|
--transition-duration-loop: var(--duration-loop);
|
|
151
170
|
}
|
|
171
|
+
|
|
172
|
+
/* ── Row actions ─────────────────────────────────────────────
|
|
173
|
+
`hover-row` marks the host, `row-action` each action: hidden under a pointer
|
|
174
|
+
until hover or keyboard focus, always shown on touch. */
|
|
175
|
+
@layer components {
|
|
176
|
+
.row-action {
|
|
177
|
+
opacity: 0;
|
|
178
|
+
transition: opacity var(--duration-quick) var(--ease-smooth-out);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.hover-row:has(:focus-visible) .row-action,
|
|
182
|
+
.row-action:is(
|
|
183
|
+
:focus-visible,
|
|
184
|
+
:has(:focus-visible),
|
|
185
|
+
[data-state="open"],
|
|
186
|
+
[aria-expanded="true"],
|
|
187
|
+
:has([data-state="open"], [aria-expanded="true"])
|
|
188
|
+
) {
|
|
189
|
+
opacity: 1;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
@media (hover: hover) and (pointer: fine) {
|
|
193
|
+
.hover-row:hover .row-action {
|
|
194
|
+
opacity: 1;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
@media (hover: none), (any-pointer: coarse) {
|
|
199
|
+
.row-action {
|
|
200
|
+
opacity: 1;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
@media (prefers-reduced-motion: reduce) {
|
|
205
|
+
.row-action {
|
|
206
|
+
transition: none;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|