shadcn_view_component 0.3.3
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 +255 -0
- data/MIT-LICENSE +20 -0
- data/README.md +604 -0
- data/app/assets/stylesheets/shadcn-themes.css +1075 -0
- data/app/assets/stylesheets/shadcn.css +853 -0
- data/app/components/shadcn/accordion/component.rb +44 -0
- data/app/components/shadcn/accordion/content/component.rb +44 -0
- data/app/components/shadcn/accordion/item/component.rb +41 -0
- data/app/components/shadcn/accordion/preview.rb +16 -0
- data/app/components/shadcn/accordion/previews/default.html.erb +10 -0
- data/app/components/shadcn/accordion/previews/multiple.html.erb +13 -0
- data/app/components/shadcn/accordion/trigger/component.rb +52 -0
- data/app/components/shadcn/alert/component.rb +52 -0
- data/app/components/shadcn/alert/preview.rb +11 -0
- data/app/components/shadcn/alert/previews/default.html.erb +8 -0
- data/app/components/shadcn/alert.rb +17 -0
- data/app/components/shadcn/alert_dialog/action/component.rb +35 -0
- data/app/components/shadcn/alert_dialog/cancel/component.rb +16 -0
- data/app/components/shadcn/alert_dialog/component.rb +20 -0
- data/app/components/shadcn/alert_dialog/content/component.rb +55 -0
- data/app/components/shadcn/alert_dialog/header/component.rb +33 -0
- data/app/components/shadcn/alert_dialog/preview.rb +16 -0
- data/app/components/shadcn/alert_dialog/previews/default.html.erb +13 -0
- data/app/components/shadcn/alert_dialog/previews/small_with_media.html.erb +38 -0
- data/app/components/shadcn/alert_dialog.rb +32 -0
- data/app/components/shadcn/application_view_component.rb +225 -0
- data/app/components/shadcn/application_view_component_preview.rb +8 -0
- data/app/components/shadcn/aspect_ratio/component.rb +34 -0
- data/app/components/shadcn/aspect_ratio/preview.rb +16 -0
- data/app/components/shadcn/aspect_ratio/previews/default.html.erb +5 -0
- data/app/components/shadcn/aspect_ratio/previews/ratios.html.erb +12 -0
- data/app/components/shadcn/attachment/action/component.rb +19 -0
- data/app/components/shadcn/attachment/component.rb +76 -0
- data/app/components/shadcn/attachment/media/component.rb +56 -0
- data/app/components/shadcn/attachment/preview.rb +11 -0
- data/app/components/shadcn/attachment/previews/default.html.erb +80 -0
- data/app/components/shadcn/attachment/trigger/component.rb +31 -0
- data/app/components/shadcn/attachment.rb +42 -0
- data/app/components/shadcn/avatar/component.rb +43 -0
- data/app/components/shadcn/avatar/fallback/component.rb +23 -0
- data/app/components/shadcn/avatar/image/component.rb +20 -0
- data/app/components/shadcn/avatar/preview.rb +11 -0
- data/app/components/shadcn/avatar/previews/default.html.erb +13 -0
- data/app/components/shadcn/avatar.rb +32 -0
- data/app/components/shadcn/badge/component.rb +57 -0
- data/app/components/shadcn/badge/preview.rb +11 -0
- data/app/components/shadcn/badge/previews/default.html.erb +5 -0
- data/app/components/shadcn/breadcrumb/component.rb +15 -0
- data/app/components/shadcn/breadcrumb/ellipsis/component.rb +28 -0
- data/app/components/shadcn/breadcrumb/page/component.rb +25 -0
- data/app/components/shadcn/breadcrumb/preview.rb +16 -0
- data/app/components/shadcn/breadcrumb/previews/custom_separator.html.erb +17 -0
- data/app/components/shadcn/breadcrumb/previews/default.html.erb +15 -0
- data/app/components/shadcn/breadcrumb/separator/component.rb +31 -0
- data/app/components/shadcn/breadcrumb.rb +18 -0
- data/app/components/shadcn/bubble/component.rb +90 -0
- data/app/components/shadcn/bubble/preview.rb +16 -0
- data/app/components/shadcn/bubble/previews/alignment.html.erb +33 -0
- data/app/components/shadcn/bubble/previews/default.html.erb +22 -0
- data/app/components/shadcn/bubble/reactions/component.rb +54 -0
- data/app/components/shadcn/bubble.rb +24 -0
- data/app/components/shadcn/button/component.rb +85 -0
- data/app/components/shadcn/button/preview.rb +31 -0
- data/app/components/shadcn/button/previews/default.html.erb +8 -0
- data/app/components/shadcn/button/previews/sizes.html.erb +5 -0
- data/app/components/shadcn/button/previews/variants.html.erb +5 -0
- data/app/components/shadcn/button_group/component.rb +53 -0
- data/app/components/shadcn/button_group/preview.rb +15 -0
- data/app/components/shadcn/button_group/previews/default.html.erb +18 -0
- data/app/components/shadcn/button_group/previews/vertical.html.erb +5 -0
- data/app/components/shadcn/button_group/separator/component.rb +30 -0
- data/app/components/shadcn/button_group/text/component.rb +20 -0
- data/app/components/shadcn/calendar/component.rb +558 -0
- data/app/components/shadcn/calendar/month.rb +74 -0
- data/app/components/shadcn/calendar/preview.rb +64 -0
- data/app/components/shadcn/calendar/previews/booked.html.erb +18 -0
- data/app/components/shadcn/calendar/previews/custom_cell_size.html.erb +11 -0
- data/app/components/shadcn/calendar/previews/date_and_time.html.erb +31 -0
- data/app/components/shadcn/calendar/previews/default.html.erb +8 -0
- data/app/components/shadcn/calendar/previews/dropdown_caption.html.erb +11 -0
- data/app/components/shadcn/calendar/previews/in_a_form.html.erb +8 -0
- data/app/components/shadcn/calendar/previews/presets.html.erb +20 -0
- data/app/components/shadcn/calendar/previews/range.html.erb +10 -0
- data/app/components/shadcn/calendar/previews/week_numbers.html.erb +8 -0
- data/app/components/shadcn/card/component.rb +24 -0
- data/app/components/shadcn/card/header/component.rb +26 -0
- data/app/components/shadcn/card/preview.rb +16 -0
- data/app/components/shadcn/card/previews/default.html.erb +18 -0
- data/app/components/shadcn/card/previews/image.html.erb +37 -0
- data/app/components/shadcn/card.rb +21 -0
- data/app/components/shadcn/carousel/component.rb +61 -0
- data/app/components/shadcn/carousel/content/component.rb +86 -0
- data/app/components/shadcn/carousel/item/component.rb +42 -0
- data/app/components/shadcn/carousel/next/component.rb +59 -0
- data/app/components/shadcn/carousel/preview.rb +22 -0
- data/app/components/shadcn/carousel/previews/default.html.erb +22 -0
- data/app/components/shadcn/carousel/previews/sizes.html.erb +42 -0
- data/app/components/shadcn/carousel/previews/vertical.html.erb +27 -0
- data/app/components/shadcn/carousel/previous/component.rb +59 -0
- data/app/components/shadcn/chart/area/component.rb +43 -0
- data/app/components/shadcn/chart/bar/component.rb +75 -0
- data/app/components/shadcn/chart/cartesian/component.rb +152 -0
- data/app/components/shadcn/chart/component.rb +124 -0
- data/app/components/shadcn/chart/focusable.rb +38 -0
- data/app/components/shadcn/chart/legend/component.rb +55 -0
- data/app/components/shadcn/chart/line/component.rb +50 -0
- data/app/components/shadcn/chart/pie/component.rb +169 -0
- data/app/components/shadcn/chart/plot.rb +147 -0
- data/app/components/shadcn/chart/preview.rb +45 -0
- data/app/components/shadcn/chart/previews/area.html.erb +31 -0
- data/app/components/shadcn/chart/previews/bar.html.erb +32 -0
- data/app/components/shadcn/chart/previews/bar_stacked.html.erb +30 -0
- data/app/components/shadcn/chart/previews/default.html.erb +30 -0
- data/app/components/shadcn/chart/previews/donut.html.erb +16 -0
- data/app/components/shadcn/chart/previews/line.html.erb +30 -0
- data/app/components/shadcn/chart/previews/percentages.html.erb +17 -0
- data/app/components/shadcn/chart/table/component.rb +75 -0
- data/app/components/shadcn/chart/tooltip/component.rb +111 -0
- data/app/components/shadcn/checkbox/component.rb +109 -0
- data/app/components/shadcn/checkbox/preview.rb +16 -0
- data/app/components/shadcn/checkbox/previews/default.html.erb +14 -0
- data/app/components/shadcn/checkbox/previews/states.html.erb +23 -0
- data/app/components/shadcn/collapsible/component.rb +35 -0
- data/app/components/shadcn/collapsible/content/component.rb +20 -0
- data/app/components/shadcn/collapsible/preview.rb +11 -0
- data/app/components/shadcn/collapsible/previews/default.html.erb +9 -0
- data/app/components/shadcn/collapsible/trigger/component.rb +23 -0
- data/app/components/shadcn/combobox/chip/component.rb +60 -0
- data/app/components/shadcn/combobox/chip_input/component.rb +43 -0
- data/app/components/shadcn/combobox/chips/component.rb +54 -0
- data/app/components/shadcn/combobox/clear/component.rb +32 -0
- data/app/components/shadcn/combobox/component.rb +113 -0
- data/app/components/shadcn/combobox/content/component.rb +59 -0
- data/app/components/shadcn/combobox/empty/component.rb +23 -0
- data/app/components/shadcn/combobox/input/component.rb +85 -0
- data/app/components/shadcn/combobox/item/component.rb +69 -0
- data/app/components/shadcn/combobox/list/component.rb +27 -0
- data/app/components/shadcn/combobox/preview.rb +31 -0
- data/app/components/shadcn/combobox/previews/default.html.erb +20 -0
- data/app/components/shadcn/combobox/previews/grouped.html.erb +25 -0
- data/app/components/shadcn/combobox/previews/multiple.html.erb +28 -0
- data/app/components/shadcn/combobox/previews/with_clear.html.erb +16 -0
- data/app/components/shadcn/combobox/trigger/component.rb +54 -0
- data/app/components/shadcn/combobox.rb +16 -0
- data/app/components/shadcn/command/component.rb +67 -0
- data/app/components/shadcn/command/dialog/component.rb +72 -0
- data/app/components/shadcn/command/empty/component.rb +29 -0
- data/app/components/shadcn/command/group/component.rb +63 -0
- data/app/components/shadcn/command/input/component.rb +66 -0
- data/app/components/shadcn/command/item/component.rb +62 -0
- data/app/components/shadcn/command/list/component.rb +36 -0
- data/app/components/shadcn/command/preview.rb +26 -0
- data/app/components/shadcn/command/previews/default.html.erb +41 -0
- data/app/components/shadcn/command/previews/dialog.html.erb +33 -0
- data/app/components/shadcn/command/previews/ranking.html.erb +16 -0
- data/app/components/shadcn/command/separator/component.rb +33 -0
- data/app/components/shadcn/command.rb +14 -0
- data/app/components/shadcn/concerns/modal_root.rb +43 -0
- data/app/components/shadcn/concerns/submits_value.rb +81 -0
- data/app/components/shadcn/context_menu/component.rb +45 -0
- data/app/components/shadcn/context_menu/content/component.rb +40 -0
- data/app/components/shadcn/context_menu/preview.rb +16 -0
- data/app/components/shadcn/context_menu/previews/default.html.erb +34 -0
- data/app/components/shadcn/context_menu/previews/items.html.erb +35 -0
- data/app/components/shadcn/context_menu/sub/component.rb +47 -0
- data/app/components/shadcn/context_menu/sub_content/component.rb +31 -0
- data/app/components/shadcn/context_menu/trigger/component.rb +50 -0
- data/app/components/shadcn/context_menu.rb +43 -0
- data/app/components/shadcn/dialog/close/component.rb +20 -0
- data/app/components/shadcn/dialog/component.rb +20 -0
- data/app/components/shadcn/dialog/content/component.rb +78 -0
- data/app/components/shadcn/dialog/header/component.rb +23 -0
- data/app/components/shadcn/dialog/overlay/component.rb +28 -0
- data/app/components/shadcn/dialog/preview.rb +28 -0
- data/app/components/shadcn/dialog/previews/default.html.erb +17 -0
- data/app/components/shadcn/dialog/previews/inside_stacking_context.html.erb +25 -0
- data/app/components/shadcn/dialog/previews/no_close_button.html.erb +15 -0
- data/app/components/shadcn/dialog/previews/scrollable_content.html.erb +29 -0
- data/app/components/shadcn/dialog/trigger/component.rb +24 -0
- data/app/components/shadcn/dialog.rb +17 -0
- data/app/components/shadcn/drawer/component.rb +33 -0
- data/app/components/shadcn/drawer/content/component.rb +95 -0
- data/app/components/shadcn/drawer/header/component.rb +34 -0
- data/app/components/shadcn/drawer/preview.rb +29 -0
- data/app/components/shadcn/drawer/previews/default.html.erb +21 -0
- data/app/components/shadcn/drawer/previews/scrollable.html.erb +20 -0
- data/app/components/shadcn/drawer/previews/scrollable_side.html.erb +21 -0
- data/app/components/shadcn/drawer/previews/sides.html.erb +17 -0
- data/app/components/shadcn/drawer.rb +27 -0
- data/app/components/shadcn/dropdown_menu/checkbox_item/component.rb +61 -0
- data/app/components/shadcn/dropdown_menu/component.rb +48 -0
- data/app/components/shadcn/dropdown_menu/content/component.rb +49 -0
- data/app/components/shadcn/dropdown_menu/group/component.rb +16 -0
- data/app/components/shadcn/dropdown_menu/item/component.rb +50 -0
- data/app/components/shadcn/dropdown_menu/preview.rb +16 -0
- data/app/components/shadcn/dropdown_menu/previews/checkboxes_and_radio.html.erb +34 -0
- data/app/components/shadcn/dropdown_menu/previews/default.html.erb +23 -0
- data/app/components/shadcn/dropdown_menu/radio_group/component.rb +16 -0
- data/app/components/shadcn/dropdown_menu/radio_item/component.rb +63 -0
- data/app/components/shadcn/dropdown_menu/sub/component.rb +44 -0
- data/app/components/shadcn/dropdown_menu/sub_content/component.rb +45 -0
- data/app/components/shadcn/dropdown_menu/sub_trigger/component.rb +58 -0
- data/app/components/shadcn/dropdown_menu/trigger/component.rb +25 -0
- data/app/components/shadcn/dropdown_menu.rb +18 -0
- data/app/components/shadcn/empty/component.rb +17 -0
- data/app/components/shadcn/empty/media/component.rb +48 -0
- data/app/components/shadcn/empty/preview.rb +16 -0
- data/app/components/shadcn/empty/previews/default.html.erb +15 -0
- data/app/components/shadcn/empty/previews/media.html.erb +32 -0
- data/app/components/shadcn/empty.rb +26 -0
- data/app/components/shadcn/field/component.rb +61 -0
- data/app/components/shadcn/field/error/component.rb +49 -0
- data/app/components/shadcn/field/legend/component.rb +30 -0
- data/app/components/shadcn/field/preview.rb +17 -0
- data/app/components/shadcn/field/previews/default.html.erb +29 -0
- data/app/components/shadcn/field/previews/form_builder.html.erb +38 -0
- data/app/components/shadcn/field/separator/component.rb +35 -0
- data/app/components/shadcn/field.rb +44 -0
- data/app/components/shadcn/hover_card/component.rb +51 -0
- data/app/components/shadcn/hover_card/content/component.rb +45 -0
- data/app/components/shadcn/hover_card/preview.rb +16 -0
- data/app/components/shadcn/hover_card/previews/default.html.erb +36 -0
- data/app/components/shadcn/hover_card/previews/sides.html.erb +15 -0
- data/app/components/shadcn/hover_card/trigger/component.rb +33 -0
- data/app/components/shadcn/icon/component.rb +76 -0
- data/app/components/shadcn/icon.rb +43 -0
- data/app/components/shadcn/input/component.rb +26 -0
- data/app/components/shadcn/input/preview.rb +16 -0
- data/app/components/shadcn/input/previews/default.html.erb +5 -0
- data/app/components/shadcn/input/previews/states.html.erb +20 -0
- data/app/components/shadcn/input_group/addon/component.rb +65 -0
- data/app/components/shadcn/input_group/button/component.rb +40 -0
- data/app/components/shadcn/input_group/component.rb +43 -0
- data/app/components/shadcn/input_group/input/component.rb +25 -0
- data/app/components/shadcn/input_group/preview.rb +17 -0
- data/app/components/shadcn/input_group/previews/addons.html.erb +53 -0
- data/app/components/shadcn/input_group/previews/default.html.erb +25 -0
- data/app/components/shadcn/input_group/text/component.rb +20 -0
- data/app/components/shadcn/input_group/textarea/component.rb +21 -0
- data/app/components/shadcn/input_otp/component.rb +95 -0
- data/app/components/shadcn/input_otp/preview.rb +17 -0
- data/app/components/shadcn/input_otp/previews/default.html.erb +19 -0
- data/app/components/shadcn/input_otp/previews/variations.html.erb +33 -0
- data/app/components/shadcn/input_otp/separator/component.rb +21 -0
- data/app/components/shadcn/input_otp/slot/component.rb +60 -0
- data/app/components/shadcn/input_otp.rb +11 -0
- data/app/components/shadcn/item/component.rb +56 -0
- data/app/components/shadcn/item/group/component.rb +53 -0
- data/app/components/shadcn/item/media/component.rb +53 -0
- data/app/components/shadcn/item/preview.rb +20 -0
- data/app/components/shadcn/item/previews/default.html.erb +32 -0
- data/app/components/shadcn/item/previews/link.html.erb +39 -0
- data/app/components/shadcn/item/previews/outline.html.erb +8 -0
- data/app/components/shadcn/item/separator/component.rb +24 -0
- data/app/components/shadcn/item.rb +34 -0
- data/app/components/shadcn/kbd/component.rb +23 -0
- data/app/components/shadcn/kbd/group/component.rb +17 -0
- data/app/components/shadcn/kbd/preview.rb +11 -0
- data/app/components/shadcn/kbd/previews/default.html.erb +4 -0
- data/app/components/shadcn/label/component.rb +20 -0
- data/app/components/shadcn/label/preview.rb +11 -0
- data/app/components/shadcn/label/previews/default.html.erb +4 -0
- data/app/components/shadcn/marker/component.rb +46 -0
- data/app/components/shadcn/marker/icon/component.rb +23 -0
- data/app/components/shadcn/marker/preview.rb +17 -0
- data/app/components/shadcn/marker/previews/default.html.erb +18 -0
- data/app/components/shadcn/marker/previews/status.html.erb +32 -0
- data/app/components/shadcn/marker.rb +17 -0
- data/app/components/shadcn/menubar/component.rb +36 -0
- data/app/components/shadcn/menubar/content/component.rb +49 -0
- data/app/components/shadcn/menubar/menu/component.rb +39 -0
- data/app/components/shadcn/menubar/preview.rb +16 -0
- data/app/components/shadcn/menubar/previews/default.html.erb +52 -0
- data/app/components/shadcn/menubar/previews/items.html.erb +38 -0
- data/app/components/shadcn/menubar/sub/component.rb +43 -0
- data/app/components/shadcn/menubar/sub_content/component.rb +31 -0
- data/app/components/shadcn/menubar/sub_trigger/component.rb +31 -0
- data/app/components/shadcn/menubar/trigger/component.rb +41 -0
- data/app/components/shadcn/menubar.rb +47 -0
- data/app/components/shadcn/message/component.rb +33 -0
- data/app/components/shadcn/message/preview.rb +16 -0
- data/app/components/shadcn/message/previews/actions.html.erb +51 -0
- data/app/components/shadcn/message/previews/default.html.erb +35 -0
- data/app/components/shadcn/message.rb +35 -0
- data/app/components/shadcn/message_scroller/button/component.rb +78 -0
- data/app/components/shadcn/message_scroller/component.rb +59 -0
- data/app/components/shadcn/message_scroller/content/component.rb +50 -0
- data/app/components/shadcn/message_scroller/item/component.rb +45 -0
- data/app/components/shadcn/message_scroller/preview.rb +11 -0
- data/app/components/shadcn/message_scroller/previews/default.html.erb +72 -0
- data/app/components/shadcn/message_scroller/viewport/component.rb +41 -0
- data/app/components/shadcn/mode_switcher/component.rb +67 -0
- data/app/components/shadcn/mode_switcher/preview.rb +11 -0
- data/app/components/shadcn/mode_switcher/previews/default.html.erb +5 -0
- data/app/components/shadcn/mode_toggle/component.rb +75 -0
- data/app/components/shadcn/mode_toggle/preview.rb +11 -0
- data/app/components/shadcn/mode_toggle/previews/default.html.erb +20 -0
- data/app/components/shadcn/native_select/component.rb +59 -0
- data/app/components/shadcn/native_select/preview.rb +16 -0
- data/app/components/shadcn/native_select/previews/default.html.erb +9 -0
- data/app/components/shadcn/native_select/previews/groups.html.erb +26 -0
- data/app/components/shadcn/native_select.rb +16 -0
- data/app/components/shadcn/navigation_menu/component.rb +51 -0
- data/app/components/shadcn/navigation_menu/content/component.rb +58 -0
- data/app/components/shadcn/navigation_menu/preview.rb +11 -0
- data/app/components/shadcn/navigation_menu/previews/default.html.erb +49 -0
- data/app/components/shadcn/navigation_menu/trigger/component.rb +59 -0
- data/app/components/shadcn/navigation_menu.rb +28 -0
- data/app/components/shadcn/pagination/component.rb +19 -0
- data/app/components/shadcn/pagination/ellipsis/component.rb +28 -0
- data/app/components/shadcn/pagination/link/component.rb +37 -0
- data/app/components/shadcn/pagination/next/component.rb +31 -0
- data/app/components/shadcn/pagination/preview.rb +16 -0
- data/app/components/shadcn/pagination/previews/default.html.erb +18 -0
- data/app/components/shadcn/pagination/previews/simple.html.erb +39 -0
- data/app/components/shadcn/pagination/previous/component.rb +31 -0
- data/app/components/shadcn/pagination.rb +14 -0
- data/app/components/shadcn/parts.rb +38 -0
- data/app/components/shadcn/popover/anchor/component.rb +17 -0
- data/app/components/shadcn/popover/component.rb +42 -0
- data/app/components/shadcn/popover/content/component.rb +40 -0
- data/app/components/shadcn/popover/preview.rb +28 -0
- data/app/components/shadcn/popover/previews/align.html.erb +13 -0
- data/app/components/shadcn/popover/previews/default.html.erb +9 -0
- data/app/components/shadcn/popover/previews/inside_containing_block.html.erb +41 -0
- data/app/components/shadcn/popover/previews/inside_stacking_context.html.erb +30 -0
- data/app/components/shadcn/popover/trigger/component.rb +24 -0
- data/app/components/shadcn/popover.rb +16 -0
- data/app/components/shadcn/progress/component.rb +62 -0
- data/app/components/shadcn/progress/preview.rb +11 -0
- data/app/components/shadcn/progress/previews/default.html.erb +5 -0
- data/app/components/shadcn/radio_group/component.rb +51 -0
- data/app/components/shadcn/radio_group/item/component.rb +74 -0
- data/app/components/shadcn/radio_group/preview.rb +16 -0
- data/app/components/shadcn/radio_group/previews/default.html.erb +15 -0
- data/app/components/shadcn/radio_group/previews/states.html.erb +41 -0
- data/app/components/shadcn/resizable/component.rb +59 -0
- data/app/components/shadcn/resizable/handle/component.rb +70 -0
- data/app/components/shadcn/resizable/panel/component.rb +53 -0
- data/app/components/shadcn/resizable/preview.rb +23 -0
- data/app/components/shadcn/resizable/previews/default.html.erb +25 -0
- data/app/components/shadcn/resizable/previews/vertical.html.erb +19 -0
- data/app/components/shadcn/resizable/previews/with_handle.html.erb +17 -0
- data/app/components/shadcn/scroll_area/component.rb +84 -0
- data/app/components/shadcn/scroll_area/preview.rb +11 -0
- data/app/components/shadcn/scroll_area/previews/default.html.erb +36 -0
- data/app/components/shadcn/scroll_area/scrollbar/component.rb +72 -0
- data/app/components/shadcn/scroll_area/viewport/component.rb +70 -0
- data/app/components/shadcn/scroll_area.rb +14 -0
- data/app/components/shadcn/select/component.rb +67 -0
- data/app/components/shadcn/select/content/component.rb +157 -0
- data/app/components/shadcn/select/empty/component.rb +38 -0
- data/app/components/shadcn/select/group/component.rb +16 -0
- data/app/components/shadcn/select/item/component.rb +71 -0
- data/app/components/shadcn/select/list/component.rb +41 -0
- data/app/components/shadcn/select/preview.rb +24 -0
- data/app/components/shadcn/select/previews/default.html.erb +17 -0
- data/app/components/shadcn/select/previews/scrollable.html.erb +19 -0
- data/app/components/shadcn/select/previews/searchable.html.erb +18 -0
- data/app/components/shadcn/select/previews/states.html.erb +25 -0
- data/app/components/shadcn/select/search/component.rb +54 -0
- data/app/components/shadcn/select/trigger/component.rb +78 -0
- data/app/components/shadcn/select/value/component.rb +28 -0
- data/app/components/shadcn/select.rb +15 -0
- data/app/components/shadcn/separator/component.rb +41 -0
- data/app/components/shadcn/separator/preview.rb +11 -0
- data/app/components/shadcn/separator/previews/default.html.erb +9 -0
- data/app/components/shadcn/sheet/component.rb +16 -0
- data/app/components/shadcn/sheet/content/component.rb +98 -0
- data/app/components/shadcn/sheet/header/component.rb +23 -0
- data/app/components/shadcn/sheet/preview.rb +16 -0
- data/app/components/shadcn/sheet/previews/default.html.erb +16 -0
- data/app/components/shadcn/sheet/previews/no_close_button.html.erb +14 -0
- data/app/components/shadcn/sheet.rb +22 -0
- data/app/components/shadcn/sidebar/component.rb +232 -0
- data/app/components/shadcn/sidebar/input/component.rb +25 -0
- data/app/components/shadcn/sidebar/menu_action/component.rb +58 -0
- data/app/components/shadcn/sidebar/menu_button/component.rb +100 -0
- data/app/components/shadcn/sidebar/menu_skeleton/component.rb +51 -0
- data/app/components/shadcn/sidebar/menu_sub_button/component.rb +60 -0
- data/app/components/shadcn/sidebar/preview.rb +22 -0
- data/app/components/shadcn/sidebar/previews/default.html.erb +189 -0
- data/app/components/shadcn/sidebar/previews/floating.html.erb +39 -0
- data/app/components/shadcn/sidebar/previews/inset.html.erb +48 -0
- data/app/components/shadcn/sidebar/provider/component.rb +53 -0
- data/app/components/shadcn/sidebar/rail/component.rb +43 -0
- data/app/components/shadcn/sidebar/separator/component.rb +23 -0
- data/app/components/shadcn/sidebar/trigger/component.rb +39 -0
- data/app/components/shadcn/sidebar.rb +98 -0
- data/app/components/shadcn/skeleton/component.rb +14 -0
- data/app/components/shadcn/skeleton/preview.rb +11 -0
- data/app/components/shadcn/skeleton/previews/default.html.erb +7 -0
- data/app/components/shadcn/slider/component.rb +145 -0
- data/app/components/shadcn/slider/preview.rb +16 -0
- data/app/components/shadcn/slider/previews/default.html.erb +17 -0
- data/app/components/shadcn/slider/previews/vertical.html.erb +7 -0
- data/app/components/shadcn/slider/range/component.rb +51 -0
- data/app/components/shadcn/slider/thumb/component.rb +58 -0
- data/app/components/shadcn/slider/track/component.rb +32 -0
- data/app/components/shadcn/spinner/component.rb +21 -0
- data/app/components/shadcn/spinner/preview.rb +11 -0
- data/app/components/shadcn/spinner/previews/default.html.erb +7 -0
- data/app/components/shadcn/switch/component.rb +104 -0
- data/app/components/shadcn/switch/preview.rb +11 -0
- data/app/components/shadcn/switch/previews/default.html.erb +24 -0
- data/app/components/shadcn/table/component.rb +26 -0
- data/app/components/shadcn/table/preview.rb +16 -0
- data/app/components/shadcn/table/previews/default.html.erb +19 -0
- data/app/components/shadcn/table/previews/footer.html.erb +25 -0
- data/app/components/shadcn/table.rb +33 -0
- data/app/components/shadcn/tabs/component.rb +40 -0
- data/app/components/shadcn/tabs/content/component.rb +34 -0
- data/app/components/shadcn/tabs/list/component.rb +51 -0
- data/app/components/shadcn/tabs/preview.rb +16 -0
- data/app/components/shadcn/tabs/previews/default.html.erb +21 -0
- data/app/components/shadcn/tabs/previews/line.html.erb +23 -0
- data/app/components/shadcn/tabs/trigger/component.rb +66 -0
- data/app/components/shadcn/textarea/component.rb +24 -0
- data/app/components/shadcn/textarea/preview.rb +16 -0
- data/app/components/shadcn/textarea/previews/default.html.erb +3 -0
- data/app/components/shadcn/textarea/previews/states.html.erb +18 -0
- data/app/components/shadcn/theme_selector/component.rb +88 -0
- data/app/components/shadcn/theme_selector/preview.rb +17 -0
- data/app/components/shadcn/theme_selector/previews/all_themes.html.erb +20 -0
- data/app/components/shadcn/theme_selector/previews/default.html.erb +18 -0
- data/app/components/shadcn/toaster/component.rb +139 -0
- data/app/components/shadcn/toaster/preview.rb +16 -0
- data/app/components/shadcn/toaster/previews/default.html.erb +19 -0
- data/app/components/shadcn/toaster/previews/from_the_server.html.erb +15 -0
- data/app/components/shadcn/toaster/toast/component.rb +134 -0
- data/app/components/shadcn/toggle/component.rb +81 -0
- data/app/components/shadcn/toggle/preview.rb +11 -0
- data/app/components/shadcn/toggle/previews/default.html.erb +6 -0
- data/app/components/shadcn/toggle_group/component.rb +62 -0
- data/app/components/shadcn/toggle_group/item/component.rb +59 -0
- data/app/components/shadcn/toggle_group/preview.rb +16 -0
- data/app/components/shadcn/toggle_group/previews/default.html.erb +12 -0
- data/app/components/shadcn/toggle_group/previews/sizes.html.erb +37 -0
- data/app/components/shadcn/tooltip/component.rb +38 -0
- data/app/components/shadcn/tooltip/content/component.rb +62 -0
- data/app/components/shadcn/tooltip/preview.rb +21 -0
- data/app/components/shadcn/tooltip/previews/default.html.erb +4 -0
- data/app/components/shadcn/tooltip/previews/sides.html.erb +19 -0
- data/app/components/shadcn/tooltip/previews/with_keyboard_shortcut.html.erb +11 -0
- data/app/components/shadcn/tooltip/provider/component.rb +28 -0
- data/app/components/shadcn/tooltip/trigger/component.rb +25 -0
- data/app/helpers/shadcn_view_component/form_helper.rb +25 -0
- data/app/helpers/shadcn_view_component/theme_helper.rb +61 -0
- data/app/javascript/shadcn/animation.js +180 -0
- data/app/javascript/shadcn/command_score.js +110 -0
- data/app/javascript/shadcn/controllers/accordion_controller.js +127 -0
- data/app/javascript/shadcn/controllers/avatar_controller.js +32 -0
- data/app/javascript/shadcn/controllers/calendar_controller.js +633 -0
- data/app/javascript/shadcn/controllers/carousel_controller.js +139 -0
- data/app/javascript/shadcn/controllers/chart_controller.js +110 -0
- data/app/javascript/shadcn/controllers/checkbox_controller.js +61 -0
- data/app/javascript/shadcn/controllers/collapsible_controller.js +55 -0
- data/app/javascript/shadcn/controllers/combobox_controller.js +360 -0
- data/app/javascript/shadcn/controllers/command_controller.js +199 -0
- data/app/javascript/shadcn/controllers/dialog_controller.js +154 -0
- data/app/javascript/shadcn/controllers/drawer_controller.js +265 -0
- data/app/javascript/shadcn/controllers/dropdown_menu_controller.js +259 -0
- data/app/javascript/shadcn/controllers/hover_card_controller.js +85 -0
- data/app/javascript/shadcn/controllers/input_group_controller.js +17 -0
- data/app/javascript/shadcn/controllers/input_otp_controller.js +80 -0
- data/app/javascript/shadcn/controllers/menubar_controller.js +151 -0
- data/app/javascript/shadcn/controllers/message_scroller_controller.js +456 -0
- data/app/javascript/shadcn/controllers/navigation_menu_controller.js +211 -0
- data/app/javascript/shadcn/controllers/popover_controller.js +66 -0
- data/app/javascript/shadcn/controllers/radio_group_controller.js +69 -0
- data/app/javascript/shadcn/controllers/resizable_controller.js +167 -0
- data/app/javascript/shadcn/controllers/scroll_area_controller.js +253 -0
- data/app/javascript/shadcn/controllers/select_controller.js +320 -0
- data/app/javascript/shadcn/controllers/sidebar_controller.js +273 -0
- data/app/javascript/shadcn/controllers/slider_controller.js +230 -0
- data/app/javascript/shadcn/controllers/switch_controller.js +39 -0
- data/app/javascript/shadcn/controllers/tabs_controller.js +81 -0
- data/app/javascript/shadcn/controllers/theme_controller.js +67 -0
- data/app/javascript/shadcn/controllers/toaster_controller.js +281 -0
- data/app/javascript/shadcn/controllers/toggle_controller.js +24 -0
- data/app/javascript/shadcn/controllers/toggle_group_controller.js +73 -0
- data/app/javascript/shadcn/controllers/tooltip_controller.js +58 -0
- data/app/javascript/shadcn/direction.js +41 -0
- data/app/javascript/shadcn/dismiss.js +73 -0
- data/app/javascript/shadcn/floating.js +207 -0
- data/app/javascript/shadcn/focus.js +86 -0
- data/app/javascript/shadcn/id.js +14 -0
- data/app/javascript/shadcn/index.js +110 -0
- data/app/javascript/shadcn/popper.js +262 -0
- data/app/javascript/shadcn/scroll_geometry.js +267 -0
- data/app/javascript/shadcn/theme.js +161 -0
- data/app/javascript/shadcn/top_layer.js +59 -0
- data/app/javascript/shadcn/typeahead.js +99 -0
- data/config/importmap.rb +6 -0
- data/config/locales/en.yml +62 -0
- data/lib/generators/shadcn_view_component/install/install_generator.rb +149 -0
- data/lib/shadcn_view_component/engine.rb +73 -0
- data/lib/shadcn_view_component/form_builder.rb +264 -0
- data/lib/shadcn_view_component/icon_registry.rb +66 -0
- data/lib/shadcn_view_component/icons.rb +32 -0
- data/lib/shadcn_view_component/themes.rb +1065 -0
- data/lib/shadcn_view_component/version.rb +3 -0
- data/lib/shadcn_view_component.rb +82 -0
- data/lib/tasks/icons.rake +53 -0
- data/lib/tasks/themes.rake +171 -0
- metadata +608 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
module Field
|
|
5
|
+
module Separator
|
|
6
|
+
# FieldSeparator — a rule with optional centred content.
|
|
7
|
+
class Component < ApplicationViewComponent
|
|
8
|
+
CONTENT_CLASSES = "relative mx-auto block w-fit bg-background px-2 text-muted-foreground"
|
|
9
|
+
|
|
10
|
+
slot_name :"field-separator"
|
|
11
|
+
|
|
12
|
+
style do
|
|
13
|
+
base {
|
|
14
|
+
"relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2"
|
|
15
|
+
}
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def element_attributes(**defaults)
|
|
19
|
+
super(**{ "data-content" => content.present? }.merge(defaults))
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def call
|
|
23
|
+
body = [ render(Shadcn::Separator::Component.new(class: "absolute inset-0 top-1/2")) ]
|
|
24
|
+
|
|
25
|
+
if content.present?
|
|
26
|
+
body << tag.span(content, class: CONTENT_CLASSES,
|
|
27
|
+
"data-slot": "field-separator-content")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
render_element(body: safe_join(body))
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# The parts of the Field family that are just an element with a
|
|
5
|
+
# `data-slot` and a fixed set of classes. Anything with variants, slots or
|
|
6
|
+
# markup of its own keeps its own `component.rb`.
|
|
7
|
+
module Field
|
|
8
|
+
extend Parts
|
|
9
|
+
|
|
10
|
+
part :content, slot: "field-content",
|
|
11
|
+
classes: "group/field-content flex flex-1 flex-col gap-1.5 leading-snug"
|
|
12
|
+
|
|
13
|
+
part :description, tag: :p, slot: "field-description",
|
|
14
|
+
classes: "text-sm leading-normal font-normal text-muted-foreground " \
|
|
15
|
+
"group-has-[[data-orientation=horizontal]]/field:text-balance " \
|
|
16
|
+
"last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5 " \
|
|
17
|
+
"[&>a]:underline [&>a]:underline-offset-4 " \
|
|
18
|
+
"[&>a:hover]:text-primary"
|
|
19
|
+
|
|
20
|
+
part :group, slot: "field-group",
|
|
21
|
+
classes: "group/field-group @container/field-group flex w-full flex-col gap-7 " \
|
|
22
|
+
"data-[slot=checkbox-group]:gap-3 [&>[data-slot=field-group]]:gap-4"
|
|
23
|
+
|
|
24
|
+
part :label, tag: :label, slot: "field-label",
|
|
25
|
+
classes: "flex items-center gap-2 text-sm leading-none font-medium select-none " \
|
|
26
|
+
"group-data-[disabled=true]:pointer-events-none " \
|
|
27
|
+
"group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed " \
|
|
28
|
+
"peer-disabled:opacity-50 group/field-label peer/field-label flex w-fit " \
|
|
29
|
+
"gap-2 leading-snug group-data-[disabled=true]/field:opacity-50 " \
|
|
30
|
+
"has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col " \
|
|
31
|
+
"has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border " \
|
|
32
|
+
"[&>*]:data-[slot=field]:p-4 has-data-[state=checked]:border-primary " \
|
|
33
|
+
"has-data-[state=checked]:bg-primary/5 " \
|
|
34
|
+
"dark:has-data-[state=checked]:bg-primary/10"
|
|
35
|
+
|
|
36
|
+
part :set, tag: :fieldset, slot: "field-set",
|
|
37
|
+
classes: "flex flex-col gap-6 has-[>[data-slot=checkbox-group]]:gap-3 " \
|
|
38
|
+
"has-[>[data-slot=radio-group]]:gap-3"
|
|
39
|
+
|
|
40
|
+
part :title, slot: "field-label",
|
|
41
|
+
classes: "flex w-fit items-center gap-2 text-sm leading-snug font-medium " \
|
|
42
|
+
"group-data-[disabled=true]/field:opacity-50"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
module HoverCard
|
|
5
|
+
# Port of registry/new-york-v4/ui/hover-card.tsx, whose behaviour is Radix's
|
|
6
|
+
# `HoverCard` — vendored at `vendor/radix/ui/hover-card.tsx`.
|
|
7
|
+
#
|
|
8
|
+
# A tooltip's larger relative, and the difference is what the delays are
|
|
9
|
+
# for: this content is meant to be *entered*. Moving the pointer from the
|
|
10
|
+
# trigger onto the card keeps it open, which is why the card carries the
|
|
11
|
+
# same enter and leave handlers the trigger does.
|
|
12
|
+
class Component < ApplicationViewComponent
|
|
13
|
+
renders_one :trigger, "Shadcn::HoverCard::Trigger::Component"
|
|
14
|
+
renders_one :hover_card_content, "Shadcn::HoverCard::Content::Component"
|
|
15
|
+
|
|
16
|
+
slot_name :"hover-card"
|
|
17
|
+
|
|
18
|
+
attr_reader :side, :align, :side_offset, :open_delay, :close_delay
|
|
19
|
+
|
|
20
|
+
# `open_delay` and `close_delay` are Radix's own defaults
|
|
21
|
+
# (vendor/radix/ui/hover-card.tsx:59-60), not numbers chosen here. The
|
|
22
|
+
# closing one is the longer-lived decision of the two: it is the window in
|
|
23
|
+
# which you can cross the gap between the trigger and the card.
|
|
24
|
+
def initialize(side: :bottom, align: :center, side_offset: 4,
|
|
25
|
+
open_delay: 700, close_delay: 300, **attributes)
|
|
26
|
+
@side = side&.to_sym || :bottom
|
|
27
|
+
@align = align&.to_sym || :center
|
|
28
|
+
@side_offset = side_offset
|
|
29
|
+
@open_delay = open_delay
|
|
30
|
+
@close_delay = close_delay
|
|
31
|
+
super(**attributes)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def element_attributes(**defaults)
|
|
35
|
+
super(**{
|
|
36
|
+
style: merged_style(CONTENTS_STYLE),
|
|
37
|
+
"data-controller" => "shadcn--hover-card",
|
|
38
|
+
"data-shadcn--hover-card-side-value" => side,
|
|
39
|
+
"data-shadcn--hover-card-align-value" => align,
|
|
40
|
+
"data-shadcn--hover-card-side-offset-value" => side_offset,
|
|
41
|
+
"data-shadcn--hover-card-open-delay-value" => open_delay,
|
|
42
|
+
"data-shadcn--hover-card-close-delay-value" => close_delay
|
|
43
|
+
}.merge(defaults))
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def call
|
|
47
|
+
render_element(body: safe_join([ trigger, hover_card_content, content ].compact))
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
module HoverCard
|
|
5
|
+
module Content
|
|
6
|
+
# HoverCardContent.
|
|
7
|
+
#
|
|
8
|
+
# It carries the same pointer handlers as the trigger, and that is the
|
|
9
|
+
# component rather than a detail: without them, moving onto the card is
|
|
10
|
+
# leaving the trigger and the card closes under the pointer that went for
|
|
11
|
+
# it (hover-card.tsx:220-221).
|
|
12
|
+
#
|
|
13
|
+
# No `role` and no `aria-*`, which is Radix's own — measured, there is not
|
|
14
|
+
# one of either in the whole primitive. The card is a sighted-hover
|
|
15
|
+
# affordance, and nothing in it is announced. That is also why every
|
|
16
|
+
# tabbable inside it is taken out of the tab order; the controller does
|
|
17
|
+
# that, since the content is a host's markup rather than this component's.
|
|
18
|
+
class Component < ApplicationViewComponent
|
|
19
|
+
slot_name :"hover-card-content"
|
|
20
|
+
|
|
21
|
+
style do
|
|
22
|
+
base {
|
|
23
|
+
"z-50 w-64 origin-(--radix-hover-card-content-transform-origin) rounded-md " \
|
|
24
|
+
"border bg-popover p-4 text-popover-foreground shadow-md outline-hidden " \
|
|
25
|
+
"data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 " \
|
|
26
|
+
"data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 " \
|
|
27
|
+
"data-[state=closed]:animate-out data-[state=closed]:fade-out-0 " \
|
|
28
|
+
"data-[state=closed]:zoom-out-95 data-[state=open]:animate-in " \
|
|
29
|
+
"data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95"
|
|
30
|
+
}
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def element_attributes(**defaults)
|
|
34
|
+
super(**{
|
|
35
|
+
hidden: true,
|
|
36
|
+
"data-state" => "closed",
|
|
37
|
+
"data-shadcn--hover-card-target" => "content",
|
|
38
|
+
"data-action" => "pointerenter->shadcn--hover-card#open " \
|
|
39
|
+
"pointerleave->shadcn--hover-card#close"
|
|
40
|
+
}.merge(defaults))
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
module HoverCard
|
|
5
|
+
class Preview < ApplicationViewComponentPreview
|
|
6
|
+
def default
|
|
7
|
+
render_with_template
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Upstream's "Sides" and "Trigger Delays".
|
|
11
|
+
def sides
|
|
12
|
+
render_with_template
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<%# The card upstream's example builds: a link that, held under the pointer for
|
|
2
|
+
a moment, explains what it points at.
|
|
3
|
+
|
|
4
|
+
The trigger is `as: :a` — a hover card usually hangs off something already
|
|
5
|
+
in the text — and it stays focusable, because Radix opens on focus as well
|
|
6
|
+
as on hover (hover-card.tsx:138-141) and that is the only way a keyboard
|
|
7
|
+
reaches it at all.
|
|
8
|
+
|
|
9
|
+
Nothing inside the card is reachable by keyboard: the controller takes every
|
|
10
|
+
tabbable in it out of the tab order, which is Radix's own behaviour and
|
|
11
|
+
follows from the card having no way to be tabbed *into*. Put nothing in one
|
|
12
|
+
that is only available there. %>
|
|
13
|
+
<div class="flex h-64 items-start justify-center pt-12">
|
|
14
|
+
<%= render(Shadcn::HoverCard::Component.new) do |card| %>
|
|
15
|
+
<% card.with_trigger(as: :a, href: "#", class: "text-sm font-medium underline underline-offset-4") do %>
|
|
16
|
+
@shadcn
|
|
17
|
+
<% end %>
|
|
18
|
+
<% card.with_hover_card_content do %>
|
|
19
|
+
<div class="flex gap-4">
|
|
20
|
+
<%= render(Shadcn::Avatar::Component.new) do |avatar| %>
|
|
21
|
+
<% avatar.with_fallback { "SC" } %>
|
|
22
|
+
<% end %>
|
|
23
|
+
<div class="space-y-1">
|
|
24
|
+
<h4 class="text-sm font-semibold">@shadcn</h4>
|
|
25
|
+
<p class="text-sm">The React framework — created and maintained by @vercel.</p>
|
|
26
|
+
<p class="text-xs text-muted-foreground">Joined December 2021</p>
|
|
27
|
+
<%# A link, on purpose: it is what the tab-order behaviour has to act
|
|
28
|
+
on, and a card with nothing focusable in it would make that
|
|
29
|
+
assertion vacuous. It is also the realistic case — the reason to
|
|
30
|
+
build a card rather than a tooltip is that it can hold one. %>
|
|
31
|
+
<a href="#" class="text-xs font-medium underline underline-offset-4">View profile</a>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
<% end %>
|
|
35
|
+
<% end %>
|
|
36
|
+
</div>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<%# Upstream's "Sides" and "Trigger Delays". The delays are the part worth
|
|
2
|
+
seeing: a hover card that opened instantly would fire on every pointer that
|
|
3
|
+
crossed it, and one that closed instantly could not be reached. %>
|
|
4
|
+
<div class="flex h-48 items-center justify-center gap-2">
|
|
5
|
+
<% [[:top, 0, 0], [:right, 700, 300], [:bottom, 0, 0], [:left, 700, 300]].each do |side, open_delay, close_delay| %>
|
|
6
|
+
<%= render(Shadcn::HoverCard::Component.new(side:, open_delay:, close_delay:)) do |h| %>
|
|
7
|
+
<% h.with_trigger(as: :button, class: Shadcn::Button::Component.variant_classes(variant: :outline)) { side.to_s.capitalize } %>
|
|
8
|
+
<% h.with_hover_card_content(class: "w-56") do %>
|
|
9
|
+
<p class="text-sm">
|
|
10
|
+
On the <%= side %>, after <%= open_delay %>ms.
|
|
11
|
+
</p>
|
|
12
|
+
<% end %>
|
|
13
|
+
<% end %>
|
|
14
|
+
<% end %>
|
|
15
|
+
</div>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
module HoverCard
|
|
5
|
+
module Trigger
|
|
6
|
+
# HoverCardTrigger. Radix opens on hover *and* on focus and closes on blur
|
|
7
|
+
# (hover-card.tsx:138-141), so a keyboard reaches the card even though it
|
|
8
|
+
# cannot be tabbed into — see the content for the other half of that.
|
|
9
|
+
#
|
|
10
|
+
# `default_tag :button` because the demo's trigger is one and a hoverable
|
|
11
|
+
# affordance needs to be focusable to open on focus at all; `as: :a` is
|
|
12
|
+
# the usual override, which is what upstream's example passes.
|
|
13
|
+
class Component < ApplicationViewComponent
|
|
14
|
+
default_tag :button
|
|
15
|
+
slot_name :"hover-card-trigger"
|
|
16
|
+
|
|
17
|
+
def element_attributes(**defaults)
|
|
18
|
+
attributes = {
|
|
19
|
+
"data-state" => "closed",
|
|
20
|
+
"data-shadcn--hover-card-target" => "trigger",
|
|
21
|
+
"data-action" => "pointerenter->shadcn--hover-card#open " \
|
|
22
|
+
"pointerleave->shadcn--hover-card#close " \
|
|
23
|
+
"focus->shadcn--hover-card#open " \
|
|
24
|
+
"blur->shadcn--hover-card#close"
|
|
25
|
+
}
|
|
26
|
+
attributes[:type] = "button" if tag_name.to_sym == :button
|
|
27
|
+
|
|
28
|
+
super(**attributes.merge(defaults))
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
module Icon
|
|
5
|
+
# Inline replacement for the `lucide-react` icons the components import.
|
|
6
|
+
#
|
|
7
|
+
# <%= render Shadcn::Icon::Component.new(:"chevron-down", class: "size-4") %>
|
|
8
|
+
#
|
|
9
|
+
# Emits the same SVG lucide does — 24×24 viewBox, `currentColor` stroke,
|
|
10
|
+
# `lucide lucide-<name>` classes — so the `[&_svg:not([class*='size-'])]:size-4`
|
|
11
|
+
# rules on the surrounding components behave identically.
|
|
12
|
+
class Component < ApplicationViewComponent
|
|
13
|
+
# The drawings live in `vendor/lucide/icons` as the files lucide
|
|
14
|
+
# publishes, and `rake icons:build` turns them into
|
|
15
|
+
# `ShadcnViewComponent::Icons::PATHS`. They used to be typed into this
|
|
16
|
+
# file by hand, which went exactly as that always goes: the count in the
|
|
17
|
+
# README said eleven of twenty-two, and `search` had been drawn from an
|
|
18
|
+
# older lucide — a magnifier with a shorter handle than the one every
|
|
19
|
+
# other icon in the set was drawn with.
|
|
20
|
+
SVG_ATTRIBUTES = {
|
|
21
|
+
"xmlns" => "http://www.w3.org/2000/svg",
|
|
22
|
+
"width" => "24",
|
|
23
|
+
"height" => "24",
|
|
24
|
+
"viewBox" => "0 0 24 24",
|
|
25
|
+
"fill" => "none",
|
|
26
|
+
"stroke" => "currentColor",
|
|
27
|
+
"stroke-width" => "2",
|
|
28
|
+
"stroke-linecap" => "round",
|
|
29
|
+
"stroke-linejoin" => "round"
|
|
30
|
+
}.freeze
|
|
31
|
+
|
|
32
|
+
default_tag :svg
|
|
33
|
+
|
|
34
|
+
def initialize(name, **attributes)
|
|
35
|
+
# Resolved through the registry, which owns the alias table: a host
|
|
36
|
+
# registering `"more-horizontal"` and this rendering `"ellipsis"` have
|
|
37
|
+
# to meet somewhere, and the file that stores the drawings is the only
|
|
38
|
+
# place both can.
|
|
39
|
+
@name = ShadcnViewComponent::IconRegistry.canonical(name)
|
|
40
|
+
|
|
41
|
+
# Loud where it can be fixed, silent where it cannot. An icon is
|
|
42
|
+
# decorative, and a gem should not be able to take down a page in an
|
|
43
|
+
# application it has never seen — the same trade Rails makes with a
|
|
44
|
+
# missing translation.
|
|
45
|
+
raise ArgumentError, "unknown lucide icon: #{name}" if path.nil? && Rails.env.local?
|
|
46
|
+
|
|
47
|
+
super(**attributes)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
attr_reader :name
|
|
51
|
+
|
|
52
|
+
# The registry is read first so a host can replace one of the bundled
|
|
53
|
+
# eleven and not only add a twelfth. The other order ignored
|
|
54
|
+
# `register("check", …)` in silence — nothing raised, nothing logged, the
|
|
55
|
+
# gem's own tick still rendering — which leaves a host staring at an icon
|
|
56
|
+
# that will not change with no way to find out why.
|
|
57
|
+
def path
|
|
58
|
+
@path ||= Shadcn::Icon.registered[name] || ShadcnViewComponent::Icons::PATHS[name]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def call
|
|
62
|
+
return "".html_safe if path.nil?
|
|
63
|
+
|
|
64
|
+
render_element(body: path.html_safe)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def element_attributes(**defaults)
|
|
68
|
+
super(**SVG_ATTRIBUTES.merge(defaults))
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def css_classes(extra = nil)
|
|
72
|
+
ShadcnViewComponent.cn("lucide", "lucide-#{name}", extra)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# `Shadcn::Icon.register` / `.registered`, kept in this sibling file — the
|
|
5
|
+
# same split every other family uses between its shared module and
|
|
6
|
+
# `component.rb` — so `Shadcn::Icon` resolves without first loading
|
|
7
|
+
# `Icon::Component`, and these read naturally from a view or another
|
|
8
|
+
# component without going through it. That does *not* extend to a host's
|
|
9
|
+
# initializer: no autoloadable constant, this one included, resolves there
|
|
10
|
+
# — `ShadcnViewComponent::IconRegistry`, required directly in
|
|
11
|
+
# `lib/shadcn_view_component.rb`, is the entry point the README documents
|
|
12
|
+
# for that case.
|
|
13
|
+
#
|
|
14
|
+
# The storage itself lives in `ShadcnViewComponent::IconRegistry`, under
|
|
15
|
+
# `lib/`, rather than on this module: `app/components` reloads in
|
|
16
|
+
# development, and a hash held here would be discarded — and every
|
|
17
|
+
# registered icon un-registered — on the next code reload.
|
|
18
|
+
module Icon
|
|
19
|
+
def self.registered
|
|
20
|
+
ShadcnViewComponent::IconRegistry.registered
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Every lucide alias this gem knows — `more-horizontal` for `ellipsis`,
|
|
24
|
+
# `loader-2` for `loader-circle`. Registering under either spelling reaches
|
|
25
|
+
# both, because the registry stores one entry per icon rather than one per
|
|
26
|
+
# name.
|
|
27
|
+
def self.aliases
|
|
28
|
+
ShadcnViewComponent::IconRegistry::ALIASES
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# The ported components import a couple of dozen lucide icons; lucide has
|
|
32
|
+
# about 1,500. The exact set is `ShadcnViewComponent::Icons::PATHS`, and a
|
|
33
|
+
# spec keeps it equal to what the components render — no number is written
|
|
34
|
+
# in prose here, because that is what went stale last time.
|
|
35
|
+
# A host reaches this pair from a view, a component or anywhere else
|
|
36
|
+
# autoloading has already run — not from an initializer, which is what
|
|
37
|
+
# `ShadcnViewComponent::IconRegistry` is for. Registering a bundled name
|
|
38
|
+
# replaces it.
|
|
39
|
+
def self.register(name, path)
|
|
40
|
+
ShadcnViewComponent::IconRegistry.register(name, path)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
module Input
|
|
5
|
+
# Port of registry/new-york-v4/ui/input.tsx
|
|
6
|
+
class Component < ApplicationViewComponent
|
|
7
|
+
default_tag :input
|
|
8
|
+
slot_name :input
|
|
9
|
+
|
|
10
|
+
style do
|
|
11
|
+
base {
|
|
12
|
+
"h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 " \
|
|
13
|
+
"py-1 text-base shadow-xs transition-[color,box-shadow] outline-none " \
|
|
14
|
+
"selection:bg-primary selection:text-primary-foreground file:inline-flex " \
|
|
15
|
+
"file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium " \
|
|
16
|
+
"file:text-foreground placeholder:text-muted-foreground " \
|
|
17
|
+
"disabled:pointer-events-none disabled:cursor-not-allowed " \
|
|
18
|
+
"disabled:opacity-50 md:text-sm dark:bg-input/30 focus-visible:border-ring " \
|
|
19
|
+
"focus-visible:ring-[3px] focus-visible:ring-ring/50 " \
|
|
20
|
+
"aria-invalid:border-destructive aria-invalid:ring-destructive/20 " \
|
|
21
|
+
"dark:aria-invalid:ring-destructive/40"
|
|
22
|
+
}
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
module Input
|
|
5
|
+
class Preview < ApplicationViewComponentPreview
|
|
6
|
+
def default
|
|
7
|
+
render_with_template
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Upstream's "Disabled", "Invalid", "File" and "Required", together: each is an attribute passed straight through rather than a variant.
|
|
11
|
+
def states
|
|
12
|
+
render_with_template
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<div class="flex w-[320px] flex-col gap-4">
|
|
2
|
+
<%= render(Shadcn::Input::Component.new(type: "email", placeholder: "m@example.com")) %>
|
|
3
|
+
<%= render(Shadcn::Input::Component.new(placeholder: "Disabled", disabled: true)) %>
|
|
4
|
+
<%= render(Shadcn::Input::Component.new(placeholder: "Invalid", "aria-invalid": "true")) %>
|
|
5
|
+
</div>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<%# Upstream shows these as four examples — "Disabled", "Invalid", "File" and
|
|
2
|
+
"Required". They are one page here because none of them is a variant: each
|
|
3
|
+
is an attribute the caller passes straight through, and seeing them beside
|
|
4
|
+
one another is what shows that.
|
|
5
|
+
|
|
6
|
+
Every control is labelled. axe takes a placeholder as an accessible name, so
|
|
7
|
+
a placeholder-less input is the one that fails — which is what the invalid
|
|
8
|
+
example needs to be, since it carries a value rather than a placeholder. %>
|
|
9
|
+
<div class="flex w-[320px] flex-col gap-4">
|
|
10
|
+
<% [["ordinary", "Ordinary", {}],
|
|
11
|
+
["disabled", "Disabled", { disabled: true }],
|
|
12
|
+
["invalid", "Invalid", { value: "not-an-email", "aria-invalid": "true" }],
|
|
13
|
+
["file", "File", { type: "file" }],
|
|
14
|
+
["required", "Required", { required: true }]].each do |id, label, options| %>
|
|
15
|
+
<div class="flex flex-col gap-2">
|
|
16
|
+
<%= render(Shadcn::Label::Component.new(for: "input-#{id}")) { label } %>
|
|
17
|
+
<%= render(Shadcn::Input::Component.new(id: "input-#{id}", **options)) %>
|
|
18
|
+
</div>
|
|
19
|
+
<% end %>
|
|
20
|
+
</div>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
module InputGroup
|
|
5
|
+
module Addon
|
|
6
|
+
# InputGroupAddon. The one part of this family that is not just markup:
|
|
7
|
+
# upstream gives it an `onClick` that focuses the group's input, unless
|
|
8
|
+
# the click landed on a button. That is the `focusControl` action.
|
|
9
|
+
class Component < ApplicationViewComponent
|
|
10
|
+
slot_name :"input-group-addon"
|
|
11
|
+
|
|
12
|
+
style do
|
|
13
|
+
base {
|
|
14
|
+
"flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm " \
|
|
15
|
+
"font-medium text-muted-foreground select-none " \
|
|
16
|
+
"group-data-[disabled=true]/input-group:opacity-50 " \
|
|
17
|
+
"[&>kbd]:rounded-[calc(var(--radius)-5px)] " \
|
|
18
|
+
"[&>svg:not([class*='size-'])]:size-4"
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
variants {
|
|
22
|
+
align {
|
|
23
|
+
# The keys keep upstream's spelling, which is not valid Ruby.
|
|
24
|
+
send(:"inline-start") {
|
|
25
|
+
"order-first pl-3 has-[>button]:ml-[-0.45rem] has-[>kbd]:ml-[-0.35rem]"
|
|
26
|
+
}
|
|
27
|
+
send(:"inline-end") {
|
|
28
|
+
"order-last pr-3 has-[>button]:mr-[-0.45rem] has-[>kbd]:mr-[-0.35rem]"
|
|
29
|
+
}
|
|
30
|
+
send(:"block-start") {
|
|
31
|
+
"order-first w-full justify-start px-3 pt-3 " \
|
|
32
|
+
"group-has-[>input]/input-group:pt-2.5 [.border-b]:pb-3"
|
|
33
|
+
}
|
|
34
|
+
send(:"block-end") {
|
|
35
|
+
"order-last w-full justify-start px-3 pb-3 " \
|
|
36
|
+
"group-has-[>input]/input-group:pb-2.5 [.border-t]:pt-3"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
defaults { { align: :"inline-start" } }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
attr_reader :align
|
|
45
|
+
|
|
46
|
+
def initialize(align: :"inline-start", **attributes)
|
|
47
|
+
@align = align&.to_sym || :"inline-start"
|
|
48
|
+
super(**attributes)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def style_variants
|
|
52
|
+
{ align: }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def element_attributes(**defaults)
|
|
56
|
+
super(**{
|
|
57
|
+
role: "group",
|
|
58
|
+
"data-align" => align,
|
|
59
|
+
"data-action" => "click->shadcn--input-group#focusControl"
|
|
60
|
+
}.merge(defaults))
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
module InputGroup
|
|
5
|
+
module Button
|
|
6
|
+
# InputGroupButton renders a Button with its own size scale layered on
|
|
7
|
+
# top, and forces three of the button's props.
|
|
8
|
+
#
|
|
9
|
+
# Those three are *defaults*, not overrides: they go through `super`, so
|
|
10
|
+
# a caller asking for `variant: :destructive` still gets it. Ranking them
|
|
11
|
+
# above the caller would make the button un-restylable.
|
|
12
|
+
class Component < Shadcn::Button::Component
|
|
13
|
+
SIZES = {
|
|
14
|
+
xs: "h-6 gap-1 rounded-[calc(var(--radius)-5px)] px-2 has-[>svg]:px-2 " \
|
|
15
|
+
"[&>svg:not([class*='size-'])]:size-3.5",
|
|
16
|
+
sm: "h-8 gap-1.5 rounded-md px-2.5 has-[>svg]:px-2.5",
|
|
17
|
+
"icon-xs": "size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0",
|
|
18
|
+
"icon-sm": "size-8 p-0 has-[>svg]:p-0"
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
attr_reader :group_size
|
|
22
|
+
|
|
23
|
+
def initialize(variant: :ghost, size: :xs, **attributes)
|
|
24
|
+
@group_size = size&.to_sym || :xs
|
|
25
|
+
# The button's own size scale is not this one, so it keeps its
|
|
26
|
+
# default; the group's scale is applied as classes instead.
|
|
27
|
+
super(variant:, **attributes)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def element_attributes(**defaults)
|
|
31
|
+
super(**{ type: "button", "data-size" => group_size }.merge(defaults))
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def css_classes(extra = nil)
|
|
35
|
+
super([ SIZES.fetch(group_size), extra ].compact.join(" "))
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
module InputGroup
|
|
5
|
+
# Port of registry/new-york-v4/ui/input-group.tsx
|
|
6
|
+
#
|
|
7
|
+
# `group/input-group` is a named Tailwind group the addon's classes reach
|
|
8
|
+
# back into (`group-has-[>input]/input-group:…`), so the two halves only
|
|
9
|
+
# work as a pair.
|
|
10
|
+
class Component < ApplicationViewComponent
|
|
11
|
+
slot_name :"input-group"
|
|
12
|
+
|
|
13
|
+
style do
|
|
14
|
+
base {
|
|
15
|
+
"group/input-group relative flex w-full items-center rounded-md border " \
|
|
16
|
+
"border-input shadow-xs transition-[color,box-shadow] outline-none " \
|
|
17
|
+
"dark:bg-input/30 h-9 min-w-0 has-[>textarea]:h-auto " \
|
|
18
|
+
"has-[>[data-align=inline-start]]:[&>input]:pl-2 " \
|
|
19
|
+
"has-[>[data-align=inline-end]]:[&>input]:pr-2 " \
|
|
20
|
+
"has-[>[data-align=block-start]]:h-auto " \
|
|
21
|
+
"has-[>[data-align=block-start]]:flex-col " \
|
|
22
|
+
"has-[>[data-align=block-start]]:[&>input]:pb-3 " \
|
|
23
|
+
"has-[>[data-align=block-end]]:h-auto " \
|
|
24
|
+
"has-[>[data-align=block-end]]:flex-col " \
|
|
25
|
+
"has-[>[data-align=block-end]]:[&>input]:pt-3 " \
|
|
26
|
+
"has-[[data-slot=input-group-control]:focus-visible]:border-ring " \
|
|
27
|
+
"has-[[data-slot=input-group-control]:focus-visible]:ring-[3px] " \
|
|
28
|
+
"has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 " \
|
|
29
|
+
"has-[[data-slot][aria-invalid=true]]:border-destructive " \
|
|
30
|
+
"has-[[data-slot][aria-invalid=true]]:ring-destructive/20 " \
|
|
31
|
+
"dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40"
|
|
32
|
+
}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# The controller lives on the group rather than on the addon, so that
|
|
36
|
+
# `this.element` is the group and the lookup matches upstream's
|
|
37
|
+
# `currentTarget.parentElement.querySelector("input")`.
|
|
38
|
+
def element_attributes(**defaults)
|
|
39
|
+
super(**{ role: "group", "data-controller" => "shadcn--input-group" }.merge(defaults))
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
module InputGroup
|
|
5
|
+
module Input
|
|
6
|
+
# InputGroupInput renders the ported Input, stripped of its own border and
|
|
7
|
+
# ring so the group's box provides both.
|
|
8
|
+
#
|
|
9
|
+
# Its `data-slot` is `input-group-control`, which it shares with
|
|
10
|
+
# InputGroupTextarea. That is deliberate upstream: the group's
|
|
11
|
+
# `has-[[data-slot=input-group-control]:focus-visible]:…` classes have to
|
|
12
|
+
# match either one, so disambiguating them would break the focus ring.
|
|
13
|
+
class Component < Shadcn::Input::Component
|
|
14
|
+
slot_name :"input-group-control"
|
|
15
|
+
|
|
16
|
+
EXTRA_CLASSES = "flex-1 rounded-none border-0 bg-transparent shadow-none " \
|
|
17
|
+
"focus-visible:ring-0 dark:bg-transparent"
|
|
18
|
+
|
|
19
|
+
def css_classes(extra = nil)
|
|
20
|
+
super([ EXTRA_CLASSES, extra ].compact.join(" "))
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|