@heyramzi/ui 2.1.0 → 2.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/package.json +7 -8
- package/scripts/sync-shadcn.mjs +3 -4
- package/src/agents/agent-avatar.svelte +604 -575
- package/src/agents/agent-cast.svelte +179 -183
- package/src/agents/demo.svelte +125 -113
- package/src/agents/helpers.ts +23 -17
- package/src/agents/index.ts +4 -4
- package/src/agents/page-state.ts +6 -6
- package/src/agents/types.ts +6 -6
- package/src/ai/ai-button/AiButton.svelte +4 -12
- package/src/ai/generation-progress/GenerationProgress.svelte +10 -3
- package/src/ai/prompt-input/PromptInputTextarea.svelte +1 -2
- package/src/ai/response/Response.css +2 -3
- package/src/animations/avatar-group-hover.css +2 -16
- package/src/animations/card-tilt.css +3 -6
- package/src/animations/digit-pop-in.css +2 -15
- package/src/animations/dropdown.css +4 -17
- package/src/animations/input-clear-dissolve.css +5 -18
- package/src/animations/input-shake.css +9 -41
- package/src/animations/learn-more-hover.css +17 -9
- package/src/animations/like-button.css +2 -4
- package/src/animations/page-side-by-side.css +2 -10
- package/src/animations/plus-menu-morph.css +2 -5
- package/src/animations/shimmer-text.css +5 -18
- package/src/animations/spinning-counter.css +2 -6
- package/src/animations/success-check.css +2 -15
- package/src/animations/thinking-states.css +2 -7
- package/src/brand/brand.test.ts +21 -5
- package/src/brand/index.ts +4 -10
- package/src/canvas/card/card-frame.svelte +43 -39
- package/src/canvas/card/resize-handles.svelte +89 -80
- package/src/canvas/chrome/chrome-zoom.svelte +101 -100
- package/src/canvas/chrome/lock-badge.svelte +8 -8
- package/src/canvas/chrome/side-handles.svelte +24 -19
- package/src/canvas/chrome/size-chip.svelte +30 -30
- package/src/canvas/chrome/snap-guides.svelte +20 -20
- package/src/canvas/components/icons/ToolIcon.svelte +8 -8
- package/src/canvas/components/shared/svg/PreviewSvg.svelte +19 -19
- package/src/canvas/components/shared/svg/ShapeOutline.svelte +71 -48
- package/src/canvas/components/shared/svg/StrokeNode.svelte +21 -27
- package/src/canvas/components/shared/svg/TextRule.svelte +25 -17
- package/src/canvas/edges/bend-handle.svelte +46 -46
- package/src/canvas/edges/edge-gradient.svelte +10 -10
- package/src/canvas/edges/gradient-edge.svelte +45 -29
- package/src/canvas/editor/lock.test.ts +6 -1
- package/src/canvas/editor/style.ts +7 -1
- package/src/canvas/headless/svg-figure.test.ts +5 -1
- package/src/canvas/lod/lod-switch.svelte +111 -106
- package/src/canvas/pen/PenField.svelte +87 -81
- package/src/canvas/pen/PenOverlay.svelte +127 -127
- package/src/canvas/pen/ShapeNode.svelte +31 -32
- package/src/canvas/pen/TextNode.svelte +54 -53
- package/src/canvas/pen/host-field.test.svelte +7 -7
- package/src/canvas/pen/shapes.ts +7 -1
- package/src/canvas/screen/GlyphIcon.svelte +22 -17
- package/src/canvas/screen/PartView.svelte +138 -72
- package/src/canvas/screen/ScreenBody.svelte +81 -60
- package/src/canvas/screen/ScreenCard.svelte +20 -20
- package/src/canvas/ui/CanvasToolbar.svelte +173 -85
- package/src/canvas/ui/FrameLayer.svelte +59 -59
- package/src/canvas/ui/PenLayer.svelte +39 -30
- package/src/canvas/ui/StylePanel.svelte +119 -102
- package/src/canvas/ui/canvas-grid.svelte +137 -129
- package/src/charts/animated-value.svelte +1 -1
- package/src/charts/area-chart.css +2 -5
- package/src/charts/area-chart.svelte +1 -1
- package/src/charts/bar-chart.css +2 -3
- package/src/charts/bar-chart.svelte +1 -1
- package/src/charts/charts.css +5 -17
- package/src/charts/crosshair.svelte +1 -1
- package/src/charts/dashboard/dashboard-empty.svelte +25 -25
- package/src/charts/dashboard/dashboard-section.svelte +24 -24
- package/src/charts/dashboard/data-panel.svelte +50 -50
- package/src/charts/dashboard/data-sort-head.svelte +56 -56
- package/src/charts/dashboard/data-table.svelte +138 -138
- package/src/charts/dashboard/panel-grid.svelte +30 -30
- package/src/charts/dashboard/period-filter.svelte +25 -25
- package/src/charts/dashboard/rating-chip.svelte +19 -19
- package/src/charts/dashboard/stat-band.svelte +32 -32
- package/src/charts/dashboard/stat-tile.svelte +106 -99
- package/src/charts/donut-chart.svelte +1 -1
- package/src/charts/edge-fade.svelte +1 -1
- package/src/charts/figure.svelte +1 -1
- package/src/charts/gauge-chart.css +4 -7
- package/src/charts/gauge-chart.svelte +1 -1
- package/src/charts/hover-focus.svelte +1 -1
- package/src/charts/legend.css +1 -3
- package/src/charts/legend.svelte +1 -1
- package/src/charts/line-chart.svelte +1 -1
- package/src/charts/number-chart.css +6 -9
- package/src/charts/number-chart.svelte +1 -1
- package/src/charts/reference-line.svelte +19 -19
- package/src/charts/reveal.svelte +1 -1
- package/src/charts/ring-chart.css +2 -4
- package/src/charts/ring-chart.svelte +1 -1
- package/src/charts/sparkline.svelte +5 -2
- package/src/charts/stat-card.svelte +1 -1
- package/src/charts/time-series-chart.css +2 -5
- package/src/charts/trend-pill.css +2 -4
- package/src/charts/trend-pill.svelte +1 -1
- package/src/components/copy/copy-button.svelte +93 -98
- package/src/components/copy/copy-button.test.ts +10 -2
- package/src/components/data-table/infinite-scroll.svelte +42 -42
- package/src/components/data-table/pagination.svelte +34 -22
- package/src/components/data-table/row-arrow.svelte +19 -16
- package/src/components/data-table/row-context-menu.svelte +52 -52
- package/src/components/data-table/sortable-head.svelte +25 -25
- package/src/components/data-table/table-skeleton.svelte +41 -36
- package/src/components/docs/doc-page.svelte +18 -9
- package/src/components/docs/on-this-page.svelte +16 -3
- package/src/components/docs/scroll-spy.svelte.ts +2 -3
- package/src/components/field-row/field-row.svelte +10 -8
- package/src/components/hold-to-confirm/hold-to-confirm.svelte +5 -1
- package/src/components/icon/icon.svelte +14 -14
- package/src/components/language-flag/language-flag.svelte +3 -1
- package/src/components/list-page/detail-empty.svelte +34 -34
- package/src/components/list-page/detail-header.svelte +55 -47
- package/src/components/list-page/detail-panel.svelte +51 -59
- package/src/components/list-page/detail-row.svelte +19 -19
- package/src/components/list-page/detail-stack.svelte +21 -21
- package/src/components/list-page/empty-state.svelte +30 -30
- package/src/components/list-page/filter-item.svelte +20 -20
- package/src/components/list-page/filter-popover.svelte +60 -55
- package/src/components/list-page/grouped-list.svelte +4 -2
- package/src/components/list-page/index.ts +5 -1
- package/src/components/list-page/list-page-shell.svelte +2 -5
- package/src/components/list-page/master-detail.svelte +87 -88
- package/src/components/list-page/notice-bar.svelte +38 -38
- package/src/components/list-page/prev-next.svelte +63 -63
- package/src/components/list-page/rail-body.svelte +18 -18
- package/src/components/list-page/rail-empty.svelte +23 -30
- package/src/components/list-page/rail-head.svelte +24 -24
- package/src/components/list-page/rail-row.svelte +77 -73
- package/src/components/list-page/section-host.ts +2 -3
- package/src/components/locale-switch/locale-switch.svelte +41 -41
- package/src/components/mark/mark.svelte +34 -38
- package/src/components/open-in/index.ts +1 -5
- package/src/components/open-in/open-in.svelte +40 -40
- package/src/components/picker/mark-picker.svelte +49 -49
- package/src/components/picker/picker.svelte +5 -3
- package/src/components/section-header/section-header.test.ts +7 -1
- package/src/components/section-header/section-label.svelte +20 -20
- package/src/components/segmented/animated-tabs.svelte +31 -2
- package/src/components/segmented/collapse.test.ts +2 -1
- package/src/components/segmented/index.ts +2 -0
- package/src/components/segmented/segment.svelte +52 -49
- package/src/components/segmented/segmented-filter.svelte +79 -80
- package/src/components/segmented/segmented-nav.svelte +34 -40
- package/src/components/segmented/segmented-overflow.svelte +53 -50
- package/src/components/segmented/segmented-track.svelte +62 -46
- package/src/components/segmented/segmented.ts +2 -3
- package/src/components/star-rating-input/star-rating-input.svelte +103 -103
- package/src/components/svg/svg.svelte +4 -4
- package/src/components/table-view/table-view.svelte +17 -2
- package/src/components/table-view/table-view.test.ts +7 -1
- package/src/components/table-view/types.ts +11 -11
- package/src/components/tags-input/index.ts +13 -0
- package/src/components/tags-input/tags-input.svelte +305 -0
- package/src/components/tags-input/tags.test.ts +64 -0
- package/src/components/tags-input/tags.ts +142 -0
- package/src/components/theme/index.ts +2 -4
- package/src/components/theme/theme-provider.svelte +1 -3
- package/src/components/toolbar/toolbar-search.svelte +25 -25
- package/src/components/toolbar/toolbar.svelte +11 -7
- package/src/components/toolbar/toolbar.test.ts +10 -1
- package/src/design/index.css +21 -78
- package/src/design/polish.css +26 -35
- package/src/editor/editor-toolbar.svelte +104 -99
- package/src/editor/editor.svelte +296 -266
- package/src/editor/image-toolbar.svelte +20 -22
- package/src/editor/index.ts +1 -4
- package/src/editor/inline-editor.svelte +280 -238
- package/src/editor/markdown.ts +15 -15
- package/src/editor/renderer.svelte +71 -71
- package/src/editor/slash-menu.svelte +24 -27
- package/src/effects/animated-beam/diagrams/BeamSvg.svelte +1 -1
- package/src/effects/image-comparison/ImageComparison.svelte +11 -1
- package/src/effects/interactive-hover-button/InteractiveHoverButton.svelte +59 -46
- package/src/effects/magic-card/MagicCard.css +14 -7
- package/src/effects/marquee/Marquee.svelte +3 -5
- package/src/effects/reveal/observe.ts +0 -3
- package/src/effects/spotlight/Spotlight.svelte +1 -1
- package/src/effects/theme-toggle/ThemeToggle.css +25 -22
- package/src/effects/theme-toggle/ThemeToggle.svelte +6 -7
- package/src/gallery.test.ts +14 -4
- package/src/inbox/README.md +1 -3
- package/src/inbox/inbox.test.ts +181 -138
- package/src/inbox/index.ts +20 -20
- package/src/inbox/logic.ts +114 -111
- package/src/inbox/thread-inbox.svelte +227 -151
- package/src/inbox/types.ts +130 -108
- package/src/lib/theme-transition.ts +2 -4
- package/src/lib/tone.ts +2 -4
- package/src/lib/utils.ts +2 -2
- package/src/modules.test.ts +79 -69
- package/src/onboarding/define-tours.test.ts +55 -40
- package/src/onboarding/define-tours.ts +23 -23
- package/src/onboarding/demo.svelte +24 -5
- package/src/onboarding/index.ts +1 -8
- package/src/onboarding/onboarding-provider.svelte +19 -7
- package/src/onboarding/onboarding.svelte.ts +16 -16
- package/src/onboarding/pick.test.ts +61 -52
- package/src/onboarding/pick.ts +16 -13
- package/src/onboarding/stores.test.ts +80 -80
- package/src/onboarding/stores.ts +41 -39
- package/src/onboarding/tour-card.svelte +30 -5
- package/src/onboarding/tour-runner.svelte +12 -3
- package/src/onboarding/types.ts +20 -12
- package/src/onboarding/wait.ts +22 -22
- package/src/panel/side-panel.svelte +94 -93
- package/src/record/demo-data.ts +291 -225
- package/src/record/field-section.svelte +38 -34
- package/src/record/fields.ts +148 -96
- package/src/record/format.ts +63 -56
- package/src/record/index.ts +49 -35
- package/src/record/info-list.svelte +145 -114
- package/src/record/kinds.ts +31 -31
- package/src/record/logic.ts +216 -214
- package/src/record/next-up.svelte +45 -34
- package/src/record/project-list.svelte +56 -50
- package/src/record/record-demo.svelte +130 -110
- package/src/record/record-header.svelte +37 -28
- package/src/record/record-shell.svelte +54 -54
- package/src/record/record-stats.svelte +25 -25
- package/src/record/record-timeline.svelte +134 -114
- package/src/record/record.test.ts +430 -309
- package/src/record/steps-rail.svelte +225 -216
- package/src/record/types.ts +234 -215
- package/src/record/where-we-are.svelte +62 -58
- package/src/shadcn/button/button.svelte +1 -1
- package/src/shadcn/card/card-title.svelte +1 -5
- package/src/shadcn/hooks/use-highlighted-code.svelte.ts +2 -13
- package/src/shadcn/sheet/sheet-content.svelte +2 -4
- package/src/shadcn/table/table-cell.svelte +2 -3
- package/src/shadcn/utilities.css +3 -8
- package/src/slides/Controls.svelte +150 -124
- package/src/slides/Deck.svelte +243 -238
- package/src/slides/Fragment.svelte +64 -57
- package/src/slides/Overview.svelte +106 -101
- package/src/slides/Presenter.svelte +300 -266
- package/src/slides/Slide.svelte +185 -170
- package/src/slides/SlideList.svelte +16 -12
- package/src/slides/ambient.d.ts +1 -1
- package/src/slides/bridges.ts +42 -23
- package/src/slides/deck-controller.svelte.ts +47 -35
- package/src/slides/demo-slides.svelte +330 -306
- package/src/slides/demo.svelte +164 -160
- package/src/slides/export/capture.ts +20 -16
- package/src/slides/export/ffmpeg.ts +43 -38
- package/src/slides/export/index.ts +5 -5
- package/src/slides/export/launch.ts +3 -1
- package/src/slides/export/pdf.ts +30 -27
- package/src/slides/export/types.ts +10 -8
- package/src/slides/index.ts +11 -11
- package/src/slides/labels.ts +22 -19
- package/src/slides/layout.ts +9 -2
- package/src/slides/model.ts +4 -4
- package/src/slides/navigation.ts +43 -31
- package/src/slides/theme.css +5 -7
- package/src/slides/types.ts +9 -10
- package/src/status/define-statuses.ts +17 -24
- package/src/status/index.ts +5 -5
- package/src/status/status-badge.svelte +3 -1
- package/src/status/status-dot.svelte +1 -3
- package/src/status/status-icon.svelte +1 -1
- package/src/status/status-types.ts +21 -21
- package/src/tasks/board-order.ts +2 -4
- package/src/tasks/demo.svelte +189 -31
- package/src/tasks/detail/detail-demo.svelte +73 -13
- package/src/tasks/detail/detail-types.ts +45 -45
- package/src/tasks/detail/task-detail.svelte +40 -10
- package/src/tasks/detail/task-property.svelte +8 -2
- package/src/tasks/detail/task-thread.svelte +53 -12
- package/src/tasks/gantt/gantt-bar.svelte +17 -6
- package/src/tasks/gantt/gantt-grid.svelte +40 -9
- package/src/tasks/gantt/gantt-group-row.svelte +8 -2
- package/src/tasks/gantt/gantt-header.svelte +9 -3
- package/src/tasks/gantt/gantt-task-row.svelte +26 -6
- package/src/tasks/gantt/gantt.css +2 -1
- package/src/tasks/gantt/gantt.test.ts +3 -1
- package/src/tasks/gantt/logic.test.ts +110 -20
- package/src/tasks/gantt/logic.ts +42 -6
- package/src/tasks/inbox/inbox-card.svelte +13 -5
- package/src/tasks/inbox/inbox-draft.svelte +1 -1
- package/src/tasks/inbox/inbox-logic.ts +6 -40
- package/src/tasks/inbox/inbox-statuses.ts +23 -23
- package/src/tasks/inbox/inbox-trace.svelte +9 -3
- package/src/tasks/inbox/inbox-types.ts +54 -58
- package/src/tasks/inbox/index.ts +22 -21
- package/src/tasks/index.ts +49 -52
- package/src/tasks/kanban-board.svelte +16 -12
- package/src/tasks/kanban-types.ts +2 -3
- package/src/tasks/presets/sales.ts +6 -6
- package/src/tasks/task-gantt.svelte +28 -8
- package/src/tasks/task-list.svelte +41 -17
- package/src/tasks/time/duration.ts +40 -40
- package/src/tasks/time/time-chip.svelte +9 -2
- package/src/tasks/time/time-dot.svelte +1 -1
- package/src/tasks/time/time-ring.svelte +11 -3
- package/src/tasks/time/time-tracker.svelte +57 -15
- package/src/tasks/time/time-types.ts +57 -57
- package/src/tasks/use-kanban-dnd.svelte.ts +2 -3
- package/src/thread/index.ts +7 -7
- package/src/thread/logic.ts +36 -33
- package/src/thread/thread.svelte +82 -16
- package/src/thread/thread.test.ts +58 -47
- package/src/thread/types.ts +68 -68
- package/src/tokens/craft.css +17 -53
- package/src/tokens/index.css +3 -13
- package/src/tokens/palette.css +13 -11
- package/src/tokens/primitives.css +10 -23
- package/src/tasks/inbox/inbox.svelte +0 -203
|
@@ -13,7 +13,12 @@
|
|
|
13
13
|
personColor,
|
|
14
14
|
} from "./duration.js";
|
|
15
15
|
import { DEFAULT_TIME_LABELS } from "./time-types.js";
|
|
16
|
-
import type {
|
|
16
|
+
import type {
|
|
17
|
+
TimeLabels,
|
|
18
|
+
TimeLogInput,
|
|
19
|
+
TimeRunning,
|
|
20
|
+
TimeStopInfo,
|
|
21
|
+
} from "./time-types.js";
|
|
17
22
|
import TimeDot from "./time-dot.svelte";
|
|
18
23
|
import { CaretDown, Play, Stop, X } from "phosphor-svelte";
|
|
19
24
|
|
|
@@ -135,7 +140,9 @@
|
|
|
135
140
|
let invalid = $state(false);
|
|
136
141
|
let durationInput = $state<HTMLInputElement>();
|
|
137
142
|
let parsed = $derived(parseMinutes(duration));
|
|
138
|
-
let preview = $derived(
|
|
143
|
+
let preview = $derived(
|
|
144
|
+
parsed !== null && parsed >= 1 && parsed <= 1440 ? formatMinutes(parsed) : "",
|
|
145
|
+
);
|
|
139
146
|
|
|
140
147
|
async function submit(event: SubmitEvent): Promise<void> {
|
|
141
148
|
event.preventDefault();
|
|
@@ -228,9 +235,15 @@
|
|
|
228
235
|
<Popover.Trigger class="time-open" aria-label={labels.title}>
|
|
229
236
|
{#if running}
|
|
230
237
|
<TimeDot running size={6} />
|
|
231
|
-
<span role="timer" aria-label={labels.elapsed}
|
|
238
|
+
<span role="timer" aria-label={labels.elapsed}
|
|
239
|
+
>{clock.hours}:{clock.minutes}:{clock.seconds}</span
|
|
240
|
+
>
|
|
232
241
|
{:else if total > 0}
|
|
233
|
-
<span
|
|
242
|
+
<span
|
|
243
|
+
>{formatMinutes(total)}{#if estimate}<span
|
|
244
|
+
class="ms-1 font-normal text-muted-foreground">/ {formatMinutes(estimate)}</span
|
|
245
|
+
>{/if}</span
|
|
246
|
+
>
|
|
234
247
|
{:else}
|
|
235
248
|
<span class="text-muted-foreground">{labels.timer}</span>
|
|
236
249
|
{/if}
|
|
@@ -254,12 +267,20 @@
|
|
|
254
267
|
class={cn("time-field w-full tabular-nums", preview && "pr-12")}
|
|
255
268
|
/>
|
|
256
269
|
{#if preview && preview !== duration.trim()}
|
|
257
|
-
<span
|
|
270
|
+
<span
|
|
271
|
+
class="time-preview pointer-events-none absolute inset-y-0 right-2 flex items-center text-xs text-muted-foreground tabular-nums"
|
|
272
|
+
aria-hidden="true"
|
|
273
|
+
>
|
|
258
274
|
{preview}
|
|
259
275
|
</span>
|
|
260
276
|
{/if}
|
|
261
277
|
</div>
|
|
262
|
-
<input
|
|
278
|
+
<input
|
|
279
|
+
type="date"
|
|
280
|
+
bind:value={day}
|
|
281
|
+
aria-label={labels.date}
|
|
282
|
+
class="time-field w-full tabular-nums"
|
|
283
|
+
/>
|
|
263
284
|
<input
|
|
264
285
|
bind:value={note}
|
|
265
286
|
placeholder={labels.notePlaceholder}
|
|
@@ -288,11 +309,18 @@
|
|
|
288
309
|
<div class="h-1 overflow-hidden rounded-full bg-muted" aria-hidden="true">
|
|
289
310
|
<div
|
|
290
311
|
class="time-segment h-full rounded-full"
|
|
291
|
-
style:background={tone === "neutral"
|
|
312
|
+
style:background={tone === "neutral"
|
|
313
|
+
? "var(--color-foreground)"
|
|
314
|
+
: TONE_COLOR[tone]}
|
|
292
315
|
style:width="{Math.min(100, percent)}%"
|
|
293
316
|
></div>
|
|
294
317
|
</div>
|
|
295
|
-
<p
|
|
318
|
+
<p
|
|
319
|
+
class={cn(
|
|
320
|
+
"text-xs tabular-nums",
|
|
321
|
+
tone === "neutral" ? "text-muted-foreground" : TONE_TEXT[tone],
|
|
322
|
+
)}
|
|
323
|
+
>
|
|
296
324
|
{liveTotal > estimate
|
|
297
325
|
? labels.over(formatMinutes(liveTotal - estimate), formatMinutes(estimate))
|
|
298
326
|
: labels.remaining(formatMinutes(estimate - liveTotal), formatMinutes(estimate))}
|
|
@@ -303,7 +331,11 @@
|
|
|
303
331
|
<ul class="flex flex-wrap gap-x-3 gap-y-1">
|
|
304
332
|
{#each people as person (person.key)}
|
|
305
333
|
<li class="flex items-center gap-1.5 text-xs">
|
|
306
|
-
<span
|
|
334
|
+
<span
|
|
335
|
+
class="size-2 rounded-full"
|
|
336
|
+
style:background={personColor(person.key)}
|
|
337
|
+
aria-hidden="true"
|
|
338
|
+
></span>
|
|
307
339
|
<span class="text-muted-foreground">{person.name}</span>
|
|
308
340
|
<span class="font-medium tabular-nums">{formatMinutes(person.minutes)}</span>
|
|
309
341
|
</li>
|
|
@@ -318,8 +350,12 @@
|
|
|
318
350
|
{#each groups as group (group.day)}
|
|
319
351
|
<div>
|
|
320
352
|
<div class="flex items-baseline justify-between border-b border-border/60 pb-1">
|
|
321
|
-
<h3 class="text-xs font-medium text-muted-foreground">
|
|
322
|
-
|
|
353
|
+
<h3 class="text-xs font-medium text-muted-foreground">
|
|
354
|
+
{dayLabel(group.day, locale)}
|
|
355
|
+
</h3>
|
|
356
|
+
<span class="text-xs font-medium text-muted-foreground tabular-nums"
|
|
357
|
+
>{formatMinutes(group.minutes)}</span
|
|
358
|
+
>
|
|
323
359
|
</div>
|
|
324
360
|
<ul>
|
|
325
361
|
{#each group.items as entry (getId(entry))}
|
|
@@ -341,9 +377,13 @@
|
|
|
341
377
|
<span class="min-w-0 flex-1 truncate">
|
|
342
378
|
<span class="sr-only">{name},</span>
|
|
343
379
|
{#if text}{text}{:else}<span class="text-muted-foreground">{name}</span>{/if}
|
|
344
|
-
{#if renderMeta}<span class="ms-1.5 text-xs text-muted-foreground"
|
|
380
|
+
{#if renderMeta}<span class="ms-1.5 text-xs text-muted-foreground"
|
|
381
|
+
>{@render renderMeta(entry)}</span
|
|
382
|
+
>{/if}
|
|
345
383
|
</span>
|
|
346
|
-
<span class="shrink-0 font-medium tabular-nums"
|
|
384
|
+
<span class="shrink-0 font-medium tabular-nums"
|
|
385
|
+
>{formatMinutes(getMinutes(entry))}</span
|
|
386
|
+
>
|
|
347
387
|
{#if onDelete && (canDelete?.(entry) ?? true)}
|
|
348
388
|
<button
|
|
349
389
|
type="button"
|
|
@@ -498,7 +538,8 @@
|
|
|
498
538
|
}
|
|
499
539
|
|
|
500
540
|
.time-segment {
|
|
501
|
-
transition: width var(--duration-slow, 400ms)
|
|
541
|
+
transition: width var(--duration-slow, 400ms)
|
|
542
|
+
var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1));
|
|
502
543
|
}
|
|
503
544
|
|
|
504
545
|
.time-delete {
|
|
@@ -527,7 +568,8 @@
|
|
|
527
568
|
}
|
|
528
569
|
|
|
529
570
|
.time-row.is-new {
|
|
530
|
-
animation: time-row-in var(--duration-fast, 250ms)
|
|
571
|
+
animation: time-row-in var(--duration-fast, 250ms)
|
|
572
|
+
var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1)) backwards;
|
|
531
573
|
}
|
|
532
574
|
|
|
533
575
|
@keyframes time-pop {
|
|
@@ -1,74 +1,74 @@
|
|
|
1
1
|
/** A running timer. The host persists it, the tracker only reads it. */
|
|
2
2
|
export type TimeRunning = {
|
|
3
|
-
|
|
3
|
+
startedAt: Date | string | number;
|
|
4
4
|
};
|
|
5
5
|
|
|
6
6
|
/** What the tracker hands back when a manual entry is submitted. */
|
|
7
7
|
export type TimeLogInput = {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
minutes: number;
|
|
9
|
+
/** The day worked, `YYYY-MM-DD`. */
|
|
10
|
+
day: string;
|
|
11
|
+
note?: string;
|
|
12
12
|
};
|
|
13
13
|
|
|
14
14
|
/** What the tracker hands back when the timer stops. */
|
|
15
15
|
export type TimeStopInfo = {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
/** Elapsed whole minutes, never below 1. */
|
|
17
|
+
minutes: number;
|
|
18
|
+
startedAt: Date;
|
|
19
|
+
stoppedAt: Date;
|
|
20
|
+
/** The day it started, `YYYY-MM-DD`, in the reader's calendar. */
|
|
21
|
+
day: string;
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
export type TimeLabels = {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
25
|
+
title: string;
|
|
26
|
+
timer: string;
|
|
27
|
+
start: string;
|
|
28
|
+
stop: string;
|
|
29
|
+
running: string;
|
|
30
|
+
since: (time: string) => string;
|
|
31
|
+
started: string;
|
|
32
|
+
runningFor: (duration: string) => string;
|
|
33
|
+
stopped: (duration: string) => string;
|
|
34
|
+
elapsed: string;
|
|
35
|
+
logged: string;
|
|
36
|
+
duration: string;
|
|
37
|
+
durationPlaceholder: string;
|
|
38
|
+
date: string;
|
|
39
|
+
note: string;
|
|
40
|
+
notePlaceholder: string;
|
|
41
|
+
log: string;
|
|
42
|
+
saving: string;
|
|
43
|
+
invalid: string;
|
|
44
|
+
empty: string;
|
|
45
|
+
remaining: (left: string, estimate: string) => string;
|
|
46
|
+
over: (over: string, estimate: string) => string;
|
|
47
|
+
deleteEntry: (duration: string) => string;
|
|
48
48
|
};
|
|
49
49
|
|
|
50
50
|
export const DEFAULT_TIME_LABELS: TimeLabels = {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
51
|
+
title: "Time",
|
|
52
|
+
timer: "Timer",
|
|
53
|
+
start: "Start timer",
|
|
54
|
+
stop: "Stop timer",
|
|
55
|
+
running: "Running",
|
|
56
|
+
since: (time) => `Since ${time}`,
|
|
57
|
+
started: "Timer started",
|
|
58
|
+
runningFor: (duration) => `Timer running, ${duration}`,
|
|
59
|
+
stopped: (duration) => `Timer stopped, ${duration} to log`,
|
|
60
|
+
elapsed: "Elapsed time",
|
|
61
|
+
logged: "Logged",
|
|
62
|
+
duration: "Time spent",
|
|
63
|
+
durationPlaceholder: "1h30",
|
|
64
|
+
date: "Date worked",
|
|
65
|
+
note: "Note",
|
|
66
|
+
notePlaceholder: "What was done",
|
|
67
|
+
log: "Log",
|
|
68
|
+
saving: "Saving",
|
|
69
|
+
invalid: "Try 45m, 1h30 or 1.5h",
|
|
70
|
+
empty: "Log your first hour",
|
|
71
|
+
remaining: (left, estimate) => `${left} left of ${estimate}`,
|
|
72
|
+
over: (over, estimate) => `${over} over ${estimate}`,
|
|
73
|
+
deleteEntry: (duration) => `Delete ${duration} entry`,
|
|
74
74
|
};
|
|
@@ -22,9 +22,8 @@ export type KanbanDndConfig<T> = {
|
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* land on any other element a caller wired up.
|
|
25
|
+
* A board's drag state and handlers, on native HTML5 drag so a card can leave the board for
|
|
26
|
+
* any drop target a caller wired up.
|
|
28
27
|
*/
|
|
29
28
|
export function createKanbanDnd<T>(config: KanbanDndConfig<T>) {
|
|
30
29
|
let draggedId = $state<string | null>(null);
|
package/src/thread/index.ts
CHANGED
|
@@ -2,11 +2,11 @@ export { default as Thread } from "./thread.svelte";
|
|
|
2
2
|
export { buildThread, fillCount, resolveThreadLabels, resolvedCount } from "./logic.js";
|
|
3
3
|
export { DEFAULT_THREAD_LABELS } from "./types.js";
|
|
4
4
|
export type {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
ThreadAnchor,
|
|
6
|
+
ThreadAuthor,
|
|
7
|
+
ThreadAuthorKind,
|
|
8
|
+
ThreadComment,
|
|
9
|
+
ThreadLabels,
|
|
10
|
+
ThreadNode,
|
|
11
|
+
ThreadPost,
|
|
12
12
|
} from "./types.js";
|
package/src/thread/logic.ts
CHANGED
|
@@ -2,57 +2,60 @@ import type { ThreadComment, ThreadLabels, ThreadNode } from "./types.js";
|
|
|
2
2
|
import { DEFAULT_THREAD_LABELS } from "./types.js";
|
|
3
3
|
|
|
4
4
|
function time(comment: ThreadComment): number {
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
const ms = Date.parse(comment.createdAt);
|
|
6
|
+
return Number.isNaN(ms) ? Number.POSITIVE_INFINITY : ms;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
/** Oldest first, a malformed timestamp last, ties in the order they came. */
|
|
10
10
|
function byTime(comments: ThreadComment[]): ThreadComment[] {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
return comments
|
|
12
|
+
.map((comment, index) => ({ comment, index }))
|
|
13
|
+
.toSorted((a, b) => time(a.comment) - time(b.comment) || a.index - b.index)
|
|
14
|
+
.map((row) => row.comment);
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
* Roots in time order
|
|
19
|
-
*
|
|
18
|
+
* Roots in time order with their replies: a reply to a reply joins the root, an orphan becomes a
|
|
19
|
+
* root, and a parent loop can't hang the walk.
|
|
20
20
|
*/
|
|
21
21
|
export function buildThread(comments: ThreadComment[]): ThreadNode[] {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
22
|
+
const byId = new Map(comments.map((comment) => [comment.id, comment]));
|
|
23
|
+
|
|
24
|
+
function rootOf(comment: ThreadComment): ThreadComment {
|
|
25
|
+
const seen = new Set<string>([comment.id]);
|
|
26
|
+
let current = comment;
|
|
27
|
+
for (;;) {
|
|
28
|
+
const parent = current.parentId ? byId.get(current.parentId) : undefined;
|
|
29
|
+
if (!parent) return current;
|
|
30
|
+
if (seen.has(parent.id)) return comment;
|
|
31
|
+
seen.add(parent.id);
|
|
32
|
+
current = parent;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const replies = new Map<string, ThreadComment[]>();
|
|
37
|
+
const roots: ThreadComment[] = [];
|
|
38
|
+
for (const comment of comments) {
|
|
39
|
+
const root = rootOf(comment);
|
|
40
|
+
if (root.id === comment.id) roots.push(comment);
|
|
41
|
+
else replies.set(root.id, [...(replies.get(root.id) ?? []), comment]);
|
|
42
|
+
}
|
|
43
|
+
return byTime(roots).map((comment) => ({
|
|
44
|
+
comment,
|
|
45
|
+
replies: byTime(replies.get(comment.id) ?? []),
|
|
46
|
+
}));
|
|
44
47
|
}
|
|
45
48
|
|
|
46
49
|
/** How many conversations are resolved. */
|
|
47
50
|
export function resolvedCount(nodes: ThreadNode[]): number {
|
|
48
|
-
|
|
51
|
+
return nodes.filter((node) => node.comment.resolved).length;
|
|
49
52
|
}
|
|
50
53
|
|
|
51
54
|
export function resolveThreadLabels(overrides?: Partial<ThreadLabels>): ThreadLabels {
|
|
52
|
-
|
|
55
|
+
return { ...DEFAULT_THREAD_LABELS, ...overrides };
|
|
53
56
|
}
|
|
54
57
|
|
|
55
58
|
/** Fills `{count}` in a label, so a French host writes its own sentence. */
|
|
56
59
|
export function fillCount(label: string, count: number): string {
|
|
57
|
-
|
|
60
|
+
return label.replaceAll("{count}", String(count));
|
|
58
61
|
}
|
package/src/thread/thread.svelte
CHANGED
|
@@ -42,7 +42,8 @@
|
|
|
42
42
|
avatar,
|
|
43
43
|
attachments,
|
|
44
44
|
anchorChip,
|
|
45
|
-
when = (comment) =>
|
|
45
|
+
when = (comment) =>
|
|
46
|
+
new Date(comment.createdAt).toLocaleDateString(undefined, { month: "short", day: "numeric" }),
|
|
46
47
|
onPost,
|
|
47
48
|
onEdit,
|
|
48
49
|
onDelete,
|
|
@@ -106,8 +107,16 @@
|
|
|
106
107
|
}
|
|
107
108
|
</script>
|
|
108
109
|
|
|
109
|
-
{#snippet composer(
|
|
110
|
-
|
|
110
|
+
{#snippet composer(
|
|
111
|
+
value: string,
|
|
112
|
+
placeholder: string,
|
|
113
|
+
onSend: () => Promise<void>,
|
|
114
|
+
set: (next: string) => void,
|
|
115
|
+
isReply: boolean,
|
|
116
|
+
)}
|
|
117
|
+
<div
|
|
118
|
+
class="rounded-lg border border-input transition-shadow focus-within:ring-3 focus-within:ring-ring/50"
|
|
119
|
+
>
|
|
111
120
|
<!-- svelte-ignore a11y_autofocus -->
|
|
112
121
|
<textarea
|
|
113
122
|
{value}
|
|
@@ -120,7 +129,12 @@
|
|
|
120
129
|
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"
|
|
121
130
|
></textarea>
|
|
122
131
|
<div class="flex justify-end px-2 pb-2">
|
|
123
|
-
<button
|
|
132
|
+
<button
|
|
133
|
+
type="button"
|
|
134
|
+
class="thread-button thread-primary"
|
|
135
|
+
onclick={onSend}
|
|
136
|
+
disabled={!value.trim() || sending}
|
|
137
|
+
>
|
|
124
138
|
{sending ? labels.sending : isReply ? labels.reply : labels.send}
|
|
125
139
|
</button>
|
|
126
140
|
</div>
|
|
@@ -137,7 +151,10 @@
|
|
|
137
151
|
{:else if comment.author.avatar}
|
|
138
152
|
<img src={comment.author.avatar} alt="" class="size-6 rounded-full object-cover" />
|
|
139
153
|
{:else}
|
|
140
|
-
<span
|
|
154
|
+
<span
|
|
155
|
+
class="grid size-6 place-items-center rounded-full bg-muted text-xs font-medium text-muted-foreground"
|
|
156
|
+
aria-hidden="true"
|
|
157
|
+
>
|
|
141
158
|
{initial(comment.author.name)}
|
|
142
159
|
</span>
|
|
143
160
|
{/if}
|
|
@@ -147,7 +164,12 @@
|
|
|
147
164
|
<div class="flex min-h-6 flex-wrap items-center gap-x-2">
|
|
148
165
|
<span class="truncate text-[13px] font-medium">{comment.author.name}</span>
|
|
149
166
|
{#if comment.author.kind === "agent"}
|
|
150
|
-
<span
|
|
167
|
+
<span
|
|
168
|
+
class={cn(
|
|
169
|
+
"inline-flex h-5 items-center rounded-sm border px-1.5 text-xs font-medium",
|
|
170
|
+
TONE_CLASS.accent,
|
|
171
|
+
)}
|
|
172
|
+
>
|
|
151
173
|
{labels.agent}
|
|
152
174
|
</span>
|
|
153
175
|
{/if}
|
|
@@ -163,11 +185,15 @@
|
|
|
163
185
|
<MapPin class="size-3" aria-hidden="true" />{labels.pinned}
|
|
164
186
|
</button>
|
|
165
187
|
{:else}
|
|
166
|
-
<span class="thread-chip"
|
|
188
|
+
<span class="thread-chip"
|
|
189
|
+
><MapPin class="size-3" aria-hidden="true" />{labels.pinned}</span
|
|
190
|
+
>
|
|
167
191
|
{/if}
|
|
168
192
|
{/if}
|
|
169
193
|
{#if editingId !== comment.id}
|
|
170
|
-
<span
|
|
194
|
+
<span
|
|
195
|
+
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"
|
|
196
|
+
>
|
|
171
197
|
{#if isRoot && onResolve}
|
|
172
198
|
<button
|
|
173
199
|
type="button"
|
|
@@ -181,12 +207,25 @@
|
|
|
181
207
|
{/if}
|
|
182
208
|
{#if canEdit(comment)}
|
|
183
209
|
{#if onEdit && !comment.content}
|
|
184
|
-
<button
|
|
210
|
+
<button
|
|
211
|
+
type="button"
|
|
212
|
+
class="thread-icon"
|
|
213
|
+
aria-label={labels.edit}
|
|
214
|
+
onclick={() => {
|
|
215
|
+
editingId = comment.id;
|
|
216
|
+
editDraft = comment.body;
|
|
217
|
+
}}
|
|
218
|
+
>
|
|
185
219
|
<PencilSimple class="size-3.5" aria-hidden="true" />
|
|
186
220
|
</button>
|
|
187
221
|
{/if}
|
|
188
222
|
{#if onDelete}
|
|
189
|
-
<button
|
|
223
|
+
<button
|
|
224
|
+
type="button"
|
|
225
|
+
class="thread-icon"
|
|
226
|
+
aria-label={labels.delete}
|
|
227
|
+
onclick={() => onDelete?.(comment)}
|
|
228
|
+
>
|
|
190
229
|
<Trash class="size-3.5" aria-hidden="true" />
|
|
191
230
|
</button>
|
|
192
231
|
{/if}
|
|
@@ -203,8 +242,14 @@
|
|
|
203
242
|
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-3 focus-visible:ring-ring/50"
|
|
204
243
|
></textarea>
|
|
205
244
|
<div class="mt-2 flex justify-end gap-1.5">
|
|
206
|
-
<button type="button" class="thread-button" onclick={() => (editingId = null)}
|
|
207
|
-
|
|
245
|
+
<button type="button" class="thread-button" onclick={() => (editingId = null)}
|
|
246
|
+
>{labels.cancel}</button
|
|
247
|
+
>
|
|
248
|
+
<button
|
|
249
|
+
type="button"
|
|
250
|
+
class="thread-button thread-primary"
|
|
251
|
+
onclick={() => saveEdit(comment)}>{labels.save}</button
|
|
252
|
+
>
|
|
208
253
|
</div>
|
|
209
254
|
{:else if comment.content}
|
|
210
255
|
<div class="text-sm leading-relaxed"><RichRenderer content={comment.content} /></div>
|
|
@@ -221,7 +266,12 @@
|
|
|
221
266
|
<div class="flex h-7 items-center justify-between">
|
|
222
267
|
<h2 class="text-xs font-medium text-muted-foreground">{labels.title}</h2>
|
|
223
268
|
{#if resolved > 0}
|
|
224
|
-
<button
|
|
269
|
+
<button
|
|
270
|
+
type="button"
|
|
271
|
+
class="thread-button"
|
|
272
|
+
aria-expanded={showResolved}
|
|
273
|
+
onclick={() => (showResolved = !showResolved)}
|
|
274
|
+
>
|
|
225
275
|
{showResolved ? labels.hideResolved : fillCount(labels.showResolved, resolved)}
|
|
226
276
|
</button>
|
|
227
277
|
{/if}
|
|
@@ -242,7 +292,10 @@
|
|
|
242
292
|
{:else if shown.length > 0}
|
|
243
293
|
<ol class="flex flex-col gap-4">
|
|
244
294
|
{#each shown as node (node.comment.id)}
|
|
245
|
-
<li
|
|
295
|
+
<li
|
|
296
|
+
class={cn("flex flex-col gap-3", node.comment.resolved && "opacity-60")}
|
|
297
|
+
data-resolved={node.comment.resolved || undefined}
|
|
298
|
+
>
|
|
246
299
|
{@render row(node.comment, true)}
|
|
247
300
|
{#if node.comment.resolved}
|
|
248
301
|
<span class="ms-9 text-xs text-muted-foreground">{labels.resolved}</span>
|
|
@@ -257,10 +310,23 @@
|
|
|
257
310
|
{#if onPost && !node.comment.resolved}
|
|
258
311
|
{#if replyTo === node.comment.id}
|
|
259
312
|
<div class="ms-9">
|
|
260
|
-
{@render composer(
|
|
313
|
+
{@render composer(
|
|
314
|
+
replyDraft,
|
|
315
|
+
labels.replyPlaceholder,
|
|
316
|
+
sendReply,
|
|
317
|
+
(next) => (replyDraft = next),
|
|
318
|
+
true,
|
|
319
|
+
)}
|
|
261
320
|
</div>
|
|
262
321
|
{:else}
|
|
263
|
-
<button
|
|
322
|
+
<button
|
|
323
|
+
type="button"
|
|
324
|
+
class="thread-button ms-9 flex w-fit items-center gap-1.5"
|
|
325
|
+
onclick={() => {
|
|
326
|
+
replyTo = node.comment.id;
|
|
327
|
+
replyDraft = "";
|
|
328
|
+
}}
|
|
329
|
+
>
|
|
264
330
|
<ArrowBendDownRight class="size-3.5" aria-hidden="true" />{labels.reply}
|
|
265
331
|
</button>
|
|
266
332
|
{/if}
|