@saena-io/create 0.1.0 → 0.2.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/dist/index.js +9 -9
- package/package.json +1 -1
- package/template/base/package.json +44 -2
- package/template/base/scripts/ui-update.ts +83 -0
- package/template/base/src/components/ui/accordion.tsx +75 -0
- package/template/base/src/components/ui/alert-dialog.tsx +162 -0
- package/template/base/src/components/ui/alert.tsx +73 -0
- package/template/base/src/components/ui/app-sidebar.tsx +183 -0
- package/template/base/src/components/ui/aspect-ratio.tsx +22 -0
- package/template/base/src/components/ui/asset-input.tsx +211 -0
- package/template/base/src/components/ui/avatar.tsx +91 -0
- package/template/base/src/components/ui/badge.tsx +50 -0
- package/template/base/src/components/ui/breadcrumb.tsx +104 -0
- package/template/base/src/components/ui/button-group.tsx +78 -0
- package/template/base/src/components/ui/button.tsx +56 -0
- package/template/base/src/components/ui/calendar.tsx +205 -0
- package/template/base/src/components/ui/card.tsx +85 -0
- package/template/base/src/components/ui/carousel.tsx +232 -0
- package/template/base/src/components/ui/chart.tsx +337 -0
- package/template/base/src/components/ui/checkbox.tsx +29 -0
- package/template/base/src/components/ui/collapsible.tsx +15 -0
- package/template/base/src/components/ui/combobox.tsx +276 -0
- package/template/base/src/components/ui/command.tsx +190 -0
- package/template/base/src/components/ui/context-menu.tsx +243 -0
- package/template/base/src/components/ui/dialog.tsx +134 -0
- package/template/base/src/components/ui/direction.tsx +4 -0
- package/template/base/src/components/ui/drawer.tsx +120 -0
- package/template/base/src/components/ui/dropdown-menu.tsx +254 -0
- package/template/base/src/components/ui/empty.tsx +94 -0
- package/template/base/src/components/ui/field.tsx +222 -0
- package/template/base/src/components/ui/focal-point-picker.tsx +175 -0
- package/template/base/src/components/ui/hover-card.tsx +46 -0
- package/template/base/src/components/ui/input-group.tsx +149 -0
- package/template/base/src/components/ui/input-otp.tsx +85 -0
- package/template/base/src/components/ui/input.tsx +20 -0
- package/template/base/src/components/ui/item.tsx +188 -0
- package/template/base/src/components/ui/kbd.tsx +26 -0
- package/template/base/src/components/ui/label.tsx +20 -0
- package/template/base/src/components/ui/menubar.tsx +268 -0
- package/template/base/src/components/ui/native-select.tsx +58 -0
- package/template/base/src/components/ui/nav-main.tsx +70 -0
- package/template/base/src/components/ui/nav-projects.tsx +97 -0
- package/template/base/src/components/ui/nav-secondary.tsx +37 -0
- package/template/base/src/components/ui/nav-user.tsx +108 -0
- package/template/base/src/components/ui/navigation-menu.tsx +164 -0
- package/template/base/src/components/ui/pagination.tsx +123 -0
- package/template/base/src/components/ui/popover.tsx +80 -0
- package/template/base/src/components/ui/progress.tsx +66 -0
- package/template/base/src/components/ui/radio-group.tsx +36 -0
- package/template/base/src/components/ui/resizable.tsx +42 -0
- package/template/base/src/components/ui/rich-text/ai-chat-editor.tsx +20 -0
- package/template/base/src/components/ui/rich-text/ai-command.tsx +90 -0
- package/template/base/src/components/ui/rich-text/ai-copilot.tsx +67 -0
- package/template/base/src/components/ui/rich-text/ai-menu.tsx +456 -0
- package/template/base/src/components/ui/rich-text/ai-node.tsx +42 -0
- package/template/base/src/components/ui/rich-text/ai-toolbar-button.tsx +29 -0
- package/template/base/src/components/ui/rich-text/block-draggable.tsx +187 -0
- package/template/base/src/components/ui/rich-text/block-selection.tsx +17 -0
- package/template/base/src/components/ui/rich-text/code-block-node.tsx +204 -0
- package/template/base/src/components/ui/rich-text/codec.ts +63 -0
- package/template/base/src/components/ui/rich-text/extension.ts +53 -0
- package/template/base/src/components/ui/rich-text/ghost-text.tsx +23 -0
- package/template/base/src/components/ui/rich-text/import-export-toolbar.tsx +103 -0
- package/template/base/src/components/ui/rich-text/link.tsx +18 -0
- package/template/base/src/components/ui/rich-text/list-node.tsx +65 -0
- package/template/base/src/components/ui/rich-text/nodes.tsx +44 -0
- package/template/base/src/components/ui/rich-text/plugins.ts +233 -0
- package/template/base/src/components/ui/rich-text/rich-text-editor.tsx +82 -0
- package/template/base/src/components/ui/rich-text/static.tsx +117 -0
- package/template/base/src/components/ui/rich-text/table-node.tsx +934 -0
- package/template/base/src/components/ui/rich-text/table-toolbar.tsx +232 -0
- package/template/base/src/components/ui/rich-text/toggle-node.tsx +36 -0
- package/template/base/src/components/ui/rich-text/toolbar-slots.ts +41 -0
- package/template/base/src/components/ui/rich-text/toolbar.tsx +668 -0
- package/template/base/src/components/ui/rich-text/use-ai-chat.ts +35 -0
- package/template/base/src/components/ui/rich-text/variable-type.ts +4 -0
- package/template/base/src/components/ui/rich-text/variable.tsx +97 -0
- package/template/base/src/components/ui/scroll-area.tsx +49 -0
- package/template/base/src/components/ui/select.tsx +202 -0
- package/template/base/src/components/ui/separator.tsx +19 -0
- package/template/base/src/components/ui/sheet.tsx +126 -0
- package/template/base/src/components/ui/sidebar.tsx +695 -0
- package/template/base/src/components/ui/skeleton.tsx +13 -0
- package/template/base/src/components/ui/slider.tsx +52 -0
- package/template/base/src/components/ui/sonner.tsx +50 -0
- package/template/base/src/components/ui/spinner.tsx +18 -0
- package/template/base/src/components/ui/switch.tsx +30 -0
- package/template/base/src/components/ui/table.tsx +89 -0
- package/template/base/src/components/ui/tabs.tsx +73 -0
- package/template/base/src/components/ui/textarea.tsx +18 -0
- package/template/base/src/components/ui/toggle-group.tsx +85 -0
- package/template/base/src/components/ui/toggle.tsx +45 -0
- package/template/base/src/components/ui/toolbar.tsx +451 -0
- package/template/base/src/components/ui/tooltip.tsx +52 -0
- package/template/base/src/hooks/use-mobile.ts +19 -0
- package/template/base/src/lib/utils.ts +6 -0
- package/template/base/src/routes/__root.tsx +1 -1
- package/template/base/src/server/auth.ts +2 -2
- package/template/base/src/styles/globals.css +230 -0
- package/template/base/vite.config.ts +15 -1
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// The i18n variable node type — a tiny, React-free constant shared by the editor node (variable.tsx, which
|
|
2
|
+
// imports platejs/react) and the editor-free static renderer (static.tsx). Keeping it here lets the static
|
|
3
|
+
// path reference the node type without pulling the editor into the public bundle (ADR-0005).
|
|
4
|
+
export const VARIABLE_TYPE = 'variable';
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { cn } from '@saena-io/ui/lib/utils';
|
|
2
|
+
import type { Value } from 'platejs';
|
|
3
|
+
import {
|
|
4
|
+
PlateElement,
|
|
5
|
+
type PlateElementProps,
|
|
6
|
+
createPlatePlugin,
|
|
7
|
+
useFocused,
|
|
8
|
+
useSelected,
|
|
9
|
+
} from 'platejs/react';
|
|
10
|
+
|
|
11
|
+
// An inline, VOID Plate node for an i18n variable (an ICU placeholder like {count}). Rendered as a badge
|
|
12
|
+
// in the editor and serialized as part of the stored Slate JSON. Variables are intrinsic to i18n, so
|
|
13
|
+
// this is part of the CORE rich-text set — not an optional addon. (Public-site ICU interpolation of rich
|
|
14
|
+
// content is deferred — ADR-0005; this makes variables visible + insertable while translating.)
|
|
15
|
+
|
|
16
|
+
export { VARIABLE_TYPE } from './variable-type';
|
|
17
|
+
import { VARIABLE_TYPE } from './variable-type';
|
|
18
|
+
|
|
19
|
+
/** Build a variable element node: `{ type: 'variable', name, children: [{ text: '' }] }`. */
|
|
20
|
+
export function variableNode(name: string) {
|
|
21
|
+
return { type: VARIABLE_TYPE, name, children: [{ text: '' }] };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Renders a variable node as an inline badge. Void → its visible content is non-editable. */
|
|
25
|
+
export function VariableElement(props: PlateElementProps) {
|
|
26
|
+
const name = (props.element as { name?: string }).name ?? '';
|
|
27
|
+
// Void nodes are selected atomically; mirror the canonical mention node so a keyboard-selected badge
|
|
28
|
+
// shows a ring, and carry data-slate-value so the variable survives copy/paste — Slate serializes a
|
|
29
|
+
// void node from this attribute, and without it a copied variable is lost on paste.
|
|
30
|
+
const selected = useSelected();
|
|
31
|
+
const focused = useFocused();
|
|
32
|
+
return (
|
|
33
|
+
<PlateElement
|
|
34
|
+
{...props}
|
|
35
|
+
as="span"
|
|
36
|
+
attributes={{
|
|
37
|
+
...props.attributes,
|
|
38
|
+
contentEditable: false,
|
|
39
|
+
'data-slate-value': `{${name}}`,
|
|
40
|
+
draggable: true,
|
|
41
|
+
}}
|
|
42
|
+
>
|
|
43
|
+
<span
|
|
44
|
+
contentEditable={false}
|
|
45
|
+
className={cn(
|
|
46
|
+
'mx-0.5 inline-flex select-none items-center rounded bg-primary px-1.5 py-px font-medium font-mono text-[0.8em] text-primary-foreground',
|
|
47
|
+
selected && focused && 'ring-2 ring-ring ring-offset-1 ring-offset-background',
|
|
48
|
+
)}
|
|
49
|
+
>
|
|
50
|
+
{name}
|
|
51
|
+
</span>
|
|
52
|
+
{props.children}
|
|
53
|
+
</PlateElement>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const VariablePlugin = createPlatePlugin({
|
|
58
|
+
key: VARIABLE_TYPE,
|
|
59
|
+
node: { type: VARIABLE_TYPE, isElement: true, isInline: true, isVoid: true },
|
|
60
|
+
}).withComponent(VariableElement);
|
|
61
|
+
|
|
62
|
+
type SlateNode = { text?: string; type?: string; name?: string; children?: SlateNode[] };
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Flatten a rich (Slate) value to plain text, turning variable nodes back into `{name}`, so the same
|
|
66
|
+
* ICU-placeholder extractor works on plain strings and rich docs alike (and braces in the JSON structure
|
|
67
|
+
* never pollute the result). Intended for placeholder extraction and search projection — inter-run and
|
|
68
|
+
* inter-block spacing is NOT faithfully preserved (runs are joined with a single space).
|
|
69
|
+
*/
|
|
70
|
+
export function flattenRichText(value: Value): string {
|
|
71
|
+
const parts: string[] = [];
|
|
72
|
+
const walk = (nodes: SlateNode[]) => {
|
|
73
|
+
for (const n of nodes) {
|
|
74
|
+
if (typeof n.text === 'string') parts.push(n.text);
|
|
75
|
+
else if (n.type === VARIABLE_TYPE && typeof n.name === 'string') parts.push(`{${n.name}}`);
|
|
76
|
+
if (n.children) walk(n.children);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
walk(value as unknown as SlateNode[]);
|
|
80
|
+
return parts.join(' ');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The named `{variable}` placeholders a message references — also the arg names of ICU plural/select
|
|
85
|
+
* blocks. Accepts a plain ICU string or a rich {@link Value} (flattened via {@link flattenRichText} first,
|
|
86
|
+
* so a rich doc's variable nodes and surrounding text are both scanned). This is the single owner of the
|
|
87
|
+
* placeholder/brace contract: the editor, the translations admin, and future CMS/commerce fields all share
|
|
88
|
+
* one definition of what counts as a variable.
|
|
89
|
+
*/
|
|
90
|
+
export function extractPlaceholders(input: Value | string): string[] {
|
|
91
|
+
const text = typeof input === 'string' ? input : flattenRichText(input);
|
|
92
|
+
const names = new Set<string>();
|
|
93
|
+
for (const m of text.matchAll(/\{\s*([a-zA-Z][\w-]*)/g)) {
|
|
94
|
+
if (m[1]) names.add(m[1]);
|
|
95
|
+
}
|
|
96
|
+
return [...names];
|
|
97
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { ScrollArea as ScrollAreaPrimitive } from '@base-ui/react/scroll-area';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
|
|
4
|
+
import { cn } from '@saena-io/ui/lib/utils';
|
|
5
|
+
|
|
6
|
+
function ScrollArea({ className, children, ...props }: ScrollAreaPrimitive.Root.Props) {
|
|
7
|
+
return (
|
|
8
|
+
<ScrollAreaPrimitive.Root
|
|
9
|
+
data-slot="scroll-area"
|
|
10
|
+
className={cn('relative', className)}
|
|
11
|
+
{...props}
|
|
12
|
+
>
|
|
13
|
+
<ScrollAreaPrimitive.Viewport
|
|
14
|
+
data-slot="scroll-area-viewport"
|
|
15
|
+
className="size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1"
|
|
16
|
+
>
|
|
17
|
+
{children}
|
|
18
|
+
</ScrollAreaPrimitive.Viewport>
|
|
19
|
+
<ScrollBar />
|
|
20
|
+
<ScrollAreaPrimitive.Corner />
|
|
21
|
+
</ScrollAreaPrimitive.Root>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function ScrollBar({
|
|
26
|
+
className,
|
|
27
|
+
orientation = 'vertical',
|
|
28
|
+
...props
|
|
29
|
+
}: ScrollAreaPrimitive.Scrollbar.Props) {
|
|
30
|
+
return (
|
|
31
|
+
<ScrollAreaPrimitive.Scrollbar
|
|
32
|
+
data-slot="scroll-area-scrollbar"
|
|
33
|
+
data-orientation={orientation}
|
|
34
|
+
orientation={orientation}
|
|
35
|
+
className={cn(
|
|
36
|
+
'flex touch-none p-px transition-colors select-none data-horizontal:h-2.5 data-horizontal:flex-col data-horizontal:border-t data-horizontal:border-t-transparent data-vertical:h-full data-vertical:w-2.5 data-vertical:border-l data-vertical:border-l-transparent',
|
|
37
|
+
className,
|
|
38
|
+
)}
|
|
39
|
+
{...props}
|
|
40
|
+
>
|
|
41
|
+
<ScrollAreaPrimitive.Thumb
|
|
42
|
+
data-slot="scroll-area-thumb"
|
|
43
|
+
className="relative flex-1 rounded-full bg-border"
|
|
44
|
+
/>
|
|
45
|
+
</ScrollAreaPrimitive.Scrollbar>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export { ScrollArea, ScrollBar };
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Select as SelectPrimitive } from '@base-ui/react/select';
|
|
4
|
+
import type * as React from 'react';
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
ArrowDown01Icon,
|
|
8
|
+
ArrowUp01Icon,
|
|
9
|
+
Tick02Icon,
|
|
10
|
+
UnfoldMoreIcon,
|
|
11
|
+
} from '@hugeicons/core-free-icons';
|
|
12
|
+
import { HugeiconsIcon } from '@hugeicons/react';
|
|
13
|
+
import { cn } from '@saena-io/ui/lib/utils';
|
|
14
|
+
|
|
15
|
+
const Select = SelectPrimitive.Root;
|
|
16
|
+
|
|
17
|
+
function SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) {
|
|
18
|
+
return (
|
|
19
|
+
<SelectPrimitive.Group
|
|
20
|
+
data-slot="select-group"
|
|
21
|
+
className={cn('scroll-my-1 p-1', className)}
|
|
22
|
+
{...props}
|
|
23
|
+
/>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function SelectValue({ className, ...props }: SelectPrimitive.Value.Props) {
|
|
28
|
+
return (
|
|
29
|
+
<SelectPrimitive.Value
|
|
30
|
+
data-slot="select-value"
|
|
31
|
+
className={cn('flex flex-1 text-left', className)}
|
|
32
|
+
{...props}
|
|
33
|
+
/>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function SelectTrigger({
|
|
38
|
+
className,
|
|
39
|
+
size = 'default',
|
|
40
|
+
children,
|
|
41
|
+
...props
|
|
42
|
+
}: SelectPrimitive.Trigger.Props & {
|
|
43
|
+
size?: 'sm' | 'default';
|
|
44
|
+
}) {
|
|
45
|
+
return (
|
|
46
|
+
<SelectPrimitive.Trigger
|
|
47
|
+
data-slot="select-trigger"
|
|
48
|
+
data-size={size}
|
|
49
|
+
className={cn(
|
|
50
|
+
"flex w-fit items-center justify-between gap-1.5 rounded-md border border-input bg-input/20 px-2 py-1.5 text-xs/relaxed whitespace-nowrap transition-colors outline-none focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/30 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-7 data-[size=sm]:h-6 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
|
|
51
|
+
className,
|
|
52
|
+
)}
|
|
53
|
+
{...props}
|
|
54
|
+
>
|
|
55
|
+
{children}
|
|
56
|
+
<SelectPrimitive.Icon
|
|
57
|
+
render={
|
|
58
|
+
<HugeiconsIcon
|
|
59
|
+
icon={UnfoldMoreIcon}
|
|
60
|
+
strokeWidth={2}
|
|
61
|
+
className="pointer-events-none size-3.5 text-muted-foreground"
|
|
62
|
+
/>
|
|
63
|
+
}
|
|
64
|
+
/>
|
|
65
|
+
</SelectPrimitive.Trigger>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function SelectContent({
|
|
70
|
+
className,
|
|
71
|
+
children,
|
|
72
|
+
side = 'bottom',
|
|
73
|
+
sideOffset = 4,
|
|
74
|
+
align = 'center',
|
|
75
|
+
alignOffset = 0,
|
|
76
|
+
alignItemWithTrigger = true,
|
|
77
|
+
...props
|
|
78
|
+
}: SelectPrimitive.Popup.Props &
|
|
79
|
+
Pick<
|
|
80
|
+
SelectPrimitive.Positioner.Props,
|
|
81
|
+
'align' | 'alignOffset' | 'side' | 'sideOffset' | 'alignItemWithTrigger'
|
|
82
|
+
>) {
|
|
83
|
+
return (
|
|
84
|
+
<SelectPrimitive.Portal>
|
|
85
|
+
<SelectPrimitive.Positioner
|
|
86
|
+
side={side}
|
|
87
|
+
sideOffset={sideOffset}
|
|
88
|
+
align={align}
|
|
89
|
+
alignOffset={alignOffset}
|
|
90
|
+
alignItemWithTrigger={alignItemWithTrigger}
|
|
91
|
+
className="isolate z-50"
|
|
92
|
+
>
|
|
93
|
+
<SelectPrimitive.Popup
|
|
94
|
+
data-slot="select-content"
|
|
95
|
+
data-align-trigger={alignItemWithTrigger}
|
|
96
|
+
className={cn(
|
|
97
|
+
'relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-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',
|
|
98
|
+
className,
|
|
99
|
+
)}
|
|
100
|
+
{...props}
|
|
101
|
+
>
|
|
102
|
+
<SelectScrollUpButton />
|
|
103
|
+
<SelectPrimitive.List>{children}</SelectPrimitive.List>
|
|
104
|
+
<SelectScrollDownButton />
|
|
105
|
+
</SelectPrimitive.Popup>
|
|
106
|
+
</SelectPrimitive.Positioner>
|
|
107
|
+
</SelectPrimitive.Portal>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function SelectLabel({ className, ...props }: SelectPrimitive.GroupLabel.Props) {
|
|
112
|
+
return (
|
|
113
|
+
<SelectPrimitive.GroupLabel
|
|
114
|
+
data-slot="select-label"
|
|
115
|
+
className={cn('px-2 py-1.5 text-xs text-muted-foreground', className)}
|
|
116
|
+
{...props}
|
|
117
|
+
/>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function SelectItem({ className, children, ...props }: SelectPrimitive.Item.Props) {
|
|
122
|
+
return (
|
|
123
|
+
<SelectPrimitive.Item
|
|
124
|
+
data-slot="select-item"
|
|
125
|
+
className={cn(
|
|
126
|
+
"relative flex min-h-7 w-full cursor-default items-center gap-2 rounded-md px-2 py-1 text-xs/relaxed outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]: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-3.5 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
|
127
|
+
className,
|
|
128
|
+
)}
|
|
129
|
+
{...props}
|
|
130
|
+
>
|
|
131
|
+
<SelectPrimitive.ItemText className="flex flex-1 shrink-0 gap-2 whitespace-nowrap">
|
|
132
|
+
{children}
|
|
133
|
+
</SelectPrimitive.ItemText>
|
|
134
|
+
<SelectPrimitive.ItemIndicator
|
|
135
|
+
render={
|
|
136
|
+
<span className="pointer-events-none absolute right-2 flex items-center justify-center" />
|
|
137
|
+
}
|
|
138
|
+
>
|
|
139
|
+
<HugeiconsIcon icon={Tick02Icon} strokeWidth={2} className="pointer-events-none" />
|
|
140
|
+
</SelectPrimitive.ItemIndicator>
|
|
141
|
+
</SelectPrimitive.Item>
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function SelectSeparator({ className, ...props }: SelectPrimitive.Separator.Props) {
|
|
146
|
+
return (
|
|
147
|
+
<SelectPrimitive.Separator
|
|
148
|
+
data-slot="select-separator"
|
|
149
|
+
className={cn('pointer-events-none -mx-1 my-1 h-px bg-border/50', className)}
|
|
150
|
+
{...props}
|
|
151
|
+
/>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function SelectScrollUpButton({
|
|
156
|
+
className,
|
|
157
|
+
...props
|
|
158
|
+
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpArrow>) {
|
|
159
|
+
return (
|
|
160
|
+
<SelectPrimitive.ScrollUpArrow
|
|
161
|
+
data-slot="select-scroll-up-button"
|
|
162
|
+
className={cn(
|
|
163
|
+
"top-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-3.5",
|
|
164
|
+
className,
|
|
165
|
+
)}
|
|
166
|
+
{...props}
|
|
167
|
+
>
|
|
168
|
+
<HugeiconsIcon icon={ArrowUp01Icon} strokeWidth={2} />
|
|
169
|
+
</SelectPrimitive.ScrollUpArrow>
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function SelectScrollDownButton({
|
|
174
|
+
className,
|
|
175
|
+
...props
|
|
176
|
+
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownArrow>) {
|
|
177
|
+
return (
|
|
178
|
+
<SelectPrimitive.ScrollDownArrow
|
|
179
|
+
data-slot="select-scroll-down-button"
|
|
180
|
+
className={cn(
|
|
181
|
+
"bottom-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-3.5",
|
|
182
|
+
className,
|
|
183
|
+
)}
|
|
184
|
+
{...props}
|
|
185
|
+
>
|
|
186
|
+
<HugeiconsIcon icon={ArrowDown01Icon} strokeWidth={2} />
|
|
187
|
+
</SelectPrimitive.ScrollDownArrow>
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export {
|
|
192
|
+
Select,
|
|
193
|
+
SelectContent,
|
|
194
|
+
SelectGroup,
|
|
195
|
+
SelectItem,
|
|
196
|
+
SelectLabel,
|
|
197
|
+
SelectScrollDownButton,
|
|
198
|
+
SelectScrollUpButton,
|
|
199
|
+
SelectSeparator,
|
|
200
|
+
SelectTrigger,
|
|
201
|
+
SelectValue,
|
|
202
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Separator as SeparatorPrimitive } from '@base-ui/react/separator';
|
|
2
|
+
|
|
3
|
+
import { cn } from '@saena-io/ui/lib/utils';
|
|
4
|
+
|
|
5
|
+
function Separator({ className, orientation = 'horizontal', ...props }: SeparatorPrimitive.Props) {
|
|
6
|
+
return (
|
|
7
|
+
<SeparatorPrimitive
|
|
8
|
+
data-slot="separator"
|
|
9
|
+
orientation={orientation}
|
|
10
|
+
className={cn(
|
|
11
|
+
'shrink-0 bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch',
|
|
12
|
+
className,
|
|
13
|
+
)}
|
|
14
|
+
{...props}
|
|
15
|
+
/>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export { Separator };
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Dialog as SheetPrimitive } from '@base-ui/react/dialog';
|
|
4
|
+
import type * as React from 'react';
|
|
5
|
+
|
|
6
|
+
import { Cancel01Icon } from '@hugeicons/core-free-icons';
|
|
7
|
+
import { HugeiconsIcon } from '@hugeicons/react';
|
|
8
|
+
import { Button } from '@saena-io/ui/components/button';
|
|
9
|
+
import { cn } from '@saena-io/ui/lib/utils';
|
|
10
|
+
|
|
11
|
+
function Sheet({ ...props }: SheetPrimitive.Root.Props) {
|
|
12
|
+
return <SheetPrimitive.Root data-slot="sheet" {...props} />;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function SheetTrigger({ ...props }: SheetPrimitive.Trigger.Props) {
|
|
16
|
+
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function SheetClose({ ...props }: SheetPrimitive.Close.Props) {
|
|
20
|
+
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function SheetPortal({ ...props }: SheetPrimitive.Portal.Props) {
|
|
24
|
+
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function SheetOverlay({ className, ...props }: SheetPrimitive.Backdrop.Props) {
|
|
28
|
+
return (
|
|
29
|
+
<SheetPrimitive.Backdrop
|
|
30
|
+
data-slot="sheet-overlay"
|
|
31
|
+
className={cn(
|
|
32
|
+
'fixed inset-0 z-50 bg-black/80 transition-opacity duration-150 data-ending-style:opacity-0 data-starting-style:opacity-0 supports-backdrop-filter:backdrop-blur-xs',
|
|
33
|
+
className,
|
|
34
|
+
)}
|
|
35
|
+
{...props}
|
|
36
|
+
/>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function SheetContent({
|
|
41
|
+
className,
|
|
42
|
+
children,
|
|
43
|
+
side = 'right',
|
|
44
|
+
showCloseButton = true,
|
|
45
|
+
...props
|
|
46
|
+
}: SheetPrimitive.Popup.Props & {
|
|
47
|
+
side?: 'top' | 'right' | 'bottom' | 'left';
|
|
48
|
+
showCloseButton?: boolean;
|
|
49
|
+
}) {
|
|
50
|
+
return (
|
|
51
|
+
<SheetPortal>
|
|
52
|
+
<SheetOverlay />
|
|
53
|
+
<SheetPrimitive.Popup
|
|
54
|
+
data-slot="sheet-content"
|
|
55
|
+
data-side={side}
|
|
56
|
+
className={cn(
|
|
57
|
+
'fixed z-50 flex flex-col bg-popover bg-clip-padding text-xs/relaxed text-popover-foreground shadow-lg transition duration-200 ease-in-out data-ending-style:opacity-0 data-starting-style:opacity-0 data-[side=bottom]:inset-x-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=bottom]:border-t data-[side=bottom]:data-ending-style:translate-y-[2.5rem] data-[side=bottom]:data-starting-style:translate-y-[2.5rem] data-[side=left]:inset-y-0 data-[side=left]:left-0 data-[side=left]:h-full data-[side=left]:w-3/4 data-[side=left]:border-r data-[side=left]:data-ending-style:translate-x-[-2.5rem] data-[side=left]:data-starting-style:translate-x-[-2.5rem] data-[side=right]:inset-y-0 data-[side=right]:right-0 data-[side=right]:h-full data-[side=right]:w-3/4 data-[side=right]:border-l data-[side=right]:data-ending-style:translate-x-[2.5rem] data-[side=right]:data-starting-style:translate-x-[2.5rem] data-[side=top]:inset-x-0 data-[side=top]:top-0 data-[side=top]:h-auto data-[side=top]:border-b data-[side=top]:data-ending-style:translate-y-[-2.5rem] data-[side=top]:data-starting-style:translate-y-[-2.5rem] data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm',
|
|
58
|
+
className,
|
|
59
|
+
)}
|
|
60
|
+
{...props}
|
|
61
|
+
>
|
|
62
|
+
{children}
|
|
63
|
+
{showCloseButton && (
|
|
64
|
+
<SheetPrimitive.Close
|
|
65
|
+
data-slot="sheet-close"
|
|
66
|
+
render={<Button variant="ghost" className="absolute top-4 right-4" size="icon-sm" />}
|
|
67
|
+
>
|
|
68
|
+
<HugeiconsIcon icon={Cancel01Icon} strokeWidth={2} />
|
|
69
|
+
<span className="sr-only">Close</span>
|
|
70
|
+
</SheetPrimitive.Close>
|
|
71
|
+
)}
|
|
72
|
+
</SheetPrimitive.Popup>
|
|
73
|
+
</SheetPortal>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function SheetHeader({ className, ...props }: React.ComponentProps<'div'>) {
|
|
78
|
+
return (
|
|
79
|
+
<div
|
|
80
|
+
data-slot="sheet-header"
|
|
81
|
+
className={cn('flex flex-col gap-1.5 p-6', className)}
|
|
82
|
+
{...props}
|
|
83
|
+
/>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function SheetFooter({ className, ...props }: React.ComponentProps<'div'>) {
|
|
88
|
+
return (
|
|
89
|
+
<div
|
|
90
|
+
data-slot="sheet-footer"
|
|
91
|
+
className={cn('mt-auto flex flex-col gap-2 p-6', className)}
|
|
92
|
+
{...props}
|
|
93
|
+
/>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function SheetTitle({ className, ...props }: SheetPrimitive.Title.Props) {
|
|
98
|
+
return (
|
|
99
|
+
<SheetPrimitive.Title
|
|
100
|
+
data-slot="sheet-title"
|
|
101
|
+
className={cn('font-heading text-sm font-medium text-foreground', className)}
|
|
102
|
+
{...props}
|
|
103
|
+
/>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function SheetDescription({ className, ...props }: SheetPrimitive.Description.Props) {
|
|
108
|
+
return (
|
|
109
|
+
<SheetPrimitive.Description
|
|
110
|
+
data-slot="sheet-description"
|
|
111
|
+
className={cn('text-xs/relaxed text-muted-foreground', className)}
|
|
112
|
+
{...props}
|
|
113
|
+
/>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export {
|
|
118
|
+
Sheet,
|
|
119
|
+
SheetTrigger,
|
|
120
|
+
SheetClose,
|
|
121
|
+
SheetContent,
|
|
122
|
+
SheetHeader,
|
|
123
|
+
SheetFooter,
|
|
124
|
+
SheetTitle,
|
|
125
|
+
SheetDescription,
|
|
126
|
+
};
|