@adyen/bento-mcp 0.0.0 → 0.1.0
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.
- package/CHANGELOG.md +11 -0
- package/LICENSE +21 -0
- package/README.md +113 -0
- package/dist/assets/components/accordion/accordion.docs.mdx +77 -0
- package/dist/assets/components/accordion/accordion.stories.ts +1 -0
- package/dist/assets/components/accordion/accordion.vue +1 -0
- package/dist/assets/components/accordion/components/accordion-item.stories.ts +1 -0
- package/dist/assets/components/accordion/components/accordion-item.vue +1 -0
- package/dist/assets/components/action-bar/action-bar.docs.mdx +103 -0
- package/dist/assets/components/action-bar/action-bar.stories.ts +1 -0
- package/dist/assets/components/action-bar/action-bar.vue +1 -0
- package/dist/assets/components/alert/alert.docs.mdx +106 -0
- package/dist/assets/components/alert/alert.stories.ts +1 -0
- package/dist/assets/components/alert/alert.vue +1 -0
- package/dist/assets/components/avatar/avatar.docs.mdx +44 -0
- package/dist/assets/components/avatar/avatar.stories.ts +1 -0
- package/dist/assets/components/avatar/avatar.vue +1 -0
- package/dist/assets/components/avatar/components/avatar-image/avatar-image.vue +1 -0
- package/dist/assets/components/button/button.docs.mdx +137 -0
- package/dist/assets/components/button/button.stories.ts +1 -0
- package/dist/assets/components/button/button.vue +1 -0
- package/dist/assets/components/button/components/ai-button/ai-button.docs.mdx +40 -0
- package/dist/assets/components/button/components/ai-button/ai-button.stories.ts +1 -0
- package/dist/assets/components/button/components/ai-button/ai-button.vue +1 -0
- package/dist/assets/components/button/components/button-actions/button-actions.docs.mdx +169 -0
- package/dist/assets/components/button/components/button-actions/button-actions.stories.ts +1 -0
- package/dist/assets/components/button/components/button-actions/button-actions.vue +1 -0
- package/dist/assets/components/button/components/loading-button/loading-button.docs.mdx +106 -0
- package/dist/assets/components/button/components/loading-button/loading-button.stories.ts +1 -0
- package/dist/assets/components/button/components/loading-button/loading-button.vue +1 -0
- package/dist/assets/components/button/components/toggle-button/toggle-button.docs.mdx +81 -0
- package/dist/assets/components/button/components/toggle-button/toggle-button.stories.ts +1 -0
- package/dist/assets/components/button/components/toggle-button/toggle-button.vue +1 -0
- package/dist/assets/components/card/card.docs.mdx +166 -0
- package/dist/assets/components/card/card.stories.ts +1 -0
- package/dist/assets/components/card/card.vue +1 -0
- package/dist/assets/components/checkbox/checkbox.docs.mdx +116 -0
- package/dist/assets/components/checkbox/checkbox.stories.ts +1 -0
- package/dist/assets/components/checkbox/checkbox.vue +1 -0
- package/dist/assets/components/checkbox/components/checkbox-group/checkbox-group.docs.mdx +134 -0
- package/dist/assets/components/checkbox/components/checkbox-group/checkbox-group.stories.ts +1 -0
- package/dist/assets/components/checkbox/components/checkbox-group/checkbox-group.vue +1 -0
- package/dist/assets/components/chip/chip.docs.mdx +51 -0
- package/dist/assets/components/chip/chip.stories.ts +1 -0
- package/dist/assets/components/chip/chip.vue +1 -0
- package/dist/assets/components/click-outside/click-outside.docs.mdx +30 -0
- package/dist/assets/components/click-outside/click-outside.stories.ts +1 -0
- package/dist/assets/components/code-snippet/code-snippet.docs.mdx +73 -0
- package/dist/assets/components/code-snippet/code-snippet.stories.ts +1 -0
- package/dist/assets/components/code-snippet/code-snippet.vue +1 -0
- package/dist/assets/components/country/country.docs.mdx +61 -0
- package/dist/assets/components/country/country.stories.ts +1 -0
- package/dist/assets/components/country/country.vue +1 -0
- package/dist/assets/components/currency/currency.docs.mdx +38 -0
- package/dist/assets/components/currency/currency.stories.ts +1 -0
- package/dist/assets/components/currency/currency.vue +1 -0
- package/dist/assets/components/dashed-underline/dashed-underline.docs.mdx +57 -0
- package/dist/assets/components/dashed-underline/dashed-underline.stories.ts +1 -0
- package/dist/assets/components/dashed-underline/dashed-underline.vue +1 -0
- package/dist/assets/components/data-grid/components/data-grid-cell/data-grid-cell.vue +1 -0
- package/dist/assets/components/data-grid/components/data-grid-cell-actions/data-grid-cell-actions.vue +1 -0
- package/dist/assets/components/data-grid/components/data-grid-columns/data-grid-columns.vue +1 -0
- package/dist/assets/components/data-grid/components/data-grid-config-settings/components/data-grid-config-settings-column-row/data-grid-config-settings-column-row.vue +1 -0
- package/dist/assets/components/data-grid/components/data-grid-config-settings/data-grid-config-settings.vue +1 -0
- package/dist/assets/components/data-grid/components/data-grid-lazy-load/data-grid-lazy-load.vue +1 -0
- package/dist/assets/components/data-grid/components/data-grid-row/data-grid-row.vue +1 -0
- package/dist/assets/components/data-grid/components/data-grid-static-icon/data-grid-static-icon.docs.mdx +61 -0
- package/dist/assets/components/data-grid/components/data-grid-static-icon/data-grid-static-icon.stories.ts +1 -0
- package/dist/assets/components/data-grid/components/data-grid-static-icon/data-grid-static-icon.vue +1 -0
- package/dist/assets/components/data-grid/components/data-grid-summary-cell/data-grid-summary-cell.docs.mdx +56 -0
- package/dist/assets/components/data-grid/components/data-grid-summary-cell/data-grid-summary-cell.stories.ts +1 -0
- package/dist/assets/components/data-grid/components/data-grid-summary-cell/data-grid-summary-cell.vue +1 -0
- package/dist/assets/components/data-grid/components/data-grid-trend-cell/data-grid-trend-cell.docs.mdx +48 -0
- package/dist/assets/components/data-grid/components/data-grid-trend-cell/data-grid-trend-cell.stories.ts +1 -0
- package/dist/assets/components/data-grid/components/data-grid-trend-cell/data-grid-trend-cell.vue +1 -0
- package/dist/assets/components/data-grid/data-grid.docs.mdx +951 -0
- package/dist/assets/components/data-grid/data-grid.stories.ts +1 -0
- package/dist/assets/components/data-grid/data-grid.vue +1 -0
- package/dist/assets/components/date/date.docs.mdx +49 -0
- package/dist/assets/components/date/date.stories.ts +1 -0
- package/dist/assets/components/date/date.vue +1 -0
- package/dist/assets/components/date-picker/date-picker.docs.mdx +69 -0
- package/dist/assets/components/date-picker/date-picker.stories.ts +1 -0
- package/dist/assets/components/date-picker/date-picker.vue +1 -0
- package/dist/assets/components/date-range-picker/components/date-range-picker-calendar/date-range-picker-calendar.vue +1 -0
- package/dist/assets/components/date-range-picker/date-range-picker.docs.mdx +168 -0
- package/dist/assets/components/date-range-picker/date-range-picker.stories.ts +1 -0
- package/dist/assets/components/date-range-picker/date-range-picker.vue +1 -0
- package/dist/assets/components/divider/divider.docs.mdx +27 -0
- package/dist/assets/components/divider/divider.stories.ts +1 -0
- package/dist/assets/components/divider/divider.vue +1 -0
- package/dist/assets/components/dropdown/components/dropdown-base-textbox/dropdown-base-textbox.vue +1 -0
- package/dist/assets/components/dropdown/components/dropdown-default-textbox/dropdown-default-textbox.vue +1 -0
- package/dist/assets/components/dropdown/components/dropdown-options-container/dropdown-options-container.vue +1 -0
- package/dist/assets/components/dropdown/components/dropdown-small-textbox/dropdown-small-textbox.vue +1 -0
- package/dist/assets/components/dropdown/components/variants/dropdown-avatar/dropdown-avatar.stories.ts +1 -0
- package/dist/assets/components/dropdown/components/variants/dropdown-avatar/dropdown-avatar.vue +1 -0
- package/dist/assets/components/dropdown/components/variants/dropdown-country/dropdown-country.stories.ts +1 -0
- package/dist/assets/components/dropdown/components/variants/dropdown-country/dropdown-country.vue +1 -0
- package/dist/assets/components/dropdown/components/variants/dropdown-currency/dropdown-currency.stories.ts +1 -0
- package/dist/assets/components/dropdown/components/variants/dropdown-currency/dropdown-currency.vue +1 -0
- package/dist/assets/components/dropdown/components/variants/dropdown-payment-method/dropdown-payment-method.stories.ts +1 -0
- package/dist/assets/components/dropdown/components/variants/dropdown-payment-method/dropdown-payment-method.vue +1 -0
- package/dist/assets/components/dropdown/components/variants/dropdown-tag/dropdown-tag.stories.ts +1 -0
- package/dist/assets/components/dropdown/components/variants/dropdown-tag/dropdown-tag.vue +1 -0
- package/dist/assets/components/dropdown/dropdown.docs.mdx +420 -0
- package/dist/assets/components/dropdown/dropdown.stories.ts +1 -0
- package/dist/assets/components/dropdown/dropdown.vue +1 -0
- package/dist/assets/components/empty-state/empty-state.docs.mdx +115 -0
- package/dist/assets/components/empty-state/empty-state.stories.ts +1 -0
- package/dist/assets/components/empty-state/empty-state.vue +1 -0
- package/dist/assets/components/file-uploader/components/file-uploader-file-card/components/file-uploader-file-card-animation/file-uploader-file-card-animation.vue +1 -0
- package/dist/assets/components/file-uploader/components/file-uploader-file-card/file-uploader-file-card.stories.ts +1 -0
- package/dist/assets/components/file-uploader/components/file-uploader-file-card/file-uploader-file-card.vue +1 -0
- package/dist/assets/components/file-uploader/components/file-uploader-restrictions/file-uploader-restrictions.stories.ts +1 -0
- package/dist/assets/components/file-uploader/components/file-uploader-restrictions/file-uploader-restrictions.vue +1 -0
- package/dist/assets/components/file-uploader/file-uploader.docs.mdx +117 -0
- package/dist/assets/components/file-uploader/file-uploader.stories.ts +1 -0
- package/dist/assets/components/file-uploader/file-uploader.vue +1 -0
- package/dist/assets/components/filter-bar/components/all-filters-modal/all-filters-modal.stories.ts +1 -0
- package/dist/assets/components/filter-bar/components/all-filters-modal/all-filters-modal.vue +1 -0
- package/dist/assets/components/filter-bar/components/all-filters-modal/components/all-filters-modal-button/all-filters-modal-button.vue +1 -0
- package/dist/assets/components/filter-bar/components/base-filter/base-filter.docs.mdx +90 -0
- package/dist/assets/components/filter-bar/components/base-filter/base-filter.stories.ts +1 -0
- package/dist/assets/components/filter-bar/components/base-filter/base-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/base-filter-button/base-filter-button.vue +1 -0
- package/dist/assets/components/filter-bar/components/boolean-filter/boolean-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/checkbox-group-filter/checkbox-group-filter.stories.ts +1 -0
- package/dist/assets/components/filter-bar/components/checkbox-group-filter/checkbox-group-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/date-filter/date-filter.stories.ts +1 -0
- package/dist/assets/components/filter-bar/components/date-filter/date-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/date-range-filter/date-range-filter.stories.ts +1 -0
- package/dist/assets/components/filter-bar/components/date-range-filter/date-range-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/filter-bar-button/filter-bar-button.vue +1 -0
- package/dist/assets/components/filter-bar/components/filter-bar-button-group/filter-bar-button-group.vue +1 -0
- package/dist/assets/components/filter-bar/components/input-filter/input-filter.docs.mdx +18 -0
- package/dist/assets/components/filter-bar/components/input-filter/input-filter.stories.ts +1 -0
- package/dist/assets/components/filter-bar/components/input-filter/input-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/input-with-dropdown-filter/input-with-dropdown-filter.docs.mdx +18 -0
- package/dist/assets/components/filter-bar/components/input-with-dropdown-filter/input-with-dropdown-filter.stories.ts +1 -0
- package/dist/assets/components/filter-bar/components/input-with-dropdown-filter/input-with-dropdown-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/radio-group-filter/radio-group-filter.stories.ts +1 -0
- package/dist/assets/components/filter-bar/components/radio-group-filter/radio-group-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/range-filter/range-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/select-filter/components/select-filter-button/select-filter-button.vue +1 -0
- package/dist/assets/components/filter-bar/components/select-filter/components/select-filter-dropdown/select-filter-dropdown.vue +1 -0
- package/dist/assets/components/filter-bar/components/select-filter/select-filter.stories.ts +1 -0
- package/dist/assets/components/filter-bar/components/select-filter/select-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/select-filter/variants/select-avatar-filter/select-avatar-filter.stories.ts +1 -0
- package/dist/assets/components/filter-bar/components/select-filter/variants/select-avatar-filter/select-avatar-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/select-filter/variants/select-country-filter/select-country-filter.stories.ts +1 -0
- package/dist/assets/components/filter-bar/components/select-filter/variants/select-country-filter/select-country-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/select-filter/variants/select-currency-filter/select-currency-filter.stories.ts +1 -0
- package/dist/assets/components/filter-bar/components/select-filter/variants/select-currency-filter/select-currency-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/select-filter/variants/select-payment-method-filter/select-payment-method-filter.stories.ts +1 -0
- package/dist/assets/components/filter-bar/components/select-filter/variants/select-payment-method-filter/select-payment-method-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/select-filter/variants/select-tag-filter/select-tag-filter.stories.ts +1 -0
- package/dist/assets/components/filter-bar/components/select-filter/variants/select-tag-filter/select-tag-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/toggle-filter/toggle-filter.stories.ts +1 -0
- package/dist/assets/components/filter-bar/components/toggle-filter/toggle-filter.vue +1 -0
- package/dist/assets/components/filter-bar/filter-bar.docs.mdx +1005 -0
- package/dist/assets/components/filter-bar/filter-bar.stories.ts +1 -0
- package/dist/assets/components/filter-bar/filter-bar.vue +1 -0
- package/dist/assets/components/focus-trap/focus-trap.docs.mdx +10 -0
- package/dist/assets/components/focus-trap/focus-trap.stories.ts +1 -0
- package/dist/assets/components/focus-trap/focus-trap.vue +1 -0
- package/dist/assets/components/form-layout/components/form-layout-field-tip/form-layout-field-tip.vue +1 -0
- package/dist/assets/components/form-layout/components/form-layout-group/form-layout-group.vue +1 -0
- package/dist/assets/components/form-layout/components/form-layout-title/form-layout-title.vue +1 -0
- package/dist/assets/components/form-layout/form-layout.docs.mdx +452 -0
- package/dist/assets/components/form-layout/form-layout.stories.ts +1 -0
- package/dist/assets/components/form-layout/form-layout.vue +1 -0
- package/dist/assets/components/header/header.docs.mdx +85 -0
- package/dist/assets/components/header/header.stories.ts +1 -0
- package/dist/assets/components/header/header.vue +1 -0
- package/dist/assets/components/header-with-views/components/header-with-views-all-views/header-with-views-all-views.vue +1 -0
- package/dist/assets/components/header-with-views/components/header-with-views-item-edit-form/header-with-views-edit-form.vue +1 -0
- package/dist/assets/components/header-with-views/header-with-views.docs.mdx +103 -0
- package/dist/assets/components/header-with-views/header-with-views.stories.ts +1 -0
- package/dist/assets/components/header-with-views/header-with-views.vue +1 -0
- package/dist/assets/components/image/image.docs.mdx +34 -0
- package/dist/assets/components/image/image.stories.ts +1 -0
- package/dist/assets/components/image/image.vue +1 -0
- package/dist/assets/components/info-icon/info-icon.docs.mdx +57 -0
- package/dist/assets/components/info-icon/info-icon.stories.ts +1 -0
- package/dist/assets/components/info-icon/info-icon.vue +1 -0
- package/dist/assets/components/input-field/input-field.docs.mdx +141 -0
- package/dist/assets/components/input-field/input-field.stories.ts +1 -0
- package/dist/assets/components/input-field/input-field.vue +1 -0
- package/dist/assets/components/input-field-password/input-field-password.docs.mdx +292 -0
- package/dist/assets/components/input-field-password/input-field-password.stories.ts +1 -0
- package/dist/assets/components/input-field-password/input-field-password.vue +1 -0
- package/dist/assets/components/input-field-phone-number/input-field-phone-number.docs.mdx +72 -0
- package/dist/assets/components/input-field-phone-number/input-field-phone-number.stories.ts +1 -0
- package/dist/assets/components/input-field-phone-number/input-field-phone-number.vue +1 -0
- package/dist/assets/components/inspector/inspector.docs.mdx +118 -0
- package/dist/assets/components/inspector/inspector.stories.ts +1 -0
- package/dist/assets/components/inspector/inspector.vue +1 -0
- package/dist/assets/components/internal/base-button/base-button.vue +1 -0
- package/dist/assets/components/internal/button-actions-with-menu/button-actions-with-menu.stories.ts +1 -0
- package/dist/assets/components/internal/button-actions-with-menu/button-actions-with-menu.vue +1 -0
- package/dist/assets/components/internal/calendar/calendar.stories.ts +1 -0
- package/dist/assets/components/internal/calendar/calendar.vue +1 -0
- package/dist/assets/components/internal/calendar/components/calendar-month/calendar-month.stories.ts +1 -0
- package/dist/assets/components/internal/calendar/components/calendar-month/calendar-month.vue +1 -0
- package/dist/assets/components/internal/calendar/components/calendar-month/components/calendar-month-day/__test__/calendar-month-day-stub.vue +1 -0
- package/dist/assets/components/internal/calendar/components/calendar-month/components/calendar-month-day/calendar-month-day.stories.ts +1 -0
- package/dist/assets/components/internal/calendar/components/calendar-month/components/calendar-month-day/calendar-month-day.vue +1 -0
- package/dist/assets/components/internal/calendar/components/calendar-year/calendar-year.vue +1 -0
- package/dist/assets/components/internal/calendar/components/calendar-year/components/calendar-year-month/__test__/calendar-year-month-stub.vue +1 -0
- package/dist/assets/components/internal/calendar/components/calendar-year/components/calendar-year-month/calendar-year-month.vue +1 -0
- package/dist/assets/components/internal/checkbox-input/checkbox-input.stories.ts +1 -0
- package/dist/assets/components/internal/checkbox-input/checkbox-input.vue +1 -0
- package/dist/assets/components/internal/controls-group/controls-group.vue +1 -0
- package/dist/assets/components/internal/copy/copy.docs.mdx +43 -0
- package/dist/assets/components/internal/copy/copy.stories.ts +1 -0
- package/dist/assets/components/internal/copy/copy.vue +1 -0
- package/dist/assets/components/internal/decorative-checkbox/decorative-checkbox.vue +1 -0
- package/dist/assets/components/internal/dialog-page/dialog-page.vue +1 -0
- package/dist/assets/components/internal/error-message/error-message.vue +1 -0
- package/dist/assets/components/internal/field-label/field-label.vue +1 -0
- package/dist/assets/components/internal/fixed-scroller/fixed-scroller.docs.mdx +21 -0
- package/dist/assets/components/internal/fixed-scroller/fixed-scroller.stories.ts +1 -0
- package/dist/assets/components/internal/fixed-scroller/fixed-scroller.vue +1 -0
- package/dist/assets/components/internal/footer-actions/footer-actions.vue +1 -0
- package/dist/assets/components/internal/grid-layout/grid-layout.vue +1 -0
- package/dist/assets/components/internal/header-meta/header-meta.vue +1 -0
- package/dist/assets/components/internal/info-icon-with-popover/info-icon-with-popover.vue +1 -0
- package/dist/assets/components/internal/listbox/components/listbox-multi-select/components/listbox-multi-select-options/components/listbox-multi-select-category/listbox-multi-select-category.vue +1 -0
- package/dist/assets/components/internal/listbox/components/listbox-multi-select/components/listbox-multi-select-options/listbox-multi-select-options.vue +1 -0
- package/dist/assets/components/internal/listbox/components/listbox-multi-select/listbox-multi-select.vue +1 -0
- package/dist/assets/components/internal/listbox/components/listbox-option/listbox-option.vue +1 -0
- package/dist/assets/components/internal/listbox/components/listbox-option-tag/listbox-option-tag.vue +1 -0
- package/dist/assets/components/internal/listbox/components/listbox-single-select/components/listbox-single-select-category/listbox-single-select-category.vue +1 -0
- package/dist/assets/components/internal/listbox/components/listbox-single-select/listbox-single-select.vue +1 -0
- package/dist/assets/components/internal/listbox/components/listbox-single-select-option/listbox-single-select-option.vue +1 -0
- package/dist/assets/components/internal/listbox/listbox.stories.ts +1 -0
- package/dist/assets/components/internal/listbox/listbox.vue +1 -0
- package/dist/assets/components/internal/radio-input/radio-input.stories.ts +1 -0
- package/dist/assets/components/internal/radio-input/radio-input.vue +1 -0
- package/dist/assets/components/internal/teleport/teleport.vue +1 -0
- package/dist/assets/components/internal/tooltip/tooltip.test.vue +1 -0
- package/dist/assets/components/internal/tooltip/tooltip.vue +1 -0
- package/dist/assets/components/layout/components/layout-row/layout-row.stories.ts +1 -0
- package/dist/assets/components/layout/components/layout-row/layout-row.vue +1 -0
- package/dist/assets/components/layout/layout.docs.mdx +108 -0
- package/dist/assets/components/layout/layout.stories.ts +1 -0
- package/dist/assets/components/layout/layout.vue +1 -0
- package/dist/assets/components/link/link.docs.mdx +119 -0
- package/dist/assets/components/link/link.stories.ts +1 -0
- package/dist/assets/components/link/link.vue +1 -0
- package/dist/assets/components/loading-indicator/loading-indicator.docs.mdx +65 -0
- package/dist/assets/components/loading-indicator/loading-indicator.stories.ts +1 -0
- package/dist/assets/components/loading-indicator/loading-indicator.vue +1 -0
- package/dist/assets/components/menu/components/menu-item/menu-item.vue +1 -0
- package/dist/assets/components/menu/components/menu-item-list/menu-item-list.vue +1 -0
- package/dist/assets/components/menu/components/menu-list-container/menu-list-container.vue +1 -0
- package/dist/assets/components/menu/components/sub-menu-item-list/sub-menu-item-list.vue +1 -0
- package/dist/assets/components/menu/menu.docs.mdx +121 -0
- package/dist/assets/components/menu/menu.stories.ts +1 -0
- package/dist/assets/components/menu/menu.vue +1 -0
- package/dist/assets/components/modal/components/base-modal/base-modal.vue +1 -0
- package/dist/assets/components/modal/components/modal-default/modal-default.vue +1 -0
- package/dist/assets/components/modal/components/modal-dialog/modal-dialog.vue +1 -0
- package/dist/assets/components/modal/components/modal-page/modal-page.vue +1 -0
- package/dist/assets/components/modal/modal.docs.mdx +215 -0
- package/dist/assets/components/modal/modal.stories.ts +1 -0
- package/dist/assets/components/modal/modal.vue +1 -0
- package/dist/assets/components/modal-fullscreen/components/modal-fullscreen-page.vue +1 -0
- package/dist/assets/components/modal-fullscreen/modal-fullscreen.docs.mdx +217 -0
- package/dist/assets/components/modal-fullscreen/modal-fullscreen.stories.ts +1 -0
- package/dist/assets/components/modal-fullscreen/modal-fullscreen.vue +1 -0
- package/dist/assets/components/pagination/components/pagination-context/pagination-context.vue +1 -0
- package/dist/assets/components/pagination/components/pagination-controls/pagination-controls.vue +1 -0
- package/dist/assets/components/pagination/components/pagination-results-per-page/pagination-results-per-page.vue +1 -0
- package/dist/assets/components/pagination/pagination.docs.mdx +116 -0
- package/dist/assets/components/pagination/pagination.stories.ts +1 -0
- package/dist/assets/components/pagination/pagination.vue +1 -0
- package/dist/assets/components/payment-method/payment-method.docs.mdx +34 -0
- package/dist/assets/components/payment-method/payment-method.stories.ts +1 -0
- package/dist/assets/components/payment-method/payment-method.vue +1 -0
- package/dist/assets/components/popover/components/popover-dismiss-button/popover-dismiss-button.vue +1 -0
- package/dist/assets/components/popover/components/popover-image/popover-image.vue +1 -0
- package/dist/assets/components/popover/components/popover-title/popover-title.vue +1 -0
- package/dist/assets/components/popover/popover.docs.mdx +89 -0
- package/dist/assets/components/popover/popover.stories.ts +1 -0
- package/dist/assets/components/popover/popover.vue +1 -0
- package/dist/assets/components/popper-container/popper-container.docs.mdx +11 -0
- package/dist/assets/components/popper-container/popper-container.stories.ts +1 -0
- package/dist/assets/components/popper-container/popper-container.vue +1 -0
- package/dist/assets/components/promo-banner/promo-banner.docs.mdx +94 -0
- package/dist/assets/components/promo-banner/promo-banner.stories.ts +1 -0
- package/dist/assets/components/promo-banner/promo-banner.vue +1 -0
- package/dist/assets/components/radio-group/components/radio-button/radio-button.docs.mdx +76 -0
- package/dist/assets/components/radio-group/components/radio-button/radio-button.stories.ts +1 -0
- package/dist/assets/components/radio-group/components/radio-button/radio-button.vue +1 -0
- package/dist/assets/components/radio-group/radio-group.docs.mdx +154 -0
- package/dist/assets/components/radio-group/radio-group.stories.ts +1 -0
- package/dist/assets/components/radio-group/radio-group.vue +1 -0
- package/dist/assets/components/rich-text-editor/components/rich-text-editor-content-area/rich-text-editor-content-area.vue +1 -0
- package/dist/assets/components/rich-text-editor/components/rich-text-editor-renderer/components/rich-text-editor-code-block/rich-text-editor-code-block.vue +1 -0
- package/dist/assets/components/rich-text-editor/components/rich-text-editor-renderer/components/rich-text-editor-inline-code/rich-text-editor-inline-code.vue +1 -0
- package/dist/assets/components/rich-text-editor/components/rich-text-editor-renderer/components/rich-text-editor-link/rich-text-editor-link.vue +1 -0
- package/dist/assets/components/rich-text-editor/components/rich-text-editor-renderer/components/rich-text-editor-list-item/rich-text-editor-list-item.vue +1 -0
- package/dist/assets/components/rich-text-editor/components/rich-text-editor-renderer/components/rich-text-editor-table/rich-text-editor-table.vue +1 -0
- package/dist/assets/components/rich-text-editor/rich-text-editor.docs.mdx +52 -0
- package/dist/assets/components/rich-text-editor/rich-text-editor.stories.ts +1 -0
- package/dist/assets/components/rich-text-editor/rich-text-editor.vue +1 -0
- package/dist/assets/components/search-bar/search-bar.docs.mdx +117 -0
- package/dist/assets/components/search-bar/search-bar.stories.ts +1 -0
- package/dist/assets/components/search-bar/search-bar.vue +1 -0
- package/dist/assets/components/secondary-nav/components/secondary-nav-category/secondary-nav-category.vue +1 -0
- package/dist/assets/components/secondary-nav/components/secondary-nav-item/secondary-nav-item.vue +1 -0
- package/dist/assets/components/secondary-nav/secondary-nav.docs.mdx +40 -0
- package/dist/assets/components/secondary-nav/secondary-nav.stories.ts +1 -0
- package/dist/assets/components/secondary-nav/secondary-nav.vue +1 -0
- package/dist/assets/components/segmented-control/components/segmented-control-button/segmented-control-button.vue +1 -0
- package/dist/assets/components/segmented-control/segmented-control.docs.mdx +90 -0
- package/dist/assets/components/segmented-control/segmented-control.stories.ts +1 -0
- package/dist/assets/components/segmented-control/segmented-control.vue +1 -0
- package/dist/assets/components/selection-card/components/selection-card-group/selection-card-group.docs.mdx +119 -0
- package/dist/assets/components/selection-card/components/selection-card-group/selection-card-group.stories.ts +1 -0
- package/dist/assets/components/selection-card/components/selection-card-group/selection-card-group.vue +1 -0
- package/dist/assets/components/selection-card/selection-card.docs.mdx +108 -0
- package/dist/assets/components/selection-card/selection-card.stories.ts +1 -0
- package/dist/assets/components/selection-card/selection-card.vue +1 -0
- package/dist/assets/components/sidepanel/components/sidepanel-page.stories.ts +1 -0
- package/dist/assets/components/sidepanel/components/sidepanel-page.vue +1 -0
- package/dist/assets/components/sidepanel/sidepanel.docs.mdx +129 -0
- package/dist/assets/components/sidepanel/sidepanel.stories.ts +1 -0
- package/dist/assets/components/sidepanel/sidepanel.vue +1 -0
- package/dist/assets/components/status/status.docs.mdx +62 -0
- package/dist/assets/components/status/status.stories.ts +1 -0
- package/dist/assets/components/status/status.vue +1 -0
- package/dist/assets/components/stepper/components/step/step.stories.ts +1 -0
- package/dist/assets/components/stepper/components/step/step.vue +1 -0
- package/dist/assets/components/stepper/stepper.docs.mdx +127 -0
- package/dist/assets/components/stepper/stepper.stories.ts +1 -0
- package/dist/assets/components/stepper/stepper.vue +1 -0
- package/dist/assets/components/structured-list/components/structered-list-item/structured-list-item.stories.ts +1 -0
- package/dist/assets/components/structured-list/components/structered-list-item/structured-list-item.vue +1 -0
- package/dist/assets/components/structured-list/structured-list.docs.mdx +68 -0
- package/dist/assets/components/structured-list/structured-list.stories.ts +1 -0
- package/dist/assets/components/structured-list/structured-list.vue +1 -0
- package/dist/assets/components/summary-grid/components/summary-grid-item/summary-grid-item.vue +1 -0
- package/dist/assets/components/summary-grid/components/summary-grid-item-amount/summary-grid-item-amount.stories.ts +1 -0
- package/dist/assets/components/summary-grid/components/summary-grid-item-amount/summary-grid-item-amount.vue +1 -0
- package/dist/assets/components/summary-grid/components/summary-grid-item-custom/summary-grid-item-custom.stories.ts +1 -0
- package/dist/assets/components/summary-grid/components/summary-grid-item-custom/summary-grid-item-custom.vue +1 -0
- package/dist/assets/components/summary-grid/components/summary-grid-item-numeric/summary-grid-item-numeric.stories.ts +1 -0
- package/dist/assets/components/summary-grid/components/summary-grid-item-numeric/summary-grid-item-numeric.vue +1 -0
- package/dist/assets/components/summary-grid/components/summary-grid-item-percentage/summary-grid-item-percentage.stories.ts +1 -0
- package/dist/assets/components/summary-grid/components/summary-grid-item-percentage/summary-grid-item-percentage.vue +1 -0
- package/dist/assets/components/summary-grid/components/summary-grid-item-text/summary-grid-item-text.stories.ts +1 -0
- package/dist/assets/components/summary-grid/components/summary-grid-item-text/summary-grid-item-text.vue +1 -0
- package/dist/assets/components/summary-grid/summary-grid.docs.mdx +152 -0
- package/dist/assets/components/summary-grid/summary-grid.stories.ts +1 -0
- package/dist/assets/components/summary-grid/summary-grid.vue +1 -0
- package/dist/assets/components/tabs/components/tab.vue +1 -0
- package/dist/assets/components/tabs/tabs.docs.mdx +98 -0
- package/dist/assets/components/tabs/tabs.stories.ts +1 -0
- package/dist/assets/components/tabs/tabs.vue +1 -0
- package/dist/assets/components/tag/tag.docs.mdx +137 -0
- package/dist/assets/components/tag/tag.stories.ts +1 -0
- package/dist/assets/components/tag/tag.vue +1 -0
- package/dist/assets/components/textarea/textarea.docs.mdx +53 -0
- package/dist/assets/components/textarea/textarea.stories.ts +1 -0
- package/dist/assets/components/textarea/textarea.vue +1 -0
- package/dist/assets/components/timeline/components/timeline-item/timeline-item.stories.ts +1 -0
- package/dist/assets/components/timeline/components/timeline-item/timeline-item.vue +1 -0
- package/dist/assets/components/timeline/timeline.docs.mdx +131 -0
- package/dist/assets/components/timeline/timeline.stories.ts +1 -0
- package/dist/assets/components/timeline/timeline.vue +1 -0
- package/dist/assets/components/toast/components/toast-item/toast-item.vue +1 -0
- package/dist/assets/components/toast/components/toast-shadow-stack/toast-shadow-stack.vue +1 -0
- package/dist/assets/components/toast/toast.docs.mdx +151 -0
- package/dist/assets/components/toast/toast.stories.ts +1 -0
- package/dist/assets/components/toast/toast.vue +1 -0
- package/dist/assets/components/toggle/toggle.docs.mdx +81 -0
- package/dist/assets/components/toggle/toggle.stories.ts +1 -0
- package/dist/assets/components/toggle/toggle.vue +1 -0
- package/dist/assets/components/tutorial/components/tutorial-popover/components/tutorial-indicator/tutorial-indicator.vue +1 -0
- package/dist/assets/components/tutorial/components/tutorial-popover/tutorial-popover.vue +1 -0
- package/dist/assets/components/tutorial/tutorial.docs.mdx +85 -0
- package/dist/assets/components/tutorial/tutorial.stories.ts +1 -0
- package/dist/assets/components/tutorial/tutorial.vue +1 -0
- package/dist/assets/components/typography/typography.docs.mdx +124 -0
- package/dist/assets/components/typography/typography.stories.ts +1 -0
- package/dist/assets/components/typography/typography.vue +1 -0
- package/dist/assets/composables/use-bento-currency/use-bento-currency.docs.mdx +28 -0
- package/dist/assets/composables/use-bento-currency/use-bento-currency.stories.ts +1 -0
- package/dist/assets/composables/use-click-outside/use-click-outside.docs.mdx +10 -0
- package/dist/assets/composables/use-click-outside/use-click-outside.stories.ts +1 -0
- package/dist/assets/directives/highlight/highlight.docs.mdx +64 -0
- package/dist/assets/directives/highlight/highlight.stories.ts +1 -0
- package/dist/assets/directives/keyboard-navigation/keyboard-navigation.docs.mdx +41 -0
- package/dist/assets/directives/keyboard-navigation/keyboard-navigation.stories.ts +1 -0
- package/dist/assets/directives/tooltip/tooltip.docs.mdx +86 -0
- package/dist/assets/directives/tooltip/tooltip.stories.ts +1 -0
- package/dist/assets/index.ts +1 -0
- package/dist/assets/templates/dashboard-template/components/dashboard-section/dashboard-section.stories.ts +1 -0
- package/dist/assets/templates/dashboard-template/components/dashboard-section/dashboard-section.vue +1 -0
- package/dist/assets/templates/dashboard-template/dashboard-template.docs.mdx +148 -0
- package/dist/assets/templates/dashboard-template/dashboard-template.stories.ts +1 -0
- package/dist/assets/templates/dashboard-template/dashboard-template.vue +1 -0
- package/dist/assets/templates/data-grid-template/data-grid-template.docs.mdx +117 -0
- package/dist/assets/templates/data-grid-template/data-grid-template.stories.ts +1 -0
- package/dist/assets/templates/data-grid-template/data-grid-template.vue +1 -0
- package/dist/main.js +30031 -0
- package/package.json +45 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <Transition name="b-popover__animation"> <popper-container v-if="open" :id="computedAttributes['id']" :aria-role="computedAttributes.role" :aria-label="computedAriaLabel" :arrow="{ classes: ['b-popover__arrow'] }" :disabled-focus-trap="disableFocusTrap" :trap-all="trapAll" :trap-all-options="trapAllOptions" :class="conditionalClasses" :fallback-position="fallbackPosition" :fixed-positioning="fixedPositioning" :offset="[0, offset]" :position="position" :target-element="targetElement" class="b-popover" @placement="$emit('placement', $event)" > <div v-if="hasSlot('image')" class="b-popover__image"> <div v-if="dismissible" class="b-popover__image-dismiss-button"> <bento-popover-dismiss-button on-image @click="dismissPopover" /> </div> <slot name="image"></slot> </div> <div v-if="title" class="b-popover__header"> <div class="b-popover__header-title"> <slot v-if="hasSlot('headerIcon')" name="headerIcon"></slot> <bento-popover-title :title="title" :is-image-title="!!hasSlot('image')" /> </div> <div v-if="dismissible && !hasSlot('image')"> <bento-popover-dismiss-button @click="dismissPopover" /> </div> </div> <div v-if="hasSlot('default')" class="b-popover__content" :class="contentConditionalClasses"> <slot></slot> </div> <div v-if="actions" class="b-popover__footer"> <bento-button-actions :actions="actions" :layout="actionsLayout"></bento-button-actions> </div> </popper-container> </Transition> </template> <script setup lang="ts"> import { computed, inject, ref, toRefs, useAttrs, useSlots } from 'vue'; import BentoPopoverDismissButton from './components/popover-dismiss-button/popover-dismiss-button.vue'; import BentoPopoverTitle from './components/popover-title/popover-title.vue'; import BentoButtonActions from '@/components/button/components/button-actions/button-actions.vue'; import { PopperContainer, PopperContainerAriaRole } from '@/components/popper-container'; import { useHasSlot } from '@/composables/use-has-slot/use-has-slot'; import { INPUT_FILTER_POPOVER_INJECTION_KEY } from '@/components/filter-bar/components/input-filter/input-filter.keys'; import { INPUT_FILTER_WITH_DROPDOWN_POPOVER_INJECTION_KEY } from '@/components/filter-bar/components/input-with-dropdown-filter/input-with-dropdown-filter.keys'; import { useAriaLabel } from '@/utils/ts/aria-label'; import { POPOVER_OFFSET_INJECTION_KEY } from './popover.keys'; import type { Placement } from '@floating-ui/vue'; import type { BentoPopoverProps } from './popover.types'; const DEFAULT_OFFSET = 2; const props = withDefaults(defineProps<BentoPopoverProps>(), { actions: undefined, actionsLayout: 'buttons-end', ariaLabel: undefined, divider: false, disableFocusTrap: false, dismissible: false, fallbackPosition: () => ['bottom-start', 'right-start', 'top-start', 'left-start'], fitContent: false, fixedPositioning: false, open: false, large: false, modifiers: undefined, position: 'bottom-start', small: false, title: undefined, trapAll: false, trapAllOptions: undefined, withoutSpace: false, overflowVisible: false, }); const emit = defineEmits<{ /** * Triggered when the `dismiss` button is clicked */ (e: 'dismiss'): void; /** * Triggered when the popover `placement` changes */ (e: 'placement', placement: Placement): void; }>(); const slots = useSlots(); const attrs = useAttrs(); const computedAttributes = computed(() => ({ id: attrs.id?.toString() as string, role: attrs?.['role'] === null ? undefined : PopperContainerAriaRole.POPOVER, })); const offset = inject<number>(POPOVER_OFFSET_INJECTION_KEY, DEFAULT_OFFSET); const ariaLabelAttribute = ref(attrs['aria-label']); const { ariaLabel, title } = toRefs(props); const computedAriaLabel = useAriaLabel({ ariaLabel: ariaLabelAttribute.value as string, // TODO: take this line away when the aria-label props is removed label: ariaLabel, defaultFallback: title, }); const isOverflowVisible = inject(INPUT_FILTER_WITH_DROPDOWN_POPOVER_INJECTION_KEY, false) || inject(INPUT_FILTER_POPOVER_INJECTION_KEY, false); const conditionalClasses = computed(() => ({ 'b-popover--small': props.small && !props.large, 'b-popover--with-divider': !!props.divider, 'b-popover--large': props.large && !props.small, 'b-popover--fit-content': props.fitContent, 'b-popover--without-space': props.withoutSpace, 'b-popover--overflow-visible': props.overflowVisible || isOverflowVisible, })); const contentConditionalClasses = computed(() => ({ 'b-popover__content--with-title': !!props.title, })); const dismissPopover = () => emit('dismiss'); const hasSlot = useHasSlot(slots); </script> <script lang="ts"> /** * Example description of the component... * * @example * import { BentoPopover } from '@adyen/adl-vue2'; * * export default { * components: { BentoPopover }, * template: ` * <bento-popover id="your-popover-id"> * ...Example code of the component * </bento-popover> * ` * } */ export default {}; </script> <style lang="scss" scoped src="./popover.scss" />
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Canvas, Story, Meta } from '@storybook/blocks';
|
|
2
|
+
import * as ComponentStories from './popper-container.stories';
|
|
3
|
+
|
|
4
|
+
<Meta of={ComponentStories} />
|
|
5
|
+
|
|
6
|
+
# Popper Container / Popover
|
|
7
|
+
|
|
8
|
+
A popover is a window that opens when a user clicks on an element. A popover can contain other components like buttons
|
|
9
|
+
or lists
|
|
10
|
+
|
|
11
|
+
<Canvas of={ComponentStories.Default} />
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import { action } from '@storybook/addon-actions'; import { PopperContainerPosition } from './popper-container.types'; import { isVue2 } from 'vue-demi'; import type { Meta, StoryObj } from '@storybook/vue'; import PopperContainer from './popper-container.vue'; const meta: Meta = { title: 'Popper Container', component: PopperContainer, argTypes: { fixedPositioning: { control: 'boolean' }, position: { options: Object.values(PopperContainerPosition), mapping: PopperContainerPosition, control: { type: 'select', }, }, fallbackPosition: { options: Object.values(PopperContainerPosition), mapping: PopperContainerPosition, control: { type: 'select', }, }, withArrow: { control: 'boolean' }, }, args: { fixedPositioning: false }, }; export default meta; type Story = StoryObj<typeof PopperContainer>; export const Default: Story = { render: (_args, { argTypes }) => ({ components: { PopperContainer }, props: Object.keys(argTypes), data: () => ({ isOpen: false }), setup(props) { const onPlacementUpdate = action('placement'); return { // Values args: isVue2 ? props : _args, // Methods onPlacementUpdate, }; }, template: ` <div style="margin: 150px"> <button ref="target" @click="isOpen = !isOpen">Toggle popover</button> <popper-container v-if="isOpen" v-bind="args" :target-element="$refs.target" :arrow="args.withArrow ? { strokeClass: this.arrowStrokeClass, fillClass: this.arrowFillClass, padding: this.arrowPadding } : false" :offset="[args.offsetSkidding, args.offsetDistance]" @placement="onPlacementUpdate" > <div class="adl2-card__body"> <p class="adl2-text">I'm a popping card!</p> </div> </popper-container> </div> `, }), args: { position: 'auto', withArrow: false, arrowStrokeClass: 'arrow-stroke-class', arrowFillClass: 'arrow-fill-class', arrowPadding: 0, offsetSkidding: 0, offsetDistance: 0, matchWidth: false, }, };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <bento-focus-trap v-bind="$attrs" :id="id" ref="popperContainerRef" class="b-popper-container" :data-popper-placement="placement" :disabled="disabledFocusTrap" :style="floatingStyles" :role="ariaRole" :trap-all="trapAll" :trap-all-options="trapAllOptions" > <slot></slot> <span v-if="props.arrow" v-bind="arrowAttributes" ref="floatingArrow" class="b-popper-container__arrow" data-testid="arrow" ></span> </bento-focus-trap> </template> <script setup lang="ts"> import type {} from '@floating-ui/core'; import { computed, type PropType, type Ref, ref, toRefs, watch } from 'vue'; import { arrow as arrowMiddleware, autoPlacement, autoUpdate, flip, // eslint-disable-next-line vue/no-dupe-keys offset, shift, useFloating, } from '@floating-ui/vue'; import { BentoFocusTrap } from '@/components/focus-trap'; import { type FallbackPositionProp, PopperContainerAriaRole, type PopperContainerArrowProp, PopperContainerPosition, type PopperContainerPositionBasic, } from './popper-container.types'; import type { VueNodeElement } from '@/types/elements'; import type { AutoPlacementOptions, Middleware, Placement, Strategy } from '@floating-ui/vue'; import { getFlipModifier, getOffsetModifier, sameWidthMiddleware } from './popper-container.utils'; import type { CSSProperties } from 'vue/types/jsx.d.ts'; import { type Options as FocusTrapOptions } from 'focus-trap'; const props = defineProps({ /** * The aria role assigned to the container. Defaults to null, which means it's not set if nothing is passed. */ ariaRole: { type: String as PropType<PopperContainerAriaRole>, default: null, validator: value => Object.values(PopperContainerAriaRole).includes(value as PopperContainerAriaRole), }, /** * Indicates if container should have additional arrow, pointing at target element * Can be just a boolean flag, or an object with additional configuration: * @property {Array<string>} arrow.classes – apply any additional classes to the arrow element. * @property {string} arrow.padding – useful when content has rounded corners, @see {@link https://floating-ui.com/docs/arrow#padding} */ arrow: { type: Object as PropType<PopperContainerArrowProp | boolean>, default: undefined }, /** * Disables wrapping focus-trap component. * Mostly useful for the storybook demo. */ disabledFocusTrap: { type: Boolean, default: false }, /** * Allows to configure to fallback position to the one specified, it could be 'auto' or ['right', 'top', ...]. * Useful when not using auto as starting placement. * @see {@link https://floating-ui.com/docs/flip#fallbackplacements} */ fallbackPosition: { type: [Array, String] as PropType<FallbackPositionProp>, default: null, }, /** * Instructs popover to be visible outside of the containing container. * Useful when component would be used in limited containers like Modal, Side panel, etc. * @see {@link https://floating-ui.com/docs/usefloating#strategy} */ fixedPositioning: { type: Boolean, default: false }, /** * The id to be set on the component. Use it for accessibility purposes. */ id: { type: String, default: null }, /** * If hiding / showing popper element then use this property to tell the popper when to update it's position. * This will drastically help improve performance, as we will not need to track hidden elements. */ isHidden: { type: Boolean, default: null }, /** * Instructs popper container to have the same width as target element. */ matchWidth: { type: Boolean, default: false }, /** * Custom set of middleware. Useful for low-level control over Popper behavior in some really complex use cases. * @see {@link https://floating-ui.com/docs/middleware} */ middleware: { type: Array as PropType<Array<Middleware>>, default: null }, /** * Allows to displace a popper container from its target element. * @see {@link https://floating-ui.com/docs/offset#options} */ offset: { type: Array as PropType<Array<number>>, default: () => [0, 0], validator: (value: Array<number>) => value?.length === 2, }, /** * Position relative to the target element * @see {@link https://floating-ui.com/docs/computeposition#placement} */ position: { type: String as PropType<PopperContainerPosition | `${PopperContainerPosition}`>, default: PopperContainerPosition.AUTO, validator: value => Object.values(PopperContainerPosition).includes(value as PopperContainerPositionBasic), }, /** * Reference to the DOM element or Vue component for positioning */ targetElement: { type: [Element, Object] as PropType<VueNodeElement>, required: true }, /** * Activates focus trap for both arrow and tab keys. */ trapAll: { type: Boolean, default: false }, /** * Configurations for the focus-trap when trap-all is active. */ trapAllOptions: { type: Object as PropType<FocusTrapOptions>, default: undefined, }, }); const emit = defineEmits<{ /** * Emits new placement of popper container when it's placement is updated. */ (e: 'placement', value: Placement): void; }>(); // Popper / floating ui logic const { position } = toRefs(props); const popperContainerRef = ref(); const floatingArrow = ref(null); const emittedPosition = ref(props.position); const targetElement = computed(() => (props.targetElement as any).$el ? ((props.targetElement as any).$el as Element) : (props.targetElement as Element) ); const popperContainerElement = computed(() => (popperContainerRef.value as any).$el ? ((popperContainerRef.value as any).$el as HTMLElement) : (popperContainerRef.value as HTMLElement) ); const isFlipMiddleware = computed(() => props?.position !== 'auto' && props?.fallbackPosition !== 'auto'); const getAutoPlacementProps = computed<AutoPlacementOptions>(() => { switch (props.position) { case 'auto-start': return { alignment: 'start', crossAxis: true }; case 'auto-end': return { alignment: 'end', crossAxis: true }; default: return undefined; } }); const strategy = computed<Strategy>(() => (props.fixedPositioning ? 'fixed' : 'absolute')); const { floatingStyles, middlewareData, placement, update } = useFloating(targetElement, popperContainerRef, { placement: position as Ref<Placement>, middleware: [ // Adds flip modifier if fallbackPosition is not set to auto ...(isFlipMiddleware.value ? [ flip({ fallbackPlacements: getFlipModifier(props.fallbackPosition).fallbackPlacements, }), ] : []), // Adds autoposition middlelare if position is set to any auto variants ...(!isFlipMiddleware.value ? [autoPlacement(getAutoPlacementProps.value)] : []), // Add shifting middle in case popper is still not fully visible in view port shift(), // Adds offset middlelare if offset property is set ...(props.offset ? [offset(getOffsetModifier(props.arrow, props.offset))] : []), // Adds arrow middlelare if arrow property is set, can set additional padding property ...(props.arrow ? [arrowMiddleware({ element: floatingArrow, padding: props.arrow.padding || 0 })] : []), // Adds same width middleware to keep popper width same as referenced element's width ...(props.matchWidth ? [sameWidthMiddleware] : []), // If there is additional custom middleware to add via the property then add it ...(props.middleware?.length > 0 ? props.middleware : []), ], strategy, // If we're mounting the element with a `v-if` then we need to auto update it while it's mounted ...(props.isHidden === null ? { whileElementsMounted: autoUpdate } : []), }); // If we're hiding the floating element, then we need to manually update once it's visible let cleanupAutoUpdate: undefined | (() => void); watch( () => props.isHidden, () => { if (props.isHidden !== null && !props.isHidden) { cleanupAutoUpdate = autoUpdate(targetElement?.value, popperContainerElement?.value, update); } else if (props.isHidden !== null && props.isHidden && typeof cleanupAutoUpdate === 'function') { cleanupAutoUpdate(); } } ); // Watch placement for changes, if changed then emit new placemenet watch( () => placement.value, () => { if (emittedPosition.value !== placement.value) { emittedPosition.value = placement.value; emit('placement', emittedPosition.value); } } ); // Arrow attributes and styles const arrowStyles = computed<CSSProperties>(() => ({ position: 'absolute', left: middlewareData.value.arrow?.x != null ? `${middlewareData.value.arrow.x}px` : '', top: middlewareData.value.arrow?.y != null ? `${middlewareData.value.arrow.y}px` : '', })); const arrowAttributes = computed(() => ({ class: props.arrow.classes, style: arrowStyles.value, // Applies a data-hide attribute to arrow element depending on center offset. ...(middlewareData.value.arrow?.centerOffset !== 0 ? { 'data-hide': '' } : {}), })); </script> <script lang="ts"> import type {} from '@floating-ui/vue'; /** * A wrapper for the 'popper/core' package that overlays a small piece of content near a target element. * * NOTE: This component should not be used by itself but more implemented by other exported components * e.g. Tooltip * More info here on the package: https://floating-ui.com/docs/vue * * * @usage * import { PopperContainer } from '@adyen/bento-vue2'; * * export default { * components: { PopperContainer }, * computed: { * targetElement() { * return this.$refs.tooltipTarget; * }, * }, * template: ` * <popper-container * :target-element="targetElement" * :position="position" * > * {{ content }} * </popper-container>`, * } */ export default {}; </script> <style lang="scss" scoped src="./popper-container.scss" />
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { Canvas, Meta } from '@storybook/blocks';
|
|
2
|
+
import * as PromoBannerStories from './promo-banner.stories';
|
|
3
|
+
|
|
4
|
+
<Meta of={PromoBannerStories} />
|
|
5
|
+
|
|
6
|
+
# PromoBanner
|
|
7
|
+
|
|
8
|
+
The promotional banner, or promo-banner, is used to highlight important information, announcements, or promotions.
|
|
9
|
+
|
|
10
|
+
<Canvas of={PromoBannerStories.Default} />
|
|
11
|
+
|
|
12
|
+
## Use Cases
|
|
13
|
+
|
|
14
|
+
Use this component to:
|
|
15
|
+
|
|
16
|
+
- Communicate time-sensitive announcements (e.g., limited-time offers, updates).
|
|
17
|
+
- Highlight key actions or promotions.
|
|
18
|
+
|
|
19
|
+
### Do not use
|
|
20
|
+
|
|
21
|
+
Avoid using this component to:
|
|
22
|
+
|
|
23
|
+
- Display persistent messages that require ongoing visibility. Consider alternative components like alerts for those.
|
|
24
|
+
|
|
25
|
+
## Variations
|
|
26
|
+
|
|
27
|
+
- `horizontal` (default) - Ideal for large spaces. Changes to vertical layout in smaller containers depending on the
|
|
28
|
+
image's aspect-ratio.
|
|
29
|
+
- `vertical` - Suitable for smaller spaces, such as half a page or side panels. Does not change with container size.
|
|
30
|
+
|
|
31
|
+
## Modifiers
|
|
32
|
+
|
|
33
|
+
### Actions
|
|
34
|
+
|
|
35
|
+
The promotional banner incorporates the `BentoButton` component.
|
|
36
|
+
|
|
37
|
+
We support only `primary` or `secondary` button variants. They are optional.
|
|
38
|
+
|
|
39
|
+
### Color
|
|
40
|
+
|
|
41
|
+
We have three available colors - `green`, `grey` and `blue`.
|
|
42
|
+
|
|
43
|
+
### Image
|
|
44
|
+
|
|
45
|
+
The promotional banner incorporates the `BentoImage` component.
|
|
46
|
+
|
|
47
|
+
It only allows aspect-ratio of `16:9`, `2:1` and `1:1`. This is based on the ratio that brand uses. To change the
|
|
48
|
+
illustration/image go the brand website to download a `png` of the illustration and set the `image.src` property to the
|
|
49
|
+
downloaded image path. Choose a format based on the illustration and the width of your banner.
|
|
50
|
+
|
|
51
|
+
Depending on the image's `aspect-ratio`, the promotional banner respond at different containers sizes. Refer to
|
|
52
|
+
[here](#responsive) for more information.
|
|
53
|
+
|
|
54
|
+
### Dismissible
|
|
55
|
+
|
|
56
|
+
Emits an event that allows users to remove the banner if it is not critical. This is set to `true` by default.
|
|
57
|
+
|
|
58
|
+
## Accessibility
|
|
59
|
+
|
|
60
|
+
### Keyboard interaction
|
|
61
|
+
|
|
62
|
+
| Keyboard interaction | Output |
|
|
63
|
+
| -------------------- | ------------------------------------------------------ |
|
|
64
|
+
| `Tab` | Focus visibly moves to the first button on the banner. |
|
|
65
|
+
| `Enter` | Selects the button. |
|
|
66
|
+
| `Space` | Selects the button. |
|
|
67
|
+
|
|
68
|
+
### Roles, states and properties
|
|
69
|
+
|
|
70
|
+
| ARIA attribute | is automatic? | fallback used | manual override |
|
|
71
|
+
| ----------------- | ------------------------------ | ------------- | --------------- |
|
|
72
|
+
| `role` | Yes, has the `region` value. | - | - |
|
|
73
|
+
| `aria-labelledby` | Yes, it uses the `title` text. | - | - |
|
|
74
|
+
|
|
75
|
+
### Responsive
|
|
76
|
+
|
|
77
|
+
The promotional banner transitions between horizontal and vertical layouts based on container width. Additionally,
|
|
78
|
+
illustration aspect ratios adapt dynamically at specific breakpoints to maintain optimal visual balance.
|
|
79
|
+
|
|
80
|
+
- ≤ 850px → Landscape (2:1) shifts to vertical layout.
|
|
81
|
+
- ≤ 800px → Landscape (16:9) shifts to vertical layout.
|
|
82
|
+
- ≤ 623px → Square (1:1) shifts to vertical layout.
|
|
83
|
+
|
|
84
|
+
If the promotional banner is given `layout="vertical"`, it will not be responsive i.e. always remain vertical regardless
|
|
85
|
+
of container width.
|
|
86
|
+
|
|
87
|
+
> _Note_: The responsiveness of the promotional banner is not determined by the screen size but is instead based on the
|
|
88
|
+
> size of the container in which it is placed. It is important to note that while screen size can affect layout
|
|
89
|
+
> elements, the banner's responsiveness specifically adapts to the dimensions of its containing element, ensuring
|
|
90
|
+
> flexibility within that context.
|
|
91
|
+
|
|
92
|
+
## Resources
|
|
93
|
+
|
|
94
|
+
- [Figma link](https://www.figma.com/design/uLabwF3243jdMDsNSP7U9I/Bento---Components?node-id=38849-7952)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import { storybookDocsParameter } from '@/utils/ts/storybook'; import { action } from '@storybook/addon-actions'; import type { Meta, StoryObj } from '@storybook/vue'; import { isVue2 } from 'vue-demi'; import { BentoImageAspectRatio } from '../image'; import { BentoTypography } from '../typography'; import { BentoPromoBannerColor, BentoPromoBannerLayout } from './promo-banner.types'; import BentoPromoBanner from './promo-banner.vue'; const meta: Meta = { title: 'Promo banner', component: BentoPromoBanner, argTypes: { // Slots description: { name: 'description', description: "Provides supplementary information that reinforces the banner's title", table: { type: { summary: 'VNode[]' }, category: 'slots', }, control: { type: 'text', }, }, color: { options: Object.values(BentoPromoBannerColor), mapping: BentoPromoBannerColor, control: { type: 'select', }, }, layout: { options: Object.values(BentoPromoBannerLayout), mapping: BentoPromoBannerLayout, defaultValue: BentoPromoBannerLayout.HORIZONTAL, control: { type: 'select', }, }, // Events dismiss: { table: { type: { summary: '() => void' }, category: 'events', }, control: { disable: true }, }, }, }; export default meta; type Story = StoryObj<typeof BentoPromoBanner>; const DEFAULT_TITLE = 'Title'; const DEFAULT_DESCRIPTION = 'Brief description explaining and giving more information about the title'; const DEFAULT_PROPS = { actions: [ { title: 'Primary', event: () => action('onClick'), }, { title: 'Secondary', event: () => action('onClick'), variant: 'secondary', }, ], color: BentoPromoBannerColor.GREY, description: DEFAULT_DESCRIPTION, image: { src: './16-9.png', alt: '', aspectRatio: BentoImageAspectRatio.P16_9, }, isDismissible: true, layout: 'horizontal', title: DEFAULT_TITLE, }; const defaultCode = ` <bento-promo-banner :actions="[ { title: 'Primary', event: () => action('onClick'), }, { title: 'Secondary', event: () => action('onClick'), variant: 'secondary', } " :image="{ src: './16-9.png', alt: '', aspectRatio: '16:9', }" :title="${DEFAULT_TITLE}" @dismiss="dismissAction" > <template #description> <bento-typography variant="body"> ${DEFAULT_DESCRIPTION} </bento-typography> </template> </bento-promo-banner> `; export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoPromoBanner, BentoTypography }, props: Object.keys(argTypes), template: ` <bento-promo-banner v-bind="args" @dismiss="dismissAction"> <template #description> <bento-typography variant="body"> ${DEFAULT_DESCRIPTION} </bento-typography> </template> </bento-promo-banner> `, setup(props) { return { // Values args: isVue2 ? props : _args, // Events dismissAction: action('dismiss'), }; }, }), args: DEFAULT_PROPS, parameters: storybookDocsParameter(defaultCode), };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <section ref="promoBannerRef" class="b-promo-banner" :class="bannerConditionalClass" :aria-labelledby="promoBannerTitleId" > <div class="b-promo-banner__content"> <div class="b-promo-banner__content-header"> <bento-typography :id="promoBannerTitleId" variant="title" large> {{ title }} </bento-typography> <slot name="description" /> </div> <div class="b-promo-banner__content-actions"> <template v-if="actions"> <bento-button v-for="(button, index) in actions" :key="index" :variant="button.variant" :critical="button.critical" :disabled="button.disabled" :inverse="button.inverse" :icon-only="button.iconOnly" :link="button.link" @click="button.event" > <template v-if="button.icon || button.iconLeft" #iconLeft> <component :is="button.icon || button.iconLeft" :svg-title="button.title"></component> </template> <template v-if="!isIconOnly(button)" #default> {{ button.title }} </template> <template v-if="button.iconRight" #iconRight> <component :is="button.iconRight" :svg-title="button.title"></component> </template> </bento-button> </template> <bento-button v-if="isDismissible" variant="secondary" @click="emit('dismiss')"> {{ computedDismissButtonText }} </bento-button> </div> </div> <div class="b-promo-banner__image"> <bento-image class="b-promo-banner__image-item" :src="image.src" :alt="image.alt ?? ''" :aspect-ratio="image.aspectRatio" :object-fit="BentoImageObjectFit.CONTAIN" ></bento-image> </div> </section> </template> <script setup lang="ts"> import { useElementSize } from '@vueuse/core'; import { computed, ref } from 'vue'; // Components import { BentoButton, type BentoButtonActionObject } from '@/components/button'; import { BentoImage, BentoImageAspectRatio, BentoImageObjectFit } from '@/components/image'; import { BentoTypography } from '@/components/typography'; import { BentoPromoBannerColor, BentoPromoBannerLayout, type BentoPromoBannerProps } from './promo-banner.types'; // Utils import { useI18n } from '@/utils/ts/i18n'; import { generateUid } from '@/core/utils/ts'; import messages from './messages.json'; type MessageSchema = (typeof messages)['en-US']; const { t } = useI18n<{ message: MessageSchema }>({ messages }); const props = withDefaults(defineProps<BentoPromoBannerProps>(), { actions: null, color: BentoPromoBannerColor.GREY, isDismissible: true, dismissButtonText: null, layout: BentoPromoBannerLayout.HORIZONTAL, }); const emit = defineEmits<{ /** * Emits a dismiss event when dismiss button on the promotional banner is pressed */ (e: 'dismiss'); }>(); const promoBannerRef = ref(null); const promoBannerSize = useElementSize(promoBannerRef); const promoBannerTitleId = generateUid('promoBannerTitle'); const horizontalLayoutBreakpoint = computed(() => { if (props.image.aspectRatio === BentoImageAspectRatio.P16_9) { return 800; } else if (props.image.aspectRatio === BentoImageAspectRatio.P2_1) { return 850; } else if (props.image.aspectRatio === BentoImageAspectRatio.P1_1) { return 623; } return 0; }); const bannerLayoutClass = computed(() => { // only horizontal layout is responsive and, by design, follows the container size if ( props.layout === BentoPromoBannerLayout.HORIZONTAL && promoBannerSize.width.value <= horizontalLayoutBreakpoint.value ) { return BentoPromoBannerLayout.VERTICAL; } return props.layout; }); const bannerConditionalClass = computed(() => [`b-promo-banner--${bannerLayoutClass.value}`, `b-promo-banner--${props.color}`].join(' ') ); const isIconOnly = (button: BentoButtonActionObject) => button.iconOnly && (button.iconLeft || button.iconRight || button.icon); const computedDismissButtonText = computed(() => props.dismissButtonText || t('dismiss')); </script> <script lang="ts"> /** * Promotional Banner highlights important information, announcements, or promotions. * * @example * import { BentoPromoBanner } from '@adyen/bento-vue2'; * * export default { * components: { BentoPromoBanner }, * template: ` * <bento-promo-banner * title="Title" * description="Brief description explaining and giving more information about the title" * :image="{ src: '16-1.png', aspectRatio: '16:9' }" * :actions="[{ title: 'Primary', event: () => {} }]" * > * </bento-promo-banner> * ` * }; */ export default { i18n: { messages }, name: 'bento-promo-banner', }; </script> <style lang="scss" scoped src="./promo-banner.scss" />
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Canvas, Meta, Story } from '@storybook/blocks';
|
|
2
|
+
import * as RadioButtonStories from './radio-button.stories';
|
|
3
|
+
|
|
4
|
+
<Meta title="Radio button" of={RadioButtonStories} />
|
|
5
|
+
|
|
6
|
+
# Radio button
|
|
7
|
+
|
|
8
|
+
Radio buttons are used when two or more options are mutually exclusive. The user can only select a single option from a
|
|
9
|
+
list of pre-defined options.
|
|
10
|
+
|
|
11
|
+
<Canvas of={RadioButtonStories.Default} />
|
|
12
|
+
|
|
13
|
+
## Use Cases
|
|
14
|
+
|
|
15
|
+
**Only** use this component when:
|
|
16
|
+
|
|
17
|
+
- Creating a new component that is not provided by Bento
|
|
18
|
+
|
|
19
|
+
### Do not use
|
|
20
|
+
|
|
21
|
+
Don't use a radio button in the following cases:
|
|
22
|
+
|
|
23
|
+
- Multiple choices: Use the `bento-radio-group` as it is built with accessibility in mind.
|
|
24
|
+
- Building a form: Use `bento-radio-group`, `bento-checkbox` or `bento-toggle`.
|
|
25
|
+
|
|
26
|
+
### Guidelines
|
|
27
|
+
|
|
28
|
+
When using the `bento-radio-button` component, it is advised:
|
|
29
|
+
|
|
30
|
+
- Accessibility (a11y) must always come first.
|
|
31
|
+
- Create a group with all the individual `bento-radio-button` using a `fieldset` with a `legend` inside for
|
|
32
|
+
accessibility.
|
|
33
|
+
- Follow
|
|
34
|
+
[Bento guidelines](https://www.figma.com/design/uLabwF3243jdMDsNSP7U9I/Bento---Components?node-id=25221-4758).
|
|
35
|
+
- When creating a group the following accessibility keyboard interactions **must** be supported according to the
|
|
36
|
+
official [W3 aria specifications](https://www.w3.org/WAI/ARIA/apg/patterns/radio/examples/radio-rating/#kbd_label)
|
|
37
|
+
- `Tab`: Moves focus to the radio-button or away from it.
|
|
38
|
+
- `Space`: When focus is on the radio-button, selects the value.
|
|
39
|
+
- `Up`: When focus is on the radio-button, selects the next value.
|
|
40
|
+
- `Left`: When focus is on the radio-button, selects the next value.
|
|
41
|
+
- `Down`: When focus is on the radio-button, selects the previous value.
|
|
42
|
+
- `Right`: When focus is on the radio-button, selects the previous value.
|
|
43
|
+
|
|
44
|
+
## Variations
|
|
45
|
+
|
|
46
|
+
There are no variations available for the Radio button component
|
|
47
|
+
|
|
48
|
+
## Modifiers
|
|
49
|
+
|
|
50
|
+
### Disabled
|
|
51
|
+
|
|
52
|
+
Renders the radio button in a disabled state. In practice this makes the component non-interactive and renders a
|
|
53
|
+
`not-allowed` cursor on hover.
|
|
54
|
+
|
|
55
|
+
### Adding tags to items
|
|
56
|
+
|
|
57
|
+
If you would like to grab a user's attention or guide them in their choice on which item to select then you may want to
|
|
58
|
+
use `tag` property. This property accepts all properties a `BentoTag` accepts.
|
|
59
|
+
|
|
60
|
+
## Accessibility
|
|
61
|
+
|
|
62
|
+
The `label` and `description` areas are clickable. A click on these elements selects the radio input value.
|
|
63
|
+
|
|
64
|
+
Screen readers convey the state of the radio button automatically.
|
|
65
|
+
|
|
66
|
+
### Roles, states and properties
|
|
67
|
+
|
|
68
|
+
| ARIA attribute | is automatic? | fallback used | manual override |
|
|
69
|
+
| -------------- | -------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | --------------- |
|
|
70
|
+
| `aria-label` | Yes, corresponds to the `default` slot text | If no text is passed to the `default` slot, then the `value` property will be used. | - |
|
|
71
|
+
| `role` | Yes, through the semantic `input[type="radio"]` element with `role="radio"` set. | - | - |
|
|
72
|
+
| `aria-checked` | Yes, set when the radio button gets selected through the `input`'s `checked` property. | - | - |
|
|
73
|
+
|
|
74
|
+
## Resources
|
|
75
|
+
|
|
76
|
+
- [Figma link](https://www.figma.com/file/uLabwF3243jdMDsNSP7U9I/ADL-2.0---Components?node-id=211-4397&t=MzNjIT4cgO21YCtH-0)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import { action } from '@storybook/addon-actions'; import { storybookDocsParameter } from '@/utils/ts/storybook'; import { ref } from 'vue'; import { isVue2 } from 'vue-demi'; import type { Meta, StoryObj } from '@storybook/vue'; import { BentoRadioButton } from './index'; const meta: Meta = { title: 'Radio group/Radio button', component: BentoRadioButton, argTypes: { // Props hasError: { name: 'has-error', }, modelValue: { name: 'model-value', control: { disable: true }, }, // Slots content: { type: { name: 'string', required: false }, description: 'Conditional content slot that is shown when the radio button is checked', control: 'text', table: { type: { summary: 'VNode[]' }, }, }, default: { type: { name: 'string', required: false }, description: 'The default content slot', control: 'text', table: { type: { summary: 'VNode[]' }, }, }, description: { type: { name: 'string', required: false }, description: 'Description slot', control: 'text', table: { type: { summary: 'VNode[]' }, }, }, }, }; export default meta; type Story = StoryObj<typeof BentoRadioButton>; const LABEL = 'Label'; const DESCRIPTION = 'Optional description'; const defaultCode = ` <template> <bento-radio-button :value="value" v-model="modelValue" @update:model-value="onInput" > Label <template #description>Description text</template> <template #content>Conditional content (optional)</template> </bento-radio-button> </template> <script setup lang="ts"> import { BentoButtonRadioButton, type BentoRadioButtonValue } from '@adyen/bento-vue2' const value = 'option-value-id' const modelValue = ref<BentoRadioButtonValue>(null) const onInput = (value: BentoRadioButtonValue) => {} </script> `; export const Default: Story = { render: (_args, { argTypes }) => ({ props: Object.keys(argTypes), components: { BentoRadioButton }, template: ` <bento-radio-button v-bind="args" :model-value.sync="modelValue" @input="input" > {{args.default}} <template #description>{{args.description}}</template> <template #content>{{args.content}}</template> </bento-radio-button> `, setup(props) { const modelValue = ref('any value'); return { // Values args: isVue2 ? props : _args, modelValue, // Methods update: action('update:model-value'), input: action('input'), }; }, }), args: { default: LABEL, description: DESCRIPTION, value: 'option', content: 'Conditional content', }, parameters: storybookDocsParameter(defaultCode), };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <div class="b-radio-button" data-testid="radio-button-container" :class="conditionalClassContainer"> <radio-input :id="radioButtonId" ref="radioInputRef" :aria-label="ariaLabel" :aria-describedby="hasSlot('description') ? descriptionId : ''" :checked="isChecked" :disabled="disabled" :readonly="readonly" :has-error="hasError" :value="value" :model-value="modelValue" :name="name" @update:model-value="onChange" /> <div> <label v-if="hasSlot('default')" :for="radioButtonId" class="b-radio-button__label-container"> <span v-if="hasSlot('default')" class="b-radio-button__primary-label"> <bento-typography class="b-radio-button__label" el="span" variant="body"><slot /></bento-typography> <bento-tag v-if="tag" class="b-radio-button__label-tag" v-bind="tag" /> </span> <bento-typography v-if="hasSlot('default') && hasSlot('description')" :id="descriptionId" class="b-radio-button__description" el="span" variant="body" > <slot name="description"></slot> </bento-typography> </label> <bento-typography v-if="hasSlot('content') && isChecked" class="b-radio-button__content" el="div"> <slot name="content"></slot> </bento-typography> </div> </div> </template> <script setup lang="ts"> import { BentoTag } from '@/components/tag'; import { BentoTypography } from '@/components/typography'; import { RadioInput } from '../../../internal/radio-input'; import { useAriaLabel } from '@/utils/ts/aria-label'; import { generateUid } from '@/core/utils/ts'; import { computed, type ComputedRef, type PropType, ref, toRefs, useSlots } from 'vue'; import { useDefaultSlotValidator, useHasSlot } from '@/composables'; import { BentoRadioButtonStateClass, type BentoRadioButtonTag, type BentoRadioButtonValue } from './radio-button.types'; import { getSlotText } from '@/utils/ts/get-slot-text'; const slots = useSlots(); const hasSlot = useHasSlot(slots); const props = defineProps({ /** * Disable the radio button (optional) */ disabled: { type: Boolean, default: false }, /** * Shows bento-radio-button in error state (optional) */ hasError: { type: Boolean, default: false }, /** * This property should not be used directly. * It will be used by v-model directive on radio component. */ modelValue: { type: [String, Number] as PropType<BentoRadioButtonValue>, default: null, }, /** * Sets the radio button to readonly (optional) */ readonly: { type: Boolean, default: false }, /** * Sets the properties of the BentoTag component that is placed next to the label. * {@see BentoTag} */ tag: { type: Object as PropType<BentoRadioButtonTag>, default: undefined }, /** * Indicates the value of the input that will be returned when it's checked */ value: { type: [String, Number] as PropType<BentoRadioButtonValue>, required: true, }, /** * Sets the name of the radio input. * Used for differentiating radio groups or accessing input via form submission. */ name: { type: String, default: null, }, }); const emit = defineEmits<{ /** * Emitted when the radio button is clicked. Enables Vue2 "v-model" */ (e: 'input', value: BentoRadioButtonValue); /** * Emitted when the radio button is clicked. Enables Vue3 "v-model" */ (e: 'update:model-value', value: BentoRadioButtonValue); }>(); const { value } = toRefs(props); const radioButtonId = generateUid('bento-radio-button'); const descriptionId = generateUid('description'); const radioInputRef = ref(null); useDefaultSlotValidator('bento-radio-button', slots.default, slots.description); const conditionalClassContainer = computed(() => ({ [`b-radio-button--${BentoRadioButtonStateClass.DISABLED}`]: props.disabled, [`b-radio-button--${BentoRadioButtonStateClass.READONLY}`]: props.readonly, })); const ariaLabel = useAriaLabel({ ariaLabel: getSlotText(slots)('default'), label: value.value as string, }); const isChecked: ComputedRef<boolean> = computed(() => props.value === props.modelValue); const onChange = () => { if (!props.disabled && !props.readonly) { emit('update:model-value', props.value); emit('input', props.value); } }; const focus = () => { radioInputRef.value.focus(); }; defineExpose({ focus, }); </script> <script lang="ts"> /** * Radio buttons are used when two or more options are mutually exclusive, * the user can only select a single option from a list of pre-defined options. * The label tag area is also clickable. * Only to be used internally. * * @example * import { BentoRadioButton } from '@adyen/bento-vue2'; * * export default { * components: { BentoRadioButton }, * template: ` * <bento-radio-button * disabled * value="inputRadio" * model-value.sync="modelValue" * > * Label text * <template #description>Description text below label</template> * <template #content>Content shown when checked</template> * </bento-radio-button> * `, * setup() { * const modelValue = ref('anyValue') * const value = 'option-value' * * return { * modelValue, * value, * } * } * } */ export default { name: 'bento-radio-button', model: { prop: 'modelValue' }, }; </script> <style lang="scss" scoped src="./radio-button.scss" />
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { Canvas, Story, Source, Meta } from '@storybook/blocks';
|
|
2
|
+
import * as RadioGroupStories from './radio-group.stories';
|
|
3
|
+
|
|
4
|
+
<Meta of={RadioGroupStories} />
|
|
5
|
+
|
|
6
|
+
# Radio group
|
|
7
|
+
|
|
8
|
+
Radio buttons are used when two or more options are mutually exclusive. The user can only select a single option from a
|
|
9
|
+
list of pre-defined options.
|
|
10
|
+
|
|
11
|
+
<Canvas of={RadioGroupStories.Default} />
|
|
12
|
+
|
|
13
|
+
> IMPORTANT
|
|
14
|
+
>
|
|
15
|
+
> Labels without a description are hidden by default. You must set a `label` and a `description` (via a prop or slot) to
|
|
16
|
+
> display both. If only a `label` (without `description`) is meant to be displayed, then set `hideLabel` to `false`. See
|
|
17
|
+
> more details in [hide label](#hide-label).
|
|
18
|
+
|
|
19
|
+
## Use Cases
|
|
20
|
+
|
|
21
|
+
Here are some examples:
|
|
22
|
+
|
|
23
|
+
- Choosing a subscription plan for a service, when the user can select only one plan from a list of available plans.
|
|
24
|
+
- Payment Methods selection from various options, such as credit card, PayPal, or bank transfer.
|
|
25
|
+
|
|
26
|
+
### Do not use
|
|
27
|
+
|
|
28
|
+
Don't use a radio button group in the following cases:
|
|
29
|
+
|
|
30
|
+
- Multiple selections - the `radio-group` component is designed for selecting a single option from a group of mutually
|
|
31
|
+
exclusive choices. If you need to allow users to select multiple options simultaneously, consider using a
|
|
32
|
+
`checkbox-group` instead.
|
|
33
|
+
- If you have a large amount of options (e.g. more than four), then consider using a dropdown as it takes up less screen
|
|
34
|
+
space.
|
|
35
|
+
|
|
36
|
+
### Guidelines
|
|
37
|
+
|
|
38
|
+
When using the `radio-group` component, it is advised to:
|
|
39
|
+
|
|
40
|
+
- Align the choices vertically when possible as it is easier to scan and localize. If you still need to display them
|
|
41
|
+
horizontally, make sure you have enough space between the options so that it’s absolutely clear which choice goes with
|
|
42
|
+
which label.
|
|
43
|
+
- Make sure the options are both comprehensive and clearly distinctive from each other.
|
|
44
|
+
|
|
45
|
+
## Variations
|
|
46
|
+
|
|
47
|
+
### Variant
|
|
48
|
+
|
|
49
|
+
The `variant` property defines the layout in which the radio buttons are displayed. It can be one of the following:
|
|
50
|
+
|
|
51
|
+
- `'vertical'`: displays the radio buttons stacked on top of each other. It's the default value.
|
|
52
|
+
- `'horizontal'`: displays the radio buttons one next to the other. If not enough horizontal space is present, the items
|
|
53
|
+
that don't fit overflow on a new line.
|
|
54
|
+
|
|
55
|
+
## Modifiers
|
|
56
|
+
|
|
57
|
+
### Disabled
|
|
58
|
+
|
|
59
|
+
Renders the `radio-group` in a disabled state. In practice this makes the component non-interactive and renders a
|
|
60
|
+
`not-allowed` cursor on hover.
|
|
61
|
+
|
|
62
|
+
### Readonly
|
|
63
|
+
|
|
64
|
+
Renders the `radio-group` in a readonly state. In terms of functionality, read only states are focusable and submittable
|
|
65
|
+
in a form. They're considered to be 'active' from an application's point of view.
|
|
66
|
+
|
|
67
|
+
### Tooltip
|
|
68
|
+
|
|
69
|
+
A tooltip with extra information for the field can be rendered by passing the desired information to the `tooltipText`
|
|
70
|
+
prop.
|
|
71
|
+
|
|
72
|
+
### Error
|
|
73
|
+
|
|
74
|
+
There are two properties that control the error state of all rendered `bento-radio-button`'s within `bento-radio-group`:
|
|
75
|
+
|
|
76
|
+
- `errorMessage`: renders all radio buttons with an error state and displays an error message beneath the buttons
|
|
77
|
+
- `hasError`: renders all radio buttons with an error state
|
|
78
|
+
|
|
79
|
+
`hasError` is deprecated and will be removed in the near future, so please use `errorMessage`.
|
|
80
|
+
|
|
81
|
+
### Hide Label
|
|
82
|
+
|
|
83
|
+
The `label` is not displayed by default in Bento `1.x` if passed without a `description` property set at the same time.
|
|
84
|
+
|
|
85
|
+
To display a `label` without a `description` set, the property `hideLabel` must be set to `false` (`true` by default).
|
|
86
|
+
|
|
87
|
+
Displaying a `label` (without a `description`) will be the default in Bento `2.x` and the property `hideLabel` will be
|
|
88
|
+
removed.
|
|
89
|
+
|
|
90
|
+
<Source
|
|
91
|
+
dark
|
|
92
|
+
language="html"
|
|
93
|
+
code={`<bento-radio-group
|
|
94
|
+
:items="[
|
|
95
|
+
{ value: 1, label: 'Label text' },
|
|
96
|
+
{ value: 2, label: 'Label text 2', description: 'Description text' },
|
|
97
|
+
]"
|
|
98
|
+
label="Group label"
|
|
99
|
+
hide-label="false"
|
|
100
|
+
/>`}
|
|
101
|
+
/>
|
|
102
|
+
|
|
103
|
+
### Custom slots
|
|
104
|
+
|
|
105
|
+
The `RadioGroup` component offers a `description` slot to add a description to the group, and a `content` slot for the
|
|
106
|
+
vertical layout, which will only display the content for the selected radio option.
|
|
107
|
+
|
|
108
|
+
<Canvas sourceState="hidden">
|
|
109
|
+
<Story of={RadioGroupStories.Slots} />
|
|
110
|
+
</Canvas>
|
|
111
|
+
|
|
112
|
+
## Adding tags to items
|
|
113
|
+
|
|
114
|
+
If you would like to grab a user's attention or guide them in their choice on which item to select then you may want to
|
|
115
|
+
use `tag` property on each `BentoRadioGroupItem`. This property accepts all properties a `BentoTag` accepts.
|
|
116
|
+
|
|
117
|
+
<Canvas of={RadioGroupStories.WithTag} />
|
|
118
|
+
|
|
119
|
+
## Accessibility
|
|
120
|
+
|
|
121
|
+
The `bento-radio-group` component has been implemented using WAI-ARIA's radio group pattern and grouping controls
|
|
122
|
+
pattern.
|
|
123
|
+
|
|
124
|
+
The radio button itself is built using a semantically accessible structure which leverages the native `input` tag with a
|
|
125
|
+
role of `radio` and which is associated to its corresponding `label`. Both the `label` and `description` areas of each
|
|
126
|
+
radio button are clickable. A click on these elements selects the corresponding radio input value. Since both the the
|
|
127
|
+
radio button and the group are built using semantic elements, screen readers convey the state and grouping of these
|
|
128
|
+
components automatically.
|
|
129
|
+
|
|
130
|
+
### Keyboard interaction
|
|
131
|
+
|
|
132
|
+
Since the `bento-radio-group` component is implemented via a `fieldset` tag, it is reachable through keyboard navigation
|
|
133
|
+
and follows the logical navigation flow of the page. A detailed list of keyboard interactions is provided in the table
|
|
134
|
+
below.
|
|
135
|
+
|
|
136
|
+
| Keyboard interaction | Output |
|
|
137
|
+
| -------------------- | -------------------------------------------------------------------------------------------- |
|
|
138
|
+
| `Tab` | Moves focus to the first radio button in the group or away from the group. |
|
|
139
|
+
| `Arrow keys` | Moves focus to the different radio buttons in the group and selects the corresponding value. |
|
|
140
|
+
|
|
141
|
+
### Roles, states and properties
|
|
142
|
+
|
|
143
|
+
| ARIA attribute | is automatic? | fallback used | manual override |
|
|
144
|
+
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
145
|
+
| `role` | Yes. In the group, through the semantic `fieldset` element, which groups all its child elements. In the buttons, through the `input` tag with `[role="radio"]` set. | - | - |
|
|
146
|
+
| `label` | Yes. In the group it's set via the `label` prop, whereas in the buttons it's set via the `slot` content. | In each radio button, the radio's value gets set in the unlikely scenario no slot is present. | Yes, the consumer is required to set both values. |
|
|
147
|
+
| `form` | No, it needs to be manually set via the `form` property. | - | The user can pass the form id with which the group needs to be associated with. This allows to use such component also externally from the form and have the value submitted along with the form. |
|
|
148
|
+
| `disabled` | No, it needs to be set by setting the `disabled` prop to `true`. | - | If set to reu, it disables all the elements contained in the group. |
|
|
149
|
+
|
|
150
|
+
## Resources
|
|
151
|
+
|
|
152
|
+
- [Figma link](https://www.figma.com/file/uLabwF3243jdMDsNSP7U9I/ADL-2.0---Components?node-id=211-4397&t=MzNjIT4cgO21YCtH-0)
|
|
153
|
+
- [WAI-ARIA guidelines for grouping controls](https://www.w3.org/WAI/tutorials/forms/grouping/)
|
|
154
|
+
- [WAI-ARIA radio group pattern](https://www.w3.org/WAI/ARIA/apg/patterns/radio/)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import BentoRadioGroup from './radio-group.vue'; import { BentoInputField } from '@/components/input-field'; import { BentoRadioGroupVariant } from './radio-group.types'; import { action } from '@storybook/addon-actions'; import { storybookDocsParameter } from '@/utils/ts/storybook'; import { ref } from 'vue'; import { isVue2 } from 'vue-demi'; import RadioGroupSlotsExample from './__tests__/radio-group-slots-example.vue?raw'; import type { Meta, StoryObj } from '@storybook/vue'; const meta: Meta = { title: 'Radio Group', component: BentoRadioGroup, argTypes: { variant: { options: Object.values(BentoRadioGroupVariant), mapping: BentoRadioGroupVariant, control: { type: 'select', }, }, items: { table: { type: { summary: 'BentoRadioGroupItems' }, }, }, disabled: { control: { type: 'boolean', }, }, hasError: { control: { type: 'boolean' }, table: { category: 'Deprecated' }, }, 'update:model-value': { name: 'update:model-value', defaultValue: '', description: 'Input event handler.', table: { type: { summary: '(newValue: number | string | null) => void' }, }, control: { disable: true }, }, input: { description: 'Deprecated since version 2.0. Use `v-model` or `update:model-value` instead', table: { category: 'Deprecated', }, control: { disable: true }, }, }, }; export default meta; type Story = StoryObj<typeof BentoRadioGroup>; const RADIO_DEFAULT_LABEL = 'Label text'; const DEFAULT_PROPS = { label: 'Group label', description: 'Optional description', tooltipText: 'Tooltip text', items: [ { value: 1, label: RADIO_DEFAULT_LABEL }, { value: 2, label: `${RADIO_DEFAULT_LABEL} 2`, description: 'Descriptive sub label' }, { value: 3, label: `${RADIO_DEFAULT_LABEL} readonly`, readonly: true }, ], }; const defaultCode = ` <template> <!-- With model-value --> <bento-radio-group :items="[ { value: 1, label: 'Label text' }, { value: 2, label: 'Label text 2', description: 'Description text' }, ]" :model-value="yourModelValueVariable" @update:model-value="yourUpdateHandler" label="Group label" description="Optional description" variant="horizontal" /> <!-- With v-model Vue2/Vue3 --> <bento-radio-group :items="[ { value: 1, label: 'Label text' }, { value: 2, label: 'Label text 2', description: 'Description text' }, ]" v-model="yourModelValueVariable" label="Group label" description="Optional description" variant="horizontal" /> </template> <script setup lang="ts"> import { BentoRadioGroup } from '@adyen/bento-vue2'; const yourModelValueVariable = ref<number>(1); const yourUpdateHandler = (newValue: number) => { yourModelValueVariable.value = newValue; } </script> `; export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoRadioGroup }, props: Object.keys(argTypes), template: ` <bento-radio-group v-bind="args" :model-value="selectedValue" @update:model-value="clickedAction" /> `, setup(props) { const selectedValue = ref(undefined); const clickedAction = value => { selectedValue.value = value; action('update:model-value')(value); }; return { // Values selectedValue, args: isVue2 ? props : _args, // Events clickedAction, }; }, }), args: { ...DEFAULT_PROPS, }, parameters: storybookDocsParameter(defaultCode), }; export const Slots: Story = { render: (_args, { argTypes }) => ({ components: { BentoRadioGroup, BentoInputField }, props: Object.keys(argTypes), template: ` <bento-radio-group v-bind="args" :model-value="selectedValue" @update:model-value="clickedAction" > <template #description> Description slot text. </template> <template #content> <bento-input-field /> </template> </bento-radio-group> `, setup(props) { const selectedValue = ref(undefined); const clickedAction = value => { selectedValue.value = value; action('update:model-value')(value); }; return { // Values selectedValue, args: isVue2 ? props : _args, // Events clickedAction, }; }, }), args: { ...DEFAULT_PROPS, }, parameters: storybookDocsParameter(RadioGroupSlotsExample), }; const withTagCode = ` <template> <!-- With model-value --> <bento-radio-group :items="[ { value: 1, label: 'Label text', tag: { label: 'Recommended' } }, { value: 2, label: 'Label text 2', description: 'Description text' }, ]" :model-value="yourModelValueVariable" @update:model-value="yourUpdateHandler" label="Group label" description="Optional description" variant="horizontal" /> <!-- With v-model Vue2/Vue3 --> <bento-radio-group :items="[ { value: 1, label: 'Label text', tag: { label: 'Recommended' } }, { value: 2, label: 'Label text 2', description: 'Description text' }, ]" v-model="yourModelValueVariable" label="Group label" description="Optional description" variant="horizontal" /> </template> <script setup lang="ts"> import { BentoRadioGroup } from '@adyen/bento-vue2'; const yourModelValueVariable = ref<number>(1); const yourUpdateHandler = (newValue: number) => { yourModelValueVariable.value = newValue; } </script> `; export const WithTag = { ...Default, args: { ...Default.args, items: [ { value: 1, label: RADIO_DEFAULT_LABEL, tag: { label: 'Recommended' } }, { value: 2, label: `${RADIO_DEFAULT_LABEL} 2`, description: 'Descriptive sub label', }, { value: 3, label: `${RADIO_DEFAULT_LABEL} readonly`, readonly: true }, ], }, parameters: storybookDocsParameter(withTagCode), };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <div class="b-radio-group"> <controls-group class="b-radio-group__buttons" :aria-describedby="shouldShowErrorMessage ? errorId : ''" :component-name="COMPONENT_NAME" :class="conditionalClasses" :description="description" :disabled="disabled" :form="formId" :label="label" :optional="optional" :required="required" :hide-label="hideLabel" :tooltip-text="tooltipText" role="radiogroup" :aria-readonly="isReadOnly" > <bento-radio-button v-for="item in items" ref="radioButton" :key="item.value" :model-value="inputValue" :value="item.value" :disabled="disabled || item.disabled" :readonly="isReadOnly || item.readonly" :has-error="shouldShowError || item.hasError" :tag="item.tag" :name="formId || label" @update:model-value="onUpdateModelValue" > <template #default>{{ item.label }}</template> <template v-if="item.description" #description>{{ item.description }}</template> <template v-if="hasSlot('content') && isVertical" #content> <slot name="content"></slot> </template> </bento-radio-button> <template v-if="hasSlot('description')" #description> <slot name="description" /> </template> </controls-group> <error-message v-if="shouldShowErrorMessage" :id="errorId" :error-message="errorMessage" class="b-radio-group__error-message" /> </div> </template> <script setup lang="ts"> import { computed, ref, toRef, useSlots, watch } from 'vue'; import { ControlsGroup, ErrorMessage } from '@/components/internal'; import { BentoRadioButton } from './components/radio-button'; import { useHasSlot } from '@/composables'; import { generateUid } from '@/core/utils/ts'; import { deprecate } from '@/utils/ts/deprecate'; import { useFormFieldEmits } from '@/utils/ts/form-field/form-field'; import { type BentoRadioGroupEmits, type BentoRadioGroupProps, BentoRadioGroupVariant } from './radio-group.types'; import { useFormLayoutFieldLoading } from '@/composables/use-form-layout-loading/use-form-layout-loading'; /** * The name of the component to be used in the Vue devtools and console warnings. */ const COMPONENT_NAME = 'bento-radio-group'; const slots = useSlots(); const hasSlot = useHasSlot(slots); const emit = defineEmits<BentoRadioGroupEmits>(); const props = withDefaults(defineProps<BentoRadioGroupProps>(), { description: null, disabled: false, errorMessage: null, formId: null, hasError: false, hideLabel: null, modelValue: null, optional: false, readonly: false, required: false, tooltipText: null, variant: BentoRadioGroupVariant.VERTICAL, }); const inputValue = toRef(props, 'modelValue'); const errorId = generateUid('error'); const { isReadOnly } = useFormLayoutFieldLoading(toRef(props, 'readonly')); const { emitValue } = useFormFieldEmits(emit); const radioButton = ref(null); const shouldShowError = computed( () => !props.disabled && !isReadOnly.value && (!!props.errorMessage || props.hasError) ); const shouldShowErrorMessage = computed(() => !props.disabled && !isReadOnly.value && !!props.errorMessage); const conditionalClasses = computed(() => ({ 'b-radio-group__buttons--horizontal': props.variant === BentoRadioGroupVariant.HORIZONTAL, })); // Content slot logic const isVertical = computed(() => props.variant === 'vertical'); const shouldShowSlotError = computed(() => hasSlot('content') && !isVertical?.value); const onUpdateModelValue = value => { emitValue(value); }; if (props.hasError) { deprecate( 'BentoRadioGroup "hasError" property', `Use the BentoRadioGroup "errorMessage" property instead to indicate that there is a modifier error. Having the field as "undefined" will hide/remove the error. <bento-radio-group errorMessage="Error message text" />`, '2.0.0' ); } watch( shouldShowSlotError, newVal => { if (newVal) { throw new Error('The content slot is only available for the vertical radio group layout'); } }, { immediate: true, } ); const focus = () => { radioButton.value[0].focus(); }; defineExpose({ focus, }); </script> <script lang="ts"> /** * A radio group component should be used whenever it is necessary to use a set of radio buttons * This component conforms to the official WAI-ARIA guidelines for grouping controls: https://www.w3.org/WAI/tutorials/forms/grouping/ * * import { BentoRadioGroup } from '@adyen/bento-vue2/'; * * export default { * components: { BentoRadioGroup }, * template: ` * <bento-radio-group * label="Your group label" * :items="[ * { value: 1, label: 'Your label' }, * { value: 2, label: 'Another label', description: 'Your descriptive sub label' }, * ]" * variant="horizontal" * /> * ` * }; */ export default { name: 'bento-radio-group', model: { prop: 'modelValue' }, }; </script> <style lang="scss" scoped src="./radio-group.scss" />
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <div ref="editableRef" contenteditable="true" tabindex="0" :disabled="disabled" class="b-rich-text-editor-content-area" @input="onInput" @keydown="preserveCursor" > </div> </template> <script setup lang="ts"> import { onMounted, ref, watch } from 'vue'; import { type BentoRichTextEditorContentAreaProps } from './rich-text-editor-content-area.types'; const props = withDefaults(defineProps<BentoRichTextEditorContentAreaProps>(), { disabled: false, value: null, }); const emit = defineEmits<{ (e: 'input', value: BentoRichTextEditorContentAreaProps['value']): void; }>(); const editableRef = ref<HTMLDivElement>(null); const onInput = (event: InputEvent) => { emit('input', (event.target as HTMLElement).innerText); }; const preserveCursor = () => { // Do nothing here. This prevents unnecessary handling that can disturb user input }; // Update the contenteditable div after the component is mounted onMounted(() => { if (editableRef.value && editableRef.value.innerText !== props.value) { editableRef.value.innerText = props.value; // Initial content sync } }); watch( () => props.value, newVal => { if (editableRef.value && editableRef.value.innerText !== newVal) { editableRef.value.innerText = newVal; } } ); </script> <style lang="scss" scoped src="./rich-text-editor-content-area.scss" />
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <pre>{{ code }}</pre> </template> <script setup lang="ts"> import { type RichTextEditorCodeBlockProps } from './rich-text-editor-code-block.types'; defineProps<RichTextEditorCodeBlockProps>(); </script>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <code>{{ code }}</code> </template> <script setup lang="ts"> import { type RichTextEditorInlineCodeProps } from './rich-text-editor-inline-code.types'; defineProps<RichTextEditorInlineCodeProps>(); </script>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <bento-link :to="url" external is-not-routing> <template #default> <slot></slot> </template> </bento-link> </template> <script setup lang="ts"> import { BentoLink } from '@/components/link'; import { type RichTextEditorLinkProps } from './rich-text-editor-link.types'; withDefaults(defineProps<RichTextEditorLinkProps>(), { url: '', }); </script>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <li :style="noBullet"> <bento-checkbox v-if="isTaskList" v-model="isChecked"> <slot></slot> </bento-checkbox> <div v-else> <slot></slot> </div> </li> </template> <script setup lang="ts"> import { computed, ref } from 'vue'; import BentoCheckbox from '@/components/checkbox/checkbox.vue'; import { type RichTextEditorListItemProps } from './rich-text-editor-list-item.types'; const props = withDefaults(defineProps<RichTextEditorListItemProps>(), { checked: null, }); const isChecked = ref(props.checked); const isTaskList = computed(() => props.checked !== null); const noBullet = computed(() => (isTaskList.value ? { listStyleType: 'none', marginLeft: '-20px' } : {})); </script>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <table> <slot> <thead><slot name="head"></slot></thead> <tbody><slot name="body"></slot></tbody> </slot> </table> </template>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Canvas, Meta } from '@storybook/blocks';
|
|
2
|
+
import * as RichTextEditorStories from './rich-text-editor.stories';
|
|
3
|
+
|
|
4
|
+
<Meta of={RichTextEditorStories} />
|
|
5
|
+
|
|
6
|
+
# RichTextEditor
|
|
7
|
+
|
|
8
|
+
An editable content area with additional functionality that allows to change text styling and add links and tables.
|
|
9
|
+
|
|
10
|
+
<Canvas of={RichTextEditorStories.Default} />
|
|
11
|
+
|
|
12
|
+
## Use Cases
|
|
13
|
+
|
|
14
|
+
Use a RichTextEditor if:
|
|
15
|
+
|
|
16
|
+
- The text count exceeds 100 characters
|
|
17
|
+
- You need content with rich text formats (bold, italics, list, table)
|
|
18
|
+
|
|
19
|
+
### Do not use
|
|
20
|
+
|
|
21
|
+
- Don’t use when you need user to enter shorter text content. Use input field or text area instead.
|
|
22
|
+
- Don’t use when you need user to enter non-sentence content (like URLs). Use input field instead.
|
|
23
|
+
|
|
24
|
+
## Variations
|
|
25
|
+
|
|
26
|
+
When providing the value `visual` to the `mode` property, the editor will activate in visual mode when rendered. The
|
|
27
|
+
default value is `markdown`.
|
|
28
|
+
|
|
29
|
+
## Modifiers
|
|
30
|
+
|
|
31
|
+
### Disabled
|
|
32
|
+
|
|
33
|
+
The disabled property determines whether the component is enabled or disabled.
|
|
34
|
+
|
|
35
|
+
When a `rich-text-editor` component is disabled, it will not allow user interaction and the value of the input cannot be
|
|
36
|
+
changed.
|
|
37
|
+
|
|
38
|
+
The visual representation of a disabled `rich-text-editor` component is as a grayed-out color or and lack of hover
|
|
39
|
+
effect.
|
|
40
|
+
|
|
41
|
+
### Error message
|
|
42
|
+
|
|
43
|
+
The `errorMessage` controls the error state of `rich-text-editor`:
|
|
44
|
+
|
|
45
|
+
### Tooltip
|
|
46
|
+
|
|
47
|
+
A tooltip with extra information for the label of the `rich-text-editor` can be rendered by passing the desired
|
|
48
|
+
information to the `tooltipText` prop.
|
|
49
|
+
|
|
50
|
+
## Resources
|
|
51
|
+
|
|
52
|
+
- [Figma link](https://www.figma.com/design/uLabwF3243jdMDsNSP7U9I/Bento---Components?node-id=32951-148311&p=f&t=oKNt5rFb69AFPWN0-0)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import { isVue2 } from 'vue-demi'; import { ref, watch } from 'vue'; import { action } from '@storybook/addon-actions'; import { storybookDocsParameter } from '@/utils/ts/storybook'; import BentoRichTextEditor from './rich-text-editor.vue'; import type { Meta, StoryObj } from '@storybook/vue'; const meta: Meta = { title: 'Rich text editor', component: BentoRichTextEditor, argTypes: { mode: { name: 'mode', type: { name: 'string', required: false }, description: 'switch between Markdown or Visual mode', options: ['markdown', 'visual'], mapping: { markdown: 'markdown', visual: 'visual' }, control: { type: 'select', }, }, }, }; export default meta; type Story = StoryObj<typeof BentoRichTextEditor>; const DEFAULT_LABEL = 'Label'; const DEFAULT_DESCRIPTION = 'Supporting text'; const DEFAULT_MARKDOWN_VALUE = ` # All rendering elements ## Renders a paragraph This is just a plain paragraph ## Renders a table | Month | Savings | | -------- | ------- | | January | $250 | | February | $80 | | March | $420 | ## Renders a link [Adyen.com](https://adyen.com) ## Renders lists ### An unordered list * An unordered * List of things * To test how it renders ### An ordered list 1. An ordered 2. List of things 3. To test how it renders ### A tasks list - [ ] a task list item - [ ] list syntax required ## Renders html as plain text <html> <body> <p>This is html content</p> <script>alert('This should be rendered as text')</script> </body> </html> ## Renders code \`\`\` git status git add git commit \`\`\` ## Renders quotes > Text that is a quote `; const defaultProps = { label: DEFAULT_LABEL, hideLabel: false, disabled: false, description: DEFAULT_DESCRIPTION, value: DEFAULT_MARKDOWN_VALUE, }; export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoRichTextEditor }, props: Object.keys(argTypes), template: ` <bento-rich-text-editor v-bind="args" :label="args.label" :hideLabel="args.hideLabel" :description="args.description" :value="inputValue" @input="inputAction" /> `, setup(props) { const args = isVue2 ? props : _args; const inputValue = ref(args.value); const inputAction = (value: string) => { inputValue.value = value; action('input')(value); }; watch( () => args.value, () => { inputValue.value = args.value; } ); return { args, inputValue, inputAction, }; }, }), args: { ...defaultProps, }, parameters: storybookDocsParameter(` <bento-rich-text-editor :value="inputValue" @input="inputValue" /> `), }; export const VisualMode: Story = { render: (_args, { argTypes }) => ({ components: { BentoRichTextEditor }, props: Object.keys(argTypes), template: ` <bento-rich-text-editor v-bind="args" :label="args.label" :hideLabel="args.hideLabel" :description="args.description" mode="visual" :value="inputValue" @input="inputAction" /> `, setup(props) { const args = isVue2 ? props : _args; const inputValue = ref(args.value); const inputAction = (value: string) => { inputValue.value = value; action('input')(value); }; watch( () => args.value, () => { inputValue.value = args.value; } ); return { args, inputValue, inputAction, }; }, }), args: { ...defaultProps, mode: 'visual', }, parameters: storybookDocsParameter(` <bento-rich-text-editor :value="inputValue" mode="visual" @input="inputValue" /> `), };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <bento-typography el="div" class="b-rich-text-editor" rich-text> <field-label v-if="!hideLabel" :label="label" :tooltip-text="tooltipText" :required="required" /> <div v-bind="attrs" class="b-rich-text-editor__container" data-testid="rich-text-editor-container" :aria-disabled="disabled" :class="conditionalClasses" > <div class="b-rich-text-editor__toolbar"> <bento-segmented-control v-model="selectedMode" :items="richTextEditorModeOptions" /> </div> <keep-alive> <rich-text-editor-content-area v-if="selectedMode === 'markdown'" :value="internalValue" role="textbox" aria-multiline="true" :disabled="disabled" :aria-label="computedAriaLabel" :aria-describedby="ariaDescribedBy" @input="onInput" /> <bento-alert v-else-if="error" type="critical"> <template #default> {{ t('errorProcessingMarkdown', { value: error }) }} </template> </bento-alert> <rich-text-editor-renderer v-else :node="node" class="b-rich-text-editor__renderer" :class="rendererConditionalClasses" /> </keep-alive> </div> <error-message v-if="!!errorMessage" :id="errorId" :error-message="errorMessage" class="b-rich-text-editor__error-message" /> <span v-if="description" :id="descriptionId" class="b-rich-text-editor__description"> <bento-typography el="span" variant="body">{{ description }}</bento-typography> </span> </bento-typography> </template> <script setup lang="ts"> import { computed, ref, useAttrs, watch } from 'vue'; import type { Root } from 'mdast'; // Components import BentoTypography from '@/components/typography/typography.vue'; import BentoSegmentedControl from '@/components/segmented-control/segmented-control.vue'; import BentoAlert from '@/components/alert/alert.vue'; import { ErrorMessage, FieldLabel } from '@/internal'; import RichTextEditorContentArea from './components/rich-text-editor-content-area/rich-text-editor-content-area.vue'; import RichTextEditorRenderer from './components/rich-text-editor-renderer/rich-text-editor-renderer'; import type { BentoRichTextEditorMode, BentoRichTextEditorProps } from './rich-text-editor.types'; // Utils import { useI18n } from '@/utils/ts/i18n'; import { useAriaLabel } from '@/utils/ts/aria-label'; import { generateUid } from '@/core/utils/ts'; import { parseMarkdown } from './utils'; import messages from './messages.json'; type MessageSchema = (typeof messages)['en-US']; const { t } = useI18n<{ message: MessageSchema }>({ messages }); const errorId = generateUid('rich-text-editor-error'); const descriptionId = generateUid('rich-text-editor-description'); const attrs = useAttrs(); const props = withDefaults(defineProps<BentoRichTextEditorProps>(), { description: null, disabled: false, errorMessage: null, hideLabel: null, label: null, mode: 'markdown', required: false, tooltipText: null, value: null, }); const emit = defineEmits<{ /** * Emmited when the content of the editor changes */ (e: 'input', value: string): void; }>(); const selectedMode = ref<BentoRichTextEditorMode>(props.mode); const internalValue = ref(props.value); const node = ref<Root>(null); const error = ref<Error>(null); const richTextEditorModeOptions = computed< Array<{ label: string; value: BentoRichTextEditorMode; disabled: boolean; }> >(() => [ { label: t('visual'), value: 'visual', disabled: props.disabled, }, { label: t('markdown'), value: 'markdown', disabled: props.disabled, }, ]); const conditionalClasses = computed(() => ({ 'b-rich-text-editor__container--disabled': props.disabled, 'b-rich-text-editor__container--error': props.errorMessage, })); const rendererConditionalClasses = computed(() => ({ 'b-rich-text-editor__renderer--disabled': props.disabled, })); // ARIA const computedAriaLabelFallbackMessage = computed(() => t('ariaLabelFallback')); const ariaLabelAttribute = ref(attrs['aria-label']); const computedAriaLabel = useAriaLabel({ ariaLabel: ariaLabelAttribute.value as string, defaultFallback: computedAriaLabelFallbackMessage, }); const ariaDescribedBy = computed( () => `${props.description ? descriptionId : ''} ${props.errorMessage ? errorId : ''}`.trim() || null ); /** * Turns internal value into an AST Node */ function updateNodeValue() { try { node.value = parseMarkdown(internalValue.value); error.value = null; } catch (e) { node.value = null; error.value = e.message.split(':')[1]; } } const onInput = (newContentValue: string) => { internalValue.value = newContentValue; }; watch(internalValue, (newValue: string) => { node.value = null; // Invalidate previously parsed node emit('input', newValue); }); watch( selectedMode, newVal => { if (newVal === 'visual' && !node.value) { updateNodeValue(); } }, { immediate: true } ); </script> <script lang="ts"> /** * An editable content area with additional functionality * that allows to change text styling and add links and tables. * * @example * import { BentoRichTextEditor } from '@adyen/bento-vue2'; * * export default { * components: { BentoRichTextEditor }, * template: ` * <bento-rich-text-editor * v-model="value" * :label="label" * :description="description" * :disabled="false" * /> * ` * } */ export default { i18n: { messages }, }; </script> <style lang="scss" scoped src="./rich-text-editor.scss" />
|