kaze 1.4.0 → 2.0.1

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 (262) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +62 -1
  3. data/README.md +1 -1
  4. data/lib/kaze/commands/app/base_stack.rb +12 -2
  5. data/lib/kaze/commands/app/hotwire_stack.rb +3 -1
  6. data/lib/kaze/commands/app/inertia_react_stack.rb +6 -2
  7. data/lib/kaze/commands/app/inertia_vue_stack.rb +6 -2
  8. data/lib/kaze/commands/app.rb +9 -2
  9. data/lib/kaze/version.rb +1 -1
  10. data/stubs/default/config/routes.rb +11 -4
  11. data/stubs/default/test/integration/appearance_test.rb +11 -0
  12. data/stubs/default/test/integration/password_update_test.rb +9 -1
  13. data/stubs/hotwire/app/assets/tailwind/application.css +119 -1
  14. data/stubs/hotwire/app/components/breadcrumbs_component.rb +32 -0
  15. data/stubs/hotwire/app/components/danger_button_component.rb +1 -1
  16. data/stubs/hotwire/app/components/dropdown_component.html.erb +2 -2
  17. data/stubs/hotwire/app/components/dropdown_component.rb +5 -1
  18. data/stubs/hotwire/app/components/dropdown_link_component.rb +1 -1
  19. data/stubs/hotwire/app/components/heading_component.rb +19 -0
  20. data/stubs/hotwire/app/components/icon_component.rb +30 -0
  21. data/stubs/hotwire/app/components/input_error_component.rb +1 -1
  22. data/stubs/hotwire/app/components/input_label_component.rb +3 -3
  23. data/stubs/hotwire/app/components/modal_component.html.erb +2 -2
  24. data/stubs/hotwire/app/components/primary_button_component.rb +1 -1
  25. data/stubs/hotwire/app/components/secondary_button_component.rb +1 -1
  26. data/stubs/hotwire/app/components/sidebar_menu_button_component.rb +25 -0
  27. data/stubs/hotwire/app/components/text_input_component.rb +1 -1
  28. data/stubs/hotwire/app/components/user_avatar_component.rb +27 -0
  29. data/stubs/hotwire/app/controllers/application_controller.rb +1 -0
  30. data/stubs/hotwire/app/controllers/concerns/sidebar_state.rb +14 -0
  31. data/stubs/hotwire/app/controllers/settings/appearance_controller.rb +7 -0
  32. data/stubs/hotwire/app/controllers/settings/password_controller.rb +19 -0
  33. data/stubs/hotwire/app/controllers/{profile_controller.rb → settings/profile_controller.rb} +3 -4
  34. data/stubs/hotwire/app/views/auth/login.html.erb +1 -1
  35. data/stubs/hotwire/app/views/dashboard/index.html.erb +6 -11
  36. data/stubs/hotwire/app/views/layouts/_settings.html.erb +26 -0
  37. data/stubs/hotwire/app/views/layouts/_sidebar.html.erb +56 -0
  38. data/stubs/hotwire/app/views/layouts/_sidebar_header.html.erb +11 -0
  39. data/stubs/hotwire/app/views/layouts/_sidebar_user.html.erb +34 -0
  40. data/stubs/hotwire/app/views/layouts/application.html.erb +24 -15
  41. data/stubs/hotwire/app/views/settings/appearance/_update_appearance_form.html.erb +28 -0
  42. data/stubs/hotwire/app/views/settings/appearance/edit.html.erb +10 -0
  43. data/stubs/hotwire/app/views/{profile/partials → settings/password}/_update_password_form.html.erb +2 -10
  44. data/stubs/hotwire/app/views/settings/password/edit.html.erb +10 -0
  45. data/stubs/hotwire/app/views/{profile/partials → settings/profile}/_confirm_user_deletion_modal.html.erb +2 -2
  46. data/stubs/hotwire/app/views/settings/profile/_delete_user_form.html.erb +6 -0
  47. data/stubs/hotwire/app/views/{profile/partials → settings/profile}/_update_profile_information_form.html.erb +4 -12
  48. data/stubs/hotwire/app/views/{profile → settings/profile}/destroy.turbo_stream.erb +1 -1
  49. data/stubs/hotwire/app/views/settings/profile/edit.html.erb +14 -0
  50. data/stubs/inertia-common/app/controllers/concerns/handle_inertia_requests.rb +4 -1
  51. data/stubs/inertia-common/app/controllers/settings/appearance_controller.rb +7 -0
  52. data/stubs/inertia-common/app/controllers/settings/password_controller.rb +17 -0
  53. data/stubs/inertia-common/app/controllers/{profile_controller.rb → settings/profile_controller.rb} +2 -2
  54. data/stubs/inertia-common/test/integration/appearance_test.rb +11 -0
  55. data/stubs/inertia-common/test/integration/password_update_test.rb +10 -2
  56. data/stubs/inertia-react-ts/app/assets/tailwind/application.css +120 -1
  57. data/stubs/inertia-react-ts/app/javascript/Components/AppLogo.tsx +17 -0
  58. data/stubs/inertia-react-ts/app/javascript/Components/AppShell.tsx +9 -0
  59. data/stubs/inertia-react-ts/app/javascript/Components/AppSidebar.tsx +65 -0
  60. data/stubs/inertia-react-ts/app/javascript/Components/AppSidebarHeader.tsx +14 -0
  61. data/stubs/inertia-react-ts/app/javascript/Components/Breadcrumbs.tsx +43 -0
  62. data/stubs/inertia-react-ts/app/javascript/Components/Heading.tsx +19 -0
  63. data/stubs/inertia-react-ts/app/javascript/Components/InputError.tsx +1 -1
  64. data/stubs/inertia-react-ts/app/javascript/Components/NavFooter.tsx +32 -0
  65. data/stubs/inertia-react-ts/app/javascript/Components/NavMain.tsx +32 -0
  66. data/stubs/inertia-react-ts/app/javascript/Components/NavUser.tsx +39 -0
  67. data/stubs/inertia-react-ts/app/javascript/Components/UserInfo.tsx +23 -0
  68. data/stubs/inertia-react-ts/app/javascript/Components/UserMenuContent.tsx +57 -0
  69. data/stubs/inertia-react-ts/app/javascript/Components/ui/avatar.tsx +51 -0
  70. data/stubs/inertia-react-ts/app/javascript/Components/ui/breadcrumb.tsx +109 -0
  71. data/stubs/inertia-react-ts/app/javascript/Components/ui/button.tsx +49 -0
  72. data/stubs/inertia-react-ts/app/javascript/Components/ui/checkbox.tsx +27 -0
  73. data/stubs/inertia-react-ts/app/javascript/Components/ui/dialog.tsx +109 -0
  74. data/stubs/inertia-react-ts/app/javascript/Components/ui/dropdown-menu.tsx +217 -0
  75. data/stubs/inertia-react-ts/app/javascript/Components/ui/input.tsx +21 -0
  76. data/stubs/inertia-react-ts/app/javascript/Components/ui/label.tsx +19 -0
  77. data/stubs/inertia-react-ts/app/javascript/Components/ui/separator.tsx +26 -0
  78. data/stubs/inertia-react-ts/app/javascript/Components/ui/sheet.tsx +137 -0
  79. data/stubs/inertia-react-ts/app/javascript/Components/ui/sidebar.tsx +719 -0
  80. data/stubs/inertia-react-ts/app/javascript/Components/ui/skeleton.tsx +13 -0
  81. data/stubs/inertia-react-ts/app/javascript/Components/ui/tooltip.tsx +55 -0
  82. data/stubs/inertia-react-ts/app/javascript/Layouts/AuthenticatedLayout.tsx +19 -127
  83. data/stubs/inertia-react-ts/app/javascript/Layouts/GuestLayout.tsx +1 -1
  84. data/stubs/inertia-react-ts/app/javascript/Layouts/SettingsLayout.tsx +57 -0
  85. data/stubs/inertia-react-ts/app/javascript/Pages/Auth/ForgotPassword.tsx +6 -6
  86. data/stubs/inertia-react-ts/app/javascript/Pages/Auth/Login.tsx +17 -12
  87. data/stubs/inertia-react-ts/app/javascript/Pages/Auth/Register.tsx +14 -14
  88. data/stubs/inertia-react-ts/app/javascript/Pages/Auth/ResetPassword.tsx +10 -10
  89. data/stubs/inertia-react-ts/app/javascript/Pages/Auth/VerifyEmail.tsx +2 -2
  90. data/stubs/inertia-react-ts/app/javascript/Pages/Dashboard.tsx +13 -8
  91. data/stubs/inertia-react-ts/app/javascript/Pages/Settings/Appearance.tsx +33 -0
  92. data/stubs/inertia-react-ts/app/javascript/Pages/Settings/Partials/DeleteUserForm.tsx +107 -0
  93. data/stubs/inertia-react-ts/app/javascript/Pages/Settings/Partials/UpdateAppearanceForm.tsx +41 -0
  94. data/stubs/inertia-react-ts/app/javascript/Pages/{Profile → Settings}/Partials/UpdatePasswordForm.tsx +12 -20
  95. data/stubs/inertia-react-ts/app/javascript/Pages/{Profile → Settings}/Partials/UpdateProfileInformationForm.tsx +13 -21
  96. data/stubs/inertia-react-ts/app/javascript/Pages/Settings/Password.tsx +33 -0
  97. data/stubs/inertia-react-ts/app/javascript/Pages/Settings/Profile.tsx +35 -0
  98. data/stubs/inertia-react-ts/app/javascript/entrypoints/application.tsx +6 -1
  99. data/stubs/inertia-react-ts/app/javascript/hooks/use-initials.tsx +23 -0
  100. data/stubs/inertia-react-ts/app/javascript/hooks/use-mobile-navigation.tsx +11 -0
  101. data/stubs/inertia-react-ts/app/javascript/hooks/use-mobile.tsx +36 -0
  102. data/stubs/inertia-react-ts/app/javascript/lib/appearance.ts +1 -2
  103. data/stubs/inertia-react-ts/app/javascript/lib/utils.ts +6 -0
  104. data/stubs/inertia-react-ts/app/javascript/types/global.d.ts +2 -2
  105. data/stubs/inertia-react-ts/app/javascript/types/index.d.ts +14 -0
  106. data/stubs/inertia-react-ts/components.json +21 -0
  107. data/stubs/inertia-react-ts/package.json +13 -0
  108. data/stubs/inertia-vue-ts/app/assets/tailwind/application.css +120 -1
  109. data/stubs/inertia-vue-ts/app/javascript/Components/AppLogo.vue +17 -0
  110. data/stubs/inertia-vue-ts/app/javascript/Components/AppShell.vue +12 -0
  111. data/stubs/inertia-vue-ts/app/javascript/Components/AppSidebar.vue +65 -0
  112. data/stubs/inertia-vue-ts/app/javascript/Components/AppSidebarHeader.vue +20 -0
  113. data/stubs/inertia-vue-ts/app/javascript/Components/Breadcrumbs.vue +32 -0
  114. data/stubs/inertia-vue-ts/app/javascript/Components/Heading.vue +15 -0
  115. data/stubs/inertia-vue-ts/app/javascript/Components/InputError.vue +1 -1
  116. data/stubs/inertia-vue-ts/app/javascript/Components/NavFooter.vue +33 -0
  117. data/stubs/inertia-vue-ts/app/javascript/Components/NavMain.vue +32 -0
  118. data/stubs/inertia-vue-ts/app/javascript/Components/NavUser.vue +40 -0
  119. data/stubs/inertia-vue-ts/app/javascript/Components/UserInfo.vue +25 -0
  120. data/stubs/inertia-vue-ts/app/javascript/Components/UserMenuContent.vue +47 -0
  121. data/stubs/inertia-vue-ts/app/javascript/Components/ui/avatar/Avatar.vue +18 -0
  122. data/stubs/inertia-vue-ts/app/javascript/Components/ui/avatar/AvatarFallback.vue +21 -0
  123. data/stubs/inertia-vue-ts/app/javascript/Components/ui/avatar/AvatarImage.vue +16 -0
  124. data/stubs/inertia-vue-ts/app/javascript/Components/ui/avatar/index.ts +3 -0
  125. data/stubs/inertia-vue-ts/app/javascript/Components/ui/breadcrumb/Breadcrumb.vue +17 -0
  126. data/stubs/inertia-vue-ts/app/javascript/Components/ui/breadcrumb/BreadcrumbEllipsis.vue +23 -0
  127. data/stubs/inertia-vue-ts/app/javascript/Components/ui/breadcrumb/BreadcrumbItem.vue +17 -0
  128. data/stubs/inertia-vue-ts/app/javascript/Components/ui/breadcrumb/BreadcrumbLink.vue +21 -0
  129. data/stubs/inertia-vue-ts/app/javascript/Components/ui/breadcrumb/BreadcrumbList.vue +17 -0
  130. data/stubs/inertia-vue-ts/app/javascript/Components/ui/breadcrumb/BreadcrumbPage.vue +20 -0
  131. data/stubs/inertia-vue-ts/app/javascript/Components/ui/breadcrumb/BreadcrumbSeparator.vue +22 -0
  132. data/stubs/inertia-vue-ts/app/javascript/Components/ui/breadcrumb/index.ts +7 -0
  133. data/stubs/inertia-vue-ts/app/javascript/Components/ui/button/Button.vue +31 -0
  134. data/stubs/inertia-vue-ts/app/javascript/Components/ui/button/index.ts +34 -0
  135. data/stubs/inertia-vue-ts/app/javascript/Components/ui/checkbox/Checkbox.vue +35 -0
  136. data/stubs/inertia-vue-ts/app/javascript/Components/ui/checkbox/index.ts +1 -0
  137. data/stubs/inertia-vue-ts/app/javascript/Components/ui/dialog/Dialog.vue +15 -0
  138. data/stubs/inertia-vue-ts/app/javascript/Components/ui/dialog/DialogClose.vue +12 -0
  139. data/stubs/inertia-vue-ts/app/javascript/Components/ui/dialog/DialogContent.vue +52 -0
  140. data/stubs/inertia-vue-ts/app/javascript/Components/ui/dialog/DialogDescription.vue +23 -0
  141. data/stubs/inertia-vue-ts/app/javascript/Components/ui/dialog/DialogFooter.vue +25 -0
  142. data/stubs/inertia-vue-ts/app/javascript/Components/ui/dialog/DialogHeader.vue +14 -0
  143. data/stubs/inertia-vue-ts/app/javascript/Components/ui/dialog/DialogOverlay.vue +26 -0
  144. data/stubs/inertia-vue-ts/app/javascript/Components/ui/dialog/DialogTitle.vue +23 -0
  145. data/stubs/inertia-vue-ts/app/javascript/Components/ui/dialog/DialogTrigger.vue +12 -0
  146. data/stubs/inertia-vue-ts/app/javascript/Components/ui/dialog/index.ts +9 -0
  147. data/stubs/inertia-vue-ts/app/javascript/Components/ui/dropdown-menu/DropdownMenu.vue +15 -0
  148. data/stubs/inertia-vue-ts/app/javascript/Components/ui/dropdown-menu/DropdownMenuContent.vue +37 -0
  149. data/stubs/inertia-vue-ts/app/javascript/Components/ui/dropdown-menu/DropdownMenuGroup.vue +12 -0
  150. data/stubs/inertia-vue-ts/app/javascript/Components/ui/dropdown-menu/DropdownMenuItem.vue +41 -0
  151. data/stubs/inertia-vue-ts/app/javascript/Components/ui/dropdown-menu/DropdownMenuLabel.vue +23 -0
  152. data/stubs/inertia-vue-ts/app/javascript/Components/ui/dropdown-menu/DropdownMenuSeparator.vue +23 -0
  153. data/stubs/inertia-vue-ts/app/javascript/Components/ui/dropdown-menu/DropdownMenuTrigger.vue +14 -0
  154. data/stubs/inertia-vue-ts/app/javascript/Components/ui/dropdown-menu/index.ts +7 -0
  155. data/stubs/inertia-vue-ts/app/javascript/Components/ui/input/Input.vue +50 -0
  156. data/stubs/inertia-vue-ts/app/javascript/Components/ui/input/index.ts +1 -0
  157. data/stubs/inertia-vue-ts/app/javascript/Components/ui/label/Label.vue +26 -0
  158. data/stubs/inertia-vue-ts/app/javascript/Components/ui/label/index.ts +1 -0
  159. data/stubs/inertia-vue-ts/app/javascript/Components/ui/separator/Separator.vue +29 -0
  160. data/stubs/inertia-vue-ts/app/javascript/Components/ui/separator/index.ts +1 -0
  161. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sheet/Sheet.vue +19 -0
  162. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sheet/SheetClose.vue +15 -0
  163. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sheet/SheetContent.vue +62 -0
  164. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sheet/SheetDescription.vue +21 -0
  165. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sheet/SheetFooter.vue +16 -0
  166. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sheet/SheetHeader.vue +15 -0
  167. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sheet/SheetOverlay.vue +21 -0
  168. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sheet/SheetTitle.vue +21 -0
  169. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sheet/SheetTrigger.vue +15 -0
  170. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sheet/index.ts +8 -0
  171. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/Sidebar.vue +96 -0
  172. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarContent.vue +18 -0
  173. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarFooter.vue +18 -0
  174. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarGroup.vue +18 -0
  175. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarGroupAction.vue +27 -0
  176. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarGroupContent.vue +18 -0
  177. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarGroupLabel.vue +25 -0
  178. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarHeader.vue +18 -0
  179. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarInput.vue +22 -0
  180. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarInset.vue +21 -0
  181. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarMenu.vue +18 -0
  182. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarMenuAction.vue +35 -0
  183. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarMenuBadge.vue +26 -0
  184. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarMenuButton.vue +48 -0
  185. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarMenuButtonChild.vue +36 -0
  186. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarMenuItem.vue +18 -0
  187. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarMenuSkeleton.vue +35 -0
  188. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarMenuSub.vue +22 -0
  189. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarMenuSubButton.vue +36 -0
  190. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarMenuSubItem.vue +18 -0
  191. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarProvider.vue +82 -0
  192. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarRail.vue +33 -0
  193. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarSeparator.vue +19 -0
  194. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/SidebarTrigger.vue +28 -0
  195. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/index.ts +60 -0
  196. data/stubs/inertia-vue-ts/app/javascript/Components/ui/sidebar/utils.ts +19 -0
  197. data/stubs/inertia-vue-ts/app/javascript/Components/ui/skeleton/Skeleton.vue +17 -0
  198. data/stubs/inertia-vue-ts/app/javascript/Components/ui/skeleton/index.ts +1 -0
  199. data/stubs/inertia-vue-ts/app/javascript/Components/ui/tooltip/Tooltip.vue +19 -0
  200. data/stubs/inertia-vue-ts/app/javascript/Components/ui/tooltip/TooltipContent.vue +34 -0
  201. data/stubs/inertia-vue-ts/app/javascript/Components/ui/tooltip/TooltipProvider.vue +14 -0
  202. data/stubs/inertia-vue-ts/app/javascript/Components/ui/tooltip/TooltipTrigger.vue +15 -0
  203. data/stubs/inertia-vue-ts/app/javascript/Components/ui/tooltip/index.ts +4 -0
  204. data/stubs/inertia-vue-ts/app/javascript/Layouts/AuthenticatedLayout.vue +17 -147
  205. data/stubs/inertia-vue-ts/app/javascript/Layouts/SettingsLayout.vue +57 -0
  206. data/stubs/inertia-vue-ts/app/javascript/Pages/Auth/ForgotPassword.vue +7 -7
  207. data/stubs/inertia-vue-ts/app/javascript/Pages/Auth/Login.vue +10 -12
  208. data/stubs/inertia-vue-ts/app/javascript/Pages/Auth/Register.vue +12 -14
  209. data/stubs/inertia-vue-ts/app/javascript/Pages/Auth/ResetPassword.vue +8 -10
  210. data/stubs/inertia-vue-ts/app/javascript/Pages/Auth/VerifyEmail.vue +3 -3
  211. data/stubs/inertia-vue-ts/app/javascript/Pages/Dashboard.vue +15 -10
  212. data/stubs/inertia-vue-ts/app/javascript/Pages/Settings/Appearance.vue +33 -0
  213. data/stubs/inertia-vue-ts/app/javascript/Pages/Settings/Partials/DeleteUserForm.vue +96 -0
  214. data/stubs/inertia-vue-ts/app/javascript/Pages/Settings/Partials/UpdateAppearanceForm.vue +42 -0
  215. data/stubs/inertia-vue-ts/app/javascript/Pages/{Profile → Settings}/Partials/UpdatePasswordForm.vue +12 -20
  216. data/stubs/inertia-vue-ts/app/javascript/Pages/{Profile → Settings}/Partials/UpdateProfileInformationForm.vue +12 -20
  217. data/stubs/inertia-vue-ts/app/javascript/Pages/Settings/Password.vue +33 -0
  218. data/stubs/inertia-vue-ts/app/javascript/Pages/Settings/Profile.vue +40 -0
  219. data/stubs/inertia-vue-ts/app/javascript/composables/useInitials.ts +29 -0
  220. data/stubs/inertia-vue-ts/app/javascript/lib/appearance.ts +1 -2
  221. data/stubs/inertia-vue-ts/app/javascript/lib/utils.ts +6 -0
  222. data/stubs/inertia-vue-ts/app/javascript/types/index.d.ts +14 -0
  223. data/stubs/inertia-vue-ts/app/javascript/types/vue-shims.d.ts +10 -0
  224. data/stubs/inertia-vue-ts/components.json +20 -0
  225. data/stubs/inertia-vue-ts/package.json +8 -1
  226. data/stubs/inertia-vue-ts/vite.config.ts +25 -1
  227. metadata +171 -46
  228. data/stubs/hotwire/app/components/nav_link_component.rb +0 -15
  229. data/stubs/hotwire/app/components/responsive_nav_link_component.rb +0 -15
  230. data/stubs/hotwire/app/controllers/password_controller.rb +0 -13
  231. data/stubs/hotwire/app/views/layouts/_navigation.html.erb +0 -82
  232. data/stubs/hotwire/app/views/profile/edit.html.erb +0 -29
  233. data/stubs/hotwire/app/views/profile/partials/_delete_user_form.html.erb +0 -14
  234. data/stubs/hotwire/app/views/profile/partials/_update_appearance_form.html.erb +0 -39
  235. data/stubs/inertia-common/app/controllers/password_controller.rb +0 -13
  236. data/stubs/inertia-react-ts/app/javascript/Components/Checkbox.tsx +0 -11
  237. data/stubs/inertia-react-ts/app/javascript/Components/DangerButton.tsx +0 -20
  238. data/stubs/inertia-react-ts/app/javascript/Components/Dropdown.tsx +0 -100
  239. data/stubs/inertia-react-ts/app/javascript/Components/InputLabel.tsx +0 -14
  240. data/stubs/inertia-react-ts/app/javascript/Components/Modal.tsx +0 -66
  241. data/stubs/inertia-react-ts/app/javascript/Components/NavLink.tsx +0 -21
  242. data/stubs/inertia-react-ts/app/javascript/Components/PrimaryButton.tsx +0 -20
  243. data/stubs/inertia-react-ts/app/javascript/Components/ResponsiveNavLink.tsx +0 -21
  244. data/stubs/inertia-react-ts/app/javascript/Components/SecondaryButton.tsx +0 -22
  245. data/stubs/inertia-react-ts/app/javascript/Components/TextInput.tsx +0 -32
  246. data/stubs/inertia-react-ts/app/javascript/Pages/Profile/Edit.tsx +0 -37
  247. data/stubs/inertia-react-ts/app/javascript/Pages/Profile/Partials/DeleteUserForm.tsx +0 -102
  248. data/stubs/inertia-react-ts/app/javascript/Pages/Profile/Partials/UpdateAppearanceForm.tsx +0 -47
  249. data/stubs/inertia-vue-ts/app/javascript/Components/Checkbox.vue +0 -29
  250. data/stubs/inertia-vue-ts/app/javascript/Components/DangerButton.vue +0 -7
  251. data/stubs/inertia-vue-ts/app/javascript/Components/Dropdown.vue +0 -75
  252. data/stubs/inertia-vue-ts/app/javascript/Components/DropdownLink.vue +0 -16
  253. data/stubs/inertia-vue-ts/app/javascript/Components/InputLabel.vue +0 -12
  254. data/stubs/inertia-vue-ts/app/javascript/Components/Modal.vue +0 -109
  255. data/stubs/inertia-vue-ts/app/javascript/Components/NavLink.vue +0 -21
  256. data/stubs/inertia-vue-ts/app/javascript/Components/PrimaryButton.vue +0 -7
  257. data/stubs/inertia-vue-ts/app/javascript/Components/ResponsiveNavLink.vue +0 -21
  258. data/stubs/inertia-vue-ts/app/javascript/Components/SecondaryButton.vue +0 -19
  259. data/stubs/inertia-vue-ts/app/javascript/Components/TextInput.vue +0 -23
  260. data/stubs/inertia-vue-ts/app/javascript/Pages/Profile/Edit.vue +0 -43
  261. data/stubs/inertia-vue-ts/app/javascript/Pages/Profile/Partials/DeleteUserForm.vue +0 -99
  262. data/stubs/inertia-vue-ts/app/javascript/Pages/Profile/Partials/UpdateAppearanceForm.vue +0 -47
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cc7bad365960dce77e78e930ea414a8bdcf3d03887393200bfde19a7c0299fe4
4
- data.tar.gz: 7f3a5dffd4210452b1a3da62604e1d4b060c9659b9d596c10778db420b5fc034
3
+ metadata.gz: e59ed35aebe912f3109e4cf1937daa83a61d11235e2052d4d148c4d67f29fd52
4
+ data.tar.gz: 0c5844feaa1dd744199e50af4aecbb0f53ca8f5ea2a6060b6362b124f16a8762
5
5
  SHA512:
