@adyen/bento-mcp 0.10.0 → 0.12.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 +36 -0
- package/README.md +24 -2
- package/SKILL.md +37 -7
- package/dist/assets/components/accordion/accordion.stories.ts +1 -1
- package/dist/assets/components/accordion/components/accordion-item.stories.ts +1 -1
- package/dist/assets/components/accordion/components/accordion-item.vue +1 -1
- package/dist/assets/components/action-bar/action-bar.docs.mdx +10 -3
- package/dist/assets/components/action-bar/action-bar.internal.types.ts +1 -0
- package/dist/assets/components/action-bar/action-bar.stories.ts +1 -1
- package/dist/assets/components/action-bar/action-bar.types.ts +1 -1
- package/dist/assets/components/action-bar/action-bar.vue +1 -1
- package/dist/assets/components/ai-tag/ai-tag.stories.ts +1 -1
- package/dist/assets/components/ai-tag/ai-tag.vue +1 -1
- package/dist/assets/components/alert/alert.stories.ts +1 -1
- package/dist/assets/components/alert/alert.types.ts +1 -1
- package/dist/assets/components/anchor-scroller/anchor-scroller.stories.ts +1 -1
- package/dist/assets/components/anchor-scroller/anchor-scroller.vue +1 -1
- package/dist/assets/components/avatar/avatar.internal.types.ts +1 -0
- package/dist/assets/components/avatar/avatar.stories.ts +1 -1
- package/dist/assets/components/avatar/avatar.types.ts +1 -1
- package/dist/assets/components/button/button.docs.mdx +4 -3
- package/dist/assets/components/button/button.stories.ts +1 -1
- package/dist/assets/components/button/button.types.ts +1 -1
- package/dist/assets/components/button/components/ai-button/ai-button.stories.ts +1 -1
- package/dist/assets/components/button/components/ai-button/ai-button.vue +1 -1
- package/dist/assets/components/button/components/button-actions/button-actions.docs.mdx +3 -0
- package/dist/assets/components/button/components/button-actions/button-actions.internal.types.ts +1 -0
- package/dist/assets/components/button/components/button-actions/button-actions.stories.ts +1 -1
- package/dist/assets/components/button/components/button-actions/button-actions.types.ts +1 -1
- package/dist/assets/components/button/components/button-actions/button-actions.vue +1 -1
- package/dist/assets/components/button/components/loading-button/loading-button.docs.mdx +26 -23
- package/dist/assets/components/button/components/loading-button/loading-button.stories.ts +1 -1
- package/dist/assets/components/button/components/loading-button/loading-button.vue +1 -1
- package/dist/assets/components/button/components/toggle-button/toggle-button.stories.ts +1 -1
- package/dist/assets/components/card/card.docs.mdx +18 -1
- package/dist/assets/components/card/card.stories.ts +1 -1
- package/dist/assets/components/card/card.types.ts +1 -1
- package/dist/assets/components/card/card.vue +1 -1
- package/dist/assets/components/checkbox/checkbox.stories.ts +1 -1
- package/dist/assets/components/checkbox/checkbox.types.ts +1 -1
- package/dist/assets/components/checkbox/checkbox.vue +1 -1
- package/dist/assets/components/checkbox/components/checkbox-group/checkbox-group.docs.mdx +9 -14
- package/dist/assets/components/checkbox/components/checkbox-group/checkbox-group.stories.ts +1 -1
- package/dist/assets/components/checkbox/components/checkbox-group/checkbox-group.types.ts +1 -1
- package/dist/assets/components/checkbox/components/checkbox-group/checkbox-group.vue +1 -1
- package/dist/assets/components/chip/chip.stories.ts +1 -1
- package/dist/assets/components/chip/chip.vue +1 -1
- package/dist/assets/components/click-outside/click-outside.stories.ts +1 -1
- package/dist/assets/components/code-snippet/code-snippet.types.ts +1 -1
- package/dist/assets/components/code-snippet/code-snippet.vue +1 -1
- package/dist/assets/components/country/country.stories.ts +1 -1
- package/dist/assets/components/country/country.types.ts +1 -1
- package/dist/assets/components/country/country.vue +1 -1
- package/dist/assets/components/currency/currency.stories.ts +1 -1
- package/dist/assets/components/currency/currency.vue +1 -1
- package/dist/assets/components/dashed-underline/dashed-underline.stories.ts +1 -1
- package/dist/assets/components/data-grid/components/data-grid-cell-actions/data-grid-cell-actions.types.ts +1 -1
- package/dist/assets/components/data-grid/components/data-grid-cell-actions/data-grid-cell-actions.vue +1 -1
- package/dist/assets/components/data-grid/components/data-grid-config-settings/data-grid-config-settings.vue +1 -1
- package/dist/assets/components/data-grid/components/data-grid-row/data-grid-row.vue +1 -1
- package/dist/assets/components/data-grid/components/data-grid-static-icon/data-grid-static-icon.stories.ts +1 -1
- package/dist/assets/components/data-grid/components/data-grid-summary-cell/data-grid-summary-cell.stories.ts +1 -1
- package/dist/assets/components/data-grid/components/data-grid-summary-cell/data-grid-summary-cell.vue +1 -1
- package/dist/assets/components/data-grid/components/data-grid-tag-with-text-cell/data-grid-tag-with-text-cell.stories.ts +1 -1
- package/dist/assets/components/data-grid/components/data-grid-trend-cell/data-grid-trend-cell.stories.ts +1 -1
- package/dist/assets/components/data-grid/components/data-grid-trend-cell/data-grid-trend-cell.vue +1 -1
- package/dist/assets/components/data-grid/data-grid.docs.mdx +3 -60
- package/dist/assets/components/data-grid/data-grid.stories.ts +1 -1
- package/dist/assets/components/data-grid/data-grid.vue +1 -1
- package/dist/assets/components/date/date.stories.ts +1 -1
- package/dist/assets/components/date-picker/date-picker.stories.ts +1 -1
- package/dist/assets/components/date-picker/date-picker.types.ts +1 -1
- package/dist/assets/components/date-picker/date-picker.vue +1 -1
- package/dist/assets/components/date-range-picker/components/date-range-picker-calendar/date-range-picker-calendar.vue +1 -1
- package/dist/assets/components/date-range-picker/components/date-time-input/date-time-input.types.ts +1 -1
- package/dist/assets/components/date-range-picker/components/date-time-input/date-time-input.vue +1 -1
- package/dist/assets/components/date-range-picker/date-range-picker.docs.mdx +31 -6
- package/dist/assets/components/date-range-picker/date-range-picker.stories.ts +1 -1
- package/dist/assets/components/date-range-picker/date-range-picker.types.ts +1 -1
- package/dist/assets/components/date-range-picker/date-range-picker.vue +1 -1
- package/dist/assets/components/date-time-picker/date-time-picker.stories.ts +1 -1
- package/dist/assets/components/date-time-picker/date-time-picker.types.ts +1 -1
- package/dist/assets/components/divider/divider.internal.types.ts +1 -0
- package/dist/assets/components/divider/divider.stories.ts +1 -1
- package/dist/assets/components/divider/divider.types.ts +1 -1
- package/dist/assets/components/divider/divider.vue +1 -1
- package/dist/assets/components/draggable/components/draggable-handle/draggable-handle.stories.ts +1 -1
- package/dist/assets/components/draggable/draggable.stories.ts +1 -1
- package/dist/assets/components/draggable/draggable.vue +1 -1
- package/dist/assets/components/drawer/drawer.stories.ts +1 -1
- package/dist/assets/components/dropdown/components/dropdown-base-textbox/dropdown-base-textbox.internal.types.ts +1 -0
- package/dist/assets/components/dropdown/components/dropdown-base-textbox/dropdown-base-textbox.types.ts +1 -1
- package/dist/assets/components/dropdown/components/dropdown-base-textbox/dropdown-base-textbox.vue +1 -1
- package/dist/assets/components/dropdown/components/dropdown-default-textbox/dropdown-default-textbox.vue +1 -1
- package/dist/assets/components/dropdown/components/dropdown-options-container/dropdown-options-container.vue +1 -1
- package/dist/assets/components/dropdown/components/dropdown-small-textbox/dropdown-small-textbox.vue +1 -1
- package/dist/assets/components/dropdown/components/variants/dropdown-avatar/dropdown-avatar.stories.ts +1 -1
- package/dist/assets/components/dropdown/components/variants/dropdown-avatar/dropdown-avatar.types.ts +1 -1
- package/dist/assets/components/dropdown/components/variants/dropdown-avatar/dropdown-avatar.vue +1 -1
- package/dist/assets/components/dropdown/components/variants/dropdown-country/dropdown-country.stories.ts +1 -1
- package/dist/assets/components/dropdown/components/variants/dropdown-country/dropdown-country.types.ts +1 -1
- package/dist/assets/components/dropdown/components/variants/dropdown-country/dropdown-country.vue +1 -1
- package/dist/assets/components/dropdown/components/variants/dropdown-currency/dropdown-currency.stories.ts +1 -1
- package/dist/assets/components/dropdown/components/variants/dropdown-currency/dropdown-currency.types.ts +1 -1
- package/dist/assets/components/dropdown/components/variants/dropdown-currency/dropdown-currency.vue +1 -1
- package/dist/assets/components/dropdown/components/variants/dropdown-payment-method/dropdown-payment-method.stories.ts +1 -1
- package/dist/assets/components/dropdown/components/variants/dropdown-payment-method/dropdown-payment-method.types.ts +1 -1
- package/dist/assets/components/dropdown/components/variants/dropdown-payment-method/dropdown-payment-method.vue +1 -1
- package/dist/assets/components/dropdown/components/variants/dropdown-tag/dropdown-tag.stories.ts +1 -1
- package/dist/assets/components/dropdown/components/variants/dropdown-tag/dropdown-tag.types.ts +1 -1
- package/dist/assets/components/dropdown/components/variants/dropdown-tag/dropdown-tag.vue +1 -1
- package/dist/assets/components/dropdown/dropdown.docs.mdx +4 -40
- package/dist/assets/components/dropdown/dropdown.internal.types.ts +1 -0
- package/dist/assets/components/dropdown/dropdown.stories.ts +1 -1
- package/dist/assets/components/dropdown/dropdown.types.ts +1 -1
- package/dist/assets/components/dropdown/dropdown.vue +1 -1
- package/dist/assets/components/empty-state/empty-state.stories.ts +1 -1
- package/dist/assets/components/empty-state/empty-state.types.ts +1 -1
- package/dist/assets/components/file-uploader/components/file-uploader-file-card/file-uploader-file-card.stories.ts +1 -1
- package/dist/assets/components/file-uploader/components/file-uploader-file-card/file-uploader-file-card.types.ts +1 -1
- package/dist/assets/components/file-uploader/components/file-uploader-file-card/file-uploader-file-card.vue +1 -1
- package/dist/assets/components/file-uploader/components/file-uploader-restrictions/file-uploader-restrictions.types.ts +1 -1
- package/dist/assets/components/file-uploader/components/file-uploader-restrictions/file-uploader-restrictions.vue +1 -1
- package/dist/assets/components/file-uploader/file-uploader.internal.types.ts +1 -0
- package/dist/assets/components/file-uploader/file-uploader.stories.ts +1 -1
- package/dist/assets/components/file-uploader/file-uploader.types.ts +1 -1
- package/dist/assets/components/file-uploader/file-uploader.vue +1 -1
- package/dist/assets/components/filter-bar/components/all-filters-modal/all-filters-modal.stories.ts +1 -1
- package/dist/assets/components/filter-bar/components/all-filters-modal/all-filters-modal.types.ts +1 -1
- package/dist/assets/components/filter-bar/components/all-filters-modal/all-filters-modal.vue +1 -1
- package/dist/assets/components/filter-bar/components/all-filters-modal/components/all-filters-modal-filter-selection/all-filters-modal-filter-selection.types.ts +1 -0
- package/dist/assets/components/filter-bar/components/all-filters-modal/components/all-filters-modal-filter-selection/all-filters-modal-filter-selection.vue +1 -0
- package/dist/assets/components/filter-bar/components/all-filters-modal/components/all-filters-modal-selected-filter/all-filters-modal-selected-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/base-filter/base-filter.internal.types.ts +1 -0
- package/dist/assets/components/filter-bar/components/base-filter/base-filter.types.ts +1 -1
- package/dist/assets/components/filter-bar/components/base-filter/base-filter.vue +1 -1
- package/dist/assets/components/filter-bar/components/boolean-filter/boolean-filter.vue +1 -1
- package/dist/assets/components/filter-bar/components/date-filter/date-filter.vue +1 -1
- package/dist/assets/components/filter-bar/components/date-range-filter/date-range-filter.vue +1 -1
- package/dist/assets/components/filter-bar/components/input-filter/input-filter.types.ts +1 -1
- package/dist/assets/components/filter-bar/components/input-filter/input-filter.vue +1 -1
- package/dist/assets/components/filter-bar/components/input-with-dropdown-filter/input-with-dropdown-filter.stories.ts +1 -1
- package/dist/assets/components/filter-bar/components/input-with-dropdown-filter/input-with-dropdown-filter.vue +1 -1
- package/dist/assets/components/filter-bar/components/select-filter/components/select-filter-button/select-filter-button.vue +1 -1
- package/dist/assets/components/filter-bar/components/select-filter/select-filter.vue +1 -1
- package/dist/assets/components/filter-bar/components/select-filter/variants/select-avatar-filter/select-avatar-filter.stories.ts +1 -1
- package/dist/assets/components/filter-bar/components/select-filter/variants/select-country-filter/select-country-filter.stories.ts +1 -1
- package/dist/assets/components/filter-bar/components/select-filter/variants/select-country-filter/select-country-filter.vue +1 -1
- package/dist/assets/components/filter-bar/components/toggle-filter/toggle-filter.vue +1 -1
- package/dist/assets/components/filter-bar/filter-bar.docs.mdx +4 -62
- package/dist/assets/components/filter-bar/filter-bar.stories.ts +1 -1
- package/dist/assets/components/filter-bar/filter-bar.types.ts +1 -1
- package/dist/assets/components/filter-bar/filter-bar.vue +1 -1
- package/dist/assets/components/form-layout/form-layout.docs.mdx +0 -23
- package/dist/assets/components/form-layout/form-layout.stories.ts +1 -1
- package/dist/assets/components/header/header.stories.ts +1 -1
- package/dist/assets/components/header/header.types.ts +1 -1
- package/dist/assets/components/header/header.vue +1 -1
- package/dist/assets/components/header-with-views/components/header-with-views-all-views/header-with-views-all-views.vue +1 -1
- package/dist/assets/components/header-with-views/components/header-with-views-item-edit-form/header-with-views-edit-form.types.ts +1 -1
- package/dist/assets/components/header-with-views/components/header-with-views-item-edit-form/header-with-views-edit-form.vue +1 -1
- package/dist/assets/components/header-with-views/header-with-views.internal.types.ts +1 -0
- package/dist/assets/components/header-with-views/header-with-views.stories.ts +1 -1
- package/dist/assets/components/header-with-views/header-with-views.types.ts +1 -1
- package/dist/assets/components/header-with-views/header-with-views.vue +1 -1
- package/dist/assets/components/image/image.stories.ts +1 -1
- package/dist/assets/components/info-icon/info-icon.stories.ts +1 -1
- package/dist/assets/components/input-field/input-field.docs.mdx +1 -13
- package/dist/assets/components/input-field/input-field.stories.ts +1 -1
- package/dist/assets/components/input-field/input-field.types.ts +1 -1
- package/dist/assets/components/input-field/input-field.vue +1 -1
- package/dist/assets/components/input-field-password/input-field-password.internal.types.ts +1 -0
- package/dist/assets/components/input-field-password/input-field-password.stories.ts +1 -1
- package/dist/assets/components/input-field-password/input-field-password.types.ts +1 -1
- package/dist/assets/components/input-field-password/input-field-password.vue +1 -1
- package/dist/assets/components/input-field-phone-number/input-field-phone-number.stories.ts +1 -1
- package/dist/assets/components/input-field-phone-number/input-field-phone-number.types.ts +1 -1
- package/dist/assets/components/input-field-phone-number/input-field-phone-number.vue +1 -1
- package/dist/assets/components/input-time/input-time.docs.mdx +17 -0
- package/dist/assets/components/input-time/input-time.stories.ts +1 -1
- package/dist/assets/components/input-time/input-time.types.ts +1 -1
- package/dist/assets/components/input-time/input-time.vue +1 -1
- package/dist/assets/components/inspector/inspector.stories.ts +1 -1
- package/dist/assets/components/inspector/inspector.vue +1 -1
- package/dist/assets/components/layout/components/layout-row/layout-row.stories.ts +1 -1
- package/dist/assets/components/layout/components/layout-row/layout-row.vue +1 -1
- package/dist/assets/components/layout/layout.docs.mdx +10 -0
- package/dist/assets/components/layout/layout.internal.types.ts +1 -0
- package/dist/assets/components/layout/layout.stories.ts +1 -1
- package/dist/assets/components/layout/layout.types.ts +1 -1
- package/dist/assets/components/link/link.stories.ts +1 -1
- package/dist/assets/components/link/link.types.ts +1 -1
- package/dist/assets/components/link/link.vue +1 -1
- package/dist/assets/components/list/list.stories.ts +1 -1
- package/dist/assets/components/loading-indicator/loading-indicator.stories.ts +1 -1
- package/dist/assets/components/menu/components/menu-item/menu-item.types.ts +1 -1
- package/dist/assets/components/menu/components/menu-item/menu-item.vue +1 -1
- package/dist/assets/components/menu/components/menu-item-list/menu-item-list.vue +1 -1
- package/dist/assets/components/menu/components/sub-menu-item-list/sub-menu-item-list.vue +1 -1
- package/dist/assets/components/menu/menu.docs.mdx +17 -7
- package/dist/assets/components/menu/menu.stories.ts +1 -1
- package/dist/assets/components/menu/menu.types.ts +1 -1
- package/dist/assets/components/menu/menu.vue +1 -1
- package/dist/assets/components/modal/components/base-modal/base-modal.vue +1 -1
- package/dist/assets/components/modal/components/modal-page/modal-page.vue +1 -1
- package/dist/assets/components/modal/modal.internal.types.ts +1 -0
- package/dist/assets/components/modal/modal.stories.ts +1 -1
- package/dist/assets/components/modal/modal.types.ts +1 -1
- package/dist/assets/components/modal/modal.vue +1 -1
- package/dist/assets/components/modal-fullscreen/components/modal-fullscreen-page.vue +1 -1
- package/dist/assets/components/modal-fullscreen/modal-fullscreen.stories.ts +1 -1
- package/dist/assets/components/modal-fullscreen/modal-fullscreen.vue +1 -1
- package/dist/assets/components/navigation-menu/components/navigation-menu-group/navigation-menu-group.internal.types.ts +1 -0
- package/dist/assets/components/navigation-menu/components/navigation-menu-group/navigation-menu-group.types.ts +1 -1
- package/dist/assets/components/navigation-menu/components/navigation-menu-group/navigation-menu-group.vue +1 -1
- package/dist/assets/components/navigation-menu/components/navigation-menu-item/navigation-menu-item.vue +1 -1
- package/dist/assets/components/navigation-menu/navigation-menu.internal.types.ts +1 -0
- package/dist/assets/components/navigation-menu/navigation-menu.stories.ts +1 -1
- package/dist/assets/components/navigation-menu/navigation-menu.types.ts +1 -1
- package/dist/assets/components/navigation-menu/navigation-menu.vue +1 -1
- package/dist/assets/components/pagination/components/pagination-context/pagination-context.vue +1 -1
- package/dist/assets/components/pagination/components/pagination-results-per-page/pagination-results-per-page.vue +1 -1
- package/dist/assets/components/pagination/pagination.stories.ts +1 -1
- package/dist/assets/components/pagination/pagination.types.ts +1 -1
- package/dist/assets/components/pagination/pagination.vue +1 -1
- package/dist/assets/components/payment-method/payment-method.stories.ts +1 -1
- package/dist/assets/components/payment-method/payment-method.types.ts +1 -1
- package/dist/assets/components/popover/popover.docs.mdx +32 -9
- package/dist/assets/components/popover/popover.stories.ts +1 -1
- package/dist/assets/components/popover/popover.types.ts +1 -1
- package/dist/assets/components/popover/popover.vue +1 -1
- package/dist/assets/components/popper-container/popper-container.types.ts +1 -1
- package/dist/assets/components/popper-container/popper-container.vue +1 -1
- package/dist/assets/components/progress-bar/progress-bar.docs.mdx +102 -0
- package/dist/assets/components/progress-bar/progress-bar.stories.ts +1 -0
- package/dist/assets/components/progress-bar/progress-bar.types.ts +1 -0
- package/dist/assets/components/progress-bar/progress-bar.vue +1 -0
- package/dist/assets/components/promo-banner/promo-banner.stories.ts +1 -1
- package/dist/assets/components/promo-banner/promo-banner.types.ts +1 -1
- package/dist/assets/components/promo-banner/promo-banner.vue +1 -1
- package/dist/assets/components/radio-group/components/radio-button/radio-button.vue +1 -1
- package/dist/assets/components/radio-group/radio-group.docs.mdx +1 -6
- package/dist/assets/components/radio-group/radio-group.stories.ts +1 -1
- package/dist/assets/components/radio-group/radio-group.types.ts +1 -1
- package/dist/assets/components/radio-group/radio-group.vue +1 -1
- package/dist/assets/components/rich-text-editor/rich-text-editor.internal.types.ts +1 -0
- package/dist/assets/components/rich-text-editor/rich-text-editor.stories.ts +1 -1
- package/dist/assets/components/rich-text-editor/rich-text-editor.types.ts +1 -1
- package/dist/assets/components/rich-text-editor/rich-text-editor.vue +1 -1
- package/dist/assets/components/search-bar/search-bar.stories.ts +1 -1
- package/dist/assets/components/search-bar/search-bar.vue +1 -1
- package/dist/assets/components/secondary-nav/components/secondary-nav-item/secondary-nav-item.vue +1 -1
- package/dist/assets/components/secondary-nav/secondary-nav.stories.ts +1 -1
- package/dist/assets/components/secondary-nav/secondary-nav.vue +1 -1
- package/dist/assets/components/segmented-control/components/segmented-control-button/segmented-control-button.vue +1 -1
- package/dist/assets/components/segmented-control/segmented-control.stories.ts +1 -1
- package/dist/assets/components/segmented-control/segmented-control.types.ts +1 -1
- package/dist/assets/components/segmented-control/segmented-control.vue +1 -1
- package/dist/assets/components/selection-card/components/selection-card-group/selection-card-group.docs.mdx +12 -13
- package/dist/assets/components/selection-card/components/selection-card-group/selection-card-group.stories.ts +1 -1
- package/dist/assets/components/selection-card/components/selection-card-group/selection-card-group.vue +1 -1
- package/dist/assets/components/selection-card/selection-card.stories.ts +1 -1
- package/dist/assets/components/selection-card/selection-card.types.ts +1 -1
- package/dist/assets/components/selection-card/selection-card.vue +1 -1
- package/dist/assets/components/sidepanel/sidepanel.docs.mdx +1 -1
- package/dist/assets/components/sidepanel/sidepanel.stories.ts +1 -1
- package/dist/assets/components/sidepanel/sidepanel.vue +1 -1
- package/dist/assets/components/status/status.stories.ts +1 -1
- package/dist/assets/components/status/status.types.ts +1 -1
- package/dist/assets/components/stepper/components/step/step.vue +1 -1
- package/dist/assets/components/stepper/stepper.stories.ts +1 -1
- package/dist/assets/components/stepper/stepper.vue +1 -1
- package/dist/assets/components/structured-list/structured-list.stories.ts +1 -1
- package/dist/assets/components/structured-list/structured-list.vue +1 -1
- package/dist/assets/components/summary-grid/components/summary-grid-item/summary-grid-item.types.ts +1 -1
- package/dist/assets/components/summary-grid/summary-grid.stories.ts +1 -1
- package/dist/assets/components/summary-grid/summary-grid.types.ts +1 -1
- package/dist/assets/components/table-of-contents/components/table-of-contents-list/table-of-contents-list.vue +1 -1
- package/dist/assets/components/table-of-contents/table-of-contents.internal.types.ts +1 -0
- package/dist/assets/components/table-of-contents/table-of-contents.stories.ts +1 -1
- package/dist/assets/components/table-of-contents/table-of-contents.types.ts +1 -1
- package/dist/assets/components/table-of-contents/table-of-contents.vue +1 -1
- package/dist/assets/components/tabs/components/tab.internal.types.ts +1 -0
- package/dist/assets/components/tabs/components/tab.types.ts +1 -1
- package/dist/assets/components/tabs/components/tab.vue +1 -1
- package/dist/assets/components/tabs/tabs.stories.ts +1 -1
- package/dist/assets/components/tabs/tabs.vue +1 -1
- package/dist/assets/components/tag/tag.docs.mdx +1 -1
- package/dist/assets/components/tag/tag.stories.ts +1 -1
- package/dist/assets/components/tag/tag.types.ts +1 -1
- package/dist/assets/components/tag/tag.vue +1 -1
- package/dist/assets/components/textarea/textarea.docs.mdx +0 -7
- package/dist/assets/components/textarea/textarea.stories.ts +1 -1
- package/dist/assets/components/textarea/textarea.types.ts +1 -1
- package/dist/assets/components/textarea/textarea.vue +1 -1
- package/dist/assets/components/timeline/components/timeline-item/timeline-item.stories.ts +1 -1
- package/dist/assets/components/timeline/components/timeline-item/timeline-item.vue +1 -1
- package/dist/assets/components/timeline/timeline.docs.mdx +0 -10
- package/dist/assets/components/timeline/timeline.internal.types.ts +1 -0
- package/dist/assets/components/timeline/timeline.stories.ts +1 -1
- package/dist/assets/components/timeline/timeline.types.ts +1 -1
- package/dist/assets/components/timeline/timeline.vue +1 -1
- package/dist/assets/components/toast/components/toast-item/toast-item.vue +1 -1
- package/dist/assets/components/toggle/toggle.stories.ts +1 -1
- package/dist/assets/components/toggle/toggle.types.ts +1 -1
- package/dist/assets/components/toggle/toggle.vue +1 -1
- package/dist/assets/components/tutorial/tutorial.stories.ts +1 -1
- package/dist/assets/components/typography/typography.stories.ts +1 -1
- package/dist/assets/components/typography/typography.types.ts +1 -1
- package/dist/assets/components/typography/typography.vue +1 -1
- package/dist/assets/components.json +1 -0
- package/dist/assets/composables/use-bento-base-filter/index.ts +1 -0
- package/dist/assets/composables/use-bento-base-filter/use-bento-base-filter.ts +1 -0
- package/dist/assets/composables/use-bento-base-filter/utils/index.ts +1 -0
- package/dist/assets/composables/use-bento-base-filter/utils/utils.ts +1 -0
- package/dist/assets/composables/use-bento-currency/index.ts +1 -0
- package/dist/assets/composables/use-bento-currency/use-bento-currency.stories.ts +1 -1
- package/dist/assets/composables/use-bento-currency/use-bento-currency.ts +1 -0
- package/dist/assets/composables/use-bento-delayed-hover/index.ts +1 -0
- package/dist/assets/composables/use-bento-delayed-hover/use-bento-delayed-hover.stories.ts +1 -1
- package/dist/assets/composables/use-bento-delayed-hover/use-bento-delayed-hover.ts +1 -0
- package/dist/assets/composables/use-bento-theme/index.ts +1 -0
- package/dist/assets/composables/use-bento-theme/install.ts +1 -1
- package/dist/assets/composables/use-bento-theme/install.types.ts +1 -1
- package/dist/assets/composables/use-bento-theme/use-bento-theme.ts +1 -0
- package/dist/assets/composables/use-bento-toast-controller/index.ts +1 -0
- package/dist/assets/composables/use-bento-toast-controller/toast-stack.ts +1 -0
- package/dist/assets/composables/use-bento-toast-controller/use-bento-toast-controller.ts +1 -0
- package/dist/assets/composables/use-click-outside/index.ts +1 -0
- package/dist/assets/composables/use-click-outside/use-click-outside.stories.ts +1 -1
- package/dist/assets/composables/use-click-outside/use-click-outside.ts +1 -0
- package/dist/assets/composables/use-form-validate/index.ts +1 -0
- package/dist/assets/composables/use-form-validate/use-form.ts +1 -0
- package/dist/assets/composables/use-form-validate/use-form.types.ts +1 -1
- package/dist/assets/composables.json +9 -0
- package/dist/assets/deprecations.md +139 -243
- package/dist/assets/directives/highlight/highlight.stories.ts +1 -1
- package/dist/assets/directives/keyboard-navigation/keyboard-navigation.stories.ts +1 -1
- package/dist/assets/directives/tooltip/tooltip.internal.types.ts +1 -0
- package/dist/assets/directives/tooltip/tooltip.stories.ts +1 -1
- package/dist/assets/directives/tooltip/tooltip.types.ts +1 -1
- package/dist/assets/icons.json +1 -0
- package/dist/assets/index.ts +1 -1
- package/dist/assets/install.ts +1 -1
- package/dist/assets/tailwind/README.md +3 -3
- package/dist/assets/templates/dashboard-template/components/dashboard-section/dashboard-section.stories.ts +1 -1
- package/dist/assets/templates/dashboard-template/dashboard-template.stories.ts +1 -1
- package/dist/assets/templates/data-grid-template/data-grid-template.stories.ts +1 -1
- package/dist/assets/usage.json +81 -80
- package/dist/assets/variables.css +152 -140
- package/dist/main.js +228 -34
- package/package.json +2 -2
- package/dist/assets/components/internal/base-button/base-button.types.ts +0 -1
- package/dist/assets/components/internal/base-button/base-button.vue +0 -1
- package/dist/assets/components/internal/button-actions-with-menu/button-actions-with-menu.stories.ts +0 -1
- package/dist/assets/components/internal/button-actions-with-menu/button-actions-with-menu.types.ts +0 -1
- package/dist/assets/components/internal/button-actions-with-menu/button-actions-with-menu.vue +0 -1
- package/dist/assets/components/internal/calendar/calendar.stories.ts +0 -1
- package/dist/assets/components/internal/calendar/calendar.types.ts +0 -1
- package/dist/assets/components/internal/calendar/calendar.vue +0 -1
- package/dist/assets/components/internal/calendar/components/calendar-month/calendar-month.stories.ts +0 -1
- package/dist/assets/components/internal/calendar/components/calendar-month/calendar-month.types.ts +0 -1
- package/dist/assets/components/internal/calendar/components/calendar-month/calendar-month.vue +0 -1
- package/dist/assets/components/internal/calendar/components/calendar-month/components/calendar-month-day/__test__/calendar-month-day-stub.vue +0 -1
- package/dist/assets/components/internal/calendar/components/calendar-month/components/calendar-month-day/calendar-month-day.stories.ts +0 -1
- package/dist/assets/components/internal/calendar/components/calendar-month/components/calendar-month-day/calendar-month-day.types.ts +0 -1
- package/dist/assets/components/internal/calendar/components/calendar-month/components/calendar-month-day/calendar-month-day.vue +0 -1
- package/dist/assets/components/internal/calendar/components/calendar-month/composables/granular-min-max-date.types.ts +0 -1
- package/dist/assets/components/internal/calendar/components/calendar-year/calendar-year.types.ts +0 -1
- package/dist/assets/components/internal/calendar/components/calendar-year/calendar-year.vue +0 -1
- package/dist/assets/components/internal/calendar/components/calendar-year/components/calendar-year-month/__test__/calendar-year-month-stub.vue +0 -1
- package/dist/assets/components/internal/calendar/components/calendar-year/components/calendar-year-month/calendar-year-month.types.ts +0 -1
- package/dist/assets/components/internal/calendar/components/calendar-year/components/calendar-year-month/calendar-year-month.vue +0 -1
- package/dist/assets/components/internal/calendar/components/calendar-year-selector/calendar-year-selector.types.ts +0 -1
- package/dist/assets/components/internal/calendar/components/calendar-year-selector/calendar-year-selector.vue +0 -1
- package/dist/assets/components/internal/calendar/composables/use-granularity-adjustments.types.ts +0 -1
- package/dist/assets/components/internal/calendar/composables/use-range-pane.types.ts +0 -1
- package/dist/assets/components/internal/checkbox-input/checkbox-input.stories.ts +0 -1
- package/dist/assets/components/internal/checkbox-input/checkbox-input.types.ts +0 -1
- package/dist/assets/components/internal/checkbox-input/checkbox-input.vue +0 -1
- package/dist/assets/components/internal/controls-group/controls-group.vue +0 -1
- package/dist/assets/components/internal/copy/copy.docs.mdx +0 -43
- package/dist/assets/components/internal/copy/copy.stories.ts +0 -1
- package/dist/assets/components/internal/copy/copy.types.ts +0 -1
- package/dist/assets/components/internal/copy/copy.vue +0 -1
- package/dist/assets/components/internal/decorative-checkbox/decorative-checkbox.vue +0 -1
- package/dist/assets/components/internal/dialog-page/dialog-page.types.ts +0 -1
- package/dist/assets/components/internal/dialog-page/dialog-page.vue +0 -1
- package/dist/assets/components/internal/error-message/error-message.vue +0 -1
- package/dist/assets/components/internal/field-label/field-label.vue +0 -1
- package/dist/assets/components/internal/fixed-scroller/fixed-scroller.docs.mdx +0 -21
- package/dist/assets/components/internal/fixed-scroller/fixed-scroller.stories.ts +0 -1
- package/dist/assets/components/internal/fixed-scroller/fixed-scroller.vue +0 -1
- package/dist/assets/components/internal/footer-actions/footer-actions.vue +0 -1
- package/dist/assets/components/internal/grid-layout/grid-layout.types.ts +0 -1
- package/dist/assets/components/internal/grid-layout/grid-layout.vue +0 -1
- package/dist/assets/components/internal/header-meta/header-meta.types.ts +0 -1
- package/dist/assets/components/internal/header-meta/header-meta.vue +0 -1
- package/dist/assets/components/internal/info-icon-with-popover/info-icon-with-popover.vue +0 -1
- package/dist/assets/components/internal/listbox/components/listbox-lazy-load/listbox-lazy-load.types.ts +0 -1
- package/dist/assets/components/internal/listbox/components/listbox-lazy-load/listbox-lazy-load.vue +0 -1
- package/dist/assets/components/internal/listbox/components/listbox-multi-select/components/listbox-multi-select-options/components/listbox-multi-select-category/listbox-multi-select-category.types.ts +0 -1
- 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 +0 -1
- package/dist/assets/components/internal/listbox/components/listbox-multi-select/components/listbox-multi-select-options/components/listbox-multi-select-nested-lazy-load/listbox-multi-select-nested-lazy-load.types.ts +0 -1
- package/dist/assets/components/internal/listbox/components/listbox-multi-select/components/listbox-multi-select-options/components/listbox-multi-select-nested-lazy-load/listbox-multi-select-nested-lazy-load.vue +0 -1
- package/dist/assets/components/internal/listbox/components/listbox-multi-select/components/listbox-multi-select-options/listbox-multi-select-options.types.ts +0 -1
- package/dist/assets/components/internal/listbox/components/listbox-multi-select/components/listbox-multi-select-options/listbox-multi-select-options.vue +0 -1
- package/dist/assets/components/internal/listbox/components/listbox-multi-select/listbox-multi-select.types.ts +0 -1
- package/dist/assets/components/internal/listbox/components/listbox-multi-select/listbox-multi-select.vue +0 -1
- package/dist/assets/components/internal/listbox/components/listbox-option/listbox-option.vue +0 -1
- package/dist/assets/components/internal/listbox/components/listbox-option-tag/listbox-option-tag.vue +0 -1
- package/dist/assets/components/internal/listbox/components/listbox-single-select/components/listbox-single-select-category/listbox-single-select-category.vue +0 -1
- package/dist/assets/components/internal/listbox/components/listbox-single-select/listbox-single-select.vue +0 -1
- package/dist/assets/components/internal/listbox/components/listbox-single-select-option/listbox-single-select-option.vue +0 -1
- package/dist/assets/components/internal/listbox/listbox.stories.ts +0 -1
- package/dist/assets/components/internal/listbox/listbox.types.ts +0 -1
- package/dist/assets/components/internal/listbox/listbox.vue +0 -1
- package/dist/assets/components/internal/radio-input/radio-input.stories.ts +0 -1
- package/dist/assets/components/internal/radio-input/radio-input.types.ts +0 -1
- package/dist/assets/components/internal/radio-input/radio-input.vue +0 -1
- package/dist/assets/components/internal/teleport/teleport.types.ts +0 -1
- package/dist/assets/components/internal/teleport/teleport.vue +0 -1
- package/dist/assets/components/internal/tooltip/tooltip.test.vue +0 -1
- package/dist/assets/components/internal/tooltip/tooltip.types.ts +0 -1
- package/dist/assets/components/internal/tooltip/tooltip.vue +0 -1
- package/dist/assets/composables/use-form-validate-zod-4/use-form.types.ts +0 -1
- package/dist/assets/composables/use-numeric-input/use-numeric-input.types.ts +0 -1
- package/dist/assets/composables/use-section-navigation-keyboard/use-section-navigation-keyboard.types.ts +0 -1
- package/dist/assets/composables/use-virtual-list/use-virtual-list.types.ts +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
/** * Defines how to measure and adjust a date range for a specific granularity (daily, weekly, etc.). * Used by `clampToMaxRange` to enforce maximum range limits in a granularity-agnostic way. */ export interface GranularityClampConfig { /** Returns how many units (days, weeks, months, quarters) fit between two dates. */ getCount: (end: Date, start: Date) => number; /** Subtracts a number of units from a date (e.g. `subDays`, `subWeeks`). */ sub: (date: Date, amount: number) => Date; /** Adds a number of units to a date (e.g. `addDays`, `addWeeks`). */ add: (date: Date, amount: number) => Date; /** Snaps a date to the start of its unit boundary (e.g. `startOfMonth`). Only needed for granularities whose units don't align to individual days. */ snapStart?: (date: Date) => Date; /** Snaps a date to the end of its unit boundary (e.g. `endOfMonth`). Only needed for granularities whose units don't align to individual days. */ snapEnd?: (date: Date) => Date; }
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import { ref } from 'vue'; import { isVue2 } from 'vue-demi'; import BentoCheckboxInput from './checkbox-input.vue'; import type { Meta, StoryObj } from '@storybook/vue'; const meta: Meta = { title: 'Checkbox input', component: BentoCheckboxInput, }; export default meta; type Story = StoryObj<typeof BentoCheckboxInput>; export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoCheckboxInput }, props: Object.keys(argTypes), template: ` <bento-checkbox-input v-bind="args" :model-value.sync="selectedValue"/> `, setup(props) { const selectedValue = ref(undefined); return { // Values args: isVue2 ? props : _args, selectedValue, }; }, }), args: { value: 'checkbox-input-value', }, };
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export type MultiCheckboxInputType = Array<string | number>; export type CheckboxInputType = boolean | MultiCheckboxInputType; export enum CheckboxInputState { UNCHECKED = 'unchecked', CHECKED = 'checked', DISABLED = 'disabled', ERROR = 'error', READONLY = 'readonly', INDETERMINATE = 'indeterminate', } export enum CheckboxInputEvent { UPDATE = 'update:model-value', } interface CheckboxInputGroupItem { value: string | number; disabled?: boolean; hasError?: boolean; errorMessage?: string; readonly?: boolean; } export type CheckboxInputGroupItems = Array<CheckboxInputGroupItem>;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<template> <div class="b-checkbox-input" :class="conditionalClasses"> <!-- Don't change order because it matters for CSS. Icons first --> <minus-small-icon v-if="indeterminate" class="b-checkbox-input__icon" svg-title="" aria-hidden="true" /> <checkmark-small-icon v-else-if="isChecked" class="b-checkbox-input__icon" svg-title="" aria-hidden="true" /> <input :id="id" ref="inputRef" v-bind="computedAttrs" type="checkbox" class="b-checkbox-input__outline" :class="outlineConditionalClasses" :aria-label="ariaLabel" :aria-checked="ariaChecked" :aria-describedby="ariaDescribedby" :aria-invalid="ariaInvalid" :checked="isChecked" :disabled="disabled" :indeterminate.prop="indeterminate" :value="value" :aria-readonly="readonly" @click="onClick" @change="onChange" @keydown.space.prevent="inputRef?.click()" @keydown.enter.prevent="inputRef?.click()" /> </div> </template> <script setup lang="ts"> import { computed, type HTMLAttributes, type PropType, ref, toRefs, useAttrs } from 'vue'; import { CheckboxInputEvent, type CheckboxInputGroupItems, CheckboxInputState, type CheckboxInputType, type MultiCheckboxInputType, } from '@/internal/checkbox-input/checkbox-input.types'; import CheckmarkSmallIcon from '@adyen/ui-assets-icons-16/vue/checkmark-small'; import MinusSmallIcon from '@adyen/ui-assets-icons-16/vue/minus-small'; import { isVue3 } from 'vue-demi'; const attrs = useAttrs(); const props = defineProps({ /** * The aria label associated to the input checkbox. */ ariaLabel: { type: String, default: '' }, /** * An additional string that serves as description for the input checkbox (useful if the component shows a description). */ ariaDescribedby: { type: String, default: '' }, /** * Setting this property to true will disable the checkbox in your form. */ disabled: { type: Boolean, default: false }, /** * Setting this property to true will show the checkbox in error state. */ hasError: { type: Boolean, default: false }, /** * Sets the id of the checkbox. Required for accessibility purposes */ id: { type: String, default: null }, /** * The :indeterminate CSS pseudo-class represents any form element * whose state is indeterminate, such as checkboxes which have their HTML indeterminate attribute * set to true, radio buttons which are members of a group in which all radio buttons are unchecked, * and indeterminate <progress> elements. https://developer.mozilla.org/en-US/docs/Web/CSS/:indeterminate * This property lets you control the input.indeterminate native property. */ indeterminate: { type: Boolean, default: false }, /** * When handling a single checkbox with a singular state, use a boolean. * * When handling multiple checkboxes, use an array of keys. * These keys exist as a set "value" prop for each checkbox. */ modelValue: { type: [Boolean, Array] as PropType<CheckboxInputType | CheckboxInputGroupItems>, default: false, }, /** * Setting this property to true will set the checkbox to read only state. */ readonly: { type: Boolean, default: false }, /** * The value of the component. The DOM API casts this to a string. * The browser uses "on" as the default value. * * In the case of multiple checkboxes this value is used as a unique key. */ value: { type: [String, Number], default: null }, }); const emit = defineEmits<{ /** * Emit that emits the input value */ (e: 'update:model-value', value: boolean | Array<string | number>): void; }>(); const inputRef = ref<HTMLInputElement | null>(null); // filter out style classes because in vue3 they are also part of attrs const computedAttrs = computed(() => { if (isVue3) { const { class: _class, style: _style, ...rest } = attrs; return rest; } else { return attrs; } }); const { disabled, indeterminate, modelValue, readonly, value } = toRefs(props); const conditionalClasses = computed(() => { // Append classes set from parent to first element const inheritClasses = (attrs.class as string)?.split(' ').reduce((acc, className) => { acc[className] = true; return acc; }, {}); return { [`b-checkbox-input--${CheckboxInputState.DISABLED}`]: props.disabled, [`b-checkbox-input--${CheckboxInputState.READONLY}`]: props.readonly, ...inheritClasses, }; }); const outlineConditionalClasses = computed(() => ({ [`b-checkbox-input__outline--${CheckboxInputState.UNCHECKED}`]: !isChecked.value && !props.indeterminate, [`b-checkbox-input__outline--${CheckboxInputState.ERROR}`]: props.hasError, [`b-checkbox-input__outline--${CheckboxInputState.READONLY}`]: props.readonly, })); const isCheckboxGroup = computed(() => Array.isArray(modelValue.value)); const isChecked = computed(() => { if (!isCheckboxGroup.value) { return modelValue.value as boolean; } return (modelValue.value as MultiCheckboxInputType).some(item => item === value.value); }); // ARIA fields const ariaChecked = computed<HTMLAttributes['aria-checked']>(() => props.indeterminate ? 'mixed' : `${!!isChecked.value}` ); const ariaInvalid = computed<HTMLAttributes['aria-invalid']>( () => props.hasError && !props.disabled && !props.readonly ); // readonly attr doesn't work with checkbox, so we need to manually ignore the click instead const onClick = (event: Event) => { if (readonly.value) { event.preventDefault(); return; } }; const onChange = (event: Event) => { if (disabled.value) { return; } const newValue: boolean = (event.target as HTMLInputElement).checked; if (isCheckboxGroup.value) { const newCheckedList = [...(modelValue.value as MultiCheckboxInputType)]; const index = newCheckedList.indexOf(value.value); if (index === -1) { newCheckedList.push(value.value); } else { newCheckedList.splice(index, 1); } emit(CheckboxInputEvent.UPDATE, newCheckedList); } else { emit(CheckboxInputEvent.UPDATE, newValue); } }; </script> <script lang="ts"> /** * Internal component that renders a checkbox input (without label) * * @example * import { BentoCheckboxInput } from '@adyen/bento-vue2'; * * export default { * components: { BentoCheckboxInput }, * template: ` * <label for="checkbox-id">Your label</label> * <span id="description-id">Description</span> * <bento-checkbox-input id="checkbox-id" aria=label="Your label" aria-describedby="description-id" /> * ` * } */ export default { name: 'checkbox-input', inheritAttrs: false, }; </script> <style lang="scss" scoped src="./checkbox-input.scss" />
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<template> <fieldset class="b-controls-group" v-bind="attrs" :class="conditionalClasses" :form="form" :disabled="disabled"> <legend class="b-controls-group__label-container" :class="legendConditionalClasses"> <field-label v-if="label" class="b-controls-group__label" :label="label" :tooltip-text="tooltipText" :optional="optional" :required="required" /> <bento-typography v-if="description || hasSlot('description')" class="b-controls-group__description" el="span" > <slot name="description"> {{ description }} </slot> </bento-typography> </legend> <slot /> </fieldset> </template> <script setup lang="ts"> import { computed, useAttrs, useSlots } from 'vue'; import { BentoTypography } from '@/components/typography'; import { FieldLabel } from '@/components/internal/field-label'; import { deprecate } from '@/utils/ts/deprecate'; import { useHasSlot } from '@/composables'; const attrs = useAttrs(); const slots = useSlots(); const hasSlot = useHasSlot(slots); const props = defineProps({ /** * Descriptive supporting text visbile underneath the label. */ description: { type: String, default: null }, /** * The name of the component to cite when throwing a depreacte error. */ componentName: { type: String, required: true }, /** * Sets all child controls of the <fieldset> as disabled. */ disabled: { type: Boolean, default: false }, /** * The value of the id attribute of a <form> element the <fieldset> is to part of, even if it is not inside the form itself. */ form: { type: String, default: null }, /** * Shows/hides the group label. */ hideLabel: { type: Boolean, default: null }, /** * The name associated with the group. Required for accessibility purposes. * The label is shown when `hideLabel` is set to `true` or if `description` is passed */ label: { type: String, required: true }, /** * Indicates the field is optional. */ optional: { type: Boolean, default: false }, /** * Indicates the field is required. */ required: { type: Boolean, default: false }, /** * Tooltip message. */ tooltipText: { type: String, default: null }, }); const conditionalClasses = computed(() => ({ 'b-controls-group--disabled': props.disabled, })); const legendConditionalClasses = computed(() => { const isLabelAndDescriptionPresent = props.label && (props.description || hasSlot('description')); const shouldShowLabel = isLabelAndDescriptionPresent || props.hideLabel === false; return { 'b-controls-group__label-container--hidden': !shouldShowLabel, }; }); // eslint-disable-next-line eqeqeq -- Needs to show deprecate message when hideLabel is not set and no description available const hasNoPropsDefined = !(props.description || hasSlot('description')) && props.hideLabel == undefined; if (hasNoPropsDefined) { deprecate( `${props.componentName}: The property \`label\` will always be rendered by default.`, `After version 2.0.0, the label will be shown by default. To continue hiding the label set \`:hide-label="true"\`. Up until version 2.0.0, to display the label set \`hide-label="false"\`. \`label\` will always be rendered when \`description\` is passed alongside with \`label\``, `2.0.0` ); } </script> <script lang="ts"> /** * A controls group components should be used whenever it is necessary to group a set of controls (i.e. radio buttons or checkboxes) * This component conforms to the official WAI-ARIA guidelines for grouping controls: https://www.w3.org/WAI/tutorials/forms/grouping/ * * NOTE: This component should not be used by itself but more implemented by other exported components * @example * import { ControlsGroup, BentoCheckbox, BentoRadioButton } from '@adyen/bento-vue2/'; * * export default { * components: { ControlsGroup, BentoRadioButton }, * template: ` * <controls-group * label="Your group label" * description="Description" * > * <bento-radio-button * disabled * value="inputRadio" * > * Label text * </bento-radio-button> * <bento-radio-button * value="inputRadio" * > * Another label text * </bento-radio-button> * </controls-group> * ` * }; */ export default {}; </script> <style lang="scss" scoped src="./controls-group.scss" />
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { Canvas, Meta, Story } from '@storybook/blocks';
|
|
2
|
-
import * as CopyStories from './copy.stories';
|
|
3
|
-
|
|
4
|
-
<Meta of={CopyStories} />
|
|
5
|
-
|
|
6
|
-
# Copy
|
|
7
|
-
|
|
8
|
-
`Copy` should be used to help the user more easily copy relevant information to the clipboard. When the user clicks the
|
|
9
|
-
component, text should automatically be copied to their clipboard.
|
|
10
|
-
|
|
11
|
-
<Canvas withSource="closed">
|
|
12
|
-
<Story of={CopyStories.Default} />
|
|
13
|
-
</Canvas>
|
|
14
|
-
|
|
15
|
-
## Variations
|
|
16
|
-
|
|
17
|
-
The component will be rendered by default as a copy icon, but a slot can also be passed to render something else that
|
|
18
|
-
when the user clicks on will copy the content to the clipboard. Useful, for example, with inline copy examples.
|
|
19
|
-
|
|
20
|
-
{/* TODO: change the slot example to inline code when that component is available */}
|
|
21
|
-
|
|
22
|
-
<Canvas withSource="closed">
|
|
23
|
-
<Story of={CopyStories.WithSlot} />
|
|
24
|
-
</Canvas>
|
|
25
|
-
|
|
26
|
-
## Button customization
|
|
27
|
-
|
|
28
|
-
The copy button variation can be customized using the `buttonText` and `button` properties.
|
|
29
|
-
|
|
30
|
-
<Canvas withSource="closed">
|
|
31
|
-
<Story of={CopyStories.WithCustomButton} />
|
|
32
|
-
</Canvas>
|
|
33
|
-
|
|
34
|
-
## Accessibility
|
|
35
|
-
|
|
36
|
-
Assistive technology should notify the user once a text has been copied to the clipboard (i.e. once the content of the
|
|
37
|
-
tooltip changes)
|
|
38
|
-
|
|
39
|
-
### Keyboard interaction
|
|
40
|
-
|
|
41
|
-
The button/content to be copied should be reached via keyboard navigation and the screen reader should announce the
|
|
42
|
-
content of the tooltip (copy to clipboard) The content should be copied when the button is pressed (enter/space key is
|
|
43
|
-
pressed)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import Copy from './copy.vue'; import { BentoTypography } from '@/components/typography'; import { storybookDocsParameter } from '@/utils/ts/storybook'; import type { Meta, StoryObj } from '@storybook/vue'; import './copy.stories.scss?modules'; import { isVue2 } from 'vue-demi'; const meta: Meta = { title: 'Copy', component: Copy, }; const DEFAULT_PROPS = { text: 'Copy this', }; export default meta; type Story = StoryObj<typeof Copy>; export const Default: Story = { render: (_args, { argTypes }) => ({ components: { Copy }, props: Object.keys(argTypes), setup: props => ({ args: isVue2 ? props : _args }), template: ` <copy v-bind="args" /> `, }), args: { ...DEFAULT_PROPS, }, }; export const WithSlot: Story = { render: (_args, { argTypes }) => ({ components: { Copy, BentoTypography }, props: Object.keys(argTypes), setup: props => ({ args: isVue2 ? props : _args }), template: ` <copy v-bind="args"> <bento-typography el="span">Click me to copy</bento-typography> </copy> `, }), args: { ...DEFAULT_PROPS, }, }; const WithCustomButtonCode = ` <copy buttonText="Copy text" :button="{condensed: true, variant: 'secondary' }" /> `; export const WithCustomButton: Story = { render: (_args, { argTypes }) => ({ components: { Copy, BentoTypography }, props: Object.keys(argTypes), setup: props => ({ args: isVue2 ? props : _args }), template: ` <copy v-bind="args" /> `, }), args: { ...DEFAULT_PROPS, buttonText: 'Copy text', button: { condensed: true, variant: 'secondary' }, }, parameters: storybookDocsParameter(WithCustomButtonCode), };
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import { type PopperContainerPosition } from '@/components/popper-container'; import type { BentoButton } from '@/components/button'; export enum CopyTooltipState { DEFAULT = 'default', COPYING = 'copying', ERROR = 'error', } type ButtonProps = InstanceType<typeof BentoButton>['$props']; export interface BentoCopyButtonProps { variant?: ButtonProps['variant']; condensed?: ButtonProps['condensed']; disabled?: ButtonProps['disabled']; } export interface BentoCopyProps { /** * Object containing props to change the menu button appearance. Accepts the same props present in BentoButton. */ button?: BentoCopyButtonProps; /** * Sets the button text of the copy button. */ buttonText?: string; /** * Defines the string that will get copied to the clipboard */ text: string; /** * Position the tooltip should take if space is available. Defaults to 'top' */ tooltipPosition?: PopperContainerPosition; }
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<template> <div class="b-copy" @mouseleave="resetText" @focusout="resetText"> <span v-if="$slots.default" v-bento-tooltip-directive:[tooltipPosition]="copyText" @click="onClickCopy"> <slot /> </span> <bento-button v-else v-bento-tooltip-directive:[tooltipPosition]="copyText" v-bind="button" @click="onClickCopy" > <template #iconLeft> <copy-icon :svg-title="svgTitle" /> </template> <template v-if="buttonText" #default> {{ buttonText }} </template> </bento-button> <div v-if="showScreenReaderAlert" role="alert" class="b-copy__screen-reader-alert">{{ t('copied') }}</div> </div> </template> <script setup lang="ts"> import { computed, ref } from 'vue'; import { BentoButton } from '@/components/button'; import { PopperContainerPosition } from '@/components/popper-container'; import { BentoTooltipDirective as vBentoTooltipDirective } from '@/directives'; import { useI18n } from '@/utils/ts/i18n'; import { type BentoCopyButtonProps, type BentoCopyProps, CopyTooltipState } from './copy.types'; import CopyIcon from '@adyen/ui-assets-icons-16/vue/copy'; import messages from './messages.json'; type MessageSchema = (typeof messages)['en-US']; const emit = defineEmits<{ /** * Emitted when the copy button is clicked. */ (e: 'click', event: MouseEvent): void; }>(); const props = withDefaults(defineProps<BentoCopyProps>(), { button: () => ({ variant: 'tertiary' }) as BentoCopyButtonProps, buttonText: undefined, tooltipPosition: PopperContainerPosition.TOP, }); const { t } = useI18n<{ message: MessageSchema }>({ messages }); const copyState = ref<CopyTooltipState>(CopyTooltipState.DEFAULT); const showScreenReaderAlert = ref(false); const onClickCopy = async (event: MouseEvent) => { try { await navigator.clipboard.writeText(props.text); copyState.value = CopyTooltipState.COPYING; showScreenReaderAlert.value = true; } catch { copyState.value = CopyTooltipState.ERROR; } emit('click', event); }; const resetText = () => { copyState.value = CopyTooltipState.DEFAULT; showScreenReaderAlert.value = false; }; const copyText = computed(() => { switch (copyState.value) { case CopyTooltipState.COPYING: return t('copied'); case CopyTooltipState.ERROR: return t('copyFailed'); default: return t('copy'); } }); const svgTitle = computed(() => (props.buttonText ? undefined : t('copy'))); </script> <script lang="ts"> /** * An internal button component that copies text to the user's clipboard. * * @example * import { Copy } from '@adyen/bento-vue2'; * * export default { * components: { Copy }, * template: ` * <copy :text="textToCopy" /> * ` * } */ export default { i18n: { messages }, name: 'copy', }; </script> <style lang="scss" scoped src="./copy.scss" />
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<template> <div class="b-checkbox-input" :class="conditionalClasses" role="presentation"> <!-- Don't change order because it matters for CSS. Icons first --> <minus-small-icon v-if="indeterminate" class="b-checkbox-input__icon" svg-title="" aria-hidden="true" /> <double-checkmark-small-icon v-else-if="allSelected" class="b-checkbox-input__icon" svg-title="" aria-hidden="true" data-testid="decorative-checkbox-double-checkmark-icon" /> <checkmark-small-icon v-else-if="checked" class="b-checkbox-input__icon" svg-title="" aria-hidden="true" /> <div class="b-checkbox-input__outline" :class="outlineConditionalClasses" /> </div> </template> <script setup lang="ts"> import { computed } from 'vue'; import { CheckboxInputState } from '@/internal/checkbox-input/checkbox-input.types'; import CheckmarkSmallIcon from '@adyen/ui-assets-icons-16/vue/checkmark-small'; import MinusSmallIcon from '@adyen/ui-assets-icons-16/vue/minus-small'; import DoubleCheckmarkSmallIcon from '@adyen/ui-assets-icons-16/vue/double-checkmark-small'; const props = withDefaults( defineProps<{ /** * Setting this property to true will disable the checkbox in your form. */ disabled?: boolean; /** * The :indeterminate CSS pseudo-class represents any form element * whose state is indeterminate, such as checkboxes which have their HTML indeterminate attribute * set to true, radio buttons which are members of a group in which all radio buttons are unchecked, * and indeterminate <progress> elements. https://developer.mozilla.org/en-US/docs/Web/CSS/:indeterminate * This property lets you control the input.indeterminate native property. */ indeterminate?: boolean; /** * Setting this to true will show the checkbox as checked */ checked?: boolean; /** * Setting this to true will show the checkbox as having all items selected, which shows the double checkmark icon */ allSelected?: boolean; }>(), { disabled: false, indeterminate: false, checked: false, allSelected: false, } ); const conditionalClasses = computed(() => ({ [`b-checkbox-input--${CheckboxInputState.DISABLED}`]: props.disabled, })); const outlineConditionalClasses = computed(() => ({ [`b-checkbox-input__outline--${CheckboxInputState.UNCHECKED}`]: !props.checked && !props.indeterminate && !props.allSelected, [`b-checkbox-input__outline--${CheckboxInputState.CHECKED}`]: (props.checked || props.allSelected) && !props.indeterminate, [`b-checkbox-input__outline--${CheckboxInputState.INDETERMINATE}`]: props.indeterminate, })); </script> <script lang="ts"> export default {}; </script> <style lang="scss" scoped src="@/internal/checkbox-input/checkbox-input.scss" />
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import { type Ref } from 'vue'; export enum BentoDialogPageAnimation { TRANSITION_LEVEL_DEEPER = 'b-dialog-page__animation--level-deeper', TRANSITION_LEVEL_UPPER = 'b-dialog-page__animation--level-upper', } export interface BentoDialogPageConfigData { activePage: Ref<string>; animation: Ref<string>; isAnimationActive?: Ref<boolean>; isOpen: Ref<boolean>; pages: Ref<Array<string>>; previousPages: Ref<Array<string>>; updatePage: (page: string) => void; } export type BentoDialogPageRegisterPage = (pageId: string) => { unregisterPage: () => void };
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<template> <Transition :name="animation" mode="out-in" @after-leave="updateAnimation" @leave="startAnimation"> <div v-if="isOpen && showPage" class="b-dialog-page" :class="computedClasses"> <div v-if="hasSlot('header')" class="b-dialog-page__header" :class="headerConditionalClasses"> <div v-if="showBackButton" class="b-dialog-page__back-button" :style="backButtonStyling" data-testid="back-button" > <bento-button variant="tertiary" @click="goToPreviousPage(previousPage)"> <arrow-left-icon :svg-title="t('goToPreviousPage')" /> </bento-button> </div> <slot name="header"></slot> </div> <div v-if="hasSlot('content')" ref="contentElement" class="b-dialog-page__content" :tabindex="isScrollable ? 0 : null" @scroll="onScroll" > <slot name="content" /> </div> <div v-if="hasSlot('footer')" class="b-dialog-page__footer" :class="footerConditionalClasses"> <slot name="footer" /> </div> </div> </Transition> </template> <script setup lang="ts"> import { computed, inject, nextTick, onMounted, onUnmounted, type PropType, ref, useSlots, watch } from 'vue'; import { BentoButton } from '@/components/button'; import ArrowLeftIcon from '@adyen/ui-assets-icons-16/vue/arrow-left'; import { useHasSlot } from '@/composables/use-has-slot/use-has-slot'; import { useI18n } from '@/utils/ts/i18n'; import { isDevelopmentEnviroment } from '@/utils/ts/dev-environment'; import { BentoDialogPageAnimation, type BentoDialogPageConfigData, type BentoDialogPageRegisterPage, } from './dialog-page.types'; import type { CSSProperties } from 'vue/types/jsx.d.ts'; import { DIALOG_PAGE_CONFIG_INJECTION_KEY, DIALOG_PAGE_REGISTER_PAGE_INJECTION_KEY } from './dialog-page.keys'; import messages from './messages.json'; import { useScroll } from '@vueuse/core'; type MessageSchema = (typeof messages)['en-US']; const props = defineProps({ /** * Sets `position: absolute` on the dialog page root element. * Enabled by default as it is used by default in most places. * Inspector requires it to not be absolute positioned. * @default true */ absolutePosition: { type: Boolean, default: true, }, /** * Show header and actions border */ alwaysShowBorder: { type: Boolean, default: false, }, /** * Pass custom styling to the back button to position it correctly with the header slot */ backButtonStyling: { type: Object as PropType<CSSProperties>, default: null }, /** * Identifier of the page, used with activePage prop to handle navigation */ pageId: { type: String, default: undefined, }, /** * ID of the page for the back button to navigate to. Only use it to overwrite the default back behavior * Used when the back button should not take the user to the natural previous page * Set to `null` to hide back button in the page */ previousPage: { type: String, default: undefined, }, }); const emit = defineEmits<{ /** * Fires when the animation ends */ (e: 'animationend'): void; }>(); const slots = useSlots(); const hasSlot = useHasSlot(slots); const { t } = useI18n<{ message: MessageSchema }>({ messages }); // Refs const contentElement = ref<HTMLDivElement>(null); const hasScroll = ref(false); const isMounted = ref(false); const { arrivedState } = useScroll(contentElement); // Logic to choose which page is active/shown const showPage = computed(() => (activePage.value ? activePage.value === props.pageId : true)); const computedClasses = computed(() => ({ 'b-dialog-page--absolute-position': props.absolutePosition, })); // Scrolling styling logic const headerConditionalClasses = computed(() => ({ 'b-dialog-page__header--with-border': props.alwaysShowBorder || (hasScroll.value && !arrivedState.top), })); const footerConditionalClasses = computed(() => ({ 'b-dialog-page__footer--with-border': props.alwaysShowBorder || (hasScroll.value && !arrivedState.bottom), })); const isScrollable = computed(() => !(arrivedState.bottom && arrivedState.top)); const onScroll = () => { if (!contentElement.value) { return; } }; const calculateScroll = async watchedValue => { if (!watchedValue) { return; } // Wait for content to be rendered to be able to calculate the height await nextTick(); if (!isMounted.value) { return; } hasScroll.value = contentElement.value && contentElement.value.scrollHeight > contentElement.value.clientHeight; }; const { activePage, animation, isAnimationActive, isOpen, pages, previousPages, updatePage } = inject<BentoDialogPageConfigData>(DIALOG_PAGE_CONFIG_INJECTION_KEY); watch(() => isOpen.value, calculateScroll, { immediate: true }); watch(() => showPage.value, calculateScroll, { immediate: true }); // Logic to determine whether the back button should be shown on the page const showBackButton = computed(() => { if (props.previousPage) { return true; } if (props.previousPage === null) { return false; } return hasPreviousPages.value && showPage.value; }); // Animation const updateAnimation = () => { animation.value = BentoDialogPageAnimation.TRANSITION_LEVEL_DEEPER; if (isAnimationActive) { isAnimationActive.value = false; } emit('animationend'); }; const startAnimation = () => { if (isAnimationActive) { isAnimationActive.value = true; } }; // Previous page logic const hasPreviousPages = computed(() => previousPages.value.length > 1); const lastPage = computed(() => previousPages.value[previousPages.value.length - 1]); const isActivePageValid = computed(() => pages.value.some(page => page === activePage.value)); const goToPreviousPage = async (page: string) => { if (page) { const index = previousPages.value.indexOf(page); if (index !== -1) { previousPages.value = previousPages.value.slice(0, index + 1); } else { previousPages.value = [page]; } } else { previousPages.value.pop(); } animation.value = BentoDialogPageAnimation.TRANSITION_LEVEL_UPPER; await nextTick(); if (!isMounted.value) { return; } updatePage(lastPage.value); }; // Add new page to the previousPage array to keep track of user navigation watch( () => activePage.value, async () => { await nextTick(); if (!isMounted.value || !isOpen.value) { return; } if (!!activePage.value && !isActivePageValid.value) { if (isDevelopmentEnviroment) { // eslint-disable-next-line no-console console.error( `The activePage passed "${activePage.value}" does not match the id of any of the pages` ); return; } } if (activePage.value !== lastPage.value) { previousPages.value.push(activePage.value); } }, { immediate: true } ); // Register page to parent component and inject data from parent component const registerPage = inject<BentoDialogPageRegisterPage>(DIALOG_PAGE_REGISTER_PAGE_INJECTION_KEY); const { unregisterPage } = registerPage(props.pageId); onMounted(() => { isMounted.value = true; }); // Unregister page on unmounting the page onUnmounted(() => { isMounted.value = false; unregisterPage(); }); </script> <script lang="ts"> /** * DialogPage is an internal component used to render different pages of dialog components (Modal, ModalFullscreen and Sidepanel) * * @example * import { DialogPage } from '@/internal'; * * export default { * components: { DialogPage }, * template: ` * <dialog-page pageId='step1' always-show-border> * <template #header>Header</template> * <template #content>Content</template> * <template #footer>Footer</template> * </dialog-page> * ` * } */ export default { i18n: { messages }, }; </script> <style lang="scss" scoped src="./dialog-page.scss" />
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<template> <bento-typography :id="$attrs.id" class="b-error-message" el="span" aria-live="polite"> <div class="b-error-message__icon"> <cross-circle-icon :svg-title="t('error')" /> </div> {{ errorMessage }} </bento-typography> </template> <script setup lang="ts"> import { BentoTypography } from '@/components/typography'; import CrossCircleIcon from '@adyen/ui-assets-icons-16/vue/cross-circle-fill'; import { useI18n } from '@/utils/ts/i18n'; import messages from './messages.json'; type MessageSchema = (typeof messages)['en-US']; /** * An error message component should be used whenever it is necessary to add an error message to a component. * If adding to a form component, the proper accessibility measures should be taken when necessary * (ex. associating an element with this one using aria-describedby). * @example * import { ErrorMessage } from '@/components/internal/error-message'; * * export default { * components: { ErrorMessage }, * template: ` * <error-message :error-message="errorMessage" /> * ` * } */ defineProps({ /** * The rendered error message */ errorMessage: { type: String, required: true, }, }); const { t } = useI18n<{ message: MessageSchema }>({ messages }); </script> <script lang="ts"> export default { i18n: { messages }, name: 'error-message', }; </script> <style lang="scss" scoped src="./error-message.scss" />
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<template> <label class="b-field-label" :class="conditionalClasses"> <bento-typography el="span" stronger> <template v-if="label">{{ label }}</template> <template v-else><slot></slot></template> </bento-typography> <bento-typography v-if="!!tooltipText" el="span" class="b-field-label__tooltip-wrapper"> <bento-info-icon :tooltip-text="tooltipText" class="b-field-label__tooltip" /> </bento-typography> <bento-typography v-if="required" class="b-field-label__requirement" el="span">*</bento-typography> <bento-typography v-else-if="optional" class="b-field-label__requirement" el="span" >({{ t('optional') }})</bento-typography > </label> </template> <script setup lang="ts"> import { BentoInfoIcon } from '@/components/info-icon'; import { BentoTypography } from '@/components/typography'; import { useI18n } from '@/utils/ts/i18n'; import messages from './messages.json'; import { computed } from 'vue'; type MessageSchema = (typeof messages)['en-US']; const props = defineProps({ /** * Renders the dropdown with a condensed style. */ condensed: { type: Boolean, default: false }, /** * Field label */ label: { type: String, default: null, }, /** * Sets the field as optional */ optional: { type: Boolean, default: false, }, /** * Sets the field as required */ required: { type: Boolean, default: false, }, /** * Adds a info icon next to the label to display the passed tooltip message. */ tooltipText: { type: String, default: null, }, }); const { t } = useI18n<{ message: MessageSchema }>({ messages }); const conditionalClasses = computed(() => ({ 'b-field-label--condensed': props.condensed })); </script> <script lang="ts"> /** * Internal component to handle the labels for all field components * @example * import { FieldLabel } from '@/components/internal/field-label'; * * export default { * components: { FieldLabel }, * template: ` * <field-label label="Label" tooltip-text="Tooltip info" required /> * ` * } */ export default { i18n: { messages }, }; </script> <style lang="scss" scoped src="./field-label.scss" />
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { Canvas, Meta } from '@storybook/blocks';
|
|
2
|
-
import * as FixedScrollerStories from './fixed-scroller.stories';
|
|
3
|
-
|
|
4
|
-
<Meta of={FixedScrollerStories} />
|
|
5
|
-
|
|
6
|
-
# FixedScroller
|
|
7
|
-
|
|
8
|
-
The fixed scroller component allows to scroll the content by one view on each click. If the final scroll is shorter than
|
|
9
|
-
a whole view, the last element stops at the end of the scrollable area, so that no additional gap is created.
|
|
10
|
-
|
|
11
|
-
<Canvas of={FixedScrollerStories.Default} />
|
|
12
|
-
|
|
13
|
-
## Use Cases
|
|
14
|
-
|
|
15
|
-
Use a FixedScroller if:
|
|
16
|
-
|
|
17
|
-
- The content needs to be scrolled horizontally by one view at a time
|
|
18
|
-
|
|
19
|
-
## Resources
|
|
20
|
-
|
|
21
|
-
- [Figma link](https://www.figma.com/file/uLabwF3243jdMDsNSP7U9I/branch/pouQdMaQzLTixntp0khsCy/Bento---Components?node-id=9742%3A9601&mode=dev)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import { storybookDocsParameter } from '@/utils/ts/storybook'; import { BentoButton } from '@/components/button'; import type { Meta, StoryObj } from '@storybook/vue'; import { FixedScroller } from './index'; import { isVue2 } from 'vue-demi'; const meta: Meta = { title: 'Fixed scroller', component: FixedScroller, }; export default meta; type Story = StoryObj<typeof FixedScroller>; const defaultCode = ` <template> <fixed-scroller> <div style="display: flex; gap: 8px;"> {{ Content here }} </div> </fixed-scroller> </template> <script setup lang="ts"> import { FixedScroller } from '@/internal' </script> `; export const Default: Story = { render: (_args, { argTypes }) => ({ components: { FixedScroller, BentoButton }, props: Object.keys(argTypes), template: ` <div style="width: 500px;"> <fixed-scroller v-bind="args"> <div style="display: flex; gap: 8px;"> <bento-button v-for="(x, index) in 13" :key="index" variant="secondary" :condensed="args.condensed"> Index: {{x}} </bento-button> </div> </fixed-scroller> </div> `, setup(props) { return { args: isVue2 ? props : _args, }; }, }), parameters: storybookDocsParameter(defaultCode), };
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<template> <div ref="fixedScrollerRef" class="b-fixed-scroller"> <div class="b-fixed-scroller__container"> <div ref="contentRef" class="b-fixed-scroller__content" :style="{ transform: `translateX(${offset}px)`, paddingTop: shouldShowLeftButton || shouldShowRightButton ? `${extraPaddingTop}px` : null, }" > <slot></slot> </div> </div> <div v-if="shouldShowLeftButton" ref="leftButtonRef" class="b-fixed-scroller__left b-fixed-scroller__button" :class="buttonConditionalClasses" > <bento-button variant="secondary" :aria-label="t('scrollLeft')" :condensed="condensed" @click="scrollLeft"> <template #iconLeft> <chevron-left-icon svg-title="checked" aria-hidden="true" /> </template> </bento-button> </div> <div v-if="shouldShowRightButton" ref="rightButtonRef" class="b-fixed-scroller__right b-fixed-scroller__button" :class="buttonConditionalClasses" > <bento-button variant="secondary" :aria-label="t('scrollRight')" :condensed="condensed" @click="scrollRight" > <template #iconLeft> <chevron-right-icon svg-title="checked" aria-hidden="true" /> </template> </bento-button> </div> </div> </template> <script setup lang="ts"> import { computed, ref } from 'vue'; import { BentoButton } from '@/components/button'; import { useFixedScroller } from './composables'; import { useI18n } from '@/utils/ts/i18n'; import ChevronLeftIcon from '@adyen/ui-assets-icons-16/vue/chevron-left'; import ChevronRightIcon from '@adyen/ui-assets-icons-16/vue/chevron-right'; import messages from './messages.json'; type MessageSchema = (typeof messages)['en-US']; const { t } = useI18n<{ message: MessageSchema }>({ messages }); const contentRef = ref(null); const fixedScrollerRef = ref(null); const leftButtonRef = ref(null); const rightButtonRef = ref(null); const props = defineProps({ /** * Applies the condensed style to the buttons */ condensed: { type: Boolean, default: false }, /** * Padding to be applied to the top of the scroller container */ extraPaddingTop: { type: Number, default: 0 }, /** * Padding to be applied to the top of the scroller container */ centered: { type: Boolean, default: false }, }); const { offset, shouldShowLeftButton, shouldShowRightButton, scrollLeft, scrollRight } = useFixedScroller({ contentRef, containerRef: fixedScrollerRef, leftButtonRef, rightButtonRef, }); const buttonConditionalClasses = computed(() => ({ 'b-fixed-scroller__button--centered': props.centered, })); </script> <script lang="ts"> /** * Allows to horizontally scroll content by one view at a time. * * @example * import { FixedScroller } from '@/internal'; * * export default { * components: { FixedScroller }, * template: ` * <fixed-scroller> * <div style="display: flex; gap: 8px;"> * {{ Content here }} * </div> * </fixed-scroller> * ` * } */ export default { i18n: { messages }, }; </script> <style lang="scss" scoped src="./fixed-scroller.scss" />
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<template> <div class="b-footer-actions"> <div class="b-footer-actions__wrapper"> <bento-typography v-if="actionsLabel" class="b-footer-actions__label" el="span" variant="body" stronger> {{ actionsLabel }} </bento-typography> <bento-button-actions v-if="alignedActions.leftAligned" class="b-footer-actions__left-group" :actions="alignedActions.leftAligned" layout="buttons-start" ></bento-button-actions> <bento-button-actions class="b-footer-actions__right-group" :actions="alignedActions.rightAligned" layout="buttons-end" ></bento-button-actions> </div> </div> </template> <script setup lang="ts"> import { BentoTypography } from '@/components/typography'; import { computed, onMounted, type PropType, toRefs } from 'vue'; import { BentoButtonActions, type BentoButtonActionsList, BentoButtonVariant } from '@/components/button'; const props = defineProps({ /** * List of actions that will be used to render the buttons. * First element in the list will be rendered as "primary". * All elements after the first will be rendered as "secondary". * But you may change the color of 1-st action to red with 'critial' key * Not more than 3 action buttons can be provided * Or 2 action buttons and 1 Label for them * * Each object in the list should have a `title`, an `event` and * (optional) a Vue `icon` from the library `@adyen/ui-assets-icons-16`. * * `title`: Button's text. * * `event`: Event triggered when clicking the Button. * * `icon`: Vue Icon from the library `@adyen/ui-assets-icons-16`. * * ``` * [{ * title: 'Primary',2 * event: () => { ... }, * icon: FourPeopleIcon * }, { * title: 'Secondary', * event: () => { ... } * }] * ``` */ actions: { type: Array as PropType<BentoButtonActionsList>, required: true, }, /** * Text label that is displayed left to the action buttons */ actionsLabel: { type: String, default: null, }, /** * If passed - sets first button color to red */ destructiveActions: { type: Boolean, default: false, }, }); const { actions, destructiveActions } = toRefs(props); const alignedActions = computed(() => { const rightAlignedButtons = actions.value.length < 3 ? actions.value : actions.value.slice(0, actions.value.length - 1); // Add red color to the 1-st Action button rightAlignedButtons[0].critical = destructiveActions.value || rightAlignedButtons[0].critical; return { leftAligned: actions.value.length === 3 ? [ { ...actions.value[actions.value.length - 1], variant: BentoButtonVariant.SECONDARY, }, ] : null, rightAligned: [...rightAlignedButtons], }; }); onMounted(() => { if (props.actions && props.actions.length > 3) { throw new Error('Not more than 3 action buttons may be provided to Bento Modal default variant'); } if (props.actionsLabel && props.actions && props.actions.length > 2) { throw new Error( 'Not more than 2 action buttons may be provided with actions label to Bento Modal default variant' ); } }); </script> <script lang="ts"> /** * Internal component to handle the logic of displaying actions in default Modal and Sidepanel * @example * import { FooterActions } from '@/components/internal/footer-actions'; * * export default { * components: { FooterActions }, * template: ` * <footer-actions :actions="BentoButtonActionsList" actions-label="Label" :destructive-actions="true" /> * ` * } */ export default {}; </script> <style lang="scss" scoped src="./footer-actions.scss" />
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export enum GridLayoutContainerType { INLINE = 'inline-size', SIZE = 'size', NORMAL = 'normal', } export interface GridLayoutColumnWidth { small: number | string; medium: number | string; large: number | string; } export type GridLayoutColumnWidths = Array<'auto' | GridLayoutColumnWidth>;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<template> <div class="b-grid-layout" :class="computedClasses" data-testid="grid-layout"> <div v-if="hasSlot('col-1')" class="b-grid-layout__column" :class="computeColumnSize(0)" data-testid="grid-layout-col-1" > <slot name="col-1"></slot> </div> <div v-if="hasSlot('col-2')" class="b-grid-layout__column" :class="computeColumnSize(1)" data-testid="grid-layout-col-2" > <slot name="col-2"></slot> </div> <div v-if="hasSlot('col-3')" class="b-grid-layout__column" :class="computeColumnSize(2)" data-testid="grid-layout-col-3" > <slot name="col-3"></slot> </div> <div v-if="hasSlot('col-4')" class="b-grid-layout__column" :class="computeColumnSize(3)" data-testid="grid-layout-col-4" > <slot name="col-4"></slot> </div> <div v-if="hasSlot('col-5')" class="b-grid-layout__column" :class="computeColumnSize(4)" data-testid="grid-layout-col-5" > <slot name="col-5"></slot> </div> <div v-if="hasSlot('col-6')" class="b-grid-layout__column" :class="computeColumnSize(5)" data-testid="grid-layout-col-6" > <slot name="col-6"></slot> </div> </div> </template> <script lang="ts"> import { computed, defineComponent, type PropType } from 'vue'; import { useHasSlot } from '@/composables'; import { type GridLayoutColumnWidth, type GridLayoutColumnWidths, GridLayoutContainerType } from './grid-layout.types'; /** * Component used to render the grid layout in the bento-summary-grid. For internal use only */ export default defineComponent({ name: 'grid-layout', props: { /** * Sets the responsive columns widths for the grid. */ columnWidth: { type: Array as PropType<GridLayoutColumnWidths>, default: null, validator: (array: GridLayoutColumnWidths) => array && array.length <= 6, }, /** * Set the type of CSS container the grid layout has * @values inline-size, size, normal */ containerType: { type: String as PropType<GridLayoutContainerType>, default: GridLayoutContainerType.INLINE, }, /** * Sets the gap in the grid to 0. */ noGap: { type: Boolean, default: false, }, }, setup(props, { slots }) { const hasSlot = useHasSlot(slots); // TODO: add a computedItems computed here that only returns the slots that are presents so as to be able to render the divs in the template via a v-for const computeColumnSize = index => { if (props.columnWidth?.[index] && props.columnWidth?.[index] !== 'auto') { const columnWidth = props.columnWidth[index] as GridLayoutColumnWidth; return [ `b-grid-layout__column--${columnWidth.small}`, `b-grid-layout-m__column--${columnWidth.medium}`, `b-grid-layout-l__column--${columnWidth.large}`, ]; } return null; }; const computedClasses = computed(() => ({ 'b-grid-layout--container-type-normal': props.containerType === GridLayoutContainerType.NORMAL, 'b-grid-layout--container-type-size': props.containerType === GridLayoutContainerType.SIZE, 'b-grid-layout--all-auto': props.columnWidth === null || props.columnWidth.every(width => width === 'auto'), 'b-grid-layout--no-gap': props.noGap, })); return { // Values computeColumnSize, computedClasses, // Methods hasSlot, }; }, }); </script> <style lang="scss" scoped src="./grid-layout.scss" />
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import { type BentoHeaderReloadButton } from '../../header/header.types'; export interface HeaderMetaProps { /** * Meta description for the header component */ description?: string; /** * JavaScript "Date" object for when the content was last updated */ lastUpdated?: Date | number; /** * Reload button which shows next to the header's timestamp * @see BentoHeaderReloadButton */ reloadButton?: BentoHeaderReloadButton; }
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<template> <div class="b-header-meta__container"> <bento-typography v-if="description || hasSlot('description')" class="b-header-meta__description" el="div"> <slot name="description">{{ description }}</slot> </bento-typography> <div v-if="lastUpdated" class="b-header-meta__timestamp-section"> <bento-typography class="b-header-meta__timestamp" el="div"> <time :datetime="lastUpdatedDateTime">{{ timestamp }}</time> </bento-typography> <bento-loading-button v-if="reloadButton" variant="tertiary" :state="reloadButton.state" @click="reloadButton.event" ><template #iconLeft><reload-icon aria-hidden="true" /></template >{{ reloadButton.title }}</bento-loading-button > </div> </div> </template> <script setup lang="ts"> import { computed, useSlots } from 'vue'; import { useDynamicDateFnsLocale, useHasSlot } from '@/composables'; import { formatDistanceToNow } from 'date-fns/formatDistanceToNow'; import { BentoTypography } from '@/components/typography'; import { BentoLoadingButton } from '@/components/button'; import ReloadIcon from '@adyen/ui-assets-icons-16/vue/refresh'; import { useI18n } from '@/utils/ts/i18n'; import type { HeaderMetaProps } from './header-meta.types'; import messages from './messages.json'; type MessageSchema = (typeof messages)['en-US']; const { t, locale } = useI18n<{ message: MessageSchema }>({ messages }); const props = withDefaults(defineProps<HeaderMetaProps>(), { description: null, lastUpdated: null, reloadButton: null, }); const activeLocale = computed(() => (props.lastUpdated ? locale.value : '')); const dateLocale = useDynamicDateFnsLocale(activeLocale); const lastUpdatedDateTime = computed(() => { if (!props.lastUpdated) { return undefined; } const date = props.lastUpdated instanceof Date ? props.lastUpdated : new Date(props.lastUpdated); return Number.isNaN(date.getTime()) ? undefined : date.toISOString(); }); const timestamp = computed(() => props.lastUpdated ? t('lastUpdated', { date: formatDistanceToNow(props.lastUpdated, { locale: dateLocale.value, addSuffix: true, }), }) : null ); const slots = useSlots(); const hasSlot = useHasSlot(slots); </script> <script lang="ts"> /** * An internal component used by the BentoHeader and BentoHeaderWithViews components. * * @example * import { HeaderMeta } from '@/internal'; * * export default { * components: { HeaderMeta }, * template: ` * <header-meta description="header description" :last-updated="new Date()" /> * ` * } * @slot description - The description content. Overrides the `description` prop. * @property description - Text showed under the title. * @property lastUpdated - JavaScript "Date" object for when the content was last updated. * @property reloadButton - Reload button which shows next to the header's timestamp. */ export default { i18n: { messages }, }; </script> <style lang="scss" scoped src="./header-meta.scss" />
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<template> <div class="b-info-icon-with-popover" tabindex="0" data-testid="info-icon-with-popover" v-on="hoverEvents"> <info-icon ref="targetElement" class="b-info-icon-with-popover__svg" :aria-expanded="isOpen" :aria-describedby="popoverId" :svg-title="t('moreInformation')" /> <bento-popover v-if="targetElement" :open="isOpen" :target-element="targetElement" :position="popoverPosition" role="tooltip" fit-content disable-focus-trap > <bento-typography> <slot> <div v-if="popoverText"> {{ popoverText }} </div> </slot> </bento-typography> </bento-popover> </div> </template> <script setup lang="ts"> import { BentoPopover, BentoPopoverPositions } from '@/components/popover'; import { useBentoDelayedHover } from '@/composables'; import { BentoTypography } from '@/components/typography'; import { generateUid } from '@/core/utils/ts'; import { useI18n } from '@/utils/ts/i18n'; import InfoIcon from '@adyen/ui-assets-icons-16/vue/info'; import { type PropType, ref } from 'vue'; import type { BentoPopoverProps } from '../../popover/popover.types'; import messages from './messages.json'; type MessageSchema = (typeof messages)['en-US']; defineProps({ /** * Text showed in the popover on hover */ popoverText: { type: String, default: null }, /** * Position the popover should take if space is available */ popoverPosition: { type: String as PropType<BentoPopoverProps['position']>, default: 'bottom-start', validator: (value: BentoPopoverPositions) => Object.values(BentoPopoverPositions).includes(value), }, }); const { t } = useI18n<{ message: MessageSchema }>({ messages }); const targetElement = ref(null); const popoverId = generateUid('bento-info-icon-with-popover'); const { isOpen, hoverEvents } = useBentoDelayedHover(); </script> <script lang="ts"> /** * Internal component to be used whenever an info icon needs to be rendered with a popover instead of a tooltip. * * @example * import { BentoInfoIconWithPopover } from '@adyen/bento-vue2'; * * export default { * components: { BentoInfoIconWithPopover }, * template: ` * <info-icon-with-popover popover-text="Content can be passed as a string like" :popover-position="position"> * Content can also be rendered as a slot * </info-icon-with-popover> * ` * } */ export default { i18n: { messages }, }; </script> <style lang="scss" scoped src="./info-icon-with-popover.scss" />
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import { type BentoLazyLoadType } from '@/types/lazy-load'; export interface BentoLazyLoadProps { /** * Indicates if the 'Show more' button is using the condensed variant * @default false */ condensed?: boolean; /** * Indicates whether there are more items to load * @default false */ hasMoreItems?: boolean; /** * Indicates if new options are lazy loading. * @default false */ loading?: boolean; /** * Sets the tabindex to the button. * Used for the treeitem role */ tabindex?: number; /** * The type of `Lazy Load`. * Type "automatic" will enable infinite scrolling * Type "button" will enable lazy loading with "Show more" button */ type: BentoLazyLoadType; }
|
package/dist/assets/components/internal/listbox/components/listbox-lazy-load/listbox-lazy-load.vue
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<template> <div v-if="shouldDisplayShowMoreButton || loading" class="b-listbox-lazy-load"> <bento-loading-button v-if="shouldDisplayShowMoreButton" :state="loadingButtonState" :tabindex="tabindex" :condensed="condensed" variant="secondary" @click.capture="$emit('show-more', $event)" > {{ showMoreButtonText }} </bento-loading-button> <bento-loading-indicator v-else-if="loading" /> </div> </template> <script setup lang="ts"> import { computed } from 'vue'; import { BentoListboxLazyLoadType } from '@/types/listbox'; import { BentoLoadingIndicator } from '@/components/loading-indicator'; import { BentoLoadingButton, BentoLoadingButtonState } from '@/components/button'; import { useI18n } from '@/utils/ts/i18n'; import { type BentoLazyLoadProps } from './listbox-lazy-load.types'; import messages from './messages.json'; type MessageSchema = (typeof messages)['en-US']; const { t } = useI18n<{ message: MessageSchema }>({ messages }); defineEmits<{ /** * Emitted when the "Show more" button is clicked to request loading additional items. */ (e: 'show-more', event: Event); }>(); const props = withDefaults(defineProps<BentoLazyLoadProps>(), { condensed: false, hasMoreItems: false, loading: false, tabindex: undefined, }); const shouldDisplayShowMoreButton = computed( () => props.type === BentoListboxLazyLoadType.BUTTON && props.hasMoreItems ); const loadingButtonState = computed<BentoLoadingButtonState>(() => props.loading ? BentoLoadingButtonState.LOADING : BentoLoadingButtonState.START ); const showMoreButtonText = computed(() => (props.loading ? t('loading') : t('showMore'))); </script> <script lang="ts"> export default { name: 'listbox-lazy-load', i18n: { messages }, }; </script> <style lang="scss" scoped src="./listbox-lazy-load.scss" />
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export enum ListboxMultiSelectCategoryClass { DISABLED = 'disabled', STATIC_CATEGORY = 'static-category', } export enum ListboxMultiSelectCategoryEvent { SELECT_CATEGORY = 'select-category', UNSELECT_CATEGORY = 'unselect-category', TOGGLE_CATEGORY_VISIBILITY = 'toggle-category-visibility', }
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<template> <div> <bento-listbox-option v-if="!isStaticCategory" class="b-listbox-multi-select-category__option" :class="conditionalClass" :tabindex="tabindex" :aria-selected="ariaCheckedComputed !== 'false'" :aria-checked="ariaCheckedComputed" :aria-disabled="hasAllPropsDisabled" :aria-expanded="`${isOpen}`" role="treeitem" :aria-owns="categoryGroupId" :aria-labelledby="categoryNameId" @click="handleSelectAll" @enter-pressed="handleSelectAll" @space-pressed.prevent="handleSelectAll" @keydown.arrow-left.native="toggleOpen($event, false)" @keydown.arrow-right.native="toggleOpen($event, true)" > <decorative-checkbox :indeterminate="isIndeterminate" :disabled="hasAllPropsDisabled" :checked="isAllSelected" /> <span class="b-listbox-multi-select-category__label"> <bento-typography :id="categoryNameId" el="span" class="b-listbox-multi-select-category__label-text"> {{ category.label }} </bento-typography> <div aria-hidden="true" class="b-listbox-multi-select-category__label-chevron" data-testid="toggle-listbox" @click.capture.stop="toggleOpen" > <chevron-up-icon v-if="isOpen" :svg-title="chevronTitle" /> <chevron-down-icon v-else :svg-title="chevronTitle" /> </div> </span> </bento-listbox-option> <div v-else class="b-listbox-multi-select-category b-listbox-multi-select-category--static-category" aria-hidden="true" > <span class="b-listbox-multi-select-category__label"> <bento-typography el="span" variant="caption" class="b-listbox-multi-select-category__label-text"> {{ category.label }} </bento-typography> </span> </div> <!-- Nested options --> <div v-show="showItemChildren" :id="categoryGroupId" role="group" :aria-label="category.label"> <slot /> <!-- Nested items Lazy load --> <template v-if="category.lazyLoad?.hasMoreItems"> <bento-listbox-multi-select-nested-lazy-load ref="lazyLoadRef" :option="category" :index="optionIndex" /> </template> </div> </div> </template> <script lang="ts"> import { computed, defineComponent, type PropType, ref, toRef, watch } from 'vue'; import { BentoTypography } from '@/components/typography'; import { BentoListboxOption } from '../../../../../listbox-option'; import { DecorativeCheckbox } from '@/internal/decorative-checkbox'; import { type BentoListboxIsOptionDisabled, type BentoListboxMultiSelectValue, type BentoListboxOptionItem, type BentoListboxOptions, type BentoListboxValue, } from '@/types/listbox'; import { useCheckboxSelectAll } from '@/composables'; import { ListboxMultiSelectCategoryClass, ListboxMultiSelectCategoryEvent, } from './listbox-multi-select-category.types'; import { useI18n } from '@/utils/ts/i18n'; import { generateUid } from '@/core/utils/ts'; import { BentoListboxMultiSelectNestedLazyLoad } from '../listbox-multi-select-nested-lazy-load'; import ChevronDownIcon from '@adyen/ui-assets-icons-16/vue/chevron-down'; import ChevronUpIcon from '@adyen/ui-assets-icons-16/vue/chevron-up'; import messages from './messages.json'; type MessageSchema = (typeof messages)['en-US']; /** * Listbox multi-select category list. * Allows the rendering of nested lists * * @example * import { ListboxMultiSelectCategory } from '@/internal/listbox'; * * export default { * components: { BentoListboxMultiSelectCategory }, * template: ` * <listbox-multi-select-category * :category="option" * :is-option-disabled="true" * :selected-items-list="['option-1']" * @select-category="$emit('select-category', option)" * @unselect-category="$emit('unselect-category', option)" * > * <listbox-multi-select-options * category-items * :items="[{ label: 'Option 1', value: 'option-1' }]" * :is-multi-level="true" * :selected-items-list="['option-']" * :searching="false" * @toggle-checkbox-selection="value => toggleCheckboxSelection(value)" * /> * </listbox-multi-select-category> * ` * } */ export default defineComponent({ i18n: { messages }, name: 'bento-listbox-multi-select-category', components: { BentoTypography, BentoListboxOption, BentoListboxMultiSelectNestedLazyLoad, ChevronDownIcon, ChevronUpIcon, DecorativeCheckbox, }, props: { /** * The option elements to populate the listbox with. * It must be an array of {@see BentoListboxOptionItem }. * * @property {string} value.label - Text to be displayed in the option * @property {string,number} value.value - Value of the option * @property {BentoListboxOptionItem} value.items - Category items list */ category: { type: Object as PropType<BentoListboxOptionItem>, required: true, }, /** * Function that allows the options to be disabled * * @type {BentoListboxIsOptionDisabled} * @param {BentoListboxOptionItem} option - listbox option object * @param {string} option.label - Option's label text * @param {string|number} option.value - Option's value * @param {unknown} option.data - Option's extra data to be passed to the default slot */ isOptionDisabled: { type: [Function] as PropType<BentoListboxIsOptionDisabled>, default: () => false, }, /** * Option's index within the Listbox `item` array. */ optionIndex: { type: Number, default: null, }, /** * The `input` value. * Providing an empty array will select no options. */ selectedItemsList: { type: Array as PropType<Array<BentoListboxValue>>, default: () => [], }, /** * If enabled, dropdown will display non-selectable, static categories */ staticCategories: { type: Boolean, default: false }, /** * Category's tabindex */ tabindex: { type: Number, default: -1 }, }, emits: [ ListboxMultiSelectCategoryEvent.SELECT_CATEGORY, ListboxMultiSelectCategoryEvent.UNSELECT_CATEGORY, ListboxMultiSelectCategoryEvent.TOGGLE_CATEGORY_VISIBILITY, ], setup(props, { emit }) { const isOpen = ref(false); const { t } = useI18n<{ message: MessageSchema }>({ messages }); const categoryGroupId = generateUid('category-group'); const categoryNameId = generateUid('category-name'); const lazyLoadRef = ref(null); // This method should be used to determine if an option is disabled as it works for both vue 2 & 3. const vue2Vue3FixedisOptionDisabled = option => props.isOptionDisabled ? props.isOptionDisabled(option) : false; const hasAllPropsDisabled = computed(() => { // Do not disable empty categories if they are lazy loaded if (props.category.lazyLoad && props.category.items?.length === 0) { return false; } return props.category.items.every(option => vue2Vue3FixedisOptionDisabled(option)); }); const isStaticCategory = toRef(props, 'staticCategories'); // Display item children if is open state or if static categories is enabled const showItemChildren = computed(() => isOpen.value || props.staticCategories); const conditionalClass = computed(() => ({ [`b-listbox-multi-select-category--${ListboxMultiSelectCategoryClass.DISABLED}`]: hasAllPropsDisabled.value, })); const toggleOpen = (event: Event, open?: boolean) => { event.preventDefault(); // Load lazy categories if the items list is empty (0 items) when category is opened if (props.category.lazyLoad && props.category.items?.length === 0) { lazyLoadRef.value.showMore(event); } isOpen.value = open !== undefined ? open : !isOpen.value; emit(ListboxMultiSelectCategoryEvent.TOGGLE_CATEGORY_VISIBILITY, props.category.value); }; const optionStates = (option: BentoListboxOptionItem) => { const isDisabled = vue2Vue3FixedisOptionDisabled(option); const isPreSelected = (props.selectedItemsList as BentoListboxMultiSelectValue)?.includes(option.value) || false; return { isDisabled, isPreSelected }; }; const enabledItems = computed(() => props.category.items.filter(option => { const { isDisabled, isPreSelected } = optionStates(option); return (isDisabled && isPreSelected) || !isDisabled; }) ); const visibleSelectedList = computed<BentoListboxOptions>(() => enabledItems.value?.filter(({ value }) => props.selectedItemsList?.includes(value)) ); const chevronTitle = computed(() => { const messageName = isOpen.value ? 'categoryOpened' : 'categoryClosed'; return t(messageName, { category: props.category.label }) as string; }); const selectAll = () => { emit(ListboxMultiSelectCategoryEvent.SELECT_CATEGORY); }; const unselectAll = () => { emit(ListboxMultiSelectCategoryEvent.UNSELECT_CATEGORY); }; const isLazySelectedByValue = computed( () => !!props.category.lazyLoad && props.selectedItemsList.includes(props.category.value) ); const { isAllSelected: isAllChildrenSelected, isIndeterminate: isChildrenIndeterminate, toggleSelectAll, } = useCheckboxSelectAll(visibleSelectedList, enabledItems, selectAll, unselectAll); const isAllSelected = computed(() => isLazySelectedByValue.value || isAllChildrenSelected.value); const isIndeterminate = computed(() => !isLazySelectedByValue.value && isChildrenIndeterminate.value); const ariaCheckedComputed = computed(() => { if (isIndeterminate.value) { return 'mixed'; } return `${isAllSelected.value}`; }); const isSelectAllOn = ref(isAllSelected.value); const handleSelectAll = () => { isSelectAllOn.value = !isSelectAllOn.value; toggleSelectAll(isSelectAllOn.value); }; watch(isAllSelected, () => { isSelectAllOn.value = isAllSelected.value; }); return { // Refs lazyLoadRef, // Values chevronTitle, conditionalClass, hasAllPropsDisabled, isOpen, isAllSelected, isIndeterminate, isStaticCategory, categoryGroupId, categoryNameId, // Computed showItemChildren, ariaCheckedComputed, // Methods handleSelectAll, toggleOpen, toggleSelectAll, }; }, }); </script> <style lang="scss" scoped src="./listbox-multi-select-category.scss" />
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import type { BentoListboxOptionItem } from '@/types/listbox'; export interface ListboxMultiSelectNestedLazyLoadProps { /** * Index of the option that triggers the nested lazy-load event. */ index: number; /** * Option that contains the nested lazy-load configuration. */ option: BentoListboxOptionItem; }
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<template> <div role="treeitem" :tabindex="0" @keydown.enter.prevent="showMore" @keydown.space.prevent="showMore"> <bento-empty-state v-if="failedAndRetry" variant="basic" class="b-listbox-multi-select-nested-lazy-load__error-message" > {{ t('thereWasAnErrorLoadingNewItems') }} </bento-empty-state> <bento-listbox-lazy-load type="button" :tabindex="-1" condensed :has-more-items="option.lazyLoad?.hasMoreItems" :loading="loading" @show-more="showMore" /> </div> </template> <script setup lang="ts"> import { ref } from 'vue'; import { BentoEmptyState } from '@/components/empty-state'; import { BentoListboxLazyLoad } from '@/internal/listbox/components/listbox-lazy-load'; import { useI18n } from '@/utils/ts/i18n'; import type { ListboxMultiSelectNestedLazyLoadProps } from './listbox-multi-select-nested-lazy-load.types'; import messages from './messages.json'; type MessageSchema = (typeof messages)['en-US']; const loading = ref(false); const failedAndRetry = ref(false); const { t } = useI18n<{ message: MessageSchema }>({ messages }); const props = withDefaults(defineProps<ListboxMultiSelectNestedLazyLoadProps>(), {}); async function showMore(event: Event) { event.stopImmediatePropagation(); if (loading.value) { return; } failedAndRetry.value = false; loading.value = true; try { await props.option.lazyLoad?.lazyLoadEvent({ value: props.option.value, index: props.index, }); } catch { failedAndRetry.value = true; } loading.value = false; } defineExpose({ /** * Trigger the lazy load event programmatically. */ showMore, }); </script> <script lang="ts"> export default { name: 'listbox-multi-select-nested-lazy-load', i18n: { messages }, }; </script> <style lang="scss" scoped src="./listbox-multi-select-nested-lazy-load.scss" />
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export enum ListboxMultiSelectOptionsEvent { SELECT_CATEGORY = 'select-category', TOGGLE_CHECKBOX_SELECTION = 'toggle-checkbox-selection', UNSELECT_CATEGORY = 'unselect-category', TOGGLE_CATEGORY_VISIBILITY = 'toggle-category-visibility', }
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<template> <div> <div v-for="option in items" :key="`selected-${option.value}`"> <template v-if="searching && isMultiLevel"> <bento-listbox-option class="b-listbox-multi-select-options" :class="conditionalClasses" :disabled="isOptionDisabled ? isOptionDisabled(option) : null" :tabindex="computedTabindex(option.value)" :aria-selected="`${isOptionSelected(option)}`" :aria-checked="`${isOptionSelected(option)}`" :aria-disabled="isOptionDisabled ? isOptionDisabled(option) : null" :role="$attrs['role'] || 'option'" @click="toggleSearchOption(option)" @enter-pressed="toggleSearchOption(option)" @space-pressed.prevent="toggleSearchOption(option)" > <decorative-checkbox :disabled="isOptionDisabled ? isOptionDisabled(option) : null" :checked="isOptionSelected(option)" /> <slot v-bind="option"> <span class="b-listbox-multi-select__option-text"> <bento-typography el="span">{{ option.label }}</bento-typography> <bento-typography v-if="shouldShowDescription(option)" class="b-listbox-multi-select__option-description" el="span" > {{ option.description }} </bento-typography> </span> </slot> </bento-listbox-option> </template> <template v-else> <!-- Category --> <template v-if="option.items"> <listbox-multi-select-category :category="option" :static-categories="staticCategories" :is-option-disabled="isOptionDisabled" :selected-items-list="selectedItemsList" :option-index="itemsIndex[option.value]" :tabindex="computedTabindex(option.value)" @select-category="$emit(ListboxMultiSelectOptionsEvent.SELECT_CATEGORY, option)" @unselect-category="$emit(ListboxMultiSelectOptionsEvent.UNSELECT_CATEGORY, option)" @toggle-category-visibility=" $emit(ListboxMultiSelectOptionsEvent.TOGGLE_CATEGORY_VISIBILITY, $event) " > <listbox-multi-select-options category-items :items="option.items" :is-option-disabled="isOptionDisabled" :items-index="itemsIndex" :focused-index="focusedIndex" :is-multi-level="isMultiLevel" :static-categories="staticCategories" :selected-items-list="selectedItemsList" :searching="searching" :parent-category="option" :role="$attrs['role'] || 'option'" @toggle-checkbox-selection="value => toggleCheckboxSelection(value)" /> </listbox-multi-select-category> </template> <!-- Option --> <bento-listbox-option v-else class="b-listbox-multi-select-options" :class="conditionalClasses" :disabled="isOptionDisabled ? isOptionDisabled(option) : null" :tabindex="computedTabindex(option.value)" :aria-selected="`${isOptionSelected(option)}`" :aria-checked="`${isOptionSelected(option)}`" :aria-disabled="isOptionDisabled ? isOptionDisabled(option) : null" :role="$attrs['role'] || 'option'" @click="toggleCheckboxSelection(option)" @enter-pressed="toggleCheckboxSelection(option)" @space-pressed.prevent="toggleCheckboxSelection(option)" > <decorative-checkbox :disabled="isOptionDisabled ? isOptionDisabled(option) : null" :checked="isOptionSelected(option)" /> <slot v-bind="option"> <span class="b-listbox-multi-select__option-text"> <bento-typography el="span">{{ option.label }}</bento-typography> <bento-typography v-if="shouldShowDescription(option)" class="b-listbox-multi-select__option-description" el="span" > {{ option.description }} </bento-typography> </span> </slot> </bento-listbox-option> </template> </div> </div> </template> <script lang="ts"> import { computed, defineComponent, type PropType } from 'vue'; import { BentoListboxOption } from '../../../listbox-option'; import { BentoTypography } from '@/components/typography'; import { ListboxMultiSelectCategory } from './components/listbox-multi-select-category'; import { DecorativeCheckbox } from '@/internal/decorative-checkbox'; import { BentoListboxEvent, type BentoListboxIsOptionDisabled, type BentoListboxOptionItem, type BentoListboxOptions, type BentoListboxValue, } from '@/types/listbox'; import { ListboxMultiSelectOptionsEvent } from './listbox-multi-select-options.types'; /** * Listbox multi-select options list * * @example * import { ListboxMultiSelectOptions } from './components/listbox-multi-select-options'; * * export default { * components: { ListboxMultiSelect }, * template: ` * <listbox-multi-select * :items="[{ label: 'Option 1', value: 'option-1' }]" * :selected-values="['option-1']" * /> * ` * } */ export default defineComponent({ name: 'listbox-multi-select-options', components: { BentoListboxOption, BentoTypography, ListboxMultiSelectCategory, DecorativeCheckbox, }, inheritAttrs: false, props: { /** * Indicates if the items listed are inside a category * so they can be padded to right by one level in recursion. * Items of categories are automatically defined as "categoryItems" */ categoryItems: { type: Boolean, default: false }, /** * Current focused option index */ focusedIndex: { type: Number, default: 0 }, /** * Function that allows the options to be disabled * * @type {BentoListboxIsOptionDisabled} * @param {BentoListboxOptionItem} option - listbox option object * @param {string} option.label - Option's label text * @param {string|number} option.value - Option's value * @param {unknown} option.data - Option's extra data to be passed to the default slot */ isOptionDisabled: { type: Function as PropType<BentoListboxIsOptionDisabled>, default: () => false, }, /** * The option elements to populate the listbox with. * It must be an array of {@see BentoListboxOptionItem }. * * @property {string} value.label - Text to be displayed in the option * @property {string,number} value.value - Value of the option */ items: { type: Array as PropType<BentoListboxOptions>, required: true, }, /** * Indicates if the elements options are inside a multi-level category */ isMultiLevel: { type: Boolean, default: false }, /** * Flag that indicates that an external search it's being made * to filter out the items inside the the multi-select listbox * * If enabled, hides the "Select all" checkbox in "multiple" mode. */ searching: { type: Boolean, default: false }, /** * List of selected items. * Providing an empty array will select no options. */ selectedItemsList: { type: Array as PropType<Array<BentoListboxValue>>, default: () => [], }, /** * If enabled, dropdown will display non-selectable, static categories */ staticCategories: { type: Boolean, default: false }, /** * Object with the index of each item. */ itemsIndex: { type: Object as PropType<Record<string, number>>, default: () => ({}), }, /** * The parent category of the items. */ parentCategory: { type: Object as PropType<BentoListboxOptionItem>, default: null, }, }, emits: [ ListboxMultiSelectOptionsEvent.TOGGLE_CHECKBOX_SELECTION, ListboxMultiSelectOptionsEvent.SELECT_CATEGORY, ListboxMultiSelectOptionsEvent.UNSELECT_CATEGORY, ListboxMultiSelectOptionsEvent.TOGGLE_CATEGORY_VISIBILITY, ], setup(props, { emit }) { const toggleCheckboxSelection = (option: BentoListboxOptionItem) => { if (props.isOptionDisabled(option)) { return; } emit(ListboxMultiSelectOptionsEvent.TOGGLE_CHECKBOX_SELECTION, option); }; const isCategory = (option: BentoListboxOptionItem) => !!option.items; const shouldShowDescription = (option: BentoListboxOptionItem) => { const isSearchingMultiLevel = props.isMultiLevel && props.searching; return option.description && (!props.isMultiLevel || isSearchingMultiLevel); }; const isOptionSelected = option => { const isParentLazySelected = !!props.parentCategory?.lazyLoad && props.selectedItemsList.includes(props.parentCategory.value); if (option.items) { const isLazyCategorySelected = !!option.lazyLoad && props.selectedItemsList.includes(option.value); return ( isParentLazySelected || isLazyCategorySelected || !!option.items.find(item => props.selectedItemsList.includes(item.value)) ); } return isParentLazySelected || props.selectedItemsList.includes(option.value); }; const toggleSearchOption = option => { if (isCategory(option)) { const event = isOptionSelected(option) ? ListboxMultiSelectOptionsEvent.UNSELECT_CATEGORY : ListboxMultiSelectOptionsEvent.SELECT_CATEGORY; emit(event, option); } else { toggleCheckboxSelection(option); } }; const conditionalClasses = computed(() => ({ 'b-listbox-multi-select-options__static-category-option': props.staticCategories, 'b-listbox-multi-select-options__category-option': props.categoryItems, })); const computedTabindex = value => (props.itemsIndex[value] === props.focusedIndex ? 0 : -1); return { // Values conditionalClasses, // Methods isOptionSelected, shouldShowDescription, toggleCheckboxSelection, toggleSearchOption, computedTabindex, // Enums BentoListboxEvent, ListboxMultiSelectOptionsEvent, }; }, }); </script> <style lang="scss" scoped src="./listbox-multi-select-options.scss" />
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import { type BentoListboxIsOptionDisabled, type BentoListboxLazyLoadType, type BentoListboxOptions } from '@/types/listbox'; export interface ListboxMultiSelectProps { /** * Indicates whether there are more items to load */ hasMoreItems?: boolean; /** * Function that allows the options to be disabled * * @type {BentoListboxIsOptionDisabled} * @param {BentoListboxOptionItem} option - listbox option object * @param {string} option.label - Option's label text * @param {string|number} option.value - Option's value * @param {unknown} option.data - Option's extra data to be passed to the default slot */ isOptionDisabled?: BentoListboxIsOptionDisabled; /** * The option elements to populate the listbox with. * It must be an array of {@see BentoListboxOptionItem }. * * @property {string} value.label - Text to be displayed in the option * @property {string,number} value.value - Value of the option */ items: BentoListboxOptions; /** * The type of `Lazy Load`. * Type "automatic" will enable infinite scrolling * Type "button" will enable lazy loading with "Show more" button */ lazyLoadType?: BentoListboxLazyLoadType | `${BentoListboxLazyLoadType}`; /** * Flag that indicates that an external search it's being made * to filter out the items inside the the multi-select listbox * * If enabled, hides the "Select all" checkbox in "multiple" mode. */ searching?: boolean; /** * The `input` value. * Providing an empty array will select no options. */ selectedValues: BentoListboxOptions; /** * If enabled, dropdown will display non-selectable, static categories */ staticCategories?: boolean; }
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<template> <div ref="listboxRef" class="b-listbox-multi-select"> <!-- Selected options --> <div v-if="showSelectedItems"> <ul v-bento-keyboard-navigation-directive class="b-listbox-multi-select__chips" v-on="chipsSectionListeners" > <li v-for="item in selectedOptions" :key="item.value"> <bento-chip ref="selectedListboxItemsRef" :disabled="isOptionDisabled(item)" :label="item.label" condensed @click="item.items ? unselectAllCategory(item) : toggleCheckboxSelection('select', item)" /> </li> </ul> <hr class="b-listbox-multi-select__divider" aria-hidden="true" /> </div> <div v-bento-keyboard-navigation-directive v-bind="ariaAttributes" :role="computedRole" v-on="listboxListeners"> <!-- Select all --> <bento-listbox-option v-if="showSelectAllCheckbox" ref="selectAllListboxItemsRef" class="b-listbox-multi-select__option" :tabindex="focusedIndex === 0 ? 0 : -1" :aria-selected="ariaCheckedComputed !== 'false'" :aria-checked="ariaCheckedComputed" :role="computedOptionRole" @click="handleSelectAll" @enter-pressed="handleSelectAll" @space-pressed.prevent="handleSelectAll" > <decorative-checkbox :checked="isAllSelected" :indeterminate="isIndeterminate" /> <bento-typography el="span"> {{ selectAllCheckboxText }} </bento-typography> </bento-listbox-option> <hr v-if="showSelectAllCheckbox" class="b-listbox-multi-select__divider" aria-hidden="true" /> <!-- Available options --> <listbox-multi-select-options ref="listboxItemsRef" :items="items" :is-option-disabled="isOptionDisabled" :is-multi-level="isMultiLevelSelect" :static-categories="staticCategories" :searching="searching" :selected-items-list="selectedItemsList" :focused-index="focusedIndex" :items-index="computedItemsIndex" :role="computedOptionRole" @toggle-checkbox-selection="toggleCheckboxSelection('select', $event)" @select-category="selectAllCategory" @unselect-category="unselectAllCategory" @toggle-category-visibility="toggleCategoryVisibility" @space-pressed="toggleCheckboxSelection(BentoListboxEvent.SPACE, $event)" @tab-pressed="toggleCheckboxSelection(BentoListboxEvent.TAB, $event)" @enter-pressed="toggleCheckboxSelection(BentoListboxEvent.ENTER, $event)" > <template v-for="(_, innerSlot) of slots" #[innerSlot]="scope"> <slot :name="innerSlot" v-bind="scope" /> </template> </listbox-multi-select-options> </div> </div> </template> <script setup lang="ts"> import { computed, type HTMLAttributes, nextTick, ref, toRef, useAttrs, useSlots, watch } from 'vue'; import { isVue2 } from 'vue-demi'; import { DecorativeCheckbox } from '@/internal/decorative-checkbox'; import { BentoTypography } from '@/components/typography'; import { BentoChip } from '@/components/chip'; import { BentoListboxOption } from '../listbox-option'; import { ListboxMultiSelectOptions } from './components/listbox-multi-select-options'; import { useCheckboxSelectAll } from '@/composables'; import { useListboxKeyboardNavigation } from '../../composables/useListboxKeyboardNavigation'; import { useMultiLevelItems } from '../../composables/use-multi-level-items/use-multi-level-items'; import { useVisibleDomOptions } from '@/internal/listbox/composables/use-visible-dom-options/use-visible-dom-options'; import { useI18n } from '@/utils/ts/i18n'; import { BentoKeyboardNavigationDirective as vBentoKeyboardNavigationDirective } from '@/directives'; import { BentoListboxEvent, type BentoListboxMultiSelectValue, type BentoListboxOptionItem, type BentoListboxOptions, type BentoListboxValue, } from '@/types/listbox'; import { type Booleanish } from '@/types/prop-types'; import { BentoKeyboardNavigationKeyDownEvent } from '@/types/keyboard-navigation'; import { type ListboxMultiSelectProps } from './listbox-multi-select.types'; import messages from './messages.json'; type MessageSchema = (typeof messages)['en-US']; const props = withDefaults(defineProps<ListboxMultiSelectProps>(), { hasMoreItems: false, isOptionDisabled: isVue2 ? () => () => false : () => false, lazyLoadType: 'none', searching: false, staticCategories: false, }); const emit = defineEmits<{ /** * Emits when the selection changes */ (e: 'select', value: Array<BentoListboxOptionItem>): void; }>(); const { t, n } = useI18n<{ message: MessageSchema }>({ messages }); const slots = useSlots(); const attrs = useAttrs(); const ariaAttributes = computed( () => ({ 'aria-label': attrs['aria-label']?.toString() as string, 'aria-busy': attrs['aria-busy']?.toString() as Booleanish, 'aria-atomic': attrs['aria-atomic']?.toString() as Booleanish, 'aria-live': attrs['aria-live']?.toString() as string, 'aria-multiselectable': true, }) as HTMLAttributes ); const listboxRef = ref(null); const selectAllListboxItemsRef = ref(null); const selectedListboxItemsRef = ref(null); const listboxItemsRef = ref(null); const selectedItemsList = computed<Array<BentoListboxValue>>(() => props.selectedValues.map(({ value }) => value)); const { isMultiLevelSelect, flattenedItemsList } = useMultiLevelItems(toRef(props, 'items')); const hasLazyLoadCategory = computed(() => props.items.some(item => item.items && item.lazyLoad)); const lazyLoadAwareFlattenedItems = computed(() => { if (!isMultiLevelSelect.value) { return props.items; } return props.items.reduce<BentoListboxOptions>((acc, item) => { if (item.items) { if (item.lazyLoad) { acc.push(item); } else { acc.push(...item.items); } } else { acc.push(item); } return acc; }, []); }); const isTreePattern = computed(() => isMultiLevelSelect.value && !props.staticCategories); const computedRole = computed(() => (isTreePattern.value ? 'tree' : 'listbox')); const computedOptionRole = computed(() => (isTreePattern.value ? 'treeitem' : 'option')); const optionStates = (option: BentoListboxOptionItem) => { const isDisabled = props.isOptionDisabled(option); const isPreSelected = (selectedItemsList.value as BentoListboxMultiSelectValue)?.includes(option.value) || false; return { isDisabled, isPreSelected }; }; // Items that are not disabled and pre-selected disabled items const enabledItems = computed(() => { if (props.searching && isMultiLevelSelect) { return props.items; } const fullItemsList = lazyLoadAwareFlattenedItems.value.filter(option => { if (option.lazyLoad) { return true; } const { isDisabled, isPreSelected } = optionStates(option); return (isDisabled && isPreSelected) || !isDisabled; }); props.selectedValues.forEach(selectedValue => { if (!fullItemsList.find(item => item.value === selectedValue.value)) { fullItemsList.push(selectedValue); } }); return fullItemsList; }); const visibleSelectedList = computed<BentoListboxOptions>(() => enabledItems.value?.filter(option => { return selectedItemsList.value?.includes(option.value); // Check for category }) ); const calculateTotalItems = () => { if (isMultiLevelSelect.value && !props.searching) { return flattenedItemsList.value.length; } return props.items.length; }; const selectAllCheckboxText = computed(() => { const possibleSelections = enabledItems.value.length; const total = calculateTotalItems(); const amountSelectedValues = visibleSelectedList.value?.length || 0; if (amountSelectedValues === 0) { return t('selectAll', { total: n(possibleSelections) }); } if (amountSelectedValues === total) { return t('allSelected', { total: n(total) }); } return amountSelectedValues <= total ? t('numberOfTotalSelected', { number: n(amountSelectedValues), total: n(total) }) : t('numbersSelected', { number: n(amountSelectedValues) }); }); const selectAll = (selectableItems: BentoListboxOptions) => { const filterOutDisabledAndNotPreselectedOptions = option => { const { isDisabled, isPreSelected } = optionStates(option); return (isDisabled && isPreSelected) || !isDisabled; }; // Adding or removing this list of values const addOrRemoveList = selectableItems .filter(filterOutDisabledAndNotPreselectedOptions) .filter(item => !selectedItemsList.value.includes(item.value)); // MAKE SURE NOT TO ADD DUPLICATES emit('select', [...props.selectedValues, ...addOrRemoveList]); }; const unselectAll = (selectableItems: BentoListboxOptions) => { const findEnabledUnselectedItems = option => { const { isDisabled, isPreSelected } = optionStates(option); return !(isDisabled && isPreSelected); }; const unselectedItemsList = selectableItems.filter(findEnabledUnselectedItems).map(({ value }) => value); const newSelectedList = props.selectedValues.filter(item => !unselectedItemsList?.includes(item.value)); emit('select', newSelectedList); }; const selectAllItems = () => { selectAll(flattenedItemsList.value); }; const unselectAllItems = () => { unselectAll(flattenedItemsList.value); }; const { isAllSelected, isIndeterminate, toggleSelectAll } = useCheckboxSelectAll( visibleSelectedList, enabledItems, selectAllItems, unselectAllItems ); const ariaCheckedComputed = computed(() => { if (isIndeterminate.value) { return 'mixed'; } return `${isAllSelected.value}`; }); const isSelectAllOn = ref(isAllSelected.value); const handleSelectAll = () => { isSelectAllOn.value = !isSelectAllOn.value; toggleSelectAll(isSelectAllOn.value); }; watch(isAllSelected, () => { isSelectAllOn.value = isAllSelected.value; }); const selectAllCategory = (category: BentoListboxOptionItem) => { if (category.lazyLoad) { const childValues = (category.items ?? []).map(({ value }) => value); const withoutChildren = props.selectedValues.filter(({ value }) => !childValues.includes(value)); emit('select', [...withoutChildren, category]); } else { selectAll(category.items); } }; const unselectAllCategory = (category: BentoListboxOptionItem) => { if (category.lazyLoad) { emit( 'select', props.selectedValues.filter(({ value }) => value !== category.value) ); } else { unselectAll(category.items); } }; const toggleCheckboxSelection = (eventName, selectedOption: BentoListboxOptionItem) => { let exists = false; let newSelectedList = props.selectedValues.filter(({ value }) => { if (selectedOption.value === value) { exists = true; return false; } return true; }); if (exists) { emit(eventName, newSelectedList); return; } // Check if the item's parent lazy category is selected const parentLazyCategory = props.items.find( item => item.items?.some(subItem => subItem.value === selectedOption.value) && item.lazyLoad && selectedItemsList.value.includes(item.value) ); if (parentLazyCategory) { // Remove parent category newSelectedList = newSelectedList.filter(({ value }) => value !== parentLazyCategory.value); // Add other loaded children const otherLoadedChildren = parentLazyCategory.items.filter( subItem => subItem.value !== selectedOption.value ); newSelectedList.push(...otherLoadedChildren); } else { newSelectedList.push(selectedOption); } emit(eventName, newSelectedList); }; const selectedOptions = computed(() => { if (!isMultiLevelSelect.value) { return props.selectedValues; } const isOptionSelected = (option: BentoListboxOptionItem): boolean => (selectedItemsList.value as unknown as Array<string | number>).includes(option.value); const categoryOptionsSelected = (category: BentoListboxOptionItem): boolean => { if (category.lazyLoad && isOptionSelected(category)) { return true; } return ( !props.staticCategories && category.items.length > 0 && category.items.every(option => isOptionSelected(option)) ); }; const optionsFlat = props.items.flatMap(option => option.items && !categoryOptionsSelected(option) ? option.items : option ); const options = optionsFlat.filter(option => { if (option.items) { return true; } return isOptionSelected(option); }); const knownValues = new Set<BentoListboxValue>(); optionsFlat.forEach(option => { knownValues.add(option.value); option.items?.forEach(sub => knownValues.add(sub.value)); }); const unfetchedSelected = props.selectedValues.filter(sv => !knownValues.has(sv.value)); return [...options, ...unfetchedSelected]; }); const showSelectedItems = computed(() => { if (!selectedOptions.value?.length) { return false; } const isLazyLoad = ['automatic', 'button'].includes(props.lazyLoadType); const hasAllItemsSelected = props.selectedValues?.length > 0 && visibleSelectedList.value?.length !== enabledItems.value?.length; if (isLazyLoad && props.hasMoreItems) { return !!props.selectedValues?.length; } return hasAllItemsSelected; }); const showSelectAllCheckbox = computed(() => { if (hasLazyLoadCategory.value) { return false; } const isLazyLoad = ['automatic', 'button'].includes(props.lazyLoadType); if (!isLazyLoad) { return true; } return !props.hasMoreItems; }); // Keyboard navigation const expandedCategories = ref([]); const { visibleDomOptions, updateVisibleDomOptions } = useVisibleDomOptions( listboxRef, toRef(props, 'items'), computedOptionRole ); const toggleCategoryVisibility = category => { if (expandedCategories.value.includes(category)) { expandedCategories.value = expandedCategories.value.filter(item => item !== category); } else { expandedCategories.value.push(category); } nextTick(() => { updateVisibleDomOptions(); }); }; const focusItem = (nextFocusedItemIndex: number) => { visibleDomOptions.value[nextFocusedItemIndex]?.focus(); }; const { moveToNextElement, moveToPreviousElement, moveToFirstElement, moveToLastElement, preventDefault, focusedIndex, setListboxFocusedIndex, } = useListboxKeyboardNavigation( { items: visibleDomOptions, }, focusItem ); const listboxListeners = { [BentoKeyboardNavigationKeyDownEvent.ARROW_UP]: (event: CustomEvent<KeyboardEvent>) => moveToPreviousElement(event), [BentoKeyboardNavigationKeyDownEvent.ARROW_DOWN]: (event: CustomEvent<KeyboardEvent>) => moveToNextElement(event), [BentoKeyboardNavigationKeyDownEvent.HOME]: moveToFirstElement, [BentoKeyboardNavigationKeyDownEvent.END]: moveToLastElement, }; defineExpose({ /** * Method to set the index of the item to be focused on * Used for focus management to be kept in-sync with parent e.g. dropdowns */ setListboxFocusedIndex, }); const chipsSectionListeners = { [BentoKeyboardNavigationKeyDownEvent.ARROW_UP]: preventDefault, [BentoKeyboardNavigationKeyDownEvent.ARROW_DOWN]: preventDefault, }; const computedItemsIndex = computed(() => { let itemIndex = showSelectAllCheckbox.value ? 1 : 0; const indexMap = {}; props.items.forEach(item => { let subItemsVisible = expandedCategories.value.includes(item.value); if (item.items) { if (props.staticCategories) { subItemsVisible = true; } else { indexMap[item.value] = itemIndex++; } if (subItemsVisible) { item.items.forEach(subItem => (indexMap[subItem.value] = itemIndex++)); } } else { indexMap[item.value] = itemIndex++; } }); return indexMap; }); </script> <script lang="ts"> /** * Listbox multi-select options list * * @example * import { BentoListboxMultiSelect } from '@adyen/bento-vue2'; * * export default { * components: { BentoListboxMultiSelect }, * template: ` * <bento-listbox-multi-select * :items="[{ label: 'Option 1', value: 'option-1' }]" * :selected-values="['option-1']" * /> * ` * } */ export default { i18n: { messages } }; </script> <style lang="scss" scoped src="./listbox-multi-select.scss" />
|
package/dist/assets/components/internal/listbox/components/listbox-option/listbox-option.vue
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<template> <div class="b-listbox-option" role="option" :class="conditionalClasses" v-bind="$attrs" @click="onClick" @keydown.enter="onOptionSelected(BentoListboxEvent.ENTER, $event)" @keydown.space="onOptionSelected(BentoListboxEvent.SPACE, $event)" @keydown.tab="onOptionSelected(BentoListboxEvent.TAB, $event)" > <slot></slot> </div> </template> <script lang="ts"> import { computed, defineComponent } from 'vue'; import { BentoListboxEvent } from '@/types/listbox'; import { BentoKeyboardNavigationKeyDownEvent } from '@/types/keyboard-navigation'; /** * listbox option wrapper. * Contains only styles and allows the Multi-Select and Single-Select DRY the styles. * * Options and Attributes are inherited (`inheritAttrs: true`) * * @example * <bento-listbox-option * ...props * ...attrs * /> */ export default defineComponent({ name: 'bento-listbox-option', props: { /** * Indicates if the option is active. */ active: { type: Boolean, default: false }, /** * Setting this property to true will disable the option. */ disabled: { type: Boolean, default: false }, /** * Indicates if the value is selected. Only one element can be selected at a time. */ selected: { type: Boolean, default: false }, }, emits: [BentoListboxEvent.CLICK, BentoListboxEvent.ENTER, BentoListboxEvent.SPACE, BentoListboxEvent.TAB], setup(props, { emit }) { const conditionalClasses = computed(() => ({ 'b-listbox-option--active': props.active, 'b-listbox-option--disabled': props.disabled, 'b-listbox-option--selected': props.selected, })); const onClick = (e: Event) => { emit(BentoListboxEvent.CLICK, e); }; const onOptionSelected = (eventName, e: Event) => { emit(eventName, e); }; return { // Enums BentoKeyboardNavigationKeyDownEvent, BentoListboxEvent, // Values conditionalClasses, // Events onClick, onOptionSelected, }; }, }); </script> <style lang="scss" scoped src="./listbox-option.scss" />
|
package/dist/assets/components/internal/listbox/components/listbox-option-tag/listbox-option-tag.vue
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<template> <span class="b-listbox-tag-option"> <span :aria-hidden="!!option.label"> <bento-tag class="b-listbox-tag-option__tag" :label="option.value.toString()" :variant="variant" /> </span> <bento-typography v-if="option.label" el="span" class="b-listbox-tag-option__label"> {{ option.label }} </bento-typography> </span> </template> <script lang="ts"> import { computed, defineComponent, type PropType } from 'vue'; import type { BentoListboxOptionItem } from '@/types/listbox'; import { BentoTag, type BentoTagVariant } from '@/components/tag'; import { BentoTypography } from '@/components/typography'; /** * A Tag Dropdown option item used in the Dropdown and Filter components. */ export default defineComponent({ name: 'listbox-tag-option', components: { BentoTag, BentoTypography, }, props: { /** * The option item that contains all the option item's data. * @type {BentoListboxOptionItem} */ option: { type: Object as PropType<BentoListboxOptionItem>, required: true, }, }, setup(props) { const variant = computed(() => props.option.data as BentoTagVariant); return { // Values variant, }; }, }); </script> <style lang="scss" scoped src="./listbox-option-tag.scss" />
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<template> <div class="b-listbox-single-select-category" role="group" :aria-label="categoryLabel"> <bento-typography variant="caption" el="span" class="b-listbox-single-select-category__label-text" aria-hidden="true" > {{ categoryLabel }} </bento-typography> <div> <slot /> </div> </div> </template> <script lang="ts"> import { defineComponent } from 'vue'; import { BentoTypography } from '@/components/typography'; export default defineComponent({ name: 'bento-listbox-static-category', components: { BentoTypography, }, props: { /** * Category label text */ categoryLabel: { type: String, required: true, }, }, }); </script> <style lang="scss" scoped src="./listbox-single-select-category.scss" />
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<template> <div ref="listboxRef" v-bento-keyboard-navigation-directive v-bind="ariaAttributes" role="listbox" v-on="listboxListeners" @focusin="handleVirtualScrollingFocus" > <template v-if="hasCategories && staticCategories"> <template v-for="item in items"> <bento-listbox-single-select-category v-if="item.items && item.items.length > 0" :key="`category-${item.value}`" :category-label="item.label" > <bento-listbox-single-select-option v-for="option in item.items" :id="getOptionId(option)" :key="option.value" ref="listboxItemRef" role="option" :active="activeDescendantId === getOptionId(option)" :option="option" :disabled="isOptionDisabled ? isOptionDisabled(option) : null" :selected="option.value === selectedItem.value" v-on="listboxOptionListeners" > <template v-for="(_, innerSlot) of slots" #[innerSlot]="scope"> <slot :name="innerSlot" v-bind="scope" /> </template> </bento-listbox-single-select-option> </bento-listbox-single-select-category> <bento-listbox-single-select-option v-else :id="getOptionId(item)" :key="`option-${item.value}`" ref="listboxItemRef" role="option" :active="activeDescendantId === getOptionId(item)" :option="item" :disabled="isOptionDisabled ? isOptionDisabled(item) : null" :selected="item.value === selectedItem.value" v-on="listboxOptionListeners" > <template v-for="(_, innerSlot) of slots" #[innerSlot]="scope"> <slot :name="innerSlot" v-bind="scope" /> </template> </bento-listbox-single-select-option> </template> </template> <template v-else> <bento-listbox-single-select-option v-for="option in items" :id="getOptionId(option)" :key="option.value" ref="listboxItemRef" role="option" :active="activeDescendantId === getOptionId(option)" :option="option" :disabled="isOptionDisabled ? isOptionDisabled(option) : null" :selected="option.value === selectedItem.value" v-on="listboxOptionListeners" > <template v-for="(_, innerSlot) of slots" #[innerSlot]="scope"> <slot :name="innerSlot" v-bind="scope" /> </template> </bento-listbox-single-select-option> </template> </div> </template> <script lang="ts"> import { computed, defineComponent, type HTMLAttributes, nextTick, type PropType, ref, toRef } from 'vue'; import { BentoListboxSingleSelectCategory } from './components/listbox-single-select-category'; import { BentoListboxSingleSelectOption } from '../listbox-single-select-option'; import { useListboxKeyboardNavigation } from '../../composables/useListboxKeyboardNavigation'; import { useVisibleDomOptions } from '@/internal/listbox/composables/use-visible-dom-options/use-visible-dom-options'; import { BentoListboxEvent, type BentoListboxIsOptionDisabled, type BentoListboxOptionItem, type BentoListboxOptions, } from '@/types/listbox'; import { BentoKeyboardNavigationKeyDownEvent } from '@/types/keyboard-navigation'; import { type Booleanish } from '@/types/prop-types'; import { getListboxOptionId } from '@/utils/ts/get-listbox-option-id'; import { BentoKeyboardNavigationDirective } from '@/directives'; /** * Listbox single-select options list * * @example * import { BentoListboxMultiSelect } from '@adyen/bento-vue2'; * * export default { * components: { BentoListboxMultiSelect }, * template: ` * <bento-listbox-multi-select * :items="[{ label: 'Option 1', value: 'option-1' }]" * :selected-values="['option-1']" * /> * ` * } */ export default defineComponent({ name: 'bento-listbox-single-select', components: { BentoListboxSingleSelectCategory, BentoListboxSingleSelectOption, }, directives: { BentoKeyboardNavigationDirective }, inheritAttrs: false, props: { /** * Function that allows the options to be disabled * * @type {BentoListboxIsOptionDisabled} * @param {BentoListboxOptionItem} option - listbox option object * @param {string} option.label - Option's label text * @param {string|number} option.value - Option's value * @param {unknown} option.data - Option's extra data to be passed to the default slot */ isOptionDisabled: { type: Function as PropType<BentoListboxIsOptionDisabled>, default: () => false, }, /** * Identifies the currently active option in the listbox. */ activeDescendantId: { type: String, default: null }, /** * The option elements to populate the listbox with. * It must be an array of {@see BentoListboxOptionItem }. * * @property {string} value.label - Text to be displayed in the option * @property {string,number} value.value - Value of the option */ items: { type: Array as PropType<BentoListboxOptions>, required: true, }, /** * Prefix used to generate stable option IDs. */ optionIdBase: { type: String, required: true }, /** * The `input` value. * Providing an empty string will select no options. * Set to an empty string `''` if you don't want any of the available options to be selected */ selectedValue: { type: Array as PropType<BentoListboxOptions>, default: undefined, }, /** * If enabled, dropdown will display non-selectable, static categories */ staticCategories: { type: Boolean, default: false }, /** * Total number of items in the full list (used for virtual scroll keyboard navigation). * When greater than 0, virtual scroll keyboard navigation is enabled. */ totalItemCount: { type: Number, default: 0 }, /** * Function to scroll the virtual list container to a given item index. */ scrollToIndex: { type: Function as PropType<(index: number) => void>, default: undefined }, /** * The start index of the currently rendered virtual scroll slice. */ virtualScrollStartIndex: { type: Number, default: 0 }, }, emits: [BentoListboxEvent.SELECT, BentoListboxEvent.ENTER, BentoListboxEvent.SPACE, BentoListboxEvent.TAB], setup(props, { emit, expose, slots, attrs }) { const listboxRef = ref<HTMLElement>(); const listboxItemRef = ref([]); const selectedItem = computed(() => !!props.selectedValue?.length && props.selectedValue.at(0)); const ariaAttributes = computed( () => ({ 'aria-label': attrs['aria-label']?.toString() as string, 'aria-busy': attrs['aria-busy']?.toString() as Booleanish, 'aria-atomic': attrs['aria-atomic']?.toString() as Booleanish, 'aria-live': attrs['aria-live']?.toString() as string, }) as HTMLAttributes ); /** * Finds out if items have sub items */ const hasCategories = computed(() => !!props.items.some(({ items }) => !!items)); const onOptionSelected = eventName => (selectedOption: BentoListboxOptions) => { emit(eventName, [selectedOption]); }; const getOptionId = (option: BentoListboxOptionItem) => getListboxOptionId(props.optionIdBase, option.value); // Keyboard navigation const { visibleDomOptions, updateVisibleDomOptions } = useVisibleDomOptions( listboxRef, toRef(props, 'items'), ref('option'), false ); const isVirtualScrollEnabled = computed(() => props.totalItemCount > 0); // When virtual scroll re-renders items (e.g. on mouse wheel scroll), the // browser may auto-focus an option. Sync the keyboard navigation index to // match the focused element so subsequent arrow key presses navigate from // the correct position. const handleVirtualScrollingFocus = (event: FocusEvent) => { if (!isVirtualScrollEnabled.value) { return; } const target = event.target as HTMLElement; if (target === listboxRef.value || target?.getAttribute('role') !== 'option') { return; } const localIndex = visibleDomOptions.value.indexOf(target as HTMLDivElement); if (localIndex !== -1) { const globalIndex = localIndex + props.virtualScrollStartIndex; setListboxFocusedIndex(globalIndex); } }; const focusItem = async (globalIndex: number) => { if (isVirtualScrollEnabled.value && props.scrollToIndex) { props.scrollToIndex(globalIndex); await nextTick(); await updateVisibleDomOptions(); const newDomIndex = globalIndex - props.virtualScrollStartIndex; visibleDomOptions.value[newDomIndex]?.focus(); } else { visibleDomOptions.value[globalIndex]?.focus(); } }; const navigateToItem = async (globalIndex: number) => { setListboxFocusedIndex(globalIndex); await focusItem(globalIndex); }; const scrollIntoContainer = (listboxItemElement?: HTMLElement, listboxElement?: HTMLElement) => { if (!listboxItemElement || !listboxElement) { return; } const elementRect = listboxItemElement.getBoundingClientRect(); const scrollableContainer = listboxElement; const containerRect = scrollableContainer.getBoundingClientRect(); if (elementRect.top < containerRect.top) { scrollableContainer.scrollTop -= containerRect.top - elementRect.top; } else if (elementRect.bottom > containerRect.bottom) { scrollableContainer.scrollTop += elementRect.bottom - containerRect.bottom; } }; const scrollToItem = async (globalIndex: number, listboxElement?: HTMLElement) => { await updateVisibleDomOptions(); if (isVirtualScrollEnabled.value && props.scrollToIndex) { props.scrollToIndex(globalIndex); } else { scrollIntoContainer(visibleDomOptions.value[globalIndex], listboxElement); } }; const { moveToNextElement, moveToPreviousElement, moveToFirstElement, moveToLastElement, setListboxFocusedIndex, } = useListboxKeyboardNavigation( { items: visibleDomOptions, totalItemCount: toRef(props, 'totalItemCount'), }, focusItem ); const listboxListeners = { [BentoKeyboardNavigationKeyDownEvent.ARROW_UP]: moveToPreviousElement, [BentoKeyboardNavigationKeyDownEvent.ARROW_DOWN]: moveToNextElement, [BentoKeyboardNavigationKeyDownEvent.HOME]: moveToFirstElement, [BentoKeyboardNavigationKeyDownEvent.END]: moveToLastElement, }; const listboxOptionListeners = { [BentoListboxEvent.SELECT]: onOptionSelected(BentoListboxEvent.SELECT), [BentoListboxEvent.SPACE]: onOptionSelected(BentoListboxEvent.SPACE), [BentoListboxEvent.TAB]: onOptionSelected(BentoListboxEvent.TAB), [BentoListboxEvent.ENTER]: onOptionSelected(BentoListboxEvent.ENTER), }; expose({ /** * Reference for a single listbox item. * Required for keyboard navigation in dropdown as $children is removed in vue@3 */ listboxItemRef, /** * Method to set the index of the item to be focused on * Used for focus management to be kept in-sync with parent e.g. dropdowns */ setListboxFocusedIndex, /** * Navigates to an item by global index, handling virtual scroll if enabled. * Scrolls the virtual list and focuses the item. */ navigateToItem, /** * Scrolls to an item by global index without changing focus. * Used when the dropdown opens via click to bring the selected option into view. */ scrollToItem, }); return { // Enums BentoListboxEvent, // Values listboxRef, listboxItemRef, selectedItem, slots, ariaAttributes, getOptionId, // Computed hasCategories, // Methods onOptionSelected, // Keyboard navigation listboxListeners, listboxOptionListeners, // Virtual scroll focus management handleVirtualScrollingFocus, }; }, }); </script>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<template> <bento-listbox-option class="b-listbox-single-select-option" :active="active" :disabled="disabled" :selected="selected" :aria-selected="ariaSelected" :aria-disabled="ariaDisabled" :tabindex="tabIndex" @click="onOptionSelected(BentoListboxEvent.SELECT)" @enter-pressed="onOptionSelected(BentoListboxEvent.ENTER)" @space-pressed.prevent="onOptionSelected(BentoListboxEvent.SPACE)" @tab-pressed="onOptionSelected(BentoListboxEvent.TAB)" > <slot v-bind="option"> <span class="b-listbox-single-select-option__text"> <bento-typography el="span">{{ option.label }}</bento-typography> <bento-typography v-if="option.description" class="b-listbox-single-select-option__description" el="span" > {{ option.description }} </bento-typography> </span> </slot> <span class="b-listbox-single-select-option__check-icon"> <checkmark-icon v-show="selected" svg-title="selected" /> </span> </bento-listbox-option> </template> <script lang="ts"> import { computed, defineComponent, type PropType } from 'vue'; import { BentoTypography } from '@/components/typography'; import { BentoListboxOption } from '../listbox-option'; import { BentoListboxEvent, type BentoListboxOptionItem } from '@/types/listbox'; import CheckmarkIcon from '@adyen/ui-assets-icons-16/vue/checkmark'; /** * Listbox option element. * * @example * <bento-listbox-single-select-option * v-for="option in options" * :key="option.value" * :option="option" * :selected="option.selected" * @select="onOptionSelected" * /> */ export default defineComponent({ name: 'bento-listbox-single-select-option', components: { BentoListboxOption, BentoTypography, CheckmarkIcon, }, props: { /** * Indicates if the option is active. */ active: { type: Boolean, default: false }, /** * Setting this property to true will disable the option. */ disabled: { type: Boolean, default: false }, /** * The option item that contains all the option item's data. * @type {BentoListboxOptionItem} */ option: { type: Object as PropType<BentoListboxOptionItem>, required: true, }, /** * Indicates if the value is selected. Only one element can be selected at a time. */ selected: { type: Boolean, default: false }, }, emits: [BentoListboxEvent.ENTER, BentoListboxEvent.SELECT, BentoListboxEvent.SPACE, BentoListboxEvent.TAB], setup(props, { emit }) { const onOptionSelected = eventName => { if (props.disabled) { return; } /** * Trigerred when an option is clicked or selected via keyboard navigation. * Indicates which option was selected. * * @event {string} - The corresponding event * @property {number} selectedValue - Selected option's value */ emit(eventName, props.option); }; const ariaDisabled = computed(() => (props.disabled ? true : null)); const ariaSelected = computed(() => (props.selected ? true : null)); const tabIndex = computed(() => (props.disabled ? -1 : 0)); return { // Values ariaDisabled, ariaSelected, tabIndex, // Enums BentoListboxEvent, // Events onOptionSelected, }; }, }); </script> <style lang="scss" scoped src="./listbox-single-select-option.scss" />
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from '@storybook/vue'; import { action } from '@storybook/addon-actions'; import BentoListbox from './listbox.vue'; import { ref } from 'vue'; import { BentoDropdownLazyLoadType } from '@/components/dropdown/dropdown.types'; import { BentoListboxEvent, type BentoListboxOptionItem, type BentoListboxOptions } from '@/types/listbox'; import { isVue2 } from 'vue-demi'; const meta: Meta = { title: 'Listbox', component: BentoListbox, argTypes: { // Events [BentoListboxEvent.SELECT]: { name: `${BentoListboxEvent.SELECT}`, defaultValue: '', description: 'Triggered when an option is clicked or selected via the enter or the space key. Updates the selected option from the "value" property linked to the v-model. ', table: { type: { summary: '(selectedValue: string|number) => void' }, }, control: { disable: true }, }, [BentoListboxEvent.ESCAPE]: { name: `${BentoListboxEvent.ESCAPE}`, defaultValue: '', description: 'Triggered when the escape key is pressed.', table: { type: { summary: '() => void' }, }, control: { disable: true }, }, // Values items: { control: { disable: true } }, lazyLoadType: { options: Object.values(BentoDropdownLazyLoadType), mapping: BentoDropdownLazyLoadType, control: { type: 'select', }, }, }, }; export default meta; type Story = StoryObj<typeof BentoListbox>; const SELECTED_ITEM_OUT_OF_SCOPE = { value: 999, label: 'Pre-selected value' }; export const SingleSelect: Story = { render: (_args, { argTypes }) => ({ components: { BentoListbox }, props: Object.keys(argTypes), template: ` <bento-listbox v-bind="args" class="b-dropdown-options-container" :is-option-disabled="isOptionDisabled" :selected-value="selectedValue" @select="selectedAction" @escape-pressed="escapeAction" /> `, setup(props) { const args = isVue2 ? props : _args; const selectedValue = ref([args.items[1]]); const isOptionDisabled = (option: BentoListboxOptionItem) => [3].includes(option.value as number); const selectedAction = (selectedItem: BentoListboxOptions) => { action(BentoListboxEvent.SELECT)(selectedItem); selectedValue.value = selectedItem; }; const escapeAction = () => { action(BentoListboxEvent.ESCAPE)(); }; return { // Values args: isVue2 ? props : _args, selectedValue, // Events selectedAction, escapeAction, // Methods isOptionDisabled, }; }, }), args: { id: 'listbox-id', noResultsMessage: 'No results', items: [ { label: 'Option 1', value: 1 }, { label: 'Option 2', value: 2 }, { label: 'Option 3', value: 3 }, { label: 'Option 4', value: 4 }, ] as BentoListboxOptions, }, }; export const SingleCategories: Story = { render: (_args, { argTypes }) => ({ components: { BentoListbox }, props: Object.keys(argTypes), template: ` <bento-listbox v-bind="args" class="b-dropdown-options-container" id="single-select" static-categories :selected-value="selectedValue" @select="selectedAction" @escape-pressed="escapeAction" /> `, setup(props) { const args = isVue2 ? props : _args; const selectedValue = ref([args.items[0]]); const selectedAction = value => { action(BentoListboxEvent.SELECT)(value); selectedValue.value = value; }; const escapeAction = () => { action(BentoListboxEvent.ESCAPE)(); }; return { // Values args, selectedValue, // Events selectedAction, escapeAction, }; }, }), args: { id: 'listbox-id', noResultsMessage: 'No results', items: [ { label: 'Category 1', value: 10, items: [ { label: 'Option 1', value: 1 }, { label: 'Option 2', value: 2 }, ], }, { label: 'Category 2', value: 20, items: [ { label: 'Option 3', value: 3 }, { label: 'Option 4', value: 4 }, ], }, ] as BentoListboxOptions, }, }; export const MultiSelect: Story = { render: (_args, { argTypes }) => ({ components: { BentoListbox }, props: Object.keys(argTypes), template: ` <bento-listbox v-bind="args" class="b-dropdown-options-container" id="multi-select" multiple :selected-value="selectedValue" @select="selectedAction" @escape-pressed="escapeAction" /> `, setup(props) { const args = isVue2 ? props : _args; const selectedValue = ref([args.items[0]]); const selectedAction = (selectedOption: BentoListboxOptions) => { action(BentoListboxEvent.SELECT)(selectedOption); selectedValue.value = selectedOption; }; const escapeAction = () => { action(BentoListboxEvent.ESCAPE)(); }; return { // Values args, selectedValue, // Events selectedAction, escapeAction, }; }, }), args: { id: 'listbox-id', noResultsMessage: 'No results', items: [ { label: 'Option 1', value: 1, }, { label: 'Option 2', value: 2, }, ] as BentoListboxOptions, }, }; export const MultiSelectCategories: Story = { render: (_args, { argTypes }) => ({ components: { BentoListbox }, props: Object.keys(argTypes), template: ` <bento-listbox v-bind="args" class="b-dropdown-options-container" id="multi-select-categories" multiple static-categories :selected-value="selectedValue" @select="selectedAction" @escape-pressed="escapeAction" /> `, setup(props) { const args = isVue2 ? props : _args; const selectedValue = ref([args.items[0].items[0]]); const selectedAction = (selectedOption: BentoListboxOptions) => { action(BentoListboxEvent.SELECT)(selectedOption); selectedValue.value = selectedOption; }; const escapeAction = () => { action(BentoListboxEvent.ESCAPE)(); }; return { // Values args, selectedValue, // Events selectedAction, escapeAction, }; }, }), args: { id: 'listbox-id', noResultsMessage: 'No results', items: [ { label: 'Category 1', value: 10, items: [ { label: 'Option 1', value: 1 }, { label: 'Option 2', value: 2 }, ], }, { label: 'Category 2', value: 20, items: [ { label: 'Option 3', value: 3 }, { label: 'Option 4', value: 4 }, ], }, ] as BentoListboxOptions, }, }; export const NestedMultiSelect: Story = { render: (_args, { argTypes }) => ({ components: { BentoListbox }, props: Object.keys(argTypes), template: ` <bento-listbox v-bind="args" class="b-dropdown-options-container" id="nested-select" multiple :selected-value="selectedValue" @select="selectedAction" @escape-pressed="escapeAction" /> `, setup(props) { const args = isVue2 ? props : _args; const selectedValue = ref([args.items[0].items[0]]); const selectedAction = (selectedOption: BentoListboxOptions) => { action(BentoListboxEvent.SELECT)(selectedOption); selectedValue.value = selectedOption; }; const escapeAction = () => { action(BentoListboxEvent.ESCAPE)(); }; return { // Values args, selectedValue, // Events selectedAction, escapeAction, }; }, }), args: { id: 'listbox-id', noResultsMessage: 'No results', items: [ { label: 'Category 1', value: 10, items: [ { label: 'Option 1', value: 1 }, { label: 'Option 2', value: 2 }, ], }, { label: 'Category 2', value: 20, items: [ { label: 'Option 3', value: 3 }, { label: 'Option 4', value: 4 }, ], }, ] as BentoListboxOptions, }, }; export const LazyLoadPreselctedValues: Story = { render: (_args, { argTypes }) => ({ components: { BentoListbox }, props: Object.keys(argTypes), template: ` <bento-listbox v-bind="args" class="b-dropdown-options-container" :has-more-items="true" id="multi-select" lazy-load-type="automatic" multiple :selected-value="selectedValue" @select="selectedAction" @escape-pressed="escapeAction" /> `, setup(props) { const args = isVue2 ? props : _args; const selectedValue = ref([args.items[0], { ...SELECTED_ITEM_OUT_OF_SCOPE }]); const selectedAction = (selectedOption: BentoListboxOptions) => { action(BentoListboxEvent.SELECT)(selectedOption); selectedValue.value = selectedOption; }; const escapeAction = () => { action(BentoListboxEvent.ESCAPE)(); }; return { // Values args, selectedValue, // Events selectedAction, escapeAction, }; }, }), args: { id: 'listbox-id', noResultsMessage: 'No results', items: [ { label: 'Option 1', value: 1, }, { label: 'Option 2', value: 2, }, ] as BentoListboxOptions, }, };
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
// Types for this components should be in @/types/listbox.ts
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<template> <div :id="id" ref="listboxRef" v-bento-keyboard-navigation-directive class="b-listbox" tabindex="-1" v-bind="listboxContainerAttributes" :style="containerProps?.style" data-testid="listbox-container" @scroll="onListboxContainerScroll" > <div v-bind="wrapperProps"> <!-- Loading filtered items loading --> <div v-if="componentLoading" class="b-listbox__loading-filtered-items"> <bento-loading-indicator /> </div> <!-- Empty search message --> <div v-else-if="isSearchResultEmpty" class="b-listbox__empty-search-message"> <bento-empty-state v-if="emptyState" v-bind="limitedEmptyStateProps" /> <bento-typography v-else el="span">{{ noResultsMessage }}</bento-typography> </div> <template v-else> <!-- Multi select --> <template v-if="multiple"> <bento-listbox-multi-select ref="listboxItemRef" :has-more-items="hasMoreItems" v-bind="ariaAttributes" :items="items" :lazy-load-type="lazyLoadType" :static-categories="staticCategories" :selected-values="selectedValue" :searching="searching" :is-option-disabled="isOptionDisabled" v-on="listboxListeners" > <template v-for="(_, innerSlot) of slots" #[innerSlot]="scope"> <slot :name="innerSlot" v-bind="scope" /> </template> </bento-listbox-multi-select> </template> <!-- Single select --> <template v-else> <bento-listbox-single-select ref="listboxItemRef" :active-descendant-id="activeDescendantId" :items="virtualisedItems" :option-id-base="id" :total-item-count="totalItemCount" :scroll-to-index="scrollToIndex" :virtual-scroll-start-index="virtualScrollStartIndex" v-bind="ariaAttributes" :selected-value="selectedValue" :static-categories="staticCategories" :is-option-disabled="isOptionDisabled" v-on="listboxListeners" > <template v-for="(_, innerSlot) of slots" #[innerSlot]="scope"> <slot :name="innerSlot" v-bind="scope" /> </template> </bento-listbox-single-select> </template> <!-- Lazy Loading --> <bento-listbox-lazy-load v-if="lazyLoadType !== 'none'" :has-more-items="hasMoreItems" :loading="loading" :type="lazyLoadType" @show-more="emitShowMore" /> </template> </div> </div> </template> <script lang="ts"> import { computed, defineComponent, type PropType, ref, toRef } from 'vue'; import { type BentoListboxEmptyStateProps, BentoListboxEvent, type BentoListboxIsOptionDisabled, BentoListboxLazyLoadType, type BentoListboxOptionItem, type BentoListboxOptions, type BentoListboxVirtulisationOptions, } from '@/types/listbox'; import { BentoKeyboardNavigationKeyDownEvent } from '@/types/keyboard-navigation'; import { BentoKeyboardNavigationDirective } from '@/directives'; import { BentoListboxSingleSelect } from './components/listbox-single-select'; import { BentoListboxMultiSelect } from './components/listbox-multi-select'; import { BentoTypography } from '@/components/typography'; import { BentoLoadingIndicator } from '@/components/loading-indicator'; import { BentoListboxLazyLoad } from './components/listbox-lazy-load'; import { BentoLoadingButtonState } from '@/components/button'; import { BentoEmptyState } from '@/components/empty-state'; import { debounce } from '@/utils/ts/debounce'; import { useVirtualList } from '@/composables/use-virtual-list'; import type { HTMLAttributes } from 'vue/types/jsx.d.ts'; const DEFAULT_DROPDOWN_ITEM_HEIGHT = 36; /** * Listbox container. * It lists all the available options. * * @example * <bento-listbox * v-slot="{label, value}" * :id="listboxOptionsContainerId" * :aria-label="ariaLabel" * :disabled="disabled" * :multiple="multiple" * :selected="value" * :isOptionDisabled="option => option.value === 2" * :items="[{ * { label: 'Option 1', value: 1 }, * { label: 'Option 2', value: 2 }, * }]" * @select="onOptionSelected" * > * </bento-listbox> */ export default defineComponent({ name: 'bento-listbox', components: { BentoEmptyState, BentoListboxSingleSelect, BentoListboxMultiSelect, BentoTypography, BentoLoadingIndicator, BentoListboxLazyLoad, }, directives: { BentoKeyboardNavigationDirective }, props: { /** * Defines a string value that labels an interactive element. */ ariaLabel: { type: String, default: null }, /** * Identifies the currently active option in the listbox. */ activeDescendantId: { type: String, default: null }, /** * Empty state props that will be used with the inner empty state component to be displayed when no search results are found. */ emptyState: { type: Object as PropType<BentoListboxEmptyStateProps>, default: undefined, }, /** * Indicates whether there are more items to load */ hasMoreItems: { type: Boolean, default: false }, /** * Identifies the listbox whose contents are controlled by the the combobox on which the aria-controls attribute is set. */ id: { type: String, required: true }, /** * Function that allows the options to be disabled * * @type {BentoListboxIsOptionDisabled} * @param {BentoListboxOptionItem} option - Listbox option object * @param {string} option.label - Option's label text * @param {string|number} option.value - Option's value * @param {unknown} option.data - Option's extra data to be passed to the default slot */ isOptionDisabled: { type: Function as PropType<BentoListboxIsOptionDisabled>, default: () => undefined, }, /** * The option elements to populate the listbox with. * It must be an array of {@see BentoListboxOptionItem } * * @property {string} value.label - Text to be displayed in the option * * @property {string,number} value.value - Value of the option */ items: { type: Array as PropType<BentoListboxOptions>, required: true, }, /** * Indicates if new options are lazy loading. */ loading: { type: Boolean, default: false }, /** * The type of `Lazy Load`. * Type "automatic" will enable infinite scrolling * Type "button" will enable lazy loading with "Show more" button */ lazyLoadType: { type: String as PropType<BentoListboxLazyLoadType | `${BentoListboxLazyLoadType}`>, default: BentoListboxLazyLoadType.NONE, validator: (value: BentoListboxLazyLoadType) => Object.values(BentoListboxLazyLoadType).includes(value), }, /** * Indicates the listbox is loading filtered items. */ componentLoading: { type: Boolean, default: false }, /** * If enabled, the user will be able to select multiple items. */ multiple: { type: Boolean, default: false }, /** * If enabled, dropdown will display non-selectable, static categories */ staticCategories: { type: Boolean, default: false }, /** * Messaged displayed when no items are listed in the listbox */ noResultsMessage: { type: String, required: true }, /** * Flag that indicates that an external search it's being made * to filter out the items inside the the multi-select listbox * * If enabled, hides the "Select all" checkbox in "multiple" mode. */ searching: BentoListboxMultiSelect.props.searching, /** * The `input` value. * Providing an empty string or empty array will select no options. * Set to an empty string `''` if you don't want any of the available options to be selected for single select */ selectedValue: { type: Array as PropType<BentoListboxOptions>, default: () => [], }, /** * Enables virtual scrolling if set to true or by providing an object with itemHeight function. * The itemHeight function is used to calculate the height of rendered item given it's index. */ virtualScroll: { type: [Boolean, Object] as PropType<BentoListboxVirtulisationOptions>, default: false, }, }, emits: [ BentoListboxEvent.SELECT, BentoListboxEvent.SPACE, BentoListboxEvent.SHOW_MORE, BentoListboxEvent.ENTER, BentoListboxEvent.ESCAPE, BentoListboxEvent.TAB, ], setup(props, { emit, slots, expose }) { const listboxItemRef = ref([]); const items = toRef(props, 'items'); const isLazyLoadListbox = computed(() => props.lazyLoadType !== BentoListboxLazyLoadType.AUTOMATIC); const hasAutomaticLazyLoader = computed(() => props.lazyLoadType === BentoListboxLazyLoadType.AUTOMATIC); // Virtual scrolling const virtualScrollItemHeight = () => { if (typeof props?.virtualScroll === 'object' && props?.virtualScroll?.itemHeight) { return props.virtualScroll.itemHeight; } return DEFAULT_DROPDOWN_ITEM_HEIGHT; }; const isVirtualScrollEnabled = !props?.multiple && props?.virtualScroll !== false && props?.lazyLoadType === 'none'; const { containerRef: listboxRef, containerProps, onScroll: onContainerScroll, list: virtualisedItems, wrapperProps, scrollToIndex, startIndex: virtualScrollStartIndex, } = useVirtualList<BentoListboxOptionItem>(items, { itemHeight: virtualScrollItemHeight(), // Disable virtual scrolling if not enabled or if lazy loading is enabled or if multi-select disabled: !isVirtualScrollEnabled, }); const totalItemCount = computed(() => (isVirtualScrollEnabled ? items.value.length : 0)); // Aria a11y const ariaAtomic = computed<HTMLAttributes['aria-atomic']>(() => props.componentLoading !== undefined || isLazyLoadListbox.value ? 'true' : undefined ); const ariaBusy = computed<HTMLAttributes['aria-busy']>(() => (isLazyLoadListbox.value && props.loading) || props.componentLoading ? 'true' : 'false' ); const ariaLive = computed<HTMLAttributes['aria-live']>(() => isLazyLoadListbox.value || props.componentLoading !== undefined ? 'polite' : undefined ); const ariaAttributes = computed( () => ({ 'aria-label': props.ariaLabel, 'aria-busy': ariaBusy.value, 'aria-atomic': ariaAtomic.value, 'aria-live': ariaLive.value, }) as HTMLAttributes ); const listboxContainerAttributes = computed( () => (props.componentLoading ? { ...ariaAttributes.value, role: 'alert', } : {}) as HTMLAttributes ); const onOptionSelected = eventName => (selectedItem: BentoListboxOptions) => { emit(eventName, selectedItem); }; const emitShowMore = () => { emit(BentoListboxEvent.SHOW_MORE); }; const emitShowMoreOnScrollEnd = debounce(() => { const container = listboxRef.value; if (!container) { return; } const isAtBottom = Math.round(container.scrollTop) + container.clientHeight >= container.scrollHeight; if (props.hasMoreItems && isAtBottom && !props.loading) { emitShowMore(); } }); const onListboxContainerScroll = () => { if (hasAutomaticLazyLoader.value) { emitShowMoreOnScrollEnd(); } else { onContainerScroll(); } }; const isSearchResultEmpty = computed(() => props.items?.length === 0); const shouldDisplayShowMoreButton = computed( () => props.lazyLoadType === BentoListboxLazyLoadType.BUTTON && props.hasMoreItems ); const loadingButtonState = computed(() => props.loading ? BentoLoadingButtonState.LOADING : BentoLoadingButtonState.START ); const limitedEmptyStateProps = computed<InstanceType<typeof BentoEmptyState>['$props']>(() => ({ ...props.emptyState, variant: 'condensed', })); const listboxListeners = { [BentoListboxEvent.SELECT]: onOptionSelected(BentoListboxEvent.SELECT), [BentoListboxEvent.SPACE]: onOptionSelected(BentoListboxEvent.SPACE), [BentoListboxEvent.TAB]: onOptionSelected(BentoListboxEvent.TAB), [BentoListboxEvent.ENTER]: onOptionSelected(BentoListboxEvent.ENTER), }; expose({ listboxRef, listboxItemRef }); return { // Refs listboxRef, listboxItemRef, // a11y ariaAttributes, listboxContainerAttributes, // Values containerProps, isSearchResultEmpty, limitedEmptyStateProps, loadingButtonState, slots, shouldDisplayShowMoreButton, virtualisedItems, wrapperProps, // Virtual scroll totalItemCount, scrollToIndex, virtualScrollStartIndex, // Enums BentoKeyboardNavigationKeyDownEvent, BentoListboxEvent, // Methods onListboxContainerScroll, onOptionSelected, emitShowMore, listboxListeners, }; }, }); </script> <style lang="scss" scoped src="./listbox.scss" />
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import { ref } from 'vue'; import { isVue2 } from 'vue-demi'; import BentoRadioInput from './radio-input.vue'; import type { Meta, StoryObj } from '@storybook/vue'; const meta: Meta = { title: 'Radio input', component: BentoRadioInput, }; export default meta; type Story = StoryObj<typeof BentoRadioInput>; export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoRadioInput }, props: Object.keys(argTypes), template: ` <bento-radio-input v-bind="args" :model-value.sync="selectedValue" /> `, setup(props) { const selectedValue = ref(undefined); return { // Values args: isVue2 ? props : _args, selectedValue, }; }, }), args: { value: 'radio-input-value', }, };
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export type RadioInputValue = string | number; export enum RadioInputStateClass { DISABLED = 'disabled', READONLY = 'readonly', ERROR = 'error', }
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<template> <div class="b-radio-input"> <input :id="id" ref="inputRef" :aria-checked="isChecked" :aria-label="ariaLabel" :aria-invalid="ariaInvalid" :aria-describedby="ariaDescribedby" :checked="isChecked" :disabled="disabled" :value="value" :name="name" type="radio" class="b-radio-input__input" :class="conditionalClasses" role="radio" @click="onClick" @change="onChange" /> </div> </template> <script setup lang="ts"> import { computed, type ComputedRef, type PropType, ref, toRefs } from 'vue'; import { RadioInputStateClass, type RadioInputValue } from './radio-input.types'; import type { HTMLAttributes } from 'vue/types/jsx.d.ts'; const props = defineProps({ /** * The aria label associated to the input checkbox. */ ariaLabel: { type: String, default: '' }, /** * An additional string that serves as description for the input checkbox (useful if the component shows a description). */ ariaDescribedby: { type: String, default: '' }, /** * Disable the radio button (optional) */ disabled: { type: Boolean, default: false }, /** * Shows bento-radio-button in error state (optional) */ hasError: { type: Boolean, default: false }, /** * Sets the id of the radio input. Required for accessibility purposes */ id: { type: String, default: null }, /** * This property should not be used directly. * It will be used by v-model directive on radio component. */ modelValue: { type: [String, Number] as PropType<RadioInputValue>, default: null, }, /** * Sets the radio button to readonly (optional) */ readonly: { type: Boolean, default: false }, /** * Indicates the value of the input that will be returned when it's checked */ value: { type: [String, Number] as PropType<RadioInputValue>, required: true, }, /** * Sets the name of the radio input. * Used for differentiating radio groups or accessing input via form submission. */ name: { type: String, default: null }, }); const emit = defineEmits<{ /** * Emit that emits the input value */ (e: 'update:model-value', value: RadioInputValue): void; }>(); const { value } = toRefs(props); const inputRef = ref(null); const conditionalClasses = computed(() => ({ [`b-radio-input__input--${RadioInputStateClass.DISABLED}`]: props.disabled, [`b-radio-input__input--${RadioInputStateClass.ERROR}`]: props.hasError, [`b-radio-input__input--${RadioInputStateClass.READONLY}`]: props.readonly, })); const ariaInvalid = computed<HTMLAttributes['aria-invalid']>(() => props.hasError); const isChecked: ComputedRef<boolean> = computed(() => props.value === props.modelValue); const onChange = () => { if (!props.disabled && !props.readonly) { emit('update:model-value', props.value); } }; // readonly attr doesn't work with radio button, so we need to manually ignore the click instead const onClick = event => { if (props.readonly) { event.preventDefault(); } }; const focus = () => { inputRef.value.focus(); }; defineExpose({ focus, }); </script> <script lang="ts"> /** * Internal component that renders a radio input (without label) * * @example * import { RadioInput } from '@/internal/radio-input'; * * export default { * components: { BentoCheckboxInput }, * template: ` * <label for="checkbox-id">Your label</label> * <span id="description-id">Description</span> * <radio-input id="checkbox-id" aria=label="Your label" aria-describedby="description-id" /> * ` * } */ export default { name: 'radio-input', inheritAttrs: false, }; </script> <style lang="scss" scoped src="./radio-input.scss" />
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export interface BentoTeleportProps { /** * Disables the teleport. It will prevent the slotted element to be * teleported outside of the initial parent element * @default false */ disabled: boolean; /** * Indicates the parent container in which the slotted element will be * teleported to. */ to?: string; // NOTE: "where" is not added as it doesn't exist in Vue3 }
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<!-- Taken from: https://github.com/Mechazawa/vue2-teleport/blob/master/src/Teleport.vue --> <template> <div v-if="isVue2" :class="classes"> <slot /> </div> <Teleport v-else :to="to || teleportTarget" :disabled="disabled"> <slot /> </Teleport> </template> <script setup lang="ts"> import { isVue2 } from 'vue-demi'; import { computed, defineComponent, inject, type PropType } from 'vue'; import { MODAL_DIALOG_ID_INJECTION_KEY } from '@/components/modal/components/base-modal/base-modal.keys'; import { SIDEPANEL_DIALOG_ID_INJECTION_KEY } from '@/components/sidepanel/sidepanel.keys'; import { MODAL_FULLSCREEN_DIALOG_ID_INJECTION_KEY } from '@/components/modal-fullscreen/modal-fullscreen.keys'; import type { BentoTeleportProps } from './teleport.types'; // inject() MUST be called during component setup (not inside an Options API // computed). In Vue 3.5+ calling inject outside of setup returns undefined, // which caused the "Invalid Teleport target: undefined" warning. const injectedModalKey = inject<string>(MODAL_DIALOG_ID_INJECTION_KEY, ''); const injectedModalFullscreenKey = inject<string>(MODAL_FULLSCREEN_DIALOG_ID_INJECTION_KEY, ''); const injectedSidepanelKey = inject<string>(SIDEPANEL_DIALOG_ID_INJECTION_KEY, ''); // Exposed to the template. Does not consider the `to` prop since that's // handled at the template call-site as `to || teleportTarget`. const teleportTarget = computed(() => { const key = injectedModalKey || injectedModalFullscreenKey || injectedSidepanelKey; return key ? `#${key}` : 'body'; }); </script> <script lang="ts"> export default defineComponent({ name: 'teleport', props: { to: { type: String as PropType<BentoTeleportProps['to']>, default: '', }, where: { type: String, default: 'after', }, disabled: Boolean as PropType<BentoTeleportProps['disabled']>, }, data: () => ({ childObserver: null, nodes: [], waiting: false, observer: null, parent: null, isVue2, }), computed: { classes() { if (this.disabled) { return ['b-teleport']; } return ['b-teleport', 'b-teleport--hidden']; }, // Only used by the Vue 2 polyfill path for internal DOM-move // target lookup (`document.querySelector(this.defaultTo)` in // `move()`). Vue 3 uses `teleportTarget` from <script setup> // directly in the template (`:to="to || teleportTarget"`). // // Vue 2 supports `inject()` during render-time computed // evaluation, so we resolve the modal/sidepanel injection keys // here. On Vue 3 this computed is never read because the // template branches on `isVue2` and the Vue 3 branch uses the // setup-bound `teleportTarget` instead. defaultTo() { if (this.to) { return this.to; } const injectionKey = inject<string>(MODAL_DIALOG_ID_INJECTION_KEY, '') || inject<string>(MODAL_FULLSCREEN_DIALOG_ID_INJECTION_KEY, '') || inject<string>(SIDEPANEL_DIALOG_ID_INJECTION_KEY, ''); return injectionKey ? `#${injectionKey}` : 'body'; }, }, watch: { to: 'maybeMove', where: 'maybeMove', disabled(value) { if (!isVue2) { return; } if (value) { this.disable(); // Ensure all event done. this.$nextTick(() => { this.teardownObserver(); }); } else { this.bootObserver(); this.move(); } }, }, mounted() { // In Vue 3, the built-in <Teleport> handles DOM movement. // Manual DOM manipulation and MutationObservers must be skipped // to avoid conflicting with Vue 3's virtual DOM tracking. if (!isVue2) { return; } // Store a reference to the nodes this.nodes = Array.from(this.$el.childNodes); if (!this.disabled) { this.bootObserver(); } // Move slot content to target this.maybeMove(); }, beforeDestroy() { // In Vue 3, cleanup is not needed as the built-in <Teleport> manages its own lifecycle. if (!isVue2) { return; } // Fix nodes reference this.nodes = this.getComponentChildrenNode(); // Move back this.disable(); // Stop observing this.teardownObserver(); }, methods: { // Using a fragment is faster because it'll trigger only a single reflow // See https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment getFragment(): DocumentFragment { const fragment = document.createDocumentFragment(); this.nodes.forEach(node => fragment.appendChild(node)); return fragment; }, disable() { this.$el.appendChild(this.getFragment()); this.parent = null; }, move() { this.waiting = false; this.parent = document.querySelector(this.defaultTo); if (!this.parent) { this.disable(); this.waiting = true; return; } if (this.where === 'before') { this.parent.prepend(this.getFragment()); } else { this.parent.appendChild(this.getFragment()); } }, maybeMove() { if (!isVue2 || this.disabled) { return; } this.move(); }, onMutations(mutations) { // Makes sure the move operation is only done once let shouldMove = false; for (let i = 0; i < mutations.length; i++) { const mutation = mutations[i]; const filteredAddedNodes = Array.from(mutation.addedNodes).filter( node => !this.nodes.includes(node) ); if (Array.from(mutation.removedNodes).includes(this.parent)) { this.disable(); this.waiting = !this.disabled; } else if (this.waiting && filteredAddedNodes.length > 0) { shouldMove = true; } } if (shouldMove) { this.move(); } }, bootObserver() { if (this.observer) { return; } this.observer = new MutationObserver(mutations => this.onMutations(mutations)); this.observer.observe(document.body, { childList: true, subtree: true, attributes: false, characterData: false, }); if (this.childObserver) { return; } // watch childNodes change this.childObserver = new MutationObserver(mutations => { const childChangeRecord = mutations.find(i => i.target === this.$el); if (childChangeRecord) { // Remove old nodes before update position. this.nodes.forEach(node => node.parentNode && node.parentNode.removeChild(node)); this.nodes = this.getComponentChildrenNode(); this.maybeMove(); } }); this.childObserver.observe(this.$el, { childList: true, subtree: false, attributes: false, characterData: false, }); }, teardownObserver() { if (this.observer) { this.observer.disconnect(); this.observer = null; } if (this.childObserver) { this.childObserver.disconnect(); this.childObserver = null; } }, getComponentChildrenNode() { // Vue3 if (!isVue2) { return []; } // Trick for Vue3 type checking return (this as any).$vnode.componentOptions.children.map(i => i.elm).filter(i => i); }, }, }); </script> <style lang="scss" scoped src="./teleport.scss" />
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<template> <div class="b-tooltip-test"> <div class="b-tooltip-test__item"> <button ref="target">toggle tooltip</button> <tooltip-container v-if="target" :position="BentoTooltipPosition.TOP" content="top" :target-element="target" :is-shown="true" > Toggle tooltip </tooltip-container> <tooltip-container v-if="target" :position="BentoTooltipPosition.RIGHT" content="right" :target-element="target" :is-shown="true" > Toggle tooltip </tooltip-container> <tooltip-container v-if="target" :position="BentoTooltipPosition.BOTTOM" content="bottom" :target-element="target" :is-shown="true" > Toggle tooltip </tooltip-container> <tooltip-container v-if="target" :position="BentoTooltipPosition.LEFT" content="left" :target-element="target" :is-shown="true" > Toggle tooltip </tooltip-container> </div> </div> </template> <script lang="ts"> import TooltipContainer from './tooltip.vue'; import { defineComponent, ref } from 'vue'; import { BentoTooltipPosition } from './tooltip.types'; export default defineComponent({ components: { TooltipContainer }, setup() { const target = ref(null); return { BentoTooltipPosition, target, }; }, }); </script> <style scoped lang="scss"> $margin: 150px !default; $margin-small: 80px !default; .b-tooltip-test { margin: $margin; &__item { margin-bottom: $margin-small; } } </style>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import { PopperContainerPositionBasic } from '../../popper-container/popper-container.types'; export const BentoTooltipPosition = { ...PopperContainerPositionBasic }; export type BentoTooltipPosition = PopperContainerPositionBasic; export enum BentoTooltipState { HIDDEN = 'hidden', }
|