@trycompai/design-system 1.0.0 → 1.0.2
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/package.json +6 -3
- package/src/components/atoms/badge.tsx +49 -0
- package/src/components/{ui → atoms}/button.tsx +6 -1
- package/src/components/{ui → atoms}/checkbox.tsx +3 -3
- package/src/components/{ui → atoms}/heading.tsx +6 -6
- package/src/components/atoms/index.ts +21 -0
- package/src/components/atoms/kbd.tsx +21 -0
- package/src/components/atoms/logo.tsx +52 -0
- package/src/components/{ui → atoms}/slider.tsx +4 -4
- package/src/components/{ui → atoms}/spinner.tsx +3 -3
- package/src/components/atoms/stack.tsx +97 -0
- package/src/components/{ui → atoms}/switch.tsx +1 -1
- package/src/components/{ui → atoms}/text.tsx +5 -1
- package/src/components/{ui → atoms}/textarea.tsx +8 -2
- package/src/components/{ui → atoms}/toggle.tsx +3 -6
- package/src/components/{ui → molecules}/accordion.tsx +3 -3
- package/src/components/molecules/ai-chat.tsx +217 -0
- package/src/components/{ui → molecules}/alert.tsx +5 -5
- package/src/components/{ui → molecules}/breadcrumb.tsx +9 -8
- package/src/components/{ui → molecules}/card.tsx +24 -5
- package/src/components/molecules/command-search.tsx +147 -0
- package/src/components/molecules/empty.tsx +82 -0
- package/src/components/{ui → molecules}/field.tsx +16 -37
- package/src/components/{ui → molecules}/hover-card.tsx +2 -8
- package/src/components/molecules/index.ts +29 -0
- package/src/components/{ui → molecules}/input-group.tsx +1 -1
- package/src/components/molecules/input-otp.tsx +70 -0
- package/src/components/{ui → molecules}/item.tsx +18 -36
- package/src/components/molecules/page-header.tsx +80 -0
- package/src/components/{ui → molecules}/pagination.tsx +14 -23
- package/src/components/{ui → molecules}/popover.tsx +4 -2
- package/src/components/molecules/radio-group.tsx +33 -0
- package/src/components/{ui → molecules}/scroll-area.tsx +8 -11
- package/src/components/{ui → molecules}/section.tsx +3 -3
- package/src/components/{ui → molecules}/select.tsx +22 -10
- package/src/components/molecules/settings.tsx +169 -0
- package/src/components/{ui → molecules}/table.tsx +16 -3
- package/src/components/molecules/tabs.tsx +70 -0
- package/src/components/molecules/theme-switcher.tsx +176 -0
- package/src/components/{ui → molecules}/toggle-group.tsx +1 -1
- package/src/components/organisms/alert-dialog.tsx +135 -0
- package/src/components/organisms/app-shell.tsx +822 -0
- package/src/components/{ui → organisms}/calendar.tsx +6 -7
- package/src/components/{ui → organisms}/carousel.tsx +9 -11
- package/src/components/{ui → organisms}/chart.tsx +9 -24
- package/src/components/{ui → organisms}/combobox.tsx +7 -7
- package/src/components/{ui → organisms}/command.tsx +3 -3
- package/src/components/{ui → organisms}/context-menu.tsx +23 -53
- package/src/components/{ui → organisms}/dialog.tsx +3 -3
- package/src/components/{ui → organisms}/dropdown-menu.tsx +8 -6
- package/src/components/organisms/index.ts +17 -0
- package/src/components/{ui → organisms}/menubar.tsx +3 -3
- package/src/components/organisms/navigation-menu.tsx +137 -0
- package/src/components/organisms/page-layout.tsx +95 -0
- package/src/components/{ui → organisms}/sheet.tsx +7 -7
- package/src/components/{ui → organisms}/sidebar.tsx +61 -86
- package/src/components/organisms/sonner.tsx +41 -0
- package/src/components/ui/index.ts +3 -61
- package/src/fonts/TWKLausanne/TWKLausanne-300-Italic.woff +0 -0
- package/src/fonts/TWKLausanne/TWKLausanne-300-Italic.woff2 +0 -0
- package/src/fonts/TWKLausanne/TWKLausanne-300.woff +0 -0
- package/src/fonts/TWKLausanne/TWKLausanne-300.woff2 +0 -0
- package/src/fonts/TWKLausanne/TWKLausanne-350-Italic.woff +0 -0
- package/src/fonts/TWKLausanne/TWKLausanne-350-Italic.woff2 +0 -0
- package/src/fonts/TWKLausanne/TWKLausanne-350.woff +0 -0
- package/src/fonts/TWKLausanne/TWKLausanne-350.woff2 +0 -0
- package/src/fonts/TWKLausanne/TWKLausanne-400-Italic.woff +0 -0
- package/src/fonts/TWKLausanne/TWKLausanne-400-Italic.woff2 +0 -0
- package/src/fonts/TWKLausanne/TWKLausanne-400.woff +0 -0
- package/src/fonts/TWKLausanne/TWKLausanne-400.woff2 +0 -0
- package/src/fonts/TWKLausanne/TWKLausanne-700-Italic.woff +0 -0
- package/src/fonts/TWKLausanne/TWKLausanne-700-Italic.woff2 +0 -0
- package/src/fonts/TWKLausanne/TWKLausanne-700.woff +0 -0
- package/src/fonts/TWKLausanne/TWKLausanne-700.woff2 +0 -0
- package/src/styles/globals.css +167 -23
- package/src/components/ui/alert-dialog.tsx +0 -161
- package/src/components/ui/badge.tsx +0 -48
- package/src/components/ui/empty.tsx +0 -94
- package/src/components/ui/input-otp.tsx +0 -84
- package/src/components/ui/kbd.tsx +0 -26
- package/src/components/ui/navigation-menu.tsx +0 -147
- package/src/components/ui/page-header.tsx +0 -51
- package/src/components/ui/page-layout.tsx +0 -65
- package/src/components/ui/radio-group.tsx +0 -37
- package/src/components/ui/sonner.tsx +0 -43
- package/src/components/ui/stack.tsx +0 -72
- package/src/components/ui/tabs.tsx +0 -69
- /package/src/components/{ui → atoms}/aspect-ratio.tsx +0 -0
- /package/src/components/{ui → atoms}/avatar.tsx +0 -0
- /package/src/components/{ui → atoms}/container.tsx +0 -0
- /package/src/components/{ui → atoms}/input.tsx +0 -0
- /package/src/components/{ui → atoms}/label.tsx +0 -0
- /package/src/components/{ui → atoms}/progress.tsx +0 -0
- /package/src/components/{ui → atoms}/separator.tsx +0 -0
- /package/src/components/{ui → atoms}/skeleton.tsx +0 -0
- /package/src/components/{ui → molecules}/button-group.tsx +0 -0
- /package/src/components/{ui → molecules}/collapsible.tsx +0 -0
- /package/src/components/{ui → molecules}/grid.tsx +0 -0
- /package/src/components/{ui → molecules}/resizable.tsx +0 -0
- /package/src/components/{ui → molecules}/tooltip.tsx +0 -0
- /package/src/components/{ui → organisms}/drawer.tsx +0 -0
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import { ScrollArea as ScrollAreaPrimitive } from '@base-ui/react/scroll-area';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
function ScrollArea({
|
|
4
|
+
children,
|
|
5
|
+
...props
|
|
6
|
+
}: Omit<ScrollAreaPrimitive.Root.Props, 'className'>) {
|
|
6
7
|
return (
|
|
7
8
|
<ScrollAreaPrimitive.Root
|
|
8
9
|
data-slot="scroll-area"
|
|
9
|
-
className=
|
|
10
|
+
className="relative overflow-hidden size-full"
|
|
10
11
|
{...props}
|
|
11
12
|
>
|
|
12
13
|
<ScrollAreaPrimitive.Viewport
|
|
13
14
|
data-slot="scroll-area-viewport"
|
|
14
|
-
className="focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1"
|
|
15
|
+
className="focus-visible:ring-ring/50 size-full overflow-auto rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1"
|
|
15
16
|
>
|
|
16
17
|
{children}
|
|
17
18
|
</ScrollAreaPrimitive.Viewport>
|
|
@@ -22,19 +23,15 @@ function ScrollArea({ className, children, ...props }: ScrollAreaPrimitive.Root.
|
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
function ScrollBar({
|
|
25
|
-
className,
|
|
26
26
|
orientation = 'vertical',
|
|
27
27
|
...props
|
|
28
|
-
}: ScrollAreaPrimitive.Scrollbar.Props) {
|
|
28
|
+
}: Omit<ScrollAreaPrimitive.Scrollbar.Props, 'className'>) {
|
|
29
29
|
return (
|
|
30
30
|
<ScrollAreaPrimitive.Scrollbar
|
|
31
31
|
data-slot="scroll-area-scrollbar"
|
|
32
32
|
data-orientation={orientation}
|
|
33
33
|
orientation={orientation}
|
|
34
|
-
className=
|
|
35
|
-
'data-horizontal:h-2.5 data-horizontal:flex-col data-horizontal:border-t data-horizontal:border-t-transparent data-vertical:h-full data-vertical:w-2.5 data-vertical:border-l data-vertical:border-l-transparent flex touch-none p-px transition-colors select-none',
|
|
36
|
-
className,
|
|
37
|
-
)}
|
|
34
|
+
className="data-horizontal:h-2.5 data-horizontal:flex-col data-horizontal:border-t data-horizontal:border-t-transparent data-vertical:h-full data-vertical:w-2.5 data-vertical:border-l data-vertical:border-l-transparent flex touch-none p-px transition-colors select-none"
|
|
38
35
|
{...props}
|
|
39
36
|
>
|
|
40
37
|
<ScrollAreaPrimitive.Thumb
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
|
|
3
|
-
import { Heading } from '
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { Heading } from '../atoms/heading';
|
|
4
|
+
import { Text } from '../atoms/text';
|
|
5
|
+
import { Stack } from '../atoms/stack';
|
|
6
6
|
|
|
7
7
|
interface SectionProps extends Omit<React.ComponentProps<'section'>, 'className'> {
|
|
8
8
|
title?: string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { Select as SelectPrimitive } from '@base-ui/react/select';
|
|
4
|
-
import {
|
|
4
|
+
import { Checkmark, ChevronDown, ChevronUp } from '@carbon/icons-react';
|
|
5
5
|
import * as React from 'react';
|
|
6
6
|
|
|
7
7
|
const Select = SelectPrimitive.Root;
|
|
@@ -18,9 +18,21 @@ function SelectValue({
|
|
|
18
18
|
placeholder?: string;
|
|
19
19
|
}) {
|
|
20
20
|
return (
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
<>
|
|
22
|
+
<SelectPrimitive.Value
|
|
23
|
+
data-slot="select-value"
|
|
24
|
+
className="flex-1 text-left data-[placeholder]:hidden"
|
|
25
|
+
{...props}
|
|
26
|
+
/>
|
|
27
|
+
{placeholder && (
|
|
28
|
+
<span
|
|
29
|
+
data-slot="select-placeholder"
|
|
30
|
+
className="text-muted-foreground [[data-slot=select-value]:not([data-placeholder])~&]:hidden"
|
|
31
|
+
>
|
|
32
|
+
{placeholder}
|
|
33
|
+
</span>
|
|
34
|
+
)}
|
|
35
|
+
</>
|
|
24
36
|
);
|
|
25
37
|
}
|
|
26
38
|
|
|
@@ -35,12 +47,12 @@ function SelectTrigger({
|
|
|
35
47
|
<SelectPrimitive.Trigger
|
|
36
48
|
data-slot="select-trigger"
|
|
37
49
|
data-size={size}
|
|
38
|
-
className="border-input data-[placeholder]:text-muted-foreground dark:bg-input/30 dark:hover:bg-input/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 gap-1.5 rounded-lg border bg-transparent py-2 pr-2 pl-2.5 text-sm transition-colors select-none focus-visible:ring-[3px] aria-invalid:ring-[3px] data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *:data-[slot=select-value]:flex *:data-[slot=select-value]:gap-1.5 [&_svg:not([class*='size-'])]:size-4 flex w-
|
|
50
|
+
className="border-input data-[placeholder]:text-muted-foreground dark:bg-input/30 dark:hover:bg-input/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 gap-1.5 rounded-lg border bg-transparent py-2 pr-2 pl-2.5 text-sm transition-colors select-none focus-visible:ring-[3px] aria-invalid:ring-[3px] data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *:data-[slot=select-value]:flex *:data-[slot=select-value]:data-[placeholder]:absolute *:data-[slot=select-value]:data-[placeholder]:w-0 *:data-[slot=select-value]:data-[placeholder]:overflow-hidden *:data-[slot=select-value]:gap-1.5 [&_svg:not([class*='size-'])]:size-4 flex w-full items-center justify-between whitespace-nowrap outline-none disabled:cursor-not-allowed disabled:opacity-50 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:items-center [&_svg]:pointer-events-none [&_svg]:shrink-0"
|
|
39
51
|
{...props}
|
|
40
52
|
>
|
|
41
53
|
{children}
|
|
42
54
|
<SelectPrimitive.Icon
|
|
43
|
-
render={<
|
|
55
|
+
render={<ChevronDown className="text-muted-foreground size-4 pointer-events-none" />}
|
|
44
56
|
/>
|
|
45
57
|
</SelectPrimitive.Trigger>
|
|
46
58
|
);
|
|
@@ -71,7 +83,7 @@ function SelectContent({
|
|
|
71
83
|
>
|
|
72
84
|
<SelectPrimitive.Popup
|
|
73
85
|
data-slot="select-content"
|
|
74
|
-
className="bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 min-w-
|
|
86
|
+
className="bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 min-w-(--anchor-width) max-w-80 rounded-lg p-1 shadow-md ring-1 duration-100 relative isolate z-50 max-h-(--available-height) origin-(--transform-origin) overflow-x-hidden overflow-y-auto"
|
|
75
87
|
{...props}
|
|
76
88
|
>
|
|
77
89
|
<SelectScrollUpButton />
|
|
@@ -108,7 +120,7 @@ function SelectItem({ children, ...props }: Omit<SelectPrimitive.Item.Props, 'cl
|
|
|
108
120
|
<span className="pointer-events-none absolute right-2 flex size-4 items-center justify-center" />
|
|
109
121
|
}
|
|
110
122
|
>
|
|
111
|
-
<
|
|
123
|
+
<Checkmark className="pointer-events-none" />
|
|
112
124
|
</SelectPrimitive.ItemIndicator>
|
|
113
125
|
</SelectPrimitive.Item>
|
|
114
126
|
);
|
|
@@ -133,7 +145,7 @@ function SelectScrollUpButton({
|
|
|
133
145
|
className="bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-4 top-0 w-full"
|
|
134
146
|
{...props}
|
|
135
147
|
>
|
|
136
|
-
<
|
|
148
|
+
<ChevronUp />
|
|
137
149
|
</SelectPrimitive.ScrollUpArrow>
|
|
138
150
|
);
|
|
139
151
|
}
|
|
@@ -147,7 +159,7 @@ function SelectScrollDownButton({
|
|
|
147
159
|
className="bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-4 bottom-0 w-full"
|
|
148
160
|
{...props}
|
|
149
161
|
>
|
|
150
|
-
<
|
|
162
|
+
<ChevronDown />
|
|
151
163
|
</SelectPrimitive.ScrollDownArrow>
|
|
152
164
|
);
|
|
153
165
|
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
|
|
4
|
+
import { Heading } from '../atoms/heading';
|
|
5
|
+
import { Stack } from '../atoms/stack';
|
|
6
|
+
import { Text } from '../atoms/text';
|
|
7
|
+
|
|
8
|
+
const settingRowVariants = cva(
|
|
9
|
+
'flex w-full items-start justify-between gap-4 py-4 first:pt-0 last:pb-0',
|
|
10
|
+
{
|
|
11
|
+
variants: {
|
|
12
|
+
size: {
|
|
13
|
+
default: 'py-4',
|
|
14
|
+
sm: 'py-3',
|
|
15
|
+
lg: 'py-5',
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
defaultVariants: {
|
|
19
|
+
size: 'default',
|
|
20
|
+
},
|
|
21
|
+
}
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
interface SettingRowProps
|
|
25
|
+
extends Omit<React.ComponentProps<'div'>, 'className'>,
|
|
26
|
+
VariantProps<typeof settingRowVariants> {
|
|
27
|
+
/** The setting label */
|
|
28
|
+
label: string;
|
|
29
|
+
/** Optional description text */
|
|
30
|
+
description?: string;
|
|
31
|
+
/** Whether the setting is disabled */
|
|
32
|
+
disabled?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A horizontal row for a single setting with label/description on the left
|
|
37
|
+
* and a control (switch, button, select, etc.) on the right.
|
|
38
|
+
*/
|
|
39
|
+
function SettingRow({
|
|
40
|
+
label,
|
|
41
|
+
description,
|
|
42
|
+
disabled,
|
|
43
|
+
size,
|
|
44
|
+
children,
|
|
45
|
+
...props
|
|
46
|
+
}: SettingRowProps) {
|
|
47
|
+
return (
|
|
48
|
+
<div
|
|
49
|
+
data-slot="setting-row"
|
|
50
|
+
data-disabled={disabled || undefined}
|
|
51
|
+
className={settingRowVariants({ size })}
|
|
52
|
+
{...props}
|
|
53
|
+
>
|
|
54
|
+
<div className="min-w-0 flex-1">
|
|
55
|
+
<Stack gap="1">
|
|
56
|
+
<Text weight="medium" variant={disabled ? 'muted' : undefined}>
|
|
57
|
+
{label}
|
|
58
|
+
</Text>
|
|
59
|
+
{description && (
|
|
60
|
+
<Text size="sm" variant="muted">
|
|
61
|
+
{description}
|
|
62
|
+
</Text>
|
|
63
|
+
)}
|
|
64
|
+
</Stack>
|
|
65
|
+
</div>
|
|
66
|
+
<div className="flex shrink-0 items-center">{children}</div>
|
|
67
|
+
</div>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
interface SettingGroupProps extends Omit<React.ComponentProps<'div'>, 'className'> {
|
|
72
|
+
/** Whether to show dividers between rows */
|
|
73
|
+
divided?: boolean;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Groups multiple SettingRow components together, optionally with dividers.
|
|
78
|
+
*/
|
|
79
|
+
function SettingGroup({ divided = true, children, ...props }: SettingGroupProps) {
|
|
80
|
+
return (
|
|
81
|
+
<div
|
|
82
|
+
data-slot="setting-group"
|
|
83
|
+
data-divided={divided || undefined}
|
|
84
|
+
className={
|
|
85
|
+
divided
|
|
86
|
+
? '[&>[data-slot=setting-row]:not(:last-child)]:border-border [&>[data-slot=setting-row]:not(:last-child)]:border-b'
|
|
87
|
+
: ''
|
|
88
|
+
}
|
|
89
|
+
{...props}
|
|
90
|
+
>
|
|
91
|
+
{children}
|
|
92
|
+
</div>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
interface SettingLabelProps extends Omit<React.ComponentProps<'div'>, 'className'> {}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Custom label area for complex setting rows. Use when you need more than just text.
|
|
100
|
+
*/
|
|
101
|
+
function SettingLabel({ children, ...props }: SettingLabelProps) {
|
|
102
|
+
return (
|
|
103
|
+
<div data-slot="setting-label" className="min-w-0 flex-1" {...props}>
|
|
104
|
+
{children}
|
|
105
|
+
</div>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
interface SettingControlProps extends Omit<React.ComponentProps<'div'>, 'className'> {}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Control area for setting rows. Use when you need multiple controls or custom layout.
|
|
113
|
+
*/
|
|
114
|
+
function SettingControl({ children, ...props }: SettingControlProps) {
|
|
115
|
+
return (
|
|
116
|
+
<div data-slot="setting-control" className="flex shrink-0 items-center gap-2" {...props}>
|
|
117
|
+
{children}
|
|
118
|
+
</div>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
interface SettingsCardProps extends Omit<React.ComponentProps<'div'>, 'className'> {
|
|
123
|
+
/** Card title */
|
|
124
|
+
title: string;
|
|
125
|
+
/** Optional description */
|
|
126
|
+
description?: string;
|
|
127
|
+
/** Hint text shown in footer (left side) */
|
|
128
|
+
hint?: React.ReactNode;
|
|
129
|
+
/** Action button/content shown in footer (right side) */
|
|
130
|
+
action?: React.ReactNode;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* A self-contained settings card with title, description, content area, and footer.
|
|
135
|
+
* Footer shows hint text on left and action button on right.
|
|
136
|
+
*/
|
|
137
|
+
function SettingsCard({ title, description, hint, action, children, ...props }: SettingsCardProps) {
|
|
138
|
+
const hasFooter = hint || action;
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<div
|
|
142
|
+
data-slot="settings-card"
|
|
143
|
+
className="bg-card text-card-foreground rounded-xl border shadow-sm"
|
|
144
|
+
{...props}
|
|
145
|
+
>
|
|
146
|
+
<div className="px-6 pt-6 pb-4">
|
|
147
|
+
<Stack gap="1">
|
|
148
|
+
<Text weight="semibold">{title}</Text>
|
|
149
|
+
{description && (
|
|
150
|
+
<Text size="sm" variant="muted">
|
|
151
|
+
{description}
|
|
152
|
+
</Text>
|
|
153
|
+
)}
|
|
154
|
+
</Stack>
|
|
155
|
+
</div>
|
|
156
|
+
<div className="px-6 pb-6">{children}</div>
|
|
157
|
+
{hasFooter && (
|
|
158
|
+
<div className="border-t bg-muted/30 px-6 py-4">
|
|
159
|
+
<div className="flex items-center justify-between gap-4">
|
|
160
|
+
<div className="text-muted-foreground text-sm">{hint}</div>
|
|
161
|
+
<div className="flex shrink-0 items-center gap-2">{action}</div>
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
)}
|
|
165
|
+
</div>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export { SettingControl, SettingGroup, SettingLabel, SettingRow, SettingsCard };
|
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
|
|
3
|
-
function Table({
|
|
3
|
+
function Table({
|
|
4
|
+
variant = 'default',
|
|
5
|
+
...props
|
|
6
|
+
}: Omit<React.ComponentProps<'table'>, 'className'> & {
|
|
7
|
+
variant?: 'default' | 'bordered';
|
|
8
|
+
}) {
|
|
4
9
|
return (
|
|
5
|
-
<div
|
|
6
|
-
|
|
10
|
+
<div
|
|
11
|
+
data-slot="table-container"
|
|
12
|
+
data-variant={variant}
|
|
13
|
+
className="relative w-full overflow-x-auto data-[variant=bordered]:border data-[variant=bordered]:rounded-lg"
|
|
14
|
+
>
|
|
15
|
+
<table
|
|
16
|
+
data-slot="table"
|
|
17
|
+
className="w-full caption-bottom text-sm [&_[data-slot=text][data-default-size=true]]:text-sm"
|
|
18
|
+
{...props}
|
|
19
|
+
/>
|
|
7
20
|
</div>
|
|
8
21
|
);
|
|
9
22
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Tabs as TabsPrimitive } from '@base-ui/react/tabs';
|
|
4
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
5
|
+
|
|
6
|
+
function Tabs({
|
|
7
|
+
orientation = 'horizontal',
|
|
8
|
+
...props
|
|
9
|
+
}: Omit<TabsPrimitive.Root.Props, 'className'>) {
|
|
10
|
+
return (
|
|
11
|
+
<TabsPrimitive.Root
|
|
12
|
+
data-slot="tabs"
|
|
13
|
+
data-orientation={orientation}
|
|
14
|
+
className="group/tabs flex data-[orientation=horizontal]:flex-col"
|
|
15
|
+
{...props}
|
|
16
|
+
/>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const tabsListVariants = cva(
|
|
21
|
+
'rounded-lg p-[3px] group-data-horizontal/tabs:h-8 data-[variant=line]:rounded-none data-[variant=underline]:rounded-none group/tabs-list text-muted-foreground inline-flex w-fit items-center justify-center group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col',
|
|
22
|
+
{
|
|
23
|
+
variants: {
|
|
24
|
+
variant: {
|
|
25
|
+
default: 'bg-muted',
|
|
26
|
+
line: 'gap-1 bg-transparent',
|
|
27
|
+
underline: 'p-0 pb-px bg-transparent w-full justify-start items-stretch shadow-[inset_0_-1px_0_0_var(--color-border)]',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
defaultVariants: {
|
|
31
|
+
variant: 'default',
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
function TabsList({
|
|
37
|
+
variant = 'default',
|
|
38
|
+
...props
|
|
39
|
+
}: Omit<TabsPrimitive.List.Props, 'className'> & VariantProps<typeof tabsListVariants>) {
|
|
40
|
+
return (
|
|
41
|
+
<TabsPrimitive.List
|
|
42
|
+
data-slot="tabs-list"
|
|
43
|
+
data-variant={variant}
|
|
44
|
+
className={tabsListVariants({ variant })}
|
|
45
|
+
{...props}
|
|
46
|
+
/>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function TabsTrigger({ ...props }: Omit<TabsPrimitive.Tab.Props, 'className'>) {
|
|
51
|
+
return (
|
|
52
|
+
<TabsPrimitive.Tab
|
|
53
|
+
data-slot="tabs-trigger"
|
|
54
|
+
className="gap-1.5 rounded-md border border-transparent px-1.5 py-0.5 text-sm font-medium group-data-[variant=default]/tabs-list:data-active:shadow-sm group-data-[variant=line]/tabs-list:data-active:shadow-none group-data-[variant=underline]/tabs-list:data-active:shadow-none [&_svg:not([class*='size-'])]:size-4 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring text-foreground/60 hover:text-foreground dark:text-muted-foreground dark:hover:text-foreground relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center whitespace-nowrap transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent group-data-[variant=underline]/tabs-list:bg-transparent group-data-[variant=underline]/tabs-list:data-active:bg-transparent group-data-[variant=underline]/tabs-list:px-3 group-data-[variant=underline]/tabs-list:py-2 group-data-[variant=underline]/tabs-list:mb-0 group-data-[variant=underline]/tabs-list:hover:bg-muted group-data-[variant=underline]/tabs-list:rounded-t-md group-data-[variant=underline]/tabs-list:flex-none group-data-[variant=underline]/tabs-list:justify-start group-data-[variant=underline]/tabs-list:text-left dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=underline]/tabs-list:data-active:border-transparent dark:group-data-[variant=underline]/tabs-list:data-active:bg-transparent data-active:bg-background dark:data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 data-active:text-foreground after:absolute after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:after:bg-foreground group-data-[variant=underline]/tabs-list:after:bg-primary group-data-[variant=line]/tabs-list:data-active:after:opacity-100 group-data-[variant=underline]/tabs-list:after:h-[3px] group-data-[variant=underline]/tabs-list:after:bottom-0 group-data-[variant=underline]/tabs-list:after:rounded-t-sm group-data-[variant=underline]/tabs-list:h-auto group-data-[variant=underline]/tabs-list:data-active:after:opacity-100"
|
|
55
|
+
{...props}
|
|
56
|
+
/>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function TabsContent({ ...props }: Omit<TabsPrimitive.Panel.Props, 'className'>) {
|
|
61
|
+
return (
|
|
62
|
+
<TabsPrimitive.Panel
|
|
63
|
+
data-slot="tabs-content"
|
|
64
|
+
className="text-sm flex-1 outline-none animate-in fade-in-0 duration-200"
|
|
65
|
+
{...props}
|
|
66
|
+
/>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export { Tabs, TabsContent, TabsList, tabsListVariants, TabsTrigger };
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
4
|
+
import { Moon, Screen, Sun } from '@carbon/icons-react';
|
|
5
|
+
import * as React from 'react';
|
|
6
|
+
|
|
7
|
+
const themeSwitcherVariants = cva(
|
|
8
|
+
'inline-flex items-center rounded-full p-0.5 bg-muted',
|
|
9
|
+
{
|
|
10
|
+
variants: {
|
|
11
|
+
size: {
|
|
12
|
+
sm: 'gap-0.5',
|
|
13
|
+
default: 'gap-0.5',
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
defaultVariants: {
|
|
17
|
+
size: 'default',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const themeSwitcherButtonVariants = cva(
|
|
23
|
+
'inline-flex items-center justify-center rounded-full transition-all duration-200',
|
|
24
|
+
{
|
|
25
|
+
variants: {
|
|
26
|
+
size: {
|
|
27
|
+
sm: 'size-6 [&_svg]:size-3',
|
|
28
|
+
default: 'size-7 [&_svg]:size-3.5',
|
|
29
|
+
},
|
|
30
|
+
isActive: {
|
|
31
|
+
true: 'bg-background text-foreground shadow-sm',
|
|
32
|
+
false: 'text-muted-foreground hover:text-foreground',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
defaultVariants: {
|
|
36
|
+
size: 'default',
|
|
37
|
+
isActive: false,
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
type Theme = 'light' | 'dark' | 'system';
|
|
43
|
+
|
|
44
|
+
interface ThemeSwitcherProps
|
|
45
|
+
extends Omit<React.ComponentProps<'div'>, 'className' | 'onChange'>,
|
|
46
|
+
VariantProps<typeof themeSwitcherVariants> {
|
|
47
|
+
/** Current theme value */
|
|
48
|
+
value?: Theme;
|
|
49
|
+
/** Default theme value (uncontrolled) */
|
|
50
|
+
defaultValue?: Theme;
|
|
51
|
+
/** Called when theme changes */
|
|
52
|
+
onChange?: (theme: Theme) => void;
|
|
53
|
+
/** Show system option */
|
|
54
|
+
showSystem?: boolean;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function ThemeSwitcher({
|
|
58
|
+
value: valueProp,
|
|
59
|
+
defaultValue = 'system',
|
|
60
|
+
onChange,
|
|
61
|
+
showSystem = true,
|
|
62
|
+
size = 'default',
|
|
63
|
+
...props
|
|
64
|
+
}: ThemeSwitcherProps) {
|
|
65
|
+
const [internalValue, setInternalValue] = React.useState<Theme>(defaultValue);
|
|
66
|
+
const value = valueProp ?? internalValue;
|
|
67
|
+
|
|
68
|
+
const handleChange = (newTheme: Theme) => {
|
|
69
|
+
if (valueProp === undefined) {
|
|
70
|
+
setInternalValue(newTheme);
|
|
71
|
+
}
|
|
72
|
+
onChange?.(newTheme);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const options: { value: Theme; icon: React.ReactNode; label: string }[] = [
|
|
76
|
+
{ value: 'light', icon: <Sun />, label: 'Light mode' },
|
|
77
|
+
{ value: 'dark', icon: <Moon />, label: 'Dark mode' },
|
|
78
|
+
...(showSystem
|
|
79
|
+
? [{ value: 'system' as Theme, icon: <Screen />, label: 'System theme' }]
|
|
80
|
+
: []),
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<div
|
|
85
|
+
data-slot="theme-switcher"
|
|
86
|
+
role="radiogroup"
|
|
87
|
+
aria-label="Theme"
|
|
88
|
+
className={themeSwitcherVariants({ size })}
|
|
89
|
+
{...props}
|
|
90
|
+
>
|
|
91
|
+
{options.map((option) => (
|
|
92
|
+
<button
|
|
93
|
+
key={option.value}
|
|
94
|
+
type="button"
|
|
95
|
+
role="radio"
|
|
96
|
+
aria-checked={value === option.value}
|
|
97
|
+
aria-label={option.label}
|
|
98
|
+
onClick={() => handleChange(option.value)}
|
|
99
|
+
className={themeSwitcherButtonVariants({
|
|
100
|
+
size,
|
|
101
|
+
isActive: value === option.value,
|
|
102
|
+
})}
|
|
103
|
+
>
|
|
104
|
+
{option.icon}
|
|
105
|
+
</button>
|
|
106
|
+
))}
|
|
107
|
+
</div>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Simple light/dark toggle for compact spaces
|
|
112
|
+
interface ThemeToggleProps extends Omit<React.ComponentProps<'button'>, 'className' | 'onChange'> {
|
|
113
|
+
/** Current theme - true for dark, false for light */
|
|
114
|
+
isDark?: boolean;
|
|
115
|
+
/** Called when theme changes */
|
|
116
|
+
onChange?: (isDark: boolean) => void;
|
|
117
|
+
/** Size variant */
|
|
118
|
+
size?: 'sm' | 'default';
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function ThemeToggle({
|
|
122
|
+
isDark: isDarkProp,
|
|
123
|
+
onChange,
|
|
124
|
+
size = 'default',
|
|
125
|
+
...props
|
|
126
|
+
}: ThemeToggleProps) {
|
|
127
|
+
const [internalIsDark, setInternalIsDark] = React.useState(false);
|
|
128
|
+
const isDark = isDarkProp ?? internalIsDark;
|
|
129
|
+
|
|
130
|
+
const handleToggle = () => {
|
|
131
|
+
const newValue = !isDark;
|
|
132
|
+
if (isDarkProp === undefined) {
|
|
133
|
+
setInternalIsDark(newValue);
|
|
134
|
+
}
|
|
135
|
+
onChange?.(newValue);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const iconSize = size === 'sm' ? 'size-3' : 'size-3.5';
|
|
139
|
+
const buttonSize = size === 'sm' ? 'h-6 w-12' : 'h-7 w-14';
|
|
140
|
+
const thumbSize = size === 'sm' ? 'size-5' : 'size-6';
|
|
141
|
+
const thumbTranslate = size === 'sm' ? 'translate-x-[26px]' : 'translate-x-[30px]';
|
|
142
|
+
|
|
143
|
+
return (
|
|
144
|
+
<button
|
|
145
|
+
type="button"
|
|
146
|
+
role="switch"
|
|
147
|
+
aria-checked={isDark}
|
|
148
|
+
aria-label={isDark ? 'Switch to light mode' : 'Switch to dark mode'}
|
|
149
|
+
onClick={handleToggle}
|
|
150
|
+
data-slot="theme-toggle"
|
|
151
|
+
className={`${buttonSize} relative inline-flex items-center rounded-full bg-muted p-0.5 transition-colors`}
|
|
152
|
+
{...props}
|
|
153
|
+
>
|
|
154
|
+
{/* Background icons */}
|
|
155
|
+
<span className="absolute inset-0 flex items-center justify-between px-1.5">
|
|
156
|
+
<Sun className={`${iconSize} text-amber-500`} />
|
|
157
|
+
<Moon className={`${iconSize} text-blue-400`} />
|
|
158
|
+
</span>
|
|
159
|
+
{/* Sliding thumb */}
|
|
160
|
+
<span
|
|
161
|
+
className={`${thumbSize} relative z-10 flex items-center justify-center rounded-full bg-background shadow-sm transition-transform duration-200 ${
|
|
162
|
+
isDark ? thumbTranslate : 'translate-x-0'
|
|
163
|
+
}`}
|
|
164
|
+
>
|
|
165
|
+
{isDark ? (
|
|
166
|
+
<Moon className={`${iconSize} text-blue-500`} />
|
|
167
|
+
) : (
|
|
168
|
+
<Sun className={`${iconSize} text-amber-500`} />
|
|
169
|
+
)}
|
|
170
|
+
</span>
|
|
171
|
+
</button>
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export { ThemeSwitcher, ThemeToggle };
|
|
176
|
+
export type { ThemeSwitcherProps, ThemeToggleProps, Theme };
|
|
@@ -6,7 +6,7 @@ import { type VariantProps } from 'class-variance-authority';
|
|
|
6
6
|
import * as React from 'react';
|
|
7
7
|
|
|
8
8
|
import { cn } from '../../../lib/utils';
|
|
9
|
-
import { toggleVariants } from '
|
|
9
|
+
import { toggleVariants } from '../atoms/toggle';
|
|
10
10
|
|
|
11
11
|
const ToggleGroupContext = React.createContext<
|
|
12
12
|
VariantProps<typeof toggleVariants> & {
|