@voltro/ui-shadcn 0.68.0 → 0.69.1

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/dist/signal.css CHANGED
@@ -261,3 +261,66 @@
261
261
  .signal-marketing-close::before { background: radial-gradient(ellipse at 20% 30%, #7c3aed29, transparent 65%); }
262
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
263
  @media(max-width:760px) { .signal-marketing-close { padding: 40px 24px; } }
264
+
265
+ /* Shared operating workspace: Cloud and local dashboards use the same shell. */
266
+ .signal-workspace { position: relative; isolation: isolate; }
267
+ .signal-workspace .signal-workspace-field { height: 600px; bottom: auto; opacity: .65; mask-image: linear-gradient(#000, #000 35%, transparent); }
268
+ .signal-workspace-field .field-contours { top: -220px; opacity: .6; }
269
+ .signal-workspace-header { display: flex; align-items: flex-end; justify-content: space-between; gap: 24px; margin-bottom: 40px; }
270
+ .signal-workspace-heading { min-width: 0; }
271
+ .signal-workspace-title { display: flex; align-items: center; flex-wrap: wrap; gap: 16px; }
272
+ .signal-workspace-title h1 { font-size: 3rem; line-height: 1.12; font-weight: 650; letter-spacing: -.035em; overflow-wrap: anywhere; }
273
+ .signal-workspace-description { max-width: 70ch; margin-top: 16px; color: var(--muted-foreground); font-size: 16px; line-height: 1.65; }
274
+ .signal-workspace-description code { color: var(--foreground); font-size: .875em; }
275
+ .signal-workspace-action { flex-shrink: 0; }
276
+ .signal-workspace [data-slot="card"] { border-radius: 6px; box-shadow: none; }
277
+ .signal-workspace [data-slot="button"] { box-shadow: none; border-radius: 4px; }
278
+ .signal-workspace [data-slot="button"].bg-primary { background: var(--signal-violet) linear-gradient(115deg, #6d28d9, #7c3aed 48%, #8a49ed); }
279
+ .signal-workspace-page { padding-block: 40px 80px; }
280
+ @media (max-width: 760px) {
281
+ .signal-workspace-page { padding-block: 28px 48px; }
282
+ .signal-workspace-header { align-items: flex-start; flex-direction: column; gap: 22px; margin-bottom: 30px; }
283
+ .signal-workspace-title h1 { font-size: 2.25rem; }
284
+ .signal-workspace-description { font-size: 14px; margin-top: 12px; }
285
+ }
286
+
287
+ .signal-workspace-item { display: grid; grid-template-columns: 44px minmax(0, 1fr) auto auto; align-items: center; gap: 24px; min-height: 116px; padding: 24px 4px; border-bottom: 1px solid var(--border); transition: background-color 180ms, border-color 180ms; }
288
+ .signal-workspace-item:hover { background: color-mix(in srgb, var(--accent) 40%, transparent); border-bottom-color: var(--signal-line); }
289
+ .signal-workspace-item-icon { width: 44px; height: 44px; border: 1px solid var(--signal-line); border-radius: 6px; display: grid; place-items: center; color: var(--signal-accent-ink); background: var(--background); }
290
+ .signal-workspace-item-icon svg { width: 20px; height: 20px; }
291
+ .signal-workspace-item-copy { display: grid; gap: 8px; min-width: 0; }
292
+ .signal-workspace-item-title { font-size: 20px; line-height: 1.3; font-weight: 550; letter-spacing: -.02em; overflow-wrap: anywhere; }
293
+ .signal-workspace-item-description { font-size: 13px; color: var(--muted-foreground); overflow-wrap: anywhere; line-height: 1.6; }
294
+ .signal-workspace-item-detail { display: flex; align-items: center; gap: 20px; color: var(--muted-foreground); font-size: 12px; }
295
+ .signal-workspace-item-open { display: flex; align-items: center; justify-content: flex-end; gap: 14px; min-height: 44px; margin-left: 24px; font-size: 13px; color: var(--signal-accent-ink); }
296
+ .signal-workspace-item-open svg { height: 18px; width: 18px; transition: transform 180ms var(--signal-ease); }
297
+ .signal-workspace-item:hover .signal-workspace-item-open svg { transform: translateX(4px); }
298
+ .signal-workspace-item { width: 100%; text-align: left; cursor: pointer; }
299
+ @media (max-width: 1000px) { .signal-workspace-item-detail { flex-direction: column; align-items: flex-start; gap: 8px; } }
300
+ @media (max-width: 760px) {
301
+ .signal-workspace-item { grid-template-columns: 36px minmax(0, 1fr) 24px; gap: 12px; padding-block: 20px; }
302
+ .signal-workspace-item-icon { width: 36px; height: 36px; align-self: start; }
303
+ .signal-workspace-item-title { font-size: 18px; }
304
+ .signal-workspace-item-detail { grid-column: 2; grid-row: 2; flex-direction: row; flex-wrap: wrap; }
305
+ .signal-workspace-item-open { grid-column: 3; grid-row: 1 / span 2; margin-left: 0; }
306
+ .signal-workspace-item-open > span { display: none; }
307
+ }
308
+ @media (prefers-reduced-motion: reduce) {
309
+ .signal-workspace-item, .signal-workspace-item-open svg { transition: none; }
310
+ .signal-workspace-item:hover .signal-workspace-item-open svg { transform: none; }
311
+ }
312
+
313
+ .signal-workspace-tabs { display: flex; gap: 28px; overflow-x: auto; border-bottom: 1px solid var(--border); margin-bottom: 32px; }
314
+ .signal-workspace-tabs > :is(a, span) { flex-shrink: 0; display: inline-flex; min-height: 48px; align-items: center; padding: 0 1px; border-bottom: 2px solid transparent; color: var(--muted-foreground); font-size: 14px; }
315
+ .signal-workspace-tabs > [aria-current="page"] { color: var(--foreground); border-bottom-color: var(--signal-light); background: transparent; }
316
+ .signal-workspace-tabs > a:not([aria-current="page"]):hover { color: var(--foreground); border-bottom-color: var(--signal-line); }
317
+
318
+ /* Operating chrome keeps navigation close to the task surface. */
319
+ .signal-workspace-compact { --signal-header-height: 88px; }
320
+ .signal-workspace-compact .signal-workspace-page { padding-top: 8px; }
321
+ .signal-workspace-compact .signal-workspace-tabs { margin-bottom: 16px; }
322
+ @media (max-width: 760px) {
323
+ .signal-workspace-compact { --signal-header-height: 138px; }
324
+ .signal-workspace-compact .signal-header { padding-block: 12px; gap: 12px; }
325
+ .signal-workspace-compact .signal-workspace-tabs { gap: 24px; margin-bottom: 12px; }
326
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/ui-shadcn",
3
- "version": "0.68.0",
3
+ "version": "0.69.1",
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",
@@ -52,7 +52,7 @@
52
52
  "@radix-ui/react-toggle-group": "^1.1.19",
53
53
  "@shikijs/langs": "^4.4.3",
54
54
  "@shikijs/themes": "^4.4.3",
55
- "@voltro/ui": "0.68.0",
55
+ "@voltro/ui": "0.69.1",
56
56
  "class-variance-authority": "^0.7.1",
57
57
  "clsx": "^2.1.1",
58
58
  "shiki": "^4.4.3",
@@ -0,0 +1,63 @@
1
+ import type { ReactNode } from 'react'
2
+ import { cn } from '../cn'
3
+ import { AppShell } from './appShell'
4
+ import { SignalField } from './signalField'
5
+ import { PackageIcon } from '../primitives/docIcons'
6
+
7
+ export interface SignalWorkspaceProps {
8
+ readonly brand: ReactNode
9
+ readonly header: ReactNode
10
+ readonly children: ReactNode
11
+ readonly className?: string
12
+ /** Signed-out pages can supply their own full-height, animated field. */
13
+ readonly field?: boolean
14
+ }
15
+
16
+ /** Shared Cloud/local operating shell. Import `@voltro/ui-shadcn/signal.css`. */
17
+ export function SignalWorkspace({ brand, header, children, className, field = true }: SignalWorkspaceProps): ReactNode {
18
+ return <AppShell brand={brand} header={header} className={cn('signal-operate signal-stage signal-workspace', className)}>
19
+ {field && <SignalField className="signal-workspace-field" />}
20
+ {children}
21
+ </AppShell>
22
+ }
23
+
24
+ export interface SignalWorkspaceHeaderProps {
25
+ readonly title: ReactNode
26
+ readonly description?: ReactNode
27
+ readonly badge?: ReactNode
28
+ readonly action?: ReactNode
29
+ }
30
+
31
+ /** One heading, description and action rhythm for workspace pages in either host. */
32
+ export function SignalWorkspaceHeader({ title, description, badge, action }: SignalWorkspaceHeaderProps): ReactNode {
33
+ return <div className="signal-workspace-header">
34
+ <div className="signal-workspace-heading">
35
+ <div className="signal-workspace-title"><h1>{title}</h1>{badge}</div>
36
+ {description && <p className="signal-workspace-description">{description}</p>}
37
+ </div>
38
+ {action && <div className="signal-workspace-action">{action}</div>}
39
+ </div>
40
+ }
41
+
42
+ export interface SignalWorkspaceItemProps {
43
+ readonly title: string
44
+ readonly description: ReactNode
45
+ readonly detail?: ReactNode
46
+ readonly actionLabel: string
47
+ readonly renderLink: (className: string, children: ReactNode) => ReactNode
48
+ }
49
+
50
+ /** Open workspace row; the host supplies its typed link or selection button. */
51
+ export function SignalWorkspaceItem({ title, description, detail, actionLabel, renderLink }: SignalWorkspaceItemProps): ReactNode {
52
+ return renderLink('signal-workspace-item', <>
53
+ <span className="signal-workspace-item-icon" aria-hidden="true"><PackageIcon /></span>
54
+ <span className="signal-workspace-item-copy">
55
+ <span className="signal-workspace-item-title">{title}</span>
56
+ <span className="signal-workspace-item-description">{description}</span>
57
+ </span>
58
+ <span className="signal-workspace-item-detail">{detail}</span>
59
+ <span className="signal-workspace-item-open"><span>{actionLabel}</span>
60
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" aria-hidden="true"><path d="M4 12h16m-6-6 6 6-6 6" /></svg>
61
+ </span>
62
+ </>)
63
+ }
package/src/index.ts CHANGED
@@ -160,5 +160,6 @@ export { SearchModal, SearchTrigger } from './primitives/searchModal'
160
160
  export type { SearchModalProps, SearchModalLabels } from './primitives/searchModal'
161
161
 
162
162
  export { SignalField, type SignalFieldProps } from './compositions/signalField'
163
+ export { SignalWorkspace, SignalWorkspaceHeader, SignalWorkspaceItem, type SignalWorkspaceProps, type SignalWorkspaceHeaderProps, type SignalWorkspaceItemProps } from './compositions/signalWorkspace'
163
164
  export { SignalPreferences, SignalSearch, SignalHeaderAction, type SignalPreferencesProps, type SignalSearchProps } from './compositions/signalControls'
164
165
  export { SignalHeader, type SignalHeaderProps, type SignalNavigationLink } from './compositions/signalHeader'
@@ -26,8 +26,8 @@ export const Checkbox = ({ className, indeterminate, ...props }: CheckboxProps):
26
26
  className={cn(
27
27
  'peer size-4 appearance-none rounded-[4px] border shadow-xs outline-none',
28
28
  'border-input bg-transparent dark:bg-input/30',
29
- 'checked:bg-primary checked:border-primary',
30
- 'indeterminate:bg-primary indeterminate:border-primary',
29
+ 'checked:bg-primary checked:border-primary dark:checked:bg-primary',
30
+ 'indeterminate:bg-primary indeterminate:border-primary dark:indeterminate:bg-primary',
31
31
  'transition-[color,box-shadow]',
32
32
  'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
33
33
  'disabled:cursor-not-allowed disabled:opacity-50',
package/src/signal.css CHANGED
@@ -261,3 +261,66 @@
261
261
  .signal-marketing-close::before { background: radial-gradient(ellipse at 20% 30%, #7c3aed29, transparent 65%); }
262
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
263
  @media(max-width:760px) { .signal-marketing-close { padding: 40px 24px; } }
264
+
265
+ /* Shared operating workspace: Cloud and local dashboards use the same shell. */
266
+ .signal-workspace { position: relative; isolation: isolate; }
267
+ .signal-workspace .signal-workspace-field { height: 600px; bottom: auto; opacity: .65; mask-image: linear-gradient(#000, #000 35%, transparent); }
268
+ .signal-workspace-field .field-contours { top: -220px; opacity: .6; }
269
+ .signal-workspace-header { display: flex; align-items: flex-end; justify-content: space-between; gap: 24px; margin-bottom: 40px; }
270
+ .signal-workspace-heading { min-width: 0; }
271
+ .signal-workspace-title { display: flex; align-items: center; flex-wrap: wrap; gap: 16px; }
272
+ .signal-workspace-title h1 { font-size: 3rem; line-height: 1.12; font-weight: 650; letter-spacing: -.035em; overflow-wrap: anywhere; }
273
+ .signal-workspace-description { max-width: 70ch; margin-top: 16px; color: var(--muted-foreground); font-size: 16px; line-height: 1.65; }
274
+ .signal-workspace-description code { color: var(--foreground); font-size: .875em; }
275
+ .signal-workspace-action { flex-shrink: 0; }
276
+ .signal-workspace [data-slot="card"] { border-radius: 6px; box-shadow: none; }
277
+ .signal-workspace [data-slot="button"] { box-shadow: none; border-radius: 4px; }
278
+ .signal-workspace [data-slot="button"].bg-primary { background: var(--signal-violet) linear-gradient(115deg, #6d28d9, #7c3aed 48%, #8a49ed); }
279
+ .signal-workspace-page { padding-block: 40px 80px; }
280
+ @media (max-width: 760px) {
281
+ .signal-workspace-page { padding-block: 28px 48px; }
282
+ .signal-workspace-header { align-items: flex-start; flex-direction: column; gap: 22px; margin-bottom: 30px; }
283
+ .signal-workspace-title h1 { font-size: 2.25rem; }
284
+ .signal-workspace-description { font-size: 14px; margin-top: 12px; }
285
+ }
286
+
287
+ .signal-workspace-item { display: grid; grid-template-columns: 44px minmax(0, 1fr) auto auto; align-items: center; gap: 24px; min-height: 116px; padding: 24px 4px; border-bottom: 1px solid var(--border); transition: background-color 180ms, border-color 180ms; }
288
+ .signal-workspace-item:hover { background: color-mix(in srgb, var(--accent) 40%, transparent); border-bottom-color: var(--signal-line); }
289
+ .signal-workspace-item-icon { width: 44px; height: 44px; border: 1px solid var(--signal-line); border-radius: 6px; display: grid; place-items: center; color: var(--signal-accent-ink); background: var(--background); }
290
+ .signal-workspace-item-icon svg { width: 20px; height: 20px; }
291
+ .signal-workspace-item-copy { display: grid; gap: 8px; min-width: 0; }
292
+ .signal-workspace-item-title { font-size: 20px; line-height: 1.3; font-weight: 550; letter-spacing: -.02em; overflow-wrap: anywhere; }
293
+ .signal-workspace-item-description { font-size: 13px; color: var(--muted-foreground); overflow-wrap: anywhere; line-height: 1.6; }
294
+ .signal-workspace-item-detail { display: flex; align-items: center; gap: 20px; color: var(--muted-foreground); font-size: 12px; }
295
+ .signal-workspace-item-open { display: flex; align-items: center; justify-content: flex-end; gap: 14px; min-height: 44px; margin-left: 24px; font-size: 13px; color: var(--signal-accent-ink); }
296
+ .signal-workspace-item-open svg { height: 18px; width: 18px; transition: transform 180ms var(--signal-ease); }
297
+ .signal-workspace-item:hover .signal-workspace-item-open svg { transform: translateX(4px); }
298
+ .signal-workspace-item { width: 100%; text-align: left; cursor: pointer; }
299
+ @media (max-width: 1000px) { .signal-workspace-item-detail { flex-direction: column; align-items: flex-start; gap: 8px; } }
300
+ @media (max-width: 760px) {
301
+ .signal-workspace-item { grid-template-columns: 36px minmax(0, 1fr) 24px; gap: 12px; padding-block: 20px; }
302
+ .signal-workspace-item-icon { width: 36px; height: 36px; align-self: start; }
303
+ .signal-workspace-item-title { font-size: 18px; }
304
+ .signal-workspace-item-detail { grid-column: 2; grid-row: 2; flex-direction: row; flex-wrap: wrap; }
305
+ .signal-workspace-item-open { grid-column: 3; grid-row: 1 / span 2; margin-left: 0; }
306
+ .signal-workspace-item-open > span { display: none; }
307
+ }
308
+ @media (prefers-reduced-motion: reduce) {
309
+ .signal-workspace-item, .signal-workspace-item-open svg { transition: none; }
310
+ .signal-workspace-item:hover .signal-workspace-item-open svg { transform: none; }
311
+ }
312
+
313
+ .signal-workspace-tabs { display: flex; gap: 28px; overflow-x: auto; border-bottom: 1px solid var(--border); margin-bottom: 32px; }
314
+ .signal-workspace-tabs > :is(a, span) { flex-shrink: 0; display: inline-flex; min-height: 48px; align-items: center; padding: 0 1px; border-bottom: 2px solid transparent; color: var(--muted-foreground); font-size: 14px; }
315
+ .signal-workspace-tabs > [aria-current="page"] { color: var(--foreground); border-bottom-color: var(--signal-light); background: transparent; }
316
+ .signal-workspace-tabs > a:not([aria-current="page"]):hover { color: var(--foreground); border-bottom-color: var(--signal-line); }
317
+
318
+ /* Operating chrome keeps navigation close to the task surface. */
319
+ .signal-workspace-compact { --signal-header-height: 88px; }
320
+ .signal-workspace-compact .signal-workspace-page { padding-top: 8px; }
321
+ .signal-workspace-compact .signal-workspace-tabs { margin-bottom: 16px; }
322
+ @media (max-width: 760px) {
323
+ .signal-workspace-compact { --signal-header-height: 138px; }
324
+ .signal-workspace-compact .signal-header { padding-block: 12px; gap: 12px; }
325
+ .signal-workspace-compact .signal-workspace-tabs { gap: 24px; margin-bottom: 12px; }
326
+ }
package/src/widgets.tsx CHANGED
@@ -10,6 +10,7 @@
10
10
  // the seam falls back to the plain default for any kind not overridden here.
11
11
 
12
12
  import { useId, type ReactNode } from 'react'
13
+ import { Checkbox } from './primitives/checkbox'
13
14
  import type { Widget, WidgetProps, WidgetRegistry } from '@voltro/ui'
14
15
 
15
16
  const cx = {
@@ -18,7 +19,7 @@ const cx = {
18
19
  control:
19
20
  'block w-full rounded-md border border-input bg-background px-3 py-2 text-sm shadow-sm ' +
20
21
  'focus:outline-none focus:ring-2 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
21
- checkbox: 'h-4 w-4 rounded border-input text-primary focus:ring-2 focus:ring-ring',
22
+ radio: 'h-4 w-4 rounded border-input text-primary focus:ring-2 focus:ring-ring',
22
23
  error: 'text-sm text-destructive',
23
24
  }
24
25
 
@@ -93,11 +94,9 @@ const CheckboxWidget: Widget = (props) => {
93
94
  const id = useId()
94
95
  return (
95
96
  <div className="voltro-field flex items-center gap-2">
96
- <input
97
+ <Checkbox
97
98
  id={id}
98
- type="checkbox"
99
99
  name={props.name}
100
- className={cx.checkbox}
101
100
  checked={props.value === true}
102
101
  onChange={(e) => props.onChange(e.target.checked)}
103
102
  disabled={props.disabled}
@@ -203,7 +202,7 @@ const RadioWidget: Widget = (props) => {
203
202
  type="radio"
204
203
  name={props.name}
205
204
  value={o.value}
206
- className={cx.checkbox}
205
+ className={cx.radio}
207
206
  checked={asString(props.value) === o.value}
208
207
  onChange={() => props.onChange(o.value)}
209
208
  disabled={props.disabled}