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
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class UI::Breadcrumb::ItemSlot < UI::Base
|
|
4
|
+
def initialize(href:, text:, avatar_src: nil, current: false, **html_attrs)
|
|
5
|
+
@href = href
|
|
6
|
+
@text = text
|
|
7
|
+
@avatar_src = avatar_src
|
|
8
|
+
@is_current = current
|
|
9
|
+
@html_attrs = html_attrs
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def set_context(index, total, size)
|
|
13
|
+
@index = index
|
|
14
|
+
@total = total
|
|
15
|
+
@size = size
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def effective_current?
|
|
19
|
+
@is_current || (@index == @total - 1)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def size_text_class
|
|
23
|
+
UI::Breadcrumb::SIZES[@size]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def link_classes
|
|
27
|
+
cn(
|
|
28
|
+
size_text_class,
|
|
29
|
+
effective_current? ? "text-foreground font-normal justify-start rounded" : "text-muted-foreground font-normal justify-start rounded"
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def link_options
|
|
34
|
+
opts = @html_attrs.dup
|
|
35
|
+
opts[:aria] = { current: "page" } if effective_current?
|
|
36
|
+
opts[:data] = { breadcrumb_text: @text, breadcrumb_href: @href, breadcrumb_avatar: @avatar_src }.compact
|
|
37
|
+
opts
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def data_attrs
|
|
41
|
+
attrs = { breadcrumb_text: @text, breadcrumb_href: @href, breadcrumb_avatar: @avatar_src }.compact
|
|
42
|
+
attrs[:last] = "true" if effective_current?
|
|
43
|
+
attrs
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
attr_reader :href, :text, :avatar_src
|
|
47
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= tag.ol class: classes, **@html_attrs do %><%= content %><% end %>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class UI::Breadcrumb::List < UI::Base
|
|
4
|
+
def initialize(class: nil, **html_attrs)
|
|
5
|
+
@user_class = binding.local_variable_get(:class)
|
|
6
|
+
@html_attrs = html_attrs
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def classes
|
|
10
|
+
cn("flex flex-wrap items-center gap-1.5 break-words sm:gap-2.5", @user_class)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= tag.li role: "presentation" do %><%= tag.span class: classes, aria: { hidden: true } do %>/<% end %><% end %>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class UI::Breadcrumb::Separator < UI::Base
|
|
4
|
+
def initialize(class: nil, **html_attrs)
|
|
5
|
+
@user_class = binding.local_variable_get(:class)
|
|
6
|
+
@html_attrs = html_attrs
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def classes
|
|
10
|
+
cn("text-muted-foreground/70", @user_class)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<%= tag.nav aria: { label: t("ui.breadcrumb.label", default: "Breadcrumb") }, class: classes, **@html_attrs do %>
|
|
2
|
+
<ol class="flex flex-wrap items-center gap-1.5 break-words sm:gap-2.5">
|
|
3
|
+
<% visible = visible_items %>
|
|
4
|
+
<% hidden = hidden_items %>
|
|
5
|
+
<% prefix = visible[0...-1] %>
|
|
6
|
+
<% current = visible.last %>
|
|
7
|
+
<%# START: ellipsis before visible items %>
|
|
8
|
+
<% if hidden.any? && @collapse_at == :start %>
|
|
9
|
+
<%= render(UI::Breadcrumb::AutoEllipsis.new(items: hidden, size: @size)) %>
|
|
10
|
+
<li role="presentation"><%= tag.span class: separator_classes, aria: { hidden: true } do %>/<% end %></li>
|
|
11
|
+
<% end %>
|
|
12
|
+
<%# Prefix items with separators %>
|
|
13
|
+
<% prefix.each do |item| %>
|
|
14
|
+
<%= item %>
|
|
15
|
+
<li role="presentation"><%= tag.span class: separator_classes, aria: { hidden: true } do %>/<% end %></li>
|
|
16
|
+
<% end %>
|
|
17
|
+
<%# MIDDLE / END: ellipsis before current page %>
|
|
18
|
+
<% if hidden.any? && %i[middle end].include?(@collapse_at) %>
|
|
19
|
+
<%= render(UI::Breadcrumb::AutoEllipsis.new(items: hidden, size: @size)) %>
|
|
20
|
+
<li role="presentation"><%= tag.span class: separator_classes, aria: { hidden: true } do %>/<% end %></li>
|
|
21
|
+
<% end %>
|
|
22
|
+
<%# Current page (always last, never truncated) %>
|
|
23
|
+
<%= current %>
|
|
24
|
+
<%# Legacy explicit ellipsis support (deprecated) %>
|
|
25
|
+
<% if ellipsis? && !has_items? %>
|
|
26
|
+
<%= ellipsis %>
|
|
27
|
+
<% end %>
|
|
28
|
+
<%= content %>
|
|
29
|
+
</ol>
|
|
30
|
+
<% end %>
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class UI::Breadcrumb < UI::Base
|
|
4
|
+
SEPARATOR_COLOR = "text-muted-foreground/70".freeze
|
|
5
|
+
SIZES = { sm: "text-xs", md: "text-sm" }.freeze
|
|
6
|
+
COLLAPSE_POSITIONS = %i[start middle end].freeze
|
|
7
|
+
|
|
8
|
+
renders_many :items, ->(href:, text:, avatar_src: nil, current: false, **opts) {
|
|
9
|
+
UI::Breadcrumb::ItemSlot.new(href: href, text: text, avatar_src: avatar_src, current: current, **opts)
|
|
10
|
+
}
|
|
11
|
+
# Deprecated: explicit ellipsis slot will be replaced by auto-ellipsis
|
|
12
|
+
renders_one :ellipsis, UI::Breadcrumb::Ellipsis
|
|
13
|
+
|
|
14
|
+
def initialize(size: :sm, separator_icon: :slash, max_items: nil, collapse_at: :middle, class: nil, **html_attrs)
|
|
15
|
+
@size = size
|
|
16
|
+
@separator_icon = separator_icon
|
|
17
|
+
@max_items = max_items
|
|
18
|
+
@collapse_at = collapse_at
|
|
19
|
+
@user_class = binding.local_variable_get(:class)
|
|
20
|
+
@html_attrs = html_attrs
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def before_render
|
|
24
|
+
items.each_with_index do |item, idx|
|
|
25
|
+
item.set_context(idx, items.length, @size)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def classes
|
|
30
|
+
cn(SIZES[@size], @user_class)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def separator_classes
|
|
34
|
+
cn(
|
|
35
|
+
SEPARATOR_COLOR,
|
|
36
|
+
@size == :md ? "text-sm" : "text-xs"
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def auto_collapse?
|
|
41
|
+
@max_items && items.length > @max_items
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# The last item (current page) is always preserved.
|
|
45
|
+
# Remaining budget = max_items - 1 is distributed across the prefix.
|
|
46
|
+
def visible_items
|
|
47
|
+
return items unless auto_collapse?
|
|
48
|
+
|
|
49
|
+
current = [ items.last ]
|
|
50
|
+
budget = @max_items - 1
|
|
51
|
+
prefix = items[0...-1]
|
|
52
|
+
|
|
53
|
+
case @collapse_at
|
|
54
|
+
when :start
|
|
55
|
+
prefix.last([ budget, 0 ].max) + current
|
|
56
|
+
when :end
|
|
57
|
+
prefix.first([ budget, 0 ].max) + current
|
|
58
|
+
when :middle
|
|
59
|
+
head_count = [ (budget / 2.0).ceil, prefix.length ].min
|
|
60
|
+
tail_count = [ budget - head_count, prefix.length - head_count ].min
|
|
61
|
+
tail_count = [ tail_count, 0 ].max
|
|
62
|
+
prefix.first(head_count) + prefix.last(tail_count) + current
|
|
63
|
+
else
|
|
64
|
+
items
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def hidden_items
|
|
69
|
+
return [] unless auto_collapse?
|
|
70
|
+
|
|
71
|
+
visible_set = visible_items.to_set
|
|
72
|
+
items.reject { |item| visible_set.include?(item) }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def show_separator_after?(idx, collection = items)
|
|
76
|
+
return false if idx == collection.length - 1
|
|
77
|
+
true
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def has_items?
|
|
81
|
+
items.any?
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= tag.button type: @type, class: classes, disabled: effective_disabled?, aria: { busy: @loading }, **@html_attrs do %><% if @loading %><span class="sr-only"><%= t("ui.button.loading", default: "Loading, please wait") %></span><%= render(loader_icon) %><% elsif leading_icon? %><%= leading_icon %><% end %><%= content %><% if trailing_icon? && !@loading %><%= trailing_icon %><% end %><% end %>
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class UI::Button < UI::Base
|
|
4
|
+
ICON_SIZE_FOR_BUTTON_SIZE = {
|
|
5
|
+
sm: :xs,
|
|
6
|
+
default: :sm,
|
|
7
|
+
lg: :default,
|
|
8
|
+
icon: :sm,
|
|
9
|
+
icon_sm: :xs
|
|
10
|
+
}.freeze
|
|
11
|
+
|
|
12
|
+
# The disabled-state utilities below carry Tailwind v4 `!` (important) so
|
|
13
|
+
# they beat the legacy unlayered UI CSS — unlayered rules otherwise
|
|
14
|
+
# outrank any layered Tailwind utility regardless of specificity.
|
|
15
|
+
variants(
|
|
16
|
+
base: "inline-flex items-center justify-center gap-2 whitespace-nowrap " \
|
|
17
|
+
"rounded-md text-sm font-medium " \
|
|
18
|
+
"#{UI::Base::FOCUS_RING} " \
|
|
19
|
+
"disabled:pointer-events-none " \
|
|
20
|
+
"disabled:bg-muted! disabled:text-muted-foreground!",
|
|
21
|
+
variants: {
|
|
22
|
+
variant: {
|
|
23
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
24
|
+
destructive: "bg-destructive text-white hover:bg-destructive/90 focus:ring-destructive",
|
|
25
|
+
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground " \
|
|
26
|
+
"disabled:border-input!",
|
|
27
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
28
|
+
ghost: "hover:bg-accent hover:text-accent-foreground disabled:bg-transparent!",
|
|
29
|
+
link: "text-primary underline-offset-4 hover:underline " \
|
|
30
|
+
"disabled:bg-transparent! disabled:no-underline!"
|
|
31
|
+
},
|
|
32
|
+
size: {
|
|
33
|
+
default: "h-9 px-4 py-2",
|
|
34
|
+
sm: "h-8 rounded-md px-3 text-xs",
|
|
35
|
+
lg: "h-10 rounded-md px-8",
|
|
36
|
+
icon: "h-9 w-9",
|
|
37
|
+
icon_sm: "h-7 w-7"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
defaults: { variant: :default, size: :default }
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
renders_one :leading_icon, ->(name:, size: nil, **opts) {
|
|
44
|
+
UI::Icon.new(name: name, size: size || ICON_SIZE_FOR_BUTTON_SIZE.fetch(@size, :default), **opts)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
renders_one :trailing_icon, ->(name:, size: nil, **opts) {
|
|
48
|
+
UI::Icon.new(name: name, size: size || ICON_SIZE_FOR_BUTTON_SIZE.fetch(@size, :default), **opts)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
def initialize(variant: :default, size: :default, type: :button,
|
|
52
|
+
full_width: false, loading: false, disabled: false,
|
|
53
|
+
as_child: false, class: nil, **html_attrs)
|
|
54
|
+
@variant = variant
|
|
55
|
+
@size = size
|
|
56
|
+
@type = type
|
|
57
|
+
@full_width = full_width
|
|
58
|
+
@loading = loading
|
|
59
|
+
@disabled = disabled
|
|
60
|
+
@as_child = as_child
|
|
61
|
+
@user_class = binding.local_variable_get(:class)
|
|
62
|
+
@html_attrs = html_attrs
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def effective_disabled?
|
|
66
|
+
@disabled || @loading
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def classes
|
|
70
|
+
cn(
|
|
71
|
+
variant_classes(variant: @variant, size: @size),
|
|
72
|
+
"disabled:cursor-not-allowed",
|
|
73
|
+
{ "w-full" => @full_width },
|
|
74
|
+
@user_class
|
|
75
|
+
)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def loader_icon
|
|
79
|
+
UI::Spinner.new(size: ICON_SIZE_FOR_BUTTON_SIZE.fetch(@size, :default))
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= button_to @path, **button_options do %><% if @loading %><span class="sr-only"><%= t("ui.button.loading", default: "Loading, please wait") %></span><%= render(loader_icon) %><% elsif leading_icon? %><%= leading_icon %><% end %><%= content %><% if trailing_icon? && !@loading %><%= trailing_icon %><% end %><% end %>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# UI::ButtonTo — a UI::Button rendered inside a real form via Rails' `button_to`.
|
|
4
|
+
#
|
|
5
|
+
# Use when the action is a non-GET request (sign-in with a provider, destroy,
|
|
6
|
+
# any state change) rather than navigation. Inherits UI::Button's variants,
|
|
7
|
+
# sizes and icon slots so the two stay visually identical.
|
|
8
|
+
#
|
|
9
|
+
# Velvet::ButtonTo inherited Button's icon/loading slots but its template never
|
|
10
|
+
# rendered them; this one does.
|
|
11
|
+
#
|
|
12
|
+
# Usage:
|
|
13
|
+
# <%= render(UI::ButtonTo.new(sign_out_path, method: :delete, variant: :outline)) do %>
|
|
14
|
+
# Sign out
|
|
15
|
+
# <% end %>
|
|
16
|
+
class UI::ButtonTo < UI::Button
|
|
17
|
+
def initialize(path, form_class: nil, **args)
|
|
18
|
+
@path = path
|
|
19
|
+
@form_class = form_class
|
|
20
|
+
super(**args)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# `button_to` splits its options: everything here decorates the <button>,
|
|
24
|
+
# while form-level concerns (method, params, form_class) stay on the form.
|
|
25
|
+
def button_options
|
|
26
|
+
opts = @html_attrs.dup
|
|
27
|
+
opts[:class] = classes
|
|
28
|
+
opts[:disabled] = true if effective_disabled?
|
|
29
|
+
opts[:form_class] = @form_class if @form_class
|
|
30
|
+
opts[:"aria-busy"] = "true" if @loading
|
|
31
|
+
opts
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= tag.div class: classes, **@html_attrs do %><%= content %><% end %>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class UI::Card::Content < UI::Base
|
|
4
|
+
def initialize(class: nil, **html_attrs)
|
|
5
|
+
@user_class = binding.local_variable_get(:class)
|
|
6
|
+
@html_attrs = html_attrs
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def classes
|
|
10
|
+
cn("p-6 pt-0 first:pt-6", @user_class)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= tag.p class: classes, **@html_attrs do %><%= content %><% end %>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class UI::Card::Description < UI::Base
|
|
4
|
+
def initialize(class: nil, **html_attrs)
|
|
5
|
+
@user_class = binding.local_variable_get(:class)
|
|
6
|
+
@html_attrs = html_attrs
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def classes
|
|
10
|
+
cn("text-sm text-muted-foreground", @user_class)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= tag.div class: classes, **@html_attrs do %><%= content %><% end %>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class UI::Card::Footer < UI::Base
|
|
4
|
+
def initialize(class: nil, **html_attrs)
|
|
5
|
+
@user_class = binding.local_variable_get(:class)
|
|
6
|
+
@html_attrs = html_attrs
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def classes
|
|
10
|
+
cn("flex items-center p-6 pt-0", @user_class)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= tag.div class: classes, **@html_attrs do %><% if title? %><%= title %><% end %><% if description? %><%= description %><% end %><%= content %><% end %>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class UI::Card::Header < UI::Base
|
|
4
|
+
renders_one :title, UI::Card::Title
|
|
5
|
+
renders_one :description, UI::Card::Description
|
|
6
|
+
|
|
7
|
+
def initialize(class: nil, **html_attrs)
|
|
8
|
+
@user_class = binding.local_variable_get(:class)
|
|
9
|
+
@html_attrs = html_attrs
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def classes
|
|
13
|
+
cn("flex flex-col gap-1.5 p-6", @user_class)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= tag.h3 class: classes, **@html_attrs do %><%= content %><% end %>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class UI::Card::Title < UI::Base
|
|
4
|
+
def initialize(class: nil, **html_attrs)
|
|
5
|
+
@user_class = binding.local_variable_get(:class)
|
|
6
|
+
@html_attrs = html_attrs
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def classes
|
|
10
|
+
cn("text-lg font-semibold leading-none tracking-tight", @user_class)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= tag.div class: classes, **@html_attrs do %><%= header if header? %><%= card_content if card_content? %><%= footer if footer? %><%= content %><% end %>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class UI::Card < UI::Base
|
|
4
|
+
renders_one :header, UI::Card::Header
|
|
5
|
+
renders_one :card_content, UI::Card::Content
|
|
6
|
+
renders_one :footer, UI::Card::Footer
|
|
7
|
+
|
|
8
|
+
alias_method :with_content, :with_card_content
|
|
9
|
+
|
|
10
|
+
def initialize(class: nil, **html_attrs)
|
|
11
|
+
@user_class = binding.local_variable_get(:class)
|
|
12
|
+
@html_attrs = html_attrs
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def classes
|
|
16
|
+
cn("rounded-xl border bg-card text-card-foreground shadow", @user_class)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<% if label.present? %>
|
|
2
|
+
<%= tag.label for: @id, class: wrapper_classes do %>
|
|
3
|
+
<%= tag.span class: "relative inline-flex" do %>
|
|
4
|
+
<% if form_field? %><%= form_check_box %><% else %><%= tag.input(**input_options) %><% end %>
|
|
5
|
+
<%= tag.span class: overlay_classes do %>
|
|
6
|
+
<%= render(UI::Icon.new(name: "check", size: icon_size, class: "stroke-[3]", aria: { hidden: true })) %>
|
|
7
|
+
<% end %>
|
|
8
|
+
<% end %>
|
|
9
|
+
<%= tag.span class: label_text_classes do %><%= label %><% if required? %><%= tag.span(" *", class: "text-destructive ml-0.5") %><% end %><% end %>
|
|
10
|
+
<% end %>
|
|
11
|
+
<% else %>
|
|
12
|
+
<%= tag.span class: "relative inline-flex" do %>
|
|
13
|
+
<% if form_field? %><%= form_check_box %><% else %><%= tag.input(**input_options) %><% end %>
|
|
14
|
+
<%= tag.span class: overlay_classes do %>
|
|
15
|
+
<%= render(UI::Icon.new(name: "check", size: icon_size, class: "stroke-[3]", aria: { hidden: true })) %>
|
|
16
|
+
<% end %>
|
|
17
|
+
<% end %>
|
|
18
|
+
<% end %>
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class UI::Checkbox < UI::Base
|
|
4
|
+
ICON_SIZE_FOR_SIZE = { sm: :xs, default: :xs, lg: :sm }.freeze
|
|
5
|
+
|
|
6
|
+
variants(
|
|
7
|
+
base: "peer appearance-none shrink-0 border border-input bg-background " \
|
|
8
|
+
"transition-all " \
|
|
9
|
+
"checked:bg-primary checked:border-primary " \
|
|
10
|
+
"focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-1 " \
|
|
11
|
+
"ring-offset-background",
|
|
12
|
+
variants: {
|
|
13
|
+
size: {
|
|
14
|
+
sm: "w-3.5 h-3.5 rounded-[3px]",
|
|
15
|
+
default: "w-4 h-4 rounded-[4px]",
|
|
16
|
+
lg: "w-5 h-5 rounded-[5px]"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
defaults: { size: :default }
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
def initialize(name: nil, value: "1", unchecked_value: "0", checked: false,
|
|
23
|
+
label: nil, size: :default,
|
|
24
|
+
form: nil, field: nil,
|
|
25
|
+
required: false, error: false, disabled: false,
|
|
26
|
+
class: nil, **html_attrs)
|
|
27
|
+
@form = form
|
|
28
|
+
@field = field
|
|
29
|
+
@name = name
|
|
30
|
+
@value = value
|
|
31
|
+
@unchecked_value = unchecked_value
|
|
32
|
+
@checked = checked
|
|
33
|
+
@label_text = label
|
|
34
|
+
@size = size
|
|
35
|
+
@required = required
|
|
36
|
+
@error = error
|
|
37
|
+
@disabled = disabled
|
|
38
|
+
@user_class = binding.local_variable_get(:class)
|
|
39
|
+
@html_attrs = html_attrs
|
|
40
|
+
@id = @html_attrs[:id] || ("ui-cb-#{SecureRandom.hex(4)}" if label?)
|
|
41
|
+
@html_attrs[:id] = @id if @id
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def form_field?
|
|
45
|
+
@form.present? && @field.present?
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Same convention as UI::Input: bound to a form object the attribute name
|
|
49
|
+
# supplies the label text (via human_attribute_name, so i18n applies);
|
|
50
|
+
# `label:` overrides it and `label: false` opts out.
|
|
51
|
+
def label?
|
|
52
|
+
return false if @label_text == false
|
|
53
|
+
|
|
54
|
+
@label_text.present? || form_field?
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def label
|
|
58
|
+
return nil unless label?
|
|
59
|
+
return @label_text if @label_text.is_a?(String) && @label_text.present?
|
|
60
|
+
return nil unless form_field?
|
|
61
|
+
|
|
62
|
+
object = @form.object
|
|
63
|
+
if object.respond_to?(:class) && object.class.respond_to?(:human_attribute_name)
|
|
64
|
+
object.class.human_attribute_name(@field)
|
|
65
|
+
else
|
|
66
|
+
@field.to_s.humanize
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Rails' check_box also emits the companion hidden field, so an unchecked box
|
|
71
|
+
# still submits its unchecked value.
|
|
72
|
+
def form_check_box
|
|
73
|
+
@form.check_box(@field, form_input_options, @value, @unchecked_value)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def form_input_options
|
|
77
|
+
input_options.except(:type, :name, :value, :checked)
|
|
78
|
+
end
|
|
79
|
+
def required?; @required; end
|
|
80
|
+
def icon_size; ICON_SIZE_FOR_SIZE.fetch(@size); end
|
|
81
|
+
|
|
82
|
+
def input_options
|
|
83
|
+
opts = @html_attrs.dup
|
|
84
|
+
opts[:type] = :checkbox
|
|
85
|
+
opts[:name] = @name if @name
|
|
86
|
+
opts[:value] = @value
|
|
87
|
+
opts[:checked] = true if @checked
|
|
88
|
+
opts[:disabled] = true if @disabled
|
|
89
|
+
opts[:required] = true if @required
|
|
90
|
+
opts[:"aria-invalid"] = "true" if @error
|
|
91
|
+
opts[:class] = classes
|
|
92
|
+
# Add accessible name when no visual label is present
|
|
93
|
+
if !label? && opts[:"aria-label"].blank?
|
|
94
|
+
opts[:"aria-label"] = accessible_name
|
|
95
|
+
end
|
|
96
|
+
opts
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def accessible_name
|
|
100
|
+
# Derive accessible name from name attribute or use default
|
|
101
|
+
return "Checkbox" if @name.blank?
|
|
102
|
+
|
|
103
|
+
# Convert "user[terms]" to "User terms"
|
|
104
|
+
@name.to_s.gsub(/[\[\]_]/, " ").gsub(/\s+/, " ").strip.titleize
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def classes
|
|
108
|
+
cn(
|
|
109
|
+
variant_classes(size: @size),
|
|
110
|
+
{ "border-destructive aria-invalid:focus:ring-destructive" => @error },
|
|
111
|
+
@user_class
|
|
112
|
+
)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def overlay_classes
|
|
116
|
+
"pointer-events-none absolute inset-0 flex items-center justify-center " \
|
|
117
|
+
"text-primary-foreground opacity-0 peer-checked:opacity-100 transition-opacity"
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def wrapper_classes
|
|
121
|
+
cn(
|
|
122
|
+
"inline-flex items-center gap-2 select-none",
|
|
123
|
+
{ "cursor-not-allowed opacity-60" => @disabled }
|
|
124
|
+
)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def label_text_classes
|
|
128
|
+
cn(
|
|
129
|
+
"text-sm leading-none",
|
|
130
|
+
{ "text-muted-foreground" => @disabled }
|
|
131
|
+
)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# UI::DataGrid — a sortable, filterable, server-paged table backed by AG Grid.
|
|
4
|
+
#
|
|
5
|
+
# The grid holds one page at a time and asks the server for the next, sending
|
|
6
|
+
# the sort and filter it wants applied, so paging, sorting and filtering all
|
|
7
|
+
# happen in the database. The server answers GET `url` with `startRow`,
|
|
8
|
+
# `endRow`, `sortModel` and `filterModel` (both JSON) as params, returning
|
|
9
|
+
# `{ "rows": [...], "total": n }`.
|
|
10
|
+
#
|
|
11
|
+
# Columns are declared in Ruby so the headers, their widths and which of them
|
|
12
|
+
# the server will accept a sort or filter for are decided in one place.
|
|
13
|
+
#
|
|
14
|
+
# AG Grid is ~365 KB gzipped, so the Stimulus controller fetches it at connect
|
|
15
|
+
# time: a page without a grid never downloads it.
|
|
16
|
+
#
|
|
17
|
+
# Usage:
|
|
18
|
+
# <%= render(UI::DataGrid.new(url: customers_path(format: :json)) do |grid|
|
|
19
|
+
# grid.column(:id, t(".id"), width: 90, type: :number)
|
|
20
|
+
# grid.column(:name, t(".name"))
|
|
21
|
+
# grid.column(:owner, t(".owner"), sortable: false, filterable: false)
|
|
22
|
+
# end) %>
|
|
23
|
+
class UI::DataGrid < UI::Base
|
|
24
|
+
Column = Struct.new(:field, :header, :width, :type, :sortable, :filterable,
|
|
25
|
+
keyword_init: true)
|
|
26
|
+
|
|
27
|
+
def initialize(url:, page_size: 15, empty_message: nil, class: nil, **html_attrs)
|
|
28
|
+
@url = url
|
|
29
|
+
@page_size = page_size
|
|
30
|
+
@empty_message = empty_message
|
|
31
|
+
@columns = []
|
|
32
|
+
@user_class = binding.local_variable_get(:class)
|
|
33
|
+
@html_attrs = html_attrs
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# sortable/filterable default to true, and are turned off for a column the
|
|
37
|
+
# server has no SQL expression for — a value read through an association,
|
|
38
|
+
# or one translated for display. Offering the control and then ignoring it
|
|
39
|
+
# would be worse than not offering it.
|
|
40
|
+
def column(field, header, width: nil, type: :text, sortable: true, filterable: true)
|
|
41
|
+
@columns << Column.new(field: field.to_s, header: header, width: width, type: type,
|
|
42
|
+
sortable: sortable, filterable: filterable)
|
|
43
|
+
self
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def before_render
|
|
47
|
+
content # declares the columns
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Rows navigate on click when the server sends a path with them, so the
|
|
51
|
+
# cursor has to say so.
|
|
52
|
+
def classes = cn("[&_.ag-row]:cursor-pointer", @user_class)
|
|
53
|
+
|
|
54
|
+
def grid_options
|
|
55
|
+
{
|
|
56
|
+
controller: "ui--data-grid",
|
|
57
|
+
"ui--data-grid-url-value": @url,
|
|
58
|
+
"ui--data-grid-columns-value": column_specs.to_json,
|
|
59
|
+
"ui--data-grid-page-size-value": @page_size,
|
|
60
|
+
"ui--data-grid-empty-message-value": @empty_message || t("ui.data_grid.empty"),
|
|
61
|
+
# Propshaft digests the filename, so the URL has to come from Ruby.
|
|
62
|
+
"ui--data-grid-module-url-value": helpers.asset_path("ag_grid.js")
|
|
63
|
+
}
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Reserves the grid's final height before its chunk arrives, so the page does
|
|
67
|
+
# not jump once it paints.
|
|
68
|
+
def reserved_height = "#{HEADER_HEIGHT + (ROW_HEIGHT * @page_size) + FOOTER_HEIGHT}px"
|
|
69
|
+
|
|
70
|
+
HEADER_HEIGHT = 36
|
|
71
|
+
ROW_HEIGHT = 36
|
|
72
|
+
FOOTER_HEIGHT = 52
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def column_specs
|
|
77
|
+
@columns.map do |column|
|
|
78
|
+
spec = { field: column.field, headerName: column.header, type: column.type,
|
|
79
|
+
sortable: column.sortable, filterable: column.filterable }
|
|
80
|
+
spec[:width] = column.width if column.width
|
|
81
|
+
spec
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|