datool 0.0.1
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/README.md +218 -0
- package/client-dist/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2 +0 -0
- package/client-dist/assets/geist-latin-ext-wght-normal-DMtmJ5ZE.woff2 +0 -0
- package/client-dist/assets/geist-latin-wght-normal-Dm3htQBi.woff2 +0 -0
- package/client-dist/assets/index-BeRNeRUq.css +1 -0
- package/client-dist/assets/index-uoZ4c_I8.js +164 -0
- package/client-dist/index.html +13 -0
- package/index.html +12 -0
- package/package.json +55 -0
- package/src/client/App.tsx +885 -0
- package/src/client/components/connection-status.tsx +43 -0
- package/src/client/components/data-table-cell.tsx +235 -0
- package/src/client/components/data-table-col-icon.tsx +73 -0
- package/src/client/components/data-table-header-col.tsx +225 -0
- package/src/client/components/data-table-search-input.tsx +729 -0
- package/src/client/components/data-table.tsx +2014 -0
- package/src/client/components/stream-controls.tsx +157 -0
- package/src/client/components/theme-provider.tsx +230 -0
- package/src/client/components/ui/button.tsx +68 -0
- package/src/client/components/ui/combobox.tsx +308 -0
- package/src/client/components/ui/context-menu.tsx +261 -0
- package/src/client/components/ui/dropdown-menu.tsx +267 -0
- package/src/client/components/ui/input-group.tsx +153 -0
- package/src/client/components/ui/input.tsx +19 -0
- package/src/client/components/ui/textarea.tsx +18 -0
- package/src/client/components/viewer-settings.tsx +185 -0
- package/src/client/index.css +192 -0
- package/src/client/lib/data-table-search.ts +750 -0
- package/src/client/lib/datool-icons.ts +37 -0
- package/src/client/lib/datool-url-state.ts +159 -0
- package/src/client/lib/filterable-table.ts +146 -0
- package/src/client/lib/table-search-persistence.ts +94 -0
- package/src/client/lib/utils.ts +6 -0
- package/src/client/main.tsx +14 -0
- package/src/index.ts +19 -0
- package/src/node/cli.ts +54 -0
- package/src/node/config.ts +231 -0
- package/src/node/lines.ts +82 -0
- package/src/node/runtime.ts +102 -0
- package/src/node/server.ts +403 -0
- package/src/node/sources/command.ts +82 -0
- package/src/node/sources/file.ts +116 -0
- package/src/node/sources/ssh.ts +59 -0
- package/src/shared/columns.ts +41 -0
- package/src/shared/types.ts +188 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import {
|
|
3
|
+
ChevronDownIcon,
|
|
4
|
+
LoaderCircleIcon,
|
|
5
|
+
PlayCircleIcon,
|
|
6
|
+
StopCircleIcon,
|
|
7
|
+
XIcon,
|
|
8
|
+
} from "lucide-react"
|
|
9
|
+
|
|
10
|
+
import { Button } from "@/components/ui/button"
|
|
11
|
+
import {
|
|
12
|
+
DropdownMenu,
|
|
13
|
+
DropdownMenuContent,
|
|
14
|
+
DropdownMenuLabel,
|
|
15
|
+
DropdownMenuRadioGroup,
|
|
16
|
+
DropdownMenuRadioItem,
|
|
17
|
+
DropdownMenuSeparator,
|
|
18
|
+
DropdownMenuTrigger,
|
|
19
|
+
} from "@/components/ui/dropdown-menu"
|
|
20
|
+
import { ConnectionStatus } from "@/components/connection-status"
|
|
21
|
+
import { cn } from "@/lib/utils"
|
|
22
|
+
import type { DatoolClientStream } from "../../shared/types"
|
|
23
|
+
|
|
24
|
+
type StreamControlsProps = {
|
|
25
|
+
streams: DatoolClientStream[]
|
|
26
|
+
selectedStreamId: string | null
|
|
27
|
+
isConnected: boolean
|
|
28
|
+
isConnecting: boolean
|
|
29
|
+
isDisabled?: boolean
|
|
30
|
+
canClear?: boolean
|
|
31
|
+
onClear: () => void
|
|
32
|
+
onPause: () => void
|
|
33
|
+
onPlay: () => void
|
|
34
|
+
onSelectStream: (streamId: string) => void
|
|
35
|
+
className?: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function StreamControls({
|
|
39
|
+
streams,
|
|
40
|
+
selectedStreamId,
|
|
41
|
+
isConnected,
|
|
42
|
+
isConnecting,
|
|
43
|
+
isDisabled = false,
|
|
44
|
+
canClear = true,
|
|
45
|
+
onClear,
|
|
46
|
+
onPause,
|
|
47
|
+
onPlay,
|
|
48
|
+
onSelectStream,
|
|
49
|
+
className,
|
|
50
|
+
}: StreamControlsProps) {
|
|
51
|
+
const selectedStreamLabel = React.useMemo(() => {
|
|
52
|
+
if (!selectedStreamId) {
|
|
53
|
+
return streams[0]?.label ?? "Select a stream"
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
streams.find((stream) => stream.id === selectedStreamId)?.label ??
|
|
58
|
+
"Select a stream"
|
|
59
|
+
)
|
|
60
|
+
}, [selectedStreamId, streams])
|
|
61
|
+
|
|
62
|
+
const canOpenMenu = !isDisabled && streams.length > 0
|
|
63
|
+
const canPlay = !isDisabled && Boolean(selectedStreamId) && !isConnecting
|
|
64
|
+
const playButtonLabel = isConnected ? "Pause stream" : "Play stream"
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<div className="flex items-center gap-2">
|
|
68
|
+
<div
|
|
69
|
+
className={cn(
|
|
70
|
+
"flex min-w-0 h-10 overflow-hidden rounded-md border border-input bg-background",
|
|
71
|
+
className
|
|
72
|
+
)}
|
|
73
|
+
>
|
|
74
|
+
<DropdownMenu>
|
|
75
|
+
<DropdownMenuTrigger asChild>
|
|
76
|
+
<Button
|
|
77
|
+
type="button"
|
|
78
|
+
variant="ghost"
|
|
79
|
+
className="h-10 min-w-0 border-0 flex-1 justify-between"
|
|
80
|
+
disabled={!canOpenMenu}
|
|
81
|
+
>
|
|
82
|
+
<span className="truncate text-sm">{selectedStreamLabel}</span>
|
|
83
|
+
<ChevronDownIcon className="size-4 text-muted-foreground" />
|
|
84
|
+
</Button>
|
|
85
|
+
</DropdownMenuTrigger>
|
|
86
|
+
<DropdownMenuContent
|
|
87
|
+
align="start"
|
|
88
|
+
sideOffset={10}
|
|
89
|
+
className="max-h-80 max-w-[min(24rem,calc(100vw-2rem))]"
|
|
90
|
+
>
|
|
91
|
+
<div className="px-2 py-1.5">
|
|
92
|
+
<ConnectionStatus
|
|
93
|
+
isConnected={isConnected}
|
|
94
|
+
isConnecting={isConnecting}
|
|
95
|
+
/>
|
|
96
|
+
</div>
|
|
97
|
+
<DropdownMenuSeparator />
|
|
98
|
+
<DropdownMenuLabel>Streams</DropdownMenuLabel>
|
|
99
|
+
<DropdownMenuSeparator />
|
|
100
|
+
<DropdownMenuRadioGroup
|
|
101
|
+
value={selectedStreamId ?? ""}
|
|
102
|
+
onValueChange={onSelectStream}
|
|
103
|
+
>
|
|
104
|
+
{streams.map((stream) => (
|
|
105
|
+
<DropdownMenuRadioItem
|
|
106
|
+
key={stream.id}
|
|
107
|
+
value={stream.id}
|
|
108
|
+
className="min-h-9 text-sm"
|
|
109
|
+
>
|
|
110
|
+
{stream.label}
|
|
111
|
+
</DropdownMenuRadioItem>
|
|
112
|
+
))}
|
|
113
|
+
</DropdownMenuRadioGroup>
|
|
114
|
+
</DropdownMenuContent>
|
|
115
|
+
</DropdownMenu>
|
|
116
|
+
<Button
|
|
117
|
+
type="button"
|
|
118
|
+
variant="ghost"
|
|
119
|
+
size="icon-xl"
|
|
120
|
+
disabled={!canClear}
|
|
121
|
+
aria-label="Clear rows"
|
|
122
|
+
onClick={onClear}
|
|
123
|
+
className="rounded-full"
|
|
124
|
+
>
|
|
125
|
+
<XIcon className="size-5" />
|
|
126
|
+
</Button>
|
|
127
|
+
</div>
|
|
128
|
+
|
|
129
|
+
<Button
|
|
130
|
+
type="button"
|
|
131
|
+
size="xl"
|
|
132
|
+
variant="outline"
|
|
133
|
+
disabled={!canPlay}
|
|
134
|
+
aria-label={playButtonLabel}
|
|
135
|
+
onClick={isConnected ? onPause : onPlay}
|
|
136
|
+
className={
|
|
137
|
+
cn(isConnected ? "border-blue-500 ring-1 ring-blue-500 text-blue-500!" : "",
|
|
138
|
+
"cursor-pointer gap-2"
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
>
|
|
142
|
+
<div className={cn("[&_svg]:shrink-0 [&_svg]:size-3",
|
|
143
|
+
isConnecting ? "animate-spin" : ""
|
|
144
|
+
)}>
|
|
145
|
+
{isConnecting ? (
|
|
146
|
+
<LoaderCircleIcon />
|
|
147
|
+
) : isConnected ? (
|
|
148
|
+
<StopCircleIcon className="[&_rect]:fill-current" />
|
|
149
|
+
) : (
|
|
150
|
+
<PlayCircleIcon className="[&_path]:fill-current" />
|
|
151
|
+
)}
|
|
152
|
+
</div>
|
|
153
|
+
Live
|
|
154
|
+
</Button>
|
|
155
|
+
</div>
|
|
156
|
+
)
|
|
157
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/* eslint-disable react-refresh/only-export-components */
|
|
2
|
+
import * as React from "react"
|
|
3
|
+
|
|
4
|
+
export type Theme = "dark" | "light" | "system"
|
|
5
|
+
type ResolvedTheme = "dark" | "light"
|
|
6
|
+
|
|
7
|
+
type ThemeProviderProps = {
|
|
8
|
+
children: React.ReactNode
|
|
9
|
+
defaultTheme?: Theme
|
|
10
|
+
storageKey?: string
|
|
11
|
+
disableTransitionOnChange?: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type ThemeProviderState = {
|
|
15
|
+
theme: Theme
|
|
16
|
+
setTheme: (theme: Theme) => void
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const COLOR_SCHEME_QUERY = "(prefers-color-scheme: dark)"
|
|
20
|
+
const THEME_VALUES: Theme[] = ["dark", "light", "system"]
|
|
21
|
+
|
|
22
|
+
const ThemeProviderContext = React.createContext<
|
|
23
|
+
ThemeProviderState | undefined
|
|
24
|
+
>(undefined)
|
|
25
|
+
|
|
26
|
+
function isTheme(value: string | null): value is Theme {
|
|
27
|
+
if (value === null) {
|
|
28
|
+
return false
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return THEME_VALUES.includes(value as Theme)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function getSystemTheme(): ResolvedTheme {
|
|
35
|
+
if (window.matchMedia(COLOR_SCHEME_QUERY).matches) {
|
|
36
|
+
return "dark"
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return "light"
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function disableTransitionsTemporarily() {
|
|
43
|
+
const style = document.createElement("style")
|
|
44
|
+
style.appendChild(
|
|
45
|
+
document.createTextNode(
|
|
46
|
+
"*,*::before,*::after{-webkit-transition:none!important;transition:none!important}"
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
document.head.appendChild(style)
|
|
50
|
+
|
|
51
|
+
return () => {
|
|
52
|
+
window.getComputedStyle(document.body)
|
|
53
|
+
requestAnimationFrame(() => {
|
|
54
|
+
requestAnimationFrame(() => {
|
|
55
|
+
style.remove()
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function isEditableTarget(target: EventTarget | null) {
|
|
62
|
+
if (!(target instanceof HTMLElement)) {
|
|
63
|
+
return false
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (target.isContentEditable) {
|
|
67
|
+
return true
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const editableParent = target.closest(
|
|
71
|
+
"input, textarea, select, [contenteditable='true']"
|
|
72
|
+
)
|
|
73
|
+
if (editableParent) {
|
|
74
|
+
return true
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return false
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function ThemeProvider({
|
|
81
|
+
children,
|
|
82
|
+
defaultTheme = "system",
|
|
83
|
+
storageKey = "theme",
|
|
84
|
+
disableTransitionOnChange = true,
|
|
85
|
+
...props
|
|
86
|
+
}: ThemeProviderProps) {
|
|
87
|
+
const [theme, setThemeState] = React.useState<Theme>(() => {
|
|
88
|
+
const storedTheme = localStorage.getItem(storageKey)
|
|
89
|
+
if (isTheme(storedTheme)) {
|
|
90
|
+
return storedTheme
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return defaultTheme
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
const setTheme = React.useCallback(
|
|
97
|
+
(nextTheme: Theme) => {
|
|
98
|
+
localStorage.setItem(storageKey, nextTheme)
|
|
99
|
+
setThemeState(nextTheme)
|
|
100
|
+
},
|
|
101
|
+
[storageKey]
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
const applyTheme = React.useCallback(
|
|
105
|
+
(nextTheme: Theme) => {
|
|
106
|
+
const root = document.documentElement
|
|
107
|
+
const resolvedTheme =
|
|
108
|
+
nextTheme === "system" ? getSystemTheme() : nextTheme
|
|
109
|
+
const restoreTransitions = disableTransitionOnChange
|
|
110
|
+
? disableTransitionsTemporarily()
|
|
111
|
+
: null
|
|
112
|
+
|
|
113
|
+
root.classList.remove("light", "dark")
|
|
114
|
+
root.classList.add(resolvedTheme)
|
|
115
|
+
|
|
116
|
+
if (restoreTransitions) {
|
|
117
|
+
restoreTransitions()
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
[disableTransitionOnChange]
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
React.useEffect(() => {
|
|
124
|
+
applyTheme(theme)
|
|
125
|
+
|
|
126
|
+
if (theme !== "system") {
|
|
127
|
+
return undefined
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const mediaQuery = window.matchMedia(COLOR_SCHEME_QUERY)
|
|
131
|
+
const handleChange = () => {
|
|
132
|
+
applyTheme("system")
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
mediaQuery.addEventListener("change", handleChange)
|
|
136
|
+
|
|
137
|
+
return () => {
|
|
138
|
+
mediaQuery.removeEventListener("change", handleChange)
|
|
139
|
+
}
|
|
140
|
+
}, [theme, applyTheme])
|
|
141
|
+
|
|
142
|
+
React.useEffect(() => {
|
|
143
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
144
|
+
if (event.repeat) {
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (event.metaKey || event.ctrlKey || event.altKey) {
|
|
149
|
+
return
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (isEditableTarget(event.target)) {
|
|
153
|
+
return
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (event.key.toLowerCase() !== "d") {
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
setThemeState((currentTheme) => {
|
|
161
|
+
const nextTheme =
|
|
162
|
+
currentTheme === "dark"
|
|
163
|
+
? "light"
|
|
164
|
+
: currentTheme === "light"
|
|
165
|
+
? "dark"
|
|
166
|
+
: getSystemTheme() === "dark"
|
|
167
|
+
? "light"
|
|
168
|
+
: "dark"
|
|
169
|
+
|
|
170
|
+
localStorage.setItem(storageKey, nextTheme)
|
|
171
|
+
return nextTheme
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
window.addEventListener("keydown", handleKeyDown)
|
|
176
|
+
|
|
177
|
+
return () => {
|
|
178
|
+
window.removeEventListener("keydown", handleKeyDown)
|
|
179
|
+
}
|
|
180
|
+
}, [storageKey])
|
|
181
|
+
|
|
182
|
+
React.useEffect(() => {
|
|
183
|
+
const handleStorageChange = (event: StorageEvent) => {
|
|
184
|
+
if (event.storageArea !== localStorage) {
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (event.key !== storageKey) {
|
|
189
|
+
return
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (isTheme(event.newValue)) {
|
|
193
|
+
setThemeState(event.newValue)
|
|
194
|
+
return
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
setThemeState(defaultTheme)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
window.addEventListener("storage", handleStorageChange)
|
|
201
|
+
|
|
202
|
+
return () => {
|
|
203
|
+
window.removeEventListener("storage", handleStorageChange)
|
|
204
|
+
}
|
|
205
|
+
}, [defaultTheme, storageKey])
|
|
206
|
+
|
|
207
|
+
const value = React.useMemo(
|
|
208
|
+
() => ({
|
|
209
|
+
theme,
|
|
210
|
+
setTheme,
|
|
211
|
+
}),
|
|
212
|
+
[theme, setTheme]
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
return (
|
|
216
|
+
<ThemeProviderContext.Provider {...props} value={value}>
|
|
217
|
+
{children}
|
|
218
|
+
</ThemeProviderContext.Provider>
|
|
219
|
+
)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export const useTheme = () => {
|
|
223
|
+
const context = React.useContext(ThemeProviderContext)
|
|
224
|
+
|
|
225
|
+
if (context === undefined) {
|
|
226
|
+
throw new Error("useTheme must be used within a ThemeProvider")
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return context
|
|
230
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/* eslint-disable react-refresh/only-export-components */
|
|
2
|
+
import * as React from "react"
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
4
|
+
import { Slot } from "radix-ui"
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils"
|
|
7
|
+
|
|
8
|
+
const buttonVariants = cva(
|
|
9
|
+
"group/button inline-flex shrink-0 items-center justify-center rounded-md border border-transparent bg-clip-padding text-xs/relaxed font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/30 active:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
10
|
+
{
|
|
11
|
+
variants: {
|
|
12
|
+
variant: {
|
|
13
|
+
default: "bg-gray-900 text-white hover:bg-gray-800",
|
|
14
|
+
outline:
|
|
15
|
+
"border-border hover:bg-input/50 hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:bg-input/30",
|
|
16
|
+
secondary:
|
|
17
|
+
"bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
|
|
18
|
+
ghost:
|
|
19
|
+
"hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
|
|
20
|
+
destructive:
|
|
21
|
+
"bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
|
|
22
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
23
|
+
},
|
|
24
|
+
size: {
|
|
25
|
+
default:
|
|
26
|
+
"h-7 gap-1 px-2 text-xs/relaxed has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
|
|
27
|
+
xs: "h-5 gap-1 rounded-sm px-2 text-[0.625rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-2.5",
|
|
28
|
+
sm: "h-6 gap-1 px-2 text-xs/relaxed has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
|
|
29
|
+
lg: "h-8 gap-1 px-2.5 text-xs/relaxed has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2 [&_svg:not([class*='size-'])]:size-4",
|
|
30
|
+
xl: "h-10 gap-1 px-3 text-sm/relaxed has-data-[icon=inline-end]:pr-2.5 has-data-[icon=inline-start]:pl-2.5 [&_svg:not([class*='size-'])]:size-5",
|
|
31
|
+
icon: "size-7 [&_svg:not([class*='size-'])]:size-3.5",
|
|
32
|
+
"icon-xs": "size-5 rounded-sm [&_svg:not([class*='size-'])]:size-2.5",
|
|
33
|
+
"icon-sm": "size-6 [&_svg:not([class*='size-'])]:size-3",
|
|
34
|
+
"icon-lg": "size-8 [&_svg:not([class*='size-'])]:size-4",
|
|
35
|
+
"icon-xl": "size-10 [&_svg:not([class*='size-'])]:size-5",
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
defaultVariants: {
|
|
39
|
+
variant: "default",
|
|
40
|
+
size: "default",
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
function Button({
|
|
46
|
+
className,
|
|
47
|
+
variant = "default",
|
|
48
|
+
size = "default",
|
|
49
|
+
asChild = false,
|
|
50
|
+
...props
|
|
51
|
+
}: React.ComponentProps<"button"> &
|
|
52
|
+
VariantProps<typeof buttonVariants> & {
|
|
53
|
+
asChild?: boolean
|
|
54
|
+
}) {
|
|
55
|
+
const Comp = asChild ? Slot.Root : "button"
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<Comp
|
|
59
|
+
data-slot="button"
|
|
60
|
+
data-variant={variant}
|
|
61
|
+
data-size={size}
|
|
62
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
63
|
+
{...props}
|
|
64
|
+
/>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export { Button, buttonVariants }
|