@heyramzi/ui 1.54.1 → 2.0.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/COMPONENTS.md +3 -2
- package/README.md +34 -9
- package/package.json +200 -7
- package/scripts/sync-shadcn.mjs +2 -2
- package/src/ai/ai-button/AiButton.svelte +1 -1
- package/src/ai/confirmation/Confirmation.svelte +1 -1
- package/src/ai/confirmation/ConfirmationAction.svelte +1 -1
- package/src/ai/confirmation/ConfirmationActions.svelte +1 -1
- package/src/ai/confirmation/ConfirmationTitle.svelte +1 -1
- package/src/ai/generation-progress/GenerationProgress.svelte +13 -13
- package/src/ai/loading-state/PixelLoader.svelte +1 -1
- package/src/ai/prompt-input/PromptInput.svelte +1 -1
- package/src/ai/prompt-input/PromptInputBody.svelte +1 -1
- package/src/ai/prompt-input/PromptInputSubmit.svelte +1 -1
- package/src/ai/prompt-input/PromptInputTextarea.svelte +1 -1
- package/src/ai/prompt-input/PromptInputToolbar.svelte +1 -1
- package/src/ai/reasoning/Reasoning.svelte +1 -1
- package/src/ai/reasoning/ReasoningContent.svelte +1 -1
- package/src/ai/reasoning/ReasoningTrigger.svelte +1 -1
- package/src/ai/response/Response.svelte +1 -1
- package/src/ai/suggestion/Suggestion.svelte +1 -1
- package/src/ai/suggestion/Suggestions.svelte +1 -1
- package/src/ai/thinking-dots/ThinkingDots.svelte +1 -1
- package/src/ai/typing-dots/TypingDots.svelte +1 -1
- package/src/ai/voice-waveform/VoiceWaveform.svelte +1 -1
- package/src/ambient.d.ts +8 -0
- package/src/brand/brand.test.ts +67 -0
- package/src/brand/index.ts +137 -0
- package/src/canvas/README.md +236 -0
- package/src/canvas/card/card-frame.svelte +61 -0
- package/src/canvas/card/card.css +39 -0
- package/src/canvas/card/resize-handles.svelte +119 -0
- package/src/canvas/card/resize.ts +33 -0
- package/src/canvas/chrome/chrome-zoom.svelte +120 -0
- package/src/canvas/chrome/chrome.css +231 -0
- package/src/canvas/chrome/lock-badge.svelte +18 -0
- package/src/canvas/chrome/side-handles.svelte +30 -0
- package/src/canvas/chrome/size-chip.svelte +45 -0
- package/src/canvas/chrome/snap-guides.svelte +69 -0
- package/src/canvas/components/icons/ToolIcon.svelte +30 -0
- package/src/canvas/components/shared/svg/PreviewSvg.svelte +67 -0
- package/src/canvas/components/shared/svg/ShapeOutline.svelte +76 -0
- package/src/canvas/components/shared/svg/StrokeNode.svelte +69 -0
- package/src/canvas/components/shared/svg/TextRule.svelte +37 -0
- package/src/canvas/edges/bend-handle.svelte +76 -0
- package/src/canvas/edges/edge-gradient.svelte +30 -0
- package/src/canvas/edges/edge-path.test.ts +58 -0
- package/src/canvas/edges/edge-path.ts +293 -0
- package/src/canvas/edges/gradient-edge.svelte +43 -0
- package/src/canvas/editor/context-menu.test.ts +65 -0
- package/src/canvas/editor/context-menu.ts +48 -0
- package/src/canvas/editor/dispatch.svelte.test.ts +243 -0
- package/src/canvas/editor/dispatch.svelte.ts +205 -0
- package/src/canvas/editor/editor-context.ts +62 -0
- package/src/canvas/editor/editor-mode.ts +67 -0
- package/src/canvas/editor/frame.ts +98 -0
- package/src/canvas/editor/hand-context.ts +15 -0
- package/src/canvas/editor/history-core.test.ts +239 -0
- package/src/canvas/editor/history-core.ts +247 -0
- package/src/canvas/editor/keymap.test.ts +161 -0
- package/src/canvas/editor/keymap.ts +151 -0
- package/src/canvas/editor/layout-actions.test.ts +156 -0
- package/src/canvas/editor/layout-actions.ts +219 -0
- package/src/canvas/editor/lock.test.ts +68 -0
- package/src/canvas/editor/lock.ts +87 -0
- package/src/canvas/editor/rebase.test.ts +208 -0
- package/src/canvas/editor/rebase.ts +272 -0
- package/src/canvas/editor/style.ts +184 -0
- package/src/canvas/editor/text-field-context.ts +41 -0
- package/src/canvas/editor/z-order.test.ts +75 -0
- package/src/canvas/editor/z-order.ts +87 -0
- package/src/canvas/headless/alignment.test.ts +59 -0
- package/src/canvas/headless/alignment.ts +23 -0
- package/src/canvas/headless/break-apart-find.ts +418 -0
- package/src/canvas/headless/break-apart-pieces.ts +104 -0
- package/src/canvas/headless/break-apart-plan.ts +439 -0
- package/src/canvas/headless/break-apart-style.ts +106 -0
- package/src/canvas/headless/break-apart.ts +244 -0
- package/src/canvas/headless/canvas-cleanup.test.ts +181 -0
- package/src/canvas/headless/canvas-cleanup.ts +102 -0
- package/src/canvas/headless/edge-anchors.test.ts +70 -0
- package/src/canvas/headless/edge-anchors.ts +143 -0
- package/src/canvas/headless/edge-memo.test.ts +58 -0
- package/src/canvas/headless/edge-memo.ts +32 -0
- package/src/canvas/headless/edge-serialization.test.ts +55 -0
- package/src/canvas/headless/edge-serialization.ts +32 -0
- package/src/canvas/headless/figure-geometry.ts +293 -0
- package/src/canvas/headless/fit-viewport.test.ts +85 -0
- package/src/canvas/headless/fit-viewport.ts +137 -0
- package/src/canvas/headless/grid.test.ts +34 -0
- package/src/canvas/headless/grid.ts +94 -0
- package/src/canvas/headless/icon-node.test.ts +23 -0
- package/src/canvas/headless/icon-node.ts +66 -0
- package/src/canvas/headless/index.ts +9 -0
- package/src/canvas/headless/linkify.test.ts +23 -0
- package/src/canvas/headless/linkify.ts +25 -0
- package/src/canvas/headless/pen-marks.ts +133 -0
- package/src/canvas/headless/sketch.test.ts +99 -0
- package/src/canvas/headless/sketch.ts +302 -0
- package/src/canvas/headless/snap-guides.test.ts +61 -0
- package/src/canvas/headless/snap-guides.ts +146 -0
- package/src/canvas/headless/svg-figure.test.ts +49 -0
- package/src/canvas/headless/svg-figure.ts +176 -0
- package/src/canvas/headless/table-layout.test.ts +158 -0
- package/src/canvas/headless/table-layout.ts +309 -0
- package/src/canvas/index.ts +35 -0
- package/src/canvas/lod/lod-mode.svelte.ts +30 -0
- package/src/canvas/lod/lod-switch.svelte +140 -0
- package/src/canvas/lod/render-all.svelte.ts +52 -0
- package/src/canvas/ops/arrange.ts +108 -0
- package/src/canvas/ops/ops.test.ts +98 -0
- package/src/canvas/ops/ops.ts +347 -0
- package/src/canvas/pen/PenField.svelte +128 -0
- package/src/canvas/pen/PenField.test.ts +87 -0
- package/src/canvas/pen/PenOverlay.svelte +172 -0
- package/src/canvas/pen/ShapeNode.svelte +64 -0
- package/src/canvas/pen/TextNode.svelte +97 -0
- package/src/canvas/pen/colors.ts +28 -0
- package/src/canvas/pen/eraser.ts +46 -0
- package/src/canvas/pen/geometry.ts +148 -0
- package/src/canvas/pen/host-field.test.svelte +13 -0
- package/src/canvas/pen/index.ts +40 -0
- package/src/canvas/pen/ops.ts +16 -0
- package/src/canvas/pen/pen-session.ts +177 -0
- package/src/canvas/pen/pen.css +149 -0
- package/src/canvas/pen/plugin.ts +139 -0
- package/src/canvas/pen/shapes.ts +104 -0
- package/src/canvas/pen/stroke-path.ts +54 -0
- package/src/canvas/pen/types.ts +96 -0
- package/src/canvas/pen/ui.ts +26 -0
- package/src/canvas/plugin.test.ts +45 -0
- package/src/canvas/plugin.ts +159 -0
- package/src/canvas/screen/GlyphIcon.svelte +25 -0
- package/src/canvas/screen/PartView.svelte +176 -0
- package/src/canvas/screen/ScreenBody.svelte +142 -0
- package/src/canvas/screen/ScreenCard.svelte +32 -0
- package/src/canvas/screen/glyphs/button.svg +1 -0
- package/src/canvas/screen/glyphs/close.svg +1 -0
- package/src/canvas/screen/glyphs/down.svg +1 -0
- package/src/canvas/screen/glyphs/header.svg +1 -0
- package/src/canvas/screen/glyphs/hills.svg +1 -0
- package/src/canvas/screen/glyphs/image.svg +1 -0
- package/src/canvas/screen/glyphs/input.svg +1 -0
- package/src/canvas/screen/glyphs/list.svg +1 -0
- package/src/canvas/screen/glyphs/modal.svg +1 -0
- package/src/canvas/screen/glyphs/plus.svg +1 -0
- package/src/canvas/screen/glyphs/up.svg +1 -0
- package/src/canvas/screen/glyphs.ts +40 -0
- package/src/canvas/screen/labels.ts +41 -0
- package/src/canvas/screen/parts.ts +164 -0
- package/src/canvas/screen/plugin.ts +67 -0
- package/src/canvas/screen/screen.css +593 -0
- package/src/canvas/screen/types.ts +47 -0
- package/src/canvas/screen/ui.ts +13 -0
- package/src/canvas/test-utils.ts +18 -0
- package/src/canvas/types.ts +53 -0
- package/src/canvas/ui/CanvasToolbar.svelte +169 -0
- package/src/canvas/ui/FrameLayer.svelte +116 -0
- package/src/canvas/ui/PenLayer.svelte +39 -0
- package/src/canvas/ui/StylePanel.svelte +268 -0
- package/src/canvas/ui/canvas-grid.svelte +156 -0
- package/src/canvas/ui/canvas-ui.css +350 -0
- package/src/canvas/ui/forward-wheel.ts +29 -0
- package/src/canvas/ui/icons.ts +124 -0
- package/src/canvas/ui/place-panel.ts +80 -0
- package/src/canvas/ui/shortcut-tip.test.ts +16 -0
- package/src/canvas/ui/shortcut-tip.ts +17 -0
- package/src/canvas/ui/tools.ts +146 -0
- package/src/charts/README.md +104 -0
- package/src/charts/ambient.d.ts +4 -0
- package/src/charts/animated-value.css +7 -0
- package/src/charts/animated-value.svelte +110 -0
- package/src/charts/area-chart.css +29 -0
- package/src/charts/area-chart.svelte +204 -0
- package/src/charts/bar-chart.css +119 -0
- package/src/charts/bar-chart.svelte +275 -0
- package/src/charts/charts.css +201 -0
- package/src/charts/crosshair.svelte +35 -0
- package/src/charts/dashboard/chart.ts +5 -0
- package/src/charts/dashboard/dashboard-empty.svelte +55 -0
- package/src/charts/dashboard/dashboard-section.svelte +58 -0
- package/src/charts/dashboard/data-panel.svelte +81 -0
- package/src/charts/dashboard/data-sort-head.svelte +102 -0
- package/src/charts/dashboard/data-table.svelte +278 -0
- package/src/charts/dashboard/data-table.ts +26 -0
- package/src/charts/dashboard/index.ts +25 -0
- package/src/charts/dashboard/panel-grid.svelte +43 -0
- package/src/charts/dashboard/period-filter.svelte +53 -0
- package/src/charts/dashboard/rating-chip.svelte +35 -0
- package/src/charts/dashboard/stat-band.svelte +47 -0
- package/src/charts/dashboard/stat-tile.svelte +176 -0
- package/src/charts/dashboard/windows.ts +8 -0
- package/src/charts/donut-chart.css +56 -0
- package/src/charts/donut-chart.svelte +158 -0
- package/src/charts/edge-fade.svelte +43 -0
- package/src/charts/edge-fade.ts +16 -0
- package/src/charts/figure.css +100 -0
- package/src/charts/figure.svelte +122 -0
- package/src/charts/gauge-chart.css +94 -0
- package/src/charts/gauge-chart.svelte +302 -0
- package/src/charts/hover-band.test.ts +30 -0
- package/src/charts/hover-band.ts +21 -0
- package/src/charts/hover-focus.svelte +85 -0
- package/src/charts/index.ts +31 -0
- package/src/charts/legend.css +136 -0
- package/src/charts/legend.svelte +112 -0
- package/src/charts/line-chart.css +26 -0
- package/src/charts/line-chart.svelte +256 -0
- package/src/charts/motion.ts +19 -0
- package/src/charts/number-chart.css +179 -0
- package/src/charts/number-chart.svelte +187 -0
- package/src/charts/primitives/chart-container.svelte +57 -0
- package/src/charts/primitives/chart-style.svelte +36 -0
- package/src/charts/primitives/chart-tooltip.svelte +161 -0
- package/src/charts/primitives/chart-utils.ts +54 -0
- package/src/charts/primitives/index.ts +11 -0
- package/src/charts/reference-line.css +18 -0
- package/src/charts/reference-line.svelte +31 -0
- package/src/charts/reveal.svelte +61 -0
- package/src/charts/ring-chart.css +104 -0
- package/src/charts/ring-chart.svelte +166 -0
- package/src/charts/sparkline.css +45 -0
- package/src/charts/sparkline.svelte +141 -0
- package/src/charts/stat-card.css +105 -0
- package/src/charts/stat-card.svelte +267 -0
- package/src/charts/time-series-chart.css +28 -0
- package/src/charts/time-series-chart.svelte +179 -0
- package/src/charts/trend-pill.css +38 -0
- package/src/charts/trend-pill.svelte +48 -0
- package/src/components/copy/clipboard.ts +13 -9
- package/src/components/copy/copy-button.svelte +2 -2
- package/src/components/data-table/row-arrow.svelte +1 -1
- package/src/components/data-table/sortable-head.svelte +1 -1
- package/src/components/docs/doc-page.svelte +1 -1
- package/src/components/docs/on-this-page.svelte +2 -2
- package/src/components/field-row/field-row.svelte +1 -1
- package/src/components/hold-to-confirm/hold-to-confirm.svelte +1 -1
- package/src/components/language-flag/language-flag.svelte +1 -1
- package/src/components/list-page/detail-empty.svelte +1 -1
- package/src/components/list-page/detail-header.svelte +1 -1
- package/src/components/list-page/detail-panel.svelte +1 -1
- package/src/components/list-page/detail-row.svelte +1 -1
- package/src/components/list-page/detail-stack.svelte +1 -1
- package/src/components/list-page/empty-state.svelte +1 -1
- package/src/components/list-page/filter-item.svelte +1 -1
- package/src/components/list-page/filter-popover.svelte +2 -2
- package/src/components/list-page/filter-section.ts +1 -1
- package/src/components/list-page/grouped-list.svelte +1 -1
- package/src/components/list-page/list-page-shell.svelte +1 -1
- package/src/components/list-page/list-table.ts +1 -1
- package/src/components/list-page/master-detail.svelte +1 -1
- package/src/components/list-page/notice-bar.svelte +1 -1
- package/src/components/list-page/prev-next.svelte +1 -1
- package/src/components/list-page/rail-body.svelte +1 -1
- package/src/components/list-page/rail-empty.svelte +1 -1
- package/src/components/list-page/rail-head.svelte +1 -1
- package/src/components/list-page/rail-row.svelte +1 -1
- package/src/components/list-page/section-label.svelte +2 -2
- package/src/components/list-page/toolbar-search.svelte +1 -1
- package/src/components/list-page/toolbar-trigger.ts +1 -1
- package/src/components/mark/mark.svelte +1 -1
- package/src/components/open-in/open-in.svelte +1 -1
- package/src/components/picker/mark-picker.svelte +1 -1
- package/src/components/picker/picker.svelte +1 -1
- package/src/components/section-card/section-card.svelte +1 -1
- package/src/components/segmented/animated-tabs.svelte +1 -1
- package/src/components/segmented/section-runs.svelte +1 -1
- package/src/components/segmented/segment.svelte +5 -5
- package/src/components/segmented/segmented-filter.svelte +1 -1
- package/src/components/segmented/segmented-nav.svelte +1 -1
- package/src/components/segmented/segmented-overflow.svelte +1 -1
- package/src/components/segmented/segmented-track.svelte +1 -1
- package/src/components/star-rating-input/star-rating-input.svelte +1 -1
- package/src/components/table-view/table-view.svelte +1 -1
- package/src/components/theme/theme-provider.svelte +6 -4
- package/src/components/toolbar/index.ts +5 -0
- package/src/components/toolbar/toolbar.svelte +168 -0
- package/src/components/toolbar/toolbar.test.ts +66 -0
- package/src/editor/README.md +58 -0
- package/src/editor/ambient.d.ts +1 -0
- package/src/editor/block-id.ts +77 -0
- package/src/editor/block-ops.test.ts +237 -0
- package/src/editor/block-ops.ts +157 -0
- package/src/editor/content.css +418 -0
- package/src/editor/editor-toolbar.svelte +303 -0
- package/src/editor/editor.svelte +462 -0
- package/src/editor/extensions.ts +145 -0
- package/src/editor/image-toolbar.svelte +123 -0
- package/src/editor/index.ts +17 -0
- package/src/editor/inline-editor.svelte +261 -0
- package/src/editor/markdown-escaping.test.ts +158 -0
- package/src/editor/markdown-marks.test.ts +89 -0
- package/src/editor/markdown-test-helpers.ts +13 -0
- package/src/editor/markdown-youtube.test.ts +93 -0
- package/src/editor/markdown.test.ts +250 -0
- package/src/editor/markdown.ts +511 -0
- package/src/editor/renderer.svelte +92 -0
- package/src/editor/serializer.test.ts +133 -0
- package/src/editor/serializer.ts +370 -0
- package/src/editor/slash-items.ts +115 -0
- package/src/editor/slash-menu.svelte +159 -0
- package/src/editor/youtube-url.ts +35 -0
- package/src/effects/animated-beam/AnimatedBeam.svelte +1 -1
- package/src/effects/border-beam/register-properties.ts +4 -2
- package/src/effects/image-comparison/ImageComparison.svelte +1 -1
- package/src/effects/image-reveal/ImageReveal.svelte +1 -1
- package/src/effects/interactive-hover-button/InteractiveHoverButton.svelte +13 -13
- package/src/effects/magic-card/MagicCard.svelte +1 -1
- package/src/effects/marquee/Marquee.svelte +1 -1
- package/src/effects/progressive-blur/ProgressiveBlur.svelte +1 -1
- package/src/effects/reveal/AnimatedGroup.svelte +1 -1
- package/src/effects/reveal/InView.svelte +1 -1
- package/src/effects/reveal/TextEffect.svelte +1 -1
- package/src/effects/shader-gradient/ShaderGradient.svelte +1 -1
- package/src/effects/spotlight/Spotlight.svelte +1 -1
- package/src/effects/theme-toggle/ThemeToggle.svelte +4 -4
- package/src/lib/result.ts +19 -0
- package/src/lib/utils.ts +18 -0
- package/src/onboarding/README.md +120 -0
- package/src/onboarding/define-tours.test.ts +50 -0
- package/src/onboarding/define-tours.ts +31 -0
- package/src/onboarding/demo.svelte +86 -0
- package/src/onboarding/index.ts +12 -0
- package/src/onboarding/onboarding-provider.svelte +120 -0
- package/src/onboarding/onboarding.svelte.ts +24 -0
- package/src/onboarding/pick.test.ts +79 -0
- package/src/onboarding/pick.ts +36 -0
- package/src/onboarding/stores.test.ts +103 -0
- package/src/onboarding/stores.ts +58 -0
- package/src/onboarding/tour-card.svelte +70 -0
- package/src/onboarding/tour-runner.svelte +197 -0
- package/src/onboarding/types.ts +34 -0
- package/src/onboarding/wait.ts +29 -0
- package/src/shadcn/accordion/accordion-content.svelte +1 -1
- package/src/shadcn/accordion/accordion-item.svelte +1 -1
- package/src/shadcn/accordion/accordion-trigger.svelte +1 -1
- package/src/shadcn/accordion/accordion.svelte +1 -1
- package/src/shadcn/alert/alert-description.svelte +1 -1
- package/src/shadcn/alert/alert-title.svelte +1 -1
- package/src/shadcn/alert/alert.svelte +1 -1
- package/src/shadcn/alert-dialog/alert-dialog-action.svelte +1 -1
- package/src/shadcn/alert-dialog/alert-dialog-cancel.svelte +1 -1
- package/src/shadcn/alert-dialog/alert-dialog-content.svelte +1 -1
- package/src/shadcn/alert-dialog/alert-dialog-description.svelte +1 -1
- package/src/shadcn/alert-dialog/alert-dialog-footer.svelte +1 -1
- package/src/shadcn/alert-dialog/alert-dialog-header.svelte +1 -1
- package/src/shadcn/alert-dialog/alert-dialog-overlay.svelte +1 -1
- package/src/shadcn/alert-dialog/alert-dialog-title.svelte +1 -1
- package/src/shadcn/attachment/attachment-action.svelte +1 -1
- package/src/shadcn/attachment/attachment-actions.svelte +1 -1
- package/src/shadcn/attachment/attachment-content.svelte +1 -1
- package/src/shadcn/attachment/attachment-description.svelte +1 -1
- package/src/shadcn/attachment/attachment-group.svelte +1 -1
- package/src/shadcn/attachment/attachment-media.svelte +1 -1
- package/src/shadcn/attachment/attachment-title.svelte +1 -1
- package/src/shadcn/attachment/attachment-trigger.svelte +1 -1
- package/src/shadcn/attachment/attachment.svelte +1 -1
- package/src/shadcn/avatar/avatar-badge.svelte +1 -1
- package/src/shadcn/avatar/avatar-fallback.svelte +1 -1
- package/src/shadcn/avatar/avatar-group-count.svelte +1 -1
- package/src/shadcn/avatar/avatar-group.svelte +1 -1
- package/src/shadcn/avatar/avatar-image.svelte +1 -1
- package/src/shadcn/avatar/avatar.svelte +1 -1
- package/src/shadcn/badge/badge.svelte +1 -1
- package/src/shadcn/breadcrumb/breadcrumb-ellipsis.svelte +1 -1
- package/src/shadcn/breadcrumb/breadcrumb-item.svelte +1 -1
- package/src/shadcn/breadcrumb/breadcrumb-link.svelte +1 -1
- package/src/shadcn/breadcrumb/breadcrumb-list.svelte +1 -1
- package/src/shadcn/breadcrumb/breadcrumb-page.svelte +1 -1
- package/src/shadcn/breadcrumb/breadcrumb-separator.svelte +1 -1
- package/src/shadcn/breadcrumb/breadcrumb.svelte +1 -1
- package/src/shadcn/bubble/bubble-content.svelte +1 -1
- package/src/shadcn/bubble/bubble-group.svelte +1 -1
- package/src/shadcn/bubble/bubble-reactions.svelte +1 -1
- package/src/shadcn/bubble/bubble.svelte +1 -1
- package/src/shadcn/button/button.svelte +1 -1
- package/src/shadcn/calendar/calendar-cell.svelte +1 -1
- package/src/shadcn/calendar/calendar-day.svelte +1 -1
- package/src/shadcn/calendar/calendar-grid-body.svelte +1 -1
- package/src/shadcn/calendar/calendar-grid-head.svelte +1 -1
- package/src/shadcn/calendar/calendar-grid-row.svelte +1 -1
- package/src/shadcn/calendar/calendar-grid.svelte +1 -1
- package/src/shadcn/calendar/calendar-head-cell.svelte +1 -1
- package/src/shadcn/calendar/calendar-header.svelte +1 -1
- package/src/shadcn/calendar/calendar-heading.svelte +1 -1
- package/src/shadcn/calendar/calendar-month-select.svelte +1 -1
- package/src/shadcn/calendar/calendar-month.svelte +1 -1
- package/src/shadcn/calendar/calendar-months.svelte +1 -1
- package/src/shadcn/calendar/calendar-nav.svelte +1 -1
- package/src/shadcn/calendar/calendar-next-button.svelte +1 -1
- package/src/shadcn/calendar/calendar-prev-button.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-action.svelte +1 -1
- package/src/shadcn/card/card-content.svelte +1 -1
- package/src/shadcn/card/card-description.svelte +1 -1
- package/src/shadcn/card/card-footer.svelte +1 -1
- package/src/shadcn/card/card-header.svelte +1 -1
- package/src/shadcn/card/card-title.svelte +1 -1
- package/src/shadcn/card/card.svelte +1 -1
- package/src/shadcn/checkbox/checkbox.svelte +1 -1
- package/src/shadcn/code-block/code-block-actions.svelte +1 -1
- package/src/shadcn/code-block/code-block-content.svelte +1 -1
- package/src/shadcn/code-block/code-block-footer.svelte +1 -1
- package/src/shadcn/code-block/code-block-header.svelte +1 -1
- package/src/shadcn/code-block/code-block-line.svelte +1 -1
- package/src/shadcn/code-block/code-block-title.svelte +1 -1
- package/src/shadcn/code-block/code-block.svelte +1 -1
- package/src/shadcn/command/command-dialog.svelte +1 -1
- package/src/shadcn/command/command-empty.svelte +1 -1
- package/src/shadcn/command/command-group.svelte +1 -1
- package/src/shadcn/command/command-input.svelte +2 -2
- package/src/shadcn/command/command-item.svelte +1 -1
- package/src/shadcn/command/command-link-item.svelte +1 -1
- package/src/shadcn/command/command-list.svelte +1 -1
- package/src/shadcn/command/command-separator.svelte +1 -1
- package/src/shadcn/command/command-shortcut.svelte +1 -1
- package/src/shadcn/command/command.svelte +1 -1
- package/src/shadcn/context-menu/context-menu-checkbox-item.svelte +1 -1
- package/src/shadcn/context-menu/context-menu-content.svelte +2 -2
- package/src/shadcn/context-menu/context-menu-group-heading.svelte +1 -1
- package/src/shadcn/context-menu/context-menu-item.svelte +1 -1
- package/src/shadcn/context-menu/context-menu-label.svelte +1 -1
- package/src/shadcn/context-menu/context-menu-radio-item.svelte +1 -1
- package/src/shadcn/context-menu/context-menu-separator.svelte +1 -1
- package/src/shadcn/context-menu/context-menu-shortcut.svelte +1 -1
- package/src/shadcn/context-menu/context-menu-sub-content.svelte +1 -1
- package/src/shadcn/context-menu/context-menu-sub-trigger.svelte +1 -1
- package/src/shadcn/context-menu/context-menu-trigger.svelte +1 -1
- package/src/shadcn/dialog/dialog-content.svelte +1 -1
- package/src/shadcn/dialog/dialog-description.svelte +1 -1
- package/src/shadcn/dialog/dialog-footer.svelte +1 -1
- package/src/shadcn/dialog/dialog-header.svelte +1 -1
- package/src/shadcn/dialog/dialog-overlay.svelte +1 -1
- package/src/shadcn/dialog/dialog-title.svelte +1 -1
- package/src/shadcn/dropdown-menu/dropdown-menu-checkbox-item.svelte +1 -1
- package/src/shadcn/dropdown-menu/dropdown-menu-content.svelte +1 -1
- package/src/shadcn/dropdown-menu/dropdown-menu-group-heading.svelte +1 -1
- package/src/shadcn/dropdown-menu/dropdown-menu-item.svelte +1 -1
- package/src/shadcn/dropdown-menu/dropdown-menu-label.svelte +1 -1
- package/src/shadcn/dropdown-menu/dropdown-menu-radio-item.svelte +1 -1
- package/src/shadcn/dropdown-menu/dropdown-menu-separator.svelte +1 -1
- package/src/shadcn/dropdown-menu/dropdown-menu-shortcut.svelte +1 -1
- package/src/shadcn/dropdown-menu/dropdown-menu-sub-content.svelte +1 -1
- package/src/shadcn/dropdown-menu/dropdown-menu-sub-trigger.svelte +1 -1
- package/src/shadcn/empty/empty-content.svelte +1 -1
- package/src/shadcn/empty/empty-description.svelte +1 -1
- package/src/shadcn/empty/empty-header.svelte +1 -1
- package/src/shadcn/empty/empty-media.svelte +1 -1
- package/src/shadcn/empty/empty-title.svelte +1 -1
- package/src/shadcn/empty/empty.svelte +1 -1
- package/src/shadcn/field/field-content.svelte +1 -1
- package/src/shadcn/field/field-description.svelte +1 -1
- package/src/shadcn/field/field-error.svelte +1 -1
- package/src/shadcn/field/field-group.svelte +1 -1
- package/src/shadcn/field/field-label.svelte +1 -1
- package/src/shadcn/field/field-legend.svelte +1 -1
- package/src/shadcn/field/field-separator.svelte +1 -1
- package/src/shadcn/field/field-set.svelte +1 -1
- package/src/shadcn/field/field-title.svelte +1 -1
- package/src/shadcn/field/field.svelte +1 -1
- package/src/shadcn/field/simple-field.svelte +1 -1
- package/src/shadcn/hover-card/hover-card-content.svelte +1 -1
- package/src/shadcn/input/input.svelte +1 -1
- package/src/shadcn/input/secret-input.svelte +1 -1
- package/src/shadcn/input-otp/input-otp-group.svelte +1 -1
- package/src/shadcn/input-otp/input-otp-separator.svelte +1 -1
- package/src/shadcn/input-otp/input-otp-slot.svelte +1 -1
- package/src/shadcn/input-otp/input-otp.svelte +1 -1
- package/src/shadcn/kbd/kbd-group.svelte +1 -1
- package/src/shadcn/kbd/kbd.svelte +1 -1
- package/src/shadcn/label/label.svelte +1 -1
- package/src/shadcn/marker/marker-content.svelte +1 -1
- package/src/shadcn/marker/marker-icon.svelte +1 -1
- package/src/shadcn/marker/marker.svelte +1 -1
- package/src/shadcn/message/message-avatar.svelte +1 -1
- package/src/shadcn/message/message-content.svelte +1 -1
- package/src/shadcn/message/message-footer.svelte +1 -1
- package/src/shadcn/message/message-group.svelte +1 -1
- package/src/shadcn/message/message-header.svelte +1 -1
- package/src/shadcn/message/message.svelte +1 -1
- package/src/shadcn/message-scroller/message-scroller-button.svelte +1 -1
- package/src/shadcn/message-scroller/message-scroller-content.svelte +1 -1
- package/src/shadcn/message-scroller/message-scroller-item.svelte +1 -1
- package/src/shadcn/message-scroller/message-scroller-viewport.svelte +1 -1
- package/src/shadcn/message-scroller/message-scroller.svelte +1 -1
- package/src/shadcn/popover/popover-content.svelte +1 -1
- package/src/shadcn/popover/popover-description.svelte +1 -1
- package/src/shadcn/popover/popover-header.svelte +1 -1
- package/src/shadcn/popover/popover-title.svelte +1 -1
- package/src/shadcn/popover/popover-trigger.svelte +1 -1
- package/src/shadcn/progress/progress.svelte +1 -1
- package/src/shadcn/radio-group/radio-group-item.svelte +1 -1
- package/src/shadcn/radio-group/radio-group.svelte +1 -1
- package/src/shadcn/scroll-area/scroll-area-scrollbar.svelte +1 -1
- package/src/shadcn/scroll-area/scroll-area.svelte +1 -1
- package/src/shadcn/select/select-content.svelte +2 -2
- package/src/shadcn/select/select-group-heading.svelte +1 -1
- package/src/shadcn/select/select-group.svelte +1 -1
- package/src/shadcn/select/select-item.svelte +1 -1
- package/src/shadcn/select/select-label.svelte +1 -1
- package/src/shadcn/select/select-scroll-down-button.svelte +1 -1
- package/src/shadcn/select/select-scroll-up-button.svelte +1 -1
- package/src/shadcn/select/select-separator.svelte +1 -1
- package/src/shadcn/select/select-trigger.svelte +1 -1
- package/src/shadcn/separator/separator.svelte +1 -1
- package/src/shadcn/sheet/sheet-content.svelte +1 -1
- package/src/shadcn/sheet/sheet-description.svelte +1 -1
- package/src/shadcn/sheet/sheet-footer.svelte +1 -1
- package/src/shadcn/sheet/sheet-header.svelte +1 -1
- package/src/shadcn/sheet/sheet-overlay.svelte +1 -1
- package/src/shadcn/sheet/sheet-title.svelte +1 -1
- package/src/shadcn/sidebar/sidebar-content.svelte +1 -1
- package/src/shadcn/sidebar/sidebar-footer.svelte +1 -1
- package/src/shadcn/sidebar/sidebar-group-action.svelte +1 -1
- package/src/shadcn/sidebar/sidebar-group-content.svelte +1 -1
- package/src/shadcn/sidebar/sidebar-group-label.svelte +1 -1
- package/src/shadcn/sidebar/sidebar-group.svelte +1 -1
- package/src/shadcn/sidebar/sidebar-header.svelte +1 -1
- package/src/shadcn/sidebar/sidebar-input.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-badge.svelte +1 -1
- package/src/shadcn/sidebar/sidebar-menu-button.svelte +1 -1
- package/src/shadcn/sidebar/sidebar-menu-item.svelte +1 -1
- package/src/shadcn/sidebar/sidebar-menu-skeleton.svelte +1 -1
- package/src/shadcn/sidebar/sidebar-menu-sub-button.svelte +1 -1
- package/src/shadcn/sidebar/sidebar-menu-sub-item.svelte +1 -1
- package/src/shadcn/sidebar/sidebar-menu-sub.svelte +1 -1
- package/src/shadcn/sidebar/sidebar-menu.svelte +1 -1
- package/src/shadcn/sidebar/sidebar-provider.svelte +1 -1
- package/src/shadcn/sidebar/sidebar-rail.svelte +1 -1
- package/src/shadcn/sidebar/sidebar-separator.svelte +1 -1
- package/src/shadcn/sidebar/sidebar-trigger.svelte +1 -1
- package/src/shadcn/sidebar/sidebar.svelte +1 -1
- package/src/shadcn/skeleton/skeleton.svelte +1 -1
- package/src/shadcn/slider/slider.svelte +1 -1
- package/src/shadcn/spinner/spinner.svelte +1 -1
- package/src/shadcn/switch/switch.svelte +1 -1
- package/src/shadcn/table/table-body.svelte +1 -1
- package/src/shadcn/table/table-caption.svelte +1 -1
- package/src/shadcn/table/table-cell.svelte +1 -1
- package/src/shadcn/table/table-footer.svelte +1 -1
- package/src/shadcn/table/table-head.svelte +1 -1
- package/src/shadcn/table/table-header.svelte +1 -1
- package/src/shadcn/table/table-row.svelte +1 -1
- package/src/shadcn/table/table.svelte +1 -1
- package/src/shadcn/tabs/tabs-content.svelte +1 -1
- package/src/shadcn/tabs/tabs-list.svelte +1 -1
- package/src/shadcn/tabs/tabs-trigger.svelte +1 -1
- package/src/shadcn/tabs/tabs.svelte +1 -1
- package/src/shadcn/textarea/textarea.svelte +1 -1
- package/src/shadcn/toggle/toggle.svelte +1 -1
- package/src/shadcn/toggle-group/toggle-group-item.svelte +1 -1
- package/src/shadcn/toggle-group/toggle-group.svelte +1 -1
- package/src/shadcn/tooltip/tooltip-content.svelte +2 -2
- package/src/shadcn/trace/trace-collapsible-content.svelte +1 -1
- package/src/shadcn/trace/trace-collapsible-trigger.svelte +1 -1
- package/src/shadcn/trace/trace-collapsible.svelte +1 -1
- package/src/shadcn/trace/trace-description.svelte +1 -1
- package/src/shadcn/trace/trace-icon.svelte +1 -1
- package/src/shadcn/trace/trace-line.svelte +1 -1
- package/src/shadcn/trace/trace-title.svelte +1 -1
- package/src/shadcn/trace/trace.svelte +1 -1
- package/src/slides/Controls.svelte +138 -0
- package/src/slides/Deck.svelte +271 -0
- package/src/slides/Fragment.svelte +71 -0
- package/src/slides/Overview.svelte +113 -0
- package/src/slides/Presenter.svelte +292 -0
- package/src/slides/README.md +89 -0
- package/src/slides/Slide.svelte +179 -0
- package/src/slides/SlideList.svelte +19 -0
- package/src/slides/ambient.d.ts +1 -0
- package/src/slides/bridges.ts +69 -0
- package/src/slides/deck-controller.svelte.ts +292 -0
- package/src/slides/demo-slides.svelte +358 -0
- package/src/slides/demo.svelte +176 -0
- package/src/slides/export/capture.ts +113 -0
- package/src/slides/export/ffmpeg.ts +120 -0
- package/src/slides/export/index.ts +9 -0
- package/src/slides/export/launch.ts +10 -0
- package/src/slides/export/pdf.ts +152 -0
- package/src/slides/export/types.ts +48 -0
- package/src/slides/index.ts +19 -0
- package/src/slides/labels.ts +48 -0
- package/src/slides/layout.ts +51 -0
- package/src/slides/model.ts +47 -0
- package/src/slides/navigation.ts +100 -0
- package/src/slides/theme.css +205 -0
- package/src/slides/types.ts +86 -0
- package/src/tasks/README.md +312 -0
- package/src/tasks/ambient.d.ts +3 -0
- package/src/tasks/board-order.ts +137 -0
- package/src/tasks/dates.ts +31 -0
- package/src/tasks/define-statuses.ts +32 -0
- package/src/tasks/demo.svelte +389 -0
- package/src/tasks/detail/detail-demo.svelte +186 -0
- package/src/tasks/detail/detail-types.ts +54 -0
- package/src/tasks/detail/task-chip.svelte +28 -0
- package/src/tasks/detail/task-detail.svelte +315 -0
- package/src/tasks/detail/task-property.svelte +17 -0
- package/src/tasks/detail/task-thread.svelte +258 -0
- package/src/tasks/gantt/gantt-bar.svelte +243 -0
- package/src/tasks/gantt/gantt-grid.svelte +94 -0
- package/src/tasks/gantt/gantt-group-row.svelte +55 -0
- package/src/tasks/gantt/gantt-header.svelte +71 -0
- package/src/tasks/gantt/gantt-task-row.svelte +186 -0
- package/src/tasks/gantt/gantt.css +22 -0
- package/src/tasks/gantt/gantt.test.ts +77 -0
- package/src/tasks/gantt/logic.test.ts +163 -0
- package/src/tasks/gantt/logic.ts +248 -0
- package/src/tasks/inbox/inbox-card.svelte +283 -0
- package/src/tasks/inbox/inbox-draft.svelte +84 -0
- package/src/tasks/inbox/inbox-logic.ts +55 -0
- package/src/tasks/inbox/inbox-statuses.ts +48 -0
- package/src/tasks/inbox/inbox-trace.svelte +45 -0
- package/src/tasks/inbox/inbox-types.ts +108 -0
- package/src/tasks/inbox/inbox.svelte +203 -0
- package/src/tasks/inbox/index.ts +23 -0
- package/src/tasks/index.ts +85 -0
- package/src/tasks/kanban-board.svelte +561 -0
- package/src/tasks/kanban-column.svelte +117 -0
- package/src/tasks/kanban-drag.ts +38 -0
- package/src/tasks/kanban-types.ts +65 -0
- package/src/tasks/presets/sales.ts +16 -0
- package/src/tasks/record/demo-data.ts +286 -0
- package/src/tasks/record/field-section.svelte +53 -0
- package/src/tasks/record/fields.ts +142 -0
- package/src/tasks/record/format.ts +87 -0
- package/src/tasks/record/gutter.ts +2 -0
- package/src/tasks/record/index.ts +54 -0
- package/src/tasks/record/info-list.svelte +316 -0
- package/src/tasks/record/kinds.ts +42 -0
- package/src/tasks/record/logic.ts +324 -0
- package/src/tasks/record/next-up.svelte +70 -0
- package/src/tasks/record/project-list.svelte +133 -0
- package/src/tasks/record/record-demo.svelte +324 -0
- package/src/tasks/record/record-header.svelte +70 -0
- package/src/tasks/record/record-shell.svelte +138 -0
- package/src/tasks/record/record-stats.svelte +48 -0
- package/src/tasks/record/record-timeline.svelte +231 -0
- package/src/tasks/record/record.test.ts +350 -0
- package/src/tasks/record/steps-rail.svelte +474 -0
- package/src/tasks/record/types.ts +280 -0
- package/src/tasks/record/where-we-are.svelte +121 -0
- package/src/tasks/reorder.ts +19 -0
- package/src/tasks/side-panel-types.ts +16 -0
- package/src/tasks/side-panel.svelte +193 -0
- package/src/tasks/status-badge.svelte +63 -0
- package/src/tasks/status-dot.svelte +228 -0
- package/src/tasks/status-icon.svelte +30 -0
- package/src/tasks/status-types.ts +40 -0
- package/src/tasks/task-gantt.svelte +230 -0
- package/src/tasks/task-list.svelte +470 -0
- package/src/tasks/task-types.ts +33 -0
- package/src/tasks/time/duration.ts +69 -0
- package/src/tasks/time/time-chip.svelte +62 -0
- package/src/tasks/time/time-dot.svelte +58 -0
- package/src/tasks/time/time-ring.svelte +74 -0
- package/src/tasks/time/time-tracker.svelte +565 -0
- package/src/tasks/time/time-types.ts +74 -0
- package/src/tasks/use-kanban-dnd.svelte.ts +173 -0
- package/src/tokens/base.css +5 -5
- package/src/tokens/palette.css +115 -120
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
|
|
4
|
+
/** Narrow screens drop the columns that only add detail. */
|
|
5
|
+
export type DataColumnBreakpoint = "sm" | "md" | "lg";
|
|
6
|
+
|
|
7
|
+
/** One column after the lead. Give it `value`, `rating` or a `cell` snippet. */
|
|
8
|
+
export type DataColumn<T> = {
|
|
9
|
+
key: string;
|
|
10
|
+
label: string;
|
|
11
|
+
/** The long form of the head, as its tooltip. */
|
|
12
|
+
hint?: string;
|
|
13
|
+
/** A Tailwind width class. Fixed widths are what keep the lead column wide. */
|
|
14
|
+
width: string;
|
|
15
|
+
/** Figures run to the end, so digits line up. Text, pills and bars start. */
|
|
16
|
+
align?: "start" | "end";
|
|
17
|
+
/** Makes the head rank the table. Higher is better; a new column starts descending. */
|
|
18
|
+
sort?: (row: T) => number;
|
|
19
|
+
/** Null draws the table's empty label, muted. */
|
|
20
|
+
value?: (row: T) => string | null;
|
|
21
|
+
rating?: (row: T) => { grade: string; hint?: string } | null;
|
|
22
|
+
cell?: Snippet<[T]>;
|
|
23
|
+
/** 0 to 1, a share-of-max track drawn before the value. */
|
|
24
|
+
bar?: (row: T) => number;
|
|
25
|
+
/** Cell tooltip. */
|
|
26
|
+
title?: (row: T) => string | undefined;
|
|
27
|
+
muted?: boolean | ((row: T) => boolean);
|
|
28
|
+
/** A loss, an overrun: the value turns the destructive colour. */
|
|
29
|
+
negative?: (row: T) => boolean;
|
|
30
|
+
/** The column the eye lands on first. */
|
|
31
|
+
strong?: boolean;
|
|
32
|
+
/** A hairline before this column, opening a group. */
|
|
33
|
+
rule?: boolean;
|
|
34
|
+
/** Hidden under this breakpoint. */
|
|
35
|
+
hideBelow?: DataColumnBreakpoint;
|
|
36
|
+
};
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<script lang="ts" generics="T">
|
|
40
|
+
/*
|
|
41
|
+
* The one data table: wide lead cell, fixed figure columns, sortable heads,
|
|
42
|
+
* optional pager. A row opens by `rowHref` or `onRowClick`, stretched over the row from the lead.
|
|
43
|
+
*/
|
|
44
|
+
import { cn } from "../../lib/utils.js";
|
|
45
|
+
import DataSortHead from "./data-sort-head.svelte";
|
|
46
|
+
import RatingChip from "./rating-chip.svelte";
|
|
47
|
+
import { Pagination, paginateRows, totalPages } from "../../components/data-table/index.js";
|
|
48
|
+
import {
|
|
49
|
+
dataTableClass,
|
|
50
|
+
dataTdClass,
|
|
51
|
+
dataTdNumClass,
|
|
52
|
+
dataThClass,
|
|
53
|
+
dataThNumClass,
|
|
54
|
+
dataTheadClass,
|
|
55
|
+
dataTrClass,
|
|
56
|
+
} from "./data-table.js";
|
|
57
|
+
|
|
58
|
+
interface Props {
|
|
59
|
+
rows: T[];
|
|
60
|
+
columns: DataColumn<T>[];
|
|
61
|
+
rowKey: (row: T) => string;
|
|
62
|
+
leadLabel: string;
|
|
63
|
+
lead: Snippet<[T]>;
|
|
64
|
+
/** Drawn where a value or rating is null. */
|
|
65
|
+
emptyLabel: string;
|
|
66
|
+
/** A Tailwind width class. Omit and the lead takes whatever the figures leave. */
|
|
67
|
+
leadWidth?: string;
|
|
68
|
+
/** A Tailwind min-width class, past which the panel scrolls sideways. */
|
|
69
|
+
minWidth?: string;
|
|
70
|
+
/** Omit for every row on one page. */
|
|
71
|
+
pageSize?: number;
|
|
72
|
+
sortKey?: string | null;
|
|
73
|
+
descending?: boolean;
|
|
74
|
+
page?: number;
|
|
75
|
+
/** The row opens a page. The lead becomes the link. */
|
|
76
|
+
rowHref?: (row: T) => string;
|
|
77
|
+
/** The row opens in place: a panel, a detail view. The lead becomes the button. */
|
|
78
|
+
onRowClick?: (row: T) => void;
|
|
79
|
+
/** Placeholder rows while the data is on its way. */
|
|
80
|
+
loading?: boolean;
|
|
81
|
+
/** Drawn in place of the body when there are no rows. */
|
|
82
|
+
empty?: Snippet;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
let {
|
|
86
|
+
rows,
|
|
87
|
+
columns,
|
|
88
|
+
rowKey,
|
|
89
|
+
leadLabel,
|
|
90
|
+
lead,
|
|
91
|
+
emptyLabel,
|
|
92
|
+
leadWidth,
|
|
93
|
+
minWidth = "min-w-[48rem]",
|
|
94
|
+
pageSize,
|
|
95
|
+
sortKey = $bindable(null),
|
|
96
|
+
descending = $bindable(true),
|
|
97
|
+
page = $bindable(1),
|
|
98
|
+
rowHref,
|
|
99
|
+
onRowClick,
|
|
100
|
+
loading = false,
|
|
101
|
+
empty,
|
|
102
|
+
}: Props = $props();
|
|
103
|
+
|
|
104
|
+
const pad = "px-3";
|
|
105
|
+
const LOADING_ROWS = 5;
|
|
106
|
+
|
|
107
|
+
// Literal classes, so Tailwind sees each one.
|
|
108
|
+
const HIDE_BELOW: Record<DataColumnBreakpoint, string> = {
|
|
109
|
+
sm: "hidden sm:table-cell",
|
|
110
|
+
md: "hidden md:table-cell",
|
|
111
|
+
lg: "hidden lg:table-cell",
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// The ::after covers the whole row, so any cell opens it and the lead stays the one tab stop.
|
|
115
|
+
const stretch =
|
|
116
|
+
"text-left outline-none after:absolute after:inset-0 after:content-[''] focus-visible:after:ring-2 focus-visible:after:ring-inset focus-visible:after:ring-ring";
|
|
117
|
+
|
|
118
|
+
const sorted = $derived.by(() => {
|
|
119
|
+
const by = columns.find((column) => column.key === sortKey)?.sort;
|
|
120
|
+
if (!by) return rows;
|
|
121
|
+
const direction = descending ? -1 : 1;
|
|
122
|
+
return [...rows].sort((left, right) => (by(left) - by(right)) * direction);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const visible = $derived(pageSize ? paginateRows(sorted, page, pageSize) : sorted);
|
|
126
|
+
const opens = $derived(Boolean(rowHref || onRowClick));
|
|
127
|
+
|
|
128
|
+
// Fewer rows than the page you stand on would draw an empty table.
|
|
129
|
+
$effect(() => {
|
|
130
|
+
if (!pageSize) return;
|
|
131
|
+
const last = totalPages(sorted.length, pageSize);
|
|
132
|
+
if (page > last) page = last;
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
function rankBy(key: string) {
|
|
136
|
+
page = 1;
|
|
137
|
+
if (sortKey === key) {
|
|
138
|
+
descending = !descending;
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
sortKey = key;
|
|
142
|
+
descending = true;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function isMuted(column: DataColumn<T>, row: T) {
|
|
146
|
+
return typeof column.muted === "function" ? column.muted(row) : Boolean(column.muted);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function columnClass(column: DataColumn<T>) {
|
|
150
|
+
return cn(
|
|
151
|
+
pad,
|
|
152
|
+
column.align === "start" && "text-left",
|
|
153
|
+
column.rule && "border-l border-border",
|
|
154
|
+
column.hideBelow && HIDE_BELOW[column.hideBelow],
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
</script>
|
|
158
|
+
|
|
159
|
+
<table class={cn(dataTableClass, minWidth, "table-fixed")}>
|
|
160
|
+
<colgroup>
|
|
161
|
+
<col class={leadWidth} />
|
|
162
|
+
{#each columns as column (column.key)}
|
|
163
|
+
<col class={cn(column.width, column.hideBelow && HIDE_BELOW[column.hideBelow])} />
|
|
164
|
+
{/each}
|
|
165
|
+
</colgroup>
|
|
166
|
+
<thead class={dataTheadClass}>
|
|
167
|
+
<tr>
|
|
168
|
+
<th scope="col" class={dataThClass}>{leadLabel}</th>
|
|
169
|
+
{#each columns as column (column.key)}
|
|
170
|
+
{#if column.sort}
|
|
171
|
+
<DataSortHead
|
|
172
|
+
numeric={column.align !== "start"}
|
|
173
|
+
class={columnClass(column)}
|
|
174
|
+
active={sortKey === column.key}
|
|
175
|
+
{descending}
|
|
176
|
+
onSort={() => rankBy(column.key)}
|
|
177
|
+
title={column.hint}
|
|
178
|
+
>
|
|
179
|
+
{column.label}
|
|
180
|
+
</DataSortHead>
|
|
181
|
+
{:else}
|
|
182
|
+
<th
|
|
183
|
+
scope="col"
|
|
184
|
+
class={cn(column.align === "start" ? dataThClass : dataThNumClass, columnClass(column))}
|
|
185
|
+
title={column.hint}
|
|
186
|
+
>
|
|
187
|
+
{column.label}
|
|
188
|
+
</th>
|
|
189
|
+
{/if}
|
|
190
|
+
{/each}
|
|
191
|
+
</tr>
|
|
192
|
+
</thead>
|
|
193
|
+
<tbody>
|
|
194
|
+
{#if loading}
|
|
195
|
+
{#each { length: LOADING_ROWS }, index (index)}
|
|
196
|
+
<tr class={dataTrClass} aria-hidden="true">
|
|
197
|
+
<td class={cn(dataTdClass, "py-3")}>
|
|
198
|
+
<span class="block h-4 w-2/3 animate-pulse rounded bg-muted"></span>
|
|
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}
|
|
210
|
+
</tr>
|
|
211
|
+
{/each}
|
|
212
|
+
{:else if visible.length === 0 && empty}
|
|
213
|
+
<tr>
|
|
214
|
+
<td colspan={columns.length + 1}>{@render empty()}</td>
|
|
215
|
+
</tr>
|
|
216
|
+
{:else}
|
|
217
|
+
{#each visible as row (rowKey(row))}
|
|
218
|
+
<tr class={cn(dataTrClass, "group", opens && "relative cursor-pointer")}>
|
|
219
|
+
<td class={cn(dataTdClass, "py-3")}>
|
|
220
|
+
{#if rowHref}
|
|
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",
|
|
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}
|
|
251
|
+
{:else}
|
|
252
|
+
{@const value = column.value?.(row) ?? null}
|
|
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}
|
|
268
|
+
{/if}
|
|
269
|
+
</td>
|
|
270
|
+
{/each}
|
|
271
|
+
</tr>
|
|
272
|
+
{/each}
|
|
273
|
+
{/if}
|
|
274
|
+
</tbody>
|
|
275
|
+
</table>
|
|
276
|
+
{#if pageSize && !loading}
|
|
277
|
+
<Pagination {page} {pageSize} total={sorted.length} onPageChange={(next) => (page = next)} />
|
|
278
|
+
{/if}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one skin for a dashboard data table. Classes, not a component: two
|
|
3
|
+
* dashboards share nothing but the look, and each drew its own slightly different one.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Heads aren't uppercase. `SectionLabel` already is, and a nine-column head in
|
|
7
|
+
// the same style fights the panel title and reads worse where density matters.
|
|
8
|
+
export const dataTableClass = "w-full text-sm";
|
|
9
|
+
|
|
10
|
+
export const dataTheadClass = "bg-muted/40 text-xs text-muted-foreground";
|
|
11
|
+
|
|
12
|
+
export const dataThClass = "px-4 py-2.5 text-left font-medium";
|
|
13
|
+
|
|
14
|
+
/** A head over numbers. Right-aligned to the digits under it. */
|
|
15
|
+
export const dataThNumClass = "px-4 py-2.5 text-right font-medium";
|
|
16
|
+
|
|
17
|
+
export const dataTrClass =
|
|
18
|
+
"border-b border-border transition-colors last:border-0 hover:bg-muted/40";
|
|
19
|
+
|
|
20
|
+
export const dataTdClass = "px-4 py-2.5 align-top";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A cell holding a number. `tabular-nums` is required: proportional digits make
|
|
24
|
+
* a spend column ragged, and then nobody can scan it.
|
|
25
|
+
*/
|
|
26
|
+
export const dataTdNumClass = "px-4 py-2.5 text-right align-top tabular-nums";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export { default as DataPanel } from "./data-panel.svelte";
|
|
2
|
+
export { default as DataSortHead } from "./data-sort-head.svelte";
|
|
3
|
+
export {
|
|
4
|
+
default as DataTable,
|
|
5
|
+
type DataColumn,
|
|
6
|
+
type DataColumnBreakpoint,
|
|
7
|
+
} from "./data-table.svelte";
|
|
8
|
+
export { default as RatingChip } from "./rating-chip.svelte";
|
|
9
|
+
export { default as DashboardEmpty } from "./dashboard-empty.svelte";
|
|
10
|
+
export { default as DashboardSection } from "./dashboard-section.svelte";
|
|
11
|
+
export { default as PanelGrid } from "./panel-grid.svelte";
|
|
12
|
+
export { default as PeriodFilter } from "./period-filter.svelte";
|
|
13
|
+
export { default as StatBand } from "./stat-band.svelte";
|
|
14
|
+
export { default as StatTile, type StatDirection } from "./stat-tile.svelte";
|
|
15
|
+
export { CHART_HEIGHT } from "./chart.js";
|
|
16
|
+
export { DAY_WINDOWS, type DayWindow } from "./windows.js";
|
|
17
|
+
export {
|
|
18
|
+
dataTableClass,
|
|
19
|
+
dataTdClass,
|
|
20
|
+
dataTdNumClass,
|
|
21
|
+
dataThClass,
|
|
22
|
+
dataThNumClass,
|
|
23
|
+
dataTheadClass,
|
|
24
|
+
dataTrClass,
|
|
25
|
+
} from "./data-table.js";
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// =========================
|
|
3
|
+
// IMPORTS
|
|
4
|
+
// =========================
|
|
5
|
+
import type { Snippet } from "svelte";
|
|
6
|
+
import { cn } from "../../lib/utils.js";
|
|
7
|
+
|
|
8
|
+
// =========================
|
|
9
|
+
// TYPES
|
|
10
|
+
// =========================
|
|
11
|
+
interface Props {
|
|
12
|
+
/** `<DataPanel>` children, or tiles standing beside one. */
|
|
13
|
+
children: Snippet;
|
|
14
|
+
/**
|
|
15
|
+
* Panels per row: two charts, three breakdowns, or `main`, where the first
|
|
16
|
+
* child takes two thirds and the rates beside it one third.
|
|
17
|
+
*/
|
|
18
|
+
columns?: 2 | 3 | "main";
|
|
19
|
+
class?: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// =========================
|
|
23
|
+
// PROPS
|
|
24
|
+
// =========================
|
|
25
|
+
let { children, columns = 2, class: className }: Props = $props();
|
|
26
|
+
|
|
27
|
+
// =========================
|
|
28
|
+
// CONSTANTS
|
|
29
|
+
// =========================
|
|
30
|
+
const COLUMNS: Record<2 | 3 | "main", string> = {
|
|
31
|
+
2: "lg:grid-cols-2",
|
|
32
|
+
3: "lg:grid-cols-2 xl:grid-cols-3",
|
|
33
|
+
main: "lg:grid-cols-3 lg:[&>*:first-child]:col-span-2",
|
|
34
|
+
};
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<!-- =========================== -->
|
|
38
|
+
<!-- MARKUP -->
|
|
39
|
+
<!-- =========================== -->
|
|
40
|
+
<!-- The one row of panels side by side. -->
|
|
41
|
+
<div class={cn("grid gap-4", COLUMNS[columns], className)}>
|
|
42
|
+
{@render children()}
|
|
43
|
+
</div>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<script lang="ts" generics="T extends string">
|
|
2
|
+
// =========================
|
|
3
|
+
// IMPORTS
|
|
4
|
+
// =========================
|
|
5
|
+
import { cn } from "../../lib/utils.js";
|
|
6
|
+
import { SegmentedFilter, SegmentedNav, type TabDef } from "../../components/segmented/index.js";
|
|
7
|
+
|
|
8
|
+
// =========================
|
|
9
|
+
// TYPES
|
|
10
|
+
// =========================
|
|
11
|
+
interface Props {
|
|
12
|
+
options: readonly TabDef<T>[];
|
|
13
|
+
value: T;
|
|
14
|
+
/**
|
|
15
|
+
* Give this and the strip is links, so the choice survives reload, back and a
|
|
16
|
+
* shared link. Omit it and the strip is a `<SegmentedFilter>` the caller owns.
|
|
17
|
+
*/
|
|
18
|
+
href?: (value: T) => string;
|
|
19
|
+
/** Filter mode only. Ignored when `href` is set. */
|
|
20
|
+
onValueChange?: (value: T) => void;
|
|
21
|
+
ariaLabel: string;
|
|
22
|
+
class?: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// =========================
|
|
26
|
+
// PROPS
|
|
27
|
+
// =========================
|
|
28
|
+
let { options, value, href, onValueChange, ariaLabel, class: className }: Props = $props();
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<!-- =========================== -->
|
|
32
|
+
<!-- MARKUP -->
|
|
33
|
+
<!-- =========================== -->
|
|
34
|
+
<!-- WHY one component for the window, not a `SegmentedNav` here and a
|
|
35
|
+
`SegmentedFilter` there: identical pages announced themselves differently.
|
|
36
|
+
Place it first in the toolbar's right cluster. -->
|
|
37
|
+
{#if href}
|
|
38
|
+
<SegmentedNav
|
|
39
|
+
items={options.map((option) => ({ ...option, href: href(option.value) }))}
|
|
40
|
+
current={href(value)}
|
|
41
|
+
{ariaLabel}
|
|
42
|
+
class={className}
|
|
43
|
+
/>
|
|
44
|
+
{:else}
|
|
45
|
+
<SegmentedFilter
|
|
46
|
+
{options}
|
|
47
|
+
{value}
|
|
48
|
+
{onValueChange}
|
|
49
|
+
{ariaLabel}
|
|
50
|
+
block={false}
|
|
51
|
+
class={cn("shrink-0", className)}
|
|
52
|
+
/>
|
|
53
|
+
{/if}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/*
|
|
3
|
+
* One A to E grade on one colour scale, so a C means one thing everywhere.
|
|
4
|
+
* Light mode takes the 800 shades, since amber on cream fails contrast.
|
|
5
|
+
*/
|
|
6
|
+
import { cn } from "../../lib/utils.js";
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
grade: string;
|
|
10
|
+
/** What the letter hides: the score and what it was charged for. */
|
|
11
|
+
hint?: string;
|
|
12
|
+
class?: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
let { grade, hint, class: className }: Props = $props();
|
|
16
|
+
|
|
17
|
+
const TONE: Record<string, string> = {
|
|
18
|
+
A: "bg-success/12 text-success-800 dark:text-success",
|
|
19
|
+
B: "bg-success/12 text-success-800 dark:text-success",
|
|
20
|
+
C: "bg-muted text-foreground",
|
|
21
|
+
D: "bg-warning/18 text-warning-800 dark:text-warning",
|
|
22
|
+
E: "bg-destructive/12 text-destructive",
|
|
23
|
+
};
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<span
|
|
27
|
+
class={cn(
|
|
28
|
+
"inline-flex h-6 min-w-6 items-center justify-center rounded-md px-1.5 text-xs font-semibold tabular-nums",
|
|
29
|
+
TONE[grade] ?? TONE.C,
|
|
30
|
+
className,
|
|
31
|
+
)}
|
|
32
|
+
title={hint}
|
|
33
|
+
>
|
|
34
|
+
{grade}
|
|
35
|
+
</span>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// =========================
|
|
3
|
+
// IMPORTS
|
|
4
|
+
// =========================
|
|
5
|
+
import type { Snippet } from "svelte";
|
|
6
|
+
import { cn } from "../../lib/utils.js";
|
|
7
|
+
|
|
8
|
+
// =========================
|
|
9
|
+
// TYPES
|
|
10
|
+
// =========================
|
|
11
|
+
interface Props {
|
|
12
|
+
/** `<StatTile>` children. Four to a row on a desk, two on a phone. */
|
|
13
|
+
children: Snippet;
|
|
14
|
+
/**
|
|
15
|
+
* Cards per row at the widest breakpoint, four unless the set is smaller.
|
|
16
|
+
* Five and six are only for a fixed scorecard. The headline stays one row.
|
|
17
|
+
*/
|
|
18
|
+
columns?: 2 | 3 | 4 | 5 | 6;
|
|
19
|
+
class?: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// =========================
|
|
23
|
+
// PROPS
|
|
24
|
+
// =========================
|
|
25
|
+
let { children, columns = 4, class: className }: Props = $props();
|
|
26
|
+
|
|
27
|
+
// =========================
|
|
28
|
+
// DERIVED
|
|
29
|
+
// =========================
|
|
30
|
+
const COLUMNS: Record<2 | 3 | 4 | 5 | 6, string> = {
|
|
31
|
+
2: "sm:grid-cols-2",
|
|
32
|
+
3: "sm:grid-cols-2 lg:grid-cols-3",
|
|
33
|
+
4: "sm:grid-cols-2 lg:grid-cols-4",
|
|
34
|
+
5: "sm:grid-cols-3 lg:grid-cols-5",
|
|
35
|
+
6: "sm:grid-cols-3 xl:grid-cols-6",
|
|
36
|
+
};
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<!-- =========================== -->
|
|
40
|
+
<!-- MARKUP -->
|
|
41
|
+
<!-- =========================== -->
|
|
42
|
+
<!-- Numbers get number cards. One panel with hairlines made four figures float
|
|
43
|
+
in one box on a dark theme, where hairlines vanish. -->
|
|
44
|
+
<!-- No border on the band itself: the cards carry it, or it's a box of boxes. -->
|
|
45
|
+
<div class={cn("grid grid-cols-2 gap-4", COLUMNS[columns], className)}>
|
|
46
|
+
{@render children()}
|
|
47
|
+
</div>
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
/**
|
|
3
|
+
* Which way a metric has to move to be good news. `neutral` fits a figure like
|
|
4
|
+
* spend, which isn't a win or a loss until the return sits beside it.
|
|
5
|
+
*/
|
|
6
|
+
export type StatDirection = "up-good" | "down-good" | "neutral";
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<script lang="ts">
|
|
10
|
+
// =========================
|
|
11
|
+
// IMPORTS
|
|
12
|
+
// =========================
|
|
13
|
+
import NumberChart from "../number-chart.svelte";
|
|
14
|
+
import Sparkline from "../sparkline.svelte";
|
|
15
|
+
import type { Snippet } from "svelte";
|
|
16
|
+
import { cn } from "../../lib/utils.js";
|
|
17
|
+
import { Icon } from "../../components/icon/index.js";
|
|
18
|
+
import type { PhosphorIcon } from "../../lib/icon-types.js";
|
|
19
|
+
|
|
20
|
+
// =========================
|
|
21
|
+
// TYPES
|
|
22
|
+
// =========================
|
|
23
|
+
interface Props {
|
|
24
|
+
label: string;
|
|
25
|
+
/** Already formatted. The tile never guesses a currency or a locale. */
|
|
26
|
+
value: string;
|
|
27
|
+
/**
|
|
28
|
+
* Percent change against the previous period. `null` means nothing to
|
|
29
|
+
* compare yet and draws a dash. Omit it when the figure has no period at all.
|
|
30
|
+
*/
|
|
31
|
+
deltaPct?: number | null;
|
|
32
|
+
direction?: StatDirection;
|
|
33
|
+
/** The one figure the screen was opened to read. Set it on exactly one tile. */
|
|
34
|
+
lead?: boolean;
|
|
35
|
+
/** A quiet line under the value: what it was measured over, or why it is a dash. */
|
|
36
|
+
hint?: string;
|
|
37
|
+
/**
|
|
38
|
+
* 0 to 1, drawn as a track under the value: share of a total or progress to
|
|
39
|
+
* a target. Omit it for a cash balance.
|
|
40
|
+
*/
|
|
41
|
+
share?: number;
|
|
42
|
+
/** What `share` is measured against, e.g. "of revenue". Only with `share`. */
|
|
43
|
+
shareLabel?: string;
|
|
44
|
+
/** Track colour. The first series token unless a tile means something else. */
|
|
45
|
+
color?: string;
|
|
46
|
+
/**
|
|
47
|
+
* A shape behind the figure, oldest first, bled along the card's bottom.
|
|
48
|
+
* The treatment every tile gives a number with a history. Omit when there
|
|
49
|
+
* is none.
|
|
50
|
+
*/
|
|
51
|
+
series?: number[];
|
|
52
|
+
/** Leading mark before the label. */
|
|
53
|
+
icon?: PhosphorIcon;
|
|
54
|
+
/**
|
|
55
|
+
* Makes the tile a link, like a balance that opens the bank. It lives here so
|
|
56
|
+
* a linked figure isn't drawn as a second, different card style.
|
|
57
|
+
*/
|
|
58
|
+
href?: string;
|
|
59
|
+
/** What the link opens, shown on hover. Only with `href`. */
|
|
60
|
+
hrefLabel?: string;
|
|
61
|
+
/**
|
|
62
|
+
* `_blank` for a figure that leaves the app (a bank, a provider console),
|
|
63
|
+
* `_self` for one that is another page of this app. Only with `href`.
|
|
64
|
+
*/
|
|
65
|
+
target?: "_self" | "_blank";
|
|
66
|
+
/**
|
|
67
|
+
* A chip at the end of the label row: the limit the figure is judged against,
|
|
68
|
+
* the state it is in. Wins over `hrefLabel`, which is only a hover hint.
|
|
69
|
+
*/
|
|
70
|
+
badge?: Snippet;
|
|
71
|
+
/** A loss or an overrun: the figure turns the destructive colour. */
|
|
72
|
+
negative?: boolean;
|
|
73
|
+
class?: string;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// =========================
|
|
77
|
+
// PROPS
|
|
78
|
+
// =========================
|
|
79
|
+
let {
|
|
80
|
+
label,
|
|
81
|
+
value,
|
|
82
|
+
deltaPct,
|
|
83
|
+
direction = "neutral",
|
|
84
|
+
lead = false,
|
|
85
|
+
hint,
|
|
86
|
+
share,
|
|
87
|
+
shareLabel,
|
|
88
|
+
color,
|
|
89
|
+
series,
|
|
90
|
+
icon,
|
|
91
|
+
href,
|
|
92
|
+
hrefLabel,
|
|
93
|
+
target = "_blank",
|
|
94
|
+
badge,
|
|
95
|
+
negative = false,
|
|
96
|
+
class: className,
|
|
97
|
+
}: Props = $props();
|
|
98
|
+
|
|
99
|
+
// =========================
|
|
100
|
+
// DERIVED
|
|
101
|
+
// =========================
|
|
102
|
+
// `Sparkline` plots rows, and a card carries a bare series. Two points is a
|
|
103
|
+
// line, not a shape, so it is not drawn.
|
|
104
|
+
let points = $derived(series?.map((value, index) => ({ i: index, v: value })) ?? []);
|
|
105
|
+
let hasSeries = $derived(points.length > 2);
|
|
106
|
+
</script>
|
|
107
|
+
|
|
108
|
+
<!-- =========================== -->
|
|
109
|
+
<!-- MARKUP -->
|
|
110
|
+
<!-- =========================== -->
|
|
111
|
+
<!-- The figure is `NumberChart` from the chart library, because this tile and
|
|
112
|
+
the numbers on the panels beside it are the same object and drawn twice
|
|
113
|
+
they drift. -->
|
|
114
|
+
<svelte:element
|
|
115
|
+
this={href ? "a" : "div"}
|
|
116
|
+
{href}
|
|
117
|
+
target={href ? target : undefined}
|
|
118
|
+
rel={href && target === "_blank" ? "noopener noreferrer" : undefined}
|
|
119
|
+
data-series={hasSeries ? "" : undefined}
|
|
120
|
+
class={cn(
|
|
121
|
+
"hr-stat-tile group/tile relative block rounded-xl",
|
|
122
|
+
href && "t-press focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:outline-none",
|
|
123
|
+
className,
|
|
124
|
+
)}
|
|
125
|
+
>
|
|
126
|
+
{#snippet tileIcon()}
|
|
127
|
+
{#if icon}<Icon {icon} size={14} class="shrink-0" />{/if}
|
|
128
|
+
{/snippet}
|
|
129
|
+
|
|
130
|
+
<NumberChart
|
|
131
|
+
{label}
|
|
132
|
+
{value}
|
|
133
|
+
delta={deltaPct}
|
|
134
|
+
{direction}
|
|
135
|
+
{share}
|
|
136
|
+
{shareLabel}
|
|
137
|
+
{color}
|
|
138
|
+
icon={icon ? tileIcon : undefined}
|
|
139
|
+
size={lead ? "lg" : "md"}
|
|
140
|
+
caption={hint}
|
|
141
|
+
class={cn(
|
|
142
|
+
"h-full",
|
|
143
|
+
// A label is never clipped: a chip that doesn't fit drops under it.
|
|
144
|
+
"[&_.hr-number-label]:flex-wrap [&_.hr-number-label-text]:shrink-0",
|
|
145
|
+
deltaPct === undefined && "[&_.hr-number-delta]:hidden!",
|
|
146
|
+
)}
|
|
147
|
+
{negative}
|
|
148
|
+
trailing={badge ?? (href && hrefLabel ? linkHint : undefined)}
|
|
149
|
+
/>
|
|
150
|
+
|
|
151
|
+
<!-- The shape behind the number, bled to the card's bottom corners. The
|
|
152
|
+
scoped rule below reserves the room so it never sits on the caption. -->
|
|
153
|
+
{#if hasSeries}
|
|
154
|
+
<div
|
|
155
|
+
class="pointer-events-none absolute inset-x-px bottom-px h-12 overflow-hidden rounded-b-[calc(var(--radius-xl)-1px)]"
|
|
156
|
+
aria-hidden="true"
|
|
157
|
+
>
|
|
158
|
+
<Sparkline data={points} xKey="i" yKey="v" color={color ?? "var(--chart-1)"} height={48} bare />
|
|
159
|
+
</div>
|
|
160
|
+
{/if}
|
|
161
|
+
</svelte:element>
|
|
162
|
+
|
|
163
|
+
{#snippet linkHint()}
|
|
164
|
+
<span class="opacity-0 transition-opacity group-hover/tile:opacity-100">{hrefLabel}</span>
|
|
165
|
+
{/snippet}
|
|
166
|
+
|
|
167
|
+
<!-- =========================== -->
|
|
168
|
+
<!-- STYLE -->
|
|
169
|
+
<!-- =========================== -->
|
|
170
|
+
<style>
|
|
171
|
+
/* WHY scoped here: `NumberChart` has no
|
|
172
|
+
series, so the sparkline's bottom room is added on the tile that draws it. */
|
|
173
|
+
.hr-stat-tile[data-series] :global(.hr-number-chart) {
|
|
174
|
+
padding-bottom: 4.25rem;
|
|
175
|
+
}
|
|
176
|
+
</style>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Day windows a strip offers: seven, thirty, ninety, the three every dataset
|
|
3
|
+
* has. A page may add an option its data carries; labels translate at the
|
|
4
|
+
* call site.
|
|
5
|
+
*/
|
|
6
|
+
export const DAY_WINDOWS = [7, 30, 90] as const;
|
|
7
|
+
|
|
8
|
+
export type DayWindow = (typeof DAY_WINDOWS)[number];
|