@heyramzi/ui 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (312) hide show
  1. package/package.json +7 -8
  2. package/scripts/sync-shadcn.mjs +3 -4
  3. package/src/agents/agent-avatar.svelte +604 -575
  4. package/src/agents/agent-cast.svelte +179 -183
  5. package/src/agents/demo.svelte +125 -113
  6. package/src/agents/helpers.ts +23 -17
  7. package/src/agents/index.ts +4 -4
  8. package/src/agents/page-state.ts +6 -6
  9. package/src/agents/types.ts +6 -6
  10. package/src/ai/ai-button/AiButton.svelte +4 -12
  11. package/src/ai/generation-progress/GenerationProgress.svelte +10 -3
  12. package/src/ai/prompt-input/PromptInputTextarea.svelte +1 -2
  13. package/src/ai/response/Response.css +2 -3
  14. package/src/animations/avatar-group-hover.css +2 -16
  15. package/src/animations/card-tilt.css +3 -6
  16. package/src/animations/digit-pop-in.css +2 -15
  17. package/src/animations/dropdown.css +4 -17
  18. package/src/animations/input-clear-dissolve.css +5 -18
  19. package/src/animations/input-shake.css +9 -41
  20. package/src/animations/learn-more-hover.css +17 -9
  21. package/src/animations/like-button.css +2 -4
  22. package/src/animations/page-side-by-side.css +2 -10
  23. package/src/animations/plus-menu-morph.css +2 -5
  24. package/src/animations/shimmer-text.css +5 -18
  25. package/src/animations/spinning-counter.css +2 -6
  26. package/src/animations/success-check.css +2 -15
  27. package/src/animations/thinking-states.css +2 -7
  28. package/src/brand/brand.test.ts +21 -5
  29. package/src/brand/index.ts +4 -10
  30. package/src/canvas/card/card-frame.svelte +43 -39
  31. package/src/canvas/card/resize-handles.svelte +89 -80
  32. package/src/canvas/chrome/chrome-zoom.svelte +101 -100
  33. package/src/canvas/chrome/lock-badge.svelte +8 -8
  34. package/src/canvas/chrome/side-handles.svelte +24 -19
  35. package/src/canvas/chrome/size-chip.svelte +30 -30
  36. package/src/canvas/chrome/snap-guides.svelte +20 -20
  37. package/src/canvas/components/icons/ToolIcon.svelte +8 -8
  38. package/src/canvas/components/shared/svg/PreviewSvg.svelte +19 -19
  39. package/src/canvas/components/shared/svg/ShapeOutline.svelte +71 -48
  40. package/src/canvas/components/shared/svg/StrokeNode.svelte +21 -27
  41. package/src/canvas/components/shared/svg/TextRule.svelte +25 -17
  42. package/src/canvas/edges/bend-handle.svelte +46 -46
  43. package/src/canvas/edges/edge-gradient.svelte +10 -10
  44. package/src/canvas/edges/gradient-edge.svelte +45 -29
  45. package/src/canvas/editor/lock.test.ts +6 -1
  46. package/src/canvas/editor/style.ts +7 -1
  47. package/src/canvas/headless/svg-figure.test.ts +5 -1
  48. package/src/canvas/lod/lod-switch.svelte +111 -106
  49. package/src/canvas/pen/PenField.svelte +87 -81
  50. package/src/canvas/pen/PenOverlay.svelte +127 -127
  51. package/src/canvas/pen/ShapeNode.svelte +31 -32
  52. package/src/canvas/pen/TextNode.svelte +54 -53
  53. package/src/canvas/pen/host-field.test.svelte +7 -7
  54. package/src/canvas/pen/shapes.ts +7 -1
  55. package/src/canvas/screen/GlyphIcon.svelte +22 -17
  56. package/src/canvas/screen/PartView.svelte +138 -72
  57. package/src/canvas/screen/ScreenBody.svelte +81 -60
  58. package/src/canvas/screen/ScreenCard.svelte +20 -20
  59. package/src/canvas/ui/CanvasToolbar.svelte +173 -85
  60. package/src/canvas/ui/FrameLayer.svelte +59 -59
  61. package/src/canvas/ui/PenLayer.svelte +39 -30
  62. package/src/canvas/ui/StylePanel.svelte +119 -102
  63. package/src/canvas/ui/canvas-grid.svelte +137 -129
  64. package/src/charts/animated-value.svelte +1 -1
  65. package/src/charts/area-chart.css +2 -5
  66. package/src/charts/area-chart.svelte +1 -1
  67. package/src/charts/bar-chart.css +2 -3
  68. package/src/charts/bar-chart.svelte +1 -1
  69. package/src/charts/charts.css +5 -17
  70. package/src/charts/crosshair.svelte +1 -1
  71. package/src/charts/dashboard/dashboard-empty.svelte +25 -25
  72. package/src/charts/dashboard/dashboard-section.svelte +24 -24
  73. package/src/charts/dashboard/data-panel.svelte +50 -50
  74. package/src/charts/dashboard/data-sort-head.svelte +56 -56
  75. package/src/charts/dashboard/data-table.svelte +138 -138
  76. package/src/charts/dashboard/panel-grid.svelte +30 -30
  77. package/src/charts/dashboard/period-filter.svelte +25 -25
  78. package/src/charts/dashboard/rating-chip.svelte +19 -19
  79. package/src/charts/dashboard/stat-band.svelte +32 -32
  80. package/src/charts/dashboard/stat-tile.svelte +106 -99
  81. package/src/charts/donut-chart.svelte +1 -1
  82. package/src/charts/edge-fade.svelte +1 -1
  83. package/src/charts/figure.svelte +1 -1
  84. package/src/charts/gauge-chart.css +4 -7
  85. package/src/charts/gauge-chart.svelte +1 -1
  86. package/src/charts/hover-focus.svelte +1 -1
  87. package/src/charts/legend.css +1 -3
  88. package/src/charts/legend.svelte +1 -1
  89. package/src/charts/line-chart.svelte +1 -1
  90. package/src/charts/number-chart.css +6 -9
  91. package/src/charts/number-chart.svelte +1 -1
  92. package/src/charts/reference-line.svelte +19 -19
  93. package/src/charts/reveal.svelte +1 -1
  94. package/src/charts/ring-chart.css +2 -4
  95. package/src/charts/ring-chart.svelte +1 -1
  96. package/src/charts/sparkline.svelte +5 -2
  97. package/src/charts/stat-card.svelte +1 -1
  98. package/src/charts/time-series-chart.css +2 -5
  99. package/src/charts/trend-pill.css +2 -4
  100. package/src/charts/trend-pill.svelte +1 -1
  101. package/src/components/copy/copy-button.svelte +93 -98
  102. package/src/components/copy/copy-button.test.ts +10 -2
  103. package/src/components/data-table/infinite-scroll.svelte +42 -42
  104. package/src/components/data-table/pagination.svelte +34 -22
  105. package/src/components/data-table/row-arrow.svelte +19 -16
  106. package/src/components/data-table/row-context-menu.svelte +52 -52
  107. package/src/components/data-table/sortable-head.svelte +25 -25
  108. package/src/components/data-table/table-skeleton.svelte +41 -36
  109. package/src/components/docs/doc-page.svelte +18 -9
  110. package/src/components/docs/on-this-page.svelte +16 -3
  111. package/src/components/docs/scroll-spy.svelte.ts +2 -3
  112. package/src/components/field-row/field-row.svelte +10 -8
  113. package/src/components/hold-to-confirm/hold-to-confirm.svelte +5 -1
  114. package/src/components/icon/icon.svelte +14 -14
  115. package/src/components/language-flag/language-flag.svelte +3 -1
  116. package/src/components/list-page/detail-empty.svelte +34 -34
  117. package/src/components/list-page/detail-header.svelte +55 -47
  118. package/src/components/list-page/detail-panel.svelte +51 -59
  119. package/src/components/list-page/detail-row.svelte +19 -19
  120. package/src/components/list-page/detail-stack.svelte +21 -21
  121. package/src/components/list-page/empty-state.svelte +30 -30
  122. package/src/components/list-page/filter-item.svelte +20 -20
  123. package/src/components/list-page/filter-popover.svelte +60 -55
  124. package/src/components/list-page/grouped-list.svelte +4 -2
  125. package/src/components/list-page/index.ts +5 -1
  126. package/src/components/list-page/list-page-shell.svelte +2 -5
  127. package/src/components/list-page/master-detail.svelte +87 -88
  128. package/src/components/list-page/notice-bar.svelte +38 -38
  129. package/src/components/list-page/prev-next.svelte +63 -63
  130. package/src/components/list-page/rail-body.svelte +18 -18
  131. package/src/components/list-page/rail-empty.svelte +23 -30
  132. package/src/components/list-page/rail-head.svelte +24 -24
  133. package/src/components/list-page/rail-row.svelte +77 -73
  134. package/src/components/list-page/section-host.ts +2 -3
  135. package/src/components/locale-switch/locale-switch.svelte +41 -41
  136. package/src/components/mark/mark.svelte +34 -38
  137. package/src/components/open-in/index.ts +1 -5
  138. package/src/components/open-in/open-in.svelte +40 -40
  139. package/src/components/picker/mark-picker.svelte +49 -49
  140. package/src/components/picker/picker.svelte +5 -3
  141. package/src/components/section-header/section-header.test.ts +7 -1
  142. package/src/components/section-header/section-label.svelte +20 -20
  143. package/src/components/segmented/animated-tabs.svelte +31 -2
  144. package/src/components/segmented/collapse.test.ts +2 -1
  145. package/src/components/segmented/index.ts +2 -0
  146. package/src/components/segmented/segment.svelte +52 -49
  147. package/src/components/segmented/segmented-filter.svelte +79 -80
  148. package/src/components/segmented/segmented-nav.svelte +34 -40
  149. package/src/components/segmented/segmented-overflow.svelte +53 -50
  150. package/src/components/segmented/segmented-track.svelte +62 -46
  151. package/src/components/segmented/segmented.ts +2 -3
  152. package/src/components/star-rating-input/star-rating-input.svelte +103 -103
  153. package/src/components/svg/svg.svelte +4 -4
  154. package/src/components/table-view/table-view.svelte +17 -2
  155. package/src/components/table-view/table-view.test.ts +7 -1
  156. package/src/components/table-view/types.ts +11 -11
  157. package/src/components/tags-input/index.ts +13 -0
  158. package/src/components/tags-input/tags-input.svelte +305 -0
  159. package/src/components/tags-input/tags.test.ts +64 -0
  160. package/src/components/tags-input/tags.ts +142 -0
  161. package/src/components/theme/index.ts +2 -4
  162. package/src/components/theme/theme-provider.svelte +1 -3
  163. package/src/components/toolbar/toolbar-search.svelte +25 -25
  164. package/src/components/toolbar/toolbar.svelte +11 -7
  165. package/src/components/toolbar/toolbar.test.ts +10 -1
  166. package/src/design/index.css +21 -78
  167. package/src/design/polish.css +26 -35
  168. package/src/editor/editor-toolbar.svelte +104 -99
  169. package/src/editor/editor.svelte +296 -266
  170. package/src/editor/image-toolbar.svelte +20 -22
  171. package/src/editor/index.ts +1 -4
  172. package/src/editor/inline-editor.svelte +280 -238
  173. package/src/editor/markdown.ts +15 -15
  174. package/src/editor/renderer.svelte +71 -71
  175. package/src/editor/slash-menu.svelte +24 -27
  176. package/src/effects/animated-beam/diagrams/BeamSvg.svelte +1 -1
  177. package/src/effects/image-comparison/ImageComparison.svelte +11 -1
  178. package/src/effects/interactive-hover-button/InteractiveHoverButton.svelte +59 -46
  179. package/src/effects/magic-card/MagicCard.css +14 -7
  180. package/src/effects/marquee/Marquee.svelte +3 -5
  181. package/src/effects/reveal/observe.ts +0 -3
  182. package/src/effects/spotlight/Spotlight.svelte +1 -1
  183. package/src/effects/theme-toggle/ThemeToggle.css +25 -22
  184. package/src/effects/theme-toggle/ThemeToggle.svelte +6 -7
  185. package/src/gallery.test.ts +14 -4
  186. package/src/inbox/README.md +1 -3
  187. package/src/inbox/inbox.test.ts +181 -138
  188. package/src/inbox/index.ts +20 -20
  189. package/src/inbox/logic.ts +114 -111
  190. package/src/inbox/thread-inbox.svelte +227 -151
  191. package/src/inbox/types.ts +130 -108
  192. package/src/lib/theme-transition.ts +2 -4
  193. package/src/lib/tone.ts +2 -4
  194. package/src/lib/utils.ts +2 -2
  195. package/src/modules.test.ts +79 -69
  196. package/src/onboarding/define-tours.test.ts +55 -40
  197. package/src/onboarding/define-tours.ts +23 -23
  198. package/src/onboarding/demo.svelte +24 -5
  199. package/src/onboarding/index.ts +1 -8
  200. package/src/onboarding/onboarding-provider.svelte +19 -7
  201. package/src/onboarding/onboarding.svelte.ts +16 -16
  202. package/src/onboarding/pick.test.ts +61 -52
  203. package/src/onboarding/pick.ts +16 -13
  204. package/src/onboarding/stores.test.ts +80 -80
  205. package/src/onboarding/stores.ts +41 -39
  206. package/src/onboarding/tour-card.svelte +30 -5
  207. package/src/onboarding/tour-runner.svelte +12 -3
  208. package/src/onboarding/types.ts +20 -12
  209. package/src/onboarding/wait.ts +22 -22
  210. package/src/panel/side-panel.svelte +94 -93
  211. package/src/record/demo-data.ts +291 -225
  212. package/src/record/field-section.svelte +38 -34
  213. package/src/record/fields.ts +148 -96
  214. package/src/record/format.ts +63 -56
  215. package/src/record/index.ts +49 -35
  216. package/src/record/info-list.svelte +145 -114
  217. package/src/record/kinds.ts +31 -31
  218. package/src/record/logic.ts +216 -214
  219. package/src/record/next-up.svelte +45 -34
  220. package/src/record/project-list.svelte +56 -50
  221. package/src/record/record-demo.svelte +130 -110
  222. package/src/record/record-header.svelte +37 -28
  223. package/src/record/record-shell.svelte +54 -54
  224. package/src/record/record-stats.svelte +25 -25
  225. package/src/record/record-timeline.svelte +134 -114
  226. package/src/record/record.test.ts +430 -309
  227. package/src/record/steps-rail.svelte +225 -216
  228. package/src/record/types.ts +234 -215
  229. package/src/record/where-we-are.svelte +62 -58
  230. package/src/shadcn/button/button.svelte +1 -1
  231. package/src/shadcn/card/card-title.svelte +1 -5
  232. package/src/shadcn/hooks/use-highlighted-code.svelte.ts +2 -13
  233. package/src/shadcn/sheet/sheet-content.svelte +2 -4
  234. package/src/shadcn/table/table-cell.svelte +2 -3
  235. package/src/shadcn/utilities.css +3 -8
  236. package/src/slides/Controls.svelte +150 -124
  237. package/src/slides/Deck.svelte +243 -238
  238. package/src/slides/Fragment.svelte +64 -57
  239. package/src/slides/Overview.svelte +106 -101
  240. package/src/slides/Presenter.svelte +300 -266
  241. package/src/slides/Slide.svelte +185 -170
  242. package/src/slides/SlideList.svelte +16 -12
  243. package/src/slides/ambient.d.ts +1 -1
  244. package/src/slides/bridges.ts +42 -23
  245. package/src/slides/deck-controller.svelte.ts +47 -35
  246. package/src/slides/demo-slides.svelte +330 -306
  247. package/src/slides/demo.svelte +164 -160
  248. package/src/slides/export/capture.ts +20 -16
  249. package/src/slides/export/ffmpeg.ts +43 -38
  250. package/src/slides/export/index.ts +5 -5
  251. package/src/slides/export/launch.ts +3 -1
  252. package/src/slides/export/pdf.ts +30 -27
  253. package/src/slides/export/types.ts +10 -8
  254. package/src/slides/index.ts +11 -11
  255. package/src/slides/labels.ts +22 -19
  256. package/src/slides/layout.ts +9 -2
  257. package/src/slides/model.ts +4 -4
  258. package/src/slides/navigation.ts +43 -31
  259. package/src/slides/theme.css +5 -7
  260. package/src/slides/types.ts +9 -10
  261. package/src/status/define-statuses.ts +17 -24
  262. package/src/status/index.ts +5 -5
  263. package/src/status/status-badge.svelte +3 -1
  264. package/src/status/status-dot.svelte +1 -3
  265. package/src/status/status-icon.svelte +1 -1
  266. package/src/status/status-types.ts +21 -21
  267. package/src/tasks/board-order.ts +2 -4
  268. package/src/tasks/demo.svelte +189 -31
  269. package/src/tasks/detail/detail-demo.svelte +73 -13
  270. package/src/tasks/detail/detail-types.ts +45 -45
  271. package/src/tasks/detail/task-detail.svelte +40 -10
  272. package/src/tasks/detail/task-property.svelte +8 -2
  273. package/src/tasks/detail/task-thread.svelte +53 -12
  274. package/src/tasks/gantt/gantt-bar.svelte +17 -6
  275. package/src/tasks/gantt/gantt-grid.svelte +40 -9
  276. package/src/tasks/gantt/gantt-group-row.svelte +8 -2
  277. package/src/tasks/gantt/gantt-header.svelte +9 -3
  278. package/src/tasks/gantt/gantt-task-row.svelte +26 -6
  279. package/src/tasks/gantt/gantt.css +2 -1
  280. package/src/tasks/gantt/gantt.test.ts +3 -1
  281. package/src/tasks/gantt/logic.test.ts +110 -20
  282. package/src/tasks/gantt/logic.ts +42 -6
  283. package/src/tasks/inbox/inbox-card.svelte +13 -5
  284. package/src/tasks/inbox/inbox-draft.svelte +1 -1
  285. package/src/tasks/inbox/inbox-logic.ts +6 -40
  286. package/src/tasks/inbox/inbox-statuses.ts +23 -23
  287. package/src/tasks/inbox/inbox-trace.svelte +9 -3
  288. package/src/tasks/inbox/inbox-types.ts +54 -58
  289. package/src/tasks/inbox/index.ts +22 -21
  290. package/src/tasks/index.ts +49 -52
  291. package/src/tasks/kanban-board.svelte +16 -12
  292. package/src/tasks/kanban-types.ts +2 -3
  293. package/src/tasks/presets/sales.ts +6 -6
  294. package/src/tasks/task-gantt.svelte +28 -8
  295. package/src/tasks/task-list.svelte +41 -17
  296. package/src/tasks/time/duration.ts +40 -40
  297. package/src/tasks/time/time-chip.svelte +9 -2
  298. package/src/tasks/time/time-dot.svelte +1 -1
  299. package/src/tasks/time/time-ring.svelte +11 -3
  300. package/src/tasks/time/time-tracker.svelte +57 -15
  301. package/src/tasks/time/time-types.ts +57 -57
  302. package/src/tasks/use-kanban-dnd.svelte.ts +2 -3
  303. package/src/thread/index.ts +7 -7
  304. package/src/thread/logic.ts +36 -33
  305. package/src/thread/thread.svelte +82 -16
  306. package/src/thread/thread.test.ts +58 -47
  307. package/src/thread/types.ts +68 -68
  308. package/src/tokens/craft.css +17 -53
  309. package/src/tokens/index.css +3 -13
  310. package/src/tokens/palette.css +13 -11
  311. package/src/tokens/primitives.css +10 -23
  312. package/src/tasks/inbox/inbox.svelte +0 -203
