@voltro/ui-shadcn 0.66.1 → 0.68.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.
@@ -0,0 +1,263 @@
1
+ /* Voltro Signal: opt-in brand theme. Import after tokens.css. */
2
+ :root {
3
+ --signal-violet: #7c3aed;
4
+ --signal-header-height: 112px;
5
+ --signal-light: #6d28d9;
6
+ --signal-ease: cubic-bezier(.16,1,.3,1);
7
+ --background: #faf9fc;
8
+ --foreground: #21192d;
9
+ --card: #ffffff;
10
+ --card-foreground: #21192d;
11
+ --popover: #ffffff;
12
+ --popover-foreground: #21192d;
13
+ --primary: #7c3aed;
14
+ --primary-foreground: #ffffff;
15
+ --secondary: #eee9f4;
16
+ --secondary-foreground: #38264e;
17
+ --muted: #f2eef7;
18
+ --muted-foreground: #6b5c7c;
19
+ --accent: #eee6fc;
20
+ --accent-foreground: #5b21b6;
21
+ --border: #ded6e9;
22
+ --input: #d0c4df;
23
+ --ring: #7c3aed;
24
+ --sidebar: #f3eff8;
25
+ --sidebar-foreground: #38264e;
26
+ --sidebar-primary: #7c3aed;
27
+ --sidebar-primary-foreground: #fff;
28
+ --sidebar-accent: #e7ddf5;
29
+ --sidebar-accent-foreground: #5b21b6;
30
+ --sidebar-border: #ded6e9;
31
+ --sidebar-ring: #7c3aed;
32
+ --radius: .375rem;
33
+ --signal-accent-ink: #6d28d9;
34
+ --signal-ground: #f4effa;
35
+ --signal-ink: #21192d;
36
+ --signal-muted: #6b5c7c;
37
+ --signal-line: #d4c7e5;
38
+ --signal-detail: #735094;
39
+ --signal-field-a: #e4d5f5;
40
+ --signal-field-b: #efe5fb;
41
+ --signal-field-base: #faf9fc;
42
+ --signal-field-floor: #faf9fc;
43
+ }
44
+ .dark {
45
+ --signal-light: #a874ff;
46
+ --signal-ground: #0b0911;
47
+ --signal-ink: #f5f3fa;
48
+ --signal-muted: #b9afc9;
49
+ --signal-line: #342b43;
50
+ --signal-detail: #c2a1e8;
51
+ --signal-field-a: #29193d;
52
+ --signal-field-b: #1b1430;
53
+ --signal-field-base: #100c19;
54
+ --signal-field-floor: linear-gradient(145deg,#100c18 12%,#110c1e 55%,#0e0b15 100%);
55
+ --background: #100c19;
56
+ --foreground: #f5f3fa;
57
+ --card: #17111f;
58
+ --card-foreground: #f5f3fa;
59
+ --popover: #1c1427;
60
+ --popover-foreground: #f5f3fa;
61
+ --primary: #7c3aed;
62
+ --primary-foreground: #ffffff;
63
+ --secondary: #251b33;
64
+ --secondary-foreground: #e7dbf7;
65
+ --muted: #201828;
66
+ --muted-foreground: #b9afc9;
67
+ --accent: #2c1d40;
68
+ --accent-foreground: #d9bcff;
69
+ --border: #342b43;
70
+ --input: #584566;
71
+ --ring: #b995ff;
72
+ --sidebar: #140f1d;
73
+ --sidebar-foreground: #d5c9e4;
74
+ --sidebar-primary: #a874ff;
75
+ --sidebar-primary-foreground: #170c29;
76
+ --sidebar-accent: #2b1e3c;
77
+ --sidebar-accent-foreground: #e2ccff;
78
+ --sidebar-border: #342b43;
79
+ --sidebar-ring: #b995ff;
80
+ --signal-accent-ink: #c19aff;
81
+ }
82
+ @layer components {
83
+ :where(a, button, input, textarea, select, summary):focus-visible {
84
+ outline: 2px solid var(--ring);
85
+ outline-offset: 4px;
86
+ }
87
+ :where(input, textarea) { caret-color: var(--signal-accent-ink); }
88
+ ::selection { background: #6530ba; color: #fff; }
89
+ :where(table, [role="table"]) { font-variant-numeric: tabular-nums; }
90
+ }
91
+ .signal-stage { position: relative; isolation: isolate; }
92
+ .signal-atmosphere{position:absolute;inset:0;z-index:-1;overflow:hidden;pointer-events:none;background:radial-gradient(ellipse at 89% 22%,var(--signal-field-a) 0%,transparent 48%),radial-gradient(ellipse at 8% 76%,var(--signal-field-b) 0%,transparent 44%),var(--signal-field-floor)}
93
+ .field-contours{position:absolute;width:max(100%,1400px);height:auto;top:0;right:0;max-width:none}
94
+ .field-light{position:absolute;inset:20% 0 8% 0;background:radial-gradient(ellipse at 66% 49%,#7c3aed26 0%,#a874ff0b 31%,transparent 65%);opacity:.45;transition:opacity 650ms ease-out}
95
+ .signal-stage:has(.signal-demo[data-state=running]) .field-light{opacity:1}
96
+ .field-highlight{stroke-dasharray:7 93;stroke-dashoffset:-34;opacity:.22}
97
+ .signal-stage:has(.signal-demo[data-state=running]) .field-highlight{animation:field-response 2900ms ease-out 1}
98
+ @keyframes field-response{0%{stroke-dashoffset:-34;opacity:.22}40%{opacity:.55}100%{stroke-dashoffset:-78;opacity:0}}
99
+ .field-ambient-trace{stroke-dasharray:7 93;opacity:0;animation:field-travel 14s linear infinite;animation-play-state:paused}
100
+ .trace-1{animation-delay:-3s}.trace-2{animation-delay:-6s}.trace-3{animation-delay:-9s}.trace-4{animation-delay:-11s;animation-duration:17s}
101
+ .signal-atmosphere[data-motion=running] .field-ambient-trace{animation-play-state:running}
102
+ @keyframes field-travel{0%{stroke-dashoffset:3;opacity:0}10%{opacity:.32}78%{opacity:.32}100%{stroke-dashoffset:-100;opacity:0}}
103
+
104
+ .signal-stage .signal-atmosphere { mask-image: linear-gradient(#000 0%, #000 85%, transparent 100%); }
105
+ .signal-shell { width: min(100% - 88px, 1536px); margin-inline: auto; }
106
+ /* One opening rhythm for Framework, Cloud and the documentation index. */
107
+ .signal-opener { padding-top: 50px; }
108
+ .signal-opener-title { margin: 0; position: relative; z-index: 1; font-size: clamp(48px, 6.15vw, 96px); font-weight: 700; line-height: 1.06; letter-spacing: -.04em; }
109
+ .signal-opener-title > span { display: block; }
110
+ .signal-opener-accent { color: var(--signal-light); margin-top: 9px; }
111
+ .signal-opener-description { position: relative; z-index: 1; max-width: 75ch; margin: 27px 0 0; font-size: clamp(18px, 1.64vw, 25px); line-height: 1.48; letter-spacing: -.023em; color: var(--signal-ink); }
112
+ .signal-opener-description > span { display: block; }
113
+ .signal-opener-actions { position: relative; z-index: 2; display: flex; flex-wrap: wrap; align-items: center; gap: 16px 39px; width: fit-content; margin-top: 29px; }
114
+ .signal-opener-secondary { display: inline-flex; align-items: center; gap: 12px; min-height: 44px; font-size: 15px; color: var(--signal-ink); }
115
+ .signal-opener-secondary > span { position: relative; padding-block: 7px; }
116
+ .signal-opener-secondary > span::after { content: ''; position: absolute; left: 0; right: 0; bottom: 1px; height: 1px; background: var(--signal-light); transform-origin: left; transform: scaleX(.22); transition: transform 220ms var(--signal-ease); }
117
+ .signal-opener-actions .signal-opener-secondary svg { width: 16px; height: 16px; color: var(--signal-light); transition: transform 200ms var(--signal-ease); }
118
+ .signal-opener-secondary:hover > span::after, .signal-opener-secondary:focus-visible > span::after { transform: scaleX(1); }
119
+ .signal-opener-secondary:hover svg, .signal-opener-secondary:focus-visible svg { transform: translate(2px, -2px); }
120
+ .signal-opener-actions .signal-primary { position: relative; isolation: isolate; }
121
+ .signal-opener-actions .signal-arrow svg { width: 22px; height: 22px; }
122
+ .signal-opener-actions .signal-primary::after { content: ''; position: absolute; height: 2px; bottom: 0; left: 0; right: 0; background: #d5beff; transform: scaleX(0); transform-origin: left; transition: transform 280ms var(--signal-ease); }
123
+ .signal-opener-actions .signal-primary:hover::after, .signal-opener-actions .signal-primary:focus-visible::after { transform: scaleX(1); }
124
+ .signal-opener-actions .signal-primary:active svg { transform: translateX(2px); }
125
+ @media (min-width: 1600px) { .signal-opener { padding-top: 70px; } }
126
+ @media (max-width: 1150px) {
127
+ .signal-opener { padding-top: 40px; }
128
+ .signal-opener-title { font-size: clamp(48px, 6.1vw, 75px); }
129
+ }
130
+ @media (max-width: 760px) {
131
+ .signal-opener { padding-top: 29px; }
132
+ .signal-opener-title { font-size: clamp(37px, 7.5vw, 57px); line-height: 1.1; letter-spacing: -.035em; }
133
+ .signal-opener-accent { margin-top: 5px; }
134
+ .signal-opener-description { max-width: 40ch; margin-top: 22px; font-size: 16px; line-height: 1.5; }
135
+ .signal-opener-description > span { display: inline; }
136
+ .signal-opener-description > span + span::before { content: ' '; }
137
+ .signal-opener-actions { width: 100%; gap: 12px 22px; margin-top: 24px; }
138
+ .signal-opener-secondary { gap: 5px; font-size: 13px; }
139
+ .signal-opener-actions .signal-opener-secondary svg { width: 14px; height: 14px; }
140
+ }
141
+ @media (prefers-reduced-motion: reduce) {
142
+ .signal-opener-secondary > span::after, .signal-opener-actions .signal-primary::after { transition: none; }
143
+ .signal-opener-actions svg { transition: none; }
144
+ .signal-opener-actions a:hover svg, .signal-opener-actions a:focus-visible svg, .signal-opener-actions a:active svg { transform: none; }
145
+ }
146
+ .signal-primary {
147
+ display: inline-flex; align-items: stretch; min-height: 54px; width: fit-content;
148
+ background: #7c3aed linear-gradient(115deg, #6d28d9, #7c3aed 48%, #8a49ed);
149
+ background-size: 140% 100%; color: #fff; border-radius: 4px;
150
+ box-shadow: 0 5px 20px -9px #7c3aed66; overflow: hidden;
151
+ font-size: 1rem; font-weight: 500; text-decoration: none;
152
+ transition: background-position 280ms var(--signal-ease), box-shadow 240ms var(--signal-ease);
153
+ }
154
+ .signal-primary > span:first-child { padding: 14px 23px; display: flex; align-items: center; }
155
+ .signal-primary .signal-arrow { width: 55px; border-left: 1px solid #a57bdd; display: grid; place-items: center; }
156
+ .signal-primary svg { width: 22px; height: 22px; transition: transform 220ms var(--signal-ease); }
157
+ .signal-primary:hover, .signal-primary:focus-visible { background-position: 100% 50%; box-shadow: 0 8px 30px -8px #7c3aed7a; }
158
+ .signal-primary:hover svg, .signal-primary:focus-visible svg { transform: translateX(4px); }
159
+ .signal-text-link { display: inline-flex; gap: 10px; align-items: center; min-height: 44px; text-underline-offset: 8px; text-decoration-color: var(--signal-light); }
160
+ .signal-text-link:hover, .signal-text-link:focus-visible { text-decoration-line: underline; }
161
+ .signal-text-link svg { width: 16px; height: 16px; color: var(--signal-accent-ink); }
162
+ .signal-motion-toggle { display: inline-flex; align-items: center; gap: 7px; min-height: 44px; color: var(--muted-foreground); font-size: 12px; background: none; border: 0; cursor: pointer; }
163
+ .signal-motion-toggle svg { width: 16px; height: 16px; }
164
+ .signal-motion-toggle:hover { color: var(--signal-accent-ink); }
165
+ @media (max-width: 1150px) { .signal-shell { width: calc(100% - 56px); } }
166
+ @media (max-width: 760px) {
167
+ .signal-shell { width: calc(100% - 40px); }
168
+ .field-contours { width: 1000px; right: -390px; top: 50px; }
169
+ .signal-atmosphere { background: radial-gradient(ellipse at 100% 24%, var(--signal-field-a), transparent 55%), radial-gradient(ellipse at 0% 80%, var(--signal-field-b), transparent 48%), var(--signal-field-base); }
170
+ .signal-primary { min-height: 50px; font-size: 14px; }
171
+ .signal-primary > span:first-child { padding-inline: 18px; }
172
+ .signal-primary .signal-arrow { width: 44px; }
173
+ }
174
+ @media (prefers-reduced-motion: reduce) {
175
+ .field-ambient-trace { animation: none; }
176
+ .signal-primary, .signal-primary svg { transition: none; }
177
+ .signal-motion-toggle { display: none; }
178
+ html { scroll-behavior: auto; }
179
+ }
180
+ @media (forced-colors: active) {
181
+ .signal-atmosphere { display: none; }
182
+ .signal-primary { border: 1px solid ButtonText; }
183
+ }
184
+ .signal-masthead { position: relative; z-index: 40; color: var(--foreground); background: transparent; transition: background-color 180ms var(--signal-ease); }
185
+ .signal-masthead-sticky { position: sticky; top: 0; }
186
+ .signal-masthead[data-scrolled=true] { background: var(--background); }
187
+ .signal-header { min-height: var(--signal-header-height); display: flex; align-items: center; justify-content: space-between; position: relative; z-index: 20; gap: 24px; }
188
+ .signal-brand { flex: none; }
189
+ .signal-brand .voltro-wordmark { --voltro-wordmark-size: 42px; }
190
+ .signal-brand > a { display: inline-flex; align-items: center; gap: 8px; min-height: 44px; }
191
+ .signal-brand svg { flex: none; }
192
+ .signal-navigation-area { display: flex; align-items: center; gap: 24px; }
193
+ .signal-navigation { position: relative; display: flex; align-items: center; padding: 5px; gap: 5px; border: 1px solid var(--border); border-radius: 7px; background: var(--background); }
194
+ .signal-nav-marker { position: absolute; inset-block: 5px; left: 5px; width: 100px; border-radius: 2px; background: var(--secondary); transform-origin: left; transition: transform 240ms var(--signal-ease); pointer-events: none; }
195
+ .signal-nav-item { border: 0; background: none; position: relative; display: flex; align-items: center; gap: 10px; padding: 11px 17px; font-size: 14px; font-weight: 500; min-height: 44px; white-space: nowrap; color: var(--foreground); }
196
+ .signal-nav-dot { width: 5px; height: 5px; background: var(--signal-light); flex: none; }
197
+ .signal-header svg:not(.voltro-logo) { stroke-linecap: round; stroke-linejoin: round; }
198
+ .signal-nav-item > svg, .signal-menu-links svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.5; color: var(--muted-foreground); transition: transform 180ms var(--signal-ease); }
199
+ .signal-nav-item[aria-expanded=true] > svg { transform: rotate(180deg); }
200
+ .signal-nav-item[href]:hover > svg { transform: translate(2px, -2px); }
201
+ .signal-header-utilities { display: flex; align-items: center; gap: 12px; }
202
+ .signal-control { display: inline-flex; flex: none; align-items: center; justify-content: center; gap: 10px; min-height: 56px; padding: 0 16px; border: 1px solid var(--border); border-radius: 7px; background: var(--background); color: var(--foreground); font-size: 14px; font-weight: 500; line-height: 1.4; white-space: nowrap; cursor: pointer; text-decoration: none; transition: color 180ms var(--signal-ease), border-color 180ms var(--signal-ease), background-color 180ms var(--signal-ease); }
203
+ .signal-control > svg { width: 18px; height: 18px; flex: none; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
204
+ .signal-control:hover, .signal-control[data-state=open] { border-color: var(--input); background: var(--secondary); }
205
+ .signal-preferences { padding-inline: 12px; border-color: transparent; background: transparent; color: var(--muted-foreground); font-size: 12px; }
206
+ .signal-preferences:hover, .signal-preferences[data-state=open] { color: var(--foreground); }
207
+ .signal-header-action { line-height: 1.5; }
208
+ .signal-header-action > svg { width: 16px; height: 16px; color: var(--signal-accent-ink); transition: transform 180ms var(--signal-ease); }
209
+ .signal-header-action:hover > svg { transform: translateX(3px); }
210
+ .signal-search { width: 224px; justify-content: flex-start; color: var(--muted-foreground); font-weight: 400; }
211
+ .signal-search kbd { margin-left: auto; padding: 2px 5px; border: 1px solid var(--border); border-radius: 3px; font-family: inherit; font-size: 11px; line-height: 1.4; }
212
+ .signal-search:hover { color: var(--foreground); }
213
+ @media(max-width:1250px) { .signal-search { width: 56px; padding: 0; justify-content: center; } .signal-search > span, .signal-search kbd { display: none; } }
214
+ .signal-menu { position: absolute; right: 100px; top: calc(100% - 16px); width: 570px; padding: 26px; display: grid; grid-template-columns: 1fr 1.4fr; gap: 28px; background: var(--popover); color: var(--popover-foreground); border: 1px solid var(--input); border-radius: 7px; }
215
+ .signal-menu[hidden] { display: none; }
216
+ .signal-menu-intro { font-size: 22px; font-weight: 500; line-height: 1.25; letter-spacing: -.025em; }
217
+ .signal-menu-links { display: flex; flex-direction: column; }
218
+ .signal-menu-links a { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 13px 0; border-bottom: 1px solid var(--border); font-size: 14px; }
219
+ .signal-menu-links a:first-child { padding-top: 0; }
220
+ .signal-menu-links a:last-child { border: 0; padding-bottom: 0; }
221
+ .signal-menu-links a:hover { color: var(--signal-accent-ink); }
222
+ .signal-menu-links small { display: block; font-size: 12px; color: var(--muted-foreground); margin-top: 5px; }
223
+ @media(max-width:1150px) { :root { --signal-header-height: 96px; } .signal-navigation-area { gap: 24px; } .signal-nav-item { padding-inline: 12px; } }
224
+ @media(max-width:900px) {
225
+ :root { --signal-header-height: 158px; }
226
+ .signal-header { padding-block: 18px; align-items: center; flex-wrap: wrap; gap: 16px; }
227
+ .signal-brand .voltro-wordmark { --voltro-wordmark-size: 32px; display: grid; grid-template-columns: auto auto; row-gap: 5px; }
228
+ .signal-brand .voltro-wordmark > svg { grid-row: 1 / 3; }
229
+ .signal-brand .voltro-wordmark-label, .signal-brand .voltro-product-badge { grid-column: 2; justify-self: start; }
230
+ .signal-navigation-area { display: contents; }
231
+ .signal-navigation { order: 3; width: 100%; gap: 3px; }
232
+ .signal-nav-item { flex: 1; justify-content: center; padding: 7px; font-size: 12px; gap: 7px; }
233
+ .signal-header-utilities { margin-left: auto; }
234
+ .signal-control { min-height: 44px; font-size: 12px; padding-inline: 12px; border-radius: 5px; }
235
+ .signal-search, .signal-preferences { width: 44px; padding: 0; }
236
+ .signal-preferences > span { display: none; }
237
+ .signal-menu { top: 100%; right: 0; width: 100%; grid-template-columns: 1fr; padding: 20px; gap: 21px; }
238
+ .signal-menu-intro { font-size: 20px; }
239
+ .signal-menu-links a { min-height: 55px; }
240
+ }
241
+ @media(prefers-reduced-motion:reduce) { .signal-masthead, .signal-nav-marker, .signal-nav-item > svg, .signal-control, .signal-header-action > svg { transition: none; } }
242
+
243
+ .signal-skip { position: fixed; top: 10px; left: 20px; padding: 12px 20px; background: var(--foreground); color: var(--background); z-index: 100; transform: translateY(-160%); }
244
+ .signal-skip:focus { transform: none; }
245
+
246
+ .signal-operate h1 { letter-spacing: -.03em; }
247
+ .signal-operate h2 { letter-spacing: -.02em; }
248
+ .signal-operate [data-sidebar="sidebar"], .signal-operate aside { background-color: var(--sidebar); }
249
+ .signal-operate [aria-current="page"], .signal-operate [data-active="true"] { color: var(--sidebar-accent-foreground); background-color: var(--sidebar-accent); }
250
+ .signal-operate [role="tab"][aria-selected="true"] { color: var(--signal-accent-ink); }
251
+ .signal-operate :where(td, th) { padding-block: .8rem; }
252
+ .signal-operate :where(tbody tr) { transition: background-color 140ms; }
253
+ .signal-operate :where(tbody tr):hover { background-color: var(--muted); }
254
+ .signal-operate :where(input, select, textarea) { border-radius: 4px; }
255
+ @media(prefers-reduced-motion:reduce) { .signal-operate :where(tbody tr) { transition: none; } }
256
+
257
+ .text-primary { color: var(--signal-accent-ink); }
258
+
259
+ /* The marketing close repeats the hero's violet action and plum surface. */
260
+ .signal-marketing-close { background: var(--signal-ground); backdrop-filter: none; }
261
+ .signal-marketing-close::before { background: radial-gradient(ellipse at 20% 30%, #7c3aed29, transparent 65%); }
262
+ .signal-marketing-close .group { background: linear-gradient(115deg, #6d28d9, #7c3aed 48%, #8a49ed); color: #fff; border-radius: 4px; box-shadow: 0 5px 20px -9px #7c3aed66; }
263
+ @media(max-width:760px) { .signal-marketing-close { padding: 40px 24px; } }
package/dist/tokens.css CHANGED
@@ -51,6 +51,9 @@
51
51
  --accent-foreground: oklch(0 0 0);
52
52
  --destructive: oklch(0.63 0.19 23.03);
53
53
  --destructive-foreground: oklch(1 0 0);
54
+ --success: #167252;
55
+ --warning: #805e00;
56
+ --info: #2563b4;
54
57
  --border: oklch(0.92 0 0);
55
58
  --input: oklch(0.94 0 0);
56
59
  --ring: oklch(0 0 0);
@@ -111,6 +114,9 @@
111
114
  --accent-foreground: oklch(1 0 0);
112
115
  --destructive: oklch(0.69 0.2 23.91);
113
116
  --destructive-foreground: oklch(0 0 0);
117
+ --success: #62cda1;
118
+ --warning: #e4ba65;
119
+ --info: #86b7f2;
114
120
  --border: oklch(0.26 0 0);
115
121
  --input: oklch(0.32 0 0);
116
122
  --ring: oklch(0.72 0 0);
@@ -173,6 +179,9 @@
173
179
  --color-accent-foreground: var(--accent-foreground);
174
180
  --color-destructive: var(--destructive);
175
181
  --color-destructive-foreground: var(--destructive-foreground);
182
+ --color-success: var(--success);
183
+ --color-warning: var(--warning);
184
+ --color-info: var(--info);
176
185
  --color-border: var(--border);
177
186
  --color-input: var(--input);
178
187
  --color-ring: var(--ring);
@@ -0,0 +1,207 @@
1
+ import { cn as e } from "./cn.js";
2
+ import { useId as t } from "react";
3
+ import { Fragment as n, jsx as r, jsxs as i } from "react/jsx-runtime";
4
+ import { cva as a } from "class-variance-authority";
5
+ //#region src/primitives/badge.tsx
6
+ var o = a("inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 [&>svg]:pointer-events-none gap-1 transition-[color,box-shadow] overflow-hidden focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", {
7
+ variants: { variant: {
8
+ default: "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
9
+ secondary: "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
10
+ destructive: "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90",
11
+ outline: "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground"
12
+ } },
13
+ defaultVariants: { variant: "default" }
14
+ }), s = ({ className: t, variant: n, children: i, ...a }) => /* @__PURE__ */ r("span", {
15
+ "data-slot": "badge",
16
+ className: e(o({ variant: n }), t),
17
+ ...a,
18
+ children: i
19
+ }), c = "M28 5 13 27 22 26 20 43 35 21 26 22Z", l = "M28 5 13 27 22 26 20 43Z", u = "M28 5 20 43 35 21 26 22Z", d = "#7C3AED", f = ({ size: a = 32, variant: o = "gradient", title: s, className: d, ...f }) => {
20
+ let p = t(), m = `vmF-${p}`, h = `vmB-${p}`;
21
+ return /* @__PURE__ */ r("svg", {
22
+ width: a,
23
+ height: a,
24
+ viewBox: "0 0 48 48",
25
+ fill: "none",
26
+ xmlns: "http://www.w3.org/2000/svg",
27
+ ...s === void 0 ? { "aria-hidden": !0 } : {
28
+ role: "img",
29
+ "aria-label": s
30
+ },
31
+ className: e("inline-block shrink-0", d),
32
+ ...f,
33
+ children: o === "gradient" ? /* @__PURE__ */ i(n, { children: [
34
+ /* @__PURE__ */ i("defs", { children: [/* @__PURE__ */ i("linearGradient", {
35
+ id: m,
36
+ x1: "13",
37
+ y1: "5",
38
+ x2: "33",
39
+ y2: "43",
40
+ gradientUnits: "userSpaceOnUse",
41
+ children: [/* @__PURE__ */ r("stop", { stopColor: "#A874FF" }), /* @__PURE__ */ r("stop", {
42
+ offset: "1",
43
+ stopColor: "#7C3AED"
44
+ })]
45
+ }), /* @__PURE__ */ i("linearGradient", {
46
+ id: h,
47
+ x1: "22",
48
+ y1: "5",
49
+ x2: "35",
50
+ y2: "43",
51
+ gradientUnits: "userSpaceOnUse",
52
+ children: [/* @__PURE__ */ r("stop", { stopColor: "#6D28D9" }), /* @__PURE__ */ r("stop", {
53
+ offset: "1",
54
+ stopColor: "#4C1D95"
55
+ })]
56
+ })] }),
57
+ /* @__PURE__ */ r("path", {
58
+ d: l,
59
+ fill: `url(#${m})`
60
+ }),
61
+ /* @__PURE__ */ r("path", {
62
+ d: u,
63
+ fill: `url(#${h})`
64
+ })
65
+ ] }) : /* @__PURE__ */ r("path", {
66
+ d: c,
67
+ fill: o === "white" ? "#FFFFFF" : "currentColor"
68
+ })
69
+ });
70
+ }, p = ({ size: a = 40, tone: o = "brand", radius: s = .225, title: c = "Voltro", className: d, style: f }) => {
71
+ let p = t(), m = `viBg-${p}`, h = `viGlow-${p}`, g = `viF-${p}`, _ = `viB-${p}`, v = (64 * s).toFixed(2);
72
+ return /* @__PURE__ */ i("svg", {
73
+ width: a,
74
+ height: a,
75
+ viewBox: "0 0 64 64",
76
+ fill: "none",
77
+ xmlns: "http://www.w3.org/2000/svg",
78
+ role: "img",
79
+ "aria-label": c,
80
+ className: e("inline-block shrink-0", d),
81
+ style: f,
82
+ children: [
83
+ /* @__PURE__ */ i("defs", { children: [
84
+ /* @__PURE__ */ r("linearGradient", {
85
+ id: m,
86
+ x1: "0",
87
+ y1: "0",
88
+ x2: "64",
89
+ y2: "64",
90
+ gradientUnits: "userSpaceOnUse",
91
+ children: o === "brand" ? /* @__PURE__ */ i(n, { children: [/* @__PURE__ */ r("stop", { stopColor: "#8B5CFF" }), /* @__PURE__ */ r("stop", {
92
+ offset: "1",
93
+ stopColor: "#6D28D9"
94
+ })] }) : /* @__PURE__ */ i(n, { children: [/* @__PURE__ */ r("stop", { stopColor: "#1B1726" }), /* @__PURE__ */ r("stop", {
95
+ offset: "1",
96
+ stopColor: "#120F1A"
97
+ })] })
98
+ }),
99
+ o === "dark" ? /* @__PURE__ */ i("radialGradient", {
100
+ id: h,
101
+ cx: "0.5",
102
+ cy: "0.46",
103
+ r: "0.5",
104
+ children: [/* @__PURE__ */ r("stop", {
105
+ stopColor: "#7C3AED",
106
+ stopOpacity: "0.55"
107
+ }), /* @__PURE__ */ r("stop", {
108
+ offset: "1",
109
+ stopColor: "#7C3AED",
110
+ stopOpacity: "0"
111
+ })]
112
+ }) : null,
113
+ /* @__PURE__ */ i("linearGradient", {
114
+ id: g,
115
+ x1: "13",
116
+ y1: "5",
117
+ x2: "33",
118
+ y2: "43",
119
+ gradientUnits: "userSpaceOnUse",
120
+ children: [/* @__PURE__ */ r("stop", { stopColor: "#C4A6FF" }), /* @__PURE__ */ r("stop", {
121
+ offset: "1",
122
+ stopColor: "#9D5BFF"
123
+ })]
124
+ }),
125
+ /* @__PURE__ */ i("linearGradient", {
126
+ id: _,
127
+ x1: "22",
128
+ y1: "5",
129
+ x2: "35",
130
+ y2: "43",
131
+ gradientUnits: "userSpaceOnUse",
132
+ children: [/* @__PURE__ */ r("stop", { stopColor: "#A874FF" }), /* @__PURE__ */ r("stop", {
133
+ offset: "1",
134
+ stopColor: "#7C3AED"
135
+ })]
136
+ })
137
+ ] }),
138
+ /* @__PURE__ */ r("rect", {
139
+ width: "64",
140
+ height: "64",
141
+ rx: v,
142
+ fill: `url(#${m})`
143
+ }),
144
+ o === "brand" ? /* @__PURE__ */ i(n, { children: [/* @__PURE__ */ r("path", {
145
+ transform: "translate(8 8)",
146
+ d: l,
147
+ fill: "#FFFFFF"
148
+ }), /* @__PURE__ */ r("path", {
149
+ transform: "translate(8 8)",
150
+ d: u,
151
+ fill: "#D7C4FF"
152
+ })] }) : /* @__PURE__ */ i(n, { children: [
153
+ /* @__PURE__ */ r("ellipse", {
154
+ cx: "32",
155
+ cy: "30",
156
+ rx: "26",
157
+ ry: "26",
158
+ fill: `url(#${h})`
159
+ }),
160
+ /* @__PURE__ */ r("path", {
161
+ transform: "translate(8 8)",
162
+ d: l,
163
+ fill: `url(#${g})`
164
+ }),
165
+ /* @__PURE__ */ r("path", {
166
+ transform: "translate(8 8)",
167
+ d: u,
168
+ fill: `url(#${_})`
169
+ })
170
+ ] })
171
+ ]
172
+ });
173
+ }, m = ({ size: t = 28, markVariant: n = "gradient", label: a = "Voltro", product: o, productTone: c = "brand", className: l, style: u }) => /* @__PURE__ */ i("span", {
174
+ className: e("voltro-wordmark inline-flex items-center font-semibold tracking-tight leading-none", l),
175
+ style: {
176
+ columnGap: `calc(var(--voltro-wordmark-size, ${t}px) * .28)`,
177
+ fontSize: `calc(var(--voltro-wordmark-size, ${t}px) * .74)`,
178
+ ...u
179
+ },
180
+ children: [
181
+ /* @__PURE__ */ r(f, {
182
+ size: Math.round(t * 1.18),
183
+ variant: n,
184
+ "aria-hidden": !0,
185
+ style: {
186
+ width: `calc(var(--voltro-wordmark-size, ${t}px) * 1.18)`,
187
+ height: `calc(var(--voltro-wordmark-size, ${t}px) * 1.18)`,
188
+ flexShrink: 0
189
+ }
190
+ }),
191
+ /* @__PURE__ */ r("span", {
192
+ className: "voltro-wordmark-label",
193
+ children: a
194
+ }),
195
+ o ? /* @__PURE__ */ r(s, {
196
+ variant: "outline",
197
+ className: e("voltro-product-badge shrink-0 rounded px-1.5 py-1 text-[10px] font-medium leading-none uppercase tracking-[.08em]", c === "warning" ? "border-rose-500/30 bg-rose-500/10 text-rose-700 dark:text-rose-200" : "border-primary/30 bg-primary/10 text-accent-foreground"),
198
+ children: o
199
+ }) : null
200
+ ]
201
+ }), h = {
202
+ full: c,
203
+ front: l,
204
+ back: u
205
+ };
206
+ //#endregion
207
+ export { m as a, f as i, d as n, s as o, p as r, o as s, h as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/ui-shadcn",
3
- "version": "0.66.1",
3
+ "version": "0.68.0",
4
4
  "description": "Voltro's first-party shadcn/ui kit: Tailwind v4 design tokens (light + dark), 30+ primitives, layout compositions, styled widgets for the @voltro/ui seam, and the canonical theme/language preference-cookie helpers.",
5
5
  "keywords": [
6
6
  "voltro",
@@ -34,7 +34,8 @@
34
34
  "default": "./dist/brand.js"
35
35
  },
36
36
  "./tokens.css": "./dist/tokens.css",
37
- "./package.json": "./package.json"
37
+ "./package.json": "./package.json",
38
+ "./signal.css": "./dist/signal.css"
38
39
  },
39
40
  "main": "./dist/index.js",
40
41
  "module": "./dist/index.js",
@@ -49,15 +50,15 @@
49
50
  "@radix-ui/react-dropdown-menu": "^2.1.24",
50
51
  "@radix-ui/react-toggle": "^1.1.18",
51
52
  "@radix-ui/react-toggle-group": "^1.1.19",
52
- "@voltro/ui": "0.66.1",
53
+ "@shikijs/langs": "^4.4.3",
54
+ "@shikijs/themes": "^4.4.3",
55
+ "@voltro/ui": "0.68.0",
53
56
  "class-variance-authority": "^0.7.1",
54
57
  "clsx": "^2.1.1",
55
58
  "shiki": "^4.4.3",
56
59
  "tailwind-merge": "^3.6.0",
57
60
  "tailwindcss": "^4.3.3",
58
- "tw-animate-css": "^1.2.5",
59
- "@shikijs/langs": "^4.4.3",
60
- "@shikijs/themes": "^4.4.3"
61
+ "tw-animate-css": "^1.2.5"
61
62
  },
62
63
  "peerDependencies": {
63
64
  "react": "^19.0.0"
@@ -20,6 +20,7 @@
20
20
  import type { CSSProperties, ReactNode, SVGProps } from 'react'
21
21
  import { useId } from 'react'
22
22
  import { cn } from '../cn'
23
+ import { Badge } from '../primitives/badge'
23
24
 
24
25
  // The bolt geometry, authored once in a 48×48 box and reused everywhere.
25
26
  // Sharp joins read as "electric"; the facets share the crease T→C.
@@ -213,6 +214,10 @@ export interface VoltroWordmarkProps {
213
214
  readonly markVariant?: VoltroMarkVariant
214
215
  /** Wordmark label. Default "Voltro". */
215
216
  readonly label?: string
217
+ /** Product badge beside the shared wordmark, e.g. Framework or Cloud. */
218
+ readonly product?: ReactNode
219
+ /** Warning tone for an operator surface; proportions stay shared. */
220
+ readonly productTone?: 'brand' | 'warning'
216
221
  readonly className?: string
217
222
  readonly style?: CSSProperties
218
223
  }
@@ -226,17 +231,21 @@ export const VoltroWordmark = ({
226
231
  size = 28,
227
232
  markVariant = 'gradient',
228
233
  label = 'Voltro',
234
+ product,
235
+ productTone = 'brand',
229
236
  className,
230
237
  style,
231
238
  }: VoltroWordmarkProps): ReactNode => (
232
239
  <span
233
- className={cn('inline-flex items-center font-semibold tracking-tight leading-none', className)}
234
- style={{ gap: size * 0.28, fontSize: size * 0.74, ...style }}
240
+ className={cn('voltro-wordmark inline-flex items-center font-semibold tracking-tight leading-none', className)}
241
+ style={{ columnGap: `calc(var(--voltro-wordmark-size, ${size}px) * .28)`, fontSize: `calc(var(--voltro-wordmark-size, ${size}px) * .74)`, ...style }}
235
242
  >
236
243
  {/* Mark rendered larger than the cap height so the bolt reads as the
237
244
  lead element, not a small adornment beside the wordmark. */}
238
- <VoltroMark size={Math.round(size * 1.18)} variant={markVariant} aria-hidden />
239
- <span>{label}</span>
245
+ <VoltroMark size={Math.round(size * 1.18)} variant={markVariant} aria-hidden
246
+ style={{ width: `calc(var(--voltro-wordmark-size, ${size}px) * 1.18)`, height: `calc(var(--voltro-wordmark-size, ${size}px) * 1.18)`, flexShrink: 0 }} />
247
+ <span className="voltro-wordmark-label">{label}</span>
248
+ {product ? <Badge variant="outline" className={cn('voltro-product-badge shrink-0 rounded px-1.5 py-1 text-[10px] font-medium leading-none uppercase tracking-[.08em]', productTone === 'warning' ? 'border-rose-500/30 bg-rose-500/10 text-rose-700 dark:text-rose-200' : 'border-primary/30 bg-primary/10 text-accent-foreground')}>{product}</Badge> : null}
240
249
  </span>
241
250
  )
242
251
 
@@ -8,11 +8,8 @@
8
8
  // page reloads. Default is plain `<a>` so the kit stays
9
9
  // router-agnostic.
10
10
  //
11
- // The top bar chrome (sticky, glass-on-scroll, h-16, max-w-7xl) is
12
- // the shared <PageHeader> — same one consumed by DocsLayout and by
13
- // landing / marketplace pages directly. That's the single visual
14
- // source of truth across the four sibling apps; do not duplicate
15
- // the header treatment here.
11
+ // PageHeader supplies the default compact top bar. The header slot accepts
12
+ // a shared product masthead while leaving task content and auth to the app.
16
13
 
17
14
  import type { ComponentType, ReactNode } from 'react'
18
15
  import { cn } from '../cn'
@@ -26,6 +23,8 @@ interface NavLink {
26
23
 
27
24
  interface AppShellProps {
28
25
  readonly brand: ReactNode
26
+ /** Custom top navigation using the app's own shared header composition. */
27
+ readonly header?: ReactNode
29
28
  readonly nav?: ReadonlyArray<NavLink>
30
29
  readonly right?: ReactNode
31
30
  readonly children: ReactNode
@@ -38,13 +37,13 @@ const PlainLink = ({ to, className, children }: ShellLinkProps): ReactNode => (
38
37
  )
39
38
 
40
39
  export const AppShell = ({
41
- brand, nav, right, children, className,
40
+ brand, header, nav, right, children, className,
42
41
  LinkComponent = PlainLink,
43
42
  }: AppShellProps): ReactNode => {
44
43
  const Link = LinkComponent
45
44
  return (
46
45
  <div className={cn('min-h-screen flex flex-col bg-background text-foreground', className)}>
47
- <PageHeader>
46
+ {header ?? <PageHeader>
48
47
  <div className="font-semibold flex items-center gap-2">{brand}</div>
49
48
  <div className="flex items-center gap-6">
50
49
  {nav && nav.length > 0 ? (
@@ -63,10 +62,9 @@ export const AppShell = ({
63
62
  ) : null}
64
63
  {right ? <div>{right}</div> : null}
65
64
  </div>
66
- </PageHeader>
65
+ </PageHeader>}
67
66
  <main className="flex-1">{children}</main>
68
67
  </div>
69
68
  )
70
69
  }
71
70
 
72
-
@@ -21,7 +21,7 @@ interface CodeComparePanelProps {
21
21
  readonly code?: string
22
22
  /** Shiki language. Triggers syntax highlighting on `code`. */
23
23
  readonly lang?: ShikiLang
24
- /** Visual treatment — `'muted'` is dimmed (the "before"),
24
+ /** Visual treatment — `'muted'` uses a neutral border (the "before"),
25
25
  * `'highlight'` keeps the full styling + adds a primary border. */
26
26
  readonly tone?: 'muted' | 'highlight'
27
27
  /** The corner tag. Pass a translated string on a localised page. */
@@ -48,7 +48,7 @@ const Panel = ({ label, html, code, lang, tone = 'muted', eyebrow }: CodeCompare
48
48
  <span
49
49
  className={cn(
50
50
  'text-[0.65rem] uppercase tracking-[0.08em] font-semibold',
51
- tone === 'highlight' ? 'text-primary' : 'text-muted-foreground/70',
51
+ tone === 'highlight' ? 'text-primary' : 'text-muted-foreground',
52
52
  )}
53
53
  >
54
54
  {eyebrow ?? (tone === 'highlight' ? 'With Voltro' : 'Before')}
@@ -57,7 +57,6 @@ const Panel = ({ label, html, code, lang, tone = 'muted', eyebrow }: CodeCompare
57
57
  <div
58
58
  className={cn(
59
59
  'text-sm leading-relaxed [&_pre]:!m-0 [&_pre]:!bg-transparent [&_pre]:p-5 overflow-x-auto',
60
- tone === 'muted' && 'opacity-60',
61
60
  )}
62
61
  >
63
62
  {html ? (