@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.
Files changed (100) hide show
  1. package/dist/index.js +9 -9
  2. package/package.json +1 -1
  3. package/template/base/package.json +44 -2
  4. package/template/base/scripts/ui-update.ts +83 -0
  5. package/template/base/src/components/ui/accordion.tsx +75 -0
  6. package/template/base/src/components/ui/alert-dialog.tsx +162 -0
  7. package/template/base/src/components/ui/alert.tsx +73 -0
  8. package/template/base/src/components/ui/app-sidebar.tsx +183 -0
  9. package/template/base/src/components/ui/aspect-ratio.tsx +22 -0
  10. package/template/base/src/components/ui/asset-input.tsx +211 -0
  11. package/template/base/src/components/ui/avatar.tsx +91 -0
  12. package/template/base/src/components/ui/badge.tsx +50 -0
  13. package/template/base/src/components/ui/breadcrumb.tsx +104 -0
  14. package/template/base/src/components/ui/button-group.tsx +78 -0
  15. package/template/base/src/components/ui/button.tsx +56 -0
  16. package/template/base/src/components/ui/calendar.tsx +205 -0
  17. package/template/base/src/components/ui/card.tsx +85 -0
  18. package/template/base/src/components/ui/carousel.tsx +232 -0
  19. package/template/base/src/components/ui/chart.tsx +337 -0
  20. package/template/base/src/components/ui/checkbox.tsx +29 -0
  21. package/template/base/src/components/ui/collapsible.tsx +15 -0
  22. package/template/base/src/components/ui/combobox.tsx +276 -0
  23. package/template/base/src/components/ui/command.tsx +190 -0
  24. package/template/base/src/components/ui/context-menu.tsx +243 -0
  25. package/template/base/src/components/ui/dialog.tsx +134 -0
  26. package/template/base/src/components/ui/direction.tsx +4 -0
  27. package/template/base/src/components/ui/drawer.tsx +120 -0
  28. package/template/base/src/components/ui/dropdown-menu.tsx +254 -0
  29. package/template/base/src/components/ui/empty.tsx +94 -0
  30. package/template/base/src/components/ui/field.tsx +222 -0
  31. package/template/base/src/components/ui/focal-point-picker.tsx +175 -0
  32. package/template/base/src/components/ui/hover-card.tsx +46 -0
  33. package/template/base/src/components/ui/input-group.tsx +149 -0
  34. package/template/base/src/components/ui/input-otp.tsx +85 -0
  35. package/template/base/src/components/ui/input.tsx +20 -0
  36. package/template/base/src/components/ui/item.tsx +188 -0
  37. package/template/base/src/components/ui/kbd.tsx +26 -0
  38. package/template/base/src/components/ui/label.tsx +20 -0
  39. package/template/base/src/components/ui/menubar.tsx +268 -0
  40. package/template/base/src/components/ui/native-select.tsx +58 -0
  41. package/template/base/src/components/ui/nav-main.tsx +70 -0
  42. package/template/base/src/components/ui/nav-projects.tsx +97 -0
  43. package/template/base/src/components/ui/nav-secondary.tsx +37 -0
  44. package/template/base/src/components/ui/nav-user.tsx +108 -0
  45. package/template/base/src/components/ui/navigation-menu.tsx +164 -0
  46. package/template/base/src/components/ui/pagination.tsx +123 -0
  47. package/template/base/src/components/ui/popover.tsx +80 -0
  48. package/template/base/src/components/ui/progress.tsx +66 -0
  49. package/template/base/src/components/ui/radio-group.tsx +36 -0
  50. package/template/base/src/components/ui/resizable.tsx +42 -0
  51. package/template/base/src/components/ui/rich-text/ai-chat-editor.tsx +20 -0
  52. package/template/base/src/components/ui/rich-text/ai-command.tsx +90 -0
  53. package/template/base/src/components/ui/rich-text/ai-copilot.tsx +67 -0
  54. package/template/base/src/components/ui/rich-text/ai-menu.tsx +456 -0
  55. package/template/base/src/components/ui/rich-text/ai-node.tsx +42 -0
  56. package/template/base/src/components/ui/rich-text/ai-toolbar-button.tsx +29 -0
  57. package/template/base/src/components/ui/rich-text/block-draggable.tsx +187 -0
  58. package/template/base/src/components/ui/rich-text/block-selection.tsx +17 -0
  59. package/template/base/src/components/ui/rich-text/code-block-node.tsx +204 -0
  60. package/template/base/src/components/ui/rich-text/codec.ts +63 -0
  61. package/template/base/src/components/ui/rich-text/extension.ts +53 -0
  62. package/template/base/src/components/ui/rich-text/ghost-text.tsx +23 -0
  63. package/template/base/src/components/ui/rich-text/import-export-toolbar.tsx +103 -0
  64. package/template/base/src/components/ui/rich-text/link.tsx +18 -0
  65. package/template/base/src/components/ui/rich-text/list-node.tsx +65 -0
  66. package/template/base/src/components/ui/rich-text/nodes.tsx +44 -0
  67. package/template/base/src/components/ui/rich-text/plugins.ts +233 -0
  68. package/template/base/src/components/ui/rich-text/rich-text-editor.tsx +82 -0
  69. package/template/base/src/components/ui/rich-text/static.tsx +117 -0
  70. package/template/base/src/components/ui/rich-text/table-node.tsx +934 -0
  71. package/template/base/src/components/ui/rich-text/table-toolbar.tsx +232 -0
  72. package/template/base/src/components/ui/rich-text/toggle-node.tsx +36 -0
  73. package/template/base/src/components/ui/rich-text/toolbar-slots.ts +41 -0
  74. package/template/base/src/components/ui/rich-text/toolbar.tsx +668 -0
  75. package/template/base/src/components/ui/rich-text/use-ai-chat.ts +35 -0
  76. package/template/base/src/components/ui/rich-text/variable-type.ts +4 -0
  77. package/template/base/src/components/ui/rich-text/variable.tsx +97 -0
  78. package/template/base/src/components/ui/scroll-area.tsx +49 -0
  79. package/template/base/src/components/ui/select.tsx +202 -0
  80. package/template/base/src/components/ui/separator.tsx +19 -0
  81. package/template/base/src/components/ui/sheet.tsx +126 -0
  82. package/template/base/src/components/ui/sidebar.tsx +695 -0
  83. package/template/base/src/components/ui/skeleton.tsx +13 -0
  84. package/template/base/src/components/ui/slider.tsx +52 -0
  85. package/template/base/src/components/ui/sonner.tsx +50 -0
  86. package/template/base/src/components/ui/spinner.tsx +18 -0
  87. package/template/base/src/components/ui/switch.tsx +30 -0
  88. package/template/base/src/components/ui/table.tsx +89 -0
  89. package/template/base/src/components/ui/tabs.tsx +73 -0
  90. package/template/base/src/components/ui/textarea.tsx +18 -0
  91. package/template/base/src/components/ui/toggle-group.tsx +85 -0
  92. package/template/base/src/components/ui/toggle.tsx +45 -0
  93. package/template/base/src/components/ui/toolbar.tsx +451 -0
  94. package/template/base/src/components/ui/tooltip.tsx +52 -0
  95. package/template/base/src/hooks/use-mobile.ts +19 -0
  96. package/template/base/src/lib/utils.ts +6 -0
  97. package/template/base/src/routes/__root.tsx +1 -1
  98. package/template/base/src/server/auth.ts +2 -2
  99. package/template/base/src/styles/globals.css +230 -0
  100. 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
+ };