@adyen/bento-mcp 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/README.md +24 -2
- package/SKILL.md +37 -7
- package/dist/assets/components/accordion/accordion.stories.ts +1 -1
- package/dist/assets/components/accordion/components/accordion-item.stories.ts +1 -1
- package/dist/assets/components/accordion/components/accordion-item.vue +1 -1
- package/dist/assets/components/action-bar/action-bar.docs.mdx +10 -3
- package/dist/assets/components/action-bar/action-bar.internal.types.ts +1 -0
- package/dist/assets/components/action-bar/action-bar.stories.ts +1 -1
- package/dist/assets/components/action-bar/action-bar.types.ts +1 -1
- package/dist/assets/components/action-bar/action-bar.vue +1 -1
- package/dist/assets/components/ai-tag/ai-tag.stories.ts +1 -1
- package/dist/assets/components/ai-tag/ai-tag.vue +1 -1
- package/dist/assets/components/alert/alert.stories.ts +1 -1
- package/dist/assets/components/alert/alert.types.ts +1 -1
- package/dist/assets/components/anchor-scroller/anchor-scroller.stories.ts +1 -1
- package/dist/assets/components/anchor-scroller/anchor-scroller.vue +1 -1
- package/dist/assets/components/avatar/avatar.internal.types.ts +1 -0
- package/dist/assets/components/avatar/avatar.stories.ts +1 -1
- package/dist/assets/components/avatar/avatar.types.ts +1 -1
- package/dist/assets/components/button/button.docs.mdx +4 -3
- package/dist/assets/components/button/button.stories.ts +1 -1
- package/dist/assets/components/button/button.types.ts +1 -1
- package/dist/assets/components/button/components/ai-button/ai-button.stories.ts +1 -1
- package/dist/assets/components/button/components/ai-button/ai-button.vue +1 -1
- package/dist/assets/components/button/components/button-actions/button-actions.docs.mdx +3 -0
- package/dist/assets/components/button/components/button-actions/button-actions.internal.types.ts +1 -0
- package/dist/assets/components/button/components/button-actions/button-actions.stories.ts +1 -1
- package/dist/assets/components/button/components/button-actions/button-actions.types.ts +1 -1
- package/dist/assets/components/button/components/button-actions/button-actions.vue +1 -1
- package/dist/assets/components/button/components/loading-button/loading-button.docs.mdx +26 -23
- package/dist/assets/components/button/components/loading-button/loading-button.stories.ts +1 -1
- package/dist/assets/components/button/components/loading-button/loading-button.vue +1 -1
- package/dist/assets/components/button/components/toggle-button/toggle-button.stories.ts +1 -1
- package/dist/assets/components/card/card.docs.mdx +18 -1
- package/dist/assets/components/card/card.stories.ts +1 -1
- package/dist/assets/components/card/card.types.ts +1 -1
- package/dist/assets/components/card/card.vue +1 -1
- package/dist/assets/components/checkbox/checkbox.stories.ts +1 -1
- package/dist/assets/components/checkbox/checkbox.types.ts +1 -1
- package/dist/assets/components/checkbox/checkbox.vue +1 -1
- package/dist/assets/components/checkbox/components/checkbox-group/checkbox-group.docs.mdx +9 -14
- package/dist/assets/components/checkbox/components/checkbox-group/checkbox-group.stories.ts +1 -1
- package/dist/assets/components/checkbox/components/checkbox-group/checkbox-group.types.ts +1 -1
- package/dist/assets/components/checkbox/components/checkbox-group/checkbox-group.vue +1 -1
- package/dist/assets/components/chip/chip.stories.ts +1 -1
- package/dist/assets/components/chip/chip.vue +1 -1
- package/dist/assets/components/click-outside/click-outside.stories.ts +1 -1
- package/dist/assets/components/code-snippet/code-snippet.types.ts +1 -1
- package/dist/assets/components/code-snippet/code-snippet.vue +1 -1
- package/dist/assets/components/country/country.stories.ts +1 -1
- package/dist/assets/components/country/country.types.ts +1 -1
- package/dist/assets/components/country/country.vue +1 -1
- package/dist/assets/components/currency/currency.stories.ts +1 -1
- package/dist/assets/components/currency/currency.vue +1 -1
- package/dist/assets/components/dashed-underline/dashed-underline.stories.ts +1 -1
- package/dist/assets/components/data-grid/components/data-grid-cell-actions/data-grid-cell-actions.types.ts +1 -1
- package/dist/assets/components/data-grid/components/data-grid-cell-actions/data-grid-cell-actions.vue +1 -1
- package/dist/assets/components/data-grid/components/data-grid-config-settings/data-grid-config-settings.vue +1 -1
- package/dist/assets/components/data-grid/components/data-grid-row/data-grid-row.vue +1 -1
- package/dist/assets/components/data-grid/components/data-grid-static-icon/data-grid-static-icon.stories.ts +1 -1
- package/dist/assets/components/data-grid/components/data-grid-summary-cell/data-grid-summary-cell.stories.ts +1 -1
- package/dist/assets/components/data-grid/components/data-grid-summary-cell/data-grid-summary-cell.vue +1 -1
- package/dist/assets/components/data-grid/components/data-grid-tag-with-text-cell/data-grid-tag-with-text-cell.stories.ts +1 -1
- package/dist/assets/components/data-grid/components/data-grid-trend-cell/data-grid-trend-cell.stories.ts +1 -1
- package/dist/assets/components/data-grid/components/data-grid-trend-cell/data-grid-trend-cell.vue +1 -1
- package/dist/assets/components/data-grid/data-grid.docs.mdx +3 -60
- package/dist/assets/components/data-grid/data-grid.stories.ts +1 -1
- package/dist/assets/components/data-grid/data-grid.vue +1 -1
- package/dist/assets/components/date/date.stories.ts +1 -1
- package/dist/assets/components/date-picker/date-picker.stories.ts +1 -1
- package/dist/assets/components/date-picker/date-picker.types.ts +1 -1
- package/dist/assets/components/date-picker/date-picker.vue +1 -1
- package/dist/assets/components/date-range-picker/components/date-range-picker-calendar/date-range-picker-calendar.vue +1 -1
- package/dist/assets/components/date-range-picker/components/date-time-input/date-time-input.types.ts +1 -1
- package/dist/assets/components/date-range-picker/components/date-time-input/date-time-input.vue +1 -1
- package/dist/assets/components/date-range-picker/date-range-picker.docs.mdx +31 -6
- package/dist/assets/components/date-range-picker/date-range-picker.stories.ts +1 -1
- package/dist/assets/components/date-range-picker/date-range-picker.types.ts +1 -1
- package/dist/assets/components/date-range-picker/date-range-picker.vue +1 -1
- package/dist/assets/components/date-time-picker/date-time-picker.stories.ts +1 -1
- package/dist/assets/components/date-time-picker/date-time-picker.types.ts +1 -1
- package/dist/assets/components/divider/divider.internal.types.ts +1 -0
- package/dist/assets/components/divider/divider.stories.ts +1 -1
- package/dist/assets/components/divider/divider.types.ts +1 -1
- package/dist/assets/components/divider/divider.vue +1 -1
- package/dist/assets/components/draggable/components/draggable-handle/draggable-handle.stories.ts +1 -1
- package/dist/assets/components/draggable/draggable.stories.ts +1 -1
- package/dist/assets/components/draggable/draggable.vue +1 -1
- package/dist/assets/components/drawer/drawer.stories.ts +1 -1
- package/dist/assets/components/dropdown/components/dropdown-base-textbox/dropdown-base-textbox.internal.types.ts +1 -0
- package/dist/assets/components/dropdown/components/dropdown-base-textbox/dropdown-base-textbox.types.ts +1 -1
- package/dist/assets/components/dropdown/components/dropdown-base-textbox/dropdown-base-textbox.vue +1 -1
- package/dist/assets/components/dropdown/components/dropdown-default-textbox/dropdown-default-textbox.vue +1 -1
- package/dist/assets/components/dropdown/components/dropdown-options-container/dropdown-options-container.vue +1 -1
- package/dist/assets/components/dropdown/components/dropdown-small-textbox/dropdown-small-textbox.vue +1 -1
- package/dist/assets/components/dropdown/components/variants/dropdown-avatar/dropdown-avatar.stories.ts +1 -1
- package/dist/assets/components/dropdown/components/variants/dropdown-avatar/dropdown-avatar.types.ts +1 -1
- package/dist/assets/components/dropdown/components/variants/dropdown-avatar/dropdown-avatar.vue +1 -1
- package/dist/assets/components/dropdown/components/variants/dropdown-country/dropdown-country.stories.ts +1 -1
- package/dist/assets/components/dropdown/components/variants/dropdown-country/dropdown-country.types.ts +1 -1
- package/dist/assets/components/dropdown/components/variants/dropdown-country/dropdown-country.vue +1 -1
- package/dist/assets/components/dropdown/components/variants/dropdown-currency/dropdown-currency.stories.ts +1 -1
- package/dist/assets/components/dropdown/components/variants/dropdown-currency/dropdown-currency.types.ts +1 -1
- package/dist/assets/components/dropdown/components/variants/dropdown-currency/dropdown-currency.vue +1 -1
- package/dist/assets/components/dropdown/components/variants/dropdown-payment-method/dropdown-payment-method.stories.ts +1 -1
- package/dist/assets/components/dropdown/components/variants/dropdown-payment-method/dropdown-payment-method.types.ts +1 -1
- package/dist/assets/components/dropdown/components/variants/dropdown-payment-method/dropdown-payment-method.vue +1 -1
- package/dist/assets/components/dropdown/components/variants/dropdown-tag/dropdown-tag.stories.ts +1 -1
- package/dist/assets/components/dropdown/components/variants/dropdown-tag/dropdown-tag.types.ts +1 -1
- package/dist/assets/components/dropdown/components/variants/dropdown-tag/dropdown-tag.vue +1 -1
- package/dist/assets/components/dropdown/dropdown.docs.mdx +4 -40
- package/dist/assets/components/dropdown/dropdown.internal.types.ts +1 -0
- package/dist/assets/components/dropdown/dropdown.stories.ts +1 -1
- package/dist/assets/components/dropdown/dropdown.types.ts +1 -1
- package/dist/assets/components/dropdown/dropdown.vue +1 -1
- package/dist/assets/components/empty-state/empty-state.stories.ts +1 -1
- package/dist/assets/components/empty-state/empty-state.types.ts +1 -1
- package/dist/assets/components/file-uploader/components/file-uploader-file-card/file-uploader-file-card.stories.ts +1 -1
- package/dist/assets/components/file-uploader/components/file-uploader-file-card/file-uploader-file-card.types.ts +1 -1
- package/dist/assets/components/file-uploader/components/file-uploader-file-card/file-uploader-file-card.vue +1 -1
- package/dist/assets/components/file-uploader/components/file-uploader-restrictions/file-uploader-restrictions.types.ts +1 -1
- package/dist/assets/components/file-uploader/components/file-uploader-restrictions/file-uploader-restrictions.vue +1 -1
- package/dist/assets/components/file-uploader/file-uploader.internal.types.ts +1 -0
- package/dist/assets/components/file-uploader/file-uploader.stories.ts +1 -1
- package/dist/assets/components/file-uploader/file-uploader.types.ts +1 -1
- package/dist/assets/components/file-uploader/file-uploader.vue +1 -1
- package/dist/assets/components/filter-bar/components/all-filters-modal/all-filters-modal.stories.ts +1 -1
- package/dist/assets/components/filter-bar/components/all-filters-modal/all-filters-modal.types.ts +1 -1
- package/dist/assets/components/filter-bar/components/all-filters-modal/all-filters-modal.vue +1 -1
- package/dist/assets/components/filter-bar/components/all-filters-modal/components/all-filters-modal-filter-selection/all-filters-modal-filter-selection.types.ts +1 -0
- package/dist/assets/components/filter-bar/components/all-filters-modal/components/all-filters-modal-filter-selection/all-filters-modal-filter-selection.vue +1 -0
- package/dist/assets/components/filter-bar/components/all-filters-modal/components/all-filters-modal-selected-filter/all-filters-modal-selected-filter.vue +1 -0
- package/dist/assets/components/filter-bar/components/base-filter/base-filter.internal.types.ts +1 -0
- package/dist/assets/components/filter-bar/components/base-filter/base-filter.types.ts +1 -1
- package/dist/assets/components/filter-bar/components/base-filter/base-filter.vue +1 -1
- package/dist/assets/components/filter-bar/components/boolean-filter/boolean-filter.vue +1 -1
- package/dist/assets/components/filter-bar/components/date-filter/date-filter.vue +1 -1
- package/dist/assets/components/filter-bar/components/date-range-filter/date-range-filter.vue +1 -1
- package/dist/assets/components/filter-bar/components/input-filter/input-filter.types.ts +1 -1
- package/dist/assets/components/filter-bar/components/input-filter/input-filter.vue +1 -1
- package/dist/assets/components/filter-bar/components/input-with-dropdown-filter/input-with-dropdown-filter.stories.ts +1 -1
- package/dist/assets/components/filter-bar/components/input-with-dropdown-filter/input-with-dropdown-filter.vue +1 -1
- package/dist/assets/components/filter-bar/components/select-filter/components/select-filter-button/select-filter-button.vue +1 -1
- package/dist/assets/components/filter-bar/components/select-filter/select-filter.vue +1 -1
- package/dist/assets/components/filter-bar/components/select-filter/variants/select-avatar-filter/select-avatar-filter.stories.ts +1 -1
- package/dist/assets/components/filter-bar/components/select-filter/variants/select-country-filter/select-country-filter.stories.ts +1 -1
- package/dist/assets/components/filter-bar/components/select-filter/variants/select-country-filter/select-country-filter.vue +1 -1
- package/dist/assets/components/filter-bar/components/toggle-filter/toggle-filter.vue +1 -1
- package/dist/assets/components/filter-bar/filter-bar.docs.mdx +4 -62
- package/dist/assets/components/filter-bar/filter-bar.stories.ts +1 -1
- package/dist/assets/components/filter-bar/filter-bar.types.ts +1 -1
- package/dist/assets/components/filter-bar/filter-bar.vue +1 -1
- package/dist/assets/components/form-layout/form-layout.docs.mdx +0 -23
- package/dist/assets/components/form-layout/form-layout.stories.ts +1 -1
- package/dist/assets/components/header/header.stories.ts +1 -1
- package/dist/assets/components/header/header.types.ts +1 -1
- package/dist/assets/components/header/header.vue +1 -1
- package/dist/assets/components/header-with-views/components/header-with-views-all-views/header-with-views-all-views.vue +1 -1
- package/dist/assets/components/header-with-views/components/header-with-views-item-edit-form/header-with-views-edit-form.types.ts +1 -1
- package/dist/assets/components/header-with-views/components/header-with-views-item-edit-form/header-with-views-edit-form.vue +1 -1
- package/dist/assets/components/header-with-views/header-with-views.internal.types.ts +1 -0
- package/dist/assets/components/header-with-views/header-with-views.stories.ts +1 -1
- package/dist/assets/components/header-with-views/header-with-views.types.ts +1 -1
- package/dist/assets/components/header-with-views/header-with-views.vue +1 -1
- package/dist/assets/components/image/image.stories.ts +1 -1
- package/dist/assets/components/info-icon/info-icon.stories.ts +1 -1
- package/dist/assets/components/input-field/input-field.docs.mdx +1 -13
- package/dist/assets/components/input-field/input-field.stories.ts +1 -1
- package/dist/assets/components/input-field/input-field.types.ts +1 -1
- package/dist/assets/components/input-field/input-field.vue +1 -1
- package/dist/assets/components/input-field-password/input-field-password.internal.types.ts +1 -0
- package/dist/assets/components/input-field-password/input-field-password.stories.ts +1 -1
- package/dist/assets/components/input-field-password/input-field-password.types.ts +1 -1
- package/dist/assets/components/input-field-password/input-field-password.vue +1 -1
- package/dist/assets/components/input-field-phone-number/input-field-phone-number.stories.ts +1 -1
- package/dist/assets/components/input-field-phone-number/input-field-phone-number.types.ts +1 -1
- package/dist/assets/components/input-field-phone-number/input-field-phone-number.vue +1 -1
- package/dist/assets/components/input-time/input-time.docs.mdx +17 -0
- package/dist/assets/components/input-time/input-time.stories.ts +1 -1
- package/dist/assets/components/input-time/input-time.types.ts +1 -1
- package/dist/assets/components/input-time/input-time.vue +1 -1
- package/dist/assets/components/inspector/inspector.stories.ts +1 -1
- package/dist/assets/components/inspector/inspector.vue +1 -1
- package/dist/assets/components/layout/components/layout-row/layout-row.stories.ts +1 -1
- package/dist/assets/components/layout/components/layout-row/layout-row.vue +1 -1
- package/dist/assets/components/layout/layout.docs.mdx +10 -0
- package/dist/assets/components/layout/layout.internal.types.ts +1 -0
- package/dist/assets/components/layout/layout.stories.ts +1 -1
- package/dist/assets/components/layout/layout.types.ts +1 -1
- package/dist/assets/components/link/link.stories.ts +1 -1
- package/dist/assets/components/link/link.types.ts +1 -1
- package/dist/assets/components/link/link.vue +1 -1
- package/dist/assets/components/list/list.stories.ts +1 -1
- package/dist/assets/components/loading-indicator/loading-indicator.stories.ts +1 -1
- package/dist/assets/components/menu/components/menu-item/menu-item.types.ts +1 -1
- package/dist/assets/components/menu/components/menu-item/menu-item.vue +1 -1
- package/dist/assets/components/menu/components/menu-item-list/menu-item-list.vue +1 -1
- package/dist/assets/components/menu/components/sub-menu-item-list/sub-menu-item-list.vue +1 -1
- package/dist/assets/components/menu/menu.docs.mdx +17 -7
- package/dist/assets/components/menu/menu.stories.ts +1 -1
- package/dist/assets/components/menu/menu.types.ts +1 -1
- package/dist/assets/components/menu/menu.vue +1 -1
- package/dist/assets/components/modal/components/base-modal/base-modal.vue +1 -1
- package/dist/assets/components/modal/components/modal-page/modal-page.vue +1 -1
- package/dist/assets/components/modal/modal.internal.types.ts +1 -0
- package/dist/assets/components/modal/modal.stories.ts +1 -1
- package/dist/assets/components/modal/modal.types.ts +1 -1
- package/dist/assets/components/modal/modal.vue +1 -1
- package/dist/assets/components/modal-fullscreen/components/modal-fullscreen-page.vue +1 -1
- package/dist/assets/components/modal-fullscreen/modal-fullscreen.stories.ts +1 -1
- package/dist/assets/components/modal-fullscreen/modal-fullscreen.vue +1 -1
- package/dist/assets/components/navigation-menu/components/navigation-menu-group/navigation-menu-group.internal.types.ts +1 -0
- package/dist/assets/components/navigation-menu/components/navigation-menu-group/navigation-menu-group.types.ts +1 -1
- package/dist/assets/components/navigation-menu/components/navigation-menu-group/navigation-menu-group.vue +1 -1
- package/dist/assets/components/navigation-menu/components/navigation-menu-item/navigation-menu-item.vue +1 -1
- package/dist/assets/components/navigation-menu/navigation-menu.internal.types.ts +1 -0
- package/dist/assets/components/navigation-menu/navigation-menu.stories.ts +1 -1
- package/dist/assets/components/navigation-menu/navigation-menu.types.ts +1 -1
- package/dist/assets/components/navigation-menu/navigation-menu.vue +1 -1
- package/dist/assets/components/pagination/components/pagination-context/pagination-context.vue +1 -1
- package/dist/assets/components/pagination/components/pagination-results-per-page/pagination-results-per-page.vue +1 -1
- package/dist/assets/components/pagination/pagination.stories.ts +1 -1
- package/dist/assets/components/pagination/pagination.types.ts +1 -1
- package/dist/assets/components/pagination/pagination.vue +1 -1
- package/dist/assets/components/payment-method/payment-method.stories.ts +1 -1
- package/dist/assets/components/payment-method/payment-method.types.ts +1 -1
- package/dist/assets/components/popover/popover.docs.mdx +32 -9
- package/dist/assets/components/popover/popover.stories.ts +1 -1
- package/dist/assets/components/popover/popover.types.ts +1 -1
- package/dist/assets/components/popover/popover.vue +1 -1
- package/dist/assets/components/popper-container/popper-container.types.ts +1 -1
- package/dist/assets/components/popper-container/popper-container.vue +1 -1
- package/dist/assets/components/progress-bar/progress-bar.docs.mdx +102 -0
- package/dist/assets/components/progress-bar/progress-bar.stories.ts +1 -0
- package/dist/assets/components/progress-bar/progress-bar.types.ts +1 -0
- package/dist/assets/components/progress-bar/progress-bar.vue +1 -0
- package/dist/assets/components/promo-banner/promo-banner.stories.ts +1 -1
- package/dist/assets/components/promo-banner/promo-banner.types.ts +1 -1
- package/dist/assets/components/promo-banner/promo-banner.vue +1 -1
- package/dist/assets/components/radio-group/components/radio-button/radio-button.vue +1 -1
- package/dist/assets/components/radio-group/radio-group.docs.mdx +1 -6
- package/dist/assets/components/radio-group/radio-group.stories.ts +1 -1
- package/dist/assets/components/radio-group/radio-group.types.ts +1 -1
- package/dist/assets/components/radio-group/radio-group.vue +1 -1
- package/dist/assets/components/rich-text-editor/rich-text-editor.internal.types.ts +1 -0
- package/dist/assets/components/rich-text-editor/rich-text-editor.stories.ts +1 -1
- package/dist/assets/components/rich-text-editor/rich-text-editor.types.ts +1 -1
- package/dist/assets/components/rich-text-editor/rich-text-editor.vue +1 -1
- package/dist/assets/components/search-bar/search-bar.stories.ts +1 -1
- package/dist/assets/components/search-bar/search-bar.vue +1 -1
- package/dist/assets/components/secondary-nav/components/secondary-nav-item/secondary-nav-item.vue +1 -1
- package/dist/assets/components/secondary-nav/secondary-nav.stories.ts +1 -1
- package/dist/assets/components/secondary-nav/secondary-nav.vue +1 -1
- package/dist/assets/components/segmented-control/components/segmented-control-button/segmented-control-button.vue +1 -1
- package/dist/assets/components/segmented-control/segmented-control.stories.ts +1 -1
- package/dist/assets/components/segmented-control/segmented-control.types.ts +1 -1
- package/dist/assets/components/segmented-control/segmented-control.vue +1 -1
- package/dist/assets/components/selection-card/components/selection-card-group/selection-card-group.docs.mdx +12 -13
- package/dist/assets/components/selection-card/components/selection-card-group/selection-card-group.stories.ts +1 -1
- package/dist/assets/components/selection-card/components/selection-card-group/selection-card-group.vue +1 -1
- package/dist/assets/components/selection-card/selection-card.stories.ts +1 -1
- package/dist/assets/components/selection-card/selection-card.types.ts +1 -1
- package/dist/assets/components/selection-card/selection-card.vue +1 -1
- package/dist/assets/components/sidepanel/sidepanel.docs.mdx +1 -1
- package/dist/assets/components/sidepanel/sidepanel.stories.ts +1 -1
- package/dist/assets/components/sidepanel/sidepanel.vue +1 -1
- package/dist/assets/components/status/status.stories.ts +1 -1
- package/dist/assets/components/status/status.types.ts +1 -1
- package/dist/assets/components/stepper/components/step/step.vue +1 -1
- package/dist/assets/components/stepper/stepper.stories.ts +1 -1
- package/dist/assets/components/stepper/stepper.vue +1 -1
- package/dist/assets/components/structured-list/structured-list.stories.ts +1 -1
- package/dist/assets/components/structured-list/structured-list.vue +1 -1
- package/dist/assets/components/summary-grid/components/summary-grid-item/summary-grid-item.types.ts +1 -1
- package/dist/assets/components/summary-grid/summary-grid.stories.ts +1 -1
- package/dist/assets/components/summary-grid/summary-grid.types.ts +1 -1
- package/dist/assets/components/table-of-contents/components/table-of-contents-list/table-of-contents-list.vue +1 -1
- package/dist/assets/components/table-of-contents/table-of-contents.internal.types.ts +1 -0
- package/dist/assets/components/table-of-contents/table-of-contents.stories.ts +1 -1
- package/dist/assets/components/table-of-contents/table-of-contents.types.ts +1 -1
- package/dist/assets/components/table-of-contents/table-of-contents.vue +1 -1
- package/dist/assets/components/tabs/components/tab.internal.types.ts +1 -0
- package/dist/assets/components/tabs/components/tab.types.ts +1 -1
- package/dist/assets/components/tabs/components/tab.vue +1 -1
- package/dist/assets/components/tabs/tabs.stories.ts +1 -1
- package/dist/assets/components/tabs/tabs.vue +1 -1
- package/dist/assets/components/tag/tag.docs.mdx +1 -1
- package/dist/assets/components/tag/tag.stories.ts +1 -1
- package/dist/assets/components/tag/tag.types.ts +1 -1
- package/dist/assets/components/tag/tag.vue +1 -1
- package/dist/assets/components/textarea/textarea.docs.mdx +0 -7
- package/dist/assets/components/textarea/textarea.stories.ts +1 -1
- package/dist/assets/components/textarea/textarea.types.ts +1 -1
- package/dist/assets/components/textarea/textarea.vue +1 -1
- package/dist/assets/components/timeline/components/timeline-item/timeline-item.stories.ts +1 -1
- package/dist/assets/components/timeline/components/timeline-item/timeline-item.vue +1 -1
- package/dist/assets/components/timeline/timeline.docs.mdx +0 -10
- package/dist/assets/components/timeline/timeline.internal.types.ts +1 -0
- package/dist/assets/components/timeline/timeline.stories.ts +1 -1
- package/dist/assets/components/timeline/timeline.types.ts +1 -1
- package/dist/assets/components/timeline/timeline.vue +1 -1
- package/dist/assets/components/toast/components/toast-item/toast-item.vue +1 -1
- package/dist/assets/components/toggle/toggle.stories.ts +1 -1
- package/dist/assets/components/toggle/toggle.types.ts +1 -1
- package/dist/assets/components/toggle/toggle.vue +1 -1
- package/dist/assets/components/tutorial/tutorial.stories.ts +1 -1
- package/dist/assets/components/typography/typography.stories.ts +1 -1
- package/dist/assets/components/typography/typography.types.ts +1 -1
- package/dist/assets/components/typography/typography.vue +1 -1
- package/dist/assets/components.json +1 -0
- package/dist/assets/composables/use-bento-base-filter/index.ts +1 -0
- package/dist/assets/composables/use-bento-base-filter/use-bento-base-filter.ts +1 -0
- package/dist/assets/composables/use-bento-base-filter/utils/index.ts +1 -0
- package/dist/assets/composables/use-bento-base-filter/utils/utils.ts +1 -0
- package/dist/assets/composables/use-bento-currency/index.ts +1 -0
- package/dist/assets/composables/use-bento-currency/use-bento-currency.stories.ts +1 -1
- package/dist/assets/composables/use-bento-currency/use-bento-currency.ts +1 -0
- package/dist/assets/composables/use-bento-delayed-hover/index.ts +1 -0
- package/dist/assets/composables/use-bento-delayed-hover/use-bento-delayed-hover.stories.ts +1 -1
- package/dist/assets/composables/use-bento-delayed-hover/use-bento-delayed-hover.ts +1 -0
- package/dist/assets/composables/use-bento-theme/index.ts +1 -0
- package/dist/assets/composables/use-bento-theme/install.ts +1 -1
- package/dist/assets/composables/use-bento-theme/install.types.ts +1 -1
- package/dist/assets/composables/use-bento-theme/use-bento-theme.ts +1 -0
- package/dist/assets/composables/use-bento-toast-controller/index.ts +1 -0
- package/dist/assets/composables/use-bento-toast-controller/toast-stack.ts +1 -0
- package/dist/assets/composables/use-bento-toast-controller/use-bento-toast-controller.ts +1 -0
- package/dist/assets/composables/use-click-outside/index.ts +1 -0
- package/dist/assets/composables/use-click-outside/use-click-outside.stories.ts +1 -1
- package/dist/assets/composables/use-click-outside/use-click-outside.ts +1 -0
- package/dist/assets/composables/use-form-validate/index.ts +1 -0
- package/dist/assets/composables/use-form-validate/use-form.ts +1 -0
- package/dist/assets/composables/use-form-validate/use-form.types.ts +1 -1
- package/dist/assets/composables.json +9 -0
- package/dist/assets/deprecations.md +139 -243
- package/dist/assets/directives/highlight/highlight.stories.ts +1 -1
- package/dist/assets/directives/keyboard-navigation/keyboard-navigation.stories.ts +1 -1
- package/dist/assets/directives/tooltip/tooltip.internal.types.ts +1 -0
- package/dist/assets/directives/tooltip/tooltip.stories.ts +1 -1
- package/dist/assets/directives/tooltip/tooltip.types.ts +1 -1
- package/dist/assets/icons.json +1 -0
- package/dist/assets/index.ts +1 -1
- package/dist/assets/install.ts +1 -1
- package/dist/assets/tailwind/README.md +3 -3
- package/dist/assets/templates/dashboard-template/components/dashboard-section/dashboard-section.stories.ts +1 -1
- package/dist/assets/templates/dashboard-template/dashboard-template.stories.ts +1 -1
- package/dist/assets/templates/data-grid-template/data-grid-template.stories.ts +1 -1
- package/dist/assets/usage.json +81 -80
- package/dist/assets/variables.css +152 -140
- package/dist/main.js +228 -34
- package/package.json +2 -2
- package/dist/assets/components/internal/base-button/base-button.types.ts +0 -1
- package/dist/assets/components/internal/base-button/base-button.vue +0 -1
- package/dist/assets/components/internal/button-actions-with-menu/button-actions-with-menu.stories.ts +0 -1
- package/dist/assets/components/internal/button-actions-with-menu/button-actions-with-menu.types.ts +0 -1
- package/dist/assets/components/internal/button-actions-with-menu/button-actions-with-menu.vue +0 -1
- package/dist/assets/components/internal/calendar/calendar.stories.ts +0 -1
- package/dist/assets/components/internal/calendar/calendar.types.ts +0 -1
- package/dist/assets/components/internal/calendar/calendar.vue +0 -1
- package/dist/assets/components/internal/calendar/components/calendar-month/calendar-month.stories.ts +0 -1
- package/dist/assets/components/internal/calendar/components/calendar-month/calendar-month.types.ts +0 -1
- package/dist/assets/components/internal/calendar/components/calendar-month/calendar-month.vue +0 -1
- package/dist/assets/components/internal/calendar/components/calendar-month/components/calendar-month-day/__test__/calendar-month-day-stub.vue +0 -1
- package/dist/assets/components/internal/calendar/components/calendar-month/components/calendar-month-day/calendar-month-day.stories.ts +0 -1
- package/dist/assets/components/internal/calendar/components/calendar-month/components/calendar-month-day/calendar-month-day.types.ts +0 -1
- package/dist/assets/components/internal/calendar/components/calendar-month/components/calendar-month-day/calendar-month-day.vue +0 -1
- package/dist/assets/components/internal/calendar/components/calendar-month/composables/granular-min-max-date.types.ts +0 -1
- package/dist/assets/components/internal/calendar/components/calendar-year/calendar-year.types.ts +0 -1
- package/dist/assets/components/internal/calendar/components/calendar-year/calendar-year.vue +0 -1
- package/dist/assets/components/internal/calendar/components/calendar-year/components/calendar-year-month/__test__/calendar-year-month-stub.vue +0 -1
- package/dist/assets/components/internal/calendar/components/calendar-year/components/calendar-year-month/calendar-year-month.types.ts +0 -1
- package/dist/assets/components/internal/calendar/components/calendar-year/components/calendar-year-month/calendar-year-month.vue +0 -1
- package/dist/assets/components/internal/calendar/components/calendar-year-selector/calendar-year-selector.types.ts +0 -1
- package/dist/assets/components/internal/calendar/components/calendar-year-selector/calendar-year-selector.vue +0 -1
- package/dist/assets/components/internal/calendar/composables/use-granularity-adjustments.types.ts +0 -1
- package/dist/assets/components/internal/calendar/composables/use-range-pane.types.ts +0 -1
- package/dist/assets/components/internal/checkbox-input/checkbox-input.stories.ts +0 -1
- package/dist/assets/components/internal/checkbox-input/checkbox-input.types.ts +0 -1
- package/dist/assets/components/internal/checkbox-input/checkbox-input.vue +0 -1
- package/dist/assets/components/internal/controls-group/controls-group.vue +0 -1
- package/dist/assets/components/internal/copy/copy.docs.mdx +0 -43
- package/dist/assets/components/internal/copy/copy.stories.ts +0 -1
- package/dist/assets/components/internal/copy/copy.types.ts +0 -1
- package/dist/assets/components/internal/copy/copy.vue +0 -1
- package/dist/assets/components/internal/decorative-checkbox/decorative-checkbox.vue +0 -1
- package/dist/assets/components/internal/dialog-page/dialog-page.types.ts +0 -1
- package/dist/assets/components/internal/dialog-page/dialog-page.vue +0 -1
- package/dist/assets/components/internal/error-message/error-message.vue +0 -1
- package/dist/assets/components/internal/field-label/field-label.vue +0 -1
- package/dist/assets/components/internal/fixed-scroller/fixed-scroller.docs.mdx +0 -21
- package/dist/assets/components/internal/fixed-scroller/fixed-scroller.stories.ts +0 -1
- package/dist/assets/components/internal/fixed-scroller/fixed-scroller.vue +0 -1
- package/dist/assets/components/internal/footer-actions/footer-actions.vue +0 -1
- package/dist/assets/components/internal/grid-layout/grid-layout.types.ts +0 -1
- package/dist/assets/components/internal/grid-layout/grid-layout.vue +0 -1
- package/dist/assets/components/internal/header-meta/header-meta.types.ts +0 -1
- package/dist/assets/components/internal/header-meta/header-meta.vue +0 -1
- package/dist/assets/components/internal/info-icon-with-popover/info-icon-with-popover.vue +0 -1
- package/dist/assets/components/internal/listbox/components/listbox-lazy-load/listbox-lazy-load.types.ts +0 -1
- package/dist/assets/components/internal/listbox/components/listbox-lazy-load/listbox-lazy-load.vue +0 -1
- package/dist/assets/components/internal/listbox/components/listbox-multi-select/components/listbox-multi-select-options/components/listbox-multi-select-category/listbox-multi-select-category.types.ts +0 -1
- package/dist/assets/components/internal/listbox/components/listbox-multi-select/components/listbox-multi-select-options/components/listbox-multi-select-category/listbox-multi-select-category.vue +0 -1
- package/dist/assets/components/internal/listbox/components/listbox-multi-select/components/listbox-multi-select-options/components/listbox-multi-select-nested-lazy-load/listbox-multi-select-nested-lazy-load.types.ts +0 -1
- package/dist/assets/components/internal/listbox/components/listbox-multi-select/components/listbox-multi-select-options/components/listbox-multi-select-nested-lazy-load/listbox-multi-select-nested-lazy-load.vue +0 -1
- package/dist/assets/components/internal/listbox/components/listbox-multi-select/components/listbox-multi-select-options/listbox-multi-select-options.types.ts +0 -1
- package/dist/assets/components/internal/listbox/components/listbox-multi-select/components/listbox-multi-select-options/listbox-multi-select-options.vue +0 -1
- package/dist/assets/components/internal/listbox/components/listbox-multi-select/listbox-multi-select.types.ts +0 -1
- package/dist/assets/components/internal/listbox/components/listbox-multi-select/listbox-multi-select.vue +0 -1
- package/dist/assets/components/internal/listbox/components/listbox-option/listbox-option.vue +0 -1
- package/dist/assets/components/internal/listbox/components/listbox-option-tag/listbox-option-tag.vue +0 -1
- package/dist/assets/components/internal/listbox/components/listbox-single-select/components/listbox-single-select-category/listbox-single-select-category.vue +0 -1
- package/dist/assets/components/internal/listbox/components/listbox-single-select/listbox-single-select.vue +0 -1
- package/dist/assets/components/internal/listbox/components/listbox-single-select-option/listbox-single-select-option.vue +0 -1
- package/dist/assets/components/internal/listbox/listbox.stories.ts +0 -1
- package/dist/assets/components/internal/listbox/listbox.types.ts +0 -1
- package/dist/assets/components/internal/listbox/listbox.vue +0 -1
- package/dist/assets/components/internal/radio-input/radio-input.stories.ts +0 -1
- package/dist/assets/components/internal/radio-input/radio-input.types.ts +0 -1
- package/dist/assets/components/internal/radio-input/radio-input.vue +0 -1
- package/dist/assets/components/internal/teleport/teleport.types.ts +0 -1
- package/dist/assets/components/internal/teleport/teleport.vue +0 -1
- package/dist/assets/components/internal/tooltip/tooltip.test.vue +0 -1
- package/dist/assets/components/internal/tooltip/tooltip.types.ts +0 -1
- package/dist/assets/components/internal/tooltip/tooltip.vue +0 -1
- package/dist/assets/composables/use-form-validate-zod-4/use-form.types.ts +0 -1
- package/dist/assets/composables/use-numeric-input/use-numeric-input.types.ts +0 -1
- package/dist/assets/composables/use-section-navigation-keyboard/use-section-navigation-keyboard.types.ts +0 -1
- package/dist/assets/composables/use-virtual-list/use-virtual-list.types.ts +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import { type Meta, type StoryObj } from '@storybook/vue'; import { action } from '@storybook/addon-actions'; import { ref } from 'vue'; import { isVue2 } from 'vue-demi'; import { format } from 'date-fns/format'; import { addMonths } from 'date-fns/addMonths'; import { endOfMonth } from 'date-fns/endOfMonth'; import { endOfWeek } from 'date-fns/endOfWeek'; import { lastDayOfQuarter } from 'date-fns/lastDayOfQuarter'; import { startOfQuarter } from 'date-fns/startOfQuarter'; import { startOfWeek } from 'date-fns/startOfWeek'; import { subDays } from 'date-fns/subDays'; import { subMonths } from 'date-fns/subMonths'; import { subQuarters } from 'date-fns/subQuarters'; import { subWeeks } from 'date-fns/subWeeks'; import { BentoDateRangePickerFirstDayOfWeek } from './date-range-picker.types'; import { storybookDocsParameter } from '@/utils/ts/storybook'; import BentoDateRangePicker from './date-range-picker.vue'; import BentoDateRangePickerDefaultExample from './__tests__/date-range-picker-default-example.vue?raw'; import { startOfDay, subHours, subMinutes } from 'date-fns'; const meta: Meta = { title: 'Date picker/Range dates', component: BentoDateRangePicker, argTypes: { // Properties maxRange: { name: 'maxRange', defaultValue: undefined, description: 'Set a maximum number of dates to be selectable by the range.', table: { type: { summary: 'number' } }, control: { type: 'number', }, }, min: { control: { type: 'date' } }, max: { control: { type: 'date' } }, // Funcs isDateDisabled: { name: 'isDateDisabled', defaultValue: '', description: 'Disables the dates that meet the criteria defined in the function.', table: { type: { summary: '(currentDate: Date) => boolean' } }, control: { disable: true }, }, // Enum props firstDayOfWeek: { options: Object.keys(BentoDateRangePickerFirstDayOfWeek) .map(key => BentoDateRangePickerFirstDayOfWeek[key]) .filter(value => typeof value !== 'string') as Array<number>, control: { type: 'select', }, }, // Events input: { name: 'input', defaultValue: '', description: 'Triggered when a date is selected', table: { type: { summary: '(selectedDate: Date) => void' } }, control: { disable: true }, }, value: { control: { type: 'text' }, table: { category: 'Deprecated' }, description: 'Deprecated for usage with properties since version 2.0. Use `v-model` or `model-value` property instead', }, }, }; export default meta; type Story = StoryObj<typeof BentoDateRangePicker>; // To be used by as the current time across all date filters const CURRENT_DATE_AND_TIME = new Date(); export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoDateRangePicker }, props: Object.keys(argTypes), template: ` <div style="max-width: 380px"> <bento-date-range-picker v-bind="args" :model-value="selectedDate" @update:model-value="onDateChanged" /> </div> `, setup(props) { const selectedDate = ref((isVue2 ? props : _args).modelValue); const onDateChanged = (newSelectedDate: Date) => { selectedDate.value = newSelectedDate; action('input')(selectedDate); action('update:model-value')(selectedDate); }; return { // Values selectedDate, args: isVue2 ? props : _args, // Events onDateChanged, }; }, }), args: { yearRange: { minYear: undefined, maxYear: undefined }, allowTimeInput: true, description: 'Supporting text', label: 'Label', firstDayOfWeek: 1, numberOfMonths: 2, maxRange: 0, optional: true, required: true, isDateDisabled: (date: Date) => date.valueOf() > Date.now(), modelValue: { startDate: subDays(new Date(), 3), endDate: new Date(), range: 'lastFourDays' }, hasActions: true, }, parameters: storybookDocsParameter(BentoDateRangePickerDefaultExample), }; export const RangeSelector = { ...Default, args: { yearRange: { minYear: undefined, maxYear: undefined }, ...Default.args, modelValue: { startDate: subDays(startOfDay(CURRENT_DATE_AND_TIME), 1), endDate: CURRENT_DATE_AND_TIME, range: 'lastTwoDays', }, 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 2 days', value: 'lastTwoDays', data: { startDate: subDays(startOfDay(CURRENT_DATE_AND_TIME), 1) }, }, { label: 'Last 3 days', value: 'lastThreeDays', data: { startDate: subDays(startOfDay(CURRENT_DATE_AND_TIME), 2) }, }, { label: 'Last 4 days', value: 'lastFourDays', data: { startDate: subDays(startOfDay(CURRENT_DATE_AND_TIME), 3) }, }, { label: 'Last 24 hours', value: 'lastTwentyFourHours', data: { startDate: subHours(CURRENT_DATE_AND_TIME, 24), isRelativeToNow: true, }, }, { label: 'Last 2 days', value: 'lastTwoDays', data: { startDate: subDays(startOfDay(CURRENT_DATE_AND_TIME), 1) }, }, { label: 'Last 3 days', value: 'lastThreeDays', data: { startDate: subDays(startOfDay(CURRENT_DATE_AND_TIME), 2) }, }, { label: 'Last 4 days', value: 'lastFourDays', data: { startDate: subDays(startOfDay(CURRENT_DATE_AND_TIME), 3) }, }, ], }, parameters: storybookDocsParameter(` <bento-date-range-picker v-model="selectedDate" label="Label" description="Supporting text" :first-day-of-week="BentoDateRangePickerFirstDayOfWeek.SUNDAY" isDateDisabled="(currentDate: Date) => boolean" maxRange required optional disabled hasActions :quick-select-ranges="[ { label: 'Last 2 days', value: 'lastTwoDays', data: { startDate: subDays(new Date(), 1) }, }, { label: 'Last 3 days', value: 'lastThreeDays', data: { startDate: subDays(new Date(), 2), endDate: new Date() }, }, { label: 'Last 4 days', value: 'lastFourDays', data: { startDate: subDays(new Date(), 3), endDate: new Date() }, }, ]" /> `), }; const GRANULARITIES_VALUE = { startDate: startOfWeek(subWeeks(new Date(), 1), { weekStartsOn: Default.args.firstDayOfWeek }), endDate: endOfWeek(subWeeks(new Date(), 1), { weekStartsOn: Default.args.firstDayOfWeek }), granularity: 'weekly', }; const granularitiesCode = ` <template> <bento-date-range-picker v-model="selectedDate" label="Label" description="Supporting text" :allow-time-input="false" :first-day-of-week="BentoDateRangePickerFirstDayOfWeek.MONDAY" :granularities="granularities" :max="new Date('${format(subDays(endOfMonth(new Date()), 2), 'yyyy-MM-dd')}')" hasActions /> </template> <script setup lang="ts"> import { BentoDateRangePicker, BentoDateRangePickerFirstDayOfWeek, type BentoDateRangePickerGranularityConfig, type BentoDateRangePickerValue, } from "@adyen/bento-vue2" const selectedDate = ref<BentoDateRangePickerValue>({ startDate: new Date('${format(GRANULARITIES_VALUE.startDate, 'yyyy-MM-dd')}'), endDate: new Date('${format(GRANULARITIES_VALUE.endDate, 'yyyy-MM-dd')}'), granularity: 'monthly', }); const granularities: Array<BentoDateRangePickerGranularityConfig> = [ { type: 'daily' }, { type: 'weekly' }, { type: 'monthly' }, }] </script> `; export const Granularities = { ...Default, args: { yearRange: { minYear: undefined, maxYear: undefined }, allowTimeInput: false, description: 'Supporting text', label: 'Label', numberOfMonths: 2, min: subMonths(endOfMonth(new Date()), 6), max: subDays(endOfMonth(new Date()), 2), granularities: [ { type: 'daily', }, { type: 'weekly', }, { type: 'monthly', }, ], modelValue: GRANULARITIES_VALUE, quickSelectRanges: [ { label: 'Last 2 days', value: 'lastTwoDays', data: { startDate: subDays(new Date(), 1), endDate: new Date(), granularity: 'daily' }, }, { label: 'Last quarter', value: 'lastQuarter', data: { startDate: startOfQuarter(subQuarters(new Date(), 1)), endDate: lastDayOfQuarter(subQuarters(new Date(), 1)), granularity: 'monthly', }, }, ], hasActions: true, }, parameters: storybookDocsParameter(granularitiesCode), }; export const MonthVariant = { ...Default, args: { yearRange: { minYear: undefined, maxYear: undefined }, ...Default.args, allowTimeInput: false, variant: 'month', modelValue: { startDate: subMonths(new Date(), 2), endDate: new Date(), granularity: 'monthly' }, isDateDisabled: null, max: addMonths(new Date(), 6), granularities: [{ type: 'monthly' }, { type: 'quarterly' }], quickSelectRanges: [ { label: 'Last 3 months', value: 'lastTwoMonths', data: { startDate: subMonths(new Date(), 3), endDate: subMonths(new Date(), 1), granularity: 'monthly', }, }, { label: 'Last quarter', value: 'lastQuarter', data: { startDate: startOfQuarter(subQuarters(new Date(), 1)), endDate: lastDayOfQuarter(subQuarters(new Date(), 1)), granularity: 'quarterly', }, }, ], }, parameters: storybookDocsParameter(` <bento-date-range-picker v-model="selectedDate" label="Label" description="Supporting text" isDateDisabled="(currentDate: Date) => boolean" maxRange required optional disabled hasActions variant="month" /> `), };
|
|
1
|
+
import { type Meta, type StoryObj } from '@storybook/vue'; import { action } from '@storybook/addon-actions'; import { ref } from 'vue'; import { isVue2 } from 'vue-demi'; import { addMonths } from 'date-fns/addMonths'; import { endOfMonth } from 'date-fns/endOfMonth'; import { endOfWeek } from 'date-fns/endOfWeek'; import { lastDayOfQuarter } from 'date-fns/lastDayOfQuarter'; import { startOfHour } from 'date-fns/startOfHour'; import { startOfQuarter } from 'date-fns/startOfQuarter'; import { startOfWeek } from 'date-fns/startOfWeek'; import { subDays } from 'date-fns/subDays'; import { subMonths } from 'date-fns/subMonths'; import { subQuarters } from 'date-fns/subQuarters'; import { subWeeks } from 'date-fns/subWeeks'; import { startOfDay, subHours, subMinutes } from 'date-fns'; import { storybookDocsParameter } from '@/utils/ts/storybook'; import { BentoDateRangePickerFirstDayOfWeek } from './date-range-picker.types'; import BentoDateRangePicker from './date-range-picker.vue'; const DateRangePickerPredefinedExample = "<template>\n <bento-date-range-picker\n v-model=\"selectedDate\"\n :allow-time-input=\"true\"\n :error-message=\"errorMessage\"\n :time-interval=\"60\"\n description=\"Supporting text\"\n label=\"Label\"\n />\n</template>\n<script setup lang=\"ts\">\n import { BentoDateRangePicker, type BentoDateRangePickerValue } from '@adyen/bento-vue2';\n import { startOfHour } from 'date-fns/startOfHour';\n import { subDays } from 'date-fns/subDays';\n import { ref } from 'vue';\n\n const errorMessage = ref<string>('');\n\n const selectedDate = ref<BentoDateRangePickerValue>({\n startDate: startOfHour(subDays(new Date(), 1)),\n endDate: startOfHour(new Date()),\n });\n</script>\n"; const DateRangePickerDefault = "<template>\n <bento-date-range-picker\n v-model=\"selectedDate\"\n allow-time-input\n description=\"Supporting text\"\n :first-day-of-week=\"BentoDateRangePickerFirstDayOfWeek.MONDAY\"\n :is-date-disabled=\"isDateDisabled\"\n label=\"Label\"\n :max-range=\"0\"\n :number-of-months=\"2\"\n required\n />\n</template>\n<script setup lang=\"ts\">\n import {\n BentoDateRangePicker,\n BentoDateRangePickerFirstDayOfWeek,\n type BentoDateRangePickerIsDateDisabled,\n type BentoDateRangePickerValue,\n } from '@adyen/bento-vue2';\n import { subDays } from 'date-fns/subDays';\n import { ref } from 'vue';\n\n const isDateDisabled: BentoDateRangePickerIsDateDisabled = (currentDate: Date) =>\n currentDate.valueOf() > Date.now();\n const selectedDate = ref<BentoDateRangePickerValue>({\n startDate: subDays(new Date(), 3),\n endDate: new Date(),\n range: 'lastFourDays',\n });\n</script>\n"; const DateRangePickerGranularities = "<template>\n <bento-date-range-picker\n v-model=\"selectedDate\"\n description=\"Supporting text\"\n :first-day-of-week=\"BentoDateRangePickerFirstDayOfWeek.MONDAY\"\n :granularities=\"granularities\"\n label=\"Label\"\n :max=\"maxDate\"\n :min=\"minDate\"\n :number-of-months=\"2\"\n :quick-select-ranges=\"quickSelectRanges\"\n />\n</template>\n\n<script setup lang=\"ts\">\n import {\n BentoDateRangePicker,\n BentoDateRangePickerFirstDayOfWeek,\n type BentoDateRangePickerGranularityConfig,\n type BentoDateRangePickerQuickSelectRanges,\n type BentoDateRangePickerValue,\n } from '@adyen/bento-vue2';\n import { endOfMonth } from 'date-fns/endOfMonth';\n import { endOfWeek } from 'date-fns/endOfWeek';\n import { startOfWeek } from 'date-fns/startOfWeek';\n import { lastDayOfQuarter } from 'date-fns/lastDayOfQuarter';\n import { startOfQuarter } from 'date-fns/startOfQuarter';\n import { subDays } from 'date-fns/subDays';\n import { subMonths } from 'date-fns/subMonths';\n import { subQuarters } from 'date-fns/subQuarters';\n import { subWeeks } from 'date-fns/subWeeks';\n import { ref } from 'vue';\n\n const selectedDate = ref<BentoDateRangePickerValue>({\n startDate: startOfWeek(subWeeks(new Date(), 1), { weekStartsOn: 1 }),\n endDate: endOfWeek(subWeeks(new Date(), 1), { weekStartsOn: 1 }),\n granularity: 'weekly',\n });\n const granularities: Array<BentoDateRangePickerGranularityConfig> = [\n { type: 'daily' },\n { type: 'weekly', maxRange: 2 },\n { type: 'monthly', maxRange: 2 },\n ];\n const minDate = subMonths(endOfMonth(new Date()), 6);\n const maxDate = subDays(endOfMonth(new Date()), 2);\n const quickSelectRanges: BentoDateRangePickerQuickSelectRanges = [\n {\n label: 'Last 2 days',\n value: 'lastTwoDays',\n data: { startDate: subDays(new Date(), 1), endDate: new Date(), granularity: 'daily' },\n },\n {\n label: 'Last quarter',\n value: 'lastQuarter',\n data: {\n startDate: startOfQuarter(subQuarters(new Date(), 1)),\n endDate: lastDayOfQuarter(subQuarters(new Date(), 1)),\n granularity: 'monthly',\n },\n },\n ];\n</script>\n"; const DateRangePickerMonthVariant = "<template>\n <bento-date-range-picker\n v-model=\"selectedDate\"\n description=\"Supporting text\"\n :first-day-of-week=\"BentoDateRangePickerFirstDayOfWeek.MONDAY\"\n :granularities=\"granularities\"\n label=\"Label\"\n :max=\"maxDate\"\n :max-range=\"0\"\n :number-of-months=\"2\"\n :quick-select-ranges=\"quickSelectRanges\"\n required\n variant=\"month\"\n />\n</template>\n\n<script setup lang=\"ts\">\n import {\n BentoDateRangePicker,\n BentoDateRangePickerFirstDayOfWeek,\n type BentoDateRangePickerGranularityConfig,\n type BentoDateRangePickerQuickSelectRanges,\n type BentoDateRangePickerValue,\n } from '@adyen/bento-vue2';\n import { addMonths } from 'date-fns/addMonths';\n import { lastDayOfQuarter } from 'date-fns/lastDayOfQuarter';\n import { startOfQuarter } from 'date-fns/startOfQuarter';\n import { subMonths } from 'date-fns/subMonths';\n import { subQuarters } from 'date-fns/subQuarters';\n import { ref } from 'vue';\n\n const selectedDate = ref<BentoDateRangePickerValue>({\n startDate: subMonths(new Date(), 2),\n endDate: new Date(),\n granularity: 'monthly',\n });\n const maxDate = addMonths(new Date(), 6);\n const granularities: Array<BentoDateRangePickerGranularityConfig> = [{ type: 'monthly' }, { type: 'quarterly' }];\n const quickSelectRanges: BentoDateRangePickerQuickSelectRanges = [\n {\n label: 'Last 3 months',\n value: 'lastThreeMonths',\n data: {\n startDate: subMonths(new Date(), 3),\n endDate: subMonths(new Date(), 1),\n granularity: 'monthly',\n },\n },\n {\n label: 'Last quarter',\n value: 'lastQuarter',\n data: {\n startDate: startOfQuarter(subQuarters(new Date(), 1)),\n endDate: lastDayOfQuarter(subQuarters(new Date(), 1)),\n granularity: 'quarterly',\n },\n },\n ];\n</script>\n"; const DateRangePickerRangeSelector = "<template>\n <bento-date-range-picker\n v-model=\"selectedDate\"\n allow-time-input\n description=\"Supporting text\"\n :first-day-of-week=\"BentoDateRangePickerFirstDayOfWeek.MONDAY\"\n :is-date-disabled=\"isDateDisabled\"\n label=\"Label\"\n :max-range=\"0\"\n :number-of-months=\"2\"\n :quick-select-ranges=\"quickSelectRanges\"\n required\n />\n</template>\n\n<script setup lang=\"ts\">\n import {\n BentoDateRangePicker,\n BentoDateRangePickerFirstDayOfWeek,\n type BentoDateRangePickerIsDateDisabled,\n type BentoDateRangePickerQuickSelectRanges,\n type BentoDateRangePickerValue,\n } from '@adyen/bento-vue2';\n import { startOfDay } from 'date-fns/startOfDay';\n import { subDays } from 'date-fns/subDays';\n import { subHours } from 'date-fns/subHours';\n import { subMinutes } from 'date-fns/subMinutes';\n import { ref } from 'vue';\n\n const now = new Date();\n const isDateDisabled: BentoDateRangePickerIsDateDisabled = date => date.valueOf() > Date.now();\n const selectedDate = ref<BentoDateRangePickerValue>({\n startDate: subDays(startOfDay(now), 1),\n endDate: now,\n range: 'lastTwoDays',\n });\n const quickSelectRanges: BentoDateRangePickerQuickSelectRanges = [\n {\n label: 'Last 15 min',\n value: 'lastFifteenMin',\n data: { startDate: subMinutes(now, 15), isRelativeToNow: true },\n },\n {\n label: 'Last 24 hours',\n value: 'lastTwentyFourHours',\n data: { startDate: subHours(now, 24), isRelativeToNow: true },\n },\n {\n label: 'Last 2 days',\n value: 'lastTwoDays',\n data: { startDate: subDays(startOfDay(now), 1) },\n },\n {\n label: 'Last 3 days',\n value: 'lastThreeDays',\n data: { startDate: subDays(startOfDay(now), 2) },\n },\n {\n label: 'Last 4 days',\n value: 'lastFourDays',\n data: { startDate: subDays(startOfDay(now), 3) },\n },\n ];\n</script>\n"; const meta: Meta = { title: 'Date picker/Range dates', component: BentoDateRangePicker, argTypes: { // Properties maxRange: { name: 'maxRange', defaultValue: undefined, description: 'Set a maximum number of dates to be selectable by the range.', table: { type: { summary: 'number' } }, control: { type: 'number', }, }, min: { control: { type: 'date' } }, max: { control: { type: 'date' } }, // Funcs isDateDisabled: { name: 'isDateDisabled', defaultValue: '', description: 'Disables the dates that meet the criteria defined in the function.', table: { type: { summary: '(currentDate: Date) => boolean' } }, control: { disable: true }, }, // Enum props firstDayOfWeek: { options: Object.keys(BentoDateRangePickerFirstDayOfWeek) .map(key => BentoDateRangePickerFirstDayOfWeek[key]) .filter(value => typeof value !== 'string') as Array<number>, control: { type: 'select', }, }, }, }; export default meta; type Story = StoryObj<typeof BentoDateRangePicker>; // To be used by as the current time across all date filters const CURRENT_DATE_AND_TIME = new Date(); export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoDateRangePicker }, props: Object.keys(argTypes), template: ` <div style="max-width: 380px"> <bento-date-range-picker v-bind="args" :model-value="selectedDate" @update:model-value="onDateChanged" /> </div> `, setup(props) { const selectedDate = ref((isVue2 ? props : _args).modelValue); const onDateChanged = (newSelectedDate: Date) => { selectedDate.value = newSelectedDate; action('update:model-value')(selectedDate); }; return { // Values selectedDate, args: isVue2 ? props : _args, // Events onDateChanged, }; }, }), args: { yearRange: { minYear: undefined, maxYear: undefined }, allowTimeInput: true, description: 'Supporting text', label: 'Label', firstDayOfWeek: 1, numberOfMonths: 2, maxRange: 0, required: true, isDateDisabled: (date: Date) => date.valueOf() > Date.now(), modelValue: { startDate: subDays(new Date(), 3), endDate: new Date(), range: 'lastFourDays' }, }, parameters: storybookDocsParameter(DateRangePickerDefault), }; export const RangeSelector = { ...Default, args: { yearRange: { minYear: undefined, maxYear: undefined }, ...Default.args, modelValue: { startDate: subDays(startOfDay(CURRENT_DATE_AND_TIME), 1), endDate: CURRENT_DATE_AND_TIME, range: 'lastTwoDays', }, 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 2 days', value: 'lastTwoDays', data: { startDate: subDays(startOfDay(CURRENT_DATE_AND_TIME), 1) }, }, { label: 'Last 3 days', value: 'lastThreeDays', data: { startDate: subDays(startOfDay(CURRENT_DATE_AND_TIME), 2) }, }, { label: 'Last 4 days', value: 'lastFourDays', data: { startDate: subDays(startOfDay(CURRENT_DATE_AND_TIME), 3) }, }, ], }, parameters: storybookDocsParameter(DateRangePickerRangeSelector), }; export const PredefinedTimes = { ...Default, args: { ...Default.args, timeInterval: 60, modelValue: { startDate: startOfHour(subDays(CURRENT_DATE_AND_TIME, 1)), endDate: CURRENT_DATE_AND_TIME, }, }, parameters: storybookDocsParameter(DateRangePickerPredefinedExample), }; const GRANULARITIES_VALUE = { startDate: startOfWeek(subWeeks(new Date(), 1), { weekStartsOn: Default.args.firstDayOfWeek }), endDate: endOfWeek(subWeeks(new Date(), 1), { weekStartsOn: Default.args.firstDayOfWeek }), granularity: 'weekly', }; export const Granularities = { ...Default, args: { yearRange: { minYear: undefined, maxYear: undefined }, allowTimeInput: false, description: 'Supporting text', label: 'Label', numberOfMonths: 2, min: subMonths(endOfMonth(new Date()), 6), max: subDays(endOfMonth(new Date()), 2), granularities: [ { type: 'daily', }, { type: 'weekly', maxRange: 2, }, { type: 'monthly', maxRange: 2, }, ], modelValue: GRANULARITIES_VALUE, quickSelectRanges: [ { label: 'Last 2 days', value: 'lastTwoDays', data: { startDate: subDays(new Date(), 1), endDate: new Date(), granularity: 'daily' }, }, { label: 'Last quarter', value: 'lastQuarter', data: { startDate: startOfQuarter(subQuarters(new Date(), 1)), endDate: lastDayOfQuarter(subQuarters(new Date(), 1)), granularity: 'monthly', }, }, ], }, parameters: storybookDocsParameter(DateRangePickerGranularities), }; export const MonthVariant = { ...Default, args: { yearRange: { minYear: undefined, maxYear: undefined }, ...Default.args, allowTimeInput: false, variant: 'month', modelValue: { startDate: subMonths(new Date(), 2), endDate: new Date(), granularity: 'monthly' }, isDateDisabled: null, max: addMonths(new Date(), 6), granularities: [{ type: 'monthly' }, { type: 'quarterly' }], quickSelectRanges: [ { label: 'Last 3 months', value: 'lastThreeMonths', data: { startDate: subMonths(new Date(), 3), endDate: subMonths(new Date(), 1), granularity: 'monthly', }, }, { label: 'Last quarter', value: 'lastQuarter', data: { startDate: startOfQuarter(subQuarters(new Date(), 1)), endDate: lastDayOfQuarter(subQuarters(new Date(), 1)), granularity: 'quarterly', }, }, ], }, parameters: storybookDocsParameter(DateRangePickerMonthVariant), };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import type { FormField, FormFieldEmits } from '../../types/form-field'; import { CalendarFirstDayOfWeek as BentoDateRangePickerFirstDayOfWeek, type CalendarGranularityConfig as BentoDateRangePickerGranularityConfig, type CalendarIsDateDisabled as BentoDateRangePickerIsDateDisabled, type CalendarRangeDateValue as BentoDateRangePickerValue, CalendarVariant as BentoDateRangePickerVariant, type CalendarYearRange, } from '@/internal/calendar/calendar.types'; import type { DateRangePickerCalendarRangeSelectorItems as BentoDateRangePickerQuickSelectRanges } from './components/date-range-picker-calendar/date-range-picker-calendar.types'; export { BentoDateRangePickerFirstDayOfWeek, type BentoDateRangePickerIsDateDisabled, type BentoDateRangePickerValue, type BentoDateRangePickerGranularityConfig, type BentoDateRangePickerQuickSelectRanges, BentoDateRangePickerVariant, }; /** * @deprecated Since
|
|
1
|
+
import type { FormField, FormFieldEmits } from '../../types/form-field'; import { CalendarFirstDayOfWeek as BentoDateRangePickerFirstDayOfWeek, type CalendarGranularityConfig as BentoDateRangePickerGranularityConfig, type CalendarIsDateDisabled as BentoDateRangePickerIsDateDisabled, type CalendarRangeDateValue as BentoDateRangePickerValue, CalendarVariant as BentoDateRangePickerVariant, type CalendarYearRange, } from '@/internal/calendar/calendar.types'; import type { DateRangePickerCalendarRangeSelectorItems as BentoDateRangePickerQuickSelectRanges } from './components/date-range-picker-calendar/date-range-picker-calendar.types'; export { BentoDateRangePickerFirstDayOfWeek, type BentoDateRangePickerIsDateDisabled, type BentoDateRangePickerValue, type BentoDateRangePickerGranularityConfig, type BentoDateRangePickerQuickSelectRanges, BentoDateRangePickerVariant, }; /** * @deprecated Since v3.0.0. Use string literal instead. */ export enum BentoDateRangePickerEvent { INPUT = 'input', } export type BentoDateRangePickerEmits = FormFieldEmits<any>; export type BentoDateRangePickerYearRange = CalendarYearRange; export interface BentoDateRangePickerProps extends FormField { /** * Allows user to enter time values in the form * @default false */ allowTimeInput?: boolean; /** * Allows user to set the first day of the week. 0 is sunday, 1 is monday, 6 is saturday * @default undefined */ firstDayOfWeek?: BentoDateRangePickerFirstDayOfWeek | number; /** * A list of available granularities. * If present, the date picker will display a segmented control to change granularity. */ granularities?: Array<BentoDateRangePickerGranularityConfig>; /** * Indicate if a date should be disabled or not */ isDateDisabled?: BentoDateRangePickerIsDateDisabled; /** * Upon opening date range picker, end date's month will be pre-selected. * @default false */ showEndDateOnOpen?: boolean; /** * Enables the range selector. * If provided, must be an array that sets the custom range dropdown items. */ quickSelectRanges?: BentoDateRangePickerQuickSelectRanges; /** * Set a maximum number of dates to be selectable by the range. */ maxRange?: number; /** * Sets a minimum limit for selectable dates - dates before `min` will not be selectable */ min?: Date; /** * Sets a maximum limit for selectable dates - dates after `max` will not be selectable */ max?: Date; /** * Number of months rendered on pane */ numberOfMonths?: number; /** * Limits the selectable times to a fixed interval, expressed in minutes. * Requires `allowTimeInput` and is not supported by the `month` variant. * @default undefined */ timeInterval?: number; /** * Selected date */ modelValue?: BentoDateRangePickerValue | null; /** * The type of calendar to display. Defaults to showing days. */ variant?: `${BentoDateRangePickerVariant}`; /** * Range of years to be selectable from the year dropdown */ yearRange?: BentoDateRangePickerYearRange; }
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<!-- TODO: Remove aria props as these are part of JSX elements /vue/types/jsx.d.ts --> <!-- eslint-disable vue/attribute-hyphenation --> <template> <div v-bento-click-outside-directive="closeDateRangePicker" class="b-date-range-picker"> <!-- Label --> <field-label v-if="label" :id="labelId" :label="label" :tooltip-text="tooltipText" :optional="optional" :required="required" /> <!-- Input --> <div @keydown.enter="onEnterPressedOverInput"> <dropdown-input-default ref="inputContainerRef" always-combobox-is-input :ariaControls="datePickerContainerId" :ariaExpanded="isDateRangePickerOpen" :aria-labelledby="dropdownInputAriaLabelledBy" :aria-describedby="ariaDescribedBy" :aria-required="required" :disabled="disabled" :is-invalid="!!errorMessage || shouldDisplayError" :open="isDateRangePickerOpen" :display-value="displayedDateValue" :readonly="isReadOnly" @open="openDateRangePicker" @clear="clearDateRangePickerValue" @close="closeDateRangePicker" /> </div> <!-- Error Messages --> <error-message v-if="shouldDisplayError" :id="dateErrorId" :error-message="t('invalidDateFormat')" class="b-date-range-picker__error-message" /> <error-message v-if="!!errorMessage" :id="errorId" :error-message="errorMessage" class="b-date-range-picker__error-message" /> <!-- Description --> <bento-typography v-if="description" :id="descriptionId" class="b-date-range-picker__description" :class="descriptionConditionalClasses" el="span" > {{ description }} </bento-typography> <!-- Calendar container --> <bento-popover v-if="inputContainerRef" :id="datePickerContainerId" class="b-date-range-picker__container" :open="isDateRangePickerOpen" :target-element="inputContainerRef" :aria-label="popoverAriaLabel" position="bottom-start" without-space fit-content trap-all overflow-visible :trap-all-options="trapAllOptions" @keydown.esc.native="closeDateRangePicker" > <date-range-picker-calendar :allow-time-input="withTimeInput" :quick-select-ranges="adjustedQuickSelectRanges" :value="dateRangePickerCalendarValue" :first-day-of-week="resolvedFirstDayOfWeek" :granularities="granularities" :is-date-disabled="isDateDisabled" :max-range="maxRange" :min="min" :max="max" :number-of-months="computedNumberOfMonths" :show-end-date-on-open="showEndDateOnOpen" :date-form-data="displayedTextInputValue" :variant="variant" :year-range="yearRange" @custom-range="onRangeSelectorInput" @input="onDateRangePickerInput" @error="onDateRangePickerError" @form-date="onDateRangePickerFormDateInputUpdate" > <template v-if="hasActions" #actions> <bento-divider /> <div class="b-date-range-picker__footer"> <bento-button-actions :actions="rangePickerButtonActions" :layout="BentoButtonActionsLayout.SPACE_BETWEEN" /> </div> </template> </date-range-picker-calendar> </bento-popover> </div> </template> <script setup lang="ts"> import { computed, type HTMLAttributes, onMounted, ref, toRaw, toRef, useAttrs, watch } from 'vue'; import { useBreakpoints } from '@vueuse/core'; // Components import { BentoButtonActions, BentoButtonActionsLayout } from '../button'; import { BentoDivider } from '@/components/divider'; import { BentoPopover } from '@/components/popover'; import { BentoTypography } from '../typography'; import { DateRangePickerCalendar } from './components/date-range-picker-calendar'; import { DropdownInputDefault } from '@/components/dropdown/components'; import { ErrorMessage, FieldLabel } from '@/internal'; // Utils import { generateUid } from '@/core/utils/ts'; import { useI18n } from '@/utils/ts/i18n'; import { dateToTimeInputString } from '@/utils/ts/format-date'; import { useDateInputFormatter } from '@/composables/use-date-input-formatter/use-date-input-formatter'; // Composables import { useDateRangePickerCalendarText } from './composables/use-date-range-picker-calendar-text'; import { useFirstDayOfWeek, useFormLayoutFieldLoading } from '@/composables'; // Directives import { BentoClickOutsideDirective as vBentoClickOutsideDirective } from '@/directives'; // Types import { type BentoDateRangePickerEmits, type BentoDateRangePickerProps, type BentoDateRangePickerValue, } from './date-range-picker.types'; import { type DateRangePickerCalendarFormData, type DateRangePickerCalendarRangeSelectorItem, } from './components/date-range-picker-calendar/date-range-picker-calendar.types'; import { deprecate } from '@/utils/ts/deprecate'; import messages from './messages.json'; import { useFormFieldEmits } from '@/utils/ts/form-field/form-field'; type MessageSchema = (typeof messages)['en-US']; const emit = defineEmits<BentoDateRangePickerEmits>(); const props = withDefaults(defineProps<BentoDateRangePickerProps>(), { description: undefined, disabled: false, errorMessage: null, firstDayOfWeek: undefined, hasActions: false, isDateDisabled: undefined, label: undefined, max: null, maxRange: null, min: null, modelValue: null, numberOfMonths: undefined, optional: false, placeholder: null, quickSelectRanges: undefined, readonly: false, required: false, showEndDateOnOpen: false, tooltipText: null, value: null, variant: undefined, // Default will be set by @/internal/calendar yearRange: undefined, }); const attrs: HTMLAttributes = useAttrs(); const { t } = useI18n<{ message: MessageSchema }>({ messages }); const { dateToInputDateString } = useDateInputFormatter(); const datePickerContainerId = generateUid('bento-date-range-picker-container'); const labelId = generateUid('bento-date-range-picker-label'); const descriptionId = generateUid('bento-date-range-picker-description'); const errorId = generateUid('bento-date-range-picker-error'); const dateErrorId = generateUid('bento-date-range-picker-date-error'); const popoverAriaLabel = computed(() => props.label || t('dateRangePicker', { name: datePickerContainerId })); const { resolvedFirstDayOfWeek } = useFirstDayOfWeek(toRef(props, 'firstDayOfWeek')); const breakpoints = useBreakpoints({ none: 0, one: 710, // width of date range picker with one panes two: 1030, // width of date range picker with two panes three: 1290, // width of date range picker with three panes }); const computedNumberOfMonths = computed(() => { const activeBreakpoint = breakpoints.active().value; const breakpointToCalendarPanesMap = { none: 0, one: 1, two: 2, }; if (activeBreakpoint in breakpointToCalendarPanesMap) { const maxPanes = breakpointToCalendarPanesMap[activeBreakpoint as keyof typeof breakpointToCalendarPanesMap]; // If numberOfMonths is not provided, use the max panes for the breakpoint. // Otherwise, use the smaller of the two values. return props.numberOfMonths === undefined ? maxPanes : Math.min(props.numberOfMonths, maxPanes); } // Default case for 'three' and larger breakpoints, respect user-provided numberOfMonths. return props.numberOfMonths; }); const adjustedQuickSelectRanges = computed<Array<DateRangePickerCalendarRangeSelectorItem>>(() => { return ( props?.quickSelectRanges && props.quickSelectRanges.map((range: DateRangePickerCalendarRangeSelectorItem) => { const timeDiff = !range.data.endDate ? new Date(Date.now()).getTime() - range.data.startDate.getTime() : undefined; return { ...range, data: { ...range.data, // Adding timeDifference to calculate an up-to-date endDate on range selection timeDifference: timeDiff, }, }; }) ); }); const dropdownInputAriaLabelledBy = computed(() => (props.label ? labelId : (attrs?.['aria-labelledby'] ?? null))); const isDateIncorrect = ref(false); const inputContainerRef = ref(null); const isDateRangePickerOpen = ref(false); const { isReadOnly } = useFormLayoutFieldLoading(toRef(props, 'readonly')); const { emitValue } = useFormFieldEmits(emit); watch( () => props.disabled, () => { // Close date picker popover if it's opened when it's disabled if (props.disabled && isDateRangePickerOpen.value) { isDateRangePickerOpen.value = false; } } ); const internalModelValue = computed(() => props.modelValue || props.value); const shouldDisplayError = computed(() => { if (props.hasActions) { return !isApplyDisabled.value && isDateIncorrect.value; } return isDateIncorrect.value; }); const descriptionConditionalClasses = computed(() => ({ 'b-date-range-picker__description--error': !!props.errorMessage || shouldDisplayError.value, })); const isMonthVariant = computed(() => props.variant === 'month'); const withTimeInput = computed(() => props.allowTimeInput && props.variant !== 'month'); const { dateDisplayValue, textInputDisplayValue, formatTextInputDisplayValue } = useDateRangePickerCalendarText({ datePickerValue: internalModelValue, isDateIncorrect, quickSelectRanges: adjustedQuickSelectRanges, isMonthVariant, withTimeInput, placeholder: toRef(props, 'placeholder'), }); const ariaDescribedBy = computed( () => `${props.description ? descriptionId : ''} ${props.errorMessage ? errorId : ''} ${ isDateIncorrect.value ? dateErrorId : '' }`.trim() || null ); const closeDateRangePicker = () => { isDateRangePickerOpen.value = false; }; const openDateRangePicker = () => { isDateRangePickerOpen.value = true; formatTextInputDisplayValue(); }; /* * Used when `hasActions` is set to true */ const localDateDisplayValue = ref(dateDisplayValue.value); const localTextInputDisplayValue = ref<DateRangePickerCalendarFormData>( // textInputDisplayValue is a reactive object structuredClone(toRaw(textInputDisplayValue)) ); const selectedDate = ref<BentoDateRangePickerValue>(props.modelValue ?? props.value); const isApplyDisabled = ref(true); const dateRangePickerCalendarValue = computed(() => props.hasActions ? selectedDate.value : internalModelValue.value ); const displayedTextInputValue = ref<DateRangePickerCalendarFormData>( props.hasActions ? localTextInputDisplayValue.value : textInputDisplayValue ); const displayedDateValue = computed(() => props.hasActions ? localDateDisplayValue.value : dateDisplayValue.value ); const trapAllOptions = { allowOutsideClick: true, }; onMounted(() => { if (!props.hasActions) { deprecate( 'BentoDateRangePicker "hasActions" property', `Set the BentoDateRangePicker "hasActions" property true which will display the "Apply" and "Cancel" buttons. This will be the default behavior in the v2 and "hasActions" will be removed.`, '2.0.0' ); } if (props.value) { deprecate( 'BentoDateRangePicker "value" property', 'The use of "value" prop in "BentoDateRangePicker" is no longer supported. Use the "v-model" or "model-value" property instead.', '2.0.0' ); } if ( props.granularities && props.variant !== 'month' && props.granularities.find(({ type }) => type === 'quarterly') ) { throw new Error('Quarterly granularity is only supported by BentoDateRangePicker with variant: "month"'); } }); // Sync the local draft display state back to the committed model value. // Since `displayedTextInputValue` is a mutable ref, it must point to the new cloned draft object. const syncLocalDisplayStateFromCommittedValue = () => { localDateDisplayValue.value = dateDisplayValue.value; localTextInputDisplayValue.value = structuredClone(toRaw(textInputDisplayValue)); displayedTextInputValue.value = props.hasActions ? localTextInputDisplayValue.value : textInputDisplayValue; selectedDate.value = internalModelValue.value; }; // On update for the value we need to update all the locally stored data watch([() => props.value, () => props.modelValue], () => { syncLocalDisplayStateFromCommittedValue(); }); const cancelDateRangePicker = () => { // Reset local date range picker state syncLocalDisplayStateFromCommittedValue(); isDateIncorrect.value = false; isApplyDisabled.value = true; closeDateRangePicker(); }; const rangePickerButtonActions = computed(() => [ { title: t('apply'), event: () => { emitValue(selectedDate.value); closeDateRangePicker(); isApplyDisabled.value = true; }, disabled: isApplyDisabled.value, }, { title: t('cancel'), event: cancelDateRangePicker, }, ]); watch(selectedDate, date => { // skip emit until Apply button is clicked if (props.hasActions) { isApplyDisabled.value = isDateIncorrect.value || date === internalModelValue.value; return; } emitValue(date); }); const onDateRangePickerInput = (selectedRange: BentoDateRangePickerValue) => { // Reset the errors when the date is correct isDateIncorrect.value = false; displayedTextInputValue.value.startDate = dateToInputDateString(selectedRange.startDate); displayedTextInputValue.value.endDate = dateToInputDateString(selectedRange.endDate); displayedTextInputValue.value.startTime = dateToTimeInputString(selectedRange.startDate); displayedTextInputValue.value.endTime = dateToTimeInputString(selectedRange.endDate); selectedDate.value = selectedRange; }; const clearDateRangePickerValue = async () => { if (isDateIncorrect.value) { isDateIncorrect.value = false; } selectedDate.value = null; }; const onEnterPressedOverInput = () => { // open the datepicker if (!isDateRangePickerOpen.value) { isDateRangePickerOpen.value = true; return; } // Close the date picker if the date is correct and it is open if (!isDateIncorrect.value) { isDateRangePickerOpen.value = false; } }; const onDateRangePickerFormDateInputUpdate = (dateFormDate: DateRangePickerCalendarFormData) => { isDateIncorrect.value = false; if (dateFormDate.startTime) { displayedTextInputValue.value.startTime = dateFormDate.startTime; } if (dateFormDate.endTime) { displayedTextInputValue.value.endTime = dateFormDate.endTime; } }; const onDateRangePickerError = dateTextInput => { isDateIncorrect.value = true; displayedTextInputValue.value.startDate = dateTextInput.startDate; displayedTextInputValue.value.endDate = dateTextInput.endDate; // Remove current date range selection selectedDate.value = { startDate: null, endDate: null }; }; const onRangeSelectorInput = (quickSelectRanges?: BentoDateRangePickerValue) => { if (quickSelectRanges) { displayedTextInputValue.value.startDate = dateToInputDateString(quickSelectRanges.startDate); displayedTextInputValue.value.endDate = dateToInputDateString(quickSelectRanges.endDate); displayedTextInputValue.value.startTime = dateToTimeInputString(quickSelectRanges.startDate); displayedTextInputValue.value.endTime = dateToTimeInputString(quickSelectRanges.endDate); selectedDate.value = quickSelectRanges; } }; </script> <script lang="ts"> /** * Date range picker selector. * * @example * import { BentoDateRangePicker } from '@adyen/bento-vue2'; * * export default { * components: { BentoDateRangePicker }, * template: ` * <bento-date-range-picker * label="Label" * description="Supporting text" * required * optional * disabled * @input="onDateChanged" * v-model="selectedDate" * /> * `, * setup() { * const selectedDate = ref(new Date()); // reactive({ startDate: new Date(), endDate: new Date() }) * return { * selectedDate, * } * } * } */ export default { i18n: { messages }, name: 'bento-date-range-picker', model: { prop: 'modelValue' }, }; </script> <style lang="scss" scoped src="./date-range-picker.scss" />
|
|
1
|
+
<!-- TODO: Remove aria props as these are part of JSX elements /vue/types/jsx.d.ts --> <!-- eslint-disable vue/attribute-hyphenation --> <template> <div v-bento-click-outside-directive="closeDateRangePicker" class="b-date-range-picker"> <!-- Label --> <field-label v-if="label" :id="labelId" :label="label" :tooltip-text="tooltipText" :optional="optional" :required="required" /> <!-- Input --> <div ref="inputWrapperRef" @keydown.enter="onEnterPressedOverInput"> <dropdown-input-default ref="inputContainerRef" always-combobox-is-input :ariaControls="datePickerContainerId" :ariaExpanded="isDateRangePickerOpen" :aria-labelledby="dropdownInputAriaLabelledBy" :aria-describedby="ariaDescribedBy" :aria-required="required" :disabled="disabled" :is-invalid="!!errorMessage || shouldDisplayError" :open="isDateRangePickerOpen" :display-value="localDateDisplayValue" :readonly="isReadOnly" @open="openDateRangePicker" @clear="clearDateRangePickerValue" @close="closeDateRangePicker" /> </div> <!-- Error Messages --> <error-message v-if="shouldDisplayError" :id="dateErrorId" :error-message="t('invalidDateFormat')" class="b-date-range-picker__error-message" /> <error-message v-if="!!errorMessage" :id="errorId" :error-message="errorMessage" class="b-date-range-picker__error-message" /> <!-- Description --> <bento-typography v-if="description" :id="descriptionId" class="b-date-range-picker__description" :class="descriptionConditionalClasses" el="span" > {{ description }} </bento-typography> <!-- Calendar container --> <bento-popover v-if="inputContainerRef" :id="datePickerContainerId" class="b-date-range-picker__container" :open="isDateRangePickerOpen" :target-element="inputContainerRef" :aria-label="popoverAriaLabel" position="bottom-start" fallback-strategy="initialPlacement" without-space fit-content :match-width="shouldMatchPopoverWidth" trap-all overflow-visible :trap-all-options="trapAllOptions" @keydown.esc.native="closeDateRangePicker" > <date-range-picker-calendar :allow-time-input="withTimeInput" :quick-select-ranges="adjustedQuickSelectRanges" :value="selectedDate" :first-day-of-week="resolvedFirstDayOfWeek" :granularities="granularities" :is-date-disabled="isDateDisabled" :max-range="maxRange" :min="min" :max="max" :number-of-months="computedNumberOfMonths" :show-end-date-on-open="showEndDateOnOpen" :time-interval="resolvedTimeInterval" :date-form-data="displayedTextInputValue" :variant="variant" :year-range="yearRange" @custom-range="onRangeSelectorInput" @input="onDateRangePickerInput" @error="onDateRangePickerError" @form-date="onDateRangePickerFormDateInputUpdate" > <template #actions> <bento-divider /> <div class="b-date-range-picker__footer"> <bento-button-actions :actions="rangePickerButtonActions" :layout="BentoButtonActionsLayout.SPACE_BETWEEN" /> </div> </template> </date-range-picker-calendar> </bento-popover> </div> </template> <script setup lang="ts"> import { computed, type HTMLAttributes, onMounted, ref, toRaw, toRef, useAttrs, watch } from 'vue'; import { useBreakpoints, useElementSize } from '@vueuse/core'; // Components import { BentoButtonActions, BentoButtonActionsLayout } from '../button'; import { BentoDivider } from '@/components/divider'; import { BentoPopover } from '@/components/popover'; import { BentoTypography } from '../typography'; import { DateRangePickerCalendar } from './components/date-range-picker-calendar'; import { DropdownInputDefault } from '@/components/dropdown/components'; import { ErrorMessage, FieldLabel } from '@/internal'; // Utils import { generateUid } from '@/utils/ts/generate-uid'; import { useI18n } from '@/utils/ts/i18n'; import { printDevelopmentWarning } from '@/utils/ts/print-development-warning'; import { snapTimeToInterval } from '@/utils/ts/time-input'; import { dateToTimeInputString, setTimeInDateObject } from '@/utils/ts/format-date'; import { useDateInputFormatter } from '@/composables/use-date-input-formatter/use-date-input-formatter'; // Composables import { useDateRangePickerCalendarText } from './composables/use-date-range-picker-calendar-text'; import { useFirstDayOfWeek, useFormLayoutFieldLoading } from '@/composables'; // Directives import { BentoClickOutsideDirective as vBentoClickOutsideDirective } from '@/directives'; // Types import { type BentoDateRangePickerEmits, type BentoDateRangePickerProps, type BentoDateRangePickerValue, } from './date-range-picker.types'; import type { MaybeElement } from '@/types/elements'; import { type DateRangePickerCalendarFormData, type DateRangePickerCalendarRangeSelectorItem, } from './components/date-range-picker-calendar/date-range-picker-calendar.types'; import messages from './messages.json'; import { useFormFieldEmits } from '@/utils/ts/form-field/form-field'; type MessageSchema = (typeof messages)['en-US']; const emit = defineEmits<BentoDateRangePickerEmits>(); const props = withDefaults(defineProps<BentoDateRangePickerProps>(), { description: undefined, disabled: false, errorMessage: null, firstDayOfWeek: undefined, isDateDisabled: undefined, label: undefined, max: null, maxRange: null, min: null, modelValue: null, numberOfMonths: undefined, optional: false, placeholder: null, quickSelectRanges: undefined, readonly: false, required: false, showEndDateOnOpen: false, timeInterval: undefined, tooltipText: null, variant: undefined, // Default will be set by @/internal/calendar yearRange: undefined, }); const attrs: HTMLAttributes = useAttrs(); const { t } = useI18n<{ message: MessageSchema }>({ messages }); const { dateToInputDateString } = useDateInputFormatter(); const datePickerContainerId = generateUid('bento-date-range-picker-container'); const labelId = generateUid('bento-date-range-picker-label'); const descriptionId = generateUid('bento-date-range-picker-description'); const errorId = generateUid('bento-date-range-picker-error'); const dateErrorId = generateUid('bento-date-range-picker-date-error'); const popoverAriaLabel = computed(() => props.label || t('dateRangePicker', { name: datePickerContainerId })); const { resolvedFirstDayOfWeek } = useFirstDayOfWeek(toRef(props, 'firstDayOfWeek')); const breakpoints = useBreakpoints({ none: 0, one: 710, // width of date range picker with one panes two: 1030, // width of date range picker with two panes three: 1290, // width of date range picker with three panes }); const computedNumberOfMonths = computed(() => { const activeBreakpoint = breakpoints.active().value; const breakpointToCalendarPanesMap = { none: 0, one: 1, two: 2, }; if (activeBreakpoint in breakpointToCalendarPanesMap) { const maxPanes = breakpointToCalendarPanesMap[activeBreakpoint as keyof typeof breakpointToCalendarPanesMap]; // If numberOfMonths is not provided, use the max panes for the breakpoint. // Otherwise, use the smaller of the two values. return props.numberOfMonths === undefined ? maxPanes : Math.min(props.numberOfMonths, maxPanes); } // Default case for 'three' and larger breakpoints, respect user-provided numberOfMonths. return props.numberOfMonths; }); /** * Snaps the time of a date to the closest time selectable through the time interval. * @param date Date whose time is snapped * @param direction Whether to round the time down or up */ const snapDateTime = (date: Date, direction: 'down' | 'up') => setTimeInDateObject( date, snapTimeToInterval(dateToTimeInputString(date), resolvedTimeInterval.value, direction) ); const adjustedQuickSelectRanges = computed<Array<DateRangePickerCalendarRangeSelectorItem>>(() => { return ( props?.quickSelectRanges && props.quickSelectRanges.map((range: DateRangePickerCalendarRangeSelectorItem) => { const timeDiff = !range.data.endDate ? new Date(Date.now()).getTime() - range.data.startDate.getTime() : undefined; // Quick select ranges can hold times the time dropdowns do not offer, so they are // snapped to keep matching the range that gets selected from them const snappedDates = resolvedTimeInterval.value ? { startDate: snapDateTime(range.data.startDate, 'down'), ...(range.data.endDate ? { endDate: snapDateTime(range.data.endDate, 'up') } : {}), } : {}; return { ...range, data: { ...range.data, ...snappedDates, // Adding timeDifference to calculate an up-to-date endDate on range selection timeDifference: timeDiff, }, }; }) ); }); const dropdownInputAriaLabelledBy = computed(() => (props.label ? labelId : (attrs?.['aria-labelledby'] ?? null))); const isDateIncorrect = ref(false); const inputContainerRef = ref<MaybeElement>(null); const inputWrapperRef = ref<HTMLElement | null>(null); const isDateRangePickerOpen = ref(false); const { width: inputWrapperWidth } = useElementSize(inputWrapperRef); const { isReadOnly } = useFormLayoutFieldLoading(toRef(props, 'readonly')); const { emitValue } = useFormFieldEmits(emit); watch( () => props.disabled, () => { // Close date picker popover if it's opened when it's disabled if (props.disabled && isDateRangePickerOpen.value) { isDateRangePickerOpen.value = false; } } ); const shouldMatchPopoverWidth = computed( () => computedNumberOfMonths.value === 0 && inputWrapperWidth.value > 0 && inputWrapperWidth.value < 332 // size of date range picker form ); const internalModelValue = computed(() => props.modelValue); const shouldDisplayError = computed(() => !isApplyDisabled.value && isDateIncorrect.value); const descriptionConditionalClasses = computed(() => ({ 'b-date-range-picker__description--error': !!props.errorMessage || shouldDisplayError.value, })); const isMonthVariant = computed(() => props.variant === 'month'); const withTimeInput = computed(() => props.allowTimeInput && props.variant !== 'month'); const resolvedTimeInterval = computed(() => (withTimeInput.value ? props.timeInterval : null)); watch( [() => props.timeInterval, () => props.allowTimeInput], ([timeInterval, allowTimeInput]) => { if (timeInterval && !allowTimeInput) { printDevelopmentWarning( '"bento-date-range-picker" ignores the "timeInterval" prop unless "allowTimeInput" is enabled.' ); } }, { immediate: true } ); const { dateDisplayValue, textInputDisplayValue, formatTextInputDisplayValue } = useDateRangePickerCalendarText({ datePickerValue: internalModelValue, isDateIncorrect, quickSelectRanges: adjustedQuickSelectRanges, isMonthVariant, withTimeInput, placeholder: toRef(props, 'placeholder'), }); const ariaDescribedBy = computed( () => `${props.description ? descriptionId : ''} ${props.errorMessage ? errorId : ''} ${ isDateIncorrect.value ? dateErrorId : '' }`.trim() || null ); const closeDateRangePicker = () => { isDateRangePickerOpen.value = false; }; const openDateRangePicker = () => { isDateRangePickerOpen.value = true; formatTextInputDisplayValue(); }; const localDateDisplayValue = ref(dateDisplayValue.value); const localTextInputDisplayValue = ref<DateRangePickerCalendarFormData>( // textInputDisplayValue is a reactive object structuredClone(toRaw(textInputDisplayValue)) ); const selectedDate = ref<BentoDateRangePickerValue>(props.modelValue); const isApplyDisabled = ref(true); const displayedTextInputValue = ref<DateRangePickerCalendarFormData>(localTextInputDisplayValue.value); const trapAllOptions = { allowOutsideClick: true, }; onMounted(() => { if ( props.granularities && props.variant !== 'month' && props.granularities.find(({ type }) => type === 'quarterly') ) { throw new Error('Quarterly granularity is only supported by BentoDateRangePicker with variant: "month"'); } }); // Sync the local draft display state back to the committed model value. // Since `displayedTextInputValue` is a mutable ref, it must point to the new cloned draft object. const syncLocalDisplayStateFromCommittedValue = () => { localDateDisplayValue.value = dateDisplayValue.value; localTextInputDisplayValue.value = structuredClone(toRaw(textInputDisplayValue)); displayedTextInputValue.value = localTextInputDisplayValue.value; selectedDate.value = internalModelValue.value; }; // On update for the model value we need to update all the locally stored data watch( () => props.modelValue, () => { syncLocalDisplayStateFromCommittedValue(); } ); const cancelDateRangePicker = () => { // Reset local date range picker state syncLocalDisplayStateFromCommittedValue(); isDateIncorrect.value = false; isApplyDisabled.value = true; closeDateRangePicker(); }; const rangePickerButtonActions = computed(() => [ { title: t('apply'), event: () => { emitValue(selectedDate.value); closeDateRangePicker(); isApplyDisabled.value = true; }, disabled: isApplyDisabled.value, }, { title: t('cancel'), event: cancelDateRangePicker, }, ]); watch(selectedDate, date => { isApplyDisabled.value = isDateIncorrect.value || date === internalModelValue.value; }); const onDateRangePickerInput = (selectedRange: BentoDateRangePickerValue) => { // Reset the errors when the date is correct isDateIncorrect.value = false; displayedTextInputValue.value.startDate = dateToInputDateString(selectedRange.startDate); displayedTextInputValue.value.endDate = dateToInputDateString(selectedRange.endDate); displayedTextInputValue.value.startTime = dateToTimeInputString(selectedRange.startDate); displayedTextInputValue.value.endTime = dateToTimeInputString(selectedRange.endDate); selectedDate.value = selectedRange; }; const clearDateRangePickerValue = async () => { if (isDateIncorrect.value) { isDateIncorrect.value = false; } selectedDate.value = null; }; const onEnterPressedOverInput = () => { // open the datepicker if (!isDateRangePickerOpen.value) { isDateRangePickerOpen.value = true; return; } // Close the date picker if the date is correct and it is open if (!isDateIncorrect.value) { isDateRangePickerOpen.value = false; } }; const onDateRangePickerFormDateInputUpdate = (dateFormDate: DateRangePickerCalendarFormData) => { isDateIncorrect.value = false; if (dateFormDate.startTime) { displayedTextInputValue.value.startTime = dateFormDate.startTime; } if (dateFormDate.endTime) { displayedTextInputValue.value.endTime = dateFormDate.endTime; } }; const onDateRangePickerError = dateTextInput => { isDateIncorrect.value = true; displayedTextInputValue.value.startDate = dateTextInput.startDate; displayedTextInputValue.value.endDate = dateTextInput.endDate; // Remove current date range selection selectedDate.value = { startDate: null, endDate: null }; }; const onRangeSelectorInput = (quickSelectRanges?: BentoDateRangePickerValue) => { if (quickSelectRanges) { displayedTextInputValue.value.startDate = dateToInputDateString(quickSelectRanges.startDate); displayedTextInputValue.value.endDate = dateToInputDateString(quickSelectRanges.endDate); displayedTextInputValue.value.startTime = dateToTimeInputString(quickSelectRanges.startDate); displayedTextInputValue.value.endTime = dateToTimeInputString(quickSelectRanges.endDate); selectedDate.value = quickSelectRanges; } }; </script> <script lang="ts"> /** * Date range picker selector. * * @example * import { BentoDateRangePicker } from '@adyen/bento-vue2'; * * export default { * components: { BentoDateRangePicker }, * template: ` * <bento-date-range-picker * label="Label" * description="Supporting text" * required * optional * disabled * v-model="selectedDate" * /> * `, * setup() { * const selectedDate = ref(new Date()); // reactive({ startDate: new Date(), endDate: new Date() }) * return { * selectedDate, * } * } * } */ export default { i18n: { messages }, name: 'bento-date-range-picker', model: { prop: 'modelValue', event: 'update:model-value' }, }; </script> <style lang="scss" scoped src="./date-range-picker.scss" />
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import { action } from '@storybook/addon-actions'; import { isVue2 } from 'vue-demi'; import { ref } from 'vue'; import { storybookDocsParameter } from '@/utils/ts/storybook'; import BentoDateTimePicker from './date-time-picker.vue'; import type { Meta, StoryObj } from '@storybook/vue'; import type { BentoDateTimePickerValue } from './date-time-picker.types';
|
|
1
|
+
import { action } from '@storybook/addon-actions'; import { isVue2 } from 'vue-demi'; import { ref } from 'vue'; import { storybookDocsParameter } from '@/utils/ts/storybook'; import BentoDateTimePicker from './date-time-picker.vue'; import type { Meta, StoryObj } from '@storybook/vue'; import type { BentoDateTimePickerValue } from './date-time-picker.types'; const DateTimePickerDefault = "<template>\n <bento-date-time-picker v-model=\"internalModelValue\" :date-props=\"dateProps\" :time-props=\"timeProps\" />\n</template>\n\n<script setup lang=\"ts\">\n import { BentoDateTimePicker, type BentoDateTimePickerProps } from '@adyen/bento-vue2';\n import { ref } from 'vue';\n import type { BentoDateTimePickerValue } from '@adyen/bento-vue2';\n\n const internalModelValue = ref<BentoDateTimePickerValue>(new Date(2026, 4, 1, 0, 0, 0));\n\n const dateProps: BentoDateTimePickerProps['dateProps'] = { label: 'Date' };\n const timeProps: BentoDateTimePickerProps['timeProps'] = { label: 'Time' };\n</script>\n"; const meta: Meta = { title: 'Date time picker', component: BentoDateTimePicker, }; export default meta; type Story = StoryObj<typeof BentoDateTimePicker>; export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoDateTimePicker }, props: Object.keys(argTypes), template: ` <div style="max-width: 600px"> <bento-date-time-picker v-bind="args" :model-value="modelValue" @update:model-value="onValueChanged" @input:error="onInputError" @input:valid="onInputValid" /> </div> `, setup(props) { const modelValue = ref<BentoDateTimePickerValue>(props.modelValue ?? null); const onValueChanged = (newValue: BentoDateTimePickerValue) => { modelValue.value = newValue; action('update:model-value')(newValue); }; return { args: isVue2 ? props : _args, modelValue, onInputError: action('input:error'), onInputValid: action('input:valid'), onValueChanged, }; }, }), args: { dateProps: { label: 'Date', description: 'Supporting text', required: true, }, timeProps: { label: 'Time', }, modelValue: new Date(2026, 4, 1, 10, 30, 0), }, parameters: storybookDocsParameter(DateTimePickerDefault), }; export const WithSharedError: Story = { ...Default, args: { ...Default.args, errorMessage: 'The selected date and time must be in the future', }, }; export const Disabled: Story = { ...Default, args: { ...Default.args, disabled: true, }, };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import type { BentoDatePickerIsDateDisabled, BentoDatePickerProps } from '@/components/date-picker'; import type { BentoInputTimeProps } from '@/components/input-time'; export type BentoDateTimePickerValue = Date | null; export interface BentoDateTimePickerEmits { /** * Emitted when the component's model changes */ (e: 'update:model-value', value: BentoDateTimePickerValue): void; /** * Emitted when the combined date and time value passes validation. */ (e: 'input:valid'): void; /** * Emitted when the combined date and time value fails validation. */ (e: 'input:error'): void; } export interface BentoDateTimePickerProps { /** * The combined date and time value. * @default null */ modelValue?: BentoDateTimePickerValue; /** * If true, disables both fields unless overridden by `dateProps` or `timeProps`. * @default false */ disabled?: boolean; /** * If set, displays a shared error message and applies error styling to both fields. * @default null */ errorMessage?: string | null; /** * Indicate if a date should be disabled or not. */ isDateDisabled?: BentoDatePickerIsDateDisabled; /** * Sets a maximum date-time value. * @default null */ max?: Date | null; /** * Sets a minimum date-time value. * @default null */ min?: Date | null; /** * If true, makes both fields readonly unless overridden by `dateProps` or `timeProps`. * @default false */ readonly?: boolean; /** * If true, marks both fields required unless overridden by `dateProps` or `timeProps`. * @default false */ required?: boolean; /** * Props passed directly to the inner `bento-date-picker`. * Accepts all `BentoDatePickerProps` except `modelValue
|
|
1
|
+
import type { BentoDatePickerIsDateDisabled, BentoDatePickerProps } from '@/components/date-picker'; import type { BentoInputTimeProps } from '@/components/input-time'; export type BentoDateTimePickerValue = Date | null; export interface BentoDateTimePickerEmits { /** * Emitted when the component's model changes */ (e: 'update:model-value', value: BentoDateTimePickerValue): void; /** * Emitted when the combined date and time value passes validation. */ (e: 'input:valid'): void; /** * Emitted when the combined date and time value fails validation. */ (e: 'input:error'): void; } export interface BentoDateTimePickerProps { /** * The combined date and time value. * @default null */ modelValue?: BentoDateTimePickerValue; /** * If true, disables both fields unless overridden by `dateProps` or `timeProps`. * @default false */ disabled?: boolean; /** * If set, displays a shared error message and applies error styling to both fields. * @default null */ errorMessage?: string | null; /** * Indicate if a date should be disabled or not. */ isDateDisabled?: BentoDatePickerIsDateDisabled; /** * Sets a maximum date-time value. * @default null */ max?: Date | null; /** * Sets a minimum date-time value. * @default null */ min?: Date | null; /** * If true, makes both fields readonly unless overridden by `dateProps` or `timeProps`. * @default false */ readonly?: boolean; /** * If true, marks both fields required unless overridden by `dateProps` or `timeProps`. * @default false */ required?: boolean; /** * Props passed directly to the inner `bento-date-picker`. * Accepts all `BentoDatePickerProps` except `modelValue`. * @default {} */ dateProps?: Omit<BentoDatePickerProps, 'modelValue'>; /** * Props passed directly to the inner `bento-input-time`. * Accepts all `BentoInputTimeProps` except `modelValue`. * @default {} */ timeProps?: Omit<BentoInputTimeProps, 'modelValue'>; }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const BentoDividerVariantOptions = ['horizontal', 'vertical'] as const;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import BentoDivider from './divider.vue'; import type { Meta, StoryObj } from '@storybook/vue'; import { isVue2 } from 'vue-demi'; import { storybookDocsParameter } from '@/utils/ts/storybook'; import {
|
|
1
|
+
import BentoDivider from './divider.vue'; import type { Meta, StoryObj } from '@storybook/vue'; import { isVue2 } from 'vue-demi'; import { storybookDocsParameter } from '@/utils/ts/storybook'; const DividerDefault = "<template>\n <bento-divider />\n</template>\n\n<script setup lang=\"ts\">\n import { BentoDivider } from '@adyen/bento-vue2';\n</script>\n"; import { BentoDividerVariantOptions } from './divider.internal.types'; const meta: Meta = { title: 'Divider', component: BentoDivider, argTypes: { variant: { options: BentoDividerVariantOptions, table: { defaultValue: { summary: 'horizontal' }, }, control: { type: 'select', }, }, }, }; export default meta; type Story = StoryObj<typeof BentoDivider>; export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoDivider }, props: Object.keys(argTypes), template: ` <div style="height: 50px;"> <bento-divider v-bind="args" /> </div> `, setup(props) { return { args: isVue2 ? props : _args, }; }, }), parameters: storybookDocsParameter(DividerDefault), };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
import type { BentoDividerVariantOptions } from './divider.internal.types'; export type BentoDividerVariant = (typeof BentoDividerVariantOptions)[number]; export interface BentoDividerProps { /** * Renders the divider in a horizontal or vertical variant */ variant?: BentoDividerVariant; }
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<template> <div class="b-divider"> <div v-if="isVerticalVariant" role="separator" aria-orientation="vertical" class="b-divider__rule b-divider__rule--vertical" :class="conditionalClasses" ></div> <hr v-else class="b-divider__rule b-divider__rule--horizontal" :class="conditionalClasses" /> </div> </template> <script setup lang="ts"> import { computed, inject, ref } from 'vue'; import { DIVIDER_INVERSE_INJECTION_KEY } from './divider.keys'; import { type BentoDividerProps
|
|
1
|
+
<template> <div class="b-divider"> <div v-if="isVerticalVariant" role="separator" aria-orientation="vertical" class="b-divider__rule b-divider__rule--vertical" :class="conditionalClasses" ></div> <hr v-else class="b-divider__rule b-divider__rule--horizontal" :class="conditionalClasses" /> </div> </template> <script setup lang="ts"> import { computed, inject, ref } from 'vue'; import { DIVIDER_INVERSE_INJECTION_KEY } from './divider.keys'; import { type BentoDividerProps } from './divider.types'; const props = withDefaults(defineProps<BentoDividerProps>(), { variant: 'horizontal', }); const isInverseColorSet = inject(DIVIDER_INVERSE_INJECTION_KEY, ref(false)); const isVerticalVariant = computed(() => props.variant === 'vertical'); const conditionalClasses = computed(() => ({ 'b-divider--inverse': isInverseColorSet.value, })); </script> <script lang="ts"> /** * Divider component is a thin line that helps separate content and group them visually into sections. * * @example * import { BentoDivider } from '@adyen/bento-vue2'; * * export default { * components: { BentoDivider }, * template: `<bento-divider />` * } */ export default {}; </script> <style lang="scss" scoped src="./divider.scss" />
|
package/dist/assets/components/draggable/components/draggable-handle/draggable-handle.stories.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import { action } from '@storybook/addon-actions'; import { isVue2 } from 'vue-demi'; import { storybookDocsParameter } from '@/utils/ts/storybook'; import type { Meta, StoryObj } from '@storybook/vue'; import BentoDraggableHandle from './draggable-handle.vue';
|
|
1
|
+
import { action } from '@storybook/addon-actions'; import { isVue2 } from 'vue-demi'; import { storybookDocsParameter } from '@/utils/ts/storybook'; import type { Meta, StoryObj } from '@storybook/vue'; import BentoDraggableHandle from './draggable-handle.vue'; const DraggableHandleDefaultExample = "<template>\n <bento-draggable-handle\n label=\"Draggable element\"\n variant=\"all-directions\"\n @move-up=\"moveUp\"\n @move-down=\"moveDown\"\n @move-right=\"moveRight\"\n @move-left=\"moveLeft\"\n @move-top=\"moveTop\"\n @move-bottom=\"moveBottom\"\n />\n</template>\n\n<script setup lang=\"ts\">\n /* eslint-disable @typescript-eslint/no-empty-function */\n import { BentoDraggableHandle } from '../index';\n\n const moveUp = () => {};\n const moveDown = () => {};\n const moveRight = () => {};\n const moveLeft = () => {};\n const moveTop = () => {};\n const moveBottom = () => {};\n</script>\n"; const meta: Meta = { title: 'Draggable / Draggable handle', component: BentoDraggableHandle, argTypes: { condensed: { control: 'boolean', }, }, }; export default meta; type Story = StoryObj<typeof BentoDraggableHandle>; const DEFAULT_LABEL = 'Draggable element'; export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoDraggableHandle }, props: Object.keys(argTypes), template: ` <bento-draggable-handle v-bind="args" @move-up="clickedAction('move-up')" @move-down="clickedAction('move-down')" @move-right="clickedAction('move-right')" @move-left="clickedAction('move-left')" @move-top="clickedAction('move-top')" @move-bottom="clickedAction('move-bottom')" /> `, setup(props) { const clickedAction = (actionName: string) => action(actionName)(); return { // Values args: isVue2 ? props : _args, // Events clickedAction, }; }, }), args: { label: DEFAULT_LABEL, }, parameters: storybookDocsParameter(DraggableHandleDefaultExample), };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from '@storybook/vue'; import { type BentoDraggableItems, type BentoDraggableOptions, type BentoDraggableProps } from './draggable.types'; import { action } from '@storybook/addon-actions'; import { isVue2 } from 'vue-demi'; import { ref } from 'vue'; import { storybookDocsParameter } from '@/utils/ts/storybook'; import { BentoDraggableHandle } from './components/draggable-handle'; import { BentoTypography } from '../typography'; import { BentoButton } from '@/components/button'; import BinIcon from '@adyen/ui-assets-icons-16/vue/bin'; import BentoDraggable from './draggable.vue'; import BentoDraggableDefaultExample from './__tests__/bento-draggable-default-example.vue?raw'; import BentoDraggableNestedExample from './__tests__/bento-draggable-nested-example.vue?raw'; import BentoDraggableGridExample from './__tests__/bento-draggable-grid-example.vue?raw'; import BentoDraggableBoardExample from './__tests__/bento-draggable-board-example.vue?raw'; import './draggable.stories.scss?module'; const meta: Meta = { title: 'Draggable', component: BentoDraggable, }; export default meta; type Story = StoryObj<typeof BentoDraggable>; const ITEMS: BentoDraggableItems = [ { id: '1', label: 'Item 1' }, { id: '2', label: 'Item 2' }, { id: '3', label: 'Item 3' }, { id: '4', label: 'Item 4' }, { id: '5', label: 'Item 5' }, { id: '6', label: 'Item 6' }, ]; export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoDraggable, BentoDraggableHandle, BentoTypography }, props: Object.keys(argTypes), template: ` <bento-draggable v-bind="args" :items="itemsList" class="b-draggable-default__container" @update:items="onUpdateItems" > <template #default="{item, moveUp, moveDown, moveTop, moveBottom, disabledMenus}"> <div class="b-draggable-default__item" :key="item.id"> <div :style="{ display: 'flex', alignItems: 'center'}"> <bento-draggable-handle :label="item.label" condensed :disabled="item.label === 'Item 2'" :disabled-menus="disabledMenus" variant="up-and-down" @move-up="moveUp" @move-down="moveDown" @move-top="moveTop" @move-bottom="moveBottom" /> <bento-typography>{{ item.label }}</bento-typography> </div> </div> </template> </bento-draggable> `, setup(props) { const args = isVue2 ? props : _args; const itemsList = ref(args.items); const onUpdateItems = (items: BentoDraggableItems) => { itemsList.value = [...items]; action('update:items')(items); }; return { args: isVue2 ? props : _args, itemsList, onUpdateItems, }; }, }), args: { items: ITEMS, options: { group: 'default', }, }, parameters: storybookDocsParameter(BentoDraggableDefaultExample), }; const NESTED_ITEMS: BentoDraggableItems = [ { id: '1.1', label: 'Item 1.1', children: [ { id: '2.1', label: 'Item 2.1' }, { id: '2.2', label: 'Item 2.2', children: [ { id: '3.1', label: 'Item 3.1' }, { id: '3.2', label: 'Item 3.2' }, ], }, { id: '2.3', label: 'Item 2.3' }, ], }, { id: '1.2', label: 'Item 1.2' }, { id: '1.3', label: 'Item 1.3' }, ]; export const Nested: Story = { render: (_args, { argTypes }) => ({ components: { BentoDraggable, BentoDraggableHandle, BentoTypography }, props: Object.keys(argTypes), template: ` <bento-draggable v-bind="args" :items="itemsList" class="b-draggable-nested__container" @update:items="itemsList = $event" > <template #default="{item, moveUp, moveDown, moveTop, moveBottom, disabledMenus}"> <div class="b-draggable-nested__item" :key="item.id"> <div :style="{ display: 'flex', alignItems: 'center'}"> <bento-draggable-handle :label="item.label" condensed variant="up-and-down" :disabled-menus="disabledMenus" @move-up="moveUp" @move-down="moveDown" @move-top="moveTop" @move-bottom="moveBottom" /> <bento-typography>{{ item.label }}</bento-typography> </div> <!-- Children --> <template v-if="item.children"> <bento-draggable class="b-draggable-nested__container" :options="args.options" :items="item.children" @update:items="item.children = $event" > <template v-slot:default="{item: childItem, moveUp: moveUpChild, moveDown: moveDownChild, moveTop: moveTopChild, moveBottom: moveBottomChild, disabledMenus: disabledMenusChild}"> <div class="b-draggable-nested__item" :key="childItem.id"> <div :style="{ display: 'flex', alignItems: 'center'}"> <bento-draggable-handle :label="childItem.label" condensed variant="up-and-down" :disabled-menus="disabledMenusChild" @move-up="moveUpChild" @move-down="moveDownChild" @move-top="moveTopChild" @move-bottom="moveBottomChild" /> <bento-typography>{{ childItem.label }}</bento-typography> </div> </div> </template> </bento-draggable> </template> </div> </template> </bento-draggable> `, setup(props) { const args = isVue2 ? props : _args; const itemsList = ref(args.items); return { args, itemsList, }; }, }), args: { items: NESTED_ITEMS, options: {}, } as BentoDraggableProps, parameters: storybookDocsParameter(BentoDraggableNestedExample), }; export const Grid: Story = { render: (_args, { argTypes }) => ({ components: { BentoButton, BentoDraggable, BentoDraggableHandle, BentoTypography, BinIcon }, props: Object.keys(argTypes), template: ` <bento-draggable ref="draggableRef" v-bind="args" :items="itemsList" @update:items="itemsList = $event" :style="{ padding: 'var(--b-spacer-050)', width: '670px', display: 'flex', flexDirection: 'row', flexWrap: 'wrap', gap: 'var(--b-spacer-050)', }" > <template #default="{item, index, moveTop, moveBottom, moveLeft, moveRight, disabledMenus}"> <div :key="item.id" :style="{ width: '160px' }"> <div :style="{ display: 'flex', background: 'var(--b-color-background-tertiary)', borderTopLeftRadius: 'var(--b-border-radius-m)', borderTopRightRadius: 'var(--b-border-radius-m)', justifyContent: 'space-between', padding: 'var(--b-spacer-010) var(--b-spacer-020)', }" > <bento-draggable-handle :label="item.label" :disabled-menus="{ ...disabledMenus, up: index < COLUMNS, down: index + COLUMNS >= itemsList.length, left: index % COLUMNS === 0, right: index % COLUMNS === COLUMNS - 1 || index === itemsList.length - 1, }" condensed variant="all-directions" @move-up="gridMoveUp(index)" @move-down="gridMoveDown(index)" @move-top="moveTop" @move-bottom="moveBottom" @move-left="moveLeft" @move-right="moveRight" /> <bento-button condensed variant="secondary"> <template #iconLeft> <bin-icon svg-title="remove" /> </template> </bento-button> </div> <div :style="{ backgroundColor: 'var(--b-color-background-secondary)', display: 'flex', flexDirection: 'column', borderBottomLeftRadius: 'var(--b-border-radius-m)', borderBottomRightRadius: 'var(--b-border-radius-m)', gap: 'var(--b-spacer-060)', padding: 'var(--b-spacer-040)', }" > <bento-typography>Card {{ item.id }}</bento-typography> </div> </div> </template> </bento-draggable> `, setup(props) { const args = isVue2 ? props : _args; const itemsList = ref(args.items); const draggableRef = ref(null); const COLUMNS = 4; const gridMoveUp = (index: number) => { draggableRef.value?.moveItem(index, index - COLUMNS); }; const gridMoveDown = (index: number) => { draggableRef.value?.moveItem(index, index + COLUMNS); }; return { args, itemsList, draggableRef, COLUMNS, gridMoveUp, gridMoveDown, }; }, }), args: { items: ITEMS, options: {}, }, parameters: storybookDocsParameter(BentoDraggableGridExample), }; export const Board: Story = { render: (_args, { argTypes }) => ({ components: { BentoDraggable, BentoDraggableHandle, BentoTypography }, props: Object.keys(argTypes), template: ` <div :style="{ display: 'flex', gap: 'var(--b-spacer-100)', alignItems: 'flex-start', padding: 'var(--b-spacer-080)', backgroundColor: 'var(--b-color-background-tertiary)', minHeight: '400px' }"> <!-- TODO Column --> <div :style="{ flex: 1, background: 'var(--b-color-background-secondary)', padding: 'var(--b-spacer-040)', borderRadius: 'var(--b-border-radius-m)', boxShadow: 'var(--b-shadow-small)' }"> <bento-typography variant="title" :style="{ marginBottom: 'var(--b-spacer-040)', padding: 'var(--b-spacer-020) var(--b-spacer-040)', display: 'block' }"> TODO </bento-typography> <bento-draggable ref="todoDraggableRef" :items="todoItems" :options="toDoOptions" @update:items="todoItems = $event" class="b-draggable-board__container" style="min-height: 50px;" > <template #default="{ item, index, moveUp, moveDown, moveTop, moveBottom, disabledMenus }"> <div class="b-draggable-board__item" :key="item.id"> <div :style="{ display: 'flex', alignItems: 'center' }"> <bento-draggable-handle :label="item.label" condensed variant="all-directions" :disabled-menus="{ ...disabledMenus, right: false, left: true, }" @move-up="moveUp" @move-down="moveDown" @move-top="moveTop" @move-bottom="moveBottom" @move-right="moveToDone(index)" /> <bento-typography>{{ item.label }}</bento-typography> </div> </div> </template> </bento-draggable> </div> <!-- DONE Column --> <div :style="{ flex: 1, background: 'var(--b-color-background-secondary)', padding: 'var(--b-spacer-040)', borderRadius: 'var(--b-border-radius-m)', boxShadow: 'var(--b-shadow-small)' }"> <bento-typography variant="title" :style="{ marginBottom: 'var(--b-spacer-040)', padding: 'var(--b-spacer-020) var(--b-spacer-040)', display: 'block' }"> DONE </bento-typography> <bento-draggable ref="doneDraggableRef" :items="doneItems" :options="doneOptions" @update:items="doneItems = $event" class="b-draggable-board__container" style="min-height: 50px;" > <template #default="{ item, index, moveUp, moveDown, moveTop, moveBottom, moveLeft, disabledMenus }"> <div class="b-draggable-board__item" :key="item.id"> <div :style="{ display: 'flex', alignItems: 'center' }"> <bento-draggable-handle :label="item.label" condensed variant="all-directions" :disabled-menus="{ ...disabledMenus, left: false, }" @move-up="moveUp" @move-down="moveDown" @move-top="moveTop" @move-bottom="moveBottom" @move-left="moveToTodo(index)" /> <bento-typography>{{ item.label }}</bento-typography> </div> </div> </template> </bento-draggable> </div> </div> `, setup() { const todoItems = ref<BentoDraggableItems>([ { id: '1', label: 'Improve documentation' }, { id: '2', label: 'Fix navigation bug' }, { id: '3', label: 'Add unit tests' }, ]); const doneItems = ref<BentoDraggableItems>([{ id: '4', label: 'Initial release' }]); const todoDraggableRef = ref<InstanceType<typeof BentoDraggable> | null>(null); const doneDraggableRef = ref<InstanceType<typeof BentoDraggable> | null>(null); // Since we're moving items between completely separate refs, we track the item being dragged // manually to facilitate transfers between the todoItems and doneItems lists. let draggedItem: BentoDraggableItems[number] | null = null; const moveToDone = (index: number) => { const [item] = todoItems.value.splice(index, 1); doneItems.value.push(item); doneDraggableRef.value?.announce( `${item.label} moved to Done, position ${doneItems.value.length} of ${doneItems.value.length}` ); action('move-to-done')(item, { todo: todoItems.value, done: doneItems.value }); }; const moveToTodo = (index: number) => { const [item] = doneItems.value.splice(index, 1); todoItems.value.push(item); todoDraggableRef.value?.announce( `${item.label} moved to Todo, position ${todoItems.value.length} of ${todoItems.value.length}` ); action('move-to-todo')(item, { todo: todoItems.value, done: doneItems.value }); }; const toDoOptions: BentoDraggableOptions = { group: 'board', animation: 150, onStart: event => { draggedItem = todoItems.value[event.oldIndex!]; }, onAdd: event => { todoItems.value.splice(event.newIndex!, 0, draggedItem!); todoDraggableRef.value?.announce( `${draggedItem!.label} added to Todo, position ${event.newIndex! + 1} of ${todoItems.value.length}` ); }, onRemove: event => { todoItems.value.splice(event.oldIndex!, 1); }, onSort: event => { const { oldIndex, newIndex, from, to } = event; if (from === to && oldIndex !== undefined && newIndex !== undefined) { const [item] = todoItems.value.splice(oldIndex, 1); todoItems.value.splice(newIndex, 0, item); todoDraggableRef.value?.announce( `${item.label} moved to position ${newIndex + 1} of ${todoItems.value.length} in Todo` ); } }, }; const doneOptions: BentoDraggableOptions = { group: 'board', animation: 150, onStart: event => { draggedItem = doneItems.value[event.oldIndex!]; }, onAdd: event => { doneItems.value.splice(event.newIndex!, 0, draggedItem!); doneDraggableRef.value?.announce( `${draggedItem!.label} added to Done, position ${event.newIndex! + 1} of ${doneItems.value.length}` ); }, onRemove: event => { doneItems.value.splice(event.oldIndex!, 1); }, onSort: event => { const { oldIndex, newIndex, from, to } = event; if (from === to && oldIndex !== undefined && newIndex !== undefined) { const [item] = doneItems.value.splice(oldIndex, 1); doneItems.value.splice(newIndex, 0, item); doneDraggableRef.value?.announce( `${item.label} moved to position ${newIndex + 1} of ${doneItems.value.length} in Done` ); action('sort-done')(event, doneItems.value); } }, }; return { todoItems, doneItems, toDoOptions, doneOptions, moveToDone, moveToTodo, todoDraggableRef, doneDraggableRef, }; }, }), parameters: storybookDocsParameter(BentoDraggableBoardExample), };
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue'; import { type BentoDraggableItems, type BentoDraggableOptions, type BentoDraggableProps } from './draggable.types'; import { action } from '@storybook/addon-actions'; import { isVue2 } from 'vue-demi'; import { ref } from 'vue'; import { storybookDocsParameter } from '@/utils/ts/storybook'; import { BentoDraggableHandle } from './components/draggable-handle'; import { BentoTypography } from '../typography'; import { BentoButton } from '@/components/button'; import BinIcon from '@adyen/ui-assets-icons-16/vue/bin'; import BentoDraggable from './draggable.vue'; const BentoDraggableDefaultExample = "<template>\n <bento-draggable\n :items=\"itemsList\"\n class=\"b-draggable-default__container\"\n :options=\"options\"\n @update:items=\"itemsList = $event\"\n >\n <template #default=\"{ item, moveUp, moveDown, moveTop, moveBottom, disabledMenus }\">\n <div :key=\"item.id\" class=\"b-draggable-default__item\">\n <div :style=\"{ display: 'flex', alignItems: 'center' }\">\n <bento-draggable-handle\n :label=\"item.label\"\n condensed\n :disabled=\"item.label === 'Item 2'\"\n :disabled-menus=\"disabledMenus\"\n variant=\"up-and-down\"\n @move-up=\"moveUp\"\n @move-down=\"moveDown\"\n @move-top=\"moveTop\"\n @move-bottom=\"moveBottom\"\n />\n <bento-typography>{{ item.label }}</bento-typography>\n </div>\n </div>\n </template>\n </bento-draggable>\n</template>\n\n<script setup lang=\"ts\">\n import { ref } from 'vue';\n import {\n BentoDraggable,\n BentoDraggableHandle,\n type BentoDraggableItems,\n type BentoDraggableOptions,\n BentoTypography,\n } from '@adyen/bento-vue2';\n\n const itemsList = ref<BentoDraggableItems>([\n { id: '1', label: 'Item 1' },\n { id: '2', label: 'Item 2' },\n { id: '3', label: 'Item 3' },\n { id: '4', label: 'Item 4' },\n { id: '5', label: 'Item 5' },\n { id: '6', label: 'Item 6' },\n ]);\n\n const options: BentoDraggableOptions = {\n group: 'default',\n };\n</script>\n\n<style lang=\"scss\" scoped>\n .b-draggable-default {\n &__container {\n display: flex;\n flex-direction: column;\n gap: var(--b-spacer-040);\n max-width: 500px;\n }\n\n &__item {\n background-color: var(--b-color-background-primary);\n border: var(--b-border-width-s) solid var(--b-color-outline-primary);\n display: flex;\n flex-direction: column;\n gap: var(--b-spacer-020);\n padding: var(--b-spacer-020) var(--b-spacer-040);\n }\n }\n</style>\n"; const BentoDraggableNestedExample = "<template>\n <bento-draggable\n :items=\"itemsList\"\n class=\"b-draggable-nested__container\"\n :options=\"options\"\n @update:items=\"itemsList = $event\"\n >\n <template #default=\"{ item, moveUp, moveDown, moveTop, moveBottom, disabledMenus }\">\n <div :key=\"item.id\" class=\"b-draggable-nested__item\">\n <div :style=\"{ display: 'flex', alignItems: 'center' }\">\n <bento-draggable-handle\n :label=\"item.label\"\n condensed\n variant=\"up-and-down\"\n :disabled-menus=\"disabledMenus\"\n @move-up=\"moveUp\"\n @move-down=\"moveDown\"\n @move-top=\"moveTop\"\n @move-bottom=\"moveBottom\"\n />\n <bento-typography>{{ item.label }}</bento-typography>\n </div>\n\n <!-- Children -->\n <template v-if=\"item.children\">\n <bento-draggable\n :items=\"item.children\"\n class=\"b-draggable-nested__container\"\n :options=\"options\"\n @update:items=\"item.children = $event\"\n >\n <template\n #default=\"{\n item: childItem,\n moveUp: moveUpChild,\n moveDown: moveDownChild,\n moveTop: moveTopChild,\n moveBottom: moveBottomChild,\n disabledMenus: disabledMenusChild,\n }\"\n >\n <div :key=\"childItem.id\" class=\"b-draggable-nested__item\">\n <div :style=\"{ display: 'flex', alignItems: 'center' }\">\n <bento-draggable-handle\n :label=\"childItem.label\"\n condensed\n variant=\"up-and-down\"\n :disabled-menus=\"disabledMenusChild\"\n @move-up=\"moveUpChild\"\n @move-down=\"moveDownChild\"\n @move-top=\"moveTopChild\"\n @move-bottom=\"moveBottomChild\"\n />\n <bento-typography>{{ childItem.label }}</bento-typography>\n </div>\n </div>\n </template>\n </bento-draggable>\n </template>\n </div>\n </template>\n </bento-draggable>\n</template>\n\n<script setup lang=\"ts\">\n import { ref } from 'vue';\n import {\n BentoDraggable,\n BentoDraggableHandle,\n type BentoDraggableItems,\n type BentoDraggableOptions,\n BentoTypography,\n } from '@adyen/bento-vue2';\n\n const itemsList = ref<BentoDraggableItems>([\n {\n id: '1.1',\n label: 'Item 1.1',\n children: [\n { id: '2.1', label: 'Item 2.1' },\n {\n id: '2.2',\n label: 'Item 2.2',\n children: [\n { id: '3.1', label: 'Item 3.1' },\n { id: '3.2', label: 'Item 3.2' },\n ],\n },\n { id: '2.3', label: 'Item 2.3' },\n ],\n },\n { id: '1.2', label: 'Item 1.2' },\n { id: '1.3', label: 'Item 1.3' },\n ]);\n\n const options: BentoDraggableOptions = {\n group: 'nested',\n animation: 150,\n };\n</script>\n\n<style lang=\"scss\" scoped>\n .b-draggable-nested {\n &__container {\n display: flex;\n flex-direction: column;\n gap: var(--b-spacer-040);\n max-width: 500px;\n }\n\n &__item {\n background-color: var(--b-color-background-primary);\n border: var(--b-border-width-s) solid var(--b-color-outline-primary);\n display: flex;\n flex-direction: column;\n gap: var(--b-spacer-020);\n padding: var(--b-spacer-020) var(--b-spacer-040);\n }\n }\n</style>\n"; const BentoDraggableGridExample = "<template>\n <bento-draggable\n ref=\"draggableRef\"\n :items=\"itemsList\"\n :style=\"{\n padding: 'var(--b-spacer-050)',\n width: '670px',\n display: 'flex',\n flexDirection: 'row',\n flexWrap: 'wrap',\n gap: 'var(--b-spacer-050)',\n }\"\n @update:items=\"itemsList = $event\"\n >\n <template #default=\"{ item, index, moveTop, moveBottom, moveLeft, moveRight, disabledMenus }\">\n <div :key=\"item.id\" :style=\"{ width: '160px' }\">\n <div\n :style=\"{\n display: 'flex',\n background: 'var(--b-color-background-tertiary)',\n borderTopLeftRadius: 'var(--b-border-radius-m)',\n borderTopRightRadius: 'var(--b-border-radius-m)',\n justifyContent: 'space-between',\n padding: 'var(--b-spacer-010) var(--b-spacer-020)',\n }\"\n >\n <bento-draggable-handle\n :label=\"item.label\"\n :disabled-menus=\"{\n ...disabledMenus,\n up: index < COLUMNS,\n down: index + COLUMNS >= itemsList.length,\n left: index % COLUMNS === 0,\n right: index % COLUMNS === COLUMNS - 1 || index === itemsList.length - 1,\n }\"\n condensed\n variant=\"all-directions\"\n @move-up=\"gridMoveUp(index)\"\n @move-down=\"gridMoveDown(index)\"\n @move-top=\"moveTop\"\n @move-bottom=\"moveBottom\"\n @move-left=\"moveLeft\"\n @move-right=\"moveRight\"\n />\n <bento-button condensed variant=\"secondary\">\n <template #iconLeft>\n <bin-icon svg-title=\"remove\" />\n </template>\n </bento-button>\n </div>\n <div\n :style=\"{\n backgroundColor: 'var(--b-color-background-secondary)',\n display: 'flex',\n flexDirection: 'column',\n borderBottomLeftRadius: 'var(--b-border-radius-m)',\n borderBottomRightRadius: 'var(--b-border-radius-m)',\n gap: 'var(--b-spacer-060)',\n padding: 'var(--b-spacer-040)',\n }\"\n >\n <bento-typography>Card {{ item.id }}</bento-typography>\n </div>\n </div>\n </template>\n </bento-draggable>\n</template>\n\n<script setup lang=\"ts\">\n import { ref } from 'vue';\n import {\n BentoButton,\n BentoDraggable,\n BentoDraggableHandle,\n type BentoDraggableItems,\n BentoTypography,\n } from '@adyen/bento-vue2';\n import BinIcon from '@adyen/ui-assets-icons-16/vue/bin';\n\n const itemsList = ref<BentoDraggableItems>([\n { id: '1', label: 'Item 1' },\n { id: '2', label: 'Item 2' },\n { id: '3', label: 'Item 3' },\n { id: '4', label: 'Item 4' },\n { id: '5', label: 'Item 5' },\n { id: '6', label: 'Item 6' },\n ]);\n\n const draggableRef = ref<InstanceType<typeof BentoDraggable> | null>(null);\n const COLUMNS = 4;\n\n const gridMoveUp = (index: number) => {\n draggableRef.value?.moveItem(index, index - COLUMNS);\n };\n const gridMoveDown = (index: number) => {\n draggableRef.value?.moveItem(index, index + COLUMNS);\n };\n</script>\n"; const BentoDraggableBoardExample = "<template>\n <div class=\"b-draggable-board\">\n <!-- TODO Column -->\n <div class=\"b-draggable-board__column\">\n <bento-typography variant=\"title\" class=\"b-draggable-board__title\"> TODO </bento-typography>\n <bento-draggable\n ref=\"todoDraggableRef\"\n :items=\"todoItems\"\n :options=\"toDoOptions\"\n class=\"b-draggable-board__list\"\n @update:items=\"todoItems = $event\"\n >\n <template #default=\"{ item, index, moveUp, moveDown, moveTop, moveBottom, disabledMenus }\">\n <div :key=\"item.id\" class=\"b-draggable-board__item\">\n <div class=\"b-draggable-board__item-content\">\n <bento-draggable-handle\n :label=\"item.label\"\n condensed\n variant=\"all-directions\"\n :disabled-menus=\"{\n ...disabledMenus,\n right: false,\n left: true,\n }\"\n @move-up=\"moveUp\"\n @move-down=\"moveDown\"\n @move-top=\"moveTop\"\n @move-bottom=\"moveBottom\"\n @move-right=\"moveToDone(index)\"\n />\n <bento-typography>{{ item.label }}</bento-typography>\n </div>\n </div>\n </template>\n </bento-draggable>\n </div>\n\n <!-- DONE Column -->\n <div class=\"b-draggable-board__column\">\n <bento-typography variant=\"title\" class=\"b-draggable-board__title\"> DONE </bento-typography>\n <bento-draggable\n ref=\"doneDraggableRef\"\n :items=\"doneItems\"\n :options=\"doneOptions\"\n class=\"b-draggable-board__list\"\n @update:items=\"doneItems = $event\"\n >\n <template #default=\"{ item, index, moveUp, moveDown, moveTop, moveBottom, disabledMenus }\">\n <div :key=\"item.id\" class=\"b-draggable-board__item\">\n <div class=\"b-draggable-board__item-content\">\n <bento-draggable-handle\n :label=\"item.label\"\n condensed\n variant=\"all-directions\"\n :disabled-menus=\"{\n ...disabledMenus,\n left: false,\n }\"\n @move-up=\"moveUp\"\n @move-down=\"moveDown\"\n @move-top=\"moveTop\"\n @move-bottom=\"moveBottom\"\n @move-left=\"moveToTodo(index)\"\n />\n <bento-typography>{{ item.label }}</bento-typography>\n </div>\n </div>\n </template>\n </bento-draggable>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\n import { ref } from 'vue';\n import {\n BentoDraggable,\n BentoDraggableHandle,\n type BentoDraggableItems,\n type BentoDraggableOptions,\n BentoTypography,\n } from '@adyen/bento-vue2';\n\n const todoItems = ref<BentoDraggableItems>([\n { id: '1', label: 'Improve documentation' },\n { id: '2', label: 'Fix navigation bug' },\n { id: '3', label: 'Add unit tests' },\n ]);\n const doneItems = ref<BentoDraggableItems>([{ id: '4', label: 'Initial release' }]);\n\n const todoDraggableRef = ref<InstanceType<typeof BentoDraggable> | null>(null);\n const doneDraggableRef = ref<InstanceType<typeof BentoDraggable> | null>(null);\n\n // Track the item being dragged to facilitate transfers between lists.\n let draggedItem: BentoDraggableItems[number] | null = null;\n\n const moveToDone = (index: number) => {\n const [item] = todoItems.value.splice(index, 1);\n doneItems.value.push(item);\n doneDraggableRef.value?.announce(\n `${item.label} moved to Done, position ${doneItems.value.length} of ${doneItems.value.length}`\n );\n };\n\n const moveToTodo = (index: number) => {\n const [item] = doneItems.value.splice(index, 1);\n todoItems.value.push(item);\n todoDraggableRef.value?.announce(\n `${item.label} moved to Todo, position ${todoItems.value.length} of ${todoItems.value.length}`\n );\n };\n\n const toDoOptions: BentoDraggableOptions = {\n group: 'board',\n animation: 150,\n onStart: event => {\n draggedItem = todoItems.value[event.oldIndex!];\n },\n onAdd: event => {\n todoItems.value.splice(event.newIndex!, 0, draggedItem!);\n todoDraggableRef.value?.announce(\n `${draggedItem!.label} added to Todo, position ${event.newIndex! + 1} of ${todoItems.value.length}`\n );\n },\n onRemove: event => {\n todoItems.value.splice(event.oldIndex!, 1);\n },\n onSort: event => {\n const { oldIndex, newIndex, from, to } = event;\n if (from === to && oldIndex !== undefined && newIndex !== undefined) {\n const [item] = todoItems.value.splice(oldIndex, 1);\n todoItems.value.splice(newIndex, 0, item);\n todoDraggableRef.value?.announce(\n `${item.label} moved to position ${newIndex + 1} of ${todoItems.value.length} in Todo`\n );\n }\n },\n };\n\n const doneOptions: BentoDraggableOptions = {\n group: 'board',\n animation: 150,\n onStart: event => {\n draggedItem = doneItems.value[event.oldIndex!];\n },\n onAdd: event => {\n doneItems.value.splice(event.newIndex!, 0, draggedItem!);\n doneDraggableRef.value?.announce(\n `${draggedItem!.label} added to Done, position ${event.newIndex! + 1} of ${doneItems.value.length}`\n );\n },\n onRemove: event => {\n doneItems.value.splice(event.oldIndex!, 1);\n },\n onSort: event => {\n const { oldIndex, newIndex, from, to } = event;\n if (from === to && oldIndex !== undefined && newIndex !== undefined) {\n const [item] = doneItems.value.splice(oldIndex, 1);\n doneItems.value.splice(newIndex, 0, item);\n doneDraggableRef.value?.announce(\n `${item.label} moved to position ${newIndex + 1} of ${doneItems.value.length} in Done`\n );\n }\n },\n };\n</script>\n\n<style lang=\"scss\" scoped>\n .b-draggable-board {\n align-items: flex-start;\n background-color: var(--b-color-background-tertiary);\n display: flex;\n gap: var(--b-spacer-100);\n min-height: 400px;\n padding: var(--b-spacer-080);\n\n &__column {\n background: var(--b-color-background-secondary);\n border-radius: var(--b-border-radius-m);\n box-shadow: var(--b-shadow-small);\n flex: 1;\n padding: var(--b-spacer-040);\n }\n\n &__title {\n display: block;\n margin-bottom: var(--b-spacer-040);\n padding: var(--b-spacer-020) var(--b-spacer-040);\n }\n\n &__list {\n display: flex;\n flex-direction: column;\n gap: var(--b-spacer-040);\n min-height: 50px;\n }\n\n &__item {\n background-color: var(--b-color-background-primary);\n border: var(--b-border-width-s) solid var(--b-color-outline-primary);\n display: flex;\n flex-direction: column;\n gap: var(--b-spacer-020);\n padding: var(--b-spacer-020) var(--b-spacer-040);\n }\n\n &__item-content {\n align-items: center;\n display: flex;\n }\n }\n</style>\n"; import './draggable.stories.scss?module'; const meta: Meta = { title: 'Draggable', component: BentoDraggable, }; export default meta; type Story = StoryObj<typeof BentoDraggable>; const ITEMS: BentoDraggableItems = [ { id: '1', label: 'Item 1' }, { id: '2', label: 'Item 2' }, { id: '3', label: 'Item 3' }, { id: '4', label: 'Item 4' }, { id: '5', label: 'Item 5' }, { id: '6', label: 'Item 6' }, ]; export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoDraggable, BentoDraggableHandle, BentoTypography }, props: Object.keys(argTypes), template: ` <bento-draggable v-bind="args" :items="itemsList" class="b-draggable-default__container" @update:items="onUpdateItems" > <template #default="{item, moveUp, moveDown, moveTop, moveBottom, disabledMenus}"> <div class="b-draggable-default__item" :key="item.id"> <div :style="{ display: 'flex', alignItems: 'center'}"> <bento-draggable-handle :label="item.label" condensed :disabled="item.label === 'Item 2'" :disabled-menus="disabledMenus" variant="up-and-down" @move-up="moveUp" @move-down="moveDown" @move-top="moveTop" @move-bottom="moveBottom" /> <bento-typography>{{ item.label }}</bento-typography> </div> </div> </template> </bento-draggable> `, setup(props) { const args = isVue2 ? props : _args; const itemsList = ref(args.items); const onUpdateItems = (items: BentoDraggableItems) => { itemsList.value = [...items]; action('update:items')(items); }; return { args: isVue2 ? props : _args, itemsList, onUpdateItems, }; }, }), args: { items: ITEMS, options: { group: 'default', }, }, parameters: storybookDocsParameter(BentoDraggableDefaultExample), }; const NESTED_ITEMS: BentoDraggableItems = [ { id: '1.1', label: 'Item 1.1', children: [ { id: '2.1', label: 'Item 2.1' }, { id: '2.2', label: 'Item 2.2', children: [ { id: '3.1', label: 'Item 3.1' }, { id: '3.2', label: 'Item 3.2' }, ], }, { id: '2.3', label: 'Item 2.3' }, ], }, { id: '1.2', label: 'Item 1.2' }, { id: '1.3', label: 'Item 1.3' }, ]; export const Nested: Story = { render: (_args, { argTypes }) => ({ components: { BentoDraggable, BentoDraggableHandle, BentoTypography }, props: Object.keys(argTypes), template: ` <bento-draggable v-bind="args" :items="itemsList" class="b-draggable-nested__container" @update:items="itemsList = $event" > <template #default="{item, moveUp, moveDown, moveTop, moveBottom, disabledMenus}"> <div class="b-draggable-nested__item" :key="item.id"> <div :style="{ display: 'flex', alignItems: 'center'}"> <bento-draggable-handle :label="item.label" condensed variant="up-and-down" :disabled-menus="disabledMenus" @move-up="moveUp" @move-down="moveDown" @move-top="moveTop" @move-bottom="moveBottom" /> <bento-typography>{{ item.label }}</bento-typography> </div> <!-- Children --> <template v-if="item.children"> <bento-draggable class="b-draggable-nested__container" :options="args.options" :items="item.children" @update:items="item.children = $event" > <template v-slot:default="{item: childItem, moveUp: moveUpChild, moveDown: moveDownChild, moveTop: moveTopChild, moveBottom: moveBottomChild, disabledMenus: disabledMenusChild}"> <div class="b-draggable-nested__item" :key="childItem.id"> <div :style="{ display: 'flex', alignItems: 'center'}"> <bento-draggable-handle :label="childItem.label" condensed variant="up-and-down" :disabled-menus="disabledMenusChild" @move-up="moveUpChild" @move-down="moveDownChild" @move-top="moveTopChild" @move-bottom="moveBottomChild" /> <bento-typography>{{ childItem.label }}</bento-typography> </div> </div> </template> </bento-draggable> </template> </div> </template> </bento-draggable> `, setup(props) { const args = isVue2 ? props : _args; const itemsList = ref(args.items); return { args, itemsList, }; }, }), args: { items: NESTED_ITEMS, options: {}, } as BentoDraggableProps, parameters: storybookDocsParameter(BentoDraggableNestedExample), }; export const Grid: Story = { render: (_args, { argTypes }) => ({ components: { BentoButton, BentoDraggable, BentoDraggableHandle, BentoTypography, BinIcon }, props: Object.keys(argTypes), template: ` <bento-draggable ref="draggableRef" v-bind="args" :items="itemsList" @update:items="itemsList = $event" :style="{ padding: 'var(--b-spacer-050)', width: '670px', display: 'flex', flexDirection: 'row', flexWrap: 'wrap', gap: 'var(--b-spacer-050)', }" > <template #default="{item, index, moveTop, moveBottom, moveLeft, moveRight, disabledMenus}"> <div :key="item.id" :style="{ width: '160px' }"> <div :style="{ display: 'flex', background: 'var(--b-color-background-tertiary)', borderTopLeftRadius: 'var(--b-border-radius-m)', borderTopRightRadius: 'var(--b-border-radius-m)', justifyContent: 'space-between', padding: 'var(--b-spacer-010) var(--b-spacer-020)', }" > <bento-draggable-handle :label="item.label" :disabled-menus="{ ...disabledMenus, up: index < COLUMNS, down: index + COLUMNS >= itemsList.length, left: index % COLUMNS === 0, right: index % COLUMNS === COLUMNS - 1 || index === itemsList.length - 1, }" condensed variant="all-directions" @move-up="gridMoveUp(index)" @move-down="gridMoveDown(index)" @move-top="moveTop" @move-bottom="moveBottom" @move-left="moveLeft" @move-right="moveRight" /> <bento-button condensed variant="secondary"> <template #iconLeft> <bin-icon svg-title="remove" /> </template> </bento-button> </div> <div :style="{ backgroundColor: 'var(--b-color-background-secondary)', display: 'flex', flexDirection: 'column', borderBottomLeftRadius: 'var(--b-border-radius-m)', borderBottomRightRadius: 'var(--b-border-radius-m)', gap: 'var(--b-spacer-060)', padding: 'var(--b-spacer-040)', }" > <bento-typography>Card {{ item.id }}</bento-typography> </div> </div> </template> </bento-draggable> `, setup(props) { const args = isVue2 ? props : _args; const itemsList = ref(args.items); const draggableRef = ref(null); const COLUMNS = 4; const gridMoveUp = (index: number) => { draggableRef.value?.moveItem(index, index - COLUMNS); }; const gridMoveDown = (index: number) => { draggableRef.value?.moveItem(index, index + COLUMNS); }; return { args, itemsList, draggableRef, COLUMNS, gridMoveUp, gridMoveDown, }; }, }), args: { items: ITEMS, options: {}, }, parameters: storybookDocsParameter(BentoDraggableGridExample), }; export const Board: Story = { render: (_args, { argTypes }) => ({ components: { BentoDraggable, BentoDraggableHandle, BentoTypography }, props: Object.keys(argTypes), template: ` <div :style="{ display: 'flex', gap: 'var(--b-spacer-100)', alignItems: 'flex-start', padding: 'var(--b-spacer-080)', backgroundColor: 'var(--b-color-background-tertiary)', minHeight: '400px' }"> <!-- TODO Column --> <div :style="{ flex: 1, background: 'var(--b-color-background-secondary)', padding: 'var(--b-spacer-040)', borderRadius: 'var(--b-border-radius-m)', boxShadow: 'var(--b-shadow-small)' }"> <bento-typography variant="title" :style="{ marginBottom: 'var(--b-spacer-040)', padding: 'var(--b-spacer-020) var(--b-spacer-040)', display: 'block' }"> TODO </bento-typography> <bento-draggable ref="todoDraggableRef" :items="todoItems" :options="toDoOptions" @update:items="todoItems = $event" class="b-draggable-board__container" style="min-height: 50px;" > <template #default="{ item, index, moveUp, moveDown, moveTop, moveBottom, disabledMenus }"> <div class="b-draggable-board__item" :key="item.id"> <div :style="{ display: 'flex', alignItems: 'center' }"> <bento-draggable-handle :label="item.label" condensed variant="all-directions" :disabled-menus="{ ...disabledMenus, right: false, left: true, }" @move-up="moveUp" @move-down="moveDown" @move-top="moveTop" @move-bottom="moveBottom" @move-right="moveToDone(index)" /> <bento-typography>{{ item.label }}</bento-typography> </div> </div> </template> </bento-draggable> </div> <!-- DONE Column --> <div :style="{ flex: 1, background: 'var(--b-color-background-secondary)', padding: 'var(--b-spacer-040)', borderRadius: 'var(--b-border-radius-m)', boxShadow: 'var(--b-shadow-small)' }"> <bento-typography variant="title" :style="{ marginBottom: 'var(--b-spacer-040)', padding: 'var(--b-spacer-020) var(--b-spacer-040)', display: 'block' }"> DONE </bento-typography> <bento-draggable ref="doneDraggableRef" :items="doneItems" :options="doneOptions" @update:items="doneItems = $event" class="b-draggable-board__container" style="min-height: 50px;" > <template #default="{ item, index, moveUp, moveDown, moveTop, moveBottom, moveLeft, disabledMenus }"> <div class="b-draggable-board__item" :key="item.id"> <div :style="{ display: 'flex', alignItems: 'center' }"> <bento-draggable-handle :label="item.label" condensed variant="all-directions" :disabled-menus="{ ...disabledMenus, left: false, }" @move-up="moveUp" @move-down="moveDown" @move-top="moveTop" @move-bottom="moveBottom" @move-left="moveToTodo(index)" /> <bento-typography>{{ item.label }}</bento-typography> </div> </div> </template> </bento-draggable> </div> </div> `, setup() { const todoItems = ref<BentoDraggableItems>([ { id: '1', label: 'Improve documentation' }, { id: '2', label: 'Fix navigation bug' }, { id: '3', label: 'Add unit tests' }, ]); const doneItems = ref<BentoDraggableItems>([{ id: '4', label: 'Initial release' }]); const todoDraggableRef = ref<InstanceType<typeof BentoDraggable> | null>(null); const doneDraggableRef = ref<InstanceType<typeof BentoDraggable> | null>(null); // Since we're moving items between completely separate refs, we track the item being dragged // manually to facilitate transfers between the todoItems and doneItems lists. let draggedItem: BentoDraggableItems[number] | null = null; const moveToDone = (index: number) => { const [item] = todoItems.value.splice(index, 1); doneItems.value.push(item); doneDraggableRef.value?.announce( `${item.label} moved to Done, position ${doneItems.value.length} of ${doneItems.value.length}` ); action('move-to-done')(item, { todo: todoItems.value, done: doneItems.value }); }; const moveToTodo = (index: number) => { const [item] = doneItems.value.splice(index, 1); todoItems.value.push(item); todoDraggableRef.value?.announce( `${item.label} moved to Todo, position ${todoItems.value.length} of ${todoItems.value.length}` ); action('move-to-todo')(item, { todo: todoItems.value, done: doneItems.value }); }; const toDoOptions: BentoDraggableOptions = { group: 'board', animation: 150, onStart: event => { draggedItem = todoItems.value[event.oldIndex!]; }, onAdd: event => { todoItems.value.splice(event.newIndex!, 0, draggedItem!); todoDraggableRef.value?.announce( `${draggedItem!.label} added to Todo, position ${event.newIndex! + 1} of ${todoItems.value.length}` ); }, onRemove: event => { todoItems.value.splice(event.oldIndex!, 1); }, onSort: event => { const { oldIndex, newIndex, from, to } = event; if (from === to && oldIndex !== undefined && newIndex !== undefined) { const [item] = todoItems.value.splice(oldIndex, 1); todoItems.value.splice(newIndex, 0, item); todoDraggableRef.value?.announce( `${item.label} moved to position ${newIndex + 1} of ${todoItems.value.length} in Todo` ); } }, }; const doneOptions: BentoDraggableOptions = { group: 'board', animation: 150, onStart: event => { draggedItem = doneItems.value[event.oldIndex!]; }, onAdd: event => { doneItems.value.splice(event.newIndex!, 0, draggedItem!); doneDraggableRef.value?.announce( `${draggedItem!.label} added to Done, position ${event.newIndex! + 1} of ${doneItems.value.length}` ); }, onRemove: event => { doneItems.value.splice(event.oldIndex!, 1); }, onSort: event => { const { oldIndex, newIndex, from, to } = event; if (from === to && oldIndex !== undefined && newIndex !== undefined) { const [item] = doneItems.value.splice(oldIndex, 1); doneItems.value.splice(newIndex, 0, item); doneDraggableRef.value?.announce( `${item.label} moved to position ${newIndex + 1} of ${doneItems.value.length} in Done` ); action('sort-done')(event, doneItems.value); } }, }; return { todoItems, doneItems, toDoOptions, doneOptions, moveToDone, moveToTodo, todoDraggableRef, doneDraggableRef, }; }, }), parameters: storybookDocsParameter(BentoDraggableBoardExample), };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<template> <component :is="el" ref="containerRef"> <slot v-for="(item, index) of items" :item="item" :index="index" :move-up="() => moveItemUp(index)" :move-right="() => moveItemRight(index)" :move-down="() => moveItemDown(index)" :move-left="() => moveItemLeft(index)" :move-top="() => moveItemTop(index)" :move-bottom="() => moveItemBottom(index)" :disabled-menus="disabledMenuItems(index)" name="default" ></slot> <div class="b-draggable__announcement" role="status" aria-live="polite"> {{ announcementMessage }} </div> </component> </template> <script setup lang="ts"> import { computed, nextTick, ref, watch } from 'vue'; import type { BentoDraggableEmits, BentoDraggableItems, BentoDraggableOptions, BentoDraggableProps, BentoDraggableSortableEvent, } from './draggable.types'; import { useBentoDraggable } from './composables/use-bento-draggable/use-bento-draggable'; import { useDraggableAnnouncement } from './composables/use-draggable-announcement'; import { useTrackKeyboardUser } from './composables/use-track-keyboard-user'; import { useI18n } from '@/utils/ts/i18n'; import messages from './messages.json'; type MessageSchema = (typeof messages)['en-US']; const ALL_CHILDREN_EXCEPT_SCREEN_READER_ELEMENTS = '>:not(.b-draggable__announcement)'; const props = withDefaults(defineProps<BentoDraggableProps>(), { el: 'div', items: () => [], options: () => ({}), announcement: '', disableAutoSort: false, }); const emit = defineEmits<BentoDraggableEmits>(); /** * Reference to the container of the draggable elements. */ const containerRef = ref<HTMLElement | null>(null); /** * Indicates if there is an element being dragged inside the container. */ const isDragging = ref<boolean>(false); /** * Internal copy of items used to track position changes for focus restoration. */ const internalItems = ref<BentoDraggableItems>([]); /** * Tracks the item being moved via keyboard so focus can be restored to the correct element. */ const lastMovedItem = ref<BentoDraggableItems[number] | null>(null); const computedOptions = computed<BentoDraggableOptions>(() => { if (!containerRef.value) { return {}; } return { ...props.options, draggable: props.options?.draggable ?? ALL_CHILDREN_EXCEPT_SCREEN_READER_ELEMENTS, onStart: (event: BentoDraggableSortableEvent) => { isDragging.value = true; if (props.options?.onStart) { props.options?.onStart(event); } }, onEnd: (event: BentoDraggableSortableEvent) => { // This is a hack to move the event to the end of the event queue. // cf this issue: https://github.com/SortableJS/Sortable/issues/1184 setTimeout(() => { isDragging.value = false; if (props.options?.onEnd) { props.options?.onEnd(event); } }); }, onSort: (event: BentoDraggableSortableEvent) => { // Always execute the user's custom onSort callback if provided if (props.options?.onSort) { props.options.onSort(event); return; } // Automatically sync the v-model unless the user explicitly disabled it if (!props.disableAutoSort) { const { oldIndex, newIndex, from, to } = event; if ( from === to && from === containerRef.value && oldIndex !== undefined && newIndex !== undefined ) { moveItem(oldIndex, newIndex); } } }, }; }); /** * Handle reordering announcements for screen readers. */ const { announcementMessage, announce } = useDraggableAnnouncement(); const { t } = useI18n<{ message: MessageSchema }>({ messages }); const { draggableElement } = useBentoDraggable(containerRef, computedOptions); // When moving items between sibling `bento-draggable`, the item may be removed, causing the DOM to be destroyed and temporarily lose focus // This is an alternative to query element `:focus-within` as the `:focus-within` will be gone until the other `bento-draggable` adds it into the list const { isKeyboardUser } = useTrackKeyboardUser(); /** * Focuses the draggable handle button at the given index. * Waits for the next tick to ensure the DOM has been updated before focusing. * * @param index - The index of the item whose handle should receive focus. */ const focus = async (index: number) => { await nextTick(); if (index !== -1 && containerRef.value) { const itemElement = containerRef.value.children[index] as HTMLElement; const button = itemElement?.querySelector<HTMLButtonElement>('.b-draggable-handle button'); await nextTick(); button?.focus(); } }; /** * Moves an item from one position to another within the list, * emits the updated list, and announces the change for screen readers. * * @param indexOrigin - The current index of the item to move. * @param indexTarget - The desired index to move the item to. */ const moveItem = async (indexOrigin: number, indexTarget: number) => { if ( indexOrigin < 0 || indexTarget < 0 || indexOrigin >= props.items.length || indexTarget >= props.items.length || indexOrigin === indexTarget ) { return; } const newList = Array.from(props.items as BentoDraggableItems); const [removed] = newList.splice(indexOrigin, 1); newList.splice(indexTarget, 0, removed); lastMovedItem.value = removed; // Occasionally, there may be race conditions where the index is out-of-date due to the fast drag-and-drop or // other async operations like onEnd > setTimeout. // Filtering out null/undefined values ensures the list remains valid. emit('update:items', newList.filter(Boolean)); const itemLabel = removed.label; const message = props?.announcement || t('youHaveMovedFromPositionToPosition', { itemLabel, indexOrigin: indexOrigin + 1, indexTarget: indexTarget + 1, total: props.items.length, }); if (props.announcement !== 'disabled') { announce(message); } }; const moveItemUp = (index: number) => moveItem(index, index - 1); const moveItemDown = (index: number) => moveItem(index, index + 1); const moveItemLeft = (index: number) => moveItem(index, index - 1); const moveItemRight = (index: number) => moveItem(index, index + 1); const moveItemTop = (index: number) => moveItem(index, 0); const moveItemBottom = (index: number) => moveItem(index, props.items.length - 1); /** * Returns a pre-calculated disabled menus object for the current item. */ const disabledMenuItems = (index: number) => { return { up: index === 0, top: index === 0, down: index === props.items.length - 1, bottom: index === props.items.length - 1, left: true, right: true, }; }; /** * Watch for items changes and restore focus automatically */ watch( () => props.items, async newItems => { if (!newItems) { return; } if (isKeyboardUser.value && newItems.length > 0 && internalItems.value.length > 0) { if (lastMovedItem.value) { // Within-list reorder: focus the moved item at its new position const movedItemIndex = newItems.indexOf(lastMovedItem.value); if (movedItemIndex !== -1) { await focus(movedItemIndex); } lastMovedItem.value = null; } else if (newItems.length > internalItems.value.length) { // Cross-list addition: focus the newly added item const newItemIndex = newItems.findIndex(item => !internalItems.value.includes(item)); if (newItemIndex !== -1) { await focus(newItemIndex); } } } internalItems.value = [...newItems]; }, { deep: true, immediate: true } ); defineExpose({ /** * A ref to the container DOM element. */ containerRef, /** * The underlying SortableJS instance, which provides methods like `toArray()`, `sort()`, and `destroy()`. */ draggableElement, /** * A boolean ref that is `true` when an item is being dragged. */ isDragging, /** * Announces a message to screen reader users via the live region. */ announce, /** * Moves an item from one index to another and emits the updated list. */ moveItem, /** * Programmatically focuses the draggable handle button at the given index. */ focus, }); </script> <script lang="ts"> /** * Drag and drop container that transforms a list of items into draggable elements. * The library is based on SortableJS. * @see {@link https://github.com/SortableJS/Sortable#readme | SortableJS documentation} * * @example * import { BentoDraggable, BentoDraggableHandle, type BentoDraggableEvents } from '@adyen/bento-vue2'; * * export default { * components: { BentoDraggable, BentoDraggableHandle }, * template: ` * <bento-draggable * v-model="items" * condensed * :options="{ * group: 'nested', * animation: 150 * }" * > * <template #default="{ item, index, moveUp, moveDown }"> * <div :key="item.id"> * <bento-draggable-handle * :label="item.label" * @move-up="moveUp" * @move-down="moveDown" * /> * </div> * </template> * </bento-draggable> * `, * setup () { * const items = ref([ * { id: '1', label: 'Item 1' }, * { id: '2', label: 'Item 2' } * ]); * return { * items, * } * } * } */ export default { name: 'bento-draggable', model: { prop: 'items', event: 'update:items' }, i18n: { messages }, }; </script> <style lang="scss" scoped src="./draggable.scss" />
|
|
1
|
+
<template> <component :is="el" ref="containerRef"> <slot v-for="(item, index) of items" :item="item" :index="index" :move-up="() => moveItemUp(index)" :move-right="() => moveItemRight(index)" :move-down="() => moveItemDown(index)" :move-left="() => moveItemLeft(index)" :move-top="() => moveItemTop(index)" :move-bottom="() => moveItemBottom(index)" :disabled-menus="disabledMenuItems(index)" name="default" ></slot> <div class="b-draggable__announcement" role="status" aria-live="polite"> {{ announcementMessage }} </div> </component> </template> <script setup lang="ts"> import { computed, nextTick, ref, watch } from 'vue'; import type { BentoDraggableEmits, BentoDraggableItems, BentoDraggableOptions, BentoDraggableProps, BentoDraggableSortableEvent, } from './draggable.types'; import { useBentoDraggable } from './composables/use-bento-draggable/use-bento-draggable'; import { useDraggableAnnouncement } from './composables/use-draggable-announcement'; import { useTrackKeyboardUser } from './composables/use-track-keyboard-user'; import { useI18n } from '@/utils/ts/i18n'; import messages from './messages.json'; type MessageSchema = (typeof messages)['en-US']; const ALL_CHILDREN_EXCEPT_SCREEN_READER_ELEMENTS = '>:not(.b-draggable__announcement)'; const props = withDefaults(defineProps<BentoDraggableProps>(), { el: 'div', items: () => [], options: () => ({}), announcement: '', disableAutoSort: false, }); const emit = defineEmits<BentoDraggableEmits>(); /** * Reference to the container of the draggable elements. */ const containerRef = ref<HTMLElement | null>(null); /** * Indicates if there is an element being dragged inside the container. */ const isDragging = ref<boolean>(false); /** * Internal copy of items used to track position changes for focus restoration. */ const internalItems = ref<BentoDraggableItems>([]); /** * Tracks the item being moved via keyboard so focus can be restored to the correct element. */ const lastMovedItem = ref<BentoDraggableItems[number] | null>(null); const computedOptions = computed<BentoDraggableOptions>(() => { if (!containerRef.value) { return {}; } return { ...props.options, draggable: props.options?.draggable ?? ALL_CHILDREN_EXCEPT_SCREEN_READER_ELEMENTS, onStart: (event: BentoDraggableSortableEvent) => { isDragging.value = true; if (props.options?.onStart) { props.options?.onStart(event); } }, onEnd: (event: BentoDraggableSortableEvent) => { // This is a hack to move the event to the end of the event queue. // cf this issue: https://github.com/SortableJS/Sortable/issues/1184 setTimeout(() => { isDragging.value = false; if (props.options?.onEnd) { props.options?.onEnd(event); } }); }, onSort: (event: BentoDraggableSortableEvent) => { // Always execute the user's custom onSort callback if provided if (props.options?.onSort) { props.options.onSort(event); return; } // Automatically sync the v-model unless the user explicitly disabled it if (!props.disableAutoSort) { const { oldIndex, newIndex, from, to } = event; if ( from === to && from === containerRef.value && oldIndex !== undefined && newIndex !== undefined ) { restoreItemDomPosition(event); moveItem(oldIndex, newIndex); } } }, }; }); /** * Handle reordering announcements for screen readers. */ const { announcementMessage, announce } = useDraggableAnnouncement(); const { t } = useI18n<{ message: MessageSchema }>({ messages }); const { draggableElement } = useBentoDraggable(containerRef, computedOptions); /** * Restores the item that SortableJS moved directly in the DOM before updating the reactive items. * This keeps Vue responsible for rendering the final order and prevents the move from being applied twice. * * @param event SortableJS event containing the moved item and its original index. */ const restoreItemDomPosition = (event: BentoDraggableSortableEvent) => { const { from, item, oldIndex } = event; if (!item || oldIndex === undefined) { return; } item.remove(); from.insertBefore(item, from.children[oldIndex] ?? null); }; // When moving items between sibling `bento-draggable`, the item may be removed, causing the DOM to be destroyed and temporarily lose focus // This is an alternative to query element `:focus-within` as the `:focus-within` will be gone until the other `bento-draggable` adds it into the list const { isKeyboardUser } = useTrackKeyboardUser(); /** * Focuses the draggable handle button at the given index. * Waits for the next tick to ensure the DOM has been updated before focusing. * * @param index - The index of the item whose handle should receive focus. */ const focus = async (index: number) => { await nextTick(); if (index !== -1 && containerRef.value) { const itemElement = containerRef.value.children[index] as HTMLElement; const button = itemElement?.querySelector<HTMLButtonElement>('.b-draggable-handle button'); await nextTick(); button?.focus(); } }; /** * Moves an item from one position to another within the list, * emits the updated list, and announces the change for screen readers. * * @param indexOrigin - The current index of the item to move. * @param indexTarget - The desired index to move the item to. */ const moveItem = async (indexOrigin: number, indexTarget: number) => { if ( indexOrigin < 0 || indexTarget < 0 || indexOrigin >= props.items.length || indexTarget >= props.items.length || indexOrigin === indexTarget ) { return; } const newList = Array.from(props.items as BentoDraggableItems); const [removed] = newList.splice(indexOrigin, 1); newList.splice(indexTarget, 0, removed); lastMovedItem.value = removed; // Occasionally, there may be race conditions where the index is out-of-date due to the fast drag-and-drop or // other async operations like onEnd > setTimeout. // Filtering out null/undefined values ensures the list remains valid. emit('update:items', newList.filter(Boolean)); const itemLabel = removed.label; const message = props?.announcement || t('youHaveMovedFromPositionToPosition', { itemLabel, indexOrigin: indexOrigin + 1, indexTarget: indexTarget + 1, total: props.items.length, }); if (props.announcement !== 'disabled') { announce(message); } }; const moveItemUp = (index: number) => moveItem(index, index - 1); const moveItemDown = (index: number) => moveItem(index, index + 1); const moveItemLeft = (index: number) => moveItem(index, index - 1); const moveItemRight = (index: number) => moveItem(index, index + 1); const moveItemTop = (index: number) => moveItem(index, 0); const moveItemBottom = (index: number) => moveItem(index, props.items.length - 1); /** * Returns a pre-calculated disabled menus object for the current item. */ const disabledMenuItems = (index: number) => { return { up: index === 0, top: index === 0, down: index === props.items.length - 1, bottom: index === props.items.length - 1, left: true, right: true, }; }; /** * Watch for items changes and restore focus automatically */ watch( () => props.items, async newItems => { if (!newItems) { return; } if (isKeyboardUser.value && newItems.length > 0 && internalItems.value.length > 0) { if (lastMovedItem.value) { // Within-list reorder: focus the moved item at its new position const movedItemIndex = newItems.indexOf(lastMovedItem.value); if (movedItemIndex !== -1) { await focus(movedItemIndex); } lastMovedItem.value = null; } else if (newItems.length > internalItems.value.length) { // Cross-list addition: focus the newly added item const newItemIndex = newItems.findIndex(item => !internalItems.value.includes(item)); if (newItemIndex !== -1) { await focus(newItemIndex); } } } internalItems.value = [...newItems]; }, { deep: true, immediate: true } ); defineExpose({ /** * A ref to the container DOM element. */ containerRef, /** * The underlying SortableJS instance, which provides methods like `toArray()`, `sort()`, and `destroy()`. */ draggableElement, /** * A boolean ref that is `true` when an item is being dragged. */ isDragging, /** * Announces a message to screen reader users via the live region. */ announce, /** * Moves an item from one index to another and emits the updated list. */ moveItem, /** * Programmatically focuses the draggable handle button at the given index. */ focus, }); </script> <script lang="ts"> /** * Drag and drop container that transforms a list of items into draggable elements. * The library is based on SortableJS. * @see {@link https://github.com/SortableJS/Sortable#readme | SortableJS documentation} * * @example * import { BentoDraggable, BentoDraggableHandle, type BentoDraggableEvents } from '@adyen/bento-vue2'; * * export default { * components: { BentoDraggable, BentoDraggableHandle }, * template: ` * <bento-draggable * v-model="items" * condensed * :options="{ * group: 'nested', * animation: 150 * }" * > * <template #default="{ item, index, moveUp, moveDown }"> * <div :key="item.id"> * <bento-draggable-handle * :label="item.label" * @move-up="moveUp" * @move-down="moveDown" * /> * </div> * </template> * </bento-draggable> * `, * setup () { * const items = ref([ * { id: '1', label: 'Item 1' }, * { id: '2', label: 'Item 2' } * ]); * return { * items, * } * } * } */ export default { name: 'bento-draggable', model: { prop: 'items', event: 'update:items' }, i18n: { messages }, }; </script> <style lang="scss" scoped src="./draggable.scss" />
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import { isVue2 } from 'vue-demi'; import { storybookDocsParameter } from '@/utils/ts/storybook'; import BentoDrawer from './drawer.vue'; import { BentoButton } from '@/components/button'; import { BentoTypography } from '@/components/typography'; import { ref, watch } from 'vue'; import type { Meta, StoryObj } from '@storybook/vue';
|
|
1
|
+
import { isVue2 } from 'vue-demi'; import { storybookDocsParameter } from '@/utils/ts/storybook'; import BentoDrawer from './drawer.vue'; import { BentoButton } from '@/components/button'; import { BentoTypography } from '@/components/typography'; import { ref, watch } from 'vue'; import type { Meta, StoryObj } from '@storybook/vue'; const DrawerDefaultExample = "<template>\n <div :style=\"{ display: 'flex', flexDirection: 'column', height: '100vh' }\">\n <div :style=\"{ borderBottom: '1px solid var(--b-color-separator-primary)', padding: 'var(--b-spacer-060)' }\">\n <bento-button\n variant=\"secondary\"\n condensed\n aria-controls=\"main-drawer\"\n aria-label=\"toggle drawer\"\n :aria-expanded=\"String(isOpen)\"\n @click=\"isOpen = !isOpen\"\n >\n Toggle\n </bento-button>\n </div>\n\n <bento-drawer\n drawer-id=\"main-drawer\"\n drawer-label=\"Main navigation\"\n :is-drawer-open=\"isOpen\"\n :style=\"{ flexGrow: '1', minHeight: '0' }\"\n >\n <template #drawer>\n <div :style=\"{ padding: 'var(--b-spacer-080)' }\">\n <bento-typography variant=\"body\">Drawer content</bento-typography>\n </div>\n </template>\n <template #page>\n <div :style=\"{ padding: 'var(--b-spacer-080)' }\">\n <bento-typography variant=\"body\">Page content</bento-typography>\n </div>\n </template>\n </bento-drawer>\n </div>\n</template>\n\n<script setup lang=\"ts\">\n import { ref } from 'vue';\n import { BentoButton, BentoDrawer, BentoTypography } from '@adyen/bento-vue2';\n\n const isOpen = ref(true);\n</script>\n"; const meta: Meta = { title: 'Drawer', component: BentoDrawer, parameters: { layout: 'fullscreen', }, }; export default meta; type Story = StoryObj<typeof BentoDrawer>; export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoDrawer, BentoButton, BentoTypography, }, props: Object.keys(argTypes), template: ` <div :style="{ display: 'flex', flexDirection: 'column', height: '100vh' }"> <div :style="{ borderBottom: '1px solid var(--b-color-separator-primary)', padding: 'var(--b-spacer-060)' }"> <bento-button variant="secondary" condensed :aria-controls="args.drawerId" aria-label="toggle drawer" :aria-expanded="String(isOpen)" @click="isOpen = !isOpen" > Toggle </bento-button> </div> <bento-drawer :drawer-id="args.drawerId" :drawer-label="args.drawerLabel" :is-drawer-open="isOpen" :style="{ flexGrow: '1', minHeight: '0' }" > <template #drawer> <div :style="{ padding: 'var(--b-spacer-080)' }"> <bento-typography variant="body">Drawer content</bento-typography> </div> </template> <template #page> <div :style="{ padding: 'var(--b-spacer-080)' }"> <bento-typography variant="body">Page content</bento-typography> </div> </template> </bento-drawer> </div> `, setup(props) { const args = isVue2 ? props : _args; const isOpen = ref(args.isDrawerOpen); watch( () => args.isDrawerOpen, newValue => { isOpen.value = newValue; } ); return { args, isOpen, }; }, }), args: { drawerId: 'main-navigation', drawerLabel: 'Main navigation', isDrawerOpen: true, }, parameters: storybookDocsParameter(DrawerDefaultExample), };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import { type PropType } from 'vue'; import { type Booleanish } from '@/types/prop-types'; // TODO: Change this to script setup + Typescript interface export const DropdownBaseTextboxProps = { /** * The number of additional items that have been selected in the dropdown. */ additionalItemsSelected: { type: [Number, String] as PropType<number | string>, default: 0 }, /** * Sets the possibility to type into the textbox */ dynamicFiltering: { type: Boolean, default: false, }, /** * Sets the aria-controls attribute on the textbox. This should correspond to the popover's id that the textbox triggers. */ ariaControls: { type: String, required: true, }, /** * Indicates whether the content the textbox triggers is visible or not. Needs to be set accordingly when the textbox is used and changed every time the popover is shown/hidden. */ ariaExpanded: { type: [String, Boolean] as PropType<Booleanish>, required: true, }, /** * Corresponds to the label's id that labels the textbox element. */ ariaLabelledby: { type: String, default: null, }, /** * Disables any textbox functionality */ disabled: { type: Boolean, default: false }, /** * Marks the textbox as invalid, rendering an error state */ isInvalid: { type: Boolean, default: false }, /** * Selected option's label */ displayValue: { type: String, default: null }, /** * Indicates if the dropdown is open so that * the respective event can be emitted */ open: { type: Boolean, default: false }, /** * The input value */ value: { type: String, default: '' }, /** * Custom debounce time. Default: 300 */ debounceTime: { type: Number, default: 300 }, }; export enum DropdownBaseTextboxEvent { OPEN = 'dropdown-base-open', KEYDOWN = 'dropdown-base-keydown', CLOSE = 'dropdown-base-close', INPUT = 'dropdown-base-input', CLEAR = 'dropdown-base-clear', } export default DropdownBaseTextboxProps;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
export enum DropdownBaseTextboxState { DISABLED = 'disabled', READONLY = 'readonly', HAS_ITEMS = 'has-items', OPENED = 'opened', ERROR = 'error', }
|
package/dist/assets/components/dropdown/components/dropdown-base-textbox/dropdown-base-textbox.vue
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
<template> <div :class="$attrs.class" data-testid="textbox-wrapper" @click="onToggleDropdown" @keydown.escape="onCloseDropdown" > <bento-typography class="b-dropdown-base-textbox" :class="conditionalClasses" :variant="isUnderlineStyle ? 'title' : 'body'" el="div" data-testid="base-textbox" > <span v-if="shouldShowAutocompleteSuggestion(hasSlot('autocomplete-suggestion'))" class="b-dropdown-base-textbox__autocomplete-suggestion" aria-hidden="true" data-testid="autocomplete-suggestion" > <slot name="autocomplete-suggestion"></slot> </span> <input v-if="shouldShowInput" v-bind="ariaAttributes" :id="textboxId" ref="textbox" class="b-dropdown-base-textbox__input" role="combobox" type="text" :aria-autocomplete=" shouldShowAutocompleteSuggestion(hasSlot('autocomplete-suggestion')) ? 'both' : 'list' " size="1" :value="inputValue" :disabled="disabled ? true : null" :readonly="readonly" :required="showRequiredAttribute" @keydown="$emit(DropdownBaseTextboxEvent.KEYDOWN, $event)" @keydown.enter="onTextboxEnterKey" @focus="onTextboxFocus" @blur="onTextboxBlur" @input="onInputEvent" /> <div v-else :id="textboxId" v-bind="ariaAttributes" ref="textbox" class="b-dropdown-base-textbox__input" :class="comboboxConditionalClasses" :aria-readonly="readonly ? 'true' : null" role="combobox" :tabindex="disabled ? -1 : 0" @focus="onTextboxFocus" @mousedown="onClosedComboboxMouseDown" > <template v-if="shouldShowSingleSelectSlot"> <slot v-if="hasSlot('display-value')" v-bind="selectedValueItem" name="display-value"></slot> <slot v-else v-bind="selectedValueItem"> {{ displayValue }} </slot> </template> <template v-else-if="shouldShowMultipleSlot"> <div class="b-dropdown-base-textbox__input-multiple-selected"> <div v-for="singleItem of selectedValueItems" :key="singleItem.value"> <div class="b-dropdown-base-textbox__input-selected-element"> <slot v-bind="singleItem"> {{ displayValue }} </slot> </div> </div> </div> </template> <template v-else>{{ displayValue }}</template> </div> <span v-if="additionalItemsSelected" class="b-dropdown-base-textbox__additional-items"> {{ additionalItemsSelected }} </span> <span v-if="hasSlot('icon') && !isFiltering" class="b-dropdown-base-textbox__icon" aria-hidden="true"> <slot name="icon"></slot> </span> <span v-if="isFiltering" class="b-dropdown-base-textbox__clear-search" :tabindex="disabled ? -1 : 0" data-testid="clear-search-button" @click.stop="onClearSearch" @keydown.enter.stop="onClearSearch" @keyup.space="onClearSearch" > <cross-circle-fill-small-icon v-if="size === BentoDropdownSize.SMALL" :svg-title="t('clearSearch')" /> <cross-circle-fill-icon v-else :svg-title="t('clearSearch')" /> </span> </bento-typography> </div> </template> <script setup lang="ts"> import { computed, defineComponent, inject, nextTick, type PropType, ref, useAttrs, useSlots, watch } from 'vue'; import { generateUid } from '@/core/utils/ts'; import { useI18n } from '@/utils/ts/i18n'; import { stopEventPropagation } from '@/utils/ts/events'; import { useHasSlot } from '@/composables'; import { BentoTypography } from '@/components/typography'; import { type Booleanish } from '@/types/prop-types'; import { type BentoListboxOptionItem } from '@/types/listbox'; import type { HTMLAttributes } from 'vue/types/jsx.d.ts'; import { DropdownBaseTextboxEvent, DropdownBaseTextboxState } from './dropdown-base-textbox.types'; import CrossCircleFillIcon from '@adyen/ui-assets-icons-16/vue/cross-circle-fill'; import CrossCircleFillSmallIcon from '@adyen/ui-assets-icons-16/vue/cross-circle-fill-small'; import { BentoDropdownSize } from '@/components/dropdown/dropdown.types'; import { DROPDOWN_UNDERLINE_STYLE_INJECTION_KEY } from '../../dropdown.keys'; import messages from './messages.json'; type MessageSchema = (typeof messages)['en-US']; const { t } = useI18n<{ message: MessageSchema }>({ messages }); const attrs = useAttrs(); const slots = useSlots(); const hasSlot = useHasSlot(slots); const props = defineProps({ /** * The number of additional items that have been selected in the dropdown. */ additionalItemsSelected: { type: [Number, String] as PropType<number | string>, default: 0 }, /** * If the combobox DOM element should be an `input` element. */ alwaysComboboxIsInput: { type: Boolean, default: false, }, /** * Sets the aria-controls attribute on the textbox. This should correspond to the popover's id that the textbox triggers. */ ariaControls: { type: String, required: true }, /** * Indicates whether the content the textbox triggers is visible or not. Needs to be set accordingly when the textbox is used and changed every time the popover is shown/hidden. */ ariaExpanded: { type: [String, Boolean] as PropType<Booleanish>, required: true, }, /** * If no label element is used then the aria-label is be set should be set. */ ariaLabel: { type: String, default: null }, /** * Identifies the currently active option in the listbox. */ ariaActiveDescendant: { type: String, default: null }, /** * Corresponds to the label's id that labels the textbox element. */ ariaLabelledby: { type: String, default: null }, /** * Disables any textbox functionality */ disabled: { type: Boolean, default: false }, /** * Selected option's label */ displayValue: { type: String, default: null }, /** * Sets the possibility to type into the textbox. */ dynamicFiltering: { type: Boolean, default: false }, /** * Indicates whether the dropdown currently has a selected value. */ hasSelectedValue: { type: Boolean, default: false }, /** * Marks the textbox as invalid, rendering an error state */ isInvalid: { type: Boolean, default: false }, /* * If enabled, the user will be able to select multiple items. */ multiple: { type: Boolean, default: false }, /** * Indicates if the dropdown is open so that * the respective event can be emitted */ open: { type: Boolean, default: false }, /** * Sets the dropdown to readonly state */ readonly: { type: Boolean, default: false }, /** * The input value */ value: { type: String, default: '' }, /** * The selected value's list box item. */ selectedValueItem: { type: [Object, Array] as PropType<BentoListboxOptionItem | Array<BentoListboxOptionItem>>, default: undefined, }, /** * Toggles whether to use the slot content select items shown in the textbox in multiple mode. */ showSlotContentInMultiple: { type: Boolean, default: false }, /** * Dropdown size */ size: { type: String as PropType<BentoDropdownSize | `${BentoDropdownSize}`>, default: null, }, }); const emit = defineEmits<{ /** * Emitted when the "clear" button is clicked */ (e: DropdownBaseTextboxEvent.CLEAR); /** * Emitted on click, space, enter and esc user events. It will close the dropdown. */ (e: DropdownBaseTextboxEvent.CLOSE); /** * Emitted when `dynamicFiltering` is enabled and a search input is entered */ (e: DropdownBaseTextboxEvent.INPUT, searchValue: string); /** * Emitted when any key is pressed and the focus is on the input field */ (e: DropdownBaseTextboxEvent.KEYDOWN, event: KeyboardEvent); /** * Emitted on click, space and enter user events. It will open the dropdown. */ (e: DropdownBaseTextboxEvent.OPEN); }>(); const textbox = ref(null); const textboxId = generateUid('textbox'); const isTextboxFocused = ref(false); const localInputValue = ref(props.value); // Inject underline style key to change the dropdown layout const isUnderlineStyle = inject(DROPDOWN_UNDERLINE_STYLE_INJECTION_KEY, false); const conditionalClasses = computed(() => ({ [`b-dropdown-base-textbox--${DropdownBaseTextboxState.DISABLED}`]: props.disabled, [`b-dropdown-base-textbox--${DropdownBaseTextboxState.READONLY}`]: props.readonly, [`b-dropdown-base-textbox--${DropdownBaseTextboxState.HAS_ITEMS}`]: !!props.additionalItemsSelected, 'b-dropdown-base-textbox--underline': isUnderlineStyle, })); const showRequiredAttribute = computed(() => shouldShowInput.value && attrs['aria-required'] ? ('true' as Booleanish) : null ); const comboboxConditionalClasses = computed(() => ({ [`b-dropdown-base-textbox__input--dynamic-filtering`]: props.dynamicFiltering, })); const isFiltering = computed(() => props.dynamicFiltering && !!props.value && props.open); const shouldShowAutocompleteSuggestion = (hasAutocompleteSuggestionSlot: boolean) => shouldShowInput.value && !props.multiple && !!props.value && hasAutocompleteSuggestionSlot; const ariaAttributes = computed( () => ({ 'aria-haspopup': 'listbox', 'aria-controls': props.ariaControls, 'aria-describedby': attrs['aria-describedby']?.toString() as string, 'aria-expanded': props.ariaExpanded ? 'true' : 'false', 'aria-activedescendant': props.ariaActiveDescendant, 'aria-labelledby': props.ariaLabelledby, 'aria-label': props.ariaLabelledby ? null : props.ariaLabel, 'aria-invalid': props.isInvalid, 'aria-disabled': props.disabled ? true : null, 'aria-required': attrs['aria-required'] as Booleanish, }) as HTMLAttributes ); // Cast selectedValueItem if it is an array of selected items const selectedValueItems = computed(() => props.multiple ? (props.selectedValueItem as Array<BentoListboxOptionItem>) : [] ); const shouldUseDynamicFilteringInputMode = computed( () => props.dynamicFiltering && !props.multiple && !props.alwaysComboboxIsInput ); const shouldShowSearchValue = computed( () => props.open || props.alwaysComboboxIsInput || (shouldUseDynamicFilteringInputMode.value && isTextboxFocused.value) ); const shouldShowInput = computed(() => props.dynamicFiltering && shouldShowSearchValue.value); const getInitialInputValue = () => { if (!props.value && props.hasSelectedValue && shouldUseDynamicFilteringInputMode.value) { return props.displayValue ?? ''; } return props.value; }; const inputValue = computed(() => { if (!shouldShowSearchValue.value || (props.alwaysComboboxIsInput && !props.open)) { return props.displayValue; } if (shouldUseDynamicFilteringInputMode.value) { return localInputValue.value; } return props.value; }); const shouldShowSingleSelectSlot = computed(() => props.selectedValueItem && !props.multiple); const shouldShowMultipleSlot = computed( () => props.multiple && props.selectedValueItem && selectedValueItems.value.length > 0 && props.showSlotContentInMultiple ); watch( () => shouldShowInput.value, (shouldShowInput, wasShowingInput) => { if (!shouldShowInput) { localInputValue.value = props.value; } else if (!wasShowingInput && shouldUseDynamicFilteringInputMode.value) { localInputValue.value = getInitialInputValue(); } if (shouldShowInput && !props.alwaysComboboxIsInput) { nextTick(() => { textbox.value?.focus(); if ( !props.open && !props.value && props.hasSelectedValue && shouldUseDynamicFilteringInputMode.value ) { textbox.value?.select(); } }); } } ); watch( () => props.value, newValue => { if (!shouldUseDynamicFilteringInputMode.value || newValue !== localInputValue.value) { localInputValue.value = newValue; } } ); watch( () => props.displayValue, () => { if (shouldUseDynamicFilteringInputMode.value && props.hasSelectedValue) { localInputValue.value = getInitialInputValue(); } } ); watch( () => props.open, open => { if (!open) { isTextboxFocused.value = false; } } ); const onOpenDropdown = async () => { if (!props.disabled && !props.readonly) { emit(DropdownBaseTextboxEvent.OPEN); // wait for popover to open before setting focus await nextTick(); textbox.value.focus(); } }; const onCloseDropdown = (event?: Event) => { if (props.open) { if (event) { stopEventPropagation(event); } textbox.value.focus(); emit(DropdownBaseTextboxEvent.CLOSE); } }; const onToggleDropdown = (event: Event) => { const isDynamicFilteringInputClickEvent = props.open && (event.target as HTMLDivElement).tagName === 'INPUT'; if (isDynamicFilteringInputClickEvent) { /** * When there's a click even on the input during filtering * and the popover is open, do not toggle the popover. * Allow for click events inside the input field */ return null; } return props.open ? onCloseDropdown() : onOpenDropdown(); }; const onTextboxEnterKey = (event: KeyboardEvent) => { if (props.dynamicFiltering) { return null; } return onToggleDropdown(event); }; const onClearSearch = () => { emit(DropdownBaseTextboxEvent.CLEAR); emit(DropdownBaseTextboxEvent.INPUT, ''); textbox.value.focus(); }; const onTextboxFocus = () => { isTextboxFocused.value = true; }; const onTextboxBlur = () => { isTextboxFocused.value = false; }; const onClosedComboboxMouseDown = (event: MouseEvent) => { if (!shouldUseDynamicFilteringInputMode.value || props.open || props.readonly || props.disabled) { return; } event.preventDefault(); event.stopPropagation(); isTextboxFocused.value = true; emit(DropdownBaseTextboxEvent.OPEN); }; const onInputEvent = (event: Event) => { if (!props.readonly && !props.disabled) { if (shouldUseDynamicFilteringInputMode.value) { localInputValue.value = (event.target as HTMLInputElement).value; } if (props.dynamicFiltering && !props.open) { emit(DropdownBaseTextboxEvent.OPEN); } emit(DropdownBaseTextboxEvent.INPUT, (event.target as HTMLInputElement).value); nextTick(() => { if (shouldShowInput.value) { textbox.value?.focus(); } }); } else { event.preventDefault(); event.stopPropagation(); } }; const focus = () => { textbox.value.focus(); }; defineExpose({ focus, }); </script> <script lang="ts"> /** * Only for internal use, implements the logic of the textbox element to be implemented in the combobox. */ export default defineComponent({ i18n: { messages }, name: 'dropdown-base-textbox', inheritAttrs: false, }); </script> <style lang="scss" scoped src="./dropdown-base-textbox.scss" />
|
|
1
|
+
<template> <div :class="$attrs.class" data-testid="textbox-wrapper" @click="onToggleDropdown" @keydown.escape="onCloseDropdown" > <bento-typography class="b-dropdown-base-textbox" :class="conditionalClasses" :variant="isUnderlineStyle ? 'title' : 'body'" el="div" data-testid="base-textbox" > <span v-if="shouldShowAutocompleteSuggestion(hasSlot('autocomplete-suggestion'))" class="b-dropdown-base-textbox__autocomplete-suggestion" aria-hidden="true" data-testid="autocomplete-suggestion" > <slot name="autocomplete-suggestion"></slot> </span> <input v-if="shouldShowInput" v-bind="ariaAttributes" :id="textboxId" ref="textbox" class="b-dropdown-base-textbox__input" role="combobox" type="text" :aria-autocomplete=" shouldShowAutocompleteSuggestion(hasSlot('autocomplete-suggestion')) ? 'both' : 'list' " size="1" :value="inputValue" :disabled="disabled ? true : null" :readonly="readonly" :required="showRequiredAttribute" @keydown="$emit(DropdownBaseTextboxEvent.KEYDOWN, $event)" @keydown.enter="onTextboxEnterKey" @focus="onTextboxFocus" @blur="onTextboxBlur" @input="onInputEvent" /> <div v-else :id="textboxId" v-bind="ariaAttributes" ref="textbox" class="b-dropdown-base-textbox__input" :class="comboboxConditionalClasses" :aria-readonly="readonly ? 'true' : null" role="combobox" :tabindex="disabled ? -1 : 0" @focus="onTextboxFocus" @mousedown="onClosedComboboxMouseDown" > <template v-if="shouldShowSingleSelectSlot"> <slot v-if="hasSlot('display-value')" v-bind="selectedValueItem" name="display-value"></slot> <slot v-else v-bind="selectedValueItem"> {{ displayValue }} </slot> </template> <template v-else-if="shouldShowMultipleSlot"> <div class="b-dropdown-base-textbox__input-multiple-selected"> <div v-for="singleItem of selectedValueItems" :key="singleItem.value"> <div class="b-dropdown-base-textbox__input-selected-element"> <slot v-bind="singleItem"> {{ displayValue }} </slot> </div> </div> </div> </template> <template v-else>{{ displayValue }}</template> </div> <span v-if="additionalItemsSelected" class="b-dropdown-base-textbox__additional-items"> {{ additionalItemsSelected }} </span> <span v-if="hasSlot('icon') && !isFiltering" class="b-dropdown-base-textbox__icon" aria-hidden="true"> <slot name="icon"></slot> </span> <span v-if="isFiltering" class="b-dropdown-base-textbox__clear-search" :tabindex="disabled ? -1 : 0" data-testid="clear-search-button" @click.stop="onClearSearch" @keydown.enter.stop="onClearSearch" @keyup.space="onClearSearch" > <cross-circle-fill-small-icon v-if="size === BentoDropdownSize.SMALL" :svg-title="t('clearSearch')" /> <cross-circle-fill-icon v-else :svg-title="t('clearSearch')" /> </span> </bento-typography> </div> </template> <script setup lang="ts"> import { computed, defineComponent, inject, nextTick, type PropType, ref, useAttrs, useSlots, watch } from 'vue'; import { generateUid } from '@/utils/ts/generate-uid'; import { useI18n } from '@/utils/ts/i18n'; import { stopEventPropagation } from '@/utils/ts/events'; import { useHasSlot } from '@/composables'; import { BentoTypography } from '@/components/typography'; import { type Booleanish } from '@/types/prop-types'; import { type BentoListboxOptionItem } from '@/types/listbox'; import type { HTMLAttributes } from 'vue/types/jsx.d.ts'; import { DropdownBaseTextboxEvent } from './dropdown-base-textbox.internal.types'; import { DropdownBaseTextboxState } from './dropdown-base-textbox.types'; import CrossCircleFillIcon from '@adyen/ui-assets-icons-16/vue/cross-circle-fill'; import CrossCircleFillSmallIcon from '@adyen/ui-assets-icons-16/vue/cross-circle-fill-small'; import { BentoDropdownSize } from '@/components/dropdown/dropdown.internal.types'; import { DROPDOWN_UNDERLINE_STYLE_INJECTION_KEY } from '../../dropdown.keys'; import messages from './messages.json'; type MessageSchema = (typeof messages)['en-US']; const { t } = useI18n<{ message: MessageSchema }>({ messages }); const attrs = useAttrs(); const slots = useSlots(); const hasSlot = useHasSlot(slots); const props = defineProps({ /** * The number of additional items that have been selected in the dropdown. */ additionalItemsSelected: { type: [Number, String] as PropType<number | string>, default: 0 }, /** * If the combobox DOM element should be an `input` element. */ alwaysComboboxIsInput: { type: Boolean, default: false, }, /** * Sets the aria-controls attribute on the textbox. This should correspond to the popover's id that the textbox triggers. */ ariaControls: { type: String, required: true }, /** * Indicates whether the content the textbox triggers is visible or not. Needs to be set accordingly when the textbox is used and changed every time the popover is shown/hidden. */ ariaExpanded: { type: [String, Boolean] as PropType<Booleanish>, required: true, }, /** * If no label element is used then the aria-label is be set should be set. */ ariaLabel: { type: String, default: null }, /** * Identifies the currently active option in the listbox. */ ariaActiveDescendant: { type: String, default: null }, /** * Corresponds to the label's id that labels the textbox element. */ ariaLabelledby: { type: String, default: null }, /** * Disables any textbox functionality */ disabled: { type: Boolean, default: false }, /** * Selected option's label */ displayValue: { type: String, default: null }, /** * Sets the possibility to type into the textbox. */ dynamicFiltering: { type: Boolean, default: false }, /** * Indicates whether the dropdown currently has a selected value. */ hasSelectedValue: { type: Boolean, default: false }, /** * Marks the textbox as invalid, rendering an error state */ isInvalid: { type: Boolean, default: false }, /* * If enabled, the user will be able to select multiple items. */ multiple: { type: Boolean, default: false }, /** * Indicates if the dropdown is open so that * the respective event can be emitted */ open: { type: Boolean, default: false }, /** * Sets the dropdown to readonly state */ readonly: { type: Boolean, default: false }, /** * The input value */ value: { type: String, default: '' }, /** * The selected value's list box item. */ selectedValueItem: { type: [Object, Array] as PropType<BentoListboxOptionItem | Array<BentoListboxOptionItem>>, default: undefined, }, /** * Toggles whether to use the slot content select items shown in the textbox in multiple mode. */ showSlotContentInMultiple: { type: Boolean, default: false }, /** * Dropdown size */ size: { type: String as PropType<BentoDropdownSize | `${BentoDropdownSize}`>, default: null, }, }); const emit = defineEmits<{ /** * Emitted when the "clear" button is clicked */ (e: DropdownBaseTextboxEvent.CLEAR); /** * Emitted on click, space, enter and esc user events. It will close the dropdown. */ (e: DropdownBaseTextboxEvent.CLOSE); /** * Emitted when `dynamicFiltering` is enabled and a search input is entered */ (e: DropdownBaseTextboxEvent.INPUT, searchValue: string); /** * Emitted when any key is pressed and the focus is on the input field */ (e: DropdownBaseTextboxEvent.KEYDOWN, event: KeyboardEvent); /** * Emitted on click, space and enter user events. It will open the dropdown. */ (e: DropdownBaseTextboxEvent.OPEN); }>(); const textbox = ref(null); const textboxId = generateUid('textbox'); const isTextboxFocused = ref(false); const localInputValue = ref(props.value); // Inject underline style key to change the dropdown layout const isUnderlineStyle = inject(DROPDOWN_UNDERLINE_STYLE_INJECTION_KEY, false); const conditionalClasses = computed(() => ({ [`b-dropdown-base-textbox--${DropdownBaseTextboxState.DISABLED}`]: props.disabled, [`b-dropdown-base-textbox--${DropdownBaseTextboxState.READONLY}`]: props.readonly, [`b-dropdown-base-textbox--${DropdownBaseTextboxState.HAS_ITEMS}`]: !!props.additionalItemsSelected, 'b-dropdown-base-textbox--underline': isUnderlineStyle, })); const showRequiredAttribute = computed(() => shouldShowInput.value && attrs['aria-required'] ? ('true' as Booleanish) : null ); const comboboxConditionalClasses = computed(() => ({ [`b-dropdown-base-textbox__input--dynamic-filtering`]: props.dynamicFiltering, })); const isFiltering = computed(() => props.dynamicFiltering && !!props.value && props.open); const shouldShowAutocompleteSuggestion = (hasAutocompleteSuggestionSlot: boolean) => shouldShowInput.value && !props.multiple && !!props.value && hasAutocompleteSuggestionSlot; const ariaAttributes = computed( () => ({ 'aria-haspopup': 'listbox', 'aria-controls': props.ariaControls, 'aria-describedby': attrs['aria-describedby']?.toString() as string, 'aria-expanded': props.ariaExpanded ? 'true' : 'false', 'aria-activedescendant': props.ariaActiveDescendant, 'aria-labelledby': props.ariaLabelledby, 'aria-label': props.ariaLabelledby ? null : props.ariaLabel, 'aria-invalid': props.isInvalid, 'aria-disabled': props.disabled ? true : null, 'aria-required': attrs['aria-required'] as Booleanish, }) as HTMLAttributes ); // Cast selectedValueItem if it is an array of selected items const selectedValueItems = computed(() => props.multiple ? (props.selectedValueItem as Array<BentoListboxOptionItem>) : [] ); const shouldUseDynamicFilteringInputMode = computed( () => props.dynamicFiltering && !props.multiple && !props.alwaysComboboxIsInput ); const shouldShowSearchValue = computed( () => props.open || props.alwaysComboboxIsInput || (shouldUseDynamicFilteringInputMode.value && isTextboxFocused.value) ); const shouldShowInput = computed(() => props.dynamicFiltering && shouldShowSearchValue.value); const getInitialInputValue = () => { if (!props.value && props.hasSelectedValue && shouldUseDynamicFilteringInputMode.value) { return props.displayValue ?? ''; } return props.value; }; const inputValue = computed(() => { if (!shouldShowSearchValue.value || (props.alwaysComboboxIsInput && !props.open)) { return props.displayValue; } if (shouldUseDynamicFilteringInputMode.value) { return localInputValue.value; } return props.value; }); const shouldShowSingleSelectSlot = computed(() => props.selectedValueItem && !props.multiple); const shouldShowMultipleSlot = computed( () => props.multiple && props.selectedValueItem && selectedValueItems.value.length > 0 && props.showSlotContentInMultiple ); watch( () => shouldShowInput.value, (shouldShowInput, wasShowingInput) => { if (!shouldShowInput) { localInputValue.value = props.value; } else if (!wasShowingInput && shouldUseDynamicFilteringInputMode.value) { localInputValue.value = getInitialInputValue(); } if (shouldShowInput && !props.alwaysComboboxIsInput) { nextTick(() => { textbox.value?.focus(); textbox.value?.select(); }); } } ); watch( () => props.value, newValue => { if (!shouldUseDynamicFilteringInputMode.value || newValue !== localInputValue.value) { localInputValue.value = newValue; } } ); watch( () => props.displayValue, () => { if (shouldUseDynamicFilteringInputMode.value && props.hasSelectedValue) { localInputValue.value = getInitialInputValue(); } } ); watch( () => props.open, open => { if (!open) { isTextboxFocused.value = false; } } ); const onOpenDropdown = async () => { if (!props.disabled && !props.readonly) { emit(DropdownBaseTextboxEvent.OPEN); // wait for popover to open before setting focus await nextTick(); textbox.value.focus(); } }; const onCloseDropdown = (event?: Event) => { if (props.open) { if (event) { stopEventPropagation(event); } textbox.value.focus(); emit(DropdownBaseTextboxEvent.CLOSE); } }; const onToggleDropdown = (event: Event) => { const isDynamicFilteringInputClickEvent = props.open && (event.target as HTMLDivElement).tagName === 'INPUT'; if (isDynamicFilteringInputClickEvent) { /** * When there's a click even on the input during filtering * and the popover is open, do not toggle the popover. * Allow for click events inside the input field */ return null; } return props.open ? onCloseDropdown() : onOpenDropdown(); }; const onTextboxEnterKey = (event: KeyboardEvent) => { if (props.dynamicFiltering) { return null; } return onToggleDropdown(event); }; const onClearSearch = () => { emit(DropdownBaseTextboxEvent.CLEAR); emit(DropdownBaseTextboxEvent.INPUT, ''); textbox.value.focus(); }; const onTextboxFocus = () => { isTextboxFocused.value = true; }; const onTextboxBlur = () => { isTextboxFocused.value = false; }; const onClosedComboboxMouseDown = (event: MouseEvent) => { if (!shouldUseDynamicFilteringInputMode.value || props.open || props.readonly || props.disabled) { return; } event.preventDefault(); event.stopPropagation(); }; const onInputEvent = (event: Event) => { if (!props.readonly && !props.disabled) { if (shouldUseDynamicFilteringInputMode.value) { localInputValue.value = (event.target as HTMLInputElement).value; } if (props.dynamicFiltering && !props.open) { emit(DropdownBaseTextboxEvent.OPEN); } emit(DropdownBaseTextboxEvent.INPUT, (event.target as HTMLInputElement).value); nextTick(() => { if (shouldShowInput.value) { textbox.value?.focus(); } }); } else { event.preventDefault(); event.stopPropagation(); } }; const focus = () => { textbox.value.focus(); }; defineExpose({ focus, }); </script> <script lang="ts"> /** * Only for internal use, implements the logic of the textbox element to be implemented in the combobox. */ export default defineComponent({ i18n: { messages }, name: 'dropdown-base-textbox', inheritAttrs: false, }); </script> <style lang="scss" scoped src="./dropdown-base-textbox.scss" />
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<template> <div class="b-dropdown-default-textbox" :class="conditionalClasses" data-testid="default-textbox"> <dropdown-base-textbox ref="baseTextbox" v-bind="{ ...$attrs, ...$props }" class="b-dropdown-default-textbox__textbox" :value="value" v-on="listeners" > <template v-for="(_, innerSlot) of slots" #[innerSlot]="scope"> <slot :name="innerSlot" v-bind="scope ?? {}" /> </template> <template #icon> <div class="b-dropdown-default-textbox__chevron"> <chevron-up-icon v-show="isDropdownOpen" svg-title="opened" /> <chevron-down-icon v-show="!isDropdownOpen" svg-title="closed" /> </div> </template> </dropdown-base-textbox> </div> </template> <script lang="ts" setup> import { computed, defineComponent, type PropType, ref, useSlots } from 'vue'; import DropdownBaseTextbox from '@/components/dropdown/components/dropdown-base-textbox/dropdown-base-textbox.vue'; import { type Booleanish } from '@/types/prop-types'; import { type BentoListboxOptionItem } from '@/types/listbox'; import { stopEventPropagation } from '@/utils/ts/events'; import { DropdownBaseTextboxEvent
|
|
1
|
+
<template> <div class="b-dropdown-default-textbox" :class="conditionalClasses" data-testid="default-textbox"> <dropdown-base-textbox ref="baseTextbox" v-bind="{ ...$attrs, ...$props }" class="b-dropdown-default-textbox__textbox" :value="value" v-on="listeners" > <template v-for="(_, innerSlot) of slots" #[innerSlot]="scope"> <slot :name="innerSlot" v-bind="scope ?? {}" /> </template> <template #icon> <div class="b-dropdown-default-textbox__chevron"> <chevron-up-icon v-show="isDropdownOpen" svg-title="opened" /> <chevron-down-icon v-show="!isDropdownOpen" svg-title="closed" /> </div> </template> </dropdown-base-textbox> </div> </template> <script lang="ts" setup> import { computed, defineComponent, type PropType, ref, useSlots } from 'vue'; import DropdownBaseTextbox from '@/components/dropdown/components/dropdown-base-textbox/dropdown-base-textbox.vue'; import { type Booleanish } from '@/types/prop-types'; import { type BentoListboxOptionItem } from '@/types/listbox'; import { stopEventPropagation } from '@/utils/ts/events'; import { DropdownBaseTextboxEvent } from '../dropdown-base-textbox/dropdown-base-textbox.internal.types'; import { DropdownBaseTextboxState } from '../dropdown-base-textbox/dropdown-base-textbox.types'; import ChevronDownIcon from '@adyen/ui-assets-icons-16/vue/chevron-down'; import ChevronUpIcon from '@adyen/ui-assets-icons-16/vue/chevron-up'; const emit = defineEmits<{ /** * Emitted on clearing the input. */ (e: 'clear'); /** * Emitted on click, space and enter user events. It will open the dropdown. */ (e: 'close'); /** * Emitted on input event. */ (e: 'input', newInput: string); /** * Emitted on keydown event. */ (e: 'keydown', keydownEvent: KeyboardEvent); /** * Emitted on click, space, enter and esc user events. It will close the dropdown. */ (e: 'open'); }>(); const slots = useSlots(); const props = defineProps({ /** * The number of additional items that have been selected in the dropdown. */ additionalItemsSelected: { type: [Number, String] as PropType<number | string>, default: 0 }, /** * Sets the aria-controls attribute on the textbox. This should correspond to the popover's id that the textbox triggers. */ ariaControls: { type: String, required: true, }, /** * Identifies the currently active option in the listbox. */ ariaActiveDescendant: { type: String, default: null, }, /** * Indicates whether the content the textbox triggers is visible or not. Needs to be set accordingly when the textbox is used and changed every time the popover is shown/hidden. */ ariaExpanded: { type: [String, Boolean] as PropType<Booleanish>, required: true, }, /** * If no label element is used then the aria-label is be set should be set. */ ariaLabel: { type: String, default: null, }, /** * Corresponds to the label's id that labels the textbox element. */ ariaLabelledby: { type: String, default: null, }, /** * Renders the dropdown with a condensed style. */ condensed: { type: Boolean, default: false }, /** * Disables any textbox functionality */ disabled: { type: Boolean, default: false }, /** * Selected option's label */ displayValue: { type: String, default: null }, /** * Sets the possibility to type into the textbox */ dynamicFiltering: { type: Boolean, default: false, }, /** * Marks the textbox as invalid, rendering an error state */ isInvalid: { type: Boolean, default: false }, /** * Indicates if the dropdown is open so that * the respective event can be emitted */ open: { type: Boolean, default: false }, /** * Sets the dropdown to readonly state */ readonly: { type: Boolean, default: false }, /** * The selected value's list box item. */ selectedValueItem: { type: [Object, Array] as PropType<BentoListboxOptionItem | Array<BentoListboxOptionItem>>, default: undefined, }, /** * Toggles whether to use the slot content select items shown in the textbox in multiple mode. */ showSlotContentInMultiple: { type: Boolean, default: false }, /** * The input value */ value: { type: String, default: '' }, /* * If enabled, the user will be able to select multiple items. */ multiple: { type: Boolean, default: false }, }); const baseTextbox = ref(null); const conditionalClasses = computed(() => ({ [`b-dropdown-default-textbox--${DropdownBaseTextboxState.ERROR}`]: !props.disabled && !props.readonly && props.isInvalid, [`b-dropdown-default-textbox--${DropdownBaseTextboxState.DISABLED}`]: props.disabled, [`b-dropdown-default-textbox--${DropdownBaseTextboxState.READONLY}`]: props.readonly, [`b-dropdown-default-textbox--${DropdownBaseTextboxState.OPENED}`]: props.open, 'b-dropdown-default-textbox--condensed': props.condensed, })); const isDropdownOpen = computed(() => props.open && !props.disabled && !props.readonly); const onInput = (newInput: string) => { emit('input', newInput); }; const onKeyDown = (e: KeyboardEvent) => { emit('keydown', e); }; const openDropdown = (e: Event) => { stopEventPropagation(e); emit('open'); }; const closeDropdown = (e: Event) => { stopEventPropagation(e); emit('close'); }; const clearValue = (e: Event) => { stopEventPropagation(e); emit('clear'); }; const listeners = { [DropdownBaseTextboxEvent.INPUT]: onInput, [DropdownBaseTextboxEvent.OPEN]: openDropdown, [DropdownBaseTextboxEvent.CLOSE]: closeDropdown, [DropdownBaseTextboxEvent.CLEAR]: clearValue, [DropdownBaseTextboxEvent.KEYDOWN]: onKeyDown, }; const focus = () => { baseTextbox.value.focus(); }; defineExpose({ focus, }); </script> <script lang="ts"> /** * For internal use only, textbox component used in the dropdown component. */ export default defineComponent({ name: 'dropdown-default-textbox', inheritAttrs: false, }); </script> <style lang="scss" scoped src="./dropdown-default-textbox.scss" />
|