@heyramzi/ui 7.0.0 → 8.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.
Files changed (253) hide show
  1. package/COMPONENTS.md +28 -17
  2. package/README.md +5 -1
  3. package/package.json +25 -15
  4. package/scripts/sync-shadcn.mjs +17 -13
  5. package/scripts/sync-transitions.mjs +11 -11
  6. package/src/ai/ai-button/AiButton.svelte +86 -0
  7. package/src/ai/ai-button/index.ts +4 -0
  8. package/src/ai/confirmation/Confirmation.svelte +8 -8
  9. package/src/ai/confirmation/confirmation-context.svelte.ts +2 -2
  10. package/src/ai/generation-progress/GenerationProgress.svelte +354 -0
  11. package/src/ai/generation-progress/index.ts +7 -0
  12. package/src/ai/index.ts +16 -6
  13. package/src/ai/prompt-input/PromptInput.svelte +19 -1
  14. package/src/ai/prompt-input/PromptInputSubmit.svelte +9 -3
  15. package/src/ai/reasoning/Reasoning.svelte +11 -8
  16. package/src/ambient.d.ts +4 -0
  17. package/src/animations/README.md +14 -14
  18. package/src/animations/index.css +3 -14
  19. package/src/animations/upstream-coverage.test.ts +1 -1
  20. package/src/components/copy/clipboard.test.ts +206 -0
  21. package/src/components/copy/clipboard.ts +49 -0
  22. package/src/components/copy/copied.svelte.ts +39 -0
  23. package/src/components/copy/copy-button.svelte +175 -0
  24. package/src/components/copy/copy-button.test.ts +110 -0
  25. package/src/components/copy/index.ts +3 -0
  26. package/src/components/data-table/data-table.test.ts +134 -0
  27. package/src/components/data-table/data-table.ts +77 -0
  28. package/src/components/data-table/index.ts +20 -0
  29. package/src/components/data-table/infinite-scroll.svelte +69 -0
  30. package/src/components/data-table/pagination.svelte +62 -0
  31. package/src/components/data-table/row-arrow.svelte +28 -0
  32. package/src/components/data-table/row-context-menu.svelte +106 -0
  33. package/src/components/data-table/row-context-menu.test.ts +18 -0
  34. package/src/components/data-table/sortable-head.svelte +56 -0
  35. package/src/components/data-table/table-skeleton.svelte +83 -0
  36. package/src/components/hold-to-confirm/hold-to-confirm.svelte +232 -0
  37. package/src/components/hold-to-confirm/index.ts +1 -0
  38. package/src/components/icon/icon.svelte +23 -0
  39. package/src/components/icon/index.ts +1 -0
  40. package/src/components/language-flag/index.ts +1 -0
  41. package/src/components/language-flag/language-flag.svelte +60 -0
  42. package/src/components/list-page/detail-empty.svelte +50 -0
  43. package/src/components/list-page/detail-header.svelte +107 -0
  44. package/src/components/list-page/detail-panel.svelte +90 -0
  45. package/src/components/list-page/detail-row.svelte +36 -0
  46. package/src/components/list-page/detail-stack.svelte +33 -0
  47. package/src/components/list-page/empty-state.svelte +91 -0
  48. package/src/components/list-page/filter-item.svelte +49 -0
  49. package/src/components/list-page/filter-popover.svelte +88 -0
  50. package/src/components/list-page/filter-section.ts +12 -0
  51. package/src/components/list-page/grouped-list.svelte +116 -0
  52. package/src/components/list-page/index.ts +28 -0
  53. package/src/components/list-page/list-page-shell.svelte +195 -0
  54. package/src/components/list-page/list-table.ts +19 -0
  55. package/src/components/list-page/master-detail.svelte +206 -0
  56. package/src/components/list-page/master-detail.test.ts +37 -0
  57. package/src/components/list-page/notice-bar.svelte +68 -0
  58. package/src/components/list-page/prev-next.svelte +104 -0
  59. package/src/components/list-page/queue-rail.ts +25 -0
  60. package/src/components/list-page/rail-body.svelte +29 -0
  61. package/src/components/list-page/rail-empty.svelte +51 -0
  62. package/src/components/list-page/rail-head.svelte +49 -0
  63. package/src/components/list-page/rail-row.svelte +163 -0
  64. package/src/components/list-page/section-host.ts +24 -0
  65. package/src/components/list-page/section-label.svelte +39 -0
  66. package/src/components/list-page/step.test.ts +21 -0
  67. package/src/components/list-page/step.ts +10 -0
  68. package/src/components/list-page/toolbar-search.svelte +63 -0
  69. package/src/components/list-page/toolbar-trigger.ts +23 -0
  70. package/src/components/list-page/types.ts +1 -0
  71. package/src/components/locale-switch/index.ts +1 -0
  72. package/src/components/locale-switch/locale-switch.svelte +59 -0
  73. package/src/components/mark/index.ts +1 -0
  74. package/src/components/mark/mark.svelte +70 -0
  75. package/src/components/open-in/index.ts +6 -0
  76. package/src/components/open-in/open-in.svelte +106 -0
  77. package/src/components/picker/index.ts +4 -0
  78. package/src/components/picker/mark-picker.svelte +84 -0
  79. package/src/components/picker/picker.svelte +143 -0
  80. package/src/components/picker/trigger.ts +42 -0
  81. package/src/components/picker/types.ts +26 -0
  82. package/src/components/segmented/animated-tabs.svelte +175 -0
  83. package/src/components/segmented/collapse.test.ts +49 -0
  84. package/src/components/segmented/collapse.ts +39 -0
  85. package/src/components/segmented/index.ts +41 -0
  86. package/src/components/segmented/section-runs.svelte +79 -0
  87. package/src/components/segmented/segment.svelte +106 -0
  88. package/src/components/segmented/segmented-filter.svelte +125 -0
  89. package/src/components/segmented/segmented-nav.svelte +82 -0
  90. package/src/components/segmented/segmented-overflow.svelte +85 -0
  91. package/src/components/segmented/segmented-track.svelte +181 -0
  92. package/src/components/segmented/segmented.test.ts +20 -0
  93. package/src/components/segmented/segmented.ts +80 -0
  94. package/src/components/star-rating-input/index.ts +9 -0
  95. package/src/components/star-rating-input/star-rating-input.svelte +256 -0
  96. package/src/components/svg/index.ts +1 -0
  97. package/src/components/svg/svg.svelte +9 -0
  98. package/src/effects/border-beam/BorderBeam.svelte +1 -1
  99. package/src/effects/image-comparison/ImageComparison.css +42 -8
  100. package/src/effects/image-comparison/ImageComparison.svelte +138 -21
  101. package/src/effects/image-comparison/compare.test.ts +17 -0
  102. package/src/effects/image-comparison/compare.ts +5 -0
  103. package/src/effects/interactive-hover-button/InteractiveHoverButton.svelte +338 -0
  104. package/src/effects/magic-card/MagicCard.css +47 -0
  105. package/src/effects/magic-card/MagicCard.svelte +57 -0
  106. package/src/effects/marquee/Marquee.css +27 -1
  107. package/src/effects/marquee/Marquee.svelte +67 -3
  108. package/src/effects/reveal/index.ts +2 -1
  109. package/src/effects/reveal/observe.test.ts +60 -0
  110. package/src/effects/reveal/observe.ts +33 -1
  111. package/src/effects/reveal/types.ts +7 -0
  112. package/src/effects/theme-toggle/ThemeToggle.css +199 -6
  113. package/src/effects/theme-toggle/ThemeToggle.svelte +51 -26
  114. package/src/gallery.test.ts +52 -0
  115. package/src/index.ts +5 -1
  116. package/src/lib/panel.ts +7 -1
  117. package/src/lib/theme-transition.ts +37 -11
  118. package/src/lib/tone.ts +48 -0
  119. package/src/shadcn/accordion/accordion-trigger.svelte +15 -6
  120. package/src/shadcn/alert/alert.svelte +3 -1
  121. package/src/shadcn/badge/badge.svelte +8 -7
  122. package/src/shadcn/breadcrumb/breadcrumb-ellipsis.svelte +2 -2
  123. package/src/shadcn/breadcrumb/breadcrumb-separator.svelte +2 -2
  124. package/src/shadcn/calendar/calendar-month-select.svelte +2 -2
  125. package/src/shadcn/calendar/calendar-next-button.svelte +2 -2
  126. package/src/shadcn/calendar/calendar-prev-button.svelte +2 -2
  127. package/src/shadcn/calendar/calendar-year-select.svelte +2 -2
  128. package/src/shadcn/calendar/calendar.svelte +6 -0
  129. package/src/shadcn/code-block/code-block-copy-button.svelte +6 -55
  130. package/src/shadcn/command/index.ts +3 -0
  131. package/src/shadcn/field/index.ts +3 -0
  132. package/src/shadcn/field/simple-field.svelte +71 -0
  133. package/src/shadcn/field/simple-field.test.ts +47 -0
  134. package/src/shadcn/message-scroller/message-scroller-button.svelte +2 -2
  135. package/src/shadcn/radio-group/radio-group-item.svelte +3 -2
  136. package/src/shadcn/sheet/sheet-content.svelte +1 -0
  137. package/src/shadcn/sheet/sheet.css +48 -0
  138. package/src/shadcn/sonner/sonner.svelte +7 -10
  139. package/src/shadcn/toggle-group/toggle-group.svelte +10 -3
  140. package/src/shadcn/trace/trace-collapsible-trigger.svelte +2 -2
  141. package/src/ai/composer/Composer.css +0 -108
  142. package/src/ai/composer/Composer.svelte +0 -111
  143. package/src/ai/composer/index.ts +0 -10
  144. package/src/animations/accordion.css +0 -58
  145. package/src/animations/icon-swap.css +0 -43
  146. package/src/animations/notification-badge.css +0 -81
  147. package/src/animations/panel.css +0 -86
  148. package/src/animations/reasoning-stream.css +0 -49
  149. package/src/animations/skeleton-reveal.css +0 -97
  150. package/src/animations/streaming-text.css +0 -31
  151. package/src/animations/tabs-sliding.css +0 -83
  152. package/src/animations/texts-reveal.css +0 -70
  153. package/src/animations/tooltip.css +0 -72
  154. package/src/animations/view-transition-reveal.ts +0 -74
  155. package/src/lib/utils.ts +0 -45
  156. package/src/showcase/AiSection.svelte +0 -150
  157. package/src/showcase/AnimationsSection.svelte +0 -1658
  158. package/src/showcase/EffectsSection.svelte +0 -152
  159. package/src/showcase/ShadcnSection.svelte +0 -27
  160. package/src/showcase/ShowcaseImport.svelte +0 -126
  161. package/src/showcase/ShowcaseList.svelte +0 -132
  162. package/src/showcase/ShowcaseNav.svelte +0 -247
  163. package/src/showcase/ShowcaseSection.svelte +0 -112
  164. package/src/showcase/ShowcaseShell.svelte +0 -55
  165. package/src/showcase/ShowcaseStage.svelte +0 -342
  166. package/src/showcase/ShowcaseVariant.svelte +0 -109
  167. package/src/showcase/assets/image-reveal/art-1.jpg +0 -0
  168. package/src/showcase/assets/image-reveal/art-2.jpg +0 -0
  169. package/src/showcase/assets/image-reveal/art-3.jpg +0 -0
  170. package/src/showcase/check.svg +0 -1
  171. package/src/showcase/chevron-arms.svg +0 -2
  172. package/src/showcase/chevron-down.svg +0 -1
  173. package/src/showcase/copy.svg +0 -2
  174. package/src/showcase/demos/ai/confirmation.svelte +0 -59
  175. package/src/showcase/demos/ai/loading-state.svelte +0 -19
  176. package/src/showcase/demos/ai/prompt-input.svelte +0 -57
  177. package/src/showcase/demos/ai/reasoning.svelte +0 -19
  178. package/src/showcase/demos/ai/thinking-dots.svelte +0 -15
  179. package/src/showcase/demos/ai/voice-waveform.svelte +0 -24
  180. package/src/showcase/demos/effects/animated-beam.svelte +0 -50
  181. package/src/showcase/demos/effects/animated-group.svelte +0 -47
  182. package/src/showcase/demos/effects/border-beam-default.svelte +0 -20
  183. package/src/showcase/demos/effects/border-beam-line.svelte +0 -20
  184. package/src/showcase/demos/effects/border-beam-mono.svelte +0 -20
  185. package/src/showcase/demos/effects/border-beam-sunset.svelte +0 -20
  186. package/src/showcase/demos/effects/image-comparison.svelte +0 -25
  187. package/src/showcase/demos/effects/image-reveal.svelte +0 -49
  188. package/src/showcase/demos/effects/in-view.svelte +0 -41
  189. package/src/showcase/demos/effects/integrations-hub.svelte +0 -82
  190. package/src/showcase/demos/effects/magnetic.svelte +0 -31
  191. package/src/showcase/demos/effects/marquee.svelte +0 -33
  192. package/src/showcase/demos/effects/progressive-blur.svelte +0 -47
  193. package/src/showcase/demos/effects/spotlight.svelte +0 -40
  194. package/src/showcase/demos/effects/text-effect.svelte +0 -55
  195. package/src/showcase/demos/shadcn/accordion.svelte +0 -45
  196. package/src/showcase/demos/shadcn/alert-dialog.svelte +0 -23
  197. package/src/showcase/demos/shadcn/alert.svelte +0 -30
  198. package/src/showcase/demos/shadcn/attachment.svelte +0 -71
  199. package/src/showcase/demos/shadcn/avatar.svelte +0 -30
  200. package/src/showcase/demos/shadcn/badge.svelte +0 -26
  201. package/src/showcase/demos/shadcn/breadcrumb.svelte +0 -34
  202. package/src/showcase/demos/shadcn/bubble.svelte +0 -36
  203. package/src/showcase/demos/shadcn/button.svelte +0 -11
  204. package/src/showcase/demos/shadcn/calendar.svelte +0 -8
  205. package/src/showcase/demos/shadcn/card.svelte +0 -39
  206. package/src/showcase/demos/shadcn/checkbox.svelte +0 -39
  207. package/src/showcase/demos/shadcn/code-block.svelte +0 -80
  208. package/src/showcase/demos/shadcn/collapsible.svelte +0 -20
  209. package/src/showcase/demos/shadcn/command.svelte +0 -48
  210. package/src/showcase/demos/shadcn/context-menu.svelte +0 -54
  211. package/src/showcase/demos/shadcn/demo-harness.svelte +0 -12
  212. package/src/showcase/demos/shadcn/demos.test.ts +0 -20
  213. package/src/showcase/demos/shadcn/dialog.svelte +0 -38
  214. package/src/showcase/demos/shadcn/dropdown-menu.svelte +0 -59
  215. package/src/showcase/demos/shadcn/empty.svelte +0 -29
  216. package/src/showcase/demos/shadcn/field.svelte +0 -115
  217. package/src/showcase/demos/shadcn/hover-card.svelte +0 -32
  218. package/src/showcase/demos/shadcn/index.ts +0 -382
  219. package/src/showcase/demos/shadcn/input-otp.svelte +0 -19
  220. package/src/showcase/demos/shadcn/input.svelte +0 -5
  221. package/src/showcase/demos/shadcn/label.svelte +0 -11
  222. package/src/showcase/demos/shadcn/marker.svelte +0 -30
  223. package/src/showcase/demos/shadcn/message-scroller.svelte +0 -44
  224. package/src/showcase/demos/shadcn/message.svelte +0 -75
  225. package/src/showcase/demos/shadcn/popover.svelte +0 -36
  226. package/src/showcase/demos/shadcn/progress.svelte +0 -13
  227. package/src/showcase/demos/shadcn/radio-group.svelte +0 -19
  228. package/src/showcase/demos/shadcn/scroll-area.svelte +0 -18
  229. package/src/showcase/demos/shadcn/select.svelte +0 -29
  230. package/src/showcase/demos/shadcn/separator.svelte +0 -12
  231. package/src/showcase/demos/shadcn/sheet.svelte +0 -48
  232. package/src/showcase/demos/shadcn/skeleton.svelte +0 -11
  233. package/src/showcase/demos/shadcn/slider.svelte +0 -7
  234. package/src/showcase/demos/shadcn/sonner.svelte +0 -18
  235. package/src/showcase/demos/shadcn/spinner.svelte +0 -11
  236. package/src/showcase/demos/shadcn/switch.svelte +0 -9
  237. package/src/showcase/demos/shadcn/table.svelte +0 -76
  238. package/src/showcase/demos/shadcn/tabs.svelte +0 -62
  239. package/src/showcase/demos/shadcn/textarea.svelte +0 -5
  240. package/src/showcase/demos/shadcn/toggle-group.svelte +0 -18
  241. package/src/showcase/demos/shadcn/toggle.svelte +0 -14
  242. package/src/showcase/demos/shadcn/tooltip.svelte +0 -13
  243. package/src/showcase/demos/shadcn/trace.svelte +0 -51
  244. package/src/showcase/heart.svg +0 -1
  245. package/src/showcase/index.ts +0 -38
  246. package/src/showcase/kit.test.ts +0 -72
  247. package/src/showcase/kit.ts +0 -16
  248. package/src/showcase/plus.svg +0 -1
  249. package/src/showcase/showcase-shell.css +0 -91
  250. package/src/showcase/stacked-section.css +0 -15
  251. package/src/showcase/success-check.svg +0 -1
  252. package/src/showcase/types.ts +0 -29
  253. package/src/svg-raw.d.ts +0 -7
