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,633 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
import { readDirection } from "shadcn/direction"
|
|
3
|
+
|
|
4
|
+
// The calendar is rendered by the server — a month is a `<table>`, and Ruby
|
|
5
|
+
// builds it in `Calendar::Month`. This moves it: the nav, the two dropdowns and
|
|
6
|
+
// the grid's keyboard, plus the re-render each of those asks for.
|
|
7
|
+
//
|
|
8
|
+
// Which means the grid exists twice, and that is the one duplication this port
|
|
9
|
+
// introduces. Two rules keep it from drifting, and both are the reason the
|
|
10
|
+
// values below are as long as they are:
|
|
11
|
+
//
|
|
12
|
+
// 1. **No text of its own.** Month names, weekday names, the date format and
|
|
13
|
+
// every label template come from the server, so `I18n` stays the only thing
|
|
14
|
+
// that decides how a date reads. A `toLocaleDateString` here would answer to
|
|
15
|
+
// the *browser's* locale, and the month you navigated to would be spelled
|
|
16
|
+
// differently from the one you started on.
|
|
17
|
+
// 2. **No class of its own.** The modifier classes arrive as values. Tailwind
|
|
18
|
+
// scans source text and does not read this file, and `parity_spec` reads the
|
|
19
|
+
// Ruby, so a class written here would be both uncompiled and unchecked.
|
|
20
|
+
//
|
|
21
|
+
// What it does build is structure, and even that is cloned from what the server
|
|
22
|
+
// rendered rather than written out.
|
|
23
|
+
export default class extends Controller {
|
|
24
|
+
static targets = [ "grid", "weeks", "caption", "previous", "next", "monthSelect", "yearSelect", "inputs" ]
|
|
25
|
+
|
|
26
|
+
static values = {
|
|
27
|
+
month: String,
|
|
28
|
+
// The application's today, not the browser's. `Time.zone` and a laptop's
|
|
29
|
+
// clock disagree by a day for a few hours out of every twenty-four, and the
|
|
30
|
+
// server has already marked a cell `data-today` from its own — so a
|
|
31
|
+
// `new Date()` here would light one day and count presets from another.
|
|
32
|
+
today: String,
|
|
33
|
+
weekStartsOn: { type: Number, default: 1 },
|
|
34
|
+
fixedWeeks: Boolean,
|
|
35
|
+
showOutsideDays: { type: Boolean, default: true },
|
|
36
|
+
showWeekNumber: Boolean,
|
|
37
|
+
startMonth: String,
|
|
38
|
+
endMonth: String,
|
|
39
|
+
mode: { type: String, default: "single" },
|
|
40
|
+
required: Boolean,
|
|
41
|
+
inputNames: Array,
|
|
42
|
+
selected: Array,
|
|
43
|
+
disabled: Array,
|
|
44
|
+
dayNames: Array,
|
|
45
|
+
shortDayNames: Array,
|
|
46
|
+
monthNames: Array,
|
|
47
|
+
shortMonthNames: Array,
|
|
48
|
+
dateFormat: String,
|
|
49
|
+
monthFormat: String,
|
|
50
|
+
todayLabel: String,
|
|
51
|
+
selectedLabel: String,
|
|
52
|
+
weekNumberLabel: String,
|
|
53
|
+
todayClass: String,
|
|
54
|
+
outsideClass: String,
|
|
55
|
+
disabledClass: String,
|
|
56
|
+
hiddenClass: String,
|
|
57
|
+
rangeStartClass: String,
|
|
58
|
+
rangeMiddleClass: String,
|
|
59
|
+
rangeEndClass: String
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
connect() {
|
|
63
|
+
this.direction = readDirection(this.element)
|
|
64
|
+
// One nav, one keyboard, and as many grids as `number_of_months` asked
|
|
65
|
+
// for — so everything below indexes by grid, and the month a grid shows is
|
|
66
|
+
// the root's month plus its own offset.
|
|
67
|
+
// The prototypes for a re-render, taken from the server's own output: a
|
|
68
|
+
// week row, and a cell with every modifier class stripped back off it. The
|
|
69
|
+
// alternative is markup in a template literal here, which is the thing
|
|
70
|
+
// rule 2 above is about.
|
|
71
|
+
this.rowPrototype = this.weeksTargets[0].querySelector("tr").cloneNode(true)
|
|
72
|
+
this.cellPrototype = this.plainCell()
|
|
73
|
+
this.baseCellClass = this.cellPrototype.className
|
|
74
|
+
this.numberPrototype = this.showWeekNumberValue
|
|
75
|
+
? this.weeksTargets[0].querySelector("td:not([data-day])").cloneNode(true)
|
|
76
|
+
: null
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
previous(event) {
|
|
80
|
+
event.preventDefault()
|
|
81
|
+
this.goTo(this.shiftMonths(this.month, -1))
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
next(event) {
|
|
85
|
+
event.preventDefault()
|
|
86
|
+
this.goTo(this.shiftMonths(this.month, 1))
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
chooseMonth(event) {
|
|
90
|
+
const month = this.month
|
|
91
|
+
month.setMonth(Number(event.target.value) - 1)
|
|
92
|
+
this.goTo(month)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
chooseYear(event) {
|
|
96
|
+
const year = this.month
|
|
97
|
+
year.setFullYear(Number(event.target.value))
|
|
98
|
+
this.goTo(year)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// `react-day-picker`'s own three, read from `selection/useSingle.js`,
|
|
102
|
+
// `selection/useMulti.js` and `utils/addToRange.js` rather than invented:
|
|
103
|
+
// single toggles off unless it was asked to be `required`, multiple toggles
|
|
104
|
+
// each day, and a range grows from one end or starts again.
|
|
105
|
+
select(event) {
|
|
106
|
+
const cell = event.target.closest("td[data-day]")
|
|
107
|
+
if (!cell || cell.dataset.disabled) return
|
|
108
|
+
|
|
109
|
+
event.preventDefault()
|
|
110
|
+
const day = cell.dataset.day
|
|
111
|
+
this.selectedValue = this.selectionWith(day)
|
|
112
|
+
this.repaint()
|
|
113
|
+
this.writeInputs()
|
|
114
|
+
this.dispatch("select", { detail: { selected: this.selectedValue, day } })
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// A day counted from today, which is what a preset is: upstream's own example
|
|
118
|
+
// holds five of them in React state, and a Rails app has no state to hold
|
|
119
|
+
// them in — so the offset rides on the button and the controller does the
|
|
120
|
+
// arithmetic it is already doing.
|
|
121
|
+
preset({ params: { offset } }) {
|
|
122
|
+
const day = this.parse(this.todayValue)
|
|
123
|
+
day.setDate(day.getDate() + Number(offset || 0))
|
|
124
|
+
|
|
125
|
+
this.selectedValue = this.modeValue === "range" ? [ this.iso(day), this.iso(day) ] : [ this.iso(day) ]
|
|
126
|
+
this.goTo(day)
|
|
127
|
+
this.repaint()
|
|
128
|
+
this.writeInputs()
|
|
129
|
+
this.dispatch("select", { detail: { selected: this.selectedValue, day: this.iso(day) } })
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
selectionWith(day) {
|
|
133
|
+
if (this.modeValue === "multiple") return this.multipleWith(day)
|
|
134
|
+
if (this.modeValue === "range") return this.rangeWith(day)
|
|
135
|
+
|
|
136
|
+
return this.selectedValue.includes(day) && !this.requiredValue ? [] : [ day ]
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
multipleWith(day) {
|
|
140
|
+
return this.selectedValue.includes(day)
|
|
141
|
+
? this.selectedValue.filter((selected) => selected !== day)
|
|
142
|
+
: [ ...this.selectedValue, day ].sort()
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// `addToRange`, with its cases in its order: nothing yet, one end only, or a
|
|
146
|
+
// finished range that a third click starts over.
|
|
147
|
+
rangeWith(day) {
|
|
148
|
+
const [ from, to ] = this.selectedValue
|
|
149
|
+
|
|
150
|
+
if (!from) return [ day, day ]
|
|
151
|
+
|
|
152
|
+
if (from && !to) return day < from ? [ day, from ] : [ from, day ]
|
|
153
|
+
|
|
154
|
+
if (day === from && day === to) return this.requiredValue ? [ from, to ] : []
|
|
155
|
+
if (day === from || day === to) return [ day, day ]
|
|
156
|
+
if (day < from) return [ day, to ]
|
|
157
|
+
|
|
158
|
+
return [ from, day ]
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// What a Rails form receives, rewritten from the selection. `<input
|
|
162
|
+
// type=hidden>` is the one element this file builds rather than clones: it
|
|
163
|
+
// carries no class, so nothing here is invisible to Tailwind or to parity.
|
|
164
|
+
writeInputs() {
|
|
165
|
+
if (!this.hasInputsTarget || !this.inputNamesValue.length) return
|
|
166
|
+
|
|
167
|
+
const pairs = this.modeValue === "range"
|
|
168
|
+
? this.inputNamesValue.map((name, index) => [ name, this.selectedValue[index] || "" ])
|
|
169
|
+
: (this.selectedValue.length
|
|
170
|
+
? this.selectedValue.map((value) => [ this.inputNamesValue[0], value ])
|
|
171
|
+
: [ [ this.inputNamesValue[0], "" ] ])
|
|
172
|
+
|
|
173
|
+
this.inputsTarget.replaceChildren(...pairs.map(([ name, value ]) => {
|
|
174
|
+
const input = document.createElement("input")
|
|
175
|
+
input.type = "hidden"
|
|
176
|
+
input.autocomplete = "off"
|
|
177
|
+
input.name = name
|
|
178
|
+
input.value = value
|
|
179
|
+
return input
|
|
180
|
+
}))
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// The grid pattern: one day is tabbable and the arrows reach the rest. Radix
|
|
184
|
+
// is not the reference here — `react-day-picker`'s own `DayPicker.js:176-190`
|
|
185
|
+
// is, and it moves by day, by week, to the ends of the week, and by month or
|
|
186
|
+
// year on Page keys.
|
|
187
|
+
keydown(event) {
|
|
188
|
+
const from = this.dayOf(event.target)
|
|
189
|
+
if (!from) return
|
|
190
|
+
|
|
191
|
+
const back = this.direction === "rtl" ? 1 : -1
|
|
192
|
+
const moves = {
|
|
193
|
+
ArrowLeft: [ "day", back ],
|
|
194
|
+
ArrowRight: [ "day", -back ],
|
|
195
|
+
ArrowUp: [ "week", -1 ],
|
|
196
|
+
ArrowDown: [ "week", 1 ],
|
|
197
|
+
Home: [ "weekStart", 0 ],
|
|
198
|
+
End: [ "weekEnd", 0 ],
|
|
199
|
+
PageUp: [ event.shiftKey ? "year" : "month", -1 ],
|
|
200
|
+
PageDown: [ event.shiftKey ? "year" : "month", 1 ]
|
|
201
|
+
}
|
|
202
|
+
const move = moves[event.key]
|
|
203
|
+
if (!move) return
|
|
204
|
+
|
|
205
|
+
event.preventDefault()
|
|
206
|
+
this.focusDay(this.step(from, ...move))
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// A day that is focused becomes the tabbable one, so tabbing away and back
|
|
210
|
+
// returns to where the reader was rather than to the top of the month.
|
|
211
|
+
focused(event) {
|
|
212
|
+
const day = this.dayOf(event.target)
|
|
213
|
+
if (day) this.markFocusTarget(event.target)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
get month() {
|
|
217
|
+
return this.parse(this.monthValue)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// --- moving ---------------------------------------------------------------
|
|
221
|
+
|
|
222
|
+
step(from, unit, by) {
|
|
223
|
+
const to = new Date(from)
|
|
224
|
+
|
|
225
|
+
if (unit === "day") to.setDate(to.getDate() + by)
|
|
226
|
+
if (unit === "week") to.setDate(to.getDate() + by * 7)
|
|
227
|
+
if (unit === "month") to.setMonth(to.getMonth() + by)
|
|
228
|
+
if (unit === "year") to.setFullYear(to.getFullYear() + by)
|
|
229
|
+
if (unit === "weekStart") to.setDate(to.getDate() - this.offsetInWeek(to))
|
|
230
|
+
if (unit === "weekEnd") to.setDate(to.getDate() + (6 - this.offsetInWeek(to)))
|
|
231
|
+
|
|
232
|
+
return this.clampToBounds(to)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Focus follows the day even when the day is in another month — which is what
|
|
236
|
+
// makes ArrowRight on the last of the month work at all. The month changes
|
|
237
|
+
// under the focus, and the focus is restored once the new grid is there.
|
|
238
|
+
focusDay(date) {
|
|
239
|
+
const iso = this.iso(date)
|
|
240
|
+
if (!this.isDisplayed(date)) this.goTo(date)
|
|
241
|
+
|
|
242
|
+
const button = this.buttonFor(iso)
|
|
243
|
+
if (!button) return
|
|
244
|
+
|
|
245
|
+
this.markFocusTarget(button)
|
|
246
|
+
button.focus()
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
goTo(date) {
|
|
250
|
+
const target = this.clampToBounds(date)
|
|
251
|
+
const first = new Date(target.getFullYear(), target.getMonth(), 1)
|
|
252
|
+
if (this.iso(first) === this.monthValue) return
|
|
253
|
+
|
|
254
|
+
this.monthValue = this.iso(first)
|
|
255
|
+
this.render()
|
|
256
|
+
this.dispatch("month", { detail: { month: this.monthValue } })
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Compared as ISO text rather than as `Date`s: the bounds arrive as strings,
|
|
260
|
+
// and `new Date("2026-01-01")` is parsed as UTC where `new Date(y, m, d)` is
|
|
261
|
+
// local — a comparison between the two is off by a day for half the world.
|
|
262
|
+
clampToBounds(date) {
|
|
263
|
+
const iso = this.iso(date)
|
|
264
|
+
|
|
265
|
+
if (this.startMonthValue && iso < this.startMonthValue) return this.parse(this.startMonthValue)
|
|
266
|
+
if (this.endMonthValue && iso > this.endMonthValue) return this.parse(this.endMonthValue)
|
|
267
|
+
|
|
268
|
+
return date
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// --- rendering ------------------------------------------------------------
|
|
272
|
+
|
|
273
|
+
render() {
|
|
274
|
+
this.weeksTargets.forEach((weeks, index) => {
|
|
275
|
+
const month = this.monthAt(index)
|
|
276
|
+
|
|
277
|
+
weeks.replaceChildren(...this.weeks(month).map((week) => this.row(week, month)))
|
|
278
|
+
this.captionTargets[index].textContent = this.formatMonth(month)
|
|
279
|
+
// Only where this port put one: with a caller's own `aria-labelledby` the
|
|
280
|
+
// grid is named by that and by the caption's id, and neither changes here.
|
|
281
|
+
if (this.gridTargets[index].hasAttribute("aria-label")) {
|
|
282
|
+
this.gridTargets[index].setAttribute("aria-label", this.formatMonth(month))
|
|
283
|
+
}
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
this.updateNav()
|
|
287
|
+
this.updateDropdowns()
|
|
288
|
+
this.markInitialFocusTarget()
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
monthAt(index) {
|
|
292
|
+
return new Date(this.month.getFullYear(), this.month.getMonth() + index, 1)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// The Ruby rule, repeated: the selected day if it is in this grid, otherwise
|
|
296
|
+
// today, otherwise the first of the month — and never a disabled one. Without
|
|
297
|
+
// it a navigated month has no tabbable day at all and the grid leaves the tab
|
|
298
|
+
// order, which is how this first worked.
|
|
299
|
+
// One tab stop per grid, which is what the server renders too: each grid is a
|
|
300
|
+
// `role="grid"` of its own.
|
|
301
|
+
markInitialFocusTarget() {
|
|
302
|
+
this.weeksTargets.forEach((weeks, index) => {
|
|
303
|
+
const month = this.monthAt(index)
|
|
304
|
+
const candidates = [ ...this.selectedValue, this.todayValue, this.iso(month) ]
|
|
305
|
+
|
|
306
|
+
for (const iso of candidates) {
|
|
307
|
+
const button = weeks.querySelector(`td[data-day="${iso}"]:not([data-outside]) button`)
|
|
308
|
+
if (button && !button.disabled) {
|
|
309
|
+
for (const other of weeks.querySelectorAll("button")) other.tabIndex = -1
|
|
310
|
+
button.tabIndex = 0
|
|
311
|
+
return
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
})
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// The same grid `Calendar::Month#weeks` builds, and it has to stay the same:
|
|
318
|
+
// the first day of the week the month starts in, through the last day of the
|
|
319
|
+
// week it ends in, six rows when a fixed height was asked for.
|
|
320
|
+
weeks(month) {
|
|
321
|
+
const first = new Date(month.getFullYear(), month.getMonth(), 1)
|
|
322
|
+
const start = new Date(first)
|
|
323
|
+
start.setDate(start.getDate() - this.offsetInWeek(first))
|
|
324
|
+
|
|
325
|
+
const last = new Date(month.getFullYear(), month.getMonth() + 1, 0)
|
|
326
|
+
const span = Math.round((last - start) / 86400000) + 1
|
|
327
|
+
const rows = this.fixedWeeksValue ? 6 : Math.ceil(span / 7)
|
|
328
|
+
|
|
329
|
+
return Array.from({ length: rows }, (_, row) =>
|
|
330
|
+
Array.from({ length: 7 }, (_, column) => {
|
|
331
|
+
const day = new Date(start)
|
|
332
|
+
day.setDate(start.getDate() + row * 7 + column)
|
|
333
|
+
return day
|
|
334
|
+
}))
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
row(week, month) {
|
|
338
|
+
const row = this.rowPrototype.cloneNode(false)
|
|
339
|
+
|
|
340
|
+
if (this.numberPrototype) row.appendChild(this.weekNumber(week))
|
|
341
|
+
for (const day of week) row.appendChild(this.cell(day, month))
|
|
342
|
+
|
|
343
|
+
return row
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
weekNumber(week) {
|
|
347
|
+
const cell = this.numberPrototype.cloneNode(true)
|
|
348
|
+
const number = this.isoWeek(week[0])
|
|
349
|
+
|
|
350
|
+
cell.querySelector("div").textContent = number
|
|
351
|
+
cell.setAttribute("aria-label", this.weekNumberLabelValue.replace("%{number}", number))
|
|
352
|
+
|
|
353
|
+
return cell
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
cell(day, month) {
|
|
357
|
+
const cell = this.cellPrototype.cloneNode(true)
|
|
358
|
+
this.applyState(cell, day, month)
|
|
359
|
+
|
|
360
|
+
return cell
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Selecting repaints rather than rebuilds: replacing the rows would take the
|
|
364
|
+
// button the pointer or the keyboard is on out of the document, and the focus
|
|
365
|
+
// with it.
|
|
366
|
+
repaint() {
|
|
367
|
+
this.weeksTargets.forEach((weeks, index) => {
|
|
368
|
+
for (const cell of weeks.querySelectorAll("td[data-day]")) {
|
|
369
|
+
this.applyState(cell, this.parse(cell.dataset.day), this.monthAt(index))
|
|
370
|
+
}
|
|
371
|
+
})
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
applyState(cell, day, month) {
|
|
375
|
+
const outside = day.getMonth() !== month.getMonth()
|
|
376
|
+
const hidden = outside && !this.showOutsideDaysValue
|
|
377
|
+
const selected = this.isSelected(day)
|
|
378
|
+
const disabled = this.isDisabled(day)
|
|
379
|
+
|
|
380
|
+
cell.className = [
|
|
381
|
+
this.baseCellClass,
|
|
382
|
+
this.isToday(day) && this.todayClassValue,
|
|
383
|
+
outside && this.outsideClassValue,
|
|
384
|
+
disabled && this.disabledClassValue,
|
|
385
|
+
hidden && this.hiddenClassValue,
|
|
386
|
+
this.isRangeStart(day) && this.rangeStartClassValue,
|
|
387
|
+
this.isRangeMiddle(day) && this.rangeMiddleClassValue,
|
|
388
|
+
this.isRangeEnd(day) && this.rangeEndClassValue
|
|
389
|
+
].filter(Boolean).join(" ")
|
|
390
|
+
|
|
391
|
+
this.set(cell, "data-day", this.iso(day))
|
|
392
|
+
this.set(cell, "data-month", outside ? this.iso(day).slice(0, 7) : null)
|
|
393
|
+
this.set(cell, "data-outside", outside ? "true" : null)
|
|
394
|
+
this.set(cell, "data-today", this.isToday(day) ? "true" : null)
|
|
395
|
+
this.set(cell, "data-selected", selected ? "true" : null)
|
|
396
|
+
this.set(cell, "data-disabled", disabled ? "true" : null)
|
|
397
|
+
this.set(cell, "data-hidden", hidden ? "true" : null)
|
|
398
|
+
this.set(cell, "aria-selected", selected ? "true" : null)
|
|
399
|
+
|
|
400
|
+
const button = cell.querySelector("button")
|
|
401
|
+
if (hidden) button?.remove()
|
|
402
|
+
else if (button) this.dayButton(button, day, { selected, disabled })
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
dayButton(button, day, { selected, disabled }) {
|
|
406
|
+
button.textContent = String(day.getDate())
|
|
407
|
+
button.disabled = disabled
|
|
408
|
+
if (button.tabIndex !== 0) button.tabIndex = -1
|
|
409
|
+
this.set(button, "data-day", this.format(day, this.dateFormatValue))
|
|
410
|
+
this.set(button, "aria-label", this.dayLabel(day, selected))
|
|
411
|
+
this.set(button, "data-selected-single", this.modeValue === "single" && selected ? "true" : null)
|
|
412
|
+
this.set(button, "data-range-start", this.isRangeStart(day) ? "true" : null)
|
|
413
|
+
this.set(button, "data-range-middle", this.isRangeMiddle(day) ? "true" : null)
|
|
414
|
+
this.set(button, "data-range-end", this.isRangeEnd(day) ? "true" : null)
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Exactly one button in the grid is tabbable: the selected day if it is in
|
|
418
|
+
// this month, otherwise today, otherwise the first of the month.
|
|
419
|
+
markFocusTarget(button) {
|
|
420
|
+
for (const other of this.buttons) other.tabIndex = -1
|
|
421
|
+
button.tabIndex = 0
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// A day shown in two grids at once — the last of August is also an outside
|
|
425
|
+
// day in September's — belongs to the grid it is not outside of.
|
|
426
|
+
buttonFor(iso) {
|
|
427
|
+
return this.element.querySelector(`td[data-day="${iso}"]:not([data-outside]) button`) ||
|
|
428
|
+
this.element.querySelector(`td[data-day="${iso}"] button`)
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
updateNav() {
|
|
432
|
+
const back = this.shiftMonths(this.month, -1)
|
|
433
|
+
const forward = this.shiftMonths(this.month, 1)
|
|
434
|
+
|
|
435
|
+
this.mark(this.previousTarget, this.outOfBounds(back))
|
|
436
|
+
this.mark(this.nextTarget, this.outOfBounds(forward))
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
mark(button, unavailable) {
|
|
440
|
+
this.set(button, "aria-disabled", unavailable ? "true" : null)
|
|
441
|
+
this.set(button, "tabindex", unavailable ? "-1" : null)
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// The dropdowns are the caption, so they follow it rather than lead it — a
|
|
445
|
+
// month reached with the arrow keys has to leave them showing where it went.
|
|
446
|
+
updateDropdowns() {
|
|
447
|
+
if (this.hasMonthSelectTarget) {
|
|
448
|
+
this.monthSelectTarget.value = String(this.month.getMonth() + 1)
|
|
449
|
+
// The *short* name: the options the server rendered are the abbreviated
|
|
450
|
+
// ones, and what is shown is the selected option's own label. Writing the
|
|
451
|
+
// full name here made the two renderers disagree in the one place a
|
|
452
|
+
// person is looking.
|
|
453
|
+
this.label(this.monthSelectTarget, this.shortMonthNamesValue[this.month.getMonth()])
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
if (this.hasYearSelectTarget) {
|
|
457
|
+
this.yearSelectTarget.value = String(this.month.getFullYear())
|
|
458
|
+
this.label(this.yearSelectTarget, String(this.month.getFullYear()))
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// What is read is the `aria-hidden` span under the invisible select, so the
|
|
463
|
+
// two have to be set together or the calendar says one month and shows
|
|
464
|
+
// another.
|
|
465
|
+
label(select, text) {
|
|
466
|
+
const shown = select.parentElement.querySelector("[aria-hidden=true]")
|
|
467
|
+
if (!shown?.firstChild) return
|
|
468
|
+
|
|
469
|
+
shown.firstChild.textContent = text
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// --- what a day is --------------------------------------------------------
|
|
473
|
+
|
|
474
|
+
isToday(day) {
|
|
475
|
+
return this.iso(day) === this.todayValue
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
isSelected(day) {
|
|
479
|
+
if (this.range) return this.iso(day) >= this.range.from && this.iso(day) <= this.range.to
|
|
480
|
+
|
|
481
|
+
return this.selectedValue.includes(this.iso(day))
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
isRangeStart(day) { return Boolean(this.range) && this.iso(day) === this.range.from }
|
|
485
|
+
isRangeEnd(day) { return Boolean(this.range) && this.iso(day) === this.range.to }
|
|
486
|
+
|
|
487
|
+
isRangeMiddle(day) {
|
|
488
|
+
return this.isSelected(day) && Boolean(this.range) && !this.isRangeStart(day) && !this.isRangeEnd(day)
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
get range() {
|
|
492
|
+
return this.modeValue === "range" && this.selectedValue.length === 2
|
|
493
|
+
? { from: this.selectedValue[0], to: this.selectedValue[1] }
|
|
494
|
+
: null
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// A `disabled:` that is a Date or a Range crosses to the browser as itself. A
|
|
498
|
+
// callable cannot, and does not: it decided the month the server rendered and
|
|
499
|
+
// says nothing about the ones reached from here — see features/calendar.md.
|
|
500
|
+
isDisabled(day) {
|
|
501
|
+
const iso = this.iso(day)
|
|
502
|
+
if (this.outOfBounds(day)) return true
|
|
503
|
+
|
|
504
|
+
return this.disabledValue.some((matcher) =>
|
|
505
|
+
typeof matcher === "string" ? matcher === iso : iso >= matcher.from && iso <= matcher.to)
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// At the granularity the bounds are about: `start_month` and `end_month` name
|
|
509
|
+
// months, so a day in the same month as the bound is inside it.
|
|
510
|
+
outOfBounds(date) {
|
|
511
|
+
const month = this.iso(date).slice(0, 7)
|
|
512
|
+
|
|
513
|
+
return Boolean(this.startMonthValue && month < this.startMonthValue.slice(0, 7)) ||
|
|
514
|
+
Boolean(this.endMonthValue && month > this.endMonthValue.slice(0, 7))
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// True of any month on screen, not only the first: with two months shown,
|
|
518
|
+
// stepping off the end of the first lands in the second and nothing moves.
|
|
519
|
+
isDisplayed(date) {
|
|
520
|
+
return this.weeksTargets.some((_, index) => {
|
|
521
|
+
const month = this.monthAt(index)
|
|
522
|
+
return date.getFullYear() === month.getFullYear() && date.getMonth() === month.getMonth()
|
|
523
|
+
})
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// --- reading the DOM ------------------------------------------------------
|
|
527
|
+
|
|
528
|
+
get buttons() {
|
|
529
|
+
return this.weeksTargets.flatMap((weeks) => [ ...weeks.querySelectorAll("button") ])
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
dayOf(element) {
|
|
533
|
+
const cell = element.closest("td[data-day]")
|
|
534
|
+
|
|
535
|
+
return cell ? this.parse(cell.dataset.day) : null
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// A cell with the state stripped off it, which is what every later cell is
|
|
539
|
+
// built from. Taken from a plain day rather than assembled: an outside or a
|
|
540
|
+
// selected one would carry classes this then has to guess at removing.
|
|
541
|
+
plainCell() {
|
|
542
|
+
const plain = this.weeksTargets[0].querySelector(
|
|
543
|
+
"td[data-day]:not([data-today]):not([data-outside]):not([data-selected]):not([data-disabled])"
|
|
544
|
+
)
|
|
545
|
+
const cell = (plain || this.weeksTargets[0].querySelector("td[data-day]")).cloneNode(true)
|
|
546
|
+
|
|
547
|
+
for (const state of [ "day", "month", "outside", "today", "selected", "disabled", "hidden" ]) {
|
|
548
|
+
delete cell.dataset[state]
|
|
549
|
+
}
|
|
550
|
+
cell.removeAttribute("aria-selected")
|
|
551
|
+
// The cell this was taken from may have been the tabbable one, and every
|
|
552
|
+
// later cell is a copy of it — one tab stop would have become thirty-five.
|
|
553
|
+
const button = cell.querySelector("button")
|
|
554
|
+
if (button) button.tabIndex = -1
|
|
555
|
+
|
|
556
|
+
return cell
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
set(element, name, value) {
|
|
560
|
+
if (value === null || value === undefined || value === false) element.removeAttribute(name)
|
|
561
|
+
else element.setAttribute(name, value)
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// --- dates ----------------------------------------------------------------
|
|
565
|
+
|
|
566
|
+
parse(iso) {
|
|
567
|
+
const [ year, month, day ] = iso.split("-").map(Number)
|
|
568
|
+
|
|
569
|
+
return new Date(year, month - 1, day)
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
shiftMonths(date, by) {
|
|
573
|
+
return new Date(date.getFullYear(), date.getMonth() + by, 1)
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
offsetInWeek(date) {
|
|
577
|
+
return (date.getDay() - this.weekStartsOnValue + 7) % 7
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
iso(date) {
|
|
581
|
+
return [
|
|
582
|
+
date.getFullYear(),
|
|
583
|
+
String(date.getMonth() + 1).padStart(2, "0"),
|
|
584
|
+
String(date.getDate()).padStart(2, "0")
|
|
585
|
+
].join("-")
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// ISO-8601, matching `Date#cweek` on the Ruby side: the week holding the
|
|
589
|
+
// Thursday of this week is the week of its year.
|
|
590
|
+
isoWeek(date) {
|
|
591
|
+
const thursday = new Date(date)
|
|
592
|
+
thursday.setDate(thursday.getDate() + 3 - ((date.getDay() + 6) % 7))
|
|
593
|
+
const first = new Date(thursday.getFullYear(), 0, 4)
|
|
594
|
+
|
|
595
|
+
return 1 + Math.round(((thursday - first) / 86400000 - 3 + ((first.getDay() + 6) % 7)) / 7)
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
dayLabel(day, selected) {
|
|
599
|
+
let label = this.format(day, this.dateFormatValue)
|
|
600
|
+
if (this.isToday(day)) label = this.todayLabelValue.replace("%{date}", label)
|
|
601
|
+
if (selected) label = this.selectedLabelValue.replace("%{date}", label)
|
|
602
|
+
|
|
603
|
+
return label
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
formatMonth(date) {
|
|
607
|
+
return this.format(date, this.monthFormatValue)
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// Enough `strftime` to read the formats Rails' own locales are written in,
|
|
611
|
+
// and no more. The pattern comes from `I18n` with the rest of the text, so a
|
|
612
|
+
// host that has translated `date.formats.long` gets its own order and its own
|
|
613
|
+
// punctuation here rather than this file's idea of them.
|
|
614
|
+
format(date, pattern) {
|
|
615
|
+
const day = date.getDate()
|
|
616
|
+
const month = date.getMonth() + 1
|
|
617
|
+
const tokens = {
|
|
618
|
+
"%A": this.dayNamesValue[date.getDay()],
|
|
619
|
+
"%a": this.shortDayNamesValue[date.getDay()],
|
|
620
|
+
"%B": this.monthNamesValue[date.getMonth()],
|
|
621
|
+
"%b": this.shortMonthNamesValue[date.getMonth()],
|
|
622
|
+
"%d": String(day).padStart(2, "0"),
|
|
623
|
+
"%-d": String(day),
|
|
624
|
+
"%e": String(day).padStart(2, " "),
|
|
625
|
+
"%m": String(month).padStart(2, "0"),
|
|
626
|
+
"%-m": String(month),
|
|
627
|
+
"%Y": String(date.getFullYear()),
|
|
628
|
+
"%y": String(date.getFullYear()).slice(-2)
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
return pattern.replace(/%-?[A-Za-z]/g, (token) => (token in tokens ? tokens[token] : token))
|
|
632
|
+
}
|
|
633
|
+
}
|