@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,3 @@
1
+ export { TableView, useTableView, emptyFilterGroup } from './TableView'
2
+ export type { TableViewProps, TableViewContextValue } from './TableView'
3
+ export type { TableViewConfig, TableViewsApi, ViewConfig } from './types'
@@ -0,0 +1,51 @@
1
+ import type { FilterGroup } from '../../../types/filter-types'
2
+ import type { ViewCustomizerField } from '../../forms/ViewCustomizer'
3
+ import type { FilterFieldConfig } from '../../forms/FilterBuilder/FilterGroupBlock'
4
+
5
+ export interface ViewConfig {
6
+ id: string
7
+ label: string
8
+ fields: string[]
9
+ groupBy: string
10
+ filters: FilterGroup
11
+ scope: 'user' | 'tenant'
12
+ /** When true, this view is used as the default when opening. System default view is fallback when no view is pinned. */
13
+ pinned?: boolean
14
+ }
15
+
16
+ /** API for persisting views. When provided, TableView fetches/saves views via these methods. */
17
+ export interface TableViewsApi {
18
+ fetchViews: (entityType: string) => Promise<ViewConfig[]>
19
+ createView: (entityType: string, view: Omit<ViewConfig, 'id'>) => Promise<ViewConfig>
20
+ updateView: (entityType: string, view: ViewConfig) => Promise<void>
21
+ deleteView: (entityType: string, viewId: string) => Promise<void>
22
+ }
23
+
24
+ export interface TableViewConfig {
25
+ /** Entity type for API (e.g. 'contacts', 'jobs'). Required when viewsApi is provided. */
26
+ entityType?: string
27
+ /** Default/system views. Must include the system default view (e.g. id: 'all'). */
28
+ defaultViews: ViewConfig[]
29
+ /** ID of the system default view shown when no view is pinned (e.g. 'all') */
30
+ systemDefaultViewId: string
31
+ /** All fields available for columns (used in ViewCustomizer) */
32
+ allFields: ViewCustomizerField[]
33
+ /** Fields for the filter builder */
34
+ filterFields: FilterFieldConfig[]
35
+ /** Get operator options for a filter field type */
36
+ getOperatorsForType: (type: string) => { value: string; label: string }[]
37
+ /** Initial field IDs for new views */
38
+ initialFields: string[]
39
+ /** Row count for the count badge on tabs (fallback when getFilteredCountForView not provided) */
40
+ rowCount?: number
41
+ /** Return filtered row count for a specific view. When provided, each tab shows its own filtered count. */
42
+ getFilteredCountForView?: (view: ViewConfig) => number
43
+ /** Customize drawer title */
44
+ customizeTitle?: string
45
+ /** Customize drawer description */
46
+ customizeDescription?: string
47
+ /** Filter drawer title */
48
+ filterTitle?: string
49
+ /** Filter drawer description */
50
+ filterDescription?: string
51
+ }
@@ -0,0 +1,40 @@
1
+ import type { JSX, ParentComponent } from 'solid-js'
2
+
3
+ export interface WizardStepProps {
4
+ /** Step number (1-based) for display */
5
+ stepNumber?: number
6
+ /** Step title */
7
+ title: string
8
+ /** Optional description below title */
9
+ description?: string
10
+ /** Optional class for the wrapper */
11
+ class?: string
12
+ children: JSX.Element
13
+ }
14
+
15
+ /** Wraps one wizard step's content with optional step badge, title, and description. */
16
+ export const WizardStep: ParentComponent<WizardStepProps> = (props) => {
17
+ return (
18
+ <div class={props.class}>
19
+ <div class="mb-1 flex items-center gap-3">
20
+ {props.stepNumber != null && (
21
+ <span
22
+ class="inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-primary-500/10 text-sm font-semibold text-primary-600 dark:bg-primary-500/20 dark:text-primary-400"
23
+ aria-hidden
24
+ >
25
+ {props.stepNumber}
26
+ </span>
27
+ )}
28
+ <h2 class="font-semibold tracking-tight text-xl text-ink-900">
29
+ {props.title}
30
+ </h2>
31
+ </div>
32
+ {props.description && (
33
+ <p class="mb-6 text-[0.9375rem] text-ink-500">
34
+ {props.description}
35
+ </p>
36
+ )}
37
+ {props.children}
38
+ </div>
39
+ )
40
+ }
@@ -0,0 +1,173 @@
1
+ import { Check, ChevronRight } from 'lucide-solid'
2
+ import { cn } from '../../utilities/classNames'
3
+
4
+ export type WizardStepperVariant = 'default' | 'compact' | 'pills' | 'chevrons'
5
+
6
+ export interface WizardStepperProps {
7
+ /** Current step (1-based) */
8
+ step: number
9
+ /** Total number of steps */
10
+ totalSteps: number
11
+ /** Step labels (e.g. ['About you', 'Your company']) */
12
+ stepLabels: string[]
13
+ /** 'horizontal' | 'vertical' */
14
+ orientation?: 'horizontal' | 'vertical'
15
+ /** Visual style: default (circles + line), compact (smaller), pills (tag-style + progress bar), or chevrons (chevron separators). Default: default. */
16
+ variant?: WizardStepperVariant
17
+ /** Optional class for the root */
18
+ class?: string
19
+ }
20
+
21
+ /** Reusable stepper: numbered steps with labels and connector. */
22
+ export function WizardStepper(props: WizardStepperProps) {
23
+ const orientation = () => props.orientation ?? 'horizontal'
24
+ const currentStep = () => props.step
25
+ const variant = () => props.variant ?? 'default'
26
+ const isCompact = () => variant() === 'compact'
27
+ const isPills = () => variant() === 'pills'
28
+ const isChevrons = () => variant() === 'chevrons'
29
+
30
+ // Pills: horizontal only, tag-style labels with progress bar underneath
31
+ if (isPills() && orientation() === 'horizontal') {
32
+ const progressPercent = (currentStep() / props.stepLabels.length) * 100
33
+ return (
34
+ <nav class={cn('wizard-stepper wizard-stepper--pills', props.class)} aria-label="Progress">
35
+ <div class="flex flex-col gap-3">
36
+ <ol class="m-0 flex list-none gap-2 p-0">
37
+ {props.stepLabels.map((label, index) => {
38
+ const stepNum = index + 1
39
+ const isActive = currentStep() === stepNum
40
+ const isCompleted = currentStep() > stepNum
41
+ return (
42
+ <li
43
+ class="flex min-w-0 flex-1 flex-col items-stretch gap-1.5"
44
+ aria-current={isActive ? 'step' : undefined}
45
+ >
46
+ <div class="flex h-5 w-full shrink-0 items-center justify-center">
47
+ {isCompleted ? (
48
+ <span class="flex h-5 w-5 items-center justify-center rounded-full bg-primary-500 text-white" aria-hidden="true">
49
+ <Check size={12} strokeWidth={2.5} />
50
+ </span>
51
+ ) : isActive ? (
52
+ <span class="h-2 w-2 shrink-0 rounded-full bg-primary-500" aria-hidden="true" />
53
+ ) : (
54
+ <span class="h-2 w-2 shrink-0 rounded-full bg-transparent" aria-hidden="true" />
55
+ )}
56
+ </div>
57
+ <span
58
+ class={cn(
59
+ 'w-full rounded-full px-3 py-1.5 text-center text-sm font-medium',
60
+ isActive && 'bg-primary-500/15 text-primary-700 dark:bg-primary-400/20 dark:text-primary-300',
61
+ isCompleted && 'bg-surface-overlay text-ink-700',
62
+ !isActive && !isCompleted && 'bg-surface-overlay text-ink-500'
63
+ )}
64
+ >
65
+ {label}
66
+ </span>
67
+ </li>
68
+ )
69
+ })}
70
+ </ol>
71
+ <div class="h-1.5 w-full overflow-hidden rounded-full bg-surface-dim">
72
+ <div
73
+ class="h-full rounded-full bg-primary-500 transition-[width] duration-300 ease-out"
74
+ style={{ width: `${progressPercent}%` }}
75
+ aria-hidden
76
+ />
77
+ </div>
78
+ </div>
79
+ </nav>
80
+ )
81
+ }
82
+
83
+ return (
84
+ <nav class={cn('wizard-stepper', props.class)} aria-label="Progress">
85
+ <ol
86
+ class={cn(
87
+ 'm-0 flex list-none items-center p-0',
88
+ orientation() === 'vertical' && 'flex-col items-stretch gap-0',
89
+ orientation() === 'horizontal' && isChevrons() && 'w-full gap-2 sm:gap-6'
90
+ )}
91
+ >
92
+ {props.stepLabels.map((label, index) => {
93
+ const stepNum = index + 1
94
+ const isActive = currentStep() === stepNum
95
+ const isCompleted = currentStep() > stepNum
96
+ return (
97
+ <li
98
+ class={cn(
99
+ 'flex items-center',
100
+ orientation() === 'horizontal' &&
101
+ (isChevrons()
102
+ ? 'min-w-0 flex-1 flex-shrink-0 basis-0 justify-center gap-2 sm:gap-3'
103
+ : 'min-w-0 flex-1 first:min-w-0 first:flex-initial'),
104
+ orientation() === 'vertical' && 'flex-col items-start gap-0'
105
+ )}
106
+ aria-current={isActive ? 'step' : undefined}
107
+ >
108
+ {orientation() === 'horizontal' && index > 0 &&
109
+ (isChevrons() ? (
110
+ <span class="flex shrink-0 items-center text-ink-300" aria-hidden="true">
111
+ <ChevronRight size={isCompact() ? 16 : 20} />
112
+ </span>
113
+ ) : (
114
+ <span
115
+ class={cn(
116
+ 'h-0.5 min-w-[1rem] flex-1 shrink rounded transition-colors',
117
+ isCompact() ? 'ml-2 mr-2 sm:ml-2.5 sm:mr-2.5' : 'ml-3 mr-3 sm:ml-4 sm:mr-4',
118
+ isCompleted ? 'bg-primary-500' : 'bg-surface-dim'
119
+ )}
120
+ aria-hidden="true"
121
+ />
122
+ ))
123
+ }
124
+ <div
125
+ class={cn(
126
+ 'flex shrink-0 items-center gap-3',
127
+ isCompact() && 'gap-2',
128
+ orientation() === 'vertical' && 'py-2 first:pt-0',
129
+ isCompact() && orientation() === 'vertical' && 'py-1.5 first:pt-0'
130
+ )}
131
+ >
132
+ <span
133
+ class={cn(
134
+ 'flex shrink-0 items-center justify-center rounded-full text-sm font-semibold transition-colors',
135
+ isCompact() ? 'h-6 w-6 text-xs' : 'h-8 w-8',
136
+ isCompleted && 'bg-primary-500 text-white',
137
+ isActive && 'bg-primary-500 text-white ring-4 ring-primary-500/20 dark:ring-primary-500/30',
138
+ isCompact() && isActive && 'ring-2',
139
+ !isActive && !isCompleted && 'bg-ink-200 text-ink-500'
140
+ )}
141
+ >
142
+ {isCompleted ? <Check size={isCompact() ? 12 : 16} strokeWidth={2.5} /> : stepNum}
143
+ </span>
144
+ <span
145
+ class={cn(
146
+ 'font-medium',
147
+ isCompact() ? 'text-sm' : 'text-xs sm:text-sm',
148
+ orientation() === 'vertical' && !isCompact() && 'text-sm',
149
+ isActive && 'text-ink-900',
150
+ isCompleted && 'text-ink-600',
151
+ !isActive && !isCompleted && 'text-ink-400'
152
+ )}
153
+ >
154
+ {label}
155
+ </span>
156
+ </div>
157
+ {orientation() === 'vertical' && index < props.stepLabels.length - 1 && (
158
+ <span
159
+ class={cn(
160
+ 'shrink-0 rounded transition-colors',
161
+ isCompact() ? 'ml-3 h-3 w-0.5' : 'ml-4 h-4 w-0.5',
162
+ isCompleted ? 'bg-primary-500' : 'bg-surface-dim'
163
+ )}
164
+ aria-hidden="true"
165
+ />
166
+ )}
167
+ </li>
168
+ )
169
+ })}
170
+ </ol>
171
+ </nav>
172
+ )
173
+ }
@@ -0,0 +1,96 @@
1
+ export { Alert } from './Alert'
2
+ export type { AlertProps, AlertStatus, AlertAppearance } from './Alert'
3
+
4
+ export { AlertDialog } from '../overlays/AlertDialog'
5
+ export type { AlertDialogProps } from '../overlays/AlertDialog'
6
+
7
+ export { Divider } from './Divider'
8
+ export type { DividerProps, DividerStyle, DividerWeight } from './Divider'
9
+
10
+ export { Drawer } from '../overlays/Drawer'
11
+ export type { DrawerProps, DrawerSize, DrawerSide, DrawerOffset, DrawerActionsPosition } from '../overlays/Drawer'
12
+
13
+ export { Dialog } from '../overlays/Dialog'
14
+ export type { DialogProps, DialogSize, DialogOverlayAnimation, DialogPanelAnimation } from '../overlays/Dialog'
15
+ export { WizardStep } from './WizardStep'
16
+ export type { WizardStepProps } from './WizardStep'
17
+ export { WizardStepper } from './WizardStepper'
18
+ export type { WizardStepperProps, WizardStepperVariant } from './WizardStepper'
19
+ export { PromptWithAction } from './PromptWithAction'
20
+ export type { PromptWithActionAllProps } from './PromptWithAction'
21
+ export { Section } from './Section'
22
+ export type { SectionProps } from './Section'
23
+ export { Form } from './Form'
24
+ export type { FormProps } from './Form'
25
+ export { BlockQuote } from './BlockQuote'
26
+ export type { BlockQuoteProps, BlockQuoteJustify } from './BlockQuote'
27
+
28
+ export { PageHeading } from './PageHeading'
29
+ export type { PageHeadingProps } from './PageHeading'
30
+ export { Card } from './Card'
31
+ export type {
32
+ CardProps,
33
+ CardComponent,
34
+ CardHeaderProps,
35
+ CardImageProps,
36
+ CardAvatarTitleProps,
37
+ CardContentProps,
38
+ CardBodyProps,
39
+ } from './Card'
40
+ export { Container } from './Container'
41
+ export type { ContainerProps, ContainerSize, ContainerAlign } from './Container'
42
+ export { Grid } from './Grid'
43
+ export type { GridProps, GridCols, GridGap } from './Grid'
44
+ export { FormActions } from './FormActions'
45
+ export type { FormActionsAllProps } from './FormActions'
46
+ export { TablePanel } from './TablePanel'
47
+ export type { TablePanelProps } from './TablePanel'
48
+
49
+ export { TableView, useTableView, emptyFilterGroup } from './TableView'
50
+ export type { TableViewProps, TableViewContextValue } from './TableView'
51
+ export type { TableViewConfig, ViewConfig } from './TableView/types'
52
+
53
+ export { CodeBlock } from './CodeBlock/CodeBlock'
54
+ export type { CodeBlockProps, CodeBlockLanguage } from './CodeBlock/CodeBlock'
55
+
56
+ export {
57
+ AccordionRoot,
58
+ AccordionItem,
59
+ AccordionHeader,
60
+ AccordionTrigger,
61
+ AccordionContent,
62
+ AccordionContentStyled,
63
+ AccordionTriggerStyled,
64
+ AccordionItemStyled,
65
+ } from './Accordion'
66
+ export type { AccordionContentProps, AccordionTriggerStyledProps, AccordionItemStyledProps } from './Accordion'
67
+
68
+ export {
69
+ TooltipRoot,
70
+ TooltipTrigger,
71
+ TooltipPortal,
72
+ TooltipContentPrimitive,
73
+ TooltipArrow,
74
+ TooltipContent,
75
+ } from '../overlays/Tooltip'
76
+ export type { TooltipContentProps } from '../overlays/Tooltip'
77
+
78
+ export {
79
+ PopoverRoot,
80
+ PopoverTrigger,
81
+ PopoverAnchor,
82
+ PopoverPortal,
83
+ PopoverContentPrimitive,
84
+ PopoverArrow,
85
+ PopoverContent,
86
+ } from '../overlays/Popover'
87
+ export type { PopoverContentProps, PopoverRootProps, PopoverSide, PopoverAlign } from '../overlays/Popover'
88
+
89
+ export {
90
+ CollapsibleRoot,
91
+ CollapsibleTrigger,
92
+ CollapsibleContent,
93
+ CollapsibleContentStyled,
94
+ CollapsibleTriggerStyled,
95
+ } from './Collapsible'
96
+ export type { CollapsibleContentProps, CollapsibleTriggerStyledProps } from './Collapsible'
@@ -0,0 +1,66 @@
1
+ import { type JSX, For, Show, splitProps } from 'solid-js'
2
+ import { ChevronRight } from 'lucide-solid'
3
+ import { Breadcrumbs as KobalteBreadcrumbs } from '@kobalte/core/breadcrumbs'
4
+ import { cn } from '../../utilities/classNames'
5
+
6
+ export interface BreadcrumbItem {
7
+ /** Label to show. */
8
+ label: string
9
+ /** If set, rendered as a link (anchor). Use your router's Link in the app if needed. */
10
+ href?: string
11
+ }
12
+
13
+ export interface BreadcrumbsProps {
14
+ /** List of items. Last item is typically current page (no href). */
15
+ items: BreadcrumbItem[]
16
+ /** Optional separator between items. Default ChevronRight. */
17
+ separator?: JSX.Element
18
+ /** Root class. */
19
+ class?: string
20
+ }
21
+
22
+ function DefaultSeparator(): JSX.Element {
23
+ return <ChevronRight class="h-4 w-4 shrink-0 text-ink-400" aria-hidden="true" />
24
+ }
25
+
26
+ export function Breadcrumbs(props: BreadcrumbsProps) {
27
+ const [local] = splitProps(props, ['items', 'separator', 'class'])
28
+
29
+ const separator = () => local.separator ?? <DefaultSeparator />
30
+
31
+ return (
32
+ <KobalteBreadcrumbs class={cn('flex flex-wrap items-center gap-1 text-sm', local.class)}>
33
+ <ol class="flex flex-wrap items-center gap-1">
34
+ <For each={local.items}>
35
+ {(item, i) => {
36
+ const isLast = () => i() === local.items.length - 1
37
+ return (
38
+ <li class="flex items-center gap-1" classList={{ 'text-ink-500': !isLast() }}>
39
+ <Show when={i() > 0}>
40
+ <KobalteBreadcrumbs.Separator class="flex shrink-0">
41
+ {separator()}
42
+ </KobalteBreadcrumbs.Separator>
43
+ </Show>
44
+ <Show
45
+ when={!!item.href && !isLast()}
46
+ fallback={
47
+ <span class="font-medium text-ink-800" {...(isLast() && { 'aria-current': 'page' })}>
48
+ {item.label}
49
+ </span>
50
+ }
51
+ >
52
+ <KobalteBreadcrumbs.Link
53
+ href={item.href!}
54
+ class="text-ink-600 hover:text-ink-900 dark:hover:text-ink-100 underline-offset-2 hover:underline"
55
+ >
56
+ {item.label}
57
+ </KobalteBreadcrumbs.Link>
58
+ </Show>
59
+ </li>
60
+ )
61
+ }}
62
+ </For>
63
+ </ol>
64
+ </KobalteBreadcrumbs>
65
+ )
66
+ }
@@ -0,0 +1,86 @@
1
+ import { type JSX, splitProps } from 'solid-js'
2
+ import { DropdownMenu as KobalteDropdownMenu } from '@kobalte/core/dropdown-menu'
3
+ import type {
4
+ DropdownMenuContentProps as KobalteDropdownMenuContentProps,
5
+ DropdownMenuItemProps as KobalteDropdownMenuItemProps,
6
+ DropdownMenuSeparatorProps as KobalteDropdownMenuSeparatorProps,
7
+ DropdownMenuTriggerProps as KobalteDropdownMenuTriggerProps
8
+ } from '@kobalte/core/dropdown-menu'
9
+ import { cn } from '../../utilities/classNames'
10
+
11
+ export interface DropdownMenuContentProps extends KobalteDropdownMenuContentProps {
12
+ class?: string
13
+ children: JSX.Element
14
+ }
15
+
16
+ export function DropdownMenuContent(props: DropdownMenuContentProps) {
17
+ const [local, others] = splitProps(props, ['class', 'children'])
18
+ return (
19
+ <KobalteDropdownMenu.Portal>
20
+ <KobalteDropdownMenu.Content
21
+ class={cn(
22
+ 'z-50 min-w-[160px] rounded-lg border border-surface-border bg-surface-raised p-1 shadow-lg',
23
+ local.class,
24
+ )}
25
+ {...others}
26
+ >
27
+ {local.children}
28
+ </KobalteDropdownMenu.Content>
29
+ </KobalteDropdownMenu.Portal>
30
+ )
31
+ }
32
+
33
+ export interface DropdownMenuItemProps extends KobalteDropdownMenuItemProps {
34
+ class?: string
35
+ children: JSX.Element
36
+ }
37
+
38
+ export function DropdownMenuItem(props: DropdownMenuItemProps) {
39
+ const [local, others] = splitProps(props, ['class', 'children'])
40
+ return (
41
+ <KobalteDropdownMenu.Item
42
+ class={cn(
43
+ 'flex cursor-pointer select-none items-center rounded-md px-2 py-1.5 text-sm text-ink-700 outline-none',
44
+ 'data-[highlighted]:bg-surface-overlay data-[disabled]:opacity-50 data-[disabled]:cursor-not-allowed',
45
+ local.class,
46
+ )}
47
+ {...others}
48
+ >
49
+ {local.children}
50
+ </KobalteDropdownMenu.Item>
51
+ )
52
+ }
53
+
54
+ export interface DropdownMenuSeparatorProps extends KobalteDropdownMenuSeparatorProps {
55
+ class?: string
56
+ }
57
+
58
+ export function DropdownMenuSeparator(props: DropdownMenuSeparatorProps) {
59
+ const [local, others] = splitProps(props, ['class'])
60
+ return (
61
+ <KobalteDropdownMenu.Separator
62
+ class={cn('my-1 h-px bg-surface-dim', local.class)}
63
+ {...others}
64
+ />
65
+ )
66
+ }
67
+
68
+ export interface DropdownMenuTriggerProps extends Omit<KobalteDropdownMenuTriggerProps, 'class' | 'children'> {
69
+ class?: string
70
+ children: JSX.Element
71
+ }
72
+
73
+ export function DropdownMenuTrigger(props: DropdownMenuTriggerProps) {
74
+ const [local, others] = splitProps(props, ['class', 'children'])
75
+ return (
76
+ <KobalteDropdownMenu.Trigger
77
+ class={cn(local.class)}
78
+ {...others}
79
+ >
80
+ {local.children}
81
+ </KobalteDropdownMenu.Trigger>
82
+ )
83
+ }
84
+
85
+ // Re-export the root Kobalte DropdownMenu for convenience
86
+ export { DropdownMenu } from '@kobalte/core/dropdown-menu'