poetry-ui 0.0.2
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/.gitattributes +6 -0
- data/CHANGELOG.md +3 -0
- data/LICENSE.txt +21 -0
- data/README.md +87 -0
- data/THIRD_PARTY_NOTICES.md +201 -0
- data/app/components/poetry/ui/accordion/component.html.erb +3 -0
- data/app/components/poetry/ui/accordion/component.rb +197 -0
- data/app/components/poetry/ui/accordion/preview.rb +39 -0
- data/app/components/poetry/ui/accordion/style.rb +37 -0
- data/app/components/poetry/ui/alert/component.html.erb +6 -0
- data/app/components/poetry/ui/alert/component.rb +61 -0
- data/app/components/poetry/ui/alert/preview.rb +35 -0
- data/app/components/poetry/ui/alert/style.rb +24 -0
- data/app/components/poetry/ui/alert_dialog/component.html.erb +17 -0
- data/app/components/poetry/ui/alert_dialog/component.rb +208 -0
- data/app/components/poetry/ui/alert_dialog/preview.rb +48 -0
- data/app/components/poetry/ui/alert_dialog/style.rb +46 -0
- data/app/components/poetry/ui/aspect_ratio/component.rb +63 -0
- data/app/components/poetry/ui/aspect_ratio/preview.rb +29 -0
- data/app/components/poetry/ui/aspect_ratio/style.rb +13 -0
- data/app/components/poetry/ui/attachment/component.html.erb +13 -0
- data/app/components/poetry/ui/attachment/component.rb +139 -0
- data/app/components/poetry/ui/attachment/preview.rb +83 -0
- data/app/components/poetry/ui/attachment/style.rb +59 -0
- data/app/components/poetry/ui/autocomplete/component.html.erb +16 -0
- data/app/components/poetry/ui/autocomplete/component.rb +193 -0
- data/app/components/poetry/ui/autocomplete/preview.rb +39 -0
- data/app/components/poetry/ui/autocomplete/style.rb +18 -0
- data/app/components/poetry/ui/avatar/component.rb +103 -0
- data/app/components/poetry/ui/avatar/group_preview.html.erb +11 -0
- data/app/components/poetry/ui/avatar/preview.rb +50 -0
- data/app/components/poetry/ui/avatar/style.rb +34 -0
- data/app/components/poetry/ui/badge/component.rb +73 -0
- data/app/components/poetry/ui/badge/preview.rb +54 -0
- data/app/components/poetry/ui/badge/style.rb +35 -0
- data/app/components/poetry/ui/breadcrumb/component.html.erb +32 -0
- data/app/components/poetry/ui/breadcrumb/component.rb +102 -0
- data/app/components/poetry/ui/breadcrumb/dropdown_crumb_preview.html.erb +21 -0
- data/app/components/poetry/ui/breadcrumb/preview.rb +45 -0
- data/app/components/poetry/ui/breadcrumb/style.rb +25 -0
- data/app/components/poetry/ui/bubble/component.html.erb +4 -0
- data/app/components/poetry/ui/bubble/component.rb +106 -0
- data/app/components/poetry/ui/bubble/preview.rb +62 -0
- data/app/components/poetry/ui/bubble/style.rb +47 -0
- data/app/components/poetry/ui/button/component.html.erb +14 -0
- data/app/components/poetry/ui/button/component.rb +168 -0
- data/app/components/poetry/ui/button/preview.rb +94 -0
- data/app/components/poetry/ui/button/style.rb +41 -0
- data/app/components/poetry/ui/button_group/component.rb +70 -0
- data/app/components/poetry/ui/button_group/popup_triggers_preview.html.erb +21 -0
- data/app/components/poetry/ui/button_group/preview.rb +46 -0
- data/app/components/poetry/ui/button_group/style.rb +27 -0
- data/app/components/poetry/ui/button_group/text_prefix_preview.html.erb +7 -0
- data/app/components/poetry/ui/calendar/component.html.erb +72 -0
- data/app/components/poetry/ui/calendar/component.rb +366 -0
- data/app/components/poetry/ui/calendar/preview.rb +55 -0
- data/app/components/poetry/ui/calendar/style.rb +55 -0
- data/app/components/poetry/ui/card/component.html.erb +11 -0
- data/app/components/poetry/ui/card/component.rb +90 -0
- data/app/components/poetry/ui/card/preview.rb +29 -0
- data/app/components/poetry/ui/card/style.rb +23 -0
- data/app/components/poetry/ui/carousel/component.html.erb +17 -0
- data/app/components/poetry/ui/carousel/component.rb +179 -0
- data/app/components/poetry/ui/carousel/preview.rb +67 -0
- data/app/components/poetry/ui/carousel/style.rb +39 -0
- data/app/components/poetry/ui/checkbox/component.html.erb +22 -0
- data/app/components/poetry/ui/checkbox/component.rb +179 -0
- data/app/components/poetry/ui/checkbox/preview.rb +68 -0
- data/app/components/poetry/ui/checkbox/style.rb +28 -0
- data/app/components/poetry/ui/clipboard_text/component.html.erb +15 -0
- data/app/components/poetry/ui/clipboard_text/component.rb +154 -0
- data/app/components/poetry/ui/clipboard_text/preview.rb +24 -0
- data/app/components/poetry/ui/clipboard_text/style.rb +23 -0
- data/app/components/poetry/ui/code_block/component.html.erb +13 -0
- data/app/components/poetry/ui/code_block/component.rb +141 -0
- data/app/components/poetry/ui/code_block/preview.rb +40 -0
- data/app/components/poetry/ui/code_block/style.rb +38 -0
- data/app/components/poetry/ui/collapsible/component.html.erb +4 -0
- data/app/components/poetry/ui/collapsible/component.rb +134 -0
- data/app/components/poetry/ui/collapsible/preview.rb +24 -0
- data/app/components/poetry/ui/combobox/component.html.erb +49 -0
- data/app/components/poetry/ui/combobox/component.rb +1086 -0
- data/app/components/poetry/ui/combobox/preview.rb +164 -0
- data/app/components/poetry/ui/combobox/style.rb +148 -0
- data/app/components/poetry/ui/command/component.html.erb +20 -0
- data/app/components/poetry/ui/command/component.rb +526 -0
- data/app/components/poetry/ui/command/dialog_component.html.erb +14 -0
- data/app/components/poetry/ui/command/dialog_component.rb +206 -0
- data/app/components/poetry/ui/command/dialog_preview.rb +39 -0
- data/app/components/poetry/ui/command/preview.rb +121 -0
- data/app/components/poetry/ui/command/style.rb +78 -0
- data/app/components/poetry/ui/composable_trigger.rb +63 -0
- data/app/components/poetry/ui/context_menu/component.html.erb +6 -0
- data/app/components/poetry/ui/context_menu/component.rb +358 -0
- data/app/components/poetry/ui/context_menu/preview.rb +111 -0
- data/app/components/poetry/ui/context_menu/style.rb +59 -0
- data/app/components/poetry/ui/data_table/component.html.erb +78 -0
- data/app/components/poetry/ui/data_table/component.rb +277 -0
- data/app/components/poetry/ui/data_table/preview.rb +68 -0
- data/app/components/poetry/ui/data_table/state.rb +115 -0
- data/app/components/poetry/ui/data_table/style.rb +42 -0
- data/app/components/poetry/ui/date_field/component.html.erb +7 -0
- data/app/components/poetry/ui/date_field/component.rb +209 -0
- data/app/components/poetry/ui/date_field/preview.rb +42 -0
- data/app/components/poetry/ui/date_field/style.rb +35 -0
- data/app/components/poetry/ui/date_picker/component.html.erb +23 -0
- data/app/components/poetry/ui/date_picker/component.rb +242 -0
- data/app/components/poetry/ui/date_picker/preview.rb +31 -0
- data/app/components/poetry/ui/date_picker/style.rb +25 -0
- data/app/components/poetry/ui/deferred/component.html.erb +16 -0
- data/app/components/poetry/ui/deferred/component.rb +90 -0
- data/app/components/poetry/ui/deferred/preview.rb +24 -0
- data/app/components/poetry/ui/dialog/component.html.erb +21 -0
- data/app/components/poetry/ui/dialog/component.rb +194 -0
- data/app/components/poetry/ui/dialog/preview.rb +33 -0
- data/app/components/poetry/ui/dialog/style.rb +30 -0
- data/app/components/poetry/ui/drawer/component.html.erb +16 -0
- data/app/components/poetry/ui/drawer/component.rb +207 -0
- data/app/components/poetry/ui/drawer/preview.rb +69 -0
- data/app/components/poetry/ui/drawer/style.rb +105 -0
- data/app/components/poetry/ui/dropdown_menu/component.html.erb +6 -0
- data/app/components/poetry/ui/dropdown_menu/component.rb +350 -0
- data/app/components/poetry/ui/dropdown_menu/preview.rb +101 -0
- data/app/components/poetry/ui/dropdown_menu/style.rb +60 -0
- data/app/components/poetry/ui/empty/component.html.erb +10 -0
- data/app/components/poetry/ui/empty/component.rb +79 -0
- data/app/components/poetry/ui/empty/preview.rb +27 -0
- data/app/components/poetry/ui/empty/style.rb +27 -0
- data/app/components/poetry/ui/family_identity.rb +52 -0
- data/app/components/poetry/ui/field/component.html.erb +19 -0
- data/app/components/poetry/ui/field/component.rb +183 -0
- data/app/components/poetry/ui/field/preview.html.erb +30 -0
- data/app/components/poetry/ui/field/preview.rb +72 -0
- data/app/components/poetry/ui/field/style.rb +68 -0
- data/app/components/poetry/ui/field_group/component.rb +61 -0
- data/app/components/poetry/ui/field_group/preview.html.erb +30 -0
- data/app/components/poetry/ui/field_group/preview.rb +20 -0
- data/app/components/poetry/ui/field_group/style.rb +25 -0
- data/app/components/poetry/ui/field_separator/component.rb +70 -0
- data/app/components/poetry/ui/field_separator/preview.rb +18 -0
- data/app/components/poetry/ui/field_separator/style.rb +20 -0
- data/app/components/poetry/ui/fieldset/component.rb +90 -0
- data/app/components/poetry/ui/fieldset/preview.html.erb +22 -0
- data/app/components/poetry/ui/fieldset/preview.rb +25 -0
- data/app/components/poetry/ui/fieldset/style.rb +18 -0
- data/app/components/poetry/ui/file_input/component.html.erb +14 -0
- data/app/components/poetry/ui/file_input/component.rb +188 -0
- data/app/components/poetry/ui/file_input/preview.rb +30 -0
- data/app/components/poetry/ui/file_input/style.rb +35 -0
- data/app/components/poetry/ui/hover_card/component.html.erb +6 -0
- data/app/components/poetry/ui/hover_card/component.rb +201 -0
- data/app/components/poetry/ui/hover_card/preview.rb +60 -0
- data/app/components/poetry/ui/hover_card/style.rb +17 -0
- data/app/components/poetry/ui/icon/component.rb +127 -0
- data/app/components/poetry/ui/icon/preview.rb +21 -0
- data/app/components/poetry/ui/input/component.rb +80 -0
- data/app/components/poetry/ui/input/preview.rb +35 -0
- data/app/components/poetry/ui/input/style.rb +18 -0
- data/app/components/poetry/ui/input_group/button_preview.html.erb +12 -0
- data/app/components/poetry/ui/input_group/component.rb +69 -0
- data/app/components/poetry/ui/input_group/preview.rb +23 -0
- data/app/components/poetry/ui/input_group/search_preview.html.erb +13 -0
- data/app/components/poetry/ui/input_group/style.rb +40 -0
- data/app/components/poetry/ui/input_group/textarea_preview.html.erb +10 -0
- data/app/components/poetry/ui/input_group_field.rb +61 -0
- data/app/components/poetry/ui/input_otp/component.html.erb +25 -0
- data/app/components/poetry/ui/input_otp/component.rb +250 -0
- data/app/components/poetry/ui/input_otp/preview.rb +91 -0
- data/app/components/poetry/ui/input_otp/style.rb +36 -0
- data/app/components/poetry/ui/item/component.html.erb +13 -0
- data/app/components/poetry/ui/item/component.rb +99 -0
- data/app/components/poetry/ui/item/group_preview.html.erb +15 -0
- data/app/components/poetry/ui/item/preview.rb +52 -0
- data/app/components/poetry/ui/item/style.rb +45 -0
- data/app/components/poetry/ui/kbd/component.rb +47 -0
- data/app/components/poetry/ui/kbd/group_preview.html.erb +4 -0
- data/app/components/poetry/ui/kbd/preview.rb +23 -0
- data/app/components/poetry/ui/kbd/style.rb +13 -0
- data/app/components/poetry/ui/label/component.rb +46 -0
- data/app/components/poetry/ui/label/preview.html.erb +5 -0
- data/app/components/poetry/ui/label/preview.rb +16 -0
- data/app/components/poetry/ui/label/style.rb +13 -0
- data/app/components/poetry/ui/link/component.rb +62 -0
- data/app/components/poetry/ui/link/preview.rb +31 -0
- data/app/components/poetry/ui/link/style.rb +20 -0
- data/app/components/poetry/ui/marker/component.html.erb +4 -0
- data/app/components/poetry/ui/marker/component.rb +81 -0
- data/app/components/poetry/ui/marker/preview.rb +43 -0
- data/app/components/poetry/ui/marker/style.rb +24 -0
- data/app/components/poetry/ui/menubar/component.html.erb +3 -0
- data/app/components/poetry/ui/menubar/component.rb +493 -0
- data/app/components/poetry/ui/menubar/preview.rb +164 -0
- data/app/components/poetry/ui/menubar/style.rb +71 -0
- data/app/components/poetry/ui/menus.rb +386 -0
- data/app/components/poetry/ui/message/component.html.erb +8 -0
- data/app/components/poetry/ui/message/component.rb +64 -0
- data/app/components/poetry/ui/message/preview.rb +33 -0
- data/app/components/poetry/ui/message/style.rb +29 -0
- data/app/components/poetry/ui/message_scroller/component.html.erb +15 -0
- data/app/components/poetry/ui/message_scroller/component.rb +163 -0
- data/app/components/poetry/ui/message_scroller/preview.rb +33 -0
- data/app/components/poetry/ui/message_scroller/style.rb +39 -0
- data/app/components/poetry/ui/metadata_list/component.rb +88 -0
- data/app/components/poetry/ui/metadata_list/preview.rb +55 -0
- data/app/components/poetry/ui/metadata_list/style.rb +35 -0
- data/app/components/poetry/ui/meter/component.rb +117 -0
- data/app/components/poetry/ui/meter/preview.rb +28 -0
- data/app/components/poetry/ui/meter/style.rb +16 -0
- data/app/components/poetry/ui/native_select/component.rb +124 -0
- data/app/components/poetry/ui/native_select/labelled_preview.html.erb +7 -0
- data/app/components/poetry/ui/native_select/preview.rb +20 -0
- data/app/components/poetry/ui/native_select/style.rb +22 -0
- data/app/components/poetry/ui/navigation_menu/bar_preview.html.erb +14 -0
- data/app/components/poetry/ui/navigation_menu/component.html.erb +25 -0
- data/app/components/poetry/ui/navigation_menu/component.rb +254 -0
- data/app/components/poetry/ui/navigation_menu/preview.rb +20 -0
- data/app/components/poetry/ui/navigation_menu/style.rb +73 -0
- data/app/components/poetry/ui/navigation_menu/viewport_preview.html.erb +23 -0
- data/app/components/poetry/ui/number_field/component.html.erb +19 -0
- data/app/components/poetry/ui/number_field/component.rb +263 -0
- data/app/components/poetry/ui/number_field/preview.rb +66 -0
- data/app/components/poetry/ui/number_field/style.rb +14 -0
- data/app/components/poetry/ui/pagination/component.html.erb +40 -0
- data/app/components/poetry/ui/pagination/component.rb +182 -0
- data/app/components/poetry/ui/pagination/preview.rb +47 -0
- data/app/components/poetry/ui/pagination/style.rb +27 -0
- data/app/components/poetry/ui/popover/component.html.erb +17 -0
- data/app/components/poetry/ui/popover/component.rb +203 -0
- data/app/components/poetry/ui/popover/preview.html.erb +29 -0
- data/app/components/poetry/ui/popover/preview.rb +66 -0
- data/app/components/poetry/ui/popover/style.rb +26 -0
- data/app/components/poetry/ui/popper_consumer.rb +69 -0
- data/app/components/poetry/ui/progress/component.rb +95 -0
- data/app/components/poetry/ui/progress/preview.rb +22 -0
- data/app/components/poetry/ui/progress/style.rb +19 -0
- data/app/components/poetry/ui/questionnaire/component.html.erb +80 -0
- data/app/components/poetry/ui/questionnaire/component.rb +396 -0
- data/app/components/poetry/ui/questionnaire/preview.rb +99 -0
- data/app/components/poetry/ui/questionnaire/style.rb +53 -0
- data/app/components/poetry/ui/radio_group/component.html.erb +4 -0
- data/app/components/poetry/ui/radio_group/component.rb +323 -0
- data/app/components/poetry/ui/radio_group/preview.rb +109 -0
- data/app/components/poetry/ui/radio_group/style.rb +53 -0
- data/app/components/poetry/ui/resizable/component.html.erb +10 -0
- data/app/components/poetry/ui/resizable/component.rb +178 -0
- data/app/components/poetry/ui/resizable/preview.rb +42 -0
- data/app/components/poetry/ui/resizable/style.rb +30 -0
- data/app/components/poetry/ui/scroll_area/component.rb +76 -0
- data/app/components/poetry/ui/scroll_area/preview.rb +35 -0
- data/app/components/poetry/ui/scroll_area/style.rb +19 -0
- data/app/components/poetry/ui/search_field/component.html.erb +15 -0
- data/app/components/poetry/ui/search_field/component.rb +137 -0
- data/app/components/poetry/ui/search_field/preview.rb +23 -0
- data/app/components/poetry/ui/search_field/style.rb +18 -0
- data/app/components/poetry/ui/select/component.html.erb +15 -0
- data/app/components/poetry/ui/select/component.rb +639 -0
- data/app/components/poetry/ui/select/preview.rb +113 -0
- data/app/components/poetry/ui/select/style.rb +58 -0
- data/app/components/poetry/ui/sensitive_input/component.html.erb +29 -0
- data/app/components/poetry/ui/sensitive_input/component.rb +274 -0
- data/app/components/poetry/ui/sensitive_input/preview.rb +40 -0
- data/app/components/poetry/ui/sensitive_input/style.rb +49 -0
- data/app/components/poetry/ui/separator/component.rb +57 -0
- data/app/components/poetry/ui/separator/preview.rb +19 -0
- data/app/components/poetry/ui/separator/style.rb +15 -0
- data/app/components/poetry/ui/sheet/component.html.erb +21 -0
- data/app/components/poetry/ui/sheet/component.rb +97 -0
- data/app/components/poetry/ui/sheet/preview.rb +57 -0
- data/app/components/poetry/ui/sheet/style.rb +47 -0
- data/app/components/poetry/ui/sidebar/component.html.erb +20 -0
- data/app/components/poetry/ui/sidebar/component.rb +249 -0
- data/app/components/poetry/ui/sidebar/preview.rb +21 -0
- data/app/components/poetry/ui/sidebar/shell_preview.html.erb +47 -0
- data/app/components/poetry/ui/sidebar/style.rb +139 -0
- data/app/components/poetry/ui/skeleton/component.rb +39 -0
- data/app/components/poetry/ui/skeleton/preview.rb +22 -0
- data/app/components/poetry/ui/skeleton/style.rb +13 -0
- data/app/components/poetry/ui/slider/component.html.erb +13 -0
- data/app/components/poetry/ui/slider/component.rb +360 -0
- data/app/components/poetry/ui/slider/preview.rb +73 -0
- data/app/components/poetry/ui/slider/style.rb +55 -0
- data/app/components/poetry/ui/spinner/component.rb +58 -0
- data/app/components/poetry/ui/spinner/preview.rb +22 -0
- data/app/components/poetry/ui/spinner/style.rb +12 -0
- data/app/components/poetry/ui/stat/component.html.erb +13 -0
- data/app/components/poetry/ui/stat/component.rb +102 -0
- data/app/components/poetry/ui/stat/preview.rb +47 -0
- data/app/components/poetry/ui/stat/style.rb +29 -0
- data/app/components/poetry/ui/switch/component.html.erb +15 -0
- data/app/components/poetry/ui/switch/component.rb +136 -0
- data/app/components/poetry/ui/switch/preview.rb +66 -0
- data/app/components/poetry/ui/switch/style.rb +22 -0
- data/app/components/poetry/ui/table/component.html.erb +3 -0
- data/app/components/poetry/ui/table/component.rb +93 -0
- data/app/components/poetry/ui/table/preview.rb +96 -0
- data/app/components/poetry/ui/table/style.rb +26 -0
- data/app/components/poetry/ui/tabs/component.html.erb +10 -0
- data/app/components/poetry/ui/tabs/component.rb +253 -0
- data/app/components/poetry/ui/tabs/preview.rb +43 -0
- data/app/components/poetry/ui/tabs/style.rb +49 -0
- data/app/components/poetry/ui/tag_group/component.html.erb +8 -0
- data/app/components/poetry/ui/tag_group/component.rb +203 -0
- data/app/components/poetry/ui/tag_group/preview.rb +43 -0
- data/app/components/poetry/ui/tag_group/style.rb +29 -0
- data/app/components/poetry/ui/textarea/component.rb +64 -0
- data/app/components/poetry/ui/textarea/preview.rb +75 -0
- data/app/components/poetry/ui/textarea/style.rb +18 -0
- data/app/components/poetry/ui/time_field/component.html.erb +6 -0
- data/app/components/poetry/ui/time_field/component.rb +85 -0
- data/app/components/poetry/ui/time_field/preview.rb +40 -0
- data/app/components/poetry/ui/time_field/style.rb +30 -0
- data/app/components/poetry/ui/timeline/component.rb +134 -0
- data/app/components/poetry/ui/timeline/preview.rb +61 -0
- data/app/components/poetry/ui/timeline/style.rb +54 -0
- data/app/components/poetry/ui/toast/component.html.erb +24 -0
- data/app/components/poetry/ui/toast/component.rb +178 -0
- data/app/components/poetry/ui/toast/preview.rb +69 -0
- data/app/components/poetry/ui/toast/style.rb +33 -0
- data/app/components/poetry/ui/toast_trigger/addressed_preview.html.erb +12 -0
- data/app/components/poetry/ui/toast_trigger/component.html.erb +3 -0
- data/app/components/poetry/ui/toast_trigger/component.rb +82 -0
- data/app/components/poetry/ui/toast_trigger/default_preview.html.erb +11 -0
- data/app/components/poetry/ui/toast_trigger/preview.rb +19 -0
- data/app/components/poetry/ui/toaster/component.html.erb +1 -0
- data/app/components/poetry/ui/toaster/component.rb +99 -0
- data/app/components/poetry/ui/toaster/positions_preview.html.erb +24 -0
- data/app/components/poetry/ui/toaster/preview.rb +46 -0
- data/app/components/poetry/ui/toaster/stamp_preview.html.erb +15 -0
- data/app/components/poetry/ui/toaster/style.rb +26 -0
- data/app/components/poetry/ui/toggle/component.rb +120 -0
- data/app/components/poetry/ui/toggle/preview.rb +79 -0
- data/app/components/poetry/ui/toggle/style.rb +33 -0
- data/app/components/poetry/ui/toggle_group/component.html.erb +4 -0
- data/app/components/poetry/ui/toggle_group/component.rb +252 -0
- data/app/components/poetry/ui/toggle_group/preview.rb +143 -0
- data/app/components/poetry/ui/toggle_group/style.rb +33 -0
- data/app/components/poetry/ui/toolbar/component.rb +125 -0
- data/app/components/poetry/ui/toolbar/preview.rb +41 -0
- data/app/components/poetry/ui/toolbar/style.rb +20 -0
- data/app/components/poetry/ui/tooltip/component.html.erb +20 -0
- data/app/components/poetry/ui/tooltip/component.rb +185 -0
- data/app/components/poetry/ui/tooltip/preview.rb +74 -0
- data/app/components/poetry/ui/tooltip/previews/provider_row.html.erb +25 -0
- data/app/components/poetry/ui/tooltip/style.rb +24 -0
- data/app/components/poetry/ui/tree/component.html.erb +32 -0
- data/app/components/poetry/ui/tree/component.rb +233 -0
- data/app/components/poetry/ui/tree/preview.rb +55 -0
- data/app/components/poetry/ui/tree/style.rb +29 -0
- data/app/components/poetry/ui/typeset/component.rb +73 -0
- data/app/components/poetry/ui/typeset/preview.rb +45 -0
- data/app/components/poetry/ui/typeset/style.rb +15 -0
- data/app/controllers/poetry/ui/llms_controller.rb +28 -0
- data/app/helpers/poetry/ui/components_helper.rb +1794 -0
- data/app/helpers/poetry/ui/form_builder/type_inference.rb +117 -0
- data/app/helpers/poetry/ui/form_builder.rb +916 -0
- data/app/helpers/poetry/ui/optimistic_form_builder.rb +55 -0
- data/app/helpers/poetry/ui/toast_stream_actions.rb +27 -0
- data/config/component_descriptions.yml +96 -0
- data/config/component_registry.yml +12257 -0
- data/config/locales/en.yml +93 -0
- data/config/routes.rb +8 -0
- data/config/template_classes.txt +113 -0
- data/lib/generators/poetry/add/add_generator.rb +218 -0
- data/lib/generators/poetry/agent_rules/agent_rules_generator.rb +91 -0
- data/lib/generators/poetry/agents/agents_generator.rb +24 -0
- data/lib/generators/poetry/agents_section.rb +148 -0
- data/lib/generators/poetry/block/block_generator.rb +67 -0
- data/lib/generators/poetry/block/templates/action_bar.html.erb +45 -0
- data/lib/generators/poetry/block/templates/app_shell.html.erb +139 -0
- data/lib/generators/poetry/block/templates/data_index.html.erb +85 -0
- data/lib/generators/poetry/block/templates/destructive_panel.html.erb +26 -0
- data/lib/generators/poetry/block/templates/page_header.html.erb +25 -0
- data/lib/generators/poetry/block/templates/section_card.html.erb +42 -0
- data/lib/generators/poetry/block/templates/stepper.html.erb +79 -0
- data/lib/generators/poetry/block/templates/top_nav.html.erb +44 -0
- data/lib/generators/poetry/diff/diff_generator.rb +135 -0
- data/lib/generators/poetry/editor/editor_generator.rb +344 -0
- data/lib/generators/poetry/editor/templates/poetry-check-hook +81 -0
- data/lib/generators/poetry/install/install_generator.rb +364 -0
- data/lib/generators/poetry/pagination/pagination_generator.rb +76 -0
- data/lib/generators/poetry/pagination/templates/kaminari_paginator.html.erb +29 -0
- data/lib/generators/poetry/pagination/templates/poetry_link_renderer.rb +34 -0
- data/lib/generators/poetry/pagination/templates/poetry_pagy_helper.rb +28 -0
- data/lib/generators/poetry/recipes/agent_embed/app/javascript/controllers/agent_demo_controller.js +131 -0
- data/lib/generators/poetry/recipes/screen_data_index/app/controllers/orders_controller.rb +15 -0
- data/lib/generators/poetry/recipes/screen_data_index/app/views/orders/index.html.erb +7 -0
- data/lib/generators/poetry/recipes/screen_data_index/test/system/orders_index_test.rb +16 -0
- data/lib/generators/poetry/recipes/screen_settings/app/controllers/settings_controller.rb +14 -0
- data/lib/generators/poetry/recipes/screen_settings/app/views/settings/show.html.erb +9 -0
- data/lib/generators/poetry/recipes/screen_settings/test/system/settings_show_test.rb +16 -0
- data/lib/generators/poetry/scaffold_templates/scaffold_templates_generator.rb +60 -0
- data/lib/generators/poetry/scaffold_templates/templates/_form.html.erb.tt +77 -0
- data/lib/generators/poetry/scaffold_templates/templates/controller.rb.tt +78 -0
- data/lib/generators/poetry/scaffold_templates/templates/edit.html.erb.tt +14 -0
- data/lib/generators/poetry/scaffold_templates/templates/index.html.erb.tt +24 -0
- data/lib/generators/poetry/scaffold_templates/templates/new.html.erb.tt +11 -0
- data/lib/generators/poetry/scaffold_templates/templates/partial.html.erb.tt +13 -0
- data/lib/generators/poetry/scaffold_templates/templates/show.html.erb.tt +18 -0
- data/lib/generators/poetry/skill/skill_generator.rb +34 -0
- data/lib/generators/poetry/skill/templates/poetry-component/SKILL.md +58 -0
- data/lib/generators/poetry/skill/templates/poetry-component/references/anatomy.md +152 -0
- data/lib/generators/poetry/skill/templates/poetry-component/references/checklist.md +67 -0
- data/lib/generators/poetry/skill/templates/poetry-component/references/documentation.md +137 -0
- data/lib/generators/poetry/skill/templates/poetry-design/SKILL.md +56 -0
- data/lib/generators/poetry/skill/templates/poetry-design/references/audit.md +123 -0
- data/lib/generators/poetry/skill/templates/poetry-design/references/compose.md +73 -0
- data/lib/generators/poetry/skill/templates/poetry-design/references/figma.md +68 -0
- data/lib/generators/poetry/skill/templates/poetry-design/references/paper.md +56 -0
- data/lib/generators/poetry/skill/templates/poetry-design/references/study.md +47 -0
- data/lib/generators/poetry/skill/templates/poetry-design/references/theme.md +70 -0
- data/lib/generators/poetry/skills_section.rb +58 -0
- data/lib/poetry/ui/chat.rb +283 -0
- data/lib/poetry/ui/code_block_highlighter.rb +60 -0
- data/lib/poetry/ui/engine.rb +55 -0
- data/lib/poetry/ui/recipes.rb +157 -0
- data/lib/poetry/ui/testing/combobox.rb +167 -0
- data/lib/poetry/ui/testing/dialog.rb +73 -0
- data/lib/poetry/ui/testing/menu.rb +127 -0
- data/lib/poetry/ui/testing/registration.rb +58 -0
- data/lib/poetry/ui/testing/select.rb +147 -0
- data/lib/poetry/ui/testing/tester.rb +130 -0
- data/lib/poetry/ui/testing.rb +130 -0
- data/lib/poetry/ui/themes.rb +109 -0
- data/lib/poetry/ui/version.rb +8 -0
- data/lib/poetry/ui/webmcp.rb +91 -0
- data/lib/poetry/ui.rb +285 -0
- data/lib/poetry-ui.rb +5 -0
- data/lib/tasks/poetry/check.rake +73 -0
- data/lib/tasks/poetry/design.rake +293 -0
- data/lib/tasks/poetry/verify.rake +36 -0
- data/themes/default.css +1793 -0
- data/themes/luma.css +1764 -0
- data/themes/lyra.css +1768 -0
- data/themes/maia.css +1768 -0
- data/themes/mira.css +1760 -0
- data/themes/nova.css +1755 -0
- data/themes/rhea.css +1771 -0
- data/themes/sera.css +1757 -0
- data/themes/vega.css +1775 -0
- data/typeset/typeset.css +499 -0
- metadata +502 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Poetry
|
|
4
|
+
module Ui
|
|
5
|
+
# DataTable family: the sortable, filterable, paginated table plus
|
|
6
|
+
# its URL-state object.
|
|
7
|
+
module DataTable
|
|
8
|
+
# A data table: the composed Table with sortable column headers, a
|
|
9
|
+
# filter box, and Pagination, where sort/filter/page live in the
|
|
10
|
+
# URL as query params - shareable and back-button-correct. Data
|
|
11
|
+
# stays with the host: the controller builds a sanitized State from
|
|
12
|
+
# params, runs its own scope, and hands the page of rows in.
|
|
13
|
+
# Row-level mutation (inline edit, row actions) belongs to reactive
|
|
14
|
+
# components rendered inside cells, never to this component.
|
|
15
|
+
#
|
|
16
|
+
# @example A sortable notes table
|
|
17
|
+
# <%= poetry_data_table(rows: @notes, state: state, total: @pages,
|
|
18
|
+
# path: ->(p) { notes_path(**p) }, caption: "Notes") do |t| %>
|
|
19
|
+
# <% t.with_column("Title", key: :title, sortable: true) { |note| note.title } %>
|
|
20
|
+
# <% t.with_column("Created", key: :created_at, sortable: true) { |note| note.created_at.to_date } %>
|
|
21
|
+
# <% end %>
|
|
22
|
+
class Component < Poetry::Core::Component
|
|
23
|
+
# Projected into the registry, llms.txt, and the agent surface.
|
|
24
|
+
AGENT_RULES = [
|
|
25
|
+
"Build State.from_params(params, sortable: [...]) in the controller - NEVER order by raw params; " \
|
|
26
|
+
"the whitelist is what makes state.order_clause injection-safe.",
|
|
27
|
+
"Column cell blocks RETURN the cell content ({ |row| row.title }) - they must not write to the " \
|
|
28
|
+
"template buffer.",
|
|
29
|
+
"Sort/filter/page are URL state over GET links and a GET form. Row mutations (inline edit, row " \
|
|
30
|
+
"actions) belong to poetry-reactive components rendered inside cells - never to this component.",
|
|
31
|
+
"Give the table a caption: - it is the table's accessible purpose."
|
|
32
|
+
].freeze
|
|
33
|
+
|
|
34
|
+
# The cell block is a per-row RENDERER, not captured content: it
|
|
35
|
+
# receives each row record (SLOT_BLOCK_YIELDS exempts it from the
|
|
36
|
+
# yieldless contract), and a column cannot exist without one.
|
|
37
|
+
SLOT_BLOCK_YIELDS = { column: "the row record" }.freeze
|
|
38
|
+
# Slots whose block content is required, with the hint the check surfaces.
|
|
39
|
+
SLOT_REQUIRED_CONTENT = { column: "the cell renderer - { |row| ... }" }.freeze
|
|
40
|
+
|
|
41
|
+
# The required slots, stated statically so static checks can flag
|
|
42
|
+
# a missing column without rendering.
|
|
43
|
+
REQUIRED_SLOTS = { column: "at least one column" }.freeze
|
|
44
|
+
|
|
45
|
+
renders_many :columns,
|
|
46
|
+
doc: "Columns are DECLARED here and rendered per row by the template. A sortable column's key " \
|
|
47
|
+
"must be in the state's whitelist - catching drift between the view's columns and the " \
|
|
48
|
+
"controller's sortable: list at render, not as a silently unsortable header.",
|
|
49
|
+
renders: lambda { |label, key: nil, sortable: false, classes: nil, &cell|
|
|
50
|
+
if sortable && !key
|
|
51
|
+
raise ArgumentError,
|
|
52
|
+
"DataTable column #{label.inspect}: sortable: true requires a key:"
|
|
53
|
+
end
|
|
54
|
+
if sortable && !state.sortable?(key)
|
|
55
|
+
raise ArgumentError,
|
|
56
|
+
"DataTable column #{key.inspect} is sortable in the view but missing from the " \
|
|
57
|
+
"controller's State sortable: whitelist (#{state.sortable.inspect}) - add it there"
|
|
58
|
+
end
|
|
59
|
+
raise ArgumentError, "DataTable column #{label.inspect} requires a cell block" unless cell
|
|
60
|
+
|
|
61
|
+
column_defs << Column.new(label: label, key: key&.to_s, sortable: sortable, classes: classes,
|
|
62
|
+
cell: cell)
|
|
63
|
+
nil
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# The whole selection surface is selectable?-gated.
|
|
67
|
+
use_stimulus do
|
|
68
|
+
on :root, if: :selectable? do
|
|
69
|
+
controller :table_selection do
|
|
70
|
+
register
|
|
71
|
+
value :label, from: :selected_count_label
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
on :select_all do
|
|
75
|
+
controller :table_selection do
|
|
76
|
+
target :all
|
|
77
|
+
action :toggleAll, on: :change
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
on :row_checkbox do
|
|
81
|
+
controller :table_selection do
|
|
82
|
+
action :press, on: %i[pointerdown keydown]
|
|
83
|
+
action :toggled, on: :change
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
option :caption, :string, doc: "The table's accessible purpose, rendered as its <caption>."
|
|
89
|
+
option :empty_text, :string, default: "No results.", doc: "Shown in a full-width row when rows are empty."
|
|
90
|
+
option :filter, :boolean, default: true, doc: "Renders the filter form; false drops the toolbar row."
|
|
91
|
+
option :filter_label, :string, default: "Filter", doc: "The filter input's accessible label."
|
|
92
|
+
option :filter_placeholder, :string, default: "Filter…", doc: "The filter input's placeholder text."
|
|
93
|
+
option :filter_name, :string, default: "q", doc: "The query-param key the filter submits under."
|
|
94
|
+
option :frame, :string,
|
|
95
|
+
doc: "Wrap in a <turbo-frame data-turbo-action=\"advance\"> so hosts with Turbo scope the round trip " \
|
|
96
|
+
"to the table while the URL still advances. The host response must render the same frame id."
|
|
97
|
+
option :sticky_header, :boolean, default: false,
|
|
98
|
+
doc: "Forwarded to the inner Table: sticky_header pins the thead while the " \
|
|
99
|
+
"table's scroll container scrolls; container_class caps that " \
|
|
100
|
+
"container's height (\"max-h-96\") - without a cap nothing sticks. The " \
|
|
101
|
+
"sticky scroll region needs an accessible name (the ScrollArea rule); " \
|
|
102
|
+
"scroll_label: falls back to caption:."
|
|
103
|
+
option :container_class, :string,
|
|
104
|
+
doc: "Caps the scroll container's height (e.g. \"max-h-96\") - without a cap the sticky header has " \
|
|
105
|
+
"nothing to stick inside."
|
|
106
|
+
option :scroll_label, :string, doc: "Accessible name for the sticky scroll region; falls back to caption:."
|
|
107
|
+
option :selectable, ActiveModel::Type::Value.new,
|
|
108
|
+
doc: "Row selection: a lambda mapping each row to its id turns the feature ON - a leading checkbox " \
|
|
109
|
+
"column (select-all with a real indeterminate middle state, shift ranges, count announcements) " \
|
|
110
|
+
"whose checkboxes ARE the form value (selection_name[], plain checkboxes with no JS). Pair with " \
|
|
111
|
+
"the action-bar block for bulk actions."
|
|
112
|
+
option :selection_name, :string, default: "selected_ids",
|
|
113
|
+
doc: "The checkbox field name; selected row ids post as selection_name[]."
|
|
114
|
+
|
|
115
|
+
part "data-table", "Root surface - toolbar, table, and pagination footer stack here"
|
|
116
|
+
part "data-table-toolbar", "The row above the table holding the filter form - " \
|
|
117
|
+
"renders unless filter: false"
|
|
118
|
+
part "data-table-filter", "The GET filter form (role=search) - hidden fields carry the " \
|
|
119
|
+
"current sort; a new filter resets the page"
|
|
120
|
+
part "table-container", "The composed Table's scroll container - Table renders it, " \
|
|
121
|
+
"this surface owns where it sits"
|
|
122
|
+
part "data-table-footer", "The Pagination row - renders when total: is more than one page"
|
|
123
|
+
# The selection checkboxes (selectable: only) render INSIDE the
|
|
124
|
+
# composed Table's root, so ownership attributes them to Table
|
|
125
|
+
# (the NumberField-stepper precedent) - prose, not parts:
|
|
126
|
+
# data-slot="data-table-select-all" is the header checkbox (the
|
|
127
|
+
# controller drives its INDETERMINATE middle state as a property);
|
|
128
|
+
# data-slot="data-table-select-row" is one row's checkbox - THE
|
|
129
|
+
# form value (selection_name[], value from the selectable: lambda);
|
|
130
|
+
# the controller mirrors aria-selected/data-selected onto rows.
|
|
131
|
+
|
|
132
|
+
# The structural collaborators handed in at construction.
|
|
133
|
+
# @api private
|
|
134
|
+
attr_reader :rows, :state, :total
|
|
135
|
+
|
|
136
|
+
# Enforces at least one declared column.
|
|
137
|
+
# @api private
|
|
138
|
+
def before_render
|
|
139
|
+
raise ArgumentError, "DataTable requires at least one with_column" unless columns?
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# The declared columns in order.
|
|
143
|
+
# @api private
|
|
144
|
+
def column_defs
|
|
145
|
+
@column_defs ||= []
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Attributes for the root surface.
|
|
149
|
+
# @api private
|
|
150
|
+
def root_attributes
|
|
151
|
+
attrs = { "data-slot" => "data-table" }.merge(component_data_attributes)
|
|
152
|
+
attrs = attrs.merge(stimulus_attributes_for(:root))
|
|
153
|
+
html_attributes.merge_if_not_set(attrs)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Whether row selection is on (selectable: present).
|
|
157
|
+
# @api private
|
|
158
|
+
def selectable?
|
|
159
|
+
selectable.present?
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Attributes for the select-all header checkbox.
|
|
163
|
+
# @api private
|
|
164
|
+
def select_all_attributes
|
|
165
|
+
{
|
|
166
|
+
"type" => "checkbox", "data-slot" => "data-table-select-all",
|
|
167
|
+
"class" => css(:checkbox),
|
|
168
|
+
"aria-label" => t("poetry.data_table.select_all")
|
|
169
|
+
}.merge(stimulus_attributes_for(:select_all))
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Attributes for one row's selection checkbox - the form value.
|
|
173
|
+
# @api private
|
|
174
|
+
def select_row_attributes(row)
|
|
175
|
+
{
|
|
176
|
+
"type" => "checkbox", "data-slot" => "data-table-select-row",
|
|
177
|
+
"name" => "#{selection_name}[]", "value" => selectable.call(row),
|
|
178
|
+
"class" => css(:checkbox),
|
|
179
|
+
"aria-label" => t("poetry.data_table.select_row")
|
|
180
|
+
}.merge(stimulus_attributes_for(:row_checkbox))
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# The localized count-announcement template.
|
|
184
|
+
# @api private
|
|
185
|
+
def selected_count_label = t("poetry.data_table.selected_count")
|
|
186
|
+
|
|
187
|
+
# Builds a URL for the given state params via the path: lambda.
|
|
188
|
+
# @api private
|
|
189
|
+
def path_for(params)
|
|
190
|
+
@path.call(params)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# th attributes: the Table head classes plus aria-sort on the
|
|
194
|
+
# actively sorted column (the APG announcement mechanism - one
|
|
195
|
+
# column at a time).
|
|
196
|
+
# @api private
|
|
197
|
+
def head_attributes(column)
|
|
198
|
+
attrs = { "data-slot" => "table-head", class: merge_classes(Table::Style.css(:head), column.classes) }
|
|
199
|
+
if column.sortable && state.sorted_by?(column.key)
|
|
200
|
+
attrs["aria-sort"] = state.dir == "asc" ? "ascending" : "descending"
|
|
201
|
+
end
|
|
202
|
+
attrs
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# td attributes for one column's cells.
|
|
206
|
+
# @api private
|
|
207
|
+
def cell_attributes(column)
|
|
208
|
+
{ "data-slot" => "table-cell", class: merge_classes(Table::Style.css(:cell), column.classes) }
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# The sort affordance: a ghost Button-styled LINK to the toggled
|
|
212
|
+
# ordering (a real href - keyboard, middle-click, and share all work).
|
|
213
|
+
# @api private
|
|
214
|
+
def sort_link_options(column)
|
|
215
|
+
{
|
|
216
|
+
tag: :a, href: path_for(state.toggle_params(column.key)),
|
|
217
|
+
variant: :ghost, size: :sm, class: Style.css(:sort_link),
|
|
218
|
+
data: { slot: "data-table-sort" }
|
|
219
|
+
}
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# The header glyph for a column's current sort state.
|
|
223
|
+
# @api private
|
|
224
|
+
def sort_icon(column)
|
|
225
|
+
return :"arrow-up-down" unless state.sorted_by?(column.key)
|
|
226
|
+
|
|
227
|
+
state.dir == "asc" ? :"arrow-up" : :"arrow-down"
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# The filter input's id (the label's for= target).
|
|
231
|
+
# @api private
|
|
232
|
+
def filter_id
|
|
233
|
+
@filter_id ||= "#{poetry_instance_id("poetry-data-table")}-filter"
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# The filter form's action is the bare collection URL; the current
|
|
237
|
+
# sort rides hidden fields and the page deliberately resets (a new
|
|
238
|
+
# filter is a new list).
|
|
239
|
+
# @api private
|
|
240
|
+
def filter_form_action
|
|
241
|
+
path_for({})
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Whether the pagination footer renders (total: > 1 page).
|
|
245
|
+
# @api private
|
|
246
|
+
def pagination?
|
|
247
|
+
total.present? && total > 1
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# A declared column: header label, the whitelisted sort key, and the
|
|
251
|
+
# cell block (called per row, returns the cell content).
|
|
252
|
+
# @api private
|
|
253
|
+
Column = Data.define(:label, :key, :sortable, :classes, :cell)
|
|
254
|
+
|
|
255
|
+
private
|
|
256
|
+
|
|
257
|
+
# rows/state/path/total are structural collaborators, not typed
|
|
258
|
+
# options (see Pagination's path: for the precedent).
|
|
259
|
+
def initialize(rows:, state:, path:, total: nil, **)
|
|
260
|
+
super(**)
|
|
261
|
+
@rows = rows
|
|
262
|
+
@state = state
|
|
263
|
+
@path = path
|
|
264
|
+
@total = total
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def merge_classes(*values)
|
|
268
|
+
values.compact.join(" ")
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
private :column_defs, :root_attributes, :selectable?, :select_all_attributes, :select_row_attributes
|
|
272
|
+
private :selected_count_label, :path_for, :head_attributes, :cell_attributes, :sort_link_options, :sort_icon
|
|
273
|
+
private :filter_id, :filter_form_action, :pagination?
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Poetry
|
|
4
|
+
module Ui
|
|
5
|
+
module DataTable
|
|
6
|
+
# The DataTable preview: a small invoices dataset in the three states
|
|
7
|
+
# a host sees - sorted, filtered+paginated, and empty.
|
|
8
|
+
class Preview < Poetry::Core::Preview::Base
|
|
9
|
+
Invoice = Struct.new(:number, :customer, :amount, keyword_init: true)
|
|
10
|
+
|
|
11
|
+
ROWS = [
|
|
12
|
+
Invoice.new(number: "INV-001", customer: "Acme", amount: "$250.00"),
|
|
13
|
+
Invoice.new(number: "INV-002", customer: "Globex", amount: "$1,150.00"),
|
|
14
|
+
Invoice.new(number: "INV-003", customer: "Initech", amount: "$420.00")
|
|
15
|
+
].freeze
|
|
16
|
+
|
|
17
|
+
def default
|
|
18
|
+
render_component(rows: ROWS, state: state(sort: "number", dir: "asc"),
|
|
19
|
+
path: path, caption: "A list of recent invoices.") do |table|
|
|
20
|
+
columns(table)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Row selection: selectable: maps rows to ids; the header
|
|
25
|
+
# checkbox tri-states, shift-click ranges, the checkboxes ARE the
|
|
26
|
+
# selected_ids[] form value. Pair with the action-bar block.
|
|
27
|
+
def selectable
|
|
28
|
+
render_component(rows: ROWS, state: state(sort: "number", dir: "asc"),
|
|
29
|
+
path: path, caption: "A list of recent invoices.",
|
|
30
|
+
selectable: ->(invoice) { invoice.number }) do |table| # rubocop:disable Style/SymbolProc
|
|
31
|
+
columns(table)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def filtered_and_paginated
|
|
36
|
+
render_component(rows: ROWS.first(2), total: 3,
|
|
37
|
+
state: state(q: "inv", sort: "customer", dir: "desc", page: 2),
|
|
38
|
+
path: path, caption: "A list of recent invoices.") do |table|
|
|
39
|
+
columns(table)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def empty
|
|
44
|
+
render_component(rows: [], state: state(q: "zzz"), path: path,
|
|
45
|
+
caption: "A list of recent invoices.") do |table|
|
|
46
|
+
columns(table)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def columns(table)
|
|
53
|
+
table.with_column("Invoice", key: :number, sortable: true, &:number)
|
|
54
|
+
table.with_column("Customer", key: :customer, sortable: true, &:customer)
|
|
55
|
+
table.with_column("Amount", classes: "text-right", &:amount)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def state(**params)
|
|
59
|
+
State.from_params(params, sortable: %w[number customer])
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def path
|
|
63
|
+
->(params) { "?#{params.to_query}" }
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Poetry
|
|
4
|
+
module Ui
|
|
5
|
+
module DataTable
|
|
6
|
+
# The DataTable's URL state - sort/filter/page as query params
|
|
7
|
+
# (shareable, back-button-correct), sanitized at the door so request
|
|
8
|
+
# params can never reach an ORDER BY. `sort` is kept ONLY when it
|
|
9
|
+
# appears in the `sortable:` whitelist and `dir` only when it is
|
|
10
|
+
# asc/desc - so #order_clause is safe by construction, never by
|
|
11
|
+
# caller discipline.
|
|
12
|
+
#
|
|
13
|
+
# @example Building state in the controller
|
|
14
|
+
# state = Poetry::Ui::DataTable::State.from_params(
|
|
15
|
+
# params, sortable: %w[title created_at], default_sort: "created_at", default_dir: "desc"
|
|
16
|
+
# )
|
|
17
|
+
# scope = Note.all
|
|
18
|
+
# scope = scope.where("title LIKE ?", "%#{Note.sanitize_sql_like(state.q)}%") if state.q
|
|
19
|
+
# notes = scope.order(state.order_clause).offset(...).limit(per)
|
|
20
|
+
class State
|
|
21
|
+
# The closed vocabulary for the sort direction.
|
|
22
|
+
DIRECTIONS = %w[asc desc].freeze
|
|
23
|
+
|
|
24
|
+
# The sanitized pieces: filter query, sort column, direction,
|
|
25
|
+
# page number, and the sortable whitelist.
|
|
26
|
+
attr_reader :q, :sort, :dir, :page, :sortable
|
|
27
|
+
|
|
28
|
+
# Builds state from request params: sort survives only when it
|
|
29
|
+
# appears in the sortable: whitelist, dir only when asc/desc, the
|
|
30
|
+
# page floors at 1, and the query is stripped.
|
|
31
|
+
#
|
|
32
|
+
# @param params [Hash] the request params
|
|
33
|
+
# @param sortable [Array] the whitelist of sortable column keys
|
|
34
|
+
# @param default_sort [String, nil] the column when none is given
|
|
35
|
+
# @param default_dir [String] the direction when none is given
|
|
36
|
+
def self.from_params(params, sortable:, default_sort: nil, default_dir: "asc")
|
|
37
|
+
allowed = Array(sortable).map(&:to_s)
|
|
38
|
+
raw_sort = params[:sort].to_s
|
|
39
|
+
raw_dir = params[:dir].to_s
|
|
40
|
+
|
|
41
|
+
new(
|
|
42
|
+
q: params[:q].to_s.strip.presence,
|
|
43
|
+
sort: allowed.include?(raw_sort) ? raw_sort : default_sort&.to_s,
|
|
44
|
+
dir: DIRECTIONS.include?(raw_dir) ? raw_dir : default_dir.to_s,
|
|
45
|
+
page: [params[:page].to_i, 1].max,
|
|
46
|
+
sortable: allowed
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Direct construction with pre-sanitized values; prefer
|
|
51
|
+
# .from_params for anything that came in on a request.
|
|
52
|
+
def initialize(q: nil, sort: nil, dir: "asc", page: 1, sortable: [])
|
|
53
|
+
@q = q
|
|
54
|
+
@sort = sort
|
|
55
|
+
@dir = dir
|
|
56
|
+
@page = page
|
|
57
|
+
@sortable = sortable
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Whether the column is in the sortable whitelist.
|
|
61
|
+
def sortable?(column)
|
|
62
|
+
sortable.include?(column.to_s)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Whether the column is the actively sorted one.
|
|
66
|
+
def sorted_by?(column)
|
|
67
|
+
sort == column.to_s
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Injection-safe by construction: sort survived the whitelist and dir
|
|
71
|
+
# the asc/desc check, so this can go straight into .order(). Nil when
|
|
72
|
+
# nothing is sorted.
|
|
73
|
+
def order_clause
|
|
74
|
+
return nil unless sort
|
|
75
|
+
|
|
76
|
+
{ sort => dir.to_sym }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# The full state as query params (blank pieces omitted; page 1 is the
|
|
80
|
+
# default and stays out of the URL).
|
|
81
|
+
def to_params
|
|
82
|
+
params = {}
|
|
83
|
+
params[:q] = q if q
|
|
84
|
+
if sort
|
|
85
|
+
params[:sort] = sort
|
|
86
|
+
params[:dir] = dir
|
|
87
|
+
end
|
|
88
|
+
params[:page] = page if page > 1
|
|
89
|
+
params
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Params for clicking a sortable header: same column flips the
|
|
93
|
+
# direction, a new column starts ascending; the page resets (a new
|
|
94
|
+
# ordering is a new list).
|
|
95
|
+
def toggle_params(column)
|
|
96
|
+
column = column.to_s
|
|
97
|
+
next_dir = sorted_by?(column) && dir == "asc" ? "desc" : "asc"
|
|
98
|
+
to_params.except(:page).merge(sort: column, dir: next_dir)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Params for navigating to a page of the current view.
|
|
102
|
+
def page_params(number)
|
|
103
|
+
number > 1 ? to_params.merge(page: number) : to_params.except(:page)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# The state a filter submission must carry as hidden fields: sort
|
|
107
|
+
# survives, the page resets (a new filter is a new list), and q is
|
|
108
|
+
# the form input itself.
|
|
109
|
+
def filter_params
|
|
110
|
+
to_params.except(:q, :page)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Poetry
|
|
4
|
+
module Ui
|
|
5
|
+
module DataTable
|
|
6
|
+
# Re-expressed through the cn-* theme layer: the chrome rides
|
|
7
|
+
# poetry-named rules (own component - no upstream cn vocabulary).
|
|
8
|
+
class Style < Poetry::Core::Style
|
|
9
|
+
base "w-full"
|
|
10
|
+
|
|
11
|
+
element :toolbar, "cn-data-table-toolbar flex items-center"
|
|
12
|
+
element :container, "cn-data-table-container overflow-hidden " \
|
|
13
|
+
"[&_tr[data-selected]]:bg-muted/50"
|
|
14
|
+
element :footer, "cn-data-table-footer"
|
|
15
|
+
element :empty, "cn-data-table-empty"
|
|
16
|
+
|
|
17
|
+
# POETRY ADDITION: the caption lives INSIDE the bordered container
|
|
18
|
+
# (a <caption> cannot leave its <table>), so Table's mt-4 needs a
|
|
19
|
+
# matching bottom margin or the text hugs the frame's border.
|
|
20
|
+
# Upstream never composes this - its bordered data-table demo has
|
|
21
|
+
# no caption; its captioned table demo has no border.
|
|
22
|
+
element :caption, "mb-4"
|
|
23
|
+
|
|
24
|
+
# POETRY ADDITION: the ghost sort button's px-3 shifts the header
|
|
25
|
+
# LABEL 12px off the column's data text (upstream's demo carries
|
|
26
|
+
# the same quirk and users patch it with a negative margin - here
|
|
27
|
+
# the component owns the link, so the component compensates).
|
|
28
|
+
# Negative on BOTH sides: only the binding side of the cell's
|
|
29
|
+
# text-align has any layout effect, so one class serves left- and
|
|
30
|
+
# right-aligned columns alike.
|
|
31
|
+
element :sort_link, "-mx-3"
|
|
32
|
+
# Selection: native checkboxes on the token accent; the
|
|
33
|
+
# selected-row wash rides the container (rows are controller-
|
|
34
|
+
# marked data-selected).
|
|
35
|
+
element :checkbox, "size-4 shrink-0 translate-y-px rounded-sm border-input " \
|
|
36
|
+
"accent-primary outline-none " \
|
|
37
|
+
"focus-visible:ring-[3px] focus-visible:ring-ring/50"
|
|
38
|
+
element :select_cell, "w-10"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<%# Progressive enhancement: the native input is the visible control (and
|
|
2
|
+
the form value) until the controller builds segments into the group
|
|
3
|
+
and hides it from the tab order. %>
|
|
4
|
+
<%= content_tag(:div, **root_attributes.to_attributes) do %>
|
|
5
|
+
<%= content_tag(:span, "", **Poetry::Core::HTML::Attributes.new(group_attributes).to_attributes) %>
|
|
6
|
+
<%= tag.input(**input_attributes.to_attributes) %>
|
|
7
|
+
<% end %>
|