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,70 @@
|
|
|
1
|
+
<div class="<%= classes %>" data-controller="ui--date-picker"
|
|
2
|
+
data-ui--date-picker-module-url-value="<%= helpers.asset_path('cally.js') %>"
|
|
3
|
+
data-ui--date-picker-order-value="<%= part_order %>"
|
|
4
|
+
data-ui--date-picker-separator-value="<%= separator %>"
|
|
5
|
+
data-action="click@window->ui--date-picker#closeOnOutsideClick keydown@window->ui--date-picker#closeOnEscape"
|
|
6
|
+
<%= tag.attributes(@html_attrs) %>>
|
|
7
|
+
<% if render_label? %>
|
|
8
|
+
<label for="<%= input_id %>" class="mb-1.5 block text-sm font-medium text-foreground">
|
|
9
|
+
<%= label_text %><% if @required %><span class="ml-0.5 text-destructive">*</span><% end %>
|
|
10
|
+
</label>
|
|
11
|
+
<% end %>
|
|
12
|
+
|
|
13
|
+
<div class="relative">
|
|
14
|
+
<%# What the user types. Not name'd: it is never submitted, so a
|
|
15
|
+
half-typed date cannot reach the server. %>
|
|
16
|
+
<input type="text" id="<%= input_id %>" inputmode="numeric" autocomplete="off"
|
|
17
|
+
placeholder="<%= placeholder %>" class="<%= input_classes %>"
|
|
18
|
+
value="<%= typed_value %>"
|
|
19
|
+
<%= "required" if @required %> <%= "disabled" if @disabled %>
|
|
20
|
+
<%= "aria-describedby=\"#{input_id}-hint\"".html_safe if render_hint? %>
|
|
21
|
+
data-ui--date-picker-target="input"
|
|
22
|
+
data-action="input->ui--date-picker#typed">
|
|
23
|
+
|
|
24
|
+
<%# What the form submits. %>
|
|
25
|
+
<input type="hidden" name="<%= field_name %>" value="<%= iso_value %>"
|
|
26
|
+
data-ui--date-picker-target="hidden">
|
|
27
|
+
|
|
28
|
+
<button type="button" aria-haspopup="dialog" aria-expanded="false"
|
|
29
|
+
aria-controls="<%= panel_id %>"
|
|
30
|
+
aria-label="<%= t("ui.date_picker.open") %>"
|
|
31
|
+
class="absolute inset-y-0 right-0 flex w-9 items-center justify-center rounded-r-md text-muted-foreground hover:text-foreground focus:outline-none focus:ring-2 focus:ring-primary"
|
|
32
|
+
<%= "disabled" if @disabled %>
|
|
33
|
+
data-ui--date-picker-target="toggle"
|
|
34
|
+
data-action="ui--date-picker#toggle">
|
|
35
|
+
<%= render(UI::Icon.new(name: "calendar", class: "size-4")) %>
|
|
36
|
+
</button>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<% if render_hint? %>
|
|
40
|
+
<p id="<%= input_id %>-hint" class="mt-1 text-xs text-muted-foreground">
|
|
41
|
+
<%= t("ui.date_picker.hint", pattern: placeholder) %>
|
|
42
|
+
</p>
|
|
43
|
+
<% end %>
|
|
44
|
+
|
|
45
|
+
<div id="<%= panel_id %>" role="dialog" hidden
|
|
46
|
+
class="absolute z-30 mt-1 rounded-md border bg-popover p-3 text-popover-foreground shadow-md"
|
|
47
|
+
data-ui--date-picker-target="panel">
|
|
48
|
+
<%# calendar-select-year is why this library was chosen: reaching 2012 for
|
|
49
|
+
a date of birth is one click, not a hundred and seventy. %>
|
|
50
|
+
<%# Cally puts each arrow inside its own button, so ours are icons with
|
|
51
|
+
words for screen readers; the month and year selects fill its heading
|
|
52
|
+
slot, between the arrows. %>
|
|
53
|
+
<%= tag.send(:"calendar-date",
|
|
54
|
+
safe_join([
|
|
55
|
+
tag.span(safe_join([ render(UI::Icon.new(name: "chevron-left", size: :sm)),
|
|
56
|
+
tag.span(t("ui.date_picker.previous"), class: "sr-only") ]),
|
|
57
|
+
slot: "previous", class: "inline-flex"),
|
|
58
|
+
tag.span(safe_join([ render(UI::Icon.new(name: "chevron-right", size: :sm)),
|
|
59
|
+
tag.span(t("ui.date_picker.next"), class: "sr-only") ]),
|
|
60
|
+
slot: "next", class: "inline-flex"),
|
|
61
|
+
tag.div(safe_join([ tag.send(:"calendar-select-month"), tag.send(:"calendar-select-year") ]),
|
|
62
|
+
slot: "heading", class: "flex items-center gap-1.5"),
|
|
63
|
+
tag.send(:"calendar-month")
|
|
64
|
+
]),
|
|
65
|
+
locale: I18n.locale == :en ? "en-IN" : I18n.locale.to_s,
|
|
66
|
+
"first-day-of-week": 1, min: @min, max: @max,
|
|
67
|
+
data: { "ui--date-picker-target": "calendar",
|
|
68
|
+
action: "change->ui--date-picker#picked" }) %>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# UI::DatePicker — a typed date field with a calendar beside it.
|
|
4
|
+
#
|
|
5
|
+
# Three inputs in one: what the user types (DD/MM/YYYY), what the form submits
|
|
6
|
+
# (ISO, hidden), and the calendar. The typed field comes first because people
|
|
7
|
+
# who know the date type it in a second: reaching a date years away in a
|
|
8
|
+
# calendar means clicking back month after month.
|
|
9
|
+
#
|
|
10
|
+
# The written format is not decided here. It comes from `date.formats.typed`
|
|
11
|
+
# in the locale, and this component derives the placeholder, the parse order
|
|
12
|
+
# and the separator from that one string — so a locale that writes dates
|
|
13
|
+
# differently changes what the field accepts, what it shows and what the hint
|
|
14
|
+
# says, together, with nothing hardcoded.
|
|
15
|
+
#
|
|
16
|
+
# The shipped English format is day-first; change `date.formats.typed` for
|
|
17
|
+
# month-first. A native date input follows the browser's locale instead, which
|
|
18
|
+
# is how the same form reads MM/DD on one machine and DD/MM on another, and how
|
|
19
|
+
# the 2nd of May gets recorded as the 5th of February.
|
|
20
|
+
#
|
|
21
|
+
# Cally is ~9 KB gzipped and loaded only when the calendar is first opened, so
|
|
22
|
+
# a form nobody opens a calendar on downloads nothing.
|
|
23
|
+
#
|
|
24
|
+
# Usage:
|
|
25
|
+
# <%= render(UI::DatePicker.new(form: f, field: :starts_on, required: true)) %>
|
|
26
|
+
# <%= render(UI::DatePicker.new(name: "event[date]", value: @date, label: "Date")) %>
|
|
27
|
+
class UI::DatePicker < UI::Base
|
|
28
|
+
def initialize(name: nil, value: nil, form: nil, field: nil, label: nil,
|
|
29
|
+
required: false, error: false, disabled: false, hint: true,
|
|
30
|
+
min: nil, max: nil, class: nil, **html_attrs)
|
|
31
|
+
@name = name
|
|
32
|
+
@value = value
|
|
33
|
+
@form = form
|
|
34
|
+
@field = field
|
|
35
|
+
@label = label
|
|
36
|
+
@required = required
|
|
37
|
+
# The hint teaches the format, which a form field wants and a filter
|
|
38
|
+
# toolbar does not — there the value is already on
|
|
39
|
+
# screen in the format the hint would describe.
|
|
40
|
+
@hint = hint
|
|
41
|
+
@error = error
|
|
42
|
+
@disabled = disabled
|
|
43
|
+
@min = min
|
|
44
|
+
@max = max
|
|
45
|
+
@user_class = binding.local_variable_get(:class)
|
|
46
|
+
@html_attrs = html_attrs
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def field_name = @name || (@form && @field && "#{@form.object_name}[#{@field}]")
|
|
50
|
+
|
|
51
|
+
# The value the form submits, always ISO, whatever was typed.
|
|
52
|
+
def iso_value
|
|
53
|
+
raw = @value || (@form && @field && @form.object&.public_send(@field))
|
|
54
|
+
raw.respond_to?(:strftime) ? raw.strftime("%Y-%m-%d") : raw.presence
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def label_text
|
|
58
|
+
return @label if @label.present?
|
|
59
|
+
return nil unless @form && @field
|
|
60
|
+
|
|
61
|
+
@form.object.class.try(:human_attribute_name, @field) || @field.to_s.humanize
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def render_label? = @label != false && label_text.present?
|
|
65
|
+
def render_hint? = @hint != false
|
|
66
|
+
|
|
67
|
+
def input_id = @input_id ||= "ui-date-#{SecureRandom.hex(4)}"
|
|
68
|
+
def panel_id = "#{input_id}-panel"
|
|
69
|
+
|
|
70
|
+
def classes = cn("relative", @user_class)
|
|
71
|
+
|
|
72
|
+
def input_classes
|
|
73
|
+
cn(
|
|
74
|
+
"flex w-full rounded-md border border-input bg-background text-foreground",
|
|
75
|
+
"placeholder:text-muted-foreground transition-all focus:outline-none",
|
|
76
|
+
"focus:ring-2 focus:ring-primary focus:ring-offset-1 ring-offset-background",
|
|
77
|
+
"disabled:cursor-not-allowed disabled:opacity-50 h-9 pl-3 pr-10 py-2 text-sm",
|
|
78
|
+
# aria-invalid rather than a Ruby flag, so a date the user mistypes is
|
|
79
|
+
# marked without a round trip.
|
|
80
|
+
"aria-invalid:border-destructive aria-invalid:ring-destructive",
|
|
81
|
+
@error && "border-destructive"
|
|
82
|
+
)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# The one string everything else here is derived from.
|
|
86
|
+
def date_format = I18n.t("date.formats.typed")
|
|
87
|
+
|
|
88
|
+
# "%d/%m/%Y" -> "DD/MM/YYYY". Shown as the placeholder and in the hint, so
|
|
89
|
+
# what the user is told matches what the parser accepts by construction.
|
|
90
|
+
def placeholder
|
|
91
|
+
date_format.gsub("%d", "DD").gsub("%m", "MM").gsub("%Y", "YYYY").gsub("%y", "YY")
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# "%d/%m/%Y" -> "DMY". The controller builds its parser from this rather
|
|
95
|
+
# than assuming an order.
|
|
96
|
+
def part_order = date_format.scan(/%([dmyY])/).flatten.map(&:upcase).join
|
|
97
|
+
|
|
98
|
+
def separator = date_format[/[^%a-zA-Z]/] || "/"
|
|
99
|
+
|
|
100
|
+
# Rendered server-side in the locale's format, so the field is already right
|
|
101
|
+
# before any JavaScript has loaded.
|
|
102
|
+
def typed_value
|
|
103
|
+
iso = iso_value
|
|
104
|
+
return nil if iso.blank?
|
|
105
|
+
|
|
106
|
+
date = iso.respond_to?(:strftime) ? iso : Date.parse(iso.to_s)
|
|
107
|
+
I18n.l(date, format: :typed)
|
|
108
|
+
rescue Date::Error
|
|
109
|
+
iso
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class UI::Dialog::Close < UI::Base
|
|
4
|
+
def initialize(variant: :ghost, size: :icon_sm, class: nil, **html_attrs)
|
|
5
|
+
@variant = variant
|
|
6
|
+
@size = size
|
|
7
|
+
@user_class = binding.local_variable_get(:class)
|
|
8
|
+
@html_attrs = html_attrs
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def set_content_context(content_context)
|
|
12
|
+
@content_context = content_context
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def button_class
|
|
16
|
+
if content.present?
|
|
17
|
+
cn(@user_class)
|
|
18
|
+
else
|
|
19
|
+
cn("absolute right-4 top-4 p-0", @user_class)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def button_options
|
|
24
|
+
opts = @html_attrs.deep_dup
|
|
25
|
+
opts[:variant] = @variant
|
|
26
|
+
opts[:size] = effective_size
|
|
27
|
+
opts[:type] = :button
|
|
28
|
+
opts[:class] = button_class
|
|
29
|
+
opts[:data] = merged_data
|
|
30
|
+
opts[:aria] = merged_aria
|
|
31
|
+
opts
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def merged_data
|
|
37
|
+
user_data = (@html_attrs[:data] || {}).deep_dup
|
|
38
|
+
action = [ user_data.delete(:action) || user_data.delete("action"), "click->ui--dialog#close" ].compact.join(" ")
|
|
39
|
+
|
|
40
|
+
user_data.merge(
|
|
41
|
+
action: action,
|
|
42
|
+
ui_dialog_close: true
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def merged_aria
|
|
47
|
+
(@html_attrs[:aria] || {}).deep_dup.reverse_merge(label: I18n.t("ui.dialog.close_label", default: "Close"))
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def effective_size
|
|
51
|
+
content.present? && [ :icon, :icon_sm ].include?(@size) ? :default : @size
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<%# Force the slot block to run first so with_title/with_description/with_footer
|
|
2
|
+
side effects materialise BEFORE we check title?/description?/footer?. %>
|
|
3
|
+
<% body = content %>
|
|
4
|
+
<%= tag.dialog(**html_options) do %>
|
|
5
|
+
<%= tag.div(class: panel_classes, data: { state: "closed", "ui--dialog-part": "panel" }) do %>
|
|
6
|
+
<% if show_close? %>
|
|
7
|
+
<%= render(UI::Dialog::Close.new) %>
|
|
8
|
+
<% end %>
|
|
9
|
+
<% if title? || description? %>
|
|
10
|
+
<header class="flex flex-col space-y-1.5 text-center sm:text-left">
|
|
11
|
+
<% if title? %>
|
|
12
|
+
<%= tag.h2(title_html, id: title_id, class: "text-lg font-semibold leading-none tracking-tight") %>
|
|
13
|
+
<% end %>
|
|
14
|
+
<% if description? %>
|
|
15
|
+
<%= tag.p(description_html, id: description_id, class: "text-sm text-muted-foreground") %>
|
|
16
|
+
<% end %>
|
|
17
|
+
</header>
|
|
18
|
+
<% end %>
|
|
19
|
+
<% if body.present? %>
|
|
20
|
+
<div class="overflow-y-auto flex-1 min-h-0 <%= title? || description? ? 'mt-4' : '' %>"><%= body %></div>
|
|
21
|
+
<% end %>
|
|
22
|
+
<% if footer? %>
|
|
23
|
+
<footer class="mt-6 flex flex-col-reverse gap-2 sm:flex-row sm:justify-end"><%= footer_html %></footer>
|
|
24
|
+
<% end %>
|
|
25
|
+
<% end %>
|
|
26
|
+
<% end %>
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# UI::Dialog::Content — the native <dialog> element. Exposes block helpers for
|
|
4
|
+
# the optional title, description, and footer regions instead of nested
|
|
5
|
+
# ViewComponent sub-classes (one file, fewer indirections).
|
|
6
|
+
class UI::Dialog::Content < UI::Base
|
|
7
|
+
PANEL_BASE =
|
|
8
|
+
"relative flex flex-col w-full max-h-[calc(100vh-2rem)] overflow-hidden " \
|
|
9
|
+
"rounded-lg border bg-background p-6 shadow-lg " \
|
|
10
|
+
"data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 " \
|
|
11
|
+
"data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95"
|
|
12
|
+
|
|
13
|
+
variants(
|
|
14
|
+
base: PANEL_BASE,
|
|
15
|
+
variants: {
|
|
16
|
+
size: {
|
|
17
|
+
sm: "max-w-md",
|
|
18
|
+
default: "max-w-lg",
|
|
19
|
+
lg: "max-w-2xl",
|
|
20
|
+
xl: "max-w-4xl"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
defaults: { size: :default }
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
def initialize(size: :default, show_close: true, class: nil, **html_attrs)
|
|
27
|
+
@size = size
|
|
28
|
+
@show_close = show_close
|
|
29
|
+
@user_class = binding.local_variable_get(:class)
|
|
30
|
+
@html_attrs = html_attrs
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def set_dialog_context(dialog)
|
|
34
|
+
@dialog = dialog
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# ── Inline slot helpers (block ↔ text) ────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
def with_title(text = nil, &block)
|
|
40
|
+
@title_text = text
|
|
41
|
+
@title_block = block
|
|
42
|
+
self
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def with_description(text = nil, &block)
|
|
46
|
+
@description_text = text
|
|
47
|
+
@description_block = block
|
|
48
|
+
self
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def with_footer(&block)
|
|
52
|
+
@footer_block = block
|
|
53
|
+
self
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def title?; @title_text.present? || @title_block.present?; end
|
|
57
|
+
def description?; @description_text.present? || @description_block.present?; end
|
|
58
|
+
def footer?; @footer_block.present?; end
|
|
59
|
+
def show_close?; @show_close; end
|
|
60
|
+
|
|
61
|
+
def title_html
|
|
62
|
+
@title_block ? view_context.capture(&@title_block) : @title_text
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def description_html
|
|
66
|
+
@description_block ? view_context.capture(&@description_block) : @description_text
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def footer_html
|
|
70
|
+
view_context.capture(&@footer_block) if @footer_block
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# ── Class strings ────────────────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
def panel_classes
|
|
76
|
+
cn(variant_classes(size: @size), @user_class)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# The native <dialog> takes the full viewport and centers the panel inside.
|
|
80
|
+
# Backdrop styling is owned by the ::backdrop pseudo-element so we don't
|
|
81
|
+
# need a separate overlay div.
|
|
82
|
+
def dialog_classes
|
|
83
|
+
"fixed inset-0 m-0 p-0 max-w-none max-h-none w-full h-full " \
|
|
84
|
+
"bg-transparent border-0 overflow-visible " \
|
|
85
|
+
"open:flex open:items-center open:justify-center " \
|
|
86
|
+
"backdrop:bg-black/60 backdrop:backdrop-blur-[1px]"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def html_options
|
|
90
|
+
opts = @html_attrs.dup
|
|
91
|
+
opts[:id] = @dialog.id
|
|
92
|
+
opts[:class] = dialog_classes
|
|
93
|
+
opts[:"aria-labelledby"] = @dialog.title_id if title?
|
|
94
|
+
opts[:"aria-describedby"] = @dialog.description_id if description?
|
|
95
|
+
opts[:"data-ui--dialog-target"] = "content"
|
|
96
|
+
opts[:"data-state"] = "closed"
|
|
97
|
+
opts
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def title_id; @dialog.title_id; end
|
|
101
|
+
def description_id; @dialog.description_id; end
|
|
102
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= tag.p(id: @content_context.description_id, class: classes, **@html_attrs) do %><%= content %><% end %>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class UI::Dialog::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 set_content_context(content_context)
|
|
10
|
+
@content_context = content_context
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def classes
|
|
14
|
+
cn("text-sm text-muted-foreground", @user_class)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= tag.div(class: classes, data: { "ui--dialog-part": "footer" }, **@html_attrs) do %><%= content %><% end %>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class UI::Dialog::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 set_content_context(_content_context); end
|
|
10
|
+
|
|
11
|
+
def classes
|
|
12
|
+
cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", @user_class)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= tag.div(class: classes, data: { "ui--dialog-part": "header" }, **@html_attrs) do %><%= content %><% end %>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class UI::Dialog::Header < 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 set_content_context(content_context)
|
|
10
|
+
@content_context = content_context
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def classes
|
|
14
|
+
cn("flex flex-col space-y-1.5 text-center sm:text-left", @user_class)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= tag.h2(id: @content_context.title_id, class: classes, **@html_attrs) do %><%= content %><% end %>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class UI::Dialog::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 set_content_context(content_context)
|
|
10
|
+
@content_context = content_context
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def classes
|
|
14
|
+
cn("text-lg font-semibold leading-none tracking-tight", @user_class)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# UI::Dialog::Trigger — click-to-open wrapper.
|
|
4
|
+
#
|
|
5
|
+
# By default renders a UI::Button so variant/size styling works out of the
|
|
6
|
+
# box. Pass as: :a (or any other tag name) to fall back to a plain tag.
|
|
7
|
+
class UI::Dialog::Trigger < UI::Base
|
|
8
|
+
def initialize(as: :button, variant: :default, size: :default,
|
|
9
|
+
class: nil, **html_attrs)
|
|
10
|
+
@as = as.to_sym
|
|
11
|
+
@variant = variant
|
|
12
|
+
@size = size
|
|
13
|
+
@user_class = binding.local_variable_get(:class)
|
|
14
|
+
@html_attrs = html_attrs
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def set_dialog_context(dialog)
|
|
18
|
+
@dialog = dialog
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# ── Options passed to UI::Button (when @as == :button) ───────────────────
|
|
22
|
+
|
|
23
|
+
def button_options
|
|
24
|
+
opts = @html_attrs.deep_dup
|
|
25
|
+
opts[:variant] = @variant
|
|
26
|
+
opts[:size] = @size
|
|
27
|
+
opts[:type] = :button
|
|
28
|
+
opts[:class] = cn(@user_class)
|
|
29
|
+
opts[:data] = merged_data
|
|
30
|
+
opts[:aria] = merged_aria
|
|
31
|
+
opts
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# ── Options passed to tag.public_send (when @as != :button) ──────────────
|
|
35
|
+
|
|
36
|
+
def html_options
|
|
37
|
+
opts = @html_attrs.dup
|
|
38
|
+
opts[:class] = cn(@user_class)
|
|
39
|
+
|
|
40
|
+
user_data = (opts.delete(:data) || {}).dup
|
|
41
|
+
user_action = user_data.delete(:action) || user_data.delete("action")
|
|
42
|
+
opts[:data] = user_data.merge(
|
|
43
|
+
"ui--dialog-target": "trigger",
|
|
44
|
+
action: [ user_action, "click->ui--dialog#open" ].compact.join(" ")
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
user_aria = (opts.delete(:aria) || {}).dup
|
|
48
|
+
opts[:aria] = user_aria.merge(
|
|
49
|
+
haspopup: "dialog",
|
|
50
|
+
expanded: "false",
|
|
51
|
+
controls: @dialog.id
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
opts
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def merged_data
|
|
60
|
+
user_data = (@html_attrs[:data] || {}).deep_dup
|
|
61
|
+
user_action = user_data.delete(:action) || user_data.delete("action")
|
|
62
|
+
user_data.merge(
|
|
63
|
+
"ui--dialog-target": "trigger",
|
|
64
|
+
action: [ user_action, "click->ui--dialog#open" ].compact.join(" ")
|
|
65
|
+
)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def merged_aria
|
|
69
|
+
(@html_attrs[:aria] || {}).deep_dup.merge(
|
|
70
|
+
haspopup: "dialog",
|
|
71
|
+
expanded: "false",
|
|
72
|
+
controls: @dialog.id
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# UI::Dialog — modal dialog backed by the native <dialog> element + showModal().
|
|
4
|
+
#
|
|
5
|
+
# Why native <dialog>:
|
|
6
|
+
# - Browser owns the modal mode, focus trap, ESC handling, and inert
|
|
7
|
+
# background. We do not reimplement any of that in JS.
|
|
8
|
+
# - The Stimulus controller only opens, closes, and dismisses on backdrop
|
|
9
|
+
# click. Everything else is delegated to the platform.
|
|
10
|
+
#
|
|
11
|
+
# Compound:
|
|
12
|
+
# UI::Dialog
|
|
13
|
+
# UI::Dialog::Trigger — wraps any element with a click action
|
|
14
|
+
# UI::Dialog::Content — the <dialog> box; exposes with_title /
|
|
15
|
+
# with_description / with_footer block helpers
|
|
16
|
+
# (no further sub-component classes).
|
|
17
|
+
class UI::Dialog < UI::Base
|
|
18
|
+
renders_one :trigger, ->(**opts) {
|
|
19
|
+
UI::Dialog::Trigger.new(**opts).tap { |t| t.set_dialog_context(self) }
|
|
20
|
+
}
|
|
21
|
+
renders_one :body, ->(**opts) {
|
|
22
|
+
UI::Dialog::Content.new(**opts).tap { |c| c.set_dialog_context(self) }
|
|
23
|
+
}
|
|
24
|
+
alias_method :with_content, :with_body
|
|
25
|
+
|
|
26
|
+
def initialize(class: nil, **html_attrs)
|
|
27
|
+
@user_class = binding.local_variable_get(:class)
|
|
28
|
+
@html_attrs = html_attrs
|
|
29
|
+
@id = @html_attrs[:id] || "ui-dialog-#{SecureRandom.hex(4)}"
|
|
30
|
+
@html_attrs.delete(:id)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
attr_reader :id
|
|
34
|
+
|
|
35
|
+
def title_id; "#{@id}-title"; end
|
|
36
|
+
def description_id; "#{@id}-description"; end
|
|
37
|
+
|
|
38
|
+
def root_classes
|
|
39
|
+
cn("contents", @user_class)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<% if label.present? %>
|
|
2
|
+
<%= tag.div class: wrapper_classes, **@html_attrs do %>
|
|
3
|
+
<%= tag.hr class: rule_classes %>
|
|
4
|
+
<%= tag.span label, class: label_classes %>
|
|
5
|
+
<%= tag.hr class: rule_classes %>
|
|
6
|
+
<% end %>
|
|
7
|
+
<% else %>
|
|
8
|
+
<%= tag.hr class: classes, **@html_attrs %>
|
|
9
|
+
<% end %>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class UI::Divider < UI::Base
|
|
4
|
+
variants(
|
|
5
|
+
base: "shrink-0 border-border",
|
|
6
|
+
variants: {
|
|
7
|
+
orientation: {
|
|
8
|
+
horizontal: "border-t w-full",
|
|
9
|
+
vertical: "border-l self-stretch h-full"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
defaults: { orientation: :horizontal }
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
def initialize(orientation: :horizontal, label: nil, class: nil, **html_attrs)
|
|
16
|
+
@orientation = orientation.to_sym
|
|
17
|
+
@label_text = label
|
|
18
|
+
@user_class = binding.local_variable_get(:class)
|
|
19
|
+
@html_attrs = html_attrs
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def label; @label_text; end
|
|
23
|
+
|
|
24
|
+
def classes
|
|
25
|
+
cn(variant_classes(orientation: @orientation), @user_class)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Beside a label each rule takes half the leftover space; `w-full` on both
|
|
29
|
+
# pushed the label and the second rule out of the row.
|
|
30
|
+
def rule_classes
|
|
31
|
+
cn(classes, "w-auto flex-1")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def wrapper_classes
|
|
35
|
+
cn("flex items-center gap-3", @user_class) if label.present?
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def label_classes
|
|
39
|
+
"text-xs text-muted-foreground whitespace-nowrap"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= tag.i data: (@html_attrs[:data] || {}).merge(lucide: @name), class: classes, aria: aria_attributes, **@html_attrs.except(:data) %>
|