@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.
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +18 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/add.d.ts +3 -0
- package/dist/commands/add.d.ts.map +1 -0
- package/dist/commands/add.js +86 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/list.d.ts +3 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +36 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/registry.d.ts +18 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +712 -0
- package/dist/registry.js.map +1 -0
- package/package.json +40 -0
- package/templates/accordion.tsx +77 -0
- package/templates/alert-dialog.tsx +66 -0
- package/templates/alert.tsx +41 -0
- package/templates/aspect-ratio.tsx +15 -0
- package/templates/avatar.tsx +27 -0
- package/templates/badge.tsx +1 -0
- package/templates/block-loader.tsx +1 -0
- package/templates/breadcrumb.tsx +31 -0
- package/templates/button.tsx +1 -0
- package/templates/calendar.tsx +45 -0
- package/templates/card.tsx +35 -0
- package/templates/carousel.tsx +39 -0
- package/templates/checkbox.tsx +50 -0
- package/templates/code-block.tsx +1 -0
- package/templates/collapsible.tsx +35 -0
- package/templates/combobox.tsx +154 -0
- package/templates/command.tsx +50 -0
- package/templates/contact-footer.tsx +193 -0
- package/templates/context-menu.tsx +16 -0
- package/templates/dialog.tsx +67 -0
- package/templates/drawer.tsx +12 -0
- package/templates/dropdown-menu.tsx +95 -0
- package/templates/form-input.tsx +64 -0
- package/templates/form.tsx +10 -0
- package/templates/hover-card.tsx +5 -0
- package/templates/input-otp.tsx +6 -0
- package/templates/label.tsx +1 -0
- package/templates/layout-primitives.tsx +11 -0
- package/templates/layouts.tsx +346 -0
- package/templates/lib/utils.ts +49 -0
- package/templates/list-item.tsx +1 -0
- package/templates/list-items.tsx +41 -0
- package/templates/list.tsx +89 -0
- package/templates/logo.tsx +12 -0
- package/templates/marketing-footer.tsx +33 -0
- package/templates/marketing-header.tsx +46 -0
- package/templates/menubar.tsx +16 -0
- package/templates/navigation-menu.tsx +11 -0
- package/templates/pagination.tsx +86 -0
- package/templates/popover.tsx +8 -0
- package/templates/pricing-receipt.tsx +71 -0
- package/templates/pricing-tabs.tsx +60 -0
- package/templates/progress.tsx +29 -0
- package/templates/radio-group.tsx +58 -0
- package/templates/receipt-card.tsx +1 -0
- package/templates/receipt.tsx +269 -0
- package/templates/resizable.tsx +1 -0
- package/templates/scroll-area.tsx +1 -0
- package/templates/select.tsx +110 -0
- package/templates/separator.tsx +1 -0
- package/templates/sheet.tsx +12 -0
- package/templates/sidebar.tsx +15 -0
- package/templates/simple-footer.tsx +43 -0
- package/templates/simple-header.tsx +77 -0
- package/templates/skeleton.tsx +33 -0
- package/templates/slider.tsx +55 -0
- package/templates/styles/dockets.css +104 -0
- package/templates/switch.tsx +49 -0
- package/templates/table.tsx +73 -0
- package/templates/tabs.tsx +61 -0
- package/templates/theme-toggle.tsx +46 -0
- package/templates/toast.tsx +1 -0
- package/templates/toggle-group.tsx +1 -0
- package/templates/toggle.tsx +1 -0
- package/templates/tooltip.tsx +31 -0
- package/templates/tree-view.tsx +1 -0
- package/templates/ui/accordion.tsx +73 -0
- package/templates/ui/alert-dialog.tsx +128 -0
- package/templates/ui/alert.tsx +56 -0
- package/templates/ui/aspect-ratio.tsx +19 -0
- package/templates/ui/avatar.tsx +74 -0
- package/templates/ui/badge.tsx +48 -0
- package/templates/ui/block-loader.tsx +40 -0
- package/templates/ui/button.tsx +77 -0
- package/templates/ui/calendar.tsx +160 -0
- package/templates/ui/card.tsx +73 -0
- package/templates/ui/carousel.tsx +149 -0
- package/templates/ui/checkbox.tsx +33 -0
- package/templates/ui/code-block.tsx +36 -0
- package/templates/ui/collapsible.tsx +48 -0
- package/templates/ui/combobox.tsx +295 -0
- package/templates/ui/command.tsx +148 -0
- package/templates/ui/context-menu.tsx +212 -0
- package/templates/ui/dialog.tsx +138 -0
- package/templates/ui/drawer.tsx +134 -0
- package/templates/ui/dropdown-menu.tsx +254 -0
- package/templates/ui/form.tsx +122 -0
- package/templates/ui/hover-card.tsx +44 -0
- package/templates/ui/input-group.tsx +148 -0
- package/templates/ui/input-otp.tsx +153 -0
- package/templates/ui/input.tsx +20 -0
- package/templates/ui/label.tsx +17 -0
- package/templates/ui/layout.tsx +252 -0
- package/templates/ui/list-item.tsx +50 -0
- package/templates/ui/menubar.tsx +225 -0
- package/templates/ui/navigation-menu.tsx +117 -0
- package/templates/ui/pagination.tsx +110 -0
- package/templates/ui/popover.tsx +77 -0
- package/templates/ui/progress.tsx +37 -0
- package/templates/ui/radio-group.tsx +41 -0
- package/templates/ui/receipt-card.tsx +70 -0
- package/templates/ui/resizable.tsx +140 -0
- package/templates/ui/scroll-area.tsx +64 -0
- package/templates/ui/select.tsx +186 -0
- package/templates/ui/separator.tsx +21 -0
- package/templates/ui/sheet.tsx +134 -0
- package/templates/ui/sidebar.tsx +222 -0
- package/templates/ui/skeleton.tsx +35 -0
- package/templates/ui/slider.tsx +60 -0
- package/templates/ui/switch.tsx +33 -0
- package/templates/ui/table.tsx +114 -0
- package/templates/ui/tabs.tsx +79 -0
- package/templates/ui/textarea.tsx +18 -0
- package/templates/ui/toast.tsx +139 -0
- package/templates/ui/toggle-group.tsx +68 -0
- package/templates/ui/toggle.tsx +47 -0
- package/templates/ui/tooltip.tsx +53 -0
- package/templates/ui/tree-view.tsx +76 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Separator } from '@/components/ui/separator'
|
|
@@ -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'
|