@heyramzi/ui 6.1.0 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/COMPONENTS.md +6 -25
- package/README.md +6 -4
- package/package.json +7 -15
- package/scripts/sync-shadcn.mjs +12 -3
- package/src/ai/index.ts +0 -18
- package/src/animations/README.md +3 -3
- package/src/lib/sliding-pill.ts +68 -0
- package/src/lib/theme-transition.ts +4 -2
- package/src/shadcn/code-block/code-block-actions.svelte +23 -0
- package/src/shadcn/code-block/code-block-content.svelte +55 -0
- package/src/shadcn/code-block/code-block-copy-button.svelte +59 -0
- package/src/shadcn/code-block/code-block-footer.svelte +20 -0
- package/src/shadcn/code-block/code-block-header.svelte +23 -0
- package/src/shadcn/code-block/code-block-line.svelte +71 -0
- package/src/shadcn/code-block/code-block-title.svelte +23 -0
- package/src/shadcn/code-block/code-block.svelte +47 -0
- package/src/shadcn/code-block/index.ts +42 -0
- package/src/shadcn/hooks/highlighter.ts +132 -0
- package/src/shadcn/hooks/use-highlighted-code.svelte.ts +98 -0
- package/src/shadcn/kbd/index.ts +10 -0
- package/src/shadcn/kbd/kbd-group.svelte +20 -0
- package/src/shadcn/kbd/kbd.svelte +23 -0
- package/src/shadcn/message-scroller/index.ts +28 -0
- package/src/shadcn/message-scroller/message-scroller-button.svelte +52 -0
- package/src/shadcn/message-scroller/message-scroller-content.svelte +17 -0
- package/src/shadcn/message-scroller/message-scroller-item.svelte +22 -0
- package/src/shadcn/message-scroller/message-scroller-provider.svelte +7 -0
- package/src/shadcn/message-scroller/message-scroller-viewport.svelte +20 -0
- package/src/shadcn/message-scroller/message-scroller.svelte +20 -0
- package/src/shadcn/tabs/tabs-list.svelte +13 -2
- package/src/shadcn/tabs/tabs-trigger.svelte +1 -1
- package/src/shadcn/trace/index.ts +29 -0
- package/src/shadcn/trace/trace-collapsible-content.svelte +29 -0
- package/src/shadcn/trace/trace-collapsible-trigger.svelte +29 -0
- package/src/shadcn/trace/trace-collapsible.svelte +20 -0
- package/src/shadcn/trace/trace-description.svelte +20 -0
- package/src/shadcn/trace/trace-icon.svelte +24 -0
- package/src/shadcn/trace/trace-line.svelte +31 -0
- package/src/shadcn/trace/trace-title.svelte +20 -0
- package/src/shadcn/trace/trace.svelte +23 -0
- package/src/showcase/AiSection.svelte +0 -7
- package/src/showcase/demos/shadcn/code-block.svelte +80 -0
- package/src/showcase/demos/shadcn/index.ts +24 -0
- package/src/showcase/demos/shadcn/message-scroller.svelte +44 -0
- package/src/showcase/demos/shadcn/trace.svelte +51 -0
- package/src/showcase/kit.ts +1 -2
- package/src/ai/conversation/Conversation.svelte +0 -28
- package/src/ai/conversation/ConversationContent.svelte +0 -35
- package/src/ai/conversation/ConversationEmptyState.svelte +0 -46
- package/src/ai/conversation/ConversationScrollButton.svelte +0 -51
- package/src/ai/conversation/index.ts +0 -33
- package/src/ai/conversation/stick-to-bottom-context.svelte.ts +0 -131
- package/src/charts/animated-value.css +0 -7
- package/src/charts/animated-value.svelte +0 -110
- package/src/charts/area-chart.css +0 -29
- package/src/charts/area-chart.svelte +0 -195
- package/src/charts/bar-chart.css +0 -119
- package/src/charts/bar-chart.svelte +0 -269
- package/src/charts/charts.css +0 -201
- package/src/charts/crosshair.svelte +0 -35
- package/src/charts/donut-chart.css +0 -56
- package/src/charts/donut-chart.svelte +0 -158
- package/src/charts/edge-fade.svelte +0 -43
- package/src/charts/edge-fade.ts +0 -16
- package/src/charts/figure.css +0 -100
- package/src/charts/figure.svelte +0 -122
- package/src/charts/gauge-chart.css +0 -94
- package/src/charts/gauge-chart.svelte +0 -303
- package/src/charts/hover-band.test.ts +0 -30
- package/src/charts/hover-band.ts +0 -21
- package/src/charts/hover-focus.svelte +0 -85
- package/src/charts/index.ts +0 -30
- package/src/charts/legend.css +0 -131
- package/src/charts/legend.svelte +0 -112
- package/src/charts/line-chart.css +0 -26
- package/src/charts/line-chart.svelte +0 -256
- package/src/charts/motion.ts +0 -19
- package/src/charts/number-chart.css +0 -168
- package/src/charts/number-chart.svelte +0 -177
- package/src/charts/primitives/chart-container.svelte +0 -57
- package/src/charts/primitives/chart-style.svelte +0 -36
- package/src/charts/primitives/chart-tooltip.svelte +0 -161
- package/src/charts/primitives/chart-utils.ts +0 -54
- package/src/charts/primitives/index.ts +0 -11
- package/src/charts/reveal.svelte +0 -61
- package/src/charts/ring-chart.css +0 -96
- package/src/charts/ring-chart.svelte +0 -158
- package/src/charts/sparkline.css +0 -45
- package/src/charts/sparkline.svelte +0 -140
- package/src/charts/stat-card.css +0 -103
- package/src/charts/stat-card.svelte +0 -267
- package/src/charts/time-series-chart.css +0 -28
- package/src/charts/time-series-chart.svelte +0 -179
- package/src/charts/trend-pill.css +0 -38
- package/src/charts/trend-pill.svelte +0 -48
- package/src/lib/hooks/is-mobile.svelte.ts +0 -11
- package/src/showcase/ChartsSection.svelte +0 -458
- package/src/showcase/demos/ai/conversation.svelte +0 -59
package/COMPONENTS.md
CHANGED
|
@@ -21,6 +21,7 @@ The style layer lives in [`src/tokens.css`](./src/tokens.css) and the motion CSS
|
|
|
21
21
|
- `calendar` — Calendar
|
|
22
22
|
- `card` — Card
|
|
23
23
|
- `checkbox` — Checkbox
|
|
24
|
+
- `code-block` — CodeBlock. Highlight with `useHighlightedCode` from `@heyramzi/ui/shadcn/hooks/use-highlighted-code.svelte` (Shiki, ANSI too)
|
|
24
25
|
- `collapsible` — Collapsible
|
|
25
26
|
- `command` — Command
|
|
26
27
|
- `context-menu` — ContextMenu
|
|
@@ -34,6 +35,7 @@ The style layer lives in [`src/tokens.css`](./src/tokens.css) and the motion CSS
|
|
|
34
35
|
- `label` — Label
|
|
35
36
|
- `marker` — Marker
|
|
36
37
|
- `message` — Message
|
|
38
|
+
- `message-scroller` — MessageScroller, the chat transcript scroller: follows a streamed reply, anchors each turn, keeps place on prepend
|
|
37
39
|
- `popover` — Popover
|
|
38
40
|
- `progress` — Progress
|
|
39
41
|
- `radio-group` — RadioGroup
|
|
@@ -53,6 +55,7 @@ The style layer lives in [`src/tokens.css`](./src/tokens.css) and the motion CSS
|
|
|
53
55
|
- `toggle` — toggleVariants, ToggleSize, ToggleVariant, ToggleVariants
|
|
54
56
|
- `toggle-group` — ToggleGroup
|
|
55
57
|
- `tooltip` — Tooltip
|
|
58
|
+
- `trace` — Trace, an agent's steps: reasoning, tool calls and sources as status rows that can expand
|
|
56
59
|
|
|
57
60
|
## Effects (`src/effects`)
|
|
58
61
|
|
|
@@ -66,7 +69,7 @@ The style layer lives in [`src/tokens.css`](./src/tokens.css) and the motion CSS
|
|
|
66
69
|
- `reveal` — InView, AnimatedGroup, TextEffect, observeInView, RevealPreset, RevealPer
|
|
67
70
|
- `shader-gradient` — ShaderGradient, ShaderGradientProps, ShaderGradientUniforms
|
|
68
71
|
- `spotlight` — Spotlight
|
|
69
|
-
- `theme-toggle` — ThemeToggle, revealTheme. Props: `isDark`, `mode` (`light`, `dark`, `system`; swaps in the Monitor icon), `onToggle`, `variant` (`outline`, `ghost`, `inverted`; `inverted` boolean is an alias), `size`, `label`, `title`, `skipTransition`, `class`. `revealTheme(event, apply, { skip, duration })` is the circular wipe on its own: `import { revealTheme } from "@heyramzi/ui"` (the `./lib/*` subpath points at `.js` and the source is `.ts`).
|
|
72
|
+
- `theme-toggle` — ThemeToggle, revealTheme. Props: `isDark`, `mode` (`light`, `dark`, `system`; swaps in the Monitor icon), `onToggle`, `variant` (`outline`, `ghost`, `inverted`; `inverted` boolean is an alias), `size`, `label`, `title`, `skipTransition`, `class`. `revealTheme(event, apply, { skip, duration, easing })` is the circular wipe on its own: `import { revealTheme } from "@heyramzi/ui"` (the `./lib/*` subpath points at `.js` and the source is `.ts`).
|
|
70
73
|
|
|
71
74
|
## Animations (`src/animations`)
|
|
72
75
|
|
|
@@ -102,7 +105,6 @@ The style layer lives in [`src/tokens.css`](./src/tokens.css) and the motion CSS
|
|
|
102
105
|
|
|
103
106
|
- `composer` — Composer, ComposerProps
|
|
104
107
|
- `confirmation` — Confirmation, ConfirmationTitle, ConfirmationRequest, ConfirmationAccepted, ConfirmationRejected, ConfirmationActions, ConfirmationAction
|
|
105
|
-
- `conversation` — Conversation, ConversationContent, ConversationEmptyState, ConversationScrollButton
|
|
106
108
|
- `loading-state` — PixelLoader
|
|
107
109
|
- `prompt-input` — PromptInput, PromptInputBody, PromptInputToolbar, PromptInputTextarea, PromptInputSubmit
|
|
108
110
|
- `reasoning` — Reasoning, ReasoningTrigger, ReasoningContent
|
|
@@ -112,30 +114,9 @@ The style layer lives in [`src/tokens.css`](./src/tokens.css) and the motion CSS
|
|
|
112
114
|
- `typing-dots` — TypingDots
|
|
113
115
|
- `voice-waveform` — VoiceWaveform
|
|
114
116
|
|
|
115
|
-
## Charts (`src/charts`)
|
|
116
|
-
|
|
117
|
-
- `animated-value` — AnimatedValue
|
|
118
|
-
- `area-chart` — AreaChart
|
|
119
|
-
- `bar-chart` — BarChart
|
|
120
|
-
- `crosshair` — Crosshair
|
|
121
|
-
- `donut-chart` — DonutChart
|
|
122
|
-
- `edge-fade` — EdgeFade
|
|
123
|
-
- `figure` — Figure
|
|
124
|
-
- `gauge-chart` — GaugeChart
|
|
125
|
-
- `hover-focus` — HoverFocus
|
|
126
|
-
- `legend` — Legend
|
|
127
|
-
- `line-chart` — LineChart
|
|
128
|
-
- `number-chart` — NumberChart
|
|
129
|
-
- `reveal` — Reveal
|
|
130
|
-
- `ring-chart` — RingChart
|
|
131
|
-
- `sparkline` — Sparkline
|
|
132
|
-
- `stat-card` — StatCard
|
|
133
|
-
- `time-series-chart` — TimeSeriesChart
|
|
134
|
-
- `trend-pill` — TrendPill
|
|
135
|
-
- `primitives` — ChartContainer, ChartTooltip, ChartStyle, ChartConfig, TooltipSeriesItem, getPayloadConfigFromPayload
|
|
136
|
-
|
|
137
117
|
---
|
|
138
118
|
|
|
139
119
|
Skipped on purpose: brand assets (logos, marks, brand renderer, language flags,
|
|
140
|
-
locale switch), kanban and status (they live in `@heyramzi/
|
|
120
|
+
locale switch), kanban, list, gantt and status (they live in `@heyramzi/tasks`), and charts
|
|
121
|
+
(they live in `@heyramzi/charts`). The bot avatars
|
|
141
122
|
are in `src/agents`, imported from `@heyramzi/ui/agents`.
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# @heyramzi/ui
|
|
2
2
|
|
|
3
3
|
Svelte 5 UI primitives you copy into your project and own. Components, visual
|
|
4
|
-
effects, CSS animations
|
|
4
|
+
effects, CSS animations and a small set of chat and AI surfaces. No build step, no runtime wrapper,
|
|
5
5
|
no framework lock-in.
|
|
6
6
|
|
|
7
7
|
The package is a source registry. Every component is a folder under
|
|
@@ -15,9 +15,7 @@ only a convenience.
|
|
|
15
15
|
image comparison, shimmer gradients and a theme toggle.
|
|
16
16
|
- `src/animations` keyframe CSS for dropdowns, modals, tooltips, text swaps,
|
|
17
17
|
success checks and the rest of the motion vocabulary.
|
|
18
|
-
- `src/ai` chat and AI surfaces:
|
|
19
|
-
- `src/charts` SVG charts: line, area, bar, donut, ring, gauge, sparkline,
|
|
20
|
-
time series, and stat cards.
|
|
18
|
+
- `src/ai` chat and AI surfaces: composer, prompt input, response, reasoning, confirmation and loaders.
|
|
21
19
|
- `src/agents` bot avatars and a cast grid, imported from `@heyramzi/ui/agents`:
|
|
22
20
|
`AgentAvatar` draws an eyes-only face from a name, role, shape and colour, and
|
|
23
21
|
`AgentCast` lays a list of agents out as cards you can pick from.
|
|
@@ -43,6 +41,10 @@ Retheme by overriding any variable on `:root`, for example
|
|
|
43
41
|
`--color-brand-indigo` or `--radius-lg`. Dark mode keys off a `.dark`
|
|
44
42
|
class on `html`.
|
|
45
43
|
|
|
44
|
+
Charts live in their own package now,
|
|
45
|
+
[`@heyramzi/charts`](https://github.com/heyramzi/ui/tree/main/packages/charts). It reads
|
|
46
|
+
the same tokens, so import `tokens.css` once and both work.
|
|
47
|
+
|
|
46
48
|
React and other framework ports are out of scope. The source is Svelte 5
|
|
47
49
|
runes only.
|
|
48
50
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heyramzi/ui",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.0",
|
|
4
4
|
"description": "Svelte 5 UI primitives: effects, animations, and shared component CSS.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -115,11 +115,6 @@
|
|
|
115
115
|
"./animations/*": {
|
|
116
116
|
"default": "./src/animations/*"
|
|
117
117
|
},
|
|
118
|
-
"./charts": {
|
|
119
|
-
"svelte": "./src/charts/index.ts",
|
|
120
|
-
"types": "./src/charts/index.ts",
|
|
121
|
-
"default": "./src/charts/index.ts"
|
|
122
|
-
},
|
|
123
118
|
"./showcase": {
|
|
124
119
|
"svelte": "./src/showcase/index.ts",
|
|
125
120
|
"types": "./src/showcase/index.ts",
|
|
@@ -130,10 +125,10 @@
|
|
|
130
125
|
"types": "./src/showcase/kit.ts",
|
|
131
126
|
"default": "./src/showcase/kit.ts"
|
|
132
127
|
},
|
|
133
|
-
"./shadcn/hooks
|
|
134
|
-
"svelte": "./src/shadcn/hooks
|
|
135
|
-
"types": "./src/shadcn/hooks
|
|
136
|
-
"default": "./src/shadcn/hooks
|
|
128
|
+
"./shadcn/hooks/*": {
|
|
129
|
+
"svelte": "./src/shadcn/hooks/*.ts",
|
|
130
|
+
"types": "./src/shadcn/hooks/*.ts",
|
|
131
|
+
"default": "./src/shadcn/hooks/*.ts"
|
|
137
132
|
},
|
|
138
133
|
"./shadcn/*": {
|
|
139
134
|
"svelte": "./src/shadcn/*/index.ts",
|
|
@@ -164,20 +159,17 @@
|
|
|
164
159
|
"dependencies": {
|
|
165
160
|
"@heyramzi/utils": "^1.3.3",
|
|
166
161
|
"@lucide/svelte": "^1.52.0",
|
|
162
|
+
"@shadcn-svelte/primitives": "^0.0.1",
|
|
167
163
|
"clsx": "^2.1.1",
|
|
168
|
-
"d3-scale": "^4.0.2",
|
|
169
|
-
"d3-shape": "^3.2.0",
|
|
170
|
-
"layerchart": "^2.5.1",
|
|
171
164
|
"marked": "^18.1.0",
|
|
172
165
|
"phosphor-svelte": "^3.1.0",
|
|
166
|
+
"shiki": "^4.5.0",
|
|
173
167
|
"tailwind-merge": "^3.7.0",
|
|
174
168
|
"tailwind-variants": "^3.3.1"
|
|
175
169
|
},
|
|
176
170
|
"devDependencies": {
|
|
177
171
|
"@internationalized/date": "^3.12.4",
|
|
178
172
|
"@sveltejs/vite-plugin-svelte": "^7.3.1",
|
|
179
|
-
"@types/d3-scale": "^4.0.9",
|
|
180
|
-
"@types/d3-shape": "^3.2.0",
|
|
181
173
|
"bits-ui": "^2.19.5",
|
|
182
174
|
"jsdom": "^30.1.2",
|
|
183
175
|
"mode-watcher": "^1.1.0",
|
package/scripts/sync-shadcn.mjs
CHANGED
|
@@ -42,12 +42,16 @@ const ALIASES = [
|
|
|
42
42
|
["$UTILS$.js", "@heyramzi/utils"],
|
|
43
43
|
["$UI$/", "../src/shadcn"],
|
|
44
44
|
["$HOOKS$/", "../src/shadcn/hooks"],
|
|
45
|
+
["$LIB$/", "../src/shadcn/hooks"],
|
|
45
46
|
];
|
|
46
47
|
|
|
47
48
|
/* Upstream target path -> ours, where the two differ. Everything else lands at
|
|
48
49
|
* src/shadcn/<target>. */
|
|
49
50
|
const REROUTE = {
|
|
50
51
|
"is-mobile/is-mobile.svelte.ts": "src/shadcn/hooks/is-mobile.svelte.ts",
|
|
52
|
+
"highlighter/highlighter.ts": "src/shadcn/hooks/highlighter.ts",
|
|
53
|
+
"use-highlighted-code/use-highlighted-code.svelte.ts":
|
|
54
|
+
"src/shadcn/hooks/use-highlighted-code.svelte.ts",
|
|
51
55
|
};
|
|
52
56
|
|
|
53
57
|
const DOCS = "https://raw.githubusercontent.com/huntabyte/shadcn-svelte/main/docs";
|
|
@@ -90,12 +94,17 @@ const NOT_SHIPPED = new Set([
|
|
|
90
94
|
// `cn` ships from @heyramzi/utils in vibe-kit, so a copy here would be a stray.
|
|
91
95
|
"utils",
|
|
92
96
|
"questionnaire",
|
|
93
|
-
// Every app chat runs on our ai/conversation, so a second scroller would be two answers to one job.
|
|
94
|
-
"message-scroller",
|
|
95
97
|
]);
|
|
96
98
|
|
|
97
99
|
/* No demo to take: `hooks`, `is-mobile` and `utils` are not components, and `sidebar` demos as a page block. */
|
|
98
|
-
const NO_DEMO = new Set([
|
|
100
|
+
const NO_DEMO = new Set([
|
|
101
|
+
"hooks",
|
|
102
|
+
"is-mobile",
|
|
103
|
+
"highlighter",
|
|
104
|
+
"use-highlighted-code",
|
|
105
|
+
"sidebar",
|
|
106
|
+
"utils",
|
|
107
|
+
]);
|
|
99
108
|
|
|
100
109
|
const adapt = (text) => ALIASES.reduce((acc, [from, to]) => acc.replaceAll(from, to), text);
|
|
101
110
|
const adaptDemo = (text) =>
|
package/src/ai/index.ts
CHANGED
|
@@ -1,21 +1,3 @@
|
|
|
1
|
-
// =========================
|
|
2
|
-
// CONVERSATION (scroll container)
|
|
3
|
-
// =========================
|
|
4
|
-
|
|
5
|
-
export {
|
|
6
|
-
Conversation,
|
|
7
|
-
ConversationContent,
|
|
8
|
-
ConversationEmptyState,
|
|
9
|
-
ConversationScrollButton,
|
|
10
|
-
StickToBottomContext,
|
|
11
|
-
getStickToBottomContext,
|
|
12
|
-
setStickToBottomContext,
|
|
13
|
-
type ConversationProps,
|
|
14
|
-
type ConversationContentProps,
|
|
15
|
-
type ConversationEmptyStateProps,
|
|
16
|
-
type ConversationScrollButtonProps,
|
|
17
|
-
} from "./conversation/index.js";
|
|
18
|
-
|
|
19
1
|
// =========================
|
|
20
2
|
// COMPOSER
|
|
21
3
|
// =========================
|
package/src/animations/README.md
CHANGED
|
@@ -53,8 +53,8 @@ One behaviour, one implementation. Where a component already did the job, the up
|
|
|
53
53
|
|
|
54
54
|
| Upstream | Where it lives instead |
|
|
55
55
|
| --- | --- |
|
|
56
|
-
| `toggle` | `shadcn/switch/switch.
|
|
57
|
-
| `checkbox-check` | `shadcn/checkbox/checkbox.
|
|
56
|
+
| `toggle` | `shadcn/switch/switch.svelte` — the thumb slide, on our switch. |
|
|
57
|
+
| `checkbox-check` | `shadcn/checkbox/checkbox.svelte` — the fill and border, on our checkbox. |
|
|
58
58
|
| `toast-open-close` | `shadcn/sonner` — svelte-sonner owns toast motion. |
|
|
59
59
|
| `banner-stacking` | `shadcn/sonner` — stacking and hover spread are what it does. |
|
|
60
60
|
| `matrix-dot-loader` | `ai/loading-state` — `PixelLoader` is the dot-grid loader. |
|
|
@@ -81,4 +81,4 @@ Every transition reads semantic CSS variables (`--modal-open-dur`, `--tilt-follo
|
|
|
81
81
|
|
|
82
82
|
So the scale is single-sourced when the design layer is imported, and the transition still runs at the shipped value when it is not. A value the scale has no token for — a 1000ms tilt return, a 90ms reel stagger — stays a literal rather than being rounded onto the nearest rung. Override any of them at `:root` or per-scope to retune without forking the CSS.
|
|
83
83
|
|
|
84
|
-
See the
|
|
84
|
+
See the `transitions-dev` skill in vibe-kit (`ai-doc/skills/design/motion/transitions-dev/SKILL.md`) for the full token list and the per-transition variables.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { Attachment } from "svelte/attachments";
|
|
2
|
+
|
|
3
|
+
/** The travelling highlight behind the active tab. Attach it to a span inside the list. */
|
|
4
|
+
|
|
5
|
+
// WHY attach to the pill, not the list: the pill finds its list via
|
|
6
|
+
// `parentElement`, so any strip that renders one span gets the motion.
|
|
7
|
+
|
|
8
|
+
// WHY measure: the active item's width is its label. The selector covers the
|
|
9
|
+
// three ways a strip marks it: a tab, a nav link, a toggle.
|
|
10
|
+
|
|
11
|
+
// Per transitions.dev `tabs-sliding`: first paint and resizes write the
|
|
12
|
+
// position with the transition off, so the pill snaps in instead of flying.
|
|
13
|
+
const ACTIVE = '[data-state="active"],[aria-current="page"],[aria-pressed="true"]';
|
|
14
|
+
|
|
15
|
+
export const slidingPill: Attachment<HTMLElement> = (pill) => {
|
|
16
|
+
const list = pill.parentElement;
|
|
17
|
+
if (!list) return;
|
|
18
|
+
|
|
19
|
+
let settled = false;
|
|
20
|
+
|
|
21
|
+
function move(animate: boolean) {
|
|
22
|
+
const active = list!.querySelector<HTMLElement>(ACTIVE);
|
|
23
|
+
if (!active) {
|
|
24
|
+
pill.style.opacity = "0";
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
pill.style.opacity = "1";
|
|
28
|
+
if (animate) {
|
|
29
|
+
pill.style.transform = `translateX(${active.offsetLeft}px)`;
|
|
30
|
+
pill.style.width = `${active.offsetWidth}px`;
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
// Suspend, write, reflow, restore. Without the reflow the browser
|
|
34
|
+
// coalesces the two writes and the snap animates anyway.
|
|
35
|
+
pill.style.transition = "none";
|
|
36
|
+
pill.style.transform = `translateX(${active.offsetLeft}px)`;
|
|
37
|
+
pill.style.width = `${active.offsetWidth}px`;
|
|
38
|
+
void pill.offsetWidth;
|
|
39
|
+
pill.style.transition = "";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Position first, THEN hand the highlight over. Swapped, one frame draws both
|
|
43
|
+
// the trigger's own background and a zero-width pill: a flicker on hydration.
|
|
44
|
+
move(false);
|
|
45
|
+
list.dataset.pill = "on";
|
|
46
|
+
settled = true;
|
|
47
|
+
|
|
48
|
+
// The selection lives in an attribute bits-ui owns, and a badge arriving
|
|
49
|
+
// changes a trigger's width without any attribute changing.
|
|
50
|
+
const attributes = new MutationObserver(() => move(settled));
|
|
51
|
+
attributes.observe(list, {
|
|
52
|
+
subtree: true,
|
|
53
|
+
childList: true,
|
|
54
|
+
attributes: true,
|
|
55
|
+
attributeFilter: ["data-state", "aria-current", "aria-pressed"],
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// A list that reflows (a rail collapsing, a font landing) re-measures
|
|
59
|
+
// without motion: nothing was chosen, so nothing should travel.
|
|
60
|
+
const resize = new ResizeObserver(() => move(false));
|
|
61
|
+
resize.observe(list);
|
|
62
|
+
|
|
63
|
+
return () => {
|
|
64
|
+
attributes.disconnect();
|
|
65
|
+
resize.disconnect();
|
|
66
|
+
delete list.dataset.pill;
|
|
67
|
+
};
|
|
68
|
+
};
|
|
@@ -3,6 +3,8 @@ export interface RevealThemeOptions {
|
|
|
3
3
|
skip?: boolean;
|
|
4
4
|
/** Wipe length in ms. */
|
|
5
5
|
duration?: number;
|
|
6
|
+
/** Wipe easing, any CSS easing value. */
|
|
7
|
+
easing?: string;
|
|
6
8
|
}
|
|
7
9
|
|
|
8
10
|
const DEFAULT_DURATION = 400;
|
|
@@ -20,7 +22,7 @@ export function revealTheme(
|
|
|
20
22
|
apply: () => void,
|
|
21
23
|
options: RevealThemeOptions = {},
|
|
22
24
|
): void {
|
|
23
|
-
const { skip = false, duration = DEFAULT_DURATION } = options;
|
|
25
|
+
const { skip = false, duration = DEFAULT_DURATION, easing = "ease-in-out" } = options;
|
|
24
26
|
const origin = event.currentTarget;
|
|
25
27
|
|
|
26
28
|
const prefersReduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
@@ -45,7 +47,7 @@ export function revealTheme(
|
|
|
45
47
|
transition.ready.then(() => {
|
|
46
48
|
document.documentElement.animate(
|
|
47
49
|
{ clipPath: [`circle(0px at ${x}px ${y}px)`, `circle(${endRadius}px at ${x}px ${y}px)`] },
|
|
48
|
-
{ duration, easing
|
|
50
|
+
{ duration, easing, pseudoElement: "::view-transition-new(root)" },
|
|
49
51
|
);
|
|
50
52
|
});
|
|
51
53
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn, type WithElementRef } from "@heyramzi/utils";
|
|
3
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
ref = $bindable(null),
|
|
7
|
+
class: className,
|
|
8
|
+
children,
|
|
9
|
+
...restProps
|
|
10
|
+
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<div
|
|
14
|
+
bind:this={ref}
|
|
15
|
+
data-slot="code-block-actions"
|
|
16
|
+
class={cn(
|
|
17
|
+
"-my-1 -me-1.5 gap-1 [[data-slot=code-block]>&]:m-0 [[data-slot=code-block]>&]:top-3 [[data-slot=code-block]>&]:end-3 group-data-[size=sm]/code-block:[[data-slot=code-block]>&]:top-2 group-data-[size=sm]/code-block:[[data-slot=code-block]>&]:end-2 [[data-slot=code-block]>&]:*:[button]:bg-background [[data-slot=code-block]>&]:*:[button]:ring-1 [[data-slot=code-block]>&]:*:[button]:ring-foreground/10 ms-auto flex shrink-0 items-center [[data-slot=code-block]>&]:absolute [[data-slot=code-block]>&]:z-10 [[data-slot=code-block]>&]:opacity-0 [[data-slot=code-block]>&]:transition-opacity [[data-slot=code-block]>&]:group-focus-within/code-block:opacity-100 [[data-slot=code-block]>&]:group-hover/code-block:opacity-100 [[data-slot=code-block]>&]:pointer-coarse:opacity-100",
|
|
18
|
+
className
|
|
19
|
+
)}
|
|
20
|
+
{...restProps}
|
|
21
|
+
>
|
|
22
|
+
{@render children?.()}
|
|
23
|
+
</div>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn, type WithElementRef } from "@heyramzi/utils";
|
|
3
|
+
import CodeBlockLine from "./code-block-line.svelte";
|
|
4
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
ref = $bindable(null),
|
|
8
|
+
class: className,
|
|
9
|
+
style,
|
|
10
|
+
code,
|
|
11
|
+
wrap = false,
|
|
12
|
+
lineNumbers = false,
|
|
13
|
+
startLine = 1,
|
|
14
|
+
children,
|
|
15
|
+
...restProps
|
|
16
|
+
}: WithElementRef<HTMLAttributes<HTMLPreElement>> & {
|
|
17
|
+
/** Plain text to render. Split into lines when `lineNumbers` is set. */
|
|
18
|
+
code?: string;
|
|
19
|
+
wrap?: boolean;
|
|
20
|
+
lineNumbers?: boolean;
|
|
21
|
+
startLine?: number;
|
|
22
|
+
} = $props();
|
|
23
|
+
|
|
24
|
+
// A `code` string is split into lines so each one can be numbered.
|
|
25
|
+
const lines = $derived(
|
|
26
|
+
code !== undefined && lineNumbers ? code.replace(/\n$/, "").split("\n") : null
|
|
27
|
+
);
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
{#snippet content()}
|
|
31
|
+
<code
|
|
32
|
+
data-slot="code-block-code"class=" grid min-h-lh min-w-max content-start in-data-numbered:grid-cols-[auto_1fr] in-data-wrap:min-w-0">
|
|
33
|
+
{#if lines}
|
|
34
|
+
{#each lines as line, index (index)}
|
|
35
|
+
<CodeBlockLine code={line} />
|
|
36
|
+
{/each}
|
|
37
|
+
{:else if code !== undefined}
|
|
38
|
+
{code}
|
|
39
|
+
{:else}
|
|
40
|
+
{@render children?.()}
|
|
41
|
+
{/if}
|
|
42
|
+
</code>
|
|
43
|
+
{/snippet}
|
|
44
|
+
|
|
45
|
+
<!-- Whitespace inside a <pre> is rendered, so its content is a snippet defined outside it, where Svelte trims whitespace as usual. -->
|
|
46
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
47
|
+
<pre
|
|
48
|
+
bind:this={ref}
|
|
49
|
+
data-slot="code-block-content"data-wrap={wrap || undefined}
|
|
50
|
+
data-numbered={lineNumbers || undefined}
|
|
51
|
+
dir="ltr"tabindex={0}
|
|
52
|
+
style="counter-reset: line {startLine - 1};{style ?? ''}"class={cn("px-4 py-4 leading-relaxed group-has-data-[slot=code-block-header]/code-block:pt-2 group-has-data-[slot=code-block-footer]/code-block:pb-2 group-data-[size=sm]/code-block:px-3 group-data-[size=sm]/code-block:py-3 group-data-[size=sm]/code-block:group-has-data-[slot=code-block-header]/code-block:pt-1.5 min-h-0 min-w-0 scroll-fade-x scrollbar-thin overflow-auto text-start font-mono outline-none data-wrap:scroll-fade-y data-wrap:wrap-break-word data-wrap:whitespace-pre-wrap",
|
|
53
|
+
className
|
|
54
|
+
)}
|
|
55
|
+
{...restProps}>{@render content()}</pre>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onDestroy } from "svelte";
|
|
3
|
+
import CheckIcon from "@lucide/svelte/icons/check";
|
|
4
|
+
import CopyIcon from "@lucide/svelte/icons/copy";
|
|
5
|
+
import { Button, type ButtonProps } from "../button/index.js";
|
|
6
|
+
import { cn } from "@heyramzi/utils";
|
|
7
|
+
|
|
8
|
+
let {
|
|
9
|
+
ref = $bindable(null),
|
|
10
|
+
class: className,
|
|
11
|
+
value,
|
|
12
|
+
variant = "ghost",
|
|
13
|
+
size = "sm",
|
|
14
|
+
onclick,
|
|
15
|
+
...restProps
|
|
16
|
+
}: Omit<ButtonProps, "onclick"> & {
|
|
17
|
+
value: string;
|
|
18
|
+
onclick?: (e: MouseEvent) => void;
|
|
19
|
+
} = $props();
|
|
20
|
+
|
|
21
|
+
let copied = $state(false);
|
|
22
|
+
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
23
|
+
|
|
24
|
+
function copy() {
|
|
25
|
+
navigator.clipboard
|
|
26
|
+
.writeText(value)
|
|
27
|
+
.then(() => {
|
|
28
|
+
copied = true;
|
|
29
|
+
clearTimeout(timeout);
|
|
30
|
+
timeout = setTimeout(() => (copied = false), 2000);
|
|
31
|
+
})
|
|
32
|
+
.catch(() => {});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
onDestroy(() => clearTimeout(timeout));
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<Button
|
|
39
|
+
bind:ref
|
|
40
|
+
data-slot="code-block-copy-button"
|
|
41
|
+
data-copied={copied || undefined}
|
|
42
|
+
{variant}
|
|
43
|
+
{size}
|
|
44
|
+
icon
|
|
45
|
+
aria-label={copied ? "Copied" : "Copy"}
|
|
46
|
+
class={cn(className)}
|
|
47
|
+
onclick={(e) => {
|
|
48
|
+
onclick?.(e);
|
|
49
|
+
copy();
|
|
50
|
+
}}
|
|
51
|
+
{...restProps}
|
|
52
|
+
>
|
|
53
|
+
{#if copied}
|
|
54
|
+
<CheckIcon />
|
|
55
|
+
{:else}
|
|
56
|
+
<CopyIcon />
|
|
57
|
+
{/if}
|
|
58
|
+
</Button>
|
|
59
|
+
<span aria-live="polite" class="sr-only">{copied ? "Copied" : ""}</span>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn, type WithElementRef } from "@heyramzi/utils";
|
|
3
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
ref = $bindable(null),
|
|
7
|
+
class: className,
|
|
8
|
+
children,
|
|
9
|
+
...restProps
|
|
10
|
+
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<div
|
|
14
|
+
bind:this={ref}
|
|
15
|
+
data-slot="code-block-footer"
|
|
16
|
+
class={cn("gap-2 px-4 pb-4 text-muted-foreground group-data-[size=sm]/code-block:px-3 group-data-[size=sm]/code-block:pb-3 flex min-w-0 shrink-0 items-center", className)}
|
|
17
|
+
{...restProps}
|
|
18
|
+
>
|
|
19
|
+
{@render children?.()}
|
|
20
|
+
</div>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn, type WithElementRef } from "@heyramzi/utils";
|
|
3
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
ref = $bindable(null),
|
|
7
|
+
class: className,
|
|
8
|
+
children,
|
|
9
|
+
...restProps
|
|
10
|
+
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<div
|
|
14
|
+
bind:this={ref}
|
|
15
|
+
data-slot="code-block-header"
|
|
16
|
+
class={cn(
|
|
17
|
+
"gap-2 px-4 pt-4 text-muted-foreground group-data-[variant=destructive]/code-block:text-destructive group-data-[size=sm]/code-block:px-3 group-data-[size=sm]/code-block:pt-3 flex min-w-0 shrink-0 items-center *:has-[>[data-slot=code-block-title]]:min-w-0",
|
|
18
|
+
className
|
|
19
|
+
)}
|
|
20
|
+
{...restProps}
|
|
21
|
+
>
|
|
22
|
+
{@render children?.()}
|
|
23
|
+
</div>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import { tv, type VariantProps } from "tailwind-variants";
|
|
3
|
+
|
|
4
|
+
export const codeBlockLineVariants = tv({
|
|
5
|
+
base: "-mx-4 px-4 in-data-numbered:gap-x-3 before:min-w-6 before:text-end before:text-muted-foreground after:start-1 after:text-muted-foreground group-data-[size=sm]/code-block:-mx-3 group-data-[size=sm]/code-block:px-3 relative block min-h-lh after:absolute after:top-0 after:select-none in-data-numbered:col-span-2 in-data-numbered:grid in-data-numbered:grid-cols-subgrid in-data-numbered:[counter-increment:line] in-data-numbered:before:content-[counter(line)] in-data-numbered:before:select-none in-data-numbered:supports-[content:counter(line)_/_'']:before:content-[counter(line)_/_'']",
|
|
6
|
+
variants: {
|
|
7
|
+
variant: {
|
|
8
|
+
default: "",
|
|
9
|
+
added: "bg-muted after:content-['+']",
|
|
10
|
+
removed: "bg-destructive/10 after:text-destructive after:content-['−']",
|
|
11
|
+
highlighted: "bg-muted",
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
defaultVariants: {
|
|
15
|
+
variant: "default",
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export type CodeBlockLineVariant = VariantProps<typeof codeBlockLineVariants>["variant"];
|
|
20
|
+
|
|
21
|
+
// One highlighted token, such as one returned by `useHighlightedCode`.
|
|
22
|
+
export type CodeBlockLineToken = {
|
|
23
|
+
content: string;
|
|
24
|
+
style?: string;
|
|
25
|
+
class?: string;
|
|
26
|
+
};
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<script lang="ts">
|
|
30
|
+
import { cn, type WithElementRef } from "@heyramzi/utils";
|
|
31
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
32
|
+
|
|
33
|
+
let {
|
|
34
|
+
ref = $bindable(null),
|
|
35
|
+
class: className,
|
|
36
|
+
variant = "default",
|
|
37
|
+
code,
|
|
38
|
+
children,
|
|
39
|
+
...restProps
|
|
40
|
+
}: WithElementRef<HTMLAttributes<HTMLSpanElement>> & {
|
|
41
|
+
variant?: CodeBlockLineVariant;
|
|
42
|
+
/** The line as plain text, or as highlighted tokens. When it is set, `children` is ignored. */
|
|
43
|
+
code?: string | CodeBlockLineToken[];
|
|
44
|
+
} = $props();
|
|
45
|
+
|
|
46
|
+
const newline = "\n";
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<!-- An empty line holds a newline so copying a selection keeps blank lines. -->
|
|
50
|
+
<span
|
|
51
|
+
bind:this={ref}
|
|
52
|
+
data-slot="code-block-line"data-variant={variant}
|
|
53
|
+
class={cn(codeBlockLineVariants({ variant }), className)}
|
|
54
|
+
{...restProps}
|
|
55
|
+
>
|
|
56
|
+
<span data-slot="code-block-line-content"class="min-w-0">
|
|
57
|
+
{#if typeof code ==="string"}
|
|
58
|
+
{code || newline}
|
|
59
|
+
{:else if code?.length}
|
|
60
|
+
{#each code as token, index (index)}
|
|
61
|
+
<span style={token.style} class={token.class}>{token.content}</span>
|
|
62
|
+
{/each}
|
|
63
|
+
{:else if code}
|
|
64
|
+
{newline}
|
|
65
|
+
{:else if children}
|
|
66
|
+
{@render children()}
|
|
67
|
+
{:else}
|
|
68
|
+
{newline}
|
|
69
|
+
{/if}
|
|
70
|
+
</span>
|
|
71
|
+
</span>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn, type WithElementRef } from "@heyramzi/utils";
|
|
3
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
ref = $bindable(null),
|
|
7
|
+
class: className,
|
|
8
|
+
children,
|
|
9
|
+
...restProps
|
|
10
|
+
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<div
|
|
14
|
+
bind:this={ref}
|
|
15
|
+
data-slot="code-block-title"
|
|
16
|
+
class={cn(
|
|
17
|
+
"font-sans text-sm font-medium text-foreground group-data-[variant=destructive]/code-block:text-destructive *:[svg]:me-1.5 [&_svg:not([class*='size-'])]:size-4 group-data-[size=sm]/code-block:text-xs group-data-[size=sm]/code-block:[&_svg:not([class*='size-'])]:size-3.5 min-w-0 truncate [&_svg]:pointer-events-none [&_svg]:inline-block [&_svg]:align-middle",
|
|
18
|
+
className
|
|
19
|
+
)}
|
|
20
|
+
{...restProps}
|
|
21
|
+
>
|
|
22
|
+
{@render children?.()}
|
|
23
|
+
</div>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import { tv, type VariantProps } from "tailwind-variants";
|
|
3
|
+
|
|
4
|
+
export const codeBlockVariants = tv({
|
|
5
|
+
base: "rounded-lg text-sm data-[size=sm]:text-xs has-[>[data-slot=code-block-content]:focus-visible]:ring-3 has-[>[data-slot=code-block-content]:focus-visible]:ring-ring/50 group/code-block relative flex w-full min-w-0 flex-col overflow-hidden",
|
|
6
|
+
variants: {
|
|
7
|
+
variant: {
|
|
8
|
+
default: "border border-input text-foreground dark:bg-input/30 has-[>[data-slot=code-block-content]:focus-visible]:border-ring",
|
|
9
|
+
destructive: "border border-destructive/30 text-destructive dark:border-destructive/40",
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
defaultVariants: {
|
|
13
|
+
variant: "default",
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export type CodeBlockVariant = VariantProps<typeof codeBlockVariants>["variant"];
|
|
18
|
+
export type CodeBlockSize = "default" | "sm";
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<script lang="ts">
|
|
22
|
+
import { cn, type WithElementRef } from "@heyramzi/utils";
|
|
23
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
24
|
+
|
|
25
|
+
let {
|
|
26
|
+
ref = $bindable(null),
|
|
27
|
+
class: className,
|
|
28
|
+
variant = "default",
|
|
29
|
+
size = "default",
|
|
30
|
+
children,
|
|
31
|
+
...restProps
|
|
32
|
+
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
|
|
33
|
+
variant?: CodeBlockVariant;
|
|
34
|
+
size?: CodeBlockSize;
|
|
35
|
+
} = $props();
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<div
|
|
39
|
+
bind:this={ref}
|
|
40
|
+
data-slot="code-block"
|
|
41
|
+
data-variant={variant}
|
|
42
|
+
data-size={size}
|
|
43
|
+
class={cn(codeBlockVariants({ variant }), className)}
|
|
44
|
+
{...restProps}
|
|
45
|
+
>
|
|
46
|
+
{@render children?.()}
|
|
47
|
+
</div>
|