@torch-ui/solid 0.1.3

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 (118) hide show
  1. package/README.md +166 -0
  2. package/package.json +67 -0
  3. package/src/components/actions/Button.tsx +612 -0
  4. package/src/components/actions/ButtonGroup.tsx +728 -0
  5. package/src/components/actions/Copy.tsx +98 -0
  6. package/src/components/actions/DarkModeToggle.tsx +80 -0
  7. package/src/components/actions/Link.tsx +37 -0
  8. package/src/components/actions/index.ts +19 -0
  9. package/src/components/actions/useCopyToClipboard.ts +90 -0
  10. package/src/components/charts/Chart.tsx +331 -0
  11. package/src/components/charts/Sparkline.tsx +156 -0
  12. package/src/components/charts/index.ts +13 -0
  13. package/src/components/data-display/Avatar.tsx +208 -0
  14. package/src/components/data-display/AvatarGroup.tsx +228 -0
  15. package/src/components/data-display/Badge.tsx +70 -0
  16. package/src/components/data-display/Carousel.tsx +214 -0
  17. package/src/components/data-display/ColorSwatch.tsx +56 -0
  18. package/src/components/data-display/DataTable.tsx +886 -0
  19. package/src/components/data-display/EmptyState.tsx +61 -0
  20. package/src/components/data-display/Image.tsx +277 -0
  21. package/src/components/data-display/Kbd.tsx +114 -0
  22. package/src/components/data-display/Persona.tsx +78 -0
  23. package/src/components/data-display/StatCard.tsx +338 -0
  24. package/src/components/data-display/Table.tsx +147 -0
  25. package/src/components/data-display/Tag.tsx +91 -0
  26. package/src/components/data-display/Timeline.tsx +200 -0
  27. package/src/components/data-display/TreeView.tsx +172 -0
  28. package/src/components/data-display/Video.tsx +95 -0
  29. package/src/components/data-display/avatar-utils.ts +32 -0
  30. package/src/components/data-display/index.ts +81 -0
  31. package/src/components/feedback/Loading.tsx +159 -0
  32. package/src/components/feedback/Progress.tsx +321 -0
  33. package/src/components/feedback/Skeleton.tsx +62 -0
  34. package/src/components/feedback/SkeletonBlocks.tsx +222 -0
  35. package/src/components/feedback/Toast.tsx +648 -0
  36. package/src/components/feedback/index.ts +44 -0
  37. package/src/components/feedback/password/PasswordStrengthIndicator.tsx +232 -0
  38. package/src/components/feedback/password/password-strength.ts +115 -0
  39. package/src/components/feedback/password/password-validation-data.ts +66 -0
  40. package/src/components/feedback/password/password-validation.ts +93 -0
  41. package/src/components/forms/Autocomplete.tsx +268 -0
  42. package/src/components/forms/Checkbox.tsx +155 -0
  43. package/src/components/forms/CodeInput.tsx +237 -0
  44. package/src/components/forms/ColorPicker/ColorPicker.tsx +469 -0
  45. package/src/components/forms/ColorPicker/color-utils.ts +75 -0
  46. package/src/components/forms/ColorPicker/index.ts +2 -0
  47. package/src/components/forms/DatePicker.tsx +516 -0
  48. package/src/components/forms/DateRangePicker.tsx +464 -0
  49. package/src/components/forms/FieldPicker.tsx +64 -0
  50. package/src/components/forms/FileUpload.tsx +614 -0
  51. package/src/components/forms/FilterBuilder/FilterGroupBlock.ts +6 -0
  52. package/src/components/forms/FilterBuilder.tsx +16 -0
  53. package/src/components/forms/FilterRuleRow.tsx +68 -0
  54. package/src/components/forms/Input.tsx +200 -0
  55. package/src/components/forms/MultiSelect.tsx +361 -0
  56. package/src/components/forms/NumberField.tsx +145 -0
  57. package/src/components/forms/RadioGroup.tsx +135 -0
  58. package/src/components/forms/RelativeDateDefaultInput.tsx +62 -0
  59. package/src/components/forms/ReorderableList.tsx +163 -0
  60. package/src/components/forms/Select.tsx +268 -0
  61. package/src/components/forms/Slider.tsx +260 -0
  62. package/src/components/forms/Switch.tsx +135 -0
  63. package/src/components/forms/TextArea.tsx +202 -0
  64. package/src/components/forms/ViewCustomizer.tsx +44 -0
  65. package/src/components/forms/index.ts +43 -0
  66. package/src/components/layout/Accordion.tsx +110 -0
  67. package/src/components/layout/Alert.tsx +156 -0
  68. package/src/components/layout/BlockQuote.tsx +70 -0
  69. package/src/components/layout/Card.tsx +166 -0
  70. package/src/components/layout/CodeBlock/CodeBlock.tsx +477 -0
  71. package/src/components/layout/CodeBlock/code-block-tokens.css +104 -0
  72. package/src/components/layout/CodeBlock/prism.ts +81 -0
  73. package/src/components/layout/Collapsible.tsx +84 -0
  74. package/src/components/layout/Container.tsx +55 -0
  75. package/src/components/layout/Divider.tsx +64 -0
  76. package/src/components/layout/Form.tsx +39 -0
  77. package/src/components/layout/FormActions.tsx +50 -0
  78. package/src/components/layout/Grid.tsx +53 -0
  79. package/src/components/layout/PageHeading.tsx +46 -0
  80. package/src/components/layout/PromptWithAction.tsx +49 -0
  81. package/src/components/layout/Section.tsx +60 -0
  82. package/src/components/layout/TablePanel.tsx +24 -0
  83. package/src/components/layout/TableView/TableView.tsx +1018 -0
  84. package/src/components/layout/TableView/index.ts +3 -0
  85. package/src/components/layout/TableView/types.ts +51 -0
  86. package/src/components/layout/WizardStep.tsx +40 -0
  87. package/src/components/layout/WizardStepper.tsx +173 -0
  88. package/src/components/layout/index.ts +96 -0
  89. package/src/components/navigation/Breadcrumbs.tsx +66 -0
  90. package/src/components/navigation/DropdownMenu.tsx +86 -0
  91. package/src/components/navigation/MegaMenu.tsx +480 -0
  92. package/src/components/navigation/NavigationMenu.tsx +305 -0
  93. package/src/components/navigation/Pagination.tsx +298 -0
  94. package/src/components/navigation/Sidebar.tsx +280 -0
  95. package/src/components/navigation/Tabs.tsx +122 -0
  96. package/src/components/navigation/ViewSwitcher.tsx +314 -0
  97. package/src/components/navigation/index.ts +66 -0
  98. package/src/components/overlays/AlertDialog.tsx +174 -0
  99. package/src/components/overlays/ContextMenu.tsx +65 -0
  100. package/src/components/overlays/Dialog.tsx +279 -0
  101. package/src/components/overlays/Drawer.tsx +370 -0
  102. package/src/components/overlays/HoverCard.tsx +107 -0
  103. package/src/components/overlays/Popover.tsx +73 -0
  104. package/src/components/overlays/Tooltip.tsx +31 -0
  105. package/src/components/overlays/index.ts +71 -0
  106. package/src/components/typography/Code.tsx +72 -0
  107. package/src/components/typography/Icon.tsx +36 -0
  108. package/src/components/typography/index.ts +10 -0
  109. package/src/env.d.ts +9 -0
  110. package/src/index.ts +13 -0
  111. package/src/styles/theme.css +226 -0
  112. package/src/types/avatar-types.ts +11 -0
  113. package/src/types/filter-types.ts +35 -0
  114. package/src/utilities/classNames.ts +6 -0
  115. package/src/utilities/componentSize.ts +46 -0
  116. package/src/utilities/i18n.tsx +60 -0
  117. package/src/utilities/mergeRefs.ts +12 -0
  118. package/src/utilities/relativeDateDefault.ts +14 -0
