@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,338 @@
1
+ import { type JSX, Show, splitProps } from 'solid-js'
2
+
3
+ import { cn } from '../../utilities/classNames'
4
+
5
+
6
+
7
+ export interface StatCardProps extends JSX.HTMLAttributes<HTMLDivElement> {
8
+
9
+ label: string
10
+
11
+ /** Optional subtitle or category below the label (e.g. "Travel and tourism"). */
12
+
13
+ subtitle?: string
14
+
15
+ /** Optional icon (e.g. app logo) shown at top left. */
16
+
17
+ icon?: JSX.Element
18
+
19
+ /** Accessible label for the icon when it conveys meaning. When set, icon wrapper gets role="img" + aria-label. When omitted, the wrapper is transparent — the icon child controls its own accessibility. */
20
+
21
+ iconLabel?: string
22
+
23
+ /** Optional content at top right (e.g. Tag or "Connect" button). */
24
+
25
+ topRight?: JSX.Element
26
+
27
+ value?: string | number | null
28
+
29
+ helperText?: string
30
+
31
+ trendLabel?: string
32
+
33
+ /** Default: 'positive' (emerald). */
34
+
35
+ trendVariant?: 'positive' | 'neutral' | 'negative'
36
+
37
+ trendIcon?: JSX.Element
38
+
39
+ emptyText?: string
40
+
41
+ /** Optional chart or sparkline. Use chartPosition to place under the trend or to the right. */
42
+
43
+ chart?: JSX.Element
44
+
45
+ /** Where to render the chart: under the trend (default) or in a column to the right of the value/trend. */
46
+
47
+ chartPosition?: 'under' | 'right'
48
+
49
+ /** When set, the chart wrapper gets role="img" + aria-label instead of aria-hidden. The chart child should be decorative (aria-hidden) to avoid duplicate announcements. */
50
+
51
+ chartA11yLabel?: string
52
+
53
+ }
54
+
55
+
56
+
57
+ export function StatCard(props: StatCardProps) {
58
+
59
+ const [local, others] = splitProps(props, [
60
+
61
+ 'label',
62
+
63
+ 'subtitle',
64
+
65
+ 'icon',
66
+
67
+ 'topRight',
68
+
69
+ 'value',
70
+
71
+ 'helperText',
72
+
73
+ 'trendLabel',
74
+
75
+ 'trendVariant',
76
+
77
+ 'trendIcon',
78
+
79
+ 'emptyText',
80
+
81
+ 'chart',
82
+
83
+ 'chartPosition',
84
+
85
+ 'chartA11yLabel',
86
+
87
+ 'iconLabel',
88
+
89
+ 'class',
90
+
91
+ ])
92
+
93
+
94
+
95
+ const chartOnRight = () => local.chart != null && local.chartPosition === 'right'
96
+
97
+ const hasHeaderContent = () => local.icon != null || local.topRight != null
98
+
99
+ const contentMt = () => (hasHeaderContent() ? 'mt-5' : 'mt-2')
100
+
101
+ const hasValue = () => local.value != null && local.value !== ''
102
+
103
+
104
+
105
+ // Default: positive (emerald)
106
+
107
+ const trendClasses = () => {
108
+
109
+ if (local.trendVariant === 'negative') return 'text-red-600 dark:text-red-400'
110
+
111
+ if (local.trendVariant === 'neutral') return 'text-ink-600'
112
+
113
+ return 'text-emerald-600 dark:text-emerald-400'
114
+
115
+ }
116
+
117
+
118
+
119
+ /** Value or empty-state placeholder. `compact` uses text-2xl sm:text-3xl for the right-chart layout. */
120
+
121
+ const ValueOrEmpty = (p: { compact?: boolean }) => (
122
+
123
+ <Show
124
+
125
+ when={hasValue()}
126
+
127
+ fallback={
128
+
129
+ <div class="text-xs font-normal text-ink-400">
130
+
131
+ {local.emptyText ?? 'No data yet'}
132
+
133
+ </div>
134
+
135
+ }
136
+
137
+ >
138
+
139
+ <div class={cn(
140
+
141
+ 'font-bold tracking-tight text-ink-900',
142
+
143
+ p.compact ? 'text-2xl sm:text-3xl' : 'text-3xl',
144
+
145
+ )}>
146
+
147
+ {local.value}
148
+
149
+ </div>
150
+
151
+ </Show>
152
+
153
+ )
154
+
155
+
156
+
157
+ /** Helper text + trend label/icon. `gap` controls top margin between value and trend. */
158
+
159
+ const TrendBlock = (p: { gap?: string }) => (
160
+
161
+ <>
162
+
163
+ <Show when={local.helperText}>
164
+
165
+ <div class="mt-1 text-sm text-ink-500">{local.helperText}</div>
166
+
167
+ </Show>
168
+
169
+ <Show when={local.trendLabel}>
170
+
171
+ <div class={cn(p.gap ?? 'mt-3', 'flex items-center gap-1.5 text-sm font-medium')}>
172
+
173
+ <Show when={local.trendIcon}>
174
+
175
+ <span class={cn('flex shrink-0', trendClasses())}>{local.trendIcon}</span>
176
+
177
+ </Show>
178
+
179
+ <span class={cn(trendClasses())}>{local.trendLabel}</span>
180
+
181
+ </div>
182
+
183
+ </Show>
184
+
185
+ </>
186
+
187
+ )
188
+
189
+
190
+
191
+ return (
192
+
193
+ <div
194
+
195
+ class={cn(
196
+
197
+ 'rounded-2xl border border-surface-border bg-surface-raised p-5 shadow-sm',
198
+
199
+ local.class,
200
+
201
+ )}
202
+
203
+ {...others}
204
+
205
+ >
206
+
207
+ {/* Top row: optional icon + label, optional topRight */}
208
+
209
+ <div class="flex items-start justify-between gap-3">
210
+
211
+ <div class="min-w-0 flex-1">
212
+
213
+ <div class="flex items-center gap-2">
214
+
215
+ <Show when={local.icon}>
216
+
217
+ <span
218
+
219
+ class="flex h-9 w-9 shrink-0 items-center justify-center overflow-hidden rounded-lg text-[0]"
220
+
221
+ role={local.iconLabel ? 'img' : undefined}
222
+
223
+ aria-label={local.iconLabel}
224
+
225
+ >
226
+
227
+ {local.icon}
228
+
229
+ </span>
230
+
231
+ </Show>
232
+
233
+ <div class="min-w-0">
234
+
235
+ <div class="text-sm font-semibold text-ink-700">{local.label}</div>
236
+
237
+ <Show when={local.subtitle}>
238
+
239
+ <div class="mt-0.5 text-xs text-ink-500">{local.subtitle}</div>
240
+
241
+ </Show>
242
+
243
+ </div>
244
+
245
+ </div>
246
+
247
+ </div>
248
+
249
+ <Show when={local.topRight}>
250
+
251
+ <div class="shrink-0">{local.topRight}</div>
252
+
253
+ </Show>
254
+
255
+ </div>
256
+
257
+
258
+
259
+ {/* Main content: when chart on right, use two columns; otherwise single column */}
260
+
261
+ <Show
262
+
263
+ when={chartOnRight()}
264
+
265
+ fallback={
266
+
267
+ <>
268
+
269
+ <div class={contentMt()}>
270
+
271
+ <ValueOrEmpty />
272
+
273
+ </div>
274
+
275
+ <TrendBlock />
276
+
277
+ <Show when={local.chart != null && local.chartPosition !== 'right'}>
278
+
279
+ <div
280
+
281
+ class="mt-3 h-10 w-full min-w-0"
282
+
283
+ aria-hidden={local.chartA11yLabel ? undefined : true}
284
+
285
+ role={local.chartA11yLabel ? 'img' : undefined}
286
+
287
+ aria-label={local.chartA11yLabel}
288
+
289
+ >
290
+
291
+ {local.chart}
292
+
293
+ </div>
294
+
295
+ </Show>
296
+
297
+ </>
298
+
299
+ }
300
+
301
+ >
302
+
303
+ <div class={cn(contentMt(), 'flex gap-4')}>
304
+
305
+ <div class="min-w-0 flex-1">
306
+
307
+ <ValueOrEmpty compact />
308
+
309
+ <TrendBlock gap="mt-2" />
310
+
311
+ </div>
312
+
313
+ <div
314
+
315
+ class="h-14 w-24 shrink-0 sm:w-32"
316
+
317
+ aria-hidden={local.chartA11yLabel ? undefined : true}
318
+
319
+ role={local.chartA11yLabel ? 'img' : undefined}
320
+
321
+ aria-label={local.chartA11yLabel}
322
+
323
+ >
324
+
325
+ {local.chart}
326
+
327
+ </div>
328
+
329
+ </div>
330
+
331
+ </Show>
332
+
333
+ </div>
334
+
335
+ )
336
+
337
+ }
338
+
@@ -0,0 +1,147 @@
1
+ import { type JSX, Show, splitProps, createContext, useContext } from 'solid-js'
2
+ import { cn } from '../../utilities/classNames'
3
+
4
+ type TableSection = 'head' | 'body' | 'foot'
5
+
6
+ const TableContext = createContext<boolean>(false)
7
+ /** Which table section a row lives in. Hover/stripe only apply to 'body' rows. */
8
+ const TableSectionContext = createContext<TableSection | undefined>(undefined)
9
+
10
+ export interface TableProps extends JSX.HTMLAttributes<HTMLTableElement> {
11
+ /** When true, even rows get a subtle background (striped). Default: false.
12
+ * Note: striping uses CSS `even:` which counts every `<tr>` in the `<tbody>`,
13
+ * including group headers, add-form rows, etc. If you mix data rows with non-data
14
+ * rows, stripes will be based on DOM order, not logical data-row index. */
15
+ striped?: boolean
16
+ /** Accessible caption for the table. Rendered as a visually-hidden `<caption>` by default. Pass JSX for a visible caption. */
17
+ caption?: JSX.Element | string
18
+ }
19
+
20
+ export function Table(props: TableProps) {
21
+ const [local, others] = splitProps(props, ['class', 'striped', 'caption', 'children'])
22
+ const striped = () => local.striped === true
23
+ return (
24
+ <TableContext.Provider value={striped()}>
25
+ <table
26
+ class={cn('w-full text-sm text-ink-900', local.class)}
27
+ {...others}
28
+ >
29
+ <Show when={local.caption != null}>
30
+ <caption class={typeof local.caption === 'string' ? 'sr-only' : undefined}>
31
+ {local.caption}
32
+ </caption>
33
+ </Show>
34
+ {local.children}
35
+ </table>
36
+ </TableContext.Provider>
37
+ )
38
+ }
39
+
40
+ export interface TableHeaderProps
41
+ extends JSX.HTMLAttributes<HTMLTableSectionElement> {}
42
+
43
+ /**
44
+ * Renders `<thead>` with sticky positioning.
45
+ * Sticky header requires a vertically scrollable ancestor (e.g. `max-h-* overflow-y-auto`).
46
+ */
47
+ export function TableHeader(props: TableHeaderProps) {
48
+ const [local, others] = splitProps(props, ['class', 'children'])
49
+ return (
50
+ <thead
51
+ class={cn(
52
+ 'sticky top-0 z-10 border-b border-surface-border bg-surface-raised',
53
+ local.class,
54
+ )}
55
+ {...others}
56
+ >
57
+ <TableSectionContext.Provider value={'head'}>{local.children}</TableSectionContext.Provider>
58
+ </thead>
59
+ )
60
+ }
61
+
62
+ export interface TableBodyProps
63
+ extends JSX.HTMLAttributes<HTMLTableSectionElement> {}
64
+
65
+ export function TableBody(props: TableBodyProps) {
66
+ const [local, others] = splitProps(props, ['class', 'children'])
67
+ return (
68
+ <tbody
69
+ class={cn(
70
+ 'divide-y divide-surface-border bg-surface-base',
71
+ local.class,
72
+ )}
73
+ {...others}
74
+ >
75
+ <TableSectionContext.Provider value={'body'}>{local.children}</TableSectionContext.Provider>
76
+ </tbody>
77
+ )
78
+ }
79
+
80
+ export interface TableFooterProps
81
+ extends JSX.HTMLAttributes<HTMLTableSectionElement> {}
82
+
83
+ export function TableFooter(props: TableFooterProps) {
84
+ const [local, others] = splitProps(props, ['class', 'children'])
85
+ return (
86
+ <tfoot
87
+ class={cn(
88
+ 'border-t border-surface-border bg-surface-raised',
89
+ local.class,
90
+ )}
91
+ {...others}
92
+ >
93
+ <TableSectionContext.Provider value={'foot'}>{local.children}</TableSectionContext.Provider>
94
+ </tfoot>
95
+ )
96
+ }
97
+
98
+ export interface TableRowProps extends JSX.HTMLAttributes<HTMLTableRowElement> {
99
+ /** When false, hover background is not applied (e.g. for empty state, loading, or group header rows). Default true for body rows. */
100
+ hover?: boolean
101
+ }
102
+
103
+ export function TableRow(props: TableRowProps) {
104
+ const [local, others] = splitProps(props, ['class', 'hover'])
105
+ const striped = useContext(TableContext) ?? false
106
+ const section = useContext(TableSectionContext)
107
+ const inBody = section === 'body'
108
+ const allowHover = () => local.hover !== false && inBody
109
+ return (
110
+ <tr
111
+ class={cn(
112
+ 'transition-colors',
113
+ inBody && striped ? 'even:bg-surface-overlay' : '',
114
+ allowHover() ? 'hover:bg-surface-dim/60' : '',
115
+ local.class,
116
+ )}
117
+ {...others}
118
+ />
119
+ )
120
+ }
121
+
122
+ export interface TableHeadProps
123
+ extends JSX.ThHTMLAttributes<HTMLTableCellElement> {}
124
+
125
+ export function TableHead(props: TableHeadProps) {
126
+ const [local, others] = splitProps(props, ['class', 'scope'])
127
+ return (
128
+ <th
129
+ scope={local.scope ?? 'col'}
130
+ class={cn(
131
+ 'px-4 py-3 text-left text-[11px] font-semibold uppercase tracking-[0.12em] text-ink-500',
132
+ local.class,
133
+ )}
134
+ {...others}
135
+ />
136
+ )
137
+ }
138
+
139
+ export interface TableCellProps
140
+ extends JSX.TdHTMLAttributes<HTMLTableCellElement> {}
141
+
142
+ export function TableCell(props: TableCellProps) {
143
+ const [local, others] = splitProps(props, ['class'])
144
+ return (
145
+ <td class={cn('px-4 py-3 align-middle text-sm text-ink-900', local.class)} {...others} />
146
+ )
147
+ }
@@ -0,0 +1,91 @@
1
+ import { type JSX, splitProps, Show, createMemo } from 'solid-js'
2
+ import { cn } from '../../utilities/classNames'
3
+
4
+ export type TagVariant =
5
+ | 'neutral'
6
+ | 'primary'
7
+ | 'success'
8
+ | 'warning'
9
+ | 'danger'
10
+ | 'info'
11
+
12
+ export type TagSize = 'sm' | 'md'
13
+
14
+ export interface TagProps extends Omit<JSX.HTMLAttributes<HTMLSpanElement>, 'color'> {
15
+ /** Semantic color variant. Ignored when color is set. Default: neutral. */
16
+ variant?: TagVariant
17
+ /** Tag size. Default: md. */
18
+ size?: TagSize
19
+ /** CSS color for a status indicator dot before children (e.g. "#22c55e"). */
20
+ statusColor?: string
21
+ /** Accessible label for the status dot (e.g. "Active"). Rendered as sr-only text. When omitted, the dot is purely decorative. */
22
+ statusLabel?: string
23
+ /** Arbitrary CSS color for a fully custom tag. Sets bg (10% opacity), border (25% opacity), and text color. Overrides variant. */
24
+ color?: string
25
+ }
26
+
27
+ const tagVariants: Record<TagVariant, string> = {
28
+ neutral:
29
+ 'bg-ink-100 text-ink-700 border-ink-200',
30
+ primary:
31
+ 'bg-primary-50 text-primary-700 border-primary-100 dark:bg-primary-500/20 dark:text-primary-200 dark:border-primary-500/40',
32
+ success:
33
+ 'bg-success-50 text-success-700 border-success-100 dark:bg-success-950 dark:text-success-200 dark:border-success-800',
34
+ warning:
35
+ 'bg-warning-50 text-warning-700 border-warning-100 dark:bg-warning-950 dark:text-warning-200 dark:border-warning-800',
36
+ danger:
37
+ 'bg-danger-50 text-danger-700 border-danger-100 dark:bg-danger-950 dark:text-danger-200 dark:border-danger-800',
38
+ info:
39
+ 'bg-info-50 text-info-700 border-info-100 dark:bg-info-950 dark:text-info-200 dark:border-info-800',
40
+ }
41
+
42
+ const tagSizes: Record<TagSize, string> = {
43
+ sm: 'px-2 py-0.5 text-[11px]',
44
+ md: 'px-2.5 py-0.5 text-xs',
45
+ }
46
+
47
+ export function Tag(props: TagProps) {
48
+ const [local, others] = splitProps(props, ['variant', 'size', 'statusColor', 'statusLabel', 'color', 'class', 'style', 'children'])
49
+ const variant = () => local.variant ?? 'neutral'
50
+ const size = () => local.size ?? 'md'
51
+
52
+ const customStyle = createMemo((): JSX.CSSProperties => {
53
+ const c = local.color
54
+ if (!c) return {}
55
+ return {
56
+ 'background-color': `color-mix(in srgb, ${c} 10%, transparent)`,
57
+ 'border-color': `color-mix(in srgb, ${c} 25%, transparent)`,
58
+ color: c,
59
+ }
60
+ })
61
+
62
+ const mergedStyle = createMemo((): JSX.CSSProperties => {
63
+ const base = customStyle()
64
+ const s = local.style
65
+ return typeof s === 'object' && s != null ? { ...base, ...s } as JSX.CSSProperties : base
66
+ })
67
+
68
+ return (
69
+ <span
70
+ class={cn(
71
+ 'inline-flex items-center gap-1 rounded-full border font-medium',
72
+ !local.color && tagVariants[variant()],
73
+ tagSizes[size()],
74
+ local.class,
75
+ )}
76
+ style={mergedStyle()}
77
+ {...others}
78
+ >
79
+ <Show when={local.statusColor}>
80
+ <span
81
+ class="size-2 shrink-0 rounded-full ring-1 ring-ink-200/80 dark:ring-ink-500/80"
82
+ style={{ 'background-color': local.statusColor }}
83
+ aria-hidden={local.statusLabel ? undefined : 'true'}
84
+ role={local.statusLabel ? 'img' : undefined}
85
+ aria-label={local.statusLabel}
86
+ />
87
+ </Show>
88
+ {local.children}
89
+ </span>
90
+ )
91
+ }