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,42 @@
|
|
|
1
|
+
<%# Upstream's "Sizes" and "Spacing".
|
|
2
|
+
|
|
3
|
+
Neither is an argument. A slide is `basis-full` until the caller says
|
|
4
|
+
`basis-1/2` or `basis-1/3`, and the gutter is the track's negative margin
|
|
5
|
+
against the item's padding — `-ml-4`/`pl-4` by default, and any matching
|
|
6
|
+
pair the caller likes. Both are upstream's own instructions rather than this
|
|
7
|
+
port's: `carousel.tsx` has no prop for either. %>
|
|
8
|
+
<div class="flex flex-col gap-10 px-14">
|
|
9
|
+
<%= render(Shadcn::Carousel::Component.new(class: "w-full max-w-sm")) do |c| %>
|
|
10
|
+
<% c.with_carousel_content do |content| %>
|
|
11
|
+
<% 6.times do |i| %>
|
|
12
|
+
<% content.with_item(class: "basis-1/2") do %>
|
|
13
|
+
<div class="p-1">
|
|
14
|
+
<%= render(Shadcn::Card::Component.new) do |card| %>
|
|
15
|
+
<% card.with_card_content(class: "flex aspect-square items-center justify-center p-6") do %>
|
|
16
|
+
<span class="text-3xl font-semibold"><%= i + 1 %></span>
|
|
17
|
+
<% end %>
|
|
18
|
+
<% end %>
|
|
19
|
+
</div>
|
|
20
|
+
<% end %>
|
|
21
|
+
<% end %>
|
|
22
|
+
<% end %>
|
|
23
|
+
<% c.with_previous %>
|
|
24
|
+
<% c.with_next_control %>
|
|
25
|
+
<% end %>
|
|
26
|
+
|
|
27
|
+
<%= render(Shadcn::Carousel::Component.new(class: "w-full max-w-sm")) do |c| %>
|
|
28
|
+
<% c.with_carousel_content(class: "-ml-2") do |content| %>
|
|
29
|
+
<% 6.times do |i| %>
|
|
30
|
+
<% content.with_item(class: "basis-1/3 pl-2") do %>
|
|
31
|
+
<%= render(Shadcn::Card::Component.new) do |card| %>
|
|
32
|
+
<% card.with_card_content(class: "flex aspect-square items-center justify-center p-6") do %>
|
|
33
|
+
<span class="text-2xl font-semibold"><%= i + 1 %></span>
|
|
34
|
+
<% end %>
|
|
35
|
+
<% end %>
|
|
36
|
+
<% end %>
|
|
37
|
+
<% end %>
|
|
38
|
+
<% end %>
|
|
39
|
+
<% c.with_previous %>
|
|
40
|
+
<% c.with_next_control %>
|
|
41
|
+
<% end %>
|
|
42
|
+
</div>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<%# Upstream's "Orientation". The axis reaches three places — the track becomes
|
|
2
|
+
a column, the items pad from the top, and the controls move above and below
|
|
3
|
+
and turn — so it is the root's argument and each part reads it.
|
|
4
|
+
|
|
5
|
+
`basis-1/2` is upstream's own example and it is the caller's, not the
|
|
6
|
+
component's: two slides in the window rather than one. Without it a card
|
|
7
|
+
sized by its own text floats in the middle of a 240px slot, which is what
|
|
8
|
+
this preview shipped as and what a person reported. %>
|
|
9
|
+
<div class="py-14">
|
|
10
|
+
<%= render(Shadcn::Carousel::Component.new(orientation: :vertical, class: "w-full max-w-xs")) do |c| %>
|
|
11
|
+
<% c.with_carousel_content(orientation: :vertical, class: "h-[340px]") do |content| %>
|
|
12
|
+
<% 5.times do |i| %>
|
|
13
|
+
<% content.with_item(orientation: :vertical, class: "basis-1/2") do %>
|
|
14
|
+
<div class="p-1">
|
|
15
|
+
<%= render(Shadcn::Card::Component.new) do |card| %>
|
|
16
|
+
<% card.with_card_content(class: "flex items-center justify-center p-6") do %>
|
|
17
|
+
<span class="text-3xl font-semibold"><%= i + 1 %></span>
|
|
18
|
+
<% end %>
|
|
19
|
+
<% end %>
|
|
20
|
+
</div>
|
|
21
|
+
<% end %>
|
|
22
|
+
<% end %>
|
|
23
|
+
<% end %>
|
|
24
|
+
<% c.with_previous(orientation: :vertical) %>
|
|
25
|
+
<% c.with_next_control(orientation: :vertical) %>
|
|
26
|
+
<% end %>
|
|
27
|
+
</div>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
module Carousel
|
|
5
|
+
module Previous
|
|
6
|
+
# CarouselPrevious — the button's own variants, as `pagination-link` takes
|
|
7
|
+
# them: upstream renders a `<Button variant="outline" size="icon">` and
|
|
8
|
+
# adds its placement on top, so the classes come from
|
|
9
|
+
# `Button.variant_classes` rather than from a style block of this
|
|
10
|
+
# component's own. Redefining `style` here would *replace* the button's
|
|
11
|
+
# base rather than add to it, which is a round control with no round.
|
|
12
|
+
class Component < ApplicationViewComponent
|
|
13
|
+
default_tag :button
|
|
14
|
+
slot_name :"carousel-previous"
|
|
15
|
+
|
|
16
|
+
PLACEMENT = {
|
|
17
|
+
horizontal: "top-1/2 -left-12 -translate-y-1/2",
|
|
18
|
+
vertical: "-top-12 left-1/2 -translate-x-1/2 rotate-90"
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
attr_reader :orientation, :variant, :size
|
|
22
|
+
|
|
23
|
+
def initialize(orientation: :horizontal, variant: :outline, size: :icon, **attributes)
|
|
24
|
+
@orientation = orientation&.to_sym == :vertical ? :vertical : :horizontal
|
|
25
|
+
@variant = variant&.to_sym || :outline
|
|
26
|
+
@size = size&.to_sym || :icon
|
|
27
|
+
super(**attributes)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Disabled until the controller says otherwise: at rest a carousel is at
|
|
31
|
+
# one end, and which end has room is a question about a viewport the
|
|
32
|
+
# server has never measured.
|
|
33
|
+
def element_attributes(**defaults)
|
|
34
|
+
super(**{
|
|
35
|
+
type: "button",
|
|
36
|
+
disabled: true,
|
|
37
|
+
"data-shadcn--carousel-target" => "previous",
|
|
38
|
+
"data-action" => "click->shadcn--carousel#previous"
|
|
39
|
+
}.merge(defaults))
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def css_classes(extra = nil)
|
|
43
|
+
Button::Component.variant_classes(
|
|
44
|
+
variant:,
|
|
45
|
+
size:,
|
|
46
|
+
class: [ "absolute size-8 rounded-full", PLACEMENT.fetch(orientation), extra ].compact.join(" ")
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def call
|
|
51
|
+
render_element(body: safe_join([
|
|
52
|
+
render(Icon::Component.new("arrow-left")),
|
|
53
|
+
tag.span(shadcn_t("carousel.previous"), class: "sr-only")
|
|
54
|
+
]))
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
module Chart
|
|
5
|
+
module Area
|
|
6
|
+
# Upstream's "Area Chart": the line chart with the space beneath each
|
|
7
|
+
# series filled in.
|
|
8
|
+
#
|
|
9
|
+
# chart.with_area(data: { "January" => { desktop: 186, mobile: 80 } })
|
|
10
|
+
class Component < Line::Component
|
|
11
|
+
# Flat, and see-through enough that two overlapping series both stay
|
|
12
|
+
# readable. Upstream's docs example fills from a `<linearGradient>`
|
|
13
|
+
# instead: that needs a `<defs>` with an id per series, and an id that
|
|
14
|
+
# has to stay unique in a page this gem cannot see is a cost the fill
|
|
15
|
+
# does not repay.
|
|
16
|
+
OPACITY = 0.4
|
|
17
|
+
|
|
18
|
+
slot_name :"chart-area"
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
# The fills go down before every line and dot, or a series drawn later
|
|
23
|
+
# would wash out the one before it.
|
|
24
|
+
def shapes = plot.series.map { |key| fill(key) } + super
|
|
25
|
+
|
|
26
|
+
def fill(key)
|
|
27
|
+
tag.polygon(points: closed(key), fill: colour_of(key), "fill-opacity": OPACITY)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# A line, then back along the baseline to where it started — the two
|
|
31
|
+
# corners that turn a stroke into a shape.
|
|
32
|
+
def closed(key)
|
|
33
|
+
points = plot.points(key)
|
|
34
|
+
return "" if points.empty?
|
|
35
|
+
|
|
36
|
+
[ "#{points.first[:x]},#{plot.baseline.round(2)}",
|
|
37
|
+
*points.map { |point| "#{point[:x]},#{point[:y]}" },
|
|
38
|
+
"#{points.last[:x]},#{plot.baseline.round(2)}" ].join(" ")
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
module Chart
|
|
5
|
+
module Bar
|
|
6
|
+
# Upstream's "Bar Chart" and "Bar Chart - Stacked", drawn on the server.
|
|
7
|
+
#
|
|
8
|
+
# chart.with_bar(data: { "January" => { desktop: 186, mobile: 80 } })
|
|
9
|
+
#
|
|
10
|
+
# `data:` is a Hash of category to a Hash of series to number — one
|
|
11
|
+
# `each_with_object` away from `group(:month, :platform).sum(:visits)`.
|
|
12
|
+
class Component < Cartesian::Component
|
|
13
|
+
# `radius={4}`, which upstream's bar examples pass at the call site in
|
|
14
|
+
# the docs. Nothing of recharts is vendored here, so unlike a class
|
|
15
|
+
# string this is checked against the rendered example and not against
|
|
16
|
+
# `vendor/`.
|
|
17
|
+
RADIUS = 4
|
|
18
|
+
|
|
19
|
+
slot_name :"chart-bar"
|
|
20
|
+
|
|
21
|
+
attr_reader :stacked
|
|
22
|
+
|
|
23
|
+
# `stacked:` lives here and not on the frame because it is a bar's
|
|
24
|
+
# question. A stacked *line* would need cumulative points, and `Plot`
|
|
25
|
+
# does not compute them — so the option is absent where it would lie.
|
|
26
|
+
def initialize(stacked: false, **attributes)
|
|
27
|
+
@stacked = stacked
|
|
28
|
+
super(**attributes)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def plot = @plot ||= Plot.new(data: @data, series: @series, stacked:)
|
|
34
|
+
|
|
35
|
+
# A `<rect>` rounds all four corners or none, because `rx` takes one
|
|
36
|
+
# number. A stack needs its seams square and only its two ends round,
|
|
37
|
+
# which is the per-corner form of the same prop. So a bar is a path,
|
|
38
|
+
# like the pie's slices.
|
|
39
|
+
def shapes
|
|
40
|
+
plot.bars.group_by { |bar| bar[:category] }.values.flat_map do |bars|
|
|
41
|
+
bars.map.with_index do |bar, position|
|
|
42
|
+
tag.path(d: outline(bar, **corners(position, bars.size)), fill: colour_of(bar[:key]),
|
|
43
|
+
**mark_attributes(**bar.slice(:category, :key, :value)))
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Grouped, every bar stands on its own and rounds both ends. Stacked,
|
|
49
|
+
# only the bottom of the first segment and the top of the last are on
|
|
50
|
+
# the outside of anything.
|
|
51
|
+
def corners(position, count)
|
|
52
|
+
return { top: true, bottom: true } unless stacked
|
|
53
|
+
|
|
54
|
+
{ top: position == count - 1, bottom: position.zero? }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# One formula for all four cases: a zero-radius arc is drawn as a
|
|
58
|
+
# straight line, which is the SVG spec's own answer to `A 0 0`.
|
|
59
|
+
def outline(bar, top:, bottom:)
|
|
60
|
+
radius = [ RADIUS, bar[:width] / 2.0, bar[:height] / 2.0 ].min.round(2)
|
|
61
|
+
t = top ? radius : 0
|
|
62
|
+
b = bottom ? radius : 0
|
|
63
|
+
x, y = bar.values_at(:x, :y)
|
|
64
|
+
right = (x + bar[:width]).round(2)
|
|
65
|
+
base = (y + bar[:height]).round(2)
|
|
66
|
+
|
|
67
|
+
"M #{x} #{(y + t).round(2)} A #{t} #{t} 0 0 1 #{(x + t).round(2)} #{y} " \
|
|
68
|
+
"L #{(right - t).round(2)} #{y} A #{t} #{t} 0 0 1 #{right} #{(y + t).round(2)} " \
|
|
69
|
+
"L #{right} #{(base - b).round(2)} A #{b} #{b} 0 0 1 #{(right - b).round(2)} #{base} " \
|
|
70
|
+
"L #{(x + b).round(2)} #{base} A #{b} #{b} 0 0 1 #{x} #{(base - b).round(2)} Z"
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
module Chart
|
|
5
|
+
module Cartesian
|
|
6
|
+
# What a bar, a line and an area all need and the pie did not: a grid, an
|
|
7
|
+
# axis down the left, category labels along the bottom, and one accessible
|
|
8
|
+
# name carrying every number. Abstract — `shapes` is the subclass's half.
|
|
9
|
+
#
|
|
10
|
+
# **Ours**, like the pie: `chart.tsx` draws nothing, so there is no
|
|
11
|
+
# upstream markup for this to be 1:1 with. What *is* reproduced is the
|
|
12
|
+
# contract around it — every mark is filled from `--color-<key>`, which
|
|
13
|
+
# the container publishes from `config`, so a host restyles this chart
|
|
14
|
+
# from the same stylesheet that restyles upstream's.
|
|
15
|
+
#
|
|
16
|
+
# The axis is drawn in SVG attributes and CSS custom properties rather
|
|
17
|
+
# than Tailwind classes. `stroke="var(--border)"` is a token `shadcn.css`
|
|
18
|
+
# already defines, and it keeps the grid out of `reverse_parity_spec`'s
|
|
19
|
+
# way: a class here would be one no vendored source contains, and every
|
|
20
|
+
# such class has to be listed with its reason.
|
|
21
|
+
class Component < ApplicationViewComponent
|
|
22
|
+
include Focusable
|
|
23
|
+
|
|
24
|
+
default_tag :svg
|
|
25
|
+
# No `slot_name` of its own: this class is never rendered, and a
|
|
26
|
+
# `data-slot="chart-cartesian"` would name an element no page contains.
|
|
27
|
+
# Each shape declares its own.
|
|
28
|
+
|
|
29
|
+
style do
|
|
30
|
+
base { "w-full" }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
attr_reader :config, :label
|
|
34
|
+
|
|
35
|
+
def initialize(data: {}, config: {}, series: nil, label: nil, **attributes)
|
|
36
|
+
@data = data
|
|
37
|
+
@series = series
|
|
38
|
+
@config = config
|
|
39
|
+
@label = label
|
|
40
|
+
super(**attributes)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def element_attributes(**defaults)
|
|
44
|
+
super(**{ viewBox: "0 0 #{plot.width} #{plot.height}" }.merge(keyboard).merge(defaults))
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# The grid goes down first so the shapes are drawn over it, which is the
|
|
48
|
+
# one thing SVG's painter order decides for us. The table follows the
|
|
49
|
+
# graphic it describes, which is the order a reader meets them in.
|
|
50
|
+
def call
|
|
51
|
+
drawing = tag.g(safe_join([ grid, tick_labels, category_labels, *shapes ]), "aria-hidden": "true")
|
|
52
|
+
|
|
53
|
+
safe_join([ render_element(body: drawing), table ])
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def drawn? = plot.categories.any?
|
|
59
|
+
|
|
60
|
+
# No rows, no table: an empty scope on a quiet week is a thing a host's
|
|
61
|
+
# data does, and a table of nothing announces a name and then leaves a
|
|
62
|
+
# reader in an empty grid.
|
|
63
|
+
def table
|
|
64
|
+
return unless drawn?
|
|
65
|
+
|
|
66
|
+
render(Table::Component.new(caption: label, columns: plot.series.map { |key| label_for(key) },
|
|
67
|
+
rows: table_rows))
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def table_rows
|
|
71
|
+
plot.data.map do |category, values|
|
|
72
|
+
[ category, *plot.series.map { |key| number(values[key]) } ]
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def plot = @plot ||= Plot.new(data: @data, series: @series)
|
|
77
|
+
|
|
78
|
+
def shapes = raise NotImplementedError, "#{self.class} draws no shape"
|
|
79
|
+
|
|
80
|
+
def grid
|
|
81
|
+
safe_join(plot.ticks.map do |value|
|
|
82
|
+
y = plot.y_of(value).round(2)
|
|
83
|
+
|
|
84
|
+
tag.line(x1: Plot::PADDING[:left], x2: plot.width - Plot::PADDING[:right], y1: y, y2: y,
|
|
85
|
+
stroke: "var(--border)", "stroke-dasharray": ("3 3" unless value.zero?))
|
|
86
|
+
end)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def tick_labels
|
|
90
|
+
safe_join(plot.ticks.map do |value|
|
|
91
|
+
tag.text(number(value), x: Plot::PADDING[:left] - 6, y: (plot.y_of(value) + 3).round(2),
|
|
92
|
+
"text-anchor": "end", **label_attributes)
|
|
93
|
+
end)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Only every `label_every`th, and the first is always drawn: a chart
|
|
97
|
+
# whose axis starts at "Week 5" reads as though the data does.
|
|
98
|
+
def category_labels
|
|
99
|
+
every = plot.label_every
|
|
100
|
+
|
|
101
|
+
safe_join(plot.categories.each_with_index.filter_map do |category, index|
|
|
102
|
+
next unless (index % every).zero?
|
|
103
|
+
|
|
104
|
+
x = category_x(index)
|
|
105
|
+
|
|
106
|
+
tag.text(category, x: x.round(2), y: plot.height - 8,
|
|
107
|
+
"text-anchor": anchor_at(x), **label_attributes)
|
|
108
|
+
end)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Under the middle of the band, which is where a bar stands. A line
|
|
112
|
+
# chart puts its readings on a point scale instead and says so.
|
|
113
|
+
def category_x(index) = plot.x_of(index) + plot.band / 2
|
|
114
|
+
|
|
115
|
+
# A label centred on a reading that sits on the edge of the plot has
|
|
116
|
+
# half of itself outside the viewBox, and an SVG clips at its own box:
|
|
117
|
+
# the line chart's "June" was drawn as "Jun". Only a point scale reaches
|
|
118
|
+
# the edge, so this changes nothing for a bar.
|
|
119
|
+
def anchor_at(x)
|
|
120
|
+
return "start" if x <= Plot::PADDING[:left]
|
|
121
|
+
return "end" if x >= plot.width - Plot::PADDING[:right]
|
|
122
|
+
|
|
123
|
+
"middle"
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def label_attributes = { fill: "var(--muted-foreground)", "font-size": 10 }
|
|
127
|
+
|
|
128
|
+
# What every mark hands the controller, so the tooltip is filled from
|
|
129
|
+
# the DOM rather than from the series sent a second time as JSON.
|
|
130
|
+
def mark_attributes(category:, key:, value:)
|
|
131
|
+
{
|
|
132
|
+
"data-shadcn--chart-target": "mark",
|
|
133
|
+
"data-action": "pointerenter->shadcn--chart#show pointermove->shadcn--chart#move " \
|
|
134
|
+
"pointerleave->shadcn--chart#hide",
|
|
135
|
+
"data-key": key,
|
|
136
|
+
"data-label": category,
|
|
137
|
+
"data-name": label_for(key),
|
|
138
|
+
"data-display": number(value)
|
|
139
|
+
}
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def colour_of(key) = "var(--color-#{key.to_s.parameterize.underscore.dasherize})"
|
|
143
|
+
|
|
144
|
+
def label_for(key) = config.dig(key.to_sym, :label) || config.dig(key.to_s, :label) || key.to_s.humanize
|
|
145
|
+
|
|
146
|
+
# `|| 0` because a series may be missing from a category — a month
|
|
147
|
+
# where nothing was sold is a gap in the Hash, not a zero someone typed.
|
|
148
|
+
def number(value) = ActiveSupport::NumberHelper.number_to_delimited(value || 0)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
module Chart
|
|
5
|
+
# Port of registry/new-york-v4/ui/chart.tsx — the half of it that is markup.
|
|
6
|
+
#
|
|
7
|
+
# `chart.tsx` draws nothing. It is a *frame*: a container that publishes one
|
|
8
|
+
# custom property per series, and the contents of a tooltip and a legend
|
|
9
|
+
# that `recharts` fills in. The chart itself — the SVG, the scales, the
|
|
10
|
+
# axes — is `recharts`, 29,091 lines of compiled ES6 over eleven packages,
|
|
11
|
+
# and the caller writes it in JSX.
|
|
12
|
+
#
|
|
13
|
+
# So this family is the frame ported 1:1 and the drawing written here: a
|
|
14
|
+
# pie, and the three cartesian shapes — bar, line, area — over the grid and
|
|
15
|
+
# axes `Chart::Plot` computes. See
|
|
16
|
+
# [features/chart.md](../../../../.claude/docs/features/chart.md).
|
|
17
|
+
#
|
|
18
|
+
# `config:` is upstream's `ChartConfig`, key for key:
|
|
19
|
+
#
|
|
20
|
+
# config: {
|
|
21
|
+
# chrome: { label: "Chrome", color: "var(--chart-1)" },
|
|
22
|
+
# safari: { label: "Safari", theme: { light: "…", dark: "…" } }
|
|
23
|
+
# }
|
|
24
|
+
#
|
|
25
|
+
# Every colour is published as `--color-<key>` on the container, which is
|
|
26
|
+
# what lets a slice say `fill="var(--color-chrome)"` and a host restyle the
|
|
27
|
+
# chart from its own stylesheet.
|
|
28
|
+
class Component < ApplicationViewComponent
|
|
29
|
+
THEMES = { light: "", dark: ".dark" }.freeze
|
|
30
|
+
|
|
31
|
+
default_tag :div
|
|
32
|
+
slot_name :chart
|
|
33
|
+
|
|
34
|
+
style do
|
|
35
|
+
base {
|
|
36
|
+
# Upstream's own, minus thirteen `[&_.recharts-*]` variants: they
|
|
37
|
+
# select `recharts`' DOM, which nothing here renders, so they would
|
|
38
|
+
# compile to rules that match nothing. `parity_spec` holds them in
|
|
39
|
+
# `allowed_missing` with that reason.
|
|
40
|
+
#
|
|
41
|
+
# `relative` is this port's: upstream's tooltip is positioned by
|
|
42
|
+
# recharts inside a wrapper of its own, and here it is positioned
|
|
43
|
+
# against the container.
|
|
44
|
+
"relative flex aspect-video justify-center text-xs"
|
|
45
|
+
}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
renders_one :pie, ->(**options) { Pie::Component.new(config:, **options) }
|
|
49
|
+
renders_one :bar, ->(**options) { Bar::Component.new(config:, **options) }
|
|
50
|
+
renders_one :line, ->(**options) { Line::Component.new(config:, **options) }
|
|
51
|
+
renders_one :area, ->(**options) { Area::Component.new(config:, **options) }
|
|
52
|
+
renders_one :chart_tooltip, ->(**options) { Tooltip::Component.new(**options) }
|
|
53
|
+
renders_one :chart_legend, ->(**options) { Legend::Component.new(config:, **options) }
|
|
54
|
+
|
|
55
|
+
attr_reader :config
|
|
56
|
+
|
|
57
|
+
def initialize(config: {}, **attributes)
|
|
58
|
+
@config = config.transform_keys(&:to_s)
|
|
59
|
+
super(**attributes)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def element_attributes(**defaults)
|
|
63
|
+
super(**{
|
|
64
|
+
"data-chart" => chart_id,
|
|
65
|
+
"data-controller" => "shadcn--chart"
|
|
66
|
+
}.merge(defaults))
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Slots rather than block content, and rendered in an order this decides:
|
|
70
|
+
# the tooltip has to come after the shape it floats over, and slot content
|
|
71
|
+
# is emitted before block content — the trap that has shipped twice here.
|
|
72
|
+
def call
|
|
73
|
+
render_element(body: safe_join([ colours, stack, chart_tooltip ].compact))
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
# Upstream's container is a flex *row* because it has exactly one child:
|
|
79
|
+
# recharts draws the legend inside the SVG. Here the legend is its own
|
|
80
|
+
# element, so the two need a column of their own — and it is a plain div
|
|
81
|
+
# rather than a class on the container, which stays as `chart.tsx` wrote
|
|
82
|
+
# it.
|
|
83
|
+
def stack
|
|
84
|
+
tag.div(safe_join([ pie, bar, line, area, content, chart_legend ].compact),
|
|
85
|
+
class: "flex w-full min-w-0 flex-col items-center justify-center gap-2")
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def chart_id
|
|
89
|
+
@chart_id ||= "chart-#{SecureRandom.hex(4)}"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Upstream's `ChartStyle`, which is a real `<style>` element rather than
|
|
93
|
+
# inline properties: a custom property has to be readable by descendants
|
|
94
|
+
# *and* by a `.dark` rule, and an inline style cannot carry the second.
|
|
95
|
+
def colours
|
|
96
|
+
rules = THEMES.filter_map do |theme, prefix|
|
|
97
|
+
declarations = config.filter_map do |key, item|
|
|
98
|
+
colour = safe_colour(item.dig(:theme, theme) || item[:color])
|
|
99
|
+
" --color-#{key.parameterize.underscore.dasherize}: #{colour};" if colour
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
"#{prefix} [data-chart=#{chart_id}] {\n#{declarations.join("\n")}\n}" if declarations.any?
|
|
103
|
+
end
|
|
104
|
+
return if rules.empty?
|
|
105
|
+
|
|
106
|
+
tag.style(rules.join("\n").html_safe) # rubocop:disable Rails/OutputSafety
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# A colour reaches a `<style>` element, where a `}` would end the rule and
|
|
110
|
+
# everything after it would be the caller's own CSS running in the host's
|
|
111
|
+
# page. Upstream writes the same string through
|
|
112
|
+
# `dangerouslySetInnerHTML` and is exposed to exactly this; a library that
|
|
113
|
+
# runs inside an application it cannot see does not get to be.
|
|
114
|
+
#
|
|
115
|
+
# What is allowed is what a colour is made of: `oklch(0.7 0.1 20)`,
|
|
116
|
+
# `var(--chart-1)`, `#0f172a`, `rgb(1 2 3 / 40%)`.
|
|
117
|
+
COLOUR = %r{\A[\w\s().,%#/-]+\z}
|
|
118
|
+
|
|
119
|
+
def safe_colour(value)
|
|
120
|
+
value.to_s.strip.presence&.then { |colour| colour if colour.match?(COLOUR) }
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
module Chart
|
|
5
|
+
# What makes a chart reachable without a pointer, shared by every shape.
|
|
6
|
+
#
|
|
7
|
+
# This is upstream's own shape, read from the rendered example rather than
|
|
8
|
+
# from source — nothing of recharts is vendored here. Its
|
|
9
|
+
# `accessibilityLayer` puts `role="application" tabindex="0"` on the
|
|
10
|
+
# surface, and `application` is not decoration: it is what stops a screen
|
|
11
|
+
# reader from swallowing the arrow keys before they reach the page.
|
|
12
|
+
#
|
|
13
|
+
# Where this port diverges: upstream leaves the graphic itself exposed, so
|
|
14
|
+
# its axis labels reach the accessibility tree as loose text — *"400 300 200
|
|
15
|
+
# 100 0 January February …"*. Here the drawing sits inside an
|
|
16
|
+
# `aria-hidden` group, so the element that takes focus is not hidden — the
|
|
17
|
+
# pair is what axe calls `aria-hidden-focus` — and nothing inside it is
|
|
18
|
+
# read twice. The numbers are the table's job.
|
|
19
|
+
module Focusable
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def keyboard
|
|
23
|
+
return {} unless drawn?
|
|
24
|
+
|
|
25
|
+
{
|
|
26
|
+
tabindex: 0,
|
|
27
|
+
role: "application",
|
|
28
|
+
"aria-label" => label.presence || shadcn_t("chart.label"),
|
|
29
|
+
"data-action" => "keydown->shadcn--chart#navigate blur->shadcn--chart#hide"
|
|
30
|
+
}
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# A chart of nothing has nowhere for a cursor to go, and a tab stop that
|
|
34
|
+
# answers no key is worse than no tab stop.
|
|
35
|
+
def drawn? = raise NotImplementedError, "#{self.class} cannot say whether it drew anything"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
module Chart
|
|
5
|
+
module Legend
|
|
6
|
+
# `ChartLegendContent`, 1:1 in markup.
|
|
7
|
+
#
|
|
8
|
+
# Upstream's is handed a payload by recharts' `Legend`; here it is the
|
|
9
|
+
# `config` itself, which is the same list arrived at without a round trip
|
|
10
|
+
# through a chart library.
|
|
11
|
+
class Component < ApplicationViewComponent
|
|
12
|
+
default_tag :div
|
|
13
|
+
slot_name :"chart-legend"
|
|
14
|
+
|
|
15
|
+
style do
|
|
16
|
+
base { "flex items-center justify-center gap-4" }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
attr_reader :config, :vertical_align, :hide_icon
|
|
20
|
+
|
|
21
|
+
def initialize(config: {}, vertical_align: :bottom, hide_icon: false, **attributes)
|
|
22
|
+
@config = config
|
|
23
|
+
@vertical_align = vertical_align&.to_sym || :bottom
|
|
24
|
+
@hide_icon = hide_icon
|
|
25
|
+
super(**attributes)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def css_classes(extra = nil)
|
|
29
|
+
super([ vertical_align == :top ? "pb-3" : "pt-3", extra ].compact.join(" ").presence)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def call
|
|
33
|
+
render_element(body: safe_join(entries))
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def entries
|
|
39
|
+
config.map do |key, item|
|
|
40
|
+
tag.div(class: "flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground") do
|
|
41
|
+
safe_join([ swatch(key), item[:label] ].compact)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def swatch(key)
|
|
47
|
+
return if hide_icon
|
|
48
|
+
|
|
49
|
+
tag.div(class: "h-2 w-2 shrink-0 rounded-[2px]",
|
|
50
|
+
style: "background-color: var(--color-#{key.to_s.parameterize.underscore.dasherize})")
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
module Chart
|
|
5
|
+
module Line
|
|
6
|
+
# Upstream's "Line Chart", drawn on the server: one polyline per series,
|
|
7
|
+
# with a dot at each reading.
|
|
8
|
+
#
|
|
9
|
+
# chart.with_line(data: { "January" => { desktop: 186, mobile: 80 } })
|
|
10
|
+
class Component < Cartesian::Component
|
|
11
|
+
DOT = 4
|
|
12
|
+
# The dot a pointer has to hit, which is not the dot a reader sees. Four
|
|
13
|
+
# pixels is a target nobody can hold, so a transparent circle takes the
|
|
14
|
+
# events and the visible one takes the colour.
|
|
15
|
+
REACH = 10
|
|
16
|
+
|
|
17
|
+
slot_name :"chart-line"
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
# A label belongs under the reading it names, and a line's readings run
|
|
22
|
+
# edge to edge rather than band by band.
|
|
23
|
+
def category_x(index) = plot.point_x(index)
|
|
24
|
+
|
|
25
|
+
def shapes
|
|
26
|
+
plot.series.flat_map { |key| [ path(key), *dots(key) ] }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def path(key)
|
|
30
|
+
tag.polyline(points: coordinates(key), fill: "none", stroke: colour_of(key),
|
|
31
|
+
"stroke-width": 2, "stroke-linecap": "round", "stroke-linejoin": "round")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def coordinates(key)
|
|
35
|
+
plot.points(key).map { |point| "#{point[:x]},#{point[:y]}" }.join(" ")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def dots(key)
|
|
39
|
+
plot.points(key).flat_map do |point|
|
|
40
|
+
[
|
|
41
|
+
tag.circle(cx: point[:x], cy: point[:y], r: DOT, fill: colour_of(key)),
|
|
42
|
+
tag.circle(cx: point[:x], cy: point[:y], r: REACH, fill: "transparent",
|
|
43
|
+
**mark_attributes(category: point[:category], key:, value: point[:value]))
|
|
44
|
+
]
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|