6
- metadata.gz: cc6441a4bae8a2e94d4662a00d2c4dc302cfe9b425b15f112f7c9fdb1d0cf218d860fdcf503bf2d5f4bb92e66b0bf1b7cccf517b663606a2535a48a8a53d0853
7
- data.tar.gz: ed22d4a753d6a7acdc48d59ca3ef0c76838129a467811ca52c608db891e9c9ecde792ebb37765bed82a635837ce51498dca10607da8a3ddf76374aef7b4bf986
6
+ metadata.gz: a02c56cd8f4e53405776b9d1240b54b5fe6b6a71f01339a90fe83a04f0864ebef3f68a8d5a6f41e2a80ae9b3d4d70ce376c4a165ec4c38d9354bfe874b969d66
7
+ data.tar.gz: 512b952e164d688a07204f3d3137b39215b5753bdeab6a01543562d3a9156960d0e383ffdd0798270aa75440187fcaa939d756bab7c6316637ac8d00d71cfa52
data/CHANGELOG.md CHANGED
@@ -6,6 +6,65 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [2.0.1] - 2026-10-22
10
+
11
+ ### Fixed
12
+
13
+ - `kaze install` run through `bundle exec` no longer fails to build gems with native extensions. Bundler exports `RUBYOPT` and `BUNDLE_*` so child processes join its bundle, which is wrong for a nested bundler call: the `bundle add` that kaze shells out to inherited them, and `extconf.rb` then loaded `bundler/setup` and tried to resolve a lockfile needing the very gem it was about to compile, failing with `Could not find bcrypt-… in locally installed gems`. The bundler shell-outs now run inside `Bundler.with_unbundled_env`.
14
+
15
+ The README's own instructions — `gem install kaze` then `kaze install` — were never affected, because that runs outside bundler. This reached `bundle exec kaze install`, which is how kaze's CI invokes it and how anyone who adds kaze to their Gemfile would.
16
+
17
+ ## [2.0.0] - 2026-10-22
18
+
19
+ ### Added
20
+
21
+ - The frontend is rebuilt on [shadcn/ui](https://ui.shadcn.com), following Laravel's [react](https://github.com/laravel/react-starter-kit) and [vue](https://github.com/laravel/vue-starter-kit) starter kits ([#27](https://github.com/cuonggt/kaze/pull/27), [#28](https://github.com/cuonggt/kaze/pull/28), [#29](https://github.com/cuonggt/kaze/pull/29), [#30](https://github.com/cuonggt/kaze/pull/30), [#31](https://github.com/cuonggt/kaze/pull/31), [#32](https://github.com/cuonggt/kaze/pull/32), [#33](https://github.com/cuonggt/kaze/pull/33), [#34](https://github.com/cuonggt/kaze/pull/34)). shadcn copies source into the project rather than shipping a package, which is how Kaze already works, so the primitives are vendored into `Components/ui` and are yours to edit.
22
+
23
+ A design token layer comes with it. Colour is now `bg-background`, `text-muted-foreground`, `border-sidebar-border` and so on, resolving through oklch variables defined once per stylesheet, instead of `bg-white dark:bg-gray-800` repeated per element. A `components.json` records the aliases so `npx shadcn@latest add <component>` lands in the right place — note that Kaze uses a capitalised `@/Components`, which that file already reflects.
24
+
25
+ Thirteen primitives for each Inertia stack: button, input, label, checkbox, dialog, dropdown-menu, separator, skeleton, sheet, tooltip, avatar, breadcrumb and sidebar, plus a `cn` helper in `lib/utils`. React builds on Radix, Vue on reka-ui.
26
+
27
+ - A collapsible sidebar replaces the top navigation bar in all three stacks. It collapses to icons, becomes a drawer below `md`, and remembers its state in a `sidebar_state` cookie that the server reads back, so the first paint matches the last choice rather than flashing open.
28
+
29
+ There is no shadcn for Hotwire, so its sidebar is hand-rolled in Alpine and ERB against the same tokens, with the lucide icons inlined through a new `IconComponent` since Hotwire has no npm.
30
+
31
+ - A settings area at `/settings/profile`, `/settings/password` and `/settings/appearance`, replacing the single profile page ([#35](https://github.com/cuonggt/kaze/pull/35), [#36](https://github.com/cuonggt/kaze/pull/36)). `/settings` redirects to the profile page. Each has its own breadcrumb and page title, and the delete-account form stays on the profile page.
32
+
33
+ ### Changed
34
+
35
+ - **The profile page has moved and its routes with it.** `/profile` is now `/settings/profile`, and password updates post to `/settings/password` rather than to the profile page. The route helper names are unchanged — `profile_edit_path` still exists, it just points somewhere new — so an application that uses the helpers rather than hard-coded paths needs no edit. `password_edit_path` and `appearance_edit_path` are new. `ProfileController` and `PasswordController` become `Settings::ProfileController`, `Settings::PasswordController` and `Settings::AppearanceController`.
36
+
37
+ - The `dark` variant is redefined, in all three stylesheets:
38
+
39
+ ```css
40
+ /* was */ @custom-variant dark (&:where(.dark, .dark *));
41
+ /* now */ @custom-variant dark (&:is(.dark *));
42
+ ```
43
+
44
+ `:where()` contributes no specificity, so a `dark:` utility tied with its light counterpart and won or lost on the order Tailwind happened to emit them in — `dark:text-black` lost to `text-white` on the same element. `:is()` gives the variant the higher specificity, which is what Laravel's kits use. If you have written a `dark:` override that was silently losing, it will now apply; if you wrote one that depended on losing, it will not.
45
+
46
+ - The Vue stack pins `typescript` to `^5.9.3`, down from `^7.0.2` ([#30](https://github.com/cuonggt/kaze/pull/30)). `@vue/compiler-sfc` needs filesystem access to resolve prop types that cross a package boundary, which the vendored primitives do, and the resolver it uses is not compatible with TypeScript 7. The React stack stays on 7.
47
+
48
+ ### Removed
49
+
50
+ - The hand-rolled components the shadcn primitives replace: `PrimaryButton`, `SecondaryButton`, `DangerButton`, `TextInput`, `InputLabel`, `Checkbox`, `Modal`, `Dropdown`, `NavLink` and `ResponsiveNavLink` from both Inertia stacks, `DropdownLink` from the Vue stack, and `NavLinkComponent` and `ResponsiveNavLinkComponent` from Hotwire. The buttons, inputs and labels map onto `Button`, `Input` and `Label` with a variant; the navigation links have no successor, because the sidebar replaced what used them.
51
+
52
+ ### Fixed
53
+
54
+ - `kaze install` exits non-zero when it fails. Every step is guarded, so the installer already stopped at the first failure and withheld its success message — but the process still exited 0, so a script or CI job saw a green step over a half-installed application. This is the same class of silent failure as [#11](https://github.com/cuonggt/kaze/issues/11), in the exit code rather than the output. Anything that runs `kaze install` unattended and currently passes over a failure will now fail, which is the point.
55
+
56
+ ### Upgrading
57
+
58
+ Kaze scaffolds rather than vendors, so nothing changes under an existing application on its own. Re-running `kaze install` over one built with 1.4.0 or earlier does **not** remove what this release replaced — the installer copies files in and never deletes, deliberately, since by then they are yours and may be edited. The replaced files are left behind for you to delete:
59
+
60
+ | stack | left behind |
61
+ |---|---|
62
+ | all | `app/controllers/profile_controller.rb`, `app/controllers/password_controller.rb` |
63
+ | react, vue | `app/javascript/Components/{PrimaryButton,SecondaryButton,DangerButton,TextInput,InputLabel,Checkbox,Modal,Dropdown,NavLink,ResponsiveNavLink}`, `DropdownLink` on vue, and the whole of `app/javascript/Pages/Profile/` |
64
+ | hotwire | `app/components/{nav_link,responsive_nav_link}_component.rb`, `app/views/layouts/_navigation.html.erb`, and the whole of `app/views/profile/` |
65
+
66
+ On the React stack this is not optional: `tsc` typechecks every file under `app/javascript`, and the stale `Pages/Profile/Edit.tsx` still passes a `header` prop that the layout no longer takes, so the build fails until that directory is gone. Vue and Hotwire build and run with the stale files in place — they are dead weight rather than a break, since nothing routes to them any more.
67
+
9
68
  ## [1.4.0] - 2026-10-21
10
69
 
11
70
  ### Added
@@ -126,7 +185,9 @@ Together these take a freshly scaffolded Inertia application from seven npm advi
126
185
 
127
186
  Releases before 1.0.0 are not catalogued here. See the [tags](https://github.com/cuonggt/kaze/tags) for their history.
128
187
 
129
- [Unreleased]: https://github.com/cuonggt/kaze/compare/v1.4.0...HEAD
188
+ [Unreleased]: https://github.com/cuonggt/kaze/compare/v2.0.1...HEAD
189
+ [2.0.1]: https://github.com/cuonggt/kaze/compare/v2.0.0...v2.0.1
190
+ [2.0.0]: https://github.com/cuonggt/kaze/compare/v1.4.0...v2.0.0
130
191
  [1.4.0]: https://github.com/cuonggt/kaze/compare/v1.3.1...v1.4.0
131
192
  [1.3.1]: https://github.com/cuonggt/kaze/compare/v1.3.0...v1.3.1
132
193
  [1.3.0]: https://github.com/cuonggt/kaze/compare/v1.2.1...v1.3.0
data/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  Heavily inspired by [Laravel Breeze](https://github.com/laravel/breeze), this gem offers authentication and application starter kits to give you a head start building your new [Rails](https://rubyonrails.org) application. These kits automatically scaffold your application with the routes, controllers, and views you need to register and authenticate your application's users.
6
6
 
7
- [Kaze](https://github.com/cuonggt/kaze) is a opinionated minimal, simple implementation of all authentication features that a modern web application should have, including login, registration, password reset, email verification. In addition, Kaze includes a simple "profile" page where the user may update their name, email address, and password.
7
+ [Kaze](https://github.com/cuonggt/kaze) is a opinionated minimal, simple implementation of all authentication features that a modern web application should have, including login, registration, password reset, email verification. In addition, Kaze includes a settings area where the user may update their name and email address, change their password, choose an appearance, and delete their account.
8
8
 
9
9
  Kaze provides scaffolding options based on [Hotwire](https://hotwired.dev) or [Inertia](https://inertiajs.com), with the choice of using Vue or React for the Inertia-based scaffolding.
10
10
 
@@ -13,6 +13,16 @@ class Kaze::Commands::App::BaseStack
13
13
  @bundle_command ||= File.exist?("#{Dir.pwd}/bin/bundle") ? "#{Dir.pwd}/bin/bundle" : 'bundle'
14
14
  end
15
15
 
16
+ # Bundler exports RUBYOPT and BUNDLE_* so that child processes join its
17
+ # bundle. That is wrong for a nested bundler call: when kaze itself is run
18
+ # through `bundle exec`, a `bundle add` it shells out to inherits them, and
19
+ # any gem with a native extension fails to build, because extconf.rb loads
20
+ # bundler/setup and tries to resolve a lockfile needing the very gem it is
21
+ # about to compile. Shell out to bundler with that environment removed.
22
+ def run_bundle_command(command)
23
+ Bundler.with_unbundled_env { run_command(command) }
24
+ end
25
+
16
26
  def install_gems(gems = [], group = nil)
17
27
  installed_gems = Bundler::Definition.build("#{Dir.pwd}/Gemfile", nil, {}).dependencies.map(&:name)
18
28
 
@@ -20,7 +30,7 @@ class Kaze::Commands::App::BaseStack
20
30
 
21
31
  return true if gem_being_installed.empty?
22
32
 
23
- status = run_command("#{bundle_command} add #{gem_being_installed.join(" ")}#{group ? " --group \"#{group}\"" : ""}")
33
+ status = run_bundle_command("#{bundle_command} add #{gem_being_installed.join(" ")}#{group ? " --group \"#{group}\"" : ""}")
24
34
 
25
35
  status.success?
26
36
  end
@@ -32,7 +42,7 @@ class Kaze::Commands::App::BaseStack
32
42
 
33
43
  return true if gems_being_removed.empty?
34
44
 
35
- status = run_command("#{bundle_command} remove #{gems_being_removed.join(" ")}")
45
+ status = run_bundle_command("#{bundle_command} remove #{gems_being_removed.join(" ")}")
36
46
 
37
47
  status.success?
38
48
  end
@@ -3,7 +3,7 @@ class Kaze::Commands::App::HotwireStack < Kaze::Commands::App::BaseStack
3
3
  # Gems...
4
4
  return unless remove_gems([ 'sprockets-rails', 'stimulus-rails' ])
5
5
  return unless install_gems([ 'propshaft', 'view_component', 'turbo-rails', 'dotenv', 'bcrypt' ])
6
- return unless run_command("#{bundle_command} add tailwindcss-rails --version=\"~> 4.0\"").success?
6
+ return unless run_bundle_command("#{bundle_command} add tailwindcss-rails --version=\"~> 4.0\"").success?
7
7
  return unless install_gems([ 'hotwire-livereload' ], 'development')
8
8
  return unless install_gems([ 'factory_bot_rails', 'faker' ], 'development, test')
9
9
 
@@ -60,5 +60,7 @@ class Kaze::Commands::App::HotwireStack < Kaze::Commands::App::BaseStack
60
60
 
61
61
  say ''
62
62
  say 'Kaze scaffolding installed successfully.', :green
63
+
64
+ true
63
65
  end
64
66
  end
@@ -3,7 +3,7 @@ class Kaze::Commands::App::InertiaReactStack < Kaze::Commands::App::BaseStack
3
3
  # Gems...
4
4
  return unless remove_gems([ 'sprockets-rails', 'turbo-rails', 'stimulus-rails' ])
5
5
  return unless install_gems([ 'propshaft', 'inertia_rails', 'vite_rails', 'dotenv', 'bcrypt', 'js-routes' ])
6
- return unless run_command("#{bundle_command} add tailwindcss-rails --version=\"~> 4.0\"").success?
6
+ return unless run_bundle_command("#{bundle_command} add tailwindcss-rails --version=\"~> 4.0\"").success?
7
7
  return unless install_gems([ 'factory_bot_rails', 'faker' ], 'development, test')
8
8
 
9
9
  # NPM Packages...
@@ -56,6 +56,7 @@ class Kaze::Commands::App::InertiaReactStack < Kaze::Commands::App::BaseStack
56
56
  FileUtils.copy_file("#{stubs_path}/inertia-common/config/vite.json", "#{Dir.pwd}/config/vite.json")
57
57
  ensure_directory_exists("#{Dir.pwd}/config/initializers")
58
58
  FileUtils.copy_file("#{stubs_path}/inertia-common/config/initializers/inertia_rails.rb", "#{Dir.pwd}/config/initializers/inertia_rails.rb")
59
+ FileUtils.copy_file("#{stubs_path}/inertia-react-ts/components.json", "#{Dir.pwd}/components.json")
59
60
  FileUtils.copy_file("#{stubs_path}/inertia-react-ts/tsconfig.json", "#{Dir.pwd}/tsconfig.json")
60
61
  FileUtils.copy_file("#{stubs_path}/inertia-react-ts/vite.config.ts", "#{Dir.pwd}/vite.config.ts")
61
62
  FileUtils.copy_file("#{stubs_path}/default/bin/dev", "#{Dir.pwd}/bin/dev")
@@ -63,7 +64,8 @@ class Kaze::Commands::App::InertiaReactStack < Kaze::Commands::App::BaseStack
63
64
  FileUtils.copy_file("#{stubs_path}/default/Procfile.dev", "#{Dir.pwd}/Procfile.dev")
64
65
  replace_in_file('#vite: bin/vite dev', 'vite: bin/vite dev', "#{Dir.pwd}/Procfile.dev")
65
66
  run_command("#{Dir.pwd}/bin/rails generate js_routes:middleware")
66
- return unless run_command("#{Dir.pwd}/bin/rails tailwindcss:build").success?
67
+ # The CSS is built by `npm run build` below, once node_modules exists --
68
+ # the stylesheet imports tw-animate-css, which cannot resolve before then.
67
69
 
68
70
  say ''
69
71
  say 'Installing and building Node dependencies.', :magenta
@@ -83,5 +85,7 @@ class Kaze::Commands::App::InertiaReactStack < Kaze::Commands::App::BaseStack
83
85
 
84
86
  say ''
85
87
  say 'Kaze scaffolding installed successfully.', :green
88
+
89
+ true
86
90
  end
87
91
  end
@@ -3,7 +3,7 @@ class Kaze::Commands::App::InertiaVueStack < Kaze::Commands::App::BaseStack
3
3
  # Gems...
4
4
  return unless remove_gems([ 'sprockets-rails', 'turbo-rails', 'stimulus-rails' ])
5
5
  return unless install_gems([ 'propshaft', 'inertia_rails', 'vite_rails', 'dotenv', 'bcrypt', 'js-routes' ])
6
- return unless run_command("#{bundle_command} add tailwindcss-rails --version=\"~> 4.0\"").success?
6
+ return unless run_bundle_command("#{bundle_command} add tailwindcss-rails --version=\"~> 4.0\"").success?
7
7
  return unless install_gems([ 'factory_bot_rails', 'faker' ], 'development, test')
8
8
 
9
9
  # NPM Packages...
@@ -56,6 +56,7 @@ class Kaze::Commands::App::InertiaVueStack < Kaze::Commands::App::BaseStack
56
56
  FileUtils.copy_file("#{stubs_path}/inertia-common/config/vite.json", "#{Dir.pwd}/config/vite.json")
57
57
  ensure_directory_exists("#{Dir.pwd}/config/initializers")
58
58
  FileUtils.copy_file("#{stubs_path}/inertia-common/config/initializers/inertia_rails.rb", "#{Dir.pwd}/config/initializers/inertia_rails.rb")
59
+ FileUtils.copy_file("#{stubs_path}/inertia-vue-ts/components.json", "#{Dir.pwd}/components.json")
59
60
  FileUtils.copy_file("#{stubs_path}/inertia-vue-ts/tsconfig.json", "#{Dir.pwd}/tsconfig.json")
60
61
  FileUtils.copy_file("#{stubs_path}/inertia-vue-ts/vite.config.ts", "#{Dir.pwd}/vite.config.ts")
61
62
  FileUtils.copy_file("#{stubs_path}/default/bin/dev", "#{Dir.pwd}/bin/dev")
@@ -63,7 +64,8 @@ class Kaze::Commands::App::InertiaVueStack < Kaze::Commands::App::BaseStack
63
64
  FileUtils.copy_file("#{stubs_path}/default/Procfile.dev", "#{Dir.pwd}/Procfile.dev")
64
65
  replace_in_file('#vite: bin/vite dev', 'vite: bin/vite dev', "#{Dir.pwd}/Procfile.dev")
65
66
  run_command("#{Dir.pwd}/bin/rails generate js_routes:middleware")
66
- return unless run_command("#{Dir.pwd}/bin/rails tailwindcss:build").success?
67
+ # The CSS is built by `npm run build` below, once node_modules exists --
68
+ # the stylesheet imports tw-animate-css, which cannot resolve before then.
67
69
 
68
70
  say ''
69
71
  say 'Installing and building Node dependencies.', :magenta
@@ -83,5 +85,7 @@ class Kaze::Commands::App::InertiaVueStack < Kaze::Commands::App::BaseStack
83
85
 
84
86
  say ''
85
87
  say 'Kaze scaffolding installed successfully.', :green
88
+
89
+ true
86
90
  end
87
91
  end
@@ -7,11 +7,18 @@ class Kaze::Commands::App < Thor
7
7
 
8
8
  desc 'install [STACK]', 'Install the Kaze controllers and resources. Supported stacks: hotwire, react, vue.'
9
9
  def install(stack = 'hotwire')
10
- return say 'Kaze must be run in a new Rails application.', :red unless File.exist?("#{Dir.pwd}/bin/rails")
10
+ unless File.exist?("#{Dir.pwd}/bin/rails")
11
+ say 'Kaze must be run in a new Rails application.', :red
12
+ exit 1
13
+ end
11
14
 
12
- StackFactory.make(stack.to_sym).install
15
+ # The stack installers stop at the first step that fails, so a falsy return
16
+ # means the application is half-installed. Exiting non-zero keeps that from
17
+ # passing silently in a script or a CI job.
18
+ exit 1 unless StackFactory.make(stack.to_sym).install
13
19
  rescue => e
14
20
  say e.message, :red
21
+ exit 1
15
22
  end
16
23
 
17
24
  desc 'version', 'Show Kaze version'
data/lib/kaze/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Kaze
2
- VERSION = '1.4.0'
2
+ VERSION = '2.0.1'
3
3
  end
@@ -23,9 +23,16 @@ Rails.application.routes.draw do
23
23
 
24
24
  get 'dashboard', to: 'dashboard#index', as: :dashboard
25
25
 
26
- get 'profile', to: 'profile#edit', as: :profile_edit
27
- patch 'profile', to: 'profile#update', as: :profile_update
28
- delete 'profile', to: 'profile#destroy', as: :profile_destroy
26
+ get 'settings', to: redirect('/settings/profile')
29
27
 
30
- put 'password', to: 'password#update', as: :password_update
28
+ scope 'settings', module: 'settings' do
29
+ get 'profile', to: 'profile#edit', as: :profile_edit
30
+ patch 'profile', to: 'profile#update', as: :profile_update
31
+ delete 'profile', to: 'profile#destroy', as: :profile_destroy
32
+
33
+ get 'password', to: 'password#edit', as: :password_edit
34
+ put 'password', to: 'password#update', as: :password_update
35
+
36
+ get 'appearance', to: 'appearance#edit', as: :appearance_edit
37
+ end
31
38
  end
@@ -0,0 +1,11 @@
1
+ require 'test_helper'
2
+
3
+ class AppearanceTest < ActionDispatch::IntegrationTest
4
+ test 'appearance page is displayed' do
5
+ user = FactoryBot.create(:user)
6
+
7
+ acting_as(user).get appearance_edit_path
8
+
9
+ assert_response :success
10
+ end
11
+ end
@@ -1,6 +1,14 @@
1
1
  require 'test_helper'
2
2
 
3
3
  class PasswordUpdateTest < ActionDispatch::IntegrationTest
4
+ test 'password page is displayed' do
5
+ user = FactoryBot.create(:user)
6
+
7
+ acting_as(user).get password_edit_path
8
+
9
+ assert_response :success
10
+ end
11
+
4
12
  test 'password can be updated' do
5
13
  user = FactoryBot.create(:user)
6
14
 
@@ -10,7 +18,7 @@ class PasswordUpdateTest < ActionDispatch::IntegrationTest
10
18
  password_confirmation: 'new-password'
11
19
  }
12
20
 
13
- assert_redirected_to profile_edit_path
21
+ assert_redirected_to password_edit_path
14
22
  assert BCrypt::Password.new(user.reload.password_digest).is_password?('new-password')
15
23
  end
16
24
 
@@ -3,7 +3,125 @@
3
3
  /* Drive `dark:` from a `dark` class rather than prefers-color-scheme, so the
4
4
  appearance preference can override the operating system. The layouts apply
5
5
  the class before first paint. */
6
- @custom-variant dark (&:where(.dark, .dark *));
6
+ @custom-variant dark (&:is(.dark *));
7
+
8
+ @theme inline {
9
+ --color-background: var(--background);
10
+ --color-foreground: var(--foreground);
11
+
12
+ --color-card: var(--card);
13
+ --color-card-foreground: var(--card-foreground);
14
+
15
+ --color-popover: var(--popover);
16
+ --color-popover-foreground: var(--popover-foreground);
17
+
18
+ --color-primary: var(--primary);
19
+ --color-primary-foreground: var(--primary-foreground);
20
+
21
+ --color-secondary: var(--secondary);
22
+ --color-secondary-foreground: var(--secondary-foreground);
23
+
24
+ --color-muted: var(--muted);
25
+ --color-muted-foreground: var(--muted-foreground);
26
+
27
+ --color-accent: var(--accent);
28
+ --color-accent-foreground: var(--accent-foreground);
29
+
30
+ --color-destructive: var(--destructive);
31
+ --color-destructive-foreground: var(--destructive-foreground);
32
+
33
+ --color-border: var(--border);
34
+ --color-input: var(--input);
35
+ --color-ring: var(--ring);
36
+
37
+ --color-sidebar: var(--sidebar);
38
+ --color-sidebar-foreground: var(--sidebar-foreground);
39
+ --color-sidebar-primary: var(--sidebar-primary);
40
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
41
+ --color-sidebar-accent: var(--sidebar-accent);
42
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
43
+ --color-sidebar-border: var(--sidebar-border);
44
+ --color-sidebar-ring: var(--sidebar-ring);
45
+
46
+ --radius-sm: calc(var(--radius) - 4px);
47
+ --radius-md: calc(var(--radius) - 2px);
48
+ --radius-lg: var(--radius);
49
+ --radius-xl: calc(var(--radius) + 4px);
50
+ }
51
+
52
+ :root {
53
+ --radius: 0.625rem;
54
+
55
+ --background: oklch(1 0 0);
56
+ --foreground: oklch(0.145 0 0);
57
+ --card: oklch(1 0 0);
58
+ --card-foreground: oklch(0.145 0 0);
59
+ --popover: oklch(1 0 0);
60
+ --popover-foreground: oklch(0.145 0 0);
61
+ --primary: oklch(0.205 0 0);
62
+ --primary-foreground: oklch(0.985 0 0);
63
+ --secondary: oklch(0.97 0 0);
64
+ --secondary-foreground: oklch(0.205 0 0);
65
+ --muted: oklch(0.97 0 0);
66
+ --muted-foreground: oklch(0.556 0 0);
67
+ --accent: oklch(0.97 0 0);
68
+ --accent-foreground: oklch(0.205 0 0);
69
+ --destructive: oklch(0.577 0.245 27.325);
70
+ --destructive-foreground: oklch(0.985 0 0);
71
+ --border: oklch(0.922 0 0);
72
+ --input: oklch(0.922 0 0);
73
+ --ring: oklch(0.87 0 0);
74
+
75
+ --sidebar: oklch(0.985 0 0);
76
+ --sidebar-foreground: oklch(0.145 0 0);
77
+ --sidebar-primary: oklch(0.205 0 0);
78
+ --sidebar-primary-foreground: oklch(0.985 0 0);
79
+ --sidebar-accent: oklch(0.97 0 0);
80
+ --sidebar-accent-foreground: oklch(0.205 0 0);
81
+ --sidebar-border: oklch(0.922 0 0);
82
+ --sidebar-ring: oklch(0.87 0 0);
83
+ }
84
+
85
+ .dark {
86
+ --background: oklch(0.145 0 0);
87
+ --foreground: oklch(0.985 0 0);
88
+ --card: oklch(0.145 0 0);
89
+ --card-foreground: oklch(0.985 0 0);
90
+ --popover: oklch(0.145 0 0);
91
+ --popover-foreground: oklch(0.985 0 0);
92
+ --primary: oklch(0.985 0 0);
93
+ --primary-foreground: oklch(0.205 0 0);
94
+ --secondary: oklch(0.269 0 0);
95
+ --secondary-foreground: oklch(0.985 0 0);
96
+ --muted: oklch(0.269 0 0);
97
+ --muted-foreground: oklch(0.708 0 0);
98
+ --accent: oklch(0.269 0 0);
99
+ --accent-foreground: oklch(0.985 0 0);
100
+ --destructive: oklch(0.396 0.141 25.723);
101
+ --destructive-foreground: oklch(0.985 0 0);
102
+ --border: oklch(0.269 0 0);
103
+ --input: oklch(0.269 0 0);
104
+ --ring: oklch(0.439 0 0);
105
+
106
+ --sidebar: oklch(0.205 0 0);
107
+ --sidebar-foreground: oklch(0.985 0 0);
108
+ --sidebar-primary: oklch(0.985 0 0);
109
+ --sidebar-primary-foreground: oklch(0.985 0 0);
110
+ --sidebar-accent: oklch(0.269 0 0);
111
+ --sidebar-accent-foreground: oklch(0.985 0 0);
112
+ --sidebar-border: oklch(0.269 0 0);
113
+ --sidebar-ring: oklch(0.439 0 0);
114
+ }
115
+
116
+ @layer base {
117
+ * {
118
+ @apply border-border outline-ring/50;
119
+ }
120
+
121
+ body {
122
+ @apply bg-background text-foreground;
123
+ }
124
+ }
7
125
 
8
126
  @plugin "@tailwindcss/forms";
9
127
 
@@ -0,0 +1,32 @@
1
+ # The trail in the sidebar header. Takes [{ title:, href: }], where the last
2
+ # entry is the current page and is not a link.
3
+ class BreadcrumbsComponent < ViewComponent::Base
4
+ erb_template <<~ERB
5
+ <nav aria-label="breadcrumb">
6
+ <ol class="flex flex-wrap items-center gap-1.5 text-sm break-words text-muted-foreground sm:gap-2.5">
7
+ <% @items.each_with_index do |item, index| %>
8
+ <li class="inline-flex items-center gap-1.5">
9
+ <% if index == @items.length - 1 %>
10
+ <span class="font-normal text-foreground" aria-current="page"><%= item[:title] %></span>
11
+ <% else %>
12
+ <a href="<%= item[:href] %>" class="transition-colors hover:text-foreground"><%= item[:title] %></a>
13
+ <% end %>
14
+ </li>
15
+ <% unless index == @items.length - 1 %>
16
+ <li role="presentation" aria-hidden="true">
17
+ <%= render(IconComponent.new('chevron-right', classes: 'size-3.5')) %>
18
+ </li>
19
+ <% end %>
20
+ <% end %>
21
+ </ol>
22
+ </nav>
23
+ ERB
24
+
25
+ def initialize(items)
26
+ @items = items
27
+ end
28
+
29
+ def render?
30
+ @items.present?
31
+ end
32
+ end
@@ -7,7 +7,7 @@ class DangerButtonComponent < ViewComponent::Base
7
7
 
8
8
  def initialize(attributes = {})
9
9
  attributes[:type] = attributes[:type] || 'submit'
10
- attributes[:class] = "inline-flex items-center px-4 py-2 bg-red-600 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-red-500 active:bg-red-700 focus:outline-hidden focus:ring-2 focus:ring-red-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 transition ease-in-out duration-150#{" #{attributes[:class]}" if attributes[:class]}"
10
+ attributes[:class] = "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-[color,box-shadow] disabled:pointer-events-none disabled:opacity-50 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] h-9 px-4 py-2 bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40#{" #{attributes[:class]}" if attributes[:class]}"
11
11
  @attributes = attributes.map { |key, attribute| "#{key}=\"#{attribute}\"" }
12
12
  end
13
13
  end
@@ -9,10 +9,10 @@
9
9
  x-transition:leave="transition ease-in duration-75"
10
10
  x-transition:leave-start="opacity-100 scale-100"
11
11
  x-transition:leave-end="opacity-0 scale-95"
12
- class="absolute z-50 mt-2 <%= @width %> rounded-md shadow-lg <%= @alignment_classes %>"
12
+ class="absolute z-50 <%= @side_classes %> <%= @width %> rounded-md shadow-lg <%= @alignment_classes %>"
13
13
  style="display: none;"
14
14
  x-on:click="open = false">
15
- <div class="rounded-md ring-1 ring-black ring-opacity-5 <%= @content_classes %>">
15
+ <div class="rounded-md border shadow-md <%= @content_classes %>">
16
16
  <%= content %>
17
17
  </div>
18
18
  </div>
@@ -1,7 +1,10 @@
1
1
  class DropdownComponent < ViewComponent::Base
2
2
  renders_one :trigger
3
3
 
4
- def initialize(align: 'right', width: '48', content_classes: 'py-1 bg-white dark:bg-gray-700')
4
+ # `side` is which way the menu opens off the trigger. The sidebar's user row
5
+ # sits at the foot of the viewport, so it opens upwards; everything else
6
+ # opens downwards.
7
+ def initialize(align: 'right', side: 'bottom', width: '48', content_classes: 'p-1 bg-popover text-popover-foreground')
5
8
  if align == 'left'
6
9
  @alignment_classes = 'ltr:origin-top-left rtl:origin-top-right start-0'
7
10
  elsif align == 'top'
@@ -9,6 +12,7 @@ class DropdownComponent < ViewComponent::Base
9
12
  else
10
13
  @alignment_classes = 'ltr:origin-top-right rtl:origin-top-left end-0'
11
14
  end
15
+ @side_classes = side == 'top' ? 'bottom-full mb-2' : 'mt-2'
12
16
  @width = width == '48' ? 'w-48' : width
13
17
  @content_classes = content_classes
14
18
  end
@@ -6,7 +6,7 @@ class DropdownLinkComponent < ViewComponent::Base
6
6
  ERB
7
7
 
8
8
  def initialize(attributes = {})
9
- attributes[:class] = "block w-full px-4 py-2 text-start text-sm leading-5 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 focus:outline-hidden focus:bg-gray-100 dark:focus:bg-gray-800 transition duration-150 ease-in-out#{" #{attributes[:class]}" if attributes[:class]}"
9
+ attributes[:class] = "relative flex w-full cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-start text-sm outline-hidden select-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground#{" #{attributes[:class]}" if attributes[:class]}"
10
10
  @attributes = attributes.map { |key, attribute| "#{key}=\"#{attribute}\"" }
11
11
  end
12
12
  end
@@ -0,0 +1,19 @@
1
+ # A section heading with an optional description. `small` is the variant the
2
+ # settings pages use above each form.
3
+ class HeadingComponent < ViewComponent::Base
4
+ erb_template <<~ERB
5
+ <header class="<%= @header_classes %>">
6
+ <h2 class="<%= @title_classes %>"><%= @title %></h2>
7
+ <% if @description %>
8
+ <p class="text-sm text-muted-foreground"><%= @description %></p>
9
+ <% end %>
10
+ </header>
11
+ ERB
12
+
13
+ def initialize(title:, description: nil, variant: 'default')
14
+ @title = title
15
+ @description = description
16
+ @header_classes = variant == 'small' ? '' : 'mb-8 space-y-0.5'
17
+ @title_classes = variant == 'small' ? 'mb-0.5 text-base font-medium' : 'text-xl font-semibold tracking-tight'
18
+ end
19
+ end
@@ -0,0 +1,30 @@
1
+ # Inlines the lucide icons the layouts use. The Inertia stacks get these from
2
+ # the lucide package; there is no npm here, so the paths are carried by hand.
3
+ # Each entry is the body of lucide's 24x24 outline svg.
4
+ class IconComponent < ViewComponent::Base
5
+ PATHS = {
6
+ "book-open" => '<path d="M12 5v16" /><path d="M20.001 19A2 2 0 0022 17V5a2 2 0 00-1.999-2L16 3.002A5 5 0 0012 5a5 5 0 00-4-2H4a2 2 0 00-2 2v12a2 2 0 001.999 2H8a5 5 0 014 2 5 5 0 014-2z" />',
7
+ "chevron-right" => '<path d="m9 18 6-6-6-6" />',
8
+ "chevrons-up-down" => '<path d="m7 15 5 5 5-5" /><path d="m7 9 5-5 5 5" />',
9
+ "folder-git-2" => '<path d="M18 19a5 5 0 0 1-5-5v8" /><path d="M9 20H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2v5" /><circle cx="13" cy="12" r="2" /><circle cx="20" cy="19" r="2" />',
10
+ "layout-grid" => '<rect width="7" height="7" x="3" y="3" rx="1" /><rect width="7" height="7" x="14" y="3" rx="1" /><rect width="7" height="7" x="14" y="14" rx="1" /><rect width="7" height="7" x="3" y="14" rx="1" />',
11
+ "log-out" => '<path d="m16 17 5-5-5-5" /><path d="M21 12H9" /><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" />',
12
+ "monitor" => '<rect width="20" height="14" x="2" y="3" rx="2" /><line x1="8" x2="16" y1="21" y2="21" /><line x1="12" x2="12" y1="17" y2="21" />',
13
+ "moon" => '<path d="M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401" />',
14
+ "panel-left-close" => '<rect width="18" height="18" x="3" y="3" rx="2" /><path d="M9 3v18" /><path d="m16 15-3-3 3-3" />',
15
+ "panel-left-open" => '<rect width="18" height="18" x="3" y="3" rx="2" /><path d="M9 3v18" /><path d="m14 9 3 3-3 3" />',
16
+ "settings" => '<path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /><circle cx="12" cy="12" r="3" />',
17
+ "sun" => '<circle cx="12" cy="12" r="4" /><path d="M12 2v2" /><path d="M12 20v2" /><path d="m4.93 4.93 1.41 1.41" /><path d="m17.66 17.66 1.41 1.41" /><path d="M2 12h2" /><path d="M20 12h2" /><path d="m6.34 17.66-1.41 1.41" /><path d="m19.07 4.93-1.41 1.41" />'
18
+ }.freeze
19
+
20
+ erb_template <<~ERB
21
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
22
+ stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
23
+ class="<%= @classes %>" aria-hidden="true"><%= @path %></svg>
24
+ ERB
25
+
26
+ def initialize(name, classes: "size-4")
27
+ @path = PATHS.fetch(name) { raise ArgumentError, "unknown icon: #{name}" }.html_safe
28
+ @classes = classes
29
+ end
30
+ end
@@ -1,7 +1,7 @@
1
1
  class InputErrorComponent < ViewComponent::Base
2
2
  erb_template <<~ERB
3
3
  <div <%= sanitize @attributes.join(" ") %>>
4
- <p class="text-sm text-red-600 dark:text-red-400">
4
+ <p class="text-sm text-destructive">
5
5
  <%= @message %>
6
6
  </p>
7
7
  </div>
@@ -1,13 +1,13 @@
1
1
  class InputLabelComponent < ViewComponent::Base
2
2
  erb_template <<~ERB
3
- <a <%= sanitize @attributes.join(" ") %>>
3
+ <label <%= sanitize @attributes.join(" ") %>>
4
4
  <%= @value ? @value : content %>
5
- </a>
5
+ </label>
6
6
  ERB
7
7
 
8
8
  def initialize(attributes = {})
9
9
  @value = attributes[:value] || nil
10
- attributes[:class] = "block font-medium text-sm text-gray-700 dark:text-gray-300#{" #{attributes[:class]}" if attributes[:class]}"
10
+ attributes[:class] = "flex items-center gap-2 text-sm leading-none font-medium select-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50#{" #{attributes[:class]}" if attributes[:class]}"
11
11
  @attributes = attributes.without(:value).map { |key, attribute| "#{key}=\"#{attribute}\"" }
12
12
  end
13
13
  end
@@ -42,11 +42,11 @@
42
42
  x-transition:leave="ease-in duration-200"
43
43
  x-transition:leave-start="opacity-100"
44
44
  x-transition:leave-end="opacity-0">
45
- <div class="absolute inset-0 bg-gray-500 dark:bg-gray-900 opacity-75"></div>
45
+ <div class="absolute inset-0 bg-black/80"></div>
46
46
  </div>
47
47
  <div
48
48
  x-show="show"
49
- class="mb-6 bg-white dark:bg-gray-800 rounded-lg overflow-hidden shadow-xl transform transition-all sm:w-full <%= @max_width %> sm:mx-auto"
49
+ class="mb-6 bg-background border rounded-lg overflow-hidden shadow-lg transform transition-all sm:w-full <%= @max_width %> sm:mx-auto"
50
50
  x-transition:enter="ease-out duration-300"
51
51
  x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
52
52
  x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"