@adyen/bento-mcp 0.0.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/LICENSE +21 -0
- package/README.md +113 -0
- package/dist/assets/components/accordion/accordion.docs.mdx +77 -0
- package/dist/assets/components/accordion/accordion.stories.ts +1 -0
- package/dist/assets/components/accordion/accordion.vue +1 -0
- package/dist/assets/components/accordion/components/accordion-item.stories.ts +1 -0
- package/dist/assets/components/accordion/components/accordion-item.vue +1 -0
- package/dist/assets/components/action-bar/action-bar.docs.mdx +103 -0
- package/dist/assets/components/action-bar/action-bar.stories.ts +1 -0
- package/dist/assets/components/action-bar/action-bar.vue +1 -0
- package/dist/assets/components/alert/alert.docs.mdx +106 -0
- package/dist/assets/components/alert/alert.stories.ts +1 -0
- package/dist/assets/components/alert/alert.vue +1 -0
- package/dist/assets/components/avatar/avatar.docs.mdx +44 -0
- package/dist/assets/components/avatar/avatar.stories.ts +1 -0
- package/dist/assets/components/avatar/avatar.vue +1 -0
- package/dist/assets/components/avatar/components/avatar-image/avatar-image.vue +1 -0
- package/dist/assets/components/button/button.docs.mdx +137 -0
- package/dist/assets/components/button/button.stories.ts +1 -0
- package/dist/assets/components/button/button.vue +1 -0
- package/dist/assets/components/button/components/ai-button/ai-button.docs.mdx +40 -0
- package/dist/assets/components/button/components/ai-button/ai-button.stories.ts +1 -0
- package/dist/assets/components/button/components/ai-button/ai-button.vue +1 -0
- package/dist/assets/components/button/components/button-actions/button-actions.docs.mdx +169 -0
- package/dist/assets/components/button/components/button-actions/button-actions.stories.ts +1 -0
- package/dist/assets/components/button/components/button-actions/button-actions.vue +1 -0
- package/dist/assets/components/button/components/loading-button/loading-button.docs.mdx +106 -0
- package/dist/assets/components/button/components/loading-button/loading-button.stories.ts +1 -0
- package/dist/assets/components/button/components/loading-button/loading-button.vue +1 -0
- package/dist/assets/components/button/components/toggle-button/toggle-button.docs.mdx +81 -0
- package/dist/assets/components/button/components/toggle-button/toggle-button.stories.ts +1 -0
- package/dist/assets/components/button/components/toggle-button/toggle-button.vue +1 -0
- package/dist/assets/components/card/card.docs.mdx +166 -0
- package/dist/assets/components/card/card.stories.ts +1 -0
- package/dist/assets/components/card/card.vue +1 -0
- package/dist/assets/components/checkbox/checkbox.docs.mdx +116 -0
- package/dist/assets/components/checkbox/checkbox.stories.ts +1 -0
- package/dist/assets/components/checkbox/checkbox.vue +1 -0
- package/dist/assets/components/checkbox/components/checkbox-group/checkbox-group.docs.mdx +134 -0
- package/dist/assets/components/checkbox/components/checkbox-group/checkbox-group.stories.ts +1 -0
- package/dist/assets/components/checkbox/components/checkbox-group/checkbox-group.vue +1 -0
- package/dist/assets/components/chip/chip.docs.mdx +51 -0
- package/dist/assets/components/chip/chip.stories.ts +1 -0
- package/dist/assets/components/chip/chip.vue +1 -0
- package/dist/assets/components/click-outside/click-outside.docs.mdx +30 -0
- package/dist/assets/components/click-outside/click-outside.stories.ts +1 -0
- package/dist/assets/components/code-snippet/code-snippet.docs.mdx +73 -0
- package/dist/assets/components/code-snippet/code-snippet.stories.ts +1 -0
- package/dist/assets/components/code-snippet/code-snippet.vue +1 -0
- package/dist/assets/components/country/country.docs.mdx +61 -0
- package/dist/assets/components/country/country.stories.ts +1 -0
- package/dist/assets/components/country/country.vue +1 -0
- package/dist/assets/components/currency/currency.docs.mdx +38 -0
- package/dist/assets/components/currency/currency.stories.ts +1 -0
- package/dist/assets/components/currency/currency.vue +1 -0
- package/dist/assets/components/dashed-underline/dashed-underline.docs.mdx +57 -0
- package/dist/assets/components/dashed-underline/dashed-underline.stories.ts +1 -0
- package/dist/assets/components/dashed-underline/dashed-underline.vue +1 -0
- package/dist/assets/components/data-grid/components/data-grid-cell/data-grid-cell.vue +1 -0
- package/dist/assets/components/data-grid/components/data-grid-cell-actions/data-grid-cell-actions.vue +1 -0
- package/dist/assets/components/data-grid/components/data-grid-columns/data-grid-columns.vue +1 -0
- package/dist/assets/components/data-grid/components/data-grid-config-settings/components/data-grid-config-settings-column-row/data-grid-config-settings-column-row.vue +1 -0
- package/dist/assets/components/data-grid/components/data-grid-config-settings/data-grid-config-settings.vue +1 -0
- package/dist/assets/components/data-grid/components/data-grid-lazy-load/data-grid-lazy-load.vue +1 -0
- package/dist/assets/components/data-grid/components/data-grid-row/data-grid-row.vue +1 -0
- package/dist/assets/components/data-grid/components/data-grid-static-icon/data-grid-static-icon.docs.mdx +61 -0
- package/dist/assets/components/data-grid/components/data-grid-static-icon/data-grid-static-icon.stories.ts +1 -0
- package/dist/assets/components/data-grid/components/data-grid-static-icon/data-grid-static-icon.vue +1 -0
- package/dist/assets/components/data-grid/components/data-grid-summary-cell/data-grid-summary-cell.docs.mdx +56 -0
- package/dist/assets/components/data-grid/components/data-grid-summary-cell/data-grid-summary-cell.stories.ts +1 -0
- package/dist/assets/components/data-grid/components/data-grid-summary-cell/data-grid-summary-cell.vue +1 -0
- package/dist/assets/components/data-grid/components/data-grid-trend-cell/data-grid-trend-cell.docs.mdx +48 -0
- package/dist/assets/components/data-grid/components/data-grid-trend-cell/data-grid-trend-cell.stories.ts +1 -0
- package/dist/assets/components/data-grid/components/data-grid-trend-cell/data-grid-trend-cell.vue +1 -0
- package/dist/assets/components/data-grid/data-grid.docs.mdx +951 -0
- package/dist/assets/components/data-grid/data-grid.stories.ts +1 -0
- package/dist/assets/components/data-grid/data-grid.vue +1 -0
- package/dist/assets/components/date/date.docs.mdx +49 -0
- package/dist/assets/components/date/date.stories.ts +1 -0
- package/dist/assets/components/date/date.vue +1 -0
- package/dist/assets/components/date-picker/date-picker.docs.mdx +69 -0
- package/dist/assets/components/date-picker/date-picker.stories.ts +1 -0
- package/dist/assets/components/date-picker/date-picker.vue +1 -0
- package/dist/assets/components/date-range-picker/components/date-range-picker-calendar/date-range-picker-calendar.vue +1 -0
- package/dist/assets/components/date-range-picker/date-range-picker.docs.mdx +168 -0
- package/dist/assets/components/date-range-picker/date-range-picker.stories.ts +1 -0
- package/dist/assets/components/date-range-picker/date-range-picker.vue +1 -0
- package/dist/assets/components/divider/divider.docs.mdx +27 -0
- package/dist/assets/components/divider/divider.stories.ts +1 -0
- package/dist/assets/components/divider/divider.vue +1 -0
- package/dist/assets/components/dropdown/components/dropdown-base-textbox/dropdown-base-textbox.vue +1 -0
- package/dist/assets/components/dropdown/components/dropdown-default-textbox/dropdown-default-textbox.vue +1 -0
- package/dist/assets/components/dropdown/components/dropdown-options-container/dropdown-options-container.vue +1 -0
- package/dist/assets/components/dropdown/components/dropdown-small-textbox/dropdown-small-textbox.vue +1 -0
- package/dist/assets/components/dropdown/components/variants/dropdown-avatar/dropdown-avatar.stories.ts +1 -0
- package/dist/assets/components/dropdown/components/variants/dropdown-avatar/dropdown-avatar.vue +1 -0
- package/dist/assets/components/dropdown/components/variants/dropdown-country/dropdown-country.stories.ts +1 -0
- package/dist/assets/components/dropdown/components/variants/dropdown-country/dropdown-country.vue +1 -0
- package/dist/assets/components/dropdown/components/variants/dropdown-currency/dropdown-currency.stories.ts +1 -0
- package/dist/assets/components/dropdown/components/variants/dropdown-currency/dropdown-currency.vue +1 -0
- package/dist/assets/components/dropdown/components/variants/dropdown-payment-method/dropdown-payment-method.stories.ts +1 -0
- package/dist/assets/components/dropdown/components/variants/dropdown-payment-method/dropdown-payment-method.vue +1 -0
- package/dist/assets/components/dropdown/components/variants/dropdown-tag/dropdown-tag.stories.ts +1 -0
- package/dist/assets/components/dropdown/components/variants/dropdown-tag/dropdown-tag.vue +1 -0
- package/dist/assets/components/dropdown/dropdown.docs.mdx +420 -0
- package/dist/assets/components/dropdown/dropdown.stories.ts +1 -0
- package/dist/assets/components/dropdown/dropdown.vue +1 -0
- package/dist/assets/components/empty-state/empty-state.docs.mdx +115 -0
- package/dist/assets/components/empty-state/empty-state.stories.ts +1 -0
- package/dist/assets/components/empty-state/empty-state.vue +1 -0
- package/dist/assets/components/file-uploader/components/file-uploader-file-card/components/file-uploader-file-card-animation/file-uploader-file-card-animation.vue +1 -0
- package/dist/assets/components/file-uploader/components/file-uploader-file-card/file-uploader-file-card.stories.ts +1 -0
- package/dist/assets/components/file-uploader/components/file-uploader-file-card/file-uploader-file-card.vue +1 -0
- package/dist/assets/components/file-uploader/components/file-uploader-restrictions/file-uploader-restrictions.stories.ts +1 -0
- package/dist/assets/components/file-uploader/components/file-uploader-restrictions/file-uploader-restrictions.vue +1 -0
- package/dist/assets/components/file-uploader/file-uploader.docs.mdx +117 -0
- package/dist/assets/components/file-uploader/file-uploader.stories.ts +1 -0
- package/dist/assets/components/file-uploader/file-uploader.vue +1 -0
- package/dist/assets/components/filter-bar/components/all-filters-modal/all-filters-modal.stories.ts +1 -0
- package/dist/assets/components/filter-bar/components/all-filters-modal/all-filters-modal.vue +1 -0
- package/dist/assets/components/filter-bar/components/all-filters-modal/components/all-filters-modal-button/all-filters-modal-button.vue +1 -0
- package/dist/assets/components/filter-bar/components/base-filter/base-filter.docs.mdx +90 -0
- package/dist/assets/components/filter-bar/components/base-filter/base-filter.stories.ts +1 -0
- package/dist/assets/components/filter-bar/components/base-filter/base-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/base-filter-button/base-filter-button.vue +1 -0
- package/dist/assets/components/filter-bar/components/boolean-filter/boolean-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/checkbox-group-filter/checkbox-group-filter.stories.ts +1 -0
- package/dist/assets/components/filter-bar/components/checkbox-group-filter/checkbox-group-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/date-filter/date-filter.stories.ts +1 -0
- package/dist/assets/components/filter-bar/components/date-filter/date-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/date-range-filter/date-range-filter.stories.ts +1 -0
- package/dist/assets/components/filter-bar/components/date-range-filter/date-range-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/filter-bar-button/filter-bar-button.vue +1 -0
- package/dist/assets/components/filter-bar/components/filter-bar-button-group/filter-bar-button-group.vue +1 -0
- package/dist/assets/components/filter-bar/components/input-filter/input-filter.docs.mdx +18 -0
- package/dist/assets/components/filter-bar/components/input-filter/input-filter.stories.ts +1 -0
- package/dist/assets/components/filter-bar/components/input-filter/input-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/input-with-dropdown-filter/input-with-dropdown-filter.docs.mdx +18 -0
- package/dist/assets/components/filter-bar/components/input-with-dropdown-filter/input-with-dropdown-filter.stories.ts +1 -0
- package/dist/assets/components/filter-bar/components/input-with-dropdown-filter/input-with-dropdown-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/radio-group-filter/radio-group-filter.stories.ts +1 -0
- package/dist/assets/components/filter-bar/components/radio-group-filter/radio-group-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/range-filter/range-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/select-filter/components/select-filter-button/select-filter-button.vue +1 -0
- package/dist/assets/components/filter-bar/components/select-filter/components/select-filter-dropdown/select-filter-dropdown.vue +1 -0
- package/dist/assets/components/filter-bar/components/select-filter/select-filter.stories.ts +1 -0
- package/dist/assets/components/filter-bar/components/select-filter/select-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/select-filter/variants/select-avatar-filter/select-avatar-filter.stories.ts +1 -0
- package/dist/assets/components/filter-bar/components/select-filter/variants/select-avatar-filter/select-avatar-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/select-filter/variants/select-country-filter/select-country-filter.stories.ts +1 -0
- package/dist/assets/components/filter-bar/components/select-filter/variants/select-country-filter/select-country-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/select-filter/variants/select-currency-filter/select-currency-filter.stories.ts +1 -0
- package/dist/assets/components/filter-bar/components/select-filter/variants/select-currency-filter/select-currency-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/select-filter/variants/select-payment-method-filter/select-payment-method-filter.stories.ts +1 -0
- package/dist/assets/components/filter-bar/components/select-filter/variants/select-payment-method-filter/select-payment-method-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/select-filter/variants/select-tag-filter/select-tag-filter.stories.ts +1 -0
- package/dist/assets/components/filter-bar/components/select-filter/variants/select-tag-filter/select-tag-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/toggle-filter/toggle-filter.stories.ts +1 -0
- package/dist/assets/components/filter-bar/components/toggle-filter/toggle-filter.vue +1 -0
- package/dist/assets/components/filter-bar/filter-bar.docs.mdx +1005 -0
- package/dist/assets/components/filter-bar/filter-bar.stories.ts +1 -0
- package/dist/assets/components/filter-bar/filter-bar.vue +1 -0
- package/dist/assets/components/focus-trap/focus-trap.docs.mdx +10 -0
- package/dist/assets/components/focus-trap/focus-trap.stories.ts +1 -0
- package/dist/assets/components/focus-trap/focus-trap.vue +1 -0
- package/dist/assets/components/form-layout/components/form-layout-field-tip/form-layout-field-tip.vue +1 -0
- package/dist/assets/components/form-layout/components/form-layout-group/form-layout-group.vue +1 -0
- package/dist/assets/components/form-layout/components/form-layout-title/form-layout-title.vue +1 -0
- package/dist/assets/components/form-layout/form-layout.docs.mdx +452 -0
- package/dist/assets/components/form-layout/form-layout.stories.ts +1 -0
- package/dist/assets/components/form-layout/form-layout.vue +1 -0
- package/dist/assets/components/header/header.docs.mdx +85 -0
- package/dist/assets/components/header/header.stories.ts +1 -0
- package/dist/assets/components/header/header.vue +1 -0
- package/dist/assets/components/header-with-views/components/header-with-views-all-views/header-with-views-all-views.vue +1 -0
- package/dist/assets/components/header-with-views/components/header-with-views-item-edit-form/header-with-views-edit-form.vue +1 -0
- package/dist/assets/components/header-with-views/header-with-views.docs.mdx +103 -0
- package/dist/assets/components/header-with-views/header-with-views.stories.ts +1 -0
- package/dist/assets/components/header-with-views/header-with-views.vue +1 -0
- package/dist/assets/components/image/image.docs.mdx +34 -0
- package/dist/assets/components/image/image.stories.ts +1 -0
- package/dist/assets/components/image/image.vue +1 -0
- package/dist/assets/components/info-icon/info-icon.docs.mdx +57 -0
- package/dist/assets/components/info-icon/info-icon.stories.ts +1 -0
- package/dist/assets/components/info-icon/info-icon.vue +1 -0
- package/dist/assets/components/input-field/input-field.docs.mdx +141 -0
- package/dist/assets/components/input-field/input-field.stories.ts +1 -0
- package/dist/assets/components/input-field/input-field.vue +1 -0
- package/dist/assets/components/input-field-password/input-field-password.docs.mdx +292 -0
- package/dist/assets/components/input-field-password/input-field-password.stories.ts +1 -0
- package/dist/assets/components/input-field-password/input-field-password.vue +1 -0
- package/dist/assets/components/input-field-phone-number/input-field-phone-number.docs.mdx +72 -0
- package/dist/assets/components/input-field-phone-number/input-field-phone-number.stories.ts +1 -0
- package/dist/assets/components/input-field-phone-number/input-field-phone-number.vue +1 -0
- package/dist/assets/components/inspector/inspector.docs.mdx +118 -0
- package/dist/assets/components/inspector/inspector.stories.ts +1 -0
- package/dist/assets/components/inspector/inspector.vue +1 -0
- package/dist/assets/components/internal/base-button/base-button.vue +1 -0
- package/dist/assets/components/internal/button-actions-with-menu/button-actions-with-menu.stories.ts +1 -0
- package/dist/assets/components/internal/button-actions-with-menu/button-actions-with-menu.vue +1 -0
- package/dist/assets/components/internal/calendar/calendar.stories.ts +1 -0
- package/dist/assets/components/internal/calendar/calendar.vue +1 -0
- package/dist/assets/components/internal/calendar/components/calendar-month/calendar-month.stories.ts +1 -0
- package/dist/assets/components/internal/calendar/components/calendar-month/calendar-month.vue +1 -0
- package/dist/assets/components/internal/calendar/components/calendar-month/components/calendar-month-day/__test__/calendar-month-day-stub.vue +1 -0
- package/dist/assets/components/internal/calendar/components/calendar-month/components/calendar-month-day/calendar-month-day.stories.ts +1 -0
- package/dist/assets/components/internal/calendar/components/calendar-month/components/calendar-month-day/calendar-month-day.vue +1 -0
- package/dist/assets/components/internal/calendar/components/calendar-year/calendar-year.vue +1 -0
- package/dist/assets/components/internal/calendar/components/calendar-year/components/calendar-year-month/__test__/calendar-year-month-stub.vue +1 -0
- package/dist/assets/components/internal/calendar/components/calendar-year/components/calendar-year-month/calendar-year-month.vue +1 -0
- package/dist/assets/components/internal/checkbox-input/checkbox-input.stories.ts +1 -0
- package/dist/assets/components/internal/checkbox-input/checkbox-input.vue +1 -0
- package/dist/assets/components/internal/controls-group/controls-group.vue +1 -0
- package/dist/assets/components/internal/copy/copy.docs.mdx +43 -0
- package/dist/assets/components/internal/copy/copy.stories.ts +1 -0
- package/dist/assets/components/internal/copy/copy.vue +1 -0
- package/dist/assets/components/internal/decorative-checkbox/decorative-checkbox.vue +1 -0
- package/dist/assets/components/internal/dialog-page/dialog-page.vue +1 -0
- package/dist/assets/components/internal/error-message/error-message.vue +1 -0
- package/dist/assets/components/internal/field-label/field-label.vue +1 -0
- package/dist/assets/components/internal/fixed-scroller/fixed-scroller.docs.mdx +21 -0
- package/dist/assets/components/internal/fixed-scroller/fixed-scroller.stories.ts +1 -0
- package/dist/assets/components/internal/fixed-scroller/fixed-scroller.vue +1 -0
- package/dist/assets/components/internal/footer-actions/footer-actions.vue +1 -0
- package/dist/assets/components/internal/grid-layout/grid-layout.vue +1 -0
- package/dist/assets/components/internal/header-meta/header-meta.vue +1 -0
- package/dist/assets/components/internal/info-icon-with-popover/info-icon-with-popover.vue +1 -0
- package/dist/assets/components/internal/listbox/components/listbox-multi-select/components/listbox-multi-select-options/components/listbox-multi-select-category/listbox-multi-select-category.vue +1 -0
- package/dist/assets/components/internal/listbox/components/listbox-multi-select/components/listbox-multi-select-options/listbox-multi-select-options.vue +1 -0
- package/dist/assets/components/internal/listbox/components/listbox-multi-select/listbox-multi-select.vue +1 -0
- package/dist/assets/components/internal/listbox/components/listbox-option/listbox-option.vue +1 -0
- package/dist/assets/components/internal/listbox/components/listbox-option-tag/listbox-option-tag.vue +1 -0
- package/dist/assets/components/internal/listbox/components/listbox-single-select/components/listbox-single-select-category/listbox-single-select-category.vue +1 -0
- package/dist/assets/components/internal/listbox/components/listbox-single-select/listbox-single-select.vue +1 -0
- package/dist/assets/components/internal/listbox/components/listbox-single-select-option/listbox-single-select-option.vue +1 -0
- package/dist/assets/components/internal/listbox/listbox.stories.ts +1 -0
- package/dist/assets/components/internal/listbox/listbox.vue +1 -0
- package/dist/assets/components/internal/radio-input/radio-input.stories.ts +1 -0
- package/dist/assets/components/internal/radio-input/radio-input.vue +1 -0
- package/dist/assets/components/internal/teleport/teleport.vue +1 -0
- package/dist/assets/components/internal/tooltip/tooltip.test.vue +1 -0
- package/dist/assets/components/internal/tooltip/tooltip.vue +1 -0
- package/dist/assets/components/layout/components/layout-row/layout-row.stories.ts +1 -0
- package/dist/assets/components/layout/components/layout-row/layout-row.vue +1 -0
- package/dist/assets/components/layout/layout.docs.mdx +108 -0
- package/dist/assets/components/layout/layout.stories.ts +1 -0
- package/dist/assets/components/layout/layout.vue +1 -0
- package/dist/assets/components/link/link.docs.mdx +119 -0
- package/dist/assets/components/link/link.stories.ts +1 -0
- package/dist/assets/components/link/link.vue +1 -0
- package/dist/assets/components/loading-indicator/loading-indicator.docs.mdx +65 -0
- package/dist/assets/components/loading-indicator/loading-indicator.stories.ts +1 -0
- package/dist/assets/components/loading-indicator/loading-indicator.vue +1 -0
- package/dist/assets/components/menu/components/menu-item/menu-item.vue +1 -0
- package/dist/assets/components/menu/components/menu-item-list/menu-item-list.vue +1 -0
- package/dist/assets/components/menu/components/menu-list-container/menu-list-container.vue +1 -0
- package/dist/assets/components/menu/components/sub-menu-item-list/sub-menu-item-list.vue +1 -0
- package/dist/assets/components/menu/menu.docs.mdx +121 -0
- package/dist/assets/components/menu/menu.stories.ts +1 -0
- package/dist/assets/components/menu/menu.vue +1 -0
- package/dist/assets/components/modal/components/base-modal/base-modal.vue +1 -0
- package/dist/assets/components/modal/components/modal-default/modal-default.vue +1 -0
- package/dist/assets/components/modal/components/modal-dialog/modal-dialog.vue +1 -0
- package/dist/assets/components/modal/components/modal-page/modal-page.vue +1 -0
- package/dist/assets/components/modal/modal.docs.mdx +215 -0
- package/dist/assets/components/modal/modal.stories.ts +1 -0
- package/dist/assets/components/modal/modal.vue +1 -0
- package/dist/assets/components/modal-fullscreen/components/modal-fullscreen-page.vue +1 -0
- package/dist/assets/components/modal-fullscreen/modal-fullscreen.docs.mdx +217 -0
- package/dist/assets/components/modal-fullscreen/modal-fullscreen.stories.ts +1 -0
- package/dist/assets/components/modal-fullscreen/modal-fullscreen.vue +1 -0
- package/dist/assets/components/pagination/components/pagination-context/pagination-context.vue +1 -0
- package/dist/assets/components/pagination/components/pagination-controls/pagination-controls.vue +1 -0
- package/dist/assets/components/pagination/components/pagination-results-per-page/pagination-results-per-page.vue +1 -0
- package/dist/assets/components/pagination/pagination.docs.mdx +116 -0
- package/dist/assets/components/pagination/pagination.stories.ts +1 -0
- package/dist/assets/components/pagination/pagination.vue +1 -0
- package/dist/assets/components/payment-method/payment-method.docs.mdx +34 -0
- package/dist/assets/components/payment-method/payment-method.stories.ts +1 -0
- package/dist/assets/components/payment-method/payment-method.vue +1 -0
- package/dist/assets/components/popover/components/popover-dismiss-button/popover-dismiss-button.vue +1 -0
- package/dist/assets/components/popover/components/popover-image/popover-image.vue +1 -0
- package/dist/assets/components/popover/components/popover-title/popover-title.vue +1 -0
- package/dist/assets/components/popover/popover.docs.mdx +89 -0
- package/dist/assets/components/popover/popover.stories.ts +1 -0
- package/dist/assets/components/popover/popover.vue +1 -0
- package/dist/assets/components/popper-container/popper-container.docs.mdx +11 -0
- package/dist/assets/components/popper-container/popper-container.stories.ts +1 -0
- package/dist/assets/components/popper-container/popper-container.vue +1 -0
- package/dist/assets/components/promo-banner/promo-banner.docs.mdx +94 -0
- package/dist/assets/components/promo-banner/promo-banner.stories.ts +1 -0
- package/dist/assets/components/promo-banner/promo-banner.vue +1 -0
- package/dist/assets/components/radio-group/components/radio-button/radio-button.docs.mdx +76 -0
- package/dist/assets/components/radio-group/components/radio-button/radio-button.stories.ts +1 -0
- package/dist/assets/components/radio-group/components/radio-button/radio-button.vue +1 -0
- package/dist/assets/components/radio-group/radio-group.docs.mdx +154 -0
- package/dist/assets/components/radio-group/radio-group.stories.ts +1 -0
- package/dist/assets/components/radio-group/radio-group.vue +1 -0
- package/dist/assets/components/rich-text-editor/components/rich-text-editor-content-area/rich-text-editor-content-area.vue +1 -0
- package/dist/assets/components/rich-text-editor/components/rich-text-editor-renderer/components/rich-text-editor-code-block/rich-text-editor-code-block.vue +1 -0
- package/dist/assets/components/rich-text-editor/components/rich-text-editor-renderer/components/rich-text-editor-inline-code/rich-text-editor-inline-code.vue +1 -0
- package/dist/assets/components/rich-text-editor/components/rich-text-editor-renderer/components/rich-text-editor-link/rich-text-editor-link.vue +1 -0
- package/dist/assets/components/rich-text-editor/components/rich-text-editor-renderer/components/rich-text-editor-list-item/rich-text-editor-list-item.vue +1 -0
- package/dist/assets/components/rich-text-editor/components/rich-text-editor-renderer/components/rich-text-editor-table/rich-text-editor-table.vue +1 -0
- package/dist/assets/components/rich-text-editor/rich-text-editor.docs.mdx +52 -0
- package/dist/assets/components/rich-text-editor/rich-text-editor.stories.ts +1 -0
- package/dist/assets/components/rich-text-editor/rich-text-editor.vue +1 -0
- package/dist/assets/components/search-bar/search-bar.docs.mdx +117 -0
- package/dist/assets/components/search-bar/search-bar.stories.ts +1 -0
- package/dist/assets/components/search-bar/search-bar.vue +1 -0
- package/dist/assets/components/secondary-nav/components/secondary-nav-category/secondary-nav-category.vue +1 -0
- package/dist/assets/components/secondary-nav/components/secondary-nav-item/secondary-nav-item.vue +1 -0
- package/dist/assets/components/secondary-nav/secondary-nav.docs.mdx +40 -0
- package/dist/assets/components/secondary-nav/secondary-nav.stories.ts +1 -0
- package/dist/assets/components/secondary-nav/secondary-nav.vue +1 -0
- package/dist/assets/components/segmented-control/components/segmented-control-button/segmented-control-button.vue +1 -0
- package/dist/assets/components/segmented-control/segmented-control.docs.mdx +90 -0
- package/dist/assets/components/segmented-control/segmented-control.stories.ts +1 -0
- package/dist/assets/components/segmented-control/segmented-control.vue +1 -0
- package/dist/assets/components/selection-card/components/selection-card-group/selection-card-group.docs.mdx +119 -0
- package/dist/assets/components/selection-card/components/selection-card-group/selection-card-group.stories.ts +1 -0
- package/dist/assets/components/selection-card/components/selection-card-group/selection-card-group.vue +1 -0
- package/dist/assets/components/selection-card/selection-card.docs.mdx +108 -0
- package/dist/assets/components/selection-card/selection-card.stories.ts +1 -0
- package/dist/assets/components/selection-card/selection-card.vue +1 -0
- package/dist/assets/components/sidepanel/components/sidepanel-page.stories.ts +1 -0
- package/dist/assets/components/sidepanel/components/sidepanel-page.vue +1 -0
- package/dist/assets/components/sidepanel/sidepanel.docs.mdx +129 -0
- package/dist/assets/components/sidepanel/sidepanel.stories.ts +1 -0
- package/dist/assets/components/sidepanel/sidepanel.vue +1 -0
- package/dist/assets/components/status/status.docs.mdx +62 -0
- package/dist/assets/components/status/status.stories.ts +1 -0
- package/dist/assets/components/status/status.vue +1 -0
- package/dist/assets/components/stepper/components/step/step.stories.ts +1 -0
- package/dist/assets/components/stepper/components/step/step.vue +1 -0
- package/dist/assets/components/stepper/stepper.docs.mdx +127 -0
- package/dist/assets/components/stepper/stepper.stories.ts +1 -0
- package/dist/assets/components/stepper/stepper.vue +1 -0
- package/dist/assets/components/structured-list/components/structered-list-item/structured-list-item.stories.ts +1 -0
- package/dist/assets/components/structured-list/components/structered-list-item/structured-list-item.vue +1 -0
- package/dist/assets/components/structured-list/structured-list.docs.mdx +68 -0
- package/dist/assets/components/structured-list/structured-list.stories.ts +1 -0
- package/dist/assets/components/structured-list/structured-list.vue +1 -0
- package/dist/assets/components/summary-grid/components/summary-grid-item/summary-grid-item.vue +1 -0
- package/dist/assets/components/summary-grid/components/summary-grid-item-amount/summary-grid-item-amount.stories.ts +1 -0
- package/dist/assets/components/summary-grid/components/summary-grid-item-amount/summary-grid-item-amount.vue +1 -0
- package/dist/assets/components/summary-grid/components/summary-grid-item-custom/summary-grid-item-custom.stories.ts +1 -0
- package/dist/assets/components/summary-grid/components/summary-grid-item-custom/summary-grid-item-custom.vue +1 -0
- package/dist/assets/components/summary-grid/components/summary-grid-item-numeric/summary-grid-item-numeric.stories.ts +1 -0
- package/dist/assets/components/summary-grid/components/summary-grid-item-numeric/summary-grid-item-numeric.vue +1 -0
- package/dist/assets/components/summary-grid/components/summary-grid-item-percentage/summary-grid-item-percentage.stories.ts +1 -0
- package/dist/assets/components/summary-grid/components/summary-grid-item-percentage/summary-grid-item-percentage.vue +1 -0
- package/dist/assets/components/summary-grid/components/summary-grid-item-text/summary-grid-item-text.stories.ts +1 -0
- package/dist/assets/components/summary-grid/components/summary-grid-item-text/summary-grid-item-text.vue +1 -0
- package/dist/assets/components/summary-grid/summary-grid.docs.mdx +152 -0
- package/dist/assets/components/summary-grid/summary-grid.stories.ts +1 -0
- package/dist/assets/components/summary-grid/summary-grid.vue +1 -0
- package/dist/assets/components/tabs/components/tab.vue +1 -0
- package/dist/assets/components/tabs/tabs.docs.mdx +98 -0
- package/dist/assets/components/tabs/tabs.stories.ts +1 -0
- package/dist/assets/components/tabs/tabs.vue +1 -0
- package/dist/assets/components/tag/tag.docs.mdx +137 -0
- package/dist/assets/components/tag/tag.stories.ts +1 -0
- package/dist/assets/components/tag/tag.vue +1 -0
- package/dist/assets/components/textarea/textarea.docs.mdx +53 -0
- package/dist/assets/components/textarea/textarea.stories.ts +1 -0
- package/dist/assets/components/textarea/textarea.vue +1 -0
- package/dist/assets/components/timeline/components/timeline-item/timeline-item.stories.ts +1 -0
- package/dist/assets/components/timeline/components/timeline-item/timeline-item.vue +1 -0
- package/dist/assets/components/timeline/timeline.docs.mdx +131 -0
- package/dist/assets/components/timeline/timeline.stories.ts +1 -0
- package/dist/assets/components/timeline/timeline.vue +1 -0
- package/dist/assets/components/toast/components/toast-item/toast-item.vue +1 -0
- package/dist/assets/components/toast/components/toast-shadow-stack/toast-shadow-stack.vue +1 -0
- package/dist/assets/components/toast/toast.docs.mdx +151 -0
- package/dist/assets/components/toast/toast.stories.ts +1 -0
- package/dist/assets/components/toast/toast.vue +1 -0
- package/dist/assets/components/toggle/toggle.docs.mdx +81 -0
- package/dist/assets/components/toggle/toggle.stories.ts +1 -0
- package/dist/assets/components/toggle/toggle.vue +1 -0
- package/dist/assets/components/tutorial/components/tutorial-popover/components/tutorial-indicator/tutorial-indicator.vue +1 -0
- package/dist/assets/components/tutorial/components/tutorial-popover/tutorial-popover.vue +1 -0
- package/dist/assets/components/tutorial/tutorial.docs.mdx +85 -0
- package/dist/assets/components/tutorial/tutorial.stories.ts +1 -0
- package/dist/assets/components/tutorial/tutorial.vue +1 -0
- package/dist/assets/components/typography/typography.docs.mdx +124 -0
- package/dist/assets/components/typography/typography.stories.ts +1 -0
- package/dist/assets/components/typography/typography.vue +1 -0
- package/dist/assets/composables/use-bento-currency/use-bento-currency.docs.mdx +28 -0
- package/dist/assets/composables/use-bento-currency/use-bento-currency.stories.ts +1 -0
- package/dist/assets/composables/use-click-outside/use-click-outside.docs.mdx +10 -0
- package/dist/assets/composables/use-click-outside/use-click-outside.stories.ts +1 -0
- package/dist/assets/directives/highlight/highlight.docs.mdx +64 -0
- package/dist/assets/directives/highlight/highlight.stories.ts +1 -0
- package/dist/assets/directives/keyboard-navigation/keyboard-navigation.docs.mdx +41 -0
- package/dist/assets/directives/keyboard-navigation/keyboard-navigation.stories.ts +1 -0
- package/dist/assets/directives/tooltip/tooltip.docs.mdx +86 -0
- package/dist/assets/directives/tooltip/tooltip.stories.ts +1 -0
- package/dist/assets/index.ts +1 -0
- package/dist/assets/templates/dashboard-template/components/dashboard-section/dashboard-section.stories.ts +1 -0
- package/dist/assets/templates/dashboard-template/components/dashboard-section/dashboard-section.vue +1 -0
- package/dist/assets/templates/dashboard-template/dashboard-template.docs.mdx +148 -0
- package/dist/assets/templates/dashboard-template/dashboard-template.stories.ts +1 -0
- package/dist/assets/templates/dashboard-template/dashboard-template.vue +1 -0
- package/dist/assets/templates/data-grid-template/data-grid-template.docs.mdx +117 -0
- package/dist/assets/templates/data-grid-template/data-grid-template.stories.ts +1 -0
- package/dist/assets/templates/data-grid-template/data-grid-template.vue +1 -0
- package/dist/main.js +30031 -0
- package/package.json +45 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <div> <bento-listbox-option v-if="!isStaticCategory" class="b-listbox-multi-select-category__option" :class="conditionalClass" :tabindex="tabindex" :aria-selected="ariaCheckedComputed !== 'false'" :aria-checked="ariaCheckedComputed" :aria-disabled="hasAllPropsDisabled" :aria-expanded="`${isOpen}`" role="treeitem" :aria-owns="categoryGroupId" :aria-labelledby="categoryNameId" @click="handleSelectAll" @enter-pressed="handleSelectAll" @space-pressed.prevent="handleSelectAll" @keydown.arrow-left.native="toggleOpen($event, false)" @keydown.arrow-right.native="toggleOpen($event, true)" > <decorative-checkbox :indeterminate="isIndeterminate" :disabled="hasAllPropsDisabled" :checked="isAllSelected" /> <span class="b-listbox-multi-select-category__label"> <bento-typography :id="categoryNameId" el="span" class="b-listbox-multi-select-category__label-text"> {{ category.label }} </bento-typography> <div aria-hidden="true" class="b-listbox-multi-select-category__label-chevron" data-testid="toggle-listbox" @click.capture.stop="toggleOpen" > <chevron-up-icon v-if="isOpen" :svg-title="chevronTitle" /> <chevron-down-icon v-else :svg-title="chevronTitle" /> </div> </span> </bento-listbox-option> <div v-else class="b-listbox-multi-select-category b-listbox-multi-select-category--static-category" aria-hidden="true" > <span class="b-listbox-multi-select-category__label"> <bento-typography el="span" variant="caption" class="b-listbox-multi-select-category__label-text"> {{ category.label }} </bento-typography> </span> </div> <div v-show="showItemChildren" :id="categoryGroupId" role="group" :aria-label="category.label"> <slot /> </div> </div> </template> <script lang="ts"> import { computed, defineComponent, type PropType, ref, toRef, watch } from 'vue'; import { BentoTypography } from '@/components/typography'; import { BentoListboxOption } from '../../../../../listbox-option'; import { DecorativeCheckbox } from '@/internal/decorative-checkbox'; import { type BentoListboxIsOptionDisabled, type BentoListboxMultiSelectValue, type BentoListboxOptionItem, type BentoListboxValue, } from '@/types/listbox'; import { useCheckboxSelectAll } from '@/composables'; import { ListboxMultiSelectCategoryClass, ListboxMultiSelectCategoryEvent, } from './listbox-multi-select-category.types'; import { useI18n } from '@/utils/ts/i18n'; import ChevronDownIcon from '@adyen/ui-assets-icons-16/vue/chevron-down'; import ChevronUpIcon from '@adyen/ui-assets-icons-16/vue/chevron-up'; import messages from './messages.json'; import { generateUid } from '@/core/utils/ts'; type MessageSchema = (typeof messages)['en-US']; /** * Listbox multi-select category list. * Allows the rendering of nested lists * * @example * import { ListboxMultiSelectCategory } from '@/internal/listbox'; * * export default { * components: { BentoListboxMultiSelectCategory }, * template: ` * <listbox-multi-select-category * :category="option" * :is-option-disabled="true" * :selected-items-list="['option-1']" * @select-category="$emit('select-category', option)" * @unselect-category="$emit('unselect-category', option)" * > * <listbox-multi-select-options * category-items * :items="[{ label: 'Option 1', value: 'option-1' }]" * :is-multi-level="true" * :selected-items-list="['option-']" * :searching="false" * @toggle-checkbox-selection="value => toggleCheckboxSelection(value)" * /> * </listbox-multi-select-category> * ` * } */ export default defineComponent({ i18n: { messages }, name: 'bento-listbox-multi-select-category', components: { BentoTypography, BentoListboxOption, ChevronDownIcon, ChevronUpIcon, DecorativeCheckbox, }, props: { /** * The option elements to populate the listbox with. * It must be an array of {@see BentoListboxOptionItem }. * * @property {string} value.label - Text to be displayed in the option * @property {string,number} value.value - Value of the option * @property {BentoListboxOptionItem} value.items - Category items list */ category: { type: Object as PropType<BentoListboxOptionItem>, required: true, }, /** * Function that allows the options to be disabled * * @type {BentoListboxIsOptionDisabled} * @param {BentoListboxOptionItem} option - listbox option object * @param {string} option.label - Option's label text * @param {string|number} option.value - Option's value * @param {unknown} option.data - Option's extra data to be passed to the default slot */ isOptionDisabled: { type: [Function] as PropType<BentoListboxIsOptionDisabled>, default: () => false, }, /** * The `input` value. * Providing an empty array will select no options. */ selectedItemsList: { type: Array as PropType<Array<BentoListboxValue>>, default: () => [], }, /** * If enabled, dropdown will display non-selectable, static categories */ staticCategories: { type: Boolean, default: false }, /** * Category's tabindex */ tabindex: { type: Number, default: -1 }, }, emits: [ ListboxMultiSelectCategoryEvent.SELECT_CATEGORY, ListboxMultiSelectCategoryEvent.UNSELECT_CATEGORY, ListboxMultiSelectCategoryEvent.TOGGLE_CATEGORY_VISIBILITY, ], setup(props, { emit }) { const isOpen = ref(false); const { t } = useI18n<{ message: MessageSchema }>({ messages }); const categoryGroupId = generateUid('category-group'); const categoryNameId = generateUid('category-name'); // This method should be used to determine if an option is disabled as it works for both vue 2 & 3. const vue2Vue3FixedisOptionDisabled = option => props.isOptionDisabled ? props.isOptionDisabled(option) : false; const hasAllPropsDisabled = computed(() => props.category.items.every(option => vue2Vue3FixedisOptionDisabled(option)) ); const isStaticCategory = toRef(props, 'staticCategories'); // Display item children if is open state or if static categories is enabled const showItemChildren = computed(() => isOpen.value || props.staticCategories); const conditionalClass = computed(() => ({ [`b-listbox-multi-select-category--${ListboxMultiSelectCategoryClass.DISABLED}`]: hasAllPropsDisabled.value, })); const toggleOpen = (event: Event, open?: boolean) => { event.preventDefault(); isOpen.value = open !== undefined ? open : !isOpen.value; emit(ListboxMultiSelectCategoryEvent.TOGGLE_CATEGORY_VISIBILITY, props.category.value); }; const optionStates = (option: BentoListboxOptionItem) => { const isDisabled = vue2Vue3FixedisOptionDisabled(option); const isPreSelected = (props.selectedItemsList as BentoListboxMultiSelectValue)?.includes(option.value) || false; return { isDisabled, isPreSelected }; }; const enabledItems = computed(() => props.category.items.filter(option => { const { isDisabled, isPreSelected } = optionStates(option); return (isDisabled && isPreSelected) || !isDisabled; }) ); const visibleSelectedList = computed( () => enabledItems.value?.filter(({ value }) => props.selectedItemsList?.includes(value) ) as unknown as BentoListboxMultiSelectValue ); const chevronTitle = computed(() => { const messageName = isOpen.value ? 'categoryOpened' : 'categoryClosed'; return t(messageName, { category: props.category.label }) as string; }); const selectAll = () => { emit(ListboxMultiSelectCategoryEvent.SELECT_CATEGORY); }; const unselectAll = () => { emit(ListboxMultiSelectCategoryEvent.UNSELECT_CATEGORY); }; const { isAllSelected, isIndeterminate, toggleSelectAll } = useCheckboxSelectAll( visibleSelectedList, enabledItems, selectAll, unselectAll ); const ariaCheckedComputed = computed(() => { if (isIndeterminate.value) { return 'mixed'; } return `${isAllSelected.value}`; }); const isSelectAllOn = ref(isAllSelected.value); const handleSelectAll = () => { isSelectAllOn.value = !isSelectAllOn.value; toggleSelectAll(isSelectAllOn.value); }; watch(isAllSelected, () => { isSelectAllOn.value = isAllSelected.value; }); return { // Values chevronTitle, conditionalClass, hasAllPropsDisabled, isOpen, isAllSelected, isIndeterminate, isStaticCategory, categoryGroupId, categoryNameId, // Computed showItemChildren, ariaCheckedComputed, // Methods handleSelectAll, toggleOpen, toggleSelectAll, }; }, }); </script> <style lang="scss" scoped src="./listbox-multi-select-category.scss" />
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <div> <div v-for="option in items" :key="`selected-${option.value}`"> <template v-if="searching && isMultiLevel"> <bento-listbox-option class="b-listbox-multi-select-options" :class="conditionalClasses" :disabled="isOptionDisabled ? isOptionDisabled(option) : null" :tabindex="computedTabindex(option.value)" :aria-selected="`${isOptionSelected(option)}`" :aria-checked="`${isOptionSelected(option)}`" :aria-disabled="isOptionDisabled ? isOptionDisabled(option) : null" :role="$attrs['role']" @click="toggleSearchOption(option)" @enter-pressed="toggleSearchOption(option)" @space-pressed.prevent="toggleSearchOption(option)" > <decorative-checkbox :disabled="isOptionDisabled ? isOptionDisabled(option) : null" :checked="isOptionSelected(option)" /> <slot v-bind="option"> <span class="b-listbox-multi-select__option-text"> <bento-typography el="span">{{ option.label }}</bento-typography> <bento-typography v-if="shouldShowDescription(option)" class="b-listbox-multi-select__option-description" el="span" > {{ option.description }} </bento-typography> </span> </slot> </bento-listbox-option> </template> <template v-else> <!-- Category --> <template v-if="option.items"> <listbox-multi-select-category :category="option" :static-categories="staticCategories" :is-option-disabled="isOptionDisabled" :selected-items-list="selectedItemsList" :tabindex="computedTabindex(option.value)" @select-category="$emit(ListboxMultiSelectOptionsEvent.SELECT_CATEGORY, option)" @unselect-category="$emit(ListboxMultiSelectOptionsEvent.UNSELECT_CATEGORY, option)" @toggle-category-visibility=" $emit(ListboxMultiSelectOptionsEvent.TOGGLE_CATEGORY_VISIBILITY, $event) " > <listbox-multi-select-options category-items :items="option.items" :is-option-disabled="isOptionDisabled" :items-index="itemsIndex" :focused-index="focusedIndex" :is-multi-level="isMultiLevel" :static-categories="staticCategories" :selected-items-list="selectedItemsList" :searching="searching" :role="$attrs['role']" @toggle-checkbox-selection="value => toggleCheckboxSelection(value)" /> </listbox-multi-select-category> </template> <!-- Option --> <bento-listbox-option v-else class="b-listbox-multi-select-options" :class="conditionalClasses" :disabled="isOptionDisabled ? isOptionDisabled(option) : null" :tabindex="computedTabindex(option.value)" :aria-selected="`${isOptionSelected(option)}`" :aria-checked="`${isOptionSelected(option)}`" :aria-disabled="isOptionDisabled ? isOptionDisabled(option) : null" :role="$attrs['role']" @click="toggleCheckboxSelection(option)" @enter-pressed="toggleCheckboxSelection(option)" @space-pressed.prevent="toggleCheckboxSelection(option)" > <decorative-checkbox :disabled="isOptionDisabled ? isOptionDisabled(option) : null" :checked="isOptionSelected(option)" /> <slot v-bind="option"> <span class="b-listbox-multi-select__option-text"> <bento-typography el="span">{{ option.label }}</bento-typography> <bento-typography v-if="shouldShowDescription(option)" class="b-listbox-multi-select__option-description" el="span" > {{ option.description }} </bento-typography> </span> </slot> </bento-listbox-option> </template> </div> </div> </template> <script lang="ts"> import { computed, defineComponent, type PropType } from 'vue'; import { BentoListboxOption } from '../../../listbox-option'; import { BentoTypography } from '@/components/typography'; import { ListboxMultiSelectCategory } from './components/listbox-multi-select-category'; import { DecorativeCheckbox } from '@/internal/decorative-checkbox'; import { BentoListboxEvent, type BentoListboxIsOptionDisabled, type BentoListboxOptionItem, type BentoListboxOptions, type BentoListboxValue, } from '@/types/listbox'; import { ListboxMultiSelectOptionsEvent } from './listbox-multi-select-options.types'; /** * Listbox multi-select options list * * @example * import { ListboxMultiSelectOptions } from './components/listbox-multi-select-options'; * * export default { * components: { ListboxMultiSelect }, * template: ` * <listbox-multi-select * :items="[{ label: 'Option 1', value: 'option-1' }]" * :selected-values="['option-1']" * /> * ` * } */ export default defineComponent({ name: 'listbox-multi-select-options', components: { BentoListboxOption, BentoTypography, ListboxMultiSelectCategory, DecorativeCheckbox, }, inheritAttrs: false, props: { /** * Indicates if the items listed are inside a category * so they can be padded to right by one level in recursion. * Items of categories are automatically defined as "categoryItems" */ categoryItems: { type: Boolean, default: false }, /** * Current focused option index */ focusedIndex: { type: Number, default: 0 }, /** * Function that allows the options to be disabled * * @type {BentoListboxIsOptionDisabled} * @param {BentoListboxOptionItem} option - listbox option object * @param {string} option.label - Option's label text * @param {string|number} option.value - Option's value * @param {unknown} option.data - Option's extra data to be passed to the default slot */ isOptionDisabled: { type: Function as PropType<BentoListboxIsOptionDisabled>, default: () => false, }, /** * The option elements to populate the listbox with. * It must be an array of {@see BentoListboxOptionItem }. * * @property {string} value.label - Text to be displayed in the option * @property {string,number} value.value - Value of the option */ items: { type: Array as PropType<BentoListboxOptions>, required: true, }, /** * Indicates if the elements options are inside a multi-level category */ isMultiLevel: { type: Boolean, default: false }, /** * Flag that indicates that an external search it's being made * to filter out the items inside the the multi-select listbox * * If enabled, hides the "Select all" checkbox in "multiple" mode. */ searching: { type: Boolean, default: false }, /** * List of selected items. * Providing an empty array will select no options. */ selectedItemsList: { type: Array as PropType<Array<BentoListboxValue>>, default: () => [], }, /** * If enabled, dropdown will display non-selectable, static categories */ staticCategories: { type: Boolean, default: false }, /** * Object with the index of each item. */ itemsIndex: { type: Object as PropType<Record<string, number>>, default: () => ({}), }, }, emits: [ ListboxMultiSelectOptionsEvent.TOGGLE_CHECKBOX_SELECTION, ListboxMultiSelectOptionsEvent.SELECT_CATEGORY, ListboxMultiSelectOptionsEvent.UNSELECT_CATEGORY, ListboxMultiSelectOptionsEvent.TOGGLE_CATEGORY_VISIBILITY, ], setup(props, { emit }) { const toggleCheckboxSelection = (option: BentoListboxOptionItem) => { if (props.isOptionDisabled(option)) { return; } emit(ListboxMultiSelectOptionsEvent.TOGGLE_CHECKBOX_SELECTION, option); }; const isCategory = (option: BentoListboxOptionItem) => !!option.items; const shouldShowDescription = (option: BentoListboxOptionItem) => { const isSearchingMultiLevel = props.isMultiLevel && props.searching; return option.description && (!props.isMultiLevel || isSearchingMultiLevel); }; const isOptionSelected = option => { if (option.items) { return !!option.items.find(item => props.selectedItemsList.includes(item.value)); } return props.selectedItemsList.includes(option.value); }; const toggleSearchOption = option => { if (isCategory(option)) { const event = isOptionSelected(option) ? ListboxMultiSelectOptionsEvent.UNSELECT_CATEGORY : ListboxMultiSelectOptionsEvent.SELECT_CATEGORY; emit(event, option); } else { toggleCheckboxSelection(option); } }; const conditionalClasses = computed(() => ({ 'b-listbox-multi-select-options__static-category-option': props.staticCategories, 'b-listbox-multi-select-options__category-option': props.categoryItems, })); const computedTabindex = value => (props.itemsIndex[value] === props.focusedIndex ? 0 : -1); return { // Values conditionalClasses, // Methods isOptionSelected, shouldShowDescription, toggleCheckboxSelection, toggleSearchOption, computedTabindex, // Enums BentoListboxEvent, ListboxMultiSelectOptionsEvent, }; }, }); </script> <style lang="scss" scoped src="./listbox-multi-select-options.scss" />
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <div ref="listboxRef" class="b-listbox-multi-select"> <!-- Selected options --> <div v-if="showSelectedItems"> <ul v-bento-keyboard-navigation-directive class="b-listbox-multi-select__chips" v-on="chipsSectionListeners" > <li v-for="item in selectedOptions" :key="item.value"> <bento-chip ref="selectedListboxItemsRef" :disabled="isOptionDisabled(item)" :label="item.label" condensed @click="item.items ? unselectAllCategory(item) : toggleCheckboxSelection('select', item)" /> </li> </ul> <hr class="b-listbox-multi-select__divider" aria-hidden="true" /> </div> <div v-bento-keyboard-navigation-directive v-bind="ariaAttributes" :role="computedRole" v-on="listboxListeners"> <!-- Select all --> <bento-listbox-option v-if="showSelectAllCheckbox" ref="selectAllListboxItemsRef" class="b-listbox-multi-select__option" :tabindex="focusedIndex === 0 ? 0 : -1" :aria-selected="ariaCheckedComputed !== 'false'" :aria-checked="ariaCheckedComputed" :role="computedOptionRole" @click="handleSelectAll" @enter-pressed="handleSelectAll" @space-pressed.prevent="handleSelectAll" > <decorative-checkbox :checked="isAllSelected" :indeterminate="isIndeterminate" /> <bento-typography el="span"> {{ selectAllCheckboxText }} </bento-typography> </bento-listbox-option> <hr v-if="showSelectAllCheckbox" class="b-listbox-multi-select__divider" aria-hidden="true" /> <!-- Available options --> <listbox-multi-select-options ref="listboxItemsRef" :items="items" :is-option-disabled="isOptionDisabled" :is-multi-level="isMultiLevelSelect" :static-categories="staticCategories" :searching="searching" :selected-items-list="selectedItemsList" :focused-index="focusedIndex" :items-index="computedItemsIndex" :role="computedOptionRole" @toggle-checkbox-selection="toggleCheckboxSelection('select', $event)" @select-category="selectAllCategory" @unselect-category="unselectAllCategory" @toggle-category-visibility="toggleCategoryVisibility" @space-pressed="toggleCheckboxSelection(BentoListboxEvent.SPACE, $event)" @tab-pressed="toggleCheckboxSelection(BentoListboxEvent.TAB, $event)" @enter-pressed="toggleCheckboxSelection(BentoListboxEvent.ENTER, $event)" > <template v-for="(_, innerSlot) of slots" #[innerSlot]="scope"> <slot :name="innerSlot" v-bind="scope" /> </template> </listbox-multi-select-options> </div> </div> </template> <script setup lang="ts"> import { computed, type HTMLAttributes, nextTick, ref, toRef, useAttrs, useSlots, watch } from 'vue'; import { isVue2 } from 'vue-demi'; import { DecorativeCheckbox } from '@/internal/decorative-checkbox'; import { BentoTypography } from '@/components/typography'; import { BentoChip } from '@/components/chip'; import { BentoListboxOption } from '../listbox-option'; import { ListboxMultiSelectOptions } from './components/listbox-multi-select-options'; import { useCheckboxSelectAll } from '@/composables'; import { useListboxKeyboardNavigation } from '../../composables/useListboxKeyboardNavigation'; import { useMultiLevelItems } from '../../composables/use-multi-level-items/use-multi-level-items'; import { useVisibleDomOptions } from '@/internal/listbox/composables/use-visible-dom-options/use-visible-dom-options'; import { useI18n } from '@/utils/ts/i18n'; import { BentoKeyboardNavigationDirective as vBentoKeyboardNavigationDirective } from '@/directives'; import { BentoListboxEvent, type BentoListboxMultiSelectValue, type BentoListboxOptionItem, type BentoListboxOptions, type BentoListboxValue, } from '@/types/listbox'; import { type Booleanish } from '@/types/prop-types'; import { BentoKeyboardNavigationKeyDownEvent } from '@/types/keyboard-navigation'; import { type ListboxMultiSelectProps } from './listbox-multi-select.types'; import messages from './messages.json'; type MessageSchema = (typeof messages)['en-US']; const props = withDefaults(defineProps<ListboxMultiSelectProps>(), { hasMoreItems: false, isOptionDisabled: isVue2 ? () => () => false : () => false, lazyLoadType: 'none', searching: false, staticCategories: false, }); const emit = defineEmits<{ /** * Emits when the selection changes */ (e: 'select', value: Array<BentoListboxOptionItem>): void; }>(); const { t, n } = useI18n<{ message: MessageSchema }>({ messages }); const slots = useSlots(); const attrs = useAttrs(); const ariaAttributes = computed( () => ({ 'aria-label': attrs['aria-label']?.toString() as string, 'aria-busy': attrs['aria-busy']?.toString() as Booleanish, 'aria-atomic': attrs['aria-atomic']?.toString() as Booleanish, 'aria-live': attrs['aria-live']?.toString() as string, 'aria-multiselectable': true, }) as HTMLAttributes ); const listboxRef = ref(null); const selectAllListboxItemsRef = ref(null); const selectedListboxItemsRef = ref(null); const listboxItemsRef = ref(null); const selectedItemsList = computed<Array<BentoListboxValue>>(() => props.selectedValues.map(({ value }) => value)); const { isMultiLevelSelect, flattenedItemsList } = useMultiLevelItems(toRef(props, 'items')); const isTreePattern = computed(() => isMultiLevelSelect.value && !props.staticCategories); const computedRole = computed(() => (isTreePattern.value ? 'tree' : 'listbox')); const computedOptionRole = computed(() => (isTreePattern.value ? 'treeitem' : 'option')); const optionStates = (option: BentoListboxOptionItem) => { const isDisabled = props.isOptionDisabled(option); const isPreSelected = (selectedItemsList.value as BentoListboxMultiSelectValue)?.includes(option.value) || false; return { isDisabled, isPreSelected }; }; // Items that are not disabled and pre-selected disabled items const enabledItems = computed(() => { if (props.searching && isMultiLevelSelect) { return props.items; } const fullItemsList = flattenedItemsList.value.filter(option => { const { isDisabled, isPreSelected } = optionStates(option); return (isDisabled && isPreSelected) || !isDisabled; }); props.selectedValues.forEach(selectedValue => { if (!fullItemsList.find(item => item.value === selectedValue.value)) { fullItemsList.push(selectedValue); } }); return fullItemsList; }); const visibleSelectedList = computed( () => enabledItems.value?.filter(option => { if (option.items && isMultiLevelSelect.value && props.searching) { const amountOfSelectedCategoryOptions = option.items.filter(categoryOption => (props.selectedValues ?? []).includes(categoryOption.value) ).length; return amountOfSelectedCategoryOptions === option.items.length; } return selectedItemsList.value?.includes(option.value); // Check for category }) as BentoListboxMultiSelectValue ); const calculateTotalItems = () => { if (isMultiLevelSelect.value && !props.searching) { return flattenedItemsList.value.length; } return props.items.length; }; const selectAllCheckboxText = computed(() => { const possibleSelections = enabledItems.value.length; const total = calculateTotalItems(); const amountSelectedValues = visibleSelectedList.value?.length || 0; if (amountSelectedValues === 0) { return t('selectAll', { total: n(possibleSelections) }); } if (amountSelectedValues === total) { return t('allSelected', { total: n(total) }); } return amountSelectedValues <= total ? t('numberOfTotalSelected', { number: n(amountSelectedValues), total: n(total) }) : t('numbersSelected', { number: n(amountSelectedValues) }); }); const selectAll = (selectableItems: BentoListboxOptions) => { const filterOutDisabledAndNotPreselectedOptions = option => { const { isDisabled, isPreSelected } = optionStates(option); return (isDisabled && isPreSelected) || !isDisabled; }; // Adding or removing this list of values const addOrRemoveList = selectableItems .filter(filterOutDisabledAndNotPreselectedOptions) .filter(item => !selectedItemsList.value.includes(item.value)); // MAKE SURE NOT TO ADD DUPLICATES emit('select', [...props.selectedValues, ...addOrRemoveList]); }; const unselectAll = (selectableItems: BentoListboxOptions) => { const findEnabledUnselectedItems = option => { const { isDisabled, isPreSelected } = optionStates(option); return !(isDisabled && isPreSelected); }; const unselectedItemsList = selectableItems.filter(findEnabledUnselectedItems).map(({ value }) => value); const newSelectedList = props.selectedValues.filter(item => !unselectedItemsList?.includes(item.value)); emit('select', newSelectedList); }; const selectAllItems = () => { selectAll(flattenedItemsList.value); }; const unselectAllItems = () => { unselectAll(flattenedItemsList.value); }; const { isAllSelected, isIndeterminate, toggleSelectAll } = useCheckboxSelectAll( visibleSelectedList, enabledItems, selectAllItems, unselectAllItems ); const ariaCheckedComputed = computed(() => { if (isIndeterminate.value) { return 'mixed'; } return `${isAllSelected.value}`; }); const isSelectAllOn = ref(isAllSelected.value); const handleSelectAll = () => { isSelectAllOn.value = !isSelectAllOn.value; toggleSelectAll(isSelectAllOn.value); }; watch(isAllSelected, () => { isSelectAllOn.value = isAllSelected.value; }); const selectAllCategory = (category: BentoListboxOptionItem) => { selectAll(category.items); }; const unselectAllCategory = (category: BentoListboxOptionItem) => { unselectAll(category.items); }; const toggleCheckboxSelection = (eventName, selectedOption: BentoListboxOptionItem) => { let exists = false; const newSelectedList = props.selectedValues.filter(({ value }) => { if (selectedOption.value === value) { exists = true; return false; } return true; }); if (!exists) { newSelectedList.push(selectedOption); } emit(eventName, newSelectedList); }; const selectedOptions = computed(() => { if (!isMultiLevelSelect.value) { return props.selectedValues; } const isOptionSelected = (option: BentoListboxOptionItem): boolean => (selectedItemsList.value as unknown as Array<string | number>).includes(option.value); const categoryOptionsSelected = (categoryOptions: Array<BentoListboxOptionItem>): boolean => !props.staticCategories && categoryOptions.every(option => isOptionSelected(option)); const optionsFlat = props.items.flatMap(option => option.items && !categoryOptionsSelected(option.items) ? option.items : option ); const options = optionsFlat.filter(option => { if (option.items) { return true; } return isOptionSelected(option); }); return options; }); const showSelectedItems = computed(() => { const isLazyLoad = ['automatic', 'button'].includes(props.lazyLoadType); const hasAllItemsSelected = props.selectedValues?.length > 0 && visibleSelectedList.value?.length !== flattenedItemsList.value?.length; if (isLazyLoad && props.hasMoreItems) { return !!props.selectedValues?.length; } return hasAllItemsSelected; }); const showSelectAllCheckbox = computed(() => { const isLazyLoad = ['automatic', 'button'].includes(props.lazyLoadType); if (!isLazyLoad) { return true; } return !props.hasMoreItems; }); // Keyboard navigation const expandedCategories = ref([]); const { visibleDomOptions, updateVisibleDomOptions } = useVisibleDomOptions( listboxRef, toRef(props, 'items'), computedOptionRole ); const toggleCategoryVisibility = category => { if (expandedCategories.value.includes(category)) { expandedCategories.value = expandedCategories.value.filter(item => item !== category); } else { expandedCategories.value.push(category); } nextTick(() => { updateVisibleDomOptions(); }); }; const focusItem = (nextFocusedItemIndex: number) => { visibleDomOptions.value[nextFocusedItemIndex]?.focus(); }; const { moveToNextElement, moveToPreviousElement, moveToFirstElement, moveToLastElement, preventDefault, focusedIndex, setListboxFocusedIndex, } = useListboxKeyboardNavigation( { items: visibleDomOptions, }, focusItem ); const listboxListeners = { [BentoKeyboardNavigationKeyDownEvent.ARROW_UP]: (event: CustomEvent<KeyboardEvent>) => moveToPreviousElement(event), [BentoKeyboardNavigationKeyDownEvent.ARROW_DOWN]: (event: CustomEvent<KeyboardEvent>) => moveToNextElement(event), [BentoKeyboardNavigationKeyDownEvent.HOME]: moveToFirstElement, [BentoKeyboardNavigationKeyDownEvent.END]: moveToLastElement, }; defineExpose({ /** * Method to set the index of the item to be focused on * Used for focus management to be kept in-sync with parent e.g. dropdowns */ setListboxFocusedIndex, }); const chipsSectionListeners = { [BentoKeyboardNavigationKeyDownEvent.ARROW_UP]: preventDefault, [BentoKeyboardNavigationKeyDownEvent.ARROW_DOWN]: preventDefault, }; const computedItemsIndex = computed(() => { let itemIndex = 1; const indexMap = {}; props.items.forEach(item => { let subItemsVisible = expandedCategories.value.includes(item.value); if (item.items) { if (props.staticCategories) { subItemsVisible = true; } else { indexMap[item.value] = itemIndex++; } if (subItemsVisible) { item.items.forEach(subItem => (indexMap[subItem.value] = itemIndex++)); } } else { indexMap[item.value] = itemIndex++; } }); return indexMap; }); </script> <script lang="ts"> /** * Listbox multi-select options list * * @example * import { BentoListboxMultiSelect } from '@adyen/bento-vue2'; * * export default { * components: { BentoListboxMultiSelect }, * template: ` * <bento-listbox-multi-select * :items="[{ label: 'Option 1', value: 'option-1' }]" * :selected-values="['option-1']" * /> * ` * } */ export default { i18n: { messages } }; </script> <style lang="scss" scoped src="./listbox-multi-select.scss" />
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <div class="b-listbox-option" role="option" :class="conditionalClasses" v-bind="$attrs" @click="onClick" @keydown.enter="onOptionSelected(BentoListboxEvent.ENTER, $event)" @keydown.space="onOptionSelected(BentoListboxEvent.SPACE, $event)" @keydown.tab="onOptionSelected(BentoListboxEvent.TAB, $event)" > <slot></slot> </div> </template> <script lang="ts"> import { computed, defineComponent } from 'vue'; import { BentoListboxEvent } from '@/types/listbox'; import { BentoKeyboardNavigationKeyDownEvent } from '@/types/keyboard-navigation'; /** * listbox option wrapper. * Contains only styles and allows the Multi-Select and Single-Select DRY the styles. * * Options and Attributes are inherited (`inheritAttrs: true`) * * @example * <bento-listbox-option * ...props * ...attrs * /> */ export default defineComponent({ name: 'bento-listbox-option', props: { /** * Setting this property to true will disable the option. */ disabled: { type: Boolean, default: false }, /** * Indicates if the value is selected. Only one element can be selected at a time. */ selected: { type: Boolean, default: false }, }, emits: [BentoListboxEvent.CLICK, BentoListboxEvent.ENTER, BentoListboxEvent.SPACE, BentoListboxEvent.TAB], setup(props, { emit }) { const conditionalClasses = computed(() => ({ 'b-listbox-option--disabled': props.disabled, 'b-listbox-option--selected': props.selected, })); const onClick = (e: Event) => { emit(BentoListboxEvent.CLICK, e); }; const onOptionSelected = (eventName, e: Event) => { emit(eventName, e); }; return { // Enums BentoKeyboardNavigationKeyDownEvent, BentoListboxEvent, // Values conditionalClasses, // Events onClick, onOptionSelected, }; }, }); </script> <style lang="scss" scoped src="./listbox-option.scss" />
|
package/dist/assets/components/internal/listbox/components/listbox-option-tag/listbox-option-tag.vue
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <span class="b-listbox-tag-option"> <span :aria-hidden="!!option.label"> <bento-tag class="b-listbox-tag-option__tag" :label="option.value.toString()" :variant="variant" /> </span> <bento-typography v-if="option.label" el="span" class="b-listbox-tag-option__label"> {{ option.label }} </bento-typography> </span> </template> <script lang="ts"> import { computed, defineComponent, type PropType } from 'vue'; import type { BentoListboxOptionItem } from '@/types/listbox'; import { BentoTag, type BentoTagVariant } from '@/components/tag'; import { BentoTypography } from '@/components/typography'; /** * A Tag Dropdown option item used in the Dropdown and Filter components. */ export default defineComponent({ name: 'listbox-tag-option', components: { BentoTag, BentoTypography, }, props: { /** * The option item that contains all the option item's data. * @type {BentoListboxOptionItem} */ option: { type: Object as PropType<BentoListboxOptionItem>, required: true, }, }, setup(props) { const variant = computed(() => props.option.data as BentoTagVariant); return { // Values variant, }; }, }); </script> <style lang="scss" scoped src="./listbox-option-tag.scss" />
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <div class="b-listbox-single-select-category" role="group" :aria-label="categoryLabel"> <bento-typography variant="caption" el="span" class="b-listbox-single-select-category__label-text" aria-hidden="true" > {{ categoryLabel }} </bento-typography> <div> <slot /> </div> </div> </template> <script lang="ts"> import { defineComponent } from 'vue'; import { BentoTypography } from '@/components/typography'; export default defineComponent({ name: 'bento-listbox-static-category', components: { BentoTypography, }, props: { /** * Category label text */ categoryLabel: { type: String, required: true, }, }, }); </script> <style lang="scss" scoped src="./listbox-single-select-category.scss" />
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <div ref="listboxRef" v-bento-keyboard-navigation-directive v-bind="ariaAttributes" role="listbox" v-on="listboxListeners" > <template v-if="hasCategories && staticCategories"> <bento-listbox-single-select-category v-for="category in items" :key="category.value" :category-label="category.label" > <bento-listbox-single-select-option v-for="option in category.items" :key="option.value" ref="listboxItemRef" role="option" :option="option" :disabled="isOptionDisabled ? isOptionDisabled(option) : null" :selected="option.value === selectedItem.value" v-on="listboxOptionListeners" > <template v-for="(_, innerSlot) of slots" #[innerSlot]="scope"> <slot :name="innerSlot" v-bind="scope" /> </template> </bento-listbox-single-select-option> </bento-listbox-single-select-category> </template> <template v-else> <bento-listbox-single-select-option v-for="option in items" :key="option.value" ref="listboxItemRef" role="option" :option="option" :disabled="isOptionDisabled ? isOptionDisabled(option) : null" :selected="option.value === selectedItem.value" v-on="listboxOptionListeners" > <template v-for="(_, innerSlot) of slots" #[innerSlot]="scope"> <slot :name="innerSlot" v-bind="scope" /> </template> </bento-listbox-single-select-option> </template> </div> </template> <script lang="ts"> import { computed, defineComponent, type HTMLAttributes, type PropType, ref, toRef } from 'vue'; import { BentoListboxSingleSelectCategory } from './components/listbox-single-select-category'; import { BentoListboxSingleSelectOption } from '../listbox-single-select-option'; import { useListboxKeyboardNavigation } from '../../composables/useListboxKeyboardNavigation'; import { useVisibleDomOptions } from '@/internal/listbox/composables/use-visible-dom-options/use-visible-dom-options'; import { BentoListboxEvent, type BentoListboxIsOptionDisabled, type BentoListboxOptions } from '@/types/listbox'; import { BentoKeyboardNavigationKeyDownEvent } from '@/types/keyboard-navigation'; import { type Booleanish } from '@/types/prop-types'; import { BentoKeyboardNavigationDirective } from '@/directives'; /** * Listbox single-select options list * * @example * import { BentoListboxMultiSelect } from '@adyen/bento-vue2'; * * export default { * components: { BentoListboxMultiSelect }, * template: ` * <bento-listbox-multi-select * :items="[{ label: 'Option 1', value: 'option-1' }]" * :selected-values="['option-1']" * /> * ` * } */ export default defineComponent({ name: 'bento-listbox-single-select', components: { BentoListboxSingleSelectCategory, BentoListboxSingleSelectOption, }, directives: { BentoKeyboardNavigationDirective }, inheritAttrs: false, props: { /** * Function that allows the options to be disabled * * @type {BentoListboxIsOptionDisabled} * @param {BentoListboxOptionItem} option - listbox option object * @param {string} option.label - Option's label text * @param {string|number} option.value - Option's value * @param {unknown} option.data - Option's extra data to be passed to the default slot */ isOptionDisabled: { type: Function as PropType<BentoListboxIsOptionDisabled>, default: () => false, }, /** * The option elements to populate the listbox with. * It must be an array of {@see BentoListboxOptionItem }. * * @property {string} value.label - Text to be displayed in the option * @property {string,number} value.value - Value of the option */ items: { type: Array as PropType<BentoListboxOptions>, required: true, }, /** * The `input` value. * Providing an empty string will select no options. * Set to an empty string `''` if you don't want any of the available options to be selected */ selectedValue: { type: Array as PropType<BentoListboxOptions>, default: undefined, }, /** * If enabled, dropdown will display non-selectable, static categories */ staticCategories: { type: Boolean, default: false }, }, emits: [BentoListboxEvent.SELECT, BentoListboxEvent.ENTER, BentoListboxEvent.SPACE, BentoListboxEvent.TAB], setup(props, { emit, expose, slots, attrs }) { const listboxRef = ref(null); const listboxItemRef = ref([]); const selectedItem = computed(() => !!props.selectedValue?.length && props.selectedValue.at(0)); const ariaAttributes = computed( () => ({ 'aria-label': attrs['aria-label']?.toString() as string, 'aria-busy': attrs['aria-busy']?.toString() as Booleanish, 'aria-atomic': attrs['aria-atomic']?.toString() as Booleanish, 'aria-live': attrs['aria-live']?.toString() as string, }) as HTMLAttributes ); /** * Finds out if items have sub items */ const hasCategories = computed(() => !!props.items.some(({ items }) => !!items)); const onOptionSelected = eventName => (selectedOption: BentoListboxOptions) => { emit(eventName, [selectedOption]); }; // Keyboard navigation const { visibleDomOptions } = useVisibleDomOptions(listboxRef, toRef(props, 'items'), ref('option'), false); const focusItem = (nextFocusedItemIndex: number) => { visibleDomOptions.value[nextFocusedItemIndex]?.focus(); }; const { moveToNextElement, moveToPreviousElement, moveToFirstElement, moveToLastElement, setListboxFocusedIndex, } = useListboxKeyboardNavigation( { items: visibleDomOptions, }, focusItem ); const listboxListeners = { [BentoKeyboardNavigationKeyDownEvent.ARROW_UP]: moveToPreviousElement, [BentoKeyboardNavigationKeyDownEvent.ARROW_DOWN]: moveToNextElement, [BentoKeyboardNavigationKeyDownEvent.HOME]: moveToFirstElement, [BentoKeyboardNavigationKeyDownEvent.END]: moveToLastElement, }; const listboxOptionListeners = { [BentoListboxEvent.SELECT]: onOptionSelected(BentoListboxEvent.SELECT), [BentoListboxEvent.SPACE]: onOptionSelected(BentoListboxEvent.SPACE), [BentoListboxEvent.TAB]: onOptionSelected(BentoListboxEvent.TAB), [BentoListboxEvent.ENTER]: onOptionSelected(BentoListboxEvent.ENTER), }; expose({ /** * Reference for a single listbox item. * Required for keyboard navigation in dropdown as $children is removed in vue@3 */ listboxItemRef, /** * Method to set the index of the item to be focused on * Used for focus management to be kept in-sync with parent e.g. dropdowns */ setListboxFocusedIndex, }); return { // Enums BentoListboxEvent, // Values listboxRef, listboxItemRef, selectedItem, slots, ariaAttributes, // Computed hasCategories, // Methods onOptionSelected, // Keyboard navigation listboxListeners, listboxOptionListeners, }; }, }); </script>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <bento-listbox-option class="b-listbox-single-select-option" :disabled="disabled" :selected="selected" :aria-selected="ariaSelected" :aria-disabled="ariaDisabled" :tabindex="tabIndex" @click="onOptionSelected(BentoListboxEvent.SELECT)" @enter-pressed="onOptionSelected(BentoListboxEvent.ENTER)" @space-pressed.prevent="onOptionSelected(BentoListboxEvent.SPACE)" @tab-pressed="onOptionSelected(BentoListboxEvent.TAB)" > <slot v-bind="option"> <span class="b-listbox-single-select-option__text"> <bento-typography el="span">{{ option.label }}</bento-typography> <bento-typography v-if="option.description" class="b-listbox-single-select-option__description" el="span" > {{ option.description }} </bento-typography> </span> </slot> <span class="b-listbox-single-select-option__check-icon"> <checkmark-icon v-show="selected" svg-title="selected" /> </span> </bento-listbox-option> </template> <script lang="ts"> import { computed, defineComponent, type PropType } from 'vue'; import { BentoTypography } from '@/components/typography'; import { BentoListboxOption } from '../listbox-option'; import { BentoListboxEvent, type BentoListboxOptionItem } from '@/types/listbox'; import CheckmarkIcon from '@adyen/ui-assets-icons-16/vue/checkmark'; /** * Listbox option element. * * @example * <bento-listbox-single-select-option * v-for="option in options" * :key="option.value" * :option="option" * :selected="option.selected" * @select="onOptionSelected" * /> */ export default defineComponent({ name: 'bento-listbox-single-select-option', components: { BentoListboxOption, BentoTypography, CheckmarkIcon, }, props: { /** * Setting this property to true will disable the option. */ disabled: { type: Boolean, default: false }, /** * The option item that contains all the option item's data. * @type {BentoListboxOptionItem} */ option: { type: Object as PropType<BentoListboxOptionItem>, required: true, }, /** * Indicates if the value is selected. Only one element can be selected at a time. */ selected: { type: Boolean, default: false }, }, emits: [BentoListboxEvent.ENTER, BentoListboxEvent.SELECT, BentoListboxEvent.SPACE, BentoListboxEvent.TAB], setup(props, { emit }) { const onOptionSelected = eventName => { if (props.disabled) { return; } /** * Trigerred when an option is clicked or selected via keyboard navigation. * Indicates which option was selected. * * @event {string} - The corresponding event * @property {number} selectedValue - Selected option's value */ emit(eventName, props.option); }; const ariaDisabled = computed(() => (props.disabled ? true : null)); const ariaSelected = computed(() => (props.selected ? true : null)); const tabIndex = computed(() => (props.disabled ? -1 : 0)); return { // Values ariaDisabled, ariaSelected, tabIndex, // Enums BentoListboxEvent, // Events onOptionSelected, }; }, }); </script> <style lang="scss" scoped src="./listbox-single-select-option.scss" />
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue'; import { action } from '@storybook/addon-actions'; import BentoListbox from './listbox.vue'; import { ref } from 'vue'; import { BentoDropdownLazyLoadType } from '@/components/dropdown/dropdown.types'; import { BentoListboxEvent, type BentoListboxOptionItem, type BentoListboxOptions } from '@/types/listbox'; import { isVue2 } from 'vue-demi'; const meta: Meta = { title: 'Listbox', component: BentoListbox, argTypes: { // Events [BentoListboxEvent.SELECT]: { name: `${BentoListboxEvent.SELECT}`, defaultValue: '', description: 'Triggered when an option is clicked or selected via the enter or the space key. Updates the selected option from the "value" property linked to the v-model. ', table: { type: { summary: '(selectedValue: string|number) => void' }, }, control: { disable: true }, }, [BentoListboxEvent.ESCAPE]: { name: `${BentoListboxEvent.ESCAPE}`, defaultValue: '', description: 'Triggered when the escape key is pressed.', table: { type: { summary: '() => void' }, }, control: { disable: true }, }, // Values items: { control: { disable: true } }, lazyLoadType: { options: Object.values(BentoDropdownLazyLoadType), mapping: BentoDropdownLazyLoadType, control: { type: 'select', }, }, }, }; export default meta; type Story = StoryObj<typeof BentoListbox>; const SELECTED_ITEM_OUT_OF_SCOPE = { value: 999, label: 'Pre-selected value' }; export const SingleSelect: Story = { render: (_args, { argTypes }) => ({ components: { BentoListbox }, props: Object.keys(argTypes), template: ` <bento-listbox v-bind="args" class="b-dropdown-options-container" :is-option-disabled="isOptionDisabled" :selected-value="selectedValue" @select="selectedAction" @escape-pressed="escapeAction" /> `, setup(props) { const args = isVue2 ? props : _args; const selectedValue = ref([args.items[1]]); const isOptionDisabled = (option: BentoListboxOptionItem) => [3].includes(option.value as number); const selectedAction = (selectedItem: BentoListboxOptions) => { action(BentoListboxEvent.SELECT)(selectedItem); selectedValue.value = selectedItem; }; const escapeAction = () => { action(BentoListboxEvent.ESCAPE)(); }; return { // Values args: isVue2 ? props : _args, selectedValue, // Events selectedAction, escapeAction, // Methods isOptionDisabled, }; }, }), args: { id: 'listbox-id', noResultsMessage: 'No results', items: [ { label: 'Option 1', value: 1 }, { label: 'Option 2', value: 2 }, { label: 'Option 3', value: 3 }, { label: 'Option 4', value: 4 }, ] as BentoListboxOptions, }, }; export const SingleCategories: Story = { render: (_args, { argTypes }) => ({ components: { BentoListbox }, props: Object.keys(argTypes), template: ` <bento-listbox v-bind="args" class="b-dropdown-options-container" id="single-select" static-categories :selected-value="selectedValue" @select="selectedAction" @escape-pressed="escapeAction" /> `, setup(props) { const args = isVue2 ? props : _args; const selectedValue = ref([args.items[0]]); const selectedAction = value => { action(BentoListboxEvent.SELECT)(value); selectedValue.value = value; }; const escapeAction = () => { action(BentoListboxEvent.ESCAPE)(); }; return { // Values args, selectedValue, // Events selectedAction, escapeAction, }; }, }), args: { id: 'listbox-id', noResultsMessage: 'No results', items: [ { label: 'Category 1', value: 10, items: [ { label: 'Option 1', value: 1 }, { label: 'Option 2', value: 2 }, ], }, { label: 'Category 2', value: 20, items: [ { label: 'Option 3', value: 3 }, { label: 'Option 4', value: 4 }, ], }, ] as BentoListboxOptions, }, }; export const MultiSelect: Story = { render: (_args, { argTypes }) => ({ components: { BentoListbox }, props: Object.keys(argTypes), template: ` <bento-listbox v-bind="args" class="b-dropdown-options-container" id="multi-select" multiple :selected-value="selectedValue" @select="selectedAction" @escape-pressed="escapeAction" /> `, setup(props) { const args = isVue2 ? props : _args; const selectedValue = ref([args.items[0]]); const selectedAction = (selectedOption: BentoListboxOptions) => { action(BentoListboxEvent.SELECT)(selectedOption); selectedValue.value = selectedOption; }; const escapeAction = () => { action(BentoListboxEvent.ESCAPE)(); }; return { // Values args, selectedValue, // Events selectedAction, escapeAction, }; }, }), args: { id: 'listbox-id', noResultsMessage: 'No results', items: [ { label: 'Option 1', value: 1, }, { label: 'Option 2', value: 2, }, ] as BentoListboxOptions, }, }; export const MultiSelectCategories: Story = { render: (_args, { argTypes }) => ({ components: { BentoListbox }, props: Object.keys(argTypes), template: ` <bento-listbox v-bind="args" class="b-dropdown-options-container" id="multi-select-categories" multiple static-categories :selected-value="selectedValue" @select="selectedAction" @escape-pressed="escapeAction" /> `, setup(props) { const args = isVue2 ? props : _args; const selectedValue = ref([args.items[0].items[0]]); const selectedAction = (selectedOption: BentoListboxOptions) => { action(BentoListboxEvent.SELECT)(selectedOption); selectedValue.value = selectedOption; }; const escapeAction = () => { action(BentoListboxEvent.ESCAPE)(); }; return { // Values args, selectedValue, // Events selectedAction, escapeAction, }; }, }), args: { id: 'listbox-id', noResultsMessage: 'No results', items: [ { label: 'Category 1', value: 10, items: [ { label: 'Option 1', value: 1 }, { label: 'Option 2', value: 2 }, ], }, { label: 'Category 2', value: 20, items: [ { label: 'Option 3', value: 3 }, { label: 'Option 4', value: 4 }, ], }, ] as BentoListboxOptions, }, }; export const NestedMultiSelect: Story = { render: (_args, { argTypes }) => ({ components: { BentoListbox }, props: Object.keys(argTypes), template: ` <bento-listbox v-bind="args" class="b-dropdown-options-container" id="nested-select" multiple :selected-value="selectedValue" @select="selectedAction" @escape-pressed="escapeAction" /> `, setup(props) { const args = isVue2 ? props : _args; const selectedValue = ref([args.items[0].items[0]]); const selectedAction = (selectedOption: BentoListboxOptions) => { action(BentoListboxEvent.SELECT)(selectedOption); selectedValue.value = selectedOption; }; const escapeAction = () => { action(BentoListboxEvent.ESCAPE)(); }; return { // Values args, selectedValue, // Events selectedAction, escapeAction, }; }, }), args: { id: 'listbox-id', noResultsMessage: 'No results', items: [ { label: 'Category 1', value: 10, items: [ { label: 'Option 1', value: 1 }, { label: 'Option 2', value: 2 }, ], }, { label: 'Category 2', value: 20, items: [ { label: 'Option 3', value: 3 }, { label: 'Option 4', value: 4 }, ], }, ] as BentoListboxOptions, }, }; export const LazyLoadPreselctedValues: Story = { render: (_args, { argTypes }) => ({ components: { BentoListbox }, props: Object.keys(argTypes), template: ` <bento-listbox v-bind="args" class="b-dropdown-options-container" :has-more-items="true" id="multi-select" lazy-load-type="automatic" multiple :selected-value="selectedValue" @select="selectedAction" @escape-pressed="escapeAction" /> `, setup(props) { const args = isVue2 ? props : _args; const selectedValue = ref([args.items[0], { ...SELECTED_ITEM_OUT_OF_SCOPE }]); const selectedAction = (selectedOption: BentoListboxOptions) => { action(BentoListboxEvent.SELECT)(selectedOption); selectedValue.value = selectedOption; }; const escapeAction = () => { action(BentoListboxEvent.ESCAPE)(); }; return { // Values args, selectedValue, // Events selectedAction, escapeAction, }; }, }), args: { id: 'listbox-id', noResultsMessage: 'No results', items: [ { label: 'Option 1', value: 1, }, { label: 'Option 2', value: 2, }, ] as BentoListboxOptions, }, };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <div :id="id" ref="listboxRef" v-bento-keyboard-navigation-directive class="b-listbox" tabindex="-1" v-bind="listboxContainerAttributes" :style="containerProps?.style" data-testid="listbox-container" @scroll="onListboxContainerScroll" > <div v-bind="wrapperProps"> <!-- Loading filtered items loading --> <div v-if="componentLoading" class="b-listbox__loading-filtered-items"> <bento-loading-indicator /> </div> <!-- Empty search message --> <div v-else-if="isSearchResultEmpty" class="b-listbox__empty-search-message"> <bento-empty-state v-if="emptyState" v-bind="limitedEmptyStateProps" /> <bento-typography v-else el="span">{{ noResultsMessage }}</bento-typography> </div> <template v-else> <!-- Multi select --> <template v-if="multiple"> <bento-listbox-multi-select ref="listboxItemRef" :has-more-items="hasMoreItems" v-bind="ariaAttributes" :items="items" :lazy-load-type="lazyLoadType" :static-categories="staticCategories" :selected-values="selectedValue" :searching="searching" :is-option-disabled="isOptionDisabled" v-on="listboxListeners" > <template v-for="(_, innerSlot) of slots" #[innerSlot]="scope"> <slot :name="innerSlot" v-bind="scope" /> </template> </bento-listbox-multi-select> </template> <!-- Single select --> <template v-else> <bento-listbox-single-select ref="listboxItemRef" :items="virtualisedItems" v-bind="ariaAttributes" :selected-value="selectedValue" :static-categories="staticCategories" :is-option-disabled="isOptionDisabled" v-on="listboxListeners" > <template v-for="(_, innerSlot) of slots" #[innerSlot]="scope"> <slot :name="innerSlot" v-bind="scope" /> </template> </bento-listbox-single-select> </template> <!-- Lazy Loading --> <div v-if="shouldDisplayShowMoreButton" class="b-listbox__lazy-load"> <bento-loading-button :state="loadingButtonState" variant="secondary" @click.capture="emitShowMore"> {{ showMoreButtonText }} </bento-loading-button> </div> <div v-else-if="loading" class="b-listbox__lazy-load"> <bento-loading-indicator /> </div> </template> </div> </div> </template> <script lang="ts"> import { computed, defineComponent, type PropType, ref, toRef } from 'vue'; import { type BentoListboxEmptyStateProps, BentoListboxEvent, type BentoListboxIsOptionDisabled, BentoListboxLazyLoadType, type BentoListboxOptionItem, type BentoListboxOptions, type BentoListboxVirtulisationOptions, } from '@/types/listbox'; import { BentoKeyboardNavigationKeyDownEvent } from '@/types/keyboard-navigation'; import { BentoKeyboardNavigationDirective } from '@/directives'; import { BentoListboxSingleSelect } from './components/listbox-single-select'; import { BentoListboxMultiSelect } from './components/listbox-multi-select'; import { BentoTypography } from '@/components/typography'; import { BentoLoadingIndicator } from '@/components/loading-indicator'; import { BentoLoadingButton, BentoLoadingButtonState } from '@/components/button'; import { BentoEmptyState } from '@/components/empty-state'; import { useI18n } from '@/utils/ts/i18n'; import { debounce } from '@/utils/ts/debounce'; import { useVirtualList } from '@/composables/use-virtual-list'; import type { HTMLAttributes } from 'vue/types/jsx.d.ts'; import messages from './messages.json'; type MessageSchema = (typeof messages)['en-US']; const DEFAULT_DROPDOWN_ITEM_HEIGHT = 36; /** * Listbox container. * It lists all the available options. * * @example * <bento-listbox * v-slot="{label, value}" * :id="listboxOptionsContainerId" * :aria-label="ariaLabel" * :disabled="disabled" * :multiple="multiple" * :selected="value" * :isOptionDisabled="option => option.value === 2" * :items="[{ * { label: 'Option 1', value: 1 }, * { label: 'Option 2', value: 2 }, * }]" * @select="onOptionSelected" * > * </bento-listbox> */ export default defineComponent({ name: 'bento-listbox', components: { BentoEmptyState, BentoListboxSingleSelect, BentoListboxMultiSelect, BentoTypography, BentoLoadingIndicator, BentoLoadingButton, }, directives: { BentoKeyboardNavigationDirective }, i18n: { messages }, props: { /** * Defines a string value that labels an interactive element. */ ariaLabel: { type: String, default: null }, /** * Empty state props that will be used with the inner empty state component to be displayed when no search results are found. */ emptyState: { type: Object as PropType<BentoListboxEmptyStateProps>, default: undefined, }, /** * Indicates whether there are more items to load */ hasMoreItems: { type: Boolean, default: false }, /** * Identifies the listbox whose contents are controlled by the the combobox on which the aria-controls attribute is set. */ id: { type: String, required: true }, /** * Function that allows the options to be disabled * * @type {BentoListboxIsOptionDisabled} * @param {BentoListboxOptionItem} option - Listbox option object * @param {string} option.label - Option's label text * @param {string|number} option.value - Option's value * @param {unknown} option.data - Option's extra data to be passed to the default slot */ isOptionDisabled: { type: Function as PropType<BentoListboxIsOptionDisabled>, default: () => undefined, }, /** * The option elements to populate the listbox with. * It must be an array of {@see BentoListboxOptionItem } * * @property {string} value.label - Text to be displayed in the option * * @property {string,number} value.value - Value of the option */ items: { type: Array as PropType<BentoListboxOptions>, required: true, }, /** * Indicates if new options are lazy loading. */ loading: { type: Boolean, default: false }, /** * The type of `Lazy Load`. * Type "automatic" will enable infinite scrolling * Type "button" will enable lazy loading with "Show more" button */ lazyLoadType: { type: String as PropType<BentoListboxLazyLoadType | `${BentoListboxLazyLoadType}`>, default: BentoListboxLazyLoadType.NONE, validator: (value: BentoListboxLazyLoadType) => Object.values(BentoListboxLazyLoadType).includes(value), }, /** * Indicates the listbox is loading filtered items. */ componentLoading: { type: Boolean, default: false }, /** * If enabled, the user will be able to select multiple items. */ multiple: { type: Boolean, default: false }, /** * If enabled, dropdown will display non-selectable, static categories */ staticCategories: { type: Boolean, default: false }, /** * Messaged displayed when no items are listed in the listbox */ noResultsMessage: { type: String, required: true }, /** * Flag that indicates that an external search it's being made * to filter out the items inside the the multi-select listbox * * If enabled, hides the "Select all" checkbox in "multiple" mode. */ searching: BentoListboxMultiSelect.props.searching, /** * The `input` value. * Providing an empty string or empty array will select no options. * Set to an empty string `''` if you don't want any of the available options to be selected for single select */ selectedValue: { type: Array as PropType<BentoListboxOptions>, default: () => [], }, /** * Enables virtual scrolling if set to true or by providing an object with itemHeight function. * The itemHeight function is used to calculate the height of rendered item given it's index. */ virtualScroll: { type: [Boolean, Object] as PropType<BentoListboxVirtulisationOptions>, default: false, }, }, emits: [ BentoListboxEvent.SELECT, BentoListboxEvent.SPACE, BentoListboxEvent.SHOW_MORE, BentoListboxEvent.ENTER, BentoListboxEvent.ESCAPE, BentoListboxEvent.TAB, ], setup(props, { emit, slots }) { const listboxItemRef = ref([]); const items = toRef(props, 'items'); const { t } = useI18n<{ message: MessageSchema }>({ messages }); const isLazyLoadListbox = computed(() => props.lazyLoadType !== BentoListboxLazyLoadType.AUTOMATIC); const hasAutomaticLazyLoader = computed(() => props.lazyLoadType === BentoListboxLazyLoadType.AUTOMATIC); // Virtual scrolling const virtualScrollItemHeight = () => { if (typeof props?.virtualScroll === 'object' && props?.virtualScroll?.itemHeight) { return props.virtualScroll.itemHeight; } return DEFAULT_DROPDOWN_ITEM_HEIGHT; }; const { containerRef: listboxRef, containerProps, onScroll: onContainerScroll, list: virtualisedItems, wrapperProps, } = useVirtualList<BentoListboxOptionItem>(items, { itemHeight: virtualScrollItemHeight(), // Disable virtual scrolling if not enabled or if lazy loading is enabled or if multi-select disabled: props?.multiple || props?.virtualScroll === false || props?.lazyLoadType !== 'none', }); // Aria a11y const ariaAtomic = computed<HTMLAttributes['aria-atomic']>(() => props.componentLoading !== undefined || isLazyLoadListbox.value ? 'true' : undefined ); const ariaBusy = computed<HTMLAttributes['aria-busy']>(() => (isLazyLoadListbox.value && props.loading) || props.componentLoading ? 'true' : 'false' ); const ariaLive = computed<HTMLAttributes['aria-live']>(() => isLazyLoadListbox.value || props.componentLoading !== undefined ? 'polite' : undefined ); const ariaAttributes = computed( () => ({ 'aria-label': props.ariaLabel, 'aria-busy': ariaBusy.value, 'aria-atomic': ariaAtomic.value, 'aria-live': ariaLive.value, }) as HTMLAttributes ); const listboxContainerAttributes = computed( () => (props.componentLoading ? { ...ariaAttributes.value, role: 'alert', } : {}) as HTMLAttributes ); const onOptionSelected = eventName => (selectedItem: BentoListboxOptions) => { emit(eventName, selectedItem); }; const emitShowMore = () => { emit(BentoListboxEvent.SHOW_MORE); }; const emitShowMoreOnScrollEnd = debounce(() => { const container = listboxRef.value; if (!container) { return; } const isAtBottom = Math.round(container.scrollTop) + container.clientHeight >= container.scrollHeight; if (props.hasMoreItems && isAtBottom && !props.loading) { emitShowMore(); } }); const onListboxContainerScroll = () => { if (hasAutomaticLazyLoader.value) { emitShowMoreOnScrollEnd(); } else { onContainerScroll(); } }; const isSearchResultEmpty = computed(() => props.items?.length === 0); const shouldDisplayShowMoreButton = computed( () => props.lazyLoadType === BentoListboxLazyLoadType.BUTTON && props.hasMoreItems ); const loadingButtonState = computed(() => props.loading ? BentoLoadingButtonState.LOADING : BentoLoadingButtonState.START ); const showMoreButtonText = computed(() => (props.loading ? t('loading') : t('showMore'))); const limitedEmptyStateProps = computed<InstanceType<typeof BentoEmptyState>['$props']>(() => ({ ...props.emptyState, variant: 'condensed', })); const listboxListeners = { [BentoListboxEvent.SELECT]: onOptionSelected(BentoListboxEvent.SELECT), [BentoListboxEvent.SPACE]: onOptionSelected(BentoListboxEvent.SPACE), [BentoListboxEvent.TAB]: onOptionSelected(BentoListboxEvent.TAB), [BentoListboxEvent.ENTER]: onOptionSelected(BentoListboxEvent.ENTER), }; return { // Refs listboxRef, listboxItemRef, // a11y ariaAttributes, listboxContainerAttributes, // Values containerProps, isSearchResultEmpty, limitedEmptyStateProps, loadingButtonState, slots, shouldDisplayShowMoreButton, showMoreButtonText, virtualisedItems, wrapperProps, // Enums BentoKeyboardNavigationKeyDownEvent, BentoListboxEvent, // Methods onListboxContainerScroll, onOptionSelected, emitShowMore, listboxListeners, }; }, }); </script> <style lang="scss" scoped src="./listbox.scss" />
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import { ref } from 'vue'; import { isVue2 } from 'vue-demi'; import BentoRadioInput from './radio-input.vue'; import type { Meta, StoryObj } from '@storybook/vue'; const meta: Meta = { title: 'Radio input', component: BentoRadioInput, }; export default meta; type Story = StoryObj<typeof BentoRadioInput>; export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoRadioInput }, props: Object.keys(argTypes), template: ` <bento-radio-input v-bind="args" :model-value.sync="selectedValue" /> `, setup(props) { const selectedValue = ref(undefined); return { // Values args: isVue2 ? props : _args, selectedValue, }; }, }), args: { value: 'radio-input-value', }, };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <input :id="id" ref="inputRef" :aria-checked="isChecked" :aria-label="ariaLabel" :aria-invalid="ariaInvalid" :aria-describedby="ariaDescribedby" :checked="isChecked" :disabled="disabled" :value="value" :name="name" type="radio" class="b-radio-input" :class="conditionalClasses" role="radio" @click="onClick" @change="onChange" /> </template> <script setup lang="ts"> import { computed, type ComputedRef, type PropType, ref, toRefs } from 'vue'; import { RadioInputStateClass, type RadioInputValue } from './radio-input.types'; import type { HTMLAttributes } from 'vue/types/jsx.d.ts'; const props = defineProps({ /** * The aria label associated to the input checkbox. */ ariaLabel: { type: String, default: '' }, /** * An additional string that serves as description for the input checkbox (useful if the component shows a description). */ ariaDescribedby: { type: String, default: '' }, /** * Disable the radio button (optional) */ disabled: { type: Boolean, default: false }, /** * Shows bento-radio-button in error state (optional) */ hasError: { type: Boolean, default: false }, /** * Sets the id of the radio input. Required for accessibility purposes */ id: { type: String, default: null }, /** * This property should not be used directly. * It will be used by v-model directive on radio component. */ modelValue: { type: [String, Number] as PropType<RadioInputValue>, default: null, }, /** * Sets the radio button to readonly (optional) */ readonly: { type: Boolean, default: false }, /** * Indicates the value of the input that will be returned when it's checked */ value: { type: [String, Number] as PropType<RadioInputValue>, required: true, }, /** * Sets the name of the radio input. * Used for differentiating radio groups or accessing input via form submission. */ name: { type: String, default: null }, }); const emit = defineEmits<{ /** * Emit that emits the input value */ (e: 'update:model-value', value: RadioInputValue): void; }>(); const { value } = toRefs(props); const inputRef = ref(null); const conditionalClasses = computed(() => ({ [`b-radio-input--${RadioInputStateClass.DISABLED}`]: props.disabled, [`b-radio-input--${RadioInputStateClass.ERROR}`]: props.hasError, [`b-radio-input--${RadioInputStateClass.READONLY}`]: props.readonly, })); const ariaInvalid = computed<HTMLAttributes['aria-invalid']>(() => props.hasError); const isChecked: ComputedRef<boolean> = computed(() => props.value === props.modelValue); const onChange = () => { if (!props.disabled && !props.readonly) { emit('update:model-value', props.value); } }; // readonly attr doesn't work with radio button, so we need to manually ignore the click instead const onClick = event => { if (props.readonly) { event.preventDefault(); } }; const focus = () => { inputRef.value.focus(); }; defineExpose({ focus, }); </script> <script lang="ts"> /** * Internal component that renders a radio input (without label) * * @example * import { RadioInput } from '@/internal/radio-input'; * * export default { * components: { BentoCheckboxInput }, * template: ` * <label for="checkbox-id">Your label</label> * <span id="description-id">Description</span> * <radio-input id="checkbox-id" aria=label="Your label" aria-describedby="description-id" /> * ` * } */ export default { name: 'radio-input', inheritAttrs: false, }; </script> <style lang="scss" scoped src="./radio-input.scss" />
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<!-- Taken from: https://github.com/Mechazawa/vue2-teleport/blob/master/src/Teleport.vue --> <template> <div v-if="isVue2" :class="classes"> <slot /> </div> <Teleport v-else :to="defaultTo" :where="where" :disabled="disabled" :class="classes"> <slot /> </Teleport> </template> <script setup lang="ts"> import { isVue2 } from 'vue-demi'; import { defineComponent, inject, type PropType } from 'vue'; import { MODAL_DIALOG_ID_INJECTION_KEY } from '@/components/modal/components/base-modal/base-modal.keys'; import { SIDEPANEL_DIALOG_ID_INJECTION_KEY } from '@/components/sidepanel/sidepanel.keys'; import { MODAL_FULLSCREEN_DIALOG_ID_INJECTION_KEY } from '@/components/modal-fullscreen/modal-fullscreen.keys'; import type { BentoTeleportProps } from './teleport.types'; </script> <script lang="ts"> export default defineComponent({ name: 'teleport', props: { to: { type: String as PropType<BentoTeleportProps['to']>, default: '', }, where: { type: String, default: 'after', }, disabled: Boolean as PropType<BentoTeleportProps['disabled']>, }, data: () => ({ childObserver: null, nodes: [], waiting: false, observer: null, parent: null, isVue2, }), computed: { classes() { if (this.disabled) { return ['b-teleport']; } return ['b-teleport', 'b-teleport--hidden']; }, defaultTo() { if (this.to) { return this.to; } const injectionKey = inject<string>(MODAL_DIALOG_ID_INJECTION_KEY, '') || inject<string>(MODAL_FULLSCREEN_DIALOG_ID_INJECTION_KEY, '') || inject<string>(SIDEPANEL_DIALOG_ID_INJECTION_KEY, ''); const teleportElement = injectionKey ? `#${injectionKey}` : 'body'; return teleportElement; }, }, watch: { to: 'maybeMove', where: 'maybeMove', disabled(value) { if (value) { this.disable(); // Ensure all event done. this.$nextTick(() => { this.teardownObserver(); }); } else { this.bootObserver(); this.move(); } }, }, mounted() { // Store a reference to the nodes this.nodes = Array.from(this.$el.childNodes); if (!this.disabled) { this.bootObserver(); } // Move slot content to target this.maybeMove(); }, beforeDestroy() { // Fix nodes reference this.nodes = this.getComponentChildrenNode(); // Move back this.disable(); // Stop observing this.teardownObserver(); }, methods: { // Using a fragment is faster because it'll trigger only a single reflow // See https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment getFragment(): DocumentFragment { const fragment = document.createDocumentFragment(); this.nodes.forEach(node => fragment.appendChild(node)); return fragment; }, disable() { this.$el.appendChild(this.getFragment()); this.parent = null; }, move() { this.waiting = false; this.parent = document.querySelector(this.defaultTo); if (!this.parent) { this.disable(); this.waiting = true; return; } if (this.where === 'before') { this.parent.prepend(this.getFragment()); } else { this.parent.appendChild(this.getFragment()); } }, maybeMove() { if (!this.disabled) { this.move(); } }, onMutations(mutations) { // Makes sure the move operation is only done once let shouldMove = false; for (let i = 0; i < mutations.length; i++) { const mutation = mutations[i]; const filteredAddedNodes = Array.from(mutation.addedNodes).filter( node => !this.nodes.includes(node) ); if (Array.from(mutation.removedNodes).includes(this.parent)) { this.disable(); this.waiting = !this.disabled; } else if (this.waiting && filteredAddedNodes.length > 0) { shouldMove = true; } } if (shouldMove) { this.move(); } }, bootObserver() { if (this.observer) { return; } this.observer = new MutationObserver(mutations => this.onMutations(mutations)); this.observer.observe(document.body, { childList: true, subtree: true, attributes: false, characterData: false, }); if (this.childObserver) { return; } // watch childNodes change this.childObserver = new MutationObserver(mutations => { const childChangeRecord = mutations.find(i => i.target === this.$el); if (childChangeRecord) { // Remove old nodes before update position. this.nodes.forEach(node => node.parentNode && node.parentNode.removeChild(node)); this.nodes = this.getComponentChildrenNode(); this.maybeMove(); } }); this.childObserver.observe(this.$el, { childList: true, subtree: false, attributes: false, characterData: false, }); }, teardownObserver() { if (this.observer) { this.observer.disconnect(); this.observer = null; } if (this.childObserver) { this.childObserver.disconnect(); this.childObserver = null; } }, getComponentChildrenNode() { // Vue3 if (!isVue2) { return []; } // Trick for Vue3 type checking return (this as any).$vnode.componentOptions.children.map(i => i.elm).filter(i => i); }, }, }); </script> <style lang="scss" scoped src="./teleport.scss" />
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <div class="b-tooltip-test"> <div class="b-tooltip-test__item"> <button ref="target">toggle tooltip</button> <tooltip-container v-if="target" :position="BentoTooltipPosition.TOP" content="top" :target-element="target" :is-shown="true" > Toggle tooltip </tooltip-container> <tooltip-container v-if="target" :position="BentoTooltipPosition.RIGHT" content="right" :target-element="target" :is-shown="true" > Toggle tooltip </tooltip-container> <tooltip-container v-if="target" :position="BentoTooltipPosition.BOTTOM" content="bottom" :target-element="target" :is-shown="true" > Toggle tooltip </tooltip-container> <tooltip-container v-if="target" :position="BentoTooltipPosition.LEFT" content="left" :target-element="target" :is-shown="true" > Toggle tooltip </tooltip-container> </div> </div> </template> <script lang="ts"> import TooltipContainer from './tooltip.vue'; import { defineComponent, ref } from 'vue'; import { BentoTooltipPosition } from './tooltip.types'; export default defineComponent({ components: { TooltipContainer }, setup() { const target = ref(null); return { BentoTooltipPosition, target, }; }, }); </script> <style scoped lang="scss"> $margin: 150px !default; $margin-small: 80px !default; .b-tooltip-test { margin: $margin; &__item { margin-bottom: $margin-small; } } </style>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <popper-container :id="id" :aria-role="PopperContainerAriaRole.TOOLTIP" :arrow="{ classes: ['b-tooltip__arrow'] }" :class="conditionalClasses" :fallback-position="fallbackPosition" :is-hidden="!isShown" :offset="[0, 4]" :position="position" :target-element="targetElement" :disabled-focus-trap="disabledFocusTrap" class="b-tooltip" > <bento-typography el="span" variant="caption"> {{ content }} </bento-typography> </popper-container> </template> <script setup lang="ts"> import type {} from '@floating-ui/vue'; import { computed, type PropType } from 'vue'; import { BentoTooltipPosition, BentoTooltipState } from './tooltip.types'; import { type FallbackPositionProp, PopperContainer, PopperContainerAriaRole } from '@/components/popper-container'; import { BentoTypography } from '@/components/typography'; import { type VueNodeElement } from '@/types/elements'; const props = defineProps({ /** * The tooltip's id. Necessary for accessibility purposes. */ id: { type: String, default: null }, /** * Tooltip's target reference to the DOM element or Vue component. * The tooltip will position itself relatively to target element, in the specified position. */ targetElement: { type: [Element, Object] as PropType<VueNodeElement>, required: true }, /** * Tooltip's position relative to the target element. * Possible values are: 'top', 'bottom', 'left' and 'right'. */ position: { type: String as PropType<BentoTooltipPosition>, default: BentoTooltipPosition.TOP, validator: value => Object.values(BentoTooltipPosition).includes(value as BentoTooltipPosition), }, /** * Tooltip's offset. * If specified, the tooltip will have an offset in pixels between it and the target element * in specified position. */ offset: { type: Number, default: 0 }, /** * Tooltip's text content. */ content: { type: String, required: true }, /** * Shows / Hides the Tooltip component */ isShown: { type: Boolean, default: false }, /** * Allows to configure to fallback position to the one specified, it could be 'auto' or ['right', 'top', ...]. * Useful when not using auto as starting placement. * @see {@link https://floating-ui.com/docs/flip#fallbackplacements} */ fallbackPosition: { type: [Array, String] as PropType<FallbackPositionProp>, default: () => ['top', 'bottom', 'right', 'left'], }, /** * Disables focus-trap for tooltip. * Useful for maintaining focus on the target element. */ disabledFocusTrap: { type: Boolean, default: false }, }); const conditionalClasses = computed(() => ({ [`b-tooltip--${BentoTooltipState.HIDDEN}`]: !props.isShown, })); </script> <script lang="ts"> /** * Tooltip container, to be used internally to render the tooltip container where the text is shown. */ export default { name: 'tooltip', }; </script> <style lang="scss" scoped src="./tooltip.scss" />
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import { isVue2 } from 'vue-demi'; import { storybookDocsParameter } from '@/utils/ts/storybook'; import type { Meta, StoryObj } from '@storybook/vue'; import { BentoLayout, BentoLayoutRow } from '@/components/layout'; import { BentoLayoutRowColumnLayoutOptions } from '@/components/layout/layout.types'; import './layout-row.stories.scss?module'; const meta: Meta = { title: 'Layout/Layout row', component: BentoLayoutRow, argTypes: { columnsLayout: { options: BentoLayoutRowColumnLayoutOptions, control: { type: 'select', }, }, default: { name: 'default', type: { name: 'string' }, description: 'Slot for a single column row, does not use layout.', control: 'text', table: { category: 'slots' }, }, // Need to set slots as col1 and col2 and hide col-1 and col-2 because args don't support args['col-1'] col1: { name: 'col-1', description: 'Slot for the content in the first column', control: 'text', table: { category: 'slots' }, }, col2: { name: 'col-2', description: 'Slot for the content in the second column', control: 'text', table: { category: 'slots' }, }, ['col-1']: { table: { disable: true }, }, ['col-2']: { table: { disable: true }, }, }, parameters: { layout: 'fullscreen', }, }; export default meta; type Story = StoryObj<typeof BentoLayout>; const defaultCode = ` <template> <bento-layout size="full-width"> <bento-layout-row columns-layout="3-9"> <template #col-1>3 cols</template> <template #col-2>9 cols</template> </bento-layout-row> </bento-layout> </template> `; export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoLayout, BentoLayoutRow, }, props: Object.keys(argTypes), template: ` <bento-layout size="full-width"> <bento-layout-row v-bind="args"> <template #default v-if="args.default"><div class="b-layout-row-story__cell">{{ args.default }}</div></template> <template #col-1 v-if="args.col1"><div class="b-layout-row-story__cell">{{ args.col1 }}</div></template> <template #col-2 v-if="args.col2"><div class="b-layout-row-story__cell">{{ args.col2 }}</div></template> </bento-layout-row> </bento-layout> `, setup(props) { const args = isVue2 ? props : _args; return { args, }; }, }), args: { columnsLayout: '3-9', col1: 'Col 1', col2: 'Col 2', }, parameters: storybookDocsParameter(defaultCode), };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <bento-typography el="div" class="b-layout-row" :class="conditionalRowClass"> <div v-if="hasSlot('default')"> <slot /> </div> <div v-else-if="hasSlot('col-1') && hasSlot('col-2')" ref="row" :class="conditionalRowWithColumnsClass"> <div ref="col1" :class="conditionalColumnClass.col1"> <slot name="col-1"></slot> </div> <div ref="col2" :class="conditionalColumnClass.col2"> <slot name="col-2"></slot> </div> </div> </bento-typography> </template> <script setup lang="ts"> import { computed, inject, nextTick, onMounted, ref, type Ref, useSlots, watch } from 'vue'; import { BentoTypography } from '@/components/typography'; import { useHasSlot } from '@/composables'; import { BentoLayoutRowColumnLayoutOptions, type BentoLayoutRowProps, type BentoLayoutSize, } from '../../layout.types'; import { useMediaQuery } from '@vueuse/core'; import { LAYOUT_SIZE_INJECTION_KEY } from '@/components/layout/layout.keys'; const props = withDefaults(defineProps<BentoLayoutRowProps>(), { columnsLayout: '6-6', }); const slots = useSlots(); const hasSlot = useHasSlot(slots); const size = inject<Ref<BentoLayoutSize>>(LAYOUT_SIZE_INJECTION_KEY, ref('full-width')); const row = ref<HTMLElement>(null); const col1 = ref<HTMLElement>(null); const col2 = ref<HTMLElement>(null); const isFullWidth = computed(() => size.value === 'full-width'); const isValidColumnLayout = computed(() => BentoLayoutRowColumnLayoutOptions.includes(props.columnsLayout)); const conditionalRowClass = computed(() => `b-layout-row--${size.value}`); const conditionalRowWithColumnsClass = computed(() => ({ 'b-layout-row__column-wrapper': isFullWidth.value, })); const conditionalColumnClass = computed(() => { const columns = isValidColumnLayout.value ? props.columnsLayout.split('-') : [6, 6]; const getColumnModifier = index => (isFullWidth.value ? columns[index] : size.value); return { col1: `b-layout-row__column--${getColumnModifier(0)}`, col2: `b-layout-row__column--${getColumnModifier(1)}`, }; }); watch( useMediaQuery('(min-width: 768px)'), async isLargeScreen => { await nextTick(); const areColumnsInTheRightOrder = row.value?.firstChild === col1.value; if (isLargeScreen && !areColumnsInTheRightOrder) { row.value?.prepend(col1.value); } else if (!isLargeScreen && (props.columnsLayout === '3-9' || props.columnsLayout === '4-8')) { row.value?.prepend(col2.value); } }, { immediate: true } ); onMounted(() => { if (!isFullWidth.value && (hasSlot('col-1') || hasSlot('col-2'))) { throw new Error( 'Column slots can only be used with the `full-width` size. Please check documentation for more info.' ); } if (!isValidColumnLayout.value) { throw new Error( `${props.columnsLayout} is not a valid column layout. Please refer to the documentation for the list of available column layouts.` ); } }); </script> <script lang="ts"> /** * Page row is subcomponent of Layout meant to be used to generate the row layout. * * @example * import { BentoLayout, BentoLayoutRow } from '@adyen/bento-vue2'; * * export default { * components: { BentoLayout, BentoLayoutRow }, * template: ` * <bento-layout> * <bento-layout-row columns-layout="3-9"> * <template #col-1>Narrow 3-9</template> * <template #col-2>Wide 3-9</template> * </bento-layout-row> * <bento-layout-row> * <template #col-1>6-6</template> * <template #col-2>6-6</template> * </bento-layout-row> * </bento-layout> * ` * } */ export default {}; </script> <style lang="scss" scoped src="./layout-row.scss" />
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { Canvas, Meta } from '@storybook/blocks';
|
|
2
|
+
import * as Layout from './layout.stories';
|
|
3
|
+
|
|
4
|
+
<Meta of={Layout} />
|
|
5
|
+
|
|
6
|
+
# Layout
|
|
7
|
+
|
|
8
|
+
Layout is a basic scaffolding of a page that defines spacing, some functionality and responsive behaviour of its top
|
|
9
|
+
level sections.
|
|
10
|
+
|
|
11
|
+
Layouts are responsive and use media queries to determine the layout, as well as margins and gutter sizes.
|
|
12
|
+
|
|
13
|
+
<Canvas of={Layout.WithColumns} />
|
|
14
|
+
|
|
15
|
+
## Variant
|
|
16
|
+
|
|
17
|
+
Layout comes with two variants: `page` and `component`. The `page` variant should be used when the entire page is inside
|
|
18
|
+
the `layout` component and provides the padding for the page, while the `component` variant should be used when `layout`
|
|
19
|
+
is used inside a section or a component on a page, and has no padding.
|
|
20
|
+
|
|
21
|
+
## Size
|
|
22
|
+
|
|
23
|
+
Layout has three available sizes: `full-width`, `narrow`, `half-width`.
|
|
24
|
+
|
|
25
|
+
### Full width (12 cols)
|
|
26
|
+
|
|
27
|
+
Full width content layout takes the entire 12 columns of the grid. Content below the page header can be full width (12
|
|
28
|
+
columns) or broken up into two uneven (3+9, 9+3, 4+8 or 8+4) or two even (6+6) columns.
|
|
29
|
+
|
|
30
|
+
It's also possible to mix and match the different layouts in multiple rows, for example:
|
|
31
|
+
|
|
32
|
+
- First row: 12 columns,
|
|
33
|
+
- Second row: 6 + 6 columns,
|
|
34
|
+
- Third row: 12 columns, and so on
|
|
35
|
+
|
|
36
|
+
and so on, as long as each row spans 12 columns in total.
|
|
37
|
+
|
|
38
|
+
#### When to use
|
|
39
|
+
|
|
40
|
+
Full width content layout is the most flexible and generic layout, suitable for majority of the pages across
|
|
41
|
+
applications. It can be used for detail pages, dashboards, and all sort of other full width pages, with the exception of
|
|
42
|
+
full page data grids (use Data Grid Template).
|
|
43
|
+
|
|
44
|
+
#### Responsive behaviour
|
|
45
|
+
|
|
46
|
+
Full width (12 columns) layout is always full width on all devices. Uneven (3+9, 9+3, 4+8 and 8+4 columns) and even (6+6
|
|
47
|
+
columns) layouts turn into a single column layout from 767px and below.
|
|
48
|
+
|
|
49
|
+
In the uneven layout the smaller column goes below the wider column. In even layout the right column goes below the left
|
|
50
|
+
column (follows reading order).
|
|
51
|
+
|
|
52
|
+
### Narrow (8 cols) and half-width (6 cols)
|
|
53
|
+
|
|
54
|
+
Narrow and half width column layouts have a single column of content that takes the 8 or 6 middle columns of the grid
|
|
55
|
+
(on desktop). All components, including the page header sit within those 8 or 6 columns.
|
|
56
|
+
|
|
57
|
+
<Canvas of={Layout.Default} />
|
|
58
|
+
|
|
59
|
+
#### When to use
|
|
60
|
+
|
|
61
|
+
These layouts work great for pages that have a relatively narrow content and don't need a secondary column with
|
|
62
|
+
additional information or some form of navigation. An example would be a single step form page.
|
|
63
|
+
|
|
64
|
+
#### Responsive behaviour
|
|
65
|
+
|
|
66
|
+
As the screen size gets smaller, the single column takes more and more of the grid space, becoming a full width layout
|
|
67
|
+
at 767px and less.
|
|
68
|
+
|
|
69
|
+
## Column layout
|
|
70
|
+
|
|
71
|
+
To break up the content into two columns, the `columnLayout` prop can be passed to the `layout-row` subcomponent to
|
|
72
|
+
determine the layout of the row. Additionally, you must also use the `col-1` and `col-2` slots to pass the content to
|
|
73
|
+
the relevant columns.
|
|
74
|
+
|
|
75
|
+
As mentioned above, there are five layouts available: `6-6`, `3-9`, `9-3`, `4-8` and `8-4`.
|
|
76
|
+
|
|
77
|
+
Breaking up the content into two columns only works when using the `full-width` size. If you're using the `narrow` or
|
|
78
|
+
`half-width` size, or passing the content in the `default` slot, the content will always span 12 columns.
|
|
79
|
+
|
|
80
|
+
## Nested
|
|
81
|
+
|
|
82
|
+
It is possible to nest rows within rows and even layout inside of a row, but this should be avoided as much as possible,
|
|
83
|
+
as the media queries are not designed for multiple nested levels.
|
|
84
|
+
|
|
85
|
+
To nest elements, it is important to always pass it inside of a `layout-row`, and not directly inside the `layout`
|
|
86
|
+
component, as `layout` only supports `layout-row` to be passed inside the slot.
|
|
87
|
+
|
|
88
|
+
<Canvas of={Layout.Nested} />
|
|
89
|
+
|
|
90
|
+
## Additional content guidelines
|
|
91
|
+
|
|
92
|
+
Uneven layouts (3+9, 9+3, 4+8 and 8+4 columns) can be used to **organise information in terms of importance**. Choose a
|
|
93
|
+
layout with a wide left column and narrow right column to put the main content on the left and secondary (less important
|
|
94
|
+
or a summary of what's in the wide column) on the right. If you're using secondary side navigation or stepper, we
|
|
95
|
+
recommend to use the mirrored layout, with narrow column on the left (to hold these components) and a wide column on the
|
|
96
|
+
right (for main content).
|
|
97
|
+
|
|
98
|
+
**Use whitespace to your advantage**. Placing just one item in the narrow column may leave you with empty space
|
|
99
|
+
underneath, if the wide column has more content in it. That's good - it will improve information scanability and make
|
|
100
|
+
finding things easier than a busy, packed layout.
|
|
101
|
+
|
|
102
|
+
In even (6 + 6) layout, visually, the two columns have equal width. We recommend using this **layout for elements that
|
|
103
|
+
have equal importance** and their order doesn't matter as much, for example, to display multiple graphs in a dashboard,
|
|
104
|
+
or to visually compare two sets of data.
|
|
105
|
+
|
|
106
|
+
## Resources
|
|
107
|
+
|
|
108
|
+
- [Figma link](https://www.figma.com/design/Diqr47KAACSr6ohrQhcNgO/Bento---Product---Fundamentals?node-id=3763-15379&m=dev)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import { computed } from 'vue'; import { isVue2 } from 'vue-demi'; import { storybookDocsParameter } from '@/utils/ts/storybook'; import type { Meta, StoryObj } from '@storybook/vue'; import { BentoLayout, BentoLayoutRow } from '@/components/layout'; import { BentoLayoutSizeOptions, BentoLayoutVariantOptions } from '@/components/layout/layout.types'; import './layout.stories.scss?module'; const meta: Meta = { title: 'Layout', component: BentoLayout, argTypes: { variant: { options: BentoLayoutVariantOptions, control: { type: 'select', }, }, size: { options: BentoLayoutSizeOptions, control: { type: 'select', }, }, }, parameters: { layout: 'fullscreen', }, }; export default meta; type Story = StoryObj<typeof BentoLayout>; const defaultCode = ` <template> <bento-layout size="half-width"> <bento-layout-row>Row 1</bento-layout-row> <bento-layout-row>Row 2</bento-layout-row> <bento-layout-row>Row 3</bento-layout-row> </bento-layout> </template> <script setup lang="ts"></script> `; export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoLayout, BentoLayoutRow, }, props: Object.keys(argTypes), template: ` <bento-layout v-bind="args"> <bento-layout-row class="b-layout-story__row"> <div>Page header</div> <div>{{ numberCols}} cols</div> </bento-layout-row> <bento-layout-row class="b-layout-story__row b-layout-story__row--content"> <div>Content</div> <div>{{ numberCols}} cols</div> </bento-layout-row> </bento-layout> `, setup(props) { const args = isVue2 ? props : _args; const colsMap = Object.freeze({ ['half-width']: 6, ['narrow']: 8, ['full-width']: 12, }); const numberCols = computed(() => colsMap[args.size]); return { args, numberCols, }; }, }), args: { size: 'half-width' }, parameters: storybookDocsParameter(defaultCode), }; const withColumnsCode = ` <template> <bento-layout size="full-width"> <bento-layout-row columns-layout="3-9"> <template #col-1>Narrow 3-9</template> <template #col-2>Wide 3-9</template> </bento-layout-row> <bento-layout-row columns-layout="4-8"> <template #col-1>Narrow 4-8</template> <template #col-2>Wide 4-8</template> </bento-layout-row> <bento-layout-row columns-layout="9-3"> <template #col-1>Wide 9-3</template> <template #col-2>Narrow 9-3</template> </bento-layout-row> <bento-layout-row columns-layout="8-4"> <template #col-1>Wide 8-4</template> <template #col-2>Narrow 8-4</template> </bento-layout-row> <bento-layout-row> <template #col-1>6-6</template> <template #col-2>6-6</template> </bento-layout-row> </bento-layout> </template> `; export const WithColumns: Story = { render: (_args, { argTypes }) => ({ components: { BentoLayout, BentoLayoutRow, }, props: Object.keys(argTypes), template: ` <bento-layout v-bind="args"> <bento-layout-row class="b-layout-story__row b-layout-story__row--thin">12 cols</bento-layout-row> <bento-layout-row columns-layout="3-9" class="b-layout-story__row-with-cells"> <template #col-1><div class="b-layout-story__cell">3 cols</div></template> <template #col-2><div class="b-layout-story__cell">9 cols</div></template> </bento-layout-row> <bento-layout-row columns-layout="9-3" class="b-layout-story__row-with-cells"> <template #col-1><div class="b-layout-story__cell">9 cols</div></template> <template #col-2><div class="b-layout-story__cell">3 cols</div></template> </bento-layout-row> <bento-layout-row columns-layout="4-8" class="b-layout-story__row-with-cells"> <template #col-1><div class="b-layout-story__cell">4 cols</div></template> <template #col-2><div class="b-layout-story__cell">8 cols</div></template> </bento-layout-row> <bento-layout-row columns-layout="8-4" class="b-layout-story__row-with-cells"> <template #col-1><div class="b-layout-story__cell">8 cols</div></template> <template #col-2><div class="b-layout-story__cell">4 cols</div></template> </bento-layout-row> <bento-layout-row columns-layout="6-6" class="b-layout-story__row-with-cells"> <template #col-1><div class="b-layout-story__cell">6 cols</div></template> <template #col-2><div class="b-layout-story__cell">6 cols</div></template> </bento-layout-row> </bento-layout> `, setup(props) { const args = isVue2 ? props : _args; return { args, }; }, }), args: { size: 'full-width' }, parameters: storybookDocsParameter(withColumnsCode), }; const nestedCode = ` <template> <bento-layout variant="page" size="full-width"> <bento-layout-row>Single column row</bento-layout-row> <bento-layout-row columns-layout="3-9"> <template #col-1>Col 1</template> <template #col-2> <bento-layout-row columns-layout="4-8"> <template #col-1>Col 2.1 (nested)</template> <template #col-2>Col 2.2 (nested)</template> </bento-layout-row> </template> </bento-layout-row> <bento-layout-row columns-layout="9-3" class="b-layout-story__row-with-cells"> <template #col-1> <bento-layout-row columns-layout="8-4"> <template #col-1>Col 1.1 (nested)</template> <template #col-2>Col 1.2 (nested)</template> </bento-layout-row> </template> <template #col-2>Col 2</template> </bento-layout-row> <bento-layout-row columns-layout="3-9"> <bento-layout size="full-width" variant="component"> <bento-layout-row>Row with nested Layout</bento-layout-row> </bento-layout> </bento-layout-row> </bento-layout> </template> `; export const Nested: Story = { render: (_args, { argTypes }) => ({ components: { BentoLayout, BentoLayoutRow, }, props: Object.keys(argTypes), template: ` <bento-layout v-bind="args"> <bento-layout-row class="b-layout-story__row b-layout-story__row--thin">Single column row</bento-layout-row> <bento-layout-row columns-layout="3-9" class="b-layout-story__row-with-cells"> <template #col-1><div class="b-layout-story__cell">Col 1</div></template> <template #col-2> <bento-layout-row columns-layout="4-8"> <template #col-1><div class="b-layout-story__cell">Col 2.1 (nested)</div></template> <template #col-2><div class="b-layout-story__cell">Col 2.2 (nested)</div></template> </bento-layout-row> </template> </bento-layout-row> <bento-layout-row columns-layout="9-3" class="b-layout-story__row-with-cells"> <template #col-1> <bento-layout-row columns-layout="8-4"> <template #col-1><div class="b-layout-story__cell">Col 1.1 (nested)</div></template> <template #col-2><div class="b-layout-story__cell">Col 1.2 (nested)</div></template> </bento-layout-row> </template> <template #col-2><div class="b-layout-story__cell">Col 2</div></template> </bento-layout-row> <bento-layout-row columns-layout="3-9" class="b-layout-story__row-with-cells"> <bento-layout size="full-width" variant="component"> <bento-layout-row class="b-layout-story__cell">Row with nested Layout</bento-layout-row> </bento-layout> </bento-layout-row> </bento-layout> `, setup(props) { const args = isVue2 ? props : _args; return { args, }; }, }), args: { size: 'full-width' }, parameters: storybookDocsParameter(nestedCode), };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <component :is="wrapperElement" class="b-layout" :class="conditionalClasses"> <slot /> </component> </template> <script setup lang="ts"> import { computed, provide, toRef } from 'vue'; import { type BentoLayoutProps } from './layout.types'; import { LAYOUT_SIZE_INJECTION_KEY } from '@/components/layout/layout.keys'; const props = withDefaults(defineProps<BentoLayoutProps>(), { size: 'full-width', variant: 'page', }); provide(LAYOUT_SIZE_INJECTION_KEY, toRef(props, 'size')); const conditionalClasses = computed(() => ({ 'b-layout--component': props.variant === 'component', })); const wrapperElement = props.variant === 'page' ? 'main' : 'div'; </script> <script lang="ts"> /** * Layout is a basic scaffolding of a page that defines spacing, some functionality and responsive behaviour of its top level sections. * * @example * import { BentoLayout, BentoLayoutRow } from '@adyen/bento-vue2'; * * export default { * components: { BentoLayout, BentoLayoutRow }, * template: ` * <bento-layout size="narrow"> * <bento-layout-row> * Row 1 * </bento-layout-row> * <bento-layout-row> * Row 2 * </bento-layout-row> * </bento-layout> * ` * } */ export default {}; </script> <style lang="scss" scoped src="./layout.scss" />
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { Canvas, Story } from '@storybook/blocks';
|
|
2
|
+
import * as LinkComponentStories from './link.stories';
|
|
3
|
+
import * as DataGridComponentStories from '../data-grid/data-grid.stories';
|
|
4
|
+
|
|
5
|
+
# Link
|
|
6
|
+
|
|
7
|
+
A Link component is used to create hyperlinks using the [`router-link`](https://v3.router.vuejs.org/api/#router-link)
|
|
8
|
+
component from the [`vue-router`](https://github.com/vuejs/vue-router) package.
|
|
9
|
+
|
|
10
|
+
<Canvas of={LinkComponentStories.Default} />
|
|
11
|
+
|
|
12
|
+
## Use Cases
|
|
13
|
+
|
|
14
|
+
Here are some examples:
|
|
15
|
+
|
|
16
|
+
- A hyperlink that navigates the user to details page.
|
|
17
|
+
- A hyperlink that navigates the user to a information page hosted externally.
|
|
18
|
+
|
|
19
|
+
### Do not use
|
|
20
|
+
|
|
21
|
+
Here are some examples:
|
|
22
|
+
|
|
23
|
+
- Wrapping a button component with the link component, instead use the button click event and the `router.push()`.
|
|
24
|
+
|
|
25
|
+
## Variations
|
|
26
|
+
|
|
27
|
+
All the properties that you can set on the `router-link` can be set on the component as well. You can find all the
|
|
28
|
+
documentation on these properties [here](https://v3.router.vuejs.org/api/#router-link-props).
|
|
29
|
+
|
|
30
|
+
### Default
|
|
31
|
+
|
|
32
|
+
<Canvas of={LinkComponentStories.Default} />
|
|
33
|
+
|
|
34
|
+
A link with an underline to separate it from paragraph text.
|
|
35
|
+
|
|
36
|
+
When to use:
|
|
37
|
+
|
|
38
|
+
- Link is part of the paragraph.
|
|
39
|
+
- Link comes directly after the paragraph.
|
|
40
|
+
|
|
41
|
+
### Quiet
|
|
42
|
+
|
|
43
|
+
<Canvas of={LinkComponentStories.Quiet} />
|
|
44
|
+
|
|
45
|
+
A link without an underline decoration in it's default state.
|
|
46
|
+
|
|
47
|
+
It should be used only when the context and placement of the link is clear enough even without the underline.
|
|
48
|
+
|
|
49
|
+
When to use:
|
|
50
|
+
|
|
51
|
+
- Links grouped in horizontal or vertical lists.
|
|
52
|
+
|
|
53
|
+
## Modifiers
|
|
54
|
+
|
|
55
|
+
### External
|
|
56
|
+
|
|
57
|
+
<Canvas of={LinkComponentStories.External} />
|
|
58
|
+
|
|
59
|
+
_NOTE: If you wish to create a regular anchor element without the external icon, please use the
|
|
60
|
+
[isNotRouting](#isnotrouting) property._
|
|
61
|
+
|
|
62
|
+
An option to display the external link icon when a link leads outside of the current platform eg: A user clicks on a
|
|
63
|
+
link that leads to Adyen Docs.
|
|
64
|
+
|
|
65
|
+
Make sure to use a filtering function on the outbound URL if one exists.
|
|
66
|
+
|
|
67
|
+
For example:
|
|
68
|
+
|
|
69
|
+
```html
|
|
70
|
+
<template>
|
|
71
|
+
<bento-link external :to="externalUrl">
|
|
72
|
+
<settings-icon />
|
|
73
|
+
</bento-link>
|
|
74
|
+
</template>
|
|
75
|
+
|
|
76
|
+
<script setup lang="ts">
|
|
77
|
+
import SettingsIcon from '@adyen/ui-assets-icons-16/vue/settings';
|
|
78
|
+
import { BentoLink } from '@adyen/bento-vue2';
|
|
79
|
+
import { getOutboundUrl } from '@/utils/navigation';
|
|
80
|
+
|
|
81
|
+
const externalUrl = getOutboundUrl('http://google.com');
|
|
82
|
+
</script>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### isNotRouting
|
|
86
|
+
|
|
87
|
+
<Canvas of={LinkComponentStories.isNotRouting} />
|
|
88
|
+
|
|
89
|
+
Sometimes you might need to render an anchor element without using any routing supplied from the internal `router-link`
|
|
90
|
+
component. In this case simply set the `isNotRouting` property to `true`.
|
|
91
|
+
|
|
92
|
+
### Target
|
|
93
|
+
|
|
94
|
+
Specifies the target of the rendered anchor tag. Default target is `_self`. If the `external` modifier is applied, the
|
|
95
|
+
default target is `_blank`.
|
|
96
|
+
|
|
97
|
+
## Links used in `BentoDataGrid`
|
|
98
|
+
|
|
99
|
+
If a link is used inside a `BentoDataGrid` row cell, the style will automatically be adjusted for you. You can see an
|
|
100
|
+
example of this in the story below.
|
|
101
|
+
|
|
102
|
+
<Canvas of={DataGridComponentStories.EverythingBagelExample} />
|
|
103
|
+
|
|
104
|
+
## Accessibility
|
|
105
|
+
|
|
106
|
+
### Keyboard Interaction
|
|
107
|
+
|
|
108
|
+
Being an interactive element, the link is reachable through keyboard navigation in the logical navigation flow of the
|
|
109
|
+
page. The following keys trigger the following actions:
|
|
110
|
+
|
|
111
|
+
| Keyboard interaction | Action |
|
|
112
|
+
| -------------------- | ----------------------------------------------------- |
|
|
113
|
+
| `Enter` | Executes the link and moves focus to the link target. |
|
|
114
|
+
| `Tab` | Moves focus to the link and away from it. |
|
|
115
|
+
|
|
116
|
+
### Roles, States, and Properties
|
|
117
|
+
|
|
118
|
+
Adhering to the [WAI-ARIA 1.2](https://www.w3.org/TR/wai-aria-1.2/) standard we follow the
|
|
119
|
+
[link pattern](https://www.w3.org/WAI/ARIA/apg/patterns/link/) documentation provided.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import BentoLink from './link.vue'; import { BentoTypography } from '@/components/typography'; import { BentoLinkVariant } from './link.types'; import { storybookDocsParameter } from '@/utils/ts/storybook'; import { type Meta, type StoryObj } from '@storybook/vue'; import { isVue2 } from 'vue-demi'; const meta: Meta<typeof BentoLink> = { title: 'Link', component: BentoLink, argTypes: { // Slots default: { name: 'default', defaultValue: '', description: 'link text', table: { type: { summary: 'VNode[]' }, }, control: { type: 'text', }, }, // Router props to: { name: 'to', defaultValue: '/', description: 'Denotes the target route of the link. When clicked, the value of the `to` prop will be passed to `router.push()` internally, so the value can be either a string or a location descriptor object.', table: { type: { summary: 'string | Location' }, }, control: { type: 'text', }, }, // Custom props variant: { options: Object.values(BentoLinkVariant), mapping: BentoLinkVariant, control: { type: 'select', }, }, target: { name: 'target', defaultValue: null, control: { type: 'text', }, }, // Emits click: { name: 'click', defaultValue: '', description: 'Click event handler.', table: { type: { summary: '($event) => void' }, }, control: { disable: true }, }, // TODO: remove this when Vue fixes extending interface issue for defineProps args: { table: { disable: true } }, }, }; export default meta; type Story = StoryObj<typeof BentoLink>; const DEFFAULT_ARGS = { to: '/', }; const EXTERNAL_HREF = 'https://www.adyen.com/'; // Default export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoLink, BentoTypography }, props: Object.keys(argTypes), template: ` <div> <bento-typography> <bento-link v-bind="args"> an interactive link </bento-link> </bento-typography> </div> `, setup: props => ({ args: isVue2 ? props : _args }), }), args: DEFFAULT_ARGS, parameters: storybookDocsParameter(` <bento-typography> <bento-link to="router/path"> an interactive link </bento-link> </bento-typography> `), }; // Quiet export const Quiet: Story = { ...Default, args: { ...DEFFAULT_ARGS, variant: 'quiet', }, parameters: storybookDocsParameter(` <bento-typography> <bento-link variant="quiet" to="router/path"> an interactive link </bento-link> </bento-typography> `), }; // External export const External: Story = { ...Default, args: { ...DEFFAULT_ARGS, external: true, to: EXTERNAL_HREF, }, parameters: storybookDocsParameter(` <bento-typography> <bento-link to="${EXTERNAL_HREF}" external> an interactive link </bento-link> </bento-typography> `), }; // isNotRouting export const isNotRouting: Story = { ...Default, args: { ...DEFFAULT_ARGS, isNotRouting: true, }, parameters: storybookDocsParameter(` <bento-typography> <bento-link to="link/path" is-not-routing> an interactive link </bento-link> </bento-typography> `), };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <a v-if="external || isNotRouting" :class="conditionalClasses" v-bind="$attrs" :aria-describedby="externalLinkId" :href="$props.to.toString()" :title="getAttributeTitle($attrs.title)" :target="computedTarget" @click="onClick" > <bento-typography v-if="!disableTypography" class="b-link__text" el="span" variant="body"> <slot /> </bento-typography> <slot v-else /> <span v-if="showExternalLinkIcon" aria-hidden="true" class="b-link__external-link-icon"> <external-link-icon /> </span> <span v-if="external" :id="externalLinkId" class="b-link--visually-hidden">{{ t('opensInNewTab') }}</span> </a> <router-link v-else v-bind="{ ...$attrs, ...$props, ...nonForwardedProps }" :class="conditionalClasses" :target="target" :to="props.to" custom @click.native="onClick" > <bento-typography v-if="!disableTypography" class="b-link__text" el="span" variant="body"> <slot /> </bento-typography> <slot v-else /> </router-link> </template> <script setup lang="ts"> import { RouterLink } from 'vue-router'; import { computed } from 'vue'; import { generateUid } from '@/core/utils/ts'; import { BentoTypography } from '@/components/typography'; import { type BentoLinkProps, BentoLinkVariant } from './link.types'; import { useI18n } from '@/utils/ts/i18n'; import type { RouterLinkSlotArgument } from 'vue-router/types/router.d.ts'; import ExternalLinkIcon from '@adyen/ui-assets-icons-16/vue/external-link'; import messages from './messages.json'; type MessageSchema = (typeof messages)['en-US']; const { t } = useI18n<{ message: MessageSchema }>({ messages }); const props = withDefaults(defineProps<BentoLinkProps>(), { target: null, variant: BentoLinkVariant.DEFAULT, }); // Props that should not be forwarded to the underlying anchor element const nonForwardedProps = { variant: undefined, isButton: undefined, truncate: undefined, disableTypography: undefined, external: undefined, isNotRouting: undefined, }; const externalLinkId = generateUid('external-link'); const getAttributeTitle = (attributesTitle: unknown) => attributesTitle as string; const conditionalClasses = computed(() => ({ 'b-link': !props.isButton, [`b-link--${BentoLinkVariant.QUIET}`]: props.variant === BentoLinkVariant.QUIET, [`b-link--truncate`]: props.truncate, })); const computedTarget = computed(() => (props.external ? (props.target ?? '_blank') : props.target)); const showExternalLinkIcon = computed(() => !(props.isButton || props.isNotRouting)); const emit = defineEmits<{ (e: 'click', clickEvent: MouseEvent): void; }>(); const onClick = (e: MouseEvent, navigateCallback?: RouterLinkSlotArgument['navigate']) => { emit('click', e); if (navigateCallback) { navigateCallback(); } }; </script> <script lang="ts"> /** * Extends the `router-link` from the `vue-router` NPM package. * * @example * import { BentoLink } from '@adyen/bento-vue2'; * * export default { * components: { BentoLink }, * template: ` * <bento-link to="/"> * link * </bento-link> * ` * } */ export default { name: 'bento-link', inheritAttrs: false, i18n: { messages }, }; </script> <style lang="scss" scoped src="@/core/components/link/link.scss" />
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Canvas, Story, Meta } from '@storybook/blocks';
|
|
2
|
+
import * as ComponentStories from './loading-indicator.stories';
|
|
3
|
+
|
|
4
|
+
<Meta of={ComponentStories} />
|
|
5
|
+
|
|
6
|
+
# Loading Indicator
|
|
7
|
+
|
|
8
|
+
<Canvas of={ComponentStories.Default} />
|
|
9
|
+
|
|
10
|
+
A loading indicator displays the state of a page or section while its content is being updated.
|
|
11
|
+
|
|
12
|
+
## Use Cases
|
|
13
|
+
|
|
14
|
+
Use a loading indicator for content that is loading and not yet ready for interaction.
|
|
15
|
+
|
|
16
|
+
### Do not use
|
|
17
|
+
|
|
18
|
+
It is advisable to use the loading indicator globally on the page (i.e. pairing it with an overlay element) and not
|
|
19
|
+
overuse it on single page elements, unless there is a need to keep some other elements interactive while the page is
|
|
20
|
+
loading. In that case, it is preferred to have loading areas and not loading on single elements.
|
|
21
|
+
|
|
22
|
+
## Variations
|
|
23
|
+
|
|
24
|
+
### Inverse
|
|
25
|
+
|
|
26
|
+
Renders the loading indicator spinner in color white.
|
|
27
|
+
|
|
28
|
+
## Modifiers
|
|
29
|
+
|
|
30
|
+
### Small
|
|
31
|
+
|
|
32
|
+
Renders the loading indicator spinner in a small size.
|
|
33
|
+
|
|
34
|
+
### Large
|
|
35
|
+
|
|
36
|
+
Renders the loading indicator spinner in a large size.
|
|
37
|
+
|
|
38
|
+
## Accessibility
|
|
39
|
+
|
|
40
|
+
Because of the need to be used in other components (e.g. `Loading Button`), the loading indicator is made up by the
|
|
41
|
+
visual spinner only and doesn't encapsulate any loading logic (i.e. using an overlay). Being the spinner just a
|
|
42
|
+
decorative element, this is taken away from the accessibility tree with the use of `aria-hidden`.
|
|
43
|
+
|
|
44
|
+
However, a note should be made that some sort of loading logic should be implemented accessibility-wise whenever using
|
|
45
|
+
this component. This can be done by leveraging
|
|
46
|
+
[aria-busy](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-busy) within the context of
|
|
47
|
+
[live regions](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions) to correctly flag to
|
|
48
|
+
assistive technology that the content is currently updating. For more information on the matter, the
|
|
49
|
+
[WAI-ARIA busy/loading pattern](https://www.w3.org/TR/wai-aria-1.0/states_and_properties#aria-busy) can be consulted.
|
|
50
|
+
|
|
51
|
+
### Keyboard interaction
|
|
52
|
+
|
|
53
|
+
Being a decorative element, the loading indicator is taken away by the accessibility tree of the page and thus is not
|
|
54
|
+
reachable by keyboard navigation.
|
|
55
|
+
|
|
56
|
+
### Roles, states and properties
|
|
57
|
+
|
|
58
|
+
| ARIA attribute | is automatic? | fallback used | manual override |
|
|
59
|
+
| -------------- | --------------------------------------------------------------------------------------------------------- | ------------- | --------------- |
|
|
60
|
+
| `aria-hidden` | Yes, it is set on the component's root level and it takes away the component from the accessibility tree. | - | - |
|
|
61
|
+
|
|
62
|
+
## Resources
|
|
63
|
+
|
|
64
|
+
- [Figma link](https://www.figma.com/file/uLabwF3243jdMDsNSP7U9I/ADL-2.0---Components?node-id=302-1925&t=7Zhgxe1lwQU3XdU1-0)
|
|
65
|
+
- [WAI-ARIA guidelines](https://www.w3.org/WAI/ARIA/apg/patterns/switch/)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import { isVue2 } from 'vue-demi'; import BentoLoadingIndicator from './loading-indicator.vue'; import { storybookDocsParameter } from '@/utils/ts/storybook'; export default { title: 'Loading Indicator', component: BentoLoadingIndicator, parameters: { layout: 'centered', }, argTypes: { small: { if: { arg: 'large', truthy: false } }, large: { if: { arg: 'small', truthy: false } }, }, }; export const Default = (_args, { argTypes }) => ({ components: { BentoLoadingIndicator }, props: Object.keys(argTypes), setup: props => ({ args: isVue2 ? props : _args }), template: ` <bento-loading-indicator v-bind="args" /> `, }); const defaultCode = ` <loading-indicator /> `; Default.args = {}; Default.parameters = storybookDocsParameter(defaultCode);
|