@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,420 @@
|
|
|
1
|
+
import { Canvas, Story, Source, Meta } from '@storybook/blocks';
|
|
2
|
+
import * as DropdownStories from './dropdown.stories';
|
|
3
|
+
import * as DropdownAvatarStories from './components/variants/dropdown-avatar/dropdown-avatar.stories.ts';
|
|
4
|
+
import * as DropdownCountryStories from './components/variants/dropdown-country/dropdown-country.stories.ts';
|
|
5
|
+
import * as DropdownCurrencyStories from './components/variants/dropdown-currency/dropdown-currency.stories.ts';
|
|
6
|
+
import * as DropdownPaymentMethodStories from './components/variants/dropdown-payment-method/dropdown-payment-method.stories.ts';
|
|
7
|
+
import * as DropdownTagStories from './components/variants/dropdown-tag/dropdown-tag.stories.ts';
|
|
8
|
+
|
|
9
|
+
<Meta title="Dropdown" of={DropdownStories} />
|
|
10
|
+
|
|
11
|
+
# Dropdown
|
|
12
|
+
|
|
13
|
+
A dropdown shows a selected option or multiple options. Use a dropdown when you want users to select from a pre-defined
|
|
14
|
+
list of options.
|
|
15
|
+
|
|
16
|
+
<Canvas sourceState="hidden">
|
|
17
|
+
<Story of={DropdownStories.Default} />
|
|
18
|
+
</Canvas>
|
|
19
|
+
|
|
20
|
+
## Table of contents
|
|
21
|
+
|
|
22
|
+
- [Use Cases](#use-cases)
|
|
23
|
+
- [Variants](#variants)
|
|
24
|
+
- [Avatar](#avatar)
|
|
25
|
+
- [Country](#country)
|
|
26
|
+
- [Currency](#currency)
|
|
27
|
+
- [Payment method](#payment-method)
|
|
28
|
+
- [Tag](#tag)
|
|
29
|
+
- [Custom slots](#custom-slots)
|
|
30
|
+
- [Modifiers](#modifiers)
|
|
31
|
+
- [Display value](#display-value)
|
|
32
|
+
- [Condensed](#condensed)
|
|
33
|
+
- [Disabling options](#disabling-options)
|
|
34
|
+
- [Dynamic filtering](#dynamic-filtering)
|
|
35
|
+
- [Empty state](#empty-state)
|
|
36
|
+
- [Error state](#error-state)
|
|
37
|
+
- [Items with description](#items-with-description)
|
|
38
|
+
- [External filtering](#external-filtering)
|
|
39
|
+
- [Lazy loading](#lazy-loading)
|
|
40
|
+
- [External filtering and Lazy loading simultaneously](#external-filtering-and-lazy-loading-simultaneously)
|
|
41
|
+
- [Multi select](#multi-select)
|
|
42
|
+
- [Nested multi-select](#nested-multi-select)
|
|
43
|
+
- [Static categories](#static-categories)
|
|
44
|
+
- [Readonly](#readonly)
|
|
45
|
+
- [Tooltip](#tooltip)
|
|
46
|
+
- [Accessibility](#accessibility)
|
|
47
|
+
- [Resources](#resources)
|
|
48
|
+
|
|
49
|
+
## Use Cases
|
|
50
|
+
|
|
51
|
+
Use a Dropdown if you need to:
|
|
52
|
+
|
|
53
|
+
- Allow the user to select a country of residence.
|
|
54
|
+
- Select multiple payment methods.
|
|
55
|
+
|
|
56
|
+
### Do not use
|
|
57
|
+
|
|
58
|
+
If there are less than 4 options - use a group of radio buttons or checkboxes instead.
|
|
59
|
+
|
|
60
|
+
## Variants
|
|
61
|
+
|
|
62
|
+
All the variants listed below accept all the same properties as the default `bento-dropdown` plus the properties listed
|
|
63
|
+
in the description of the variant.
|
|
64
|
+
|
|
65
|
+
### Avatar
|
|
66
|
+
|
|
67
|
+
<Canvas of={DropdownAvatarStories.Default} />
|
|
68
|
+
|
|
69
|
+
NOTE: this uses the `bento-dropdown-avatar` component, so you can pass a `src` string with each item in the `items`
|
|
70
|
+
array.
|
|
71
|
+
|
|
72
|
+
### Country
|
|
73
|
+
|
|
74
|
+
<Canvas of={DropdownCountryStories.Default} />
|
|
75
|
+
|
|
76
|
+
NOTE: this uses the `bento-dropdown-country` component, so alongside all other `bento-dropdown` props it additionally
|
|
77
|
+
accepts a country `variant` property which prepopulates the dropdown item labels.
|
|
78
|
+
|
|
79
|
+
e.g. when `variant` is set to `'name'`, then the dropdown will display a list of country names.
|
|
80
|
+
|
|
81
|
+
### Currency
|
|
82
|
+
|
|
83
|
+
<Canvas of={DropdownCurrencyStories.Default} />
|
|
84
|
+
|
|
85
|
+
NOTE: this uses the `bento-dropdown-country` component.
|
|
86
|
+
|
|
87
|
+
The `value` property within each option in `items` property array should translate to a currency code in
|
|
88
|
+
([ISO 4217 format](https://en.wikipedia.org/wiki/ISO_4217)).
|
|
89
|
+
|
|
90
|
+
### Payment method
|
|
91
|
+
|
|
92
|
+
<Canvas of={DropdownPaymentMethodStories.Default} />
|
|
93
|
+
|
|
94
|
+
NOTE: this uses the `bento-dropdown-payment-method` component, so the `value` property within each option in `items`
|
|
95
|
+
property array can take one of the values listed in the exported `IconsEnum` enum from the
|
|
96
|
+
`@adyen/ui-assets-payment-method-icons` passed as a string.
|
|
97
|
+
|
|
98
|
+
### Tag
|
|
99
|
+
|
|
100
|
+
<Canvas of={DropdownTagStories.Default} />
|
|
101
|
+
|
|
102
|
+
### Custom slots
|
|
103
|
+
|
|
104
|
+
If your dropdown items don't match any of the variants listed above, you can pass custom slots as the dropdown's options
|
|
105
|
+
via the default slot.
|
|
106
|
+
|
|
107
|
+
<Canvas of={DropdownStories.Slots} />
|
|
108
|
+
|
|
109
|
+
## Modifiers
|
|
110
|
+
|
|
111
|
+
### Display value
|
|
112
|
+
|
|
113
|
+
Each item in the dropdown can optionally be customized in the way it display when selected. To enable this feature, pass
|
|
114
|
+
a `display-value` property to the item's object in the items array.
|
|
115
|
+
|
|
116
|
+
<Canvas sourceState="hidden">
|
|
117
|
+
<Story of={DropdownStories.CustomDisplayValue} />
|
|
118
|
+
</Canvas>
|
|
119
|
+
|
|
120
|
+
### Condensed
|
|
121
|
+
|
|
122
|
+
Condensed variant is meant to be used in places where regular variant is too big, for example, in small areas (i.e.
|
|
123
|
+
filter bar search) or condensed versions (i.e. condensed b-data-grid) of other components. It should not be used in
|
|
124
|
+
normal page content, for example in forms or settings - use regular variant in those instead. To use this variant, pass
|
|
125
|
+
the `condensed` prop to the `bento-dropdown` when using it.
|
|
126
|
+
|
|
127
|
+
### Disabling options
|
|
128
|
+
|
|
129
|
+
The prop `isOptionDisabled` allows you to specify which options of the `items` array you want to render in a disabled
|
|
130
|
+
state. This prop takes in a function which runs over each dropdown option to dertermine if option should be disabled
|
|
131
|
+
from interaction. An example of usage would be:
|
|
132
|
+
|
|
133
|
+
```html
|
|
134
|
+
<template>
|
|
135
|
+
<bento-dropdown v-model="selectedValue" :items="apiItems" :is-option-disabled="isOptionDisabled" />
|
|
136
|
+
</template>
|
|
137
|
+
<script setup lang="ts">
|
|
138
|
+
const apiItems = ref(props.items);
|
|
139
|
+
const selectedValue = ref(props.value);
|
|
140
|
+
|
|
141
|
+
const clickedAction = (selectedOption: BentoListboxSelectedValue) => {
|
|
142
|
+
action('input')(selectedOption);
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const isOptionDisabled = (option: BentoListboxOptionItem) => [3, 5, 7].includes(option.value as number);
|
|
146
|
+
</script>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Dynamic filtering
|
|
150
|
+
|
|
151
|
+
This option can be enabled to allow the user to filter the dropdown options by typing into a value.
|
|
152
|
+
|
|
153
|
+
### Empty state
|
|
154
|
+
|
|
155
|
+
It's possible to customise the empty state message by using the `emptyState` property.
|
|
156
|
+
|
|
157
|
+
Here's an example:
|
|
158
|
+
|
|
159
|
+
```html
|
|
160
|
+
<template>
|
|
161
|
+
<bento-dropdown v-model="selectedValue" :items="apiItems" :search="search" :empty-state="emptyState" />
|
|
162
|
+
</template>
|
|
163
|
+
<script setup lang="ts">
|
|
164
|
+
const apiItems = ref(props.items);
|
|
165
|
+
const selectedValue = ref(props.value);
|
|
166
|
+
|
|
167
|
+
const emptyState = computed<BentoDropdownEmptyStateProps>(() => ({
|
|
168
|
+
title: 'Unable to retrieve items',
|
|
169
|
+
action: {
|
|
170
|
+
title: 'Refresh the page to try again',
|
|
171
|
+
event: () => {
|
|
172
|
+
// Refresh page logic
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
}));
|
|
176
|
+
|
|
177
|
+
const search = {
|
|
178
|
+
debounceTime: 2000,
|
|
179
|
+
searchEvent: async (val: string) => {
|
|
180
|
+
const { data } = await fetch('dropdown-items-api');
|
|
181
|
+
apiItems.value = data;
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
</script>
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Error state
|
|
188
|
+
|
|
189
|
+
There are two properties that control the error state of `dropdown`:
|
|
190
|
+
|
|
191
|
+
- `errorMessage`: renders `dropdown` in an error state and displays an error message beneath the field
|
|
192
|
+
- `error`: renders `dropdown` with an error state
|
|
193
|
+
|
|
194
|
+
`error` is deprecated and will be removed in the near future, so please use `errorMessage`.
|
|
195
|
+
|
|
196
|
+
### Items with description
|
|
197
|
+
|
|
198
|
+
Each item in the droepdown can optionally dispay a description. To enable this feature, pass a `description` field to
|
|
199
|
+
the item's object in the `items` array.
|
|
200
|
+
|
|
201
|
+
<Canvas sourceState="hidden">
|
|
202
|
+
<Story of={DropdownStories.WithDescription} />
|
|
203
|
+
</Canvas>
|
|
204
|
+
|
|
205
|
+
### External filtering
|
|
206
|
+
|
|
207
|
+
<Canvas sourceState="hidden">
|
|
208
|
+
<Story of={DropdownStories.externalFiltering} />
|
|
209
|
+
</Canvas>
|
|
210
|
+
|
|
211
|
+
Instead of relying on client-side filtering, you can configure the dropdown to fetch filtered results from an external
|
|
212
|
+
source, such as an API, as the user types. This is particularly useful for large datasets.
|
|
213
|
+
|
|
214
|
+
To implement external filtering, you'll use a combination of props:
|
|
215
|
+
|
|
216
|
+
- **`dynamic-filtering`**: This must be set to `true` to enable the user to type into the dropdown.
|
|
217
|
+
- **`search`**: This object configures the external search behavior. It has two properties:
|
|
218
|
+
- `searchEvent`: A function that is triggered with the search term as its argument. This is where you should place
|
|
219
|
+
your logic to call an external API.
|
|
220
|
+
- `debounceTime` (optional): The delay in milliseconds after the user stops typing before the `searchEvent` is
|
|
221
|
+
triggered. The default is `300ms`.
|
|
222
|
+
- **`items`**: This is the array of options displayed in the dropdown. You should dynamically update this prop with the
|
|
223
|
+
data you receive from your API call.
|
|
224
|
+
- **`component-loading`**: Set this boolean prop to `true` while waiting for the API response. This will display a
|
|
225
|
+
loading indicator inside the dropdown, providing feedback to the user.
|
|
226
|
+
|
|
227
|
+
The typical flow is as follows:
|
|
228
|
+
|
|
229
|
+
1. A user starts typing in the dropdown.
|
|
230
|
+
2. After the `debounceTime` has passed, the `searchEvent` function is called with the current search term.
|
|
231
|
+
3. In your `searchEvent` implementation, you set a loading state variable (bound to `component-loading`) to `true`.
|
|
232
|
+
4. You make your API request using the search term.
|
|
233
|
+
5. Once the request is complete, you update your `items` array with the results from the API.
|
|
234
|
+
6. Finally, you set the loading state variable back to `false`.
|
|
235
|
+
|
|
236
|
+
### Lazy loading
|
|
237
|
+
|
|
238
|
+
<Canvas sourceState="hidden">
|
|
239
|
+
<Story of={DropdownStories.LazyLoading} />
|
|
240
|
+
</Canvas>
|
|
241
|
+
|
|
242
|
+
The `bento-dropdown` component supports lazy loading, which is useful for handling large datasets by loading items in
|
|
243
|
+
batches as the user interacts with the dropdown. This can be done either automatically on scroll or by clicking a
|
|
244
|
+
button.
|
|
245
|
+
|
|
246
|
+
To enable lazy loading, you need to configure the following props:
|
|
247
|
+
|
|
248
|
+
- **`lazy-load-type`**: This determines how new items are loaded.
|
|
249
|
+
- `'automatic'`: The `show-more` event is emitted when the user scrolls to the bottom of the options list.
|
|
250
|
+
- `'button'`: A "Show more" button is displayed at the bottom of the list. Clicking it emits the `show-more` event.
|
|
251
|
+
- `'none'` (default): Lazy loading is disabled.
|
|
252
|
+
- **`has-more-items`**: A boolean that you should set to `true` as long as there are more items to be loaded from your
|
|
253
|
+
data source. Once all items are loaded, set it to `false` to hide the loader/button.
|
|
254
|
+
- **`loading`**: A boolean that should be set to `true` while you are fetching the next batch of items. This will
|
|
255
|
+
display a loading indicator to the user.
|
|
256
|
+
- **`@show-more`**: An event that is emitted when the user triggers the lazy load (either by scrolling or clicking the
|
|
257
|
+
button). You should listen for this event to fetch and append the next set of items to your `items` array.
|
|
258
|
+
|
|
259
|
+
### External filtering and Lazy loading simultaneously
|
|
260
|
+
|
|
261
|
+
<Canvas sourceState="hidden">
|
|
262
|
+
<Story of={DropdownStories.ExternalFilteringAndLazyLoading} />
|
|
263
|
+
</Canvas>
|
|
264
|
+
|
|
265
|
+
You can combine external filtering with lazy loading to create a powerful and efficient search experience for very large
|
|
266
|
+
datasets. This allows users to search for a specific term and then lazy-load more results that match that term.
|
|
267
|
+
|
|
268
|
+
To achieve this, you'll combine the props from both features. The key is to manage the state for both searching and lazy
|
|
269
|
+
loading.
|
|
270
|
+
|
|
271
|
+
- Use `component-loading` for the initial search loading state.
|
|
272
|
+
- Use `loading` for the subsequent lazy-loading state.
|
|
273
|
+
- When a new search is performed, you must reset your pagination and load the first page of results for the new search
|
|
274
|
+
term.
|
|
275
|
+
|
|
276
|
+
#### Pre-set values that are not in the initial loaded list.
|
|
277
|
+
|
|
278
|
+
Some times the `bento-dropdown` field comes with pre-selected values that are not present on the initial load. This can
|
|
279
|
+
be done in three ways:
|
|
280
|
+
|
|
281
|
+
- (**Recommended**) By **always** setting a `value` / `label` pair as selected value and setting the prop
|
|
282
|
+
`enable-value-label-pair` as `true`:
|
|
283
|
+
|
|
284
|
+
<Source
|
|
285
|
+
format={'html'}
|
|
286
|
+
dark
|
|
287
|
+
code={`
|
|
288
|
+
<bento-dropdown
|
|
289
|
+
:value="[{ label: 'Option 1', value: 1}]"
|
|
290
|
+
:items="[]"
|
|
291
|
+
enable-value-label-pair
|
|
292
|
+
/>
|
|
293
|
+
`}
|
|
294
|
+
/>
|
|
295
|
+
|
|
296
|
+
- By only providing the `value` of the selected options and all selected options in the _initial load_. The value will
|
|
297
|
+
be used as `label`
|
|
298
|
+
|
|
299
|
+
<Source
|
|
300
|
+
format={'html'}
|
|
301
|
+
dark
|
|
302
|
+
code={`
|
|
303
|
+
<bento-dropdown
|
|
304
|
+
:value="[100]"
|
|
305
|
+
:items="[{ value: 1, label: 'Option 1'}]"
|
|
306
|
+
/>
|
|
307
|
+
`}
|
|
308
|
+
/>
|
|
309
|
+
|
|
310
|
+
- By providing the `value` and appending the selected options as part of the `item` property after the _initial load_.
|
|
311
|
+
|
|
312
|
+
<Source
|
|
313
|
+
format={'html'}
|
|
314
|
+
dark
|
|
315
|
+
code={`
|
|
316
|
+
<bento-dropdown
|
|
317
|
+
:value="[100]"
|
|
318
|
+
:items="[
|
|
319
|
+
{ value: 1, label: 'Option 1'},
|
|
320
|
+
{ value: 100, label: 'Option 100'}
|
|
321
|
+
]"
|
|
322
|
+
/>
|
|
323
|
+
`}
|
|
324
|
+
/>
|
|
325
|
+
|
|
326
|
+
### Multi-select
|
|
327
|
+
|
|
328
|
+
To allow the user to select more than one item at a time, set the `multiple` property. This property will add checkboxes
|
|
329
|
+
to the items and allow to select multiple items at the same time.
|
|
330
|
+
|
|
331
|
+
<Canvas sourceState="hidden">
|
|
332
|
+
<Story of={DropdownStories.MultiSelect} />
|
|
333
|
+
</Canvas>
|
|
334
|
+
|
|
335
|
+
### Nested multi-select
|
|
336
|
+
|
|
337
|
+
The `bento-dropdown` multi-select variant comes with a nested functionality, which allows to group multiple child
|
|
338
|
+
options under a selectable parent category. This allows to create categories of options and select all the items inside
|
|
339
|
+
the category or the options of the category individually.
|
|
340
|
+
|
|
341
|
+
To enable this feature, pass a data structure in which each item in the `items` array which has a category will contain
|
|
342
|
+
an `items` array itself.
|
|
343
|
+
|
|
344
|
+
Categories and their options _must_ contain a 'description' that helps identifying the options and categories when
|
|
345
|
+
searching for values in `dynamicFiltering` mode.
|
|
346
|
+
|
|
347
|
+
<Canvas sourceState="hidden">
|
|
348
|
+
<Story of={DropdownStories.NestedMultiSelect} />
|
|
349
|
+
</Canvas>
|
|
350
|
+
|
|
351
|
+
### Static categories
|
|
352
|
+
|
|
353
|
+
Static categories (not to be confused with the Nested options categories) add an additional level of organisation to the
|
|
354
|
+
dropdown list. They're static, meaning they cannot be interacted with or searched for. They also don't appear in
|
|
355
|
+
selected section.
|
|
356
|
+
|
|
357
|
+
To display them the data structure must have 1 level of nested options (same as in Nested Options) and use the property
|
|
358
|
+
`static-categories`. Static categories can be used along with `multiple` to enable multi selection.
|
|
359
|
+
|
|
360
|
+
<Canvas sourceState="hidden">
|
|
361
|
+
<Story of={DropdownStories.StaticCategories} />
|
|
362
|
+
</Canvas>
|
|
363
|
+
|
|
364
|
+
### Readonly
|
|
365
|
+
|
|
366
|
+
Setting the `readonly` property renders the `dropdown` component in a readonly state. In terms of functionality, read
|
|
367
|
+
only states are focusable and submittable in a form. They're considered to be 'active' from an application's point of
|
|
368
|
+
view.
|
|
369
|
+
|
|
370
|
+
### Tooltip
|
|
371
|
+
|
|
372
|
+
A tooltip with extra information for the field can be rendered by passing the desired information to the `tooltipText`
|
|
373
|
+
prop.
|
|
374
|
+
|
|
375
|
+
## Accessibility
|
|
376
|
+
|
|
377
|
+
### Keyboard interaction
|
|
378
|
+
|
|
379
|
+
Being an interactive element, the dropdown is reachable through keyboard navigation in the logical navigation flow of
|
|
380
|
+
the page. The following keys trigger the following actions:
|
|
381
|
+
|
|
382
|
+
Dropdown `combobox` in focus:
|
|
383
|
+
|
|
384
|
+
| Keyboard interaction | Action |
|
|
385
|
+
| -------------------- | ----------------------------------------------------- |
|
|
386
|
+
| `Tab` | Moves focus to the dropdown selector or away from it. |
|
|
387
|
+
| `Space` | Opens / closes dropdown |
|
|
388
|
+
| `Enter` | Opens / closes dropdown |
|
|
389
|
+
| `Escape` | Closes dropdown |
|
|
390
|
+
| `Arrow down` | Move down the dropdown options list |
|
|
391
|
+
|
|
392
|
+
Dropdown `listbox` in focus:
|
|
393
|
+
|
|
394
|
+
| Keyboard interaction | Action |
|
|
395
|
+
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
|
|
396
|
+
| `Escape` | Closes popup. |
|
|
397
|
+
| `Enter` | Select option in dropdown and cloes popup. |
|
|
398
|
+
| `Down Arrow` | Moves focus to and selects the next option. If focus is on the last option, either returns focus to the combobox or does nothing. |
|
|
399
|
+
| `Up Arrow` | Moves focus to and selects the previous option. If focus is on the first option, either returns focus to the combobox or does nothing. |
|
|
400
|
+
| `Up Arrow` | Moves focus to and selects the previous option. If focus is on the first option, either returns focus to the combobox or does nothing. |
|
|
401
|
+
|
|
402
|
+
### Roles, states and properties
|
|
403
|
+
|
|
404
|
+
Adhering to the [WAI-ARIA 1.2](https://www.w3.org/TR/wai-aria-1.2/) standard we follow the
|
|
405
|
+
[combobox pattern](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/#wai-ariaroles,states,andproperties) documentation
|
|
406
|
+
provided.
|
|
407
|
+
|
|
408
|
+
### Roles, states and properties
|
|
409
|
+
|
|
410
|
+
| Role | Attribute | Element | Usage |
|
|
411
|
+
| -------- | ---------------------- | --------------- | ----------------------------------------------------------------------------------------------- |
|
|
412
|
+
| combobox | | `input` / `div` | Indicates the element has an associated popup. The popup may be a `listbox` or `tree` |
|
|
413
|
+
| | `aria-label` | | The input requires an accessible name that will be assigned to it |
|
|
414
|
+
| | `aria-readonly="true"` | | Set only when `readonly` is set to `true` |
|
|
415
|
+
| listbox | | | Allows the user to select one or more items from a list of choices (single and multi select) |
|
|
416
|
+
| tree | | | Allows the user to select one or more items from a hierarchically organised collection (nested) |
|
|
417
|
+
|
|
418
|
+
## Resources
|
|
419
|
+
|
|
420
|
+
- [Figma link](https://www.figma.com/file/uLabwF3243jdMDsNSP7U9I/Bento---Components?type=design&node-id=964-18157&t=VN4tRotGCnkexZI4-0)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import { action } from '@storybook/addon-actions'; import { computed, onMounted, ref, watch } from 'vue'; import { isVue2 } from 'vue-demi'; import { type BentoListboxOptionItem, type BentoListboxOptions, type BentoListboxSelectedValue } from '@/types/listbox'; import { BentoDropdownEvent, BentoDropdownLazyLoadType, type BentoDropdownSearchOptions, } from '@/components/dropdown/dropdown.types'; import { BentoTag, BentoTagVariant } from '@/components/tag'; import { useCountryName, useCountryPhoneCode } from '@/components/country'; import { storybookDocsParameter } from '@/utils/ts/storybook'; import type { Meta, StoryObj } from '@storybook/vue'; import BentoDropdown from './dropdown.vue'; import DropdownWithDescriptionExample from './__tests__/dropdown-with-description-example.vue?raw'; import DropdownWithCustomSlotsExample from './__tests__/dropdown-with-custom-slots-example.vue?raw'; // Generate array from 1 to # // { label: 'Option #', value: # }, const DEFAULT_OPTIONS: BentoListboxOptions = new Array(10) .fill(0) .map((_, index) => ({ label: `Option ${index + 1}`, value: index + 1 })); const DEFAULT_DESCRIPTION = 'Choose an option'; const DEFAULT_LABEL = 'Label'; const DEFAULT_PLACEHOLDER = 'Select an option'; const DEFAULT_PROPS = { label: DEFAULT_LABEL, description: DEFAULT_DESCRIPTION, placeholder: DEFAULT_PLACEHOLDER, lazyLoadType: 'none', enableValueLabelPair: false, }; const COUNTRY_OPTIONS = ['NL', 'BE', 'DE'].map(code => ({ label: `${useCountryName(code, 'en-US').value} ${useCountryPhoneCode(code).value}`, value: code, displayValue: `${code} (${useCountryPhoneCode(code).value})`, })); const showMoreOptions = () => action(BentoDropdownEvent.SHOW_MORE)(); const meta: Meta = { title: 'Dropdown', component: BentoDropdown, argTypes: { 'show-more': { name: 'show-more', table: { type: { summary: '() => void' }, }, control: { disable: true }, }, size: { table: { disable: true } }, // Slots default: { description: 'Custom slots for the items and display value', table: { category: 'Slots', type: { summary: 'VNode[]' }, }, control: { type: 'text' }, }, description: { description: 'Custom slot for the description', table: { category: 'Slots', type: { summary: 'VNode[]' }, }, control: { type: 'text' }, }, 'display-value': { description: 'Custom slot for the display value when the display value is different than the items', table: { category: 'Slots', type: { summary: 'VNode[]' }, }, control: { type: 'text' }, }, innerSlot: { // Hide non-existent slot inaccurately created by storybook table: { disable: true }, }, // Values lazyLoadType: { options: Object.values(BentoDropdownLazyLoadType), mapping: BentoDropdownLazyLoadType, control: { type: 'select', }, }, error: { control: { type: 'boolean' }, table: { category: 'Deprecated' }, }, input: { description: 'Deprecated since version 2.0. Use `v-model` or `update:model-value` instead', table: { category: 'Deprecated' }, control: { disable: true }, }, value: { control: { type: 'text' }, table: { category: 'Deprecated' }, description: 'Deprecated for usage with properties since version 2.0. Use `v-model` or `model-value` property instead', }, }, }; export default meta; type Story = StoryObj<typeof BentoDropdown>; const defaultDocs = ` <template> <bento-dropdown v-model="modelValue" aria-label="Descriptive definition of what the dropdown is for" description="Description of the field" :condensed="false" :disabled="false" :empty-state="emptyState" dynamic-filtering error-message="This is an error message" :multiple="false" :items="items" :is-option-disabled="isOptionDisabled" label="Dropdown input label" :optional="!isRequired" tooltip-text="Label tooltip text" placeholder="Dropdown placeholder text" :required="isRequired" enable-value-label-pair /> </template> <script setup lang="ts"> import { BentoDropdown, BentoDropdownEmptyStateProps, BentoListboxOptionItem, BentoListboxOptions, BentoListboxSelectedValue, } from '@adyen/bento-vue2'; import { computed, ref } from 'vue'; const modelValue = ref<BentoListboxSelectedValue>({ label: 'Option 1', value: 1 }); const items: BentoListboxOptions = new Array(10) .fill(0) .map((_, index) => ({ label: \`Option \${index + 1}\`, value: index + 1 })); // "required" and "optional" are mutually exclusive const isRequired = ref(false); const emptyState = computed<BentoDropdownEmptyStateProps>(() => ({ title: 'Unable to retrieve items', action: { title: 'Refresh the page to try again', event: () => { // Refresh page logic }, }, })); // Disable individual options const isOptionDisabled = (option: BentoListboxOptionItem) => option.value === 3; </script> `; export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoDropdown }, props: Object.keys(argTypes), setup(props) { const args = isVue2 ? props : _args; const selectedValue = ref(args.modelValue); const isOptionDisabled = (option: BentoListboxOptionItem) => option.value === 3; const onUpdateModelValue = (newSelectedValue: BentoListboxSelectedValue) => { selectedValue.value = newSelectedValue; action('update:model-value')(newSelectedValue); }; return { // Values args, selectedValue, // Methods isOptionDisabled, onUpdateModelValue, showMoreOptions, }; }, template: ` <div style="max-width: 300px"> <bento-dropdown v-bind="args" :model-value="selectedValue" @update:model-value="onUpdateModelValue" :isOptionDisabled="isOptionDisabled" @show-more="showMoreOptions" /> </div> `, }), args: { ...DEFAULT_PROPS, modelValue: DEFAULT_OPTIONS[0].value, items: DEFAULT_OPTIONS, }, parameters: storybookDocsParameter(defaultDocs), }; export const MultiSelect: Story = { render: (_args, { argTypes }) => ({ components: { BentoDropdown }, props: Object.keys(argTypes), setup(props) { const args = isVue2 ? props : _args; const selectedValue = ref(args.modelValue); const isOptionDisabled = (option: BentoListboxOptionItem) => [3, 5, 7, 9].includes(option.value as number); const onUpdateModelValue = (newSelectedValue: BentoListboxSelectedValue) => { selectedValue.value = newSelectedValue; action('update:model-value')(newSelectedValue); }; return { // Values selectedValue, args, // Methods isOptionDisabled, onUpdateModelValue, showMoreOptions, }; }, template: ` <div style="max-width: 300px"> <bento-dropdown v-bind="args" :model-value="selectedValue" @update:model-value="onUpdateModelValue" :isOptionDisabled="isOptionDisabled" @show-more="showMoreOptions" /> </div> `, }), args: { ...DEFAULT_PROPS, description: 'Choose one or more options', modelValue: [1, 3, 100], multiple: true, items: DEFAULT_OPTIONS, }, parameters: storybookDocsParameter(` <template> <bento-dropdown v-model="modelValue" aria-label="Descriptive definition of what the dropdown is for" description="Description of the field" :condensed="false" :disabled="false" :empty-state="emptyState" dynamic-filtering error-message="This is an error message" :multiple="true" :items="items" :is-option-disabled="isOptionDisabled" label="Dropdown input label" :optional="!isRequired" tooltip-text="Label tooltip text" placeholder="Dropdown placeholder text" :required="isRequired" enable-value-label-pair @update:model-value="onUpdateModelValue" /> </template> <script setup lang="ts"> import { BentoDropdown, BentoDropdownEmptyStateProps, BentoListboxOptionItem, BentoListboxOptions, BentoListboxSelectedValue, } from '@adyen/bento-vue2'; import { computed, ref } from 'vue'; const modelValue = ref<BentoListboxSelectedValue>([ { label: 'Option 1', value: 1 } ]); const items: BentoListboxOptions = new Array(10) .fill(0) .map((_, index) => ({ label: \`Option \${index + 1}\`, value: index + 1 })); // "required" and "optional" are mutually exclusive const isRequired = ref(false); const emptyState = computed<BentoDropdownEmptyStateProps>(() => ({ title: 'Unable to retrieve items', action: { title: 'Refresh the page to try again', event: () => { // Refresh page logic }, }, })); // Disable individual options const isOptionDisabled = (option: BentoListboxOptionItem) => option.value === 3; const onUpdateModelValue = (newSelectedValue: BentoListboxSelectedValue) => { modelValue.value = newSelectedValue; }; </script> `), }; const TAG_LIST = Object.values(BentoTagVariant); const TAG_OPTIONS: BentoListboxOptions = new Array(TAG_LIST.length) .fill(0) .map((_, index) => ({ label: `Option ${index + 1}`, value: TAG_LIST[index] })); export const Slots: Story = { render: (_args, { argTypes }) => ({ components: { BentoDropdown, BentoTag }, props: Object.keys(argTypes), setup(props) { const selectedValue = ref(props.value); const onUpdateModelValue = (newSelectedValue: BentoListboxSelectedValue) => { selectedValue.value = newSelectedValue; action('update:model-value')(newSelectedValue); }; return { // Values args: isVue2 ? props : _args, selectedValue, // Methods onUpdateModelValue, showMoreOptions, }; }, template: ` <div style="max-width: 300px"> <bento-dropdown v-bind="args" :model-value="selectedValue" @update:model-value="onUpdateModelValue" v-slot="{label, value}" @show-more="showMoreOptions" > <bento-tag :variant="value" :label="label" /> </bento-dropdown> </div> `, }), args: { ...DEFAULT_PROPS, modelValue: TAG_OPTIONS[1].value, items: TAG_OPTIONS, }, parameters: storybookDocsParameter(DropdownWithCustomSlotsExample), }; export const WithDescription: Story = { ...Default, args: { ...DEFAULT_PROPS, modelValue: [1, 2, 3], multiple: true, items: DEFAULT_OPTIONS.map(item => ({ ...item, description: `Description ${item.value}`, })), }, parameters: storybookDocsParameter(DropdownWithDescriptionExample), }; export const NestedMultiSelect: Story = { render: (_args, { argTypes }) => ({ components: { BentoDropdown }, props: Object.keys(argTypes), setup(props) { const args = isVue2 ? props : _args; const selectedValue = ref(args.modelValue); const isOptionDisabled = (option: BentoListboxOptionItem) => [4, 5].includes(option.value as number); const onUpdateModelValue = (newSelectedValue: BentoListboxSelectedValue) => { selectedValue.value = newSelectedValue; action('update:model-value')(newSelectedValue); }; return { // Values args, selectedValue, // Methods isOptionDisabled, onUpdateModelValue, }; }, template: ` <div style="max-width: 300px"> <bento-dropdown v-bind="args" :model-value="selectedValue" @update:model-value="onUpdateModelValue" :isOptionDisabled="isOptionDisabled" /> </div> `, }), args: { ...DEFAULT_PROPS, description: 'Choose one or more options', modelValue: [2, 3, 100], multiple: true, items: [ { label: 'Option 1', description: 'Uncategorized', value: 1, }, { label: 'Category 1', description: 'C1', value: 'c1', items: [ { label: 'Option 2', description: 'C1-O2', value: 2, }, { label: 'Option 3', description: 'C1-O3', value: 3, }, ], }, { label: 'Category 2', description: 'C2', value: 'c2', items: [ { label: 'Option 4', description: 'C2-O4', value: 4, }, { label: 'Option 5', description: 'C2-O5', value: 5, }, ], }, ] as BentoListboxOptions, }, }; const externalFilteringTemplate = storybookDocsParameter(` <template> <bento-dropdown v-model="selectedValue" :items="internalList" :search="search" :component-loading="isSearching" dynamic-filtering multiple has-more-items lazy-load-type="automatic" label="Dropdown with external filtering" enable-value-label-pair /> </template> <script setup lang="ts"> import { ref, computed } from 'vue'; import { BentoDropdown, BentoListboxOptions, BentoListboxSelectedValueLabelPair, BentoDropdownSearchOptions, } from '@adyen/bento-vue2'; const initialItems: BentoListboxOptions = [ { label: 'Option 1', value: 1 }, { label: 'Option 2', value: 2 }, { label: 'Option 3', value: 3 }, { label: 'Option 4', value: 4 }, { label: 'Option 5', value: 5 }, ]; const selectedValue = ref<Array<BentoListboxSelectedValueLabelPair>>([]); const internalList = ref<BentoListboxOptions>(initialItems); const isSearching = ref(false); const searchForValue = (searchString: string) => { // This is where you would typically make an API call // to fetch data based on the searchString. // For this example, we'll simulate it. if (!searchString) { internalList.value = initialItems; return; } const newItems = new Array(5).fill(0).map((_, index) => ({ label: \`\${searchString} \${index + 1}\`, value: \`\${searchString}-\${index + 1}\`, })); internalList.value = newItems; }; const search = computed<BentoDropdownSearchOptions>(() => ({ debounceTime: 500, searchEvent: async (value: string) => { isSearching.value = true; // Simulate API call delay await new Promise(resolve => setTimeout(resolve, 1000)); searchForValue(value); isSearching.value = false; }, })); </script> `); export const externalFiltering: Story = { render: (_args, { argTypes }) => ({ components: { BentoDropdown }, props: Object.keys(argTypes), setup(props) { const args = isVue2 ? props : _args; const selectedValue = ref(args.modelValue); const internalList = ref<BentoListboxOptions>(args.items); const isSearching = ref(false); const searchForValue = (searchString: string) => { // This is where you would typically make an API call // to fetch data based on the searchString. // For this example, we'll simulate it. if (!searchString) { internalList.value = args.items; return; } const newItems = new Array(5).fill(0).map((_, index) => ({ label: `${searchString} ${index + 1}`, value: `${searchString}-${index + 1}`, })); internalList.value = newItems; }; const search = computed<BentoDropdownSearchOptions>(() => ({ debounceTime: args.search.debounceTime, searchEvent: async value => { isSearching.value = true; await new Promise(resolve => setTimeout(resolve, 1000)); searchForValue(value); action('searchEvent')(value); isSearching.value = false; }, })); const onUpdateModelValue = (newSelectedValue: BentoListboxSelectedValue) => { selectedValue.value = newSelectedValue; action('update:model-value')(newSelectedValue); }; return { args, selectedValue, internalList, isSearching, search, onUpdateModelValue, }; }, template: ` <div style="max-width: 300px"> <bento-dropdown v-bind="args" :model-value="selectedValue" :items="internalList" :search="search" :component-loading="isSearching" @update:model-value="onUpdateModelValue" /> </div> `, }), args: { ...DEFAULT_PROPS, items: DEFAULT_OPTIONS, label: 'Dropdown with external filtering', description: 'Type to simulate fetching new items.', dynamicFiltering: true, multiple: true, modelValue: [], search: { debounceTime: 500, }, }, parameters: externalFilteringTemplate, }; const lazyLoadingTemplate = storybookDocsParameter(` <template> <bento-dropdown v-model="selectedValue" :items="listboxItems" :loading="isLoading" :has-more-items="hasMoreItems" :lazy-load-type="lazyLoadType" label="Lazy Loading Dropdown" multiple enable-value-label-pair @show-more="onLazyLoad" /> </template> <script setup lang="ts"> import { ref, onMounted } from 'vue'; import { BentoDropdown, BentoListboxOptions, BentoListboxSelectedValueLabelPair, BentoDropdownLazyLoadType, } from '@adyen/bento-vue2'; // --- Mock API for demonstration --- const ALL_ITEMS = Array.from({ length: 100 }, (_, i) => ({ label: \`Item \${i + 1}\`, value: i + 1, })); const PAGE_SIZE = 20; const fetchItems = (page: number) => { console.log(\`Fetching page: \${page}\`); return new Promise<{ items: BentoListboxOptions; hasMore: boolean }>(resolve => { setTimeout(() => { const start = (page - 1) * PAGE_SIZE; const end = start + PAGE_SIZE; const pageItems = ALL_ITEMS.slice(start, end); resolve({ items: pageItems, hasMore: end < ALL_ITEMS.length, }); }, 500); }); }; // --- End Mock API --- const selectedValue = ref<Array<BentoListboxSelectedValueLabelPair>>([]); const listboxItems = ref<BentoListboxOptions>([]); const isLoading = ref(false); const hasMoreItems = ref(true); const currentPage = ref(1); const lazyLoadType = ref<BentoDropdownLazyLoadType>('automatic'); // or 'button' const onLazyLoad = async () => { if (isLoading.value || !hasMoreItems.value) { return; } currentPage.value++; isLoading.value = true; const { items, hasMore } = await fetchItems(currentPage.value); // Append new items to the existing list listboxItems.value = [...listboxItems.value, ...items]; hasMoreItems.value = hasMore; isLoading.value = false; }; // Load initial data when the component mounts onMounted(async () => { isLoading.value = true; const { items, hasMore } = await fetchItems(currentPage.value); listboxItems.value = items; hasMoreItems.value = hasMore; isLoading.value = false; }); </script> `); export const LazyLoading: Story = { render: (_args, { argTypes }) => ({ components: { BentoDropdown }, props: Object.keys(argTypes), setup(props) { const args = isVue2 ? props : _args; const selectedValue = ref<BentoListboxSelectedValue>([]); const listboxItems = ref<BentoListboxOptions>([]); const isLoading = ref(false); const hasMoreItems = ref(true); const currentPage = ref(1); const allItems = Array.from({ length: 100 }, (_, i) => ({ label: `Item ${i + 1}`, value: i + 1, })); const pageSize = 20; const fetchItems = (page: number) => { return new Promise<{ items: BentoListboxOptions; hasMore: boolean }>(resolve => { setTimeout(() => { const start = (page - 1) * pageSize; const end = start + pageSize; const pageItems = allItems.slice(start, end); resolve({ items: pageItems, hasMore: end < allItems.length, }); }, 500); }); }; const onLazyLoad = async () => { if (isLoading.value || !hasMoreItems.value) { return; } currentPage.value++; isLoading.value = true; const { items, hasMore } = await fetchItems(currentPage.value); listboxItems.value = [...listboxItems.value, ...items]; hasMoreItems.value = hasMore; isLoading.value = false; action('show-more')(); }; const onUpdateModelValue = (newSelectedValue: BentoListboxSelectedValue) => { selectedValue.value = newSelectedValue; action('update:model-value')(newSelectedValue); }; const loadInitial = async () => { currentPage.value = 1; isLoading.value = true; const { items, hasMore } = await fetchItems(currentPage.value); listboxItems.value = items; hasMoreItems.value = hasMore; isLoading.value = false; }; watch(() => args.lazyLoadType, loadInitial, { immediate: true }); return { args, selectedValue, listboxItems, isLoading, hasMoreItems, onLazyLoad, onUpdateModelValue, }; }, template: ` <div style="max-width: 300px"> <bento-dropdown v-bind="args" :items="listboxItems" :model-value="selectedValue" :loading="isLoading" :has-more-items="hasMoreItems" @update:model-value="onUpdateModelValue" @show-more="onLazyLoad" /> </div> `, }), args: { ...DEFAULT_PROPS, label: 'Lazy Loading', description: 'Scroll down or click the button to load more items.', multiple: true, lazyLoadType: 'automatic', }, parameters: lazyLoadingTemplate, }; const externalFilteringAndLazyLoadingTemplate = storybookDocsParameter(` <template> <bento-dropdown v-model="selectedValue" :items="listboxItems" :component-loading="isComponentLoading" :loading="isLazyLoading" :has-more-items="hasMoreItems" :search="search" :lazy-load-type="lazyLoadType" label="External Filtering & Lazy Loading" dynamic-filtering multiple enable-value-label-pair @show-more="onLazyLoad" /> </template> <script setup lang="ts"> import { ref, computed, onMounted } from 'vue'; import { BentoDropdown, BentoListboxOptions, BentoListboxSelectedValueLabelPair, BentoDropdownSearchOptions, BentoDropdownLazyLoadType, } from '@adyen/bento-vue2'; // --- Mock API for demonstration --- const ALL_ITEMS = Array.from({ length: 1000 }, (_, i) => ({ label: \`Item \${i + 1}\`, value: i + 1, })); const PAGE_SIZE = 20; const fetchItems = (searchTerm: string, page: number) => { console.log(\`Fetching items with searchTerm: "\${searchTerm}", page: \${page}\`); return new Promise<{ items: BentoListboxOptions; hasMore: boolean }>(resolve => { setTimeout(() => { const filtered = ALL_ITEMS.filter(item => item.label.toLowerCase().includes(searchTerm.toLowerCase()) ); const start = (page - 1) * PAGE_SIZE; const end = start + PAGE_SIZE; const pageItems = filtered.slice(start, end); resolve({ items: pageItems, hasMore: end < filtered.length, }); }, 500); }); }; // --- End Mock API --- const selectedValue = ref<Array<BentoListboxSelectedValueLabelPair>>([]); const listboxItems = ref<BentoListboxOptions>([]); const isComponentLoading = ref(false); const isLazyLoading = ref(false); const hasMoreItems = ref(true); const currentPage = ref(1); const currentSearchTerm = ref(''); const lazyLoadType = ref<BentoDropdownLazyLoadType>('automatic'); const onSearch = async (searchTerm: string) => { currentSearchTerm.value = searchTerm; currentPage.value = 1; isComponentLoading.value = true; const { items, hasMore } = await fetchItems(searchTerm, currentPage.value); listboxItems.value = items; hasMoreItems.value = hasMore; isComponentLoading.value = false; }; const onLazyLoad = async () => { if (isLazyLoading.value || !hasMoreItems.value) { return; } currentPage.value++; isLazyLoading.value = true; const { items, hasMore } = await fetchItems(currentSearchTerm.value, currentPage.value); listboxItems.value = [...listboxItems.value, ...items]; hasMoreItems.value = hasMore; isLazyLoading.value = false; }; const search = computed<BentoDropdownSearchOptions>(() => ({ searchEvent: onSearch, })); onMounted(() => { onSearch(''); }); </script> `); export const ExternalFilteringAndLazyLoading: Story = { render: (_args, { argTypes }) => ({ components: { BentoDropdown }, props: Object.keys(argTypes), setup(props) { const args = isVue2 ? props : _args; const selectedValue = ref<BentoListboxSelectedValue>([{ label: 'Item 2000', value: 2000 }]); const listboxItems = ref<BentoListboxOptions>([]); const isComponentLoading = ref(false); const isLazyLoading = ref(false); const hasMoreItems = ref(true); const currentPage = ref(1); const currentSearchTerm = ref(''); const allItems = Array.from({ length: 1000 }, (_, i) => ({ label: `Item ${i + 1}`, value: i + 1, })); const pageSize = 20; const fetchItems = (searchTerm: string, page: number) => { return new Promise<{ items: BentoListboxOptions; hasMore: boolean }>(resolve => { setTimeout(() => { const filtered = allItems.filter(item => item.label.toLowerCase().includes(searchTerm.toLowerCase()) ); const start = (page - 1) * pageSize; const end = start + pageSize; const pageItems = filtered.slice(start, end); resolve({ items: pageItems, hasMore: end < filtered.length, }); }, 500); }); }; const onSearch = async (searchTerm: string) => { action('searchEvent')(searchTerm); currentSearchTerm.value = searchTerm; currentPage.value = 1; isComponentLoading.value = true; const { items, hasMore } = await fetchItems(searchTerm, currentPage.value); listboxItems.value = items; hasMoreItems.value = hasMore; isComponentLoading.value = false; }; const onLazyLoad = async () => { if (isLazyLoading.value || !hasMoreItems.value) { return; } currentPage.value++; isLazyLoading.value = true; const { items, hasMore } = await fetchItems(currentSearchTerm.value, currentPage.value); listboxItems.value = [...listboxItems.value, ...items]; hasMoreItems.value = hasMore; isLazyLoading.value = false; action('show-more')(); }; const onUpdateModelValue = (newSelectedValue: BentoListboxSelectedValue) => { selectedValue.value = newSelectedValue; action('update:model-value')(newSelectedValue); }; const search = computed<BentoDropdownSearchOptions>(() => ({ searchEvent: onSearch, debounceTime: args.search?.debounceTime, })); onMounted(() => { onSearch(''); }); return { args, selectedValue, listboxItems, isComponentLoading, isLazyLoading, hasMoreItems, search, onLazyLoad, onUpdateModelValue, }; }, template: ` <div style="max-width: 300px"> <bento-dropdown v-bind="args" :items="listboxItems" :model-value="selectedValue" :component-loading="isComponentLoading" :loading="isLazyLoading" :has-more-items="hasMoreItems" :search="search" @update:model-value="onUpdateModelValue" @show-more="onLazyLoad" /> </div> `, }), args: { ...DEFAULT_PROPS, label: 'External Filtering & Lazy Loading', description: 'Search for items and scroll to load more results.', dynamicFiltering: true, multiple: true, lazyLoadType: 'automatic', search: { debounceTime: 500, }, }, parameters: externalFilteringAndLazyLoadingTemplate, }; export const StaticCategories: Story = { render: (_args, { argTypes }) => ({ components: { BentoDropdown }, props: Object.keys(argTypes), setup(props) { const selectedValue = ref(props.value); const onUpdateModelValue = (newSelectedValue: BentoListboxSelectedValue) => { selectedValue.value = newSelectedValue; action('update:model-value')(newSelectedValue); }; return { // Values selectedValue, args: isVue2 ? props : _args, // Methods onUpdateModelValue, }; }, template: ` <div style="max-width: 300px"> <bento-dropdown v-bind="args" :model-value="selectedValue" @update:model-value="onUpdateModelValue" /> </div> `, }), args: { ...DEFAULT_PROPS, description: 'Select an option from a category', modelValue: '', multiple: false, staticCategories: true, items: [ { label: 'Carnivores', value: 'carnivores', items: [ { label: 'T-Rex', value: 'trex', }, { label: 'Velociraptor', value: 'velociraptor', }, ], }, { label: 'Herbivores', value: 'herbivores', items: [ { label: 'Iguanodon', value: 'iguanodon', }, { label: 'Barney', value: 'barney', }, ], }, { label: 'Aquatic', value: 'aquatic', items: [ { label: 'Mosasaurus', value: 'mosasaurus', }, { label: 'Nessie', value: 'nessie', }, ], }, ] as BentoListboxOptions, }, }; export const VirtualScrolling: Story = { ...Default, args: { ...Default.args, items: new Array(1000).fill(0).map((_, index) => ({ label: `Option ${index + 1}`, value: index + 1 })), virtualScroll: true, }, }; export const CustomDisplayValue: Story = { render: (_args, { argTypes }) => ({ components: { BentoDropdown }, props: Object.keys(argTypes), setup(props) { const args = isVue2 ? props : _args; const selectedValue = ref(args.modelValue); const onUpdateModelValue = (newSelectedValue: BentoListboxSelectedValue) => { selectedValue.value = newSelectedValue; action('update:model-value')(newSelectedValue); }; return { // Values args, selectedValue, // Methods onUpdateModelValue, }; }, template: ` <div style="max-width: 300px"> <bento-dropdown v-bind="args" :model-value="selectedValue" @update:model-value="onUpdateModelValue" /> </div> `, }), args: { ...DEFAULT_PROPS, modelValue: COUNTRY_OPTIONS[0].value, items: COUNTRY_OPTIONS, }, parameters: storybookDocsParameter(` <bento-dropdown :items="[ {label: "Netherlands +31", displayValue: "NL +31", value: "NL"}, {label: "Belgium +32", displayValue: "BE +32", value: "BE"}, {label: "Germany +49", displayValue: "DE +49", value: "DE"}, ]" :model-value="selectedValue" @update:model-value="onUpdateModelValue" /> `), };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<!-- TODO: Remove aria props as these are part of JSX elements /vue/types/jsx.d.ts --> <!-- eslint-disable vue/attribute-hyphenation --> <template> <div v-bento-click-outside-directive="clickOutsideDropdown" class="b-dropdown" :class="conditionalClasses"> <field-label v-if="label" :id="labelId" :condensed="condensed" :label="label" :tooltip-text="tooltipText" :optional="optional" :required="required" /> <div ref="inputContainerRefWrapper" v-bento-keyboard-navigation-directive v-on="textboxKeyboardNavigationListeners" > <component :is="computedSize" ref="inputContainerRef" :condensed="condensed" :value="searchTerm" :additional-items-selected="additionalItemsSelected" :aria-controls="dropdownOptionsContainerId" :ariaExpanded="isDropdownOpen" :aria-labelledby="label ? labelId : null" :aria-describedby="ariaDescribedBy" :aria-label="computedAriaLabel" :aria-readonly="isReadOnly" :readonly="isReadOnly" :required="required" :disabled="disabled" :display-value="displayValue" :dynamic-filtering="dynamicFiltering" :is-invalid="!!errorMessage || error" :multiple="multiple" :open="isDropdownOpen" :selected-value-item="!isInputComponent ? selectedValueItem : undefined" :show-slot-content-in-multiple="showSlotContentInMultiple" @input="searchTerm = $event" @open="toggleDropdown" @close="toggleDropdown" > <template v-for="(_, innerSlot) of slots" #[innerSlot]="scope"> <slot :name="innerSlot" v-bind="scope" /> </template> </component> </div> <bento-dropdown-options-container v-if="inputContainerRef" :id="dropdownOptionsContainerId" ref="optionsContainerRef" :aria-label="computedAriaLabel" :component-loading="componentLoading" :empty-state="emptyState" :debounce-time="search?.debounceTime" :disabled="disabled" :is-option-disabled="isOptionDisabled" :items="filteredItems" :multiple="multiple" :static-categories="staticCategories" :open="isDropdownOpen" :selected-value="cachedSelectedListboxOptions" :searching="isSearching" :target-element="inputContainerRefWrapper" :loading="loading" :has-more-items="hasMoreItems" :lazy-load-type="lazyLoadType" :virtual-scroll="virtualScroll" v-on="listboxListeners" > <template v-for="(_, innerSlot) of slots" #[innerSlot]="scope"> <slot :name="innerSlot" v-bind="scope" /> </template> </bento-dropdown-options-container> <error-message v-if="!!errorMessage" :id="errorId" :error-message="errorMessage" class="b-dropdown__error-message" /> <bento-typography v-if="description || hasSlot('description')" :id="descriptionId" class="b-dropdown__description" :class="descriptionConditionalClasses" el="span" > <slot name="description"> {{ description }} </slot> </bento-typography> </div> </template> <script setup lang="ts"> import { computed, defineComponent, inject, nextTick, type Ref, ref, toRef, toRefs, useAttrs, useSlots, watch, } from 'vue'; // Components import { BentoDropdownOptionsContainer } from './components/dropdown-options-container'; import { BentoTypography } from '@/components/typography'; import { DropdownInputDefault, DropdownInputSmall } from './components'; import { BentoListbox, ErrorMessage, FieldLabel, useCachedSelectedValues, useMultiLevelItems } from '@/internal'; // Utils import { deprecate } from '@/utils/ts/deprecate'; import { generateUid } from '@/core/utils/ts'; import { useAriaLabel } from '@/utils/ts/aria-label'; import { useI18n } from '@/utils/ts/i18n'; import { useFormFieldEmits } from '@/utils/ts/form-field/form-field'; // Composables import { useDisplayValue, useListboxKeyboardNavigation, useTextboxKeyboardNavigation } from './composables'; import { useVisibleDomOptions } from '@/internal/listbox/composables/use-visible-dom-options/use-visible-dom-options'; import { useFormLayoutFieldLoading } from '@/composables/use-form-layout-loading/use-form-layout-loading'; import { useSearchBarFilter } from '@/components/search-bar/useSearchBarDataFilter'; // Directives import { BentoClickOutsideDirective as vBentoClickOutsideDirective } from '@/directives/click-outside'; import { BentoKeyboardNavigationDirective as vBentoKeyboardNavigationDirective } from '@/directives'; // Types import { BentoDropdownEvent, type BentoDropdownProps, BentoDropdownSize } from './dropdown.types'; import { BentoListboxEvent, type BentoListboxItemRole, type BentoListboxMultiSelectValue, type BentoListboxOptions, type BentoListboxSelectedValue, type BentoListboxSelectedValueLabelPair, } from '@/types/listbox'; import messages from './messages.json'; import { useHasSlot } from '@/composables'; import { INPUT_FIELD_COMPONENT_INJECTION_KEY } from '@/components/input-field/input-field.keys'; type MessageSchema = (typeof messages)['en-US']; const attrs = useAttrs(); const slots = useSlots(); const hasSlot = useHasSlot(slots); const props = withDefaults(defineProps<BentoDropdownProps>(), { ariaLabel: null, condensed: false, emptyState: undefined, disabled: false, dynamicFiltering: false, error: false, errorMessage: null, description: null, hasMoreItems: false, isOptionDisabled: undefined, items: () => [], label: null, loading: false, componentLoading: false, lazyLoadType: BentoListbox.props.lazyLoadType.default, multiple: false, optional: false, placeholder: null, readonly: false, required: false, search: undefined, showSlotContentInMultiple: false, size: null, staticCategories: false, tooltipText: null, value: null, modelValue: null, virtualScroll: false, enableValueLabelPair: false, }); const emit = defineEmits<{ /** * Emitted when an option is clicked. Updates the selected option from the "value" property linked to the v-model. * @deprecated since version 2.0. Use `v-model` or `update:model-value instead. */ (e: 'input', selectedValue: BentoListboxSelectedValue | any); // eslint-disable-line @typescript-eslint/no-explicit-any /** * Emitted when an option is clicked. Updates the selected option from the "modelValue" property linked to the v-model. */ (e: 'update:model-value', selectedValue: BentoListboxSelectedValue | any); // eslint-disable-line @typescript-eslint/no-explicit-any /** * Emitted when: * - `lazyLoad` is `automatic` and the scroll meets the end of the list * - `lazyload` is `button` and the button is clicked */ (e: 'show-more'); /** * Emitted when the dropdown is opened. */ (e: 'open'); }>(); const { t } = useI18n<{ message: MessageSchema }>({ messages }); const { emitValue } = useFormFieldEmits<BentoListboxSelectedValue>(emit); const inputContainerRef = ref(null); const inputContainerRefWrapper = ref(null); const optionsContainerRef: Ref<InstanceType<typeof BentoDropdownOptionsContainer>> = ref(null); const isDropdownOpen = ref(false); const dropdownOptionsContainerId = generateUid('bento-dropdown-options-container'); const labelId = generateUid('dropdown-label'); const descriptionId = generateUid('dropdown-description'); const errorId = generateUid('dropdown-error'); const { isReadOnly } = useFormLayoutFieldLoading(toRef(props, 'readonly')); const dropdownValue = computed(() => props.modelValue ?? props.value); const { cachedSelectedListboxOptions, cachedListboxSelectedValues, setCachedValues } = useCachedSelectedValues( toRef(props, 'items'), dropdownValue, toRef(props, 'multiple'), toRef(props, 'enableValueLabelPair') ); const ariaLabelAttribute = ref(attrs['aria-label']); const ariaDescribedByAttribute = ref(attrs['aria-describedby']); const { ariaLabel, label } = toRefs(props); // vue-i18n issue with t() used in script - direct usage in template works // requires either createI18n({ legacy: false }) or to be wrapped in computed() const computedAriaLabelFallbackMessage = computed(() => t('ariaLabelFallback')); const computedAriaLabel = useAriaLabel({ // TODO: take ariaLabel ref away when aria-label prop is removed ariaLabel: ariaLabel.value || (ariaLabelAttribute as Ref<string>), label, defaultFallback: computedAriaLabelFallbackMessage, }); // Inject input field component key to change the dropdown size const isInputComponent = props.size === BentoDropdownSize.SMALL || inject(INPUT_FIELD_COMPONENT_INJECTION_KEY, false); const computedSize = computed(() => (isInputComponent ? BentoDropdownSize.SMALL : BentoDropdownSize.DEFAULT)); const conditionalClasses = computed(() => ({ 'b-dropdown--condensed': props.condensed, })); // Description styles const descriptionConditionalClasses = computed(() => ({ 'b-dropdown__description--error': !!props.errorMessage, })); // Filtering const searchTerm = ref(''); const items = toRef(props, 'items'); const staticCategories = toRef(props, 'staticCategories'); const filteredItems = useSearchBarFilter(searchTerm, items, props?.search?.searchEvent, staticCategories); const isSearching = computed(() => props.dynamicFiltering && !!searchTerm.value); const displayValue = useDisplayValue({ items, placeholder: toRef(props, 'placeholder'), isMultiSelect: toRef(props, 'multiple'), selectedValues: cachedSelectedListboxOptions, }); const selectedValueItem = computed(() => { if (props?.multiple && Array.isArray(dropdownValue.value)) { return props.items.filter( ({ value }) => props?.value && (props?.value as BentoListboxMultiSelectValue).includes(value) ); } if (!props.multiple && props.staticCategories) { // Find the category that holds the selected value const category = props.items.find(categoryItem => categoryItem.items.some(item => item.value === dropdownValue.value) ); // Find the specific item within the category const selectedCategoryItem = category ? category.items.find(item => item.value === dropdownValue.value) : null; return selectedCategoryItem; } return props.items.find(({ value }) => value === dropdownValue.value); }); const selectedValueIndex = computed(() => { const index = filteredItems.value.findIndex(({ value }) => { return props.enableValueLabelPair ? (dropdownValue.value as BentoListboxSelectedValueLabelPair).value === value : dropdownValue.value === value; }); return index > -1 ? index : 0; }); const { isMultiLevelSelect, flattenedItemsList } = useMultiLevelItems(items); const isTreePattern = computed(() => isMultiLevelSelect.value && !props.staticCategories); const computedOptionRole = computed<BentoListboxItemRole>(() => (isTreePattern.value ? 'treeitem' : 'option')); const additionalItemsSelected = computed(() => { if (props.multiple) { const selectedItemsCount = Array.isArray(dropdownValue.value) ? dropdownValue.value.length : 0; const total = isMultiLevelSelect.value ? flattenedItemsList.value.length : items.value.length; if (selectedItemsCount === 0) { return null; } // Display count always - show "all" only when is not external filtering (hasMoreItems = true) if (!props.hasMoreItems && total > 0 && total === selectedItemsCount) { return t('all'); } return selectedItemsCount; } return null; }); // ARIA const ariaDescribedBy = computed( () => [ props.description ? descriptionId : null, ariaDescribedByAttribute.value, props.errorMessage ? errorId : null, ] .filter(Boolean) .join(' ') || null ); const listboxRef = computed(() => optionsContainerRef.value?.$el as HTMLDivElement); const { visibleDomOptions, updateVisibleDomOptions } = useVisibleDomOptions( listboxRef, filteredItems, computedOptionRole ); const openDropdown = () => { if (isReadOnly.value) { return; } isDropdownOpen.value = true; nextTick(() => { updateVisibleDomOptions(); }); }; const closeDropdown = () => { isDropdownOpen.value = false; }; const toggleDropdown = () => { if (isReadOnly.value) { return; } isDropdownOpen.value = !isDropdownOpen.value; nextTick(() => { updateVisibleDomOptions(); }); // Clear search if (isSearching.value) { searchTerm.value = ''; } }; const clickOutsideDropdown = () => { if (isSearching.value) { searchTerm.value = ''; } if (isDropdownOpen.value) { isDropdownOpen.value = false; // Reset the internal dropdown options state optionsContainerRef.value.onOutsideDropdownClick(); } }; const onOptionSelected = (selectedValue: BentoListboxOptions) => { // Clear and close the dropdown on single selection if (!props.multiple) { searchTerm.value = ''; isDropdownOpen.value = false; focus(); } setCachedValues(selectedValue); /** * Emit the value/label pair objects if enableValueLabelPair is enabled, * otherwise just emit the list of selected values (number or string) */ emitValue(cachedListboxSelectedValues.value); }; const emitShowMoreEvent = () => { emit(BentoDropdownEvent.SHOW_MORE); }; const textboxKeyboardNavigationListeners = useTextboxKeyboardNavigation( { inputContainerRef, optionsContainerRef, selectedValueIndex, isDropdownOpen, isDynamicFltering: toRef(props, 'dynamicFiltering'), items: visibleDomOptions, isMultiple: props.multiple, }, toggleDropdown, openDropdown, closeDropdown ); const listboxKeyboardNavigationListeners = useListboxKeyboardNavigation( { inputContainerRef, isDropdownOpen, }, closeDropdown ); const listboxListeners = { [BentoListboxEvent.CLOSE_DROPDOWN]: closeDropdown, [BentoListboxEvent.SELECT]: onOptionSelected, [BentoListboxEvent.SHOW_MORE]: emitShowMoreEvent, ...listboxKeyboardNavigationListeners, }; if (props.error) { deprecate( 'BentoDropdown "error" property', `Use the BentoDropdown "errorMessage" property instead to indicate that there is a modifier error. Having the field as "undefined" will hide/remove the error. <bento-dropdown errorMessage="Error message text" />`, '2.0.0' ); } if (props.size) { deprecate('BentoDropdown "size" property', `Do not use. Only 'default' size should be used.`, '2.0.0'); } if (props.value) { deprecate( 'BentoDropdown "value" property', `The use of "value" prop in "BentoDropdown" is no longer supported. Use the "v-model" or "model-value" property instead.`, '2.0.0' ); } const focus = () => { inputContainerRef.value.focus(); }; watch( () => isDropdownOpen.value, (isOpen: boolean) => { if (isOpen) { emit('open'); } } ); defineExpose({ focus, }); </script> <script lang="ts"> /** * A Dropdown shows a selected option's. * Use a dropdown when you want users to select options * from a list of pre-defined options. * * @example * import { BentoDropdown } from '@adyen/bento-vue2'; * import type { BentoDropdownOptions } from '@adyen/bento-vue2' * * export default { * components: { BentoDropdown }, * template: ` * <bento-dropdown * v-model="selectedValue" * :disabled="false" * :multiple="true" * :isOptionsDisabled="option => option.value === 2" * :items="options" * /> * `, * setup() { * const selectedValue = ref(2) // Default value * const options: BentoDropdownOptions = [ * { label: 'Option 1', value: 1 }, * { label: 'Option 2', value: 2 }, * ]; * return { options, selectedValue }; * } * } */ export default defineComponent({ i18n: { messages }, name: 'b-dropdown', components: { DropdownInputDefault, DropdownInputSmall, }, model: { prop: 'modelValue' }, }); </script> <style lang="scss" scoped src="./dropdown.scss" />
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { Canvas, Meta } from '@storybook/blocks';
|
|
2
|
+
import * as EmptyStateStories from './empty-state.stories';
|
|
3
|
+
|
|
4
|
+
<Meta of={EmptyStateStories} />
|
|
5
|
+
|
|
6
|
+
# Empty state
|
|
7
|
+
|
|
8
|
+
Empty states are moments in the user experience when there is nothing to display.
|
|
9
|
+
|
|
10
|
+
This component can be used to provide:
|
|
11
|
+
|
|
12
|
+
- Information about system status
|
|
13
|
+
- Contextual learning cues
|
|
14
|
+
- Direct pathways for key tasks
|
|
15
|
+
|
|
16
|
+
<Canvas of={EmptyStateStories.Default} />
|
|
17
|
+
|
|
18
|
+
## Use Cases
|
|
19
|
+
|
|
20
|
+
There are seven empty state types commonly used in our interface:
|
|
21
|
+
|
|
22
|
+
- **First touch**: When the user is onboarding or starting to use a product area for the first time.
|
|
23
|
+
|
|
24
|
+
- **No results found**: When the user’s search request does not deliver any results, either due to search parameters
|
|
25
|
+
or because the information doesn’t exist.
|
|
26
|
+
|
|
27
|
+
- **Page not available (access restricted)**: When the user’s role does not allow them access to an area in the
|
|
28
|
+
interface.
|
|
29
|
+
|
|
30
|
+
- **Wrong environment**: When the user can access multiple environments and clicks into an area that is not available
|
|
31
|
+
on one of those environments.
|
|
32
|
+
|
|
33
|
+
- **All done**: When the user has read, removed, or completed all items and there is nothing more to do.
|
|
34
|
+
|
|
35
|
+
- **Planned maintenance**: A static page displayed when the website or system is temporarily unavailable.
|
|
36
|
+
|
|
37
|
+
## Variations
|
|
38
|
+
|
|
39
|
+
### Full-page
|
|
40
|
+
|
|
41
|
+
To be used in scenarios where the empty state is the primary focus of the page.
|
|
42
|
+
|
|
43
|
+
_Examples: First touch, maintenance and page access._
|
|
44
|
+
|
|
45
|
+
### Embedded
|
|
46
|
+
|
|
47
|
+
To be used only when the page contains one large component (multiple components will use the basic variant).
|
|
48
|
+
|
|
49
|
+
_Examples: Within the data grid when there are no filter results._
|
|
50
|
+
|
|
51
|
+
### Basic
|
|
52
|
+
|
|
53
|
+
To be used when multiple empty states can occur in a single page because the information is collected from different
|
|
54
|
+
backend calls.
|
|
55
|
+
|
|
56
|
+
_Example: dashboards, temporarily unavailable or broken._
|
|
57
|
+
|
|
58
|
+
### Condensed
|
|
59
|
+
|
|
60
|
+
To be used in components with limited space.
|
|
61
|
+
|
|
62
|
+
_Example: When there are no results to show when searching within filters._
|
|
63
|
+
|
|
64
|
+
## Modifiers
|
|
65
|
+
|
|
66
|
+
### Image
|
|
67
|
+
|
|
68
|
+
The illustration shown by the empty state `full-page` / `embedded` variants. You can refer to
|
|
69
|
+
[this list of illustrations](https://www.figma.com/file/qSUFsdW9fjMev5nHHBfToj/Bento---Illustrations---Empty-states),
|
|
70
|
+
which is mapped to following values:
|
|
71
|
+
|
|
72
|
+
- `1-generic-use`
|
|
73
|
+
- `2-generic-use`
|
|
74
|
+
- `3-generic-use`
|
|
75
|
+
- `4-generic-use`
|
|
76
|
+
- `adding-payment-methods`
|
|
77
|
+
- `adyen-giving`
|
|
78
|
+
- `internal-error`
|
|
79
|
+
- `no-results-found`
|
|
80
|
+
- `notifications-cleared`
|
|
81
|
+
- `page-not-found`
|
|
82
|
+
- `planned-maintenance`
|
|
83
|
+
- `referrals`
|
|
84
|
+
- `upload-files`
|
|
85
|
+
- `wrong-environment`
|
|
86
|
+
|
|
87
|
+
## Accessibility
|
|
88
|
+
|
|
89
|
+
Illustrations are considered decorative, they should be skipped by screen readers. Users should be able to:
|
|
90
|
+
|
|
91
|
+
- hear the component's label and description, and the purpose is clear;
|
|
92
|
+
- identify the component's role as an empty state.
|
|
93
|
+
|
|
94
|
+
### Keyboard interaction
|
|
95
|
+
|
|
96
|
+
A list of keyboard interactions is provided in the table below:
|
|
97
|
+
|
|
98
|
+
| Keyboard interaction | Output |
|
|
99
|
+
| -------------------- | -------------------------- |
|
|
100
|
+
| `Tab` | Moves focus to the action. |
|
|
101
|
+
| `Space`/`Enter` | Activates the action. |
|
|
102
|
+
|
|
103
|
+
### Roles, states and properties
|
|
104
|
+
|
|
105
|
+
| ARIA attribute | is automatic? | fallback used | manual override |
|
|
106
|
+
| ---------------- | --------------------------------------------------------- | ------------- | --------------------------------------------------------------- |
|
|
107
|
+
| `alt` | Yes, set to `""` since the image is only decorative. | - | - |
|
|
108
|
+
| `role="heading"` | Yes, through the semantic heading elements. | - | The user can pass a different element through `headingEl` prop. |
|
|
109
|
+
| `role="button"` | Yes, through the semantic `button` element in the action. | - | - |
|
|
110
|
+
|
|
111
|
+
## Resources
|
|
112
|
+
|
|
113
|
+
- [Figma link](https://www.figma.com/file/uLabwF3243jdMDsNSP7U9I/Bento---Components?type=design&node-id=16462-6288&mode=design&t=q79EnMp4k59VirFh-0)
|
|
114
|
+
- [Illustrations](https://www.figma.com/file/qSUFsdW9fjMev5nHHBfToj/Bento---Illustrations---Empty-states)
|
|
115
|
+
- [WAI Images (decorative)](https://www.w3.org/WAI/tutorials/images/decorative/)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import { action } from '@storybook/addon-actions'; import type { Meta, StoryObj } from '@storybook/vue'; import { isVue2 } from 'vue-demi'; import { storybookDocsParameter } from '@/utils/ts/storybook'; import BentoEmptyState from './empty-state.vue'; import { BentoEmptyStateImage, BentoEmptyStateVariant } from './empty-state.types'; const meta: Meta = { title: 'Empty state', component: BentoEmptyState, argTypes: { // Slots default: { name: 'default', type: { name: 'string', required: false }, description: 'Default slot content', control: 'text', table: { type: { summary: 'VNode[]' } }, }, // Enum props variant: { options: Object.values(BentoEmptyStateVariant), mapping: BentoEmptyStateVariant, }, image: { options: Object.values(BentoEmptyStateImage), mapping: BentoEmptyStateImage, }, }, }; export default meta; type Story = StoryObj<typeof BentoEmptyState>; const DEFAULT_BUTTON_PROPS = { title: 'No results were found', default: 'Try a different term or reset search filters', // content slot variant: 'full-page', image: 'no-results-found', action: { title: 'Reset filters', event: () => action('action clicked')(), }, }; const defaultCode = ` <bento-empty-state title="No results were found" image="no-results-found" variant="full-page" :action="{ title: 'Reset filters', event: () => {} }" > Try a different term or reset search filters </bento-empty-state> `; export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoEmptyState }, props: Object.keys(argTypes), template: ` <bento-empty-state v-bind="args"> {{ args.default }} </bento-empty-state> `, setup(props) { return { args: isVue2 ? props : _args }; }, }), args: DEFAULT_BUTTON_PROPS, parameters: storybookDocsParameter(defaultCode), };
|