@@ -0,0 +1,200 @@
1
+ import { type JSX, Show, For, splitProps } from 'solid-js'
2
+ import { cn } from '../../utilities/classNames'
3
+
4
+ export type TimelineItemStatus = 'completed' | 'active' | 'pending' | 'error'
5
+ export type TimelineVariant = 'default' | 'compact' | 'outlined'
6
+ export type TimelineConnector = 'solid' | 'dashed' | 'dotted'
7
+
8
+ export interface TimelineItem {
9
+ id?: string
10
+ /** Main heading for the event */
11
+ title: JSX.Element
12
+ /** Secondary description text */
13
+ description?: JSX.Element
14
+ /** Timestamp or label shown beside the title */
15
+ time?: JSX.Element
16
+ /** Custom icon/content inside the dot. If omitted, a default dot or status icon renders. */
17
+ icon?: JSX.Element
18
+ /** Controls the dot color and default icon. Default: 'pending'. */
19
+ status?: TimelineItemStatus
20
+ /** Extra content slot rendered below description */
21
+ content?: JSX.Element
22
+ /** Override the dot color with any Tailwind bg class e.g. 'bg-purple-500' */
23
+ color?: string
24
+ }
25
+
26
+ export interface TimelineProps extends Omit<JSX.HTMLAttributes<HTMLDivElement>, 'children'> {
27
+ /** Timeline entries */
28
+ items: TimelineItem[]
29
+ /** Visual style. Default: 'default'. */
30
+ variant?: TimelineVariant
31
+ /** Connector line style. Default: 'solid'. */
32
+ connector?: TimelineConnector
33
+ /** Show connector line between items. Default: true. */
34
+ showConnector?: boolean
35
+ /** Place timestamp to the left of the connector. Default: false. */
36
+ timeLeft?: boolean
37
+ }
38
+
39
+ const statusDotClass: Record<TimelineItemStatus, string> = {
40
+ completed: 'bg-success-500 text-white',
41
+ active: 'bg-primary-500 text-white ring-4 ring-primary-500/20',
42
+ pending: 'bg-surface-border text-ink-400 border-2 border-ink-300',
43
+ error: 'bg-danger-500 text-white',
44
+ }
45
+
46
+ const statusConnectorClass: Record<TimelineItemStatus, string> = {
47
+ completed: 'border-success-300 dark:border-success-700',
48
+ active: 'border-primary-300 dark:border-primary-700',
49
+ pending: 'border-ink-200 dark:border-ink-700',
50
+ error: 'border-danger-300 dark:border-danger-700',
51
+ }
52
+
53
+ function CheckIcon() {
54
+ return (
55
+ <svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
56
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M5 13l4 4L19 7" />
57
+ </svg>
58
+ )
59
+ }
60
+
61
+ function XIcon() {
62
+ return (
63
+ <svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
64
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M6 18L18 6M6 6l12 12" />
65
+ </svg>
66
+ )
67
+ }
68
+
69
+ function DefaultDotIcon(props: { status: TimelineItemStatus }) {
70
+ if (props.status === 'completed') return <CheckIcon />
71
+ if (props.status === 'error') return <XIcon />
72
+ if (props.status === 'active') {
73
+ return <span class="block h-2 w-2 rounded-full bg-white" />
74
+ }
75
+ return null
76
+ }
77
+
78
+ export function Timeline(props: TimelineProps) {
79
+ const [local, others] = splitProps(props, [
80
+ 'items',
81
+ 'variant',
82
+ 'connector',
83
+ 'showConnector',
84
+ 'timeLeft',
85
+ 'class',
86
+ ])
87
+
88
+ const variant = () => local.variant ?? 'default'
89
+ const connector = () => local.connector ?? 'solid'
90
+ const showConnector = () => local.showConnector !== false
91
+ const timeLeft = () => local.timeLeft ?? false
92
+
93
+ const compact = () => variant() === 'compact'
94
+ const outlined = () => variant() === 'outlined'
95
+
96
+ const connectorBorderStyle = () => {
97
+ const style = connector()
98
+ if (style === 'dashed') return 'border-dashed'
99
+ if (style === 'dotted') return 'border-dotted'
100
+ return 'border-solid'
101
+ }
102
+
103
+ return (
104
+ <div
105
+ class={cn('relative', local.class)}
106
+ {...others}
107
+ >
108
+ <ol class="list-none">
109
+ <For each={local.items}>
110
+ {(item, idx) => {
111
+ const isLast = () => idx() === local.items.length - 1
112
+ const status = () => item.status ?? 'pending'
113
+ const dotClass = () => item.color
114
+ ? `${item.color} text-white`
115
+ : statusDotClass[status()]
116
+
117
+ return (
118
+ <li class={cn('relative flex', timeLeft() ? 'flex-row-reverse' : 'flex-row', compact() ? 'gap-3' : 'gap-4')}>
119
+ {/* Left side: timestamp (when timeLeft) */}
120
+ <Show when={timeLeft() && item.time}>
121
+ <div class="w-28 shrink-0 pt-0.5 text-right">
122
+ <span class="text-xs text-ink-400">{item.time}</span>
123
+ </div>
124
+ </Show>
125
+
126
+ {/* Center: dot + connector */}
127
+ <div class={cn('flex flex-col items-center', timeLeft() ? 'mx-0' : '')}>
128
+ {/* Dot */}
129
+ <div
130
+ class={cn(
131
+ 'relative z-10 flex shrink-0 items-center justify-center rounded-full transition-all',
132
+ compact() ? 'h-5 w-5' : 'h-8 w-8',
133
+ dotClass(),
134
+ outlined() && 'ring-2 ring-offset-2 ring-offset-surface-base',
135
+ )}
136
+ aria-hidden="true"
137
+ >
138
+ <Show when={item.icon} fallback={<DefaultDotIcon status={status()} />}>
139
+ <span class={cn('flex items-center justify-center', compact() ? '[&>svg]:h-2.5 [&>svg]:w-2.5' : '[&>svg]:h-4 [&>svg]:w-4')}>
140
+ {item.icon}
141
+ </span>
142
+ </Show>
143
+ </div>
144
+
145
+ {/* Connector line */}
146
+ <Show when={showConnector() && !isLast()}>
147
+ <div
148
+ class={cn(
149
+ 'flex-1 border-l-2 my-1',
150
+ connectorBorderStyle(),
151
+ item.color
152
+ ? 'border-ink-200 dark:border-ink-700'
153
+ : statusConnectorClass[status()],
154
+ )}
155
+ style={{ 'min-height': compact() ? '1rem' : '1.5rem' }}
156
+ aria-hidden="true"
157
+ />
158
+ </Show>
159
+ </div>
160
+
161
+ {/* Right side: content */}
162
+ <div
163
+ class={cn(
164
+ 'min-w-0 flex-1',
165
+ !isLast() ? (compact() ? 'pb-4' : 'pb-6') : 'pb-0',
166
+ timeLeft() ? 'text-right' : '',
167
+ )}
168
+ >
169
+ {/* Title row */}
170
+ <div class={cn('flex items-start gap-2', timeLeft() ? 'flex-row-reverse' : 'flex-row')}>
171
+ <span class={cn('font-medium text-ink-900 leading-none', compact() ? 'text-sm' : 'text-sm')}>
172
+ {item.title}
173
+ </span>
174
+ <Show when={!timeLeft() && item.time}>
175
+ <span class="shrink-0 text-xs text-ink-400 mt-0.5">{item.time}</span>
176
+ </Show>
177
+ </div>
178
+
179
+ {/* Description */}
180
+ <Show when={item.description}>
181
+ <div class={cn('mt-1 text-ink-500 leading-relaxed', compact() ? 'text-xs' : 'text-sm')}>
182
+ {item.description}
183
+ </div>
184
+ </Show>
185
+
186
+ {/* Extra content slot */}
187
+ <Show when={item.content}>
188
+ <div class="mt-2">
189
+ {item.content}
190
+ </div>
191
+ </Show>
192
+ </div>
193
+ </li>
194
+ )
195
+ }}
196
+ </For>
197
+ </ol>
198
+ </div>
199
+ )
200
+ }
@@ -0,0 +1,172 @@
1
+ import { type JSX, Show, For, createSignal, splitProps } from 'solid-js'
2
+ import { ChevronRight } from 'lucide-solid'
3
+ import { cn } from '../../utilities/classNames'
4
+
5
+ export interface TreeNode {
6
+ /** Unique identifier */
7
+ id: string
8
+ /** Display label */
9
+ label: JSX.Element
10
+ /** Optional icon shown before the label */
11
+ icon?: JSX.Element
12
+ /** Child nodes */
13
+ children?: TreeNode[]
14
+ /** Prevents selection and interaction */
15
+ disabled?: boolean
16
+ }
17
+
18
+ export interface TreeViewProps extends Omit<JSX.HTMLAttributes<HTMLDivElement>, 'children' | 'onSelect'> {
19
+ /** Tree data */
20
+ nodes: TreeNode[]
21
+ /** Controlled selected node id */
22
+ selected?: string
23
+ /** Callback when a node is selected */
24
+ onSelect?: (id: string) => void
25
+ /** Default selected node id (uncontrolled) */
26
+ defaultSelected?: string
27
+ /** Controlled expanded node ids */
28
+ expanded?: string[]
29
+ /** Callback when expanded state changes */
30
+ onExpandedChange?: (ids: string[]) => void
31
+ /** Default expanded ids (uncontrolled) */
32
+ defaultExpanded?: string[]
33
+ /** Pixels of indentation per level. Default: 16. */
34
+ indent?: number
35
+ /** Show connecting lines between nodes. Default: true. */
36
+ showLines?: boolean
37
+ }
38
+
39
+ export function TreeView(props: TreeViewProps) {
40
+ const [local, others] = splitProps(props, [
41
+ 'nodes',
42
+ 'selected',
43
+ 'onSelect',
44
+ 'defaultSelected',
45
+ 'expanded',
46
+ 'onExpandedChange',
47
+ 'defaultExpanded',
48
+ 'indent',
49
+ 'showLines',
50
+ 'class',
51
+ ])
52
+
53
+ const [internalSelected, setInternalSelected] = createSignal<string | undefined>(
54
+ local.defaultSelected,
55
+ )
56
+ const [internalExpanded, setInternalExpanded] = createSignal<string[]>(
57
+ local.defaultExpanded ?? [],
58
+ )
59
+
60
+ const selectedId = () => local.selected ?? internalSelected()
61
+ const expandedIds = () => local.expanded ?? internalExpanded()
62
+
63
+ const isSelected = (id: string) => selectedId() === id
64
+ const isExpanded = (id: string) => expandedIds().includes(id)
65
+
66
+ const handleSelect = (id: string) => {
67
+ if (local.selected === undefined) setInternalSelected(id)
68
+ local.onSelect?.(id)
69
+ }
70
+
71
+ const toggleExpand = (id: string) => {
72
+ const current = expandedIds()
73
+ const next = current.includes(id) ? current.filter((x) => x !== id) : [...current, id]
74
+ if (local.expanded === undefined) setInternalExpanded(next)
75
+ local.onExpandedChange?.(next)
76
+ }
77
+
78
+ const indent = () => local.indent ?? 16
79
+ const showLines = () => local.showLines !== false
80
+
81
+ const renderNodes = (nodes: TreeNode[], level = 0): JSX.Element => (
82
+ <ul role={level === 0 ? 'tree' : 'group'} class="list-none">
83
+ <For each={nodes}>
84
+ {(node) => {
85
+ const hasChildren = () => (node.children?.length ?? 0) > 0
86
+ const expanded = () => isExpanded(node.id)
87
+ const selected = () => isSelected(node.id)
88
+
89
+ return (
90
+ <li role="treeitem" aria-expanded={hasChildren() ? expanded() : undefined} aria-selected={selected()}>
91
+ <button
92
+ type="button"
93
+ disabled={node.disabled}
94
+ onClick={() => {
95
+ if (node.disabled) return
96
+ if (hasChildren()) toggleExpand(node.id)
97
+ handleSelect(node.id)
98
+ }}
99
+ onKeyDown={(e: KeyboardEvent) => {
100
+ if (node.disabled) return
101
+ if (e.key === 'ArrowRight' && hasChildren() && !expanded()) {
102
+ e.preventDefault()
103
+ toggleExpand(node.id)
104
+ } else if (e.key === 'ArrowLeft' && hasChildren() && expanded()) {
105
+ e.preventDefault()
106
+ toggleExpand(node.id)
107
+ } else if (e.key === 'Enter' || e.key === ' ') {
108
+ e.preventDefault()
109
+ if (hasChildren()) toggleExpand(node.id)
110
+ handleSelect(node.id)
111
+ }
112
+ }}
113
+ class={cn(
114
+ 'flex w-full items-center gap-1.5 rounded-md px-2 py-1.5 text-left text-sm transition-colors',
115
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500/50',
116
+ selected()
117
+ ? 'bg-primary-50 text-primary-700 font-medium dark:bg-primary-500/15 dark:text-primary-400'
118
+ : 'text-ink-700 hover:bg-surface-overlay hover:text-ink-900 dark:hover:text-ink-900',
119
+ node.disabled && 'cursor-not-allowed opacity-40',
120
+ )}
121
+ style={{ 'padding-left': `${level * indent() + 8}px` }}
122
+ >
123
+ {/* Expand/collapse chevron */}
124
+ <span class={cn('flex h-4 w-4 shrink-0 items-center justify-center')}>
125
+ <Show when={hasChildren()} fallback={<span class="h-4 w-4" />}>
126
+ <ChevronRight
127
+ class={cn(
128
+ 'h-3.5 w-3.5 text-ink-400 transition-transform duration-150',
129
+ expanded() && 'rotate-90',
130
+ )}
131
+ aria-hidden="true"
132
+ />
133
+ </Show>
134
+ </span>
135
+
136
+ {/* Node icon */}
137
+ <Show when={node.icon}>
138
+ <span class={cn('flex h-4 w-4 shrink-0 items-center justify-center [&>svg]:h-4 [&>svg]:w-4', selected() ? 'text-primary-500' : 'text-ink-500')}>
139
+ {node.icon}
140
+ </span>
141
+ </Show>
142
+
143
+ {/* Label */}
144
+ <span class="min-w-0 truncate">{node.label}</span>
145
+ </button>
146
+
147
+ {/* Children */}
148
+ <Show when={hasChildren() && expanded()}>
149
+ <div
150
+ class={cn(
151
+ showLines() && level < 2
152
+ ? 'relative ml-[calc(var(--indent-offset)+8px)] border-l border-ink-200 dark:border-ink-700 pl-0'
153
+ : '',
154
+ )}
155
+ style={{ '--indent-offset': `${level * indent() + 14}px` } as any}
156
+ >
157
+ {renderNodes(node.children!, level + 1)}
158
+ </div>
159
+ </Show>
160
+ </li>
161
+ )
162
+ }}
163
+ </For>
164
+ </ul>
165
+ )
166
+
167
+ return (
168
+ <div class={cn('select-none', local.class)} {...others}>
169
+ {renderNodes(local.nodes)}
170
+ </div>
171
+ )
172
+ }
@@ -0,0 +1,95 @@
1
+ import { type JSX, createSignal, Show, splitProps } from 'solid-js'
2
+ import { cn } from '../../utilities/classNames'
3
+
4
+ export interface VideoProps {
5
+ /** Video source URL */
6
+ src: string
7
+ /** Poster image URL shown before playback */
8
+ poster?: string
9
+ /** Show native browser controls. Default: true */
10
+ controls?: boolean
11
+ /** Autoplay the video. Default: false. Note: forces muted=true to satisfy browser autoplay policy. */
12
+ autoplay?: boolean
13
+ /** Mute the video. Default: false */
14
+ muted?: boolean
15
+ /** Loop the video. Default: false */
16
+ loop?: boolean
17
+ /** CSS aspect-ratio value e.g. '16/9', '4/3', '1/1', '9/16'. Default: '16/9' */
18
+ aspectRatio?: string
19
+ /** Max width of the container (CSS value). Default: '100%' */
20
+ width?: string
21
+ /** Max height of the container (CSS value). Rarely needed with aspect-ratio. */
22
+ height?: string
23
+ /** Fallback content to render when the video fails to load */
24
+ fallback?: JSX.Element
25
+ /** Preload strategy. Default: 'metadata' */
26
+ preload?: 'none' | 'metadata' | 'auto'
27
+ /** Whether the video fills its container. Default: true */
28
+ fluid?: boolean
29
+ class?: string
30
+ /** Forwarded to the <video> element */
31
+ videoClass?: string
32
+ }
33
+
34
+ export function Video(props: VideoProps) {
35
+ const [local, others] = splitProps(props, [
36
+ 'src', 'poster', 'controls', 'autoplay', 'muted', 'loop',
37
+ 'aspectRatio', 'width', 'height', 'fallback', 'preload',
38
+ 'fluid', 'class', 'videoClass',
39
+ ])
40
+
41
+ const [error, setError] = createSignal(false)
42
+
43
+ const aspectRatio = () => local.aspectRatio ?? '16/9'
44
+ const fluid = () => local.fluid !== false
45
+
46
+ return (
47
+ <div
48
+ class={cn('relative overflow-hidden rounded-xl', local.class)}
49
+ style={{
50
+ 'aspect-ratio': aspectRatio(),
51
+ 'max-width': local.width ?? (fluid() ? '100%' : undefined),
52
+ 'max-height': local.height,
53
+ }}
54
+ >
55
+ <Show
56
+ when={!error()}
57
+ fallback={
58
+ <div class="absolute inset-0 flex items-center justify-center bg-surface-raised">
59
+ <Show
60
+ when={local.fallback}
61
+ fallback={
62
+ <div class="flex flex-col items-center gap-3 p-6 text-center">
63
+ <div class="flex h-12 w-12 items-center justify-center rounded-full bg-surface-overlay">
64
+ <svg class="h-6 w-6 text-ink-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
65
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M15.75 10.5l4.72-4.72a.75.75 0 011.28.53v11.38a.75.75 0 01-1.28.53l-4.72-4.72M12 18.75H4.5a2.25 2.25 0 01-2.25-2.25V9m12.841 9.091L16.5 19.5m-1.409-1.409c.407-.164.841-.26 1.298-.26H19.5m-14.25 0A2.25 2.25 0 013 16.5V9.75m0 0A2.25 2.25 0 015.25 7.5H12M3 9.75l9-4.5" />
66
+ </svg>
67
+ </div>
68
+ <div>
69
+ <p class="text-sm font-medium text-ink-700">Video unavailable</p>
70
+ <p class="mt-0.5 text-xs text-ink-400">This video could not be loaded.</p>
71
+ </div>
72
+ </div>
73
+ }
74
+ >
75
+ {local.fallback}
76
+ </Show>
77
+ </div>
78
+ }
79
+ >
80
+ <video
81
+ class={cn('h-full w-full object-cover', local.videoClass)}
82
+ src={local.src}
83
+ poster={local.poster}
84
+ controls={local.controls !== false}
85
+ autoplay={local.autoplay}
86
+ muted={local.autoplay ? true : local.muted}
87
+ loop={local.loop}
88
+ preload={local.preload ?? 'metadata'}
89
+ onError={() => setError(true)}
90
+ playsinline
91
+ />
92
+ </Show>
93
+ </div>
94
+ )
95
+ }
@@ -0,0 +1,32 @@
1
+ import type { AvatarShape, SizeKey } from '../../types/avatar-types'
2
+
3
+ export const shapeClasses: Record<AvatarShape, string> = {
4
+ circle: 'rounded-full',
5
+ rounded: 'rounded-lg',
6
+ square: 'rounded-none',
7
+ }
8
+
9
+ export const avatarSizeClasses: Record<SizeKey, string> = {
10
+ sm: 'h-8 w-8 text-sm',
11
+ md: 'h-10 w-10 text-sm',
12
+ lg: 'h-12 w-12 text-base',
13
+ }
14
+
15
+ /** Neutral avatar background/text — shared between Avatar and AvatarGroup overflow pill. */
16
+ export const neutralColorClass = 'bg-ink-200 text-ink-600 dark:bg-ink-700 dark:text-ink-200'
17
+
18
+ /**
19
+ * Initials from a full name: first letter of first name + first letter of last name.
20
+ * Treats the last word as last name and everything before as first name.
21
+ * "Jenny Lee Smith" → "JS", "Alex" → "A", "Alex Chen" → "AC".
22
+ */
23
+ export function getInitials(name: string): string {
24
+ const trimmed = name.trim()
25
+ if (!trimmed) return '?'
26
+ const parts = trimmed.split(/\s+/).filter(Boolean)
27
+ if (parts.length === 0) return '?'
28
+ if (parts.length === 1) return parts[0].charAt(0).toUpperCase()
29
+ const first = parts[0].charAt(0)
30
+ const last = parts[parts.length - 1].charAt(0)
31
+ return (first + last).toUpperCase()
32
+ }
@@ -0,0 +1,81 @@
1
+ /** Data Display (sidebar) */
2
+ export { Avatar } from './Avatar'
3
+ export type { AvatarProps, AvatarShape, AvatarRing, AvatarColor, AvatarBadgePlacement } from './Avatar'
4
+ export type { SizeKey } from '../../types/avatar-types'
5
+
6
+ export { ColorSwatch } from './ColorSwatch'
7
+ export type { ColorSwatchProps, ColorSwatchVariant } from './ColorSwatch'
8
+
9
+ export { Persona } from './Persona'
10
+ export type { PersonaProps } from './Persona'
11
+
12
+ export { Badge } from './Badge'
13
+ export type { BadgeProps, BadgeVariant, BadgeSize } from './Badge'
14
+
15
+ export { AvatarGroup } from './AvatarGroup'
16
+ export type { AvatarGroupProps, AvatarGroupItem, AvatarStacking } from './AvatarGroup'
17
+
18
+ export { TablePanel } from '../layout/TablePanel'
19
+ export type { TablePanelProps } from '../layout/TablePanel'
20
+
21
+ export { TableView, useTableView, emptyFilterGroup } from '../layout/TableView'
22
+ export type { TableViewProps, TableViewContextValue } from '../layout/TableView'
23
+ export type { TableViewConfig, ViewConfig } from '../layout/TableView/types'
24
+
25
+ export { Carousel } from './Carousel'
26
+ export type { CarouselProps, CarouselSlide } from './Carousel'
27
+ export { Chart } from '../charts/Chart'
28
+ export type { ChartProps, ChartData, ChartDataset, ChartType, ScatterPoint, BubblePoint } from '../charts/Chart'
29
+ export { EmptyState } from './EmptyState'
30
+ export type { EmptyStateProps } from './EmptyState'
31
+ export { Tag } from './Tag'
32
+ export type { TagProps, TagVariant, TagSize } from './Tag'
33
+
34
+ export { Kbd, KbdShortcut, KEY } from './Kbd'
35
+ export type { KbdProps, KbdShortcutProps, KbdVariant, KbdSize } from './Kbd'
36
+
37
+ export { StatCard } from './StatCard'
38
+ export type { StatCardProps } from './StatCard'
39
+
40
+ export { Timeline } from './Timeline'
41
+ export type { TimelineProps, TimelineItem, TimelineItemStatus, TimelineVariant, TimelineConnector } from './Timeline'
42
+
43
+ export { TreeView } from './TreeView'
44
+ export type { TreeViewProps, TreeNode } from './TreeView'
45
+
46
+ export { Video } from './Video'
47
+ export type { VideoProps } from './Video'
48
+ export { Sparkline } from '../charts/Sparkline'
49
+ export type { SparklineProps } from '../charts/Sparkline'
50
+ export { DataTable, TABLE_CONTAINER_CLASS } from './DataTable'
51
+ export type {
52
+ ColumnDef,
53
+ DataTableProps,
54
+ DataTablePagination,
55
+ DataTablePagingProps,
56
+ DataTableGroupByProps,
57
+ DataTableSearchProps,
58
+ DataTableButtonProps,
59
+ DataTableAddRowProps,
60
+ DataTableEditModalProps,
61
+ DataTableDeleteDialogProps,
62
+ } from './DataTable'
63
+ export {
64
+ Table,
65
+ TableBody,
66
+ TableCell,
67
+ TableFooter,
68
+ TableHead,
69
+ TableHeader,
70
+ TableRow,
71
+ } from './Table'
72
+ export type {
73
+ TableBodyProps,
74
+ TableCellProps,
75
+ TableFooterProps,
76
+ TableHeadProps,
77
+ TableHeaderProps,
78
+ TableProps,
79
+ TableRowProps,
80
+ } from './Table'
81
+