@heyramzi/ui 2.0.1 → 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 +48 -24
- 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/README.md +9 -12
- 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 +8 -4
- 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/index.ts +1 -0
- package/src/components/section-header/section-header.svelte +1 -1
- package/src/components/section-header/section-header.test.ts +7 -1
- package/src/components/section-header/section-label.svelte +39 -0
- 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/index.ts +2 -0
- package/src/components/{list-page → toolbar}/toolbar-search.svelte +25 -25
- package/src/components/toolbar/toolbar.svelte +13 -9
- 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 +27 -0
- package/src/inbox/inbox.test.ts +214 -0
- package/src/inbox/index.ts +31 -0
- package/src/inbox/logic.ts +169 -0
- package/src/inbox/thread-inbox.svelte +439 -0
- package/src/inbox/types.ts +161 -0
- 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 +105 -0
- 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/README.md +5 -0
- package/src/panel/index.ts +2 -0
- package/src/panel/side-panel.svelte +194 -0
- package/src/record/README.md +3 -0
- package/src/record/demo-data.ts +352 -0
- package/src/record/field-section.svelte +57 -0
- package/src/record/fields.ts +194 -0
- package/src/record/format.ts +94 -0
- package/src/{tasks/record → record}/index.ts +49 -35
- package/src/{tasks/record → record}/info-list.svelte +145 -114
- package/src/record/kinds.ts +42 -0
- package/src/record/logic.ts +326 -0
- package/src/record/next-up.svelte +81 -0
- package/src/{tasks/record → record}/project-list.svelte +56 -50
- package/src/{tasks/record → record}/record-demo.svelte +130 -110
- package/src/{tasks/record → record}/record-header.svelte +37 -28
- package/src/{tasks/record → record}/record-shell.svelte +54 -54
- package/src/record/record-stats.svelte +48 -0
- package/src/record/record-timeline.svelte +251 -0
- package/src/record/record.test.ts +471 -0
- package/src/{tasks/record → record}/steps-rail.svelte +225 -216
- package/src/record/types.ts +299 -0
- package/src/record/where-we-are.svelte +125 -0
- 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/README.md +18 -0
- package/src/status/define-statuses.ts +25 -0
- package/src/status/index.ts +11 -0
- package/src/{tasks → status}/status-badge.svelte +3 -1
- package/src/{tasks → status}/status-dot.svelte +1 -3
- package/src/{tasks → status}/status-icon.svelte +1 -1
- package/src/status/status-types.ts +40 -0
- package/src/tasks/README.md +1 -1
- package/src/tasks/board-order.ts +2 -4
- package/src/tasks/demo.svelte +192 -34
- package/src/tasks/detail/detail-demo.svelte +75 -15
- 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 +4 -2
- package/src/tasks/gantt/logic.test.ts +110 -20
- package/src/tasks/gantt/logic.ts +42 -6
- package/src/tasks/inbox/inbox-card.svelte +15 -7
- 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 +24 -24
- 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 +52 -53
- package/src/tasks/kanban-board.svelte +16 -12
- package/src/tasks/kanban-types.ts +2 -3
- package/src/tasks/presets/sales.ts +7 -7
- 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/README.md +31 -0
- package/src/thread/index.ts +12 -0
- package/src/thread/logic.ts +61 -0
- package/src/thread/thread.svelte +432 -0
- package/src/thread/thread.test.ts +73 -0
- package/src/thread/types.ts +93 -0
- 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/components/list-page/section-label.svelte +0 -39
- package/src/tasks/define-statuses.ts +0 -32
- package/src/tasks/inbox/inbox.svelte +0 -203
- package/src/tasks/record/demo-data.ts +0 -286
- package/src/tasks/record/field-section.svelte +0 -53
- package/src/tasks/record/fields.ts +0 -142
- package/src/tasks/record/format.ts +0 -87
- package/src/tasks/record/kinds.ts +0 -42
- package/src/tasks/record/logic.ts +0 -324
- package/src/tasks/record/next-up.svelte +0 -70
- package/src/tasks/record/record-stats.svelte +0 -48
- package/src/tasks/record/record-timeline.svelte +0 -231
- package/src/tasks/record/record.test.ts +0 -350
- package/src/tasks/record/types.ts +0 -280
- package/src/tasks/record/where-we-are.svelte +0 -121
- package/src/tasks/side-panel.svelte +0 -193
- package/src/tasks/status-types.ts +0 -40
- /package/src/components/{list-page → toolbar}/toolbar-trigger.ts +0 -0
- /package/src/{tasks → panel}/side-panel-types.ts +0 -0
- /package/src/{tasks/record → record}/gutter.ts +0 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// =========================
|
|
3
|
+
// IMPORTS
|
|
4
|
+
// =========================
|
|
5
|
+
import type { Snippet } from "svelte";
|
|
6
|
+
import { cn } from "../../lib/utils.js";
|
|
7
|
+
import type { PhosphorIcon } from "../../lib/icon-types.js";
|
|
8
|
+
|
|
9
|
+
// =========================
|
|
10
|
+
// TYPES
|
|
11
|
+
// =========================
|
|
12
|
+
interface Props {
|
|
13
|
+
// The one label style for panel, rail group and sub-section headers. Retyped
|
|
14
|
+
// copies drifted to a different tracking value.
|
|
15
|
+
icon?: PhosphorIcon;
|
|
16
|
+
children: Snippet;
|
|
17
|
+
class?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// =========================
|
|
21
|
+
// PROPS
|
|
22
|
+
// =========================
|
|
23
|
+
let { icon: Icon, children, class: className }: Props = $props();
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<!-- =========================== -->
|
|
27
|
+
<!-- MARKUP -->
|
|
28
|
+
<!-- =========================== -->
|
|
29
|
+
<span
|
|
30
|
+
class={cn(
|
|
31
|
+
"flex items-center gap-2 text-xs font-semibold uppercase tracking-[0.14em] text-muted-foreground",
|
|
32
|
+
className,
|
|
33
|
+
)}
|
|
34
|
+
>
|
|
35
|
+
{#if Icon}
|
|
36
|
+
<Icon class="size-4 text-brand" aria-hidden="true" />
|
|
37
|
+
{/if}
|
|
38
|
+
{@render children()}
|
|
39
|
+
</span>
|
|
@@ -2,7 +2,17 @@
|
|
|
2
2
|
import type { SegmentContent } from "./segment.svelte";
|
|
3
3
|
|
|
4
4
|
/** One tab. See `SegmentContent` for everything a segment can say. */
|
|
5
|
-
export type TabDef<T extends string = string> = SegmentContent & {
|
|
5
|
+
export type TabDef<T extends string = string> = SegmentContent & {
|
|
6
|
+
value: T;
|
|
7
|
+
/**
|
|
8
|
+
* The tab's group. A divider marks each change of group, and a group's `label` opens it as a
|
|
9
|
+
* quiet heading.
|
|
10
|
+
*/
|
|
11
|
+
group?: TabGroup;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/** `id` is what makes two tabs one group. `label` is optional text for a heading before the group. */
|
|
15
|
+
export type TabGroup = { id: string; label?: string };
|
|
6
16
|
|
|
7
17
|
/**
|
|
8
18
|
* Where the tab row sits, the only thing that legitimately differs between
|
|
@@ -132,7 +142,26 @@
|
|
|
132
142
|
<!-- WHY an inner `display: contents` tablist: a tablist owns only tabs, and the
|
|
133
143
|
overflow trigger isn't one. `contents` keeps segments as direct flex children. -->
|
|
134
144
|
<div class="contents" data-slot="tabs-list" role="tablist" aria-label={ariaLabel}>
|
|
135
|
-
{#each split.visible as tab (tab.value)}
|
|
145
|
+
{#each split.visible as tab, index (tab.value)}
|
|
146
|
+
{@const previous = split.visible[index - 1]}
|
|
147
|
+
{#if (tab.group?.id ?? "") !== (previous?.group?.id ?? "") || (index === 0 && tab.group?.label)}
|
|
148
|
+
<!-- WHY presentational: a tablist owns only tabs, and a divider or a heading isn't one. -->
|
|
149
|
+
{#if index > 0}
|
|
150
|
+
<span
|
|
151
|
+
role="presentation"
|
|
152
|
+
aria-hidden="true"
|
|
153
|
+
class="mx-1 my-2 w-px shrink-0 self-stretch bg-border"
|
|
154
|
+
></span>
|
|
155
|
+
{/if}
|
|
156
|
+
{#if tab.group?.label}
|
|
157
|
+
<span
|
|
158
|
+
role="presentation"
|
|
159
|
+
aria-hidden="true"
|
|
160
|
+
class="flex shrink-0 items-center px-2 text-xs font-medium tracking-wide text-muted-foreground uppercase"
|
|
161
|
+
>{tab.group.label}</span
|
|
162
|
+
>
|
|
163
|
+
{/if}
|
|
164
|
+
{/if}
|
|
136
165
|
<TabsPrimitive.Trigger
|
|
137
166
|
value={tab.value}
|
|
138
167
|
class={cn(segmentedTriggerClass({ variant, fill: block }), segmentedActiveClass)}
|
|
@@ -50,7 +50,8 @@ describe("splitSegments", () => {
|
|
|
50
50
|
|
|
51
51
|
describe("resolveSegmentMax", () => {
|
|
52
52
|
it("turns none into a limit nothing reaches, so no tail folds", async () => {
|
|
53
|
-
const { resolveSegmentMax, splitSegments } =
|
|
53
|
+
const { resolveSegmentMax, splitSegments } =
|
|
54
|
+
await import("./collapse.js");
|
|
54
55
|
const items = Array.from({ length: 20 }, (_, i) => i);
|
|
55
56
|
expect(resolveSegmentMax("none")).toBe(Number.POSITIVE_INFINITY);
|
|
56
57
|
expect(resolveSegmentMax(4)).toBe(4);
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
// decides, `segmented-overflow.svelte` draws it. No call site opts in.
|
|
11
11
|
import AnimatedTabs, {
|
|
12
12
|
type TabDef,
|
|
13
|
+
type TabGroup,
|
|
13
14
|
type TabsPlacement,
|
|
14
15
|
} from "./animated-tabs.svelte";
|
|
15
16
|
import SegmentedFilter from "./segmented-filter.svelte";
|
|
@@ -37,5 +38,6 @@ export {
|
|
|
37
38
|
type SegmentedNavItem,
|
|
38
39
|
type SegmentedVariant,
|
|
39
40
|
type TabDef,
|
|
41
|
+
type TabGroup,
|
|
40
42
|
type TabsPlacement,
|
|
41
43
|
};
|
|
@@ -1,59 +1,59 @@
|
|
|
1
1
|
<script lang="ts" module>
|
|
2
|
-
import type { PhosphorIcon } from "../../lib/icon-types.js";
|
|
2
|
+
import type { PhosphorIcon } from "../../lib/icon-types.js";
|
|
3
3
|
|
|
4
|
-
/** Everything a segment may say, for every strip drawn from this folder. */
|
|
4
|
+
/** Everything a segment may say, for every strip drawn from this folder. */
|
|
5
5
|
|
|
6
|
-
// WHY one shape: the tab, nav and filter strips each grew an item type and
|
|
7
|
-
// drifted, down to drawing a logo fallback two ways. Now it's one type, one renderer.
|
|
8
|
-
export type SegmentContent = {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
};
|
|
6
|
+
// WHY one shape: the tab, nav and filter strips each grew an item type and
|
|
7
|
+
// drifted, down to drawing a logo fallback two ways. Now it's one type, one renderer.
|
|
8
|
+
export type SegmentContent = {
|
|
9
|
+
label: string;
|
|
10
|
+
/** A number after the label. `0` still renders; omit for "no number". */
|
|
11
|
+
count?: number;
|
|
12
|
+
icon?: PhosphorIcon;
|
|
13
|
+
/** A square mark drawn in place of an icon, for a brand that has one. */
|
|
14
|
+
image?: string;
|
|
15
|
+
/**
|
|
16
|
+
* This segment's work is finished. Drawn as a dot after the label, and only
|
|
17
|
+
* where there is no count, because a count already says the same thing.
|
|
18
|
+
*/
|
|
19
|
+
done?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* A short note after the label, such as "Save 20%". Text where `count` is a
|
|
22
|
+
* number, in the accent colour. Drawn before a count or a done dot.
|
|
23
|
+
*/
|
|
24
|
+
badge?: string;
|
|
25
|
+
};
|
|
26
26
|
</script>
|
|
27
27
|
|
|
28
28
|
<script lang="ts">
|
|
29
|
-
// =========================
|
|
30
|
-
// IMPORTS
|
|
31
|
-
// =========================
|
|
32
|
-
import { cn } from "../../lib/utils.js";
|
|
33
|
-
import { Icon } from "../icon/index.js";
|
|
34
|
-
import { Mark } from "../mark/index.js";
|
|
29
|
+
// =========================
|
|
30
|
+
// IMPORTS
|
|
31
|
+
// =========================
|
|
32
|
+
import { cn } from "../../lib/utils.js";
|
|
33
|
+
import { Icon } from "../icon/index.js";
|
|
34
|
+
import { Mark } from "../mark/index.js";
|
|
35
35
|
|
|
36
|
-
// =========================
|
|
37
|
-
// TYPES
|
|
38
|
-
// =========================
|
|
39
|
-
interface Props extends SegmentContent {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
36
|
+
// =========================
|
|
37
|
+
// TYPES
|
|
38
|
+
// =========================
|
|
39
|
+
interface Props extends SegmentContent {
|
|
40
|
+
/**
|
|
41
|
+
* `true`/`false` when the caller owns the state. `"auto"` when bits-ui owns it
|
|
42
|
+
* in a `data-state` on the trigger, and the stylesheet answers.
|
|
43
|
+
*/
|
|
44
|
+
active?: boolean | "auto";
|
|
45
|
+
}
|
|
46
46
|
|
|
47
|
-
// =========================
|
|
48
|
-
// PROPS
|
|
49
|
-
// =========================
|
|
50
|
-
let { label, count, icon, image, done = false, badge, active = false }: Props = $props();
|
|
47
|
+
// =========================
|
|
48
|
+
// PROPS
|
|
49
|
+
// =========================
|
|
50
|
+
let { label, count, icon, image, done = false, badge, active = false }: Props = $props();
|
|
51
51
|
|
|
52
|
-
// =========================
|
|
53
|
-
// DERIVED
|
|
54
|
-
// =========================
|
|
55
|
-
let auto = $derived(active === "auto");
|
|
56
|
-
let on = $derived(active === true);
|
|
52
|
+
// =========================
|
|
53
|
+
// DERIVED
|
|
54
|
+
// =========================
|
|
55
|
+
let auto = $derived(active === "auto");
|
|
56
|
+
let on = $derived(active === true);
|
|
57
57
|
</script>
|
|
58
58
|
|
|
59
59
|
<!-- =========================== -->
|
|
@@ -76,7 +76,9 @@ let on = $derived(active === true);
|
|
|
76
76
|
{label}
|
|
77
77
|
|
|
78
78
|
{#if badge}
|
|
79
|
-
<span class="text-primary text-[10px] font-semibold tracking-wide whitespace-nowrap uppercase"
|
|
79
|
+
<span class="text-primary text-[10px] font-semibold tracking-wide whitespace-nowrap uppercase"
|
|
80
|
+
>{badge}</span
|
|
81
|
+
>
|
|
80
82
|
{/if}
|
|
81
83
|
|
|
82
84
|
{#if count != null}
|
|
@@ -89,7 +91,8 @@ let on = $derived(active === true);
|
|
|
89
91
|
"inline-flex h-4 min-w-4 items-center justify-center rounded-full px-1 text-[10px] leading-none font-medium tabular-nums",
|
|
90
92
|
"bg-muted-foreground/15 text-muted-foreground",
|
|
91
93
|
on && "bg-brand/14 text-brand",
|
|
92
|
-
auto &&
|
|
94
|
+
auto &&
|
|
95
|
+
"group-data-[state=active]/segment:bg-brand/14 group-data-[state=active]/segment:text-brand",
|
|
93
96
|
)}
|
|
94
97
|
>
|
|
95
98
|
{count}
|
|
@@ -1,85 +1,89 @@
|
|
|
1
1
|
<script lang="ts" generics="T extends string">
|
|
2
|
-
// =========================
|
|
3
|
-
// IMPORTS
|
|
4
|
-
// =========================
|
|
5
|
-
import { cn } from "../../lib/utils.js";
|
|
6
|
-
import Segment from "./segment.svelte";
|
|
7
|
-
import type { TabDef } from "./animated-tabs.svelte";
|
|
8
|
-
import SegmentedTrack from "./segmented-track.svelte";
|
|
9
|
-
import SegmentedOverflow from "./segmented-overflow.svelte";
|
|
10
|
-
import {
|
|
11
|
-
|
|
2
|
+
// =========================
|
|
3
|
+
// IMPORTS
|
|
4
|
+
// =========================
|
|
5
|
+
import { cn } from "../../lib/utils.js";
|
|
6
|
+
import Segment from "./segment.svelte";
|
|
7
|
+
import type { TabDef } from "./animated-tabs.svelte";
|
|
8
|
+
import SegmentedTrack from "./segmented-track.svelte";
|
|
9
|
+
import SegmentedOverflow from "./segmented-overflow.svelte";
|
|
10
|
+
import {
|
|
11
|
+
SEGMENT_MAX,
|
|
12
|
+
resolveSegmentMax,
|
|
13
|
+
splitSegments,
|
|
14
|
+
} from "./collapse.js";
|
|
15
|
+
import { segmentedTriggerClass } from "./segmented.js";
|
|
12
16
|
|
|
13
|
-
// =========================
|
|
14
|
-
// TYPES
|
|
15
|
-
// =========================
|
|
16
|
-
interface Props {
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
// =========================
|
|
18
|
+
// TYPES
|
|
19
|
+
// =========================
|
|
20
|
+
interface Props {
|
|
21
|
+
// WHY: picks which rows are listed, with no panel of its own. That's a
|
|
22
|
+
// segmented control, not a tab set.
|
|
19
23
|
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
// WHY not `AnimatedTabs`: a strip with no tabpanel is a `group` of `aria-pressed`
|
|
25
|
+
// toggles. "Tab 1 of 4" sends a screen reader looking for a panel.
|
|
22
26
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
27
|
+
// WHY `TabDef`: an option and a tab were the same five fields written twice.
|
|
28
|
+
options: readonly TabDef<T>[];
|
|
29
|
+
value: T;
|
|
30
|
+
// For a caller whose value is derived and can't be bound. Same contract as
|
|
31
|
+
// `AnimatedTabs`, so the two are picked by ARIA alone.
|
|
32
|
+
onValueChange?: (value: T) => void;
|
|
33
|
+
// Count per option, keyed by value. Merged over `options[].count`, because
|
|
34
|
+
// the counts usually arrive from the data while the labels are a constant.
|
|
35
|
+
counts?: Partial<Record<T, number>>;
|
|
36
|
+
ariaLabel: string;
|
|
37
|
+
/** Fill the row rather than sit at its natural width. On for the list-page
|
|
38
|
+
* filter strip, off for a segment sitting in a toolbar beside others. */
|
|
39
|
+
block?: boolean;
|
|
40
|
+
/** Objects the row may draw before its tail becomes a menu. See `collapse.ts`.
|
|
41
|
+
* `"none"` never folds. */
|
|
42
|
+
max?: number | "none";
|
|
43
|
+
/** What the menu segment says. Translate at the call site. */
|
|
44
|
+
moreLabel?: string;
|
|
45
|
+
class?: string;
|
|
46
|
+
}
|
|
43
47
|
|
|
44
|
-
// =========================
|
|
45
|
-
// PROPS
|
|
46
|
-
// =========================
|
|
47
|
-
let {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}: Props = $props();
|
|
48
|
+
// =========================
|
|
49
|
+
// PROPS
|
|
50
|
+
// =========================
|
|
51
|
+
let {
|
|
52
|
+
options,
|
|
53
|
+
value = $bindable(),
|
|
54
|
+
onValueChange,
|
|
55
|
+
counts,
|
|
56
|
+
ariaLabel,
|
|
57
|
+
block = true,
|
|
58
|
+
max = SEGMENT_MAX,
|
|
59
|
+
moreLabel,
|
|
60
|
+
class: className,
|
|
61
|
+
}: Props = $props();
|
|
58
62
|
|
|
59
|
-
// =========================
|
|
60
|
-
// DERIVED
|
|
61
|
-
// =========================
|
|
63
|
+
// =========================
|
|
64
|
+
// DERIVED
|
|
65
|
+
// =========================
|
|
62
66
|
|
|
63
|
-
// Counts arrive from data, labels are constant. Merged once here, so the pill
|
|
64
|
-
// and the overflow hint say the same thing.
|
|
65
|
-
let merged = $derived(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
);
|
|
71
|
-
let split = $derived(
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
);
|
|
67
|
+
// Counts arrive from data, labels are constant. Merged once here, so the pill
|
|
68
|
+
// and the overflow hint say the same thing.
|
|
69
|
+
let merged = $derived(
|
|
70
|
+
options.map((option) => {
|
|
71
|
+
const count = counts?.[option.value] ?? option.count;
|
|
72
|
+
return count === option.count ? option : { ...option, count };
|
|
73
|
+
}),
|
|
74
|
+
);
|
|
75
|
+
let split = $derived(
|
|
76
|
+
splitSegments(
|
|
77
|
+
merged,
|
|
78
|
+
resolveSegmentMax(max),
|
|
79
|
+
merged.findIndex((option) => option.value === value),
|
|
80
|
+
),
|
|
81
|
+
);
|
|
78
82
|
|
|
79
|
-
function pick(next: T) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
+
function pick(next: T) {
|
|
84
|
+
value = next;
|
|
85
|
+
onValueChange?.(next);
|
|
86
|
+
}
|
|
83
87
|
</script>
|
|
84
88
|
|
|
85
89
|
<!-- =========================== -->
|
|
@@ -114,12 +118,7 @@ function pick(next: T) {
|
|
|
114
118
|
{/each}
|
|
115
119
|
{#snippet after()}
|
|
116
120
|
{#if split.hidden.length > 0}
|
|
117
|
-
<SegmentedOverflow
|
|
118
|
-
items={split.hidden}
|
|
119
|
-
onSelect={pick}
|
|
120
|
-
label={moreLabel}
|
|
121
|
-
fill={block}
|
|
122
|
-
/>
|
|
121
|
+
<SegmentedOverflow items={split.hidden} onSelect={pick} label={moreLabel} fill={block} />
|
|
123
122
|
{/if}
|
|
124
123
|
{/snippet}
|
|
125
124
|
</SegmentedTrack>
|
|
@@ -1,52 +1,46 @@
|
|
|
1
1
|
<script lang="ts" module>
|
|
2
|
-
import type { SegmentContent } from "./segment.svelte";
|
|
2
|
+
import type { SegmentContent } from "./segment.svelte";
|
|
3
3
|
|
|
4
|
-
/** One destination. See `SegmentContent` for everything a segment can say. */
|
|
5
|
-
export type SegmentedNavItem = SegmentContent & { href: string };
|
|
4
|
+
/** One destination. See `SegmentContent` for everything a segment can say. */
|
|
5
|
+
export type SegmentedNavItem = SegmentContent & { href: string };
|
|
6
6
|
</script>
|
|
7
7
|
|
|
8
8
|
<script lang="ts">
|
|
9
|
-
// =========================
|
|
10
|
-
// IMPORTS
|
|
11
|
-
// =========================
|
|
12
|
-
import { cn } from "../../lib/utils.js";
|
|
13
|
-
import Segment from "./segment.svelte";
|
|
14
|
-
import SegmentedTrack from "./segmented-track.svelte";
|
|
15
|
-
import { segmentedTriggerClass } from "./segmented.js";
|
|
9
|
+
// =========================
|
|
10
|
+
// IMPORTS
|
|
11
|
+
// =========================
|
|
12
|
+
import { cn } from "../../lib/utils.js";
|
|
13
|
+
import Segment from "./segment.svelte";
|
|
14
|
+
import SegmentedTrack from "./segmented-track.svelte";
|
|
15
|
+
import { segmentedTriggerClass } from "./segmented.js";
|
|
16
16
|
|
|
17
|
-
// =========================
|
|
18
|
-
// TYPES
|
|
19
|
-
// =========================
|
|
20
|
-
interface Props {
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
// =========================
|
|
18
|
+
// TYPES
|
|
19
|
+
// =========================
|
|
20
|
+
interface Props {
|
|
21
|
+
// WHY a third component: tabs are a `tablist`, filters a `group` of toggles,
|
|
22
|
+
// and this goes to sibling pages, so it's a `nav` of links with `aria-current`.
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
24
|
+
// Links give middle-click, bookmarks and the back button. The look comes from
|
|
25
|
+
// `segmented.ts` and `segment.svelte`, so nothing here can drift.
|
|
26
|
+
items: readonly SegmentedNavItem[];
|
|
27
|
+
/** The href that is current. Compared after trailing slashes are dropped. */
|
|
28
|
+
current: string;
|
|
29
|
+
ariaLabel: string;
|
|
30
|
+
/** Fill the row rather than sit at its natural width. */
|
|
31
|
+
block?: boolean;
|
|
32
|
+
class?: string;
|
|
33
|
+
}
|
|
34
34
|
|
|
35
|
-
// =========================
|
|
36
|
-
// PROPS
|
|
37
|
-
// =========================
|
|
38
|
-
let {
|
|
39
|
-
items,
|
|
40
|
-
current,
|
|
41
|
-
ariaLabel,
|
|
42
|
-
block = false,
|
|
43
|
-
class: className,
|
|
44
|
-
}: Props = $props();
|
|
35
|
+
// =========================
|
|
36
|
+
// PROPS
|
|
37
|
+
// =========================
|
|
38
|
+
let { items, current, ariaLabel, block = false, class: className }: Props = $props();
|
|
45
39
|
|
|
46
|
-
// =========================
|
|
47
|
-
// DERIVED
|
|
48
|
-
// =========================
|
|
49
|
-
let currentHref = $derived(current.replace(/\/$/, ""));
|
|
40
|
+
// =========================
|
|
41
|
+
// DERIVED
|
|
42
|
+
// =========================
|
|
43
|
+
let currentHref = $derived(current.replace(/\/$/, ""));
|
|
50
44
|
</script>
|
|
51
45
|
|
|
52
46
|
<!-- =========================== -->
|
|
@@ -1,60 +1,63 @@
|
|
|
1
1
|
<script lang="ts" generics="V extends string">
|
|
2
|
-
// =========================
|
|
3
|
-
// IMPORTS
|
|
4
|
-
// =========================
|
|
5
|
-
import { CaretDown } from "phosphor-svelte";
|
|
6
|
-
import { Picker } from "../picker/index.js";
|
|
7
|
-
import { cn } from "../../lib/utils.js";
|
|
8
|
-
import {
|
|
9
|
-
|
|
2
|
+
// =========================
|
|
3
|
+
// IMPORTS
|
|
4
|
+
// =========================
|
|
5
|
+
import { CaretDown } from "phosphor-svelte";
|
|
6
|
+
import { Picker } from "../picker/index.js";
|
|
7
|
+
import { cn } from "../../lib/utils.js";
|
|
8
|
+
import {
|
|
9
|
+
segmentedTriggerClass,
|
|
10
|
+
type SegmentedVariant,
|
|
11
|
+
} from "./segmented.js";
|
|
12
|
+
import type { SegmentContent } from "./segment.svelte";
|
|
10
13
|
|
|
11
|
-
// =========================
|
|
12
|
-
// TYPES
|
|
13
|
-
// =========================
|
|
14
|
-
/** The last object in an overfull strip: one segment that opens the rest. */
|
|
14
|
+
// =========================
|
|
15
|
+
// TYPES
|
|
16
|
+
// =========================
|
|
17
|
+
/** The last object in an overfull strip: one segment that opens the rest. */
|
|
15
18
|
|
|
16
|
-
// WHY the `Picker`: every "pick one of these" goes there, so keys and
|
|
17
|
-
// filtering match the command palette.
|
|
19
|
+
// WHY the `Picker`: every "pick one of these" goes there, so keys and
|
|
20
|
+
// filtering match the command palette.
|
|
18
21
|
|
|
19
|
-
// WHY never active: `splitSegments` pulls the current item into a visible slot,
|
|
20
|
-
// so this never fights bits-ui's `data-state="open"`.
|
|
21
|
-
interface Props {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
22
|
+
// WHY never active: `splitSegments` pulls the current item into a visible slot,
|
|
23
|
+
// so this never fights bits-ui's `data-state="open"`.
|
|
24
|
+
interface Props {
|
|
25
|
+
items: readonly (SegmentContent & { value: V })[];
|
|
26
|
+
onSelect: (value: V) => void;
|
|
27
|
+
/** What the segment says. Translate at the call site. */
|
|
28
|
+
label?: string;
|
|
29
|
+
fill?: boolean;
|
|
30
|
+
variant?: SegmentedVariant;
|
|
31
|
+
searchPlaceholder?: string;
|
|
32
|
+
emptyLabel?: string;
|
|
33
|
+
}
|
|
31
34
|
|
|
32
|
-
// =========================
|
|
33
|
-
// PROPS
|
|
34
|
-
// =========================
|
|
35
|
-
let {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}: Props = $props();
|
|
35
|
+
// =========================
|
|
36
|
+
// PROPS
|
|
37
|
+
// =========================
|
|
38
|
+
let {
|
|
39
|
+
items,
|
|
40
|
+
onSelect,
|
|
41
|
+
label = "More",
|
|
42
|
+
fill = false,
|
|
43
|
+
variant = "pill",
|
|
44
|
+
searchPlaceholder,
|
|
45
|
+
emptyLabel,
|
|
46
|
+
}: Props = $props();
|
|
44
47
|
|
|
45
|
-
// =========================
|
|
46
|
-
// DERIVED
|
|
47
|
-
// =========================
|
|
48
|
+
// =========================
|
|
49
|
+
// DERIVED
|
|
50
|
+
// =========================
|
|
48
51
|
|
|
49
|
-
// A count rides as the row's hint, where the strip draws it as a pill.
|
|
50
|
-
let options = $derived(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
);
|
|
52
|
+
// A count rides as the row's hint, where the strip draws it as a pill.
|
|
53
|
+
let options = $derived(
|
|
54
|
+
items.map((item) => ({
|
|
55
|
+
value: item.value,
|
|
56
|
+
label: item.label,
|
|
57
|
+
hint: item.count != null ? String(item.count) : undefined,
|
|
58
|
+
icon: item.icon,
|
|
59
|
+
})),
|
|
60
|
+
);
|
|
58
61
|
</script>
|
|
59
62
|
|
|
60
63
|
<!-- =========================== -->
|