@open-slide/core 0.0.2 → 0.0.4
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/bin.js +2 -0
- package/dist/{build-DJGuOT6x.js → build-CuoESF2g.js} +1 -1
- package/dist/cli/bin.js +5 -5
- package/dist/config-DF58h0l4.js +641 -0
- package/dist/{dev-0SG0ArzD.js → dev-rlOZacWo.js} +1 -1
- package/dist/index.d.ts +7 -9
- package/dist/{preview-61Aawrlg.js → preview-DCrD9X36.js} +1 -1
- package/dist/vite/index.js +1 -1
- package/package.json +7 -4
- package/src/app/App.tsx +2 -2
- package/src/app/components/ClickNavZones.tsx +34 -0
- package/src/app/components/Player.tsx +26 -7
- package/src/app/components/ThumbnailRail.tsx +5 -5
- package/src/app/components/inspector/CommentPopover.tsx +3 -11
- package/src/app/components/inspector/InspectOverlay.tsx +15 -4
- package/src/app/components/inspector/InspectorProvider.tsx +12 -5
- package/src/app/components/sidebar/FolderItem.tsx +188 -0
- package/src/app/components/sidebar/IconPicker.tsx +59 -0
- package/src/app/components/sidebar/Sidebar.tsx +118 -0
- package/src/app/components/ui/dialog.tsx +141 -0
- package/src/app/components/ui/dropdown-menu.tsx +228 -0
- package/src/app/components/ui/popover.tsx +72 -0
- package/src/app/components/ui/tabs.tsx +79 -0
- package/src/app/lib/export-html.ts +313 -0
- package/src/app/lib/folders.ts +166 -0
- package/src/app/lib/inspector/fiber.ts +2 -2
- package/src/app/lib/inspector/useComments.ts +8 -8
- package/src/app/lib/sdk.ts +18 -5
- package/src/app/lib/slides.ts +8 -0
- package/src/app/routes/Home.tsx +540 -63
- package/src/app/routes/Slide.tsx +298 -0
- package/src/app/virtual.d.ts +4 -4
- package/dist/config-Opp2R1Jf.js +0 -335
- package/src/app/lib/decks.ts +0 -8
- package/src/app/routes/Deck.tsx +0 -185
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { Plus } from 'lucide-react';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import type { Folder, FolderIcon } from '@/lib/sdk';
|
|
4
|
+
import { FolderItem } from './FolderItem';
|
|
5
|
+
import { PRESET_COLORS } from './IconPicker';
|
|
6
|
+
|
|
7
|
+
export const DRAFT_ID = 'draft';
|
|
8
|
+
|
|
9
|
+
export function Sidebar({
|
|
10
|
+
folders,
|
|
11
|
+
countFor,
|
|
12
|
+
selectedId,
|
|
13
|
+
onSelect,
|
|
14
|
+
onCreate,
|
|
15
|
+
onRename,
|
|
16
|
+
onChangeIcon,
|
|
17
|
+
onDelete,
|
|
18
|
+
onDropToFolder,
|
|
19
|
+
onDropToDraft,
|
|
20
|
+
}: {
|
|
21
|
+
folders: Folder[];
|
|
22
|
+
countFor: (folderId: string | null) => number;
|
|
23
|
+
selectedId: string;
|
|
24
|
+
onSelect: (id: string) => void;
|
|
25
|
+
onCreate: (name: string, icon: FolderIcon) => Promise<Folder> | void;
|
|
26
|
+
onRename: (id: string, name: string) => void;
|
|
27
|
+
onChangeIcon: (id: string, icon: FolderIcon) => void;
|
|
28
|
+
onDelete: (id: string) => void;
|
|
29
|
+
onDropToFolder: (folderId: string, slideId: string) => void;
|
|
30
|
+
onDropToDraft: (slideId: string) => void;
|
|
31
|
+
}) {
|
|
32
|
+
const [creating, setCreating] = useState(false);
|
|
33
|
+
const [newName, setNewName] = useState('');
|
|
34
|
+
|
|
35
|
+
const commitCreate = () => {
|
|
36
|
+
const trimmed = newName.trim();
|
|
37
|
+
if (!trimmed) {
|
|
38
|
+
setCreating(false);
|
|
39
|
+
setNewName('');
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const color = PRESET_COLORS[folders.length % PRESET_COLORS.length];
|
|
43
|
+
onCreate(trimmed, { type: 'color', value: color });
|
|
44
|
+
setNewName('');
|
|
45
|
+
setCreating(false);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<aside className="flex h-full w-[17rem] shrink-0 flex-col border-r bg-card/40">
|
|
50
|
+
<div className="px-5 pt-6 pb-3">
|
|
51
|
+
<h1 className="font-heading text-lg font-bold tracking-tight">open-slide</h1>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<div className="px-2">
|
|
55
|
+
<FolderItem
|
|
56
|
+
row={{ kind: 'draft' }}
|
|
57
|
+
count={countFor(null)}
|
|
58
|
+
selected={selectedId === DRAFT_ID}
|
|
59
|
+
onSelect={() => onSelect(DRAFT_ID)}
|
|
60
|
+
onDropSlide={onDropToDraft}
|
|
61
|
+
/>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<div className="mt-4 px-4 pb-1 text-xs font-medium tracking-wide text-muted-foreground/70">
|
|
65
|
+
FOLDERS
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<div className="flex-1 overflow-y-auto px-2 pb-2">
|
|
69
|
+
{folders.map((folder) => (
|
|
70
|
+
<FolderItem
|
|
71
|
+
key={folder.id}
|
|
72
|
+
row={{
|
|
73
|
+
kind: 'folder',
|
|
74
|
+
folder,
|
|
75
|
+
onRename: (name) => onRename(folder.id, name),
|
|
76
|
+
onChangeIcon: (icon) => onChangeIcon(folder.id, icon),
|
|
77
|
+
onDelete: () => onDelete(folder.id),
|
|
78
|
+
}}
|
|
79
|
+
count={countFor(folder.id)}
|
|
80
|
+
selected={selectedId === folder.id}
|
|
81
|
+
onSelect={() => onSelect(folder.id)}
|
|
82
|
+
onDropSlide={(slideId) => onDropToFolder(folder.id, slideId)}
|
|
83
|
+
/>
|
|
84
|
+
))}
|
|
85
|
+
|
|
86
|
+
{creating ? (
|
|
87
|
+
<div className="mt-1 flex items-center gap-2 rounded-md border border-dashed bg-background px-2 py-1.5">
|
|
88
|
+
<input
|
|
89
|
+
autoFocus
|
|
90
|
+
value={newName}
|
|
91
|
+
onChange={(e) => setNewName(e.target.value)}
|
|
92
|
+
onBlur={commitCreate}
|
|
93
|
+
onKeyDown={(e) => {
|
|
94
|
+
if (e.key === 'Enter') commitCreate();
|
|
95
|
+
if (e.key === 'Escape') {
|
|
96
|
+
setCreating(false);
|
|
97
|
+
setNewName('');
|
|
98
|
+
}
|
|
99
|
+
}}
|
|
100
|
+
placeholder="Folder name"
|
|
101
|
+
maxLength={40}
|
|
102
|
+
className="min-w-0 flex-1 bg-transparent text-sm outline-none"
|
|
103
|
+
/>
|
|
104
|
+
</div>
|
|
105
|
+
) : (
|
|
106
|
+
<button
|
|
107
|
+
type="button"
|
|
108
|
+
onClick={() => setCreating(true)}
|
|
109
|
+
className="mt-1 flex w-full items-center justify-center gap-1.5 rounded-md border border-dashed border-border/70 px-2 py-1.5 text-xs text-muted-foreground transition-colors hover:border-border hover:bg-muted/40 hover:text-foreground"
|
|
110
|
+
>
|
|
111
|
+
<Plus className="size-3.5" />
|
|
112
|
+
Add folder
|
|
113
|
+
</button>
|
|
114
|
+
)}
|
|
115
|
+
</div>
|
|
116
|
+
</aside>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { XIcon } from 'lucide-react';
|
|
2
|
+
import { Dialog as DialogPrimitive } from 'radix-ui';
|
|
3
|
+
import type * as React from 'react';
|
|
4
|
+
import { Button } from '@/components/ui/button';
|
|
5
|
+
import { cn } from '@/lib/utils';
|
|
6
|
+
|
|
7
|
+
function Dialog({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>) {
|
|
8
|
+
return <DialogPrimitive.Root data-slot="dialog" {...props} />;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function DialogTrigger({ ...props }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
|
|
12
|
+
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function DialogPortal({ ...props }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
|
|
16
|
+
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function DialogClose({ ...props }: React.ComponentProps<typeof DialogPrimitive.Close>) {
|
|
20
|
+
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function DialogOverlay({
|
|
24
|
+
className,
|
|
25
|
+
...props
|
|
26
|
+
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
|
|
27
|
+
return (
|
|
28
|
+
<DialogPrimitive.Overlay
|
|
29
|
+
data-slot="dialog-overlay"
|
|
30
|
+
className={cn(
|
|
31
|
+
'fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0',
|
|
32
|
+
className,
|
|
33
|
+
)}
|
|
34
|
+
{...props}
|
|
35
|
+
/>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function DialogContent({
|
|
40
|
+
className,
|
|
41
|
+
children,
|
|
42
|
+
showCloseButton = true,
|
|
43
|
+
...props
|
|
44
|
+
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
|
|
45
|
+
showCloseButton?: boolean;
|
|
46
|
+
}) {
|
|
47
|
+
return (
|
|
48
|
+
<DialogPortal data-slot="dialog-portal">
|
|
49
|
+
<DialogOverlay />
|
|
50
|
+
<DialogPrimitive.Content
|
|
51
|
+
data-slot="dialog-content"
|
|
52
|
+
className={cn(
|
|
53
|
+
'fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg duration-200 outline-none data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 sm:max-w-lg',
|
|
54
|
+
className,
|
|
55
|
+
)}
|
|
56
|
+
{...props}
|
|
57
|
+
>
|
|
58
|
+
{children}
|
|
59
|
+
{showCloseButton && (
|
|
60
|
+
<DialogPrimitive.Close
|
|
61
|
+
data-slot="dialog-close"
|
|
62
|
+
className="absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
|
|
63
|
+
>
|
|
64
|
+
<XIcon />
|
|
65
|
+
<span className="sr-only">Close</span>
|
|
66
|
+
</DialogPrimitive.Close>
|
|
67
|
+
)}
|
|
68
|
+
</DialogPrimitive.Content>
|
|
69
|
+
</DialogPortal>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {
|
|
74
|
+
return (
|
|
75
|
+
<div
|
|
76
|
+
data-slot="dialog-header"
|
|
77
|
+
className={cn('flex flex-col gap-2 text-center sm:text-left', className)}
|
|
78
|
+
{...props}
|
|
79
|
+
/>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function DialogFooter({
|
|
84
|
+
className,
|
|
85
|
+
showCloseButton = false,
|
|
86
|
+
children,
|
|
87
|
+
...props
|
|
88
|
+
}: React.ComponentProps<'div'> & {
|
|
89
|
+
showCloseButton?: boolean;
|
|
90
|
+
}) {
|
|
91
|
+
return (
|
|
92
|
+
<div
|
|
93
|
+
data-slot="dialog-footer"
|
|
94
|
+
className={cn('flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', className)}
|
|
95
|
+
{...props}
|
|
96
|
+
>
|
|
97
|
+
{children}
|
|
98
|
+
{showCloseButton && (
|
|
99
|
+
<DialogPrimitive.Close asChild>
|
|
100
|
+
<Button variant="outline">Close</Button>
|
|
101
|
+
</DialogPrimitive.Close>
|
|
102
|
+
)}
|
|
103
|
+
</div>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>) {
|
|
108
|
+
return (
|
|
109
|
+
<DialogPrimitive.Title
|
|
110
|
+
data-slot="dialog-title"
|
|
111
|
+
className={cn('text-lg leading-none font-semibold', className)}
|
|
112
|
+
{...props}
|
|
113
|
+
/>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function DialogDescription({
|
|
118
|
+
className,
|
|
119
|
+
...props
|
|
120
|
+
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
|
|
121
|
+
return (
|
|
122
|
+
<DialogPrimitive.Description
|
|
123
|
+
data-slot="dialog-description"
|
|
124
|
+
className={cn('text-sm text-muted-foreground', className)}
|
|
125
|
+
{...props}
|
|
126
|
+
/>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export {
|
|
131
|
+
Dialog,
|
|
132
|
+
DialogClose,
|
|
133
|
+
DialogContent,
|
|
134
|
+
DialogDescription,
|
|
135
|
+
DialogFooter,
|
|
136
|
+
DialogHeader,
|
|
137
|
+
DialogOverlay,
|
|
138
|
+
DialogPortal,
|
|
139
|
+
DialogTitle,
|
|
140
|
+
DialogTrigger,
|
|
141
|
+
};
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { CheckIcon, ChevronRightIcon, CircleIcon } from 'lucide-react';
|
|
5
|
+
import { DropdownMenu as DropdownMenuPrimitive } from 'radix-ui';
|
|
6
|
+
|
|
7
|
+
import { cn } from '@/lib/utils';
|
|
8
|
+
|
|
9
|
+
function DropdownMenu({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
|
|
10
|
+
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function DropdownMenuPortal({
|
|
14
|
+
...props
|
|
15
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
|
|
16
|
+
return <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function DropdownMenuTrigger({
|
|
20
|
+
...props
|
|
21
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
|
|
22
|
+
return <DropdownMenuPrimitive.Trigger data-slot="dropdown-menu-trigger" {...props} />;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function DropdownMenuContent({
|
|
26
|
+
className,
|
|
27
|
+
sideOffset = 4,
|
|
28
|
+
...props
|
|
29
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
|
|
30
|
+
return (
|
|
31
|
+
<DropdownMenuPrimitive.Portal>
|
|
32
|
+
<DropdownMenuPrimitive.Content
|
|
33
|
+
data-slot="dropdown-menu-content"
|
|
34
|
+
sideOffset={sideOffset}
|
|
35
|
+
className={cn(
|
|
36
|
+
'z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover p-1 text-popover-foreground shadow-md 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-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
|
|
37
|
+
className,
|
|
38
|
+
)}
|
|
39
|
+
{...props}
|
|
40
|
+
/>
|
|
41
|
+
</DropdownMenuPrimitive.Portal>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function DropdownMenuGroup({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
|
|
46
|
+
return <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function DropdownMenuItem({
|
|
50
|
+
className,
|
|
51
|
+
inset,
|
|
52
|
+
variant = 'default',
|
|
53
|
+
...props
|
|
54
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
|
|
55
|
+
inset?: boolean;
|
|
56
|
+
variant?: 'default' | 'destructive';
|
|
57
|
+
}) {
|
|
58
|
+
return (
|
|
59
|
+
<DropdownMenuPrimitive.Item
|
|
60
|
+
data-slot="dropdown-menu-item"
|
|
61
|
+
data-inset={inset}
|
|
62
|
+
data-variant={variant}
|
|
63
|
+
className={cn(
|
|
64
|
+
"relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-[variant=destructive]:*:[svg]:text-destructive!",
|
|
65
|
+
className,
|
|
66
|
+
)}
|
|
67
|
+
{...props}
|
|
68
|
+
/>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function DropdownMenuCheckboxItem({
|
|
73
|
+
className,
|
|
74
|
+
children,
|
|
75
|
+
checked,
|
|
76
|
+
...props
|
|
77
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
|
|
78
|
+
return (
|
|
79
|
+
<DropdownMenuPrimitive.CheckboxItem
|
|
80
|
+
data-slot="dropdown-menu-checkbox-item"
|
|
81
|
+
className={cn(
|
|
82
|
+
"relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
83
|
+
className,
|
|
84
|
+
)}
|
|
85
|
+
checked={checked}
|
|
86
|
+
{...props}
|
|
87
|
+
>
|
|
88
|
+
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
|
89
|
+
<DropdownMenuPrimitive.ItemIndicator>
|
|
90
|
+
<CheckIcon className="size-4" />
|
|
91
|
+
</DropdownMenuPrimitive.ItemIndicator>
|
|
92
|
+
</span>
|
|
93
|
+
{children}
|
|
94
|
+
</DropdownMenuPrimitive.CheckboxItem>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function DropdownMenuRadioGroup({
|
|
99
|
+
...props
|
|
100
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
|
|
101
|
+
return <DropdownMenuPrimitive.RadioGroup data-slot="dropdown-menu-radio-group" {...props} />;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function DropdownMenuRadioItem({
|
|
105
|
+
className,
|
|
106
|
+
children,
|
|
107
|
+
...props
|
|
108
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
|
|
109
|
+
return (
|
|
110
|
+
<DropdownMenuPrimitive.RadioItem
|
|
111
|
+
data-slot="dropdown-menu-radio-item"
|
|
112
|
+
className={cn(
|
|
113
|
+
"relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
114
|
+
className,
|
|
115
|
+
)}
|
|
116
|
+
{...props}
|
|
117
|
+
>
|
|
118
|
+
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
|
119
|
+
<DropdownMenuPrimitive.ItemIndicator>
|
|
120
|
+
<CircleIcon className="size-2 fill-current" />
|
|
121
|
+
</DropdownMenuPrimitive.ItemIndicator>
|
|
122
|
+
</span>
|
|
123
|
+
{children}
|
|
124
|
+
</DropdownMenuPrimitive.RadioItem>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function DropdownMenuLabel({
|
|
129
|
+
className,
|
|
130
|
+
inset,
|
|
131
|
+
...props
|
|
132
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
|
|
133
|
+
inset?: boolean;
|
|
134
|
+
}) {
|
|
135
|
+
return (
|
|
136
|
+
<DropdownMenuPrimitive.Label
|
|
137
|
+
data-slot="dropdown-menu-label"
|
|
138
|
+
data-inset={inset}
|
|
139
|
+
className={cn('px-2 py-1.5 text-sm font-medium data-[inset]:pl-8', className)}
|
|
140
|
+
{...props}
|
|
141
|
+
/>
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function DropdownMenuSeparator({
|
|
146
|
+
className,
|
|
147
|
+
...props
|
|
148
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
|
|
149
|
+
return (
|
|
150
|
+
<DropdownMenuPrimitive.Separator
|
|
151
|
+
data-slot="dropdown-menu-separator"
|
|
152
|
+
className={cn('-mx-1 my-1 h-px bg-border', className)}
|
|
153
|
+
{...props}
|
|
154
|
+
/>
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function DropdownMenuShortcut({ className, ...props }: React.ComponentProps<'span'>) {
|
|
159
|
+
return (
|
|
160
|
+
<span
|
|
161
|
+
data-slot="dropdown-menu-shortcut"
|
|
162
|
+
className={cn('ml-auto text-xs tracking-widest text-muted-foreground', className)}
|
|
163
|
+
{...props}
|
|
164
|
+
/>
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function DropdownMenuSub({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
|
|
169
|
+
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function DropdownMenuSubTrigger({
|
|
173
|
+
className,
|
|
174
|
+
inset,
|
|
175
|
+
children,
|
|
176
|
+
...props
|
|
177
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
|
|
178
|
+
inset?: boolean;
|
|
179
|
+
}) {
|
|
180
|
+
return (
|
|
181
|
+
<DropdownMenuPrimitive.SubTrigger
|
|
182
|
+
data-slot="dropdown-menu-sub-trigger"
|
|
183
|
+
data-inset={inset}
|
|
184
|
+
className={cn(
|
|
185
|
+
"flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[inset]:pl-8 data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
|
|
186
|
+
className,
|
|
187
|
+
)}
|
|
188
|
+
{...props}
|
|
189
|
+
>
|
|
190
|
+
{children}
|
|
191
|
+
<ChevronRightIcon className="ml-auto size-4" />
|
|
192
|
+
</DropdownMenuPrimitive.SubTrigger>
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function DropdownMenuSubContent({
|
|
197
|
+
className,
|
|
198
|
+
...props
|
|
199
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
|
|
200
|
+
return (
|
|
201
|
+
<DropdownMenuPrimitive.SubContent
|
|
202
|
+
data-slot="dropdown-menu-sub-content"
|
|
203
|
+
className={cn(
|
|
204
|
+
'z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg 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-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
|
|
205
|
+
className,
|
|
206
|
+
)}
|
|
207
|
+
{...props}
|
|
208
|
+
/>
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export {
|
|
213
|
+
DropdownMenu,
|
|
214
|
+
DropdownMenuPortal,
|
|
215
|
+
DropdownMenuTrigger,
|
|
216
|
+
DropdownMenuContent,
|
|
217
|
+
DropdownMenuGroup,
|
|
218
|
+
DropdownMenuLabel,
|
|
219
|
+
DropdownMenuItem,
|
|
220
|
+
DropdownMenuCheckboxItem,
|
|
221
|
+
DropdownMenuRadioGroup,
|
|
222
|
+
DropdownMenuRadioItem,
|
|
223
|
+
DropdownMenuSeparator,
|
|
224
|
+
DropdownMenuShortcut,
|
|
225
|
+
DropdownMenuSub,
|
|
226
|
+
DropdownMenuSubTrigger,
|
|
227
|
+
DropdownMenuSubContent,
|
|
228
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Popover as PopoverPrimitive } from 'radix-ui';
|
|
3
|
+
|
|
4
|
+
import { cn } from '@/lib/utils';
|
|
5
|
+
|
|
6
|
+
function Popover({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Root>) {
|
|
7
|
+
return <PopoverPrimitive.Root data-slot="popover" {...props} />;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function PopoverTrigger({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
|
|
11
|
+
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function PopoverContent({
|
|
15
|
+
className,
|
|
16
|
+
align = 'center',
|
|
17
|
+
sideOffset = 4,
|
|
18
|
+
...props
|
|
19
|
+
}: React.ComponentProps<typeof PopoverPrimitive.Content>) {
|
|
20
|
+
return (
|
|
21
|
+
<PopoverPrimitive.Portal>
|
|
22
|
+
<PopoverPrimitive.Content
|
|
23
|
+
data-slot="popover-content"
|
|
24
|
+
align={align}
|
|
25
|
+
sideOffset={sideOffset}
|
|
26
|
+
className={cn(
|
|
27
|
+
'z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-hidden 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-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
|
|
28
|
+
className,
|
|
29
|
+
)}
|
|
30
|
+
{...props}
|
|
31
|
+
/>
|
|
32
|
+
</PopoverPrimitive.Portal>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function PopoverAnchor({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
|
|
37
|
+
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function PopoverHeader({ className, ...props }: React.ComponentProps<'div'>) {
|
|
41
|
+
return (
|
|
42
|
+
<div
|
|
43
|
+
data-slot="popover-header"
|
|
44
|
+
className={cn('flex flex-col gap-1 text-sm', className)}
|
|
45
|
+
{...props}
|
|
46
|
+
/>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function PopoverTitle({ className, ...props }: React.ComponentProps<'h2'>) {
|
|
51
|
+
return <div data-slot="popover-title" className={cn('font-medium', className)} {...props} />;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function PopoverDescription({ className, ...props }: React.ComponentProps<'p'>) {
|
|
55
|
+
return (
|
|
56
|
+
<p
|
|
57
|
+
data-slot="popover-description"
|
|
58
|
+
className={cn('text-muted-foreground', className)}
|
|
59
|
+
{...props}
|
|
60
|
+
/>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export {
|
|
65
|
+
Popover,
|
|
66
|
+
PopoverTrigger,
|
|
67
|
+
PopoverContent,
|
|
68
|
+
PopoverAnchor,
|
|
69
|
+
PopoverHeader,
|
|
70
|
+
PopoverTitle,
|
|
71
|
+
PopoverDescription,
|
|
72
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
3
|
+
import { Tabs as TabsPrimitive } from 'radix-ui';
|
|
4
|
+
|
|
5
|
+
import { cn } from '@/lib/utils';
|
|
6
|
+
|
|
7
|
+
function Tabs({
|
|
8
|
+
className,
|
|
9
|
+
orientation = 'horizontal',
|
|
10
|
+
...props
|
|
11
|
+
}: React.ComponentProps<typeof TabsPrimitive.Root>) {
|
|
12
|
+
return (
|
|
13
|
+
<TabsPrimitive.Root
|
|
14
|
+
data-slot="tabs"
|
|
15
|
+
data-orientation={orientation}
|
|
16
|
+
orientation={orientation}
|
|
17
|
+
className={cn('group/tabs flex gap-2 data-[orientation=horizontal]:flex-col', className)}
|
|
18
|
+
{...props}
|
|
19
|
+
/>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const tabsListVariants = cva(
|
|
24
|
+
'group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-[orientation=horizontal]/tabs:h-9 group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col data-[variant=line]:rounded-none',
|
|
25
|
+
{
|
|
26
|
+
variants: {
|
|
27
|
+
variant: {
|
|
28
|
+
default: 'bg-muted',
|
|
29
|
+
line: 'gap-1 bg-transparent',
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
defaultVariants: {
|
|
33
|
+
variant: 'default',
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
function TabsList({
|
|
39
|
+
className,
|
|
40
|
+
variant = 'default',
|
|
41
|
+
...props
|
|
42
|
+
}: React.ComponentProps<typeof TabsPrimitive.List> & VariantProps<typeof tabsListVariants>) {
|
|
43
|
+
return (
|
|
44
|
+
<TabsPrimitive.List
|
|
45
|
+
data-slot="tabs-list"
|
|
46
|
+
data-variant={variant}
|
|
47
|
+
className={cn(tabsListVariants({ variant }), className)}
|
|
48
|
+
{...props}
|
|
49
|
+
/>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function TabsTrigger({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
|
|
54
|
+
return (
|
|
55
|
+
<TabsPrimitive.Trigger
|
|
56
|
+
data-slot="tabs-trigger"
|
|
57
|
+
className={cn(
|
|
58
|
+
"relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap text-foreground/60 transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none dark:text-muted-foreground dark:hover:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
59
|
+
'group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:border-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent',
|
|
60
|
+
'data-[state=active]:bg-background data-[state=active]:text-foreground dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 dark:data-[state=active]:text-foreground',
|
|
61
|
+
'after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-[state=active]:after:opacity-100',
|
|
62
|
+
className,
|
|
63
|
+
)}
|
|
64
|
+
{...props}
|
|
65
|
+
/>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function TabsContent({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Content>) {
|
|
70
|
+
return (
|
|
71
|
+
<TabsPrimitive.Content
|
|
72
|
+
data-slot="tabs-content"
|
|
73
|
+
className={cn('flex-1 outline-none', className)}
|
|
74
|
+
{...props}
|
|
75
|
+
/>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants };
|