@leitware/dockets 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/dist/cli.d.ts +3 -0
  2. package/dist/cli.d.ts.map +1 -0
  3. package/dist/cli.js +18 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/commands/add.d.ts +3 -0
  6. package/dist/commands/add.d.ts.map +1 -0
  7. package/dist/commands/add.js +86 -0
  8. package/dist/commands/add.js.map +1 -0
  9. package/dist/commands/list.d.ts +3 -0
  10. package/dist/commands/list.d.ts.map +1 -0
  11. package/dist/commands/list.js +36 -0
  12. package/dist/commands/list.js.map +1 -0
  13. package/dist/registry.d.ts +18 -0
  14. package/dist/registry.d.ts.map +1 -0
  15. package/dist/registry.js +712 -0
  16. package/dist/registry.js.map +1 -0
  17. package/package.json +40 -0
  18. package/templates/accordion.tsx +77 -0
  19. package/templates/alert-dialog.tsx +66 -0
  20. package/templates/alert.tsx +41 -0
  21. package/templates/aspect-ratio.tsx +15 -0
  22. package/templates/avatar.tsx +27 -0
  23. package/templates/badge.tsx +1 -0
  24. package/templates/block-loader.tsx +1 -0
  25. package/templates/breadcrumb.tsx +31 -0
  26. package/templates/button.tsx +1 -0
  27. package/templates/calendar.tsx +45 -0
  28. package/templates/card.tsx +35 -0
  29. package/templates/carousel.tsx +39 -0
  30. package/templates/checkbox.tsx +50 -0
  31. package/templates/code-block.tsx +1 -0
  32. package/templates/collapsible.tsx +35 -0
  33. package/templates/combobox.tsx +154 -0
  34. package/templates/command.tsx +50 -0
  35. package/templates/contact-footer.tsx +193 -0
  36. package/templates/context-menu.tsx +16 -0
  37. package/templates/dialog.tsx +67 -0
  38. package/templates/drawer.tsx +12 -0
  39. package/templates/dropdown-menu.tsx +95 -0
  40. package/templates/form-input.tsx +64 -0
  41. package/templates/form.tsx +10 -0
  42. package/templates/hover-card.tsx +5 -0
  43. package/templates/input-otp.tsx +6 -0
  44. package/templates/label.tsx +1 -0
  45. package/templates/layout-primitives.tsx +11 -0
  46. package/templates/layouts.tsx +346 -0
  47. package/templates/lib/utils.ts +49 -0
  48. package/templates/list-item.tsx +1 -0
  49. package/templates/list-items.tsx +41 -0
  50. package/templates/list.tsx +89 -0
  51. package/templates/logo.tsx +12 -0
  52. package/templates/marketing-footer.tsx +33 -0
  53. package/templates/marketing-header.tsx +46 -0
  54. package/templates/menubar.tsx +16 -0
  55. package/templates/navigation-menu.tsx +11 -0
  56. package/templates/pagination.tsx +86 -0
  57. package/templates/popover.tsx +8 -0
  58. package/templates/pricing-receipt.tsx +71 -0
  59. package/templates/pricing-tabs.tsx +60 -0
  60. package/templates/progress.tsx +29 -0
  61. package/templates/radio-group.tsx +58 -0
  62. package/templates/receipt-card.tsx +1 -0
  63. package/templates/receipt.tsx +269 -0
  64. package/templates/resizable.tsx +1 -0
  65. package/templates/scroll-area.tsx +1 -0
  66. package/templates/select.tsx +110 -0
  67. package/templates/separator.tsx +1 -0
  68. package/templates/sheet.tsx +12 -0
  69. package/templates/sidebar.tsx +15 -0
  70. package/templates/simple-footer.tsx +43 -0
  71. package/templates/simple-header.tsx +77 -0
  72. package/templates/skeleton.tsx +33 -0
  73. package/templates/slider.tsx +55 -0
  74. package/templates/styles/dockets.css +104 -0
  75. package/templates/switch.tsx +49 -0
  76. package/templates/table.tsx +73 -0
  77. package/templates/tabs.tsx +61 -0
  78. package/templates/theme-toggle.tsx +46 -0
  79. package/templates/toast.tsx +1 -0
  80. package/templates/toggle-group.tsx +1 -0
  81. package/templates/toggle.tsx +1 -0
  82. package/templates/tooltip.tsx +31 -0
  83. package/templates/tree-view.tsx +1 -0
  84. package/templates/ui/accordion.tsx +73 -0
  85. package/templates/ui/alert-dialog.tsx +128 -0
  86. package/templates/ui/alert.tsx +56 -0
  87. package/templates/ui/aspect-ratio.tsx +19 -0
  88. package/templates/ui/avatar.tsx +74 -0
  89. package/templates/ui/badge.tsx +48 -0
  90. package/templates/ui/block-loader.tsx +40 -0
  91. package/templates/ui/button.tsx +77 -0
  92. package/templates/ui/calendar.tsx +160 -0
  93. package/templates/ui/card.tsx +73 -0
  94. package/templates/ui/carousel.tsx +149 -0
  95. package/templates/ui/checkbox.tsx +33 -0
  96. package/templates/ui/code-block.tsx +36 -0
  97. package/templates/ui/collapsible.tsx +48 -0
  98. package/templates/ui/combobox.tsx +295 -0
  99. package/templates/ui/command.tsx +148 -0
  100. package/templates/ui/context-menu.tsx +212 -0
  101. package/templates/ui/dialog.tsx +138 -0
  102. package/templates/ui/drawer.tsx +134 -0
  103. package/templates/ui/dropdown-menu.tsx +254 -0
  104. package/templates/ui/form.tsx +122 -0
  105. package/templates/ui/hover-card.tsx +44 -0
  106. package/templates/ui/input-group.tsx +148 -0
  107. package/templates/ui/input-otp.tsx +153 -0
  108. package/templates/ui/input.tsx +20 -0
  109. package/templates/ui/label.tsx +17 -0
  110. package/templates/ui/layout.tsx +252 -0
  111. package/templates/ui/list-item.tsx +50 -0
  112. package/templates/ui/menubar.tsx +225 -0
  113. package/templates/ui/navigation-menu.tsx +117 -0
  114. package/templates/ui/pagination.tsx +110 -0
  115. package/templates/ui/popover.tsx +77 -0
  116. package/templates/ui/progress.tsx +37 -0
  117. package/templates/ui/radio-group.tsx +41 -0
  118. package/templates/ui/receipt-card.tsx +70 -0
  119. package/templates/ui/resizable.tsx +140 -0
  120. package/templates/ui/scroll-area.tsx +64 -0
  121. package/templates/ui/select.tsx +186 -0
  122. package/templates/ui/separator.tsx +21 -0
  123. package/templates/ui/sheet.tsx +134 -0
  124. package/templates/ui/sidebar.tsx +222 -0
  125. package/templates/ui/skeleton.tsx +35 -0
  126. package/templates/ui/slider.tsx +60 -0
  127. package/templates/ui/switch.tsx +33 -0
  128. package/templates/ui/table.tsx +114 -0
  129. package/templates/ui/tabs.tsx +79 -0
  130. package/templates/ui/textarea.tsx +18 -0
  131. package/templates/ui/toast.tsx +139 -0
  132. package/templates/ui/toggle-group.tsx +68 -0
  133. package/templates/ui/toggle.tsx +47 -0
  134. package/templates/ui/tooltip.tsx +53 -0
  135. package/templates/ui/tree-view.tsx +76 -0