@@ -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 & { value: T };
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 } = await import("./collapse.js");
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
- 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
- };
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
- * `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
- }
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">{badge}</span>
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 && "group-data-[state=active]/segment:bg-brand/14 group-data-[state=active]/segment:text-brand",
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 { SEGMENT_MAX, resolveSegmentMax, splitSegments } from "./collapse.js";
11
- import { segmentedTriggerClass } from "./segmented.js";
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
- // WHY: picks which rows are listed, with no panel of its own. That's a
18
- // segmented control, not a tab set.
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
- // WHY not `AnimatedTabs`: a strip with no tabpanel is a `group` of `aria-pressed`
21
- // toggles. "Tab 1 of 4" sends a screen reader looking for a panel.
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
- // WHY `TabDef`: an option and a tab were the same five fields written twice.
24
- options: readonly TabDef<T>[];
25
- value: T;
26
- // For a caller whose value is derived and can't be bound. Same contract as
27
- // `AnimatedTabs`, so the two are picked by ARIA alone.
28
- onValueChange?: (value: T) => void;
29
- // Count per option, keyed by value. Merged over `options[].count`, because
30
- // the counts usually arrive from the data while the labels are a constant.
31
- counts?: Partial<Record<T, number>>;
32
- ariaLabel: string;
33
- /** Fill the row rather than sit at its natural width. On for the list-page
34
- * filter strip, off for a segment sitting in a toolbar beside others. */
35
- block?: boolean;
36
- /** Objects the row may draw before its tail becomes a menu. See `collapse.ts`.
37
- * `"none"` never folds. */
38
- max?: number | "none";
39
- /** What the menu segment says. Translate at the call site. */
40
- moreLabel?: string;
41
- class?: string;
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
- options,
49
- value = $bindable(),
50
- onValueChange,
51
- counts,
52
- ariaLabel,
53
- block = true,
54
- max = SEGMENT_MAX,
55
- moreLabel,
56
- class: className,
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
- options.map((option) => {
67
- const count = counts?.[option.value] ?? option.count;
68
- return count === option.count ? option : { ...option, count };
69
- }),
70
- );
71
- let split = $derived(
72
- splitSegments(
73
- merged,
74
- resolveSegmentMax(max),
75
- merged.findIndex((option) => option.value === value),
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
- value = next;
81
- onValueChange?.(next);
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
- // 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`.
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
- // 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
- };
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 { segmentedTriggerClass, type SegmentedVariant } from "./segmented.js";
9
- import type { SegmentContent } from "./segment.svelte";
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
- items: readonly (SegmentContent & { value: V })[];
23
- onSelect: (value: V) => void;
24
- /** What the segment says. Translate at the call site. */
25
- label?: string;
26
- fill?: boolean;
27
- variant?: SegmentedVariant;
28
- searchPlaceholder?: string;
29
- emptyLabel?: string;
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
- items,
37
- onSelect,
38
- label = "More",
39
- fill = false,
40
- variant = "pill",
41
- searchPlaceholder,
42
- emptyLabel,
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
- items.map((item) => ({
52
- value: item.value,
53
- label: item.label,
54
- hint: item.count != null ? String(item.count) : undefined,
55
- icon: item.icon,
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
  <!-- =========================== -->