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,169 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
module Chart
|
|
5
|
+
module Pie
|
|
6
|
+
# A pie, drawn on the server.
|
|
7
|
+
#
|
|
8
|
+
# **Ours.** `chart.tsx` has no markup for a shape — the caller writes
|
|
9
|
+
# `<PieChart><Pie …/></PieChart>` and `recharts` renders it. What is
|
|
10
|
+
# reproduced instead is the *contract* around it: each slice is filled
|
|
11
|
+
# from `--color-<key>`, which the container publishes from `config`, so
|
|
12
|
+
# the same stylesheet that themes upstream's chart themes this one.
|
|
13
|
+
#
|
|
14
|
+
# An arc is trigonometry and an SVG path, which is why the pie is the
|
|
15
|
+
# shape to draw first: no scales, no ticks, no axis labels to collide.
|
|
16
|
+
#
|
|
17
|
+
# render(Shadcn::Chart::Component.new(config:)) do |chart|
|
|
18
|
+
# chart.with_pie(data: { chrome: 275, safari: 200, firefox: 187 })
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# `data:` is a Hash of key to number, which is what `group(:x).sum(:y)`
|
|
22
|
+
# already hands back.
|
|
23
|
+
class Component < ApplicationViewComponent
|
|
24
|
+
include Focusable
|
|
25
|
+
|
|
26
|
+
SIZE = 250
|
|
27
|
+
PADDING = 4
|
|
28
|
+
# Twelve o'clock, where a reader starts. SVG angles start at three.
|
|
29
|
+
START = -90
|
|
30
|
+
|
|
31
|
+
default_tag :svg
|
|
32
|
+
slot_name :"chart-pie"
|
|
33
|
+
|
|
34
|
+
style do
|
|
35
|
+
base { "mx-auto aspect-square max-h-[250px]" }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
attr_reader :data, :config, :inner_radius, :percentage, :label
|
|
39
|
+
|
|
40
|
+
# `inner_radius:` is a fraction of the radius, as upstream's own
|
|
41
|
+
# examples pass one — 0 is a pie and 0.6 is the donut the docs show.
|
|
42
|
+
def initialize(data: {}, config: {}, inner_radius: 0, percentage: false, label: nil, **attributes)
|
|
43
|
+
@data = data.to_h.transform_keys(&:to_s).select { |_, value| value.to_f.positive? }
|
|
44
|
+
@config = config
|
|
45
|
+
@inner_radius = inner_radius.to_f.clamp(0, 0.95)
|
|
46
|
+
@percentage = percentage
|
|
47
|
+
@label = label
|
|
48
|
+
super(**attributes)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# No SVG `<title>`: it would say what the table says and then be drawn
|
|
52
|
+
# by the browser as a native tooltip on every hover, over this
|
|
53
|
+
# component's own. Reported from a screenshot — the grey box covering
|
|
54
|
+
# the panel was Chrome's, not ours.
|
|
55
|
+
def element_attributes(**defaults)
|
|
56
|
+
super(**{ viewBox: "0 0 #{SIZE} #{SIZE}" }.merge(keyboard).merge(defaults))
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# The table follows the graphic it describes, which is the order a
|
|
60
|
+
# reader meets them in.
|
|
61
|
+
def call
|
|
62
|
+
safe_join([ render_element(body: tag.g(safe_join(slices), "aria-hidden": "true")), table ])
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def drawn? = data.any?
|
|
68
|
+
|
|
69
|
+
# A pie of nothing draws nothing, and a table of nothing announces a
|
|
70
|
+
# name and then leaves a reader in an empty grid. A filtered scope
|
|
71
|
+
# reaches this.
|
|
72
|
+
def table
|
|
73
|
+
return unless drawn?
|
|
74
|
+
|
|
75
|
+
render(Table::Component.new(caption: label, columns: [ value_heading ], rows: table_rows))
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def table_rows
|
|
79
|
+
data.map { |key, value| [ label_for(key), display_for(value, value.to_f / total) ] }
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# One column, and it needs a name: a pie's numbers are counts, or the
|
|
83
|
+
# shares `percentage:` turns them into. The chart's own label heads it
|
|
84
|
+
# where there is one, since "Visitors" says more than "Value".
|
|
85
|
+
def value_heading = label.presence || shadcn_t("chart.value")
|
|
86
|
+
|
|
87
|
+
def total = @total ||= data.values.sum(&:to_f)
|
|
88
|
+
|
|
89
|
+
# Each slice carries what the tooltip needs, so the controller reads the
|
|
90
|
+
# DOM rather than being handed the series a second time as JSON.
|
|
91
|
+
def slices
|
|
92
|
+
angle = START
|
|
93
|
+
|
|
94
|
+
data.map do |key, value|
|
|
95
|
+
share = value.to_f / total
|
|
96
|
+
from = angle
|
|
97
|
+
angle += share * 360
|
|
98
|
+
|
|
99
|
+
tag.path(
|
|
100
|
+
d: path_for(from, angle),
|
|
101
|
+
fill: "var(--color-#{key.parameterize.underscore.dasherize})",
|
|
102
|
+
"data-shadcn--chart-target": "mark",
|
|
103
|
+
"data-action": "pointerenter->shadcn--chart#show pointermove->shadcn--chart#move " \
|
|
104
|
+
"pointerleave->shadcn--chart#hide",
|
|
105
|
+
"data-key": key,
|
|
106
|
+
"data-label": label_for(key),
|
|
107
|
+
"data-display": display_for(value, share)
|
|
108
|
+
)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def label_for(key) = config.dig(key.to_sym, :label) || config.dig(key.to_s, :label) || key.to_s.humanize
|
|
113
|
+
|
|
114
|
+
# What the tooltip shows. A share where one was asked for, because a
|
|
115
|
+
# pie's question is usually "how much of the whole" — and doing it here
|
|
116
|
+
# keeps the tooltip's own markup 1:1 with upstream, which has one cell
|
|
117
|
+
# for a value and no room for a second number.
|
|
118
|
+
def display_for(value, share)
|
|
119
|
+
return "#{(share * 100).round(1).to_s.delete_suffix('.0')}%" if percentage
|
|
120
|
+
|
|
121
|
+
ActiveSupport::NumberHelper.number_to_delimited(value)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def centre = SIZE / 2.0
|
|
125
|
+
|
|
126
|
+
def radius = centre - PADDING
|
|
127
|
+
|
|
128
|
+
# A full circle has no arc: its two ends are the same point, so the path
|
|
129
|
+
# would draw nothing at all. One slice is the case a Rails application
|
|
130
|
+
# reaches by filtering, not an odd one.
|
|
131
|
+
def path_for(from, to)
|
|
132
|
+
return ring if (to - from) >= 359.999
|
|
133
|
+
|
|
134
|
+
outer_start = point(from, radius)
|
|
135
|
+
outer_end = point(to, radius)
|
|
136
|
+
large = (to - from) > 180 ? 1 : 0
|
|
137
|
+
|
|
138
|
+
return "M #{centre} #{centre} L #{outer_start} A #{radius} #{radius} 0 #{large} 1 #{outer_end} Z" if hole.zero?
|
|
139
|
+
|
|
140
|
+
inner_start = point(to, hole)
|
|
141
|
+
inner_end = point(from, hole)
|
|
142
|
+
|
|
143
|
+
"M #{outer_start} A #{radius} #{radius} 0 #{large} 1 #{outer_end} " \
|
|
144
|
+
"L #{inner_start} A #{hole} #{hole} 0 #{large} 0 #{inner_end} Z"
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def ring
|
|
148
|
+
top = "#{centre} #{centre - radius}"
|
|
149
|
+
bottom = "#{centre} #{centre + radius}"
|
|
150
|
+
circle = "M #{top} A #{radius} #{radius} 0 1 1 #{bottom} A #{radius} #{radius} 0 1 1 #{top} Z"
|
|
151
|
+
return circle if hole.zero?
|
|
152
|
+
|
|
153
|
+
inner_top = "#{centre} #{centre - hole}"
|
|
154
|
+
inner_bottom = "#{centre} #{centre + hole}"
|
|
155
|
+
|
|
156
|
+
"#{circle} M #{inner_top} A #{hole} #{hole} 0 1 0 #{inner_bottom} A #{hole} #{hole} 0 1 0 #{inner_top} Z"
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def hole = @hole ||= (radius * inner_radius).round(2)
|
|
160
|
+
|
|
161
|
+
def point(degrees, distance)
|
|
162
|
+
radians = degrees * Math::PI / 180
|
|
163
|
+
|
|
164
|
+
"#{(centre + distance * Math.cos(radians)).round(2)} #{(centre + distance * Math.sin(radians)).round(2)}"
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
module Chart
|
|
5
|
+
# The arithmetic a cartesian chart needs, and nothing that draws.
|
|
6
|
+
#
|
|
7
|
+
# This is the part the pie did not have and the reason the todo said the
|
|
8
|
+
# axis is the work: a bar is a rectangle, but *where* it goes is a scale, a
|
|
9
|
+
# nice maximum, a set of ticks and a band per category. A plain object, so
|
|
10
|
+
# all of that is asserted directly rather than through a browser — the same
|
|
11
|
+
# trade `Calendar::Month` makes.
|
|
12
|
+
#
|
|
13
|
+
# `data` is a Hash of category to a Hash of series to number, which is what
|
|
14
|
+
# a `group(:month, :platform).sum(:visits)` becomes after one `each_with_object`:
|
|
15
|
+
#
|
|
16
|
+
# { "January" => { desktop: 186, mobile: 80 },
|
|
17
|
+
# "February" => { desktop: 305, mobile: 200 } }
|
|
18
|
+
class Plot
|
|
19
|
+
SIZE = { width: 560, height: 240 }.freeze
|
|
20
|
+
# Room for the tick labels on the left and the category labels below.
|
|
21
|
+
PADDING = { left: 34, right: 8, top: 12, bottom: 24 }.freeze
|
|
22
|
+
TICKS = 4
|
|
23
|
+
# The share of a band left empty, so neighbouring categories do not touch.
|
|
24
|
+
INSET = 0.15
|
|
25
|
+
|
|
26
|
+
attr_reader :data, :series, :stacked, :width, :height
|
|
27
|
+
|
|
28
|
+
def initialize(data: {}, series: nil, stacked: false, width: SIZE[:width], height: SIZE[:height])
|
|
29
|
+
@data = data.to_h { |category, values| [ category.to_s, values.to_h.transform_keys(&:to_s) ] }
|
|
30
|
+
@series = (series || @data.values.flat_map(&:keys).uniq).map(&:to_s)
|
|
31
|
+
@stacked = stacked
|
|
32
|
+
@width = width
|
|
33
|
+
@height = height
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def categories = data.keys
|
|
37
|
+
|
|
38
|
+
# The tallest thing the chart has to fit: one bar's value, or a whole
|
|
39
|
+
# stack's, rounded up to something a person would choose as a maximum.
|
|
40
|
+
def max
|
|
41
|
+
@max ||= nice(data.values.map { |values| tallest(values) }.max.to_f)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def ticks = @ticks ||= (0..TICKS).map { |step| max * step / TICKS }
|
|
45
|
+
|
|
46
|
+
# The horizontal room one category gets, including the space between.
|
|
47
|
+
def band = @band ||= (plot_width / [ categories.size, 1 ].max.to_f)
|
|
48
|
+
|
|
49
|
+
def x_of(index) = PADDING[:left] + band * index
|
|
50
|
+
|
|
51
|
+
def y_of(value) = PADDING[:top] + (plot_height - plot_height * value.to_f / max)
|
|
52
|
+
|
|
53
|
+
def baseline = y_of(0)
|
|
54
|
+
|
|
55
|
+
def plot_width = width - PADDING[:left] - PADDING[:right]
|
|
56
|
+
|
|
57
|
+
def plot_height = height - PADDING[:top] - PADDING[:bottom]
|
|
58
|
+
|
|
59
|
+
# One rectangle per series per category. Grouped side by side, or stacked
|
|
60
|
+
# one on top of the other — which is the same arithmetic with the bottom
|
|
61
|
+
# moved up by what is already there.
|
|
62
|
+
def bars
|
|
63
|
+
data.flat_map.with_index do |(category, values), index|
|
|
64
|
+
stacked ? stacked_bars(category, values, index) : grouped_bars(category, values, index)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Where a series' line goes. Not the middle of a band: a line's axis is a
|
|
69
|
+
# *point* scale, so the first reading sits on the left edge and the last
|
|
70
|
+
# on the right, and upstream's own line and area charts touch both. A bar
|
|
71
|
+
# cannot — it needs a band to be wide in — which is why the two shapes
|
|
72
|
+
# place their category labels differently.
|
|
73
|
+
def point_x(index)
|
|
74
|
+
return x_of(index) + band / 2 if categories.size < 2
|
|
75
|
+
|
|
76
|
+
PADDING[:left] + plot_width * index / (categories.size - 1).to_f
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def points(key)
|
|
80
|
+
key = key.to_s
|
|
81
|
+
|
|
82
|
+
data.map.with_index do |(category, values), index|
|
|
83
|
+
{ category:, value: values[key],
|
|
84
|
+
x: point_x(index).round(2), y: y_of(values[key]).round(2) }
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Every label would collide long before a year of days fits, so only every
|
|
89
|
+
# `n`th is drawn — `n` from the room the longest label needs against the
|
|
90
|
+
# room a band gives it. Upstream's own charts rotate them instead; a Rails
|
|
91
|
+
# app is likelier to pass twelve months than a thousand points, so this
|
|
92
|
+
# stays a skip rather than a rotation.
|
|
93
|
+
def label_every(pixels_per_character: 7)
|
|
94
|
+
longest = categories.map { |category| category.length }.max.to_i
|
|
95
|
+
|
|
96
|
+
[ ((longest * pixels_per_character) / band).ceil, 1 ].max
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
private
|
|
100
|
+
|
|
101
|
+
def tallest(values)
|
|
102
|
+
stacked ? series.sum { |key| values[key].to_f } : values.values.map(&:to_f).max.to_f
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def grouped_bars(category, values, index)
|
|
106
|
+
gutter = band * INSET
|
|
107
|
+
slot = (band - gutter * 2) / [ series.size, 1 ].max
|
|
108
|
+
|
|
109
|
+
series.each_with_index.map do |key, position|
|
|
110
|
+
value = values[key]
|
|
111
|
+
top = y_of(value)
|
|
112
|
+
|
|
113
|
+
bar(category:, key:, value:,
|
|
114
|
+
x: (x_of(index) + gutter + slot * position).round(2), y: top.round(2),
|
|
115
|
+
width: slot.round(2), height: (baseline - top).round(2))
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def stacked_bars(category, values, index)
|
|
120
|
+
gutter = band * INSET
|
|
121
|
+
bottom = baseline
|
|
122
|
+
|
|
123
|
+
series.map do |key|
|
|
124
|
+
value = values[key]
|
|
125
|
+
height = plot_height * value.to_f / max
|
|
126
|
+
bottom -= height
|
|
127
|
+
|
|
128
|
+
bar(category:, key:, value:,
|
|
129
|
+
x: (x_of(index) + gutter).round(2), y: bottom.round(2),
|
|
130
|
+
width: (band - gutter * 2).round(2), height: height.round(2))
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def bar(**attributes) = attributes
|
|
135
|
+
|
|
136
|
+
# A maximum a person would have chosen: the next round number above the
|
|
137
|
+
# tallest value, so the ticks read 0, 100, 200 rather than 0, 78.5, 157.
|
|
138
|
+
def nice(value)
|
|
139
|
+
return 1 if value <= 0
|
|
140
|
+
|
|
141
|
+
step = 10**Math.log10(value).floor
|
|
142
|
+
|
|
143
|
+
(value / step).ceil * step
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
module Chart
|
|
5
|
+
class Preview < ApplicationViewComponentPreview
|
|
6
|
+
# Upstream's "Pie Chart" example, drawn here rather than by recharts.
|
|
7
|
+
def default
|
|
8
|
+
render_with_template
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Upstream's "Pie Chart - Donut", and the shape most of its own examples
|
|
12
|
+
# use: `inner_radius:` is a fraction of the radius.
|
|
13
|
+
def donut
|
|
14
|
+
render_with_template
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# What a pie is usually asked — how much of the whole — with the share in
|
|
18
|
+
# the tooltip instead of the count.
|
|
19
|
+
def percentages
|
|
20
|
+
render_with_template
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Upstream's "Bar Chart - Multiple", and the first shape here with an axis
|
|
24
|
+
# under it.
|
|
25
|
+
def bar
|
|
26
|
+
render_with_template
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Upstream's "Bar Chart - Stacked": one bar a month, split by device.
|
|
30
|
+
def bar_stacked
|
|
31
|
+
render_with_template
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Upstream's "Line Chart - Multiple".
|
|
35
|
+
def line
|
|
36
|
+
render_with_template
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Upstream's "Area Chart".
|
|
40
|
+
def area
|
|
41
|
+
render_with_template
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<%# Upstream's "Area Chart": the line with the space beneath it filled in. Flat
|
|
2
|
+
rather than the gradient the docs show — a gradient needs a `<defs>` with an
|
|
3
|
+
id per series, and an id unique in a page this gem cannot see is a cost the
|
|
4
|
+
fill does not repay. %>
|
|
5
|
+
<%
|
|
6
|
+
config = {
|
|
7
|
+
desktop: { label: "Desktop", color: "var(--chart-1)" },
|
|
8
|
+
mobile: { label: "Mobile", color: "var(--chart-2)" }
|
|
9
|
+
}
|
|
10
|
+
data = {
|
|
11
|
+
"January" => { desktop: 186, mobile: 80 },
|
|
12
|
+
"February" => { desktop: 305, mobile: 200 },
|
|
13
|
+
"March" => { desktop: 237, mobile: 120 },
|
|
14
|
+
"April" => { desktop: 73, mobile: 190 },
|
|
15
|
+
"May" => { desktop: 209, mobile: 130 },
|
|
16
|
+
"June" => { desktop: 214, mobile: 140 }
|
|
17
|
+
}
|
|
18
|
+
%>
|
|
19
|
+
<%= render(Shadcn::Card::Component.new(class: "max-w-xl")) do |card| %>
|
|
20
|
+
<% card.with_header do |header| %>
|
|
21
|
+
<% header.with_title { "Visitors" } %>
|
|
22
|
+
<% header.with_description { "January – June 2026" } %>
|
|
23
|
+
<% end %>
|
|
24
|
+
<% card.with_card_content do %>
|
|
25
|
+
<%= render(Shadcn::Chart::Component.new(config:)) do |chart| %>
|
|
26
|
+
<% chart.with_area(data:, label: "Visitors a month, by device") %>
|
|
27
|
+
<% chart.with_chart_tooltip %>
|
|
28
|
+
<% chart.with_chart_legend %>
|
|
29
|
+
<% end %>
|
|
30
|
+
<% end %>
|
|
31
|
+
<% end %>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<%# Upstream's "Bar Chart - Multiple": two series a month, side by side.
|
|
2
|
+
|
|
3
|
+
`data:` is a Hash of category to a Hash of series to number — which is one
|
|
4
|
+
`each_with_object` away from what `group(:month, :platform).sum(:visits)`
|
|
5
|
+
already returns. %>
|
|
6
|
+
<%
|
|
7
|
+
config = {
|
|
8
|
+
desktop: { label: "Desktop", color: "var(--chart-1)" },
|
|
9
|
+
mobile: { label: "Mobile", color: "var(--chart-2)" }
|
|
10
|
+
}
|
|
11
|
+
data = {
|
|
12
|
+
"January" => { desktop: 186, mobile: 80 },
|
|
13
|
+
"February" => { desktop: 305, mobile: 200 },
|
|
14
|
+
"March" => { desktop: 237, mobile: 120 },
|
|
15
|
+
"April" => { desktop: 73, mobile: 190 },
|
|
16
|
+
"May" => { desktop: 209, mobile: 130 },
|
|
17
|
+
"June" => { desktop: 214, mobile: 140 }
|
|
18
|
+
}
|
|
19
|
+
%>
|
|
20
|
+
<%= render(Shadcn::Card::Component.new(class: "max-w-xl")) do |card| %>
|
|
21
|
+
<% card.with_header do |header| %>
|
|
22
|
+
<% header.with_title { "Visitors" } %>
|
|
23
|
+
<% header.with_description { "January – June 2026" } %>
|
|
24
|
+
<% end %>
|
|
25
|
+
<% card.with_card_content do %>
|
|
26
|
+
<%= render(Shadcn::Chart::Component.new(config:)) do |chart| %>
|
|
27
|
+
<% chart.with_bar(data:, label: "Visitors a month, by device") %>
|
|
28
|
+
<% chart.with_chart_tooltip %>
|
|
29
|
+
<% chart.with_chart_legend %>
|
|
30
|
+
<% end %>
|
|
31
|
+
<% end %>
|
|
32
|
+
<% end %>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<%# Upstream's "Bar Chart - Stacked": the same months asking a different
|
|
2
|
+
question — not which series is larger, but what the month came to. The axis
|
|
3
|
+
grows to fit the whole stack rather than its tallest part. %>
|
|
4
|
+
<%
|
|
5
|
+
config = {
|
|
6
|
+
desktop: { label: "Desktop", color: "var(--chart-1)" },
|
|
7
|
+
mobile: { label: "Mobile", color: "var(--chart-2)" }
|
|
8
|
+
}
|
|
9
|
+
data = {
|
|
10
|
+
"January" => { desktop: 186, mobile: 80 },
|
|
11
|
+
"February" => { desktop: 305, mobile: 200 },
|
|
12
|
+
"March" => { desktop: 237, mobile: 120 },
|
|
13
|
+
"April" => { desktop: 73, mobile: 190 },
|
|
14
|
+
"May" => { desktop: 209, mobile: 130 },
|
|
15
|
+
"June" => { desktop: 214, mobile: 140 }
|
|
16
|
+
}
|
|
17
|
+
%>
|
|
18
|
+
<%= render(Shadcn::Card::Component.new(class: "max-w-xl")) do |card| %>
|
|
19
|
+
<% card.with_header do |header| %>
|
|
20
|
+
<% header.with_title { "Visitors" } %>
|
|
21
|
+
<% header.with_description { "Every device, month by month" } %>
|
|
22
|
+
<% end %>
|
|
23
|
+
<% card.with_card_content do %>
|
|
24
|
+
<%= render(Shadcn::Chart::Component.new(config:)) do |chart| %>
|
|
25
|
+
<% chart.with_bar(data:, stacked: true, label: "Visitors a month, by device") %>
|
|
26
|
+
<% chart.with_chart_tooltip %>
|
|
27
|
+
<% chart.with_chart_legend %>
|
|
28
|
+
<% end %>
|
|
29
|
+
<% end %>
|
|
30
|
+
<% end %>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<%# Upstream's "Pie Chart": five browsers, one slice each, coloured from the
|
|
2
|
+
`--chart-*` tokens the gem's themes already define. The data is a plain Hash
|
|
3
|
+
of key to number — which is what `group(:browser).sum(:visitors)` returns.
|
|
4
|
+
|
|
5
|
+
`hide_label:` because a pie's label and its item name are the same word, and
|
|
6
|
+
without it the tooltip reads "Chrome / Chrome 275". Upstream's own pie
|
|
7
|
+
examples pass it for the same reason. %>
|
|
8
|
+
<%
|
|
9
|
+
config = {
|
|
10
|
+
chrome: { label: "Chrome", color: "var(--chart-1)" },
|
|
11
|
+
safari: { label: "Safari", color: "var(--chart-2)" },
|
|
12
|
+
firefox: { label: "Firefox", color: "var(--chart-3)" },
|
|
13
|
+
edge: { label: "Edge", color: "var(--chart-4)" },
|
|
14
|
+
other: { label: "Other", color: "var(--chart-5)" }
|
|
15
|
+
}
|
|
16
|
+
%>
|
|
17
|
+
<%= render(Shadcn::Card::Component.new(class: "max-w-sm")) do |card| %>
|
|
18
|
+
<% card.with_header do |header| %>
|
|
19
|
+
<% header.with_title { "Visitors by browser" } %>
|
|
20
|
+
<% header.with_description { "January – June 2026" } %>
|
|
21
|
+
<% end %>
|
|
22
|
+
<% card.with_card_content do %>
|
|
23
|
+
<%= render(Shadcn::Chart::Component.new(config:)) do |chart| %>
|
|
24
|
+
<% chart.with_pie(data: { chrome: 275, safari: 200, firefox: 187, edge: 173, other: 90 },
|
|
25
|
+
label: "Visitors by browser") %>
|
|
26
|
+
<% chart.with_chart_tooltip(hide_label: true) %>
|
|
27
|
+
<% chart.with_chart_legend %>
|
|
28
|
+
<% end %>
|
|
29
|
+
<% end %>
|
|
30
|
+
<% end %>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<%# Upstream's "Pie Chart - Donut": the same slices with the middle taken out.
|
|
2
|
+
`inner_radius:` is a fraction, so the ring keeps its proportions at any size. %>
|
|
3
|
+
<%
|
|
4
|
+
config = {
|
|
5
|
+
chrome: { label: "Chrome", color: "var(--chart-1)" },
|
|
6
|
+
safari: { label: "Safari", color: "var(--chart-2)" },
|
|
7
|
+
firefox: { label: "Firefox", color: "var(--chart-3)" },
|
|
8
|
+
edge: { label: "Edge", color: "var(--chart-4)" }
|
|
9
|
+
}
|
|
10
|
+
%>
|
|
11
|
+
<%= render(Shadcn::Chart::Component.new(config:, class: "max-w-sm")) do |chart| %>
|
|
12
|
+
<% chart.with_pie(data: { chrome: 275, safari: 200, firefox: 187, edge: 173 },
|
|
13
|
+
inner_radius: 0.6, label: "Visitors by browser") %>
|
|
14
|
+
<% chart.with_chart_tooltip(hide_label: true) %>
|
|
15
|
+
<% chart.with_chart_legend %>
|
|
16
|
+
<% end %>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<%# Upstream's "Line Chart - Multiple": one polyline a series, with a dot at
|
|
2
|
+
each reading. Each dot carries its own number, so the tooltip is filled from
|
|
3
|
+
the DOM rather than from the series sent a second time as JSON. %>
|
|
4
|
+
<%
|
|
5
|
+
config = {
|
|
6
|
+
desktop: { label: "Desktop", color: "var(--chart-1)" },
|
|
7
|
+
mobile: { label: "Mobile", color: "var(--chart-2)" }
|
|
8
|
+
}
|
|
9
|
+
data = {
|
|
10
|
+
"January" => { desktop: 186, mobile: 80 },
|
|
11
|
+
"February" => { desktop: 305, mobile: 200 },
|
|
12
|
+
"March" => { desktop: 237, mobile: 120 },
|
|
13
|
+
"April" => { desktop: 73, mobile: 190 },
|
|
14
|
+
"May" => { desktop: 209, mobile: 130 },
|
|
15
|
+
"June" => { desktop: 214, mobile: 140 }
|
|
16
|
+
}
|
|
17
|
+
%>
|
|
18
|
+
<%= render(Shadcn::Card::Component.new(class: "max-w-xl")) do |card| %>
|
|
19
|
+
<% card.with_header do |header| %>
|
|
20
|
+
<% header.with_title { "Visitors" } %>
|
|
21
|
+
<% header.with_description { "January – June 2026" } %>
|
|
22
|
+
<% end %>
|
|
23
|
+
<% card.with_card_content do %>
|
|
24
|
+
<%= render(Shadcn::Chart::Component.new(config:)) do |chart| %>
|
|
25
|
+
<% chart.with_line(data:, label: "Visitors a month, by device") %>
|
|
26
|
+
<% chart.with_chart_tooltip %>
|
|
27
|
+
<% chart.with_chart_legend %>
|
|
28
|
+
<% end %>
|
|
29
|
+
<% end %>
|
|
30
|
+
<% end %>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<%# A pie's question is usually "how much of the whole", so `percentage: true`
|
|
2
|
+
puts the share in the tooltip where the count would be. Doing it in the data
|
|
3
|
+
rather than in the tooltip keeps that part 1:1 with upstream, which has one
|
|
4
|
+
cell for a value and no room for a second number. %>
|
|
5
|
+
<%
|
|
6
|
+
config = {
|
|
7
|
+
margin: { label: "Margine", color: "var(--chart-1)" },
|
|
8
|
+
costs: { label: "Costi", color: "var(--chart-2)" },
|
|
9
|
+
deductions: { label: "Ritenute", color: "var(--chart-3)" }
|
|
10
|
+
}
|
|
11
|
+
%>
|
|
12
|
+
<%= render(Shadcn::Chart::Component.new(config:, class: "max-w-sm")) do |chart| %>
|
|
13
|
+
<% chart.with_pie(data: { margin: 42_500, costs: 118_200, deductions: 9_800 },
|
|
14
|
+
percentage: true, inner_radius: 0.55, label: "Margine commessa") %>
|
|
15
|
+
<% chart.with_chart_tooltip(indicator: :line, hide_label: true) %>
|
|
16
|
+
<% chart.with_chart_legend %>
|
|
17
|
+
<% end %>
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
module Chart
|
|
5
|
+
module Table
|
|
6
|
+
# The chart's numbers, as a table only a screen reader sees.
|
|
7
|
+
#
|
|
8
|
+
# **Ours.** `chart.tsx` has nothing like it — upstream's answer is
|
|
9
|
+
# recharts' `accessibilityLayer`, which makes the chart arrow-navigable
|
|
10
|
+
# and announces a category at a time.
|
|
11
|
+
#
|
|
12
|
+
# A table is what this port reaches for instead, because a table is
|
|
13
|
+
# already a table: a screen reader enters it in table mode, moves by row
|
|
14
|
+
# and by column, and rereads the column header on demand. What it
|
|
15
|
+
# replaces is one long sentence — *"January: Desktop 186, Mobile 80;
|
|
16
|
+
# February: …"* — which cannot be navigated, cannot be reread in pieces,
|
|
17
|
+
# and by the time a year of months is in it is not a sentence anyone
|
|
18
|
+
# listens to.
|
|
19
|
+
#
|
|
20
|
+
# It is rendered by the shape rather than by the container, so that a
|
|
21
|
+
# shape rendered on its own is still complete, and so that the graphic
|
|
22
|
+
# and its table can never disagree about who carries the data — the same
|
|
23
|
+
# object emits both.
|
|
24
|
+
#
|
|
25
|
+
# Not `Shadcn::Table`, despite the name it shadows inside this module:
|
|
26
|
+
# that family's classes are all visual — borders, padding, hover — and
|
|
27
|
+
# every one of them would be dead CSS on an element no one sees.
|
|
28
|
+
class Component < ApplicationViewComponent
|
|
29
|
+
default_tag :table
|
|
30
|
+
slot_name :"chart-table"
|
|
31
|
+
|
|
32
|
+
style do
|
|
33
|
+
base { "sr-only" }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
attr_reader :caption, :columns, :rows
|
|
37
|
+
|
|
38
|
+
# `rows` is one array per row: the row's own header, then a cell per
|
|
39
|
+
# column.
|
|
40
|
+
def initialize(caption: nil, columns: [], rows: [], **attributes)
|
|
41
|
+
@caption = caption
|
|
42
|
+
@columns = columns
|
|
43
|
+
@rows = rows
|
|
44
|
+
super(**attributes)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def call
|
|
48
|
+
render_element(body: safe_join([ heading, head, body ].compact))
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
# Not an endless method with a trailing `if`: that reads as *define
|
|
54
|
+
# this method only when the condition holds*, and the condition is
|
|
55
|
+
# evaluated once, against the class.
|
|
56
|
+
def heading
|
|
57
|
+
tag.caption(caption) if caption.present?
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# The corner cell is a `<td>` and not an empty `<th>`: it heads
|
|
61
|
+
# nothing, and a header cell with no text is a header a screen reader
|
|
62
|
+
# still announces.
|
|
63
|
+
def head
|
|
64
|
+
tag.thead(tag.tr(safe_join([ tag.td, *columns.map { |column| tag.th(column, scope: "col") } ])))
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def body
|
|
68
|
+
tag.tbody(safe_join(rows.map do |header, *cells|
|
|
69
|
+
tag.tr(safe_join([ tag.th(header, scope: "row"), *cells.map { |cell| tag.td(cell) } ]))
|
|
70
|
+
end))
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|