@adyen/bento-mcp 0.0.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/LICENSE +21 -0
- package/README.md +113 -0
- package/dist/assets/components/accordion/accordion.docs.mdx +77 -0
- package/dist/assets/components/accordion/accordion.stories.ts +1 -0
- package/dist/assets/components/accordion/accordion.vue +1 -0
- package/dist/assets/components/accordion/components/accordion-item.stories.ts +1 -0
- package/dist/assets/components/accordion/components/accordion-item.vue +1 -0
- package/dist/assets/components/action-bar/action-bar.docs.mdx +103 -0
- package/dist/assets/components/action-bar/action-bar.stories.ts +1 -0
- package/dist/assets/components/action-bar/action-bar.vue +1 -0
- package/dist/assets/components/alert/alert.docs.mdx +106 -0
- package/dist/assets/components/alert/alert.stories.ts +1 -0
- package/dist/assets/components/alert/alert.vue +1 -0
- package/dist/assets/components/avatar/avatar.docs.mdx +44 -0
- package/dist/assets/components/avatar/avatar.stories.ts +1 -0
- package/dist/assets/components/avatar/avatar.vue +1 -0
- package/dist/assets/components/avatar/components/avatar-image/avatar-image.vue +1 -0
- package/dist/assets/components/button/button.docs.mdx +137 -0
- package/dist/assets/components/button/button.stories.ts +1 -0
- package/dist/assets/components/button/button.vue +1 -0
- package/dist/assets/components/button/components/ai-button/ai-button.docs.mdx +40 -0
- package/dist/assets/components/button/components/ai-button/ai-button.stories.ts +1 -0
- package/dist/assets/components/button/components/ai-button/ai-button.vue +1 -0
- package/dist/assets/components/button/components/button-actions/button-actions.docs.mdx +169 -0
- package/dist/assets/components/button/components/button-actions/button-actions.stories.ts +1 -0
- package/dist/assets/components/button/components/button-actions/button-actions.vue +1 -0
- package/dist/assets/components/button/components/loading-button/loading-button.docs.mdx +106 -0
- package/dist/assets/components/button/components/loading-button/loading-button.stories.ts +1 -0
- package/dist/assets/components/button/components/loading-button/loading-button.vue +1 -0
- package/dist/assets/components/button/components/toggle-button/toggle-button.docs.mdx +81 -0
- package/dist/assets/components/button/components/toggle-button/toggle-button.stories.ts +1 -0
- package/dist/assets/components/button/components/toggle-button/toggle-button.vue +1 -0
- package/dist/assets/components/card/card.docs.mdx +166 -0
- package/dist/assets/components/card/card.stories.ts +1 -0
- package/dist/assets/components/card/card.vue +1 -0
- package/dist/assets/components/checkbox/checkbox.docs.mdx +116 -0
- package/dist/assets/components/checkbox/checkbox.stories.ts +1 -0
- package/dist/assets/components/checkbox/checkbox.vue +1 -0
- package/dist/assets/components/checkbox/components/checkbox-group/checkbox-group.docs.mdx +134 -0
- package/dist/assets/components/checkbox/components/checkbox-group/checkbox-group.stories.ts +1 -0
- package/dist/assets/components/checkbox/components/checkbox-group/checkbox-group.vue +1 -0
- package/dist/assets/components/chip/chip.docs.mdx +51 -0
- package/dist/assets/components/chip/chip.stories.ts +1 -0
- package/dist/assets/components/chip/chip.vue +1 -0
- package/dist/assets/components/click-outside/click-outside.docs.mdx +30 -0
- package/dist/assets/components/click-outside/click-outside.stories.ts +1 -0
- package/dist/assets/components/code-snippet/code-snippet.docs.mdx +73 -0
- package/dist/assets/components/code-snippet/code-snippet.stories.ts +1 -0
- package/dist/assets/components/code-snippet/code-snippet.vue +1 -0
- package/dist/assets/components/country/country.docs.mdx +61 -0
- package/dist/assets/components/country/country.stories.ts +1 -0
- package/dist/assets/components/country/country.vue +1 -0
- package/dist/assets/components/currency/currency.docs.mdx +38 -0
- package/dist/assets/components/currency/currency.stories.ts +1 -0
- package/dist/assets/components/currency/currency.vue +1 -0
- package/dist/assets/components/dashed-underline/dashed-underline.docs.mdx +57 -0
- package/dist/assets/components/dashed-underline/dashed-underline.stories.ts +1 -0
- package/dist/assets/components/dashed-underline/dashed-underline.vue +1 -0
- package/dist/assets/components/data-grid/components/data-grid-cell/data-grid-cell.vue +1 -0
- package/dist/assets/components/data-grid/components/data-grid-cell-actions/data-grid-cell-actions.vue +1 -0
- package/dist/assets/components/data-grid/components/data-grid-columns/data-grid-columns.vue +1 -0
- package/dist/assets/components/data-grid/components/data-grid-config-settings/components/data-grid-config-settings-column-row/data-grid-config-settings-column-row.vue +1 -0
- package/dist/assets/components/data-grid/components/data-grid-config-settings/data-grid-config-settings.vue +1 -0
- package/dist/assets/components/data-grid/components/data-grid-lazy-load/data-grid-lazy-load.vue +1 -0
- package/dist/assets/components/data-grid/components/data-grid-row/data-grid-row.vue +1 -0
- package/dist/assets/components/data-grid/components/data-grid-static-icon/data-grid-static-icon.docs.mdx +61 -0
- package/dist/assets/components/data-grid/components/data-grid-static-icon/data-grid-static-icon.stories.ts +1 -0
- package/dist/assets/components/data-grid/components/data-grid-static-icon/data-grid-static-icon.vue +1 -0
- package/dist/assets/components/data-grid/components/data-grid-summary-cell/data-grid-summary-cell.docs.mdx +56 -0
- package/dist/assets/components/data-grid/components/data-grid-summary-cell/data-grid-summary-cell.stories.ts +1 -0
- package/dist/assets/components/data-grid/components/data-grid-summary-cell/data-grid-summary-cell.vue +1 -0
- package/dist/assets/components/data-grid/components/data-grid-trend-cell/data-grid-trend-cell.docs.mdx +48 -0
- package/dist/assets/components/data-grid/components/data-grid-trend-cell/data-grid-trend-cell.stories.ts +1 -0
- package/dist/assets/components/data-grid/components/data-grid-trend-cell/data-grid-trend-cell.vue +1 -0
- package/dist/assets/components/data-grid/data-grid.docs.mdx +951 -0
- package/dist/assets/components/data-grid/data-grid.stories.ts +1 -0
- package/dist/assets/components/data-grid/data-grid.vue +1 -0
- package/dist/assets/components/date/date.docs.mdx +49 -0
- package/dist/assets/components/date/date.stories.ts +1 -0
- package/dist/assets/components/date/date.vue +1 -0
- package/dist/assets/components/date-picker/date-picker.docs.mdx +69 -0
- package/dist/assets/components/date-picker/date-picker.stories.ts +1 -0
- package/dist/assets/components/date-picker/date-picker.vue +1 -0
- package/dist/assets/components/date-range-picker/components/date-range-picker-calendar/date-range-picker-calendar.vue +1 -0
- package/dist/assets/components/date-range-picker/date-range-picker.docs.mdx +168 -0
- package/dist/assets/components/date-range-picker/date-range-picker.stories.ts +1 -0
- package/dist/assets/components/date-range-picker/date-range-picker.vue +1 -0
- package/dist/assets/components/divider/divider.docs.mdx +27 -0
- package/dist/assets/components/divider/divider.stories.ts +1 -0
- package/dist/assets/components/divider/divider.vue +1 -0
- package/dist/assets/components/dropdown/components/dropdown-base-textbox/dropdown-base-textbox.vue +1 -0
- package/dist/assets/components/dropdown/components/dropdown-default-textbox/dropdown-default-textbox.vue +1 -0
- package/dist/assets/components/dropdown/components/dropdown-options-container/dropdown-options-container.vue +1 -0
- package/dist/assets/components/dropdown/components/dropdown-small-textbox/dropdown-small-textbox.vue +1 -0
- package/dist/assets/components/dropdown/components/variants/dropdown-avatar/dropdown-avatar.stories.ts +1 -0
- package/dist/assets/components/dropdown/components/variants/dropdown-avatar/dropdown-avatar.vue +1 -0
- package/dist/assets/components/dropdown/components/variants/dropdown-country/dropdown-country.stories.ts +1 -0
- package/dist/assets/components/dropdown/components/variants/dropdown-country/dropdown-country.vue +1 -0
- package/dist/assets/components/dropdown/components/variants/dropdown-currency/dropdown-currency.stories.ts +1 -0
- package/dist/assets/components/dropdown/components/variants/dropdown-currency/dropdown-currency.vue +1 -0
- package/dist/assets/components/dropdown/components/variants/dropdown-payment-method/dropdown-payment-method.stories.ts +1 -0
- package/dist/assets/components/dropdown/components/variants/dropdown-payment-method/dropdown-payment-method.vue +1 -0
- package/dist/assets/components/dropdown/components/variants/dropdown-tag/dropdown-tag.stories.ts +1 -0
- package/dist/assets/components/dropdown/components/variants/dropdown-tag/dropdown-tag.vue +1 -0
- package/dist/assets/components/dropdown/dropdown.docs.mdx +420 -0
- package/dist/assets/components/dropdown/dropdown.stories.ts +1 -0
- package/dist/assets/components/dropdown/dropdown.vue +1 -0
- package/dist/assets/components/empty-state/empty-state.docs.mdx +115 -0
- package/dist/assets/components/empty-state/empty-state.stories.ts +1 -0
- package/dist/assets/components/empty-state/empty-state.vue +1 -0
- package/dist/assets/components/file-uploader/components/file-uploader-file-card/components/file-uploader-file-card-animation/file-uploader-file-card-animation.vue +1 -0
- package/dist/assets/components/file-uploader/components/file-uploader-file-card/file-uploader-file-card.stories.ts +1 -0
- package/dist/assets/components/file-uploader/components/file-uploader-file-card/file-uploader-file-card.vue +1 -0
- package/dist/assets/components/file-uploader/components/file-uploader-restrictions/file-uploader-restrictions.stories.ts +1 -0
- package/dist/assets/components/file-uploader/components/file-uploader-restrictions/file-uploader-restrictions.vue +1 -0
- package/dist/assets/components/file-uploader/file-uploader.docs.mdx +117 -0
- package/dist/assets/components/file-uploader/file-uploader.stories.ts +1 -0
- package/dist/assets/components/file-uploader/file-uploader.vue +1 -0
- package/dist/assets/components/filter-bar/components/all-filters-modal/all-filters-modal.stories.ts +1 -0
- package/dist/assets/components/filter-bar/components/all-filters-modal/all-filters-modal.vue +1 -0
- package/dist/assets/components/filter-bar/components/all-filters-modal/components/all-filters-modal-button/all-filters-modal-button.vue +1 -0
- package/dist/assets/components/filter-bar/components/base-filter/base-filter.docs.mdx +90 -0
- package/dist/assets/components/filter-bar/components/base-filter/base-filter.stories.ts +1 -0
- package/dist/assets/components/filter-bar/components/base-filter/base-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/base-filter-button/base-filter-button.vue +1 -0
- package/dist/assets/components/filter-bar/components/boolean-filter/boolean-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/checkbox-group-filter/checkbox-group-filter.stories.ts +1 -0
- package/dist/assets/components/filter-bar/components/checkbox-group-filter/checkbox-group-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/date-filter/date-filter.stories.ts +1 -0
- package/dist/assets/components/filter-bar/components/date-filter/date-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/date-range-filter/date-range-filter.stories.ts +1 -0
- package/dist/assets/components/filter-bar/components/date-range-filter/date-range-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/filter-bar-button/filter-bar-button.vue +1 -0
- package/dist/assets/components/filter-bar/components/filter-bar-button-group/filter-bar-button-group.vue +1 -0
- package/dist/assets/components/filter-bar/components/input-filter/input-filter.docs.mdx +18 -0
- package/dist/assets/components/filter-bar/components/input-filter/input-filter.stories.ts +1 -0
- package/dist/assets/components/filter-bar/components/input-filter/input-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/input-with-dropdown-filter/input-with-dropdown-filter.docs.mdx +18 -0
- package/dist/assets/components/filter-bar/components/input-with-dropdown-filter/input-with-dropdown-filter.stories.ts +1 -0
- package/dist/assets/components/filter-bar/components/input-with-dropdown-filter/input-with-dropdown-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/radio-group-filter/radio-group-filter.stories.ts +1 -0
- package/dist/assets/components/filter-bar/components/radio-group-filter/radio-group-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/range-filter/range-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/select-filter/components/select-filter-button/select-filter-button.vue +1 -0
- package/dist/assets/components/filter-bar/components/select-filter/components/select-filter-dropdown/select-filter-dropdown.vue +1 -0
- package/dist/assets/components/filter-bar/components/select-filter/select-filter.stories.ts +1 -0
- package/dist/assets/components/filter-bar/components/select-filter/select-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/select-filter/variants/select-avatar-filter/select-avatar-filter.stories.ts +1 -0
- package/dist/assets/components/filter-bar/components/select-filter/variants/select-avatar-filter/select-avatar-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/select-filter/variants/select-country-filter/select-country-filter.stories.ts +1 -0
- package/dist/assets/components/filter-bar/components/select-filter/variants/select-country-filter/select-country-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/select-filter/variants/select-currency-filter/select-currency-filter.stories.ts +1 -0
- package/dist/assets/components/filter-bar/components/select-filter/variants/select-currency-filter/select-currency-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/select-filter/variants/select-payment-method-filter/select-payment-method-filter.stories.ts +1 -0
- package/dist/assets/components/filter-bar/components/select-filter/variants/select-payment-method-filter/select-payment-method-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/select-filter/variants/select-tag-filter/select-tag-filter.stories.ts +1 -0
- package/dist/assets/components/filter-bar/components/select-filter/variants/select-tag-filter/select-tag-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/toggle-filter/toggle-filter.stories.ts +1 -0
- package/dist/assets/components/filter-bar/components/toggle-filter/toggle-filter.vue +1 -0
- package/dist/assets/components/filter-bar/filter-bar.docs.mdx +1005 -0
- package/dist/assets/components/filter-bar/filter-bar.stories.ts +1 -0
- package/dist/assets/components/filter-bar/filter-bar.vue +1 -0
- package/dist/assets/components/focus-trap/focus-trap.docs.mdx +10 -0
- package/dist/assets/components/focus-trap/focus-trap.stories.ts +1 -0
- package/dist/assets/components/focus-trap/focus-trap.vue +1 -0
- package/dist/assets/components/form-layout/components/form-layout-field-tip/form-layout-field-tip.vue +1 -0
- package/dist/assets/components/form-layout/components/form-layout-group/form-layout-group.vue +1 -0
- package/dist/assets/components/form-layout/components/form-layout-title/form-layout-title.vue +1 -0
- package/dist/assets/components/form-layout/form-layout.docs.mdx +452 -0
- package/dist/assets/components/form-layout/form-layout.stories.ts +1 -0
- package/dist/assets/components/form-layout/form-layout.vue +1 -0
- package/dist/assets/components/header/header.docs.mdx +85 -0
- package/dist/assets/components/header/header.stories.ts +1 -0
- package/dist/assets/components/header/header.vue +1 -0
- package/dist/assets/components/header-with-views/components/header-with-views-all-views/header-with-views-all-views.vue +1 -0
- package/dist/assets/components/header-with-views/components/header-with-views-item-edit-form/header-with-views-edit-form.vue +1 -0
- package/dist/assets/components/header-with-views/header-with-views.docs.mdx +103 -0
- package/dist/assets/components/header-with-views/header-with-views.stories.ts +1 -0
- package/dist/assets/components/header-with-views/header-with-views.vue +1 -0
- package/dist/assets/components/image/image.docs.mdx +34 -0
- package/dist/assets/components/image/image.stories.ts +1 -0
- package/dist/assets/components/image/image.vue +1 -0
- package/dist/assets/components/info-icon/info-icon.docs.mdx +57 -0
- package/dist/assets/components/info-icon/info-icon.stories.ts +1 -0
- package/dist/assets/components/info-icon/info-icon.vue +1 -0
- package/dist/assets/components/input-field/input-field.docs.mdx +141 -0
- package/dist/assets/components/input-field/input-field.stories.ts +1 -0
- package/dist/assets/components/input-field/input-field.vue +1 -0
- package/dist/assets/components/input-field-password/input-field-password.docs.mdx +292 -0
- package/dist/assets/components/input-field-password/input-field-password.stories.ts +1 -0
- package/dist/assets/components/input-field-password/input-field-password.vue +1 -0
- package/dist/assets/components/input-field-phone-number/input-field-phone-number.docs.mdx +72 -0
- package/dist/assets/components/input-field-phone-number/input-field-phone-number.stories.ts +1 -0
- package/dist/assets/components/input-field-phone-number/input-field-phone-number.vue +1 -0
- package/dist/assets/components/inspector/inspector.docs.mdx +118 -0
- package/dist/assets/components/inspector/inspector.stories.ts +1 -0
- package/dist/assets/components/inspector/inspector.vue +1 -0
- package/dist/assets/components/internal/base-button/base-button.vue +1 -0
- package/dist/assets/components/internal/button-actions-with-menu/button-actions-with-menu.stories.ts +1 -0
- package/dist/assets/components/internal/button-actions-with-menu/button-actions-with-menu.vue +1 -0
- package/dist/assets/components/internal/calendar/calendar.stories.ts +1 -0
- package/dist/assets/components/internal/calendar/calendar.vue +1 -0
- package/dist/assets/components/internal/calendar/components/calendar-month/calendar-month.stories.ts +1 -0
- package/dist/assets/components/internal/calendar/components/calendar-month/calendar-month.vue +1 -0
- package/dist/assets/components/internal/calendar/components/calendar-month/components/calendar-month-day/__test__/calendar-month-day-stub.vue +1 -0
- package/dist/assets/components/internal/calendar/components/calendar-month/components/calendar-month-day/calendar-month-day.stories.ts +1 -0
- package/dist/assets/components/internal/calendar/components/calendar-month/components/calendar-month-day/calendar-month-day.vue +1 -0
- package/dist/assets/components/internal/calendar/components/calendar-year/calendar-year.vue +1 -0
- package/dist/assets/components/internal/calendar/components/calendar-year/components/calendar-year-month/__test__/calendar-year-month-stub.vue +1 -0
- package/dist/assets/components/internal/calendar/components/calendar-year/components/calendar-year-month/calendar-year-month.vue +1 -0
- package/dist/assets/components/internal/checkbox-input/checkbox-input.stories.ts +1 -0
- package/dist/assets/components/internal/checkbox-input/checkbox-input.vue +1 -0
- package/dist/assets/components/internal/controls-group/controls-group.vue +1 -0
- package/dist/assets/components/internal/copy/copy.docs.mdx +43 -0
- package/dist/assets/components/internal/copy/copy.stories.ts +1 -0
- package/dist/assets/components/internal/copy/copy.vue +1 -0
- package/dist/assets/components/internal/decorative-checkbox/decorative-checkbox.vue +1 -0
- package/dist/assets/components/internal/dialog-page/dialog-page.vue +1 -0
- package/dist/assets/components/internal/error-message/error-message.vue +1 -0
- package/dist/assets/components/internal/field-label/field-label.vue +1 -0
- package/dist/assets/components/internal/fixed-scroller/fixed-scroller.docs.mdx +21 -0
- package/dist/assets/components/internal/fixed-scroller/fixed-scroller.stories.ts +1 -0
- package/dist/assets/components/internal/fixed-scroller/fixed-scroller.vue +1 -0
- package/dist/assets/components/internal/footer-actions/footer-actions.vue +1 -0
- package/dist/assets/components/internal/grid-layout/grid-layout.vue +1 -0
- package/dist/assets/components/internal/header-meta/header-meta.vue +1 -0
- package/dist/assets/components/internal/info-icon-with-popover/info-icon-with-popover.vue +1 -0
- package/dist/assets/components/internal/listbox/components/listbox-multi-select/components/listbox-multi-select-options/components/listbox-multi-select-category/listbox-multi-select-category.vue +1 -0
- package/dist/assets/components/internal/listbox/components/listbox-multi-select/components/listbox-multi-select-options/listbox-multi-select-options.vue +1 -0
- package/dist/assets/components/internal/listbox/components/listbox-multi-select/listbox-multi-select.vue +1 -0
- package/dist/assets/components/internal/listbox/components/listbox-option/listbox-option.vue +1 -0
- package/dist/assets/components/internal/listbox/components/listbox-option-tag/listbox-option-tag.vue +1 -0
- package/dist/assets/components/internal/listbox/components/listbox-single-select/components/listbox-single-select-category/listbox-single-select-category.vue +1 -0
- package/dist/assets/components/internal/listbox/components/listbox-single-select/listbox-single-select.vue +1 -0
- package/dist/assets/components/internal/listbox/components/listbox-single-select-option/listbox-single-select-option.vue +1 -0
- package/dist/assets/components/internal/listbox/listbox.stories.ts +1 -0
- package/dist/assets/components/internal/listbox/listbox.vue +1 -0
- package/dist/assets/components/internal/radio-input/radio-input.stories.ts +1 -0
- package/dist/assets/components/internal/radio-input/radio-input.vue +1 -0
- package/dist/assets/components/internal/teleport/teleport.vue +1 -0
- package/dist/assets/components/internal/tooltip/tooltip.test.vue +1 -0
- package/dist/assets/components/internal/tooltip/tooltip.vue +1 -0
- package/dist/assets/components/layout/components/layout-row/layout-row.stories.ts +1 -0
- package/dist/assets/components/layout/components/layout-row/layout-row.vue +1 -0
- package/dist/assets/components/layout/layout.docs.mdx +108 -0
- package/dist/assets/components/layout/layout.stories.ts +1 -0
- package/dist/assets/components/layout/layout.vue +1 -0
- package/dist/assets/components/link/link.docs.mdx +119 -0
- package/dist/assets/components/link/link.stories.ts +1 -0
- package/dist/assets/components/link/link.vue +1 -0
- package/dist/assets/components/loading-indicator/loading-indicator.docs.mdx +65 -0
- package/dist/assets/components/loading-indicator/loading-indicator.stories.ts +1 -0
- package/dist/assets/components/loading-indicator/loading-indicator.vue +1 -0
- package/dist/assets/components/menu/components/menu-item/menu-item.vue +1 -0
- package/dist/assets/components/menu/components/menu-item-list/menu-item-list.vue +1 -0
- package/dist/assets/components/menu/components/menu-list-container/menu-list-container.vue +1 -0
- package/dist/assets/components/menu/components/sub-menu-item-list/sub-menu-item-list.vue +1 -0
- package/dist/assets/components/menu/menu.docs.mdx +121 -0
- package/dist/assets/components/menu/menu.stories.ts +1 -0
- package/dist/assets/components/menu/menu.vue +1 -0
- package/dist/assets/components/modal/components/base-modal/base-modal.vue +1 -0
- package/dist/assets/components/modal/components/modal-default/modal-default.vue +1 -0
- package/dist/assets/components/modal/components/modal-dialog/modal-dialog.vue +1 -0
- package/dist/assets/components/modal/components/modal-page/modal-page.vue +1 -0
- package/dist/assets/components/modal/modal.docs.mdx +215 -0
- package/dist/assets/components/modal/modal.stories.ts +1 -0
- package/dist/assets/components/modal/modal.vue +1 -0
- package/dist/assets/components/modal-fullscreen/components/modal-fullscreen-page.vue +1 -0
- package/dist/assets/components/modal-fullscreen/modal-fullscreen.docs.mdx +217 -0
- package/dist/assets/components/modal-fullscreen/modal-fullscreen.stories.ts +1 -0
- package/dist/assets/components/modal-fullscreen/modal-fullscreen.vue +1 -0
- package/dist/assets/components/pagination/components/pagination-context/pagination-context.vue +1 -0
- package/dist/assets/components/pagination/components/pagination-controls/pagination-controls.vue +1 -0
- package/dist/assets/components/pagination/components/pagination-results-per-page/pagination-results-per-page.vue +1 -0
- package/dist/assets/components/pagination/pagination.docs.mdx +116 -0
- package/dist/assets/components/pagination/pagination.stories.ts +1 -0
- package/dist/assets/components/pagination/pagination.vue +1 -0
- package/dist/assets/components/payment-method/payment-method.docs.mdx +34 -0
- package/dist/assets/components/payment-method/payment-method.stories.ts +1 -0
- package/dist/assets/components/payment-method/payment-method.vue +1 -0
- package/dist/assets/components/popover/components/popover-dismiss-button/popover-dismiss-button.vue +1 -0
- package/dist/assets/components/popover/components/popover-image/popover-image.vue +1 -0
- package/dist/assets/components/popover/components/popover-title/popover-title.vue +1 -0
- package/dist/assets/components/popover/popover.docs.mdx +89 -0
- package/dist/assets/components/popover/popover.stories.ts +1 -0
- package/dist/assets/components/popover/popover.vue +1 -0
- package/dist/assets/components/popper-container/popper-container.docs.mdx +11 -0
- package/dist/assets/components/popper-container/popper-container.stories.ts +1 -0
- package/dist/assets/components/popper-container/popper-container.vue +1 -0
- package/dist/assets/components/promo-banner/promo-banner.docs.mdx +94 -0
- package/dist/assets/components/promo-banner/promo-banner.stories.ts +1 -0
- package/dist/assets/components/promo-banner/promo-banner.vue +1 -0
- package/dist/assets/components/radio-group/components/radio-button/radio-button.docs.mdx +76 -0
- package/dist/assets/components/radio-group/components/radio-button/radio-button.stories.ts +1 -0
- package/dist/assets/components/radio-group/components/radio-button/radio-button.vue +1 -0
- package/dist/assets/components/radio-group/radio-group.docs.mdx +154 -0
- package/dist/assets/components/radio-group/radio-group.stories.ts +1 -0
- package/dist/assets/components/radio-group/radio-group.vue +1 -0
- package/dist/assets/components/rich-text-editor/components/rich-text-editor-content-area/rich-text-editor-content-area.vue +1 -0
- package/dist/assets/components/rich-text-editor/components/rich-text-editor-renderer/components/rich-text-editor-code-block/rich-text-editor-code-block.vue +1 -0
- package/dist/assets/components/rich-text-editor/components/rich-text-editor-renderer/components/rich-text-editor-inline-code/rich-text-editor-inline-code.vue +1 -0
- package/dist/assets/components/rich-text-editor/components/rich-text-editor-renderer/components/rich-text-editor-link/rich-text-editor-link.vue +1 -0
- package/dist/assets/components/rich-text-editor/components/rich-text-editor-renderer/components/rich-text-editor-list-item/rich-text-editor-list-item.vue +1 -0
- package/dist/assets/components/rich-text-editor/components/rich-text-editor-renderer/components/rich-text-editor-table/rich-text-editor-table.vue +1 -0
- package/dist/assets/components/rich-text-editor/rich-text-editor.docs.mdx +52 -0
- package/dist/assets/components/rich-text-editor/rich-text-editor.stories.ts +1 -0
- package/dist/assets/components/rich-text-editor/rich-text-editor.vue +1 -0
- package/dist/assets/components/search-bar/search-bar.docs.mdx +117 -0
- package/dist/assets/components/search-bar/search-bar.stories.ts +1 -0
- package/dist/assets/components/search-bar/search-bar.vue +1 -0
- package/dist/assets/components/secondary-nav/components/secondary-nav-category/secondary-nav-category.vue +1 -0
- package/dist/assets/components/secondary-nav/components/secondary-nav-item/secondary-nav-item.vue +1 -0
- package/dist/assets/components/secondary-nav/secondary-nav.docs.mdx +40 -0
- package/dist/assets/components/secondary-nav/secondary-nav.stories.ts +1 -0
- package/dist/assets/components/secondary-nav/secondary-nav.vue +1 -0
- package/dist/assets/components/segmented-control/components/segmented-control-button/segmented-control-button.vue +1 -0
- package/dist/assets/components/segmented-control/segmented-control.docs.mdx +90 -0
- package/dist/assets/components/segmented-control/segmented-control.stories.ts +1 -0
- package/dist/assets/components/segmented-control/segmented-control.vue +1 -0
- package/dist/assets/components/selection-card/components/selection-card-group/selection-card-group.docs.mdx +119 -0
- package/dist/assets/components/selection-card/components/selection-card-group/selection-card-group.stories.ts +1 -0
- package/dist/assets/components/selection-card/components/selection-card-group/selection-card-group.vue +1 -0
- package/dist/assets/components/selection-card/selection-card.docs.mdx +108 -0
- package/dist/assets/components/selection-card/selection-card.stories.ts +1 -0
- package/dist/assets/components/selection-card/selection-card.vue +1 -0
- package/dist/assets/components/sidepanel/components/sidepanel-page.stories.ts +1 -0
- package/dist/assets/components/sidepanel/components/sidepanel-page.vue +1 -0
- package/dist/assets/components/sidepanel/sidepanel.docs.mdx +129 -0
- package/dist/assets/components/sidepanel/sidepanel.stories.ts +1 -0
- package/dist/assets/components/sidepanel/sidepanel.vue +1 -0
- package/dist/assets/components/status/status.docs.mdx +62 -0
- package/dist/assets/components/status/status.stories.ts +1 -0
- package/dist/assets/components/status/status.vue +1 -0
- package/dist/assets/components/stepper/components/step/step.stories.ts +1 -0
- package/dist/assets/components/stepper/components/step/step.vue +1 -0
- package/dist/assets/components/stepper/stepper.docs.mdx +127 -0
- package/dist/assets/components/stepper/stepper.stories.ts +1 -0
- package/dist/assets/components/stepper/stepper.vue +1 -0
- package/dist/assets/components/structured-list/components/structered-list-item/structured-list-item.stories.ts +1 -0
- package/dist/assets/components/structured-list/components/structered-list-item/structured-list-item.vue +1 -0
- package/dist/assets/components/structured-list/structured-list.docs.mdx +68 -0
- package/dist/assets/components/structured-list/structured-list.stories.ts +1 -0
- package/dist/assets/components/structured-list/structured-list.vue +1 -0
- package/dist/assets/components/summary-grid/components/summary-grid-item/summary-grid-item.vue +1 -0
- package/dist/assets/components/summary-grid/components/summary-grid-item-amount/summary-grid-item-amount.stories.ts +1 -0
- package/dist/assets/components/summary-grid/components/summary-grid-item-amount/summary-grid-item-amount.vue +1 -0
- package/dist/assets/components/summary-grid/components/summary-grid-item-custom/summary-grid-item-custom.stories.ts +1 -0
- package/dist/assets/components/summary-grid/components/summary-grid-item-custom/summary-grid-item-custom.vue +1 -0
- package/dist/assets/components/summary-grid/components/summary-grid-item-numeric/summary-grid-item-numeric.stories.ts +1 -0
- package/dist/assets/components/summary-grid/components/summary-grid-item-numeric/summary-grid-item-numeric.vue +1 -0
- package/dist/assets/components/summary-grid/components/summary-grid-item-percentage/summary-grid-item-percentage.stories.ts +1 -0
- package/dist/assets/components/summary-grid/components/summary-grid-item-percentage/summary-grid-item-percentage.vue +1 -0
- package/dist/assets/components/summary-grid/components/summary-grid-item-text/summary-grid-item-text.stories.ts +1 -0
- package/dist/assets/components/summary-grid/components/summary-grid-item-text/summary-grid-item-text.vue +1 -0
- package/dist/assets/components/summary-grid/summary-grid.docs.mdx +152 -0
- package/dist/assets/components/summary-grid/summary-grid.stories.ts +1 -0
- package/dist/assets/components/summary-grid/summary-grid.vue +1 -0
- package/dist/assets/components/tabs/components/tab.vue +1 -0
- package/dist/assets/components/tabs/tabs.docs.mdx +98 -0
- package/dist/assets/components/tabs/tabs.stories.ts +1 -0
- package/dist/assets/components/tabs/tabs.vue +1 -0
- package/dist/assets/components/tag/tag.docs.mdx +137 -0
- package/dist/assets/components/tag/tag.stories.ts +1 -0
- package/dist/assets/components/tag/tag.vue +1 -0
- package/dist/assets/components/textarea/textarea.docs.mdx +53 -0
- package/dist/assets/components/textarea/textarea.stories.ts +1 -0
- package/dist/assets/components/textarea/textarea.vue +1 -0
- package/dist/assets/components/timeline/components/timeline-item/timeline-item.stories.ts +1 -0
- package/dist/assets/components/timeline/components/timeline-item/timeline-item.vue +1 -0
- package/dist/assets/components/timeline/timeline.docs.mdx +131 -0
- package/dist/assets/components/timeline/timeline.stories.ts +1 -0
- package/dist/assets/components/timeline/timeline.vue +1 -0
- package/dist/assets/components/toast/components/toast-item/toast-item.vue +1 -0
- package/dist/assets/components/toast/components/toast-shadow-stack/toast-shadow-stack.vue +1 -0
- package/dist/assets/components/toast/toast.docs.mdx +151 -0
- package/dist/assets/components/toast/toast.stories.ts +1 -0
- package/dist/assets/components/toast/toast.vue +1 -0
- package/dist/assets/components/toggle/toggle.docs.mdx +81 -0
- package/dist/assets/components/toggle/toggle.stories.ts +1 -0
- package/dist/assets/components/toggle/toggle.vue +1 -0
- package/dist/assets/components/tutorial/components/tutorial-popover/components/tutorial-indicator/tutorial-indicator.vue +1 -0
- package/dist/assets/components/tutorial/components/tutorial-popover/tutorial-popover.vue +1 -0
- package/dist/assets/components/tutorial/tutorial.docs.mdx +85 -0
- package/dist/assets/components/tutorial/tutorial.stories.ts +1 -0
- package/dist/assets/components/tutorial/tutorial.vue +1 -0
- package/dist/assets/components/typography/typography.docs.mdx +124 -0
- package/dist/assets/components/typography/typography.stories.ts +1 -0
- package/dist/assets/components/typography/typography.vue +1 -0
- package/dist/assets/composables/use-bento-currency/use-bento-currency.docs.mdx +28 -0
- package/dist/assets/composables/use-bento-currency/use-bento-currency.stories.ts +1 -0
- package/dist/assets/composables/use-click-outside/use-click-outside.docs.mdx +10 -0
- package/dist/assets/composables/use-click-outside/use-click-outside.stories.ts +1 -0
- package/dist/assets/directives/highlight/highlight.docs.mdx +64 -0
- package/dist/assets/directives/highlight/highlight.stories.ts +1 -0
- package/dist/assets/directives/keyboard-navigation/keyboard-navigation.docs.mdx +41 -0
- package/dist/assets/directives/keyboard-navigation/keyboard-navigation.stories.ts +1 -0
- package/dist/assets/directives/tooltip/tooltip.docs.mdx +86 -0
- package/dist/assets/directives/tooltip/tooltip.stories.ts +1 -0
- package/dist/assets/index.ts +1 -0
- package/dist/assets/templates/dashboard-template/components/dashboard-section/dashboard-section.stories.ts +1 -0
- package/dist/assets/templates/dashboard-template/components/dashboard-section/dashboard-section.vue +1 -0
- package/dist/assets/templates/dashboard-template/dashboard-template.docs.mdx +148 -0
- package/dist/assets/templates/dashboard-template/dashboard-template.stories.ts +1 -0
- package/dist/assets/templates/dashboard-template/dashboard-template.vue +1 -0
- package/dist/assets/templates/data-grid-template/data-grid-template.docs.mdx +117 -0
- package/dist/assets/templates/data-grid-template/data-grid-template.stories.ts +1 -0
- package/dist/assets/templates/data-grid-template/data-grid-template.vue +1 -0
- package/dist/main.js +30031 -0
- package/package.json +45 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <div class="b-loading-indicator" :class="conditionalClasses" aria-hidden="true" data-testid="loading-indicator" /> </template> <script setup lang="ts"> import { computed, onMounted, onUnmounted } from 'vue'; import { BentoLoadingIndicatorEvent, type BentoLoadingIndicatorProps } from './loading-indicator.types'; const props = withDefaults(defineProps<BentoLoadingIndicatorProps>(), { small: false, large: false, inverse: false, }); const emit = defineEmits<{ (e: BentoLoadingIndicatorEvent.START): void; (e: BentoLoadingIndicatorEvent.END): void; }>(); onMounted(() => emit(BentoLoadingIndicatorEvent.START)); onUnmounted(() => emit(BentoLoadingIndicatorEvent.END)); const conditionalClasses = computed(() => ({ 'b-loading-indicator--inverse': props.inverse, 'b-loading-indicator--small': props.small, 'b-loading-indicator--large': props.large, })); </script> <script lang="ts"> /** * A loading indicator component that displays a spinning animation. * * @usage * import { BentoLoadingIndicator } from '@adyen/bento-vue2'; * * export default { * components: { BentoLoadingIndicator }, * template: ` * <bento-loading-indicator :small="true" /> * ` * }; */ export default { name: 'bento-loading-indicator', }; </script> <style lang="scss" scoped src="./loading-indicator.scss" />
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <base-button class="b-menu-item" role="menuitem" :class="conditionalClasses" :disabled="disabled" tabindex="-1" @click="onClick" > <template v-if="icon" #iconLeft> <component :is="icon" class="b-menu-item__icon-left" aria-hidden="true" /> </template> <template #default> <bento-typography el="span" variant="body" :stronger="isTitle" class="b-menu-item__primary-text"> {{ text }} </bento-typography> <bento-typography v-if="computedSecondaryText" el="span" variant="body" class="b-menu-item__secondary-text"> {{ computedSecondaryText }} </bento-typography> </template> <template v-if="chevron" #iconRight><chevron-right-icon aria-hidden="true" /></template> </base-button> </template> <script lang="ts"> import { BaseButton, BentoBaseButtonEvent } from '@/components/internal/base-button'; import { BentoTypography } from '@/components/typography'; import { truncate } from '@/utils/ts/truncate'; import ChevronRightIcon from '@adyen/ui-assets-icons-16/vue/chevron-right'; import { computed, defineComponent, type PropType } from 'vue'; import { type BentoMenuItem } from './menu-item.types'; /** * Menu item component to render items in a menu. For internal use only. */ export default defineComponent({ name: 'menu-item', components: { BaseButton, BentoTypography, ChevronRightIcon }, props: { /** * menu-item text that gets displayed as the main label */ text: { type: String, required: true, }, /** * menu-item action that gets executed when the MeniItem is clicked */ handler: { type: Function as PropType<BentoMenuItem['handler']>, required: true, }, /** * menu-item text that gets displayed as the secondary label */ secondaryText: { type: String, default: null, }, /** * Optional chevron icon displayed to the right of the menu-item */ chevron: { type: Boolean, default: false, }, /** * Optional icon displayed to the left of the menu-item */ icon: { // Types for Vue2 (Object) and Vue3 (Function) // TODO: Remove Object when fully migrated to Vue3 type: [Object, Function] as PropType<BentoMenuItem['icon']>, default: null, }, /** * Disables menu-item button functionality */ disabled: { type: Boolean, default: false, }, /** * Displays an indent variant od the menu-item */ indent: { type: Boolean, default: false, }, /** * Displays menu-item button as critical */ critical: { type: Boolean, default: false, }, /** * Renders the text in a bold variant */ isTitle: { type: Boolean, default: false, }, }, emits: [BentoBaseButtonEvent.CLICK], setup(props, { emit }) { const MAX_SECONDARY_TEXT_LENGTH = 10; const conditionalClasses = computed(() => ({ 'b-menu-item--critical': props.critical, 'b-menu-item--indent': props.indent, })); const computedSecondaryText = computed(() => truncate(props.secondaryText, MAX_SECONDARY_TEXT_LENGTH)); const onClick = (e: MouseEvent) => { e.stopPropagation(); props.handler(); emit(BentoBaseButtonEvent.CLICK); }; return { // Values conditionalClasses, computedSecondaryText, // Methods onClick, }; }, }); </script> <style lang="scss" scoped src="./menu-item.scss" />
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <menu-list-container ref="menuItemListWrapperRef" :style="computedStyle"> <ul ref="menuItemListRef" class="b-menu-item-list" role="menu" @keydown.down="focusNext" @keydown.up="focusPrevious" @keydown.esc="closeMenu" @keydown.tab="closeMenu" > <li v-for="(item, index) in computedData" :key="`menu-item-${index}`" ref="menuItemRef" class="b-menu-item-list__item" role="none" @keydown.right="focusRight(item, index)" @keydown.left="focusLeft()" > <menu-item :text="item.text" :handler="!item.items?.length ? item.handler : () => null" :secondary-text="item.secondaryText" :icon="item.icon" :chevron="!!item.items?.length" :disabled="item.disabled" :critical="item.critical" :indent="item.indent" :tabindex="index === subMenuFocusedIndex ? '0' : '-1'" :aria-haspopup="!!item.items?.length" :aria-expanded="showSubMenu && visibleSubMenuIndex === index" @click="menuItemClick(item, index)" /> <bento-divider v-if="item.addDivider" class="b-menu-item-list__divider" /> <sub-menu-item-list v-if="item.items?.length && showSubMenu && visibleSubMenuIndex === index" ref="subMenuItemListRef" :items="item.items" :title="item.text" @back="onBackButton" @close="closeMenu" /> </li> </ul> </menu-list-container> </template> <script lang="ts"> import { computed, defineComponent, nextTick, type PropType, ref, toRef } from 'vue'; import { isVue3 } from 'vue-demi'; import { BentoDivider } from '@/components/divider'; import { type BentoMenuItem, MenuItem } from '../menu-item'; import { BentoMenuEvent } from '../../menu.types'; import MenuListContainer from '../menu-list-container/menu-list-container.vue'; import SubMenuItemList from '../sub-menu-item-list/sub-menu-item-list.vue'; import { useDynamicMenuSize } from '../../composables'; /** * MenuItem list to be used in the Menu component. For internal use only. */ export default defineComponent({ name: 'menu-item-list', components: { BentoDivider, MenuItem, MenuListContainer, SubMenuItemList }, props: { /** * The data to be displayed in the list */ data: { type: Array as PropType<Array<BentoMenuItem>>, required: true, }, /** * Displays the menu-items of the list in the indent variant. Only applies to elements that don't have an icon to their left. */ indentItems: { type: Boolean, default: false, }, /** * If set to true, focuses the last item on the list instead of the first. Used for accessibility purposes when the menu is opened with the 'up' arrow key. */ focusLastElement: { type: Boolean, default: false, }, /** * If set to true, closes the menu when an item is clicked. */ closeMenuOnItemSelect: { type: Boolean, default: true, }, /** * The custom popover width if one has been set */ popoverWidth: { type: Number, default: null, }, }, emits: [BentoMenuEvent.CLOSE, 'menu-item-click'], setup(props, { emit, expose }) { const menuItemRef = ref(null); const menuItemListRef = ref(null); const menuItemListWrapperRef = ref(null); const subMenuItemListRef = ref(null); const showSubMenu = ref(false); const visibleSubMenuIndex = ref(null); const focusedIndex = ref(props.focusLastElement ? props.data.length - 1 : 0); const subMenuFocusedIndex = ref(0); const numberOfItems = ref(props.data.length - 1); const hasMenuItemsWithIcon = computed(() => props.data.some(el => el.icon)); const computedData = computed(() => { if (props.indentItems || hasMenuItemsWithIcon.value) { return props.data.map(el => { let indent = false; if (!el.icon) { indent = true; } return { ...el, indent, }; }); } return props.data; }); const focusItem = async () => { // vue 3 requires nextTick to be called before an item can be focused // this function does not need to be awaited itself if (isVue3) { await nextTick(); } menuItemListRef.value?.children[focusedIndex.value]?.children[0]?.focus(); }; const closeMenu = () => emit(BentoMenuEvent.CLOSE); const menuItemClick = (item: BentoMenuItem, index: number) => { if (item.items) { visibleSubMenuIndex.value = index; showSubMenu.value = true; return; } visibleSubMenuIndex.value = index; showSubMenu.value = false; if (!item.skipClose && props.closeMenuOnItemSelect !== false) { closeMenu(); } emit('menu-item-click'); }; const focusPrevious = () => { if (focusedIndex.value > 0) { focusedIndex.value -= 1; if (computedData.value[focusedIndex.value].disabled) { focusPrevious(); } } else { focusedIndex.value = numberOfItems.value; } focusItem(); }; const focusNext = () => { if (focusedIndex.value < numberOfItems.value) { focusedIndex.value += 1; if (computedData.value[focusedIndex.value].disabled) { focusNext(); } } else { focusedIndex.value = 0; } focusItem(); }; const focusRight = (item, index) => { if (item?.items?.length) { visibleSubMenuIndex.value = index; showSubMenu.value = true; } }; const focusLeft = () => { if (visibleSubMenuIndex.value && showSubMenu.value) { visibleSubMenuIndex.value = null; showSubMenu.value = false; } }; const onBackButton = () => { showSubMenu.value = false; visibleSubMenuIndex.value = null; // Focus the right element focusItem(); }; expose({ focusItem, }); const { computedStyle } = useDynamicMenuSize( subMenuItemListRef, visibleSubMenuIndex, showSubMenu, toRef(props, 'popoverWidth') ); return { // Values menuItemListRef, menuItemListWrapperRef, subMenuItemListRef, menuItemRef, computedData, computedStyle, focusedIndex, subMenuFocusedIndex, showSubMenu, visibleSubMenuIndex, // Methods menuItemClick, closeMenu, focusPrevious, focusNext, focusRight, focusLeft, onBackButton, }; }, }); </script> <style lang="scss" scoped src="./menu-item-list.scss" />
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <div ref="menuListContainerRef" class="b-menu-list-container" data-testid="menu-list-container" v-on="menuListContainerEvents" ><slot></slot ></div> </template> <script setup lang="ts"> import { DATA_GRID_CELL_MENU_REGISTERING_INJECTION_KEY } from '@/components/data-grid/components/data-grid-cell/data-grid-cell.keys'; import { inject, type Ref, ref, type UnwrapRef } from 'vue'; const menuListContainerRef = ref<HTMLDivElement>(); const menuListContainerEvents = ref({}); const registeringAndMousingOut = inject< | { registerMenuContainerElement: (menuContainerElement: Ref<UnwrapRef<HTMLDivElement>>) => void; menuMouseLeave: (e: MouseEvent) => void; } | false >(DATA_GRID_CELL_MENU_REGISTERING_INJECTION_KEY, false); // This is needed by the DataGridCell component in order to keep track of if a teleported menu // is being hovered over in the DataGridCellActions. if (registeringAndMousingOut) { const { registerMenuContainerElement } = registeringAndMousingOut; registerMenuContainerElement(menuListContainerRef); menuListContainerEvents.value = { mouseleave: registeringAndMousingOut?.menuMouseLeave, }; } </script> <script lang="ts"> /** * Menu list container component to set the correct styling in a menu list. For internal use only. */ export default { name: 'bento-menu-list-container', }; </script> <style lang="scss" scoped src="./menu-list-container.scss" />
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <transition name="b-sub-menu-item-list__transition" appear> <menu-list-container class="b-sub-menu-item-list"> <ul ref="subMenuItemListRefLocal" role="menu" class="b-sub-menu-item-list__list" @keydown.down.stop="focusNext" @keydown.up.stop="focusPrevious" @keydown.esc.stop="onBackButton" > <li v-for="(item, index) in computedItems" :key="`sub-menu-item-list-item-${index}`" role="none"> <menu-item :text="item.text" :handler="item.handler" :secondary-text="item.secondaryText" :icon="item.icon" :is-title="item.isTitle" :disabled="item.disabled" :critical="item.critical" :indent="item.indent" :tabindex="index === subMenuFocusedIndex ? '0' : '-1'" @click="item.clickHandler" /> <bento-divider v-if="item.addDivider" class="b-sub-menu-item-list__divider" /> </li> </ul> </menu-list-container> </transition> </template> <script lang="ts"> import { computed, defineComponent, onMounted, type PropType, ref } from 'vue'; import { BentoDivider } from '@/components/divider'; import { type BentoMenuItem, MenuItem } from '../menu-item'; import MenuListContainer from '../menu-list-container/menu-list-container.vue'; import ChevronLeftIcon from '@adyen/ui-assets-icons-16/vue/chevron-left'; import { BentoMenuEvent } from '../../menu.types'; import messages from '../../messages.json'; /** * SubMenuItemList component to render a submenu with the sliding transition. For internal use only. */ export default defineComponent({ i18n: { messages }, name: 'sub-menu-item-list', components: { BentoDivider, MenuItem, MenuListContainer }, props: { items: { type: Array as PropType<Array<BentoMenuItem>>, required: true, }, title: { type: String, required: true, }, }, emits: [BentoMenuEvent.CLOSE, BentoMenuEvent.BACK], setup(props, { emit }) { const subMenuItemListRefLocal = ref(null); const subMenuFocusedIndex = ref(0); const numberOfItems = ref(props.items.length); const closeMenu = () => emit(BentoMenuEvent.CLOSE); const onBackButton = () => emit(BentoMenuEvent.BACK); const computedItems = computed(() => { const items = props.items?.map(el => ({ ...el, indent: !el.icon, isTitle: false, clickHandler: closeMenu, })); return [ { text: props.title, secondaryText: null, critical: false, icon: ChevronLeftIcon, isTitle: true, disabled: false, indent: false, handler: onBackButton, clickHandler: onBackButton, addDivider: true, }, ...items, ]; }); const focusItem = async () => { subMenuItemListRefLocal.value?.children?.[subMenuFocusedIndex.value]?.children?.[0]?.focus(); }; onMounted(async () => { // Using setTimeout otherwise the sliding animation doesn't work anymore. The timeout imer is the same as the transition time (0.15s) setTimeout(() => focusItem(), 150); }); const focusPrevious = () => { if (subMenuFocusedIndex.value > 0) { subMenuFocusedIndex.value -= 1; if (computedItems.value[subMenuFocusedIndex.value].disabled) { focusPrevious(); } } else { subMenuFocusedIndex.value = numberOfItems.value; } focusItem(); }; const focusNext = () => { if (subMenuFocusedIndex.value < numberOfItems.value) { subMenuFocusedIndex.value += 1; if (computedItems.value[subMenuFocusedIndex.value].disabled) { focusNext(); } } else { subMenuFocusedIndex.value = 0; } focusItem(); }; return { // Enums BentoMenuEvent, // Values subMenuItemListRefLocal, subMenuFocusedIndex, ChevronLeftIcon, computedItems, // Methods closeMenu, onBackButton, focusPrevious, focusNext, }; }, }); </script> <style lang="scss" scoped src="./sub-menu-item-list.scss" />
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { Canvas, Story, Meta } from '@storybook/blocks';
|
|
2
|
+
import * as MenuStories from './menu.stories';
|
|
3
|
+
|
|
4
|
+
# Menu
|
|
5
|
+
|
|
6
|
+
Menu is a floating container displaying a list of actions. It's usually triggered by a menu/more options button.
|
|
7
|
+
|
|
8
|
+
<Canvas of={MenuStories.Default} />
|
|
9
|
+
|
|
10
|
+
## Use Cases
|
|
11
|
+
|
|
12
|
+
Use a menu when you need to display a lot of actions and there is a space constraint. Make sure the trigger is clear and
|
|
13
|
+
user expects the menu to appear.
|
|
14
|
+
|
|
15
|
+
## Variations
|
|
16
|
+
|
|
17
|
+
### Button variant
|
|
18
|
+
|
|
19
|
+
By default, the `bento-menu` renders with a secondary button as a trigger. However, some of the props present in
|
|
20
|
+
`bento-button` - such as `condensed`, `disabled` and `variant` can also be passed to `bento-menu` to customize the
|
|
21
|
+
styling of the menu button trigger.
|
|
22
|
+
|
|
23
|
+
### Button icon
|
|
24
|
+
|
|
25
|
+
The `bento-menu` component offers two distinct icon configurations.
|
|
26
|
+
|
|
27
|
+
- **Icon-Only:** To use this variant, provide an icon directly to the `iconLeft` slot and **omit the default slot**.
|
|
28
|
+
|
|
29
|
+
- **Menu with Text:** This variant is automatically activated when you provide content to the **default slot**. For this
|
|
30
|
+
configuration, only the `iconLeft` slot is configurable; the icon on the right is a fixed part of the component's
|
|
31
|
+
behavior and cannot be changed.
|
|
32
|
+
|
|
33
|
+
By default the `bento-menu` uses an `options vertical` icon in the button trigger.
|
|
34
|
+
|
|
35
|
+
<Canvas of={MenuStories.WithIconAndText} />
|
|
36
|
+
|
|
37
|
+
### Button text
|
|
38
|
+
|
|
39
|
+
To set the button text use the default slot.
|
|
40
|
+
|
|
41
|
+
### Menu Item Variations
|
|
42
|
+
|
|
43
|
+
Menu is built of flexible item components, each allowing a selection of presets to cover all kinds of cases.
|
|
44
|
+
|
|
45
|
+
#### Icon
|
|
46
|
+
|
|
47
|
+
Renders a `menu-item` with any icon from the 16x16px Bento icon set. The icon needs to be imported and passed in the
|
|
48
|
+
`MenuItem` prop object.
|
|
49
|
+
|
|
50
|
+
#### Secondary text
|
|
51
|
+
|
|
52
|
+
The item also has an optional secondary text. This renders to the right of the primary text in a lighter hue. To enable
|
|
53
|
+
this variation, set the `secondaryText` field in the corresponding `menu-item` object in the data array.
|
|
54
|
+
|
|
55
|
+
#### Chevron
|
|
56
|
+
|
|
57
|
+
Renders a chevron to the right of the `menu-item` for multi-level menus. To enable this variation, set the `chevron`
|
|
58
|
+
field to `true` in the corresponding `menu-item` object in the data array.
|
|
59
|
+
|
|
60
|
+
#### Divider
|
|
61
|
+
|
|
62
|
+
Renders a divider after the `menu-item` to separate it from other content. To enable this, set `addDivider` to `true` in
|
|
63
|
+
the corresponding `menu-item` object in the data array.
|
|
64
|
+
|
|
65
|
+
## Modifiers
|
|
66
|
+
|
|
67
|
+
### Menu Item
|
|
68
|
+
|
|
69
|
+
#### Critical
|
|
70
|
+
|
|
71
|
+
There is a critical styling available for each `menu-item`. This renders the item with a critical styling and should be
|
|
72
|
+
used for destructive actions. To enable this variation, set the `critical` field to `true` in the corresponding
|
|
73
|
+
`menu-item` object in the data array.
|
|
74
|
+
|
|
75
|
+
## Events
|
|
76
|
+
|
|
77
|
+
- `BentoMenuEvent.OPEN` is emitted when the menu opens
|
|
78
|
+
- `BentoMenuEvent.CLOSE` is emitted when the menu closes
|
|
79
|
+
|
|
80
|
+
## Accessibility
|
|
81
|
+
|
|
82
|
+
The `bento-menu` component is accessible and follows the WCAG 2.0 standard.
|
|
83
|
+
|
|
84
|
+
### Keyboard interaction
|
|
85
|
+
|
|
86
|
+
The `bento-menu` component is reachable through keyboard navigation. When closed, the button menu is placed in the
|
|
87
|
+
normal page flow and will thus be accessible just by tabbing. The table below lists all the keyboard interactions the
|
|
88
|
+
menu supports.
|
|
89
|
+
|
|
90
|
+
`Menu button`
|
|
91
|
+
|
|
92
|
+
| Keyboard interaction | Output |
|
|
93
|
+
| ------------------------------ | -------------------------------------------------------------------------------------------- |
|
|
94
|
+
| `Tab` | Moves focus to the menu button or away from it. |
|
|
95
|
+
| `Down arrow`, `Space`, `Enter` | When focus is on the menu button, it opens the menu and moves focus to the first `menuitem`. |
|
|
96
|
+
| `Up Arrow` | When focus is on the menu button, it opens menu and movesmfocus to last `menuitem`. |
|
|
97
|
+
|
|
98
|
+
`Menu`
|
|
99
|
+
|
|
100
|
+
| Keyboard interaction | Output |
|
|
101
|
+
| -------------------- | ------------------------------------------------------------------------------------------------------------- |
|
|
102
|
+
| `Enter`, `Space` | When focus is on a `menuitem`, activates said item and closes the menu. |
|
|
103
|
+
| `Escape` | Closes the menu. |
|
|
104
|
+
| `Down Arrow` | Moves focus to the next menu item. If focus is on the last menu item, moves focus to the first menu item. |
|
|
105
|
+
| `Up Arrow` | Moves focus to the previous menu item. If focus is on the first menu item, moves focus to the last menu item. |
|
|
106
|
+
|
|
107
|
+
### Roles, states and properties
|
|
108
|
+
|
|
109
|
+
| ARIA attribute | is automatic? | fallback used | manual override |
|
|
110
|
+
| ----------------- | --------------------------------------------------------------------- | ------------- | --------------- |
|
|
111
|
+
| `role="menu"` | Yes, placed on the actions list representing the menu. | - | - |
|
|
112
|
+
| `role="menuitem"` | Yes, placed on each action item. | - | - |
|
|
113
|
+
| `aria-haspopup` | Yes, automatically set to `true` on the menu button | - | - |
|
|
114
|
+
| `aria-expanded` | Yes, set to `true` whenever the menu is open and to `false` otherwise | - | - |
|
|
115
|
+
| `aria-controls` | Yes, automatically set on the button to point at the menu's popover | - | - |
|
|
116
|
+
|
|
117
|
+
## Resources
|
|
118
|
+
|
|
119
|
+
- [Figma link](https://www.figma.com/design/uLabwF3243jdMDsNSP7U9I/Bento---Components?m=auto&node-id=25200-2406)
|
|
120
|
+
- [WCAG Menu Pattern Guidelines](https://www.w3.org/WAI/ARIA/apg/patterns/menubar/)
|
|
121
|
+
- [WCAG Menu Button Pattern Guidelines](https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import { storybookDocsParameter } from '@/utils/ts/storybook'; import { action } from '@storybook/addon-actions'; import { isVue2, ref } from 'vue-demi'; import BentoMenu from './menu.vue'; import CodeIcon from '@adyen/ui-assets-icons-16/vue/code-2'; import PeopleIcon from '@adyen/ui-assets-icons-16/vue/4-people'; import ChevronDownIcon from '@adyen/ui-assets-icons-16/vue/chevron-down'; import PlusIcon from '@adyen/ui-assets-icons-16/vue/plus'; import { BentoMenuEvent } from './menu.types'; import { type Meta, type StoryObj } from '@storybook/vue'; const MENU_DATA = [ { text: 'Menu entry', secondaryText: 'Text', handler: () => { // Your code here action('click')('Menu 1'); }, critical: true, icon: CodeIcon, }, { text: 'Menu entry 2 extra long text', secondaryText: 'Disabled', handler: () => { // Your code here action('click')('Menu 2'); }, disabled: true, addDivider: true, }, { text: 'Menu entry 3', handler: () => { // Your code here action('click')('Menu 3'); }, icon: PeopleIcon, items: [ { text: 'Submenu entry', secondaryText: 'Subtext', handler: () => { // Your code here action('click')('Submenu 1'); }, }, { text: 'Submenu entry 2', handler: () => { // Your code here action('click')('Submenu 2'); }, }, ], }, { text: 'Menu entry 4', secondaryText: 'Text', handler: () => { // Your code here action('click')('Menu 4'); }, }, ]; const meta: Meta = { title: 'Menu', component: BentoMenu, argTypes: { 'menu-item-click': { name: 'menu-item-click', defaultValue: '', description: 'When a menu item that has no submenu is clicked', table: { type: { summary: '($event) => void' }, }, control: { disable: true }, }, }, }; export default meta; const defaultCode = ` <template> <bento-menu :data="menuData" /> </template> <script setup lang="ts"> import CodeIcon from '@adyen/ui-assets-icons-16/vue/code-2'; import PeopleIcon from '@adyen/ui-assets-icons-16/vue/4-people'; const menuData = [ { text: 'Menu entry', secondaryText: 'Text', handler: () => { // Your code here }, critical: true, icon: CodeIcon, }, { text: 'Menu entry 2 extra long text', secondaryText: 'Disabled entry', handler: () => { // Your code here }, disabled: true, }, { text: 'Menu entry 3', handler: () => { // Your code here }, icon: PeopleIcon, }, ]; </script> `; export const Default = { render: (_args, { argTypes }) => ({ components: { BentoMenu }, props: Object.keys(argTypes), template: ` <bento-menu v-bind="args" @open="open" @close="close" /> `, setup(props) { return { args: isVue2 ? props : _args, [BentoMenuEvent.OPEN]: action(BentoMenuEvent.OPEN), [BentoMenuEvent.CLOSE]: action(BentoMenuEvent.CLOSE), }; }, }), args: { data: MENU_DATA, button: { variant: 'secondary', }, }, parameters: storybookDocsParameter(defaultCode), }; const withIconAndTextCode = ` <template> <bento-menu :data="menuData"> <template #iconLeft> <plus-icon /> </template> Open menu </bento-menu> </template> <script setup lang="ts"> import CodeIcon from '@adyen/ui-assets-icons-16/vue/code-2'; import PeopleIcon from '@adyen/ui-assets-icons-16/vue/4-people'; import PlusIcon from '@adyen/ui-assets-icons-16/vue/plus'; const menuData = [ { text: 'Menu entry', secondaryText: 'Text', handler: () => { // Your code here console.log('Menu entry 1 clicked'); }, critical: true, icon: CodeIcon, }, { text: 'Menu entry 2 extra long text', secondaryText: 'Disabled entry', handler: () => { // Your code here }, disabled: true, }, { text: 'Menu entry 3', handler: () => { // Your code here console.log('Menu entry 3 clicked'); }, icon: PeopleIcon, }, ]; </script> `; export const WithIconAndText: StoryObj = { ...Default, render: (_args, { argTypes }) => ({ components: { BentoMenu, CodeIcon, PlusIcon, ChevronDownIcon }, props: Object.keys(argTypes), template: ` <bento-menu v-bind="args" @open="open" @close="close"> <template #iconLeft> <plus-icon /> </template> Open menu </bento-menu> `, setup(props) { const isOpen = ref(false); const toggleMenuOpenText = () => { isOpen.value = !isOpen.value; }; return { // Values args: isVue2 ? props : _args, isOpen, // Methods [BentoMenuEvent.OPEN]: () => { toggleMenuOpenText(); action(BentoMenuEvent.OPEN)(); }, [BentoMenuEvent.CLOSE]: () => { toggleMenuOpenText(); action(BentoMenuEvent.CLOSE)(); }, }; }, }), parameters: storybookDocsParameter(withIconAndTextCode), };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <div :class="computedClasses" class="b-menu" data-testid="menu" @keydown.down="onKeyDown" @keydown.up="onKeyUp" @keydown.esc="closeMenu()" > <bento-toggle-button ref="menuButtonRef" class="b-menu__toggle-button" aria-haspopup="true" v-bind="computedButtonPropsWithDefaults" :aria-controls="showMenu ? menuId : null" :toggled="showMenu" :inverse="isInverseColorSet" @click="toggleShowMenu" > <template v-if="!hasSlot('default') || hasSlot('iconLeft')" #iconLeft> <slot name="iconLeft"> <options-vertical-icon :title="t('menuButton')" /> </slot> </template> <template v-if="hasSlot('default')" #default> <slot /> </template> <template v-if="hasSlot('default')" #iconRight> <chevron-down-icon /> </template> </bento-toggle-button> <bento-teleport v-if="menuButtonRef" :disabled="!teleport"> <bento-popover v-if="menuButtonRef" :id="menuId" ref="popoverRef" :role="null" :open="showMenu" class="b-menu__popover" :style="computedMenuWidth" :target-element="menuButtonRef" :position="menuPosition" :fixed-positioning="menuFixedPositioning" :fallback-position="[ 'bottom-start', 'bottom-end', 'top-start', 'top-end', 'right-start', 'right-end', 'left-start', 'left-end', ]" :teleport="teleport" without-space > <menu-item-list ref="menuItemListRef" :close-menu-on-item-select="closeMenuOnItemSelect" :data="data" :focus-last-element="isLastElementFocused" :popover-width="menuWidth" @close="closeMenu" @menu-item-click="menuItemClick" /> </bento-popover> </bento-teleport> </div> </template> <script setup lang="ts"> import BentoToggleButton from '@/components/button/components/toggle-button/toggle-button.vue'; import { DIVIDER_INVERSE_INJECTION_KEY } from '@/components/divider/divider.keys'; import BentoPopover from '@/components/popover/popover.vue'; import { useClickOutside, useHasSlot } from '@/composables'; import { generateUid } from '@/core/utils/ts'; import { BentoTeleport } from '@/internal/teleport'; import { useI18n } from '@/utils/ts/i18n'; import { printDevelopmentWarning } from '@/utils/ts/print-development-warning'; import ChevronDownIcon from '@adyen/ui-assets-icons-16/vue/chevron-down'; import OptionsVerticalIcon from '@adyen/ui-assets-icons-16/vue/options-vertical'; import { computed, inject, nextTick, onMounted, provide, ref, useSlots } from 'vue'; import MenuItemList from './components/menu-item-list/menu-item-list.vue'; import { MENU_INVERSE_INJECTION_KEY } from './menu.keys'; import { clampMenuWidth } from './composables/use-dynamic-menu-size/use-dynamic-menu-size'; import { type BentoMenuButtonProps, BentoMenuEvent, type BentoMenuProps } from './menu.types'; import messages from './messages.json'; type MessageSchema = (typeof messages)['en-US']; const props = withDefaults(defineProps<BentoMenuProps>(), { button: () => ({ variant: 'secondary', }) as BentoMenuButtonProps, closeMenuOnItemSelect: true, disableArrowDownOpeningMenu: false, menuPosition: 'bottom-start', menuFixedPositioning: false, menuWidth: null, revertFocusToButton: true, teleport: false, closeOnClickOutsideOptions: undefined, }); const emit = defineEmits<{ (e: BentoMenuEvent.CLOSE): void; (e: BentoMenuEvent.OPEN): void; (e: 'menu-item-click'): void; (e: 'click-outside'): void; }>(); const slots = useSlots(); const hasSlot = useHasSlot(slots); const { t } = useI18n<{ message: MessageSchema }>({ messages }); const menuButtonRef = ref(null); const menuItemListRef = ref(null); const showMenu = ref(false); const isLastElementFocused = ref(false); const popoverRef = ref<HTMLElement>(null); const menuId = generateUid('menu'); const isInverseColorSet = inject(MENU_INVERSE_INJECTION_KEY, ref(false)); provide(DIVIDER_INVERSE_INJECTION_KEY, isInverseColorSet); const computedClasses = computed(() => ({ 'b-menu--inverse': isInverseColorSet.value, })); const computedMenuWidth = computed(() => props.menuWidth ? { width: `${clampMenuWidth(props.menuWidth)}px` } : null ); const computedButtonPropsWithDefaults = computed<BentoMenuButtonProps>(() => { if (!props?.button) { return undefined; } return { variant: 'secondary', ...props.button, }; }); const openMenu = () => { showMenu.value = true; emit(BentoMenuEvent.OPEN); }; const closeMenu = async (revertFocus = props.revertFocusToButton) => { if (!showMenu.value) { return; } showMenu.value = false; if (revertFocus) { // ensure that menu is closed and re-rendering is completed before focusing await nextTick(); menuButtonRef.value?.$el?.focus(); } emit(BentoMenuEvent.CLOSE); }; const toggleShowMenu = (e: MouseEvent) => { e.stopPropagation(); if (!showMenu.value) { openMenu(); } else { closeMenu(); } }; const onKeyDown = (event: KeyboardEvent) => { event.preventDefault(); isLastElementFocused.value = false; if (!props.disableArrowDownOpeningMenu) { openMenu(); } }; const onKeyUp = (event: KeyboardEvent) => { event.preventDefault(); isLastElementFocused.value = true; openMenu(); }; const menuItemClick = () => { emit('menu-item-click'); }; // Ignore the events inside the Button and the Popover useClickOutside( popoverRef, () => { closeMenu(); emit('click-outside'); }, { ...props.closeOnClickOutsideOptions, ignore: [menuButtonRef, ...(props.closeOnClickOutsideOptions?.ignore ?? [])], } ); defineExpose({ /** * Control to enable programmatic closing of the menu */ closeMenu, }); onMounted(() => { if (hasSlot('iconRight')) { printDevelopmentWarning( '"bento-menu" no longer supports the "iconRight" slot. The right-aligned icon is now fixed by design.' ); } }); </script> <script lang="ts"> /** * Menu component to render a series of buttons. Csan render nested submenus. * * @example * import { BentoMenu } from '@adyen/bento-vue2'; * * export default { * components: { BentoMenu }, * template: ` * <bento-menu * :data=[ * { text: 'Menu item text', handler: () => { some code here } }, * { text: 'Another menu item text', handler: () => { some other code here }, critical: true } * ] * /> * } */ export default { i18n: { messages }, name: 'bento-menu', }; </script> <style lang="scss" scoped src="./menu.scss" />
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <div class="b-modal-base"> <dialog ref="modal" :role="modalRole" :aria-label="computedAriaLabel" :style="{ height: height }" class="b-modal-base__container" :class="modalConditionalClasses" :open="initiallyOpen" @animationend="closeModalAfterAnimation" @keydown.esc="internalModalActionClose" > <bento-focus-trap v-if="isOpen || initiallyOpen" :id="modalId" v-bento-click-outside-directive="closeModalOnClickOutside" > <bento-button v-if="showCloseButton" class="b-modal-base__close-button" variant="tertiary" type="button" :style="customPaddingStyles" @click="internalModalActionClose" > <cross-icon :svg-title="t('close')" /> </bento-button> <template v-if="props.activePage"> <slot /> </template> <bento-modal-page v-else :with-column-layout="withColumnLayout" :size="size" :custom-padding="customPadding" :columns-size="columnsSize" > <template v-if="hasSlot('default')" #default> <slot></slot> </template> <template v-if="hasSlot('content-col-1')" #content-col-1 ><slot name="content-col-1"></slot ></template> <template v-if="hasSlot('content-col-2')" #content-col-2 ><slot name="content-col-2"></slot ></template> <template v-if="hasSlot('content')" #content><slot name="content"></slot></template> <template v-if="hasSlot('actions')" #actions><slot name="actions"></slot></template> </bento-modal-page> </bento-focus-trap> </dialog> </div> </template> <script setup lang="ts"> import { computed, defineComponent, onMounted, type PropType, provide, ref, toRefs, useAttrs, useSlots, watch, } from 'vue'; import { BentoModalColumnLayoutVariant, type BentoModalConfigData, BentoModalRole, BentoModalSize, } from '@/components/modal/modal.types'; import { BentoButton, type BentoButtonActionsList } from '@/components/button'; import { BentoFocusTrap } from '@/components/focus-trap'; import { useHasSlot } from '@/composables'; import { useI18n } from '@/utils/ts/i18n'; import { MODAL_DIALOG_ID_INJECTION_KEY } from '@/components/modal/components/base-modal/base-modal.keys'; import { generateUid } from '@/core/utils/ts'; import { BentoClickOutsideDirective as vBentoClickOutsideDirective } from '@/directives/click-outside'; import messages from './messages.json'; import { BentoModalPage } from '@/components/modal/components/modal-page'; import CrossIcon from '@adyen/ui-assets-icons-16/vue/cross'; import { MODAL_CONFIG_INJECTION_KEY } from '../../modal.keys'; import { BentoDialogPageAnimation, type BentoDialogPageConfigData, type BentoDialogPageRegisterPage, DIALOG_PAGE_CONFIG_INJECTION_KEY, DIALOG_PAGE_REGISTER_PAGE_INJECTION_KEY, } from '@/internal'; type MessageSchema = (typeof messages)['en-US']; const LOADING_BTN_CLOSE_AFTER_SUCCESS_DELAY_TIME = 500; const { t } = useI18n<{ message: MessageSchema }>({ messages }); const slots = useSlots(); const attrs = useAttrs(); const hasSlot = useHasSlot(slots); const modal = ref<HTMLDialogElement>(null); const isClosing = ref(false); const modalId = generateUid('modal'); const emit = defineEmits<{ /** * Emits update event when page is changed internally */ (e: 'update:active-page', page: string): void; /** * Emitted when the modal is closed. */ (e: 'close-modal'); }>(); 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>, default: null, }, /** * The id of the current active page * Should be the same id as the BentoModalPage to be displayed */ activePage: { type: String, default: null, }, /** * If a column layout is provided, it renders the respective left column width variant. This can be smaller than the second column, the same size as the second column or bigger than the second column. */ columnsSize: { type: String as PropType<BentoModalColumnLayoutVariant>, default: BentoModalColumnLayoutVariant.SMALLER, }, /** * If provided, manages the padding of the whole modal container */ customPadding: { type: String, default: null }, /** * If passed - sets first button color to red */ destructiveActions: { type: Boolean, default: false }, /** * Hides a cross icon on the right side of the title */ hideCloseButton: { type: Boolean, required: true }, /** * Sets the open attribute on the dialog element, use isOpen instead to toggle modal visbility. */ initiallyOpen: { type: Boolean, default: false }, /** * If set to true, it allows the modal to be closed by clicking outside of it */ isDismissible: { type: Boolean, required: true }, /** * Controls the modal's state from the outside. Set it to true to open the modal */ isOpen: { type: Boolean, required: true }, /** * Defines whether the modal will be an alert modal */ modalRole: { type: String, default: BentoModalRole.DIALOG }, /** * Sets the width of the modal */ size: { type: String as PropType<BentoModalSize | `${BentoModalSize}`>, default: BentoModalSize.SMALL, }, /** * If provided, renders the modal's content in a two-column layout an wth dividers separating its content */ withColumnLayout: { type: Boolean, default: false }, }); const showCloseButton = computed(() => props.isDismissible && !props.hideCloseButton); const pageCustomPadding = ref(props.customPadding); const customPaddingStyles = computed(() => { if (!pageCustomPadding.value) { return {}; } return { top: pageCustomPadding.value, right: pageCustomPadding.value }; }); const computedAriaLabel = computed(() => (attrs['aria-label'] as string) ?? (t('ariaLabelFallback') as string)); const hasColumnLayout = computed(() => props.withColumnLayout && props.size === BentoModalSize.LARGE); const openModal = (): void => { modal.value.showModal(); }; const emitCloseEvent = (): void => { emit('close-modal'); }; // Handle closing and closing animation for dialog element const closeModalAfterAnimation = () => { if (isClosing.value) { modal.value.close(); if (previousPages.value[0]) { emit('update:active-page', previousPages.value[0]); } previousPages.value = []; isClosing.value = false; emitCloseEvent(); } }; const startCloseModalAnimation = () => { if (!modal.value.open) { return; } isClosing.value = true; }; /** * If the user has pressed escape, clicked outside or clicked the close icon. * Needs to be called from outside, following the "dialog.close()" pattern, * when the closing event is triggered outside of the "base-modal" * component (e.g. from a closing action). */ const internalModalActionClose = (event?: Event) => { if (event) { event.preventDefault(); } if (!props.isDismissible && props.modalRole === BentoModalRole.DIALOG) { return; } startCloseModalAnimation(); }; const closeModalOnClickOutside = () => { if (props.isDismissible) { internalModalActionClose(); } }; const modalConditionalClasses = computed(() => ({ [`b-modal-base__container--${props.size}`]: props.size, 'b-modal-base__container--hide': isClosing.value, 'b-modal-base__container--column-layout': hasColumnLayout.value, 'b-modal-base__container--animation': isAnimationActive.value, })); watch( () => [props.isOpen, modal.value], ([newIsOpen, newModalValue], [, oldModalValue]): void => { if (!newModalValue) { return; } const isFirstMutation = !oldModalValue; if (newIsOpen) { openModal(); // add the first page to the array when the modal opens if (!previousPages.value.length) { previousPages.value.push(activePage.value); } } else if (!isFirstMutation) { startCloseModalAnimation(); } } ); // If the first button is a loading button then close the modal have 0.5 seconds // if the state is changed to 'success'. watch( () => props.actions, () => { if (props.actions?.[0]?.state === 'success') { setTimeout(startCloseModalAnimation, LOADING_BTN_CLOSE_AFTER_SUCCESS_DELAY_TIME); } }, { deep: true } ); onMounted(() => { if (props.isOpen && modal.value) { openModal(); } }); /** * Expose methods through its 'ref' */ defineExpose({ internalModalActionClose, }); // Let's Children components know they are inside a Modal (dialog element) provide(MODAL_DIALOG_ID_INJECTION_KEY, modalId); // Modal pagination const { activePage, isOpen, size } = toRefs(props); const animation = ref(BentoDialogPageAnimation.TRANSITION_LEVEL_DEEPER); const pages = ref<Array<string>>([]); const previousPages = ref([]); const updatePage = page => emit('update:active-page', page); // unregister page if removed / destroyed const unregisterPage = (pageIdToRemove: string) => () => { pages.value = pages.value?.filter(pageId => pageId !== pageIdToRemove); }; // Provide data and function to register page to subcomponent const registerPage: BentoDialogPageRegisterPage = pageId => { if (pageId) { pages.value.push(pageId); } return { unregisterPage: unregisterPage(pageId) }; }; const height = ref(''); const isAnimationActive = ref(false); provide<BentoModalConfigData>(MODAL_CONFIG_INJECTION_KEY, { activePage, height, isAnimationActive, pageCustomPadding, showCloseButton, size, }); provide<BentoDialogPageConfigData>(DIALOG_PAGE_CONFIG_INJECTION_KEY, { activePage, animation, isOpen, pages, previousPages, updatePage, }); provide(DIALOG_PAGE_REGISTER_PAGE_INJECTION_KEY, registerPage); </script> <script lang="ts"> /** * Dialog container. It creates a dialog HTML element for the modal to use. * * @example * import BaseModal from './components/base-modal/base-modal.vue'; * * export default { * components: { BaseModal }, * template: ` * <base-modal * :hide-close-button="true" * :is-dismissible="false" * :is-open="isOpen" * @close-modal="isOpen = false" * > * <template #content-col-1> * // Content on the left side column * </template> * * <template #content-col-2> * // Content on the right side column * </template> * * <template v-if="actions" #actions> * <footer-actions :actions="actions" /> * </template> * </base-modal> * `, * setup() { * const isOpen = ref(true) * const footerActions = [{ * title: 'Primary action', * event: () => { ... } * }] * } * } */ export default defineComponent({ i18n: { messages }, name: 'base-modal', }); </script> <style lang="scss" scoped src="./base-modal.scss" />
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <base-modal class="b-modal-default" v-bind="$attrs" :actions="actions" :active-page="activePage" :size="size" :hide-close-button="hideCloseButton" :columns-size="columnsSize" :custom-padding="customPadding" :is-dismissible="isDismissible" :is-open="isOpen" :with-column-layout="withColumnLayout" :initially-open="initiallyOpen" @update:active-page="emit('update:active-page', $event)" @close-modal="onCloseModal" > <template v-if="hasSlot('default')" #default> <slot></slot> </template> <template v-if="hasSlot('content-col-1')" #content-col-1><slot name="content-col-1"></slot></template> <template v-if="hasSlot('content-col-2')" #content-col-2><slot name="content-col-2"></slot></template> <template v-if="hasSlot('content')" #content><slot name="content"></slot></template> <template v-if="actions && actions.length > 0" #actions> <slot name="actions"> <footer-actions :actions="actions" :actions-label="actionsLabel" :destructive-actions="destructiveActions" /> </slot> </template> </base-modal> </template> <script setup lang="ts"> import { defineComponent, onMounted, type PropType, useSlots } from 'vue'; import { BentoModalColumnLayoutVariant, type BentoModalSize } from '@/components/modal/modal.types'; import { useHasSlot } from '@/composables'; import { type BentoButtonActionsList } from '@/components/button'; import { FooterActions } from '@/components/internal/footer-actions'; import BaseModal from '@/components/modal/components/base-modal/base-modal.vue'; const slots = useSlots(); const hasSlot = useHasSlot(slots); const emit = defineEmits<{ /** * Emits update event when page is changed internally */ (e: 'update:active-page', page: string): void; /** * Emitted when the modal is closed. */ (e: 'close-modal'); }>(); 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>, default: null, }, /** * Text label that is displayed left to the action buttons */ actionsLabel: { type: String, default: null }, /** * The id of the current active page * Should be the same id as the BentoModalPage to be displayed */ activePage: { type: String, default: null, }, /** * If provided, manages the padding of the whole modal container */ customPadding: { type: String, default: null }, /** * If a column layout is provided, it renders the respective left column width variant. This can be smaller than the second column, the same size as the second column or bigger than the second column. */ columnsSize: { type: String as PropType<BentoModalColumnLayoutVariant>, default: BentoModalColumnLayoutVariant.SMALLER, }, /** * If passed - sets first button color to red */ destructiveActions: { type: Boolean, default: false }, /** * Hides a cross icon on the right side of the title */ hideCloseButton: { type: Boolean, default: false }, /** * Sets the open attribute on the dialog element, use isOpen instead to toggle modal visbility. */ initiallyOpen: { type: Boolean, default: false }, /** * If set to true, it allows the modal to be closed by clicking outside of it */ isDismissible: { type: Boolean, required: true }, /** * Controls the modal's state from the outside. Set it to true to open the modal */ isOpen: { type: Boolean, required: true }, /** * Sets the width of the modal */ size: { type: String as PropType<BentoModalSize | `${BentoModalSize}`>, required: true, }, /** * If provided, renders the modal's content in a two-column layout an wth dividers separating its content */ withColumnLayout: { type: Boolean, default: false }, }); const onCloseModal = () => { emit('close-modal'); }; onMounted(() => { if (!(hasSlot('content') || (hasSlot('content-col-1') && hasSlot('content-col-2'))) && !props.activePage) { throw new Error('Bento Modal default variant requires content slot or column slots'); } }); </script> <script lang="ts"> /** * Creates a Modal based off the `base-modal` component. * It allows two column modals as well as content modals. * * @example * import { BentoModalDefault } from '@/components/modal/components'; * * export default { * components: { BentoModalDefault }, * template: ` * <bento-modal-default * :is-dismissible="false" * :is-open="isOpen" * @close-modal="isOpen = false" * > * <template #content-col-1> * // Content on the left side column * </template> * * <template #content-col-2> * // Content on the right side column * </template> * * <template v-if="actions" #actions> * <footer-actions :actions="actions" /> * </template> * </bento-modal-default> * `, * setup() { * const isOpen = ref(true) * const footerActions = [{ * title: 'Primary action', * event: () => { ... } * }] * } * } */ export default defineComponent({ name: 'bento-modal-default', }); </script>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <base-modal ref="modalRef" class="b-modal-dialog" :actions="actions" :active-page="activePage" :class="modalDialogConditionalClasses" :hide-close-button="true" size="small" :is-dismissible="isDismissible" :is-open="isOpen" :modal-role="BentoModalRole.ALERT_DIALOG" :initially-open="initiallyOpen" v-bind="$attrs" @close-modal="onCloseModal" > <template #default> <slot></slot> </template> <template #content> {{ content }} </template> <template #actions> <bento-button-actions v-if="dialogType === 'acknowledgement'" :actions="acknowledgementDialogActions" layout="buttons-end" /> <bento-button-actions v-else :actions="dialogActions" layout="buttons-end" /> </template> </base-modal> </template> <script setup lang="ts"> import { computed, defineComponent, onMounted, type PropType, type Ref, ref, toRefs, useSlots } from 'vue'; import { BentoModalDialogType, BentoModalRole } from '@/components/modal/modal.types'; import { useHasSlot } from '@/composables'; import { type BentoButtonActionObject, BentoButtonActions, type BentoButtonActionsList, BentoButtonVariant, } from '@/components/button'; import { useI18n } from '@/utils/ts/i18n'; import BaseModal from '@/components/modal/components/base-modal/base-modal.vue'; import messages from './messages.json'; type MessageSchema = (typeof messages)['en-US']; const emit = defineEmits<{ (e: 'close-modal'); }>(); const props = defineProps({ /** * List of actions that will be used to render the buttons. * Only 1 action button is allowed for Modal Dialog * * 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 * }] * ``` */ actions: { type: Array as PropType<BentoButtonActionsList>, default: null, }, /** * The id of the current active page * Should be the same id as the BentoModalPage to be displayed */ activePage: { type: String, default: null, }, /** * Text that is displayed in the content section of modal */ content: { type: String, required: true }, /** * Sets different action buttons styling and appearance */ dialogType: { type: String as PropType<BentoModalDialogType | `${BentoModalDialogType}`>, required: true, }, /** * Sets the open attribute on the dialog element, use isOpen instead to toggle modal visbility. */ initiallyOpen: { type: Boolean, default: false }, /** * If set to true, it allows the modal to be closed by clicking outside of it */ isDismissible: { type: Boolean, required: true }, /** * Controls the modal's state from the outside. Set it to true to open the modal */ isOpen: { type: Boolean, required: true }, }); const { t } = useI18n<{ message: MessageSchema }>({ messages }); const slots = useSlots(); const hasSlot = useHasSlot(slots); const { actions, dialogType } = toRefs(props); const modalRef: Ref<InstanceType<typeof BaseModal>> = ref(null); const onCloseModal = () => { emit('close-modal'); }; const onCloseActionButton = (event: Event) => { // Calls "base-modal" internal function to initialize the // "close" animation and trigger the "close" event. // This follows the same "dialog" pattern of calling "dialog.close()" modalRef.value.internalModalActionClose(event); }; const acknowledgementDialogActions = computed(() => [ { title: t('ok') as string, event: onCloseActionButton, variant: BentoButtonVariant.SECONDARY, }, ]); const dialogActions = computed(() => { const mandatoryCancelActionButton: BentoButtonActionObject = { title: 'Cancel', event: onCloseActionButton, }; const updatedPassedActionsObject: BentoButtonActionObject = actions?.value?.[0]; if (dialogType.value === BentoModalDialogType.DESTRUCTIVE) { updatedPassedActionsObject.critical = true; } else if (dialogType.value === BentoModalDialogType.INFO) { updatedPassedActionsObject.variant = BentoButtonVariant.SECONDARY; } return [updatedPassedActionsObject, mandatoryCancelActionButton]; }); const modalDialogConditionalClasses = computed(() => ({ [`b-modal-dialog--${dialogType.value}`]: dialogType.value, })); onMounted(() => { if (props.activePage) { throw new Error( 'Bento Modal Dialog cannot contain multiple pages. Use Bento Modal default variant instead.' ); } if (!hasSlot('default') || (!props.content && !hasSlot('content'))) { throw new Error('Bento Modal Dialog requires header title and content'); } if (actions?.value?.length > 1) { throw new Error('Not more than 1 action button may be provided to Modal Dialog'); } if (dialogType.value !== BentoModalDialogType.ACKNOWLEDGEMENT && !actions?.value) { throw new Error('Modal Dialog, except ACKNOWLEDGEMENT type, always has to contain one action button'); } if (dialogType.value === BentoModalDialogType.ACKNOWLEDGEMENT && actions?.value?.length) { throw new Error('Acknowledgement Modal Dialog does not accept action buttons'); } }); </script> <script lang="ts"> /** * Creates a "dialog" type of modal. * These are are modals that require a deliberate user action or acknowledgement * (confirmations or destructive actions). * * It’s not customizable and consists of title, description label and one action. * * @example * import { BentoModalDialog } from '@/components/modal/components'; * * export default { * components: { BentoModalDialog }, * template: ` * <bento-modal-dialog * dialog-type="destructive" * :is-dismissible="false" * :is-open="isOpen" * @close-modal="isOpen = false" * > * Dialog content * </bento-modal-dialog> * `, * setup() { * const isOpen = ref(true) * } * } */ export default defineComponent({ i18n: { messages }, name: 'bento-modal-dialog', }); </script>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <dialog-page ref="page" class="b-modal-page" :class="modalPageConditionalClasses" :page-id="pageId" :always-show-border="hasColumnLayout" :back-button-styling="customPaddingStyles?.backButton" :previous-page="previousPage" @animationend="isAnimationActive = false" > <template v-if="hasSlot('default') || showCloseButton" #header> <div class="b-modal-page__header-container" :style="customPaddingStyles.header"> <div class="b-modal-page__header"> <bento-typography class="b-modal-page__title" el="span" variant="title" stronger> <slot v-if="hasSlot('default')" /> </bento-typography> </div> </div> </template> <template #content> <div class="b-modal-page__content" :class="modalContentConditionalClasses" :style="customPaddingStyles.content" > <bento-typography v-if="hasSlot('content') || (hasSlot('content-col-1') && hasSlot('content-col-2'))" el="div" > <grid-layout v-if="hasColumnLayout" :column-width="computedColumnWidth" :container-type="GridLayoutContainerType.NORMAL" no-gap class="b-modal-page__grid" > <template #col-1><slot name="content-col-1"></slot></template> <template #col-2><slot name="content-col-2"></slot></template> </grid-layout> <slot v-else name="content" /> </bento-typography> </div> </template> <template v-if="hasSlot('actions')" #footer> <div class="b-modal-page__actions-container" :style="customPaddingStyles.actions"> <div aria-label="modal actions" class="b-modal-page__actions"> <slot name="actions" /> </div> </div> </template> <template v-else-if="actions && actions.length > 0" #footer> <div class="b-modal-page__actions-container" :style="customPaddingStyles.actions"> <footer-actions :actions="actions" :actions-label="actionsLabel" :destructive-actions="destructiveActions" /> </div> </template> </dialog-page> </template> <script setup lang="ts"> import { computed, defineComponent, inject, nextTick, onUnmounted, ref, useSlots, watch } from 'vue'; import { BentoModalColumnLayoutVariant, type BentoModalConfigData, BentoModalSize, } from '@/components/modal/modal.types'; import { BentoTypography } from '@/components/typography'; import { GridLayout, GridLayoutContainerType } from '@/internal'; import { useHasSlot } from '@/composables'; import { FooterActions } from '@/components/internal/footer-actions'; import DialogPage from '@/internal/dialog-page/dialog-page.vue'; import { MODAL_CONFIG_INJECTION_KEY } from '../../modal.keys'; import { observeSizeOfElement } from '@/utils/ts/resize'; import type { BentoModalPageProps } from './modal-page.types'; const CLOSE_BUTTON_ICON_WIDTH = 26; const slots = useSlots(); const hasSlot = useHasSlot(slots); const page = ref<InstanceType<typeof DialogPage>>(null); const props = withDefaults(defineProps<BentoModalPageProps>(), { actions: null, actionsLabel: null, columnsSize: BentoModalColumnLayoutVariant.SMALLER, customPadding: null, destructiveActions: false, withColumnLayout: false, pageId: undefined, previousPage: undefined, }); const modalPageConditionalClasses = computed(() => ({ 'b-modal-page--custom-padding': props.customPadding, })); const modalContentConditionalClasses = computed(() => ({ 'b-modal-page__content--with-header': hasSlot('default'), 'b-modal-page__content--with-actions': hasSlot('actions') || props.actions?.length, })); // Setting custom padding styles const customPaddingStyles = computed(() => { if (!props.customPadding) { return {}; } const xPadding = { paddingLeft: props.customPadding, paddingRight: props.customPadding, }; const content = { ...xPadding, paddingTop: undefined, paddingBottom: undefined }; if (!hasSlot('default')) { content.paddingTop = props.customPadding; } if (!hasSlot('actions')) { content.paddingBottom = props.customPadding; } return { header: { ...xPadding, paddingTop: props.customPadding, paddingRight: `${parseInt(props.customPadding, 10) + CLOSE_BUTTON_ICON_WIDTH}px`, flex: 1, }, content, actions: { ...xPadding, paddingBottom: props.customPadding }, backButton: { top: props.customPadding, paddingLeft: props.customPadding }, }; }); // Column layout styles const hasColumnLayout = computed(() => props.withColumnLayout && size.value === BentoModalSize.LARGE); const computedColumnWidth = computed(() => { if (props.columnsSize === BentoModalColumnLayoutVariant.SMALLER) { return [ { small: 4, medium: 4, large: 4 }, { small: 8, medium: 8, large: 8 }, ]; } if (props.columnsSize === BentoModalColumnLayoutVariant.BIGGER) { return [ { small: 8, medium: 8, large: 8 }, { small: 4, medium: 4, large: 4 }, ]; } return [ { small: 6, medium: 6, large: 6 }, { small: 6, medium: 6, large: 6 }, ]; }); // Register page to parent component and inject data from parent component const { activePage, height, isAnimationActive, pageCustomPadding, showCloseButton, size } = inject<BentoModalConfigData>(MODAL_CONFIG_INJECTION_KEY); const isCurrentPage = computed(() => activePage.value === props.pageId || !activePage.value); // Animation // Set resize observer on mount of update the height of the dialog on resize let pageElementObserver: ResizeObserver | null = null; // Watching activePage changes to set the height of the page and set isAnimationActive watch( () => activePage.value, async (value, oldValue) => { if (isCurrentPage.value) { await nextTick(); height.value = `${page.value?.$el?.clientHeight}px`; pageCustomPadding.value = props.customPadding; // only set isAnimationActive to true if its going from two different pages and not from null to the first page isAnimationActive.value = oldValue !== undefined; pageElementObserver = observeSizeOfElement(page.value, () => { const pageHeight = `${page.value?.$el?.clientHeight}px`; if (pageHeight && pageHeight !== height.value) { height.value = pageHeight; } }); } }, { immediate: true } ); // Disconnect observer on unmounting the page onUnmounted(() => { pageElementObserver?.disconnect(); }); </script> <script lang="ts"> /** * ModalPage is used to render different pages of the Modal component * * @example * import { BentoModal, BentoModalPage } from '@adyen/bento-vue2'; * * export default { * components: { BentoModal, BentoModalPage }, * template: ` * <bento-modal active-page='page1' :is-open='true'> * <bento-modal-page * pageId='page1' * actions-label='Move to next page' * :actions="[ * { * title: 'Next page', * event: () => { * activePage.value = 'page2'; * }, * }]"> * Page 1 * <template #content> * Page 1 content * </template> * </bento-modal-page> * <bento-modal-page * pageId='page2' * :actions="[ * { * title: 'Save', * event: () => {}, * }]"> * Page 2 * <template #content> * Page 2 content * </template> * </bento-modal-page> * </bento-modal> * ` * } */ export default defineComponent({}); </script> <style lang="scss" scoped src="./modal-page.scss" />
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { Canvas, Story, Meta } from '@storybook/blocks';
|
|
2
|
+
import * as ModalStories from './modal.stories';
|
|
3
|
+
|
|
4
|
+
<Meta title="Modal" of={ModalStories} />
|
|
5
|
+
|
|
6
|
+
# Modal
|
|
7
|
+
|
|
8
|
+
<Canvas sourceState="closed">
|
|
9
|
+
<Story of={ModalStories.Default} />
|
|
10
|
+
</Canvas>
|
|
11
|
+
|
|
12
|
+
Modal is a window that appear over any other page content and focus user attention exclusively on one task or piece of
|
|
13
|
+
information at the moment.
|
|
14
|
+
|
|
15
|
+
It also requires user interaction to return to whatever is underneath.
|
|
16
|
+
|
|
17
|
+
## Use Cases
|
|
18
|
+
|
|
19
|
+
`bento-modal` interrupts user workflow by design. User can not go back to previous workflow until they finish modal task
|
|
20
|
+
or dismiss it.
|
|
21
|
+
|
|
22
|
+
Here are some examples when to use it:
|
|
23
|
+
|
|
24
|
+
- Providing user critical information and a way to act on it (optionally).
|
|
25
|
+
- Confirming or changing user’s most recent action/decision.
|
|
26
|
+
- Providing user a way to perform a scoped short-term task without losing previous context.
|
|
27
|
+
|
|
28
|
+
### Do not use
|
|
29
|
+
|
|
30
|
+
Do not use a `bento-toast` in combination with `bento-modal`, because the modal's overlay always appears above all the
|
|
31
|
+
other content and will obscure the toast's information. When you need to provide feedback on actions taken within a
|
|
32
|
+
modal, use `bento-alert` instead.
|
|
33
|
+
|
|
34
|
+
## Variations
|
|
35
|
+
|
|
36
|
+
### Default
|
|
37
|
+
|
|
38
|
+
Acts as a container that can hold any type of content inside (forms, other components, images, videos, etc) and allow 3
|
|
39
|
+
different sizes (small, medium, large).
|
|
40
|
+
|
|
41
|
+
Use it when you need:
|
|
42
|
+
|
|
43
|
+
- Get input from user through forms
|
|
44
|
+
- Provide a complex-layout info consisting from different types of content (image, text, forms, etc)
|
|
45
|
+
|
|
46
|
+
## Navigation (with multiple pages)
|
|
47
|
+
|
|
48
|
+
Modal component supports deeper level navigation (user can go deeper in hierarchy and go back). Only for `default`
|
|
49
|
+
variant.
|
|
50
|
+
|
|
51
|
+
To handle navigation, every `bento-modal-page` should have a `pageId`, which can then be passed to the `activePage` prop
|
|
52
|
+
in `bento-modal` component to handle which page should be shown.
|
|
53
|
+
|
|
54
|
+
The back button by default, will send the user to the previous page. If there is a use case where we don't want the user
|
|
55
|
+
to navigate to the previous page, we can set the `previousPage` to null, in order to hide the button, or to a different
|
|
56
|
+
`pageId`, to send the user to a different previous page.
|
|
57
|
+
|
|
58
|
+
<Canvas of={ModalStories.MultiplePages} />
|
|
59
|
+
|
|
60
|
+
## Properties
|
|
61
|
+
|
|
62
|
+
This is the list of properties applicable to `bento-modal`, when using a single page, or `bento-modal-page`, when using
|
|
63
|
+
multiple pages:
|
|
64
|
+
|
|
65
|
+
| Property Name | Description |
|
|
66
|
+
| -------------------- | ------------------------------------------------------------------------ |
|
|
67
|
+
| `actions` | List of actions that will be used to render the buttons |
|
|
68
|
+
| `actionsLabel` | Text label that is displayed left to the action buttons |
|
|
69
|
+
| `columnsSize` | Sets the size of the column layout columns, used with `withColumnLayout` |
|
|
70
|
+
| `customPadding` | If provided, manages the padding of the whole modal container |
|
|
71
|
+
| `destructiveActions` | If passed - sets first button color to red |
|
|
72
|
+
| `withColumnLayout` | If passed, renders the content in a two-column layout |
|
|
73
|
+
|
|
74
|
+
Some properties can only be applied to `bento-modal`:
|
|
75
|
+
|
|
76
|
+
| Property Name | Description |
|
|
77
|
+
| ----------------- | ------------------------------------------------------------------------------- |
|
|
78
|
+
| `activePage` | Used to determine the current page, matches with `bento-modal-page` pageId prop |
|
|
79
|
+
| `hideCloseButton` | Hides a cross icon on the right side of the title |
|
|
80
|
+
| `isDismissible` | If set to true, it allows the modal to be closed without performing an action |
|
|
81
|
+
| `isOpen` | Controls the modal's state from the outside. Set it to true to open the modal |
|
|
82
|
+
| `size` | Sets the width of the modal |
|
|
83
|
+
| `variant` | Choose between default or dialog versions of modal |
|
|
84
|
+
|
|
85
|
+
Additionally, there are also properties applied only to `bento-modal-page`:
|
|
86
|
+
|
|
87
|
+
| Property Name | Description |
|
|
88
|
+
| -------------- | --------------------------------------------------------------------------------------------- |
|
|
89
|
+
| `pageId` | The id of the page, used with `bento-modal` activePage prop to control navigation |
|
|
90
|
+
| `previousPage` | Can be set to null to hide the back button, or to a `pageId` to override the normal behaviour |
|
|
91
|
+
|
|
92
|
+
#### Dismissible
|
|
93
|
+
|
|
94
|
+
`isDismissible` property allows the modal to be closed without performing an action. Setting `isDismissible` to false
|
|
95
|
+
will also hide the close button.
|
|
96
|
+
|
|
97
|
+
Default value for Modal "Default" variant is set to `true`
|
|
98
|
+
|
|
99
|
+
#### Close button
|
|
100
|
+
|
|
101
|
+
`bento-modal` can be dismissed by X button in the header or by clicking outside the modal (optional).
|
|
102
|
+
|
|
103
|
+
By passing `hideCloseButton` property, the X button is removed from the modal header.
|
|
104
|
+
|
|
105
|
+
Close button will not show if the modal is not dismissible.
|
|
106
|
+
|
|
107
|
+
#### Destructive actions
|
|
108
|
+
|
|
109
|
+
Renders the modal with the main action present in the actions in a destructive variant.
|
|
110
|
+
|
|
111
|
+
<Canvas sourceState="closed">
|
|
112
|
+
<Story of={ModalStories.DestructiveActions} />
|
|
113
|
+
</Canvas>
|
|
114
|
+
|
|
115
|
+
#### Size
|
|
116
|
+
|
|
117
|
+
Modal default has the following sizes:
|
|
118
|
+
|
|
119
|
+
- `small`: 480px
|
|
120
|
+
- `medium`: 600px
|
|
121
|
+
- `large`: 800px
|
|
122
|
+
|
|
123
|
+
### With column layout
|
|
124
|
+
|
|
125
|
+
Only available for `large` size modals. Renders the modal content in two columns separated by a divider.
|
|
126
|
+
|
|
127
|
+
<Canvas sourceState="closed">
|
|
128
|
+
<Story of={ModalStories.ColumnLayout} />
|
|
129
|
+
</Canvas>
|
|
130
|
+
|
|
131
|
+
#### Column width
|
|
132
|
+
|
|
133
|
+
The width of the two columns can be customized via the `columnWidth` property. This property sets the width of the
|
|
134
|
+
leftmost column and can take up the following values:
|
|
135
|
+
|
|
136
|
+
- `'smaller'`: the leftmost column is smaller than the rightmost one
|
|
137
|
+
- `'equal'`: the leftmost column is the same size as the rightmost one
|
|
138
|
+
- `'bigger'`: the leftmost column is bigger than the rightmost one
|
|
139
|
+
|
|
140
|
+
### Dialog
|
|
141
|
+
|
|
142
|
+
Modal dialog is intended to focus user on important message that requires deliberate user action.
|
|
143
|
+
|
|
144
|
+
It does not have an X button in header and can be dismissed by cancel button in the footer.
|
|
145
|
+
|
|
146
|
+
Modal dialog can not hold any types of content but text content. It also comes in one predefined size.
|
|
147
|
+
|
|
148
|
+
<Canvas sourceState="closed">
|
|
149
|
+
<Story of={ModalStories.Dialog} />
|
|
150
|
+
</Canvas>
|
|
151
|
+
|
|
152
|
+
#### Properties
|
|
153
|
+
|
|
154
|
+
This is the list of properties, applicable only to `bento-modal` of "Dialog" variant
|
|
155
|
+
|
|
156
|
+
| Property Name | Description |
|
|
157
|
+
| --------------- | ----------------------------------------------------------------------------- |
|
|
158
|
+
| `variant` | Choose between default or dialog versions of modal |
|
|
159
|
+
| `isDismissible` | If set to true, it allows the modal to be closed without performing an action |
|
|
160
|
+
| `isOpen` | Controls the modal's state from the outside. Set it to true to open the modal |
|
|
161
|
+
| `actions` | List of actions that will be used to render the buttons |
|
|
162
|
+
| `dialogType` | Changes the visual styling of action buttons |
|
|
163
|
+
| `content` | Text that will be displayed in the content section of Modal |
|
|
164
|
+
|
|
165
|
+
#### Sub-variants
|
|
166
|
+
|
|
167
|
+
The `dialogType` property controls the Modal dialog sub-variants.
|
|
168
|
+
|
|
169
|
+
Main difference between this sub-variants is button styling.
|
|
170
|
+
|
|
171
|
+
The possible options for `dialogType` are:
|
|
172
|
+
|
|
173
|
+
- `'confirmation'` (default)
|
|
174
|
+
- `'destructive'`
|
|
175
|
+
- `'info'`
|
|
176
|
+
- `'acknowledgement'`
|
|
177
|
+
|
|
178
|
+
#### Dismissible
|
|
179
|
+
|
|
180
|
+
`isDismissible` property allows the modal to be closed without performing an action.
|
|
181
|
+
|
|
182
|
+
Default value for Modal "Dialog" variant is set to `false`.
|
|
183
|
+
|
|
184
|
+
## Accessibility
|
|
185
|
+
|
|
186
|
+
`bento-modal` uses the native HTML `<dialog>` element.
|
|
187
|
+
|
|
188
|
+
It means that when `bento-modal` is opened, then everything other than the `<dialog>` and its contents is rendered inert
|
|
189
|
+
using the `inert` attribute.
|
|
190
|
+
|
|
191
|
+
It indicates the browser to ignore all the elements with `inert` attribute applied.
|
|
192
|
+
|
|
193
|
+
### Keyboard Interaction
|
|
194
|
+
|
|
195
|
+
Being an interactive element, the modal is reachable through keyboard navigation, using the advantages of focus-trap
|
|
196
|
+
feature. The following keys trigger the following actions:
|
|
197
|
+
|
|
198
|
+
| Keyboard interaction | Action |
|
|
199
|
+
| -------------------- | -------------------------------------------------------------- |
|
|
200
|
+
| `Tab` | Moves focus to the first interactive element inside modal |
|
|
201
|
+
| `ESC` | Closes the modal if its `isDismissible` property set to `true` |
|
|
202
|
+
|
|
203
|
+
### Roles, States, and Properties
|
|
204
|
+
|
|
205
|
+
Adhering to the [WAI-ARIA 1.2](https://www.w3.org/TR/wai-aria-1.2/) standard we follow the
|
|
206
|
+
[dialog pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/) documentation provided.
|
|
207
|
+
|
|
208
|
+
| ARIA attribute | is automatic? | fallback used | manual override |
|
|
209
|
+
| -------------- | ------------------------------------------------------------------------------------------ | --------------------------------------------------------- | --------------- |
|
|
210
|
+
| label | Yes, value from `default` slot | `modal` value is used in case no `default` slot provided. | - |
|
|
211
|
+
| role | Yes, semantic `dialog` element used. `alertdialog` role is used for modal "Dialog" variant | - | - |
|
|
212
|
+
|
|
213
|
+
## Resources
|
|
214
|
+
|
|
215
|
+
- [Figma link](https://www.figma.com/file/uLabwF3243jdMDsNSP7U9I/Bento---Components?node-id=7423%3A89670&mode=dev)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue'; import { action } from '@storybook/addon-actions'; import { ref } from 'vue'; import { isVue2 } from 'vue-demi'; import { BentoButton } from '@/components/button'; import { BentoModalColumnLayoutVariant, BentoModalDialogType, BentoModalSize, BentoModalVariant } from './modal.types'; import { storybookDocsParameter } from '@/utils/ts/storybook'; import { BentoModalPage } from '@/components/modal'; import BentoModal from './modal.vue'; import ModalWithMultiplePages from './__tests__/modal-with-multiple-pages.vue?raw'; const meta: Meta<typeof BentoModal> = { title: 'Modal', component: BentoModal, argTypes: { default: { name: 'default', defaultValue: '', description: 'Modal title', table: { type: { summary: 'VNode[]' }, }, control: { type: null }, }, content: { name: 'content', defaultValue: '', description: 'Modal content', table: { type: { summary: 'VNode[]' }, }, control: { type: null }, }, 'content-col-1': { name: 'content-col-1', defaultValue: '', description: 'Modal with column layout - first column content', table: { type: { summary: 'VNode[]' }, }, control: { type: null }, }, 'content-col-2': { name: 'content-col-2', defaultValue: '', description: 'Modal with column layout - second column content', table: { type: { summary: 'VNode[]' }, }, control: { type: null }, }, variant: { options: Object.values(BentoModalVariant), mapping: BentoModalVariant, control: { type: 'select', }, }, size: { options: Object.values(BentoModalSize), mapping: BentoModalSize, control: { type: 'select', }, }, dialogType: { options: Object.values(BentoModalDialogType), mapping: BentoModalDialogType, control: { type: 'select', }, }, columnsSize: { options: Object.values(BentoModalColumnLayoutVariant), mapping: BentoModalColumnLayoutVariant, control: { type: 'select', }, }, isDismissible: { control: { type: 'boolean', }, }, hideCloseButton: { control: { type: 'boolean', }, }, destructiveActions: { control: { type: 'boolean', }, }, isOpen: { control: { type: 'boolean', }, }, // Events 'close-modal': { table: { type: { summary: '() => void' }, }, control: { disable: true }, }, }, }; export default meta; type Story = StoryObj<typeof BentoModal>; const MODAL_TITLE = 'Modal title'; const MODAL_CONTENT = 'Modal content'; const MODAL_ACTIONS_LABEL = 'Label Text'; const MODAL_ACTIONS = [ { title: 'Action', event: () => action('Primary')(), }, { title: 'Action', event: () => action('Secondary')(), }, ]; const SHARED_DEFAULT_PROPS = { default: MODAL_TITLE, content: MODAL_CONTENT, isOpen: false, }; const MODAL_DEFAULT_PROPS = { ...SHARED_DEFAULT_PROPS, variant: 'default', size: 'medium', hideCloseButton: false, actions: MODAL_ACTIONS, destructiveActions: false, }; const MODAL_DIALOG_PROPS = { ...SHARED_DEFAULT_PROPS, variant: 'dialog', actions: [MODAL_ACTIONS[0]], dialogType: 'confirmation', }; const defaultTemplate = ` <div class="b-modal-story--default"> <bento-modal v-bind="args" aria-label="Aria label content" :is-open="isOpen" @close-modal="closeModal"> <template #default v-if='args.default'> {{ args.default }} </template> <template #content v-if='args.content'> {{ args.content }} </template> </bento-modal> <bento-button :aria-expanded="isOpen" @click="openModal" > Open modal </bento-button> </div> `; const defaultCode = ` <template> <bento-modal aria-label="Aria label content" :actions="actionsArray" :is-open="isOpen" @close-modal="closeModal" > Modal Title <template #content> {{ ${MODAL_DIALOG_PROPS.content} }} </template> </bento-modal> <bento-button :aria-expanded="isOpen" @click="openModal" > Open modal </bento-button> </template> <script setup lang="ts"> import { BentoModal, BentoButton, BentoButtonActionsList } from '@adyen/bento-vue2'; const actionsArray: BentoButtonActionsList = [{ title: 'Action 1', event: () => { // Your handler here }, title: 'Action 2', event: () => { // Your handler here }, }]; const isOpen = ref(false); const closeModal = () => { isOpen.value = false; }; const openModal = () => { isOpen.value = true; }; </script> `; export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoModal, BentoButton }, props: Object.keys(argTypes), template: defaultTemplate, setup(props) { const args = isVue2 ? props : _args; const isOpen = ref(args.isOpen); const closeModal = () => { isOpen.value = false; }; const openModal = () => { isOpen.value = true; }; return { // Values isOpen, args, // Methods openModal, closeModal, }; }, }), parameters: storybookDocsParameter(defaultCode), args: { ...MODAL_DEFAULT_PROPS, }, }; const columnLayoutCode = ` <template> <bento-modal v-bind="args" aria-label="Aria label content" :actions="actionsArray" :is-open="isOpen" @close-modal="closeModal" > <template #default>Modal Title</template> <template #content-col-1> Your modal content here </template> <template #content-col-2> Some other content here </template> </bento-modal> <bento-button :aria-expanded="isOpen" @click="openModal" > Open modal </bento-button> </template> <script setup lang="ts"> import { BentoModal, BentoButton, BentoButtonActionsList } from '@adyen/bento-vue2'; const actionsArray: BentoButtonActionsList = [{ title: 'Action 1', event: () => { // Your handler here }, title: 'Action 2', event: () => { // Your handler here }, }]; const isOpen = ref(false); const closeModal = () => { isOpen.value = false; }; const openModal = () => { isOpen.value = true; }; </script> `; export const ColumnLayout: Story = { render: (_args, { argTypes }) => ({ components: { BentoModal, BentoButton }, props: Object.keys(argTypes), template: ` <div class="b-modal-story--default"> <bento-modal v-bind="args" aria-label="Aria label content" :is-open="isOpen" @close-modal="closeModal" > <template #default v-if='args.default'> {{ args.default }} </template> <template v-if='args.content' #content-col-1> <div>content column 1</div> </template> <template v-if='args.content' #content-col-2> <div>content column 2</div> </template> </bento-modal> <bento-button :aria-expanded="isOpen" @click="openModal" > Open modal </bento-button> </div> `, setup(props) { const args = isVue2 ? props : _args; const isOpen = ref(args.isOpen); const closeModal = () => { isOpen.value = false; }; const openModal = () => { isOpen.value = true; }; return { // Values isOpen, args, // Methods openModal, closeModal, }; }, }), parameters: storybookDocsParameter(columnLayoutCode), args: { ...MODAL_DEFAULT_PROPS, size: 'large', withColumnLayout: true, }, }; const dialogCode = ` <template> <bento-modal aria-label="Aria label content" :content="${MODAL_DIALOG_PROPS.content}" variant="dialog" dialog-type="confirmation" :actions="dialogActions" :is-open="isOpen" @close-modal="closeModal" > Modal Title </bento-modal> <bento-button :aria-expanded="isOpen" @click="openModal" > Open modal </bento-button> </template> <script setup lang="ts"> import { BentoModal, BentoButton, BentoButtonActionsList } from '@adyen/bento-vue2'; const dialogActions: BentoButtonActionsList = [{ title: 'Action', event: () => { //Your handler here }, }] const isOpen = ref(false); const closeModal = () => { isOpen.value = false; }; const openModal = () => { isOpen.value = true; }; </script> `; export const Dialog: Story = { render: (_args, { argTypes }) => ({ components: { BentoModal, BentoButton }, props: Object.keys(argTypes), template: defaultTemplate, setup(props) { const args = isVue2 ? props : _args; const isOpen = ref(args.isOpen); const closeModal = () => { isOpen.value = false; }; const openModal = () => { isOpen.value = true; }; return { // Values isOpen, args, // Methods openModal, closeModal, }; }, }), parameters: storybookDocsParameter(dialogCode), args: { ...MODAL_DIALOG_PROPS, }, }; const ActionsWithLabelCode = ` <template> <bento-modal :actions="actionsArray" :is-open="isOpen" @close-modal="closeModal" > Modal Title <template #content> {{ ${MODAL_DIALOG_PROPS.content} }} </template> </bento-modal> <bento-button :aria-expanded="isOpen" @click="openModal" > Open modal </bento-button> </template> <script setup lang="ts"> import { BentoModal, BentoButton, BentoButtonActionsList } from '@adyen/bento-vue2'; const actionsArray: BentoButtonActionsList = [{ title: 'Action 1', event: () => { // Your handler here }, title: 'Action 2', event: () => { // Your handler here }, }]; const isOpen = ref(false); const closeModal = () => { isOpen.value = false; }; const openModal = () => { isOpen.value = true; }; </script> `; export const ActionsWithLabel: Story = { render: (_args, { argTypes }) => ({ components: { BentoModal, BentoButton }, props: Object.keys(argTypes), template: defaultTemplate, setup(props) { const args = isVue2 ? props : _args; const isOpen = ref(args.isOpen); const closeModal = () => { isOpen.value = false; }; const openModal = () => { isOpen.value = true; }; return { // Values isOpen, args, // Methods openModal, closeModal, }; }, }), parameters: storybookDocsParameter(ActionsWithLabelCode), args: { ...MODAL_DEFAULT_PROPS, actionsLabel: MODAL_ACTIONS_LABEL, }, }; const DestructiveActionsCode = ` <template> <bento-modal destructive-actions :actions="actionsArray" :is-open="isOpen" @close-modal="closeModal" > Modal Title <template #content> {{ ${MODAL_DIALOG_PROPS.content} }} </template> </bento-modal> <bento-button :aria-expanded="isOpen" @click="openModal" > Open modal </bento-button> </template> <script setup lang="ts"> import { BentoModal, BentoButton, BentoButtonActionsList } from '@adyen/bento-vue2'; const actionsArray: BentoButtonActionsList = [{ title: 'Action 1', event: () => { // Your handler here }, title: 'Action 2', event: () => { // Your handler here }, }]; const isOpen = ref(false); const closeModal = () => { isOpen.value = false; }; const openModal = () => { isOpen.value = true; }; </script> `; export const DestructiveActions: Story = { render: (_args, { argTypes }) => ({ components: { BentoModal, BentoButton }, props: Object.keys(argTypes), template: defaultTemplate, setup(props) { const args = isVue2 ? props : _args; const isOpen = ref(args.isOpen); const closeModal = () => { isOpen.value = false; }; const openModal = () => { isOpen.value = true; }; return { // Values isOpen, args, // Methods openModal, closeModal, }; }, }), parameters: storybookDocsParameter(DestructiveActionsCode), args: { ...MODAL_DEFAULT_PROPS, destructiveActions: true, }, }; export const MultiplePages: Story = { render: (_args, { argTypes }) => ({ components: { BentoModal, BentoModalPage, BentoButton }, props: Object.keys(argTypes), template: ` <div class="b-modal-story--default"> <bento-modal v-bind="args" aria-label="Aria label content" :is-open="isOpen" @close-modal="closeModal" :activePage="activePage" @update:active-page="activePage = $event"> <bento-modal-page v-bind="intro"> {{ intro.title }} <template #content> <p style="margin-top: 0;">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p> <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p> <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p> <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p> <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p> </template> </bento-modal-page> <bento-modal-page v-bind="newProfile" :withColumnLayout='true' > {{ newProfile.title }} <template #content> This page has some info about creating a new profile </template> </bento-modal-page> <bento-modal-page v-bind="editProfile" > {{ editProfile.title }} <template #content> This is where you edit your profile </template> </bento-modal-page> <bento-modal-page v-bind="newProfileForm"> {{ newProfileForm.title }} <template #content> This is where you create a new profile </template> </bento-modal-page> <bento-modal-page v-bind="success"> {{ success.title }} <template #content> Your profile is created </template> </bento-modal-page> </bento-modal> <bento-button :aria-expanded="isOpen" @click="openModal" > Open modal </bento-button> </div> `, setup(props) { const args = isVue2 ? props : _args; const isOpen = ref(args.isOpen); const activePage = ref('intro'); const closeModal = () => { isOpen.value = false; }; const openModal = () => { isOpen.value = true; }; const intro = { pageId: 'intro', title: 'Intro', actions: [ { title: 'New profile', event: () => { activePage.value = 'newProfile'; }, }, { title: 'Edit profile', event: () => { activePage.value = 'editProfile'; }, }, ], actionsLabel: 'What do you wanna do?', }; const newProfile = { pageId: 'newProfile', title: 'New profile', actions: [ { title: 'Go to form', event: () => { activePage.value = 'newProfileForm'; }, }, ], }; const editProfile = { pageId: 'editProfile', title: 'Edit profile', actions: [ { title: 'Save', event: () => action('save')(), }, { title: 'Cancel', event: () => action('cancel')(), }, ], }; const newProfileForm = { pageId: 'newProfileForm', title: 'New profile form', actions: [ { title: 'Save', event: () => { activePage.value = 'success'; }, }, { title: 'Cancel', event: () => action('cancel')(), }, ], }; const success = { pageId: 'success', title: 'Success!', destructiveActions: true, actions: [ { title: 'Delete', event: () => action('delete')(), }, { title: 'Cancel', event: () => action('cancel')(), }, ], previousPage: null, }; return { // Values isOpen, args, activePage, intro, newProfile, newProfileForm, success, editProfile, // Methods openModal, closeModal, }; }, }), parameters: storybookDocsParameter(ModalWithMultiplePages), args: MODAL_DEFAULT_PROPS, };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <component :is="computedVariant" class="b-modal" :is-open="isOpen" :actions="actions" :initially-open="initiallyOpen" v-bind="customProps" @close-modal="onCloseModal" @update:active-page="emit('update:active-page', $event)" > <template v-if="hasSlot('default')" #default> <slot></slot> </template> <template v-if="hasSlot('content-col-1')" #content-col-1><slot name="content-col-1"></slot></template> <template v-if="hasSlot('content-col-2')" #content-col-2><slot name="content-col-2"></slot></template> <template v-if="hasSlot('content')" #content><slot name="content"></slot></template> </component> </template> <script setup lang="ts"> import { computed, defineComponent, type PropType, useAttrs, useSlots, watch } from 'vue'; import { BentoModalColumnLayoutVariant, BentoModalDialogType, BentoModalSize, BentoModalVariant, BentoModalVariantMapping, } from './modal.types'; import { BentoModalDefault, BentoModalDialog } from '@/components/modal/components'; import { useHasSlot } from '@/composables'; import { type BentoButtonActionsList } from '@/components/button'; import { deprecate } from '@/utils/ts/deprecate'; const LOADING_BTN_CLOSE_AFTER_SUCCESS_DELAY_TIME = 500; type BentoModalVariantProp = BentoModalVariant | `${BentoModalVariant}` | `${BentoModalVariantMapping}`; const slots = useSlots(); const hasSlot = useHasSlot(slots); const attrs = useAttrs(); const emit = defineEmits<{ /** * Emits update event when page is changed internally */ (e: 'update:active-page', page: string): void; /** * Emitted when the modal is closed */ (e: 'close-modal'); }>(); 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". * * 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>, default: null, }, /** * The id of the current active page * Should be the same id as the BentoModalPage to be displayed */ activePage: { type: String, default: null, }, /** * Text label that is displayed left to the action buttons */ actionsLabel: { type: String, default: null }, /** * Text that will be displayed in the content section of Modal */ content: { type: String, default: null }, /** * If a column layout is provided, it renders the respective left column width variant. This can be smaller than the second column, the same size as the second column or bigger than the second column. */ columnsSize: { type: String as PropType<BentoModalColumnLayoutVariant | `${BentoModalColumnLayoutVariant}`>, default: BentoModalColumnLayoutVariant.SMALLER, validator: (value: BentoModalColumnLayoutVariant) => Object.values(BentoModalColumnLayoutVariant).includes(value), }, /** * If provided, manages the padding of the whole modal container */ customPadding: { type: String, default: null }, /** * If passed - sets first button color to red */ destructiveActions: { type: Boolean, default: false }, /** * Changes the visual styling of action buttons */ dialogType: { type: String as PropType<BentoModalDialogType | `${BentoModalDialogType}`>, default: BentoModalDialogType.CONFIRMATION, validator: (value: BentoModalDialogType) => Object.values(BentoModalDialogType).includes(value), }, /** * Hides a cross icon on the right side of the title */ hideCloseButton: { type: Boolean, default: false }, /** * If set to true, it allows the modal to be closed without performing an action */ isDismissible: { type: Boolean, default: null }, /** * Controls the modal's state from the outside. Set it to true to open the modal */ isOpen: { type: Boolean, default: false, }, /** * Sets the open attribute on the dialog element, use isOpen instead to toggle modal visbility. */ initiallyOpen: { type: Boolean, default: false }, /** * Sets the width of the modal */ size: { type: String as PropType<BentoModalSize | `${BentoModalSize}`>, default: BentoModalSize.SMALL, validator: (value: BentoModalSize) => Object.values(BentoModalSize).includes(value), }, /** * Choose between default or dialog versions of modal */ variant: { type: String as PropType<BentoModalVariantProp>, default: 'default', validator: (value: BentoModalVariantProp) => Object.values<BentoModalVariantProp>(BentoModalVariant).includes(value) || Object.values<BentoModalVariantProp>(BentoModalVariantMapping).includes(value), }, /** * If provided, renders the modal's content in a two-column layout an wth dividers separating its content */ withColumnLayout: { type: Boolean, default: false }, }); const computedVariant = computed(() => { if (Object.values<BentoModalVariantProp>(BentoModalVariantMapping).includes(props.variant)) { return props.variant; } return BentoModalVariantMapping[props.variant.toUpperCase()]; }); const customProps = computed(() => computedVariant.value === BentoModalVariantMapping.DEFAULT ? { activePage: props.activePage, hideCloseButton: props.hideCloseButton, size: props.size, customPadding: props.customPadding, actionsLabel: props.actionsLabel, destructiveActions: props.destructiveActions, isDismissible: props.isDismissible ?? true, withColumnLayout: props.withColumnLayout, columnsSize: props.columnsSize, ...attrs, } : { activePage: props.activePage, dialogType: props.dialogType, content: props.content, isDismissible: props.isDismissible ?? false, ...attrs, } ); const onCloseModal = () => { emit('close-modal'); }; // If the first button is a loading button then close the modal have 0.5 seconds // if the state is changed to 'success'. watch( () => props.actions, () => { if (props.actions?.[0]?.state === 'success') { setTimeout(onCloseModal, LOADING_BTN_CLOSE_AFTER_SUCCESS_DELAY_TIME); } } ); if (Object.keys(attrs).includes('overflow-visible')) { deprecate( 'BentoModal "overflow-visible" property', `"overflow-visible" property was made redundant. BentoModal can work correctly without the need to pass the propery. Remove the "overflow-visible" prop from BentoModal to remove this error.`, '2.0.0' ); } </script> <script lang="ts"> /** * Modal is a window that appear over any other page content * and focus user attention exclusively on one task or piece of * information at the moment. * * @example * import { BentoModal } from '@adyen/bento-vue2'; * * export default { * components: { BentoModal }, * template: ` * <bento-modal :isOpen="true"> * Title of the Modal * <template #content> * Mandatory content slot * </template> * </bento-modal> * ` * }; */ export default defineComponent({ components: { BentoModalDefault, BentoModalDialog }, }); </script>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <dialog-page class="b-modal-fullscreen-page" :page-id="pageId" :previous-page="previousPage" always-show-border> <template #header> <div class="b-modal-fullscreen-page__header"> <div class="b-modal-fullscreen-page__header-section b-modal-fullscreen-page__header-section--title"> <bento-typography v-if="title" el="h2" variant="title" class="b-modal-fullscreen-page__title" :title="title" >{{ title }}</bento-typography > </div> <div v-if="hasSlot('header')" class="b-modal-fullscreen-page__header-section b-modal-fullscreen-page__header-section--slot" > <slot name="header" /> </div> <div class="b-modal-fullscreen-page__header-section b-modal-fullscreen-page__header-section--actions"> <bento-typography v-if="contextualLabel" class="b-modal-fullscreen-page__contextual-label" el="span" :title="contextualLabel" >{{ contextualLabel }}</bento-typography > <bento-button-actions v-if="actions" class="b-modal-fullscreen-page__actions" :actions="actions" /> <div class="b-modal-fullscreen-page__separator"></div> </div> </div> </template> <template #content> <div class="b-modal-fullscreen-page__content"> <slot /> </div> </template> </dialog-page> </template> <script setup lang="ts"> import { onMounted, type PropType, useSlots } from 'vue'; import { BentoButtonActions, type BentoButtonActionsList } from '@/components/button'; import { BentoTypography } from '@/components/typography'; import { DialogPage } from '@/internal'; import { useHasSlot } from '@/composables'; 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". * * Each object in the list should have a `title`, an `event` and * (optional) a Vue `icon` from the library `@adyen/ui-assets-icons-16`. * * @see BentoButton for a list of all the other props supported by each button action. */ actions: { type: Array as PropType<BentoButtonActionsList>, default: null, }, /** * Label shown next to the modal actions */ contextualLabel: { type: String, default: null, }, /** * Identifier of the page, used with activePage prop to handle sidepanel 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, }, /** * Title of the modal */ title: { type: String, default: null, }, }); const slots = useSlots(); const hasSlot = useHasSlot(slots); onMounted(() => { if (props.actions && props.actions.length > 2) { throw new Error('Not more than 2 action buttons may be provided in Bento Modal Fullscreen'); } }); </script> <script lang="ts"> /** * ModalFullscreenPage is used to render different pages of ModalFullscreen component * * @example * import { BentoModalFullscreen, BentoModalFullscreenPage } from '@adyen/bento-vue2'; * * export default { * components: { BentoModalFullscreen, BentoModalFullscreenPage }, * template: ` * <bento-modal-fullscreen active-page='page1' is-open='true'> * <bento-modal-fullscreen-page * title='Page 1' * pageId='page1' * contextual-label='Move to next page' * :actions="[ * { * title: 'Next page', * event: () => { * activePage.value = 'page2'; * }, * }]"> * Page 1 info * </bento-modal-fullscreen-page> * <bento-modal-fullscreen-page * title='Page 2' * pageId='page2' * :actions="[ * { * title: 'Save', * event: () => {}, * }]"> * Page 2 info * </bento-modal-fullscreen-page> * </bento-modal-fullscreen> * ` * } */ export default {}; </script> <style lang="scss" scoped src="./modal-fullscreen-page.scss" />
|