create-nala 3.0.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 (316) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +58 -0
  3. package/dist/index.js +227 -0
  4. package/package.json +48 -0
  5. package/template/.env.example +45 -0
  6. package/template/_gitignore +33 -0
  7. package/template/components.json +24 -0
  8. package/template/index.html +30 -0
  9. package/template/package.json +51 -0
  10. package/template/public/favicon.svg +15 -0
  11. package/template/public/icons.svg +24 -0
  12. package/template/public/img/avatar.webp +0 -0
  13. package/template/public/screenshot.png +0 -0
  14. package/template/src/App.vue +15 -0
  15. package/template/src/auto-imports.d.ts +691 -0
  16. package/template/src/components/AppLogo.vue +159 -0
  17. package/template/src/components/EmptyState.vue +60 -0
  18. package/template/src/components/PageHeader.vue +60 -0
  19. package/template/src/components/layout/AppNavbar.vue +159 -0
  20. package/template/src/components/layout/AppSidebar.vue +74 -0
  21. package/template/src/components/layout/CommandSearchDialog.vue +120 -0
  22. package/template/src/components/ui/accordion/Accordion.vue +15 -0
  23. package/template/src/components/ui/accordion/AccordionContent.vue +20 -0
  24. package/template/src/components/ui/accordion/AccordionItem.vue +20 -0
  25. package/template/src/components/ui/accordion/AccordionTrigger.vue +34 -0
  26. package/template/src/components/ui/accordion/index.ts +4 -0
  27. package/template/src/components/ui/alert/Alert.vue +21 -0
  28. package/template/src/components/ui/alert/AlertDescription.vue +17 -0
  29. package/template/src/components/ui/alert/AlertTitle.vue +17 -0
  30. package/template/src/components/ui/alert/index.ts +30 -0
  31. package/template/src/components/ui/alert-dialog/AlertDialog.vue +15 -0
  32. package/template/src/components/ui/alert-dialog/AlertDialogAction.vue +18 -0
  33. package/template/src/components/ui/alert-dialog/AlertDialogCancel.vue +25 -0
  34. package/template/src/components/ui/alert-dialog/AlertDialogContent.vue +44 -0
  35. package/template/src/components/ui/alert-dialog/AlertDialogDescription.vue +23 -0
  36. package/template/src/components/ui/alert-dialog/AlertDialogFooter.vue +22 -0
  37. package/template/src/components/ui/alert-dialog/AlertDialogHeader.vue +17 -0
  38. package/template/src/components/ui/alert-dialog/AlertDialogTitle.vue +21 -0
  39. package/template/src/components/ui/alert-dialog/AlertDialogTrigger.vue +12 -0
  40. package/template/src/components/ui/alert-dialog/index.ts +9 -0
  41. package/template/src/components/ui/avatar/Avatar.vue +50 -0
  42. package/template/src/components/ui/avatar/AvatarFallback.vue +21 -0
  43. package/template/src/components/ui/avatar/AvatarGroup.vue +69 -0
  44. package/template/src/components/ui/avatar/AvatarImage.vue +16 -0
  45. package/template/src/components/ui/avatar/index.ts +4 -0
  46. package/template/src/components/ui/badge/Badge.vue +40 -0
  47. package/template/src/components/ui/badge/index.ts +45 -0
  48. package/template/src/components/ui/breadcrumb/Breadcrumb.vue +17 -0
  49. package/template/src/components/ui/breadcrumb/BreadcrumbEllipsis.vue +23 -0
  50. package/template/src/components/ui/breadcrumb/BreadcrumbItem.vue +17 -0
  51. package/template/src/components/ui/breadcrumb/BreadcrumbLink.vue +21 -0
  52. package/template/src/components/ui/breadcrumb/BreadcrumbList.vue +17 -0
  53. package/template/src/components/ui/breadcrumb/BreadcrumbPage.vue +20 -0
  54. package/template/src/components/ui/breadcrumb/BreadcrumbSeparator.vue +22 -0
  55. package/template/src/components/ui/breadcrumb/index.ts +7 -0
  56. package/template/src/components/ui/button/Button.spec.ts +46 -0
  57. package/template/src/components/ui/button/Button.vue +31 -0
  58. package/template/src/components/ui/button/index.ts +41 -0
  59. package/template/src/components/ui/calendar/Calendar.vue +106 -0
  60. package/template/src/components/ui/calendar/index.ts +1 -0
  61. package/template/src/components/ui/card/Card.vue +29 -0
  62. package/template/src/components/ui/card/CardAction.vue +17 -0
  63. package/template/src/components/ui/card/CardContent.vue +17 -0
  64. package/template/src/components/ui/card/CardDescription.vue +17 -0
  65. package/template/src/components/ui/card/CardFooter.vue +17 -0
  66. package/template/src/components/ui/card/CardHeader.vue +29 -0
  67. package/template/src/components/ui/card/CardTitle.vue +17 -0
  68. package/template/src/components/ui/card/index.ts +7 -0
  69. package/template/src/components/ui/chart/AreaChart.vue +143 -0
  70. package/template/src/components/ui/chart/BarChart.vue +142 -0
  71. package/template/src/components/ui/chart/DonutChart.vue +75 -0
  72. package/template/src/components/ui/chart/LineChart.vue +132 -0
  73. package/template/src/components/ui/chart/index.ts +4 -0
  74. package/template/src/components/ui/checkbox/Checkbox.vue +57 -0
  75. package/template/src/components/ui/checkbox/index.ts +1 -0
  76. package/template/src/components/ui/collapsible/Collapsible.vue +15 -0
  77. package/template/src/components/ui/collapsible/CollapsibleContent.vue +18 -0
  78. package/template/src/components/ui/collapsible/CollapsibleTrigger.vue +12 -0
  79. package/template/src/components/ui/collapsible/index.ts +3 -0
  80. package/template/src/components/ui/command/Command.vue +88 -0
  81. package/template/src/components/ui/command/CommandDialog.vue +31 -0
  82. package/template/src/components/ui/command/CommandEmpty.vue +27 -0
  83. package/template/src/components/ui/command/CommandGroup.vue +45 -0
  84. package/template/src/components/ui/command/CommandInput.vue +39 -0
  85. package/template/src/components/ui/command/CommandItem.vue +76 -0
  86. package/template/src/components/ui/command/CommandList.vue +25 -0
  87. package/template/src/components/ui/command/CommandSeparator.vue +21 -0
  88. package/template/src/components/ui/command/CommandShortcut.vue +17 -0
  89. package/template/src/components/ui/command/index.ts +25 -0
  90. package/template/src/components/ui/context-menu/ContextMenu.vue +15 -0
  91. package/template/src/components/ui/context-menu/ContextMenuCheckboxItem.vue +36 -0
  92. package/template/src/components/ui/context-menu/ContextMenuContent.vue +33 -0
  93. package/template/src/components/ui/context-menu/ContextMenuGroup.vue +12 -0
  94. package/template/src/components/ui/context-menu/ContextMenuItem.vue +36 -0
  95. package/template/src/components/ui/context-menu/ContextMenuLabel.vue +25 -0
  96. package/template/src/components/ui/context-menu/ContextMenuRadioGroup.vue +18 -0
  97. package/template/src/components/ui/context-menu/ContextMenuRadioItem.vue +36 -0
  98. package/template/src/components/ui/context-menu/ContextMenuSeparator.vue +18 -0
  99. package/template/src/components/ui/context-menu/ContextMenuShortcut.vue +17 -0
  100. package/template/src/components/ui/context-menu/ContextMenuSub.vue +15 -0
  101. package/template/src/components/ui/context-menu/ContextMenuSubContent.vue +36 -0
  102. package/template/src/components/ui/context-menu/ContextMenuSubTrigger.vue +32 -0
  103. package/template/src/components/ui/context-menu/ContextMenuTrigger.vue +13 -0
  104. package/template/src/components/ui/context-menu/index.ts +14 -0
  105. package/template/src/components/ui/date-picker/DatePicker.vue +233 -0
  106. package/template/src/components/ui/date-picker/index.ts +1 -0
  107. package/template/src/components/ui/dialog/Dialog.vue +19 -0
  108. package/template/src/components/ui/dialog/DialogClose.vue +15 -0
  109. package/template/src/components/ui/dialog/DialogContent.vue +53 -0
  110. package/template/src/components/ui/dialog/DialogDescription.vue +23 -0
  111. package/template/src/components/ui/dialog/DialogFooter.vue +27 -0
  112. package/template/src/components/ui/dialog/DialogHeader.vue +17 -0
  113. package/template/src/components/ui/dialog/DialogOverlay.vue +21 -0
  114. package/template/src/components/ui/dialog/DialogScrollContent.vue +59 -0
  115. package/template/src/components/ui/dialog/DialogTitle.vue +23 -0
  116. package/template/src/components/ui/dialog/DialogTrigger.vue +15 -0
  117. package/template/src/components/ui/dialog/index.ts +10 -0
  118. package/template/src/components/ui/dropdown-menu/DropdownMenu.vue +19 -0
  119. package/template/src/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +39 -0
  120. package/template/src/components/ui/dropdown-menu/DropdownMenuContent.vue +41 -0
  121. package/template/src/components/ui/dropdown-menu/DropdownMenuGroup.vue +15 -0
  122. package/template/src/components/ui/dropdown-menu/DropdownMenuItem.vue +31 -0
  123. package/template/src/components/ui/dropdown-menu/DropdownMenuLabel.vue +23 -0
  124. package/template/src/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue +21 -0
  125. package/template/src/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +40 -0
  126. package/template/src/components/ui/dropdown-menu/DropdownMenuSeparator.vue +23 -0
  127. package/template/src/components/ui/dropdown-menu/DropdownMenuShortcut.vue +17 -0
  128. package/template/src/components/ui/dropdown-menu/DropdownMenuSub.vue +18 -0
  129. package/template/src/components/ui/dropdown-menu/DropdownMenuSubContent.vue +27 -0
  130. package/template/src/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +31 -0
  131. package/template/src/components/ui/dropdown-menu/DropdownMenuTrigger.vue +17 -0
  132. package/template/src/components/ui/dropdown-menu/index.ts +16 -0
  133. package/template/src/components/ui/form/FormControl.vue +17 -0
  134. package/template/src/components/ui/form/FormDescription.vue +21 -0
  135. package/template/src/components/ui/form/FormItem.vue +23 -0
  136. package/template/src/components/ui/form/FormLabel.vue +25 -0
  137. package/template/src/components/ui/form/FormMessage.vue +23 -0
  138. package/template/src/components/ui/form/index.ts +7 -0
  139. package/template/src/components/ui/form/injectionKeys.ts +4 -0
  140. package/template/src/components/ui/form/useFormField.ts +30 -0
  141. package/template/src/components/ui/hover-card/HoverCard.vue +19 -0
  142. package/template/src/components/ui/hover-card/HoverCardContent.vue +43 -0
  143. package/template/src/components/ui/hover-card/HoverCardTrigger.vue +15 -0
  144. package/template/src/components/ui/hover-card/index.ts +3 -0
  145. package/template/src/components/ui/index.ts +47 -0
  146. package/template/src/components/ui/input/Input.vue +33 -0
  147. package/template/src/components/ui/input/InputAddon.vue +29 -0
  148. package/template/src/components/ui/input/InputGroup.vue +17 -0
  149. package/template/src/components/ui/input/InputIcon.vue +29 -0
  150. package/template/src/components/ui/input/index.ts +4 -0
  151. package/template/src/components/ui/kbd/Kbd.vue +30 -0
  152. package/template/src/components/ui/kbd/index.ts +1 -0
  153. package/template/src/components/ui/label/Label.vue +26 -0
  154. package/template/src/components/ui/label/index.ts +1 -0
  155. package/template/src/components/ui/loading-bar/LoadingBar.vue +41 -0
  156. package/template/src/components/ui/loading-bar/index.ts +2 -0
  157. package/template/src/components/ui/pagination/Pagination.vue +26 -0
  158. package/template/src/components/ui/pagination/PaginationContent.vue +22 -0
  159. package/template/src/components/ui/pagination/PaginationEllipsis.vue +25 -0
  160. package/template/src/components/ui/pagination/PaginationFirst.vue +33 -0
  161. package/template/src/components/ui/pagination/PaginationItem.vue +34 -0
  162. package/template/src/components/ui/pagination/PaginationLast.vue +33 -0
  163. package/template/src/components/ui/pagination/PaginationNext.vue +33 -0
  164. package/template/src/components/ui/pagination/PaginationPrevious.vue +33 -0
  165. package/template/src/components/ui/pagination/index.ts +8 -0
  166. package/template/src/components/ui/pin-input/PinInput.vue +28 -0
  167. package/template/src/components/ui/pin-input/PinInputGroup.vue +18 -0
  168. package/template/src/components/ui/pin-input/PinInputInput.vue +24 -0
  169. package/template/src/components/ui/pin-input/PinInputSeparator.vue +21 -0
  170. package/template/src/components/ui/pin-input/index.ts +4 -0
  171. package/template/src/components/ui/popover/Popover.vue +19 -0
  172. package/template/src/components/ui/popover/PopoverAnchor.vue +15 -0
  173. package/template/src/components/ui/popover/PopoverContent.vue +45 -0
  174. package/template/src/components/ui/popover/PopoverTrigger.vue +15 -0
  175. package/template/src/components/ui/popover/index.ts +4 -0
  176. package/template/src/components/ui/progress/Progress.vue +35 -0
  177. package/template/src/components/ui/progress/index.ts +1 -0
  178. package/template/src/components/ui/radio-group/RadioGroup.vue +23 -0
  179. package/template/src/components/ui/radio-group/RadioGroupItem.vue +30 -0
  180. package/template/src/components/ui/radio-group/index.ts +2 -0
  181. package/template/src/components/ui/scroll-area/ScrollArea.vue +40 -0
  182. package/template/src/components/ui/scroll-area/ScrollBar.vue +44 -0
  183. package/template/src/components/ui/scroll-area/index.ts +2 -0
  184. package/template/src/components/ui/select/Select.vue +19 -0
  185. package/template/src/components/ui/select/SelectContent.vue +51 -0
  186. package/template/src/components/ui/select/SelectGroup.vue +15 -0
  187. package/template/src/components/ui/select/SelectItem.vue +44 -0
  188. package/template/src/components/ui/select/SelectItemText.vue +15 -0
  189. package/template/src/components/ui/select/SelectLabel.vue +17 -0
  190. package/template/src/components/ui/select/SelectScrollDownButton.vue +26 -0
  191. package/template/src/components/ui/select/SelectScrollUpButton.vue +26 -0
  192. package/template/src/components/ui/select/SelectSeparator.vue +19 -0
  193. package/template/src/components/ui/select/SelectTrigger.vue +33 -0
  194. package/template/src/components/ui/select/SelectValue.vue +15 -0
  195. package/template/src/components/ui/select/index.ts +11 -0
  196. package/template/src/components/ui/separator/Separator.vue +29 -0
  197. package/template/src/components/ui/separator/index.ts +1 -0
  198. package/template/src/components/ui/sheet/Sheet.vue +19 -0
  199. package/template/src/components/ui/sheet/SheetClose.vue +15 -0
  200. package/template/src/components/ui/sheet/SheetContent.vue +62 -0
  201. package/template/src/components/ui/sheet/SheetDescription.vue +21 -0
  202. package/template/src/components/ui/sheet/SheetFooter.vue +16 -0
  203. package/template/src/components/ui/sheet/SheetHeader.vue +15 -0
  204. package/template/src/components/ui/sheet/SheetOverlay.vue +21 -0
  205. package/template/src/components/ui/sheet/SheetTitle.vue +21 -0
  206. package/template/src/components/ui/sheet/SheetTrigger.vue +15 -0
  207. package/template/src/components/ui/sheet/index.ts +8 -0
  208. package/template/src/components/ui/sidebar/Sidebar.vue +113 -0
  209. package/template/src/components/ui/sidebar/SidebarContent.vue +18 -0
  210. package/template/src/components/ui/sidebar/SidebarControl.vue +53 -0
  211. package/template/src/components/ui/sidebar/SidebarFooter.vue +18 -0
  212. package/template/src/components/ui/sidebar/SidebarGroup.vue +18 -0
  213. package/template/src/components/ui/sidebar/SidebarGroupAction.vue +27 -0
  214. package/template/src/components/ui/sidebar/SidebarGroupContent.vue +18 -0
  215. package/template/src/components/ui/sidebar/SidebarGroupLabel.vue +25 -0
  216. package/template/src/components/ui/sidebar/SidebarHeader.vue +18 -0
  217. package/template/src/components/ui/sidebar/SidebarInput.vue +22 -0
  218. package/template/src/components/ui/sidebar/SidebarInset.vue +21 -0
  219. package/template/src/components/ui/sidebar/SidebarMenu.vue +18 -0
  220. package/template/src/components/ui/sidebar/SidebarMenuAction.vue +35 -0
  221. package/template/src/components/ui/sidebar/SidebarMenuBadge.vue +26 -0
  222. package/template/src/components/ui/sidebar/SidebarMenuButton.vue +48 -0
  223. package/template/src/components/ui/sidebar/SidebarMenuButtonChild.vue +36 -0
  224. package/template/src/components/ui/sidebar/SidebarMenuItem.vue +18 -0
  225. package/template/src/components/ui/sidebar/SidebarMenuSkeleton.vue +35 -0
  226. package/template/src/components/ui/sidebar/SidebarMenuSub.vue +22 -0
  227. package/template/src/components/ui/sidebar/SidebarMenuSubButton.vue +36 -0
  228. package/template/src/components/ui/sidebar/SidebarMenuSubItem.vue +18 -0
  229. package/template/src/components/ui/sidebar/SidebarProvider.vue +136 -0
  230. package/template/src/components/ui/sidebar/SidebarRail.vue +33 -0
  231. package/template/src/components/ui/sidebar/SidebarSeparator.vue +19 -0
  232. package/template/src/components/ui/sidebar/SidebarTrigger.vue +27 -0
  233. package/template/src/components/ui/sidebar/index.ts +61 -0
  234. package/template/src/components/ui/sidebar/utils.ts +27 -0
  235. package/template/src/components/ui/skeleton/Skeleton.vue +17 -0
  236. package/template/src/components/ui/skeleton/index.ts +1 -0
  237. package/template/src/components/ui/slider/Slider.vue +42 -0
  238. package/template/src/components/ui/slider/index.ts +1 -0
  239. package/template/src/components/ui/sonner/Sonner.vue +42 -0
  240. package/template/src/components/ui/sonner/index.ts +2 -0
  241. package/template/src/components/ui/stepper/Stepper.vue +102 -0
  242. package/template/src/components/ui/stepper/index.ts +1 -0
  243. package/template/src/components/ui/switch/Switch.vue +38 -0
  244. package/template/src/components/ui/switch/index.ts +1 -0
  245. package/template/src/components/ui/table/Table.vue +16 -0
  246. package/template/src/components/ui/table/TableBody.vue +17 -0
  247. package/template/src/components/ui/table/TableCaption.vue +17 -0
  248. package/template/src/components/ui/table/TableCell.vue +22 -0
  249. package/template/src/components/ui/table/TableEmpty.vue +34 -0
  250. package/template/src/components/ui/table/TableFooter.vue +17 -0
  251. package/template/src/components/ui/table/TableHead.vue +17 -0
  252. package/template/src/components/ui/table/TableHeader.vue +17 -0
  253. package/template/src/components/ui/table/TableRow.vue +17 -0
  254. package/template/src/components/ui/table/index.ts +9 -0
  255. package/template/src/components/ui/table/utils.ts +10 -0
  256. package/template/src/components/ui/tabs/Tabs.vue +24 -0
  257. package/template/src/components/ui/tabs/TabsContent.vue +21 -0
  258. package/template/src/components/ui/tabs/TabsList.vue +24 -0
  259. package/template/src/components/ui/tabs/TabsTrigger.vue +26 -0
  260. package/template/src/components/ui/tabs/index.ts +4 -0
  261. package/template/src/components/ui/textarea/Textarea.vue +28 -0
  262. package/template/src/components/ui/textarea/index.ts +1 -0
  263. package/template/src/components/ui/timeline/Timeline.vue +73 -0
  264. package/template/src/components/ui/timeline/index.ts +1 -0
  265. package/template/src/components/ui/toggle/Toggle.vue +35 -0
  266. package/template/src/components/ui/toggle/index.ts +28 -0
  267. package/template/src/components/ui/toggle-group/ToggleGroup.vue +41 -0
  268. package/template/src/components/ui/toggle-group/ToggleGroupItem.vue +43 -0
  269. package/template/src/components/ui/toggle-group/index.ts +12 -0
  270. package/template/src/components/ui/tooltip/Tooltip.vue +19 -0
  271. package/template/src/components/ui/tooltip/TooltipContent.vue +34 -0
  272. package/template/src/components/ui/tooltip/TooltipProvider.vue +14 -0
  273. package/template/src/components/ui/tooltip/TooltipTrigger.vue +15 -0
  274. package/template/src/components/ui/tooltip/index.ts +4 -0
  275. package/template/src/components.d.ts +228 -0
  276. package/template/src/composables/useFormatter.ts +105 -0
  277. package/template/src/composables/useThemeConfig.ts +252 -0
  278. package/template/src/layouts/AdminLayout.vue +55 -0
  279. package/template/src/layouts/AuthLayout.vue +42 -0
  280. package/template/src/lib/axios.spec.ts +46 -0
  281. package/template/src/lib/axios.ts +243 -0
  282. package/template/src/lib/formatters.spec.ts +137 -0
  283. package/template/src/lib/formatters.ts +332 -0
  284. package/template/src/lib/loading-bar.ts +103 -0
  285. package/template/src/lib/utils.spec.ts +26 -0
  286. package/template/src/lib/utils.ts +7 -0
  287. package/template/src/lib/validation.spec.ts +78 -0
  288. package/template/src/main.ts +13 -0
  289. package/template/src/router/index.ts +147 -0
  290. package/template/src/services/auth.service.spec.ts +93 -0
  291. package/template/src/services/auth.service.ts +243 -0
  292. package/template/src/services/index.ts +3 -0
  293. package/template/src/services/types.ts +28 -0
  294. package/template/src/services/user.service.spec.ts +91 -0
  295. package/template/src/services/user.service.ts +283 -0
  296. package/template/src/stores/auth.spec.ts +52 -0
  297. package/template/src/stores/auth.ts +130 -0
  298. package/template/src/style.css +587 -0
  299. package/template/src/views/_starter/BlankView.vue +47 -0
  300. package/template/src/views/auth/ConfirmEmailView.vue +209 -0
  301. package/template/src/views/auth/ForgotPasswordView.vue +151 -0
  302. package/template/src/views/auth/LockScreenView.vue +175 -0
  303. package/template/src/views/auth/LoginView.vue +54 -0
  304. package/template/src/views/auth/RegisterView.vue +265 -0
  305. package/template/src/views/auth/ResetPasswordView.vue +260 -0
  306. package/template/src/views/auth/VerifyOtpView.vue +261 -0
  307. package/template/src/views/dashboard/IndexView.vue +159 -0
  308. package/template/src/views/errors/ComingSoonView.vue +241 -0
  309. package/template/src/views/errors/MaintenanceView.vue +252 -0
  310. package/template/src/views/errors/NotFoundView.vue +81 -0
  311. package/template/src/views/errors/ServerErrorView.vue +101 -0
  312. package/template/src/views/errors/UnauthorizedView.vue +64 -0
  313. package/template/tsconfig.app.json +27 -0
  314. package/template/tsconfig.json +19 -0
  315. package/template/tsconfig.node.json +23 -0
  316. package/template/vite.config.ts +46 -0
