velvetkit 0.1.0.beta
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 +7 -0
- data/CHANGELOG.md +16 -0
- data/LICENSE.txt +21 -0
- data/README.md +197 -0
- data/lib/generators/velvetkit/add/add_generator.rb +111 -0
- data/lib/generators/velvetkit/ai/ai_generator.rb +78 -0
- data/lib/generators/velvetkit/helpers.rb +64 -0
- data/lib/generators/velvetkit/install/install_generator.rb +108 -0
- data/lib/velvetkit/namespace.rb +57 -0
- data/lib/velvetkit/reference.rb +108 -0
- data/lib/velvetkit/registry.rb +162 -0
- data/lib/velvetkit/version.rb +5 -0
- data/lib/velvetkit.rb +28 -0
- data/registry/app/assets/tailwind/velvetkit.css +342 -0
- data/registry/app/components/ui/accordion/content.html.erb +5 -0
- data/registry/app/components/ui/accordion/content.rb +34 -0
- data/registry/app/components/ui/accordion/item.html.erb +1 -0
- data/registry/app/components/ui/accordion/item.rb +40 -0
- data/registry/app/components/ui/accordion/trigger.html.erb +1 -0
- data/registry/app/components/ui/accordion/trigger.rb +37 -0
- data/registry/app/components/ui/accordion.html.erb +7 -0
- data/registry/app/components/ui/accordion.rb +34 -0
- data/registry/app/components/ui/alert/description.html.erb +1 -0
- data/registry/app/components/ui/alert/description.rb +12 -0
- data/registry/app/components/ui/alert/title.html.erb +1 -0
- data/registry/app/components/ui/alert/title.rb +12 -0
- data/registry/app/components/ui/alert.html.erb +16 -0
- data/registry/app/components/ui/alert.rb +55 -0
- data/registry/app/components/ui/avatar/fallback.html.erb +1 -0
- data/registry/app/components/ui/avatar/fallback.rb +12 -0
- data/registry/app/components/ui/avatar/image.rb +20 -0
- data/registry/app/components/ui/avatar.html.erb +1 -0
- data/registry/app/components/ui/avatar.rb +56 -0
- data/registry/app/components/ui/avatar_group.html.erb +1 -0
- data/registry/app/components/ui/avatar_group.rb +35 -0
- data/registry/app/components/ui/badge.html.erb +1 -0
- data/registry/app/components/ui/badge.rb +38 -0
- data/registry/app/components/ui/banner.html.erb +10 -0
- data/registry/app/components/ui/banner.rb +47 -0
- data/registry/app/components/ui/base.rb +53 -0
- data/registry/app/components/ui/breadcrumb/auto_ellipsis.html.erb +23 -0
- data/registry/app/components/ui/breadcrumb/auto_ellipsis.rb +12 -0
- data/registry/app/components/ui/breadcrumb/ellipsis.html.erb +18 -0
- data/registry/app/components/ui/breadcrumb/ellipsis.rb +8 -0
- data/registry/app/components/ui/breadcrumb/item.html.erb +1 -0
- data/registry/app/components/ui/breadcrumb/item.rb +12 -0
- data/registry/app/components/ui/breadcrumb/item_slot.html.erb +10 -0
- data/registry/app/components/ui/breadcrumb/item_slot.rb +47 -0
- data/registry/app/components/ui/breadcrumb/list.html.erb +1 -0
- data/registry/app/components/ui/breadcrumb/list.rb +12 -0
- data/registry/app/components/ui/breadcrumb/separator.html.erb +1 -0
- data/registry/app/components/ui/breadcrumb/separator.rb +12 -0
- data/registry/app/components/ui/breadcrumb.html.erb +30 -0
- data/registry/app/components/ui/breadcrumb.rb +83 -0
- data/registry/app/components/ui/button.html.erb +1 -0
- data/registry/app/components/ui/button.rb +81 -0
- data/registry/app/components/ui/button_to.html.erb +1 -0
- data/registry/app/components/ui/button_to.rb +33 -0
- data/registry/app/components/ui/card/content.html.erb +1 -0
- data/registry/app/components/ui/card/content.rb +12 -0
- data/registry/app/components/ui/card/description.html.erb +1 -0
- data/registry/app/components/ui/card/description.rb +12 -0
- data/registry/app/components/ui/card/footer.html.erb +1 -0
- data/registry/app/components/ui/card/footer.rb +12 -0
- data/registry/app/components/ui/card/header.html.erb +1 -0
- data/registry/app/components/ui/card/header.rb +15 -0
- data/registry/app/components/ui/card/title.html.erb +1 -0
- data/registry/app/components/ui/card/title.rb +12 -0
- data/registry/app/components/ui/card.html.erb +1 -0
- data/registry/app/components/ui/card.rb +18 -0
- data/registry/app/components/ui/checkbox.html.erb +18 -0
- data/registry/app/components/ui/checkbox.rb +133 -0
- data/registry/app/components/ui/data_grid.html.erb +2 -0
- data/registry/app/components/ui/data_grid.rb +84 -0
- data/registry/app/components/ui/date_picker.html.erb +70 -0
- data/registry/app/components/ui/date_picker.rb +111 -0
- data/registry/app/components/ui/dialog/close.html.erb +7 -0
- data/registry/app/components/ui/dialog/close.rb +53 -0
- data/registry/app/components/ui/dialog/content.html.erb +26 -0
- data/registry/app/components/ui/dialog/content.rb +102 -0
- data/registry/app/components/ui/dialog/description.html.erb +1 -0
- data/registry/app/components/ui/dialog/description.rb +16 -0
- data/registry/app/components/ui/dialog/footer.html.erb +1 -0
- data/registry/app/components/ui/dialog/footer.rb +14 -0
- data/registry/app/components/ui/dialog/header.html.erb +1 -0
- data/registry/app/components/ui/dialog/header.rb +16 -0
- data/registry/app/components/ui/dialog/title.html.erb +1 -0
- data/registry/app/components/ui/dialog/title.rb +16 -0
- data/registry/app/components/ui/dialog/trigger.html.erb +5 -0
- data/registry/app/components/ui/dialog/trigger.rb +75 -0
- data/registry/app/components/ui/dialog.html.erb +3 -0
- data/registry/app/components/ui/dialog.rb +41 -0
- data/registry/app/components/ui/divider.html.erb +9 -0
- data/registry/app/components/ui/divider.rb +41 -0
- data/registry/app/components/ui/icon.html.erb +1 -0
- data/registry/app/components/ui/icon.rb +41 -0
- data/registry/app/components/ui/image.html.erb +1 -0
- data/registry/app/components/ui/image.rb +74 -0
- data/registry/app/components/ui/input.html.erb +9 -0
- data/registry/app/components/ui/input.rb +127 -0
- data/registry/app/components/ui/input_group/addon.html.erb +6 -0
- data/registry/app/components/ui/input_group/addon.rb +51 -0
- data/registry/app/components/ui/input_group/field.rb +26 -0
- data/registry/app/components/ui/input_group.html.erb +5 -0
- data/registry/app/components/ui/input_group.rb +55 -0
- data/registry/app/components/ui/label.html.erb +2 -0
- data/registry/app/components/ui/label.rb +48 -0
- data/registry/app/components/ui/link.html.erb +1 -0
- data/registry/app/components/ui/link.rb +64 -0
- data/registry/app/components/ui/menu/content.html.erb +5 -0
- data/registry/app/components/ui/menu/content.rb +106 -0
- data/registry/app/components/ui/menu/group.html.erb +1 -0
- data/registry/app/components/ui/menu/group.rb +19 -0
- data/registry/app/components/ui/menu/item.html.erb +1 -0
- data/registry/app/components/ui/menu/item.rb +68 -0
- data/registry/app/components/ui/menu/label.html.erb +1 -0
- data/registry/app/components/ui/menu/label.rb +33 -0
- data/registry/app/components/ui/menu/separator.html.erb +1 -0
- data/registry/app/components/ui/menu/separator.rb +20 -0
- data/registry/app/components/ui/menu/trigger.html.erb +1 -0
- data/registry/app/components/ui/menu/trigger.rb +29 -0
- data/registry/app/components/ui/menu.html.erb +3 -0
- data/registry/app/components/ui/menu.rb +23 -0
- data/registry/app/components/ui/native_select.html.erb +16 -0
- data/registry/app/components/ui/native_select.rb +109 -0
- data/registry/app/components/ui/pagination.html.erb +34 -0
- data/registry/app/components/ui/pagination.rb +62 -0
- data/registry/app/components/ui/password_input.html.erb +20 -0
- data/registry/app/components/ui/password_input.rb +94 -0
- data/registry/app/components/ui/popover/content.html.erb +12 -0
- data/registry/app/components/ui/popover/content.rb +83 -0
- data/registry/app/components/ui/popover/trigger.html.erb +1 -0
- data/registry/app/components/ui/popover/trigger.rb +29 -0
- data/registry/app/components/ui/popover.html.erb +3 -0
- data/registry/app/components/ui/popover.rb +24 -0
- data/registry/app/components/ui/radio_group/item.html.erb +18 -0
- data/registry/app/components/ui/radio_group/item.rb +97 -0
- data/registry/app/components/ui/radio_group.html.erb +1 -0
- data/registry/app/components/ui/radio_group.rb +35 -0
- data/registry/app/components/ui/select.html.erb +60 -0
- data/registry/app/components/ui/select.rb +181 -0
- data/registry/app/components/ui/sheet/close.rb +4 -0
- data/registry/app/components/ui/sheet/content.html.erb +24 -0
- data/registry/app/components/ui/sheet/content.rb +97 -0
- data/registry/app/components/ui/sheet/trigger.rb +4 -0
- data/registry/app/components/ui/sheet.html.erb +3 -0
- data/registry/app/components/ui/sheet.rb +37 -0
- data/registry/app/components/ui/sidebar/content.html.erb +1 -0
- data/registry/app/components/ui/sidebar/content.rb +18 -0
- data/registry/app/components/ui/sidebar/footer.html.erb +3 -0
- data/registry/app/components/ui/sidebar/footer.rb +16 -0
- data/registry/app/components/ui/sidebar/group.html.erb +1 -0
- data/registry/app/components/ui/sidebar/group.rb +19 -0
- data/registry/app/components/ui/sidebar/group_label.html.erb +1 -0
- data/registry/app/components/ui/sidebar/group_label.rb +18 -0
- data/registry/app/components/ui/sidebar/header.html.erb +1 -0
- data/registry/app/components/ui/sidebar/header.rb +16 -0
- data/registry/app/components/ui/sidebar/inset.html.erb +1 -0
- data/registry/app/components/ui/sidebar/inset.rb +17 -0
- data/registry/app/components/ui/sidebar/menu.html.erb +1 -0
- data/registry/app/components/ui/sidebar/menu.rb +18 -0
- data/registry/app/components/ui/sidebar/menu_button.html.erb +1 -0
- data/registry/app/components/ui/sidebar/menu_button.rb +30 -0
- data/registry/app/components/ui/sidebar/menu_item.html.erb +1 -0
- data/registry/app/components/ui/sidebar/menu_item.rb +23 -0
- data/registry/app/components/ui/sidebar/trigger.html.erb +1 -0
- data/registry/app/components/ui/sidebar/trigger.rb +24 -0
- data/registry/app/components/ui/sidebar/wrapper.html.erb +1 -0
- data/registry/app/components/ui/sidebar/wrapper.rb +40 -0
- data/registry/app/components/ui/sidebar.html.erb +11 -0
- data/registry/app/components/ui/sidebar.rb +29 -0
- data/registry/app/components/ui/spinner.html.erb +1 -0
- data/registry/app/components/ui/spinner.rb +20 -0
- data/registry/app/components/ui/switch.html.erb +12 -0
- data/registry/app/components/ui/switch.rb +109 -0
- data/registry/app/components/ui/table/cell.html.erb +1 -0
- data/registry/app/components/ui/table/cell.rb +16 -0
- data/registry/app/components/ui/table/column.html.erb +1 -0
- data/registry/app/components/ui/table/column.rb +18 -0
- data/registry/app/components/ui/table/head.html.erb +1 -0
- data/registry/app/components/ui/table/head.rb +13 -0
- data/registry/app/components/ui/table/row.html.erb +1 -0
- data/registry/app/components/ui/table/row.rb +26 -0
- data/registry/app/components/ui/table.html.erb +4 -0
- data/registry/app/components/ui/table.rb +45 -0
- data/registry/app/components/ui/tabs/content.html.erb +1 -0
- data/registry/app/components/ui/tabs/content.rb +43 -0
- data/registry/app/components/ui/tabs/list.html.erb +1 -0
- data/registry/app/components/ui/tabs/list.rb +28 -0
- data/registry/app/components/ui/tabs/trigger.html.erb +1 -0
- data/registry/app/components/ui/tabs/trigger.rb +91 -0
- data/registry/app/components/ui/tabs.html.erb +3 -0
- data/registry/app/components/ui/tabs.rb +27 -0
- data/registry/app/components/ui/text.html.erb +1 -0
- data/registry/app/components/ui/text.rb +48 -0
- data/registry/app/components/ui/theme_toggle.html.erb +9 -0
- data/registry/app/components/ui/theme_toggle.rb +63 -0
- data/registry/app/components/ui/tooltip/content.html.erb +1 -0
- data/registry/app/components/ui/tooltip/content.rb +65 -0
- data/registry/app/components/ui/tooltip/trigger.html.erb +1 -0
- data/registry/app/components/ui/tooltip/trigger.rb +33 -0
- data/registry/app/components/ui/tooltip.html.erb +3 -0
- data/registry/app/components/ui/tooltip.rb +24 -0
- data/registry/app/components/ui/typography/blockquote.html.erb +1 -0
- data/registry/app/components/ui/typography/blockquote.rb +15 -0
- data/registry/app/components/ui/typography/code.html.erb +1 -0
- data/registry/app/components/ui/typography/code.rb +25 -0
- data/registry/app/components/ui/typography/h1.rb +7 -0
- data/registry/app/components/ui/typography/h2.rb +7 -0
- data/registry/app/components/ui/typography/h3.rb +7 -0
- data/registry/app/components/ui/typography/h4.rb +7 -0
- data/registry/app/components/ui/typography/large.rb +7 -0
- data/registry/app/components/ui/typography/lead.rb +7 -0
- data/registry/app/components/ui/typography/muted.rb +7 -0
- data/registry/app/components/ui/typography/small.rb +7 -0
- data/registry/app/javascript/ag_grid.ts +26 -0
- data/registry/app/javascript/cally.ts +8 -0
- data/registry/app/javascript/controllers/ui/_internal/use_dismiss.ts +88 -0
- data/registry/app/javascript/controllers/ui/_internal/use_floating.ts +112 -0
- data/registry/app/javascript/controllers/ui/accordion_controller.ts +100 -0
- data/registry/app/javascript/controllers/ui/alert_controller.ts +7 -0
- data/registry/app/javascript/controllers/ui/banner_controller.ts +7 -0
- data/registry/app/javascript/controllers/ui/data_grid_controller.ts +198 -0
- data/registry/app/javascript/controllers/ui/date_picker_controller.ts +141 -0
- data/registry/app/javascript/controllers/ui/dialog_controller.ts +70 -0
- data/registry/app/javascript/controllers/ui/menu_controller.ts +201 -0
- data/registry/app/javascript/controllers/ui/password_visibility_controller.ts +43 -0
- data/registry/app/javascript/controllers/ui/popover_controller.ts +108 -0
- data/registry/app/javascript/controllers/ui/select_controller.ts +274 -0
- data/registry/app/javascript/controllers/ui/sidebar_controller.ts +64 -0
- data/registry/app/javascript/controllers/ui/tabs_controller.ts +74 -0
- data/registry/app/javascript/controllers/ui/theme_controller.ts +101 -0
- data/registry/app/javascript/controllers/ui/tooltip_controller.ts +102 -0
- data/registry/app/javascript/velvetkit_icons.ts +58 -0
- data/registry/app/views/layouts/_theme_script.html.erb +19 -0
- data/registry/components.yml +102 -0
- data/registry/config/locales/velvetkit.en.yml +56 -0
- data/registry/docs/agents.md +62 -0
- data/registry/test/components/previews/ui/accordion_preview/faq_style.html.erb +20 -0
- data/registry/test/components/previews/ui/accordion_preview/multiple.html.erb +27 -0
- data/registry/test/components/previews/ui/accordion_preview/single.html.erb +22 -0
- data/registry/test/components/previews/ui/accordion_preview.rb +31 -0
- data/registry/test/components/previews/ui/alert_preview/title_only.html.erb +12 -0
- data/registry/test/components/previews/ui/alert_preview/variants.html.erb +22 -0
- data/registry/test/components/previews/ui/alert_preview/with_icon.html.erb +12 -0
- data/registry/test/components/previews/ui/alert_preview/with_link.html.erb +18 -0
- data/registry/test/components/previews/ui/alert_preview.rb +34 -0
- data/registry/test/components/previews/ui/avatar_group_preview/default.html.erb +11 -0
- data/registry/test/components/previews/ui/avatar_group_preview/overflow.html.erb +23 -0
- data/registry/test/components/previews/ui/avatar_group_preview/small.html.erb +12 -0
- data/registry/test/components/previews/ui/avatar_group_preview.rb +23 -0
- data/registry/test/components/previews/ui/avatar_preview/colors.html.erb +8 -0
- data/registry/test/components/previews/ui/avatar_preview/shapes.html.erb +10 -0
- data/registry/test/components/previews/ui/avatar_preview/sizes.html.erb +8 -0
- data/registry/test/components/previews/ui/avatar_preview/with_image.html.erb +10 -0
- data/registry/test/components/previews/ui/avatar_preview.rb +24 -0
- data/registry/test/components/previews/ui/badge_preview/sizes.html.erb +5 -0
- data/registry/test/components/previews/ui/badge_preview/variants.html.erb +8 -0
- data/registry/test/components/previews/ui/badge_preview/with_icon.html.erb +14 -0
- data/registry/test/components/previews/ui/badge_preview.rb +20 -0
- data/registry/test/components/previews/ui/banner_preview/introduction.html.erb +9 -0
- data/registry/test/components/previews/ui/banner_preview/promotion.html.erb +9 -0
- data/registry/test/components/previews/ui/banner_preview/with_illustration.html.erb +12 -0
- data/registry/test/components/previews/ui/banner_preview.rb +25 -0
- data/registry/test/components/previews/ui/breadcrumb_preview/collapsed.html.erb +50 -0
- data/registry/test/components/previews/ui/breadcrumb_preview/default.html.erb +8 -0
- data/registry/test/components/previews/ui/breadcrumb_preview/long_path.html.erb +8 -0
- data/registry/test/components/previews/ui/breadcrumb_preview/medium.html.erb +7 -0
- data/registry/test/components/previews/ui/breadcrumb_preview/with_avatar.html.erb +8 -0
- data/registry/test/components/previews/ui/breadcrumb_preview.rb +66 -0
- data/registry/test/components/previews/ui/button_preview/disabled.html.erb +39 -0
- data/registry/test/components/previews/ui/button_preview/icon_only.html.erb +17 -0
- data/registry/test/components/previews/ui/button_preview/loading.html.erb +12 -0
- data/registry/test/components/previews/ui/button_preview/sizes.html.erb +6 -0
- data/registry/test/components/previews/ui/button_preview/variants.html.erb +8 -0
- data/registry/test/components/previews/ui/button_preview/with_both_icons.html.erb +12 -0
- data/registry/test/components/previews/ui/button_preview/with_leading_icon.html.erb +22 -0
- data/registry/test/components/previews/ui/button_preview/with_trailing_icon.html.erb +14 -0
- data/registry/test/components/previews/ui/button_preview.rb +65 -0
- data/registry/test/components/previews/ui/button_to_preview/states.html.erb +8 -0
- data/registry/test/components/previews/ui/button_to_preview/variants.html.erb +5 -0
- data/registry/test/components/previews/ui/button_to_preview/with_icon.html.erb +11 -0
- data/registry/test/components/previews/ui/button_to_preview.rb +21 -0
- data/registry/test/components/previews/ui/card_preview/complete.html.erb +31 -0
- data/registry/test/components/previews/ui/card_preview/default.html.erb +7 -0
- data/registry/test/components/previews/ui/card_preview/settings_form.html.erb +25 -0
- data/registry/test/components/previews/ui/card_preview/with_footer.html.erb +13 -0
- data/registry/test/components/previews/ui/card_preview/with_header.html.erb +11 -0
- data/registry/test/components/previews/ui/card_preview.rb +44 -0
- data/registry/test/components/previews/ui/checkbox_preview/in_form.html.erb +9 -0
- data/registry/test/components/previews/ui/checkbox_preview/states.html.erb +22 -0
- data/registry/test/components/previews/ui/checkbox_preview/with_label.html.erb +5 -0
- data/registry/test/components/previews/ui/checkbox_preview.rb +27 -0
- data/registry/test/components/previews/ui/data_grid_preview.rb +18 -0
- data/registry/test/components/previews/ui/date_picker_preview/default.html.erb +3 -0
- data/registry/test/components/previews/ui/date_picker_preview/states.html.erb +7 -0
- data/registry/test/components/previews/ui/date_picker_preview.rb +13 -0
- data/registry/test/components/previews/ui/dialog_preview/confirmation.html.erb +11 -0
- data/registry/test/components/previews/ui/dialog_preview/custom_trigger.html.erb +16 -0
- data/registry/test/components/previews/ui/dialog_preview/default.html.erb +15 -0
- data/registry/test/components/previews/ui/dialog_preview/long_content.html.erb +16 -0
- data/registry/test/components/previews/ui/dialog_preview/playground.html.erb +12 -0
- data/registry/test/components/previews/ui/dialog_preview/scrollable_body.html.erb +29 -0
- data/registry/test/components/previews/ui/dialog_preview/sizes.html.erb +46 -0
- data/registry/test/components/previews/ui/dialog_preview/without_close_button.html.erb +11 -0
- data/registry/test/components/previews/ui/dialog_preview/without_title.html.erb +13 -0
- data/registry/test/components/previews/ui/dialog_preview.rb +38 -0
- data/registry/test/components/previews/ui/divider_preview/horizontal.html.erb +5 -0
- data/registry/test/components/previews/ui/divider_preview/vertical.html.erb +9 -0
- data/registry/test/components/previews/ui/divider_preview/with_label.html.erb +5 -0
- data/registry/test/components/previews/ui/divider_preview.rb +19 -0
- data/registry/test/components/previews/ui/icon_preview/common.html.erb +8 -0
- data/registry/test/components/previews/ui/icon_preview/sizes.html.erb +7 -0
- data/registry/test/components/previews/ui/icon_preview.rb +20 -0
- data/registry/test/components/previews/ui/image_preview/radii.html.erb +8 -0
- data/registry/test/components/previews/ui/image_preview/sizing.html.erb +9 -0
- data/registry/test/components/previews/ui/image_preview.rb +16 -0
- data/registry/test/components/previews/ui/input_group_preview/addons.html.erb +29 -0
- data/registry/test/components/previews/ui/input_group_preview/playground.html.erb +12 -0
- data/registry/test/components/previews/ui/input_group_preview.rb +17 -0
- data/registry/test/components/previews/ui/input_preview/sizes.html.erb +5 -0
- data/registry/test/components/previews/ui/input_preview/states.html.erb +7 -0
- data/registry/test/components/previews/ui/input_preview/with_labels.html.erb +10 -0
- data/registry/test/components/previews/ui/input_preview.rb +33 -0
- data/registry/test/components/previews/ui/label_preview/sizes.html.erb +6 -0
- data/registry/test/components/previews/ui/label_preview/states.html.erb +6 -0
- data/registry/test/components/previews/ui/label_preview/weights.html.erb +5 -0
- data/registry/test/components/previews/ui/label_preview.rb +22 -0
- data/registry/test/components/previews/ui/link_preview/sizes.html.erb +5 -0
- data/registry/test/components/previews/ui/link_preview/variants.html.erb +8 -0
- data/registry/test/components/previews/ui/link_preview/with_icons.html.erb +11 -0
- data/registry/test/components/previews/ui/link_preview.rb +20 -0
- data/registry/test/components/previews/ui/menu_preview/positions.html.erb +122 -0
- data/registry/test/components/previews/ui/menu_preview/variants.html.erb +218 -0
- data/registry/test/components/previews/ui/menu_preview.rb +26 -0
- data/registry/test/components/previews/ui/native_select_preview/sizes.html.erb +6 -0
- data/registry/test/components/previews/ui/native_select_preview/states.html.erb +6 -0
- data/registry/test/components/previews/ui/native_select_preview.rb +23 -0
- data/registry/test/components/previews/ui/pagination_preview/states.html.erb +15 -0
- data/registry/test/components/previews/ui/pagination_preview.rb +15 -0
- data/registry/test/components/previews/ui/password_input_preview/states.html.erb +19 -0
- data/registry/test/components/previews/ui/password_input_preview.rb +21 -0
- data/registry/test/components/previews/ui/popover_preview/default.html.erb +7 -0
- data/registry/test/components/previews/ui/popover_preview/positions.html.erb +30 -0
- data/registry/test/components/previews/ui/popover_preview/with_close_button.html.erb +8 -0
- data/registry/test/components/previews/ui/popover_preview/with_rich_content.html.erb +17 -0
- data/registry/test/components/previews/ui/popover_preview/without_title.html.erb +9 -0
- data/registry/test/components/previews/ui/popover_preview.rb +32 -0
- data/registry/test/components/previews/ui/radio_group_preview/default.html.erb +10 -0
- data/registry/test/components/previews/ui/radio_group_preview/disabled.html.erb +17 -0
- data/registry/test/components/previews/ui/radio_group_preview/with_required.html.erb +10 -0
- data/registry/test/components/previews/ui/radio_group_preview.rb +25 -0
- data/registry/test/components/previews/ui/select_preview/default.html.erb +4 -0
- data/registry/test/components/previews/ui/select_preview/long_list.html.erb +5 -0
- data/registry/test/components/previews/ui/select_preview/multiple.html.erb +6 -0
- data/registry/test/components/previews/ui/select_preview/sizes.html.erb +5 -0
- data/registry/test/components/previews/ui/select_preview/states.html.erb +6 -0
- data/registry/test/components/previews/ui/select_preview.rb +45 -0
- data/registry/test/components/previews/ui/sheet_preview/bottom.html.erb +12 -0
- data/registry/test/components/previews/ui/sheet_preview/left.html.erb +10 -0
- data/registry/test/components/previews/ui/sheet_preview/playground.html.erb +15 -0
- data/registry/test/components/previews/ui/sheet_preview/right.html.erb +11 -0
- data/registry/test/components/previews/ui/sheet_preview/top.html.erb +8 -0
- data/registry/test/components/previews/ui/sheet_preview.rb +22 -0
- data/registry/test/components/previews/ui/sidebar_preview/_brand.html.erb +14 -0
- data/registry/test/components/previews/ui/sidebar_preview/_shell.html.erb +60 -0
- data/registry/test/components/previews/ui/sidebar_preview/collapsible.html.erb +55 -0
- data/registry/test/components/previews/ui/sidebar_preview/default.html.erb +17 -0
- data/registry/test/components/previews/ui/sidebar_preview/with_dropdown_header.html.erb +33 -0
- data/registry/test/components/previews/ui/sidebar_preview/with_groups.html.erb +22 -0
- data/registry/test/components/previews/ui/sidebar_preview.rb +15 -0
- data/registry/test/components/previews/ui/spinner_preview/in_buttons.html.erb +7 -0
- data/registry/test/components/previews/ui/spinner_preview/sizes.html.erb +7 -0
- data/registry/test/components/previews/ui/spinner_preview.rb +19 -0
- data/registry/test/components/previews/ui/switch_preview/default.html.erb +4 -0
- data/registry/test/components/previews/ui/switch_preview/sizes.html.erb +5 -0
- data/registry/test/components/previews/ui/switch_preview/states.html.erb +7 -0
- data/registry/test/components/previews/ui/switch_preview.rb +26 -0
- data/registry/test/components/previews/ui/table_preview/customers.html.erb +15 -0
- data/registry/test/components/previews/ui/table_preview/empty.html.erb +8 -0
- data/registry/test/components/previews/ui/table_preview/with_caption.html.erb +14 -0
- data/registry/test/components/previews/ui/table_preview.rb +12 -0
- data/registry/test/components/previews/ui/tabs_preview/default.html.erb +39 -0
- data/registry/test/components/previews/ui/tabs_preview/pill_variant.html.erb +18 -0
- data/registry/test/components/previews/ui/tabs_preview/settings_form.html.erb +42 -0
- data/registry/test/components/previews/ui/tabs_preview/with_icons.html.erb +34 -0
- data/registry/test/components/previews/ui/tabs_preview.rb +33 -0
- data/registry/test/components/previews/ui/text_preview/sizes.html.erb +10 -0
- data/registry/test/components/previews/ui/text_preview/tones.html.erb +5 -0
- data/registry/test/components/previews/ui/text_preview/weights.html.erb +6 -0
- data/registry/test/components/previews/ui/text_preview.rb +26 -0
- data/registry/test/components/previews/ui/theme_toggle_preview/in_a_header.html.erb +14 -0
- data/registry/test/components/previews/ui/theme_toggle_preview/palette.html.erb +47 -0
- data/registry/test/components/previews/ui/theme_toggle_preview.rb +15 -0
- data/registry/test/components/previews/ui/tooltip_preview/default.html.erb +8 -0
- data/registry/test/components/previews/ui/tooltip_preview/positions.html.erb +34 -0
- data/registry/test/components/previews/ui/tooltip_preview/with_button.html.erb +8 -0
- data/registry/test/components/previews/ui/tooltip_preview/with_icon.html.erb +10 -0
- data/registry/test/components/previews/ui/tooltip_preview.rb +29 -0
- data/registry/test/components/previews/ui/typography_preview/article.html.erb +11 -0
- data/registry/test/components/previews/ui/typography_preview/code_and_quotes.html.erb +6 -0
- data/registry/test/components/previews/ui/typography_preview/headings.html.erb +6 -0
- data/registry/test/components/previews/ui/typography_preview/text_styles.html.erb +7 -0
- data/registry/test/components/previews/ui/typography_preview.rb +23 -0
- data/registry/test/components/ui/accordion/content_test.rb +64 -0
- data/registry/test/components/ui/accordion/item_test.rb +61 -0
- data/registry/test/components/ui/accordion/trigger_test.rb +80 -0
- data/registry/test/components/ui/accordion_test.rb +96 -0
- data/registry/test/components/ui/alert/description_test.rb +37 -0
- data/registry/test/components/ui/alert/title_test.rb +38 -0
- data/registry/test/components/ui/alert_test.rb +184 -0
- data/registry/test/components/ui/avatar_group_test.rb +79 -0
- data/registry/test/components/ui/avatar_test.rb +129 -0
- data/registry/test/components/ui/badge_test.rb +112 -0
- data/registry/test/components/ui/banner_test.rb +116 -0
- data/registry/test/components/ui/base_test.rb +98 -0
- data/registry/test/components/ui/breadcrumb/ellipsis_test.rb +51 -0
- data/registry/test/components/ui/breadcrumb/item_test.rb +39 -0
- data/registry/test/components/ui/breadcrumb/list_test.rb +34 -0
- data/registry/test/components/ui/breadcrumb/separator_test.rb +36 -0
- data/registry/test/components/ui/breadcrumb_test.rb +276 -0
- data/registry/test/components/ui/button_test.rb +276 -0
- data/registry/test/components/ui/button_to_test.rb +74 -0
- data/registry/test/components/ui/card/content_test.rb +41 -0
- data/registry/test/components/ui/card/description_test.rb +32 -0
- data/registry/test/components/ui/card/footer_test.rb +34 -0
- data/registry/test/components/ui/card/header_test.rb +57 -0
- data/registry/test/components/ui/card/title_test.rb +34 -0
- data/registry/test/components/ui/card_test.rb +67 -0
- data/registry/test/components/ui/checkbox_test.rb +236 -0
- data/registry/test/components/ui/data_grid_test.rb +88 -0
- data/registry/test/components/ui/date_picker_test.rb +168 -0
- data/registry/test/components/ui/dialog/content_test.rb +156 -0
- data/registry/test/components/ui/dialog/trigger_test.rb +74 -0
- data/registry/test/components/ui/dialog_test.rb +54 -0
- data/registry/test/components/ui/divider_test.rb +69 -0
- data/registry/test/components/ui/icon_test.rb +82 -0
- data/registry/test/components/ui/image_test.rb +74 -0
- data/registry/test/components/ui/input_group_test.rb +143 -0
- data/registry/test/components/ui/input_test.rb +251 -0
- data/registry/test/components/ui/label_test.rb +96 -0
- data/registry/test/components/ui/link_test.rb +95 -0
- data/registry/test/components/ui/menu/content_test.rb +233 -0
- data/registry/test/components/ui/menu/group_test.rb +24 -0
- data/registry/test/components/ui/menu/item_test.rb +76 -0
- data/registry/test/components/ui/menu/label_test.rb +43 -0
- data/registry/test/components/ui/menu/separator_test.rb +32 -0
- data/registry/test/components/ui/menu/trigger_test.rb +109 -0
- data/registry/test/components/ui/menu_test.rb +152 -0
- data/registry/test/components/ui/native_select_test.rb +118 -0
- data/registry/test/components/ui/pagination_test.rb +92 -0
- data/registry/test/components/ui/password_input_test.rb +98 -0
- data/registry/test/components/ui/popover/content_test.rb +226 -0
- data/registry/test/components/ui/popover/trigger_test.rb +83 -0
- data/registry/test/components/ui/popover_test.rb +101 -0
- data/registry/test/components/ui/radio_group/item_test.rb +14 -0
- data/registry/test/components/ui/radio_group_test.rb +139 -0
- data/registry/test/components/ui/select_test.rb +181 -0
- data/registry/test/components/ui/sheet/content_test.rb +135 -0
- data/registry/test/components/ui/sheet/trigger_test.rb +23 -0
- data/registry/test/components/ui/sheet_test.rb +43 -0
- data/registry/test/components/ui/sidebar/menu_button_test.rb +43 -0
- data/registry/test/components/ui/sidebar/trigger_test.rb +17 -0
- data/registry/test/components/ui/sidebar/wrapper_test.rb +15 -0
- data/registry/test/components/ui/sidebar_test.rb +43 -0
- data/registry/test/components/ui/spinner_test.rb +63 -0
- data/registry/test/components/ui/switch_test.rb +206 -0
- data/registry/test/components/ui/table_test.rb +106 -0
- data/registry/test/components/ui/tabs/content_test.rb +93 -0
- data/registry/test/components/ui/tabs/list_test.rb +65 -0
- data/registry/test/components/ui/tabs/trigger_test.rb +159 -0
- data/registry/test/components/ui/tabs_test.rb +88 -0
- data/registry/test/components/ui/text_test.rb +84 -0
- data/registry/test/components/ui/theme_toggle_test.rb +88 -0
- data/registry/test/components/ui/tooltip/content_test.rb +135 -0
- data/registry/test/components/ui/tooltip/trigger_test.rb +52 -0
- data/registry/test/components/ui/tooltip_test.rb +97 -0
- data/registry/test/components/ui/typography/code_blockquote_test.rb +57 -0
- data/registry/test/components/ui/typography/heading_test.rb +71 -0
- data/registry/test/components/ui/typography/text_variants_test.rb +71 -0
- metadata +540 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 9f176f98bff0013d64b619054845556e1323b6c285e374297238d9e13896c444
|
|
4
|
+
data.tar.gz: 37703180175c2b19f0630f92801e5831843531a97b1e1dfe3cd9f47f5995e6ad
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: e4432fd886028e08a31f840bebc3e9128fa84f19da9ab4d8bb1f40e78f623580279228c248bd21f98e6d9e1fe56adaa951cdadc508958386b0db4cdd652d53c6
|
|
7
|
+
data.tar.gz: b2be00dac1a8d5e8682b0ae02dae3670511644c92703397ad153a8934b5843087c9dfb0c88c4ca58ed81b36dbb5b441ed6021d69db2a3a45060712bac14884e9
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0.beta — 2026-10-26
|
|
4
|
+
|
|
5
|
+
- First release of the component library.
|
|
6
|
+
- `velvetkit:install` and `velvetkit:add` generators, with a configurable namespace.
|
|
7
|
+
- 39 components, their Stimulus controllers, tests and Lookbook previews.
|
|
8
|
+
- `Select` is a styled dropdown (listbox, keyboard, type-ahead), with
|
|
9
|
+
`searchable:` and `multiple:`; it posts through a hidden native select, so
|
|
10
|
+
forms work unchanged. The browser's own control is now `NativeSelect`.
|
|
11
|
+
- `velvetkit:add --all`, and support for cssbundling-rails Tailwind entry points.
|
|
12
|
+
- Docs site for velvetkit.com in `site/`, built from the registry.
|
|
13
|
+
- `velvetkit:ai`, run by `install` and kept current by `add`: `docs/velvetkit.md`,
|
|
14
|
+
a Claude Code skill and an AGENTS.md section, so agents use the kit.
|
|
15
|
+
- Primary colour is velvet, `#4D1A7F`, lifted in dark mode to stay readable.
|
|
16
|
+
- Fix: `Icon` dropped its icon name when given `data:` attributes.
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rain Root
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# VelvetKit
|
|
2
|
+
|
|
3
|
+
Accessible UI components for Rails, built on [ViewComponent](https://viewcomponent.org),
|
|
4
|
+
Tailwind CSS 4 and Stimulus. Website: **https://velvetkit.com**
|
|
5
|
+
|
|
6
|
+
VelvetKit doesn't load a component library at runtime. A generator copies the
|
|
7
|
+
components you ask for, plus the ones they depend on, into your app. After
|
|
8
|
+
that they're your code, under whatever module name you choose, and you can
|
|
9
|
+
change them however you like.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
bin/rails generate velvetkit:add dialog
|
|
13
|
+
# create app/components/ui/base.rb
|
|
14
|
+
# create app/components/ui/icon.rb
|
|
15
|
+
# create app/components/ui/spinner.rb
|
|
16
|
+
# create app/components/ui/button.rb
|
|
17
|
+
# create app/components/ui/dialog.rb …
|
|
18
|
+
# create app/javascript/controllers/ui/dialog_controller.ts
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Requirements
|
|
22
|
+
|
|
23
|
+
- Rails 7.1+, Ruby 3.2+
|
|
24
|
+
- Tailwind CSS 4
|
|
25
|
+
- A JavaScript bundler that compiles TypeScript (Bun or esbuild through
|
|
26
|
+
`jsbundling-rails`). The Stimulus controllers are `.ts` files.
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
# Gemfile
|
|
32
|
+
gem "velvetkit", group: :development
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
bundle install
|
|
37
|
+
bin/rails generate velvetkit:install # components under UI::
|
|
38
|
+
bin/rails generate velvetkit:install --namespace=Kit # or under Kit::
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`install` does the following:
|
|
42
|
+
|
|
43
|
+
- writes `config/velvetkit.yml` with the namespace
|
|
44
|
+
- adds `view_component` and `tailwind_merge` to the Gemfile
|
|
45
|
+
- adds an acronym inflection when the namespace needs one (`UI`, not `Kit`)
|
|
46
|
+
- copies the base component, design tokens (`app/assets/tailwind/velvetkit.css`),
|
|
47
|
+
translations (`config/locales/velvetkit.en.yml`) and Lucide icon setup
|
|
48
|
+
(`app/javascript/velvetkit_icons.ts`), and imports the CSS and icons from
|
|
49
|
+
your entry points
|
|
50
|
+
- installs `@hotwired/stimulus`, `lucide` and `tw-animate-css` with your
|
|
51
|
+
package manager, which it picks from your lockfile
|
|
52
|
+
|
|
53
|
+
## Add components
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
bin/rails generate velvetkit:add # list what's available
|
|
57
|
+
bin/rails generate velvetkit:add date_picker menu # add these and what they use
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`add` does the following for each component:
|
|
61
|
+
|
|
62
|
+
- copies the component, its templates and sub-components
|
|
63
|
+
- copies its Stimulus controllers and any lazily-loaded entry points (AG Grid
|
|
64
|
+
for `data_grid`, Cally for `date_picker`)
|
|
65
|
+
- copies its Minitest tests and Lookbook previews. A preview that shows
|
|
66
|
+
components you haven't added is skipped, and `add` tells you which.
|
|
67
|
+
- registers its controllers in `app/javascript/controllers/index.js` when
|
|
68
|
+
that file lists controllers one by one
|
|
69
|
+
- installs any npm packages it needs, and adds any gems it needs (`pagy` for
|
|
70
|
+
`pagination`) to the Gemfile
|
|
71
|
+
|
|
72
|
+
Options: `--namespace=Kit`, `--skip-tests`, `--skip-previews`,
|
|
73
|
+
`--skip-packages` (list the packages instead of installing them), and the usual
|
|
74
|
+
`--force`/`--skip` for files that already exist.
|
|
75
|
+
|
|
76
|
+
## AI agents
|
|
77
|
+
|
|
78
|
+
`install` also writes three files so coding agents use your components
|
|
79
|
+
instead of inventing new ones:
|
|
80
|
+
|
|
81
|
+
- `docs/velvetkit.md`: the rules for building UI with the kit, then every
|
|
82
|
+
installed component's arguments, slots, notes and working examples, all in
|
|
83
|
+
your namespace. `add` rewrites it whenever you add components.
|
|
84
|
+
- `.claude/skills/velvetkit/SKILL.md`: a Claude Code skill pointing at it.
|
|
85
|
+
- A short section in `AGENTS.md` pointing at it, for Codex, Cursor and others.
|
|
86
|
+
Your own notes in that file are kept.
|
|
87
|
+
|
|
88
|
+
Run `bin/rails generate velvetkit:ai` to regenerate them, or install with
|
|
89
|
+
`--skip-ai` to leave them out. velvetkit.com also serves
|
|
90
|
+
[llms.txt](https://velvetkit.com/llms.txt), and every component page has a
|
|
91
|
+
Markdown version.
|
|
92
|
+
|
|
93
|
+
## Namespacing
|
|
94
|
+
|
|
95
|
+
Every component is written once, against `UI`. The generators rewrite each file
|
|
96
|
+
as they copy it, so with `--namespace=Kit`:
|
|
97
|
+
|
|
98
|
+
| In the registry | In your app |
|
|
99
|
+
| ---------------------------- | ----------------------------- |
|
|
100
|
+
| `UI::Button` | `Kit::Button` |
|
|
101
|
+
| `app/components/ui/` | `app/components/kit/` |
|
|
102
|
+
| `data-controller="ui--menu"` | `data-controller="kit--menu"` |
|
|
103
|
+
| `t("ui.dialog.close_label")` | `t("kit.dialog.close_label")` |
|
|
104
|
+
| `"ui-dialog-#{id}"` | `"kit-dialog-#{id}"` |
|
|
105
|
+
|
|
106
|
+
The namespace must be a single constant name, like `UI`, `Kit` or `VelvetKit`.
|
|
107
|
+
|
|
108
|
+
## Theming
|
|
109
|
+
|
|
110
|
+
Components style themselves from the CSS variables in `velvetkit.css`
|
|
111
|
+
(`--primary`, `--radius`, `--sidebar`, …), with a `.dark` set alongside. To
|
|
112
|
+
retheme, change those values. Don't change the components.
|
|
113
|
+
|
|
114
|
+
For dark mode without a flash on load, add `theme_toggle` and render its
|
|
115
|
+
partial in your layout's `<head>`:
|
|
116
|
+
|
|
117
|
+
```erb
|
|
118
|
+
<%= render "layouts/theme_script" %>
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Developing VelvetKit
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
bundle install
|
|
125
|
+
bundle exec rake test:gem # namespace rewriting, registry, generators
|
|
126
|
+
bundle exec rake test:components # every component, from the registry, as UI::
|
|
127
|
+
bundle exec rake test:namespaced # every component copied out as Kit::, then tested
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
`registry/` holds the components, laid out like a Rails app.
|
|
131
|
+
`registry/components.yml` holds only what the files can't say themselves
|
|
132
|
+
(descriptions, package and gem versions). Dependencies, Stimulus controllers
|
|
133
|
+
and npm imports are read from the source, so they can't drift.
|
|
134
|
+
|
|
135
|
+
## The docs site (velvetkit.com)
|
|
136
|
+
|
|
137
|
+
`site/` is a Rails app that serves https://velvetkit.com. It installs the kit
|
|
138
|
+
from `registry/` the way any app would, with `bin/sync-kit` (it runs
|
|
139
|
+
`velvetkit:install` and `velvetkit:add --all`). The copied files are
|
|
140
|
+
gitignored, so the registry is the only source.
|
|
141
|
+
|
|
142
|
+
The pages build themselves from the registry:
|
|
143
|
+
|
|
144
|
+
- each component page renders that component's Lookbook preview templates
|
|
145
|
+
live, next to their source
|
|
146
|
+
- the "Comes with" list comes from the same dependency scan `add` uses
|
|
147
|
+
- the arguments and slots are read from the component's Ruby
|
|
148
|
+
|
|
149
|
+
To document a component, write or improve its preview. The site picks it up.
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
cd site
|
|
153
|
+
bun install && bundle install
|
|
154
|
+
bin/dev # sync the kit, then run Rails, JS and CSS watchers
|
|
155
|
+
bin/rails test # every page renders, every live example, SEO and AI endpoints
|
|
156
|
+
bin/ux-check # real clicks, keys and clipboard in headless Chrome (site on :3000)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Deploying
|
|
160
|
+
|
|
161
|
+
The image is built from the `Dockerfile` at the repository root. It needs
|
|
162
|
+
the gem and the registry as well as `site/`.
|
|
163
|
+
|
|
164
|
+
**Dokploy:** create an Application from this repository with build type
|
|
165
|
+
**Dockerfile**. The defaults fit: Dockerfile path `Dockerfile`, context `.`.
|
|
166
|
+
Then:
|
|
167
|
+
|
|
168
|
+
- Environment:
|
|
169
|
+
- `SECRET_KEY_BASE`, required: generate one with `openssl rand -hex 64`.
|
|
170
|
+
- `SITE_URL`, if the site isn't served at https://velvetkit.com.
|
|
171
|
+
- Domains: `velvetkit.com` on container port **80**, with HTTPS on. Traefik
|
|
172
|
+
terminates TLS and the app trusts it (`assume_ssl`).
|
|
173
|
+
- Health check: the image declares one against `GET /up`.
|
|
174
|
+
|
|
175
|
+
**Kamal:** fill in the TODOs in `site/config/deploy.yml`, then run
|
|
176
|
+
`bin/kamal deploy` from `site/`. It uses the same Dockerfile.
|
|
177
|
+
|
|
178
|
+
**Anywhere else:**
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
docker build -t velvetkit-site .
|
|
182
|
+
docker run -p 8080:80 -e SECRET_KEY_BASE=$(openssl rand -hex 64) -e FORCE_SSL=false velvetkit-site
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### SEO and AI
|
|
186
|
+
|
|
187
|
+
Every page has its own title, description, canonical URL and Open Graph
|
|
188
|
+
and Twitter cards, using `public/og.png`. The home page carries JSON-LD for
|
|
189
|
+
`SoftwareApplication` and `WebSite`; docs pages carry `TechArticle` and
|
|
190
|
+
`BreadcrumbList`. `/sitemap.xml` and `/robots.txt` are built from the
|
|
191
|
+
routes, and robots explicitly welcomes AI crawlers and points them at
|
|
192
|
+
`/llms.txt`. Component pages link their Markdown version with
|
|
193
|
+
`rel="alternate"`.
|
|
194
|
+
|
|
195
|
+
## License
|
|
196
|
+
|
|
197
|
+
MIT
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
require "json"
|
|
5
|
+
require_relative "../helpers"
|
|
6
|
+
|
|
7
|
+
module Velvetkit
|
|
8
|
+
module Generators
|
|
9
|
+
class AddGenerator < Rails::Generators::Base
|
|
10
|
+
include Helpers
|
|
11
|
+
|
|
12
|
+
desc <<~DESC
|
|
13
|
+
Copy VelvetKit components into the app, with every component they use.
|
|
14
|
+
|
|
15
|
+
Run with no components to list what is available.
|
|
16
|
+
|
|
17
|
+
bin/rails generate velvetkit:add dialog date_picker
|
|
18
|
+
DESC
|
|
19
|
+
|
|
20
|
+
argument :components, type: :array, default: [], banner: "component component ..."
|
|
21
|
+
|
|
22
|
+
class_option :namespace, type: :string,
|
|
23
|
+
desc: "Module for the components (default: #{CONFIG_FILE}, else UI)"
|
|
24
|
+
class_option :all, type: :boolean, default: false, desc: "Add every component"
|
|
25
|
+
class_option :skip_tests, type: :boolean, default: false, desc: "Don't copy component tests"
|
|
26
|
+
class_option :skip_previews, type: :boolean, default: false, desc: "Don't copy Lookbook previews"
|
|
27
|
+
class_option :skip_packages, type: :boolean, default: false,
|
|
28
|
+
desc: "List the npm packages to add instead of installing them"
|
|
29
|
+
|
|
30
|
+
def list_components
|
|
31
|
+
return if requested.any?
|
|
32
|
+
|
|
33
|
+
say "Usage: bin/rails generate velvetkit:add COMPONENT [COMPONENT ...]\n\n"
|
|
34
|
+
registry.names.each { |name| say format(" %-16s %s", name, registry.description(name)) }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def copy_components
|
|
38
|
+
resolved.each do |name|
|
|
39
|
+
files = registry.component_files(name) + registry.javascript_files(name)
|
|
40
|
+
files += registry.test_files(name) unless options[:skip_tests]
|
|
41
|
+
files.uniq.each { |file| copy_from_registry(file) }
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def copy_previews
|
|
46
|
+
return if options[:skip_previews]
|
|
47
|
+
|
|
48
|
+
resolved.each do |name|
|
|
49
|
+
needs = registry.preview_requires(name, resolved)
|
|
50
|
+
if needs.any?
|
|
51
|
+
say_status :skip, "#{name} preview, it also shows #{needs.join(', ')}", :yellow
|
|
52
|
+
else
|
|
53
|
+
registry.preview_files(name).each { |file| copy_from_registry(file) }
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Apps that list controllers in a manifest (jsbundling with
|
|
59
|
+
# stimulus:manifest:update) need each one registered; apps that load the
|
|
60
|
+
# directory (importmap, eagerLoadControllersFrom) need nothing.
|
|
61
|
+
def register_stimulus_controllers
|
|
62
|
+
manifest = File.join(destination_root, "app/javascript/controllers/index.js")
|
|
63
|
+
return unless File.exist?(manifest) && !File.read(manifest).match?(/(eager|lazy)LoadControllersFrom/)
|
|
64
|
+
|
|
65
|
+
controllers.each do |file|
|
|
66
|
+
path = kit_namespace.rewrite_path(file).delete_prefix("app/javascript/controllers/").delete_suffix("_controller.ts")
|
|
67
|
+
identifier = path.tr("_", "-").gsub("/", "--")
|
|
68
|
+
next if File.read(manifest).match?(/["']#{Regexp.escape(identifier)}["']/)
|
|
69
|
+
|
|
70
|
+
constant = "#{path.camelize.gsub('::', '__')}Controller"
|
|
71
|
+
append_to_file "app/javascript/controllers/index.js", <<~JS
|
|
72
|
+
\nimport #{constant} from "./#{path}_controller"
|
|
73
|
+
application.register("#{identifier}", #{constant})
|
|
74
|
+
JS
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def add_gems
|
|
79
|
+
gemfile = File.read(File.join(destination_root, "Gemfile"))
|
|
80
|
+
resolved.flat_map { |name| registry.gems(name).to_a }.uniq.each do |name, version|
|
|
81
|
+
gem name, version unless gemfile.match?(/gem ["']#{name}["']/)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def add_packages
|
|
86
|
+
install_packages(resolved.flat_map { |name| registry.packages(name) }.uniq)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Keep the agents' reference listing what is installed now.
|
|
90
|
+
def refresh_ai_reference
|
|
91
|
+
return if resolved.empty? || !File.exist?(File.join(destination_root, "docs/velvetkit.md"))
|
|
92
|
+
|
|
93
|
+
invoke "velvetkit:ai", [], namespace: kit_namespace.name
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
def resolved
|
|
99
|
+
@resolved ||= requested.empty? ? [] : registry.resolve(requested)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def requested
|
|
103
|
+
options[:all] ? registry.names : components
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def controllers
|
|
107
|
+
resolved.flat_map { |name| registry.stimulus_controllers(name) }.uniq
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
require_relative "../helpers"
|
|
5
|
+
|
|
6
|
+
module Velvetkit
|
|
7
|
+
module Generators
|
|
8
|
+
# Tells AI coding agents how to build UI in this app with its components.
|
|
9
|
+
#
|
|
10
|
+
# Writes docs/velvetkit.md (the rules, then a reference for each installed
|
|
11
|
+
# component, in the app's namespace) and points agents at it: a Claude
|
|
12
|
+
# Code skill, and a section in AGENTS.md for Codex, Cursor and the rest.
|
|
13
|
+
# `add` runs this again whenever the reference exists, so it stays current.
|
|
14
|
+
class AiGenerator < Rails::Generators::Base
|
|
15
|
+
include Helpers
|
|
16
|
+
|
|
17
|
+
REFERENCE = "docs/velvetkit.md"
|
|
18
|
+
SKILL = ".claude/skills/velvetkit/SKILL.md"
|
|
19
|
+
MARKER = "<!-- velvetkit -->"
|
|
20
|
+
|
|
21
|
+
desc <<~DESC
|
|
22
|
+
Write docs/velvetkit.md for AI agents, describing the components
|
|
23
|
+
installed in this app, and point Claude Code and AGENTS.md at it.
|
|
24
|
+
DESC
|
|
25
|
+
|
|
26
|
+
class_option :namespace, type: :string,
|
|
27
|
+
desc: "Module the components are under (default: #{CONFIG_FILE}, else UI)"
|
|
28
|
+
|
|
29
|
+
def write_reference
|
|
30
|
+
create_file REFERENCE, Velvetkit.agent_guide(installed, namespace: kit_namespace), force: true
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def write_claude_skill
|
|
34
|
+
create_file SKILL, <<~MARKDOWN, force: true
|
|
35
|
+
---
|
|
36
|
+
name: velvetkit
|
|
37
|
+
description: Build Rails UI with this app's #{kit_namespace.name}:: components (VelvetKit ViewComponents). Use when writing or changing views, forms, layouts, pages or ViewComponents.
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
This app's UI is built from VelvetKit components under `#{kit_namespace.name}::`.
|
|
41
|
+
Before writing or changing any view, read `#{REFERENCE}`: it has the rules
|
|
42
|
+
and every installed component's arguments, slots and working examples.
|
|
43
|
+
|
|
44
|
+
Need a component that isn't installed? Run `bin/rails generate velvetkit:add <name>`
|
|
45
|
+
instead of writing one.
|
|
46
|
+
MARKDOWN
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def point_agents_md_at_reference
|
|
50
|
+
section = <<~MARKDOWN
|
|
51
|
+
#{MARKER}
|
|
52
|
+
## UI components
|
|
53
|
+
|
|
54
|
+
Build UI from the app's VelvetKit components (`#{kit_namespace.name}::Button`,
|
|
55
|
+
`#{kit_namespace.name}::Dialog`, …). Read `#{REFERENCE}` before writing views: it lists
|
|
56
|
+
the rules and every installed component with working examples.
|
|
57
|
+
MARKDOWN
|
|
58
|
+
|
|
59
|
+
agents = File.join(destination_root, "AGENTS.md")
|
|
60
|
+
if !File.exist?(agents)
|
|
61
|
+
create_file "AGENTS.md", "# Agent notes\n\n#{section}"
|
|
62
|
+
elsif !File.read(agents).include?(MARKER)
|
|
63
|
+
append_to_file "AGENTS.md", "\n#{section}"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
# Components present in the app, matched against the registry.
|
|
70
|
+
def installed
|
|
71
|
+
folder = File.join(destination_root, "app/components", kit_namespace.path)
|
|
72
|
+
present = Dir.glob("*.rb", base: folder).map { |file| File.basename(file, ".rb") } +
|
|
73
|
+
Dir.glob("*/", base: folder).map { |dir| dir.chomp("/") }
|
|
74
|
+
registry.names & present
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "velvetkit"
|
|
4
|
+
|
|
5
|
+
module Velvetkit
|
|
6
|
+
module Generators
|
|
7
|
+
# What the install and add generators share: where the registry is, which
|
|
8
|
+
# namespace the app uses, and how a file gets from one to the other.
|
|
9
|
+
module Helpers
|
|
10
|
+
LOCKFILES = {
|
|
11
|
+
"bun.lock" => "bun add",
|
|
12
|
+
"bun.lockb" => "bun add",
|
|
13
|
+
"yarn.lock" => "yarn add",
|
|
14
|
+
"pnpm-lock.yaml" => "pnpm add",
|
|
15
|
+
"package-lock.json" => "npm install"
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def registry
|
|
21
|
+
Velvetkit.registry
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# --namespace, else config/velvetkit.yml, else UI.
|
|
25
|
+
def kit_namespace
|
|
26
|
+
@kit_namespace ||= Namespace.new(options[:namespace] || configured_namespace || Namespace::CANONICAL)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def configured_namespace
|
|
30
|
+
config = File.join(destination_root, CONFIG_FILE)
|
|
31
|
+
YAML.load_file(config)&.fetch("namespace", nil) if File.exist?(config)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def copy_from_registry(file)
|
|
35
|
+
create_file kit_namespace.rewrite_path(file), kit_namespace.rewrite(registry.read(file))
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def install_packages(packages)
|
|
39
|
+
package_json = File.join(destination_root, "package.json")
|
|
40
|
+
missing = packages - installed_packages(package_json)
|
|
41
|
+
return if missing.empty?
|
|
42
|
+
|
|
43
|
+
specs = missing.map { |package| "#{package}@#{registry.package_version(package)}" }
|
|
44
|
+
if options[:skip_packages] || !File.exist?(package_json)
|
|
45
|
+
say_status :packages, "add these to package.json: #{specs.join(' ')}", :yellow
|
|
46
|
+
else
|
|
47
|
+
run "#{package_manager} #{specs.join(' ')}"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def installed_packages(package_json)
|
|
52
|
+
return [] unless File.exist?(package_json)
|
|
53
|
+
|
|
54
|
+
json = JSON.parse(File.read(package_json))
|
|
55
|
+
json.fetch("dependencies", {}).keys + json.fetch("devDependencies", {}).keys
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def package_manager
|
|
59
|
+
LOCKFILES.each { |lockfile, command| return command if File.exist?(File.join(destination_root, lockfile)) }
|
|
60
|
+
"npm install"
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
require "json"
|
|
5
|
+
require_relative "../helpers"
|
|
6
|
+
|
|
7
|
+
module Velvetkit
|
|
8
|
+
module Generators
|
|
9
|
+
class InstallGenerator < Rails::Generators::Base
|
|
10
|
+
include Helpers
|
|
11
|
+
|
|
12
|
+
desc <<~DESC
|
|
13
|
+
Set the app up for VelvetKit: choose the namespace, and add the base
|
|
14
|
+
component, design tokens, translations and icons every component uses.
|
|
15
|
+
|
|
16
|
+
bin/rails generate velvetkit:install --namespace=Kit
|
|
17
|
+
DESC
|
|
18
|
+
|
|
19
|
+
class_option :namespace, type: :string, default: Namespace::CANONICAL,
|
|
20
|
+
desc: "Module to put the components under"
|
|
21
|
+
class_option :skip_tests, type: :boolean, default: false, desc: "Don't copy the base component's test"
|
|
22
|
+
class_option :skip_packages, type: :boolean, default: false,
|
|
23
|
+
desc: "List the npm packages to add instead of installing them"
|
|
24
|
+
class_option :skip_ai, type: :boolean, default: false,
|
|
25
|
+
desc: "Don't write docs/velvetkit.md and the pointers to it for AI agents"
|
|
26
|
+
|
|
27
|
+
def write_config
|
|
28
|
+
create_file CONFIG_FILE, <<~YAML
|
|
29
|
+
# VelvetKit — https://velvetkit.com
|
|
30
|
+
# `bin/rails generate velvetkit:add` puts components under this module.
|
|
31
|
+
namespace: #{kit_namespace.name}
|
|
32
|
+
YAML
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def add_gems
|
|
36
|
+
gemfile = File.read(File.join(destination_root, "Gemfile"))
|
|
37
|
+
gem "view_component" unless gemfile.match?(/gem ["']view_component["']/)
|
|
38
|
+
gem "tailwind_merge" unless gemfile.match?(/gem ["']tailwind_merge["']/)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Zeitwerk maps app/components/ui to Ui unless told UI is an acronym.
|
|
42
|
+
def add_inflection
|
|
43
|
+
return unless kit_namespace.acronym?
|
|
44
|
+
|
|
45
|
+
inflections = "config/initializers/inflections.rb"
|
|
46
|
+
rule = %(inflect.acronym "#{kit_namespace.name}")
|
|
47
|
+
block = <<~RUBY
|
|
48
|
+
ActiveSupport::Inflector.inflections(:en) do |inflect|
|
|
49
|
+
#{rule}
|
|
50
|
+
end
|
|
51
|
+
RUBY
|
|
52
|
+
return create_file(inflections, block) unless exists?(inflections)
|
|
53
|
+
return if read(inflections).include?(rule)
|
|
54
|
+
|
|
55
|
+
append_to_file inflections, "\n#{block}"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def copy_foundation
|
|
59
|
+
Registry::FOUNDATION.each { |file| copy_from_registry(file) }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# tailwindcss-rails and cssbundling-rails keep the entry point in
|
|
63
|
+
# different places; the tokens always live in app/assets/tailwind.
|
|
64
|
+
TAILWIND_ENTRIES = {
|
|
65
|
+
"app/assets/tailwind/application.css" => "./velvetkit.css",
|
|
66
|
+
"app/assets/stylesheets/application.tailwind.css" => "../tailwind/velvetkit.css"
|
|
67
|
+
}.freeze
|
|
68
|
+
|
|
69
|
+
def import_stylesheet
|
|
70
|
+
entry, path = TAILWIND_ENTRIES.find { |file, _| exists?(file) }
|
|
71
|
+
return say_status(:css, "import velvetkit.css from your Tailwind entry point", :yellow) unless entry
|
|
72
|
+
return if read(entry).include?("velvetkit.css")
|
|
73
|
+
|
|
74
|
+
inject_into_file entry, %(@import "#{path}";\n), after: /@import ["']tailwindcss["'];?\n/
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def import_icons
|
|
78
|
+
entry = %w[app/javascript/application.js app/javascript/application.ts].find { |file| exists?(file) }
|
|
79
|
+
return say_status(:js, "import ./velvetkit_icons from your JavaScript entry point", :yellow) unless entry
|
|
80
|
+
return if read(entry).include?("velvetkit_icons")
|
|
81
|
+
|
|
82
|
+
append_to_file entry, %(import "./velvetkit_icons"\n)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def add_base_component
|
|
86
|
+
invoke "velvetkit:add", [ "base" ], namespace: kit_namespace.name, skip_tests: options[:skip_tests], skip_packages: true
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def add_packages
|
|
90
|
+
install_packages(Registry::FOUNDATION_PACKAGES)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def write_ai_reference
|
|
94
|
+
invoke "velvetkit:ai", [], namespace: kit_namespace.name unless options[:skip_ai]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
private
|
|
98
|
+
|
|
99
|
+
def exists?(file)
|
|
100
|
+
File.exist?(File.join(destination_root, file))
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def read(file)
|
|
104
|
+
File.read(File.join(destination_root, file))
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/core_ext/string"
|
|
4
|
+
require "active_support/core_ext/object/blank"
|
|
5
|
+
|
|
6
|
+
module Velvetkit
|
|
7
|
+
# The Ruby module the components live under in the host app.
|
|
8
|
+
#
|
|
9
|
+
# The registry is written once, against `UI`. Every file is rewritten on the
|
|
10
|
+
# way into the host, so `Kit` turns `UI::Button` into `Kit::Button`,
|
|
11
|
+
# `app/components/ui/` into `app/components/kit/`, the `ui--dialog` Stimulus
|
|
12
|
+
# identifier into `kit--dialog`, and the `ui.` I18n scope into `kit.`.
|
|
13
|
+
class Namespace
|
|
14
|
+
CANONICAL = "UI"
|
|
15
|
+
FORMAT = /\A[A-Z][A-Za-z0-9]*\z/
|
|
16
|
+
|
|
17
|
+
attr_reader :name
|
|
18
|
+
|
|
19
|
+
def initialize(name)
|
|
20
|
+
@name = name.to_s
|
|
21
|
+
return if FORMAT.match?(@name)
|
|
22
|
+
|
|
23
|
+
raise Error, "Namespace must be a single Ruby constant name like UI or Kit, got #{name.inspect}"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# True when Rails cannot get from the directory name back to the constant
|
|
27
|
+
# without an inflection: `ui`.camelize is `Ui`, not `UI`.
|
|
28
|
+
def acronym?
|
|
29
|
+
name.underscore.camelize != name
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Directory name and I18n scope: UI → ui, VelvetKit → velvet_kit.
|
|
33
|
+
def path
|
|
34
|
+
acronym? ? name.downcase : name.underscore
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Prefix for Stimulus identifiers and DOM ids: VelvetKit → velvet-kit.
|
|
38
|
+
def dom
|
|
39
|
+
path.dasherize
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def rewrite_path(file)
|
|
43
|
+
file.gsub(%r{(?<=\A|/)ui(?=/)}, path)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def rewrite(source)
|
|
47
|
+
source
|
|
48
|
+
.gsub(/\bUI(?=::)/, name) # UI::Button
|
|
49
|
+
.gsub(/\bmodule UI\b/, "module #{name}") # module UI; end
|
|
50
|
+
.gsub("ui--", "#{dom}--") # Stimulus identifiers
|
|
51
|
+
.gsub(/(?<=["'`])ui-(?=[a-z`])/, "#{dom}-") # DOM ids, storage keys
|
|
52
|
+
.gsub(/(?<=["'`])ui\.(?=[a-z`])/, "#{path}.") # I18n keys
|
|
53
|
+
.gsub(/^(\s+)ui:$/, "\\1#{path}:") # locale file scope
|
|
54
|
+
.gsub(%r{(?<=/)ui(?=/)}, path) # controllers/ui/… paths
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|