@@ -0,0 +1 @@
1
+ export { Separator } from '@/components/ui/separator'
@@ -0,0 +1,12 @@
1
+ export {
2
+ Sheet,
3
+ SheetTrigger,
4
+ SheetClose,
5
+ SheetPortal,
6
+ SheetOverlay,
7
+ SheetContent,
8
+ SheetHeader,
9
+ SheetFooter,
10
+ SheetTitle,
11
+ SheetDescription,
12
+ } from '@/components/ui/sheet'
@@ -0,0 +1,15 @@
1
+ export {
2
+ SidebarProvider,
3
+ Sidebar,
4
+ SidebarHeader,
5
+ SidebarContent,
6
+ SidebarFooter,
7
+ SidebarNav,
8
+ SidebarNavItem,
9
+ SidebarGroup,
10
+ SidebarGroupLabel,
11
+ SidebarTrigger,
12
+ SidebarInset,
13
+ SidebarRail,
14
+ useSidebar,
15
+ } from '@/components/ui/sidebar'
@@ -0,0 +1,43 @@
1
+ import { Link } from '@tanstack/react-router'
2
+
3
+ interface SimpleFooterProps {
4
+ /**
5
+ * Maximum width class for the footer content
6
+ * @default "max-w-[768px]"
7
+ */
8
+ maxWidth?: string
9
+ /**
10
+ * Company name to display
11
+ * @default "Leitware Ltd"
12
+ */
13
+ companyName?: string
14
+ }
15
+
16
+ export function SimpleFooter({
17
+ maxWidth = 'max-w-[768px]',
18
+ companyName = 'Leitware Ltd',
19
+ }: SimpleFooterProps) {
20
+ return (
21
+ <footer>
22
+ <div className={`${maxWidth} mx-auto px-6 py-8`}>
23
+ <div className="text-center text-[var(--muted-color)] text-[10px] uppercase mb-2">
24
+ © {new Date().getFullYear()} {companyName}
25
+ </div>
26
+ <div className="flex gap-4 justify-center">
27
+ <Link
28
+ to="/terms-and-conditions"
29
+ className="text-[var(--muted-color)] text-[10px] uppercase no-underline hover:underline"
30
+ >
31
+ Terms & Conditions
32
+ </Link>
33
+ <Link
34
+ to="/privacy"
35
+ className="text-[var(--muted-color)] text-[10px] uppercase no-underline hover:underline"
36
+ >
37
+ Privacy Policy
38
+ </Link>
39
+ </div>
40
+ </div>
41
+ </footer>
42
+ )
43
+ }
@@ -0,0 +1,77 @@
1
+ import { useQuery } from 'convex/react'
2
+ import { Link } from '@tanstack/react-router'
3
+ import { api } from '../../convex/_generated/api'
4
+ import { Logo } from '@/components/logo'
5
+ import { ThemeToggle } from '@/components/theme-toggle'
6
+
7
+ interface NavLinkConfig {
8
+ to: string
9
+ label: string
10
+ useLink?: boolean
11
+ }
12
+
13
+ interface SimpleHeaderProps {
14
+ /**
15
+ * Single navigation link (shorthand for navLinks with one item)
16
+ */
17
+ navLink?: NavLinkConfig
18
+ /**
19
+ * Additional navigation links rendered alongside the primary link
20
+ */
21
+ navLinks?: NavLinkConfig[]
22
+ /**
23
+ * Whether to use Link component (for internal pages) or anchor (for home page)
24
+ * @default true
25
+ */
26
+ useLink?: boolean
27
+ /**
28
+ * Where the "For Agents" link points
29
+ * @default '/for-agents'
30
+ */
31
+ agentsTo?: string
32
+ }
33
+
34
+ export function SimpleHeader({
35
+ navLink,
36
+ navLinks,
37
+ useLink = true,
38
+ agentsTo = '/for-agents',
39
+ }: SimpleHeaderProps) {
40
+ const user = useQuery(api.myFunctions.currentUser)
41
+
42
+ return (
43
+ <nav className="w-full sticky top-0 z-50">
44
+ <div className="wrap flex justify-between items-center h-16">
45
+ <div className="flex items-center gap-4">
46
+ <Link to="/" className="flex items-center gap-2 no-underline">
47
+ <Logo className="w-6 h-6" />
48
+ <span className="font-bold">LEITWARE</span>
49
+ </Link>
50
+ <a href={agentsTo} className="text-[10px] uppercase no-underline">
51
+ For Agents
52
+ </a>
53
+ </div>
54
+ <div className="flex items-center gap-4">
55
+ {[...(navLink ? [navLink] : []), ...(navLinks ?? [])].map((link) => {
56
+ const useLinkComponent = link.useLink ?? useLink
57
+ return useLinkComponent ? (
58
+ <Link key={link.to} to={link.to} className="text-[10px] uppercase no-underline">
59
+ {link.label}
60
+ </Link>
61
+ ) : (
62
+ <a key={link.to} href={link.to} className="text-[10px] uppercase no-underline">
63
+ {link.label}
64
+ </a>
65
+ )
66
+ })}
67
+ {user !== undefined && (
68
+ <Link to="/account" className="text-[10px] uppercase no-underline">
69
+ {user ? `Signed in as ${user.email}` : 'Sign In / Sign Up'}
70
+ </Link>
71
+ )}
72
+ <ThemeToggle />
73
+ </div>
74
+ </div>
75
+ </nav>
76
+ )
77
+ }
@@ -0,0 +1,33 @@
1
+ import * as React from 'react'
2
+ import { Skeleton, SkeletonText } from '@/components/ui/skeleton'
3
+ import { cn } from '@/lib/utils'
4
+
5
+ export interface SkeletonCardProps {
6
+ className?: string
7
+ lines?: number
8
+ showAvatar?: boolean
9
+ }
10
+
11
+ function SkeletonCard({ className, lines = 3, showAvatar = false }: SkeletonCardProps) {
12
+ return (
13
+ <div
14
+ className={cn(
15
+ 'rounded-[var(--radius)] border-[length:var(--border-width)] border-foreground p-4',
16
+ className,
17
+ )}
18
+ >
19
+ {showAvatar && (
20
+ <div className="mb-3 flex items-center gap-2">
21
+ <Skeleton className="size-9 rounded-[var(--radius)]" />
22
+ <div className="flex flex-col gap-1.5">
23
+ <Skeleton className="h-3 w-24" />
24
+ <Skeleton className="h-3 w-16" />
25
+ </div>
26
+ </div>
27
+ )}
28
+ <SkeletonText lines={lines} />
29
+ </div>
30
+ )
31
+ }
32
+
33
+ export { SkeletonCard, Skeleton, SkeletonText }
@@ -0,0 +1,55 @@
1
+ import * as React from 'react'
2
+ import { Slider } from '@/components/ui/slider'
3
+ import { cn } from '@/lib/utils'
4
+
5
+ export interface LabelledSliderProps {
6
+ value?: number[]
7
+ defaultValue?: number[]
8
+ min?: number
9
+ max?: number
10
+ step?: number
11
+ label?: string
12
+ showValue?: boolean
13
+ onValueChange?: (value: number[]) => void
14
+ className?: string
15
+ disabled?: boolean
16
+ }
17
+
18
+ function LabelledSlider({
19
+ value,
20
+ defaultValue,
21
+ min = 0,
22
+ max = 100,
23
+ step = 1,
24
+ label,
25
+ showValue = false,
26
+ onValueChange,
27
+ className,
28
+ disabled,
29
+ }: LabelledSliderProps) {
30
+ const displayValue = value ?? defaultValue
31
+ return (
32
+ <div className={cn('flex flex-col gap-2', className)}>
33
+ {(label || showValue) && (
34
+ <div className="flex items-center justify-between text-xs font-medium uppercase tracking-wider">
35
+ {label && <span>{label}</span>}
36
+ {showValue && displayValue && (
37
+ <span className="text-muted-foreground">{displayValue.join(' – ')}</span>
38
+ )}
39
+ </div>
40
+ )}
41
+ <Slider
42
+ value={value}
43
+ defaultValue={defaultValue}
44
+ min={min}
45
+ max={max}
46
+ step={step}
47
+ onValueChange={onValueChange}
48
+ disabled={disabled}
49
+ />
50
+ </div>
51
+ )
52
+ }
53
+
54
+ export { LabelledSlider as Slider }
55
+ export { Slider as SliderRoot } from '@/components/ui/slider'
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Dockets Design System — CSS Tokens
3
+ * ====================================
4
+ *
5
+ * Install this file alongside your Tailwind CSS and import it in your
6
+ * root layout:
7
+ *
8
+ * import "@/styles/dockets.css";
9
+ *
10
+ * BORDER WIDTH
11
+ * ─────────────
12
+ * All dockets components use `--border-width` instead of a hardcoded `1px`.
13
+ * This lets you thicken every border in your app with a single variable:
14
+ *
15
+ * :root { --border-width: 2px; } ← chunky brutalist mode
16
+ * :root { --border-width: 1px; } ← default
17
+ * :root { --border-width: 0.5px; } ← hairline
18
+ *
19
+ * In Tailwind, reference it with the arbitrary-length syntax:
20
+ * border-[length:var(--border-width)]
21
+ * border-t-[length:var(--border-width)] etc.
22
+ *
23
+ * BORDER RADIUS
24
+ * ──────────────
25
+ * All dockets components use `--radius` instead of hardcoded values.
26
+ * Default is `0px` (brutalist). To soften edges:
27
+ *
28
+ * :root { --radius: 4px; } ← subtle rounding
29
+ * :root { --radius: 8px; } ← soft
30
+ * :root { --radius: 0px; } ← default (sharp corners)
31
+ *
32
+ * In Tailwind, reference it with:
33
+ * rounded-[var(--radius)]
34
+ *
35
+ * NO COLOUR TRANSITIONS
36
+ * ──────────────────────
37
+ * Dockets components must NEVER use `transition-colors`,
38
+ * `transition-[background]`, or any property that animates colour changes.
39
+ * Hover/focus state changes are instant — no fade, no easing.
40
+ * Only structural transitions are allowed (e.g. `transition-transform`
41
+ * for sliding, `transition-[width]` for collapsing panels).
42
+ *
43
+ * "BORDERS NEVER STACK" RULE
44
+ * ───────────────────────────
45
+ * Adjacent components must never produce double borders. Enforce this with:
46
+ *
47
+ * • List/stack items: `border-b-[length:var(--border-width)] [&:last-child]:border-b-0`
48
+ * on items, `border-[length:var(--border-width)]` on the container.
49
+ *
50
+ * • Newspaper grid: Container gets `border-t-[length:var(--border-width)] border-l-[length:var(--border-width)]`.
51
+ * Cells get `border-b-[length:var(--border-width)] border-r-[length:var(--border-width)]`.
52
+ * Result: every cell has exactly one border per side, no stacking.
53
+ *
54
+ * • Tab triggers: Use `[&:not(:first-child)]:border-l-[length:var(--border-width)]`
55
+ * so only the separator between tabs is drawn once.
56
+ *
57
+ * SPACING SCALE
58
+ * ─────────────
59
+ * Dockets uses Tailwind's default spacing scale (1 = 4px, 2 = 8px, …).
60
+ * Custom named tokens are provided below for macro-level layout spacing.
61
+ *
62
+ * --space-layout-sm → tight sections / modals
63
+ * --space-layout-md → standard page sections
64
+ * --space-layout-lg → generous hero/landing sections
65
+ *
66
+ * RECEIPT AESTHETIC VARS
67
+ * ───────────────────────
68
+ * Legacy vars used by ReceiptCard and CodeBlock are kept for compatibility:
69
+ * --border-color (maps to your theme's border colour)
70
+ * --receipt-bg (maps to your theme's card background)
71
+ * --bg-color (maps to your theme's page background)
72
+ * --text-color (maps to your theme's foreground)
73
+ */
74
+
75
+ @layer base {
76
+ :root {
77
+ /* ── Border width ──────────────────────────────── */
78
+ --border-width: 1px;
79
+
80
+ /* ── Border radius ─────────────────────────────── */
81
+ --radius: 0px;
82
+
83
+ /* ── Spacing (macro layout) ────────────────────── */
84
+ --space-layout-sm: 1.5rem; /* 24px */
85
+ --space-layout-md: 3rem; /* 48px */
86
+ --space-layout-lg: 6rem; /* 96px */
87
+
88
+ /* ── Spacing scale aliases ─────────────────────── */
89
+ --space-1: 0.25rem;
90
+ --space-2: 0.5rem;
91
+ --space-3: 0.75rem;
92
+ --space-4: 1rem;
93
+ --space-5: 1.25rem;
94
+ --space-6: 1.5rem;
95
+ --space-7: 1.75rem;
96
+ --space-8: 2rem;
97
+
98
+ /* ── Receipt aesthetic (legacy compat) ─────────── */
99
+ --border-color: hsl(var(--border));
100
+ --receipt-bg: hsl(var(--card));
101
+ --bg-color: hsl(var(--background));
102
+ --text-color: hsl(var(--foreground));
103
+ }
104
+ }
@@ -0,0 +1,49 @@
1
+ import * as React from 'react'
2
+ import { Switch } from '@/components/ui/switch'
3
+ import { Label } from '@/components/ui/label'
4
+ import { cn } from '@/lib/utils'
5
+
6
+ export interface SwitchFieldProps {
7
+ id?: string
8
+ label?: React.ReactNode
9
+ description?: string
10
+ checked?: boolean
11
+ defaultChecked?: boolean
12
+ onCheckedChange?: (checked: boolean) => void
13
+ disabled?: boolean
14
+ className?: string
15
+ }
16
+
17
+ function SwitchField({
18
+ id,
19
+ label,
20
+ description,
21
+ checked,
22
+ defaultChecked,
23
+ onCheckedChange,
24
+ disabled,
25
+ className,
26
+ }: SwitchFieldProps) {
27
+ const fieldId = id ?? React.useId()
28
+ return (
29
+ <div className={cn('flex items-center justify-between gap-4', className)}>
30
+ {label && (
31
+ <div className="grid gap-0.5">
32
+ <Label htmlFor={fieldId}>{label}</Label>
33
+ {description && (
34
+ <p className="text-xs/relaxed text-muted-foreground">{description}</p>
35
+ )}
36
+ </div>
37
+ )}
38
+ <Switch
39
+ id={fieldId}
40
+ checked={checked}
41
+ defaultChecked={defaultChecked}
42
+ onCheckedChange={onCheckedChange}
43
+ disabled={disabled}
44
+ />
45
+ </div>
46
+ )
47
+ }
48
+
49
+ export { SwitchField, Switch }
@@ -0,0 +1,73 @@
1
+ import * as React from 'react'
2
+ import {
3
+ Table,
4
+ TableHeader,
5
+ TableBody,
6
+ TableFooter,
7
+ TableRow,
8
+ TableHead,
9
+ TableCell,
10
+ TableCaption,
11
+ } from '@/components/ui/table'
12
+
13
+ export interface Column<T> {
14
+ key: keyof T | string
15
+ header: string
16
+ cell?: (row: T) => React.ReactNode
17
+ align?: 'left' | 'center' | 'right'
18
+ }
19
+
20
+ export interface DataTableProps<T> {
21
+ columns: Column<T>[]
22
+ data: T[]
23
+ caption?: string
24
+ keyField?: keyof T
25
+ }
26
+
27
+ function DataTable<T extends Record<string, unknown>>({
28
+ columns,
29
+ data,
30
+ caption,
31
+ keyField,
32
+ }: DataTableProps<T>) {
33
+ return (
34
+ <Table>
35
+ {caption && <TableCaption>{caption}</TableCaption>}
36
+ <TableHeader>
37
+ <TableRow>
38
+ {columns.map((col) => (
39
+ <TableHead
40
+ key={String(col.key)}
41
+ style={{ textAlign: col.align ?? 'left' }}
42
+ >
43
+ {col.header}
44
+ </TableHead>
45
+ ))}
46
+ </TableRow>
47
+ </TableHeader>
48
+ <TableBody>
49
+ {data.map((row, i) => (
50
+ <TableRow key={keyField ? String(row[keyField as string]) : i}>
51
+ {columns.map((col) => (
52
+ <TableCell key={String(col.key)} style={{ textAlign: col.align ?? 'left' }}>
53
+ {col.cell ? col.cell(row) : String(row[col.key as string] ?? '')}
54
+ </TableCell>
55
+ ))}
56
+ </TableRow>
57
+ ))}
58
+ </TableBody>
59
+ </Table>
60
+ )
61
+ }
62
+
63
+ export {
64
+ DataTable,
65
+ Table,
66
+ TableHeader,
67
+ TableBody,
68
+ TableFooter,
69
+ TableRow,
70
+ TableHead,
71
+ TableCell,
72
+ TableCaption,
73
+ }
@@ -0,0 +1,61 @@
1
+ import type { Tabs as TabsPrimitiveBase } from '@base-ui/react/tabs'
2
+ import type * as React from 'react'
3
+
4
+ import {
5
+ TabsContent,
6
+ TabsList,
7
+ Tabs as TabsPrimitive,
8
+ TabsTrigger,
9
+ tabsListVariants,
10
+ } from '@/components/ui/tabs'
11
+
12
+ export interface TabItemData {
13
+ value: string
14
+ label: React.ReactNode
15
+ content: React.ReactNode
16
+ disabled?: boolean
17
+ }
18
+
19
+ export interface TabsProps extends Omit<TabsPrimitiveBase.Root.Props, 'children'> {
20
+ items?: TabItemData[]
21
+ variant?: 'default' | 'line'
22
+ className?: string
23
+ listClassName?: string
24
+ children?: React.ReactNode
25
+ }
26
+
27
+ function Tabs({
28
+ items,
29
+ variant = 'default',
30
+ className,
31
+ listClassName,
32
+ children,
33
+ ...tabsProps
34
+ }: TabsProps) {
35
+ if (items) {
36
+ return (
37
+ <TabsPrimitive className={className} {...tabsProps}>
38
+ <TabsList variant={variant} className={listClassName}>
39
+ {items.map((item) => (
40
+ <TabsTrigger key={item.value} value={item.value} disabled={item.disabled}>
41
+ {item.label}
42
+ </TabsTrigger>
43
+ ))}
44
+ </TabsList>
45
+ {items.map((item) => (
46
+ <TabsContent key={item.value} value={item.value}>
47
+ {item.content}
48
+ </TabsContent>
49
+ ))}
50
+ </TabsPrimitive>
51
+ )
52
+ }
53
+
54
+ return (
55
+ <TabsPrimitive className={className} {...tabsProps}>
56
+ {children}
57
+ </TabsPrimitive>
58
+ )
59
+ }
60
+
61
+ export { Tabs, TabsContent, TabsList, TabsTrigger, tabsListVariants }
@@ -0,0 +1,46 @@
1
+ import { useEffect, useState } from 'react'
2
+ import { Button } from '@/components/button'
3
+
4
+ type Theme = 'light' | 'dark' | 'deep'
5
+
6
+ const themes: Theme[] = ['light', 'dark', 'deep']
7
+
8
+ const themeIcons: Record<Theme, string> = {
9
+ light: '☀︎',
10
+ dark: '☽',
11
+ deep: '✦',
12
+ }
13
+
14
+ export function ThemeToggle() {
15
+ const [theme, setTheme] = useState<Theme>('light')
16
+
17
+ useEffect(() => {
18
+ const saved = localStorage.getItem('theme') as Theme | null
19
+ if (saved && themes.includes(saved)) {
20
+ setTheme(saved)
21
+ document.documentElement.setAttribute('data-theme', saved)
22
+ }
23
+ }, [])
24
+
25
+ const cycleTheme = () => {
26
+ const currentIndex = themes.indexOf(theme)
27
+ const nextIndex = (currentIndex + 1) % themes.length
28
+ const newTheme = themes[nextIndex]
29
+ setTheme(newTheme)
30
+ localStorage.setItem('theme', newTheme)
31
+ document.documentElement.setAttribute('data-theme', newTheme)
32
+ }
33
+
34
+ return (
35
+ <Button
36
+ variant="ghost"
37
+ size="icon"
38
+ onClick={cycleTheme}
39
+ className="text-base"
40
+ aria-label={`Current theme: ${theme}. Click to switch theme.`}
41
+ title={`Switch theme (current: ${theme})`}
42
+ >
43
+ {themeIcons[theme]}
44
+ </Button>
45
+ )
46
+ }
@@ -0,0 +1 @@
1
+ export { ToastProvider, Toaster, Toast, useToast } from '@/components/ui/toast'
@@ -0,0 +1 @@
1
+ export { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group'
@@ -0,0 +1 @@
1
+ export { Toggle, toggleVariants } from '@/components/ui/toggle'
@@ -0,0 +1,31 @@
1
+ import type * as React from 'react'
2
+
3
+ import {
4
+ TooltipContent,
5
+ Tooltip as TooltipPrimitive,
6
+ TooltipProvider,
7
+ TooltipTrigger,
8
+ } from '@/components/ui/tooltip'
9
+
10
+ export interface TooltipProps {
11
+ content: React.ReactNode
12
+ children: React.ReactNode
13
+ side?: 'top' | 'bottom' | 'left' | 'right'
14
+ align?: 'start' | 'center' | 'end'
15
+ className?: string
16
+ }
17
+
18
+ function Tooltip({ content, children, side = 'top', align = 'center', className }: TooltipProps) {
19
+ return (
20
+ <TooltipProvider delay={0}>
21
+ <TooltipPrimitive>
22
+ <TooltipTrigger>{children}</TooltipTrigger>
23
+ <TooltipContent side={side} align={align} className={className}>
24
+ {content}
25
+ </TooltipContent>
26
+ </TooltipPrimitive>
27
+ </TooltipProvider>
28
+ )
29
+ }
30
+
31
+ export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger }
@@ -0,0 +1 @@
1
+ export { TreeView } from '@/components/ui/tree-view'