@@ -0,0 +1,159 @@
1
+ <script setup lang="ts">
2
+ import { computed, useId } from 'vue'
3
+
4
+ interface Props {
5
+ size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | number | string
6
+ class?: string
7
+ withText?: boolean
8
+ withBackground?: boolean
9
+ variant?: 'supabase' | 'monochrome'
10
+ textClass?: string
11
+ }
12
+
13
+ const props = withDefaults(defineProps<Props>(), {
14
+ size: 'md',
15
+ withText: false,
16
+ withBackground: false,
17
+ variant: 'supabase',
18
+ })
19
+
20
+ const gradientId = useId()
21
+
22
+ const sizeClasses = computed(() => {
23
+ if (typeof props.size === 'number') {
24
+ return `h-[${props.size}px] w-[${props.size}px]`
25
+ }
26
+ switch (props.size) {
27
+ case 'xs':
28
+ return 'h-3.5 w-3.5'
29
+ case 'sm':
30
+ return 'h-4 w-4'
31
+ case 'md':
32
+ return 'h-5 w-5'
33
+ case 'lg':
34
+ return 'h-6 w-6'
35
+ case 'xl':
36
+ return 'h-8 w-8'
37
+ default:
38
+ return props.size
39
+ }
40
+ })
41
+
42
+ const logoPath = `M6600 7929 c-127 -13 -224 -35 -287 -64 -30 -15 -74 -33 -97 -41 -22 -8 -58 -26 -79 -40 -21 -14 -49 -29 -64 -35 -42 -16 -173 -113 -242 -178 -35 -34 -77 -71 -95 -84 -17 -12 -58 -47 -91 -78 -178 -167 -310 -288 -348 -318 -48 -38 -153 -132 -347 -309 -74 -68 -156 -143 -181 -166 -74 -67 -94 -114 -94 -221 0 -80 3 -95 27 -137 60 -100 174 -167 304 -176 59 -4 85 0 150 22 44 14 91 34 105 44 55 36 177 140 279 238 58 55 145 133 193 172 48 39 159 136 245 215 246 227 449 391 528 428 57 26 85 33 160 37 106 5 138 -3 221 -57 47 -30 68 -53 102 -110 24 -40 47 -86 52 -103 4 -17 9 -596 10 -1288 0 -975 -2 -1263 -11 -1281 -30 -58 -105 -131 -164 -161 -53 -28 -71 -32 -141 -32 -75 -1 -85 2 -162 41 -57 30 -118 75 -203 150 -66 59 -151 133 -188 163 -38 30 -139 118 -226 195 -87 77 -197 174 -246 215 -48 41 -150 129 -226 195 -76 66 -150 130 -164 141 -14 12 -101 88 -193 170 -93 82 -200 176 -239 209 -98 82 -334 290 -383 336 -22 20 -78 70 -125 110 -47 40 -143 126 -215 190 -233 209 -338 264 -589 309 -111 20 -396 7 -517 -24 -162 -41 -321 -134 -417 -244 -64 -74 -149 -212 -163 -266 -6 -23 -21 -75 -34 -117 -13 -42 -29 -114 -35 -160 -7 -58 -10 -526 -8 -1524 l3 -1440 41 -108 c45 -116 72 -161 149 -245 83 -92 265 -212 375 -247 30 -10 72 -26 92 -36 63 -32 143 -48 297 -60 182 -14 377 6 476 48 33 14 88 36 123 49 35 13 80 35 100 49 20 14 42 25 48 25 19 0 166 108 247 182 44 40 90 80 103 88 20 14 254 225 384 347 30 28 98 89 151 135 234 203 449 402 473 436 38 55 48 90 49 162 0 103 -16 147 -82 211 -80 79 -146 104 -271 104 -166 0 -217 -30 -492 -280 -84 -78 -198 -177 -253 -222 -55 -44 -111 -93 -125 -108 -53 -58 -271 -247 -503 -436 -21 -17 -75 -47 -120 -67 -140 -61 -271 -48 -384 38 -38 28 -57 55 -95 130 l-47 95 -3 1255 c-2 1160 -1 1257 14 1285 42 75 95 129 156 162 57 30 72 33 147 33 76 0 89 -3 152 -36 83 -44 143 -90 287 -218 61 -54 131 -115 156 -135 58 -46 192 -163 300 -261 47 -43 121 -108 165 -146 44 -37 126 -109 182 -159 56 -49 123 -109 150 -131 113 -97 323 -282 384 -339 36 -34 97 -86 135 -117 38 -30 110 -92 159 -138 160 -146 191 -174 290 -255 54 -44 155 -130 225 -192 70 -62 156 -132 191 -156 69 -46 212 -109 294 -127 144 -33 462 -28 605 9 216 55 395 186 514 375 57 92 122 285 137 411 14 121 12 2900 -2 2967 -20 91 -91 239 -153 317 -92 116 -227 210 -391 276 -44 17 -94 39 -111 48 -47 24 -106 35 -280 52 -87 8 -158 14 -158 14 -1 -1 -39 -6 -86 -10z`
43
+ </script>
44
+
45
+ <template>
46
+ <div v-if="withText" class="inline-flex items-center gap-2.5">
47
+ <!-- SVG with squircle background -->
48
+ <svg
49
+ v-if="withBackground"
50
+ xmlns="http://www.w3.org/2000/svg"
51
+ viewBox="0 0 1000 1000"
52
+ :class="[sizeClasses, props.class]"
53
+ aria-hidden="true"
54
+ >
55
+ <defs v-if="variant === 'supabase'">
56
+ <linearGradient :id="`grad-${gradientId}`" x1="0%" y1="0%" x2="100%" y2="100%">
57
+ <stop offset="0%" class="logo-stop-start" stop-color="#10B981" />
58
+ <stop offset="50%" class="logo-stop-mid" stop-color="#059669" />
59
+ <stop offset="100%" class="logo-stop-end" stop-color="#047857" />
60
+ </linearGradient>
61
+ </defs>
62
+ <rect x="0" y="0" width="1000" height="1000" rx="225" fill="#0A0A0E" />
63
+ <g transform="translate(500,500) scale(1.22) translate(-509.75,-495.8)">
64
+ <g transform="translate(0,1000) scale(0.1,-0.1)" :fill="variant === 'supabase' ? `url(#grad-${gradientId})` : '#ffffff'" stroke="none">
65
+ <path :d="logoPath" />
66
+ </g>
67
+ </g>
68
+ </svg>
69
+ <!-- Tight viewBox for clean icon usage without excessive transparent margin -->
70
+ <svg
71
+ v-else
72
+ xmlns="http://www.w3.org/2000/svg"
73
+ viewBox="200 185 620 620"
74
+ :class="[sizeClasses, props.class]"
75
+ aria-hidden="true"
76
+ >
77
+ <defs v-if="variant === 'supabase'">
78
+ <linearGradient :id="`grad-${gradientId}`" x1="0%" y1="0%" x2="100%" y2="100%">
79
+ <stop offset="0%" class="logo-stop-start" stop-color="#10B981" />
80
+ <stop offset="50%" class="logo-stop-mid" stop-color="#059669" />
81
+ <stop offset="100%" class="logo-stop-end" stop-color="#047857" />
82
+ </linearGradient>
83
+ </defs>
84
+ <g transform="translate(0,1000) scale(0.1,-0.1)" :fill="variant === 'supabase' ? `url(#grad-${gradientId})` : 'currentColor'">
85
+ <path :d="logoPath" />
86
+ </g>
87
+ </svg>
88
+ <span :class="['font-bold tracking-tight text-foreground', textClass || 'text-base font-semibold']">
89
+ Nala
90
+ </span>
91
+ </div>
92
+
93
+ <!-- Standalone SVG with squircle background -->
94
+ <svg
95
+ v-else-if="withBackground"
96
+ xmlns="http://www.w3.org/2000/svg"
97
+ viewBox="0 0 1000 1000"
98
+ :class="[sizeClasses, props.class]"
99
+ aria-hidden="true"
100
+ >
101
+ <defs v-if="variant === 'supabase'">
102
+ <linearGradient :id="`grad-${gradientId}`" x1="0%" y1="0%" x2="100%" y2="100%">
103
+ <stop offset="0%" class="logo-stop-start" stop-color="#10B981" />
104
+ <stop offset="50%" class="logo-stop-mid" stop-color="#059669" />
105
+ <stop offset="100%" class="logo-stop-end" stop-color="#047857" />
106
+ </linearGradient>
107
+ </defs>
108
+ <rect x="0" y="0" width="1000" height="1000" rx="225" fill="#0A0A0E" />
109
+ <g transform="translate(500,500) scale(1.22) translate(-509.75,-495.8)">
110
+ <g transform="translate(0,1000) scale(0.1,-0.1)" :fill="variant === 'supabase' ? `url(#grad-${gradientId})` : '#ffffff'" stroke="none">
111
+ <path :d="logoPath" />
112
+ </g>
113
+ </g>
114
+ </svg>
115
+
116
+ <!-- Standalone tight viewBox icon (Razor-sharp vector without CSS filter bleed) -->
117
+ <svg
118
+ v-else
119
+ xmlns="http://www.w3.org/2000/svg"
120
+ viewBox="200 185 620 620"
121
+ :class="[sizeClasses, props.class]"
122
+ aria-hidden="true"
123
+ >
124
+ <defs v-if="variant === 'supabase'">
125
+ <linearGradient :id="`grad-${gradientId}`" x1="0%" y1="0%" x2="100%" y2="100%">
126
+ <stop offset="0%" class="logo-stop-start" stop-color="#10B981" />
127
+ <stop offset="50%" class="logo-stop-mid" stop-color="#059669" />
128
+ <stop offset="100%" class="logo-stop-end" stop-color="#047857" />
129
+ </linearGradient>
130
+ </defs>
131
+ <g transform="translate(0,1000) scale(0.1,-0.1)" :fill="variant === 'supabase' ? `url(#grad-${gradientId})` : 'currentColor'">
132
+ <path :d="logoPath" />
133
+ </g>
134
+ </svg>
135
+ </template>
136
+
137
+ <style scoped>
138
+ /* Light Mode: High-contrast, rich & bold emerald (never washed out on white) */
139
+ .logo-stop-start {
140
+ stop-color: #10B981;
141
+ }
142
+ .logo-stop-mid {
143
+ stop-color: #059669;
144
+ }
145
+ .logo-stop-end {
146
+ stop-color: #047857;
147
+ }
148
+
149
+ /* Dark Mode: Vibrant, glowing mint with luminous highlight on dark background */
150
+ :global(.dark) .logo-stop-start {
151
+ stop-color: #53F5AC;
152
+ }
153
+ :global(.dark) .logo-stop-mid {
154
+ stop-color: #3ECF8E;
155
+ }
156
+ :global(.dark) .logo-stop-end {
157
+ stop-color: #24B47E;
158
+ }
159
+ </style>
@@ -0,0 +1,60 @@
1
+ <script setup lang="ts">
2
+ import type { Component } from 'vue'
3
+ import { Inbox } from '@lucide/vue'
4
+ import { Badge } from '@/components/ui/badge'
5
+
6
+ interface Props {
7
+ /** Lucide icon component or custom icon */
8
+ icon?: Component
9
+ /** Main heading */
10
+ title: string
11
+ /** Descriptive explanation or guidance */
12
+ description?: string
13
+ /** Optional badge text */
14
+ badge?: string
15
+ /** Compact padding variant for in-card or widget usage */
16
+ compact?: boolean
17
+ }
18
+
19
+ withDefaults(defineProps<Props>(), {
20
+ icon: Inbox,
21
+ description: '',
22
+ badge: '',
23
+ compact: false,
24
+ })
25
+ </script>
26
+
27
+ <template>
28
+ <div
29
+ :class="[
30
+ 'flex flex-col items-center justify-center text-center rounded-xl border border-dashed border-border/80 bg-muted/10',
31
+ compact ? 'py-8 px-4' : 'py-14 px-6'
32
+ ]"
33
+ role="status"
34
+ >
35
+ <!-- Icon Container -->
36
+ <div class="mb-4 flex h-12 w-12 items-center justify-center rounded-2xl bg-muted/60 border border-border/60 text-muted-foreground shadow-2xs">
37
+ <component :is="icon" class="h-6 w-6 stroke-[1.75]" />
38
+ </div>
39
+
40
+ <!-- Optional Badge -->
41
+ <div v-if="badge" class="mb-2">
42
+ <Badge variant="outline" class="text-[11px] font-mono px-2 py-0.5">
43
+ {{ badge }}
44
+ </Badge>
45
+ </div>
46
+
47
+ <!-- Title & Description -->
48
+ <h3 class="text-sm font-semibold text-foreground tracking-tight">
49
+ {{ title }}
50
+ </h3>
51
+ <p v-if="description" class="mt-1.5 max-w-sm text-xs text-muted-foreground leading-relaxed">
52
+ {{ description }}
53
+ </p>
54
+
55
+ <!-- Action Slot (Buttons, Recovery links, etc.) -->
56
+ <div v-if="$slots.actions" class="mt-5 flex flex-wrap items-center justify-center gap-2.5">
57
+ <slot name="actions" />
58
+ </div>
59
+ </div>
60
+ </template>
@@ -0,0 +1,60 @@
1
+ <script setup lang="ts">
2
+ import type { HTMLAttributes } from 'vue'
3
+ import { cn } from '@/lib/utils'
4
+
5
+ interface PageHeaderProps {
6
+ title?: string
7
+ description?: string
8
+ badge?: string
9
+ statusDot?: boolean
10
+ headingTag?: 'h1' | 'h2'
11
+ class?: HTMLAttributes['class']
12
+ }
13
+
14
+ const props = withDefaults(defineProps<PageHeaderProps>(), {
15
+ title: '',
16
+ description: '',
17
+ badge: '',
18
+ statusDot: true,
19
+ headingTag: 'h1',
20
+ })
21
+ </script>
22
+
23
+ <template>
24
+ <div
25
+ data-slot="page-header"
26
+ :class="cn('flex flex-col gap-1 sm:flex-row sm:items-center sm:justify-between', props.class)"
27
+ >
28
+ <div>
29
+ <div v-if="props.badge || $slots.badge" class="flex items-center gap-2">
30
+ <slot name="badge">
31
+ <span class="label-mono">{{ props.badge }}</span>
32
+ </slot>
33
+ <span v-if="props.statusDot" class="status-dot" />
34
+ </div>
35
+
36
+ <component
37
+ :is="props.headingTag"
38
+ class="text-2xl font-bold tracking-tight text-foreground mt-1"
39
+ >
40
+ <slot name="title">{{ props.title }}</slot>
41
+ </component>
42
+
43
+ <p
44
+ v-if="props.description || $slots.description"
45
+ class="text-sm text-muted-foreground"
46
+ >
47
+ <slot name="description">{{ props.description }}</slot>
48
+ </p>
49
+ </div>
50
+
51
+ <div
52
+ v-if="$slots.actions || $slots.default"
53
+ class="flex items-center gap-2 pt-2 sm:pt-0 shrink-0"
54
+ >
55
+ <slot name="actions">
56
+ <slot />
57
+ </slot>
58
+ </div>
59
+ </div>
60
+ </template>
@@ -0,0 +1,159 @@
1
+ <script setup lang="ts">
2
+ import {
3
+ Search,
4
+ ChevronDown,
5
+ LogOut,
6
+ Lock,
7
+ } from '@lucide/vue'
8
+ import {
9
+ Breadcrumb,
10
+ BreadcrumbItem,
11
+ BreadcrumbLink,
12
+ BreadcrumbList,
13
+ BreadcrumbPage,
14
+ BreadcrumbSeparator,
15
+ } from '@/components/ui/breadcrumb'
16
+ import {
17
+ DropdownMenu,
18
+ DropdownMenuContent,
19
+ DropdownMenuItem,
20
+ DropdownMenuLabel,
21
+ DropdownMenuRadioGroup,
22
+ DropdownMenuRadioItem,
23
+ DropdownMenuSeparator,
24
+ DropdownMenuTrigger,
25
+ } from '@/components/ui/dropdown-menu'
26
+ import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
27
+ import { Separator } from '@/components/ui/separator'
28
+ import { SidebarTrigger } from '@/components/ui/sidebar'
29
+ import AppLogo from '@/components/AppLogo.vue'
30
+ import { useAuthStore } from '@/stores/auth'
31
+
32
+ const emit = defineEmits<{
33
+ (e: 'open-search'): void
34
+ }>()
35
+
36
+ const route = useRoute()
37
+ const router = useRouter()
38
+ const authStore = useAuthStore()
39
+
40
+ const mode = useColorMode({
41
+ selector: 'html',
42
+ attribute: 'class',
43
+ emitAuto: true,
44
+ modes: {
45
+ auto: '',
46
+ dark: 'dark',
47
+ light: '',
48
+ },
49
+ })
50
+
51
+ const pageTitle = computed(() => {
52
+ if (route.name === 'dashboard') return 'Dashboard'
53
+ if (route.name === 'starter-blank') return 'Blank Page'
54
+ if (route.name === 'not-found' || route.name === 'catch-all-not-found') return '404 Not Found'
55
+ if (route.name === 'server-error') return '500 Server Error'
56
+ if (route.name === 'unauthorized') return '403 Access Denied'
57
+ if (route.name === 'login') return 'Login'
58
+ if (route.name === 'register') return 'Register'
59
+ if (route.name === 'forgot-password') return 'Forgot Password'
60
+ if (route.name === 'verify-otp') return 'Verify OTP'
61
+ if (route.name === 'reset-password') return 'Reset Password'
62
+ if (route.name === 'confirm-email') return 'Confirm Email'
63
+ if (route.name === 'lock-screen') return 'Lock Screen'
64
+ return 'Admin'
65
+ })
66
+
67
+ const handleLogout = () => {
68
+ authStore.logout()
69
+ router.push('/auth/login')
70
+ }
71
+ </script>
72
+
73
+ <template>
74
+ <header class="fixed inset-x-0 top-0 z-40 flex h-14 w-full shrink-0 items-center justify-between border-b border-border bg-background/80 pr-4 sm:pr-6 backdrop-blur-md">
75
+ <!-- Left: App Logo, Sidebar Toggle & Breadcrumb -->
76
+ <div class="flex items-center gap-1 sm:gap-2 min-w-0">
77
+ <!-- Logo container matched to collapsed sidebar column width -->
78
+ <div class="flex h-14 w-14 shrink-0 items-center justify-center">
79
+ <router-link to="/" class="flex items-center justify-center text-primary hover:opacity-85 transition-opacity" aria-label="Nala Home">
80
+ <AppLogo class="h-6 w-6" />
81
+ </router-link>
82
+ </div>
83
+
84
+ <SidebarTrigger class="md:hidden" />
85
+ <Separator orientation="vertical" class="h-4 hidden sm:block" />
86
+
87
+ <Breadcrumb class="hidden sm:block">
88
+ <BreadcrumbList>
89
+ <BreadcrumbItem>
90
+ <BreadcrumbLink to="/">Admin</BreadcrumbLink>
91
+ </BreadcrumbItem>
92
+ <BreadcrumbSeparator />
93
+ <BreadcrumbItem>
94
+ <BreadcrumbPage>{{ pageTitle }}</BreadcrumbPage>
95
+ </BreadcrumbItem>
96
+ </BreadcrumbList>
97
+ </Breadcrumb>
98
+ </div>
99
+
100
+ <!-- Right: Search Command Trigger & User Profile Dropdown -->
101
+ <div class="flex items-center gap-2 sm:gap-3 shrink-0">
102
+ <!-- Search Command Trigger Button -->
103
+ <button
104
+ @click="emit('open-search')"
105
+ class="flex items-center gap-2 rounded-full border border-input bg-muted/40 px-2.5 py-1.5 text-xs text-muted-foreground transition-colors hover:bg-accent hover:text-foreground cursor-pointer focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring sm:px-3.5"
106
+ >
107
+ <Search class="h-3.5 w-3.5 shrink-0" />
108
+ <span class="hidden sm:inline">Search...</span>
109
+ <span class="hidden sm:inline pointer-events-none ml-1 text-xs font-normal text-muted-foreground">
110
+ Ctrl K
111
+ </span>
112
+ </button>
113
+
114
+ <!-- User Profile & Theme Dropdown Menu -->
115
+ <DropdownMenu>
116
+ <DropdownMenuTrigger as-child>
117
+ <button class="flex items-center gap-2 rounded-full p-1 hover:bg-accent transition-colors cursor-pointer">
118
+ <Avatar class="h-8 w-8 border">
119
+ <AvatarImage :src="authStore.user?.avatar || '/img/avatar.webp'" alt="User profile" />
120
+ <AvatarFallback>AU</AvatarFallback>
121
+ </Avatar>
122
+ <ChevronDown class="h-4 w-4 text-muted-foreground" />
123
+ </button>
124
+ </DropdownMenuTrigger>
125
+ <DropdownMenuContent align="end" class="w-56">
126
+ <DropdownMenuLabel class="font-normal">
127
+ <div class="flex flex-col space-y-1">
128
+ <p class="text-sm font-medium leading-none">{{ authStore.user?.name || 'Administrator' }}</p>
129
+ <p class="text-xs leading-none text-muted-foreground">{{ authStore.user?.email || 'admin@nala.design' }}</p>
130
+ </div>
131
+ </DropdownMenuLabel>
132
+ <DropdownMenuSeparator />
133
+ <DropdownMenuItem @click="$router.push('/auth/lock-screen')">
134
+ <Lock class="mr-2 h-4 w-4" />
135
+ <span>Lock Screen</span>
136
+ </DropdownMenuItem>
137
+ <DropdownMenuSeparator />
138
+ <DropdownMenuLabel class="text-xs text-muted-foreground font-normal px-2 py-1.5">Theme</DropdownMenuLabel>
139
+ <DropdownMenuRadioGroup v-model="mode">
140
+ <DropdownMenuRadioItem value="auto">
141
+ System
142
+ </DropdownMenuRadioItem>
143
+ <DropdownMenuRadioItem value="dark">
144
+ Dark
145
+ </DropdownMenuRadioItem>
146
+ <DropdownMenuRadioItem value="light">
147
+ Light
148
+ </DropdownMenuRadioItem>
149
+ </DropdownMenuRadioGroup>
150
+ <DropdownMenuSeparator />
151
+ <DropdownMenuItem @click="handleLogout">
152
+ <LogOut class="mr-2 h-4 w-4" />
153
+ <span>Log out</span>
154
+ </DropdownMenuItem>
155
+ </DropdownMenuContent>
156
+ </DropdownMenu>
157
+ </div>
158
+ </header>
159
+ </template>
@@ -0,0 +1,74 @@
1
+ <script setup lang="ts">
2
+ import {
3
+ LayoutDashboard,
4
+ FileCode2,
5
+ } from '@lucide/vue'
6
+ import {
7
+ Sidebar,
8
+ SidebarContent,
9
+ SidebarFooter,
10
+ SidebarGroup,
11
+ SidebarGroupLabel,
12
+ SidebarMenu,
13
+ SidebarMenuButton,
14
+ SidebarMenuItem,
15
+ SidebarControl,
16
+ } from '@/components/ui/sidebar'
17
+
18
+ interface NavItem {
19
+ name: string
20
+ routeName: string
21
+ href: string
22
+ icon: any
23
+ }
24
+
25
+ const route = useRoute()
26
+
27
+ // Auto-close sidebar on mobile when navigating
28
+ const closeMobileSidebar = () => {
29
+ const triggerBtn = document.querySelector('[data-sidebar="trigger"]') as HTMLButtonElement
30
+ if (window.innerWidth <= 768 && triggerBtn) {
31
+ triggerBtn.click()
32
+ }
33
+ }
34
+
35
+ // 1. Navigation Items
36
+ const mainNav: NavItem[] = [
37
+ { name: 'Dashboard', routeName: 'dashboard', href: '/', icon: LayoutDashboard },
38
+ { name: 'Blank Page', routeName: 'starter-blank', href: '/_blank', icon: FileCode2 },
39
+ ]
40
+ </script>
41
+
42
+ <template>
43
+ <Sidebar collapsible="icon" class="top-14 h-[calc(100svh-3.5rem)]">
44
+ <SidebarContent class="py-2 space-y-0">
45
+ <!-- Main Navigation Group -->
46
+ <SidebarGroup class="py-1 px-2">
47
+ <SidebarGroupLabel class="text-[11px] font-medium tracking-wider uppercase text-muted-foreground/70">
48
+ Navigation
49
+ </SidebarGroupLabel>
50
+ <SidebarMenu class="gap-0.5">
51
+ <SidebarMenuItem v-for="item in mainNav" :key="item.name">
52
+ <SidebarMenuButton
53
+ as-child
54
+ :is-active="route.name === item.routeName"
55
+ :tooltip="item.name"
56
+ >
57
+ <router-link :to="item.href" @click="closeMobileSidebar">
58
+ <component :is="item.icon" class="h-4 w-4 shrink-0" />
59
+ <span>{{ item.name }}</span>
60
+ </router-link>
61
+ </SidebarMenuButton>
62
+ </SidebarMenuItem>
63
+ </SidebarMenu>
64
+ </SidebarGroup>
65
+ </SidebarContent>
66
+
67
+ <!-- Sidebar Footer -->
68
+ <SidebarFooter class="border-t border-sidebar-border p-2">
69
+ <div class="flex items-center justify-between w-full">
70
+ <SidebarControl />
71
+ </div>
72
+ </SidebarFooter>
73
+ </Sidebar>
74
+ </template>
@@ -0,0 +1,120 @@
1
+ <script setup lang="ts">
2
+ import {
3
+ LayoutDashboard,
4
+ FileCode2,
5
+ FileQuestion,
6
+ ServerCrash,
7
+ ShieldAlert,
8
+ LogIn,
9
+ UserRound,
10
+ KeyRound,
11
+ ScanLine,
12
+ LockKeyhole,
13
+ MailCheck,
14
+ Search,
15
+ Lock,
16
+ Wrench,
17
+ Rocket,
18
+ } from '@lucide/vue'
19
+ import {
20
+ Dialog,
21
+ DialogContent,
22
+ DialogDescription,
23
+ DialogHeader,
24
+ DialogTitle,
25
+ } from '@/components/ui/dialog'
26
+
27
+ interface SearchItem {
28
+ title: string
29
+ href: string
30
+ icon: any
31
+ category: string
32
+ }
33
+
34
+ const props = defineProps<{
35
+ open: boolean
36
+ }>()
37
+
38
+ const emit = defineEmits<{
39
+ (e: 'update:open', value: boolean): void
40
+ }>()
41
+
42
+ const router = useRouter()
43
+ const searchQuery = ref('')
44
+
45
+ const searchItems: SearchItem[] = [
46
+ { title: 'Dashboard Overview', href: '/', icon: LayoutDashboard, category: 'Overview' },
47
+ { title: 'Blank Starter Page', href: '/_blank', icon: FileCode2, category: 'Pages' },
48
+ { title: 'Login', href: '/auth/login', icon: LogIn, category: 'Auth' },
49
+ { title: 'Register', href: '/auth/register', icon: UserRound, category: 'Auth' },
50
+ { title: 'Forgot Password', href: '/auth/forgot-password', icon: KeyRound, category: 'Auth' },
51
+ { title: 'Verify OTP', href: '/auth/verify-otp', icon: ScanLine, category: 'Auth' },
52
+ { title: 'Reset Password', href: '/auth/reset-password', icon: LockKeyhole, category: 'Auth' },
53
+ { title: 'Confirm Email', href: '/auth/confirm-email', icon: MailCheck, category: 'Auth' },
54
+ { title: 'Lock Screen', href: '/auth/lock-screen', icon: Lock, category: 'Auth' },
55
+ { title: '404 Not Found Page', href: '/errors/404', icon: FileQuestion, category: 'Errors' },
56
+ { title: '500 Server Error Page', href: '/errors/500', icon: ServerCrash, category: 'Errors' },
57
+ { title: '403 Access Denied', href: '/errors/403', icon: ShieldAlert, category: 'Errors' },
58
+ { title: 'Maintenance Mode Page', href: '/errors/maintenance', icon: Wrench, category: 'Errors' },
59
+ { title: 'Coming Soon Page', href: '/errors/coming-soon', icon: Rocket, category: 'Errors' },
60
+ ]
61
+
62
+ const filteredSearchItems = computed(() => {
63
+ if (!searchQuery.value) return searchItems
64
+ return searchItems.filter((item) =>
65
+ item.title.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
66
+ item.category.toLowerCase().includes(searchQuery.value.toLowerCase())
67
+ )
68
+ })
69
+
70
+ const navigateTo = (href: string) => {
71
+ emit('update:open', false)
72
+ searchQuery.value = ''
73
+ if (href.startsWith('http')) {
74
+ window.open(href, '_blank', 'noopener,noreferrer')
75
+ } else {
76
+ router.push(href)
77
+ }
78
+ }
79
+ </script>
80
+
81
+ <template>
82
+ <Dialog :open="open" @update:open="emit('update:open', $event)">
83
+ <DialogContent class="p-0 w-[calc(100%-2rem)] sm:w-full max-w-lg overflow-hidden gap-0 top-16 sm:top-20 translate-y-0">
84
+ <DialogHeader class="sr-only">
85
+ <DialogTitle>Quick Search</DialogTitle>
86
+ <DialogDescription>Search pages and commands in Nala admin</DialogDescription>
87
+ </DialogHeader>
88
+ <div class="flex items-center border-b border-border px-3">
89
+ <Search class="mr-2 h-4 w-4 shrink-0 opacity-50" />
90
+ <input
91
+ v-model="searchQuery"
92
+ placeholder="Type a command or search page..."
93
+ class="flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50"
94
+ />
95
+ </div>
96
+ <div class="max-h-75 overflow-y-auto p-2">
97
+ <div v-if="filteredSearchItems.length === 0" class="py-6 text-center text-sm text-muted-foreground">
98
+ No results found for "{{ searchQuery }}".
99
+ </div>
100
+ <div v-else class="space-y-1">
101
+ <div class="px-2 py-1.5 text-[11px] font-medium text-muted-foreground">
102
+ Navigation & Pages
103
+ </div>
104
+ <button
105
+ v-for="item in filteredSearchItems"
106
+ :key="item.href"
107
+ @click="navigateTo(item.href)"
108
+ class="w-full flex items-center justify-between rounded-md px-2 py-2 text-sm text-foreground hover:bg-accent hover:text-accent-foreground cursor-pointer transition-colors"
109
+ >
110
+ <div class="flex items-center gap-2">
111
+ <component :is="item.icon" class="h-4 w-4 text-muted-foreground" />
112
+ <span>{{ item.title }}</span>
113
+ </div>
114
+ <span class="text-[11px] text-muted-foreground">{{ item.category }}</span>
115
+ </button>
116
+ </div>
117
+ </div>
118
+ </DialogContent>
119
+ </Dialog>
120
+ </template>
@@ -0,0 +1,15 @@
1
+ <script setup lang="ts">
2
+ import type { AccordionRootEmits, AccordionRootProps } from "reka-ui"
3
+ import { AccordionRoot, useForwardPropsEmits } from "reka-ui"
4
+
5
+ const props = defineProps<AccordionRootProps>()
6
+ const emits = defineEmits<AccordionRootEmits>()
7
+
8
+ const forwarded = useForwardPropsEmits(props, emits)
9
+ </script>
10
+
11
+ <template>
12
+ <AccordionRoot v-bind="forwarded" data-slot="accordion">
13
+ <slot />
14
+ </AccordionRoot>
15
+ </template>
@@ -0,0 +1,20 @@
1
+ <script setup lang="ts">
2
+ import type { AccordionContentProps } from "reka-ui"
3
+ import type { HTMLAttributes } from "vue"
4
+ import { cn } from "@/lib/utils"
5
+ import { AccordionContent } from "reka-ui"
6
+
7
+ const props = defineProps<AccordionContentProps & { class?: HTMLAttributes["class"] }>()
8
+ </script>
9
+
10
+ <template>
11
+ <AccordionContent
12
+ v-bind="props"
13
+ data-slot="accordion-content"
14
+ class="overflow-hidden text-xs data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
15
+ >
16
+ <div :class="cn('pb-4 pt-0 text-muted-foreground leading-relaxed', props.class)">
17
+ <slot />
18
+ </div>
19
+ </AccordionContent>
20
+ </template>