@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
|
+
import { computed, onMounted, ref } from 'vue'; import { action } from '@storybook/addon-actions'; import { type BentoDateRangeFilterOptions, type BentoFilterBarContainerSizeLayout, type BentoFilterBarModel, type BentoFilterBarSearchBarProps, type BentoFilterBarValue, BentoFilterItemType, type BentoFilterValueObject, type BentoFilterValues, } from './filter-bar.types'; import { getPokemonFromApi, storybookDocsParameter, withToastDecorator } from '@/utils/ts/storybook'; import { type BentoListboxOptions } from '@/types/listbox'; import { BentoCountryCode } from '@/components/country/country.types'; import { BentoCurrencyISOCode } from '@/components/currency/currency.types'; import { isVue2 } from 'vue-demi'; import { type Meta, type StoryObj } from '@storybook/vue'; import { BentoToggle } from '@/components/toggle'; import BentoFilterBar from './filter-bar.vue'; import { endOfWeek } from 'date-fns/endOfWeek'; import { startOfWeek } from 'date-fns/startOfWeek'; import { subDays } from 'date-fns/subDays'; import { subWeeks } from 'date-fns/subWeeks'; import { subMonths } from 'date-fns/subMonths'; import { startOfMonth } from 'date-fns/startOfMonth'; import { endOfDay, startOfDay, subHours, subMinutes } from 'date-fns'; // Examples import FilterBarCustomFilterSubComponentSingleValueExample from './__tests__/filter-bar-custom-filter-sub-component-single-value-example.vue'; import FilterBarCustomFilterSubComponentMultiValueExample from './__tests__/filter-bar-custom-filter-sub-component-multiple-value-example.vue'; import FilterBarCustomFilterSubComponentWithPopoverExample from './__tests__/filter-bar-custom-filter-sub-component-with-popover-example.vue'; import FilterBarCustomFiltersExample from './__tests__/filter-bar-custom-filters-example.vue?raw'; // Generate array from 1 to # // { label: 'Option #', value: # }, const DEFAULT_OPTIONS: BentoListboxOptions = new Array(10) .fill(0) .map((_, index) => ({ label: `Option ${index + 1}`, value: index + 1 })); // To be used by as the current time across all date filters const CURRENT_DATE_AND_TIME = new Date(); const meta: Meta = { title: 'Filter bar', component: BentoFilterBar, decorators: [withToastDecorator], argTypes: { // Props searchTerm: { name: 'searchTerm', defaultValue: undefined, description: "Search term useful to filter all the data by text. If the value is non-existant (`null` or `undefined`) the search bar won't be enabled.", control: { type: 'text', }, }, // Events input: { name: 'input', defaultValue: '', description: 'Triggered whenever the filters are updated.', table: { type: { summary: '([{ field, label, value, type }]) => void' } }, control: { disable: true }, }, // Search Events 'search-input': { name: 'search-input', defaultValue: '', description: "Triggered whenever the filter's search is updated.", table: { type: { summary: '(newSearchTerm) => void' } }, control: { disable: true }, }, 'search-clear': { name: 'search-clear', defaultValue: '', description: "Triggered whenever the filter's search is cleared.", table: { type: { summary: '() => void' } }, control: { disable: true }, }, 'update:search-term': { name: 'update:search-term', defaultValue: '', description: "Triggered whenever the filter's search is updated to sync the model.", table: { type: { summary: '(newSearchTerm) => void' } }, control: { disable: true }, }, }, }; export default meta; type Story = StoryObj<typeof BentoFilterBar>; const FILTER_VALUES: BentoFilterValues = [ { field: 'input', value: undefined, }, { field: 'boolean', value: 'yes', }, { field: 'input-with-dropdown', value: undefined, }, { field: 'hidden-input', value: 'Active value of a hidden filter', }, { field: 'merchant', value: 2, }, { field: 'company', value: [1, 3, 5], }, { field: 'nested-select', value: ['amsterdam', 'den-haag'], }, { field: 'currency', value: [], }, { field: 'range', value: ['', ''], }, { field: 'date', value: CURRENT_DATE_AND_TIME, }, { field: 'date-range', value: { startDate: subDays(startOfDay(CURRENT_DATE_AND_TIME), 2), endDate: endOfDay(CURRENT_DATE_AND_TIME) }, }, { field: 'granular-date-range', value: { startDate: subDays(CURRENT_DATE_AND_TIME, 2), endDate: CURRENT_DATE_AND_TIME }, }, { field: 'date-range-month', value: { startDate: startOfMonth(subMonths(CURRENT_DATE_AND_TIME, 2)), endDate: startOfMonth(CURRENT_DATE_AND_TIME), }, }, { field: 'paymentMethod', value: undefined, }, { field: 'checkbox-group', value: [], }, { field: 'toggle', value: true, }, ]; const FILTERS: BentoFilterBarModel = [ { field: 'input', label: 'Input filter', type: BentoFilterItemType.INPUT, options: { placeholder: 'Input filter placeholder', tooltipPosition: 'bottom', tooltipText: 'Input filter tooltip text', }, visible: true, }, { field: 'input-with-dropdown', label: 'Input with dropdown filter', type: BentoFilterItemType.INPUT_DROPDOWN, visible: true, options: { dropdown: { items: [ { label: 'EUR', value: 'EUR' }, { label: 'USD', value: 'USD' }, ], modelValue: 'EUR', dynamicFiltering: true, }, }, }, { field: 'hidden-input', label: 'Hidden input filter', description: 'This is a hidden input filter only visible through the modal', type: BentoFilterItemType.INPUT, }, { field: 'country', label: 'Country/Region', options: { listboxItems: [ { label: 'Option 1', value: BentoCountryCode.NL, }, { label: 'Option 2', value: BentoCountryCode.BE, }, ], multiple: true, variant: 'capital', }, type: BentoFilterItemType.SELECT_COUNTRY, }, { field: 'avatar', label: 'Avatar', options: { listboxItems: [ { label: 'Avatar 1', value: 'avatar-1', }, { label: 'Avatar 2', value: 'avatar-2', }, ], }, type: BentoFilterItemType.SELECT_AVATAR, }, { field: 'merchant', label: 'Merchant', options: { listboxItems: DEFAULT_OPTIONS, enableValueLabelPair: true, isOptionDisabled: option => option.value === 3, }, type: BentoFilterItemType.SELECT, visible: true, }, { field: 'company', label: 'Company', options: { listboxItems: DEFAULT_OPTIONS, enableValueLabelPair: true, isOptionDisabled: option => option.value === 5, multiple: true, }, type: BentoFilterItemType.SELECT, visible: true, }, { field: 'nested-select', label: 'Nested select', options: { listboxItems: [ { label: 'Belgium', description: 'Country/Region', value: 'belgium', }, { label: 'Netherlands', description: 'Country/Region', value: 'netherlands', items: [ { label: 'Amsterdam', description: 'City', value: 'amsterdam', }, { label: 'Den Haag', description: 'City', value: 'den-haag', }, ], }, ], multiple: true, }, type: BentoFilterItemType.SELECT, visible: true, }, { field: 'currency', label: 'Currency select', options: { listboxItems: [ { label: 'Euro', value: BentoCurrencyISOCode.EUR, }, { label: 'US Dollars', value: BentoCurrencyISOCode.USD, }, ], multiple: true, }, type: BentoFilterItemType.SELECT_CURRENCY, }, { field: 'range', label: 'Range', type: BentoFilterItemType.RANGE, visible: true, }, { field: 'date', label: 'Date', type: BentoFilterItemType.DATE, visible: true, }, { field: 'date-range', label: 'Date range', options: { allowTimeInput: true, numberOfMonths: 1, quickSelectRanges: [ { label: 'Last 15 min', value: 'lastFifteenMin', data: { startDate: subMinutes(CURRENT_DATE_AND_TIME, 15), isRelativeToNow: true, }, }, { label: 'Last 24 hours', value: 'lastTwentyFourHours', data: { startDate: subHours(CURRENT_DATE_AND_TIME, 24), isRelativeToNow: true, }, }, { label: 'Last three days', value: 'lastThreeDays', data: { startDate: subDays(startOfDay(CURRENT_DATE_AND_TIME), 2), }, }, { label: 'Last week', value: 'lastWeek', data: { startDate: subWeeks(startOfDay(CURRENT_DATE_AND_TIME), 1), }, }, ], } as BentoDateRangeFilterOptions, type: BentoFilterItemType.DATE_RANGE, visible: true, }, { field: 'granular-date-range', label: 'Granular date range', options: { allowTimeInput: true, numberOfMonths: 2, granularities: [{ type: 'daily' }, { type: 'weekly' }, { type: 'monthly', disabled: true }], min: subWeeks(startOfWeek(CURRENT_DATE_AND_TIME, { weekStartsOn: 1 }), 12), max: endOfWeek(CURRENT_DATE_AND_TIME, { weekStartsOn: 1 }), } as BentoDateRangeFilterOptions, type: BentoFilterItemType.DATE_RANGE, visible: true, }, { field: 'date-range-month', label: 'Date range (month)', options: { numberOfMonths: 2, variant: 'month', } as BentoDateRangeFilterOptions, type: BentoFilterItemType.DATE_RANGE, visible: true, }, { field: 'paymentMethod', label: 'Payment method', options: { listboxItems: [ { label: 'iDEAL', value: 'ideal' }, { label: 'Visa', value: 'visa' }, ], multiple: true, }, type: BentoFilterItemType.SELECT_PAYMENT_METHOD, }, { field: 'tag', label: 'Tag', options: { listboxItems: [ { label: 'Offline', value: 'unique-id-1', variant: 'red' }, { label: 'Online', value: 'unique-id-2', variant: 'green' }, { label: 'Standby', value: 'unique-id-3', variant: 'orange' }, { label: 'Busy', value: 'unique-id-4', variant: 'blue' }, ], multiple: true, }, type: BentoFilterItemType.SELECT_TAG, }, { field: 'radio-group', label: 'Radio group', options: { items: [ { label: 'Option 1', value: 'option-1' }, { label: 'Option 2', value: 'option-2' }, { label: 'Option 3', value: 'option-3' }, ], }, visible: false, type: BentoFilterItemType.RADIO_GROUP, }, { field: 'checkbox-group', label: 'Checkbox group', options: { checkboxItems: [ { label: 'Item 1', value: 'unique-id-1' }, { label: 'Item 2', value: 'unique-id-2' }, { label: 'Item 3', value: 'unique-id-3' }, { label: 'Item with a very long name', value: 'unique-id-4' }, ], }, type: BentoFilterItemType.CHECKBOX_GROUP, visible: true, }, { field: 'toggle', label: 'Toggle', type: BentoFilterItemType.TOGGLE, visible: true, description: 'Optional description for the toggle filter', options: { tooltipPosition: 'bottom', tooltipText: 'Toggle filter tooltip text', }, }, ]; const FILTERS_WITH_DEFAULTS: BentoFilterBarModel = [ { field: 'input', label: 'Input filter', defaultValue: 'hello', type: BentoFilterItemType.INPUT, }, { field: 'input-with-dropdown', label: 'Input with dropdown filter', type: BentoFilterItemType.INPUT_DROPDOWN, visible: true, defaultValue: { inputValue: 'Peristent input filter', dropdownValue: 'EUR', }, options: { dropdown: { items: [ { label: 'EUR', value: 'EUR' }, { label: 'USD', value: 'USD' }, ], }, }, }, { field: 'range', label: 'Range', defaultValue: ['1', '4'], type: BentoFilterItemType.RANGE, }, { field: 'merchant', label: 'Merchants', defaultValue: ['merchant_1'], options: { listboxItems: [ { label: 'Merchant 1', value: 'merchant_1', }, { label: 'Merchant 2', value: 'merchant_2', }, { label: 'Merchant 3', value: 'merchant_3', }, ], multiple: true, enableValueLabelPair: true, }, type: BentoFilterItemType.SELECT, }, { field: 'thing', label: 'Things', defaultValue: 'merchant_1', options: { listboxItems: [ { label: 'Thing 1', value: 'merchant_1', }, { label: 'Thing 2', value: 'merchant_2', }, { label: 'Thing 3', value: 'merchant_3', }, ], }, type: BentoFilterItemType.SELECT, }, { field: 'country', label: 'Country/Region', options: { listboxItems: [ { label: 'Option 1', value: BentoCountryCode.NL, }, { label: 'Option 2', value: BentoCountryCode.BE, }, ], multiple: true, variant: 'capital', }, type: BentoFilterItemType.SELECT_COUNTRY, }, { field: 'radio-group', label: 'Radio group', defaultValue: 'option-1', options: { items: [ { label: 'Option 1', value: 'option-1' }, { label: 'Option 2', value: 'option-2' }, { label: 'Option 3', value: 'option-3' }, ], }, visible: false, type: BentoFilterItemType.RADIO_GROUP, }, { field: 'checkbox-group', label: 'Checkbox group', defaultValue: ['unique-id-1'], options: { checkboxItems: [ { label: 'Item 1', value: 'unique-id-1' }, { label: 'Item 2', value: 'unique-id-2' }, { label: 'Item 3', value: 'unique-id-3' }, { label: 'Item 4', value: 'unique-id-4' }, ], }, type: BentoFilterItemType.CHECKBOX_GROUP, }, ]; const defaultFilterTemplate = storybookDocsParameter(` <template> <bento-filter-bar :config="config" :filter-values="filterValues" :search-term.sync="searchTerm" @input="onFilter" @search-input="onSearchInput" @search-clear="onSearchClear" /> </template> <script setup lang="ts"> import { BentoFilterBar, BentoFilterItemType } from '@adyen/bento-vue2' import type { BentoFilterBarModel, BentoFilterValues } from '@adyen/bento-vue2' const searchTerm = ref<string>(undefined) const config = [{ field: 'name', label: 'Name', type: BentoFilterItemType.INPUT, visible: true, }, { field: 'merchant', label: 'Merchant', options: { listboxItems: DEFAULT_OPTIONS, }, type: BentoFilterItemType.SELECT, }, { field: 'company', label: 'Company', options: { listboxItems: DEFAULT_OPTIONS, isOptionDisabled: (option) => option.value === 5, multiple: true, }, type: BentoFilterItemType.SELECT, }] const filterValues = ref<BentoFilterValues>([{ field: 'merchant', value: 2, }, { field: 'company', value: [1, 3, 5], }]) const onFilter = (newFilters: BentoFilterBarModel) => { } const onSearchInput = (newTerm: string) => { } const onSearchClear = () => { } </script> `); export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoFilterBar }, props: Object.keys(argTypes), setup(props) { const args = isVue2 ? props : _args; const filtersConfig = ref<BentoFilterBarModel>(args.config); const filterValues = ref<BentoFilterValues>(args.filterValues); const filterValuesUpdate = (newFilterValues: BentoFilterValues) => { filterValues.value = newFilterValues; action('update:filter-values')(newFilterValues); }; const filterValuesChanged = (newFilterValues: BentoFilterValues) => { action('input')(newFilterValues); }; const searchTermField = computed(() => args.searchTerm); const searchInput = (newSearchTerm: string) => action('search-input')(newSearchTerm); const searchClear = () => action('search-clear')(); const searchTermSync = (newSearchTerm: string) => action('update:search-term')(newSearchTerm); return { // Props args, // Values filtersConfig, filterValues, searchTermField, // Events filterValuesChanged, filterValuesUpdate, searchInput, searchClear, searchTermSync, }; }, template: ` <div style="padding-top: 24px"> <bento-filter-bar v-bind="args" :config="filtersConfig" :searchTerm="searchTermField" :filter-values="filterValues" @input="filterValuesChanged" @update:filter-values="filterValuesUpdate" @update:search-term="searchTermSync" @search-input="searchInput" @search-clear="searchClear" /> </div> `, }), args: { config: FILTERS, filterValues: FILTER_VALUES, containerSizeLayout: { medium: 'multi-line', small: 'one-line', } satisfies BentoFilterBarContainerSizeLayout, search: { inputFieldAriaLabel: 'Input field aria label', debounceTime: 500, hint: 'Hint', placeholder: 'Placeholder content', } satisfies BentoFilterBarSearchBarProps, searchTerm: 'something', }, parameters: defaultFilterTemplate, }; const persistentFilterTemplate = storybookDocsParameter(` <bento-filter-bar :config="[ { field: 'input', label: 'Input filter', defaultValue: 'hello', type: BentoFilterItemType.INPUT, }, { field: 'range', label: 'Range', defaultValue: ['1', '4'], type: BentoFilterItemType.RANGE, }, { field: 'merchant', label: 'Merchants', defaultValue: ['merchant_1'], options: { listboxItems: [ { label: 'Merchant 1', value: 'merchant_1', }, { label: 'Merchant 2', value: 'merchant_2', }, { label: 'Merchant 3', value: 'merchant_3', }, ], multiple: true, }, type: BentoFilterItemType.SELECT, }, { field: 'thing', label: 'Things', defaultValue: 'merchant_1', options: { listboxItems: [ { label: 'Thing 1', value: 'merchant_1', }, { label: 'Thing 2', value: 'merchant_2', }, { label: 'Thing 3', value: 'merchant_3', }, ], }, type: BentoFilterItemType.SELECT, }, { field: 'country', label: 'Country/Region', options: { listboxItems: [ { label: 'Option 1', value: BentoCountryCode.NL, }, { label: 'Option 2', value: BentoCountryCode.BE, }, ], multiple: true, variant: 'capital', }, type: BentoFilterItemType.SELECT_COUNTRY, }, ]" :filter-values="[ { field: 'input', value: 'non-default value', }, ]" :search="{ inputFieldAriaLabel: 'Input field aria label', debounceTime: 500, hint: 'Hint', placeholder: 'Placeholder content', }" :search-term.sync="searchTerm" @input="onFilter" @search-input="onSearchInput" @search-clear="onSearchClear" /> `); export const Persistent: Story = { ...Default, args: { config: FILTERS_WITH_DEFAULTS, filterValues: [ { field: 'input', value: 'non-default value', }, { field: 'checkbox-group', value: ['unique-id-2'], }, ], searchTerm: null, }, parameters: persistentFilterTemplate, }; const externalFilteringTemplate = storybookDocsParameter(` <template> <bento-filter-bar :config="filtersConfig" :filter-values.sync="filterValues" /> </template> <script setup lang="ts"> import { ref, computed, onMounted } from 'vue'; import { BentoFilterBar, BentoFilterItemType, BentoFilterBarModel, BentoFilterValues, BentoSelectFilterOptions, } from '@adyen/bento-vue2'; // --- Mock API for demonstration --- const getPokemonFromApi = async (searchTerm?: string) => { // In a real application, this would be an API call. // We're mocking it with a timeout and static data. console.log(\`Searching for: \${searchTerm}\`); await new Promise(resolve => setTimeout(resolve, 1000)); const allPokemon = [ { label: 'Bulbasaur', value: 'bulbasaur' }, { label: 'Ivysaur', value: 'ivysaur' }, { label: 'Venusaur', value: 'venusaur' }, { label: 'Charmander', value: 'charmander' }, { label: 'Charmeleon', value: 'charmeleon' }, { label: 'Charizard', value: 'charizard' }, ]; const items = searchTerm ? allPokemon.filter(p => p.label.toLowerCase().includes(searchTerm.toLowerCase())) : allPokemon; return { items }; }; // --- End Mock API --- const filterValues = ref<BentoFilterValues>([{ field: 'pokemon', value: 'bulbasaur' }]); const apiItems = ref([]); const isLoading = ref(false); const getAndSetPokemonFromApi = async (val?: string) => { isLoading.value = true; const { items } = await getPokemonFromApi(val); apiItems.value = items; isLoading.value = false; }; onMounted(() => { getAndSetPokemonFromApi(); }); const filtersConfig = computed<BentoFilterBarModel>(() => [ { field: 'name', label: 'Name', type: BentoFilterItemType.INPUT }, { field: 'pokemon', label: 'Pokemon', options: { isOptionDisabled: option => ['ivysaur', 'charmeleon'].includes(option.value as string), listboxItems: apiItems.value, search: { debounceTime: 500, searchEvent: getAndSetPokemonFromApi }, loading: isLoading.value, }, type: BentoFilterItemType.SELECT, }, ]); </script> `); export const ExternalFilteringSelectFilter: Story = { render: (_args, { argTypes }) => ({ components: { BentoFilterBar, }, props: Object.keys(argTypes), setup(props) { const apiItems = ref([]); const filterValues = ref<BentoFilterValues>([{ field: 'pokemon', value: 'bulbasaur' }]); const isLoading = ref(false); const getAndSetPokemonFromApi = async (val?: string) => { isLoading.value = true; await new Promise(resolve => setTimeout(resolve, 1000)); const { items } = await getPokemonFromApi(val); apiItems.value = items; isLoading.value = false; }; const search = { debounceTime: 500, searchEvent: getAndSetPokemonFromApi, }; onMounted(() => { getAndSetPokemonFromApi(); }); const filtersConfig = computed(() => [ { field: 'name', label: 'Name', type: BentoFilterItemType.INPUT, }, { field: 'pokemon', label: 'Pokemon', options: { isOptionDisabled: option => ['ivysaur', 'charmeleon'].includes(option.value as string), listboxItems: apiItems.value, search, loading: isLoading.value, }, type: BentoFilterItemType.SELECT, visible: false, }, ]); return { args: isVue2 ? props : _args, // Values filtersConfig, filterValues, }; }, template: ` <div style="padding-top: 24px"> <bento-filter-bar v-bind="args" :config="filtersConfig" :filter-values.sync="filterValues" /> </div> `, }), args: { config: FILTERS, searchTerm: null }, parameters: externalFilteringTemplate, }; const SearchAndLazyLoadTemplate = storybookDocsParameter(` <template> <bento-filter-bar :config="filtersConfig" :filter-values.sync="filterValues" /> </template> <script setup lang="ts"> import { ref, computed, onMounted } from 'vue'; import { BentoFilterBar, BentoFilterItemType, BentoFilterBarModel, BentoFilterValues, BentoSelectFilterOptions, BentoListboxOptions, } from '@adyen/bento-vue2'; // --- Mock API for demonstration --- const ALL_ITEMS = Array.from({ length: 100 }, (_, i) => ({ label: \`Item \${i + 1}\`, value: i + 1, })); const fetchItems = (searchTerm: string, page: number, pageSize: number = 20) => { console.log(\`Fetching items with searchTerm: "\${searchTerm}", page: \${page}\`); return new Promise<{ items: BentoListboxOptions; hasMore: boolean }>(resolve => { setTimeout(() => { const filtered = ALL_ITEMS.filter(item => item.label.toLowerCase().includes(searchTerm.toLowerCase()) ); const start = (page - 1) * pageSize; const end = start + pageSize; const pageItems = filtered.slice(start, end); resolve({ items: pageItems, hasMore: end < filtered.length, }); }, 500); }); }; // --- End Mock API --- const filterValues = ref<BentoFilterValues>([]); // State for the select filter const listboxItems = ref<BentoListboxOptions>([]); const isLoading = ref(false); const hasMoreItems = ref(true); const currentPage = ref(1); const currentSearchTerm = ref(''); /** * Handles the search event from the filter. * This should reset pagination and fetch a new list of items. */ const onSearch = async (searchTerm: string) => { currentSearchTerm.value = searchTerm; currentPage.value = 1; isLoading.value = true; const { items, hasMore } = await fetchItems(searchTerm, currentPage.value); listboxItems.value = items; hasMoreItems.value = hasMore; isLoading.value = false; }; /** * Handles the lazy load event. * This should fetch the next page of items and append them. */ const onLazyLoad = async () => { if (isLoading.value || !hasMoreItems.value) { return; } currentPage.value++; isLoading.value = true; const { items, hasMore } = await fetchItems(currentSearchTerm.value, currentPage.value); // Append new items to the existing list listboxItems.value = [...listboxItems.value, ...items]; hasMoreItems.value = hasMore; isLoading.value = false; }; // Load initial data when the component mounts onMounted(() => { onSearch(''); }); const filtersConfig = computed<BentoFilterBarModel>(() => [ { field: 'lazy-select', label: 'Lazy Load Select', type: BentoFilterItemType.SELECT, options: { listboxItems: listboxItems.value, multiple: true, // External filtering configuration search: { searchEvent: onSearch, }, // Lazy loading configuration lazy: { hasMoreItems: hasMoreItems.value, lazyLoadType: 'automatic', // or 'button' loading: isLoading.value, showMoreEvent: onLazyLoad, }, }, }, ]); </script> `); export const ExternalFilteringAndLazyLoadingSelectFilter: Story = { ...ExternalFilteringSelectFilter, render: (_args, { argTypes }) => ({ components: { BentoFilterBar, }, props: Object.keys(argTypes), setup(props) { const args = isVue2 ? props : _args; const filterValues = ref<BentoFilterValues>([]); const lazyLoadingPageSize = 20; // State for the select filter const listboxItems = ref<BentoListboxOptions>([]); const isSearchLoading = ref(false); const isLazyLoading = ref(false); const hasMoreItems = ref(true); const currentPage = ref(1); const currentSearchTerm = ref(''); /** * Handles the search event from the filter. * This should reset pagination and fetch a new list of items. */ const onSearch = async (searchTerm: string) => { currentSearchTerm.value = searchTerm; currentPage.value = 1; isSearchLoading.value = true; const { items, hasMore } = await getPokemonFromApi(searchTerm, currentPage.value, lazyLoadingPageSize); listboxItems.value = items; hasMoreItems.value = hasMore; isSearchLoading.value = false; }; /** * Handles the lazy load event. * This should fetch the next page of items and append them. */ const onLazyLoad = async () => { if (isLazyLoading.value || !hasMoreItems.value) { return; } currentPage.value++; isLazyLoading.value = true; const { items, hasMore } = await getPokemonFromApi( currentSearchTerm.value, currentPage.value, lazyLoadingPageSize ); // Append new items to the existing list listboxItems.value = [...listboxItems.value, ...items]; hasMoreItems.value = hasMore; isLazyLoading.value = false; }; // Load initial data when the component mounts onMounted(() => { onSearch(''); }); const filtersConfig = computed<BentoFilterBarModel>(() => [ { field: 'lazy-select', label: 'External filtering and lazy loading', type: BentoFilterItemType.SELECT, options: { listboxItems: listboxItems.value, multiple: true, loading: isSearchLoading.value, // External filtering configuration search: { searchEvent: onSearch, }, // Lazy loading configuration lazy: { hasMoreItems: hasMoreItems.value, lazyLoadType: 'automatic', // or 'button' loading: isLazyLoading.value, showMoreEvent: onLazyLoad, }, }, }, ]); return { args, filtersConfig, filterValues, }; }, template: ` <div style="padding-top: 24px"> <bento-filter-bar v-bind="args" :config="filtersConfig" :filter-values.sync="filterValues" /> </div> `, }), args: { ...ExternalFilteringSelectFilter.args, }, parameters: SearchAndLazyLoadTemplate, }; export const CustomFilters: Story = { render: (_args, { argTypes }) => ({ components: { BentoFilterBar, BentoToggle, FilterBarCustomFilterSubComponentSingleValueExample, FilterBarCustomFilterSubComponentMultiValueExample, }, props: Object.keys(argTypes), template: ` <div style="padding-top: 24px"> <bento-filter-bar :config="filtersConfig" :filter-values-object="filterValues" @update:filter-values-object="filterValuesChanged" /> </div> `, setup() { interface CustomFilterOptions { required?: boolean; optional?: boolean; disabled?: boolean; } const filtersConfig: BentoFilterBarModel<CustomFilterOptions> = [ { field: 'input', label: 'Input filter', type: BentoFilterItemType.INPUT, options: { placeholder: 'Input filter placeholder', }, visible: true, }, { field: 'custom-single', label: 'Custom single value filter', type: FilterBarCustomFilterSubComponentSingleValueExample, visible: true, }, { field: 'custom-multi', label: 'Custom multi value filter', type: FilterBarCustomFilterSubComponentMultiValueExample, options: { // ICustomFilterOptions disabled: false, optional: false, required: false, }, visible: false, }, { field: 'custom-popover', label: 'Custom filter with popover', type: FilterBarCustomFilterSubComponentWithPopoverExample, visible: true, }, ]; /** * Custom filter values */ type FilterKeys = 'input' | 'custom-single' | 'custom-multi' | 'custom-popover'; type CustomFilterValues = | BentoFilterBarValue | boolean | { // ICustomFilterValues first: boolean; second: boolean; } | undefined; const filterValues = ref<BentoFilterValueObject<FilterKeys, CustomFilterValues>>({ input: undefined, 'custom-single': undefined, 'custom-multi': { first: false, second: false, }, 'custom-popover': undefined, }); const filterValuesChanged = (newFilterValues: BentoFilterValueObject<FilterKeys, CustomFilterValues>) => { filterValues.value = newFilterValues; action('input')(newFilterValues); }; return { filtersConfig, filterValues, filterValuesChanged, }; }, }), args: false, parameters: storybookDocsParameter(FilterBarCustomFiltersExample), };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <div ref="filterBarRef" class="b-filter-bar"> <div v-if="!showSearchbarInsideContainer && isSearchBarEnabled" class="b-filter-bar__search-external-container"> <bento-search-bar class="b-filter-bar__search-bar" v-bind="searchBarProps" :input-field-aria-label="searchBarAriaLabel" :value="searchTerm" condensed @input="onSearchbarInput" @clear="onSearchbarClear" /> </div> <component :is="screenLayoutComponent" :class="containerConditionalClasses" condensed> <div :class="contentConditionalClasses"> <div v-if="showSearchbarInsideContainer && isSearchBarEnabled"> <bento-search-bar class="b-filter-bar__search-bar" v-bind="searchBarProps" :input-field-aria-label="searchBarAriaLabel" :value="searchTerm" condensed @input="onSearchbarInput" @clear="onSearchbarClear" /> </div> <template v-if="persistentFilters.length"> <component :is="filter.type" v-for="filter in persistentFilters" :key="filter.field" :field="filter.field" :label="filter.label" :disabled="filter?.disabled" :options="filter.options" :value="filter.value" :default-value="filter.defaultValue" @update="updateFilterHandler" /> </template> <component :is="filter.type" v-for="filter in regularFilters" :key="filter.field" :field="filter.field" :label="filter.label" :disabled="filter?.disabled" :options="filter.options" :value="filter.value" @update="updateFilterHandler" /> <!-- All filters button --> <all-filters-modal v-if="hasHiddenFilters" class="b-filter-bar__all-filters-modal" :value="valueAndFilterValuesPropsMerged" @input="updateAllFilters" /> <!-- Clear all filters button --> <filter-bar-button v-if="isClearAllVisible" class="b-filter-bar__clear-all" @click="clearAllFilters"> {{ t('clearFilters') }} <template #iconLeft> <refresh-icon aria-hidden="true" /> </template> </filter-bar-button> <!-- Reset all filters button --> <filter-bar-button v-if="isResetAllVisible" class="b-filter-bar__reset-all" @click="resetAllFilters"> {{ t('resetFilters') }} <template #iconLeft> <refresh-icon aria-hidden="true" /> </template> </filter-bar-button> </div> </component> </div> </template> <script setup lang="ts" generic=""> import { computed, ref, toRef } from 'vue'; import { FilterBarButton } from './components/filter-bar-button'; import { BentoFilterBarEvent, type BentoFilterBarModel, type BentoFilterBarProps, type BentoFilterModel, } from './filter-bar.types'; import BentoSearchBar from '@/components/search-bar/search-bar.vue'; import { BentoSearchBarEvent } from '@/components/search-bar/search-bar.types'; import { useBentoToastController } from '@/composables'; import { useI18n } from '@/utils/ts/i18n'; import { useContainerSizeOptionsLayout } from './composables/use-container-size-options-layout/use-container-size-options-layout'; import AllFiltersModal from './components/all-filters-modal/all-filters-modal.vue'; import RefreshIcon from '@adyen/ui-assets-icons-16/vue/refresh'; import messages from './messages.json'; type MessageSchema = (typeof messages)['en-US']; // TODO: setup Vue3 generics in Bento 2.0.0 const props = withDefaults(defineProps<BentoFilterBarProps>(), { config: () => [], filterValues: () => [], containerSizeLayout: () => ({ medium: 'one-line', small: 'multi-line', }), search: undefined, searchTerm: undefined, value: () => [], showAppliedHiddenFilters: false, filterValuesObject: undefined, }); const emit = defineEmits([ BentoFilterBarEvent.INPUT, BentoFilterBarEvent.UPDATE_FILTER_VALUES_OBJECT, BentoFilterBarEvent.UPDATE_FILTER_VALUES_ARRAY, BentoSearchBarEvent.CLEAR, BentoSearchBarEvent.INPUT, BentoSearchBarEvent.UPDATE, ]); const { t } = useI18n<{ message: MessageSchema }>({ messages }); const filterBarRef = ref<HTMLDivElement>(null); const valueAndFilterValuesPropsMerged = computed<BentoFilterBarModel>(() => (props.config?.length > 0 ? props.config : props.value)?.map(filterModel => { let filterValue = props.filterValues.find(({ field }) => field === filterModel.field)?.value; if (props.filterValuesObject && filterModel.field in props.filterValuesObject) { filterValue = props.filterValuesObject[filterModel.field]; } if (filterValue === undefined) { // If the filter is using the deprecated `config` value prop use that // But if that is also not set then check if a default value exists set it to that if it is defined. filterValue = filterModel?.value === undefined && filterModel?.defaultValue !== undefined ? filterModel?.defaultValue : filterModel?.value; } return { ...filterModel, value: filterValue !== undefined ? filterValue : filterModel?.value }; }) ); const { addToast } = useBentoToastController(); /** * Container size layout. * Allows to change the layout from multi-line to one-line for each container size type: * - large: >=700 * - medium: >=430 & <700 * - small: <430 */ const { screenLayoutComponent, showSearchbarInsideContainer, isOneLineLayout } = useContainerSizeOptionsLayout( filterBarRef, toRef(props, 'containerSizeLayout') ); /** * Classes */ /** * Handles the classes of the swappable component ('div' or 'fixed-scroller) */ const containerConditionalClasses = computed(() => ({ 'b-filter-bar__fixed-scroller': isOneLineLayout.value, 'b-filter-bar__container': !isOneLineLayout.value, // Multi-line })); /** * Handles the classes of the container inside the swappable component. */ const contentConditionalClasses = computed(() => ({ 'b-filter-bar__scroller-content': isOneLineLayout.value, 'b-filter-bar__filters': !isOneLineLayout.value, // Multi-line })); /** * Search bar pops */ const searchBarAriaLabel = computed(() => props.search?.inputFieldAriaLabel || (t('searchBar') as string)); const searchBarProps = computed(() => { if (!props.search) { return undefined; } const { debounceTime, disabled, inputFieldAriaLabel, hint, placeholder } = props.search; return { debounceTime, disabled, inputFieldAriaLabel, hint, placeholder }; }); const hasHiddenFilters = computed(() => { // Has props with visible set const hasVisibleSet = valueAndFilterValuesPropsMerged.value.some( ({ visible }) => visible !== undefined && visible !== null ); return hasVisibleSet ? valueAndFilterValuesPropsMerged.value.some(({ visible }) => !visible) : false; }); // Persistent filters i.e. filters with default value const persistentFilters = computed(() => valueAndFilterValuesPropsMerged.value.filter(({ defaultValue }) => defaultValue !== undefined) ); // Show non-persistent & visible filters const regularFilters = computed(() => { const hasVisibleSet = valueAndFilterValuesPropsMerged.value.some( ({ visible }) => visible !== undefined && visible !== null ); // If no filter has the `visible` prop set then all are visible, otherwise search for the "visible: true" elements return hasVisibleSet ? valueAndFilterValuesPropsMerged.value.filter( ({ defaultValue, visible, value }) => // eslint-disable-next-line eqeqeq (defaultValue === undefined && visible) || (props.showAppliedHiddenFilters && value != undefined) ) : valueAndFilterValuesPropsMerged.value.filter( ({ defaultValue, value }) => // eslint-disable-next-line eqeqeq defaultValue === undefined || (props.showAppliedHiddenFilters && value != undefined) ); }); // Enable search bar. // Soft equality to check for "null" and "undefined" // eslint-disable-next-line eqeqeq const isSearchBarEnabled = computed(() => props.searchTerm != undefined); const hasActiveFilters = computed(() => valueAndFilterValuesPropsMerged.value .filter(({ defaultValue }) => defaultValue === undefined || defaultValue === null) // Disabling strict equality to check for both undefined and null // eslint-disable-next-line eqeqeq .some(({ value }) => value != undefined) ); // Show the "Clear all" button (true) if at least one filter is active (contains a value) // and there are not persistent filters present. const isClearAllVisible = computed(() => persistentFilters.value.length === 0 && hasActiveFilters.value); const isResetAllVisible = computed(() => { if (persistentFilters.value.length === 0) { return false; } const areAllValuesEqualToDefaults = valueAndFilterValuesPropsMerged.value .filter(({ defaultValue }) => defaultValue !== undefined || defaultValue !== null) .every(({ value, defaultValue }) => JSON.stringify(value) === JSON.stringify(defaultValue)); return !areAllValuesEqualToDefaults || hasActiveFilters.value; }); const updateAllFilters = (newFilters: BentoFilterBarModel) => { emit(BentoFilterBarEvent.INPUT, newFilters); emit( BentoFilterBarEvent.UPDATE_FILTER_VALUES_ARRAY, newFilters ?.filter(({ value }) => value !== null || value !== undefined) .map(({ field, value }) => ({ field, value })) ); emit( BentoFilterBarEvent.UPDATE_FILTER_VALUES_OBJECT, newFilters ?.filter(({ value }) => value !== null || value !== undefined) .reduce((acc, filter) => { acc[filter.field] = filter.value; return acc; }, {}) ); }; const updateFilterHandler = (updatedFilter: BentoFilterModel) => { const filterIndex = valueAndFilterValuesPropsMerged.value.findIndex( filter => filter.field === updatedFilter.field ); const newFilters = valueAndFilterValuesPropsMerged.value.slice(); newFilters[filterIndex].value = updatedFilter.value; updateAllFilters(newFilters); }; const clearAllFilters = () => { const previousFilters = valueAndFilterValuesPropsMerged.value.slice(); const clearedFilters = valueAndFilterValuesPropsMerged.value.map(({ value, ...otherFields }) => ({ ...otherFields, value: undefined, })); addToast({ text: t('filtersHaveBeenCleared') as string, action: { handler: () => updateAllFilters(previousFilters), text: t('undo') as string, }, }); updateAllFilters(clearedFilters); }; const resetAllFilters = () => { const previousFilters = valueAndFilterValuesPropsMerged.value.slice(); const resetFilters = valueAndFilterValuesPropsMerged.value.map(({ defaultValue, ...otherFields }) => ({ ...otherFields, defaultValue, value: defaultValue, })); addToast({ text: t('filtersHaveBeenReset') as string, action: { handler: () => updateAllFilters(previousFilters), text: t('undo') as string, }, }); updateAllFilters(resetFilters); }; const onSearchbarInput = (newSearchTerm: string) => { emit(BentoSearchBarEvent.INPUT, newSearchTerm); emit(BentoSearchBarEvent.UPDATE, newSearchTerm); }; const onSearchbarClear = () => { emit(BentoSearchBarEvent.CLEAR); }; // Expose methods for BentoDataGrid to access defineExpose({ isClearAllVisible, clearAllFilters, isResetAllVisible, resetAllFilters }); </script> <script lang="ts"> import { BentoBooleanFilter, BentoCheckboxGroupFilter, BentoDateFilter, BentoDateRangeFilter, BentoInputFilter, BentoInputWithDropdownFilter, BentoRadioGroupFilter, BentoRangeFilter, BentoSelectAvatarFilter, BentoSelectCountryFilter, BentoSelectCurrencyFilter, BentoSelectFilter, BentoSelectPaymentMethodFilter, BentoSelectTagFilter, BentoToggleFilter, } from './components'; import { BentoTeleport, FixedScroller } from '@/internal'; /** * The filter bar filters item lists and tables according to various filter criteria. * * @example * import { BentoFilterBar } from '@adyen/bento-vue2' * * export default { * components: { BentoFilterBar }, * data: () => ({ * filters: [{ * field: 'name', * label: 'Name', * value: null, * options: { ... }, * type: FilterType.TEXT, * }] * }), * template: ` * <bento-filter-bar * v-model="filter" * /> * ` * } */ export default { i18n: { messages }, name: 'bento-filter-bar', components: { BentoBooleanFilter, BentoCheckboxGroupFilter, BentoDateFilter, BentoDateRangeFilter, BentoInputFilter, BentoInputWithDropdownFilter, BentoRadioGroupFilter, BentoRangeFilter, BentoSelectAvatarFilter, BentoSelectCountryFilter, BentoSelectCurrencyFilter, BentoSelectFilter, BentoSelectPaymentMethodFilter, BentoSelectTagFilter, BentoTeleport, BentoToggleFilter, FixedScroller, }, }; </script> <style lang="scss" scoped src="./filter-bar.scss" />
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Canvas, Story, Meta } from '@storybook/blocks';
|
|
2
|
+
import * as ComponentStories from './focus-trap.stories';
|
|
3
|
+
|
|
4
|
+
<Meta of={ComponentStories} />
|
|
5
|
+
|
|
6
|
+
# FocusTrap
|
|
7
|
+
|
|
8
|
+
A FocusTrap represents a component. Use a FocusTrap when you want users to...
|
|
9
|
+
|
|
10
|
+
<Canvas of={ComponentStories.Default} />
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import { BentoButton } from '@/components/button'; import { BentoCard } from '@/components/card'; import { BentoFocusTrapArrowNavigationDirection } from './focus-trap.types'; import { BentoFocusTrap } from './index'; export default { title: 'Focus trap', component: BentoFocusTrap, argTypes: { enableArrowNavigation: { name: 'Enable arrow navigation', control: 'boolean', description: 'Arrow navigation', }, arrowNavigation: { options: Object.values(BentoFocusTrapArrowNavigationDirection), mapping: BentoFocusTrapArrowNavigationDirection, control: { type: 'inline-radio', }, }, default: { name: 'default', defaultValue: '', description: 'Trapped focus content', table: { type: { summary: 'VNode[]' }, }, control: { type: null }, }, }, }; export const Default = (_args, { argTypes }) => ({ components: { BentoFocusTrap, BentoButton, BentoCard }, props: Object.keys(argTypes), data: () => ({ isOpened: false, }), computed: { arrowNavigationValue() { return this.enableArrowNavigation && this.arrowNavigation; }, }, template: ` <div> <bento-button @click="isOpened = true">Open content</bento-button> <div style="margin-top: 24px"> <bento-focus-trap v-if="isOpened" :arrow-navigation="arrowNavigationValue" :disabled="disabled"> <bento-card> Focus will be trapped inside this card <template #actions> <bento-button @click="isOpened = false">Close</bento-button> </template> <div> <bento-button>Button 1</bento-button> <bento-button>Button 2</bento-button> <bento-button>Button 3</bento-button> <bento-button>Button 4</bento-button> <div v-if="arrowNavigationValue"> <span contenteditable tabindex="0"> Arrow navigation doesn't work when element is content editable </span> </div> </div> </bento-card> </bento-focus-trap> </div> </div> `, });
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <div v-if="hasSlot('default')" ref="root" class="b-focus-trap" data-testid="focus-trap" tabindex="-1" v-on="arrowNavigation ? { keydown: onKeyDown } : {}" @focusout="onBlur" @keydown.esc="handleKeydownEscape" > <slot></slot> </div> </template> <script lang="ts" setup> import { computed, type PropType, ref, toRef, useSlots } from 'vue'; import { useHasSlot } from '@/composables'; import { useHandleLayers } from './composables/use-handle-layers'; import { type BentoFocusTrapOptions, useTrapAll } from './composables/use-trap-all'; import { useTrapArrowKeys } from './composables/use-trap-arrow-keys'; import { BentoFocusTrapArrowNavigationDirection } from './focus-trap.types'; const props = defineProps({ /** * Enables / Disables arrow navigation as well can determine the type of arrow navigation. * The types of arrow navigation are horizontal and vertical. */ arrowNavigation: { type: String as PropType<BentoFocusTrapArrowNavigationDirection>, default: undefined, validator: (value: BentoFocusTrapArrowNavigationDirection) => Object.values(BentoFocusTrapArrowNavigationDirection).includes(value), }, /** * Enables / disables the focus trap. */ disabled: { type: Boolean, default: false, }, /** * Enables / disables the focus trap for all keys. */ trapAll: { type: Boolean, default: false, }, /** * Configurations for the focus-trap when trap-all is active. */ trapAllOptions: { type: Object as PropType<BentoFocusTrapOptions>, default: undefined, }, }); const root = ref<HTMLElement | null>(null); const slots = useSlots(); const hasSlot = useHasSlot(slots); const activateTrapAll = computed(() => { return props.trapAll && !props.disabled && !props.arrowNavigation; }); const activateTrapArrowKeysOnly = computed(() => { return !props.disabled && !props.trapAll; }); useTrapAll(root, activateTrapAll, toRef(props, 'trapAllOptions')); const { handleKeydownEscape } = useHandleLayers(root); const { onKeyDown, onBlur } = useTrapArrowKeys(root, activateTrapArrowKeysOnly, props.arrowNavigation); </script> <script lang="ts"> /** * Locks the focus inside an element for a11y navigation. * Useful for things like modals, popovers, etc... * * @example * import { BentoFocusTrap, BentoButton } from '@adyen/bento-vue2'; * * export default { * components: { BentoFocusTrap, BentoButton }, * template: ` * <div id="import-choice"> * <bento-focus-trap> * Make a choice to proceed further! * <bento-button>Yes</bento-button> * <bento-button>No</bento-button> * </bento-focus-trap> * </div> * ` * }; */ export default { name: 'bento-focus-trap', }; </script> <style lang="scss" scoped> .b-focus-trap { outline: none; } </style>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <div class="b-form-layout-field-tip"> <slot /> <bento-alert v-if="hasSlot('message')" :id="id" variant="tip" :type="type"> <template #description> <slot name="message" /> </template> </bento-alert> </div> </template> <script setup lang="ts"> import { type PropType, useSlots } from 'vue'; import { type BentoFormLayoutFieldTipTypeOption, BentoFormLayoutFieldTipTypeOptions, } from './form-layout-tip.types'; import { BentoAlert } from '@/components/alert'; import { useHasSlot } from '@/composables/use-has-slot/use-has-slot'; defineProps({ /** * Type of a alert; set a distinctive icon and color. Defaults to highlight. * @values highlight, success, critical, warning */ type: { type: String as PropType<BentoFormLayoutFieldTipTypeOption>, default: 'highlight', validator: (value: BentoFormLayoutFieldTipTypeOption) => BentoFormLayoutFieldTipTypeOptions.includes(value), }, /** * Unique identifier to the form field. Used for accessibility. */ id: { type: String, default: undefined, }, }); const slots = useSlots(); const hasSlot = useHasSlot(slots); </script> <script lang="ts"> /** * You can help users provide accurate and well formatted information * by using various elements that come with an input field. * * Use description/supporting text to add additional context to the * field or guidance how to format the input correctly. * Because description is always visible it should be the first * choice in providing guidance. * * @example * import { BentoFormLayoutFieldTip } from '@adyen/bento-vue2'; * * export default { * components: { BentoFormLayoutFieldTip }, * template: ` * <bento-form-layout-field-tip> * <bento-dropdown label="Dropdown" :items="dropdownItems" placeholder="Select an animal" /> * * <template #message>This is a small tip that can be used in small spaces</template> * </bento-form-layout-field-tip> * ` */ export default {}; </script> <style lang="scss" scoped src="./form-layout-field-tip.scss" />
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <div class="b-form-layout-group" :class="conditionalClasses"> <slot></slot> </div> </template> <script setup lang="ts"> import { computed, type PropType } from 'vue'; import { BentoLayoutGroupProportions } from './form-layout-group.types'; // Default proportion const DEFAULT_PROPORTION = 100; const props = defineProps({ /** * The layout of the pair group of components. * This will render the components within the width of the proportions. * Only two colums are available. * * If only one value is entered, then it will assume there's only one child in the group. * If two values are entered, the first value will be use for the first child and the second * value for the second child. * * If the value entered is not listed in the example * list below it will use 100% by default. * @example * '33-33-33' * '50-50' * '25-75' * '75-25' * '33-66' * '66-33' * '25' * '33' * '50' * '100' */ layout: { type: String as PropType<BentoLayoutGroupProportions>, default: '100', validator: (value: BentoLayoutGroupProportions) => BentoLayoutGroupProportions.includes(value), }, }); // Sets DEFAULT_PROPORTION by default if `layout` is not in the list const computedLayout = computed(() => BentoLayoutGroupProportions.includes(props.layout) ? props.layout : DEFAULT_PROPORTION ); const conditionalClasses = computed(() => ({ // Transform into column if the layout is unsupported 'b-form-layout-group--column': !BentoLayoutGroupProportions.includes(props.layout), // Assings the correct layout class [`b-form-layout-group--${computedLayout.value}`]: true, })); </script> <script lang="ts"> /** * Use to provide horizontal layouts to build forms * with two fields per line. * * These can be used for field combinations, * such as post code and city or title and first name. * * @example * import { BentoFormLayoutGroup } from '@adyen/bento-vue2'; * * export default { * components: { BentoFormLayoutGroup }, * template: ` * <bento-form-layout-group> * <first-element /> * <second-element /> * </bento-form-layout-group> * ` * } */ export default {}; </script> <style lang="scss" scoped src="./form-layout-group.scss" />
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<template> <div class="b-form-layout-title" :class="conditionalClasses"> <bento-typography :el="el" variant="title" :medium="isSection"> <slot /> </bento-typography> <bento-typography v-if="hasSlot('description')" class="b-form-layout-title__description" el="span" wide> <slot name="description" /> </bento-typography> </div> </template> <script setup lang="ts"> import { computed, useSlots } from 'vue'; import { BentoTypography, BentoTypographyElement } from '@/components/typography'; import { useHasSlot } from '@/composables'; const slots = useSlots(); const props = defineProps({ /** * Defines if the Form Layout title is * a "section" or a "subsection". * Default is "section" (false). Turn into "true" for "subsection" */ subsection: { type: Boolean, default: false, }, /** * Sets the HTML DOM element. * @values h1, h2, h3, h4, h5, h6, div, p, span */ el: { type: BentoTypography.props.el.type, required: true, validator: (value: BentoTypographyElement) => Object.values(BentoTypographyElement).includes(value), }, }); // Uses "medium" typography if it is a section title const isSection = computed(() => !props.subsection); const hasSlot = useHasSlot(slots); const conditionalClasses = computed(() => ({ 'b-form-layout-title--subsection': props.subsection, })); </script> <script lang="ts"> /** * If you’re building a lengthy form, organise related * fields into sections and, if needed, subsections. * * A form can have a "Section Title" and multiple "Subsection titles". * * One of the values provided in @see BentoTypographyElement must always be provided as a string * to define correct HTML sementic from the application's context * * @example * import { BentoFormLayoutTitle } from '@adyen/bento-vue2'; * * export default { * components: { BentoFormLayoutTitle }, * template: ` * <bento-form-layout-title subsection el="h2"> * Title * <template #description>Description text</template> * </bento-form-layout-title> * ` * } */ export default {}; </script> <style lang="scss" scoped src="./form-layout-title.scss" />
|
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
import { Canvas, Meta, Source } from '@storybook/blocks';
|
|
2
|
+
import * as FormLayoutStories from './form-layout.stories';
|
|
3
|
+
|
|
4
|
+
<Meta of={FormLayoutStories} />
|
|
5
|
+
|
|
6
|
+
# Form layout
|
|
7
|
+
|
|
8
|
+
The `bento-form-layout` component is a pre-designed layout that serves as a starting point for creating a form.
|
|
9
|
+
|
|
10
|
+
Form layout is context agnostic and can be dropped in different places, for example in modals, side panels or pages. For
|
|
11
|
+
page-specific guidance, such as multi-page forms and how to place a form in a page.
|
|
12
|
+
|
|
13
|
+
## Table of contents
|
|
14
|
+
|
|
15
|
+
- [Use Cases](#use-cases)
|
|
16
|
+
- [Do not use](#do-not-use)
|
|
17
|
+
- [Variations](#variations)
|
|
18
|
+
- [Sections and sub sections](#sections-and-sub-sections)
|
|
19
|
+
- [Section title](#section-title)
|
|
20
|
+
- [Subsection title](#subsection-title)
|
|
21
|
+
- [Modifiers](#modifiers)
|
|
22
|
+
- [Render as an HTML "form"](#render-as-an-html-form)
|
|
23
|
+
- [Field with tip](#field-with-tip)
|
|
24
|
+
- [Type](#type)
|
|
25
|
+
- [Group](#group)
|
|
26
|
+
- [Dynamic default slot content](#dynamic-default-slot-content)
|
|
27
|
+
- [Form actions](#form-actions)
|
|
28
|
+
- [Loading](#loading)
|
|
29
|
+
- [Validation](#validation)
|
|
30
|
+
- [Common use cases](#common-use-cases)
|
|
31
|
+
- [Field depends on another field or external reactive data](#field-depends-on-another-field-or-external-reactive-data)
|
|
32
|
+
- [Error message depends on external reactive data](#error-message-depends-on-external-reactive-data)
|
|
33
|
+
- [Migrating from vue 2 to vue 3](#migrating-from-vue-2-to-vue-3)
|
|
34
|
+
|
|
35
|
+
<Canvas of={FormLayoutStories.Default} />
|
|
36
|
+
|
|
37
|
+
## Use Cases
|
|
38
|
+
|
|
39
|
+
Use this component as a base for all kinds of forms or configuration flows that use form components. The form layout can
|
|
40
|
+
be placed within pages, modals, side panels and so on.
|
|
41
|
+
|
|
42
|
+
### Do not use
|
|
43
|
+
|
|
44
|
+
If you need just one or two input fields that sit within a bigger component, it's better to use those components
|
|
45
|
+
directly, as spacing here is optimised for larger forms/configuration flows.
|
|
46
|
+
|
|
47
|
+
## Variations
|
|
48
|
+
|
|
49
|
+
### Sections and sub sections
|
|
50
|
+
|
|
51
|
+
If you're building a lengthy form, organise related fields into sections and, if needed, subsections. This adds
|
|
52
|
+
additional context and makes filling it out easier.
|
|
53
|
+
|
|
54
|
+
An element (`el`) of type string corresponding to one of the values contained in `BentoTypographyElement` must always be
|
|
55
|
+
provided to define correct HTML sementic from the application's context.
|
|
56
|
+
|
|
57
|
+
#### Section title
|
|
58
|
+
|
|
59
|
+
<Source
|
|
60
|
+
dark
|
|
61
|
+
language="html"
|
|
62
|
+
code={`
|
|
63
|
+
<BentoFormLayoutTitle el="h1">
|
|
64
|
+
Section title
|
|
65
|
+
<template #description>
|
|
66
|
+
Optional section description
|
|
67
|
+
</template>
|
|
68
|
+
</BentoFormLayoutTitle>
|
|
69
|
+
`}
|
|
70
|
+
/>
|
|
71
|
+
|
|
72
|
+
#### Subsection title
|
|
73
|
+
|
|
74
|
+
<Source
|
|
75
|
+
dark
|
|
76
|
+
language="html"
|
|
77
|
+
code={`
|
|
78
|
+
<BentoFormLayoutTitle el="h2" subsection>
|
|
79
|
+
Subsection title
|
|
80
|
+
<template #description>
|
|
81
|
+
Optional section description
|
|
82
|
+
</template>
|
|
83
|
+
</BentoFormLayoutTitle>
|
|
84
|
+
`}
|
|
85
|
+
/>
|
|
86
|
+
|
|
87
|
+
## Modifiers
|
|
88
|
+
|
|
89
|
+
### Render as an HTML "form"
|
|
90
|
+
|
|
91
|
+
When rendering an HTML `form` element is necessary, the Form Layout provides a `form` property that will render an HTML
|
|
92
|
+
`form` instead:
|
|
93
|
+
|
|
94
|
+
<Source
|
|
95
|
+
dark
|
|
96
|
+
language="html"
|
|
97
|
+
code={`
|
|
98
|
+
<BentoFormLayout form>
|
|
99
|
+
... Form Content
|
|
100
|
+
</BentoFormLayout>
|
|
101
|
+
`}
|
|
102
|
+
/>
|
|
103
|
+
|
|
104
|
+
### Field with tip
|
|
105
|
+
|
|
106
|
+
You can help users provide accurate and well formatted information by using various elements that come with an input
|
|
107
|
+
field.
|
|
108
|
+
|
|
109
|
+
Use description/supporting text to add additional context to the field or guidance how to format the input correctly.
|
|
110
|
+
Because description is always visible it should be the first choice in providing guidance.
|
|
111
|
+
|
|
112
|
+
To make sure the field and tip are correctly linked for accessibility purposes. Pass an `id` to
|
|
113
|
+
`bento-form-layout-field-tip` and set `aria-describedby="id"` in your field, as shown in the example below.
|
|
114
|
+
|
|
115
|
+
<Canvas of={FormLayoutStories.WithTip} />
|
|
116
|
+
|
|
117
|
+
#### Type
|
|
118
|
+
|
|
119
|
+
Determines the type of tip that is displayed. By setting the `type` property, the component will render a corresponding
|
|
120
|
+
visual representation of the tip type with predefined icon and background.
|
|
121
|
+
|
|
122
|
+
It accepts one of the following values, that correspond to the alert's `type` prop:
|
|
123
|
+
|
|
124
|
+
- `'success'`
|
|
125
|
+
- `'highlight'`
|
|
126
|
+
- `'warning'`
|
|
127
|
+
- `'critical'`
|
|
128
|
+
|
|
129
|
+
### Group
|
|
130
|
+
|
|
131
|
+
<Canvas of={FormLayoutStories.Group} />
|
|
132
|
+
|
|
133
|
+
Use to provide horizontal layouts to build forms with two or three fields per line. These can be used for field
|
|
134
|
+
combinations, such as post code and city or title and first name.
|
|
135
|
+
|
|
136
|
+
- `25-75`
|
|
137
|
+
- `33-66`
|
|
138
|
+
- `50-50`
|
|
139
|
+
- `66-33`
|
|
140
|
+
- `75-25`
|
|
141
|
+
- `33-33-33`
|
|
142
|
+
|
|
143
|
+
There are also some shorter field options, such as 25%, 33% and 50% of layout's width. These can be used for cases when
|
|
144
|
+
a single field is needed, but its expected length of input is short, and where a full width field would look awkward.
|
|
145
|
+
|
|
146
|
+
- `25`
|
|
147
|
+
- `50`
|
|
148
|
+
- `75`
|
|
149
|
+
|
|
150
|
+
#### Dynamic default slot content
|
|
151
|
+
|
|
152
|
+
The <code>bento-form-layout-group</code> is not a dynamic component and does not support changing its content/default
|
|
153
|
+
slot. If this functionality is needed:
|
|
154
|
+
|
|
155
|
+
- set the <code>v-if</code> statement <b>on</b> the component itself
|
|
156
|
+
|
|
157
|
+
<Source
|
|
158
|
+
dark
|
|
159
|
+
language="html"
|
|
160
|
+
code={`
|
|
161
|
+
<bento-form-layout-group v-if="condition" layout="33-33-33">
|
|
162
|
+
<bento-input-field>Input field 1</bento-input-field>
|
|
163
|
+
<bento-input-field>Input field 2</bento-input-field>
|
|
164
|
+
<bento-input-field>Input field 3</bento-input-field>
|
|
165
|
+
</bento-form-layout-group>
|
|
166
|
+
`}
|
|
167
|
+
/>
|
|
168
|
+
|
|
169
|
+
- or around/outside it
|
|
170
|
+
|
|
171
|
+
<Source
|
|
172
|
+
dark
|
|
173
|
+
language="html"
|
|
174
|
+
code={`
|
|
175
|
+
<template v-if="condition">
|
|
176
|
+
<bento-form-layout-group layout="33-33-33">
|
|
177
|
+
<bento-input-field>Input field 1</bento-input-field>
|
|
178
|
+
<bento-input-field>Input field 2</bento-input-field>
|
|
179
|
+
<bento-input-field>Input field 3</bento-input-field>
|
|
180
|
+
</bento-form-layout-group>
|
|
181
|
+
</template>
|
|
182
|
+
`}
|
|
183
|
+
/>
|
|
184
|
+
|
|
185
|
+
### Form actions
|
|
186
|
+
|
|
187
|
+
The form provides two slots for actions:
|
|
188
|
+
|
|
189
|
+
* `actions-left`
|
|
190
|
+
* `actions-right`
|
|
191
|
+
|
|
192
|
+
Form actions can be added by either using the `bento-button` or `bento-loading-button` components.
|
|
193
|
+
|
|
194
|
+
### Loading
|
|
195
|
+
|
|
196
|
+
If a specific form often takes a bit longer to process, it's possible to create a loading state.
|
|
197
|
+
|
|
198
|
+
Use the `bento-loading-button` component as primary action and change the state to `loading`.
|
|
199
|
+
|
|
200
|
+
All form field components inside `bento-form-layout` are changed to `readonly` mode when the prop `loading` and set as
|
|
201
|
+
`true` is passed to `bento-form-layout`
|
|
202
|
+
|
|
203
|
+
<Canvas of={FormLayoutStories.LoadingForm} />
|
|
204
|
+
|
|
205
|
+
## Validation
|
|
206
|
+
|
|
207
|
+
> **IMPORTANT** When errors from the validation appear:
|
|
208
|
+
>
|
|
209
|
+
> - focus must be _manually_ set by the developer using the `bento-form-layout` component to the first field in error so
|
|
210
|
+
> that the error is announced when the field is focused.
|
|
211
|
+
> - errors should be listed or summarised in the alert so that assistive technologies can identify them
|
|
212
|
+
|
|
213
|
+
<Canvas of={FormLayoutStories.Validate} />
|
|
214
|
+
|
|
215
|
+
Given that Vee-Validate v4 has no Vue2 support, Bento provides a _composable_ that helps with **migration** from Vue2 to
|
|
216
|
+
Vue3 that mimics a basic version of
|
|
217
|
+
[Vee-Validate v4](https://vee-validate.logaretm.com/v4/guide/composition-api/typed-schema#zod) with
|
|
218
|
+
[Zod](https://zod.dev/).
|
|
219
|
+
|
|
220
|
+
> **IMPORTANT** This is only meant to be used as a migration helper, once in Vue3 replace the dependency for
|
|
221
|
+
> [`vee-validate`](https://vee-validate.logaretm.com/v4/integrations/zod-schema-validation/). If already in Vue3
|
|
222
|
+
> `vee-validate` should be preferred over using Bento composable as it is much more feature rich & will have better
|
|
223
|
+
> support.
|
|
224
|
+
|
|
225
|
+
Bento exposes the `useForm` composable which uses a Zod/v4 schema to validate the data of a form.
|
|
226
|
+
|
|
227
|
+
This is an over-simplified version of Vee-Validate v4 implemented with Zod to validate a form schema. It returns
|
|
228
|
+
reactive values that can be used directly in form input elements with `v-model` alongside with functions to "reset" the
|
|
229
|
+
data (sets values to default value) and a function to execute the successful form validation process (through a
|
|
230
|
+
callback).
|
|
231
|
+
|
|
232
|
+
<Source
|
|
233
|
+
dark
|
|
234
|
+
language="typescript"
|
|
235
|
+
code={`
|
|
236
|
+
import { z } from 'zod/v4'
|
|
237
|
+
import { useForm, toTypedSchema } from '@adyen/bento-vue2/use-form-v4';
|
|
238
|
+
const { values, errors, submitForm, handleReset } = useForm({
|
|
239
|
+
validationSchema: toTypedSchema(
|
|
240
|
+
z.object({
|
|
241
|
+
// Schema here
|
|
242
|
+
})
|
|
243
|
+
)
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
const handleSubmit = submitForm(() => { Submit code here }) `} />
|
|
247
|
+
|
|
248
|
+
- `values`: Provides a reactive values object that allows to set/get the values used in the form fields:
|
|
249
|
+
`values.<FIELD_NAME>`. Can be used directly with `v-model` in components.
|
|
250
|
+
- `errors`: Provides an errors object generated through zod validation of the values object against the schema.
|
|
251
|
+
- Contains the first error of each one of the fields.
|
|
252
|
+
- Interface: `{ [key: string]: string }`, where key is the name of the field that has errors.
|
|
253
|
+
- `errorBag`: Provides an object generated through `zod` validation of the `values` object against the schema.
|
|
254
|
+
- Contains all the errors found for each one of the fields.
|
|
255
|
+
- Interface: ` { [key: string]: Array<string> }`, where `key` is the name of the field that has errors and
|
|
256
|
+
`Array<string>` is the list of all the errors found after validating the values against the schema. The first
|
|
257
|
+
element of this array will always be the one shown in the `errors` object.
|
|
258
|
+
- `handleReset`: Resets the form values to default (initial values) when it gets called.
|
|
259
|
+
- `submitForm`: Provides a function that verifies the data.
|
|
260
|
+
- The callback passed receives all the values of the form as parameter.
|
|
261
|
+
- If no errors were found: Executes the callback function so the user can execute further processes on the form
|
|
262
|
+
data.
|
|
263
|
+
- If errors were found: The `errors` and `errorBag` objects are filled with the respective errors, the callback is
|
|
264
|
+
NOT called.
|
|
265
|
+
|
|
266
|
+
### Common use cases
|
|
267
|
+
|
|
268
|
+
#### Field depends on another field or external reactive data
|
|
269
|
+
|
|
270
|
+
Use `refine` ([docs](https://zod.dev/?id=refine)) or `superRefine` ([docs](https://zod.dev/?id=superrefine)):
|
|
271
|
+
|
|
272
|
+
<Source
|
|
273
|
+
dark
|
|
274
|
+
language="typescript"
|
|
275
|
+
code={`
|
|
276
|
+
import { z } from 'zod/v4'
|
|
277
|
+
import { useForm, toTypedSchema } from '@adyen/bento-vue2/use-form-v4';
|
|
278
|
+
const { values, errors, submitForm, handleReset } = useForm({
|
|
279
|
+
validationSchema: toTypedSchema(
|
|
280
|
+
z.object({
|
|
281
|
+
name: z.string(),
|
|
282
|
+
lastName: z.string()
|
|
283
|
+
}).refine(data => data.name === data.lastName, {
|
|
284
|
+
message: "Name and Last name can't be identical",
|
|
285
|
+
path: ['role'] // Indicate which field is invalid
|
|
286
|
+
})
|
|
287
|
+
)
|
|
288
|
+
});`}
|
|
289
|
+
/>
|
|
290
|
+
|
|
291
|
+
#### Error message depends on external reactive data
|
|
292
|
+
|
|
293
|
+
With `Zod/v3` (**deprecated**) use `errorMap`
|
|
294
|
+
([docs](https://zod.dev/ERROR_HANDLING?id=customizing-errors-with-zoderrormap)):
|
|
295
|
+
|
|
296
|
+
<Source
|
|
297
|
+
dark
|
|
298
|
+
language="typescript"
|
|
299
|
+
code={`
|
|
300
|
+
import { z } from 'zod/v3'
|
|
301
|
+
import { useForm, toTypedSchema } from '@adyen/bento-vue2';
|
|
302
|
+
const externalValue = computed(() => { ... });
|
|
303
|
+
const { values, errors, submitForm, handleReset } = useForm({
|
|
304
|
+
validationSchema: toTypedSchema(
|
|
305
|
+
z.object({
|
|
306
|
+
percentage: z
|
|
307
|
+
.string({
|
|
308
|
+
errorMap: (data, ctx) => {
|
|
309
|
+
switch (data.code) {
|
|
310
|
+
case ZodIssueCode.too_big:
|
|
311
|
+
return { message: \`Field needs to be greater than value \`\${externalValue.value\}\` };
|
|
312
|
+
case ZodIssueCode.too_small:
|
|
313
|
+
case ZodIssueCode.invalid_type:
|
|
314
|
+
return { message: 'Value must be positive' };
|
|
315
|
+
default:
|
|
316
|
+
return {
|
|
317
|
+
message: 'Default error',
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
})
|
|
322
|
+
.nonnegative()
|
|
323
|
+
.lte(100),
|
|
324
|
+
})
|
|
325
|
+
)
|
|
326
|
+
});`}
|
|
327
|
+
/>
|
|
328
|
+
|
|
329
|
+
With `Zod/v4` use `error` ([docs](https://zod.dev/v4/changelog?id=drops-errormap)):
|
|
330
|
+
|
|
331
|
+
<Source
|
|
332
|
+
dark
|
|
333
|
+
language="typescript"
|
|
334
|
+
code={`
|
|
335
|
+
import { z } from 'zod/v4'
|
|
336
|
+
import { useForm, toTypedSchema } from '@adyen/bento-vue2/use-form-v4';
|
|
337
|
+
const externalValue = computed(() => { ... });
|
|
338
|
+
const { values, errors, submitForm, handleReset } = useForm({
|
|
339
|
+
validationSchema: toTypedSchema(
|
|
340
|
+
z.object({
|
|
341
|
+
percentage: z
|
|
342
|
+
.string({
|
|
343
|
+
error: issue => 'Default error';
|
|
344
|
+
})
|
|
345
|
+
.nonnegative({
|
|
346
|
+
error: issue => 'Value must be positive'
|
|
347
|
+
})
|
|
348
|
+
.lte(100, {
|
|
349
|
+
error: issue => \`Field needs to be greater than value \`\${externalValue.value\}\`
|
|
350
|
+
}),
|
|
351
|
+
})
|
|
352
|
+
)
|
|
353
|
+
});`}
|
|
354
|
+
/>
|
|
355
|
+
|
|
356
|
+
### Migrating from vue 2 to vue 3
|
|
357
|
+
|
|
358
|
+
It's recommended that you should use the
|
|
359
|
+
[`vee-validate`](https://vee-validate.logaretm.com/v4/integrations/zod-schema-validation/) library instead when working
|
|
360
|
+
in Vue 3.
|
|
361
|
+
|
|
362
|
+
Simply replace the import of `useForm` to `vee-validate` and the import of `toTypedSchema` to `@vee-validate/zod`
|
|
363
|
+
|
|
364
|
+
Vue2
|
|
365
|
+
|
|
366
|
+
<Source
|
|
367
|
+
dark
|
|
368
|
+
language="typescript"
|
|
369
|
+
code={`
|
|
370
|
+
import { useForm, toTypedSchema } from '@adyen/bento-vue2/use-form-v4';
|
|
371
|
+
import { z } from 'zod/v4'
|
|
372
|
+
const { values, errors, submitForm } = useForm({
|
|
373
|
+
validationSchema: toTypedSchema(
|
|
374
|
+
z.object({
|
|
375
|
+
name: z
|
|
376
|
+
.string({
|
|
377
|
+
error: issue =>
|
|
378
|
+
issue.input === undefined
|
|
379
|
+
? 'This field is required'
|
|
380
|
+
: 'Not a string',
|
|
381
|
+
})
|
|
382
|
+
.min(3, {
|
|
383
|
+
error: issue => {
|
|
384
|
+
if (issue.code === 'too_small') {
|
|
385
|
+
return \`Value must be >\${issue.minimum}\`
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
})
|
|
389
|
+
.startsWith('Jane')
|
|
390
|
+
.max(4, {
|
|
391
|
+
error: issue => {
|
|
392
|
+
if (issue.code === 'too_big') {
|
|
393
|
+
return \`Value must be >\${issue.maximum}\`
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
})
|
|
397
|
+
.default('Joe'),
|
|
398
|
+
})
|
|
399
|
+
)
|
|
400
|
+
});
|
|
401
|
+
const handleSubmit = submitForm(() => {
|
|
402
|
+
// Your code here
|
|
403
|
+
});
|
|
404
|
+
`}
|
|
405
|
+
/>
|
|
406
|
+
|
|
407
|
+
to Vue 3 using `vee-validate`
|
|
408
|
+
|
|
409
|
+
<Source
|
|
410
|
+
dark
|
|
411
|
+
language="typescript"
|
|
412
|
+
code={`import { useForm, useField } from 'vee-validate';
|
|
413
|
+
import { toTypedSchema } from '@vee-validate/zod';
|
|
414
|
+
import { z } from 'zod/v4';
|
|
415
|
+
const { values, errors, submitForm } = useForm({
|
|
416
|
+
validationSchema: toTypedSchema(
|
|
417
|
+
z.object({
|
|
418
|
+
name: z
|
|
419
|
+
.string({
|
|
420
|
+
error: issue =>
|
|
421
|
+
issue.input === undefined
|
|
422
|
+
? 'This field is required'
|
|
423
|
+
: 'Not a string',
|
|
424
|
+
})
|
|
425
|
+
.min(3, {
|
|
426
|
+
error: issue => {
|
|
427
|
+
if (issue.code === 'too_small') {
|
|
428
|
+
return \`Value must be >\${issue.minimum}\`
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
})
|
|
432
|
+
.startsWith('Jane')
|
|
433
|
+
.max(4, {
|
|
434
|
+
error: issue => {
|
|
435
|
+
if (issue.code === 'too_big') {
|
|
436
|
+
return \`Value must be >\${issue.maximum}\`
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
})
|
|
440
|
+
.default('Joe'),
|
|
441
|
+
})
|
|
442
|
+
),
|
|
443
|
+
});
|
|
444
|
+
const { value: name } = useField('name');
|
|
445
|
+
const handleSubmit = submitForm(() => {
|
|
446
|
+
// Your code here
|
|
447
|
+
});`}
|
|
448
|
+
/>
|
|
449
|
+
|
|
450
|
+
## Resources
|
|
451
|
+
|
|
452
|
+
- [Figma link](https://www.figma.com/file/uLabwF3243jdMDsNSP7U9I/Bento---Components?node-id=18381%3A219174)
|