@heyramzi/ui 3.1.0 → 3.2.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/lib/sliding-pill.js +2 -2
- package/dist/record/measure-clamp.js +23 -0
- package/dist/shadcn/button-group/index.js +6 -0
- package/dist/shadcn/checkbox/checkbox-box.js +3 -0
- package/dist/shadcn/input-group/index.js +9 -0
- package/dist/shadcn/item/index.js +13 -0
- package/dist/shadcn/native-select/index.js +6 -0
- package/dist/shadcn/pagination/index.js +12 -0
- package/package.json +1 -1
- package/scripts/sync-shadcn.mjs +0 -5
- package/src/ai/generation-progress/GenerationProgress.svelte +14 -12
- package/src/canvas/pen/PenOverlay.svelte +1 -30
- package/src/canvas/screen/PartView.svelte +13 -8
- package/src/canvas/screen/ScreenBody.svelte +18 -11
- package/src/canvas/ui/BarTooltip.svelte +46 -0
- package/src/canvas/ui/BarTooltip.svelte.d.ts +17 -0
- package/src/canvas/ui/CanvasToolbar.svelte +170 -156
- package/src/canvas/ui/StylePanel.svelte +179 -102
- package/src/canvas/ui/canvas-ui.css +12 -76
- package/src/charts/dashboard/data-table.svelte +80 -80
- package/src/charts/dashboard/hint.svelte +34 -0
- package/src/charts/dashboard/hint.svelte.d.ts +10 -0
- package/src/charts/dashboard/rating-chip.svelte +4 -3
- package/src/charts/legend.css +0 -8
- package/src/charts/legend.svelte +26 -30
- package/src/components/copy/copy-button.svelte +10 -2
- package/src/components/copy/copy-button.test.ts +7 -0
- package/src/components/data-table/table-skeleton.svelte +15 -7
- package/src/components/data-table/table-skeleton.svelte.d.ts +4 -0
- package/src/components/docs/on-this-page.svelte +2 -2
- package/src/components/hold-to-confirm/hold-to-confirm.svelte +2 -1
- package/src/components/list-page/filter-item.svelte +20 -7
- package/src/components/list-page/filter-popover.svelte +41 -16
- package/src/components/list-page/filter-popover.svelte.d.ts +1 -1
- package/src/components/list-page/grouped-list.svelte +41 -23
- package/src/components/picker/picker.svelte +8 -3
- package/src/components/picker/picker.svelte.d.ts +2 -1
- package/src/components/segmented/segmented-filter.svelte +19 -10
- package/src/components/segmented/segmented-track.svelte +11 -3
- package/src/components/table-view/table-view.svelte +22 -6
- package/src/components/tags-input/tags-input.svelte +94 -77
- package/src/components/toolbar/toolbar.svelte +2 -1
- package/src/components/toolbar/toolbar.test.ts +5 -4
- package/src/components/work-parts/group-head.svelte +28 -9
- package/src/components/work-parts/group-head.svelte.d.ts +5 -2
- package/src/editor/editor-toolbar.svelte +223 -193
- package/src/editor/editor.svelte +56 -38
- package/src/editor/image-toolbar.svelte +99 -37
- package/src/editor/image-toolbar.svelte.d.ts +4 -5
- package/src/editor/url-dialog.svelte +96 -0
- package/src/editor/url-dialog.svelte.d.ts +15 -0
- package/src/effects/theme-toggle/ThemeToggle.css +8 -8
- package/src/effects/theme-toggle/ThemeToggle.svelte +20 -6
- package/src/inbox/thread-inbox.svelte +9 -1
- package/src/lib/live-region.svelte +12 -4
- package/src/lib/live-region.svelte.d.ts +5 -1
- package/src/lib/sliding-pill.ts +3 -2
- package/src/record/info-list.svelte +33 -59
- package/src/record/measure-clamp.ts +31 -0
- package/src/record/record-shell.svelte +38 -39
- package/src/record/record-timeline.svelte +10 -5
- package/src/record/steps-rail.svelte +50 -36
- package/src/record/where-we-are.svelte +5 -19
- package/src/shadcn/button-group/button-group-separator.svelte +23 -0
- package/src/shadcn/button-group/button-group-separator.svelte.d.ts +13 -0
- package/src/shadcn/button-group/button-group-text.svelte +28 -0
- package/src/shadcn/button-group/button-group-text.svelte.d.ts +11 -0
- package/src/shadcn/button-group/button-group.svelte +46 -0
- package/src/shadcn/button-group/button-group.svelte.d.ts +16 -0
- package/src/shadcn/button-group/index.ts +15 -0
- package/src/shadcn/checkbox/checkbox-box.ts +4 -0
- package/src/shadcn/checkbox/checkbox.svelte +2 -4
- package/src/shadcn/input-group/index.ts +22 -0
- package/src/shadcn/input-group/input-group-addon.svelte +51 -0
- package/src/shadcn/input-group/input-group-addon.svelte.d.ts +18 -0
- package/src/shadcn/input-group/input-group-button.svelte +49 -0
- package/src/shadcn/input-group/input-group-button.svelte.d.ts +18 -0
- package/src/shadcn/input-group/input-group-input.svelte +20 -0
- package/src/shadcn/input-group/input-group-input.svelte.d.ts +11 -0
- package/src/shadcn/input-group/input-group-text.svelte +19 -0
- package/src/shadcn/input-group/input-group-text.svelte.d.ts +5 -0
- package/src/shadcn/input-group/input-group-textarea.svelte +20 -0
- package/src/shadcn/input-group/input-group-textarea.svelte.d.ts +3 -0
- package/src/shadcn/input-group/input-group.svelte +24 -0
- package/src/shadcn/input-group/input-group.svelte.d.ts +5 -0
- package/src/shadcn/item/index.ts +34 -0
- package/src/shadcn/item/item-actions.svelte +20 -0
- package/src/shadcn/item/item-actions.svelte.d.ts +5 -0
- package/src/shadcn/item/item-content.svelte +23 -0
- package/src/shadcn/item/item-content.svelte.d.ts +5 -0
- package/src/shadcn/item/item-description.svelte +23 -0
- package/src/shadcn/item/item-description.svelte.d.ts +5 -0
- package/src/shadcn/item/item-footer.svelte +20 -0
- package/src/shadcn/item/item-footer.svelte.d.ts +5 -0
- package/src/shadcn/item/item-group.svelte +21 -0
- package/src/shadcn/item/item-group.svelte.d.ts +5 -0
- package/src/shadcn/item/item-header.svelte +20 -0
- package/src/shadcn/item/item-header.svelte.d.ts +5 -0
- package/src/shadcn/item/item-media.svelte +42 -0
- package/src/shadcn/item/item-media.svelte.d.ts +17 -0
- package/src/shadcn/item/item-separator.svelte +19 -0
- package/src/shadcn/item/item-separator.svelte.d.ts +13 -0
- package/src/shadcn/item/item-title.svelte +20 -0
- package/src/shadcn/item/item-title.svelte.d.ts +5 -0
- package/src/shadcn/item/item.svelte +61 -0
- package/src/shadcn/item/item.svelte.d.ts +28 -0
- package/src/shadcn/native-select/index.ts +13 -0
- package/src/shadcn/native-select/native-select-opt-group.svelte +20 -0
- package/src/shadcn/native-select/native-select-opt-group.svelte.d.ts +5 -0
- package/src/shadcn/native-select/native-select-option.svelte +20 -0
- package/src/shadcn/native-select/native-select-option.svelte.d.ts +5 -0
- package/src/shadcn/native-select/native-select.svelte +45 -0
- package/src/shadcn/native-select/native-select.svelte.d.ts +8 -0
- package/src/shadcn/pagination/index.ts +31 -0
- package/src/shadcn/pagination/pagination-content.svelte +20 -0
- package/src/shadcn/pagination/pagination-content.svelte.d.ts +5 -0
- package/src/shadcn/pagination/pagination-ellipsis.svelte +22 -0
- package/src/shadcn/pagination/pagination-ellipsis.svelte.d.ts +5 -0
- package/src/shadcn/pagination/pagination-item.svelte +14 -0
- package/src/shadcn/pagination/pagination-item.svelte.d.ts +5 -0
- package/src/shadcn/pagination/pagination-link.svelte +41 -0
- package/src/shadcn/pagination/pagination-link.svelte.d.ts +9 -0
- package/src/shadcn/pagination/pagination-next-button.svelte +34 -0
- package/src/shadcn/pagination/pagination-next-button.svelte.d.ts +4 -0
- package/src/shadcn/pagination/pagination-next.svelte +26 -0
- package/src/shadcn/pagination/pagination-next.svelte.d.ts +4 -0
- package/src/shadcn/pagination/pagination-prev-button.svelte +34 -0
- package/src/shadcn/pagination/pagination-prev-button.svelte.d.ts +4 -0
- package/src/shadcn/pagination/pagination-previous.svelte +30 -0
- package/src/shadcn/pagination/pagination-previous.svelte.d.ts +4 -0
- package/src/shadcn/pagination/pagination.svelte +27 -0
- package/src/shadcn/pagination/pagination.svelte.d.ts +4 -0
- package/src/slides/Controls.svelte +39 -36
- package/src/slides/Deck.svelte +11 -8
- package/src/thread/thread.svelte +15 -8
- package/src/work/detail/task-detail.svelte +1 -1
- package/src/work/gantt/gantt-task-row.svelte +3 -6
- package/src/work/inbox/inbox-card.svelte +7 -5
- package/src/work/inbox/inbox-trace.svelte +14 -10
- package/src/work/kanban-board.svelte +40 -25
- package/src/work/kanban-hidden-rail.svelte +67 -65
- package/src/work/task-list.svelte +145 -124
- package/src/work/task-list.svelte.test.ts +5 -2
- package/src/work/time/time-tracker.svelte +35 -10
|
@@ -23,11 +23,13 @@
|
|
|
23
23
|
// IMPORTS
|
|
24
24
|
// =========================
|
|
25
25
|
import { Panel, useStore } from "@xyflow/svelte";
|
|
26
|
+
import { Toolbar } from "bits-ui";
|
|
26
27
|
import type { CanvasOp } from "../ops/ops.js";
|
|
27
28
|
import {
|
|
28
29
|
FONT_SIZES,
|
|
29
30
|
styleOps,
|
|
30
31
|
summarizeStyle,
|
|
32
|
+
type FillValue,
|
|
31
33
|
type FontWeight,
|
|
32
34
|
type StyleNode,
|
|
33
35
|
type StyleValues,
|
|
@@ -35,8 +37,10 @@
|
|
|
35
37
|
} from "../editor/style.js";
|
|
36
38
|
import { PEN_COLORS } from "../pen/types.js";
|
|
37
39
|
import "../pen/pen.css";
|
|
40
|
+
import BarTooltip from "./BarTooltip.svelte";
|
|
38
41
|
import { placeBeside, type Box } from "./place-panel.js";
|
|
39
42
|
import "./canvas-ui.css";
|
|
43
|
+
import * as Tooltip from "../../shadcn/tooltip/index.js";
|
|
40
44
|
|
|
41
45
|
// =========================
|
|
42
46
|
// TYPES
|
|
@@ -70,6 +74,8 @@
|
|
|
70
74
|
const LEVELS: readonly WidthLevel[] = [0, 1, 2];
|
|
71
75
|
|
|
72
76
|
let summary = $derived(summarizeStyle(nodes, frameType));
|
|
77
|
+
// Roving focus remembers its tab stop by element, so a panel whose sections change is rebuilt fresh.
|
|
78
|
+
let controlsKey = $derived(summary ? [...summary.controls].join() : "");
|
|
73
79
|
let strokeLabel = $derived(
|
|
74
80
|
summary?.controls.has("fill") || summary?.controls.has("width") ? labels.stroke : labels.colour,
|
|
75
81
|
);
|
|
@@ -118,12 +124,24 @@
|
|
|
118
124
|
const ops = styleOps(nodes, next, frameType);
|
|
119
125
|
if (ops.length > 0) commit(ops);
|
|
120
126
|
}
|
|
127
|
+
|
|
128
|
+
// A single-select group sends "" when its pressed item is pressed again. A style stays set.
|
|
129
|
+
// Each group's values are the item values written below, so a value maps back to its own type.
|
|
130
|
+
function pick(key: keyof StyleValues, value: string | undefined) {
|
|
131
|
+
if (!value) return;
|
|
132
|
+
if (key === "fill") change({ fill: value as FillValue });
|
|
133
|
+
else if (key === "stroke") change({ stroke: value as PenColor });
|
|
134
|
+
else if (key === "width") change({ width: Number(value) as WidthLevel });
|
|
135
|
+
else if (key === "fontSize") change({ fontSize: Number(value) });
|
|
136
|
+
else change({ fontWeight: value as FontWeight });
|
|
137
|
+
}
|
|
121
138
|
</script>
|
|
122
139
|
|
|
123
140
|
<!-- =========================== -->
|
|
124
141
|
<!-- MARKUP -->
|
|
125
142
|
<!-- =========================== -->
|
|
126
143
|
{#if summary}
|
|
144
|
+
{@const { values, controls } = summary}
|
|
127
145
|
<Panel
|
|
128
146
|
position="center-right"
|
|
129
147
|
class="cv-dock cv-style-dock {placed ? 'is-placed' : ''}"
|
|
@@ -132,111 +150,165 @@
|
|
|
132
150
|
: undefined}
|
|
133
151
|
data-side={placed?.side}
|
|
134
152
|
>
|
|
135
|
-
<
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
153
|
+
<Tooltip.Provider>
|
|
154
|
+
{#key controlsKey}
|
|
155
|
+
<Toolbar.Root aria-label={labels.panel}>
|
|
156
|
+
{#snippet child({ props })}
|
|
157
|
+
<div
|
|
158
|
+
{...props}
|
|
159
|
+
bind:offsetWidth={barWidth}
|
|
160
|
+
bind:offsetHeight={barHeight}
|
|
161
|
+
class="cv-bar cv-style t-enter"
|
|
162
|
+
data-testid="style-panel"
|
|
163
|
+
>
|
|
164
|
+
{#if controls.has("fill")}
|
|
165
|
+
<div
|
|
166
|
+
class="cv-section"
|
|
167
|
+
role="group"
|
|
168
|
+
aria-label={labels.fill}
|
|
169
|
+
data-testid="style-fill"
|
|
170
|
+
>
|
|
171
|
+
<span class="cv-label" aria-hidden="true">{labels.fill}</span>
|
|
172
|
+
<Toolbar.Group
|
|
173
|
+
type="single"
|
|
174
|
+
class="cv-swatches"
|
|
175
|
+
bind:value={() => values.fill, (value) => pick("fill", value)}
|
|
176
|
+
>
|
|
177
|
+
<BarTooltip label={labels.none} name="{labels.fill}: {labels.none}" side="left">
|
|
178
|
+
{#snippet child({ props })}
|
|
179
|
+
<Toolbar.GroupItem
|
|
180
|
+
{...props}
|
|
181
|
+
value="none"
|
|
182
|
+
class="cv-swatch cv-swatch-none"
|
|
183
|
+
/>
|
|
184
|
+
{/snippet}
|
|
185
|
+
</BarTooltip>
|
|
186
|
+
{#each PEN_COLORS as swatch (swatch)}
|
|
187
|
+
{@render colorItem(swatch, labels.fill)}
|
|
188
|
+
{/each}
|
|
189
|
+
</Toolbar.Group>
|
|
190
|
+
</div>
|
|
191
|
+
{/if}
|
|
169
192
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
193
|
+
{#if controls.has("stroke")}
|
|
194
|
+
<div
|
|
195
|
+
class="cv-section"
|
|
196
|
+
role="group"
|
|
197
|
+
aria-label={strokeLabel}
|
|
198
|
+
data-testid="style-stroke"
|
|
199
|
+
>
|
|
200
|
+
<span class="cv-label" aria-hidden="true">{strokeLabel}</span>
|
|
201
|
+
<Toolbar.Group
|
|
202
|
+
type="single"
|
|
203
|
+
class="cv-swatches"
|
|
204
|
+
bind:value={() => values.stroke, (value) => pick("stroke", value)}
|
|
205
|
+
>
|
|
206
|
+
{#each PEN_COLORS as swatch (swatch)}
|
|
207
|
+
{@render colorItem(swatch, strokeLabel)}
|
|
208
|
+
{/each}
|
|
209
|
+
</Toolbar.Group>
|
|
210
|
+
</div>
|
|
211
|
+
{/if}
|
|
188
212
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
213
|
+
{#if controls.has("width")}
|
|
214
|
+
<div
|
|
215
|
+
class="cv-section"
|
|
216
|
+
role="group"
|
|
217
|
+
aria-label={labels.width}
|
|
218
|
+
data-testid="style-width"
|
|
219
|
+
>
|
|
220
|
+
<span class="cv-label" aria-hidden="true">{labels.width}</span>
|
|
221
|
+
<Toolbar.Group
|
|
222
|
+
type="single"
|
|
223
|
+
class="cv-swatches"
|
|
224
|
+
bind:value={() => String(values.width), (value) => pick("width", value)}
|
|
225
|
+
>
|
|
226
|
+
{#each LEVELS as level (level)}
|
|
227
|
+
<BarTooltip
|
|
228
|
+
label={labels.widths[level]}
|
|
229
|
+
name="{labels.width}: {labels.widths[level]}"
|
|
230
|
+
side="left"
|
|
231
|
+
>
|
|
232
|
+
{#snippet child({ props })}
|
|
233
|
+
<Toolbar.GroupItem {...props} value={String(level)} class="cv-btn">
|
|
234
|
+
<span class="cv-line" style:--line="{[1.5, 3, 5][level]}px"></span>
|
|
235
|
+
</Toolbar.GroupItem>
|
|
236
|
+
{/snippet}
|
|
237
|
+
</BarTooltip>
|
|
238
|
+
{/each}
|
|
239
|
+
</Toolbar.Group>
|
|
240
|
+
</div>
|
|
241
|
+
{/if}
|
|
208
242
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
243
|
+
{#if controls.has("fontSize")}
|
|
244
|
+
<div
|
|
245
|
+
class="cv-section"
|
|
246
|
+
role="group"
|
|
247
|
+
aria-label={labels.font}
|
|
248
|
+
data-testid="style-font"
|
|
249
|
+
>
|
|
250
|
+
<span class="cv-label" aria-hidden="true">{labels.font}</span>
|
|
251
|
+
<!-- Size and weight are two picks that share one grid, so each group lays out as its items. -->
|
|
252
|
+
<div class="cv-swatches">
|
|
253
|
+
<Toolbar.Group
|
|
254
|
+
type="single"
|
|
255
|
+
class="cv-contents"
|
|
256
|
+
bind:value={() => String(values.fontSize), (value) => pick("fontSize", value)}
|
|
257
|
+
>
|
|
258
|
+
{#each FONT_SIZES as size (size)}
|
|
259
|
+
<Toolbar.GroupItem
|
|
260
|
+
value={String(size)}
|
|
261
|
+
class="cv-btn cv-figure"
|
|
262
|
+
aria-label="{labels.font}: {size}">{size}</Toolbar.GroupItem
|
|
263
|
+
>
|
|
264
|
+
{/each}
|
|
265
|
+
</Toolbar.Group>
|
|
266
|
+
<Toolbar.Group
|
|
267
|
+
type="single"
|
|
268
|
+
class="cv-contents"
|
|
269
|
+
bind:value={() => values.fontWeight, (value) => pick("fontWeight", value)}
|
|
270
|
+
>
|
|
271
|
+
{#each WEIGHTS as weight (weight)}
|
|
272
|
+
<BarTooltip
|
|
273
|
+
label={weight === "bold" ? labels.bold : labels.regular}
|
|
274
|
+
side="left"
|
|
275
|
+
>
|
|
276
|
+
{#snippet child({ props })}
|
|
277
|
+
<Toolbar.GroupItem
|
|
278
|
+
{...props}
|
|
279
|
+
value={weight}
|
|
280
|
+
class="cv-btn cv-figure"
|
|
281
|
+
style="font-weight: {weight === 'bold' ? 700 : 400}"
|
|
282
|
+
>Aa</Toolbar.GroupItem
|
|
283
|
+
>
|
|
284
|
+
{/snippet}
|
|
285
|
+
</BarTooltip>
|
|
286
|
+
{/each}
|
|
287
|
+
</Toolbar.Group>
|
|
288
|
+
</div>
|
|
289
|
+
</div>
|
|
290
|
+
{/if}
|
|
291
|
+
</div>
|
|
292
|
+
{/snippet}
|
|
293
|
+
</Toolbar.Root>
|
|
294
|
+
{/key}
|
|
295
|
+
</Tooltip.Provider>
|
|
237
296
|
</Panel>
|
|
238
297
|
{/if}
|
|
239
298
|
|
|
299
|
+
{#snippet colorItem(swatch: PenColor, section: string)}
|
|
300
|
+
<BarTooltip label={labels.colors[swatch]} name="{section}: {labels.colors[swatch]}" side="left">
|
|
301
|
+
{#snippet child({ props })}
|
|
302
|
+
<Toolbar.GroupItem
|
|
303
|
+
{...props}
|
|
304
|
+
value={swatch}
|
|
305
|
+
class="cv-swatch"
|
|
306
|
+
style="--swatch: var(--pen-{swatch})"
|
|
307
|
+
/>
|
|
308
|
+
{/snippet}
|
|
309
|
+
</BarTooltip>
|
|
310
|
+
{/snippet}
|
|
311
|
+
|
|
240
312
|
<style>
|
|
241
313
|
@layer theme, base, components, utilities;
|
|
242
314
|
|
|
@@ -261,12 +333,17 @@
|
|
|
261
333
|
line-height: calc(16 / 12);
|
|
262
334
|
}
|
|
263
335
|
|
|
264
|
-
.
|
|
336
|
+
/* Bits renders the groups and their items, so those classes are reached through the bar. */
|
|
337
|
+
.cv-style :global(.cv-swatches) {
|
|
265
338
|
display: grid;
|
|
266
339
|
grid-template-columns: repeat(3, var(--cv-btn));
|
|
267
340
|
gap: 0;
|
|
268
341
|
}
|
|
269
342
|
|
|
343
|
+
.cv-style :global(.cv-contents) {
|
|
344
|
+
display: contents;
|
|
345
|
+
}
|
|
346
|
+
|
|
270
347
|
.cv-line {
|
|
271
348
|
width: 18px;
|
|
272
349
|
height: var(--line);
|
|
@@ -274,13 +351,13 @@
|
|
|
274
351
|
background: currentColor;
|
|
275
352
|
}
|
|
276
353
|
|
|
277
|
-
.cv-figure {
|
|
354
|
+
.cv-style :global(.cv-figure) {
|
|
278
355
|
font-size: 12px;
|
|
279
356
|
font-variant-numeric: tabular-nums;
|
|
280
357
|
}
|
|
281
358
|
|
|
282
359
|
/* No fill is an empty disc struck through, since a swatch can't show the absence of a colour. */
|
|
283
|
-
.cv-swatch-none::before {
|
|
360
|
+
.cv-style :global(.cv-swatch-none)::before {
|
|
284
361
|
background:
|
|
285
362
|
linear-gradient(135deg, transparent 46%, var(--cv-ink-muted) 46% 54%, transparent 54%),
|
|
286
363
|
var(--cv-surface);
|
|
@@ -67,6 +67,14 @@
|
|
|
67
67
|
pointer-events: none;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
/* A toolbar group lays its items out as if they sat in the bar itself. */
|
|
71
|
+
.cv-group {
|
|
72
|
+
display: flex;
|
|
73
|
+
flex-direction: inherit;
|
|
74
|
+
align-items: center;
|
|
75
|
+
gap: inherit;
|
|
76
|
+
}
|
|
77
|
+
|
|
70
78
|
/* Groups are set apart by space, never by a rule. */
|
|
71
79
|
.cv-gap {
|
|
72
80
|
flex: none;
|
|
@@ -107,6 +115,7 @@
|
|
|
107
115
|
|
|
108
116
|
/* One active style for a tool, a size, a brush and an open popover. */
|
|
109
117
|
.cv-btn[aria-pressed="true"],
|
|
118
|
+
.cv-btn[data-state="on"],
|
|
110
119
|
.cv-btn[aria-expanded="true"],
|
|
111
120
|
.cv-btn[data-state="open"] {
|
|
112
121
|
background: var(--cv-surface-hover);
|
|
@@ -169,7 +178,8 @@
|
|
|
169
178
|
}
|
|
170
179
|
}
|
|
171
180
|
|
|
172
|
-
.cv-swatch[aria-pressed="true"]::before
|
|
181
|
+
.cv-swatch[aria-pressed="true"]::before,
|
|
182
|
+
.cv-swatch[data-state="on"]::before {
|
|
173
183
|
box-shadow:
|
|
174
184
|
0 0 0 2px var(--cv-surface),
|
|
175
185
|
0 0 0 4px var(--cv-ink);
|
|
@@ -182,64 +192,9 @@
|
|
|
182
192
|
background: currentColor;
|
|
183
193
|
}
|
|
184
194
|
|
|
185
|
-
/* A label on hover, for a pointer that can hover. Touch gets the aria-label alone. */
|
|
186
|
-
@media (hover: hover) {
|
|
187
|
-
.cv-btn[data-tip]::after,
|
|
188
|
-
.cv-swatch[data-tip]::after {
|
|
189
|
-
content: attr(data-tip);
|
|
190
|
-
position: absolute;
|
|
191
|
-
bottom: calc(100% + 10px);
|
|
192
|
-
left: 50%;
|
|
193
|
-
translate: -50% 4px;
|
|
194
|
-
padding: 4px 8px;
|
|
195
|
-
border-radius: var(--radius-sm, 6px);
|
|
196
|
-
background: var(--foreground, #1c1c24);
|
|
197
|
-
color: var(--background, #fff);
|
|
198
|
-
font-size: 12px;
|
|
199
|
-
line-height: 1.2;
|
|
200
|
-
white-space: pre;
|
|
201
|
-
pointer-events: none;
|
|
202
|
-
opacity: 0;
|
|
203
|
-
z-index: 5;
|
|
204
|
-
transition:
|
|
205
|
-
opacity var(--duration-fast, 150ms) var(--ease-smooth-out, ease-out),
|
|
206
|
-
translate var(--duration-fast, 150ms) var(--ease-smooth-out, ease-out);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
.cv-btn[data-tip]:hover::after,
|
|
210
|
-
.cv-btn[data-tip]:focus-visible::after,
|
|
211
|
-
.cv-swatch[data-tip]:hover::after,
|
|
212
|
-
.cv-swatch[data-tip]:focus-visible::after {
|
|
213
|
-
opacity: 1;
|
|
214
|
-
translate: -50% 0;
|
|
215
|
-
transition-delay: 350ms;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/* On the rail the label opens to the right. */
|
|
219
|
-
.cv-rail .cv-btn[data-tip]::after {
|
|
220
|
-
top: 50%;
|
|
221
|
-
bottom: auto;
|
|
222
|
-
left: calc(100% + 10px);
|
|
223
|
-
translate: -4px -50%;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
.cv-rail .cv-btn[data-tip]:hover::after,
|
|
227
|
-
.cv-rail .cv-btn[data-tip]:focus-visible::after {
|
|
228
|
-
translate: 0 -50%;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
.cv-btn:active::after,
|
|
232
|
-
.cv-btn:disabled::after,
|
|
233
|
-
.cv-btn[aria-expanded="true"]::after,
|
|
234
|
-
.cv-btn[data-state="open"]::after {
|
|
235
|
-
opacity: 0 !important;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
195
|
@media (prefers-reduced-motion: reduce) {
|
|
240
196
|
.cv-btn,
|
|
241
|
-
.cv-swatch::before
|
|
242
|
-
.cv-btn[data-tip]::after {
|
|
197
|
+
.cv-swatch::before {
|
|
243
198
|
transition: none;
|
|
244
199
|
}
|
|
245
200
|
|
|
@@ -247,25 +202,6 @@
|
|
|
247
202
|
scale: 1;
|
|
248
203
|
}
|
|
249
204
|
}
|
|
250
|
-
|
|
251
|
-
/* On a bar docked to the right edge the label opens to the left. */
|
|
252
|
-
@media (hover: hover) {
|
|
253
|
-
.cv-side .cv-btn[data-tip]::after,
|
|
254
|
-
.cv-side .cv-swatch[data-tip]::after {
|
|
255
|
-
top: 50%;
|
|
256
|
-
bottom: auto;
|
|
257
|
-
left: auto;
|
|
258
|
-
right: calc(100% + 10px);
|
|
259
|
-
translate: 4px -50%;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
.cv-side .cv-btn[data-tip]:hover::after,
|
|
263
|
-
.cv-side .cv-btn[data-tip]:focus-visible::after,
|
|
264
|
-
.cv-side .cv-swatch[data-tip]:hover::after,
|
|
265
|
-
.cv-side .cv-swatch[data-tip]:focus-visible::after {
|
|
266
|
-
translate: 0 -50%;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
205
|
}
|
|
270
206
|
|
|
271
207
|
@property --cv-fade-start {
|
|
@@ -43,9 +43,11 @@
|
|
|
43
43
|
*/
|
|
44
44
|
import { cn } from "../../lib/utils.js";
|
|
45
45
|
import RatingChip from "./rating-chip.svelte";
|
|
46
|
+
import Hint from "./hint.svelte";
|
|
46
47
|
import {
|
|
47
48
|
Pagination,
|
|
48
49
|
SortableHead,
|
|
50
|
+
TableSkeleton,
|
|
49
51
|
paginateRows,
|
|
50
52
|
totalPages,
|
|
51
53
|
type SortState,
|
|
@@ -127,6 +129,18 @@
|
|
|
127
129
|
});
|
|
128
130
|
|
|
129
131
|
const visible = $derived(pageSize ? paginateRows(sorted, page, pageSize) : sorted);
|
|
132
|
+
|
|
133
|
+
// Placeholder rows hold the 40px of a lead cell's padding, touch or not, and the rule sits
|
|
134
|
+
// outside it as before. Each cell keeps its column's padding, rule and breakpoint.
|
|
135
|
+
const loadingCell = "box-content h-10 any-pointer-coarse:h-10";
|
|
136
|
+
const loadingColumns = $derived([
|
|
137
|
+
{ width: "w-2/3", class: cn("px-4", loadingCell) },
|
|
138
|
+
...columns.map((column) => ({
|
|
139
|
+
width: "w-3/4",
|
|
140
|
+
align: column.align === "start" ? ("left" as const) : ("right" as const),
|
|
141
|
+
class: cn(columnClass(column), loadingCell),
|
|
142
|
+
})),
|
|
143
|
+
]);
|
|
130
144
|
const opens = $derived(Boolean(rowHref || onRowClick));
|
|
131
145
|
|
|
132
146
|
// Fewer rows than the page you stand on would draw an empty table.
|
|
@@ -182,96 +196,82 @@
|
|
|
182
196
|
<th
|
|
183
197
|
scope="col"
|
|
184
198
|
class={cn(column.align === "start" ? dataThClass : dataThNumClass, columnClass(column))}
|
|
185
|
-
title={column.hint}
|
|
186
199
|
>
|
|
187
|
-
{column.label}
|
|
200
|
+
<Hint text={column.hint}>{column.label}</Hint>
|
|
188
201
|
</th>
|
|
189
202
|
{/if}
|
|
190
203
|
{/each}
|
|
191
204
|
</tr>
|
|
192
205
|
</thead>
|
|
193
|
-
|
|
194
|
-
{
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
</td>
|
|
200
|
-
{#each columns as column (column.key)}
|
|
201
|
-
<td class={cn(dataTdNumClass, columnClass(column), "align-middle")}>
|
|
202
|
-
<span
|
|
203
|
-
class={cn(
|
|
204
|
-
"block h-4 w-3/4 animate-pulse rounded bg-muted",
|
|
205
|
-
column.align !== "start" && "ml-auto",
|
|
206
|
-
)}
|
|
207
|
-
></span>
|
|
208
|
-
</td>
|
|
209
|
-
{/each}
|
|
206
|
+
{#if loading}
|
|
207
|
+
<TableSkeleton rows={LOADING_ROWS} columns={loadingColumns} rowClass={dataTrClass} />
|
|
208
|
+
{:else}
|
|
209
|
+
<tbody>
|
|
210
|
+
{#if visible.length === 0 && empty}
|
|
211
|
+
<tr>
|
|
212
|
+
<td colspan={columns.length + 1}>{@render empty()}</td>
|
|
210
213
|
</tr>
|
|
211
|
-
{
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
<a href={rowHref(row)} class={stretch}>{@render lead(row)}</a>
|
|
222
|
-
{:else if onRowClick}
|
|
223
|
-
<button type="button" class={stretch} onclick={() => onRowClick(row)}>
|
|
224
|
-
{@render lead(row)}
|
|
225
|
-
</button>
|
|
226
|
-
{:else}
|
|
227
|
-
{@render lead(row)}
|
|
228
|
-
{/if}
|
|
229
|
-
</td>
|
|
230
|
-
{#each columns as column (column.key)}
|
|
231
|
-
<td
|
|
232
|
-
class={cn(
|
|
233
|
-
dataTdNumClass,
|
|
234
|
-
columnClass(column),
|
|
235
|
-
"align-middle whitespace-nowrap",
|
|
236
|
-
column.strong && "font-medium",
|
|
237
|
-
isMuted(column, row) && "text-muted-foreground",
|
|
238
|
-
column.negative?.(row) && "text-destructive-ink",
|
|
239
|
-
)}
|
|
240
|
-
title={column.title?.(row)}
|
|
241
|
-
>
|
|
242
|
-
{#if column.cell}
|
|
243
|
-
{@render column.cell(row)}
|
|
244
|
-
{:else if column.rating}
|
|
245
|
-
{@const rating = column.rating(row)}
|
|
246
|
-
{#if rating}
|
|
247
|
-
<RatingChip grade={rating.grade} hint={rating.hint} />
|
|
248
|
-
{:else}
|
|
249
|
-
<span class="text-muted-foreground">{emptyLabel}</span>
|
|
250
|
-
{/if}
|
|
214
|
+
{:else}
|
|
215
|
+
{#each visible as row (rowKey(row))}
|
|
216
|
+
<tr class={cn(dataTrClass, "group", opens && "relative cursor-pointer")}>
|
|
217
|
+
<td class={cn(dataTdClass, "py-3")}>
|
|
218
|
+
{#if rowHref}
|
|
219
|
+
<a href={rowHref(row)} class={stretch}>{@render lead(row)}</a>
|
|
220
|
+
{:else if onRowClick}
|
|
221
|
+
<button type="button" class={stretch} onclick={() => onRowClick(row)}>
|
|
222
|
+
{@render lead(row)}
|
|
223
|
+
</button>
|
|
251
224
|
{:else}
|
|
252
|
-
{@
|
|
253
|
-
{#if value === null}
|
|
254
|
-
<span class="text-muted-foreground">{emptyLabel}</span>
|
|
255
|
-
{:else if column.bar}
|
|
256
|
-
<span class="flex items-center justify-end gap-3">
|
|
257
|
-
<span class="h-1.5 min-w-0 flex-1 overflow-hidden rounded-full bg-muted">
|
|
258
|
-
<span
|
|
259
|
-
class="block h-full origin-left rounded-full bg-primary/70"
|
|
260
|
-
style:transform="scaleX({Math.min(1, Math.max(0, column.bar(row)))})"
|
|
261
|
-
></span>
|
|
262
|
-
</span>
|
|
263
|
-
<span>{value}</span>
|
|
264
|
-
</span>
|
|
265
|
-
{:else}
|
|
266
|
-
{value}
|
|
267
|
-
{/if}
|
|
225
|
+
{@render lead(row)}
|
|
268
226
|
{/if}
|
|
269
227
|
</td>
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
228
|
+
{#each columns as column (column.key)}
|
|
229
|
+
<td
|
|
230
|
+
class={cn(
|
|
231
|
+
dataTdNumClass,
|
|
232
|
+
columnClass(column),
|
|
233
|
+
"align-middle whitespace-nowrap",
|
|
234
|
+
column.strong && "font-medium",
|
|
235
|
+
isMuted(column, row) && "text-muted-foreground",
|
|
236
|
+
column.negative?.(row) && "text-destructive-ink",
|
|
237
|
+
)}
|
|
238
|
+
>
|
|
239
|
+
<Hint text={column.title?.(row)} class="block">
|
|
240
|
+
{#if column.cell}
|
|
241
|
+
{@render column.cell(row)}
|
|
242
|
+
{:else if column.rating}
|
|
243
|
+
{@const rating = column.rating(row)}
|
|
244
|
+
{#if rating}
|
|
245
|
+
<RatingChip grade={rating.grade} hint={rating.hint} />
|
|
246
|
+
{:else}
|
|
247
|
+
<span class="text-muted-foreground">{emptyLabel}</span>
|
|
248
|
+
{/if}
|
|
249
|
+
{:else}
|
|
250
|
+
{@const value = column.value?.(row) ?? null}
|
|
251
|
+
{#if value === null}
|
|
252
|
+
<span class="text-muted-foreground">{emptyLabel}</span>
|
|
253
|
+
{:else if column.bar}
|
|
254
|
+
<span class="flex items-center justify-end gap-3">
|
|
255
|
+
<span class="h-1.5 min-w-0 flex-1 overflow-hidden rounded-full bg-muted">
|
|
256
|
+
<span
|
|
257
|
+
class="block h-full origin-left rounded-full bg-primary/70"
|
|
258
|
+
style:transform="scaleX({Math.min(1, Math.max(0, column.bar(row)))})"
|
|
259
|
+
></span>
|
|
260
|
+
</span>
|
|
261
|
+
<span>{value}</span>
|
|
262
|
+
</span>
|
|
263
|
+
{:else}
|
|
264
|
+
{value}
|
|
265
|
+
{/if}
|
|
266
|
+
{/if}
|
|
267
|
+
</Hint>
|
|
268
|
+
</td>
|
|
269
|
+
{/each}
|
|
270
|
+
</tr>
|
|
271
|
+
{/each}
|
|
272
|
+
{/if}
|
|
273
|
+
</tbody>
|
|
274
|
+
{/if}
|
|
275
275
|
</table>
|
|
276
276
|
{#if pageSize && !loading}
|
|
277
277
|
<Pagination {page} {pageSize} total={sorted.length} onPageChange={(next) => (page = next)} />
|