@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,20 @@
1
+ 'use client';
2
+
3
+ import type * as React from 'react';
4
+
5
+ import { cn } from '@saena-io/ui/lib/utils';
6
+
7
+ function Label({ className, ...props }: React.ComponentProps<'label'>) {
8
+ return (
9
+ <label
10
+ data-slot="label"
11
+ className={cn(
12
+ 'flex items-center gap-2 text-xs/relaxed leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50',
13
+ className,
14
+ )}
15
+ {...props}
16
+ />
17
+ );
18
+ }
19
+
20
+ export { Label };
@@ -0,0 +1,268 @@
1
+ 'use client';
2
+
3
+ import { Menu as MenuPrimitive } from '@base-ui/react/menu';
4
+ import { Menubar as MenubarPrimitive } from '@base-ui/react/menubar';
5
+ import type * as React from 'react';
6
+
7
+ import { Tick02Icon } from '@hugeicons/core-free-icons';
8
+ import { HugeiconsIcon } from '@hugeicons/react';
9
+ import {
10
+ DropdownMenu,
11
+ DropdownMenuContent,
12
+ DropdownMenuGroup,
13
+ DropdownMenuItem,
14
+ DropdownMenuLabel,
15
+ DropdownMenuPortal,
16
+ DropdownMenuRadioGroup,
17
+ DropdownMenuSeparator,
18
+ DropdownMenuShortcut,
19
+ DropdownMenuSub,
20
+ DropdownMenuSubContent,
21
+ DropdownMenuSubTrigger,
22
+ DropdownMenuTrigger,
23
+ } from '@saena-io/ui/components/dropdown-menu';
24
+ import { cn } from '@saena-io/ui/lib/utils';
25
+
26
+ function Menubar({ className, ...props }: MenubarPrimitive.Props) {
27
+ return (
28
+ <MenubarPrimitive
29
+ data-slot="menubar"
30
+ className={cn('flex h-9 items-center rounded-lg border p-1', className)}
31
+ {...props}
32
+ />
33
+ );
34
+ }
35
+
36
+ function MenubarMenu({ ...props }: React.ComponentProps<typeof DropdownMenu>) {
37
+ return <DropdownMenu data-slot="menubar-menu" {...props} />;
38
+ }
39
+
40
+ function MenubarGroup({ ...props }: React.ComponentProps<typeof DropdownMenuGroup>) {
41
+ return <DropdownMenuGroup data-slot="menubar-group" {...props} />;
42
+ }
43
+
44
+ function MenubarPortal({ ...props }: React.ComponentProps<typeof DropdownMenuPortal>) {
45
+ return <DropdownMenuPortal data-slot="menubar-portal" {...props} />;
46
+ }
47
+
48
+ function MenubarTrigger({ className, ...props }: React.ComponentProps<typeof DropdownMenuTrigger>) {
49
+ return (
50
+ <DropdownMenuTrigger
51
+ data-slot="menubar-trigger"
52
+ className={cn(
53
+ 'flex items-center rounded-[calc(var(--radius-md)-2px)] px-2 py-[calc(--spacing(0.85))] text-xs/relaxed font-medium outline-hidden select-none hover:bg-muted aria-expanded:bg-muted',
54
+ className,
55
+ )}
56
+ {...props}
57
+ />
58
+ );
59
+ }
60
+
61
+ function MenubarContent({
62
+ className,
63
+ align = 'start',
64
+ alignOffset = -4,
65
+ sideOffset = 8,
66
+ ...props
67
+ }: React.ComponentProps<typeof DropdownMenuContent>) {
68
+ return (
69
+ <DropdownMenuContent
70
+ data-slot="menubar-content"
71
+ align={align}
72
+ alignOffset={alignOffset}
73
+ sideOffset={sideOffset}
74
+ className={cn(
75
+ 'min-w-32 rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 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',
76
+ className,
77
+ )}
78
+ {...props}
79
+ />
80
+ );
81
+ }
82
+
83
+ function MenubarItem({
84
+ className,
85
+ inset,
86
+ variant = 'default',
87
+ ...props
88
+ }: React.ComponentProps<typeof DropdownMenuItem>) {
89
+ return (
90
+ <DropdownMenuItem
91
+ data-slot="menubar-item"
92
+ data-inset={inset}
93
+ data-variant={variant}
94
+ className={cn(
95
+ "group/menubar-item min-h-7 gap-2 rounded-md px-2 py-1 text-xs/relaxed focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7.5 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 data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-3.5 data-[variant=destructive]:*:[svg]:text-destructive!",
96
+ className,
97
+ )}
98
+ {...props}
99
+ />
100
+ );
101
+ }
102
+
103
+ function MenubarCheckboxItem({
104
+ className,
105
+ children,
106
+ checked,
107
+ inset,
108
+ ...props
109
+ }: MenuPrimitive.CheckboxItem.Props & {
110
+ inset?: boolean;
111
+ }) {
112
+ return (
113
+ <MenuPrimitive.CheckboxItem
114
+ data-slot="menubar-checkbox-item"
115
+ data-inset={inset}
116
+ className={cn(
117
+ 'relative flex min-h-7 cursor-default items-center gap-2 rounded-md py-1.5 pr-2 pl-7.5 text-xs outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-7.5 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0',
118
+ className,
119
+ )}
120
+ checked={checked}
121
+ {...props}
122
+ >
123
+ <span className="pointer-events-none absolute left-2 flex size-4 items-center justify-center [&_svg:not([class*='size-'])]:size-4">
124
+ <MenuPrimitive.CheckboxItemIndicator>
125
+ <HugeiconsIcon icon={Tick02Icon} strokeWidth={2} />
126
+ </MenuPrimitive.CheckboxItemIndicator>
127
+ </span>
128
+ {children}
129
+ </MenuPrimitive.CheckboxItem>
130
+ );
131
+ }
132
+
133
+ function MenubarRadioGroup({ ...props }: React.ComponentProps<typeof DropdownMenuRadioGroup>) {
134
+ return <DropdownMenuRadioGroup data-slot="menubar-radio-group" {...props} />;
135
+ }
136
+
137
+ function MenubarRadioItem({
138
+ className,
139
+ children,
140
+ inset,
141
+ ...props
142
+ }: MenuPrimitive.RadioItem.Props & {
143
+ inset?: boolean;
144
+ }) {
145
+ return (
146
+ <MenuPrimitive.RadioItem
147
+ data-slot="menubar-radio-item"
148
+ data-inset={inset}
149
+ className={cn(
150
+ "relative flex min-h-7 cursor-default items-center gap-2 rounded-md py-1.5 pr-2 pl-7.5 text-xs outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-7.5 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
151
+ className,
152
+ )}
153
+ {...props}
154
+ >
155
+ <span className="pointer-events-none absolute left-2 flex size-4 items-center justify-center [&_svg:not([class*='size-'])]:size-4">
156
+ <MenuPrimitive.RadioItemIndicator>
157
+ <HugeiconsIcon icon={Tick02Icon} strokeWidth={2} />
158
+ </MenuPrimitive.RadioItemIndicator>
159
+ </span>
160
+ {children}
161
+ </MenuPrimitive.RadioItem>
162
+ );
163
+ }
164
+
165
+ function MenubarLabel({
166
+ className,
167
+ inset,
168
+ ...props
169
+ }: React.ComponentProps<typeof DropdownMenuLabel> & {
170
+ inset?: boolean;
171
+ }) {
172
+ return (
173
+ <DropdownMenuLabel
174
+ data-slot="menubar-label"
175
+ data-inset={inset}
176
+ className={cn('px-2 py-1.5 text-xs text-muted-foreground data-inset:pl-7.5', className)}
177
+ {...props}
178
+ />
179
+ );
180
+ }
181
+
182
+ function MenubarSeparator({
183
+ className,
184
+ ...props
185
+ }: React.ComponentProps<typeof DropdownMenuSeparator>) {
186
+ return (
187
+ <DropdownMenuSeparator
188
+ data-slot="menubar-separator"
189
+ className={cn('-mx-1 my-1 h-px bg-border/50', className)}
190
+ {...props}
191
+ />
192
+ );
193
+ }
194
+
195
+ function MenubarShortcut({
196
+ className,
197
+ ...props
198
+ }: React.ComponentProps<typeof DropdownMenuShortcut>) {
199
+ return (
200
+ <DropdownMenuShortcut
201
+ data-slot="menubar-shortcut"
202
+ className={cn(
203
+ 'ml-auto text-[0.625rem] tracking-widest text-muted-foreground group-focus/menubar-item:text-accent-foreground',
204
+ className,
205
+ )}
206
+ {...props}
207
+ />
208
+ );
209
+ }
210
+
211
+ function MenubarSub({ ...props }: React.ComponentProps<typeof DropdownMenuSub>) {
212
+ return <DropdownMenuSub data-slot="menubar-sub" {...props} />;
213
+ }
214
+
215
+ function MenubarSubTrigger({
216
+ className,
217
+ inset,
218
+ ...props
219
+ }: React.ComponentProps<typeof DropdownMenuSubTrigger> & {
220
+ inset?: boolean;
221
+ }) {
222
+ return (
223
+ <DropdownMenuSubTrigger
224
+ data-slot="menubar-sub-trigger"
225
+ data-inset={inset}
226
+ className={cn(
227
+ "min-h-7 gap-2 rounded-md px-2 py-1 text-xs focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7.5 data-open:bg-accent data-open:text-accent-foreground [&_svg:not([class*='size-'])]:size-3.5",
228
+ className,
229
+ )}
230
+ {...props}
231
+ />
232
+ );
233
+ }
234
+
235
+ function MenubarSubContent({
236
+ className,
237
+ ...props
238
+ }: React.ComponentProps<typeof DropdownMenuSubContent>) {
239
+ return (
240
+ <DropdownMenuSubContent
241
+ data-slot="menubar-sub-content"
242
+ className={cn(
243
+ 'min-w-32 rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95',
244
+ className,
245
+ )}
246
+ {...props}
247
+ />
248
+ );
249
+ }
250
+
251
+ export {
252
+ Menubar,
253
+ MenubarPortal,
254
+ MenubarMenu,
255
+ MenubarTrigger,
256
+ MenubarContent,
257
+ MenubarGroup,
258
+ MenubarSeparator,
259
+ MenubarLabel,
260
+ MenubarItem,
261
+ MenubarShortcut,
262
+ MenubarCheckboxItem,
263
+ MenubarRadioGroup,
264
+ MenubarRadioItem,
265
+ MenubarSub,
266
+ MenubarSubTrigger,
267
+ MenubarSubContent,
268
+ };
@@ -0,0 +1,58 @@
1
+ import type * as React from 'react';
2
+
3
+ import { UnfoldMoreIcon } from '@hugeicons/core-free-icons';
4
+ import { HugeiconsIcon } from '@hugeicons/react';
5
+ import { cn } from '@saena-io/ui/lib/utils';
6
+
7
+ type NativeSelectProps = Omit<React.ComponentProps<'select'>, 'size'> & {
8
+ size?: 'sm' | 'default';
9
+ };
10
+
11
+ function NativeSelect({ className, size = 'default', ...props }: NativeSelectProps) {
12
+ return (
13
+ <div
14
+ className={cn(
15
+ 'group/native-select relative w-fit has-[select:disabled]:opacity-50',
16
+ className,
17
+ )}
18
+ data-slot="native-select-wrapper"
19
+ data-size={size}
20
+ >
21
+ <select
22
+ data-slot="native-select"
23
+ data-size={size}
24
+ className="h-7 w-full min-w-0 appearance-none rounded-md border border-input bg-input/20 py-0.5 pr-6 pl-2 text-xs/relaxed transition-colors outline-none select-none selection:bg-primary selection:text-primary-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/30 disabled:pointer-events-none disabled:cursor-not-allowed aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/20 data-[size=sm]:h-6 data-[size=sm]:text-[0.625rem] dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40"
25
+ {...props}
26
+ />
27
+ <HugeiconsIcon
28
+ icon={UnfoldMoreIcon}
29
+ strokeWidth={2}
30
+ className="pointer-events-none absolute top-1/2 right-1.5 size-3.5 -translate-y-1/2 text-muted-foreground select-none group-data-[size=sm]/native-select:size-3 group-data-[size=sm]/native-select:-translate-y-[calc(--spacing(1.25))]"
31
+ aria-hidden="true"
32
+ data-slot="native-select-icon"
33
+ />
34
+ </div>
35
+ );
36
+ }
37
+
38
+ function NativeSelectOption({ className, ...props }: React.ComponentProps<'option'>) {
39
+ return (
40
+ <option
41
+ data-slot="native-select-option"
42
+ className={cn('bg-[Canvas] text-[CanvasText]', className)}
43
+ {...props}
44
+ />
45
+ );
46
+ }
47
+
48
+ function NativeSelectOptGroup({ className, ...props }: React.ComponentProps<'optgroup'>) {
49
+ return (
50
+ <optgroup
51
+ data-slot="native-select-optgroup"
52
+ className={cn('bg-[Canvas] text-[CanvasText]', className)}
53
+ {...props}
54
+ />
55
+ );
56
+ }
57
+
58
+ export { NativeSelect, NativeSelectOptGroup, NativeSelectOption };
@@ -0,0 +1,70 @@
1
+ import { ArrowRight01Icon } from '@hugeicons/core-free-icons';
2
+ import { HugeiconsIcon } from '@hugeicons/react';
3
+ import {
4
+ Collapsible,
5
+ CollapsibleContent,
6
+ CollapsibleTrigger,
7
+ } from '@saena-io/ui/components/collapsible';
8
+ import {
9
+ SidebarGroup,
10
+ SidebarGroupLabel,
11
+ SidebarMenu,
12
+ SidebarMenuAction,
13
+ SidebarMenuButton,
14
+ SidebarMenuItem,
15
+ SidebarMenuSub,
16
+ SidebarMenuSubButton,
17
+ SidebarMenuSubItem,
18
+ } from '@saena-io/ui/components/sidebar';
19
+
20
+ export function NavMain({
21
+ items,
22
+ }: {
23
+ items: {
24
+ title: string;
25
+ url: string;
26
+ icon: React.ReactNode;
27
+ isActive?: boolean;
28
+ items?: {
29
+ title: string;
30
+ url: string;
31
+ }[];
32
+ }[];
33
+ }) {
34
+ return (
35
+ <SidebarGroup>
36
+ <SidebarGroupLabel>Platform</SidebarGroupLabel>
37
+ <SidebarMenu>
38
+ {items.map((item) => (
39
+ <Collapsible key={item.title} defaultOpen={item.isActive} render={<SidebarMenuItem />}>
40
+ <SidebarMenuButton tooltip={item.title} render={<a href={item.url} />}>
41
+ {item.icon}
42
+ <span>{item.title}</span>
43
+ </SidebarMenuButton>
44
+ {item.items?.length ? (
45
+ <>
46
+ <CollapsibleTrigger
47
+ render={<SidebarMenuAction className="aria-expanded:rotate-90" />}
48
+ >
49
+ <HugeiconsIcon icon={ArrowRight01Icon} strokeWidth={2} />
50
+ <span className="sr-only">Toggle</span>
51
+ </CollapsibleTrigger>
52
+ <CollapsibleContent>
53
+ <SidebarMenuSub>
54
+ {item.items?.map((subItem) => (
55
+ <SidebarMenuSubItem key={subItem.title}>
56
+ <SidebarMenuSubButton render={<a href={subItem.url} />}>
57
+ <span>{subItem.title}</span>
58
+ </SidebarMenuSubButton>
59
+ </SidebarMenuSubItem>
60
+ ))}
61
+ </SidebarMenuSub>
62
+ </CollapsibleContent>
63
+ </>
64
+ ) : null}
65
+ </Collapsible>
66
+ ))}
67
+ </SidebarMenu>
68
+ </SidebarGroup>
69
+ );
70
+ }
@@ -0,0 +1,97 @@
1
+ 'use client';
2
+
3
+ import {
4
+ Delete02Icon,
5
+ FolderIcon,
6
+ MoreHorizontalCircle01Icon,
7
+ Share03Icon,
8
+ } from '@hugeicons/core-free-icons';
9
+ import { HugeiconsIcon } from '@hugeicons/react';
10
+ import {
11
+ DropdownMenu,
12
+ DropdownMenuContent,
13
+ DropdownMenuItem,
14
+ DropdownMenuSeparator,
15
+ DropdownMenuTrigger,
16
+ } from '@saena-io/ui/components/dropdown-menu';
17
+ import {
18
+ SidebarGroup,
19
+ SidebarGroupLabel,
20
+ SidebarMenu,
21
+ SidebarMenuAction,
22
+ SidebarMenuButton,
23
+ SidebarMenuItem,
24
+ useSidebar,
25
+ } from '@saena-io/ui/components/sidebar';
26
+
27
+ export function NavProjects({
28
+ projects,
29
+ }: {
30
+ projects: {
31
+ name: string;
32
+ url: string;
33
+ icon: React.ReactNode;
34
+ }[];
35
+ }) {
36
+ const { isMobile } = useSidebar();
37
+ return (
38
+ <SidebarGroup className="group-data-[collapsible=icon]:hidden">
39
+ <SidebarGroupLabel>Projects</SidebarGroupLabel>
40
+ <SidebarMenu>
41
+ {projects.map((item) => (
42
+ <SidebarMenuItem key={item.name}>
43
+ <SidebarMenuButton render={<a href={item.url} />}>
44
+ {item.icon}
45
+ <span>{item.name}</span>
46
+ </SidebarMenuButton>
47
+ <DropdownMenu>
48
+ <DropdownMenuTrigger
49
+ render={<SidebarMenuAction showOnHover className="aria-expanded:bg-muted" />}
50
+ >
51
+ <HugeiconsIcon icon={MoreHorizontalCircle01Icon} strokeWidth={2} />
52
+ <span className="sr-only">More</span>
53
+ </DropdownMenuTrigger>
54
+ <DropdownMenuContent
55
+ className="w-48"
56
+ side={isMobile ? 'bottom' : 'right'}
57
+ align={isMobile ? 'end' : 'start'}
58
+ >
59
+ <DropdownMenuItem>
60
+ <HugeiconsIcon
61
+ icon={FolderIcon}
62
+ strokeWidth={2}
63
+ className="text-muted-foreground"
64
+ />
65
+ <span>View Project</span>
66
+ </DropdownMenuItem>
67
+ <DropdownMenuItem>
68
+ <HugeiconsIcon
69
+ icon={Share03Icon}
70
+ strokeWidth={2}
71
+ className="text-muted-foreground"
72
+ />
73
+ <span>Share Project</span>
74
+ </DropdownMenuItem>
75
+ <DropdownMenuSeparator />
76
+ <DropdownMenuItem>
77
+ <HugeiconsIcon
78
+ icon={Delete02Icon}
79
+ strokeWidth={2}
80
+ className="text-muted-foreground"
81
+ />
82
+ <span>Delete Project</span>
83
+ </DropdownMenuItem>
84
+ </DropdownMenuContent>
85
+ </DropdownMenu>
86
+ </SidebarMenuItem>
87
+ ))}
88
+ <SidebarMenuItem>
89
+ <SidebarMenuButton>
90
+ <HugeiconsIcon icon={MoreHorizontalCircle01Icon} strokeWidth={2} />
91
+ <span>More</span>
92
+ </SidebarMenuButton>
93
+ </SidebarMenuItem>
94
+ </SidebarMenu>
95
+ </SidebarGroup>
96
+ );
97
+ }
@@ -0,0 +1,37 @@
1
+ import type * as React from 'react';
2
+
3
+ import {
4
+ SidebarGroup,
5
+ SidebarGroupContent,
6
+ SidebarMenu,
7
+ SidebarMenuButton,
8
+ SidebarMenuItem,
9
+ } from '@saena-io/ui/components/sidebar';
10
+
11
+ export function NavSecondary({
12
+ items,
13
+ ...props
14
+ }: {
15
+ items: {
16
+ title: string;
17
+ url: string;
18
+ icon: React.ReactNode;
19
+ }[];
20
+ } & React.ComponentPropsWithoutRef<typeof SidebarGroup>) {
21
+ return (
22
+ <SidebarGroup {...props}>
23
+ <SidebarGroupContent>
24
+ <SidebarMenu>
25
+ {items.map((item) => (
26
+ <SidebarMenuItem key={item.title}>
27
+ <SidebarMenuButton size="sm" render={<a href={item.url} />}>
28
+ {item.icon}
29
+ <span>{item.title}</span>
30
+ </SidebarMenuButton>
31
+ </SidebarMenuItem>
32
+ ))}
33
+ </SidebarMenu>
34
+ </SidebarGroupContent>
35
+ </SidebarGroup>
36
+ );
37
+ }
@@ -0,0 +1,108 @@
1
+ 'use client';
2
+
3
+ import {
4
+ CheckmarkBadgeIcon,
5
+ CreditCardIcon,
6
+ LogoutIcon,
7
+ NotificationIcon,
8
+ SparklesIcon,
9
+ UnfoldMoreIcon,
10
+ } from '@hugeicons/core-free-icons';
11
+ import { HugeiconsIcon } from '@hugeicons/react';
12
+ import { Avatar, AvatarFallback, AvatarImage } from '@saena-io/ui/components/avatar';
13
+ import {
14
+ DropdownMenu,
15
+ DropdownMenuContent,
16
+ DropdownMenuGroup,
17
+ DropdownMenuItem,
18
+ DropdownMenuLabel,
19
+ DropdownMenuSeparator,
20
+ DropdownMenuTrigger,
21
+ } from '@saena-io/ui/components/dropdown-menu';
22
+ import {
23
+ SidebarMenu,
24
+ SidebarMenuButton,
25
+ SidebarMenuItem,
26
+ useSidebar,
27
+ } from '@saena-io/ui/components/sidebar';
28
+
29
+ export function NavUser({
30
+ user,
31
+ }: {
32
+ user: {
33
+ name: string;
34
+ email: string;
35
+ avatar: string;
36
+ };
37
+ }) {
38
+ const { isMobile } = useSidebar();
39
+ return (
40
+ <SidebarMenu>
41
+ <SidebarMenuItem>
42
+ <DropdownMenu>
43
+ <DropdownMenuTrigger
44
+ render={<SidebarMenuButton size="lg" className="aria-expanded:bg-muted" />}
45
+ >
46
+ <Avatar>
47
+ <AvatarImage src={user.avatar} alt={user.name} />
48
+ <AvatarFallback>CN</AvatarFallback>
49
+ </Avatar>
50
+ <div className="grid flex-1 text-left text-sm leading-tight">
51
+ <span className="truncate font-medium">{user.name}</span>
52
+ <span className="truncate text-xs">{user.email}</span>
53
+ </div>
54
+ <HugeiconsIcon icon={UnfoldMoreIcon} strokeWidth={2} className="ml-auto size-4" />
55
+ </DropdownMenuTrigger>
56
+ <DropdownMenuContent
57
+ className="min-w-56 rounded-lg"
58
+ side={isMobile ? 'bottom' : 'right'}
59
+ align="end"
60
+ sideOffset={4}
61
+ >
62
+ <DropdownMenuGroup>
63
+ <DropdownMenuLabel className="p-0 font-normal">
64
+ <div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
65
+ <Avatar>
66
+ <AvatarImage src={user.avatar} alt={user.name} />
67
+ <AvatarFallback>CN</AvatarFallback>
68
+ </Avatar>
69
+ <div className="grid flex-1 text-left text-sm leading-tight">
70
+ <span className="truncate font-medium">{user.name}</span>
71
+ <span className="truncate text-xs">{user.email}</span>
72
+ </div>
73
+ </div>
74
+ </DropdownMenuLabel>
75
+ </DropdownMenuGroup>
76
+ <DropdownMenuSeparator />
77
+ <DropdownMenuGroup>
78
+ <DropdownMenuItem>
79
+ <HugeiconsIcon icon={SparklesIcon} strokeWidth={2} />
80
+ Upgrade to Pro
81
+ </DropdownMenuItem>
82
+ </DropdownMenuGroup>
83
+ <DropdownMenuSeparator />
84
+ <DropdownMenuGroup>
85
+ <DropdownMenuItem>
86
+ <HugeiconsIcon icon={CheckmarkBadgeIcon} strokeWidth={2} />
87
+ Account
88
+ </DropdownMenuItem>
89
+ <DropdownMenuItem>
90
+ <HugeiconsIcon icon={CreditCardIcon} strokeWidth={2} />
91
+ Billing
92
+ </DropdownMenuItem>
93
+ <DropdownMenuItem>
94
+ <HugeiconsIcon icon={NotificationIcon} strokeWidth={2} />
95
+ Notifications
96
+ </DropdownMenuItem>
97
+ </DropdownMenuGroup>
98
+ <DropdownMenuSeparator />
99
+ <DropdownMenuItem>
100
+ <HugeiconsIcon icon={LogoutIcon} strokeWidth={2} />
101
+ Log out
102
+ </DropdownMenuItem>
103
+ </DropdownMenuContent>
104
+ </DropdownMenu>
105
+ </SidebarMenuItem>
106
+ </SidebarMenu>
107
+ );
108
+ }