@gunjo/ui 0.0.1-alpha.1 → 0.0.1-alpha.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/LICENSE +21 -0
- package/README.ja.md +90 -0
- package/README.md +52 -91
- package/package.json +47 -6
- package/src/components/display/Accordion.tsx +185 -0
- package/src/components/display/AccordionGroup.tsx +155 -0
- package/src/components/display/ActionDataTable.tsx +413 -0
- package/src/components/display/ActivityTimelineCard.tsx +483 -0
- package/src/components/display/AnalyticsCard.tsx +167 -0
- package/src/components/display/AssetCard.tsx +242 -0
- package/src/components/display/AssetGrid.tsx +164 -0
- package/src/components/display/Avatar.tsx +127 -0
- package/src/components/display/AvatarGroup.tsx +131 -0
- package/src/components/{atoms → display}/Badge.tsx +3 -3
- package/src/components/display/BarChart.tsx +247 -0
- package/src/components/{molecules → display}/Card.tsx +1 -1
- package/src/components/display/Carousel.tsx +593 -0
- package/src/components/display/ChartLegend.tsx +124 -0
- package/src/components/display/ChatMessage.tsx +382 -0
- package/src/components/display/ChoroplethMap.tsx +613 -0
- package/src/components/display/Code.tsx +42 -0
- package/src/components/display/CodeBlock.tsx +338 -0
- package/src/components/display/ColorSwatch.tsx +71 -0
- package/src/components/display/ConcentricProgressCard.tsx +545 -0
- package/src/components/display/DataTable.tsx +522 -0
- package/src/components/display/DistributionBar.tsx +102 -0
- package/src/components/display/DocNote.tsx +36 -0
- package/src/components/display/DonutChart.tsx +257 -0
- package/src/components/display/EmptyState.tsx +44 -0
- package/src/components/display/FileTree.tsx +180 -0
- package/src/components/display/GaugeChart.tsx +219 -0
- package/src/components/display/HeatmapChart.tsx +266 -0
- package/src/components/display/Icon.tsx +66 -0
- package/src/components/display/ImagePreview.tsx +140 -0
- package/src/components/{atoms → display}/Img.tsx +46 -12
- package/src/components/display/LabeledDonutCard.tsx +475 -0
- package/src/components/display/LineChart.tsx +464 -0
- package/src/components/{molecules → display}/List.tsx +20 -13
- package/src/components/display/MarkdownRenderer.tsx +157 -0
- package/src/components/display/MetadataList.tsx +81 -0
- package/src/components/display/MiniDistributionBarCard.tsx +314 -0
- package/src/components/display/PieChart.tsx +234 -0
- package/src/components/display/QuadrantMatrix.tsx +330 -0
- package/src/components/display/RadarChart.tsx +335 -0
- package/src/components/display/RadialBarChart.tsx +264 -0
- package/src/components/display/RetentionCohortCard.tsx +350 -0
- package/src/components/display/RibbonChart.tsx +618 -0
- package/src/components/display/SearchableAccordion.tsx +270 -0
- package/src/components/display/SegmentTimelineCard.tsx +452 -0
- package/src/components/display/SegmentedGaugeCard.tsx +607 -0
- package/src/components/display/Spacer.tsx +51 -0
- package/src/components/display/SparklineChart.tsx +394 -0
- package/src/components/display/StackedBarChart.tsx +393 -0
- package/src/components/display/Statistic.tsx +70 -0
- package/src/components/{molecules → display}/Table.tsx +22 -7
- package/src/components/display/Tag.tsx +80 -0
- package/src/components/display/TagEditor.tsx +141 -0
- package/src/components/display/Timeline.tsx +121 -0
- package/src/components/{atoms → display}/ToolPill.tsx +42 -18
- package/src/components/display/TreeView.tsx +226 -0
- package/src/components/display/chart-tooltip.tsx +423 -0
- package/src/components/display/chart-utils.ts +71 -0
- package/src/components/display/circular-chart-utils.ts +147 -0
- package/src/components/display/generated/default-variant-keys.ts +90 -0
- package/src/components/display/generated/variant-keys.ts +169 -0
- package/src/components/{atoms → feedback}/Alert.tsx +12 -5
- package/src/components/feedback/Banner.tsx +90 -0
- package/src/components/{molecules → feedback}/NotificationCenter.tsx +64 -31
- package/src/components/feedback/ProgressWidget.tsx +44 -0
- package/src/components/{atoms → feedback}/Spinner.tsx +2 -2
- package/src/components/{molecules → feedback}/StatusBar.tsx +4 -4
- package/src/components/feedback/StatusScreen.tsx +148 -0
- package/src/components/{molecules → feedback}/Stepper.tsx +10 -5
- package/src/components/feedback/Toast.tsx +108 -0
- package/src/components/feedback/ToastProvider.tsx +78 -0
- package/src/components/feedback/generated/default-variant-keys.ts +16 -0
- package/src/components/feedback/generated/variant-keys.ts +21 -0
- package/src/components/generated/component-manifest.ts +1568 -454
- package/src/components/generated/component-style-hints.ts +1958 -718
- package/src/components/{atoms → inputs}/ButtonVariants.ts +13 -3
- package/src/components/inputs/Calendar.tsx +212 -0
- package/src/components/inputs/ChatComposer.tsx +75 -0
- package/src/components/inputs/ChatInput.tsx +528 -0
- package/src/components/{atoms → inputs}/Checkbox.tsx +2 -2
- package/src/components/inputs/Combobox.tsx +175 -0
- package/src/components/inputs/CopyButton.tsx +187 -0
- package/src/components/inputs/DatePicker.tsx +519 -0
- package/src/components/inputs/DateRangePicker.tsx +878 -0
- package/src/components/inputs/EditableField.tsx +182 -0
- package/src/components/{organisms → inputs}/FileUploader.tsx +24 -9
- package/src/components/inputs/FilterButton.tsx +163 -0
- package/src/components/{molecules → inputs}/Form.tsx +20 -3
- package/src/components/{atoms → inputs}/Input.tsx +2 -0
- package/src/components/inputs/InputOTP.tsx +75 -0
- package/src/components/inputs/Mention.tsx +279 -0
- package/src/components/inputs/NumberInput.tsx +109 -0
- package/src/components/inputs/PasswordGroup.tsx +138 -0
- package/src/components/inputs/PasswordInput.tsx +74 -0
- package/src/components/inputs/PasswordRequirementList.tsx +96 -0
- package/src/components/inputs/PasswordStrengthMeter.tsx +93 -0
- package/src/components/inputs/PhoneInput.tsx +99 -0
- package/src/components/inputs/PostalCodeInput.tsx +98 -0
- package/src/components/inputs/RangeSlider.tsx +129 -0
- package/src/components/inputs/SearchInput.tsx +76 -0
- package/src/components/inputs/Select.tsx +39 -0
- package/src/components/{atoms → inputs}/Slider.tsx +18 -5
- package/src/components/{molecules → inputs}/SortButton.tsx +5 -2
- package/src/components/{atoms → inputs}/Switch.tsx +15 -4
- package/src/components/inputs/TagInput.tsx +114 -0
- package/src/components/{atoms → inputs}/Textarea.tsx +1 -0
- package/src/components/inputs/TimePicker.tsx +150 -0
- package/src/components/inputs/Toggle.tsx +48 -0
- package/src/components/{atoms → inputs}/ToggleGroup.tsx +2 -2
- package/src/components/inputs/TooltipButton.tsx +148 -0
- package/src/components/inputs/VoiceInputButton.tsx +317 -0
- package/src/components/inputs/calendar-holidays.ts +56 -0
- package/src/components/inputs/generated/default-variant-keys.ts +32 -0
- package/src/components/{atoms → inputs}/generated/variant-keys.ts +19 -27
- package/src/components/layout/AspectRatio.tsx +12 -0
- package/src/components/layout/AssetInspectorPanel.tsx +416 -0
- package/src/components/layout/Cluster.tsx +56 -0
- package/src/components/layout/CollapsiblePanelToggle.tsx +94 -0
- package/src/components/layout/Container.tsx +43 -0
- package/src/components/layout/DeviceFrame.tsx +227 -0
- package/src/components/layout/Grid.tsx +65 -0
- package/src/components/layout/HStack.tsx +73 -0
- package/src/components/{organisms → layout}/InspectorPanel.tsx +6 -5
- package/src/components/layout/MarqueeFrame.tsx +158 -0
- package/src/components/layout/Resizable.tsx +94 -0
- package/src/components/layout/ScrollArea.tsx +71 -0
- package/src/components/{organisms → layout}/SpatialCanvas.tsx +12 -7
- package/src/components/layout/VStack.tsx +69 -0
- package/src/components/layout/generated/default-variant-keys.ts +16 -0
- package/src/components/layout/generated/variant-keys.ts +21 -0
- package/src/components/{molecules → navigation}/Breadcrumb.tsx +5 -4
- package/src/components/navigation/Command.tsx +266 -0
- package/src/components/navigation/CommandPalette.tsx +83 -0
- package/src/components/navigation/DocumentPager.tsx +171 -0
- package/src/components/navigation/Footer.tsx +88 -0
- package/src/components/navigation/Header.tsx +80 -0
- package/src/components/{molecules → navigation}/Menubar.tsx +45 -12
- package/src/components/navigation/NavigationMenu.tsx +128 -0
- package/src/components/navigation/PageAside.tsx +84 -0
- package/src/components/{molecules → navigation}/Pagination.tsx +60 -7
- package/src/components/{organisms → navigation}/RightRail.tsx +1 -1
- package/src/components/navigation/Sidebar.tsx +223 -0
- package/src/components/navigation/SidebarItem.tsx +160 -0
- package/src/components/{molecules → navigation}/Tabs.tsx +2 -2
- package/src/components/navigation/TextLink.tsx +71 -0
- package/src/components/navigation/generated/default-variant-keys.ts +12 -0
- package/src/components/navigation/generated/variant-keys.ts +13 -0
- package/src/components/overlay/AIChatInput.tsx +5 -0
- package/src/components/overlay/AIChatMessage.tsx +6 -0
- package/src/components/overlay/AlertDialog.tsx +145 -0
- package/src/components/overlay/ChatPanel.tsx +180 -0
- package/src/components/{molecules → overlay}/ContextMenu.tsx +65 -29
- package/src/components/{molecules → overlay}/Dialog.tsx +21 -13
- package/src/components/overlay/Drawer.tsx +131 -0
- package/src/components/{molecules → overlay}/DropdownMenu.tsx +52 -17
- package/src/components/overlay/FloatingPanel.tsx +90 -0
- package/src/components/overlay/HoverCard.tsx +36 -0
- package/src/components/overlay/MediaLightbox.tsx +403 -0
- package/src/components/overlay/MediaPickerDialog.tsx +198 -0
- package/src/components/overlay/Modal.tsx +103 -0
- package/src/components/overlay/OnboardingFlow.tsx +172 -0
- package/src/components/overlay/Popover.tsx +36 -0
- package/src/components/overlay/ShareModal.tsx +324 -0
- package/src/components/{molecules → overlay}/Sheet.tsx +76 -19
- package/src/components/overlay/Tooltip.tsx +130 -0
- package/src/components/overlay/generated/default-variant-keys.ts +14 -0
- package/src/components/overlay/generated/variant-keys.ts +17 -0
- package/src/components/patterns/BlogTemplate.tsx +46 -0
- package/src/components/{templates → patterns}/DashboardTemplate.tsx +2 -2
- package/src/components/patterns/DocsTemplate.tsx +41 -0
- package/src/components/{templates → patterns}/MediaLibraryTemplate.tsx +1 -1
- package/src/components/patterns/OnboardingTemplate.tsx +32 -0
- package/src/components/patterns/PricingTemplate.tsx +106 -0
- package/src/globals.css +173 -22
- package/src/index.ts +177 -76
- package/tailwind-theme-extend.cjs +48 -3
- package/design/atoms-metadata.json +0 -82
- package/design/molecules-metadata.json +0 -130
- package/design/organisms-metadata.json +0 -38
- package/design/templates-metadata.json +0 -38
- package/src/components/atoms/Avatar.tsx +0 -57
- package/src/components/atoms/Select.tsx +0 -28
- package/src/components/atoms/generated/default-variant-keys.ts +0 -36
- package/src/components/molecules/AIChatInput.tsx +0 -140
- package/src/components/molecules/AIChatMessage.tsx +0 -109
- package/src/components/molecules/Accordion.tsx +0 -99
- package/src/components/molecules/Calendar.tsx +0 -60
- package/src/components/molecules/Carousel.tsx +0 -261
- package/src/components/molecules/Command.tsx +0 -152
- package/src/components/molecules/FilterButton.tsx +0 -133
- package/src/components/molecules/HoverCard.tsx +0 -29
- package/src/components/molecules/Modal.tsx +0 -66
- package/src/components/molecules/Popover.tsx +0 -31
- package/src/components/molecules/ProgressWidget.tsx +0 -40
- package/src/components/molecules/Resizable.tsx +0 -47
- package/src/components/molecules/ScrollArea.tsx +0 -48
- package/src/components/molecules/SidebarItem.tsx +0 -134
- package/src/components/molecules/Toast.tsx +0 -57
- package/src/components/molecules/Tooltip.tsx +0 -30
- package/src/components/molecules/generated/default-variant-keys.ts +0 -22
- package/src/components/molecules/generated/variant-keys.ts +0 -33
- package/src/components/organisms/CommandPalette.tsx +0 -58
- package/src/components/organisms/FloatingPanel.tsx +0 -46
- package/src/components/organisms/ShareModal.tsx +0 -182
- package/src/components/organisms/ToastProvider.tsx +0 -49
- /package/src/components/{atoms → display}/Kbd.tsx +0 -0
- /package/src/components/{atoms → display}/Separator.tsx +0 -0
- /package/src/components/{atoms → display}/Skeleton.tsx +0 -0
- /package/src/components/{atoms → feedback}/Progress.tsx +0 -0
- /package/src/components/{atoms → inputs}/Button.tsx +0 -0
- /package/src/components/{atoms → inputs}/Label.tsx +0 -0
- /package/src/components/{atoms → inputs}/RadioGroup.tsx +0 -0
- /package/src/components/{organisms → navigation}/AppRail.tsx +0 -0
- /package/src/components/{templates → patterns}/AuthTemplate.tsx +0 -0
- /package/src/components/{templates → patterns}/BannalyzeTemplate.tsx +0 -0
- /package/src/components/{templates → patterns}/ChatTemplate.tsx +0 -0
- /package/src/components/{templates → patterns}/EditorTemplate.tsx +0 -0
- /package/src/components/{templates → patterns}/KanbanTemplate.tsx +0 -0
- /package/src/components/{templates → patterns}/LandingTemplate.tsx +0 -0
- /package/src/components/{templates → patterns}/SettingsTemplate.tsx +0 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { IconLock as Lock, IconDeviceDesktop as Monitor, IconDeviceMobile as Smartphone, IconDeviceTablet as Tablet } from "@tabler/icons-react";
|
|
5
|
+
|
|
6
|
+
import { cn } from "../../lib/utils"
|
|
7
|
+
import { deviceFrameDefaultVariantKey } from "./generated/default-variant-keys"
|
|
8
|
+
import type { DeviceFrameVariantKey } from "./generated/variant-keys"
|
|
9
|
+
import { TooltipButton } from "../inputs/TooltipButton"
|
|
10
|
+
|
|
11
|
+
export type MarqueeViewport = "desktop" | "tablet" | "mobile"
|
|
12
|
+
|
|
13
|
+
export interface DeviceFrameLabels {
|
|
14
|
+
url?: string
|
|
15
|
+
desktop?: string
|
|
16
|
+
tablet?: string
|
|
17
|
+
mobile?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface DeviceFrameProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
21
|
+
/** Fake host shown in the URL bar, without a trailing slash. */
|
|
22
|
+
host: string
|
|
23
|
+
/** Current path inside the fake host. */
|
|
24
|
+
path: string
|
|
25
|
+
/** Path used when the URL bar is submitted as `/`. */
|
|
26
|
+
defaultPath?: string
|
|
27
|
+
/** Optional tab title shown beside the URL on tablet and desktop frames. */
|
|
28
|
+
tabTitle?: string
|
|
29
|
+
viewport: MarqueeViewport
|
|
30
|
+
variant?: DeviceFrameVariantKey
|
|
31
|
+
onViewportChange: (next: MarqueeViewport) => void
|
|
32
|
+
/** Optional whitelist for URL-bar navigation. */
|
|
33
|
+
navigablePaths?: string[]
|
|
34
|
+
/** Called with the normalized path after a valid URL-bar submit. */
|
|
35
|
+
onPathChange?: (path: string) => void
|
|
36
|
+
labels?: DeviceFrameLabels
|
|
37
|
+
children: React.ReactNode
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const VIEWPORTS: {
|
|
41
|
+
key: MarqueeViewport
|
|
42
|
+
Icon: typeof Monitor
|
|
43
|
+
labelKey: keyof DeviceFrameLabels
|
|
44
|
+
fallback: string
|
|
45
|
+
}[] = [
|
|
46
|
+
{ key: "desktop", Icon: Monitor, labelKey: "desktop", fallback: "Desktop" },
|
|
47
|
+
{ key: "tablet", Icon: Tablet, labelKey: "tablet", fallback: "Tablet" },
|
|
48
|
+
{ key: "mobile", Icon: Smartphone, labelKey: "mobile", fallback: "Mobile" },
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
interface DeviceFrameClassNames {
|
|
52
|
+
root: string
|
|
53
|
+
shell: string
|
|
54
|
+
chrome: string
|
|
55
|
+
url: string
|
|
56
|
+
viewportActive: string
|
|
57
|
+
viewportIdle: string
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const variantClasses: Record<DeviceFrameVariantKey, DeviceFrameClassNames> = {
|
|
61
|
+
default: {
|
|
62
|
+
root: "p-0",
|
|
63
|
+
shell: "overflow-hidden rounded-xl border border-border/60 bg-background shadow-2xl",
|
|
64
|
+
chrome: "border-b border-border/60 bg-muted/40",
|
|
65
|
+
url: "border-border/40 bg-background/60 focus-within:border-primary focus-within:ring-primary-border",
|
|
66
|
+
viewportActive: "bg-foreground/10 text-foreground",
|
|
67
|
+
viewportIdle: "text-muted-foreground hover:bg-foreground/5 hover:text-foreground",
|
|
68
|
+
},
|
|
69
|
+
windows11: {
|
|
70
|
+
root: "p-0",
|
|
71
|
+
shell: "overflow-hidden rounded-lg border border-border bg-background shadow-xl",
|
|
72
|
+
chrome: "border-b border-border bg-background",
|
|
73
|
+
url: "border-border/70 bg-muted/50 focus-within:border-primary focus-within:ring-primary-border",
|
|
74
|
+
viewportActive: "bg-primary-subtle text-primary-subtle-foreground",
|
|
75
|
+
viewportIdle: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
76
|
+
},
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function normalizeFramePath(value: string, host: string, defaultPath: string) {
|
|
80
|
+
let candidate = value
|
|
81
|
+
.trim()
|
|
82
|
+
.replace(/^https?:\/\//, "")
|
|
83
|
+
.replace(new RegExp(`^${host.replace(/\./g, "\\.")}`), "")
|
|
84
|
+
|
|
85
|
+
if (!candidate.startsWith("/")) candidate = `/${candidate}`
|
|
86
|
+
return candidate === "/" ? defaultPath : candidate
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const DeviceFrame = React.forwardRef<HTMLDivElement, DeviceFrameProps>(
|
|
90
|
+
(
|
|
91
|
+
{
|
|
92
|
+
host,
|
|
93
|
+
path,
|
|
94
|
+
defaultPath = "/",
|
|
95
|
+
tabTitle,
|
|
96
|
+
viewport,
|
|
97
|
+
variant = deviceFrameDefaultVariantKey,
|
|
98
|
+
onViewportChange,
|
|
99
|
+
navigablePaths,
|
|
100
|
+
onPathChange,
|
|
101
|
+
labels,
|
|
102
|
+
children,
|
|
103
|
+
className,
|
|
104
|
+
...props
|
|
105
|
+
},
|
|
106
|
+
ref
|
|
107
|
+
) => {
|
|
108
|
+
const classes = variantClasses[variant]
|
|
109
|
+
const [draftUrl, setDraftUrl] = React.useState(`${host}${path}`)
|
|
110
|
+
const [editing, setEditing] = React.useState(false)
|
|
111
|
+
const inputRef = React.useRef<HTMLInputElement | null>(null)
|
|
112
|
+
|
|
113
|
+
React.useEffect(() => {
|
|
114
|
+
if (!editing) setDraftUrl(`${host}${path}`)
|
|
115
|
+
}, [host, path, editing])
|
|
116
|
+
|
|
117
|
+
const commitDraftUrl = () => {
|
|
118
|
+
const candidate = normalizeFramePath(draftUrl, host, defaultPath)
|
|
119
|
+
if (!navigablePaths || navigablePaths.includes(candidate)) {
|
|
120
|
+
inputRef.current?.blur()
|
|
121
|
+
onPathChange?.(candidate)
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
setDraftUrl(`${host}${path}`)
|
|
125
|
+
inputRef.current?.blur()
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const isCompact = viewport === "mobile"
|
|
129
|
+
const chromeOuter = cn(
|
|
130
|
+
isCompact
|
|
131
|
+
? "flex items-center gap-2 px-2 py-2.5"
|
|
132
|
+
: "flex items-center gap-3 px-4 py-2.5",
|
|
133
|
+
classes.chrome
|
|
134
|
+
)
|
|
135
|
+
const urlBoxClass = cn(
|
|
136
|
+
isCompact
|
|
137
|
+
? "flex min-w-0 w-full items-center gap-1.5 rounded-md border px-2 py-1 text-xs focus-within:ring-1"
|
|
138
|
+
: "flex min-w-0 w-full max-w-md items-center gap-2 rounded-md border px-3 py-1 text-xs focus-within:ring-1",
|
|
139
|
+
classes.url
|
|
140
|
+
)
|
|
141
|
+
const isWindows11 = variant === "windows11"
|
|
142
|
+
|
|
143
|
+
return (
|
|
144
|
+
<div ref={ref} className={cn("w-full", classes.root, className)} {...props}>
|
|
145
|
+
<div className={classes.shell}>
|
|
146
|
+
<div className={chromeOuter}>
|
|
147
|
+
{!isCompact && !isWindows11 ? (
|
|
148
|
+
<div className="flex items-center gap-1.5" aria-hidden="true">
|
|
149
|
+
<span className="h-3 w-3 rounded-full bg-destructive" />
|
|
150
|
+
<span className="h-3 w-3 rounded-full bg-warning" />
|
|
151
|
+
<span className="h-3 w-3 rounded-full bg-success" />
|
|
152
|
+
</div>
|
|
153
|
+
) : null}
|
|
154
|
+
<form
|
|
155
|
+
onSubmit={(event) => {
|
|
156
|
+
event.preventDefault()
|
|
157
|
+
commitDraftUrl()
|
|
158
|
+
}}
|
|
159
|
+
className="flex min-w-0 flex-1 items-center justify-center"
|
|
160
|
+
>
|
|
161
|
+
<div className={urlBoxClass}>
|
|
162
|
+
<Lock className="h-3 w-3 shrink-0 text-muted-foreground" aria-hidden="true" />
|
|
163
|
+
{tabTitle && !isCompact ? (
|
|
164
|
+
<span className="shrink-0 font-medium text-foreground">
|
|
165
|
+
{tabTitle}
|
|
166
|
+
</span>
|
|
167
|
+
) : null}
|
|
168
|
+
<input
|
|
169
|
+
ref={inputRef}
|
|
170
|
+
type="text"
|
|
171
|
+
value={draftUrl}
|
|
172
|
+
onChange={(event) => setDraftUrl(event.currentTarget.value)}
|
|
173
|
+
onKeyDown={(event) => {
|
|
174
|
+
if (event.key === "Enter") {
|
|
175
|
+
event.preventDefault()
|
|
176
|
+
commitDraftUrl()
|
|
177
|
+
}
|
|
178
|
+
}}
|
|
179
|
+
onFocus={() => setEditing(true)}
|
|
180
|
+
onBlur={() => setEditing(false)}
|
|
181
|
+
className="min-w-0 flex-1 truncate bg-transparent font-mono text-xs text-muted-foreground outline-none placeholder:text-muted-foreground/60"
|
|
182
|
+
spellCheck={false}
|
|
183
|
+
aria-label={labels?.url ?? "URL"}
|
|
184
|
+
/>
|
|
185
|
+
</div>
|
|
186
|
+
</form>
|
|
187
|
+
<div className="flex shrink-0 items-center gap-0.5">
|
|
188
|
+
{VIEWPORTS.map(({ key, Icon, labelKey, fallback }) => {
|
|
189
|
+
const label = labels?.[labelKey] ?? fallback
|
|
190
|
+
return (
|
|
191
|
+
<TooltipButton
|
|
192
|
+
key={key}
|
|
193
|
+
type="button"
|
|
194
|
+
onClick={() => onViewportChange(key)}
|
|
195
|
+
aria-label={label}
|
|
196
|
+
aria-pressed={viewport === key}
|
|
197
|
+
tooltip={label}
|
|
198
|
+
tooltipSide="bottom"
|
|
199
|
+
variant="ghost"
|
|
200
|
+
size="icon"
|
|
201
|
+
className={cn(
|
|
202
|
+
"h-7 w-7 rounded-md p-1.5",
|
|
203
|
+
viewport === key ? classes.viewportActive : classes.viewportIdle
|
|
204
|
+
)}
|
|
205
|
+
>
|
|
206
|
+
<Icon className="h-3.5 w-3.5" />
|
|
207
|
+
</TooltipButton>
|
|
208
|
+
)
|
|
209
|
+
})}
|
|
210
|
+
</div>
|
|
211
|
+
{!isCompact && isWindows11 ? (
|
|
212
|
+
<div className="ml-1 flex h-6 shrink-0 items-center overflow-hidden rounded-md border border-border/60 text-muted-foreground" aria-hidden="true">
|
|
213
|
+
<span className="flex h-6 w-8 items-center justify-center text-xs leading-none">-</span>
|
|
214
|
+
<span className="flex h-6 w-8 items-center justify-center text-[10px] leading-none">□</span>
|
|
215
|
+
<span className="flex h-6 w-8 items-center justify-center text-xs leading-none">×</span>
|
|
216
|
+
</div>
|
|
217
|
+
) : null}
|
|
218
|
+
</div>
|
|
219
|
+
<div className="bg-background">{children}</div>
|
|
220
|
+
</div>
|
|
221
|
+
</div>
|
|
222
|
+
)
|
|
223
|
+
}
|
|
224
|
+
)
|
|
225
|
+
DeviceFrame.displayName = "DeviceFrame"
|
|
226
|
+
|
|
227
|
+
export { DeviceFrame }
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../lib/utils"
|
|
4
|
+
|
|
5
|
+
const GAP_MAP = {
|
|
6
|
+
0: "gap-0",
|
|
7
|
+
1: "gap-1",
|
|
8
|
+
2: "gap-2",
|
|
9
|
+
3: "gap-3",
|
|
10
|
+
4: "gap-4",
|
|
11
|
+
5: "gap-5",
|
|
12
|
+
6: "gap-6",
|
|
13
|
+
8: "gap-8",
|
|
14
|
+
} as const
|
|
15
|
+
|
|
16
|
+
const COLS_MAP = {
|
|
17
|
+
1: "grid-cols-1",
|
|
18
|
+
2: "grid-cols-2",
|
|
19
|
+
3: "grid-cols-3",
|
|
20
|
+
4: "grid-cols-4",
|
|
21
|
+
5: "grid-cols-5",
|
|
22
|
+
6: "grid-cols-6",
|
|
23
|
+
12: "grid-cols-12",
|
|
24
|
+
} as const
|
|
25
|
+
|
|
26
|
+
export interface GridProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
27
|
+
/** Fixed number of columns. Mutually exclusive with `minItemWidth`. */
|
|
28
|
+
cols?: keyof typeof COLS_MAP
|
|
29
|
+
/**
|
|
30
|
+
* If set, uses CSS grid auto-fit with this minimum item width (px).
|
|
31
|
+
* Items wrap and fill available space responsively.
|
|
32
|
+
*/
|
|
33
|
+
minItemWidth?: number
|
|
34
|
+
gap?: keyof typeof GAP_MAP
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const Grid = React.forwardRef<HTMLDivElement, GridProps>(
|
|
38
|
+
({ className, cols, minItemWidth, gap = 4, style, ...props }, ref) => {
|
|
39
|
+
const useAutoFit = minItemWidth !== undefined
|
|
40
|
+
const computedStyle = useAutoFit
|
|
41
|
+
? {
|
|
42
|
+
...style,
|
|
43
|
+
gridTemplateColumns: `repeat(auto-fit, minmax(${minItemWidth}px, 1fr))`,
|
|
44
|
+
}
|
|
45
|
+
: style
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<div
|
|
49
|
+
ref={ref}
|
|
50
|
+
style={computedStyle}
|
|
51
|
+
className={cn(
|
|
52
|
+
"grid",
|
|
53
|
+
!useAutoFit && cols !== undefined && COLS_MAP[cols],
|
|
54
|
+
!useAutoFit && cols === undefined && "grid-cols-3",
|
|
55
|
+
GAP_MAP[gap],
|
|
56
|
+
className
|
|
57
|
+
)}
|
|
58
|
+
{...props}
|
|
59
|
+
/>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
)
|
|
63
|
+
Grid.displayName = "Grid"
|
|
64
|
+
|
|
65
|
+
export { Grid }
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../lib/utils"
|
|
4
|
+
|
|
5
|
+
const GAP_MAP = {
|
|
6
|
+
0: "gap-0",
|
|
7
|
+
1: "gap-1",
|
|
8
|
+
2: "gap-2",
|
|
9
|
+
3: "gap-3",
|
|
10
|
+
4: "gap-4",
|
|
11
|
+
5: "gap-5",
|
|
12
|
+
6: "gap-6",
|
|
13
|
+
8: "gap-8",
|
|
14
|
+
10: "gap-10",
|
|
15
|
+
12: "gap-12",
|
|
16
|
+
} as const
|
|
17
|
+
|
|
18
|
+
const ALIGN_MAP = {
|
|
19
|
+
start: "items-start",
|
|
20
|
+
center: "items-center",
|
|
21
|
+
end: "items-end",
|
|
22
|
+
baseline: "items-baseline",
|
|
23
|
+
stretch: "items-stretch",
|
|
24
|
+
} as const
|
|
25
|
+
|
|
26
|
+
const JUSTIFY_MAP = {
|
|
27
|
+
start: "justify-start",
|
|
28
|
+
center: "justify-center",
|
|
29
|
+
end: "justify-end",
|
|
30
|
+
between: "justify-between",
|
|
31
|
+
around: "justify-around",
|
|
32
|
+
evenly: "justify-evenly",
|
|
33
|
+
} as const
|
|
34
|
+
|
|
35
|
+
export interface HStackProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
36
|
+
gap?: keyof typeof GAP_MAP
|
|
37
|
+
align?: keyof typeof ALIGN_MAP
|
|
38
|
+
justify?: keyof typeof JUSTIFY_MAP
|
|
39
|
+
wrap?: boolean
|
|
40
|
+
inline?: boolean
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const HStack = React.forwardRef<HTMLDivElement, HStackProps>(
|
|
44
|
+
(
|
|
45
|
+
{
|
|
46
|
+
className,
|
|
47
|
+
gap = 2,
|
|
48
|
+
align = "center",
|
|
49
|
+
justify = "start",
|
|
50
|
+
wrap = false,
|
|
51
|
+
inline = false,
|
|
52
|
+
...props
|
|
53
|
+
},
|
|
54
|
+
ref
|
|
55
|
+
) => (
|
|
56
|
+
<div
|
|
57
|
+
ref={ref}
|
|
58
|
+
className={cn(
|
|
59
|
+
inline ? "inline-flex" : "flex",
|
|
60
|
+
"flex-row",
|
|
61
|
+
wrap && "flex-wrap",
|
|
62
|
+
GAP_MAP[gap],
|
|
63
|
+
ALIGN_MAP[align],
|
|
64
|
+
JUSTIFY_MAP[justify],
|
|
65
|
+
className
|
|
66
|
+
)}
|
|
67
|
+
{...props}
|
|
68
|
+
/>
|
|
69
|
+
)
|
|
70
|
+
)
|
|
71
|
+
HStack.displayName = "HStack"
|
|
72
|
+
|
|
73
|
+
export { HStack }
|
|
@@ -5,25 +5,26 @@ import { cn } from "../../lib/utils"
|
|
|
5
5
|
|
|
6
6
|
export interface InspectorPanelProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
7
7
|
title?: string
|
|
8
|
+
header?: React.ReactNode
|
|
8
9
|
footer?: React.ReactNode
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
export const InspectorPanel = React.forwardRef<HTMLDivElement, InspectorPanelProps>(
|
|
12
|
-
({ className, title, children, footer, ...props }, ref) => {
|
|
13
|
+
({ className, title, header, children, footer, ...props }, ref) => {
|
|
13
14
|
return (
|
|
14
15
|
<div
|
|
15
16
|
ref={ref}
|
|
16
17
|
className={cn(
|
|
17
|
-
"flex
|
|
18
|
+
"flex h-full w-full flex-col border-l border-border bg-background w-[320px] h-[420px]",
|
|
18
19
|
className
|
|
19
20
|
)}
|
|
20
21
|
{...props}
|
|
21
22
|
>
|
|
22
|
-
{title && (
|
|
23
|
+
{header ?? (title && (
|
|
23
24
|
<div className="flex items-center h-12 px-4 border-b border-border bg-muted/30">
|
|
24
25
|
<h3 className="text-sm font-semibold text-foreground">{title}</h3>
|
|
25
26
|
</div>
|
|
26
|
-
)}
|
|
27
|
+
))}
|
|
27
28
|
<div className="flex-1 overflow-y-auto overflow-x-hidden p-4 space-y-6">
|
|
28
29
|
{children}
|
|
29
30
|
</div>
|
|
@@ -57,7 +58,7 @@ export const InspectorField = React.forwardRef<
|
|
|
57
58
|
HTMLDivElement,
|
|
58
59
|
React.HTMLAttributes<HTMLDivElement> & { label: string }
|
|
59
60
|
>(({ className, label, children, ...props }, ref) => (
|
|
60
|
-
<div ref={ref} className={cn("grid gap-1.5", className)} {...props}>
|
|
61
|
+
<div ref={ref} className={cn("grid min-w-0 gap-1.5", className)} {...props}>
|
|
61
62
|
<label className="text-xs font-medium text-foreground">{label}</label>
|
|
62
63
|
{children}
|
|
63
64
|
</div>
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
|
|
5
|
+
import { cn } from "../../lib/utils"
|
|
6
|
+
import { DeviceFrame, type DeviceFrameLabels, type MarqueeViewport } from "./DeviceFrame"
|
|
7
|
+
import { marqueeFrameDefaultVariantKey } from "./generated/default-variant-keys"
|
|
8
|
+
import type { MarqueeFrameVariantKey } from "./generated/variant-keys"
|
|
9
|
+
|
|
10
|
+
export type { MarqueeViewport } from "./DeviceFrame"
|
|
11
|
+
|
|
12
|
+
export const MARQUEE_VIEWPORT_SIZES: Record<
|
|
13
|
+
MarqueeViewport,
|
|
14
|
+
{ width: number; height: number }
|
|
15
|
+
> = {
|
|
16
|
+
desktop: { width: 1280, height: 720 },
|
|
17
|
+
tablet: { width: 768, height: 1024 },
|
|
18
|
+
mobile: { width: 375, height: 667 },
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface MarqueeFrameProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "children"> {
|
|
22
|
+
host?: string
|
|
23
|
+
path: string
|
|
24
|
+
defaultPath?: string
|
|
25
|
+
tabTitle?: string
|
|
26
|
+
navigablePaths?: string[]
|
|
27
|
+
onPathChange?: (path: string) => void
|
|
28
|
+
storageKey?: string
|
|
29
|
+
initialViewport?: MarqueeViewport
|
|
30
|
+
variant?: MarqueeFrameVariantKey
|
|
31
|
+
viewportSizes?: Record<MarqueeViewport, { width: number; height: number }>
|
|
32
|
+
maxCanvasHeight?: number
|
|
33
|
+
labels?: DeviceFrameLabels
|
|
34
|
+
children: (viewport: MarqueeViewport) => React.ReactNode
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const VIEWPORTS: MarqueeViewport[] = ["desktop", "tablet", "mobile"]
|
|
38
|
+
|
|
39
|
+
const variantClasses: Record<MarqueeFrameVariantKey, { root: string }> = {
|
|
40
|
+
default: { root: "" },
|
|
41
|
+
desktop: { root: "" },
|
|
42
|
+
tablet: { root: "" },
|
|
43
|
+
mobile: { root: "" },
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const MarqueeFrame = React.forwardRef<HTMLDivElement, MarqueeFrameProps>(
|
|
47
|
+
(
|
|
48
|
+
{
|
|
49
|
+
host = "gunjo.example",
|
|
50
|
+
path,
|
|
51
|
+
defaultPath = "/",
|
|
52
|
+
tabTitle = "GunjoUI",
|
|
53
|
+
navigablePaths,
|
|
54
|
+
onPathChange,
|
|
55
|
+
storageKey,
|
|
56
|
+
initialViewport = "desktop",
|
|
57
|
+
variant = marqueeFrameDefaultVariantKey,
|
|
58
|
+
viewportSizes = MARQUEE_VIEWPORT_SIZES,
|
|
59
|
+
maxCanvasHeight = 720,
|
|
60
|
+
labels,
|
|
61
|
+
children,
|
|
62
|
+
className,
|
|
63
|
+
...props
|
|
64
|
+
},
|
|
65
|
+
ref
|
|
66
|
+
) => {
|
|
67
|
+
const classes = variantClasses[variant]
|
|
68
|
+
const containerRef = React.useRef<HTMLDivElement | null>(null)
|
|
69
|
+
const [viewport, setViewport] = React.useState<MarqueeViewport>(initialViewport)
|
|
70
|
+
const [scale, setScale] = React.useState(1)
|
|
71
|
+
|
|
72
|
+
React.useImperativeHandle(ref, () => containerRef.current as HTMLDivElement)
|
|
73
|
+
|
|
74
|
+
React.useEffect(() => {
|
|
75
|
+
if (!storageKey) return
|
|
76
|
+
const stored = window.sessionStorage.getItem(storageKey)
|
|
77
|
+
if (VIEWPORTS.includes(stored as MarqueeViewport)) {
|
|
78
|
+
setViewport(stored as MarqueeViewport)
|
|
79
|
+
}
|
|
80
|
+
}, [storageKey])
|
|
81
|
+
|
|
82
|
+
const { width: viewportWidth, height: viewportHeight } = viewportSizes[viewport]
|
|
83
|
+
|
|
84
|
+
React.useLayoutEffect(() => {
|
|
85
|
+
const node = containerRef.current
|
|
86
|
+
if (!node) return
|
|
87
|
+
const updateScale = (available: number) => {
|
|
88
|
+
const widthRatio = Math.max(0, available - 8) / viewportWidth
|
|
89
|
+
const heightRatio = maxCanvasHeight / viewportHeight
|
|
90
|
+
const next = Math.min(1, widthRatio, heightRatio)
|
|
91
|
+
const stableNext = Math.round((next > 0 ? next : 1) * 1000) / 1000
|
|
92
|
+
setScale((current) => (Math.abs(current - stableNext) < 0.001 ? current : stableNext))
|
|
93
|
+
}
|
|
94
|
+
updateScale(node.clientWidth)
|
|
95
|
+
const resizeObserver = new ResizeObserver((entries) => {
|
|
96
|
+
const entry = entries[0]
|
|
97
|
+
updateScale(entry?.contentRect.width ?? node.clientWidth)
|
|
98
|
+
})
|
|
99
|
+
resizeObserver.observe(node)
|
|
100
|
+
return () => resizeObserver.disconnect()
|
|
101
|
+
}, [maxCanvasHeight, viewportHeight, viewportWidth])
|
|
102
|
+
|
|
103
|
+
const handleViewportChange = (next: MarqueeViewport) => {
|
|
104
|
+
setViewport(next)
|
|
105
|
+
if (storageKey) window.sessionStorage.setItem(storageKey, next)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<div
|
|
110
|
+
className={cn("w-full bg-muted/40 p-0 px-4 py-8 sm:px-8 lg:py-12", classes.root, className)}
|
|
111
|
+
data-slot="marquee-frame"
|
|
112
|
+
{...props}
|
|
113
|
+
>
|
|
114
|
+
<div ref={containerRef} className="mx-auto w-full" data-slot="marquee-frame-scale-container">
|
|
115
|
+
<div
|
|
116
|
+
className="mx-auto"
|
|
117
|
+
data-slot="marquee-frame-scaled-box"
|
|
118
|
+
style={{
|
|
119
|
+
height: viewportHeight * scale + 56,
|
|
120
|
+
width: viewportWidth * scale,
|
|
121
|
+
}}
|
|
122
|
+
>
|
|
123
|
+
<div
|
|
124
|
+
data-slot="marquee-frame-transform"
|
|
125
|
+
style={{
|
|
126
|
+
transform: `scale(${scale})`,
|
|
127
|
+
transformOrigin: "top left",
|
|
128
|
+
width: viewportWidth,
|
|
129
|
+
}}
|
|
130
|
+
>
|
|
131
|
+
<DeviceFrame
|
|
132
|
+
host={host}
|
|
133
|
+
path={path}
|
|
134
|
+
defaultPath={defaultPath}
|
|
135
|
+
tabTitle={tabTitle}
|
|
136
|
+
viewport={viewport}
|
|
137
|
+
onViewportChange={handleViewportChange}
|
|
138
|
+
navigablePaths={navigablePaths}
|
|
139
|
+
onPathChange={onPathChange}
|
|
140
|
+
labels={labels}
|
|
141
|
+
>
|
|
142
|
+
<div
|
|
143
|
+
className="overflow-hidden bg-background"
|
|
144
|
+
style={{ width: viewportWidth, height: viewportHeight }}
|
|
145
|
+
>
|
|
146
|
+
{children(viewport)}
|
|
147
|
+
</div>
|
|
148
|
+
</DeviceFrame>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
)
|
|
156
|
+
MarqueeFrame.displayName = "MarqueeFrame"
|
|
157
|
+
|
|
158
|
+
export { MarqueeFrame }
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { IconGripVertical as GripVertical } from "@tabler/icons-react";
|
|
5
|
+
import { Panel, Group as PanelGroup, Separator as PanelResizeHandle, type GroupImperativeHandle, type GroupProps } from "react-resizable-panels"
|
|
6
|
+
|
|
7
|
+
import { cn } from "../../lib/utils"
|
|
8
|
+
|
|
9
|
+
type ResizablePanelGroupProps = Omit<GroupProps, "orientation"> & {
|
|
10
|
+
direction: "vertical" | "horizontal"
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const ResizablePanelGroupResetContext = React.createContext<(() => void) | null>(null)
|
|
14
|
+
|
|
15
|
+
const ResizablePanelGroup = ({
|
|
16
|
+
className,
|
|
17
|
+
direction,
|
|
18
|
+
defaultLayout,
|
|
19
|
+
groupRef,
|
|
20
|
+
...props
|
|
21
|
+
}: ResizablePanelGroupProps) => {
|
|
22
|
+
const internalGroupRef = React.useRef<GroupImperativeHandle | null>(null)
|
|
23
|
+
const setGroupRef = React.useCallback((node: GroupImperativeHandle | null) => {
|
|
24
|
+
internalGroupRef.current = node
|
|
25
|
+
|
|
26
|
+
if (typeof groupRef === "function") {
|
|
27
|
+
groupRef(node)
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (groupRef) {
|
|
32
|
+
(groupRef as React.MutableRefObject<GroupImperativeHandle | null>).current = node
|
|
33
|
+
}
|
|
34
|
+
}, [groupRef])
|
|
35
|
+
const resetLayout = React.useCallback(() => {
|
|
36
|
+
if (defaultLayout) {
|
|
37
|
+
internalGroupRef.current?.setLayout(defaultLayout)
|
|
38
|
+
}
|
|
39
|
+
}, [defaultLayout])
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<ResizablePanelGroupResetContext.Provider value={defaultLayout ? resetLayout : null}>
|
|
43
|
+
<PanelGroup
|
|
44
|
+
groupRef={setGroupRef}
|
|
45
|
+
defaultLayout={defaultLayout}
|
|
46
|
+
orientation={direction}
|
|
47
|
+
className={cn(
|
|
48
|
+
"flex h-full w-full data-[panel-group-direction=vertical]:flex-col",
|
|
49
|
+
className
|
|
50
|
+
)}
|
|
51
|
+
{...props}
|
|
52
|
+
/>
|
|
53
|
+
</ResizablePanelGroupResetContext.Provider>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const ResizablePanel = Panel
|
|
58
|
+
|
|
59
|
+
const ResizableHandle = ({
|
|
60
|
+
withHandle,
|
|
61
|
+
className,
|
|
62
|
+
onDoubleClick,
|
|
63
|
+
...props
|
|
64
|
+
}: React.ComponentProps<typeof PanelResizeHandle> & {
|
|
65
|
+
withHandle?: boolean
|
|
66
|
+
}) => {
|
|
67
|
+
const resetLayout = React.useContext(ResizablePanelGroupResetContext)
|
|
68
|
+
const handleDoubleClick = React.useCallback((event: React.MouseEvent<HTMLDivElement>) => {
|
|
69
|
+
onDoubleClick?.(event)
|
|
70
|
+
|
|
71
|
+
if (!event.defaultPrevented) {
|
|
72
|
+
resetLayout?.()
|
|
73
|
+
}
|
|
74
|
+
}, [onDoubleClick, resetLayout])
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<PanelResizeHandle
|
|
78
|
+
className={cn(
|
|
79
|
+
"relative flex w-px items-center justify-center bg-border after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 [&[aria-orientation=horizontal]]:h-px [&[aria-orientation=horizontal]]:w-full [&[aria-orientation=horizontal]]:after:left-0 [&[aria-orientation=horizontal]]:after:top-1/2 [&[aria-orientation=horizontal]]:after:h-1 [&[aria-orientation=horizontal]]:after:w-full [&[aria-orientation=horizontal]]:after:-translate-y-1/2 [&[aria-orientation=horizontal]]:after:translate-x-0 [&[aria-orientation=horizontal]>div]:rotate-90",
|
|
80
|
+
className
|
|
81
|
+
)}
|
|
82
|
+
onDoubleClick={handleDoubleClick}
|
|
83
|
+
{...props}
|
|
84
|
+
>
|
|
85
|
+
{withHandle && (
|
|
86
|
+
<div className="z-10 flex h-4 w-3 items-center justify-center rounded-sm border bg-border">
|
|
87
|
+
<GripVertical className="h-2.5 w-2.5" />
|
|
88
|
+
</div>
|
|
89
|
+
)}
|
|
90
|
+
</PanelResizeHandle>
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export { ResizablePanelGroup, ResizablePanel, ResizableHandle }
|