@@ -1,152 +0,0 @@
1
- <!-- ========================== -->
2
- <!-- IMPORTS -->
3
- <!-- ========================== -->
4
- <script lang="ts">
5
- import type { Component } from "svelte";
6
- import ShowcaseStage from "./ShowcaseStage.svelte";
7
- import ShowcaseVariant from "./ShowcaseVariant.svelte";
8
- import Default from "./demos/effects/border-beam-default.svelte";
9
- import Sunset from "./demos/effects/border-beam-sunset.svelte";
10
- import Line from "./demos/effects/border-beam-line.svelte";
11
- import Mono from "./demos/effects/border-beam-mono.svelte";
12
- import Beam from "./demos/effects/animated-beam.svelte";
13
- import Hub from "./demos/effects/integrations-hub.svelte";
14
- import SpotlightDemo from "./demos/effects/spotlight.svelte";
15
- import MarqueeDemo from "./demos/effects/marquee.svelte";
16
- import RevealDemo from "./demos/effects/image-reveal.svelte";
17
- import TextEffectDemo from "./demos/effects/text-effect.svelte";
18
- import AnimatedGroupDemo from "./demos/effects/animated-group.svelte";
19
- import InViewDemo from "./demos/effects/in-view.svelte";
20
- import ProgressiveBlurDemo from "./demos/effects/progressive-blur.svelte";
21
- import ImageComparisonDemo from "./demos/effects/image-comparison.svelte";
22
- import MagneticDemo from "./demos/effects/magnetic.svelte";
23
-
24
- // =========================
25
- // TYPES
26
- // =========================
27
- // A family shows its presets stacked in one stage. One component, one number,
28
- // one copy control, however many variants it ships.
29
- type Demo = {
30
- title: string;
31
- note: string;
32
- path: string;
33
- Comp?: Component;
34
- variants?: { label: string; Comp: Component }[];
35
- };
36
-
37
- // =========================
38
- // STATE
39
- // =========================
40
- const demos: Demo[] = [
41
- {
42
- title: "BorderBeam",
43
- note: "Animated gradient border, four presets",
44
- path: "import { BorderBeam } from '@heyramzi/ui';",
45
- variants: [
46
- { label: "default", Comp: Default },
47
- { label: "staticColors, duration=6, colorVariant=sunset", Comp: Sunset },
48
- { label: "size=line, colorVariant=ocean", Comp: Line },
49
- { label: "size=sm, colorVariant=mono", Comp: Mono },
50
- ],
51
- },
52
- {
53
- title: "AnimatedBeam",
54
- note: "Gradient comet along a path between two anchors",
55
- path: "import { AnimatedBeam } from '@heyramzi/ui';",
56
- variants: [
57
- { label: "two nodes", Comp: Beam },
58
- { label: "many beams fanning into one node", Comp: Hub },
59
- ],
60
- },
61
- {
62
- title: "Spotlight",
63
- note: "Soft sweeping beam over a dark surface",
64
- Comp: SpotlightDemo,
65
- path: "import { Spotlight } from '@heyramzi/ui';",
66
- },
67
- {
68
- title: "Marquee",
69
- note: "pauseOnHover, masked edges",
70
- Comp: MarqueeDemo,
71
- path: "import { Marquee } from '@heyramzi/ui';",
72
- },
73
- {
74
- title: "ImageReveal",
75
- note: "Pixel-mosaic loader dissolving into a generated image",
76
- Comp: RevealDemo,
77
- path: "import { ImageReveal } from '@heyramzi/ui';",
78
- },
79
- {
80
- title: "TextEffect",
81
- note: "per=char, preset=blur-slide, staggered",
82
- Comp: TextEffectDemo,
83
- path: "import { TextEffect } from '@heyramzi/ui';",
84
- },
85
- {
86
- title: "AnimatedGroup",
87
- note: "Staggered entrance for any children",
88
- Comp: AnimatedGroupDemo,
89
- path: "import { AnimatedGroup } from '@heyramzi/ui';",
90
- },
91
- {
92
- title: "InView",
93
- note: "Replays as the block scrolls back in",
94
- Comp: InViewDemo,
95
- path: "import { InView } from '@heyramzi/ui';",
96
- },
97
- {
98
- title: "ProgressiveBlur",
99
- note: "Masked backdrop-filter ramp, no runtime",
100
- Comp: ProgressiveBlurDemo,
101
- path: "import { ProgressiveBlur } from '@heyramzi/ui';",
102
- },
103
- {
104
- title: "ImageComparison",
105
- note: "Before / after wipe, drag or arrow keys",
106
- Comp: ImageComparisonDemo,
107
- path: "import { ImageComparison } from '@heyramzi/ui';",
108
- },
109
- {
110
- title: "Magnetic",
111
- note: "use:magnetic pulls the button toward the pointer",
112
- Comp: MagneticDemo,
113
- path: "import { magnetic } from '@heyramzi/ui';",
114
- },
115
- ];
116
- </script>
117
-
118
- <!-- ========================== -->
119
- <!-- MARKUP -->
120
- <!-- ========================== -->
121
- <div class="grid">
122
- {#each demos as d (d.title)}
123
- <ShowcaseStage title={d.title} note={d.note} importPath={d.path}>
124
- {#if d.variants}
125
- {#each d.variants as v (v.label)}
126
- <ShowcaseVariant label={v.label}>
127
- <div class="card"><v.Comp /></div>
128
- </ShowcaseVariant>
129
- {/each}
130
- {:else if d.Comp}
131
- <div class="card"><d.Comp /></div>
132
- {/if}
133
- </ShowcaseStage>
134
- {/each}
135
- </div>
136
-
137
- <!-- ========================== -->
138
- <!-- STYLE -->
139
- <!-- ========================== -->
140
- <style>
141
- /* One demo per row, the rule ShowcaseSection states for `.sc-stack`. An
142
- effect is read by watching it run, and a quarter-width stage clips it. */
143
- .grid {
144
- display: grid;
145
- grid-template-columns: minmax(0, 1fr);
146
- gap: 1rem;
147
- }
148
- .card {
149
- width: 100%;
150
- min-height: 5rem;
151
- }
152
- </style>
@@ -1,27 +0,0 @@
1
- <!-- ========================== -->
2
- <!-- IMPORTS -->
3
- <!-- ========================== -->
4
- <script lang="ts">
5
- // Every shadcn primitive this package ships, demoed by upstream's own example
6
- // and listed by the sync. Nothing here is typed by hand: see
7
- // ../../docs/decisions.md and `pnpm sync:shadcn`.
8
- import { TooltipProvider } from "../shadcn/tooltip/index.js";
9
- import ShowcaseStage from "./ShowcaseStage.svelte";
10
- import { SHADCN_DEMOS } from "./demos/shadcn/index.js";
11
- import "./stacked-section.css";
12
- </script>
13
-
14
- <!-- ========================== -->
15
- <!-- MARKUP -->
16
- <!-- ========================== -->
17
- <!-- The provider an app mounts once at its root: upstream's demos assume one is
18
- there, and `input-group` throws without it. -->
19
- <TooltipProvider>
20
- <div class="sc-stacked">
21
- {#each SHADCN_DEMOS as demo (demo.name)}
22
- <ShowcaseStage title={demo.title} note={demo.note} importPath={demo.path} tag="shadcn" wide>
23
- <demo.Comp />
24
- </ShowcaseStage>
25
- {/each}
26
- </div>
27
- </TooltipProvider>
@@ -1,126 +0,0 @@
1
- <script lang="ts" module>
2
- export interface ShowcaseImportProps {
3
- /** The line a consumer pastes, e.g. `@heyramzi/ui/shadcn/button`. */
4
- path: string;
5
- /** Accessible name suffix, so two rows on a page do not both read "Copy import". */
6
- label?: string;
7
- class?: string;
8
- }
9
- </script>
10
-
11
- <script lang="ts">
12
- import { cn } from "@heyramzi/utils";
13
- import checkIcon from "./check.svg?raw";
14
- import copyIcon from "./copy.svg?raw";
15
-
16
- let { path, label, class: className }: ShowcaseImportProps = $props();
17
-
18
- let copied = $state(false);
19
- let timer: ReturnType<typeof setTimeout> | undefined;
20
-
21
- async function copy() {
22
- try {
23
- await navigator.clipboard.writeText(path);
24
- } catch {
25
- // A denied clipboard is not worth an error state on a dev-only surface;
26
- // the path is on screen and selectable either way.
27
- return;
28
- }
29
- copied = true;
30
- clearTimeout(timer);
31
- timer = setTimeout(() => (copied = false), 1600);
32
- }
33
- </script>
34
-
35
- <button
36
- type="button"
37
- class={cn("sc-import", className)}
38
- data-copied={copied ? "true" : undefined}
39
- onclick={copy}
40
- aria-label="Copy import for {label ?? path}"
41
- >
42
- <code>{path}</code>
43
- <span class="sc-import-state" aria-hidden="true">
44
- {#if copied}
45
- {@html checkIcon}
46
- {:else}
47
- {@html copyIcon}
48
- {/if}
49
- </span>
50
- </button>
51
-
52
- <style>
53
- .sc-import {
54
- display: flex;
55
- align-items: center;
56
- justify-content: space-between;
57
- gap: 0.75rem;
58
- width: 100%;
59
- min-width: 0;
60
- padding: 0.375rem 0.5rem 0.375rem 0.625rem;
61
- border: 1px solid transparent;
62
- border-radius: 0.5rem;
63
- background: transparent;
64
- color: var(--color-muted-foreground, var(--muted-foreground));
65
- cursor: pointer;
66
- text-align: start;
67
- transition:
68
- color 160ms ease,
69
- background-color 160ms ease,
70
- border-color 160ms ease;
71
- }
72
-
73
- .sc-import:hover {
74
- color: var(--color-foreground, var(--foreground));
75
- background: color-mix(in oklab, var(--color-muted, var(--muted)) 70%, transparent);
76
- }
77
-
78
- .sc-import:focus-visible {
79
- outline: 2px solid var(--color-ring, var(--ring));
80
- outline-offset: 2px;
81
- }
82
-
83
- .sc-import[data-copied] {
84
- color: var(--color-foreground, var(--foreground));
85
- border-color: color-mix(in oklab, var(--color-ring, var(--ring)) 45%, transparent);
86
- }
87
-
88
- .sc-import code {
89
- font-family: var(--font-mono, ui-monospace, "SF Mono", "JetBrains Mono", monospace);
90
- font-size: 0.6875rem;
91
- letter-spacing: -0.01em;
92
- overflow: hidden;
93
- text-overflow: ellipsis;
94
- white-space: nowrap;
95
- }
96
-
97
- .sc-import-state {
98
- display: grid;
99
- place-items: center;
100
- flex-shrink: 0;
101
- width: 0.875rem;
102
- height: 0.875rem;
103
- }
104
-
105
- .sc-import-state :global(svg) {
106
- width: 100%;
107
- height: 100%;
108
- animation: sc-icon-in 200ms cubic-bezier(0.22, 1, 0.36, 1);
109
- }
110
-
111
- @keyframes sc-icon-in {
112
- from {
113
- opacity: 0;
114
- transform: scale(0.6);
115
- }
116
- }
117
-
118
- @media (prefers-reduced-motion: reduce) {
119
- .sc-import {
120
- transition: none;
121
- }
122
- .sc-import-state :global(svg) {
123
- animation: none;
124
- }
125
- }
126
- </style>
@@ -1,132 +0,0 @@
1
- <script lang="ts" module>
2
- export interface ShowcaseListItem {
3
- /** Component name, e.g. "Context Menu". */
4
- title: string;
5
- /** Import line for the copy control. */
6
- importPath: string;
7
- /** Right-hand detail: a file count, a parts count, a source path. */
8
- meta?: string;
9
- /** Provenance beside the name, e.g. "shadcn" on a primitive the kit took as is. */
10
- tag?: string;
11
- }
12
-
13
- export interface ShowcaseListProps {
14
- items: ShowcaseListItem[];
15
- /** Heading above the rows. Omit inside a section that is list-only. */
16
- caption?: string;
17
- }
18
- </script>
19
-
20
- <script lang="ts">
21
- import ShowcaseImport from "./ShowcaseImport.svelte";
22
- import "./showcase-shell.css";
23
-
24
- let { items, caption }: ShowcaseListProps = $props();
25
- </script>
26
-
27
- <!-- WHY a row and not a stage: a component with no demo has one useful fact, its
28
- import line. Given a card each, the undemoed majority buries the demos. -->
29
- <div class="sc-list">
30
- {#if caption}
31
- <p class="sc-list-caption">{caption}</p>
32
- {/if}
33
- <ul>
34
- {#each items as item (item.importPath)}
35
- <li>
36
- <span class="sc-list-title">
37
- {item.title}
38
- {#if item.tag}
39
- <span class="sc-tag">{item.tag}</span>
40
- {/if}
41
- </span>
42
- <ShowcaseImport path={item.importPath} label={item.title} class="sc-list-import" />
43
- {#if item.meta}
44
- <span class="sc-list-meta">{item.meta}</span>
45
- {/if}
46
- </li>
47
- {/each}
48
- </ul>
49
- </div>
50
-
51
- <style>
52
- .sc-list {
53
- display: flex;
54
- flex-direction: column;
55
- gap: 0.625rem;
56
- }
57
-
58
- .sc-list-caption {
59
- margin: 0;
60
- font-size: 0.6875rem;
61
- font-weight: 500;
62
- letter-spacing: 0.06em;
63
- text-transform: uppercase;
64
- color: var(--color-muted-foreground, var(--muted-foreground));
65
- }
66
-
67
- .sc-list ul {
68
- list-style: none;
69
- margin: 0;
70
- padding: 0;
71
- border: 1px solid var(--color-border, var(--border));
72
- border-radius: calc(var(--radius, 0.625rem) + 0.375rem);
73
- background: var(--color-card, var(--card));
74
- overflow: hidden;
75
- }
76
-
77
- .sc-list li {
78
- position: relative;
79
- display: grid;
80
- grid-template-columns: minmax(7rem, 12rem) minmax(0, 1fr) auto;
81
- align-items: center;
82
- gap: 0.75rem;
83
- padding: 0.1875rem 0.75rem 0.1875rem 1rem;
84
- min-height: 2.5rem;
85
- }
86
-
87
- /* Inset to the title column, so a scan down the names is not cut by a full-bleed
88
- rule on every row. */
89
- .sc-list li + li::before {
90
- content: "";
91
- position: absolute;
92
- inset-block-start: 0;
93
- inset-inline: 1rem 0;
94
- height: 1px;
95
- background: var(--color-border, var(--border));
96
- }
97
-
98
- .sc-list-title {
99
- font-size: 0.8125rem;
100
- font-weight: 500;
101
- color: var(--color-card-foreground, var(--color-foreground, var(--foreground)));
102
- overflow: hidden;
103
- text-overflow: ellipsis;
104
- white-space: nowrap;
105
- }
106
-
107
- .sc-list-meta {
108
- font-size: 0.6875rem;
109
- font-variant-numeric: tabular-nums;
110
- color: var(--color-muted-foreground, var(--muted-foreground));
111
- white-space: nowrap;
112
- }
113
-
114
- @media (max-width: 40rem) {
115
- .sc-list li {
116
- grid-template-columns: minmax(0, 1fr) auto;
117
- grid-template-areas:
118
- "title meta"
119
- "import import";
120
- padding-block: 0.5rem;
121
- }
122
- .sc-list-title {
123
- grid-area: title;
124
- }
125
- .sc-list-meta {
126
- grid-area: meta;
127
- }
128
- .sc-list :global(.sc-list-import) {
129
- grid-area: import;
130
- }
131
- }
132
- </style>
@@ -1,247 +0,0 @@
1
- <script lang="ts" module>
2
- import type { ShowcaseSectionMeta } from "./types.js";
3
-
4
- export interface ShowcaseNavProps {
5
- sections: ShowcaseSectionMeta[];
6
- }
7
- </script>
8
-
9
- <script lang="ts">
10
- import { onMount } from "svelte";
11
-
12
- let { sections }: ShowcaseNavProps = $props();
13
-
14
- let active = $state(sections[0]?.id ?? "");
15
- // Nothing is written back until the reader's place has been restored, or the
16
- // observer's first call would overwrite it with whatever the top of the page is.
17
- let restored = $state(false);
18
-
19
- const storeKey = () => `sc-rail-active:${location.pathname}`;
20
-
21
- // Consecutive sections sharing a group are drawn under one heading.
22
-
23
- // Grouping by run rather than by key means the page's own order is the rail's order, and a section can be moved without also moving it in a second list.
24
- const runs = $derived(
25
- sections.reduce<{ group?: string; items: ShowcaseSectionMeta[] }[]>((acc, section) => {
26
- const last = acc.at(-1);
27
- if (last && last.group === section.group) last.items.push(section);
28
- else acc.push({ group: section.group, items: [section] });
29
- return acc;
30
- }, []),
31
- );
32
-
33
- // The rail follows the reader rather than the last click: an anchor jump and a
34
- // scroll both end in the same place, so only one source of truth is needed.
35
- $effect(() => {
36
- const targets = sections
37
- .map((section) => document.getElementById(section.id))
38
- .filter((element): element is HTMLElement => element !== null);
39
- if (targets.length === 0) return;
40
-
41
- const observer = new IntersectionObserver(
42
- (entries) => {
43
- const visible = entries
44
- .filter((entry) => entry.isIntersecting)
45
- .toSorted((a, b) => a.boundingClientRect.top - b.boundingClientRect.top);
46
- if (visible[0]) active = visible[0].target.id;
47
- },
48
- { rootMargin: "-12% 0px -75% 0px", threshold: 0 },
49
- );
50
- for (const target of targets) observer.observe(target);
51
- return () => observer.disconnect();
52
- });
53
-
54
- // The band the observer watches sits near the top of the viewport, and the last section is often too short to reach it,
55
-
56
- // so the rail stopped one entry early at the foot of the page. The bottom of the scroll names the last section.
57
- $effect(() => {
58
- const onScroll = () => {
59
- const last = sections.at(-1)?.id;
60
- if (last && window.innerHeight + window.scrollY >= document.body.scrollHeight - 2) {
61
- active = last;
62
- }
63
- };
64
- window.addEventListener("scroll", onScroll, { passive: true });
65
- return () => window.removeEventListener("scroll", onScroll);
66
- });
67
-
68
- // A reload put the reader back at the top.
69
-
70
- // The browser does restore a scroll offset, but the charts, images and fonts on this page settle after it, so the offset it restores is no longer the section it
71
-
72
- // was taken from. The section id survives instead: the hash when the reader followed one, the last section read otherwise.
73
- onMount(() => {
74
- let wanted = decodeURIComponent(location.hash.slice(1));
75
- if (!wanted) {
76
- try {
77
- wanted = sessionStorage.getItem(storeKey()) ?? "";
78
- } catch {
79
- wanted = "";
80
- }
81
- }
82
- if (!wanted || !sections.some((section) => section.id === wanted)) {
83
- restored = true;
84
- return;
85
- }
86
-
87
- active = wanted;
88
- // Two frames: the first paints the sections, the second lets what is inside
89
- // them take its height, so the anchor lands on the heading and not above it.
90
- requestAnimationFrame(() =>
91
- requestAnimationFrame(() => {
92
- document.getElementById(wanted)?.scrollIntoView({ block: "start" });
93
- restored = true;
94
- }),
95
- );
96
- });
97
-
98
- $effect(() => {
99
- if (!restored || !active) return;
100
- try {
101
- sessionStorage.setItem(storeKey(), active);
102
- } catch {
103
- // A private window refuses the write. The rail still follows the reader.
104
- }
105
- });
106
- </script>
107
-
108
- <nav aria-label="Component sections">
109
- {#each runs as run, index (run.group ?? index)}
110
- {#if run.group}
111
- <p class="sc-rail-group">{run.group}</p>
112
- {/if}
113
- <ul>
114
- {#each run.items as section (section.id)}
115
- <li>
116
- <a
117
- href="#{section.id}"
118
- data-active={active === section.id ? "true" : undefined}
119
- aria-current={active === section.id ? "page" : undefined}
120
- >
121
- <span class="sc-rail-label">{section.label}</span>
122
- <span class="sc-rail-count">{section.demos || section.listed}</span>
123
- </a>
124
- </li>
125
- {/each}
126
- </ul>
127
- {/each}
128
- </nav>
129
-
130
- <style>
131
- /*
132
- * WHY a rail and not a row of pills: the row was one line of chips that ran to
133
- */
134
-
135
- /*
136
- * nine sections and wrapped, so the reader's place in a long page was a chip somewhere in a block of chips. A vertical list holds a stable order, has room
137
- */
138
-
139
- /*
140
- * for group headings, and leaves the whole content column to the components.
141
- */
142
- nav {
143
- position: sticky;
144
- top: var(--showcase-nav-top, 0px);
145
- display: flex;
146
- flex-direction: column;
147
- gap: 0.125rem;
148
- max-height: calc(100dvh - var(--showcase-nav-top, 0px));
149
- padding: 1.25rem 0.75rem 2rem 0.4375rem;
150
- overflow-y: auto;
151
- /* WHY only x: `contain` on y stopped this zero-overflow rail chaining the wheel to the page. */
152
- overscroll-behavior-x: contain;
153
- scrollbar-width: thin;
154
- }
155
-
156
- .sc-rail-group {
157
- margin: 1.125rem 0 0.375rem 0.5rem;
158
- font-size: 0.625rem;
159
- font-weight: 600;
160
- letter-spacing: 0.08em;
161
- text-transform: uppercase;
162
- color: var(--color-muted-foreground, var(--muted-foreground));
163
- opacity: 0.75;
164
- }
165
-
166
- .sc-rail-group:first-child {
167
- margin-top: 0;
168
- }
169
-
170
- ul {
171
- display: flex;
172
- flex-direction: column;
173
- gap: 0.0625rem;
174
- margin: 0;
175
- padding: 0;
176
- list-style: none;
177
- }
178
-
179
- a {
180
- position: relative;
181
- display: flex;
182
- align-items: center;
183
- justify-content: space-between;
184
- gap: 0.5rem;
185
- padding: 0.3125rem 0.5rem 0.3125rem 0.6875rem;
186
- border-radius: var(--radius-lg, 8px);
187
- font-size: 0.78125rem;
188
- line-height: 1.35;
189
- color: var(--color-muted-foreground, var(--muted-foreground));
190
- text-decoration: none;
191
- transition:
192
- color var(--duration-quick, 150ms) var(--ease-smooth-out, ease),
193
- background-color var(--duration-quick, 150ms) var(--ease-smooth-out, ease);
194
- }
195
-
196
- a:hover {
197
- color: var(--color-foreground, var(--foreground));
198
- background: color-mix(in oklab, var(--color-muted, var(--muted)) 60%, transparent);
199
- }
200
-
201
- /*
202
- * The active row is a filled slot with a bar in the gutter. The bar is what the
203
- */
204
-
205
- /*
206
- * eye finds when it comes back to the rail after reading. A fill alone is too
207
- */
208
-
209
- /*
210
- * quiet at this weight, and a full-strength pill is too loud for a list this long.
211
- */
212
- a[data-active] {
213
- color: var(--color-foreground, var(--foreground));
214
- font-weight: 500;
215
- background: color-mix(in oklab, var(--color-muted, var(--muted)) 90%, transparent);
216
- }
217
-
218
- a[data-active]::before {
219
- content: "";
220
- position: absolute;
221
- inset-block: 0.3125rem;
222
- inset-inline-start: -0.375rem;
223
- width: 2px;
224
- border-radius: 999px;
225
- background: var(--color-ring, var(--ring));
226
- }
227
-
228
- .sc-rail-label {
229
- overflow: hidden;
230
- text-overflow: ellipsis;
231
- white-space: nowrap;
232
- }
233
-
234
- .sc-rail-count {
235
- flex-shrink: 0;
236
- font-size: 0.625rem;
237
- font-variant-numeric: tabular-nums;
238
- color: var(--color-muted-foreground, var(--muted-foreground));
239
- opacity: 0.7;
240
- }
241
-
242
- @media (prefers-reduced-motion: reduce) {
243
- a {
244
- transition: none;
245
- }
246
- }
247
- </style>