@multicomma/arkcn 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/README.md +78 -0
- package/bin/arkcn.mjs +78 -0
- package/cli/commands.mjs +251 -0
- package/cli/project.mjs +168 -0
- package/cli/registry.mjs +50 -0
- package/cli/ui.mjs +46 -0
- package/mcp/server.mjs +149 -0
- package/package.json +201 -0
- package/registry/base.css +680 -0
- package/registry/index.json +1833 -0
- package/registry/items/accordion.json +18 -0
- package/registry/items/activity-feed.json +18 -0
- package/registry/items/alert-dialog.json +18 -0
- package/registry/items/alert.json +17 -0
- package/registry/items/angle-slider.json +17 -0
- package/registry/items/app-shell.json +28 -0
- package/registry/items/aspect-ratio.json +13 -0
- package/registry/items/attachment.json +19 -0
- package/registry/items/avatar.json +17 -0
- package/registry/items/badge.json +18 -0
- package/registry/items/breadcrumb.json +18 -0
- package/registry/items/bubble.json +18 -0
- package/registry/items/button-group.json +19 -0
- package/registry/items/button.json +18 -0
- package/registry/items/calendar.json +19 -0
- package/registry/items/canvas.json +24 -0
- package/registry/items/card.json +15 -0
- package/registry/items/carousel.json +19 -0
- package/registry/items/cascader.json +23 -0
- package/registry/items/chart.json +17 -0
- package/registry/items/chat.json +23 -0
- package/registry/items/checkbox.json +18 -0
- package/registry/items/clipboard.json +19 -0
- package/registry/items/collapsible.json +15 -0
- package/registry/items/color-picker.json +19 -0
- package/registry/items/combobox.json +20 -0
- package/registry/items/command.json +21 -0
- package/registry/items/comment-thread.json +25 -0
- package/registry/items/context-menu.json +18 -0
- package/registry/items/controllable.json +14 -0
- package/registry/items/data-grid.json +24 -0
- package/registry/items/data-table-adapter.json +14 -0
- package/registry/items/data-table.json +32 -0
- package/registry/items/date-input.json +17 -0
- package/registry/items/dialog.json +19 -0
- package/registry/items/direction.json +13 -0
- package/registry/items/drag-preview.json +16 -0
- package/registry/items/drawer.json +18 -0
- package/registry/items/dropdown-menu.json +18 -0
- package/registry/items/editable.json +17 -0
- package/registry/items/empty.json +17 -0
- package/registry/items/field.json +19 -0
- package/registry/items/file-upload.json +19 -0
- package/registry/items/floating-panel.json +19 -0
- package/registry/items/floating-toolbar.json +16 -0
- package/registry/items/form.json +14 -0
- package/registry/items/format.json +15 -0
- package/registry/items/gantt.json +22 -0
- package/registry/items/history.json +14 -0
- package/registry/items/hotkeys.json +18 -0
- package/registry/items/hover-card.json +17 -0
- package/registry/items/image-cropper.json +17 -0
- package/registry/items/input-group.json +20 -0
- package/registry/items/input-otp.json +18 -0
- package/registry/items/input.json +15 -0
- package/registry/items/item.json +19 -0
- package/registry/items/json-tree-view.json +17 -0
- package/registry/items/kanban.json +26 -0
- package/registry/items/kbd.json +15 -0
- package/registry/items/label.json +15 -0
- package/registry/items/live-region.json +16 -0
- package/registry/items/marker.json +18 -0
- package/registry/items/marquee.json +17 -0
- package/registry/items/menubar.json +19 -0
- package/registry/items/message.json +15 -0
- package/registry/items/navigation-menu.json +19 -0
- package/registry/items/node-graph.json +22 -0
- package/registry/items/number-input.json +18 -0
- package/registry/items/pagination.json +18 -0
- package/registry/items/password-input.json +18 -0
- package/registry/items/popconfirm.json +20 -0
- package/registry/items/popover.json +18 -0
- package/registry/items/progress.json +17 -0
- package/registry/items/qr-code.json +17 -0
- package/registry/items/query-builder.json +23 -0
- package/registry/items/radio-group.json +17 -0
- package/registry/items/rating-group.json +18 -0
- package/registry/items/resizable.json +17 -0
- package/registry/items/rich-text-editor.json +37 -0
- package/registry/items/row-layout.json +15 -0
- package/registry/items/scheduler.json +23 -0
- package/registry/items/scroll-area.json +17 -0
- package/registry/items/segment-group.json +17 -0
- package/registry/items/select.json +19 -0
- package/registry/items/separator.json +15 -0
- package/registry/items/sheet.json +19 -0
- package/registry/items/sidebar.json +27 -0
- package/registry/items/signature-pad.json +19 -0
- package/registry/items/skeleton.json +15 -0
- package/registry/items/slider.json +17 -0
- package/registry/items/sonner.json +17 -0
- package/registry/items/spinner.json +17 -0
- package/registry/items/steps.json +19 -0
- package/registry/items/swap.json +17 -0
- package/registry/items/switch.json +17 -0
- package/registry/items/table.json +16 -0
- package/registry/items/tabs.json +18 -0
- package/registry/items/tags-input.json +18 -0
- package/registry/items/textarea.json +15 -0
- package/registry/items/time.json +14 -0
- package/registry/items/timer.json +18 -0
- package/registry/items/toc.json +17 -0
- package/registry/items/toggle-group.json +19 -0
- package/registry/items/toggle.json +18 -0
- package/registry/items/tooltip.json +17 -0
- package/registry/items/tour.json +19 -0
- package/registry/items/transfer-list.json +24 -0
- package/registry/items/tree-select.json +24 -0
- package/registry/items/tree-view.json +18 -0
- package/registry/items/use-mobile.json +13 -0
- package/registry/items/utils.json +17 -0
- package/registry/items/virtual-list.json +16 -0
- package/src/components/ui/accordion.tsx +111 -0
- package/src/components/ui/activity-feed.tsx +228 -0
- package/src/components/ui/alert-dialog.tsx +204 -0
- package/src/components/ui/alert.tsx +55 -0
- package/src/components/ui/angle-slider.tsx +117 -0
- package/src/components/ui/app-shell.tsx +985 -0
- package/src/components/ui/aspect-ratio.tsx +35 -0
- package/src/components/ui/attachment.tsx +193 -0
- package/src/components/ui/avatar.tsx +94 -0
- package/src/components/ui/badge.tsx +45 -0
- package/src/components/ui/breadcrumb.tsx +93 -0
- package/src/components/ui/bubble.tsx +119 -0
- package/src/components/ui/button-group.tsx +79 -0
- package/src/components/ui/button.tsx +65 -0
- package/src/components/ui/calendar.tsx +172 -0
- package/src/components/ui/canvas.tsx +723 -0
- package/src/components/ui/card.tsx +69 -0
- package/src/components/ui/carousel.tsx +228 -0
- package/src/components/ui/cascader.tsx +754 -0
- package/src/components/ui/chart.tsx +303 -0
- package/src/components/ui/chat.tsx +544 -0
- package/src/components/ui/checkbox.tsx +94 -0
- package/src/components/ui/clipboard.tsx +94 -0
- package/src/components/ui/collapsible.tsx +32 -0
- package/src/components/ui/color-picker.tsx +441 -0
- package/src/components/ui/combobox.tsx +400 -0
- package/src/components/ui/command.tsx +292 -0
- package/src/components/ui/comment-thread.tsx +923 -0
- package/src/components/ui/context-menu.tsx +331 -0
- package/src/components/ui/data-grid.tsx +1227 -0
- package/src/components/ui/data-table.tsx +993 -0
- package/src/components/ui/date-input.tsx +103 -0
- package/src/components/ui/dialog.tsx +159 -0
- package/src/components/ui/direction.tsx +32 -0
- package/src/components/ui/drawer.tsx +200 -0
- package/src/components/ui/dropdown-menu.tsx +329 -0
- package/src/components/ui/editable.tsx +90 -0
- package/src/components/ui/empty.tsx +86 -0
- package/src/components/ui/field.tsx +218 -0
- package/src/components/ui/file-upload.tsx +204 -0
- package/src/components/ui/floating-panel.tsx +199 -0
- package/src/components/ui/floating-toolbar.tsx +79 -0
- package/src/components/ui/format.tsx +22 -0
- package/src/components/ui/gantt.tsx +950 -0
- package/src/components/ui/hotkeys.tsx +340 -0
- package/src/components/ui/hover-card.tsx +83 -0
- package/src/components/ui/image-cropper.tsx +100 -0
- package/src/components/ui/input-group.tsx +133 -0
- package/src/components/ui/input-otp.tsx +82 -0
- package/src/components/ui/input.tsx +18 -0
- package/src/components/ui/item.tsx +174 -0
- package/src/components/ui/json-tree-view.tsx +37 -0
- package/src/components/ui/kanban.tsx +884 -0
- package/src/components/ui/kbd.tsx +20 -0
- package/src/components/ui/label.tsx +30 -0
- package/src/components/ui/live-region.tsx +54 -0
- package/src/components/ui/marker.tsx +66 -0
- package/src/components/ui/marquee.tsx +61 -0
- package/src/components/ui/menubar.tsx +456 -0
- package/src/components/ui/message.tsx +74 -0
- package/src/components/ui/navigation-menu.tsx +202 -0
- package/src/components/ui/node-graph.tsx +1723 -0
- package/src/components/ui/number-input.tsx +148 -0
- package/src/components/ui/pagination.tsx +88 -0
- package/src/components/ui/password-input.tsx +104 -0
- package/src/components/ui/popconfirm.tsx +149 -0
- package/src/components/ui/popover.tsx +126 -0
- package/src/components/ui/progress.tsx +114 -0
- package/src/components/ui/qr-code.tsx +51 -0
- package/src/components/ui/query-builder.tsx +938 -0
- package/src/components/ui/radio-group.tsx +108 -0
- package/src/components/ui/rating-group.tsx +91 -0
- package/src/components/ui/resizable.tsx +41 -0
- package/src/components/ui/rich-text-editor.tsx +845 -0
- package/src/components/ui/scheduler.tsx +1164 -0
- package/src/components/ui/scroll-area.tsx +82 -0
- package/src/components/ui/segment-group.tsx +95 -0
- package/src/components/ui/select.tsx +354 -0
- package/src/components/ui/separator.tsx +32 -0
- package/src/components/ui/sheet.tsx +130 -0
- package/src/components/ui/sidebar.tsx +665 -0
- package/src/components/ui/signature-pad.tsx +112 -0
- package/src/components/ui/skeleton.tsx +7 -0
- package/src/components/ui/slider.tsx +183 -0
- package/src/components/ui/sonner.tsx +37 -0
- package/src/components/ui/spinner.tsx +16 -0
- package/src/components/ui/steps.tsx +152 -0
- package/src/components/ui/swap.tsx +33 -0
- package/src/components/ui/switch.tsx +83 -0
- package/src/components/ui/table.tsx +72 -0
- package/src/components/ui/tabs.tsx +100 -0
- package/src/components/ui/tags-input.tsx +153 -0
- package/src/components/ui/textarea.tsx +17 -0
- package/src/components/ui/timer.tsx +67 -0
- package/src/components/ui/toc.tsx +64 -0
- package/src/components/ui/toggle-group.tsx +89 -0
- package/src/components/ui/toggle.tsx +46 -0
- package/src/components/ui/tooltip.tsx +113 -0
- package/src/components/ui/tour.tsx +168 -0
- package/src/components/ui/transfer-list.tsx +700 -0
- package/src/components/ui/tree-select.tsx +638 -0
- package/src/components/ui/tree-view.tsx +217 -0
- package/src/components/ui/virtual-list.tsx +343 -0
- package/src/hooks/use-mobile.ts +19 -0
- package/src/lib/controllable.ts +31 -0
- package/src/lib/data-table-adapter.ts +67 -0
- package/src/lib/drag-preview.ts +39 -0
- package/src/lib/form.ts +39 -0
- package/src/lib/history.ts +55 -0
- package/src/lib/row-layout.ts +81 -0
- package/src/lib/table.ts +483 -0
- package/src/lib/time.ts +37 -0
- package/src/lib/utils.ts +6 -0
- package/src/styles/arkcn.css +764 -0
- package/src/styles/index.css +3 -0
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cn } from "@/lib/utils"
|
|
5
|
+
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"
|
|
6
|
+
import { Kbd, KbdGroup } from "@/components/ui/kbd"
|
|
7
|
+
|
|
8
|
+
/* -------------------------------- parsing -------------------------------- */
|
|
9
|
+
|
|
10
|
+
const isMac = () => typeof navigator !== "undefined" && /mac|iphone|ipad|ipod/i.test(navigator.platform)
|
|
11
|
+
|
|
12
|
+
type Chord = { key: string; mod: boolean; ctrl: boolean; meta: boolean; alt: boolean; shift: boolean }
|
|
13
|
+
|
|
14
|
+
const keyAliases: Record<string, string> = {
|
|
15
|
+
esc: "escape",
|
|
16
|
+
return: "enter",
|
|
17
|
+
space: " ",
|
|
18
|
+
up: "arrowup",
|
|
19
|
+
down: "arrowdown",
|
|
20
|
+
left: "arrowleft",
|
|
21
|
+
right: "arrowright",
|
|
22
|
+
plus: "+",
|
|
23
|
+
del: "delete",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** "mod+shift+k" → chord; "g d" → sequence of chords. */
|
|
27
|
+
function parseHotkey(hotkey: string): Chord[] {
|
|
28
|
+
return hotkey
|
|
29
|
+
.trim()
|
|
30
|
+
.split(/\s+/)
|
|
31
|
+
.map((part) => {
|
|
32
|
+
const tokens = part.split("+").map((t) => t.toLowerCase())
|
|
33
|
+
// "+" itself is written as "plus"
|
|
34
|
+
const key = tokens.pop() ?? ""
|
|
35
|
+
const chord: Chord = {
|
|
36
|
+
key: keyAliases[key] ?? key,
|
|
37
|
+
mod: false,
|
|
38
|
+
ctrl: false,
|
|
39
|
+
meta: false,
|
|
40
|
+
alt: false,
|
|
41
|
+
shift: false,
|
|
42
|
+
}
|
|
43
|
+
for (const t of tokens) {
|
|
44
|
+
if (t === "mod" || t === "cmd" || t === "command") chord.mod = true
|
|
45
|
+
else if (t === "ctrl" || t === "control") chord.ctrl = true
|
|
46
|
+
else if (t === "meta" || t === "win" || t === "super") chord.meta = true
|
|
47
|
+
else if (t === "alt" || t === "option") chord.alt = true
|
|
48
|
+
else if (t === "shift") chord.shift = true
|
|
49
|
+
}
|
|
50
|
+
return chord
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function matchesChord(event: KeyboardEvent, chord: Chord, mac: boolean) {
|
|
55
|
+
const key = event.key.length === 1 ? event.key.toLowerCase() : event.key.toLowerCase()
|
|
56
|
+
if (key !== chord.key) return false
|
|
57
|
+
const wantMeta = chord.meta || (chord.mod && mac)
|
|
58
|
+
const wantCtrl = chord.ctrl || (chord.mod && !mac)
|
|
59
|
+
if (event.metaKey !== wantMeta || event.ctrlKey !== wantCtrl || event.altKey !== chord.alt) return false
|
|
60
|
+
// Punctuation like "?" already implies Shift on most layouts, so only enforce Shift for letters.
|
|
61
|
+
const letter = /^[a-z0-9]$/.test(chord.key) || chord.key.length > 1
|
|
62
|
+
if (letter && event.shiftKey !== chord.shift) return false
|
|
63
|
+
return true
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const symbol: Record<string, string> = {
|
|
67
|
+
mod: "⌘",
|
|
68
|
+
meta: "⌘",
|
|
69
|
+
ctrl: "⌃",
|
|
70
|
+
alt: "⌥",
|
|
71
|
+
shift: "⇧",
|
|
72
|
+
enter: "↵",
|
|
73
|
+
escape: "Esc",
|
|
74
|
+
backspace: "⌫",
|
|
75
|
+
delete: "⌦",
|
|
76
|
+
arrowup: "↑",
|
|
77
|
+
arrowdown: "↓",
|
|
78
|
+
arrowleft: "←",
|
|
79
|
+
arrowright: "→",
|
|
80
|
+
" ": "Space",
|
|
81
|
+
tab: "Tab",
|
|
82
|
+
}
|
|
83
|
+
const winSymbol: Record<string, string> = {
|
|
84
|
+
...symbol,
|
|
85
|
+
mod: "Ctrl",
|
|
86
|
+
meta: "Win",
|
|
87
|
+
ctrl: "Ctrl",
|
|
88
|
+
alt: "Alt",
|
|
89
|
+
shift: "Shift",
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Platform-aware key labels: "mod+k" → ["⌘", "K"] on Mac, ["Ctrl", "K"] elsewhere. Sequences give one array per chord. */
|
|
93
|
+
function formatHotkey(hotkey: string, mac = isMac()): string[][] {
|
|
94
|
+
const table = mac ? symbol : winSymbol
|
|
95
|
+
return parseHotkey(hotkey).map((chord) => {
|
|
96
|
+
const parts: string[] = []
|
|
97
|
+
if (chord.ctrl) parts.push(table.ctrl)
|
|
98
|
+
if (chord.alt) parts.push(table.alt)
|
|
99
|
+
if (chord.shift) parts.push(table.shift)
|
|
100
|
+
if (chord.mod) parts.push(table.mod)
|
|
101
|
+
if (chord.meta && !chord.mod) parts.push(table.meta)
|
|
102
|
+
parts.push(table[chord.key] ?? (chord.key.length === 1 ? chord.key.toUpperCase() : chord.key))
|
|
103
|
+
return parts
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/* -------------------------------- registry ------------------------------- */
|
|
108
|
+
|
|
109
|
+
type HotkeyEntry = {
|
|
110
|
+
id: string
|
|
111
|
+
hotkey: string
|
|
112
|
+
label: string
|
|
113
|
+
group: string
|
|
114
|
+
enabled: boolean
|
|
115
|
+
/** Fire even while typing in an input, textarea, or contenteditable. */
|
|
116
|
+
allowInInput: boolean
|
|
117
|
+
handler: (event: KeyboardEvent) => void
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
type HotkeysContextValue = {
|
|
121
|
+
entries: HotkeyEntry[]
|
|
122
|
+
register: (entry: HotkeyEntry) => () => void
|
|
123
|
+
dialogOpen: boolean
|
|
124
|
+
setDialogOpen: (open: boolean) => void
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const HotkeysContext = React.createContext<HotkeysContextValue | null>(null)
|
|
128
|
+
|
|
129
|
+
function useHotkeysContext() {
|
|
130
|
+
const ctx = React.useContext(HotkeysContext)
|
|
131
|
+
if (!ctx) throw new Error("Hotkeys parts must be used inside <HotkeysProvider>")
|
|
132
|
+
return ctx
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const isEditable = (target: EventTarget | null) => {
|
|
136
|
+
const el = target as HTMLElement | null
|
|
137
|
+
if (!el || !("tagName" in el)) return false
|
|
138
|
+
const tag = el.tagName
|
|
139
|
+
return tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || el.isContentEditable
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* One document-level keydown listener for every registered hotkey. Supports chords
|
|
144
|
+
* ("mod+k"), sequences ("g d", one second between keys), and a built-in "?" that opens
|
|
145
|
+
* `HotkeysDialog` when one is rendered.
|
|
146
|
+
*/
|
|
147
|
+
function HotkeysProvider({
|
|
148
|
+
children,
|
|
149
|
+
dialogHotkey = "?",
|
|
150
|
+
sequenceTimeout = 1000,
|
|
151
|
+
}: {
|
|
152
|
+
children: React.ReactNode
|
|
153
|
+
/** Opens the shortcuts dialog; `null` disables it. */
|
|
154
|
+
dialogHotkey?: string | null
|
|
155
|
+
sequenceTimeout?: number
|
|
156
|
+
}) {
|
|
157
|
+
const [entries, setEntries] = React.useState<HotkeyEntry[]>([])
|
|
158
|
+
const [dialogOpen, setDialogOpen] = React.useState(false)
|
|
159
|
+
const entriesRef = React.useRef(entries)
|
|
160
|
+
entriesRef.current = entries
|
|
161
|
+
const buffer = React.useRef<{ keys: KeyboardEvent[]; at: number }>({ keys: [], at: 0 })
|
|
162
|
+
|
|
163
|
+
const register = React.useCallback((entry: HotkeyEntry) => {
|
|
164
|
+
setEntries((prev) => [...prev.filter((e) => e.id !== entry.id), entry])
|
|
165
|
+
return () => setEntries((prev) => prev.filter((e) => e.id !== entry.id))
|
|
166
|
+
}, [])
|
|
167
|
+
|
|
168
|
+
const mac = React.useMemo(isMac, [])
|
|
169
|
+
React.useEffect(() => {
|
|
170
|
+
const down = (event: KeyboardEvent) => {
|
|
171
|
+
if (event.defaultPrevented || event.isComposing) return
|
|
172
|
+
if (["Shift", "Control", "Alt", "Meta"].includes(event.key)) return
|
|
173
|
+
const editing = isEditable(event.target)
|
|
174
|
+
const now = Date.now()
|
|
175
|
+
const buf = buffer.current
|
|
176
|
+
if (now - buf.at > sequenceTimeout) buf.keys = []
|
|
177
|
+
buf.keys.push(event)
|
|
178
|
+
buf.at = now
|
|
179
|
+
if (buf.keys.length > 4) buf.keys.shift()
|
|
180
|
+
|
|
181
|
+
const candidates = [...entriesRef.current]
|
|
182
|
+
if (dialogHotkey) {
|
|
183
|
+
candidates.push({
|
|
184
|
+
id: "hotkeys:dialog",
|
|
185
|
+
hotkey: dialogHotkey,
|
|
186
|
+
label: "Keyboard shortcuts",
|
|
187
|
+
group: "General",
|
|
188
|
+
enabled: true,
|
|
189
|
+
allowInInput: false,
|
|
190
|
+
handler: () => setDialogOpen((open) => !open),
|
|
191
|
+
})
|
|
192
|
+
}
|
|
193
|
+
let partial = false
|
|
194
|
+
for (const entry of candidates) {
|
|
195
|
+
if (!entry.enabled || (editing && !entry.allowInInput)) continue
|
|
196
|
+
const chords = parseHotkey(entry.hotkey)
|
|
197
|
+
const tail = buf.keys.slice(-chords.length)
|
|
198
|
+
if (tail.length === chords.length && chords.every((c, i) => matchesChord(tail[i], c, mac))) {
|
|
199
|
+
event.preventDefault()
|
|
200
|
+
buf.keys = []
|
|
201
|
+
entry.handler(event)
|
|
202
|
+
return
|
|
203
|
+
}
|
|
204
|
+
// Is the buffer a prefix of a longer sequence? Then keep it.
|
|
205
|
+
if (chords.length > 1 && buf.keys.length < chords.length) {
|
|
206
|
+
const head = chords.slice(0, buf.keys.length)
|
|
207
|
+
if (head.every((c, i) => matchesChord(buf.keys[i], c, mac))) partial = true
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (!partial) buf.keys = []
|
|
211
|
+
}
|
|
212
|
+
document.addEventListener("keydown", down)
|
|
213
|
+
return () => document.removeEventListener("keydown", down)
|
|
214
|
+
}, [dialogHotkey, sequenceTimeout, mac])
|
|
215
|
+
|
|
216
|
+
const ctx = React.useMemo<HotkeysContextValue>(
|
|
217
|
+
() => ({ entries, register, dialogOpen, setDialogOpen }),
|
|
218
|
+
[entries, register, dialogOpen]
|
|
219
|
+
)
|
|
220
|
+
return <HotkeysContext.Provider value={ctx}>{children}</HotkeysContext.Provider>
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** Register a hotkey for the lifetime of the component. Listed in `HotkeysDialog` when it has a label. */
|
|
224
|
+
function useHotkey(
|
|
225
|
+
hotkey: string,
|
|
226
|
+
handler: (event: KeyboardEvent) => void,
|
|
227
|
+
options: { label?: string; group?: string; enabled?: boolean; allowInInput?: boolean } = {}
|
|
228
|
+
) {
|
|
229
|
+
const { register } = useHotkeysContext()
|
|
230
|
+
const { label = "", group = "General", enabled = true, allowInInput = false } = options
|
|
231
|
+
const id = React.useId()
|
|
232
|
+
const handlerRef = React.useRef(handler)
|
|
233
|
+
handlerRef.current = handler
|
|
234
|
+
React.useEffect(
|
|
235
|
+
() =>
|
|
236
|
+
register({
|
|
237
|
+
id,
|
|
238
|
+
hotkey,
|
|
239
|
+
label,
|
|
240
|
+
group,
|
|
241
|
+
enabled,
|
|
242
|
+
allowInInput,
|
|
243
|
+
handler: (event) => handlerRef.current(event),
|
|
244
|
+
}),
|
|
245
|
+
[register, id, hotkey, label, group, enabled, allowInInput]
|
|
246
|
+
)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** Every registered hotkey with a label, grouped, for rendering a cheat sheet. */
|
|
250
|
+
function useHotkeys() {
|
|
251
|
+
const { entries } = useHotkeysContext()
|
|
252
|
+
return React.useMemo(() => {
|
|
253
|
+
const groups = new Map<string, HotkeyEntry[]>()
|
|
254
|
+
for (const e of entries) {
|
|
255
|
+
if (!e.label) continue
|
|
256
|
+
const list = groups.get(e.group) ?? []
|
|
257
|
+
list.push(e)
|
|
258
|
+
groups.set(e.group, list)
|
|
259
|
+
}
|
|
260
|
+
return Array.from(groups, ([group, hotkeys]) => ({ group, hotkeys }))
|
|
261
|
+
}, [entries])
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/* ---------------------------------- parts -------------------------------- */
|
|
265
|
+
|
|
266
|
+
/** Renders a hotkey as Kbd caps ("mod+k" → ⌘ K; "g d" → G then D). */
|
|
267
|
+
function HotkeyKbd({ hotkey, className, ...props }: React.ComponentProps<"span"> & { hotkey: string }) {
|
|
268
|
+
const chords = formatHotkey(hotkey)
|
|
269
|
+
return (
|
|
270
|
+
<span data-slot="hotkey-kbd" className={cn("inline-flex items-center gap-1", className)} {...props}>
|
|
271
|
+
{chords.map((keys, i) => (
|
|
272
|
+
<React.Fragment key={i}>
|
|
273
|
+
{i > 0 && <span className="text-xs text-muted-foreground">then</span>}
|
|
274
|
+
<KbdGroup>
|
|
275
|
+
{keys.map((k, j) => (
|
|
276
|
+
<Kbd key={j}>{k}</Kbd>
|
|
277
|
+
))}
|
|
278
|
+
</KbdGroup>
|
|
279
|
+
</React.Fragment>
|
|
280
|
+
))}
|
|
281
|
+
</span>
|
|
282
|
+
)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** The shortcuts cheat sheet. Opens from the provider's `dialogHotkey` ("?" by default) or `useHotkeysDialog()`. */
|
|
286
|
+
function HotkeysDialog({
|
|
287
|
+
title = "Keyboard shortcuts",
|
|
288
|
+
description = "Shortcuts work anywhere on the page except while typing.",
|
|
289
|
+
children,
|
|
290
|
+
className,
|
|
291
|
+
}: {
|
|
292
|
+
title?: React.ReactNode
|
|
293
|
+
description?: React.ReactNode
|
|
294
|
+
children?: React.ReactNode
|
|
295
|
+
className?: string
|
|
296
|
+
}) {
|
|
297
|
+
const { dialogOpen, setDialogOpen } = useHotkeysContext()
|
|
298
|
+
const registered = useHotkeys()
|
|
299
|
+
const groups = React.useMemo(() => {
|
|
300
|
+
const self = { id: "hotkeys:dialog", hotkey: "?", label: "Show this dialog" }
|
|
301
|
+
const general = registered.find((g) => g.group === "General")
|
|
302
|
+
if (general) return registered.map((g) => (g === general ? { ...g, hotkeys: [...g.hotkeys, self] } : g))
|
|
303
|
+
return [...registered, { group: "General", hotkeys: [self] }]
|
|
304
|
+
}, [registered])
|
|
305
|
+
return (
|
|
306
|
+
<Dialog open={dialogOpen} onOpenChange={({ open }) => setDialogOpen(open)}>
|
|
307
|
+
<DialogContent data-slot="hotkeys-dialog" className={cn("sm:max-w-lg", className)}>
|
|
308
|
+
<DialogHeader>
|
|
309
|
+
<DialogTitle>{title}</DialogTitle>
|
|
310
|
+
<DialogDescription>{description}</DialogDescription>
|
|
311
|
+
</DialogHeader>
|
|
312
|
+
<div className="grid gap-5 sm:grid-cols-2">
|
|
313
|
+
{groups.map(({ group, hotkeys }) => (
|
|
314
|
+
<div key={group} data-slot="hotkeys-dialog-group" className="flex flex-col gap-1.5">
|
|
315
|
+
<h4 className="text-xs font-medium text-muted-foreground uppercase">{group}</h4>
|
|
316
|
+
{hotkeys.map((h) => (
|
|
317
|
+
<div
|
|
318
|
+
key={h.id}
|
|
319
|
+
data-slot="hotkeys-dialog-item"
|
|
320
|
+
className="flex items-center justify-between gap-3 text-sm"
|
|
321
|
+
>
|
|
322
|
+
<span className="truncate">{h.label}</span>
|
|
323
|
+
<HotkeyKbd hotkey={h.hotkey} />
|
|
324
|
+
</div>
|
|
325
|
+
))}
|
|
326
|
+
</div>
|
|
327
|
+
))}
|
|
328
|
+
</div>
|
|
329
|
+
{children}
|
|
330
|
+
</DialogContent>
|
|
331
|
+
</Dialog>
|
|
332
|
+
)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function useHotkeysDialog() {
|
|
336
|
+
const { dialogOpen, setDialogOpen } = useHotkeysContext()
|
|
337
|
+
return { open: dialogOpen, setOpen: setDialogOpen }
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export { HotkeyKbd, HotkeysDialog, HotkeysProvider, formatHotkey, parseHotkey, useHotkey, useHotkeys, useHotkeysDialog }
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cn } from "@/lib/utils"
|
|
3
|
+
import { HoverCard as HoverCardPrimitive, Portal as PortalPrimitive } from "@ark-ui/react"
|
|
4
|
+
|
|
5
|
+
function HoverCard({
|
|
6
|
+
positioning,
|
|
7
|
+
lazyMount = true,
|
|
8
|
+
unmountOnExit = true,
|
|
9
|
+
...props
|
|
10
|
+
}: React.ComponentProps<typeof HoverCardPrimitive.Root>) {
|
|
11
|
+
return (
|
|
12
|
+
<HoverCardPrimitive.Root
|
|
13
|
+
positioning={{ placement: "bottom", gutter: 4, ...positioning }}
|
|
14
|
+
lazyMount={lazyMount}
|
|
15
|
+
unmountOnExit={unmountOnExit}
|
|
16
|
+
{...props}
|
|
17
|
+
/>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function HoverCardTrigger({ ...props }: React.ComponentProps<typeof HoverCardPrimitive.Trigger>) {
|
|
22
|
+
return <HoverCardPrimitive.Trigger data-slot="hover-card-trigger" {...props} />
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function HoverCardPortal({ ...props }: React.ComponentProps<typeof PortalPrimitive>) {
|
|
26
|
+
return <PortalPrimitive {...props} />
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function HoverCardContext({ ...props }: React.ComponentProps<typeof HoverCardPrimitive.Context>) {
|
|
30
|
+
return <HoverCardPrimitive.Context {...props} />
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function HoverCardPositioner({ className, ...props }: React.ComponentProps<typeof HoverCardPrimitive.Positioner>) {
|
|
34
|
+
return (
|
|
35
|
+
<HoverCardPrimitive.Positioner
|
|
36
|
+
data-slot="hover-card-positioner"
|
|
37
|
+
className={cn("[--z-index:50]", className)}
|
|
38
|
+
{...props}
|
|
39
|
+
/>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function HoverCardArrow({ className, ...props }: React.ComponentProps<typeof HoverCardPrimitive.Arrow>) {
|
|
44
|
+
return (
|
|
45
|
+
<HoverCardPrimitive.Arrow
|
|
46
|
+
data-slot="hover-card-arrow"
|
|
47
|
+
className={cn("[--arrow-background:var(--color-popover)] [--arrow-size:0.625rem]", className)}
|
|
48
|
+
{...props}
|
|
49
|
+
>
|
|
50
|
+
<HoverCardPrimitive.ArrowTip
|
|
51
|
+
data-slot="hover-card-arrow-tip"
|
|
52
|
+
className="border-t border-l border-foreground/10"
|
|
53
|
+
/>
|
|
54
|
+
</HoverCardPrimitive.Arrow>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function HoverCardContent({ className, ...props }: React.ComponentProps<typeof HoverCardPrimitive.Content>) {
|
|
59
|
+
return (
|
|
60
|
+
<HoverCardPortal>
|
|
61
|
+
<HoverCardPositioner>
|
|
62
|
+
<HoverCardPrimitive.Content
|
|
63
|
+
data-slot="hover-card-content"
|
|
64
|
+
className={cn(
|
|
65
|
+
"z-50 w-64 origin-(--transform-origin) rounded-lg bg-popover p-2.5 text-sm text-popover-foreground shadow-md ring-1 ring-foreground/10 outline-hidden duration-100 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 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
|
66
|
+
className
|
|
67
|
+
)}
|
|
68
|
+
{...props}
|
|
69
|
+
/>
|
|
70
|
+
</HoverCardPositioner>
|
|
71
|
+
</HoverCardPortal>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export {
|
|
76
|
+
HoverCard,
|
|
77
|
+
HoverCardArrow,
|
|
78
|
+
HoverCardContent,
|
|
79
|
+
HoverCardContext,
|
|
80
|
+
HoverCardPortal,
|
|
81
|
+
HoverCardPositioner,
|
|
82
|
+
HoverCardTrigger,
|
|
83
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cn } from "@/lib/utils"
|
|
5
|
+
import { ImageCropper as ImageCropperPrimitive } from "@ark-ui/react"
|
|
6
|
+
|
|
7
|
+
const handlePositions = ["n", "e", "s", "w", "ne", "se", "sw", "nw"] as const
|
|
8
|
+
|
|
9
|
+
function ImageCropper({ className, ...props }: React.ComponentProps<typeof ImageCropperPrimitive.Root>) {
|
|
10
|
+
return (
|
|
11
|
+
<ImageCropperPrimitive.Root
|
|
12
|
+
data-slot="image-cropper"
|
|
13
|
+
className={cn("flex w-full flex-col gap-2", className)}
|
|
14
|
+
{...props}
|
|
15
|
+
/>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function ImageCropperContext({ ...props }: React.ComponentProps<typeof ImageCropperPrimitive.Context>) {
|
|
20
|
+
return <ImageCropperPrimitive.Context {...props} />
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function ImageCropperViewport({ className, ...props }: React.ComponentProps<typeof ImageCropperPrimitive.Viewport>) {
|
|
24
|
+
return (
|
|
25
|
+
<ImageCropperPrimitive.Viewport
|
|
26
|
+
data-slot="image-cropper-viewport"
|
|
27
|
+
className={cn(
|
|
28
|
+
"relative h-72 w-full touch-none overflow-hidden rounded-xl border bg-muted select-none",
|
|
29
|
+
className
|
|
30
|
+
)}
|
|
31
|
+
{...props}
|
|
32
|
+
/>
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function ImageCropperImage({ className, ...props }: React.ComponentProps<typeof ImageCropperPrimitive.Image>) {
|
|
37
|
+
return (
|
|
38
|
+
<ImageCropperPrimitive.Image data-slot="image-cropper-image" className={cn("max-w-none", className)} {...props} />
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function ImageCropperSelection({
|
|
43
|
+
className,
|
|
44
|
+
children,
|
|
45
|
+
...props
|
|
46
|
+
}: React.ComponentProps<typeof ImageCropperPrimitive.Selection>) {
|
|
47
|
+
return (
|
|
48
|
+
<ImageCropperPrimitive.Selection
|
|
49
|
+
data-slot="image-cropper-selection"
|
|
50
|
+
className={cn(
|
|
51
|
+
"shadow-[0_0_0_9999px_rgba(0,0,0,0.45)] outline-1 outline-white/80 data-[shape=circle]:rounded-full",
|
|
52
|
+
className
|
|
53
|
+
)}
|
|
54
|
+
{...props}
|
|
55
|
+
>
|
|
56
|
+
{children ?? (
|
|
57
|
+
<>
|
|
58
|
+
<ImageCropperGrid axis="horizontal" />
|
|
59
|
+
<ImageCropperGrid axis="vertical" />
|
|
60
|
+
{handlePositions.map((position) => (
|
|
61
|
+
<ImageCropperHandle key={position} position={position} />
|
|
62
|
+
))}
|
|
63
|
+
</>
|
|
64
|
+
)}
|
|
65
|
+
</ImageCropperPrimitive.Selection>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function ImageCropperGrid({ className, ...props }: React.ComponentProps<typeof ImageCropperPrimitive.Grid>) {
|
|
70
|
+
return (
|
|
71
|
+
<ImageCropperPrimitive.Grid
|
|
72
|
+
data-slot="image-cropper-grid"
|
|
73
|
+
className={cn("stroke-white/50", className)}
|
|
74
|
+
{...props}
|
|
75
|
+
/>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function ImageCropperHandle({ className, ...props }: React.ComponentProps<typeof ImageCropperPrimitive.Handle>) {
|
|
80
|
+
return (
|
|
81
|
+
<ImageCropperPrimitive.Handle
|
|
82
|
+
data-slot="image-cropper-handle"
|
|
83
|
+
className={cn(
|
|
84
|
+
"after:absolute after:top-1/2 after:left-1/2 after:size-2.5 after:-translate-1/2 after:rounded-full after:border after:border-ring after:bg-white data-[axis=x]:after:h-4 data-[axis=x]:after:w-1 data-[axis=y]:after:h-1 data-[axis=y]:after:w-4",
|
|
85
|
+
className
|
|
86
|
+
)}
|
|
87
|
+
{...props}
|
|
88
|
+
/>
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export {
|
|
93
|
+
ImageCropper,
|
|
94
|
+
ImageCropperContext,
|
|
95
|
+
ImageCropperGrid,
|
|
96
|
+
ImageCropperHandle,
|
|
97
|
+
ImageCropperImage,
|
|
98
|
+
ImageCropperSelection,
|
|
99
|
+
ImageCropperViewport,
|
|
100
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
import { cn } from "@/lib/utils"
|
|
4
|
+
|
|
5
|
+
import { Button } from "@/components/ui/button"
|
|
6
|
+
import { Input } from "@/components/ui/input"
|
|
7
|
+
import { Textarea } from "@/components/ui/textarea"
|
|
8
|
+
|
|
9
|
+
function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
10
|
+
return (
|
|
11
|
+
<div
|
|
12
|
+
data-slot="input-group"
|
|
13
|
+
role="group"
|
|
14
|
+
className={cn(
|
|
15
|
+
"group/input-group relative flex h-8 w-full min-w-0 items-center rounded-lg border border-input transition-colors outline-none in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-disabled:bg-input/50 has-disabled:opacity-50 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-3 has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-3 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>textarea]:h-auto dark:bg-input/30 dark:has-disabled:bg-input/80 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5",
|
|
16
|
+
className
|
|
17
|
+
)}
|
|
18
|
+
{...props}
|
|
19
|
+
/>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const inputGroupAddonVariants = cva(
|
|
24
|
+
"flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium text-muted-foreground select-none group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4",
|
|
25
|
+
{
|
|
26
|
+
variants: {
|
|
27
|
+
align: {
|
|
28
|
+
"inline-start": "order-first pl-2 has-[>button]:ml-[-0.3rem] has-[>kbd]:ml-[-0.15rem]",
|
|
29
|
+
"inline-end": "order-last pr-2 has-[>button]:mr-[-0.3rem] has-[>kbd]:mr-[-0.15rem]",
|
|
30
|
+
"block-start":
|
|
31
|
+
"order-first w-full justify-start px-2.5 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2",
|
|
32
|
+
"block-end": "order-last w-full justify-start px-2.5 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
defaultVariants: {
|
|
36
|
+
align: "inline-start",
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
function InputGroupAddon({
|
|
42
|
+
className,
|
|
43
|
+
align = "inline-start",
|
|
44
|
+
...props
|
|
45
|
+
}: React.ComponentProps<"div"> & VariantProps<typeof inputGroupAddonVariants>) {
|
|
46
|
+
return (
|
|
47
|
+
<div
|
|
48
|
+
role="group"
|
|
49
|
+
data-slot="input-group-addon"
|
|
50
|
+
data-align={align}
|
|
51
|
+
className={cn(inputGroupAddonVariants({ align }), className)}
|
|
52
|
+
onClick={(e) => {
|
|
53
|
+
if ((e.target as HTMLElement).closest("button")) {
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
e.currentTarget.parentElement?.querySelector("input")?.focus()
|
|
57
|
+
}}
|
|
58
|
+
{...props}
|
|
59
|
+
/>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const inputGroupTriggerVariants = cva("flex items-center gap-2 text-sm shadow-none", {
|
|
64
|
+
variants: {
|
|
65
|
+
size: {
|
|
66
|
+
xs: "h-6 gap-1 rounded-[calc(var(--radius)-3px)] px-1.5 [&>svg:not([class*='size-'])]:size-3.5",
|
|
67
|
+
sm: "",
|
|
68
|
+
"icon-xs": "size-6 rounded-[calc(var(--radius)-3px)] p-0 has-[>svg]:p-0",
|
|
69
|
+
"icon-sm": "size-8 p-0 has-[>svg]:p-0",
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
defaultVariants: {
|
|
73
|
+
size: "xs",
|
|
74
|
+
},
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
function InputGroupTrigger({
|
|
78
|
+
className,
|
|
79
|
+
type = "button",
|
|
80
|
+
variant = "ghost",
|
|
81
|
+
size = "xs",
|
|
82
|
+
...props
|
|
83
|
+
}: Omit<React.ComponentProps<typeof Button>, "size"> & VariantProps<typeof inputGroupTriggerVariants>) {
|
|
84
|
+
return (
|
|
85
|
+
<Button
|
|
86
|
+
type={type}
|
|
87
|
+
data-size={size}
|
|
88
|
+
variant={variant}
|
|
89
|
+
className={cn(inputGroupTriggerVariants({ size }), className)}
|
|
90
|
+
{...props}
|
|
91
|
+
/>
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function InputGroupText({ className, ...props }: React.ComponentProps<"span">) {
|
|
96
|
+
return (
|
|
97
|
+
<span
|
|
98
|
+
className={cn(
|
|
99
|
+
"flex items-center gap-2 text-sm text-muted-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
|
|
100
|
+
className
|
|
101
|
+
)}
|
|
102
|
+
{...props}
|
|
103
|
+
/>
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function InputGroupInput({ className, ...props }: React.ComponentProps<"input">) {
|
|
108
|
+
return (
|
|
109
|
+
<Input
|
|
110
|
+
data-slot="input-group-control"
|
|
111
|
+
className={cn(
|
|
112
|
+
"flex-1 rounded-none border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0 dark:bg-transparent dark:disabled:bg-transparent",
|
|
113
|
+
className
|
|
114
|
+
)}
|
|
115
|
+
{...props}
|
|
116
|
+
/>
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function InputGroupTextarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
|
121
|
+
return (
|
|
122
|
+
<Textarea
|
|
123
|
+
data-slot="input-group-control"
|
|
124
|
+
className={cn(
|
|
125
|
+
"flex-1 resize-none rounded-none border-0 bg-transparent py-2 shadow-none ring-0 focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0 dark:bg-transparent dark:disabled:bg-transparent",
|
|
126
|
+
className
|
|
127
|
+
)}
|
|
128
|
+
{...props}
|
|
129
|
+
/>
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export { InputGroup, InputGroupAddon, InputGroupTrigger, InputGroupText, InputGroupInput, InputGroupTextarea }
|