@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,117 @@
|
|
|
1
|
+
import { Canvas, Story, Meta } from '@storybook/blocks';
|
|
2
|
+
import * as ComponentStories from './search-bar.stories';
|
|
3
|
+
|
|
4
|
+
<Meta of={ComponentStories} />
|
|
5
|
+
|
|
6
|
+
# Search bar
|
|
7
|
+
|
|
8
|
+
Search functionality to help users find the desired data quicker. When a user starts typing, the data is dynamically
|
|
9
|
+
filtered to only show the relevant results.
|
|
10
|
+
|
|
11
|
+
Clearing the search field displays the entire list again.
|
|
12
|
+
|
|
13
|
+
<Canvas of={ComponentStories.Default} />
|
|
14
|
+
|
|
15
|
+
## Use Cases
|
|
16
|
+
|
|
17
|
+
Use a Search bar if:
|
|
18
|
+
|
|
19
|
+
- Filtering a list of elements through a substring is required inside the filters.
|
|
20
|
+
|
|
21
|
+
### Do not use
|
|
22
|
+
|
|
23
|
+
Here are some "don't dos":
|
|
24
|
+
|
|
25
|
+
- Use it as a replacement of the filters.
|
|
26
|
+
|
|
27
|
+
### Guidelines
|
|
28
|
+
|
|
29
|
+
In order to use the component with its full potential, it is recommended to use the `useSearchBarFilter` composable
|
|
30
|
+
which works together with the `bento-search-bar` component by complementing it's functionality.
|
|
31
|
+
|
|
32
|
+
The `useSearchBarFilter` composible receives the `searchTerm` property which contains the value by which the search will
|
|
33
|
+
be made and a list of items in the `data` property that implements the `BentoSearchBarItem` interface, that is, an array
|
|
34
|
+
of elements of which each item in that list contains a property `label` that will be used to perform the
|
|
35
|
+
search/filtering process.
|
|
36
|
+
|
|
37
|
+
The `useSearchBarFilter` filters the `data` by the `searchTerm` returning a new instance of the `data` object with only
|
|
38
|
+
the values that match `searchTerm` by containing the term or by fully matching it.
|
|
39
|
+
|
|
40
|
+
> It is recommended to use the value returned by `useSearchBarFilter` as the object used to display the list of items in
|
|
41
|
+
> the view.
|
|
42
|
+
|
|
43
|
+
Example:
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
import {
|
|
47
|
+
BentoSearchBar,
|
|
48
|
+
BentoSearchBarData,
|
|
49
|
+
useSearchBarFilter
|
|
50
|
+
} from '@adyen/bento-vue2';
|
|
51
|
+
|
|
52
|
+
export default {
|
|
53
|
+
components: { BentoSearchBar },
|
|
54
|
+
template: `
|
|
55
|
+
<bento-search-bar
|
|
56
|
+
input-field-aria-label="Search bar"
|
|
57
|
+
v-model="searchTerm"
|
|
58
|
+
@input="onInput"
|
|
59
|
+
@clear="onClearSearch"
|
|
60
|
+
/>
|
|
61
|
+
// Render the filtered data
|
|
62
|
+
{{ filteredData }}
|
|
63
|
+
`,
|
|
64
|
+
setup() {
|
|
65
|
+
const searchTerm = ref('');
|
|
66
|
+
const data = ref<BentoSearchBarData>([]);
|
|
67
|
+
const filteredData = useSearchBarFilter(searchTerm, data);
|
|
68
|
+
|
|
69
|
+
consdt onInput = () => {}
|
|
70
|
+
consdt onClearSearch = () => {}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
searchTerm,
|
|
74
|
+
filteredData,
|
|
75
|
+
onInput,
|
|
76
|
+
onClearSearch,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Accessibility
|
|
83
|
+
|
|
84
|
+
Being an interactive element, the search bar is reachable through keyboard navigation in the logical navigation flow of
|
|
85
|
+
the page.
|
|
86
|
+
|
|
87
|
+
The following keys trigger the following actions:
|
|
88
|
+
|
|
89
|
+
### Keyboard interaction
|
|
90
|
+
|
|
91
|
+
### Search input
|
|
92
|
+
|
|
93
|
+
| Keyboard interaction | Action |
|
|
94
|
+
| -------------------- | --------------------------------------------------- |
|
|
95
|
+
| `Tab` | Moves focus to "clear search" button (if available) |
|
|
96
|
+
|
|
97
|
+
### Clear search button
|
|
98
|
+
|
|
99
|
+
| Keyboard interaction | Action |
|
|
100
|
+
| -------------------- | -------------------------------------------------------------- |
|
|
101
|
+
| `Tab` | Moves focus away from the "clear search" button (if available) |
|
|
102
|
+
| `Space` | Activates the button, triggering the click event. |
|
|
103
|
+
| `Enter` | Activates the button, triggering the click event. |
|
|
104
|
+
|
|
105
|
+
### Roles, states and properties
|
|
106
|
+
|
|
107
|
+
Adhering to the [WAI-ARIA 1.2](https://www.w3.org/TR/wai-aria-1.2/) standard we follow the
|
|
108
|
+
[searchbox role](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/searchbox_role) documentation
|
|
109
|
+
provided.
|
|
110
|
+
|
|
111
|
+
| ARIA attribute | is automatic? | fallback used | manual override |
|
|
112
|
+
| -------------- | ------------- | ------------- | --------------- |
|
|
113
|
+
| label | No | - | - |
|
|
114
|
+
|
|
115
|
+
## Resources
|
|
116
|
+
|
|
117
|
+
- [Figma link](https://www.figma.com/file/uLabwF3243jdMDsNSP7U9I/Bento---Components?type=design&node-id=2705-57473&t=59PhABPWX0eH9nSi-0)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import { action } from '@storybook/addon-actions'; import { ref } from 'vue'; import { storybookDocsParameter } from '@/utils/ts/storybook'; import { isVue2 } from 'vue-demi'; import BentoSearchBar from './search-bar.vue'; export default { title: 'Search bar', component: BentoSearchBar, }; export const Default = (_args, { argTypes }) => ({ components: { BentoSearchBar }, props: Object.keys(argTypes), setup(props) { const searchTerm = ref(props.value); const inputAction = inputValue => { action('input')(inputValue); }; const clearAction = () => { searchTerm.value = ''; action('clear')(); }; return { // Values searchTerm, args: isVue2 ? props : _args, // Methods inputAction, clearAction, }; }, // :input-field-aria-label="args.inputFieldAriaLabel" template: ` <bento-search-bar v-bind="args" :debounce-time="args.debounceTime" v-model="searchTerm" @input="inputAction" @clear="clearAction" /> `, }); Default.args = { inputFieldAriaLabel: 'Search bar short description', placeholder: 'Placeholder text', }; const DefaultCode = ` <bento-search-bar input-field-aria-label="Search bar short description" :debounce-time="500" v-model="searchTerm" @input="inputAction" @clear="clearAction" /> `; Default.parameters = storybookDocsParameter(DefaultCode);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <div class="b-search-bar"> <bento-input-field ref="bentoInputField" v-bind="attrs" :model-value="searchTerm" type="search" :condensed="condensed" :aria-label="inputFieldAriaLabel" :aria-hidden="false" :disabled="disabled" :placeholder="placeholder" @update:model-value="onInputEvent" @focus="emit('focus')" @blur="emit('blur')" @escape-pressed="emit(BentoSearchBarInternalEvent.ESCAPE)" > <template #iconBefore> <search-icon :svg-title="t('search')" aria-hidden="true" /> </template> <template v-if="searchTerm" #iconAfter> <div class="b-search-bar__clear-search" tabindex="0" data-testid="b-search-bar-clear-search-button" @click="onClearSearch" @keypress.enter="onClearSearch" @keyup.space="onClearSearch" > <cross-circle-fill-icon :svg-title="t('clearSearch')" /> </div> </template> </bento-input-field> </div> </template> <script setup lang="ts"> import { provide, ref, toRef, useAttrs, watch } from 'vue'; import { debounce } from '@/utils/ts/debounce'; import { BentoSearchBarInternalEvent, type BentoSearchBarProps } from './search-bar.types'; import { useI18n } from '@/utils/ts/i18n'; import { INPUT_FIELD_HINT_INJECTION_KEY } from '@/components/input-field/input-field.keys'; // eslint-disable-next-line @typescript-eslint/consistent-type-imports import BentoInputField from '@/components/input-field/input-field.vue'; import SearchIcon from '@adyen/ui-assets-icons-16/vue/search'; import CrossCircleFillIcon from '@adyen/ui-assets-icons-16/vue/cross-circle-fill'; import messages from './messages.json'; type MessageSchema = (typeof messages)['en-US']; const { t } = useI18n<{ message: MessageSchema }>({ messages }); const props = withDefaults(defineProps<BentoSearchBarProps>(), { condensed: false, debounceTime: 300, disabled: false, hint: '', placeholder: '', value: undefined, }); const emit = defineEmits<{ /** * Emitted when the input value changes by typing a search. Debounced by the `debounceTime` property */ (e: 'input', inputValue: string): void; /** * Emitted when the "clear search" button is clicked */ (e: 'clear'): void; /** * Emitted when the "ESC" key is pressed inside the Search Bar input */ (e: 'escape-pressed'): void; /** * Emitted when the element has lost focus */ (e: 'blur'): void; /** * Emitted when the element has received focus */ (e: 'focus'): void; }>(); provide(INPUT_FIELD_HINT_INJECTION_KEY, toRef(props, 'hint')); const attrs = useAttrs(); const searchTerm = ref(props.value); const bentoInputField = ref<InstanceType<typeof BentoInputField> | null>(); watch( () => props.value, () => { searchTerm.value = props.value; }, { immediate: true } ); const onClearSearch = () => { searchTerm.value = ''; emit('clear'); emit('input', ''); }; const debouncedEmit = debounce(() => { emit('input', searchTerm.value); }, props.debounceTime); const onInputEvent = (value: string) => { searchTerm.value = value; debouncedEmit(); }; const focus = () => { // bentoInputField.value?.focusInput(); bentoInputField.value.focusInput(); }; // Expose method so that it can be used to focus on the input from outside the component defineExpose({ /** * Focus on the input field when called. */ focus, }); </script> <script lang="ts"> /** * Search functionality to help users find the desired data quicker. * When a user starts typing, the data is dynamically filtered to * only show the relevant results. * * Clearing the search field displays the entire list again. * * @example * import { * BentoSearchBar, * BentoSearchBarData, * useSearchBarFilter * } from '@adyen/bento-vue2'; * * export default { * components: { BentoSearchBar }, * template: ` * <bento-search-bar * input-field-aria-label="Search bar" * v-model="searchTerm" * @input="onInput" * @clear="onClearSearch" * /> * // Render the filtered data * {{ filteredData }} * `, * setup() { * const searchTerm = ref(''); * const data = ref<BentoSearchBarData>([]); * const filteredData = useSearchBarFilter(searchTerm, data); * * consdt onInput = () => {} * consdt onClearSearch = () => {} * * return { * searchTerm, * filteredData, * onInput, * onClearSearch, * }; * } * } */ export default { i18n: { messages }, }; </script> <style lang="scss" scoped src="./search-bar.scss" />
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <section class="b-secondary-nav-category" :aria-label="label"> <bento-typography el="h1" class="b-secondary-nav-category__label" variant="caption" stronger>{{ label }}</bento-typography> <ul class="b-secondary-nav__list"> <li v-for="item in items" :key="item.value"> <secondary-nav-item :label="item.label" :value="item.value" /> </li> </ul> </section> </template> <script lang="ts" setup> import { BentoTypography } from '@/components/typography'; import { SecondaryNavItem } from '../secondary-nav-item'; import { type BentoSecondaryNavItemsItem } from '../../secondary-nav.types'; defineProps<{ /** * The display name of the category */ label: string; /** * A list of {@see BentoSecondaryNavItemsItem} that belongs to this category */ items: Array<BentoSecondaryNavItemsItem>; }>(); </script> <script lang="ts"> /** * This component shouldn't be used directly. It is internally used by {@see BentoSecondaryNav} */ export default {}; </script> <style lang="scss" scoped src="./secondary-nav-category.scss" />
|
package/dist/assets/components/secondary-nav/components/secondary-nav-item/secondary-nav-item.vue
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <button class="b-secondary-nav-item" :class="conditionalClasses" :aria-selected="isItemSelected" @click="onClick"> <bento-typography class="b-secondary-nav-item__title" variant="body" stronger> {{ label }} </bento-typography> </button> </template> <script lang="ts" setup> import { SECONDARY_NAV_STATE } from '../../secondary-nav.keys'; import { BentoTypography } from '@/components/typography'; import { computed, inject } from 'vue'; const { activeItemValue, updateActiveItemValue } = inject(SECONDARY_NAV_STATE); const props = defineProps<{ /** * The display name of the navigation item */ label: string; /** * The value of this item. */ value: string; }>(); const isItemSelected = computed(() => activeItemValue.value === props.value); const conditionalClasses = computed(() => ({ 'b-secondary-nav-item--active': isItemSelected.value, })); const emit = defineEmits<{ /** * Event emitted when this element is clicked */ (e: 'click', clickEvent: MouseEvent): void; }>(); const onClick = (e: MouseEvent) => { emit('click', e); updateActiveItemValue(props.value); }; </script> <script lang="ts"> /** * This component shouldn't be used directly. It is internally used by {@see BentoSecondaryNav} */ export default {}; </script> <style lang="scss" scoped src="./secondary-nav-item.scss" />
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Canvas, Meta, Story } from '@storybook/blocks';
|
|
2
|
+
import * as SecondaryNavStories from './secondary-nav.stories';
|
|
3
|
+
|
|
4
|
+
<Meta of={SecondaryNavStories} />
|
|
5
|
+
|
|
6
|
+
# SecondaryNav
|
|
7
|
+
|
|
8
|
+
Secondary Nav offers users a structured way to access information. It also provides a clear hierarchy for users to
|
|
9
|
+
understand and navigate through various information.
|
|
10
|
+
|
|
11
|
+
<Canvas of={SecondaryNavStories.Default} />
|
|
12
|
+
|
|
13
|
+
## Use Cases
|
|
14
|
+
|
|
15
|
+
Use a SecondaryNav if:
|
|
16
|
+
|
|
17
|
+
- You need to organize information in a way that offers users structural guidance for complex pages like settings.
|
|
18
|
+
|
|
19
|
+
## Variations
|
|
20
|
+
|
|
21
|
+
### With categories
|
|
22
|
+
|
|
23
|
+
<Canvas of={SecondaryNavStories.WithCategories} />
|
|
24
|
+
|
|
25
|
+
## Accessibility
|
|
26
|
+
|
|
27
|
+
### Keyboard interaction
|
|
28
|
+
|
|
29
|
+
Being an interactive element, the button is reachable through keyboard navigation in the logical navigation flow of the
|
|
30
|
+
page. The following keys trigger the following actions:
|
|
31
|
+
|
|
32
|
+
| Keyboard interaction | Action |
|
|
33
|
+
| -------------------- | -------------------------------------------------------------------------------- |
|
|
34
|
+
| `Tab` | Moves focus to the one of the first item on the nav or the next one if available |
|
|
35
|
+
| `Space` | Activates the focused item, triggering the change event. |
|
|
36
|
+
| `Enter` | Activates the focused item, triggering the change event. |
|
|
37
|
+
|
|
38
|
+
## Resources
|
|
39
|
+
|
|
40
|
+
- [Figma link](https://www.figma.com/design/uLabwF3243jdMDsNSP7U9I/Bento---Components?node-id=25227-18624&t=2F6m9PIJUQlVcUJj-0)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import { action } from '@storybook/addon-actions'; import { isVue2 } from 'vue-demi'; import { storybookDocsParameter } from '@/utils/ts/storybook'; import BentoSecondaryNav from './secondary-nav.vue'; import type { Meta, StoryObj } from '@storybook/vue'; import { type BentoSecondaryNavItems } from '@/components/secondary-nav/secondary-nav.types'; const meta: Meta = { title: 'Secondary Nav', component: BentoSecondaryNav, }; export default meta; type Story = StoryObj<typeof BentoSecondaryNav>; const DEFAULT_ITEMS: BentoSecondaryNavItems = [ { label: 'Option 1', value: 'option_1', }, { label: 'Option 2', value: 'option_2', }, { label: 'Option 3', value: 'option_3', }, ]; const ITEMS_WITH_CATEGORIES: BentoSecondaryNavItems = [ { label: 'Category 1', items: [ { label: 'Option 1', value: 'option_1', }, { label: 'Option 2', value: 'option_2', }, ], }, { label: 'Category 2', items: [ { label: 'Option 3', value: 'option_3', }, { label: 'Option 4', value: 'option_4', }, { label: 'Option 5', value: 'option_5', }, ], }, ]; const DEFAULT_STORY_CODE_SAMPLE = ` <template> <bento-secondary-nav :items="items" :value="activeItem" @change="onChange" /> <template> <script lang="ts" setup> const items = [ { label: 'Option 1', value: 'option_1', }, { label: 'Option 2', value: 'option_2', }, { label: 'Option 3', value: 'option_3', }, ]; const activeItem = ref(items[0].value); const onChange = (newActiveItem: string) => { activeItem.value = newActiveItem; }; </script> `; export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoSecondaryNav, }, props: Object.keys(argTypes), template: ` <bento-secondary-nav v-bind="args" @change="clickedAction" /> `, setup(props) { const clickedAction = value => action('change')(value); return { // Values args: isVue2 ? props : _args, // Events clickedAction, }; }, }), args: { items: DEFAULT_ITEMS, value: 'option_1', }, parameters: storybookDocsParameter(DEFAULT_STORY_CODE_SAMPLE), }; const WITH_CATEGORIES_STORY_CODE_SAMPLE = ` <template> <bento-secondary-nav :items="items" :value="activeItem" @change="onChange" /> <template> <script lang="ts" setup> const items = [ { label: 'Category 1', items: [ { label: 'Option 1', value: 'option_1', }, { label: 'Option 2', value: 'option_2', }, ], }, { label: 'Category 2', items: [ { label: 'Option 3', value: 'option_3', }, { label: 'Option 4', value: 'option_4', }, { label: 'Option 5', value: 'option_5', }, ], }, ]; const activeItem = ref(items[0].value); const onChange = (newActiveItem: string) => { activeItem.value = newActiveItem; }; </script> `; export const WithCategories: Story = { render: (_args, { argTypes }) => ({ components: { BentoSecondaryNav, }, props: Object.keys(argTypes), template: ` <bento-secondary-nav v-bind="args" @change="clickedAction" /> `, setup(props) { const clickedAction = value => action('change')(value); return { // Values args: isVue2 ? props : _args, // Events clickedAction, }; }, }), args: { items: ITEMS_WITH_CATEGORIES, }, parameters: storybookDocsParameter(WITH_CATEGORIES_STORY_CODE_SAMPLE), };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <nav class="b-secondary-nav"> <bento-dropdown v-if="isMobile" :aria-label="t('mobileDropdownAriaLabel')" :items="dropdownItems" :static-categories="hasCategories" :model-value="activeItemValue" @update:model-value="updateActiveItemValue" /> <ul v-else class="b-secondary-nav__list"> <li v-for="item in items" :key="item.label"> <secondary-nav-category v-if="'items' in item" :label="item.label" :items="item.items" /> <secondary-nav-item v-else :label="item.label" :value="item.value" /> </li> </ul> </nav> </template> <script setup lang="ts"> import { computed, provide, readonly, ref, watch } from 'vue'; import { SecondaryNavItem } from './components/secondary-nav-item'; import { SecondaryNavCategory } from './components/secondary-nav-category'; import { SECONDARY_NAV_STATE } from './secondary-nav.keys'; import { type BentoSecondaryNavItems, type BentoSecondaryNavState } from './secondary-nav.types'; import { BentoDropdown } from '@/components/dropdown'; import { useBreakpoints } from '@vueuse/core'; import { useI18n } from '@/utils/ts/i18n'; import { type BentoListboxOptions } from '@/types/listbox'; import { BMediaQuerySMax } from '@adyen/bento-design-tokens/dist/js/bento/es6'; import messages from './messages.json'; type MessageSchema = (typeof messages)['en-US']; const { t } = useI18n<{ message: MessageSchema }>({ messages }); const breakpoints = useBreakpoints({ s: BMediaQuerySMax, }); const isMobile = breakpoints.smallerOrEqual('s'); const props = defineProps<{ /** * An array containing all the items on the nav. The array can have either {@see BentoSecondaryNavItemsCategory }, * which is used to group different items or {@see BentoSecondaryNavItemsItem } if you don't need to group them. */ items: BentoSecondaryNavItems; /** * The value of the selected item. * If nothing is passed, it defaults to the first item value in items. */ value?: string; }>(); const emit = defineEmits<{ /** * Emmited when the current selected item changes */ (e: 'change', category: string): void; }>(); const hasCategories = computed(() => props.items.some(item => Object.hasOwn(item, 'items'))); const dropdownItems = computed(() => props.items as BentoListboxOptions); const activeItemValueFallback = computed(() => { const firstDropdownItem = (dropdownItems.value as BentoSecondaryNavItems)[0]; if ('items' in firstDropdownItem) { return firstDropdownItem.items[0].value; } else { return firstDropdownItem.value; } }); const activeItemValue = ref(props.value ?? activeItemValueFallback.value); const updateActiveItemValue = (value: string) => { activeItemValue.value = value; emit('change', value); }; // The component's state is inject on `secondary-nav-item` to determine the // current active item and to change its value const state: BentoSecondaryNavState = { activeItemValue: readonly(activeItemValue), updateActiveItemValue, }; watch( () => props.value, value => { activeItemValue.value = value; } ); provide(SECONDARY_NAV_STATE, state); </script> <script lang="ts"> /** * Secondary Nav offers users a structured way to access information. * It also provides a clear hierarchy for users to understand and navigate through various information. * * @example * import { BentoSecondaryNav } from '@adyen/bento-vue2'; * * export default { * components: { BentoSecondaryNav }, * template: ` * <bento-secondary-nav :value="value" :items="items" /> * `, * setup() { * const value = ref(2) // Active item value * const items: BentoSecondaryNavItemInfo[] = [ * { label: 'Option 1', value: 1 }, * { label: 'Option 2', value: 2 }, * ]; * return { items, value }; * } * } */ export default { i18n: { messages }, }; </script> <style lang="scss" scoped src="./secondary-nav.scss" />
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <div class="b-segmented-control-button" :class="conditionalClasses"> <input :id="segmentedControlButtonId" v-bento-tooltip-directive="tooltipText" :aria-checked="isChecked" :aria-label="label" :checked="isChecked" :disabled="disabled" :value="value" type="radio" class="b-segmented-control-button__input" role="radio" @change="onClick" /> <label v-if="label" :for="segmentedControlButtonId" class="b-segmented-control-button__label-container"> <bento-typography ref="segmentedControlButtonLabel" class="b-segmented-control-button__label" el="span" variant="body" stronger > {{ label }} </bento-typography> </label> </div> </template> <script setup lang="ts"> import { computed, type ComputedRef, ref } from 'vue'; import { BentoTypography } from '@/components/typography'; import { generateUid } from '@/core/utils/ts'; import { BentoTooltipDirective as vBentoTooltipDirective } from '@/directives'; const props = withDefaults( defineProps<{ /** * Disable the radio button (optional) */ disabled?: boolean; /** * Renders the button with a 100% width */ fullWidth?: boolean; /** * The string the button will showcase */ label: string; /** * This property should not be used directly. * It will be used by v-model directive on the segmented control component */ modelValue?: string | number; /** * Renders tooltip text if provided */ tooltipText?: string; /** * Renders the button with a truncated text if the space is not enough to render the whole content */ truncate?: boolean; /** * Indicates the value of the input that will be returned when it's selected */ value: string | number; /** * Renders the button with centered text in case the text has to wrap multiple lines */ wrap?: boolean; }>(), { disabled: false, fullWidth: false, modelValue: null, truncate: false, wrap: false, tooltipText: undefined, } ); const emit = defineEmits<{ /** * Emitted every time the segmented control internal value is changed */ (e: 'update:model-value', value: string | number); }>(); const segmentedControlButtonLabel = ref(); const segmentedControlButtonId = generateUid('segmented-control-button'); const isChecked: ComputedRef<boolean> = computed(() => props.value === props.modelValue); const conditionalClasses = computed(() => ({ 'b-segmented-control-button--hug-content': !props.fullWidth, 'b-segmented-control-button--full-width': props.fullWidth, 'b-segmented-control-button--truncate': props.truncate, 'b-segmented-control-button--wrap': props.wrap && !props.truncate, })); const onClick = () => { if (!props.disabled) { emit('update:model-value', props.value); } }; </script> <script lang="ts"> /** * Internal component to be used within the BentoSegmentedControl component only */ export default { name: 'bento-segmented-control-button', }; </script> <style lang="scss" scoped src="./segmented-control-button.scss" />
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { Canvas, Story, Meta } from '@storybook/blocks';
|
|
2
|
+
import * as SegmentedControlStories from './segmented-control.stories';
|
|
3
|
+
|
|
4
|
+
<Meta title="Segmented Control" of={SegmentedControlStories} />
|
|
5
|
+
|
|
6
|
+
# Segmented Control
|
|
7
|
+
|
|
8
|
+
A segmented control affects a specific object or area and helps users quickly sort, switch or reformat content. It
|
|
9
|
+
changes the context or view of the affected element.
|
|
10
|
+
|
|
11
|
+
<Canvas withSource="closed">
|
|
12
|
+
<Story of={SegmentedControlStories.Default} />
|
|
13
|
+
</Canvas>
|
|
14
|
+
|
|
15
|
+
## Use Cases
|
|
16
|
+
|
|
17
|
+
Use a segmented control to provide related choices that affect an object or view, for example:
|
|
18
|
+
|
|
19
|
+
- to change dataset displayed within a data grid or graph (changing view)
|
|
20
|
+
- to switch between all, read and unread notifications (filtering content)
|
|
21
|
+
- to change page layout from list to grid (reformatting content)
|
|
22
|
+
|
|
23
|
+
Keep the segment names as short and clear as possible. Consider translations to other languages, where the words may be
|
|
24
|
+
significantly longer. Do not use acronyms, as although they save space, they may not be understood by the users.
|
|
25
|
+
|
|
26
|
+
When segmented control stops fitting, it collapses into a dropdown with one option always selected.
|
|
27
|
+
|
|
28
|
+
By default, the first segment is always active. Selecting another segment activates it and changes the view. There can
|
|
29
|
+
be only one active segment at a time.
|
|
30
|
+
|
|
31
|
+
### Segmented controls or tabs?
|
|
32
|
+
|
|
33
|
+
Use segmented control to change a view or to filter content within a component/section e.g. change subset of data
|
|
34
|
+
displayed in a graph. Use tabs to slice a page into distinct sub-pages, with different types of content or sets of
|
|
35
|
+
components e.g. show different content in each tab.
|
|
36
|
+
|
|
37
|
+
### Segmented controls or toggle, radio button and checkbox?
|
|
38
|
+
|
|
39
|
+
Although segmented control component can be used for binary views, like switching between a grid and list view, it
|
|
40
|
+
shouldn't be used as a binary input control.
|
|
41
|
+
|
|
42
|
+
For binary actions or choices with immediate effect, such as yes/no or on/off use a toggle instead. If they require user
|
|
43
|
+
confirmation or additional steps, use a radio button or checkbox.
|
|
44
|
+
|
|
45
|
+
### Do not use
|
|
46
|
+
|
|
47
|
+
Do not use more than 5 segments at a time. If you need to provide more options, use a dropdown component.
|
|
48
|
+
|
|
49
|
+
## Variations
|
|
50
|
+
|
|
51
|
+
Segmented control comes in two variants:
|
|
52
|
+
|
|
53
|
+
### Hug (default)
|
|
54
|
+
|
|
55
|
+
The component is as wide as the content inside of it. Each segment is also as wide as the content inside of it, which
|
|
56
|
+
means they can be uneven. This keeps the overall component smaller.
|
|
57
|
+
|
|
58
|
+
### Full width
|
|
59
|
+
|
|
60
|
+
The component takes width of the parent container (shape it sits in). This is useful in small spaces where other content
|
|
61
|
+
is also aligned edge to edge. In this variant all segments have equal width. To enable this variant, set the
|
|
62
|
+
`full-width` prop to `true`.
|
|
63
|
+
|
|
64
|
+
## Modifiers
|
|
65
|
+
|
|
66
|
+
The segmented control component has no modifiers.
|
|
67
|
+
|
|
68
|
+
## Accessibility
|
|
69
|
+
|
|
70
|
+
Since the `bento-segmented-control` component is implemented via a `fieldset` tag, it is reachable through keyboard
|
|
71
|
+
navigation and follows the logical navigation flow of the page. A detailed list of keyboard interactions is provided in
|
|
72
|
+
the table below.
|
|
73
|
+
|
|
74
|
+
### Keyboard interaction
|
|
75
|
+
|
|
76
|
+
| Keyboard interaction | Output |
|
|
77
|
+
| --------------------------- | -------------------------------------------------------------------------------- |
|
|
78
|
+
| `Tab` | Moves focus to the segmented control or away from it. |
|
|
79
|
+
| `Left arrow`, `right arrow` | When focus is on the segmented control, moves between segments and selects them. |
|
|
80
|
+
|
|
81
|
+
### Roles, states and properties
|
|
82
|
+
|
|
83
|
+
| ARIA attribute | is automatic? | fallback used | manual override |
|
|
84
|
+
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | ------------------------------------------------- |
|
|
85
|
+
| `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. | - | - |
|
|
86
|
+
| `label` | Yes. In the group it's set via an internal `aria-label`, whereas in the buttons it's set via the button's content. | - | Yes, the consumer is required to set both values. |
|
|
87
|
+
|
|
88
|
+
## Resources
|
|
89
|
+
|
|
90
|
+
- [Figma link](https://www.figma.com/file/uLabwF3243jdMDsNSP7U9I/Bento---Components?node-id=6533%3A68008&mode=dev)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import { action } from '@storybook/addon-actions'; import type { Meta, StoryObj } from '@storybook/vue'; import { ref } from 'vue'; import { isVue2 } from 'vue-demi'; import { BentoSegmentedControlEvent } from './segmented-control.types'; import BentoSegmentedControl from './segmented-control.vue'; import { storybookDocsParameter } from '@/core/utils/ts/storybook'; const ITEMS = [ { label: 'Segment 1', value: 1 }, { label: 'Segment 2', value: 2 }, { label: 'Segment 3', value: 3, disabled: true }, ]; const meta: Meta = { title: 'Segmented Control', component: BentoSegmentedControl, argTypes: { items: { control: { disable: true }, }, modelValue: { control: { disable: true }, }, // Events 'update:model-value': { table: { type: { summary: '(modelValue: string | number) => void' } }, control: { disable: true }, }, input: { table: { type: { summary: '(modelValue: string | number) => void' } }, control: { disable: true }, }, }, }; export default meta; type Story = StoryObj<typeof BentoSegmentedControl>; const defaultCode = ` <template> <bento-segmented-control v-model="selectedValue" :items="${JSON.stringify(ITEMS)}" aria-label="Segmented control example" full-width wrap @update:model-value="updateaction" @input="inputAction" /> </template> <script setup lang="ts"> import { BentoSegmentedControl, BentoSegmentedControlValue } from '@adyen/bento-vue2' import { ref } from 'vue' const selectedValue = ref<BentoSegmentedControlValue>() // Events const updateaction = () => { // Manually control the "@update:model-value" event in Vue3 } const inputAction = () => { // Manually control the "input" event in Vue2 } </script> `; export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoSegmentedControl }, props: Object.keys(argTypes), template: ` <bento-segmented-control v-bind="args" v-model="selectedValue" :items="items" aria-label="Segmented control example" @update:model-value="updateAction" @input="inputAction" /> `, setup(props) { const selectedValue = ref(ITEMS[0].value); const items = ITEMS; const updateAction = value => { action(BentoSegmentedControlEvent.UPDATE)(value); }; const inputAction = value => { action('input')(value); }; return { // Values args: isVue2 ? props : _args, selectedValue, items, // Events updateAction, inputAction, }; }, }), args: {}, parameters: storybookDocsParameter(defaultCode), };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <div ref="segmentedControlRef" role="group" class="b-segmented-control" :class="conditionalClasses"> <segmented-control-button v-for="(item, index) in computedItems" ref="segmentedControlButtonRef" :key="`segmented-control-item-${index}-${item.label}`" class="b-segmented-control__item" :label="item.label" :value="item.value" :disabled="item.disabled" :truncate="truncate" :wrap="wrap" :full-width="fullWidth" :model-value="inputValue" :tooltip-text="item.tooltipText" @update:model-value="onInput" /> </div> </template> <script setup lang="ts"> import { computed, onMounted, ref, toRef, watch } from 'vue'; import SegmentedControlButton from './components/segmented-control-button/segmented-control-button.vue'; import { type BentoSegmentedControlProps, type BentoSegmentedControlValue } from './segmented-control.types'; import { observeSizeOfElement } from '@/utils/ts/resize'; import { printDevelopmentWarning } from '@/utils/ts/print-development-warning'; const emit = defineEmits<{ /** * Emitted every time the segmented control internal value is changed for Vue3 */ (e: 'update:model-value', value: any); /** * Emitted every time the segmented control internal value is changed for Vue2 * */ (e: 'input', value: BentoSegmentedControlValue): void; }>(); const props = withDefaults(defineProps<BentoSegmentedControlProps>(), { ariaLabel: null, fullWidth: false, wrap: false, modelValue: null, }); const inputValue = toRef(props, 'modelValue'); const segmentedControlRef = ref(); const segmentedControlButtonRef = ref(); const hasEllipsis = ref(); const truncate = computed(() => !props.wrap); watch( () => props.items, () => { if (props.items.length > 5) { printDevelopmentWarning( 'Do not use when you need to provide more than 5 segments at a time. If you need to provide more options, use a dropdown component.' ); } }, { immediate: true } ); onMounted(() => { observeSizeOfElement(segmentedControlRef.value, () => { if (segmentedControlRef.value) { hasEllipsis.value = segmentedControlButtonRef?.value?.map( el => el.$refs.segmentedControlButtonLabel.$el.offsetWidth < el.$refs.segmentedControlButtonLabel.$el.scrollWidth ); } }); }); const computedItems = computed(() => props.items.map((el, index) => ({ ...el, tooltipText: truncate.value && hasEllipsis?.value?.[index] ? el.label : undefined, })) ); const onInput = value => { emit('update:model-value', value); emit('input', value); }; const conditionalClasses = computed(() => ({ 'b-segmented-control--hug': !props.fullWidth, 'b-segmented-control--full-width': props.fullWidth, 'b-segmented-control--truncate': truncate.value, 'b-segmented-control--wrap': props.wrap, })); </script> <script lang="ts"> /** * @example * import { BentoSegmentedControl } from '@adyen/bento-vue2'; * * export default { * components: { BentoSegmentedControl }, * template: ` * <bento-segmented-control * :items="[{ label: 'Segment 1', value: 1 }, { label: 'Segment 2', value: 2 }, { label: 'Segment 3', value: 3, disabled: true }]" * :model-value="1" * @update:model-value="yourUpdateHandler" * /> * ` * } */ export default { name: 'bento-segmented-control', model: { prop: 'modelValue' }, }; </script> <style lang="scss" scoped src="./segmented-control.scss" />
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { Canvas, Meta } from '@storybook/blocks';
|
|
2
|
+
import * as SelectionCardGroupStories from './selection-card-group.stories';
|
|
3
|
+
|
|
4
|
+
<Meta of={SelectionCardGroupStories} />
|
|
5
|
+
|
|
6
|
+
# Selection Card Group
|
|
7
|
+
|
|
8
|
+
Selection cards that are related to each other form a group using the `bento-selection-card-group` component, which also
|
|
9
|
+
handles the group's label and error handling logic.
|
|
10
|
+
|
|
11
|
+
<Canvas of={SelectionCardGroupStories.EverythingBagel} />
|
|
12
|
+
|
|
13
|
+
## Use Cases
|
|
14
|
+
|
|
15
|
+
Use a `bento-selection-card-group` if:
|
|
16
|
+
|
|
17
|
+
- You need to render related `bento-selection-card`
|
|
18
|
+
- You need to render `bento-selection-card` with a `radio` input
|
|
19
|
+
|
|
20
|
+
### Do not use
|
|
21
|
+
|
|
22
|
+
Use a `bento-selection-card-group` if you only need to render one `bento-selection-card` in its default variant (i.e.
|
|
23
|
+
with a `checkbox` input and without an associated label). In this case, use the `bento-selection-card` component itself.
|
|
24
|
+
|
|
25
|
+
## Variations
|
|
26
|
+
|
|
27
|
+
### Variant
|
|
28
|
+
|
|
29
|
+
Determines what kind of input element and selection logic is applied to the `bento-selection-card`s. The possible
|
|
30
|
+
`variants` supported are:
|
|
31
|
+
|
|
32
|
+
- `checkbox`: Renders a group of `bento-selection-card` with checkbox inputs. Multiple values can be selected at the
|
|
33
|
+
same time. The `modelValue` prop should be an array of values (or an empty array if no card is selected).
|
|
34
|
+
|
|
35
|
+
<Canvas of={SelectionCardGroupStories.Default} />
|
|
36
|
+
|
|
37
|
+
- `radio`: Renders a group of `bento-selection-card` with radio inputs. Only one value can be selected at a time. The
|
|
38
|
+
`modelValue` prop should correspond to the `value` of the selected card (or `undefined` if no card has been
|
|
39
|
+
selected).
|
|
40
|
+
|
|
41
|
+
<Canvas of={SelectionCardGroupStories.Radio} />
|
|
42
|
+
|
|
43
|
+
## Modifiers
|
|
44
|
+
|
|
45
|
+
### Layout
|
|
46
|
+
|
|
47
|
+
Items in a `bento-selection-card-group` can be stacked vertically or horizontally by passing the correct value to the
|
|
48
|
+
`layout` prop. In particular:
|
|
49
|
+
|
|
50
|
+
- `horizontal`: will render the cards in the group next to each other.
|
|
51
|
+
- `vertical`: will stack the cards in the group vertically.
|
|
52
|
+
|
|
53
|
+
### Items per row
|
|
54
|
+
|
|
55
|
+
Passing the property `itemsPerRow` to a `bento-selection-card-group` enables you to control the number of cards
|
|
56
|
+
displayed per row when the layout is set to `horizontal`.
|
|
57
|
+
|
|
58
|
+
<Canvas of={SelectionCardGroupStories.ItemsPerRow} />
|
|
59
|
+
|
|
60
|
+
The `itemsPerRow` props is an object of type `BentoSelectionCardGroupItemsPerRow` and is of the format:
|
|
61
|
+
|
|
62
|
+
```javascript
|
|
63
|
+
{
|
|
64
|
+
small: Number,
|
|
65
|
+
medium: Number,
|
|
66
|
+
large: Number
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Where the values for each key represent the number of cards displayed per row on the specific container size. Note that
|
|
71
|
+
you can pass a maximum of 6 items per row.
|
|
72
|
+
|
|
73
|
+
### Optional label
|
|
74
|
+
|
|
75
|
+
Shows the `optional` label next to the main group label when the `optional` prop is set to `true`.
|
|
76
|
+
|
|
77
|
+
### Required label
|
|
78
|
+
|
|
79
|
+
Shows the `required` label next to the main group label when the `required` prop is set to `true`.
|
|
80
|
+
|
|
81
|
+
### Icon slot
|
|
82
|
+
|
|
83
|
+
Each card in the group can be rendered with an icon rendering on its left. To achieve this, pass the icon slot as
|
|
84
|
+
follows in the code example below.
|
|
85
|
+
|
|
86
|
+
<Canvas of={SelectionCardGroupStories.WithSlots} />
|
|
87
|
+
|
|
88
|
+
### Content slot
|
|
89
|
+
|
|
90
|
+
Each card in the group can be rendered with a content slot rendering when the card is selected. To achieve this, pass
|
|
91
|
+
the icon slot as follows in the code example below.
|
|
92
|
+
|
|
93
|
+
<Canvas of={SelectionCardGroupStories.WithSlots} />
|
|
94
|
+
|
|
95
|
+
## Accessibility
|
|
96
|
+
|
|
97
|
+
### Keyboard interaction
|
|
98
|
+
|
|
99
|
+
The `bento-selection-card-group` has been implemented using WAI-ARIA's pattern for grouping controls. Since the
|
|
100
|
+
component is implemented via a `fieldset` tag, it is reachable through keyboard navigation and follows the logical
|
|
101
|
+
navigation flow of the page. A detailed list of keyboard interactions is provided in the table below.
|
|
102
|
+
|
|
103
|
+
| Keyboard interaction | Output |
|
|
104
|
+
| -------------------- | ------------------------------------------------------------- |
|
|
105
|
+
| `Tab` | Moves focus to each checkbox in the group or away from it. |
|
|
106
|
+
| `Space` | When focus is on one of the checkboxes, it selects the value. |
|
|
107
|
+
|
|
108
|
+
### Roles, states and properties
|
|
109
|
+
|
|
110
|
+
| ARIA attribute | is automatic? | fallback used | manual override |
|
|
111
|
+
| -------------- | ---------------------------------------------------------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
112
|
+
| `role` | Yes, through the semantic `fieldset` element, which groups all its child elements. | - | - |
|
|
113
|
+
| `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. |
|
|
114
|
+
| `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. |
|
|
115
|
+
|
|
116
|
+
## Resources
|
|
117
|
+
|
|
118
|
+
- [Figma link](https://www.figma.com/design/uLabwF3243jdMDsNSP7U9I/Bento---Components?node-id=25526-44502&node-type=frame&t=vr0mgcsqz2FWuNua-0)
|
|
119
|
+
- [WAI-ARIA guidelines for grouping controls](https://www.w3.org/WAI/tutorials/forms/grouping/)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import { ref } from 'vue'; import { isVue2 } from 'vue-demi'; import { action } from '@storybook/addon-actions'; import RetailIcon from '@adyen/ui-assets-icons-16/vue/retail'; import LeafIcon from '@adyen/ui-assets-icons-16/vue/leaf'; import { storybookDocsParameter } from '@/utils/ts/storybook'; import BentoSelectionCardGroup from './selection-card-group.vue'; import type { Meta, StoryObj } from '@storybook/vue'; import SelectCardGroupEverythingBagelExample from './__tests__/selection-card-group-everything-bagel-example.vue?raw'; import SelectCardGroupWithSlotsExample from './__tests__/selection-card-group-with-slots-example.vue?raw'; const meta: Meta = { title: 'Selection Card/Selection Card Group', component: BentoSelectionCardGroup, argTypes: { layout: { control: { type: 'radio' }, }, 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 BentoSelectionCardGroup>; const ITEMS = [ { title: 'Card 1', value: 'card-1', }, { title: 'Card 2', value: 'card-2', }, { title: 'Card 3', value: 'card-3', }, { title: 'Card 4', value: 'card-4', }, ]; const ITEMS_WITH_CONTENT = [ { title: 'Card 1', value: 'card-1', content: 'Slot content 1', icon: RetailIcon, fullyClickable: true, }, { title: 'Card 2', value: 'card-2', content: 'Slot content 2', icon: LeafIcon, fullyClickable: true, }, ]; const ITEMS_EVERYTHING_BAGEL = [ { title: 'Card 1', value: 'card-1', description: 'Description card 1', subtitle: 'Subtitle card 1', content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus et tellus at sem finibus posuere. Quisque nec velit sapien. Proin ultrices enim at placerat rhoncus. Ut volutpat nisl non hendrerit laoreet. ', dynamicContent: 'Dynamic content 1', icon: RetailIcon, fullyClickable: true, }, { title: 'Card 2', value: 'card-2', description: 'Description card 2', subtitle: 'Subtitle card 2', content: 'Content slot 2', icon: LeafIcon, fullyClickable: true, }, ]; const defaultCode = ` <template> <bento-selection-card-group label="Your group label" :items="items" v-model="selectedValue" /> </template> <script setup lang="ts"> import { BentoSelectionCardGroup } from '@adyen/bento-vue2'; const items = ref(${JSON.stringify(ITEMS)}); const selectedValue = ref([]); </script> `; export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoSelectionCardGroup }, props: Object.keys(argTypes), template: ` <bento-selection-card-group v-bind="args" :model-value="selectedValue" @update:model-value="updateAction" /> `, setup(props) { const args = isVue2 ? props : _args; const selectedValue = ref(args.modelValue); const updateAction = value => { selectedValue.value = value; action('update:model-value')(value); }; return { // Values args: isVue2 ? props : _args, selectedValue, updateAction, }; }, }), args: { label: 'Label', items: ITEMS, itemsPerRow: {}, modelValue: [], }, parameters: storybookDocsParameter(defaultCode), }; const radioCode = ` <template> <bento-selection-card-group label="Your group label" variant="radio" :items="items" v-model="selectedValue" /> </template> <script setup lang="ts"> import { BentoSelectionCardGroup } from '@adyen/bento-vue2'; const items = ref(${JSON.stringify(ITEMS)}); const selectedValue = ref(undefined); </script> `; export const Radio: Story = { ...Default, args: { label: 'Label', variant: 'radio', items: ITEMS, itemsPerRow: {}, modelValue: undefined, }, parameters: storybookDocsParameter(radioCode), }; export const WithSlots: Story = { render: (_args, { argTypes }) => ({ components: { BentoSelectionCardGroup, LeafIcon, RetailIcon }, props: Object.keys(argTypes), template: ` <bento-selection-card-group v-bind="args" :model-value="selectedValue" @update:model-value="updateAction" > <template #icon="{ icon }"> <component :is="icon"/> </template> <template #content="{ content }"> <div>{{ content }}</div> </template> <template #dynamicContent="{ dynamicContent }"> <div>{{ dynamicContent }}</div> </template> </bento-selection-card-group> `, setup(props) { const selectedValue = ref([]); const updateAction = value => { selectedValue.value = value; action('update:model-value')(value); }; return { // Values args: isVue2 ? props : _args, selectedValue, updateAction, }; }, }), args: { label: 'Label', items: ITEMS_WITH_CONTENT, }, parameters: storybookDocsParameter(SelectCardGroupWithSlotsExample), }; const itemsPerRowCode = ` <template> <bento-selection-card-group label="Your group label" :items="items" :items-per-row="{ small: 2, medium: 3, large: 4 }" v-model="selectedValue" /> </template> <script setup lang="ts"> import { BentoSelectionCardGroup } from '@adyen/bento-vue2'; const items = ref(${JSON.stringify(ITEMS)}); const selectedValue = ref([]); </script> `; export const ItemsPerRow: Story = { render: (_args, { argTypes }) => ({ components: { BentoSelectionCardGroup }, props: Object.keys(argTypes), template: ` <bento-selection-card-group v-bind="args" :model-value="selectedValue" @update:model-value="updateAction" /> `, setup(props) { const args = isVue2 ? props : _args; const selectedValue = ref(args.modelValue); const updateAction = value => { selectedValue.value = value; action('update:model-value')(value); }; return { // Values args: isVue2 ? props : _args, selectedValue, updateAction, }; }, }), args: { label: 'With responsive items per row', items: ITEMS, layout: 'horizontal', itemsPerRow: { small: 2, medium: 3, large: 4 }, modelValue: [], }, parameters: storybookDocsParameter(itemsPerRowCode), }; export const EverythingBagel: Story = { ...WithSlots, args: { label: 'Label', items: ITEMS_EVERYTHING_BAGEL, required: true, hideLabel: false, description: 'Supporting text', tooltipText: 'Tooltip text', }, parameters: storybookDocsParameter(SelectCardGroupEverythingBagelExample), };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <controls-group ref="containerRef" :aria-describedby="shouldShowGroupError ? errorMessageId : null" :label="label" :hide-label="hideLabel" :description="description" :disabled="disabled" :tooltip-text="tooltipText" :readonly="readonly" :required="required" :optional="optional" class="b-selection-card-group" component-name="selection-card-group" > <div v-if="!itemsPerRow || layout === 'vertical'" class="b-selection-card-group__items" :class="conditionalItemsClasses" > <bento-selection-card v-for="item in items" :key="`selection-card-${item.value}`" class="b-selection-card-group__item" v-bind="item" :model-value="inputValue" :readonly="readonly || item.readonly" :has-error="shouldShowGroupError" :disabled="disabled || item.disabled" @update:model-value="onInput" > <template v-if="hasSlot('icon') && item.icon" #icon> <slot name="icon" v-bind="item"></slot> </template> <template v-if="hasSlot('content') && item.content" #content> <slot name="content" v-bind="item"></slot> </template> <template v-if="hasSlot('dynamicContent') && item.dynamicContent" #dynamicContent> <slot name="dynamicContent" v-bind="item"></slot> </template> </bento-selection-card> </div> <template v-else> <div :key="`selection-card-group-layout-breakpoint-${currentBreakpoint}`"> <grid-layout v-for="(row, rowIndex) in computedRows" :key="`selection-card-group-row-${rowIndex}-cols-${computedItemsPerRow}`" class="b-selection-card-group__items--wrap" :column-width="computedColumnWidth" > <template v-for="(item, columnIndex) in row" #[`col-${columnIndex+1}`]> <bento-selection-card :key="`selection-card-${item.value}`" class="b-selection-card-group__item b-selection-card-group__item--full-height" v-bind="item" :model-value="inputValue" :readonly="readonly || item.readonly" :has-error="shouldShowGroupError" :disabled="disabled || item.disabled" @update:model-value="onInput" > <template v-if="hasSlot('icon') && item.icon" #icon> <slot name="icon" v-bind="item"></slot> </template> <template v-if="hasSlot('content') && item.content" #content> <slot name="content" v-bind="item"></slot> </template> <template v-if="hasSlot('dynamicContent') && item.dynamicContent" #dynamicContent> <slot name="dynamicContent" v-bind="item"></slot> </template> </bento-selection-card> </template> </grid-layout> </div> </template> <error-message v-if="shouldShowGroupError" :id="errorMessageId" :error-message="errorMessage" class="b-selection-card-group__error-message" /> </controls-group> </template> <script setup lang="ts"> import { computed, onMounted, provide, ref, useSlots, watch } from 'vue'; import { useElementSize } from '@vueuse/core'; import { BMediaQueryMMax, BMediaQuerySMax } from '@adyen/bento-design-tokens/dist/js/bento/es6'; import { type BentoSelectionCardGroupEmits, type BentoSelectionCardGroupProps } from './selection-card-group.types'; import BentoSelectionCard from '../../selection-card.vue'; import { ControlsGroup, ErrorMessage, GridLayout } from '../../../internal'; import { SELECTION_CARD_RADIO_INPUT_INJECTION_KEY } from '../../selection-card.keys'; import { generateUid } from '@/core/utils/ts'; import { useFormFieldEmits } from '@/utils/ts/form-field/form-field'; const props = withDefaults(defineProps<BentoSelectionCardGroupProps>(), { description: null, disabled: false, errorMessage: null, formId: null, hideLabel: null, label: null, layout: 'vertical', itemsPerRow: undefined, modelValue: null, optional: false, required: false, tooltipText: null, readonly: false, variant: 'checkbox', }); const emit = defineEmits<BentoSelectionCardGroupEmits>(); const { emitValue } = useFormFieldEmits(emit); const slots = useSlots(); // Slots logic // Temporary, use-has-slots composable doesn't currently work for vue3 named scoped slots // We do the same in bento-checkbox-group const hasSlot = name => !!slots[name]; const conditionalItemsClasses = computed(() => ({ 'b-selection-card-group__items--horizontal': props.layout === 'horizontal', })); const inputValue = ref(props.modelValue); watch( () => props.modelValue, () => { inputValue.value = props.modelValue; } ); // Error message logic const errorMessageId = generateUid('error'); const shouldShowGroupError = computed(() => !props.disabled && !props.readonly && !!props.errorMessage); // itemsPerRow logic const containerRef = ref<HTMLElement | null>(null); const { width } = useElementSize(containerRef); const smallBreakpoint = computed(() => BMediaQuerySMax.split('px')[0]); const mediumBreakpoint = computed(() => BMediaQueryMMax.split('px')[0]); const currentBreakpoint = computed(() => { if (width.value < smallBreakpoint.value) { return 'small'; } if (width.value >= smallBreakpoint.value && width.value < mediumBreakpoint.value) { return 'medium'; } return 'large'; }); const computedItemsPerRow = computed(() => { return (props.itemsPerRow?.[currentBreakpoint?.value] as number) || props.items.length; }); const computedRows = computed(() => { if (!props.itemsPerRow) { return []; } const rows = []; for (let i = 0; i < props.items.length; i += computedItemsPerRow?.value) { rows.push(props.items.slice(i, i + computedItemsPerRow?.value)); } return rows; }); // We need to compute the column width on 12 because we're using a 12-column system for out grid-layout logic const itemColumnWidth = computed(() => 12 / computedItemsPerRow?.value); /** * Filling the columnWidth array with the same parameters per item to pass * to the `grid-layout` layout so the each card can have * the same width according to the screen size. */ const computedColumnWidth = computed(() => { return computedItemsPerRow?.value ? new Array(computedItemsPerRow?.value).fill({ // Instead of passing the actual screen sizes values, passing the same values per each screen size and recomputing it makes sure visually the change is smoother and not laggy small: itemColumnWidth.value, medium: itemColumnWidth.value, large: itemColumnWidth.value, }) : []; }); const onInput = value => { inputValue.value = value; emitValue(value); }; const isRadioVariant = computed(() => props.variant === 'radio'); provide(SELECTION_CARD_RADIO_INPUT_INJECTION_KEY, isRadioVariant); onMounted(() => { if (props.itemsPerRow) { Object.values(props.itemsPerRow).forEach(el => { if (el && (el < 2 || el > 6)) { throw new Error( 'itemsPerRow values must have a value between 2 and 6 items per row. Read the documentation for more information.' ); } }); } }); </script> <script lang="ts"> /** * Group of bento-selection-cards. * * @example * import { BentoSelectionCardGroup } from '@adyen/bento-vue2'; * * export default { * components: { BentoSelectionCardGroup }, * template: ` * <bento-selection-card-group * :items="[ * { * title: 'Card 1', * value: 'card-1', * content: 'Slot content 1', * icon: RetailIcon, * }, * { * title: 'Card 2', * value: 'card-2', * content: 'Slot content 2', * icon: LeafIcon, * }, * ]" * > * <template #icon="{ icon }"> * <component :is="icon"/> * </template> * <template #content="{ content }"> * <div>{{ content }}</div> * </template> * </bento-selection-card-group> * `, * } */ export default { model: { prop: 'modelValue' }, }; </script> <style lang="scss" scoped src="./selection-card-group.scss" />
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { Canvas, Meta } from '@storybook/blocks';
|
|
2
|
+
import * as SelectionCardStories from './selection-card.stories';
|
|
3
|
+
|
|
4
|
+
<Meta of={SelectionCardStories} />
|
|
5
|
+
|
|
6
|
+
# Selection Card
|
|
7
|
+
|
|
8
|
+
The `bento-selection-card` component is the equivalent of a checkbox or radio button, used when a more prominent UI is
|
|
9
|
+
needed for those. It can also hold additional content that can be static or revealed upon selection.
|
|
10
|
+
|
|
11
|
+
<Canvas of={SelectionCardStories.EverythingBagel} />
|
|
12
|
+
|
|
13
|
+
## Use Cases
|
|
14
|
+
|
|
15
|
+
Use a `bento-selection-card` when you need a more prominent UI for checkbox or radio button.
|
|
16
|
+
|
|
17
|
+
### Do not use
|
|
18
|
+
|
|
19
|
+
Do not use a `bento-selection-card`:
|
|
20
|
+
|
|
21
|
+
- In small spaces or cramped layouts;
|
|
22
|
+
- When the user has to turn an option on or off with immediate effect. In this case, use a toggle instead.
|
|
23
|
+
|
|
24
|
+
## Variations
|
|
25
|
+
|
|
26
|
+
### Default
|
|
27
|
+
|
|
28
|
+
The `bento-selection-card` comes in a default variant which accepts a title and an optional subtite and description.
|
|
29
|
+
|
|
30
|
+
<Canvas of={SelectionCardStories.Default} />
|
|
31
|
+
|
|
32
|
+
### With icon
|
|
33
|
+
|
|
34
|
+
It is possible to render the `bento-selection-card` with an icon by passing the `icon` slot.
|
|
35
|
+
|
|
36
|
+
<Canvas of={SelectionCardStories.WithIcon} />
|
|
37
|
+
|
|
38
|
+
### With content slot
|
|
39
|
+
|
|
40
|
+
It is possible to render the `bento-selection-card` with a custom content slot rendered below the description by passing
|
|
41
|
+
the `content` slot.
|
|
42
|
+
|
|
43
|
+
<Canvas of={SelectionCardStories.WithContentSlot} />
|
|
44
|
+
|
|
45
|
+
### With dynamic content slot
|
|
46
|
+
|
|
47
|
+
It is possible to render the `bento-selection-card` with a custom content slot rendered when the card is in a checked
|
|
48
|
+
state by passing the `dynamicContent` slot.
|
|
49
|
+
|
|
50
|
+
<Canvas of={SelectionCardStories.WithDynamicContentSlot} />
|
|
51
|
+
|
|
52
|
+
## Modifiers
|
|
53
|
+
|
|
54
|
+
### Disabled
|
|
55
|
+
|
|
56
|
+
Renders the `bento-selection-card` component in a disabled state. In practice this makes the component non-interactive
|
|
57
|
+
and renders a not-allowed cursor on hover.
|
|
58
|
+
|
|
59
|
+
Use this checkbox when another action has to be completed before the selection card is usable.
|
|
60
|
+
|
|
61
|
+
### Readonly
|
|
62
|
+
|
|
63
|
+
Renders the `bento-selection-card` component in a readonly state. In terms of functionality, read only states are
|
|
64
|
+
focusable and submittable in a form. They're considered to be 'active' from an application's point of view. User can
|
|
65
|
+
highlight and copy the data in them.
|
|
66
|
+
|
|
67
|
+
### Error
|
|
68
|
+
|
|
69
|
+
Renders the `bento-selection-card` component in an error state. To enable this, set the `has-error` prop to `true`.
|
|
70
|
+
|
|
71
|
+
## Accessibility
|
|
72
|
+
|
|
73
|
+
The `bento-selection-card` follows the same WCAG guidelines for developing accessible checkboxes and radio buttons. Like
|
|
74
|
+
checkboxes and radio buttons, the whole card area is clickable except for the content slot, since this one might be
|
|
75
|
+
interactive.
|
|
76
|
+
|
|
77
|
+
## Fully clickable
|
|
78
|
+
|
|
79
|
+
The `bento-selection-card` by default _does not_ select the card when clicking over the content inside the `content` or
|
|
80
|
+
`dynamicContent` slots, this is so that interactive elements (such as `bento-dropdown`, etc) can be added and interacted
|
|
81
|
+
with. The `bento-selection-card` with `fullyClickable` enabled (`true`) can be selected when clicking over the `content`
|
|
82
|
+
and `dynamicContent` slots, for cases when no interactive elements are needed inside these slots.
|
|
83
|
+
|
|
84
|
+
> Do not place interactive elements inside `content` or `dynamicContent` slots if `fullyClickable` is set to `true`.
|
|
85
|
+
|
|
86
|
+
### Keyboard interaction
|
|
87
|
+
|
|
88
|
+
The `bento-selection-card` is reachable through keyboard navigation and follows the logical navigation flow of the page.
|
|
89
|
+
A detailed list of keyboard interactions is provided in the table below.
|
|
90
|
+
|
|
91
|
+
| Keyboard interaction | Output |
|
|
92
|
+
| -------------------- | ------------------------------------------------------- |
|
|
93
|
+
| `Tab` | Moves focus to the selection card or away from it. |
|
|
94
|
+
| `Space` | When focus is on the selection card, selects the value. |
|
|
95
|
+
|
|
96
|
+
### Roles, states and properties
|
|
97
|
+
|
|
98
|
+
| ARIA attribute | is automatic? | fallback used | manual override |
|
|
99
|
+
| -------------- | ---------------------------------------------------------------------------------------- | ------------- | --------------- |
|
|
100
|
+
| `aria-label` | Yes, corresponds to the `title` text. | - |
|
|
101
|
+
| `role` | Yes, through the semantic `input` element with `role="checkbox"` or `role="radio"` set. | - | - |
|
|
102
|
+
| `aria-checked` | Yes, set when the selection card gets selected through the `input`'s `checked` property. | - | - |
|
|
103
|
+
|
|
104
|
+
## Resources
|
|
105
|
+
|
|
106
|
+
- [Figma link](https://www.figma.com/design/uLabwF3243jdMDsNSP7U9I/Bento---Components?node-id=25526-44502&node-type=frame&t=PhbWh8b862cBYSzF-0)
|
|
107
|
+
- [WAI-ARIA checkbox guidelines](https://www.w3.org/WAI/ARIA/apg/patterns/checkbox/examples/checkbox/)
|
|
108
|
+
- [WAI-ARIA radio guidelines](https://www.w3.org/WAI/ARIA/apg/patterns/radio/)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import { action } from '@storybook/addon-actions'; import { isVue2 } from 'vue-demi'; import { ref } from 'vue'; import { storybookDocsParameter } from '@/utils/ts/storybook'; import BentoSelectionCard from './selection-card.vue'; import type { Meta, StoryObj } from '@storybook/vue'; import EmoticonHappyIcon from '@adyen/ui-assets-icons-16/vue/emoticon-happy'; const meta: Meta = { title: 'Selection Card', component: BentoSelectionCard, }; export default meta; type Story = StoryObj<typeof BentoSelectionCard>; const defaultCode = ` <bento-selection-card title="Card title" value="card-value" :model-value.sync="yourModelValueVariable" /> `; export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoSelectionCard }, props: Object.keys(argTypes), template: ` <bento-selection-card v-bind="args" :model-value="selectedValue" @update:model-value="clickedAction" /> `, setup(props) { const selectedValue = ref(undefined); const clickedAction = value => { selectedValue.value = value; action('input')(value); action('update:model-value')(value); }; return { // Values args: isVue2 ? props : _args, selectedValue, // Events clickedAction, }; }, }), args: { title: 'Card title', value: 'value', }, parameters: storybookDocsParameter(defaultCode), }; const withIconCode = ` <template> <bento-selection-card title="Card title" value="card-value" :model-value.sync="yourModelValueVariable" > <template #icon> <emoticon-happy-icon /> </template> </bento-selection-card> </template> <script setup lang="ts"> import { BentoSelectionCard } from '@adyen/bento-vue2'; import EmoticonHappyIcon from '@adyen/ui-assets-icons-16/vue/emoticon-happy'; </script> `; export const WithIcon: Story = { render: (_args, { argTypes }) => ({ components: { BentoSelectionCard, EmoticonHappyIcon }, props: Object.keys(argTypes), template: ` <bento-selection-card v-bind="args" :model-value="selectedValue" @update:model-value="clickedAction" > <template #icon> <emoticon-happy-icon /> </template> </bento-selection-card> `, setup(props) { const selectedValue = ref(undefined); const clickedAction = value => { selectedValue.value = value; action('input')(value); action('update:model-value')(value); }; return { // Values args: isVue2 ? props : _args, selectedValue, // Events clickedAction, }; }, }), args: { title: 'Card title', value: 'value', }, parameters: storybookDocsParameter(withIconCode), }; const withContentSlotCode = ` <template> <bento-selection-card title="Card title" value="card-value" :model-value.sync="yourModelValueVariable" > <template #content> I'm a content slot that can be replaced with any content! </template> </bento-selection-card> </template> `; export const WithContentSlot: Story = { render: (_args, { argTypes }) => ({ components: { BentoSelectionCard }, props: Object.keys(argTypes), template: ` <bento-selection-card v-bind="args" :model-value="selectedValue" @update:model-value="clickedAction" > <template #content> I'm a content slot that can be replaced with any content! </template> </bento-selection-card> `, setup(props) { const selectedValue = ref(undefined); const clickedAction = value => { selectedValue.value = value; action('input')(value); action('update:model-value')(value); }; return { // Values args: isVue2 ? props : _args, selectedValue, // Events clickedAction, }; }, }), args: { title: 'Card title', value: 'value', }, parameters: storybookDocsParameter(withContentSlotCode), }; const withDynamicContentSlotCode = ` <template> <bento-selection-card title="Card title" value="card-value" :model-value.sync="yourModelValueVariable" > <template #dynamicContent> I'm a content slot that will only show when the card is selected! </template> </bento-selection-card> </template> `; export const WithDynamicContentSlot: Story = { render: (_args, { argTypes }) => ({ components: { BentoSelectionCard }, props: Object.keys(argTypes), template: ` <bento-selection-card v-bind="args" :model-value="selectedValue" @update:model-value="clickedAction" > <template #dynamicContent> I'm a content slot that will only show when the card is selected! </template> </bento-selection-card> `, setup(props) { const selectedValue = ref(undefined); const clickedAction = value => { selectedValue.value = value; action('input')(value); action('update:model-value')(value); }; return { // Values args: isVue2 ? props : _args, selectedValue, // Events clickedAction, }; }, }), args: { title: 'Card title', value: 'value', }, parameters: storybookDocsParameter(withDynamicContentSlotCode), }; const everythingBagelCode = ` <template> <bento-selection-card title="Card title" subtitle="Subtitle" description="Description" value="card-value" :model-value.sync="yourModelValueVariable" > <template #icon> <emoticon-happy-icon /> </template> <template #content> I'm a content slot that can be replaced with any content! </template> <template #dynamicContent> I'm a content slot that will only show when the card is selected! </template> </bento-selection-card> </template> <script setup lang="ts"> import { BentoSelectionCard } from '@adyen/bento-vue2'; import EmoticonHappyIcon from '@adyen/ui-assets-icons-16/vue/emoticon-happy'; </script> `; export const EverythingBagel: Story = { render: (_args, { argTypes }) => ({ components: { BentoSelectionCard, EmoticonHappyIcon }, props: Object.keys(argTypes), template: ` <bento-selection-card v-bind="args" :model-value="selectedValue" @update:model-value="clickedAction" > <template #icon> <emoticon-happy-icon /> </template> <template #content> I'm a content slot that can be replaced with any content! </template> <template #dynamicContent> I'm a content slot that will only show when the card is selected! </template> </bento-selection-card> `, setup(props) { const selectedValue = ref(undefined); const clickedAction = value => { selectedValue.value = value; action('input')(value); action('update:model-value')(value); }; return { // Values args: isVue2 ? props : _args, selectedValue, // Events clickedAction, }; }, }), args: { title: 'Card title', description: 'Description', subtitle: 'Subtitle', value: 'value', }, parameters: storybookDocsParameter(everythingBagelCode), };
|