kaze 1.3.1 → 2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +72 -1
- data/README.md +1 -1
- data/lib/kaze/commands/app/hotwire_stack.rb +2 -0
- data/lib/kaze/commands/app/inertia_react_stack.rb +5 -1
- data/lib/kaze/commands/app/inertia_vue_stack.rb +5 -1
- data/lib/kaze/commands/app.rb +9 -2
- data/lib/kaze/version.rb +1 -1
- data/stubs/default/config/routes.rb +11 -4
- data/stubs/default/test/integration/appearance_test.rb +11 -0
- data/stubs/default/test/integration/password_update_test.rb +9 -1
- data/stubs/hotwire/app/assets/tailwind/application.css +123 -0
- data/stubs/hotwire/app/components/breadcrumbs_component.rb +32 -0
- data/stubs/hotwire/app/components/danger_button_component.rb +1 -1
- data/stubs/hotwire/app/components/dropdown_component.html.erb +2 -2
- data/stubs/hotwire/app/components/dropdown_component.rb +5 -1
- data/stubs/hotwire/app/components/dropdown_link_component.rb +1 -1
- data/stubs/hotwire/app/components/heading_component.rb +19 -0
- data/stubs/hotwire/app/components/icon_component.rb +30 -0
- data/stubs/hotwire/app/components/input_error_component.rb +1 -1
- data/stubs/hotwire/app/components/input_label_component.rb +3 -3
- data/stubs/hotwire/app/components/modal_component.html.erb +2 -2
- data/stubs/hotwire/app/components/primary_button_component.rb +1 -1
- data/stubs/hotwire/app/components/secondary_button_component.rb +1 -1
- data/stubs/hotwire/app/components/sidebar_menu_button_component.rb +25 -0
- data/stubs/hotwire/app/components/text_input_component.rb +1 -1
- data/stubs/hotwire/app/components/user_avatar_component.rb +27 -0
- data/stubs/hotwire/app/controllers/application_controller.rb +1 -0
- data/stubs/hotwire/app/controllers/concerns/sidebar_state.rb +14 -0
- data/stubs/hotwire/app/controllers/settings/appearance_controller.rb +7 -0
- data/stubs/hotwire/app/controllers/settings/password_controller.rb +19 -0
- data/stubs/hotwire/app/controllers/{profile_controller.rb → settings/profile_controller.rb} +3 -4
- data/stubs/hotwire/app/views/auth/login.html.erb +1 -1
- data/stubs/hotwire/app/views/dashboard/index.html.erb +6 -11
- data/stubs/hotwire/app/views/layouts/_appearance.html.erb +18 -0
- data/stubs/hotwire/app/views/layouts/_settings.html.erb +26 -0
- data/stubs/hotwire/app/views/layouts/_sidebar.html.erb +56 -0
- data/stubs/hotwire/app/views/layouts/_sidebar_header.html.erb +11 -0
- data/stubs/hotwire/app/views/layouts/_sidebar_user.html.erb +34 -0
- data/stubs/hotwire/app/views/layouts/application.html.erb +25 -15
- data/stubs/hotwire/app/views/layouts/guest.html.erb +1 -0
- data/stubs/hotwire/app/views/settings/appearance/_update_appearance_form.html.erb +28 -0
- data/stubs/hotwire/app/views/settings/appearance/edit.html.erb +10 -0
- data/stubs/hotwire/app/views/{profile/partials → settings/password}/_update_password_form.html.erb +2 -10
- data/stubs/hotwire/app/views/settings/password/edit.html.erb +10 -0
- data/stubs/hotwire/app/views/{profile/partials → settings/profile}/_confirm_user_deletion_modal.html.erb +2 -2
- data/stubs/hotwire/app/views/settings/profile/_delete_user_form.html.erb +6 -0
- data/stubs/hotwire/app/views/{profile/partials → settings/profile}/_update_profile_information_form.html.erb +4 -12
- data/stubs/hotwire/app/views/{profile → settings/profile}/destroy.turbo_stream.erb +1 -1
- data/stubs/hotwire/app/views/settings/profile/edit.html.erb +14 -0
- data/stubs/hotwire/app/views/welcome/index.html.erb +1 -0
- data/stubs/inertia-common/app/controllers/concerns/handle_inertia_requests.rb +4 -1
- data/stubs/inertia-common/app/controllers/settings/appearance_controller.rb +7 -0
- data/stubs/inertia-common/app/controllers/settings/password_controller.rb +17 -0
- data/stubs/inertia-common/app/controllers/{profile_controller.rb → settings/profile_controller.rb} +2 -2
- data/stubs/inertia-common/test/integration/appearance_test.rb +11 -0
- data/stubs/inertia-common/test/integration/password_update_test.rb +10 -2
- data/stubs/inertia-react-ts/app/assets/tailwind/application.css +124 -0
- data/stubs/inertia-react-ts/app/javascript/Components/AppLogo.tsx +17 -0
- data/stubs/inertia-react-ts/app/javascript/Components/AppShell.tsx +9 -0
- data/stubs/inertia-react-ts/app/javascript/Components/AppSidebar.tsx +65 -0
- data/stubs/inertia-react-ts/app/javascript/Components/AppSidebarHeader.tsx +14 -0
- data/stubs/inertia-react-ts/app/javascript/Components/Breadcrumbs.tsx +43 -0
- data/stubs/inertia-react-ts/app/javascript/Components/Heading.tsx +19 -0
- data/stubs/inertia-react-ts/app/javascript/Components/InputError.tsx +1 -1
- data/stubs/inertia-react-ts/app/javascript/Components/NavFooter.tsx +32 -0
- data/stubs/inertia-react-ts/app/javascript/Components/NavMain.tsx +32 -0
- data/stubs/inertia-react-ts/app/javascript/Components/NavUser.tsx +39 -0
- data/stubs/inertia-react-ts/app/javascript/Components/UserInfo.tsx +23 -0
- data/stubs/inertia-react-ts/app/javascript/Components/UserMenuContent.tsx +57 -0
- data/stubs/inertia-react-ts/app/javascript/Components/ui/avatar.tsx +51 -0
- data/stubs/inertia-react-ts/app/javascript/Components/ui/breadcrumb.tsx +109 -0
- data/stubs/inertia-react-ts/app/javascript/Components/ui/button.tsx +49 -0
- data/stubs/inertia-react-ts/app/javascript/Components/ui/checkbox.tsx +27 -0
- data/stubs/inertia-react-ts/app/javascript/Components/ui/dialog.tsx +109 -0
- data/stubs/inertia-react-ts/app/javascript/Components/ui/dropdown-menu.tsx +217 -0
- data/stubs/inertia-react-ts/app/javascript/Components/ui/input.tsx +21 -0
- data/stubs/inertia-react-ts/app/javascript/Components/ui/label.tsx +19 -0
- data/stubs/inertia-react-ts/app/javascript/Components/ui/separator.tsx +26 -0
- data/stubs/inertia-react-ts/app/javascript/Components/ui/sheet.tsx +137 -0
- data/stubs/inertia-react-ts/app/javascript/Components/ui/sidebar.tsx +719 -0
- data/stubs/inertia-react-ts/app/javascript/Components/ui/skeleton.tsx +13 -0
- data/stubs/inertia-react-ts/app/javascript/Components/ui/tooltip.tsx +55 -0
- data/stubs/inertia-react-ts/app/javascript/Layouts/AuthenticatedLayout.tsx +19 -127
- data/stubs/inertia-react-ts/app/javascript/Layouts/GuestLayout.tsx +2 -2
- data/stubs/inertia-react-ts/app/javascript/Layouts/SettingsLayout.tsx +57 -0
- data/stubs/inertia-react-ts/app/javascript/Pages/Auth/ForgotPassword.tsx +6 -6
- data/stubs/inertia-react-ts/app/javascript/Pages/Auth/Login.tsx +17 -12
- data/stubs/inertia-react-ts/app/javascript/Pages/Auth/Register.tsx +14 -14
- data/stubs/inertia-react-ts/app/javascript/Pages/Auth/ResetPassword.tsx +10 -10
- data/stubs/inertia-react-ts/app/javascript/Pages/Auth/VerifyEmail.tsx +2 -2
- data/stubs/inertia-react-ts/app/javascript/Pages/Dashboard.tsx +13 -8
- data/stubs/inertia-react-ts/app/javascript/Pages/Settings/Appearance.tsx +33 -0
- data/stubs/inertia-react-ts/app/javascript/Pages/Settings/Partials/DeleteUserForm.tsx +107 -0
- data/stubs/inertia-react-ts/app/javascript/Pages/Settings/Partials/UpdateAppearanceForm.tsx +41 -0
- data/stubs/inertia-react-ts/app/javascript/Pages/{Profile → Settings}/Partials/UpdatePasswordForm.tsx +12 -20
- data/stubs/inertia-react-ts/app/javascript/Pages/{Profile → Settings}/Partials/UpdateProfileInformationForm.tsx +13 -21
- data/stubs/inertia-react-ts/app/javascript/Pages/Settings/Password.tsx +33 -0
- data/stubs/inertia-react-ts/app/javascript/Pages/Settings/Profile.tsx +35 -0
- data/stubs/inertia-react-ts/app/javascript/entrypoints/application.tsx +6 -1
- data/stubs/inertia-react-ts/app/javascript/hooks/use-initials.tsx +23 -0
- data/stubs/inertia-react-ts/app/javascript/hooks/use-mobile-navigation.tsx +11 -0
- data/stubs/inertia-react-ts/app/javascript/hooks/use-mobile.tsx +36 -0
- data/stubs/inertia-react-ts/app/javascript/lib/appearance.ts +22 -0
- data/stubs/inertia-react-ts/app/javascript/lib/utils.ts +6 -0
- data/stubs/inertia-react-ts/app/javascript/types/global.d.ts +2 -2
- data/stubs/inertia-react-ts/app/javascript/types/index.d.ts +14 -0
- data/stubs/inertia-react-ts/app/views/layouts/application.html.erb +19 -1
- data/stubs/inertia-react-ts/components.json +21 -0
- data/stubs/inertia-react-ts/package.json +13 -0
- data/stubs/inertia-vue-ts/app/assets/tailwind/application.css +124 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/AppLogo.vue +17 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/AppShell.vue +12 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/AppSidebar.vue +65 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/AppSidebarHeader.vue +20 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/Breadcrumbs.vue +32 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/Heading.vue +15 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/InputError.vue +1 -1
- data/stubs/inertia-vue-ts/app/javascript/Components/NavFooter.vue +33 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/NavMain.vue +32 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/NavUser.vue +40 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/UserInfo.vue +25 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/UserMenuContent.vue +47 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/avatar/Avatar.vue +18 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/avatar/AvatarFallback.vue +21 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/avatar/AvatarImage.vue +16 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/avatar/index.ts +3 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/breadcrumb/Breadcrumb.vue +17 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/breadcrumb/BreadcrumbEllipsis.vue +23 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/breadcrumb/BreadcrumbItem.vue +17 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/breadcrumb/BreadcrumbLink.vue +21 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/breadcrumb/BreadcrumbList.vue +17 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/breadcrumb/BreadcrumbPage.vue +20 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/breadcrumb/BreadcrumbSeparator.vue +22 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/breadcrumb/index.ts +7 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/button/Button.vue +31 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/button/index.ts +34 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/checkbox/Checkbox.vue +35 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/checkbox/index.ts +1 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/dialog/Dialog.vue +15 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/dialog/DialogClose.vue +12 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/dialog/DialogContent.vue +52 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/dialog/DialogDescription.vue +23 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/dialog/DialogFooter.vue +25 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/dialog/DialogHeader.vue +14 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/dialog/DialogOverlay.vue +26 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/dialog/DialogTitle.vue +23 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/dialog/DialogTrigger.vue +12 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/dialog/index.ts +9 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/dropdown-menu/DropdownMenu.vue +15 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/dropdown-menu/DropdownMenuContent.vue +37 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/dropdown-menu/DropdownMenuGroup.vue +12 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/dropdown-menu/DropdownMenuItem.vue +41 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/dropdown-menu/DropdownMenuLabel.vue +23 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/dropdown-menu/DropdownMenuSeparator.vue +23 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/dropdown-menu/DropdownMenuTrigger.vue +14 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/dropdown-menu/index.ts +7 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/input/Input.vue +50 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/input/index.ts +1 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/label/Label.vue +26 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/label/index.ts +1 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/separator/Separator.vue +29 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/separator/index.ts +1 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/sheet/Sheet.vue +19 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/sheet/SheetClose.vue +15 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/sheet/SheetContent.vue +62 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/sheet/SheetDescription.vue +21 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/sheet/SheetFooter.vue +16 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/sheet/SheetHeader.vue +15 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/sheet/SheetOverlay.vue +21 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/sheet/SheetTitle.vue +21 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/sheet/SheetTrigger.vue +15 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/sheet/index.ts +8 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/Sidebar.vue +96 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarContent.vue +18 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarFooter.vue +18 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarGroup.vue +18 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarGroupAction.vue +27 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarGroupContent.vue +18 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarGroupLabel.vue +25 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarHeader.vue +18 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarInput.vue +22 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarInset.vue +21 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarMenu.vue +18 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarMenuAction.vue +35 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarMenuBadge.vue +26 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarMenuButton.vue +48 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarMenuButtonChild.vue +36 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarMenuItem.vue +18 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarMenuSkeleton.vue +35 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarMenuSub.vue +22 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarMenuSubButton.vue +36 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarMenuSubItem.vue +18 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarProvider.vue +82 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarRail.vue +33 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarSeparator.vue +19 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarTrigger.vue +28 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/index.ts +60 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/utils.ts +19 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/skeleton/Skeleton.vue +17 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/skeleton/index.ts +1 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/tooltip/Tooltip.vue +19 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/tooltip/TooltipContent.vue +34 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/tooltip/TooltipProvider.vue +14 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/tooltip/TooltipTrigger.vue +15 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ui/tooltip/index.ts +4 -0
- data/stubs/inertia-vue-ts/app/javascript/Layouts/AuthenticatedLayout.vue +17 -147
- data/stubs/inertia-vue-ts/app/javascript/Layouts/SettingsLayout.vue +57 -0
- data/stubs/inertia-vue-ts/app/javascript/Pages/Auth/ForgotPassword.vue +7 -7
- data/stubs/inertia-vue-ts/app/javascript/Pages/Auth/Login.vue +10 -12
- data/stubs/inertia-vue-ts/app/javascript/Pages/Auth/Register.vue +12 -14
- data/stubs/inertia-vue-ts/app/javascript/Pages/Auth/ResetPassword.vue +8 -10
- data/stubs/inertia-vue-ts/app/javascript/Pages/Auth/VerifyEmail.vue +3 -3
- data/stubs/inertia-vue-ts/app/javascript/Pages/Dashboard.vue +15 -10
- data/stubs/inertia-vue-ts/app/javascript/Pages/Settings/Appearance.vue +33 -0
- data/stubs/inertia-vue-ts/app/javascript/Pages/Settings/Partials/DeleteUserForm.vue +96 -0
- data/stubs/inertia-vue-ts/app/javascript/Pages/Settings/Partials/UpdateAppearanceForm.vue +42 -0
- data/stubs/inertia-vue-ts/app/javascript/Pages/{Profile → Settings}/Partials/UpdatePasswordForm.vue +12 -20
- data/stubs/inertia-vue-ts/app/javascript/Pages/{Profile → Settings}/Partials/UpdateProfileInformationForm.vue +12 -20
- data/stubs/inertia-vue-ts/app/javascript/Pages/Settings/Password.vue +33 -0
- data/stubs/inertia-vue-ts/app/javascript/Pages/Settings/Profile.vue +40 -0
- data/stubs/inertia-vue-ts/app/javascript/composables/useInitials.ts +29 -0
- data/stubs/inertia-vue-ts/app/javascript/lib/appearance.ts +22 -0
- data/stubs/inertia-vue-ts/app/javascript/lib/utils.ts +6 -0
- data/stubs/inertia-vue-ts/app/javascript/types/index.d.ts +14 -0
- data/stubs/inertia-vue-ts/app/javascript/types/vue-shims.d.ts +10 -0
- data/stubs/inertia-vue-ts/app/views/layouts/application.html.erb +19 -1
- data/stubs/inertia-vue-ts/components.json +20 -0
- data/stubs/inertia-vue-ts/package.json +8 -1
- data/stubs/inertia-vue-ts/vite.config.ts +25 -1
- metadata +174 -43
- data/stubs/hotwire/app/components/nav_link_component.rb +0 -15
- data/stubs/hotwire/app/components/responsive_nav_link_component.rb +0 -15
- data/stubs/hotwire/app/controllers/password_controller.rb +0 -13
- data/stubs/hotwire/app/views/layouts/_navigation.html.erb +0 -82
- data/stubs/hotwire/app/views/profile/edit.html.erb +0 -24
- data/stubs/hotwire/app/views/profile/partials/_delete_user_form.html.erb +0 -14
- data/stubs/inertia-common/app/controllers/password_controller.rb +0 -13
- data/stubs/inertia-react-ts/app/javascript/Components/Checkbox.tsx +0 -11
- data/stubs/inertia-react-ts/app/javascript/Components/DangerButton.tsx +0 -20
- data/stubs/inertia-react-ts/app/javascript/Components/Dropdown.tsx +0 -100
- data/stubs/inertia-react-ts/app/javascript/Components/InputLabel.tsx +0 -14
- data/stubs/inertia-react-ts/app/javascript/Components/Modal.tsx +0 -66
- data/stubs/inertia-react-ts/app/javascript/Components/NavLink.tsx +0 -21
- data/stubs/inertia-react-ts/app/javascript/Components/PrimaryButton.tsx +0 -20
- data/stubs/inertia-react-ts/app/javascript/Components/ResponsiveNavLink.tsx +0 -21
- data/stubs/inertia-react-ts/app/javascript/Components/SecondaryButton.tsx +0 -22
- data/stubs/inertia-react-ts/app/javascript/Components/TextInput.tsx +0 -32
- data/stubs/inertia-react-ts/app/javascript/Pages/Profile/Edit.tsx +0 -32
- data/stubs/inertia-react-ts/app/javascript/Pages/Profile/Partials/DeleteUserForm.tsx +0 -102
- data/stubs/inertia-vue-ts/app/javascript/Components/Checkbox.vue +0 -29
- data/stubs/inertia-vue-ts/app/javascript/Components/DangerButton.vue +0 -7
- data/stubs/inertia-vue-ts/app/javascript/Components/Dropdown.vue +0 -75
- data/stubs/inertia-vue-ts/app/javascript/Components/DropdownLink.vue +0 -16
- data/stubs/inertia-vue-ts/app/javascript/Components/InputLabel.vue +0 -12
- data/stubs/inertia-vue-ts/app/javascript/Components/Modal.vue +0 -109
- data/stubs/inertia-vue-ts/app/javascript/Components/NavLink.vue +0 -21
- data/stubs/inertia-vue-ts/app/javascript/Components/PrimaryButton.vue +0 -7
- data/stubs/inertia-vue-ts/app/javascript/Components/ResponsiveNavLink.vue +0 -21
- data/stubs/inertia-vue-ts/app/javascript/Components/SecondaryButton.vue +0 -19
- data/stubs/inertia-vue-ts/app/javascript/Components/TextInput.vue +0 -23
- data/stubs/inertia-vue-ts/app/javascript/Pages/Profile/Edit.vue +0 -38
- data/stubs/inertia-vue-ts/app/javascript/Pages/Profile/Partials/DeleteUserForm.vue +0 -99
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { useRef, useState, FormEventHandler } from 'react'
|
|
2
|
+
import { Button } from '@/Components/ui/button'
|
|
3
|
+
import {
|
|
4
|
+
Dialog,
|
|
5
|
+
DialogContent,
|
|
6
|
+
DialogDescription,
|
|
7
|
+
DialogFooter,
|
|
8
|
+
DialogHeader,
|
|
9
|
+
DialogTitle,
|
|
10
|
+
} from '@/Components/ui/dialog'
|
|
11
|
+
import { Input } from '@/Components/ui/input'
|
|
12
|
+
import { Label } from '@/Components/ui/label'
|
|
13
|
+
import InputError from '@/Components/InputError'
|
|
14
|
+
import { useForm } from '@inertiajs/react'
|
|
15
|
+
import { profile_destroy_path } from '@/routes'
|
|
16
|
+
|
|
17
|
+
export default function DeleteUserForm({ className = '' }: { className?: string }) {
|
|
18
|
+
const [confirmingUserDeletion, setConfirmingUserDeletion] = useState(false)
|
|
19
|
+
const passwordInput = useRef<HTMLInputElement>(null)
|
|
20
|
+
|
|
21
|
+
const {
|
|
22
|
+
data,
|
|
23
|
+
setData,
|
|
24
|
+
delete: destroy,
|
|
25
|
+
processing,
|
|
26
|
+
reset,
|
|
27
|
+
errors,
|
|
28
|
+
clearErrors,
|
|
29
|
+
} = useForm({
|
|
30
|
+
password: '',
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const confirmUserDeletion = () => {
|
|
34
|
+
setConfirmingUserDeletion(true)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const deleteUser: FormEventHandler = (e) => {
|
|
38
|
+
e.preventDefault()
|
|
39
|
+
|
|
40
|
+
destroy(profile_destroy_path(), {
|
|
41
|
+
preserveScroll: true,
|
|
42
|
+
onSuccess: () => closeModal(),
|
|
43
|
+
onError: () => passwordInput.current?.focus(),
|
|
44
|
+
onFinish: () => reset(),
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const closeModal = () => {
|
|
49
|
+
setConfirmingUserDeletion(false)
|
|
50
|
+
|
|
51
|
+
clearErrors()
|
|
52
|
+
reset()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<section className={`space-y-6 ${className}`}>
|
|
57
|
+
<Button variant="destructive" onClick={confirmUserDeletion}>
|
|
58
|
+
Delete Account
|
|
59
|
+
</Button>
|
|
60
|
+
|
|
61
|
+
<Dialog open={confirmingUserDeletion} onOpenChange={(open) => !open && closeModal()}>
|
|
62
|
+
<DialogContent>
|
|
63
|
+
<form onSubmit={deleteUser}>
|
|
64
|
+
<DialogHeader>
|
|
65
|
+
<DialogTitle>Are you sure you want to delete your account?</DialogTitle>
|
|
66
|
+
|
|
67
|
+
<DialogDescription>
|
|
68
|
+
Once your account is deleted, all of its resources and data will be permanently deleted. Please enter
|
|
69
|
+
your password to confirm you would like to permanently delete your account.
|
|
70
|
+
</DialogDescription>
|
|
71
|
+
</DialogHeader>
|
|
72
|
+
|
|
73
|
+
<div className="mt-6">
|
|
74
|
+
<Label htmlFor="password" className="sr-only">
|
|
75
|
+
Password
|
|
76
|
+
</Label>
|
|
77
|
+
|
|
78
|
+
<Input
|
|
79
|
+
id="password"
|
|
80
|
+
type="password"
|
|
81
|
+
name="password"
|
|
82
|
+
ref={passwordInput}
|
|
83
|
+
value={data.password}
|
|
84
|
+
onChange={(e) => setData('password', e.target.value)}
|
|
85
|
+
className="mt-1 block w-3/4"
|
|
86
|
+
autoFocus
|
|
87
|
+
placeholder="Password"
|
|
88
|
+
/>
|
|
89
|
+
|
|
90
|
+
<InputError message={errors.password} className="mt-2" />
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<DialogFooter>
|
|
94
|
+
<Button type="button" variant="outline" onClick={closeModal}>
|
|
95
|
+
Cancel
|
|
96
|
+
</Button>
|
|
97
|
+
|
|
98
|
+
<Button variant="destructive" disabled={processing}>
|
|
99
|
+
Delete Account
|
|
100
|
+
</Button>
|
|
101
|
+
</DialogFooter>
|
|
102
|
+
</form>
|
|
103
|
+
</DialogContent>
|
|
104
|
+
</Dialog>
|
|
105
|
+
</section>
|
|
106
|
+
)
|
|
107
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import { LucideIcon, Monitor, Moon, Sun } from 'lucide-react'
|
|
3
|
+
import { Appearance, getAppearance, setAppearance } from '@/lib/appearance'
|
|
4
|
+
import { cn } from '@/lib/utils'
|
|
5
|
+
|
|
6
|
+
const options: { value: Appearance; icon: LucideIcon; label: string }[] = [
|
|
7
|
+
{ value: 'light', icon: Sun, label: 'Light' },
|
|
8
|
+
{ value: 'dark', icon: Moon, label: 'Dark' },
|
|
9
|
+
{ value: 'system', icon: Monitor, label: 'System' },
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
export default function UpdateAppearanceForm({ className = '' }: { className?: string }) {
|
|
13
|
+
const [appearance, setCurrentAppearance] = useState<Appearance>(getAppearance)
|
|
14
|
+
|
|
15
|
+
const select = (value: Appearance) => {
|
|
16
|
+
setCurrentAppearance(value)
|
|
17
|
+
setAppearance(value)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<div className={cn('inline-flex gap-1 rounded-lg bg-neutral-100 p-1 dark:bg-neutral-800', className)}>
|
|
22
|
+
{options.map(({ value, icon: Icon, label }) => (
|
|
23
|
+
<button
|
|
24
|
+
key={value}
|
|
25
|
+
type="button"
|
|
26
|
+
onClick={() => select(value)}
|
|
27
|
+
aria-pressed={appearance === value}
|
|
28
|
+
className={cn(
|
|
29
|
+
'flex items-center rounded-md px-3.5 py-1.5 transition-colors outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50',
|
|
30
|
+
appearance === value
|
|
31
|
+
? 'bg-white shadow-xs dark:bg-neutral-700 dark:text-neutral-100'
|
|
32
|
+
: 'text-neutral-500 hover:bg-neutral-200/60 hover:text-black dark:text-neutral-400 dark:hover:bg-neutral-700/60',
|
|
33
|
+
)}
|
|
34
|
+
>
|
|
35
|
+
<Icon className="-ml-1 h-4 w-4" />
|
|
36
|
+
<span className="ml-1.5 text-sm">{label}</span>
|
|
37
|
+
</button>
|
|
38
|
+
))}
|
|
39
|
+
</div>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useRef, FormEventHandler } from 'react'
|
|
2
|
+
import { Button } from '@/Components/ui/button'
|
|
3
|
+
import { Input } from '@/Components/ui/input'
|
|
4
|
+
import { Label } from '@/Components/ui/label'
|
|
2
5
|
import InputError from '@/Components/InputError'
|
|
3
|
-
import InputLabel from '@/Components/InputLabel'
|
|
4
|
-
import PrimaryButton from '@/Components/PrimaryButton'
|
|
5
|
-
import TextInput from '@/Components/TextInput'
|
|
6
6
|
import { useForm } from '@inertiajs/react'
|
|
7
7
|
import { Transition } from '@headlessui/react'
|
|
8
8
|
import { password_update_path } from '@/routes'
|
|
@@ -39,19 +39,11 @@ export default function UpdatePasswordForm({ className = '' }: { className?: str
|
|
|
39
39
|
|
|
40
40
|
return (
|
|
41
41
|
<section className={className}>
|
|
42
|
-
<
|
|
43
|
-
<h2 className="text-lg font-medium text-gray-900 dark:text-gray-100">Update Password</h2>
|
|
44
|
-
|
|
45
|
-
<p className="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
|
46
|
-
Ensure your account is using a long, random password to stay secure.
|
|
47
|
-
</p>
|
|
48
|
-
</header>
|
|
49
|
-
|
|
50
|
-
<form onSubmit={updatePassword} className="mt-6 space-y-6">
|
|
42
|
+
<form onSubmit={updatePassword} className="space-y-6">
|
|
51
43
|
<div>
|
|
52
|
-
<
|
|
44
|
+
<Label htmlFor="current_password">Current Password</Label>
|
|
53
45
|
|
|
54
|
-
<
|
|
46
|
+
<Input
|
|
55
47
|
id="current_password"
|
|
56
48
|
ref={currentPasswordInput}
|
|
57
49
|
value={data.current_password}
|
|
@@ -65,9 +57,9 @@ export default function UpdatePasswordForm({ className = '' }: { className?: str
|
|
|
65
57
|
</div>
|
|
66
58
|
|
|
67
59
|
<div>
|
|
68
|
-
<
|
|
60
|
+
<Label htmlFor="password">New Password</Label>
|
|
69
61
|
|
|
70
|
-
<
|
|
62
|
+
<Input
|
|
71
63
|
id="password"
|
|
72
64
|
ref={passwordInput}
|
|
73
65
|
value={data.password}
|
|
@@ -81,9 +73,9 @@ export default function UpdatePasswordForm({ className = '' }: { className?: str
|
|
|
81
73
|
</div>
|
|
82
74
|
|
|
83
75
|
<div>
|
|
84
|
-
<
|
|
76
|
+
<Label htmlFor="password_confirmation">Confirm Password</Label>
|
|
85
77
|
|
|
86
|
-
<
|
|
78
|
+
<Input
|
|
87
79
|
id="password_confirmation"
|
|
88
80
|
value={data.password_confirmation}
|
|
89
81
|
onChange={(e) => setData('password_confirmation', e.target.value)}
|
|
@@ -96,7 +88,7 @@ export default function UpdatePasswordForm({ className = '' }: { className?: str
|
|
|
96
88
|
</div>
|
|
97
89
|
|
|
98
90
|
<div className="flex items-center gap-4">
|
|
99
|
-
<
|
|
91
|
+
<Button disabled={processing}>Save</Button>
|
|
100
92
|
|
|
101
93
|
<Transition
|
|
102
94
|
show={recentlySuccessful}
|
|
@@ -105,7 +97,7 @@ export default function UpdatePasswordForm({ className = '' }: { className?: str
|
|
|
105
97
|
leave="transition ease-in-out"
|
|
106
98
|
leaveTo="opacity-0"
|
|
107
99
|
>
|
|
108
|
-
<p className="text-sm text-
|
|
100
|
+
<p className="text-sm text-muted-foreground">Saved.</p>
|
|
109
101
|
</Transition>
|
|
110
102
|
</div>
|
|
111
103
|
</form>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import InputError from '@/Components/InputError'
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
2
|
+
import { Button } from '@/Components/ui/button'
|
|
3
|
+
import { Input } from '@/Components/ui/input'
|
|
4
|
+
import { Label } from '@/Components/ui/label'
|
|
5
5
|
import { Link, useForm, usePage } from '@inertiajs/react'
|
|
6
6
|
import { Transition } from '@headlessui/react'
|
|
7
7
|
import { FormEventHandler } from 'react'
|
|
@@ -31,25 +31,17 @@ export default function UpdateProfileInformation({
|
|
|
31
31
|
|
|
32
32
|
return (
|
|
33
33
|
<section className={className}>
|
|
34
|
-
<
|
|
35
|
-
<h2 className="text-lg font-medium text-gray-900 dark:text-gray-100">Profile Information</h2>
|
|
36
|
-
|
|
37
|
-
<p className="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
|
38
|
-
Update your account's profile information and email address.
|
|
39
|
-
</p>
|
|
40
|
-
</header>
|
|
41
|
-
|
|
42
|
-
<form onSubmit={submit} className="mt-6 space-y-6">
|
|
34
|
+
<form onSubmit={submit} className="space-y-6">
|
|
43
35
|
<div>
|
|
44
|
-
<
|
|
36
|
+
<Label htmlFor="name">Name</Label>
|
|
45
37
|
|
|
46
|
-
<
|
|
38
|
+
<Input
|
|
47
39
|
id="name"
|
|
48
40
|
className="mt-1 block w-full"
|
|
49
41
|
value={data.name}
|
|
50
42
|
onChange={(e) => setData('name', e.target.value)}
|
|
51
43
|
required
|
|
52
|
-
|
|
44
|
+
autoFocus
|
|
53
45
|
autoComplete="name"
|
|
54
46
|
/>
|
|
55
47
|
|
|
@@ -57,9 +49,9 @@ export default function UpdateProfileInformation({
|
|
|
57
49
|
</div>
|
|
58
50
|
|
|
59
51
|
<div>
|
|
60
|
-
<
|
|
52
|
+
<Label htmlFor="email">Email</Label>
|
|
61
53
|
|
|
62
|
-
<
|
|
54
|
+
<Input
|
|
63
55
|
id="email"
|
|
64
56
|
type="email"
|
|
65
57
|
className="mt-1 block w-full"
|
|
@@ -74,13 +66,13 @@ export default function UpdateProfileInformation({
|
|
|
74
66
|
|
|
75
67
|
{mustVerifyEmail && user.email_verified_at === null && (
|
|
76
68
|
<div>
|
|
77
|
-
<p className="
|
|
69
|
+
<p className="mt-2 text-sm text-foreground">
|
|
78
70
|
Your email address is unverified.
|
|
79
71
|
<Link
|
|
80
72
|
href={verification_send_path()}
|
|
81
73
|
method="post"
|
|
82
74
|
as="button"
|
|
83
|
-
className="
|
|
75
|
+
className="rounded-md text-sm text-muted-foreground underline outline-none hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50"
|
|
84
76
|
>
|
|
85
77
|
Click here to re-send the verification email.
|
|
86
78
|
</Link>
|
|
@@ -95,7 +87,7 @@ export default function UpdateProfileInformation({
|
|
|
95
87
|
)}
|
|
96
88
|
|
|
97
89
|
<div className="flex items-center gap-4">
|
|
98
|
-
<
|
|
90
|
+
<Button disabled={processing}>Save</Button>
|
|
99
91
|
|
|
100
92
|
<Transition
|
|
101
93
|
show={recentlySuccessful}
|
|
@@ -104,7 +96,7 @@ export default function UpdateProfileInformation({
|
|
|
104
96
|
leave="transition ease-in-out"
|
|
105
97
|
leaveTo="opacity-0"
|
|
106
98
|
>
|
|
107
|
-
<p className="text-sm text-
|
|
99
|
+
<p className="text-sm text-muted-foreground">Saved.</p>
|
|
108
100
|
</Transition>
|
|
109
101
|
</div>
|
|
110
102
|
</form>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Head } from '@inertiajs/react'
|
|
2
|
+
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'
|
|
3
|
+
import SettingsLayout from '@/Layouts/SettingsLayout'
|
|
4
|
+
import Heading from '@/Components/Heading'
|
|
5
|
+
import UpdatePasswordForm from './Partials/UpdatePasswordForm'
|
|
6
|
+
import { password_edit_path } from '@/routes'
|
|
7
|
+
import { BreadcrumbItem } from '@/types'
|
|
8
|
+
|
|
9
|
+
const breadcrumbs: BreadcrumbItem[] = [
|
|
10
|
+
{
|
|
11
|
+
title: 'Password settings',
|
|
12
|
+
href: password_edit_path(),
|
|
13
|
+
},
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
export default function Password() {
|
|
17
|
+
return (
|
|
18
|
+
<AuthenticatedLayout breadcrumbs={breadcrumbs}>
|
|
19
|
+
<Head title="Password settings" />
|
|
20
|
+
|
|
21
|
+
<SettingsLayout>
|
|
22
|
+
<div className="space-y-6">
|
|
23
|
+
<Heading
|
|
24
|
+
variant="small"
|
|
25
|
+
title="Update password"
|
|
26
|
+
description="Ensure your account is using a long, random password to stay secure"
|
|
27
|
+
/>
|
|
28
|
+
<UpdatePasswordForm />
|
|
29
|
+
</div>
|
|
30
|
+
</SettingsLayout>
|
|
31
|
+
</AuthenticatedLayout>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Head } from '@inertiajs/react'
|
|
2
|
+
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'
|
|
3
|
+
import SettingsLayout from '@/Layouts/SettingsLayout'
|
|
4
|
+
import Heading from '@/Components/Heading'
|
|
5
|
+
import DeleteUserForm from './Partials/DeleteUserForm'
|
|
6
|
+
import UpdateProfileInformationForm from './Partials/UpdateProfileInformationForm'
|
|
7
|
+
import { profile_edit_path } from '@/routes'
|
|
8
|
+
import { BreadcrumbItem, PageProps } from '@/types'
|
|
9
|
+
|
|
10
|
+
const breadcrumbs: BreadcrumbItem[] = [
|
|
11
|
+
{
|
|
12
|
+
title: 'Profile settings',
|
|
13
|
+
href: profile_edit_path(),
|
|
14
|
+
},
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
export default function Profile({ mustVerifyEmail, status }: PageProps<{ mustVerifyEmail: boolean; status?: string }>) {
|
|
18
|
+
return (
|
|
19
|
+
<AuthenticatedLayout breadcrumbs={breadcrumbs}>
|
|
20
|
+
<Head title="Profile settings" />
|
|
21
|
+
|
|
22
|
+
<SettingsLayout>
|
|
23
|
+
<div className="space-y-6">
|
|
24
|
+
<Heading variant="small" title="Profile information" description="Update your name and email address" />
|
|
25
|
+
<UpdateProfileInformationForm mustVerifyEmail={mustVerifyEmail} status={status} />
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<div className="space-y-6">
|
|
29
|
+
<Heading variant="small" title="Delete account" description="Delete your account and all of its resources" />
|
|
30
|
+
<DeleteUserForm />
|
|
31
|
+
</div>
|
|
32
|
+
</SettingsLayout>
|
|
33
|
+
</AuthenticatedLayout>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
@@ -3,6 +3,7 @@ import '../../assets/builds/tailwind.css'
|
|
|
3
3
|
|
|
4
4
|
import { createRoot } from 'react-dom/client'
|
|
5
5
|
import { createInertiaApp, type ResolvedComponent } from '@inertiajs/react'
|
|
6
|
+
import { TooltipProvider } from '@/Components/ui/tooltip'
|
|
6
7
|
|
|
7
8
|
const pages = import.meta.glob<{ default: ResolvedComponent }>('../Pages/**/*.tsx')
|
|
8
9
|
|
|
@@ -24,7 +25,11 @@ createInertiaApp({
|
|
|
24
25
|
setup({ el, App, props }) {
|
|
25
26
|
const root = createRoot(el)
|
|
26
27
|
|
|
27
|
-
root.render(
|
|
28
|
+
root.render(
|
|
29
|
+
<TooltipProvider delayDuration={0}>
|
|
30
|
+
<App {...props} />
|
|
31
|
+
</TooltipProvider>,
|
|
32
|
+
)
|
|
28
33
|
},
|
|
29
34
|
progress: {
|
|
30
35
|
color: '#4B5563',
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { useCallback } from 'react'
|
|
2
|
+
|
|
3
|
+
export type GetInitialsFn = (fullName: string) => string
|
|
4
|
+
|
|
5
|
+
function getInitial(name: string): string {
|
|
6
|
+
return Array.from(name)[0] ?? ''
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function useInitials(): GetInitialsFn {
|
|
10
|
+
return useCallback((fullName: string): string => {
|
|
11
|
+
const names = fullName.trim().split(/\s+/u).filter(Boolean)
|
|
12
|
+
|
|
13
|
+
if (names.length === 0) {
|
|
14
|
+
return ''
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (names.length === 1) {
|
|
18
|
+
return getInitial(names[0]).toUpperCase()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return `${getInitial(names[0])}${getInitial(names[names.length - 1])}`.toUpperCase()
|
|
22
|
+
}, [])
|
|
23
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { useCallback } from 'react'
|
|
2
|
+
|
|
3
|
+
export type CleanupFn = () => void
|
|
4
|
+
|
|
5
|
+
export function useMobileNavigation(): CleanupFn {
|
|
6
|
+
return useCallback(() => {
|
|
7
|
+
// The dropdown leaves pointer-events on the body when it closes during a
|
|
8
|
+
// page change, which would make the new page unclickable.
|
|
9
|
+
document.body.style.removeProperty('pointer-events')
|
|
10
|
+
}, [])
|
|
11
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { useSyncExternalStore } from 'react'
|
|
2
|
+
|
|
3
|
+
const MOBILE_BREAKPOINT = 768
|
|
4
|
+
|
|
5
|
+
const mql =
|
|
6
|
+
typeof window === 'undefined'
|
|
7
|
+
? undefined
|
|
8
|
+
: window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
|
|
9
|
+
|
|
10
|
+
function mediaQueryListener(callback: (event: MediaQueryListEvent) => void) {
|
|
11
|
+
if (!mql) {
|
|
12
|
+
return () => {}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
mql.addEventListener('change', callback)
|
|
16
|
+
|
|
17
|
+
return () => {
|
|
18
|
+
mql.removeEventListener('change', callback)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function isSmallerThanBreakpoint(): boolean {
|
|
23
|
+
return mql?.matches ?? false
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function getServerSnapshot(): boolean {
|
|
27
|
+
return false
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function useIsMobile(): boolean {
|
|
31
|
+
return useSyncExternalStore(
|
|
32
|
+
mediaQueryListener,
|
|
33
|
+
isSmallerThanBreakpoint,
|
|
34
|
+
getServerSnapshot,
|
|
35
|
+
)
|
|
36
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type Appearance = 'light' | 'dark' | 'system'
|
|
2
|
+
|
|
3
|
+
const STORAGE_KEY = 'appearance'
|
|
4
|
+
|
|
5
|
+
export function getAppearance(): Appearance {
|
|
6
|
+
const stored = localStorage.getItem(STORAGE_KEY)
|
|
7
|
+
|
|
8
|
+
return stored === 'light' || stored === 'dark' ? stored : 'system'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function applyAppearance(appearance: Appearance = getAppearance()): void {
|
|
12
|
+
const dark =
|
|
13
|
+
appearance === 'dark' || (appearance === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches)
|
|
14
|
+
|
|
15
|
+
document.documentElement.classList.toggle('dark', dark)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function setAppearance(appearance: Appearance): void {
|
|
19
|
+
localStorage.setItem(STORAGE_KEY, appearance)
|
|
20
|
+
|
|
21
|
+
applyAppearance(appearance)
|
|
22
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { PageProps as InertiaPageProps } from '@inertiajs/core'
|
|
1
|
+
import { PageProps as InertiaPageProps } from '@inertiajs/core'
|
|
2
2
|
import { AxiosInstance } from 'axios'
|
|
3
|
-
import { PageProps as AppPageProps } from './'
|
|
3
|
+
import { PageProps as AppPageProps } from './'
|
|
4
4
|
|
|
5
5
|
declare global {
|
|
6
6
|
interface Window {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { LucideIcon } from 'lucide-react'
|
|
2
|
+
|
|
1
3
|
export interface User {
|
|
2
4
|
id: number
|
|
3
5
|
name: string
|
|
@@ -5,8 +7,20 @@ export interface User {
|
|
|
5
7
|
email_verified_at?: string
|
|
6
8
|
}
|
|
7
9
|
|
|
10
|
+
export interface NavItem {
|
|
11
|
+
title: string
|
|
12
|
+
href: string
|
|
13
|
+
icon?: LucideIcon
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface BreadcrumbItem {
|
|
17
|
+
title: string
|
|
18
|
+
href: string
|
|
19
|
+
}
|
|
20
|
+
|
|
8
21
|
export type PageProps<T extends Record<string, unknown> = Record<string, unknown>> = T & {
|
|
9
22
|
auth: {
|
|
10
23
|
user: User
|
|
11
24
|
}
|
|
25
|
+
sidebar_open: boolean
|
|
12
26
|
}
|
|
@@ -6,12 +6,30 @@
|
|
|
6
6
|
<title><%= content_for(:title) || "Rails" %></title>
|
|
7
7
|
<%= csrf_meta_tags %>
|
|
8
8
|
<%= csp_meta_tag %>
|
|
9
|
+
<%# Apply the stored appearance before first paint, so the page never flashes the wrong theme. %>
|
|
10
|
+
<%= javascript_tag nonce: true do %>
|
|
11
|
+
(function () {
|
|
12
|
+
var preference = localStorage.getItem('appearance') || 'system'
|
|
13
|
+
var query = window.matchMedia('(prefers-color-scheme: dark)')
|
|
14
|
+
|
|
15
|
+
document.documentElement.classList.toggle(
|
|
16
|
+
'dark',
|
|
17
|
+
preference === 'dark' || (preference === 'system' && query.matches)
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
query.addEventListener('change', function () {
|
|
21
|
+
if ((localStorage.getItem('appearance') || 'system') === 'system') {
|
|
22
|
+
document.documentElement.classList.toggle('dark', query.matches)
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
})()
|
|
26
|
+
<% end %>
|
|
9
27
|
<%= vite_client_tag %>
|
|
10
28
|
<%= vite_react_refresh_tag %>
|
|
11
29
|
<%= vite_typescript_tag 'application.tsx', defer: true %>
|
|
12
30
|
</head>
|
|
13
31
|
<body class="font-sans antialiased">
|
|
14
|
-
<div class="min-h-screen bg-gray-100">
|
|
32
|
+
<div class="min-h-screen bg-gray-100 dark:bg-gray-900">
|
|
15
33
|
<%= yield %>
|
|
16
34
|
</div>
|
|
17
35
|
</body>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
3
|
+
"style": "new-york",
|
|
4
|
+
"rsc": false,
|
|
5
|
+
"tsx": true,
|
|
6
|
+
"tailwind": {
|
|
7
|
+
"config": "",
|
|
8
|
+
"css": "app/assets/tailwind/application.css",
|
|
9
|
+
"baseColor": "neutral",
|
|
10
|
+
"cssVariables": true,
|
|
11
|
+
"prefix": ""
|
|
12
|
+
},
|
|
13
|
+
"aliases": {
|
|
14
|
+
"components": "@/Components",
|
|
15
|
+
"ui": "@/Components/ui",
|
|
16
|
+
"utils": "@/lib/utils",
|
|
17
|
+
"lib": "@/lib",
|
|
18
|
+
"hooks": "@/hooks"
|
|
19
|
+
},
|
|
20
|
+
"iconLibrary": "lucide"
|
|
21
|
+
}
|
|
@@ -8,13 +8,26 @@
|
|
|
8
8
|
"devDependencies": {
|
|
9
9
|
"@headlessui/react": "^2.2.10",
|
|
10
10
|
"@inertiajs/react": "^3.9.1",
|
|
11
|
+
"@radix-ui/react-avatar": "^1.1.3",
|
|
12
|
+
"@radix-ui/react-checkbox": "^1.1.4",
|
|
13
|
+
"@radix-ui/react-dialog": "^1.1.6",
|
|
14
|
+
"@radix-ui/react-dropdown-menu": "^2.1.6",
|
|
15
|
+
"@radix-ui/react-label": "^2.1.2",
|
|
16
|
+
"@radix-ui/react-separator": "^1.1.2",
|
|
17
|
+
"@radix-ui/react-slot": "^1.2.3",
|
|
18
|
+
"@radix-ui/react-tooltip": "^1.1.8",
|
|
11
19
|
"@types/node": "^26.6.5",
|
|
12
20
|
"@types/react": "^19.3.0",
|
|
13
21
|
"@types/react-dom": "^19.3.0",
|
|
14
22
|
"@vitejs/plugin-react": "^6.1.2",
|
|
15
23
|
"axios": "^1.6.4",
|
|
24
|
+
"class-variance-authority": "^0.7.1",
|
|
25
|
+
"clsx": "^2.1.1",
|
|
26
|
+
"lucide-react": "^1.61.0",
|
|
16
27
|
"react": "^19.3.1",
|
|
17
28
|
"react-dom": "^19.3.1",
|
|
29
|
+
"tailwind-merge": "^3.7.0",
|
|
30
|
+
"tw-animate-css": "^1.4.0",
|
|
18
31
|
"typescript": "^7.0.2",
|
|
19
32
|
"vite": "^8.3.4",
|
|
20
33
|
"vite-plugin-rails": "^0.6.0"
|