@adyen/bento-mcp 0.1.4 → 0.3.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.
Files changed (198) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.md +50 -5
  3. package/SKILL.md +188 -0
  4. package/bin/add-skill.js +46 -0
  5. package/dist/assets/components/accordion/accordion.types.ts +1 -0
  6. package/dist/assets/components/action-bar/action-bar.types.ts +1 -0
  7. package/dist/assets/components/ai-tag/ai-tag.types.ts +1 -0
  8. package/dist/assets/components/alert/alert.types.ts +1 -0
  9. package/dist/assets/components/anchor-scroller/anchor-scroller.types.ts +1 -0
  10. package/dist/assets/components/anchor-scroller/components/anchor-scroller-list/anchor-scroller-list.types.ts +1 -0
  11. package/dist/assets/components/avatar/components/avatar-image/avatar-image.types.ts +1 -0
  12. package/dist/assets/components/button/button.types.ts +1 -0
  13. package/dist/assets/components/button/components/ai-button/ai-button.types.ts +1 -0
  14. package/dist/assets/components/button/components/button-actions/button-actions.types.ts +1 -0
  15. package/dist/assets/components/button/components/loading-button/loading-button.types.ts +1 -0
  16. package/dist/assets/components/button/components/toggle-button/toggle.types.ts +1 -0
  17. package/dist/assets/components/card/card.types.ts +1 -0
  18. package/dist/assets/components/checkbox/checkbox.types.ts +1 -0
  19. package/dist/assets/components/checkbox/components/checkbox-group/checkbox-group.types.ts +1 -0
  20. package/dist/assets/components/chip/chip.types.ts +1 -0
  21. package/dist/assets/components/code-snippet/code-snippet.types.ts +1 -0
  22. package/dist/assets/components/country/country.types.ts +1 -0
  23. package/dist/assets/components/currency/currency.types.ts +1 -0
  24. package/dist/assets/components/dashed-underline/dashed-underline.types.ts +1 -0
  25. package/dist/assets/components/data-grid/components/data-grid-cell/data-grid-cell.types.ts +1 -0
  26. package/dist/assets/components/data-grid/components/data-grid-cell/data-grid-cell.vue +1 -1
  27. package/dist/assets/components/data-grid/components/data-grid-cell-actions/data-grid-cell-actions.types.ts +1 -0
  28. package/dist/assets/components/data-grid/components/data-grid-columns/data-grid-columns.types.ts +1 -0
  29. package/dist/assets/components/data-grid/components/data-grid-columns/data-grid-columns.vue +1 -1
  30. package/dist/assets/components/data-grid/components/data-grid-config-settings/data-grid-config-settings.types.ts +1 -0
  31. package/dist/assets/components/data-grid/components/data-grid-row/data-grid-row.types.ts +1 -0
  32. package/dist/assets/components/data-grid/components/data-grid-row/data-grid-row.vue +1 -1
  33. package/dist/assets/components/data-grid/components/data-grid-static-icon/data-grid-static-icon.types.ts +1 -0
  34. package/dist/assets/components/data-grid/components/data-grid-summary-cell/data-grid-summary-cell.types.ts +1 -0
  35. package/dist/assets/components/data-grid/components/data-grid-tag-with-text-cell/data-grid-tag-with-text-cell.types.ts +1 -0
  36. package/dist/assets/components/data-grid/components/data-grid-trend-cell/data-grid-trend-cell.types.ts +1 -0
  37. package/dist/assets/components/data-grid/composables/use-calculate-column-width/use-calculate-column-width.types.ts +1 -0
  38. package/dist/assets/components/data-grid/composables/use-column-hovering/use-column-hovering.types.ts +1 -0
  39. package/dist/assets/components/data-grid/composables/use-config-settings/use-config.settings.types.ts +1 -0
  40. package/dist/assets/components/data-grid/composables/use-keyboard-navigator/use-keyboard-navigator.types.ts +1 -0
  41. package/dist/assets/components/data-grid/data-grid.types.ts +1 -0
  42. package/dist/assets/components/data-grid/data-grid.vue +1 -1
  43. package/dist/assets/components/date/date.types.ts +1 -0
  44. package/dist/assets/components/date-picker/composables/use-date-picker-single-calendar-text.types.ts +1 -0
  45. package/dist/assets/components/date-picker/date-picker.types.ts +1 -0
  46. package/dist/assets/components/date-range-picker/components/date-range-picker-calendar/date-range-picker-calendar.types.ts +1 -0
  47. package/dist/assets/components/date-range-picker/components/date-range-picker-calendar/date-range-picker-calendar.vue +1 -1
  48. package/dist/assets/components/date-range-picker/composables/use-date-range-picker-calendar-text.types.ts +1 -0
  49. package/dist/assets/components/date-range-picker/composables/use-granularity-memory.types.ts +1 -0
  50. package/dist/assets/components/date-range-picker/date-range-picker.types.ts +1 -0
  51. package/dist/assets/components/divider/divider.types.ts +1 -0
  52. package/dist/assets/components/draggable-handle/draggable-handle.types.ts +1 -0
  53. package/dist/assets/components/dropdown/components/dropdown-base-textbox/dropdown-base-textbox.types.ts +1 -0
  54. package/dist/assets/components/dropdown/components/variants/dropdown-avatar/dropdown-avatar.types.ts +1 -0
  55. package/dist/assets/components/dropdown/components/variants/dropdown-country/dropdown-country.types.ts +1 -0
  56. package/dist/assets/components/dropdown/components/variants/dropdown-currency/dropdown-currency.types.ts +1 -0
  57. package/dist/assets/components/dropdown/components/variants/dropdown-payment-method/dropdown-payment-method.types.ts +1 -0
  58. package/dist/assets/components/dropdown/components/variants/dropdown-tag/dropdown-tag.types.ts +1 -0
  59. package/dist/assets/components/dropdown/composables/use-display-value.types.ts +1 -0
  60. package/dist/assets/components/dropdown/composables/use-keyboard-navigation.types.ts +1 -0
  61. package/dist/assets/components/dropdown/dropdown.types.ts +1 -0
  62. package/dist/assets/components/empty-state/empty-state.types.ts +1 -0
  63. package/dist/assets/components/file-uploader/components/file-uploader-file-card/file-uploader-file-card.types.ts +1 -0
  64. package/dist/assets/components/file-uploader/components/file-uploader-restrictions/file-uploader-restrictions.types.ts +1 -0
  65. package/dist/assets/components/file-uploader/composables/use-drag-drop-state/use-drag-drop-state.types.ts +1 -0
  66. package/dist/assets/components/file-uploader/file-uploader.types.ts +1 -0
  67. package/dist/assets/components/filter-bar/components/all-filters-modal/all-filters-modal.types.ts +1 -0
  68. package/dist/assets/components/filter-bar/components/all-filters-modal/all-filters-modal.vue +1 -1
  69. package/dist/assets/components/filter-bar/components/base-filter/base-filter.types.ts +1 -0
  70. package/dist/assets/components/filter-bar/components/checkbox-group-filter/checkbox-group-filter.types.ts +1 -0
  71. package/dist/assets/components/filter-bar/components/filter-bar-button-group/filter-bar-button-group.types.ts +1 -0
  72. package/dist/assets/components/filter-bar/components/input-filter/input-filter.types.ts +1 -0
  73. package/dist/assets/components/filter-bar/components/radio-group-filter/radio-group-filter.types.ts +1 -0
  74. package/dist/assets/components/filter-bar/components/toggle-filter/toggle-filter.types.ts +1 -0
  75. package/dist/assets/components/filter-bar/filter-bar.types.ts +1 -0
  76. package/dist/assets/components/focus-trap/focus-trap.types.ts +1 -0
  77. package/dist/assets/components/form-layout/components/form-layout-field-tip/form-layout-tip.types.ts +1 -0
  78. package/dist/assets/components/form-layout/components/form-layout-group/form-layout-group.types.ts +1 -0
  79. package/dist/assets/components/form-layout/form-layout.types.ts +1 -0
  80. package/dist/assets/components/header/header.types.ts +1 -0
  81. package/dist/assets/components/header-with-views/components/header-with-views-all-views/header-with-views-all-views.types.ts +1 -0
  82. package/dist/assets/components/header-with-views/components/header-with-views-item-edit-form/header-with-views-edit-form.types.ts +1 -0
  83. package/dist/assets/components/header-with-views/header-with-views.types.ts +1 -0
  84. package/dist/assets/components/image/image.types.ts +1 -0
  85. package/dist/assets/components/info-icon/info-icon.types.ts +1 -0
  86. package/dist/assets/components/input-field/input-field.types.ts +1 -0
  87. package/dist/assets/components/input-field-password/input-field-password.types.ts +1 -0
  88. package/dist/assets/components/input-field-phone-number/input-field-phone-number.types.ts +1 -0
  89. package/dist/assets/components/input-time/input-time.docs.mdx +52 -0
  90. package/dist/assets/components/input-time/input-time.stories.ts +1 -0
  91. package/dist/assets/components/input-time/input-time.types.ts +1 -0
  92. package/dist/assets/components/input-time/input-time.vue +1 -0
  93. package/dist/assets/components/inspector/inspector.types.ts +1 -0
  94. package/dist/assets/components/internal/base-button/base-button.types.ts +1 -0
  95. package/dist/assets/components/internal/button-actions-with-menu/button-actions-with-menu.types.ts +1 -0
  96. package/dist/assets/components/internal/calendar/calendar.types.ts +1 -0
  97. package/dist/assets/components/internal/calendar/components/calendar-month/calendar-month.types.ts +1 -0
  98. package/dist/assets/components/internal/calendar/components/calendar-month/components/calendar-month-day/calendar-month-day.types.ts +1 -0
  99. package/dist/assets/components/internal/calendar/components/calendar-month/composables/granular-min-max-date.types.ts +1 -0
  100. package/dist/assets/components/internal/calendar/components/calendar-year/calendar-year.types.ts +1 -0
  101. package/dist/assets/components/internal/calendar/components/calendar-year/components/calendar-year-month/calendar-year-month.types.ts +1 -0
  102. package/dist/assets/components/internal/calendar/composables/use-granularity-adjustments.types.ts +1 -0
  103. package/dist/assets/components/internal/checkbox-input/checkbox-input.types.ts +1 -0
  104. package/dist/assets/components/internal/copy/copy.types.ts +1 -0
  105. package/dist/assets/components/internal/dialog-page/dialog-page.types.ts +1 -0
  106. package/dist/assets/components/internal/grid-layout/grid-layout.types.ts +1 -0
  107. package/dist/assets/components/internal/header-meta/header-meta.types.ts +1 -0
  108. 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 +1 -0
  109. package/dist/assets/components/internal/listbox/components/listbox-multi-select/components/listbox-multi-select-options/listbox-multi-select-options.types.ts +1 -0
  110. package/dist/assets/components/internal/listbox/components/listbox-multi-select/listbox-multi-select.types.ts +1 -0
  111. package/dist/assets/components/internal/listbox/listbox.types.ts +1 -0
  112. package/dist/assets/components/internal/radio-input/radio-input.types.ts +1 -0
  113. package/dist/assets/components/internal/teleport/teleport.types.ts +1 -0
  114. package/dist/assets/components/internal/tooltip/tooltip.types.ts +1 -0
  115. package/dist/assets/components/layout/layout.types.ts +1 -0
  116. package/dist/assets/components/link/link.types.ts +1 -0
  117. package/dist/assets/components/list/components/list-item/list-item.stories.ts +1 -0
  118. package/dist/assets/components/list/components/list-item/list-item.types.ts +1 -0
  119. package/dist/assets/components/list/components/list-item/list-item.vue +1 -0
  120. package/dist/assets/components/list/list.docs.mdx +97 -0
  121. package/dist/assets/components/list/list.stories.ts +1 -0
  122. package/dist/assets/components/list/list.types.ts +1 -0
  123. package/dist/assets/components/list/list.vue +1 -0
  124. package/dist/assets/components/loading-indicator/loading-indicator.types.ts +1 -0
  125. package/dist/assets/components/menu/components/menu-item/menu-item.types.ts +1 -0
  126. package/dist/assets/components/menu/menu.types.ts +1 -0
  127. package/dist/assets/components/modal/components/modal-page/modal-page.types.ts +1 -0
  128. package/dist/assets/components/modal/modal.types.ts +1 -0
  129. package/dist/assets/components/pagination/pagination.types.ts +1 -0
  130. package/dist/assets/components/payment-method/payment-method.types.ts +1 -0
  131. package/dist/assets/components/popover/popover.types.ts +1 -0
  132. package/dist/assets/components/popper-container/popper-container.types.ts +1 -0
  133. package/dist/assets/components/promo-banner/promo-banner.types.ts +1 -0
  134. package/dist/assets/components/radio-group/components/radio-button/radio-button.types.ts +1 -0
  135. package/dist/assets/components/radio-group/radio-group.types.ts +1 -0
  136. package/dist/assets/components/rich-text-editor/components/rich-text-editor-content-area/rich-text-editor-content-area.types.ts +1 -0
  137. package/dist/assets/components/rich-text-editor/components/rich-text-editor-renderer/components/rich-text-editor-code-block/rich-text-editor-code-block.types.ts +1 -0
  138. package/dist/assets/components/rich-text-editor/components/rich-text-editor-renderer/components/rich-text-editor-inline-code/rich-text-editor-inline-code.types.ts +1 -0
  139. package/dist/assets/components/rich-text-editor/components/rich-text-editor-renderer/components/rich-text-editor-link/rich-text-editor-link.types.ts +1 -0
  140. package/dist/assets/components/rich-text-editor/components/rich-text-editor-renderer/components/rich-text-editor-list-item/rich-text-editor-list-item.types.ts +1 -0
  141. package/dist/assets/components/rich-text-editor/components/rich-text-editor-renderer/rich-text-editor-renderer.types.ts +1 -0
  142. package/dist/assets/components/rich-text-editor/rich-text-editor.types.ts +1 -0
  143. package/dist/assets/components/search-bar/search-bar.types.ts +1 -0
  144. package/dist/assets/components/secondary-nav/components/secondary-nav-category/secondary-nav-category.vue +1 -1
  145. package/dist/assets/components/secondary-nav/components/secondary-nav-item/secondary-nav-item.vue +1 -1
  146. package/dist/assets/components/secondary-nav/secondary-nav.docs.mdx +32 -2
  147. package/dist/assets/components/secondary-nav/secondary-nav.stories.ts +1 -1
  148. package/dist/assets/components/secondary-nav/secondary-nav.types.ts +1 -0
  149. package/dist/assets/components/secondary-nav/secondary-nav.vue +1 -1
  150. package/dist/assets/components/segmented-control/segmented-control.types.ts +1 -0
  151. package/dist/assets/components/selection-card/components/selection-card-group/selection-card-group.types.ts +1 -0
  152. package/dist/assets/components/selection-card/selection-card.types.ts +1 -0
  153. package/dist/assets/components/sidepanel/sidepanel.types.ts +1 -0
  154. package/dist/assets/components/status/status.types.ts +1 -0
  155. package/dist/assets/components/stepper/stepper.types.ts +1 -0
  156. package/dist/assets/components/structured-list/structured-list.types.ts +1 -0
  157. package/dist/assets/components/summary-grid/components/summary-grid-item/summary-grid-item.types.ts +1 -0
  158. package/dist/assets/components/summary-grid/components/summary-grid-item-amount/summary-grid-item-amount.types.ts +1 -0
  159. package/dist/assets/components/summary-grid/components/summary-grid-item-custom/summary-grid-item-custom.types.ts +1 -0
  160. package/dist/assets/components/summary-grid/components/summary-grid-item-numeric/summary-grid-item-numeric.types.ts +1 -0
  161. package/dist/assets/components/summary-grid/components/summary-grid-item-percentage/summary-grid-item-percentage.types.ts +1 -0
  162. package/dist/assets/components/summary-grid/components/summary-grid-item-text/summary-grid-item-text.types.ts +1 -0
  163. package/dist/assets/components/summary-grid/summary-grid.types.ts +1 -0
  164. package/dist/assets/components/tabs/components/tab.types.ts +1 -0
  165. package/dist/assets/components/tabs/tabs.types.ts +1 -0
  166. package/dist/assets/components/tag/tag.types.ts +1 -0
  167. package/dist/assets/components/textarea/textarea.types.ts +1 -0
  168. package/dist/assets/components/timeline/components/timeline-item/timeline-item.vue +1 -1
  169. package/dist/assets/components/timeline/timeline.stories.ts +1 -1
  170. package/dist/assets/components/timeline/timeline.types.ts +1 -0
  171. package/dist/assets/components/toast/components/toast-shadow-stack/toast-shadow-stack.types.ts +1 -0
  172. package/dist/assets/components/toggle/toggle.types.ts +1 -0
  173. package/dist/assets/components/tutorial/tutorial.types.ts +1 -0
  174. package/dist/assets/components/typography/typography.types.ts +1 -0
  175. package/dist/assets/components.json +102 -0
  176. package/dist/assets/composables/use-bento-theme/install.ts +1 -0
  177. package/dist/assets/composables/use-bento-theme/install.types.ts +1 -0
  178. package/dist/assets/composables/use-bento-theme/use-bento-theme.types.ts +1 -0
  179. package/dist/assets/composables/use-bento-toast-controller/install.ts +1 -0
  180. package/dist/assets/composables/use-bento-toast-controller/use-bento-toast-controller.types.ts +1 -0
  181. package/dist/assets/composables/use-form-validate/use-form.types.ts +1 -0
  182. package/dist/assets/composables/use-form-validate-zod-4/use-form.types.ts +1 -0
  183. package/dist/assets/composables/use-virtual-list/use-virtual-list.types.ts +1 -0
  184. package/dist/assets/deprecations.md +330 -0
  185. package/dist/assets/directives/keyboard-navigation/keyboard-navigation.types.ts +1 -0
  186. package/dist/assets/directives/tooltip/tooltip.types.ts +1 -0
  187. package/dist/assets/icons.json +415 -0
  188. package/dist/assets/index.ts +1 -1
  189. package/dist/assets/install.ts +1 -0
  190. package/dist/assets/tailwind/README.md +89 -0
  191. package/dist/assets/templates/dashboard-template/dashboard-template.types.ts +1 -0
  192. package/dist/assets/templates/data-grid-template/data-grid-template.stories.ts +1 -1
  193. package/dist/assets/templates/data-grid-template/data-grid-template.types.ts +1 -0
  194. package/dist/assets/utility-classes.md +92 -0
  195. package/dist/assets/utils/ts/currency/currency.types.ts +1 -0
  196. package/dist/assets/variables.css +309 -0
  197. package/dist/main.js +395 -29
  198. package/package.json +15 -6
@@ -1 +1 @@
1
- <template> <div class="b-date-range-picker-calendar"> <!-- Form --> <div class="b-date-range-picker-calendar__form-container" :class="computedFormContainerClasses" data-testid="date-range-picker-calendar-form-container" > <div class="b-date-range-picker-calendar__form"> <template v-if="hasSlot('title')"> <slot name="title" /> </template> <bento-dropdown v-if="quickSelectRanges" :aria-label="t('customRange')" :items="quickSelectRangeDefaultItems" :model-value="selectedRangeSelectorValue" @update:model-value="onRangeSelectorInput" /> <bento-segmented-control v-if="granularities" :items="granularityItems" :model-value="internalRangeDate.granularity" full-width @update:model-value="onGranularityInput" > </bento-segmented-control> <div> <bento-typography stronger el="span" class="b-date-range-picker-calendar__label"> From </bento-typography> <div :class="formInputWrapperConditionalClasses"> <bento-input-field :aria-label="t('dateFrom')" :model-value="dateTextInput.startDate" :error="startDateError" class="b-date-range-picker-calendar__form-input" @update:model-value="onStartDateInput" @keydown="onDateInputKeyDown" > <template #description>{{ t(dateFormat) }}</template> </bento-input-field> <bento-input-field v-if="allowTimeInput" :aria-label="t('timeFrom')" :model-value="dateTextInput.startTime" :error="startTimeError" class="b-date-range-picker-calendar__form-input" @update:model-value="onStartTimeInput" @keydown="onTimeInputKeyDown" > <template #description>HH:MM:SS</template> </bento-input-field> </div> </div> <div> <bento-typography stronger el="span" class="b-date-range-picker-calendar__label"> To </bento-typography> <div :class="formInputWrapperConditionalClasses"> <bento-input-field class="b-date-range-picker-calendar__form-input" :aria-label="t('dateTo')" :model-value="dateTextInput.endDate" :error="endDateError" @update:model-value="onEndDateInput" @keydown="onDateInputKeyDown" > <template #description>{{ t(dateFormat) }}</template> </bento-input-field> <bento-input-field v-if="allowTimeInput" :aria-label="t('timeTo')" :model-value="dateTextInput.endTime" :error="endTimeError" class="b-date-range-picker-calendar__form-input" @update:model-value="onEndTimeInput" @keydown="onTimeInputKeyDown" > <template #description>HH:MM:SS</template> </bento-input-field> </div> </div> </div> <div v-if="hasSlot('actions')"> <slot name="actions" /> </div> </div> <!-- Calendar --> <div v-if="!hasNoCalendars" class="b-date-range-picker-calendar__calendars-container" data-testid="date-range-picker-calendar-calendar-container" > <calendar :value="internalRangeDate" :first-day-of-week="firstDayOfWeek" :is-date-disabled="isDateDisabled" :max-range="maxRange" :min="computedMinMax.min" :max="computedMinMax.max" :number-of-months="numberOfMonths" :show-end-date-on-open="showEndDateOnOpen" :granularity="selectedGranularity" :variant="variant" is-range @input="onDateInput" @start-date-selected="onStartDateSelected" @end-date-selected="onEndDateSelected" /> </div> </div> </template> <script setup lang="ts"> import { computed, onMounted, type PropType, reactive, ref, toRaw, useSlots } from 'vue'; import { Calendar, type CalendarGranularityType } from '@/internal'; import { BentoTypography } from '@/components/typography'; import { BentoInputField } from '@/components/input-field'; import BentoDropdown from '@/components/dropdown/dropdown.vue'; import { BentoSegmentedControl, type BentoSegmentedControlItem } from '@/components/segmented-control'; import { useI18n } from '@/utils/ts/i18n'; import { debounce } from '@/utils/ts/debounce'; import { useDateInputFormatter, useHasSlot } from '@/composables'; import { useGranularityAdjustments } from '@/components/internal/calendar/composables/use-granularity-adjustments'; import { useGranularMinMaxDate } from '@/components/internal/calendar/components/calendar-month/composables/granular-min-max-date'; import { useGranularityMemory } from '../../composables/use-granularity-memory'; import { DateRangePickerCalendarEvent, type DateRangePickerCalendarFormData, type DateRangePickerCalendarRangeSelectorItems, } from './date-range-picker-calendar.types'; import type { BentoDateRangePickerGranularityConfig, BentoDateRangePickerValue, } from '../../date-range-picker.types'; import { endOfDay } from 'date-fns/endOfDay'; import { setHours } from 'date-fns/setHours'; import { setMinutes } from 'date-fns/setMinutes'; import { setSeconds } from 'date-fns/setSeconds'; import { startOfDay } from 'date-fns/startOfDay'; import { isSameSecond } from 'date-fns/isSameSecond'; import { isSameDay } from 'date-fns/isSameDay'; import { isEqual } from 'date-fns/isEqual'; import { isToday } from 'date-fns/isToday'; import { setMilliseconds } from 'date-fns/setMilliseconds'; import { dateToTimeInputString } from '@/utils/ts/format-date/format-date'; import { formatTimeInputValue, validateTimeInput } from '@/utils/ts/time-input'; import messages from './messages.json'; const FORM_INPUT_DEBOUNCE_TIME = 300; const RANGE_SELECTOR_CUSTOM_RANGE_KEY = 'customRange'; type MessageSchema = (typeof messages)['en-US']; const props = defineProps({ /** * Allows user to enter time values in the form */ allowTimeInput: { type: Boolean, default: false }, /** * Ranges form persistance data */ dateFormData: { type: Object as PropType<DateRangePickerCalendarFormData>, default: () => ({ startDate: undefined, endDate: undefined, startTime: undefined, endTime: undefined }), }, /** * Allows you to set the first day of the week. 0 is sunday, 1 is monday, 6 is saturday */ firstDayOfWeek: Calendar.props.firstDayOfWeek, /** * A list of available granularities. * If present, the date picker will display a segmented control to change granularity. */ granularities: { type: Array as PropType<Array<BentoDateRangePickerGranularityConfig>>, default: null, }, /** * Indicate if a date should be disabled or not */ isDateDisabled: Calendar.props.isDateDisabled, /** * Set a maximum number of dates to be selectable by the range. */ maxRange: Calendar.props.maxRange, /** * Sets a minimum limit for selectable dates - dates before `min` will not be selectable */ min: Calendar.props.min, /** * Sets a maximum limit for selectable dates - dates after `max` will not be selectable */ max: Calendar.props.max, /** * Number of months rendered on pane */ numberOfMonths: { type: Calendar.props.numberOfMonths.type, default: Calendar.props.numberOfMonths.default, validator: (n: number) => n >= 0, }, /** * Displays the end date's month when the calendar is opened. Defaults to false i.e. the start date's month is displayed. */ showEndDateOnOpen: { type: Boolean, default: false }, /** * Enables the custom range selector. * If provided, must be an array that sets the custom range dropdown items. Items are of the structure: * `label` - label of custom range item. * `value` - a unique key of the custom range item. * `data` - an object `{ startDate: Date; endDate: Date }` to set the date picker range to upon selecting. */ quickSelectRanges: { type: Array as PropType<DateRangePickerCalendarRangeSelectorItems>, default: undefined, }, /** * Selected date */ value: { type: Object as PropType<BentoDateRangePickerValue>, default: undefined }, /** * The type of calendar to display. Defaults to showing days. */ variant: Calendar.props.variant, }); const emit = defineEmits([ DateRangePickerCalendarEvent.CUSTOM_RANGE, DateRangePickerCalendarEvent.INPUT, DateRangePickerCalendarEvent.ERROR, DateRangePickerCalendarEvent.FORM_DATE, DateRangePickerCalendarEvent.START_DATE_SELECTED, ]); const slots = useSlots(); const hasSlot = useHasSlot(slots); const { t } = useI18n<{ message: MessageSchema }>({ messages }); const { dateFormat, isFormatValid, parseDate, onKeyDown, autoFormat, formatDate: formatDateUtil, } = useDateInputFormatter(); const calculatedIsRelativeToNowQuickSelectRanges = ref({}); const defaultGranularity = computed(() => (props.variant === 'month' ? 'monthly' : 'daily')); const hasNoCalendars = computed(() => props.numberOfMonths === 0); const internalRangeDate = reactive<BentoDateRangePickerValue>({ startDate: props.value?.startDate, endDate: props.value?.endDate, range: props.value?.range, ...(props.granularities ? { granularity: props.value?.granularity || defaultGranularity.value, } : {}), }); /** * Currently range selector dropdown item that is selected. * The logic will try and find in each range item: * - If an endDate does not exist, then only check if the startDate day matches and if today's day matches. * - if time input has been enabled, then check if the seconds match. * - if an endDate does exist then just check if the startDate day and the endDate day match. * * Note: All date comparisons use local time consistently. Both sides of each comparison * originate from the same timezone context, so no UTC normalization is needed. */ const selectedRangeSelectorValue = computed(() => { return ( // eslint-disable-next-line consistent-return props?.quickSelectRanges?.find(({ value, data }) => { if (!data.endDate) { // Check if this quick select range is explicitly marked as "relative to now" if (data.isRelativeToNow) { // Retrieve previously calculated start/end dates for this relative range. // We use these if the user has already clicked on this time of quick range const newlyCalculatedQuickSelectDates = calculatedIsRelativeToNowQuickSelectRanges.value?.[value]; return newlyCalculatedQuickSelectDates ? isEqual(newlyCalculatedQuickSelectDates.startDate, internalRangeDate.startDate) && isEqual(newlyCalculatedQuickSelectDates.endDate, internalRangeDate.endDate) : // If not stored calculated dates, fall back to comparing the quick select item's value // with the internal range's stored range identifier. value === internalRangeDate.range; } // This block handles quick select ranges that do not have an endDate // AND are NOT explicitly marked as 'isRelativeToNow'. // This means the endDate will be today at the end of the day. // Uses isEqual for startDate to prevent false matches (e.g. same-day selection // accidentally matching a "This week" range). When time input is enabled, // also verify the end time is at end of day so that manual time changes // correctly fall back to "Custom range". return ( isEqual(data.startDate, internalRangeDate.startDate) && isToday(internalRangeDate.endDate) && (!props.allowTimeInput || isSameSecond(internalRangeDate.endDate, endOfDay(internalRangeDate.endDate))) && (props.granularities ? data.granularity === internalRangeDate.granularity : true) ); } if (props.allowTimeInput) { const res = isSameSecond(data.startDate, internalRangeDate.startDate) && isSameSecond(data.endDate, internalRangeDate.endDate) && (props.granularities ? data.granularity === internalRangeDate.granularity : true); return res; } if (data.endDate) { return ( isSameDay(data.startDate, internalRangeDate.startDate) && isSameDay(data.endDate, internalRangeDate.endDate) && (props.granularities ? data.granularity === internalRangeDate.granularity : true) ); } })?.value ?? RANGE_SELECTOR_CUSTOM_RANGE_KEY ); }); const formInputWrapperConditionalClasses = computed(() => ({ 'b-date-range-picker-calendar__form-input-wrapper': props.allowTimeInput, })); const quickSelectRangeDefaultItems = computed(() => [ { label: t('customRange'), value: RANGE_SELECTOR_CUSTOM_RANGE_KEY }, ...(props.quickSelectRanges ? props.quickSelectRanges : []), ]); const selectedGranularity = computed( () => props.granularities?.find(({ type }) => type === internalRangeDate.granularity) || null ); const granularityItems = computed<Array<BentoSegmentedControlItem>>( () => props.granularities?.map(({ type, disabled }) => ({ label: t(type), value: type, disabled, })) ?? [] ); const { getGranularCalendarLimits } = useGranularMinMaxDate(props); const computedMinMax = computed(() => props.granularities ? getGranularCalendarLimits(selectedGranularity.value) : { min: props.min ? startOfDay(props.min) : null, max: props.max ? endOfDay(props.max) : null } ); const { adjustDateForQuarterly, adjustDateForMonthly, adjustDateForWeekly, adjustStartDateInputForWeekly, adjustEndDateInputForWeekly, adjustStartDateInputForMonthly, adjustEndDateInputForMonthly, adjustStartDateInputForQuarterly, adjustEndDateInputForQuarterly, } = useGranularityAdjustments(props.firstDayOfWeek); const { savedRanges, saveRange, resetSavedRanges } = useGranularityMemory(); /** * Format date to a string value in current format * @param dateToFormat - Date to be formatted */ const formatDate = (dateToFormat?: Date) => { if (!dateToFormat) { return ''; } return formatDateUtil(dateToFormat); }; // Form error flags const startDateError = ref(false); const endDateError = ref(false); const startTimeError = ref(false); const endTimeError = ref(false); // Form input text used in range variant const dateTextInput = reactive({ startDate: props.dateFormData.startDate, endDate: props.dateFormData.endDate, startTime: props.dateFormData.startTime, endTime: props.dateFormData.endTime, }); /** * Sets the time of a date object from a time string. * @param dateToSetTimeIn The date object to set the time in. * @param time The time string in HH:MM:SS format. * @returns A new Date object with the time set. */ const setTimeInDateObject = (dateToSetTimeIn: Date, time: string) => { let dateWithTime = new Date(dateToSetTimeIn); const [hours, minutes, seconds] = time.split(':'); dateWithTime = setHours(dateWithTime, parseInt(hours, 10)); dateWithTime = setMinutes(dateWithTime, parseInt(minutes, 10)); dateWithTime = setSeconds(dateWithTime, parseInt(seconds, 10)); return dateWithTime; }; const isValidTimeString = (timeText: string) => /^(?:[01]\d|2[01234]):(?:[012345]\d):(?:[012345]\d)$/.test(timeText); /** * Checks if a time string is valid and within the min/max bounds if a date is provided. * @param timeText The time string to validate. * @param date The date string to check the time against. * @returns True if the time is valid, false otherwise. */ const isValidTime = (timeText: string, date?: string) => { const isTimeAllowed = () => { if (date && isValidDate(date)) { const dateToCheckWithTime = setTimeInDateObject(parseDate(date), timeText); // Check if date is between min/max bounds, if there are any if ( (props.min && dateToCheckWithTime < setMilliseconds(props.min, 0)) || (props.max && dateToCheckWithTime > setMilliseconds(props.max, 0)) ) { return false; } } return true; }; return isValidTimeString(timeText) && isTimeAllowed(); }; /** * Sets the time in a date object if the time is valid and the variant is not 'month'. * @param date The date object to modify. * @param time The time string to set. * @returns The modified date object or the original if time is not set. */ const setTimeInDateRef = (date: Date, time: string) => { // Do not set time if month variant if (!time || !isValidTimeString(time) || props.variant === 'month') { return date; } return setTimeInDateObject(new Date(date), time); }; const onDateInput = (selectedRange: BentoDateRangePickerValue) => { dateTextInput.startTime = dateToTimeInputString( props.min && props.allowTimeInput && isSameDay(props.min, selectedRange.startDate) ? props.min : startOfDay(selectedRange.startDate) ); dateTextInput.endTime = dateToTimeInputString( props.max && props.allowTimeInput && isSameDay(props.max, selectedRange.endDate) ? props.max : endOfDay(selectedRange.endDate) ); internalRangeDate.startDate = setTimeInDateRef(selectedRange.startDate, dateTextInput.startTime); internalRangeDate.endDate = setTimeInDateRef(selectedRange.endDate, dateTextInput.endTime); // Resetting internal range date when selecting a custom range through the calendar internalRangeDate.range = null; if (props.granularities) { // Reset saved ranges on input resetSavedRanges(); } emit(DateRangePickerCalendarEvent.INPUT, { ...internalRangeDate, range: selectedRangeSelectorValue.value, }); }; /** * Checks if a date string is in current format and if the date is enabled. * @param dateText The date string to validate. * @returns True if the date is valid and enabled, false otherwise. */ const isValidDate = (dateText: string) => { if (!dateText) { return false; } // Is date selectable const isDateEnabled = (date: Date) => { // Check if date is between or equal to min/max bounds // Set the time to the start of the day to disregard time if ( (props.min && startOfDay(date) < startOfDay(props.min)) || (props.max && startOfDay(date) > startOfDay(props.max)) ) { return false; } // Check if function is set, otherwise all dates are enabled if (props.isDateDisabled) { return !props.isDateDisabled(startOfDay(date)); } return true; }; // Date matches text regex and is not disabled return isFormatValid(dateText) && isDateEnabled(parseDate(dateText)); }; /** * When mounted, set the input error states to true if the values are not valid */ onMounted(() => { // Start date validation startDateError.value = !!(props.dateFormData.startDate && !isValidDate(props.dateFormData.startDate)); // Start time validation if (props.allowTimeInput) { startTimeError.value = !!( props.dateFormData.startTime && !isValidTime(props.dateFormData.startTime, props.dateFormData.startDate) ); } else { startTimeError.value = false; } // End date validation endDateError.value = !!(props.dateFormData.endDate && !isValidDate(props.dateFormData.endDate)); // End time validation if (props.allowTimeInput) { endTimeError.value = !!( props.dateFormData.endTime && !isValidTime(props.dateFormData.endTime, props.dateFormData.endDate) ); } else { endTimeError.value = false; } }); const onStartDateSelected = (newStartDate: Date) => { dateTextInput.startDate = formatDate(newStartDate); if (!dateTextInput.startTime) { // Set the time to the beginning of the day dateTextInput.startTime = dateToTimeInputString(newStartDate); } else if (startTimeError.value) { // Set the form time if it was previously set dateTextInput.startTime = props.dateFormData?.startTime; } // Reset errors startDateError.value = false; startTimeError.value = false; emit(DateRangePickerCalendarEvent.START_DATE_SELECTED, newStartDate); }; const onEndDateSelected = (newEndDate: Date) => { dateTextInput.endDate = formatDate(newEndDate); if (!dateTextInput.endTime) { // Set the time to the end of the day dateTextInput.endTime = dateToTimeInputString(endOfDay(newEndDate)); } else if (endTimeError.value) { // Set the form time if it was previously set dateTextInput.endTime = props.dateFormData?.endTime; } // Reset errors endDateError.value = false; endTimeError.value = false; }; const getDate = (keyName: keyof BentoDateRangePickerValue) => { const dateProp = (props.value as BentoDateRangePickerValue)[keyName]; if (dateProp) { return dateProp; } if (props.dateFormData[keyName] && isValidDate(props.dateFormData[keyName])) { return parseDate(props.dateFormData[keyName]); } return undefined; }; const validateStartDate = debounce((startDateText: string) => { if (isValidDate(startDateText)) { let dateText = startDateText; startDateError.value = false; switch (selectedGranularity.value?.type) { case 'weekly': dateText = adjustStartDateInputForWeekly( startDateText, formatDate, computedMinMax.value.min, computedMinMax.value.max ); dateTextInput.startDate = dateText; break; case 'monthly': dateText = adjustStartDateInputForMonthly( startDateText, formatDate, computedMinMax.value.min, computedMinMax.value.max ); dateTextInput.startDate = dateText; break; case 'quarterly': dateText = adjustStartDateInputForQuarterly( startDateText, formatDate, computedMinMax.value.min, computedMinMax.value.max ); dateTextInput.startDate = dateText; break; case 'daily': default: break; } const startDate = startOfDay(parseDate(dateText)); internalRangeDate.startDate = setTimeInDateRef(startDate, dateTextInput.startTime); internalRangeDate.endDate = setTimeInDateRef( startOfDay(parseDate(dateTextInput.endDate)), dateTextInput.endTime ); if (isValidDate(dateTextInput.endDate)) { internalRangeDate.endDate = setTimeInDateRef( startOfDay(parseDate(dateTextInput.endDate)), dateTextInput.endTime ); } const endDate = getDate('endDate'); // Resetting internal range date when selecting a custom range through the calendar internalRangeDate.range = null; emit(DateRangePickerCalendarEvent.FORM_DATE, { startDate, endDate, }); emit(DateRangePickerCalendarEvent.INPUT, { ...internalRangeDate, range: selectedRangeSelectorValue.value, }); } else { // Indicate form error startDateError.value = true; // Emit Date picker calendar error emit(DateRangePickerCalendarEvent.ERROR, { ...dateTextInput }); } }, FORM_INPUT_DEBOUNCE_TIME); const onStartDateInput = (startDateText: string) => { // Set the date as string when input through the form dateTextInput.startDate = autoFormat(startDateText); validateStartDate(dateTextInput.startDate); }; const validateEndDate = debounce((endDateText: string) => { if (isValidDate(endDateText)) { let dateText = endDateText; endDateError.value = false; switch (selectedGranularity.value?.type) { case 'weekly': dateText = adjustEndDateInputForWeekly( endDateText, formatDate, computedMinMax.value.min, computedMinMax.value.max ); dateTextInput.endDate = dateText; break; case 'monthly': dateText = adjustEndDateInputForMonthly( endDateText, formatDate, computedMinMax.value.min, computedMinMax.value.max ); dateTextInput.endDate = dateText; break; case 'quarterly': dateText = adjustEndDateInputForQuarterly( endDateText, formatDate, computedMinMax.value.min, computedMinMax.value.max ); dateTextInput.endDate = dateText; break; case 'daily': default: break; } // Format date string to Date object const endDate = startOfDay(parseDate(dateText)); internalRangeDate.endDate = setTimeInDateRef(endDate, dateTextInput.endTime); if (isValidDate(dateTextInput.startDate)) { internalRangeDate.startDate = setTimeInDateRef( startOfDay(parseDate(dateTextInput.startDate)), dateTextInput.startTime ); } const startDate = getDate('startDate'); // Resetting internal range date when selecting a custom range through the calendar internalRangeDate.range = null; emit(DateRangePickerCalendarEvent.FORM_DATE, { startDate, endDate, }); emit(DateRangePickerCalendarEvent.INPUT, { ...internalRangeDate, range: selectedRangeSelectorValue.value, }); } else { // Indicate form error endDateError.value = true; // Emit Date picker calendar error emit(DateRangePickerCalendarEvent.ERROR, { ...dateTextInput }); } }, FORM_INPUT_DEBOUNCE_TIME); const onEndDateInput = (endDateText: string) => { // Set the date as string when input through the form dateTextInput.endDate = autoFormat(endDateText); validateEndDate(dateTextInput.endDate); }; const onDateInputKeyDown = (event: KeyboardEvent) => { onKeyDown(event); }; const validateStartTime = debounce((startTimeText: string) => { if (isValidTime(startTimeText, props.dateFormData.startDate)) { startTimeError.value = false; internalRangeDate.startDate = setTimeInDateRef(internalRangeDate.startDate, startTimeText); const endDate = getDate('endDate'); emit(DateRangePickerCalendarEvent.FORM_DATE, { startDate: internalRangeDate.startDate, endDate, startTime: startTimeText, endTime: dateTextInput.endTime ?? props.dateFormData.endTime, }); emit(DateRangePickerCalendarEvent.INPUT, { ...internalRangeDate, range: selectedRangeSelectorValue.value, }); } else { // Indicate form error startTimeError.value = true; // Emit Date picker calendar error emit(DateRangePickerCalendarEvent.ERROR, { ...dateTextInput }); } }); const validateEndTime = debounce((endTimeText: string) => { if (isValidTime(endTimeText, props.dateFormData.endDate)) { endTimeError.value = false; internalRangeDate.endDate = setTimeInDateRef(internalRangeDate.endDate, endTimeText); emit(DateRangePickerCalendarEvent.FORM_DATE, { startDate: internalRangeDate.startDate, endDate: internalRangeDate.endDate, startTime: dateTextInput.startTime ?? props.dateFormData.startTime, endTime: endTimeText, }); emit(DateRangePickerCalendarEvent.INPUT, { ...internalRangeDate, range: selectedRangeSelectorValue.value, }); } else { // Indicate form error endTimeError.value = true; // Emit Date picker calendar error emit(DateRangePickerCalendarEvent.ERROR, { ...dateTextInput }); } }); const onStartTimeInput = (startTimeText: string) => { dateTextInput.startTime = formatTimeInputValue(startTimeText); validateStartTime(dateTextInput.startTime); }; const onEndTimeInput = (endDateText: string) => { dateTextInput.endTime = formatTimeInputValue(endDateText); validateEndTime(dateTextInput.endTime); }; const onTimeInputKeyDown = (event: KeyboardEvent) => { validateTimeInput(event); }; const setInternalRange = ( startDate: Date, endDate: Date, granularity?: CalendarGranularityType, range?: string ) => { internalRangeDate.startDate = startDate; internalRangeDate.endDate = endDate; if (granularity) { internalRangeDate.granularity = granularity; } if (range) { internalRangeDate.range = range; } }; const onRangeSelectorInput = (newCustomRangeValue: string) => { const foundResult = props.quickSelectRanges?.find(({ value }) => value === newCustomRangeValue); if (foundResult) { resetSavedRanges(); const date = foundResult.data; const newEndDate = date.endDate || (date.isRelativeToNow ? new Date(Date.now()) : endOfDay(new Date(Date.now()))); // If the time difference between start and end dates is available, // calculating start date dynamically const newStartDate = date.isRelativeToNow && date.timeDifference ? new Date(newEndDate.getTime() - date.timeDifference) : date.startDate; // Set Form input Dates dateTextInput.endDate = formatDate(newEndDate); dateTextInput.startDate = formatDate(newStartDate); setInternalRange(newStartDate, newEndDate, null, newCustomRangeValue); if (props.granularities) { internalRangeDate.granularity = date.granularity || defaultGranularity.value; } // Set Form input time to quick select range time dateTextInput.startTime = dateToTimeInputString(newStartDate); dateTextInput.endTime = dateToTimeInputString(newEndDate); // Set calendar selection internalRangeDate.startDate = newStartDate; internalRangeDate.endDate = newEndDate; if (date.isRelativeToNow) { // Store this calculated start and end dates to be used in the selectedRangeSelectorValue logic calculatedIsRelativeToNowQuickSelectRanges.value = { ...calculatedIsRelativeToNowQuickSelectRanges.value, [newCustomRangeValue]: { startDate: newStartDate, endDate: newEndDate, }, }; } // Emit everytime the date range changes emit(DateRangePickerCalendarEvent.CUSTOM_RANGE, { ...foundResult.data, startDate: newStartDate, endDate: newEndDate, range: foundResult.value, }); } }; const adjustDate = { weekly: adjustDateForWeekly, monthly: adjustDateForMonthly, quarterly: adjustDateForQuarterly, }; const onGranularityInput = (value: CalendarGranularityType) => { const previousValue = internalRangeDate.granularity; internalRangeDate.granularity = value; // Save previously selected range if (previousValue) { saveRange(previousValue, structuredClone(toRaw(internalRangeDate))); } if (internalRangeDate.startDate && internalRangeDate.endDate) { // Adjust date range based on new granularity switch (value) { case 'quarterly': case 'monthly': case 'weekly': { if (savedRanges[value]) { setInternalRange(savedRanges[value].startDate, savedRanges[value].endDate, value); break; } const { startDate, endDate } = adjustDate[value]( internalRangeDate.startDate, internalRangeDate.endDate, computedMinMax.value.min, computedMinMax.value.max ); setInternalRange(startDate, endDate, value); break; } case 'daily': if (savedRanges.daily) { setInternalRange(savedRanges.daily.startDate, savedRanges.daily.endDate, value); } break; default: break; } dateTextInput.startDate = formatDate(internalRangeDate.startDate); dateTextInput.endDate = formatDate(internalRangeDate.endDate); emit(DateRangePickerCalendarEvent.INPUT, { ...internalRangeDate, range: selectedRangeSelectorValue.value, }); } }; const computedFormContainerClasses = computed(() => ({ 'b-date-range-picker-calendar__form-container--no-calendars': hasNoCalendars.value, })); </script> <script lang="ts"> /** * Calendar for range dates. Contains the logic to handle the range forms. * * @example * import DatePickerCalendarRange from './date-range-picker-calendar.vue'; * * export default { * components: { DatePickerCalendarRange }, * template: ` * <date-range-picker-calendar * :value="{startDate: new Date(), endDate: new Date() }" * :first-day-of-week="BentoDatePickerFirstDayOfWeek.MONDAY" * :is-date-disabled="(date: Date) => boolean" * :number-of-months="2" * @input="({ startDate, endDate }) => void" * /> * ` * } */ export default { name: 'date-range-picker-calendar', i18n: { messages }, }; </script> <style lang="scss" scoped src="./date-range-picker-calendar.scss" />
1
+ <template> <div class="b-date-range-picker-calendar"> <!-- Form --> <div class="b-date-range-picker-calendar__form-container" :class="computedFormContainerClasses" data-testid="date-range-picker-calendar-form-container" > <div class="b-date-range-picker-calendar__form"> <template v-if="hasSlot('title')"> <slot name="title" /> </template> <bento-dropdown v-if="quickSelectRanges" :aria-label="t('customRange')" :items="quickSelectRangeDefaultItems" :model-value="selectedRangeSelectorValue" @update:model-value="onRangeSelectorInput" /> <bento-segmented-control v-if="granularities" :items="granularityItems" :model-value="internalRangeDate.granularity" full-width @update:model-value="onGranularityInput" > </bento-segmented-control> <div> <bento-typography stronger el="span" class="b-date-range-picker-calendar__label"> From </bento-typography> <div :class="formInputWrapperConditionalClasses"> <bento-input-field :aria-label="t('dateFrom')" :model-value="dateTextInput.startDate" :error="startDateError" class="b-date-range-picker-calendar__form-input" @update:model-value="onStartDateInput" @keydown="onDateInputKeyDown" > <template #description>{{ t(dateFormat) }}</template> </bento-input-field> <bento-input-time v-if="allowTimeInput" :aria-label="t('timeFrom')" :model-value="dateTextInput.startTime" class="b-date-range-picker-calendar__form-input" @update:model-value="onStartTimeInput" @input:error="onStartTimeError" @input:valid="onStartTimeValid" /> </div> </div> <div> <bento-typography stronger el="span" class="b-date-range-picker-calendar__label"> To </bento-typography> <div :class="formInputWrapperConditionalClasses"> <bento-input-field class="b-date-range-picker-calendar__form-input" :aria-label="t('dateTo')" :model-value="dateTextInput.endDate" :error="endDateError" @update:model-value="onEndDateInput" @keydown="onDateInputKeyDown" > <template #description>{{ t(dateFormat) }}</template> </bento-input-field> <bento-input-time v-if="allowTimeInput" :aria-label="t('timeTo')" :model-value="dateTextInput.endTime" class="b-date-range-picker-calendar__form-input" @update:model-value="onEndTimeInput" @input:error="onEndTimeError" @input:valid="onEndTimeValid" /> </div> </div> </div> <div v-if="hasSlot('actions')"> <slot name="actions" /> </div> </div> <!-- Calendar --> <div v-if="!hasNoCalendars" class="b-date-range-picker-calendar__calendars-container" data-testid="date-range-picker-calendar-calendar-container" > <calendar :value="internalRangeDate" :first-day-of-week="firstDayOfWeek" :is-date-disabled="isDateDisabled" :max-range="maxRange" :min="computedMinMax.min" :max="computedMinMax.max" :number-of-months="numberOfMonths" :show-end-date-on-open="showEndDateOnOpen" :granularity="selectedGranularity" :variant="variant" is-range @input="onDateInput" @start-date-selected="onStartDateSelected" @end-date-selected="onEndDateSelected" /> </div> </div> </template> <script setup lang="ts"> import { computed, onMounted, type PropType, reactive, ref, toRaw, useSlots } from 'vue'; import { Calendar, type CalendarGranularityType } from '@/internal'; import { BentoTypography } from '@/components/typography'; import { BentoInputField } from '@/components/input-field'; import { BentoInputTime } from '@/components/input-time'; import BentoDropdown from '@/components/dropdown/dropdown.vue'; import { BentoSegmentedControl, type BentoSegmentedControlItem } from '@/components/segmented-control'; import { useI18n } from '@/utils/ts/i18n'; import { debounce } from '@/utils/ts/debounce'; import { useDateInputFormatter, useHasSlot } from '@/composables'; import { useGranularityAdjustments } from '@/components/internal/calendar/composables/use-granularity-adjustments'; import { useGranularMinMaxDate } from '@/components/internal/calendar/components/calendar-month/composables/granular-min-max-date'; import { useGranularityMemory } from '../../composables/use-granularity-memory'; import { DateRangePickerCalendarEvent, type DateRangePickerCalendarFormData, type DateRangePickerCalendarRangeSelectorItems, } from './date-range-picker-calendar.types'; import type { BentoDateRangePickerGranularityConfig, BentoDateRangePickerValue, } from '../../date-range-picker.types'; import { endOfDay } from 'date-fns/endOfDay'; import { setHours } from 'date-fns/setHours'; import { setMinutes } from 'date-fns/setMinutes'; import { setSeconds } from 'date-fns/setSeconds'; import { startOfDay } from 'date-fns/startOfDay'; import { isSameSecond } from 'date-fns/isSameSecond'; import { isSameDay } from 'date-fns/isSameDay'; import { isEqual } from 'date-fns/isEqual'; import { isToday } from 'date-fns/isToday'; import { setMilliseconds } from 'date-fns/setMilliseconds'; import { dateToTimeInputString } from '@/utils/ts/format-date/format-date'; import { isValidTimeString } from '@/utils/ts/time-input'; import messages from './messages.json'; const FORM_INPUT_DEBOUNCE_TIME = 300; const RANGE_SELECTOR_CUSTOM_RANGE_KEY = 'customRange'; type MessageSchema = (typeof messages)['en-US']; const props = defineProps({ /** * Allows user to enter time values in the form */ allowTimeInput: { type: Boolean, default: false }, /** * Ranges form persistance data */ dateFormData: { type: Object as PropType<DateRangePickerCalendarFormData>, default: () => ({ startDate: undefined, endDate: undefined, startTime: undefined, endTime: undefined }), }, /** * Allows you to set the first day of the week. 0 is sunday, 1 is monday, 6 is saturday */ firstDayOfWeek: Calendar.props.firstDayOfWeek, /** * A list of available granularities. * If present, the date picker will display a segmented control to change granularity. */ granularities: { type: Array as PropType<Array<BentoDateRangePickerGranularityConfig>>, default: null, }, /** * Indicate if a date should be disabled or not */ isDateDisabled: Calendar.props.isDateDisabled, /** * Set a maximum number of dates to be selectable by the range. */ maxRange: Calendar.props.maxRange, /** * Sets a minimum limit for selectable dates - dates before `min` will not be selectable */ min: Calendar.props.min, /** * Sets a maximum limit for selectable dates - dates after `max` will not be selectable */ max: Calendar.props.max, /** * Number of months rendered on pane */ numberOfMonths: { type: Calendar.props.numberOfMonths.type, default: Calendar.props.numberOfMonths.default, validator: (n: number) => n >= 0, }, /** * Displays the end date's month when the calendar is opened. Defaults to false i.e. the start date's month is displayed. */ showEndDateOnOpen: { type: Boolean, default: false }, /** * Enables the custom range selector. * If provided, must be an array that sets the custom range dropdown items. Items are of the structure: * `label` - label of custom range item. * `value` - a unique key of the custom range item. * `data` - an object `{ startDate: Date; endDate: Date }` to set the date picker range to upon selecting. */ quickSelectRanges: { type: Array as PropType<DateRangePickerCalendarRangeSelectorItems>, default: undefined, }, /** * Selected date */ value: { type: Object as PropType<BentoDateRangePickerValue>, default: undefined }, /** * The type of calendar to display. Defaults to showing days. */ variant: Calendar.props.variant, }); const emit = defineEmits([ DateRangePickerCalendarEvent.CUSTOM_RANGE, DateRangePickerCalendarEvent.INPUT, DateRangePickerCalendarEvent.ERROR, DateRangePickerCalendarEvent.FORM_DATE, DateRangePickerCalendarEvent.START_DATE_SELECTED, ]); const slots = useSlots(); const hasSlot = useHasSlot(slots); const { t } = useI18n<{ message: MessageSchema }>({ messages }); const { dateFormat, isFormatValid, parseDate, onKeyDown, autoFormat, formatDate: formatDateUtil, } = useDateInputFormatter(); const calculatedIsRelativeToNowQuickSelectRanges = ref({}); const defaultGranularity = computed(() => (props.variant === 'month' ? 'monthly' : 'daily')); const hasNoCalendars = computed(() => props.numberOfMonths === 0); const internalRangeDate = reactive<BentoDateRangePickerValue>({ startDate: props.value?.startDate, endDate: props.value?.endDate, range: props.value?.range, ...(props.granularities ? { granularity: props.value?.granularity || defaultGranularity.value, } : {}), }); /** * Currently range selector dropdown item that is selected. * The logic will try and find in each range item: * - If an endDate does not exist, then only check if the startDate day matches and if today's day matches. * - if time input has been enabled, then check if the seconds match. * - if an endDate does exist then just check if the startDate day and the endDate day match. * * Note: All date comparisons use local time consistently. Both sides of each comparison * originate from the same timezone context, so no UTC normalization is needed. */ const selectedRangeSelectorValue = computed(() => { return ( // eslint-disable-next-line consistent-return props?.quickSelectRanges?.find(({ value, data }) => { if (!data.endDate) { // Check if this quick select range is explicitly marked as "relative to now" if (data.isRelativeToNow) { // Retrieve previously calculated start/end dates for this relative range. // We use these if the user has already clicked on this time of quick range const newlyCalculatedQuickSelectDates = calculatedIsRelativeToNowQuickSelectRanges.value?.[value]; return newlyCalculatedQuickSelectDates ? isEqual(newlyCalculatedQuickSelectDates.startDate, internalRangeDate.startDate) && isEqual(newlyCalculatedQuickSelectDates.endDate, internalRangeDate.endDate) : // If not stored calculated dates, fall back to comparing the quick select item's value // with the internal range's stored range identifier. value === internalRangeDate.range; } // This block handles quick select ranges that do not have an endDate // AND are NOT explicitly marked as 'isRelativeToNow'. // This means the endDate will be today at the end of the day. // Uses isEqual for startDate to prevent false matches (e.g. same-day selection // accidentally matching a "This week" range). When time input is enabled, // also verify the end time is at end of day so that manual time changes // correctly fall back to "Custom range". return ( isEqual(data.startDate, internalRangeDate.startDate) && isToday(internalRangeDate.endDate) && (!props.allowTimeInput || isSameSecond(internalRangeDate.endDate, endOfDay(internalRangeDate.endDate))) && (props.granularities ? data.granularity === internalRangeDate.granularity : true) ); } if (props.allowTimeInput) { const res = isSameSecond(data.startDate, internalRangeDate.startDate) && isSameSecond(data.endDate, internalRangeDate.endDate) && (props.granularities ? data.granularity === internalRangeDate.granularity : true); return res; } if (data.endDate) { return ( isSameDay(data.startDate, internalRangeDate.startDate) && isSameDay(data.endDate, internalRangeDate.endDate) && (props.granularities ? data.granularity === internalRangeDate.granularity : true) ); } })?.value ?? RANGE_SELECTOR_CUSTOM_RANGE_KEY ); }); const formInputWrapperConditionalClasses = computed(() => ({ 'b-date-range-picker-calendar__form-input-wrapper': props.allowTimeInput, })); const quickSelectRangeDefaultItems = computed(() => [ { label: t('customRange'), value: RANGE_SELECTOR_CUSTOM_RANGE_KEY }, ...(props.quickSelectRanges ? props.quickSelectRanges : []), ]); const selectedGranularity = computed( () => props.granularities?.find(({ type }) => type === internalRangeDate.granularity) || null ); const granularityItems = computed<Array<BentoSegmentedControlItem>>( () => props.granularities?.map(({ type, disabled }) => ({ label: t(type), value: type, disabled, })) ?? [] ); const { getGranularCalendarLimits } = useGranularMinMaxDate(props); const computedMinMax = computed(() => props.granularities ? getGranularCalendarLimits(selectedGranularity.value) : { min: props.min ? startOfDay(props.min) : null, max: props.max ? endOfDay(props.max) : null } ); const { adjustDateForQuarterly, adjustDateForMonthly, adjustDateForWeekly, adjustStartDateInputForWeekly, adjustEndDateInputForWeekly, adjustStartDateInputForMonthly, adjustEndDateInputForMonthly, adjustStartDateInputForQuarterly, adjustEndDateInputForQuarterly, } = useGranularityAdjustments(props.firstDayOfWeek); const { savedRanges, saveRange, resetSavedRanges } = useGranularityMemory(); /** * Format date to a string value in current format * @param dateToFormat - Date to be formatted */ const formatDate = (dateToFormat?: Date) => { if (!dateToFormat) { return ''; } return formatDateUtil(dateToFormat); }; // Form error flags const startDateError = ref(false); const endDateError = ref(false); // Form input text used in range variant const dateTextInput = reactive({ startDate: props.dateFormData.startDate, endDate: props.dateFormData.endDate, startTime: props.dateFormData.startTime, endTime: props.dateFormData.endTime, }); /** * Sets the time of a date object from a time string. * @param dateToSetTimeIn The date object to set the time in. * @param time The time string in HH:MM:SS format. * @returns A new Date object with the time set. */ const setTimeInDateObject = (dateToSetTimeIn: Date, time: string) => { let dateWithTime = new Date(dateToSetTimeIn); const [hours, minutes, seconds] = time.split(':'); dateWithTime = setHours(dateWithTime, parseInt(hours, 10)); dateWithTime = setMinutes(dateWithTime, parseInt(minutes, 10)); dateWithTime = setSeconds(dateWithTime, parseInt(seconds, 10)); return dateWithTime; }; /** * Checks if a time string is valid and within the min/max bounds if a date is provided. * @param timeText The time string to validate. * @param date The date string to check the time against. * @returns True if the time is valid, false otherwise. */ const isValidTime = (timeText: string, date?: string) => { const isTimeAllowed = () => { if (date && isValidDate(date)) { const dateToCheckWithTime = setTimeInDateObject(parseDate(date), timeText); // Check if date is between min/max bounds, if there are any if ( (props.min && dateToCheckWithTime < setMilliseconds(props.min, 0)) || (props.max && dateToCheckWithTime > setMilliseconds(props.max, 0)) ) { return false; } } return true; }; return isValidTimeString(timeText) && isTimeAllowed(); }; /** * Sets the time in a date object if the time is valid and the variant is not 'month'. * @param date The date object to modify. * @param time The time string to set. * @returns The modified date object or the original if time is not set. */ const setTimeInDateRef = (date: Date, time: string) => { // Do not set time if month variant if (!time || !isValidTimeString(time) || props.variant === 'month') { return date; } return setTimeInDateObject(new Date(date), time); }; const onDateInput = (selectedRange: BentoDateRangePickerValue) => { dateTextInput.startTime = dateToTimeInputString( props.min && props.allowTimeInput && isSameDay(props.min, selectedRange.startDate) ? props.min : startOfDay(selectedRange.startDate) ); dateTextInput.endTime = dateToTimeInputString( props.max && props.allowTimeInput && isSameDay(props.max, selectedRange.endDate) ? props.max : endOfDay(selectedRange.endDate) ); internalRangeDate.startDate = setTimeInDateRef(selectedRange.startDate, dateTextInput.startTime); internalRangeDate.endDate = setTimeInDateRef(selectedRange.endDate, dateTextInput.endTime); // Resetting internal range date when selecting a custom range through the calendar internalRangeDate.range = null; if (props.granularities) { // Reset saved ranges on input resetSavedRanges(); } emit(DateRangePickerCalendarEvent.INPUT, { ...internalRangeDate, range: selectedRangeSelectorValue.value, }); }; /** * Checks if a date string is in current format and if the date is enabled. * @param dateText The date string to validate. * @returns True if the date is valid and enabled, false otherwise. */ const isValidDate = (dateText: string) => { if (!dateText) { return false; } // Is date selectable const isDateEnabled = (date: Date) => { // Check if date is between or equal to min/max bounds // Set the time to the start of the day to disregard time if ( (props.min && startOfDay(date) < startOfDay(props.min)) || (props.max && startOfDay(date) > startOfDay(props.max)) ) { return false; } // Check if function is set, otherwise all dates are enabled if (props.isDateDisabled) { return !props.isDateDisabled(startOfDay(date)); } return true; }; // Date matches text regex and is not disabled return isFormatValid(dateText) && isDateEnabled(parseDate(dateText)); }; /** * When mounted, set the input error states to true if the values are not valid */ onMounted(() => { // Start date validation startDateError.value = !!(props.dateFormData.startDate && !isValidDate(props.dateFormData.startDate)); // Start time validation is delegated to bento-input-time via events // End date validation endDateError.value = !!(props.dateFormData.endDate && !isValidDate(props.dateFormData.endDate)); // End time validation is delegated to bento-input-time via events }); const onStartDateSelected = (newStartDate: Date) => { dateTextInput.startDate = formatDate(newStartDate); if (!dateTextInput.startTime) { // Set the time to the beginning of the day dateTextInput.startTime = dateToTimeInputString(newStartDate); } startDateError.value = false; emit(DateRangePickerCalendarEvent.START_DATE_SELECTED, newStartDate); }; const onEndDateSelected = (newEndDate: Date) => { dateTextInput.endDate = formatDate(newEndDate); if (!dateTextInput.endTime) { // Set the time to the end of the day dateTextInput.endTime = dateToTimeInputString(endOfDay(newEndDate)); } endDateError.value = false; }; const getDate = (keyName: keyof BentoDateRangePickerValue) => { const dateProp = (props.value as BentoDateRangePickerValue)[keyName]; if (dateProp) { return dateProp; } if (props.dateFormData[keyName] && isValidDate(props.dateFormData[keyName])) { return parseDate(props.dateFormData[keyName]); } return undefined; }; const validateStartDate = debounce((startDateText: string) => { if (isValidDate(startDateText)) { let dateText = startDateText; startDateError.value = false; switch (selectedGranularity.value?.type) { case 'weekly': dateText = adjustStartDateInputForWeekly( startDateText, formatDate, computedMinMax.value.min, computedMinMax.value.max ); dateTextInput.startDate = dateText; break; case 'monthly': dateText = adjustStartDateInputForMonthly( startDateText, formatDate, computedMinMax.value.min, computedMinMax.value.max ); dateTextInput.startDate = dateText; break; case 'quarterly': dateText = adjustStartDateInputForQuarterly( startDateText, formatDate, computedMinMax.value.min, computedMinMax.value.max ); dateTextInput.startDate = dateText; break; case 'daily': default: break; } const startDate = startOfDay(parseDate(dateText)); internalRangeDate.startDate = setTimeInDateRef(startDate, dateTextInput.startTime); internalRangeDate.endDate = setTimeInDateRef( startOfDay(parseDate(dateTextInput.endDate)), dateTextInput.endTime ); if (isValidDate(dateTextInput.endDate)) { internalRangeDate.endDate = setTimeInDateRef( startOfDay(parseDate(dateTextInput.endDate)), dateTextInput.endTime ); } const endDate = getDate('endDate'); // Resetting internal range date when selecting a custom range through the calendar internalRangeDate.range = null; emit(DateRangePickerCalendarEvent.FORM_DATE, { startDate, endDate, }); emit(DateRangePickerCalendarEvent.INPUT, { ...internalRangeDate, range: selectedRangeSelectorValue.value, }); } else { // Indicate form error startDateError.value = true; // Emit Date picker calendar error emit(DateRangePickerCalendarEvent.ERROR, { ...dateTextInput }); } }, FORM_INPUT_DEBOUNCE_TIME); const onStartDateInput = (startDateText: string) => { // Set the date as string when input through the form dateTextInput.startDate = autoFormat(startDateText); validateStartDate(dateTextInput.startDate); }; const validateEndDate = debounce((endDateText: string) => { if (isValidDate(endDateText)) { let dateText = endDateText; endDateError.value = false; switch (selectedGranularity.value?.type) { case 'weekly': dateText = adjustEndDateInputForWeekly( endDateText, formatDate, computedMinMax.value.min, computedMinMax.value.max ); dateTextInput.endDate = dateText; break; case 'monthly': dateText = adjustEndDateInputForMonthly( endDateText, formatDate, computedMinMax.value.min, computedMinMax.value.max ); dateTextInput.endDate = dateText; break; case 'quarterly': dateText = adjustEndDateInputForQuarterly( endDateText, formatDate, computedMinMax.value.min, computedMinMax.value.max ); dateTextInput.endDate = dateText; break; case 'daily': default: break; } // Format date string to Date object const endDate = startOfDay(parseDate(dateText)); internalRangeDate.endDate = setTimeInDateRef(endDate, dateTextInput.endTime); if (isValidDate(dateTextInput.startDate)) { internalRangeDate.startDate = setTimeInDateRef( startOfDay(parseDate(dateTextInput.startDate)), dateTextInput.startTime ); } const startDate = getDate('startDate'); // Resetting internal range date when selecting a custom range through the calendar internalRangeDate.range = null; emit(DateRangePickerCalendarEvent.FORM_DATE, { startDate, endDate, }); emit(DateRangePickerCalendarEvent.INPUT, { ...internalRangeDate, range: selectedRangeSelectorValue.value, }); } else { // Indicate form error endDateError.value = true; // Emit Date picker calendar error emit(DateRangePickerCalendarEvent.ERROR, { ...dateTextInput }); } }, FORM_INPUT_DEBOUNCE_TIME); const onEndDateInput = (endDateText: string) => { // Set the date as string when input through the form dateTextInput.endDate = autoFormat(endDateText); validateEndDate(dateTextInput.endDate); }; const onDateInputKeyDown = (event: KeyboardEvent) => { onKeyDown(event); }; const onStartTimeError = () => { emit(DateRangePickerCalendarEvent.ERROR, { ...dateTextInput }); }; const onStartTimeValid = () => { const startTimeText = dateTextInput.startTime; if (!isValidTime(startTimeText, dateTextInput.startDate)) { emit(DateRangePickerCalendarEvent.ERROR, { ...dateTextInput }); return; } internalRangeDate.startDate = setTimeInDateRef(internalRangeDate.startDate, startTimeText); const endDate = getDate('endDate'); emit(DateRangePickerCalendarEvent.FORM_DATE, { startDate: internalRangeDate.startDate, endDate, startTime: startTimeText, endTime: dateTextInput.endTime ?? props.dateFormData.endTime, }); emit(DateRangePickerCalendarEvent.INPUT, { ...internalRangeDate, range: selectedRangeSelectorValue.value, }); }; const onEndTimeError = () => { emit(DateRangePickerCalendarEvent.ERROR, { ...dateTextInput }); }; const onEndTimeValid = () => { const endTimeText = dateTextInput.endTime; if (!isValidTime(endTimeText, dateTextInput.endDate)) { emit(DateRangePickerCalendarEvent.ERROR, { ...dateTextInput }); return; } internalRangeDate.endDate = setTimeInDateRef(internalRangeDate.endDate, endTimeText); emit(DateRangePickerCalendarEvent.FORM_DATE, { startDate: internalRangeDate.startDate, endDate: internalRangeDate.endDate, startTime: dateTextInput.startTime ?? props.dateFormData.startTime, endTime: endTimeText, }); emit(DateRangePickerCalendarEvent.INPUT, { ...internalRangeDate, range: selectedRangeSelectorValue.value, }); }; const onStartTimeInput = (startTimeText: string) => { dateTextInput.startTime = startTimeText; }; const onEndTimeInput = (endTimeText: string) => { dateTextInput.endTime = endTimeText; }; const setInternalRange = ( startDate: Date, endDate: Date, granularity?: CalendarGranularityType, range?: string ) => { internalRangeDate.startDate = startDate; internalRangeDate.endDate = endDate; if (granularity) { internalRangeDate.granularity = granularity; } if (range) { internalRangeDate.range = range; } }; const onRangeSelectorInput = (newCustomRangeValue: string) => { const foundResult = props.quickSelectRanges?.find(({ value }) => value === newCustomRangeValue); if (foundResult) { resetSavedRanges(); const date = foundResult.data; const newEndDate = date.endDate || (date.isRelativeToNow ? new Date(Date.now()) : endOfDay(new Date(Date.now()))); // If the time difference between start and end dates is available, // calculating start date dynamically const newStartDate = date.isRelativeToNow && date.timeDifference ? new Date(newEndDate.getTime() - date.timeDifference) : date.startDate; // Set Form input Dates dateTextInput.endDate = formatDate(newEndDate); dateTextInput.startDate = formatDate(newStartDate); setInternalRange(newStartDate, newEndDate, null, newCustomRangeValue); if (props.granularities) { internalRangeDate.granularity = date.granularity || defaultGranularity.value; } // Set Form input time to quick select range time dateTextInput.startTime = dateToTimeInputString(newStartDate); dateTextInput.endTime = dateToTimeInputString(newEndDate); // Set calendar selection internalRangeDate.startDate = newStartDate; internalRangeDate.endDate = newEndDate; if (date.isRelativeToNow) { // Store this calculated start and end dates to be used in the selectedRangeSelectorValue logic calculatedIsRelativeToNowQuickSelectRanges.value = { ...calculatedIsRelativeToNowQuickSelectRanges.value, [newCustomRangeValue]: { startDate: newStartDate, endDate: newEndDate, }, }; } // Emit everytime the date range changes emit(DateRangePickerCalendarEvent.CUSTOM_RANGE, { ...foundResult.data, startDate: newStartDate, endDate: newEndDate, range: foundResult.value, }); } }; const adjustDate = { weekly: adjustDateForWeekly, monthly: adjustDateForMonthly, quarterly: adjustDateForQuarterly, }; const onGranularityInput = (value: CalendarGranularityType) => { const previousValue = internalRangeDate.granularity; internalRangeDate.granularity = value; // Save previously selected range if (previousValue) { saveRange(previousValue, structuredClone(toRaw(internalRangeDate))); } if (internalRangeDate.startDate && internalRangeDate.endDate) { // Adjust date range based on new granularity switch (value) { case 'quarterly': case 'monthly': case 'weekly': { if (savedRanges[value]) { setInternalRange(savedRanges[value].startDate, savedRanges[value].endDate, value); break; } const { startDate, endDate } = adjustDate[value]( internalRangeDate.startDate, internalRangeDate.endDate, computedMinMax.value.min, computedMinMax.value.max ); setInternalRange(startDate, endDate, value); break; } case 'daily': if (savedRanges.daily) { setInternalRange(savedRanges.daily.startDate, savedRanges.daily.endDate, value); } break; default: break; } dateTextInput.startDate = formatDate(internalRangeDate.startDate); dateTextInput.endDate = formatDate(internalRangeDate.endDate); emit(DateRangePickerCalendarEvent.INPUT, { ...internalRangeDate, range: selectedRangeSelectorValue.value, }); } }; const computedFormContainerClasses = computed(() => ({ 'b-date-range-picker-calendar__form-container--no-calendars': hasNoCalendars.value, })); </script> <script lang="ts"> /** * Calendar for range dates. Contains the logic to handle the range forms. * * @example * import DatePickerCalendarRange from './date-range-picker-calendar.vue'; * * export default { * components: { DatePickerCalendarRange }, * template: ` * <date-range-picker-calendar * :value="{startDate: new Date(), endDate: new Date() }" * :first-day-of-week="BentoDatePickerFirstDayOfWeek.MONDAY" * :is-date-disabled="(date: Date) => boolean" * :number-of-months="2" * @input="({ startDate, endDate }) => void" * /> * ` * } */ export default { name: 'date-range-picker-calendar', i18n: { messages }, }; </script> <style lang="scss" scoped src="./date-range-picker-calendar.scss" />
@@ -0,0 +1 @@
1
+ import { type ComputedRef, type Ref } from 'vue'; import { type DateRangePickerCalendarFormData, type DateRangePickerCalendarRangeSelectorItems, } from '../components/date-range-picker-calendar/date-range-picker-calendar.types'; export interface UseDateDisplayTextProps<T> { datePickerValue: Ref<T>; isDateIncorrect: Ref<boolean>; quickSelectRanges: Ref<DateRangePickerCalendarRangeSelectorItems>; isMonthVariant?: Ref<boolean>; withTimeInput: ComputedRef<boolean>; placeholder?: Ref<string>; } export type UseDateDisplayText<T> = (props: UseDateDisplayTextProps<T>) => { // Values dateDisplayValue: ComputedRef<string>; textInputDisplayValue: DateRangePickerCalendarFormData; // Methods formatTextInputDisplayValue: () => void; };
@@ -0,0 +1 @@
1
+ import type { CalendarGranularityType } from '../../internal/calendar/calendar.types'; export type UseGranularityMemory = () => { savedRanges: { daily: { startDate: Date; endDate: Date } | null; weekly: { startDate: Date; endDate: Date } | null; monthly: { startDate: Date; endDate: Date } | null; quarterly: { startDate: Date; endDate: Date } | null; }; saveRange: (type: CalendarGranularityType, range: { startDate: Date; endDate: Date }) => void; resetSavedRanges: () => void; };
@@ -0,0 +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, } 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 for usage with properties since version 2.0. Use the string literal instead. */ export enum BentoDateRangePickerEvent { INPUT = 'input', } export type BentoDateRangePickerEmits = FormFieldEmits<any>; 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; /** * Selected date */ modelValue?: BentoDateRangePickerValue | null; /** * Selected date * @deprecated for usage with properties since version 2.0. Use `v-model` or `model-value` instead. */ value?: BentoDateRangePickerValue | null; /** * The type of calendar to display. Defaults to showing days. */ variant?: `${BentoDateRangePickerVariant}`; /** * Enables the date picker selection to persist until it is explicitly applied. * Displays a set of buttons: Apply and Cancel. * @deprecated This will be the default behavior in v2.0.0. * @default false */ hasActions?: boolean; }
@@ -0,0 +1 @@
1
+ /** * Private types */ export enum BentoDividerVariant { HORIZONTAL = 'horizontal', VERTICAL = 'vertical', } /** * Public types */ export interface BentoDividerProps { /** * Renders the divider in a horizontal or vertical variant */ variant?: `${BentoDividerVariant}`; }
@@ -0,0 +1 @@
1
+ import { type BentoMenuProps } from '../menu/menu.types'; export interface BentoDraggableHandleProps { /** * Reduces the size of the handle * @default false */ condensed?: BentoMenuProps['button']['condensed']; /** * Allows to disable the menus individually * @default {} */ disabledMenus?: { bottom?: boolean; down?: boolean; left?: boolean; right?: boolean; top?: boolean; up?: boolean; }; /** * Title or label used to describe the element being dragged. * Used for accessibility. */ label: string; /** * Determines the set of directional actions available in the handle's context menu. * * - `'all-directions'`: Includes 'Move up', 'Move down', 'Move left', 'Move right', 'Move to top', and 'Move to bottom'. * - `'up-and-down'`: Includes 'Move up', 'Move down', 'Move to top', and 'Move to bottom'. * * @default 'up-and-down' */ variant?: 'all-directions' | 'up-and-down'; }
@@ -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 DropdownBaseTextboxState { DISABLED = 'disabled', READONLY = 'readonly', HAS_ITEMS = 'has-items', OPENED = 'opened', ERROR = 'error', } 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;
@@ -0,0 +1 @@
1
+ import { type BentoListboxOptionItem } from '@/types/listbox'; import { type BentoDropdownProps } from '../../../dropdown.types'; export type BentoAvatarListboxOptionItem = Omit<BentoListboxOptionItem, 'data'> & { src?: string; }; export type BentoAvatarListboxOptions = Array<BentoAvatarListboxOptionItem>; export interface BentoDropdownAvatarProps extends BentoDropdownProps { /** * The option elements to populate the avatar dropdown with. * It must be an array of {@see BentoAvatarListboxOptions } * @property {string} value.label - Text to be displayed in the option. * @property {string | number} value.value - Value of the option. * @property {string} value.src - Image source of the avatar image. */ items?: BentoAvatarListboxOptions; }
@@ -0,0 +1 @@
1
+ import { type BentoListboxOptionItem } from '@/types/listbox'; import { type OmitStrict } from '@/types/omit-strict'; import { type BentoCountryCode } from '@/components/country'; export type BentoCountryListboxOptionItem = OmitStrict<BentoListboxOptionItem, 'value' | 'label'> & { value: BentoCountryCode | `${BentoCountryCode}`; /** deprecated Use BentoDropdown component for custom labels */ label?: string; }; export type BentoCountryListboxOptions = Array<BentoCountryListboxOptionItem>;
@@ -0,0 +1 @@
1
+ import { type BentoListboxOptionItem } from '@/types/listbox'; import { type OmitStrict } from '@/types/omit-strict'; import { type BentoCurrencyISOCode } from '@/components/currency'; import { type BentoDropdownProps } from '../../../dropdown.types'; export type BentoCurrencyListboxOptionItem = OmitStrict<BentoListboxOptionItem, 'value'> & { value: BentoCurrencyISOCode | `${BentoCurrencyISOCode}`; }; export type BentoCurrencyListboxOptions = Array<BentoCurrencyListboxOptionItem>; export interface BentoDropdownCurrencyProps extends BentoDropdownProps { /** * The option elements to populate the currency dropdown with. * It must be an array of {@see BentoCurrencyListboxOptions } * @property {string} value.label - Text to be displayed in the option * @property {BentoCurrencyISOCode} value.code - Currency ISO code of the option */ items?: BentoCurrencyListboxOptions; }
@@ -0,0 +1 @@
1
+ import { type BentoListboxOptionItem } from '@/types/listbox'; import { type OmitStrict } from '@/types/omit-strict'; import { type BentoDropdownProps } from '../../../dropdown.types'; export type BentoPaymentMethodListboxOptionItem = OmitStrict<BentoListboxOptionItem, 'value'> & { value: string; }; export type BentoPaymentMethodListboxOptions = Array<BentoPaymentMethodListboxOptionItem>; export interface BentoDropdownPaymentMethodProps extends BentoDropdownProps { /** * The option elements to populate the payment method dropdown with. * It must be an array of {@see BentoPaymentMethodListboxOptions } * @property {string} value.label - Text to be displayed in the option * @property {string} value.value - Payment type code that translates to a payment icon name {@see https://adyen-ui-assets.netlify.app/?path=/story/packages-ui-assets-payment-method-icons--icons} */ items?: BentoPaymentMethodListboxOptions; }
@@ -0,0 +1 @@
1
+ import { type BentoListboxOptionItem } from '@/types/listbox'; import { type OmitStrict } from '@/types/omit-strict'; import { type BentoTagVariant } from '../../../../tag'; import { type BentoDropdownProps } from '../../../dropdown.types'; export type BentoTagListboxOptionItem = OmitStrict<BentoListboxOptionItem, 'data'> & { variant: BentoTagVariant | `${BentoTagVariant}`; }; export type BentoTagListboxOptions = Array<BentoTagListboxOptionItem>; export interface BentoDropdownTagProps extends BentoDropdownProps { /** * The option elements to populate the tag dropdown with. * It must be an array of {@see BentoTagListboxOptions } * @property {string} value.label - Text to be displayed in the option * @property {string | number} value.value - Value of the option * @property {string} value.variant - The BentTag variant shown for the option. */ items?: BentoTagListboxOptions; }
@@ -0,0 +1 @@
1
+ import { type BentoListboxOptions } from '@/types/listbox'; import { type Ref } from 'vue'; export interface UseDisplayValueOptions { items: Ref<BentoListboxOptions>; placeholder: Ref<string>; isMultiSelect: Ref<boolean>; selectedValues: Ref<BentoListboxOptions>; }
@@ -0,0 +1 @@
1
+ import { type ComputedRef, type Ref } from 'vue'; export interface UseTextboxKeyboardNavigationOptions { inputContainerRef: Ref; optionsContainerRef: Ref; selectedValueIndex: ComputedRef<number>; isDropdownOpen: Ref<boolean>; isDynamicFltering: Ref<boolean>; items: Ref<Array<HTMLDivElement>>; isMultiple: boolean; } export interface UseListboxKeyboardNavigationOptions { isDropdownOpen: Ref<boolean>; inputContainerRef: Ref; }
@@ -0,0 +1 @@
1
+ import type { BentoListboxEmptyStateProps as BentoDropdownEmptyStateProps, BentoListboxSearchOptions as BentoDropdownSearchOptions, BentoListboxVirtulisationOptions as BentoDropdownVirtulisationOptions, BentoListboxIsOptionDisabled, BentoListboxLazyLoadType, BentoListboxOptions, BentoListboxSelectedValue, } from '../../types/listbox'; import { type FormField } from '../../types/form-field'; export type { BentoDropdownSearchOptions, BentoDropdownEmptyStateProps, BentoDropdownVirtulisationOptions }; export interface BentoDropdownProps extends FormField { /** * @deprecated from version 2.0.0. Use the aria-label attribute instead. * Defines a string value that labels an interactive element. */ ariaLabel?: string; /** * Renders the dropdown with a condensed style. */ condensed?: boolean; /** * Empty state props used with the inner empty state component which is displayed when no search results are found. */ emptyState?: BentoDropdownEmptyStateProps; /** * Sets the possibility to type into the textbox. */ dynamicFiltering?: boolean; /** * Highlights the dropdown in an erroneous state. * @deprecated Use "errorMessage" prop instead */ error?: boolean; /** * Indicates whether there are more items to load */ hasMoreItems?: boolean; /** * Function that dertermines which options are disabled from interaction. * @type {BentoListboxIsOptionDisabled} * @param {BentoListboxOptionItem} option - Dropdown option object * @param {string} option.label - Option's label text * @param {string|number} option.value - Option's value * @param {unknown} option.data - Option's extra data to be passed to the default slot */ isOptionDisabled?: BentoListboxIsOptionDisabled; /** * The option elements to populate the dropdown with. * It must be an array of {@see BentoListboxOptionItem } * @property value.label - Text to be displayed in the option * @property value.value - Value of the option */ items?: BentoListboxOptions; /** * Indicates if new options are lazy loading. */ loading?: boolean; /** * Indicates that dropdown items are loading */ componentLoading?: boolean; /** * The type of `Lazy Load`. * Type "automatic" will enable infinite scrolling * Type "button" will enable lazy loading with "Show more" button */ lazyLoadType?: BentoListboxLazyLoadType | `${BentoListboxLazyLoadType}`; /** * If enabled, the user will be able to select multiple items. */ multiple?: boolean; /** * Dropdown search options and events. * @see BentoDropdownSearchOptions */ search?: BentoDropdownSearchOptions; /** * Toggles whether to use the slot content select items shown in the textbox in multiple mode. */ showSlotContentInMultiple?: boolean; /** * @deprecated Do not use. Only 'default' size should be used. */ size?: BentoDropdownSize | `${BentoDropdownSize}`; /** * If enabled, dropdown will display non-selectable, static categories */ staticCategories?: boolean; /** * The `input` value. * Providing an empty string will select no options. * Set to an empty string `''` if you don't want any of the available options to be selected. * @deprecated for usage with properties since version 2.0. Use `v-model` or `model-value` instead. */ value?: BentoListboxSelectedValue; /** * The `input` value. * Providing an empty string will select no options. * Set to an empty string `''` if you don't want any of the available options to be selected. */ modelValue?: BentoListboxSelectedValue; /** * Enables virtual scrolling if set to true or by providing an object with itemHeight function. * The itemHeight function is used to calculate the height of rendered item given it's index. */ virtualScroll?: BentoDropdownVirtulisationOptions; /** * Enables value/label value handling. When `true`, the component expects and emits value/label * pair objects instead of primitive values. * * This affects both the `value` prop and the `input` event payload. * * - **Input (`value` prop):** Expects a `{ value: string | number, label: string }` object (or an array of them for multi-select). * - **Output (`input` event):** Emits a `{ value: string | number, label: string }` object (or an array of them) upon selection. * * This is particularly useful when a selected item's label is not available in the `items` prop, * allowing the component to display the correct label for values loaded from external sources. * @default false */ enableValueLabelPair?: boolean; } /** * @deprecated Do not use. Only 'default' size should be used. */ export enum BentoDropdownSize { DEFAULT = 'DropdownInputDefault', SMALL = 'DropdownInputSmall', } export { /** * @deprecated since version 2.0. Use the string literal instead. */ BentoListboxLazyLoadType as BentoDropdownLazyLoadType, BentoListboxEvent as BentoDropdownEvent, } from '@/types/listbox';
@@ -0,0 +1 @@
1
+ import type { BentoButtonActionObject } from '@/components/button/components/button-actions/button-actions.types'; import type { BentoTypographyElement } from '@/components/typography/typography.types'; export enum BentoEmptyStateVariant { FULL_PAGE = 'full-page', EMBEDDED = 'embedded', BASIC = 'basic', CONDENSED = 'condensed', } export enum BentoEmptyStateImage { GENERIC_USE1 = '1-generic-use', GENERIC_USE2 = '2-generic-use', GENERIC_USE3 = '3-generic-use', GENERIC_USE4 = '4-generic-use', ADDING_PAYMENT_METHOD = 'adding-payment-methods', ADYEN_GIVING = 'adyen-giving', DELIGHT = 'delight', INTERNAL_ERROR = 'internal-error', NO_RESULTS_FOUND = 'no-results-found', NOTIFICATIONS_CLEARED = 'notifications-cleared', PAGE_NOT_FOUND = 'page-not-found', PLANNED_MAINTENANCE = 'planned-maintenance', REFERRALS = 'referrals', SUCCESS = 'success', UPLOAD_FILES = 'upload-files', WRONG_ENVIRONMENT = 'wrong-environment', } export enum BentoEmptyStateImageVariant { SMALL = 'small', LARGE = 'large', NULL = null, } export interface BentoEmptyStateProps { /** * Defines the button at the end of the component. */ action?: BentoButtonActionObject; /** * The empty state description. * Gives additional details to the issue. * This can be used or the default slot */ description?: string; /** * Sets the heading HTML DOM element. * @values h1, h2, h3, h4, h5, h6, div, p, span */ headingEl?: `${BentoTypographyElement}`; /** * Name of the illustration for the empty state. */ image?: `${BentoEmptyStateImage}`; /** * The empty state heading. * Should clearly explain the issue. */ title?: string; /** * Types of different empty states * @values full-page, embedded, basic, condensed */ variant?: `${BentoEmptyStateVariant}`; }
@@ -0,0 +1 @@
1
+ import { type FileObject, type SupportedFileTypes } from '@/components/file-uploader/file-uploader.types'; export interface FileUploaderFileCardProps { /** * File to be uploaded */ file: FileObject; /** * Supported file types message */ supportedFileTypes?: SupportedFileTypes; /** * If true, hides file card actions */ readonly?: boolean; }
@@ -0,0 +1 @@
1
+ import type { BentoFileUploaderProps, SupportedFileTypes } from '@/components/file-uploader/file-uploader.types'; export interface FileUploaderRestrictionsProps { /** * Reduces the size of the uploader area. */ condensed?: BentoFileUploaderProps['condensed']; /** * Disables the File uploader */ disabled?: BentoFileUploaderProps['disabled']; /** * Sets a maximum file count. */ maxCount?: { value: BentoFileUploaderProps['maxCount']; error: boolean; }; /** * Sets a maximum image size (for image file types only). */ maxDimensions?: { value: BentoFileUploaderProps['maxDimensions']; error: boolean; }; /** * Sets a maximum file size in Bytes */ maxSize?: { value: BentoFileUploaderProps['maxSize']; error: boolean; }; /** * Supported file types message */ supportedFileTypes?: { value: SupportedFileTypes; error: boolean; }; }
@@ -0,0 +1 @@
1
+ import { type Ref } from 'vue'; import { type BentoFileUploaderProps } from '@/components/file-uploader/file-uploader.types'; export interface useDragDropStateProps { accept: Ref<BentoFileUploaderProps['accept']>; maxCount: Ref<BentoFileUploaderProps['maxCount']>; }
@@ -0,0 +1 @@
1
+ import { type FormField } from '../../types/form-field'; export type BentoFileUploaderValue = string | Array<string> | File | Array<File> | FileList; export interface BentoFileUploaderProps extends FormField { /** * A string that defines the file types the file input should accept. * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#accept * @default null */ accept?: HTMLInputElement['accept']; /** * Reduces the size of the uploader area. * @default false */ condensed?: boolean; /** * Sets a maximum file count. Allows for uploading multiple files. * @default 1 */ maxCount?: number; /** * Sets a maximum image size (for image file types only). * @default null */ maxDimensions?: { width?: number; height?: number }; /** * Sets a maximum file size in Bytes. Also accepts an array of FileMaxSize objects to specify different sizes for different file types. * @default null */ maxSize?: number | Array<FileMaxSize>; /** * Used to support `v-model`. Can be used to set value of FileUploader if FileList * @default null */ modelValue?: BentoFileUploaderValue; /** * Placeholder is coming from FormField and is not allowed for FileUploader, so we're explicitly disabling it here */ placeholder?: never; } /** * Private types */ export enum FileState { LOADING = 'loading', SUCCESS = 'success', ERROR = 'error', } export enum FileErrorType { ERROR_SIZE = 'error-size', ERROR_LOAD = 'error-load', ERROR_DIMENSIONS = 'error-dimensions', ERROR_TYPE = 'error-type', } export enum FileUploaderState { DISABLED = 'disabled', DRAG_OVER = 'drag-over', ERROR = 'error', READONLY = 'readonly', } export enum FileType { TABLE = 'table', IMAGE = 'image', VIDEO = 'video', AUDIO = 'audio', TEXT = 'text', PDF = 'pdf', SLIDES = 'slides', COMPRESSED = 'compressed', GENERIC = 'generic', } export interface FileMaxSize { fileType: HTMLInputElement['accept']; maxSize: number; } export interface FileObject { id: string; data: File; state: FileState; errors: Array<FileErrorType>; type: FileType; progress: number | null; dimensions?: { width: number; height: number }; } export type SupportedFileTypes = string;
@@ -0,0 +1 @@
1
+ export enum AllFiltersModalEvent { INPUT = 'input', }
@@ -1 +1 @@
1
- <template> <div class="b-all-filters-modal"> <filter-bar-button :is-filter-open="isModalOpen" :applied-filters-count="numberOfActiveFilters" no-applied-state-styles @click="onClickAllFiltersModal" > {{ t('allFilters') }} <template #iconLeft> <sliders-icon aria-hidden="true" /> </template> </filter-bar-button> <bento-modal class="'b-all-filters-modal__modal'" :is-open="isModalOpen" :with-column-layout="true" size="large" :actions="modalActions" :is-dismissible="false" hide-close-button @close-modal="cancel" > <template #default> {{ t('allFilters') }} </template> <template #content-col-1> <div ref="allFiltersModalFiltersSection" class="b-all-filters-modal__filters" @keydown.right="onKeydownRight" > <bento-search-bar class="b-all-filters-modal__search-bar" :value="searchTerm" :input-field-aria-label="t('searchBar').toString()" @input="searchTerm = $event" @clear="onSearchbarClear" /> <div v-if="isEmptyResultForSearchFilter" class="b-all-filters-modal__empty-search-message"> <bento-typography el="span"> {{ t('noFiltersMatchThisSearch') }}</bento-typography> </div> <div v-if="activeFilters.length" class="b-all-filters-modal__filters-section"> <bento-typography :id="`active-filters-${allFiltersModalUniqueId}`" class="b-all-filters-modal__filters-section-title" el="span" variant="caption" > {{ t('activeFilters') }} </bento-typography> <ul class="b-all-filters-modal__filters-list" :aria-labelledby="`active-filters-${allFiltersModalUniqueId}`" > <li v-for="item in activeFilters" :key="`${item.label}-${item.field}`"> <all-filters-modal-button :ref="el => (allFiltersModalFilterButtons[item.label] = el)" :label="item.label" :applied-filters-count="getFilterCount(item)" :is-filter-open="selectedFilter.label === item.label" :disabled="item.disabled" @click="selectFilter(item.label, item.field)" /> </li> </ul> </div> <div v-if="otherFilters.length" class="b-all-filters-modal__filters-section"> <bento-typography v-if="activeFilters.length" :id="`other-filters-${allFiltersModalUniqueId}`" class="b-all-filters-modal__filters-section-title" el="span" variant="caption" > {{ t('otherFilters') }} </bento-typography> <ul class="b-all-filters-modal__filters-list" :aria-labelledby="`other-filters-${allFiltersModalUniqueId}`" > <li v-for="item in otherFilters" :key="`${item.label}-${item.field}`"> <all-filters-modal-button :ref="el => (allFiltersModalFilterButtons[item.label] = el)" :label="item.label" :disabled="item.disabled" :applied-filters-count="getFilterCount(item)" :is-filter-open="selectedFilter.label === item.label" @click="selectFilter(item.label, item.field)" /> </li> </ul> </div> </div> </template> <template #content-col-2> <div class="b-all-filters-modal__selected-filter-content" @keydown.left="onKeydownLeft"> <div v-if="shouldShowFilterTitle" class="b-all-filters-modal__selected-filter-text"> <bento-typography :id="selectedFilter.labelId" el="div" variant="title" medium> {{ selectedFilter.label }} </bento-typography> <bento-typography v-if="selectedFilter.description" el="div" variant="body" wide> {{ selectedFilter.description }} </bento-typography> </div> <div class="b-all-filters-modal__selected-filter-component"> <component :is="selectedFilter.type" ref="selectedFilterRef" :key="selectedFilter.field" :description="selectedFilter.description" :field="selectedFilter.field" :label="selectedFilter.label" :options="selectedFilter.options" :value="selectedFilter.value" :default-value="selectedFilter.defaultValue" :only-slot="true" :disabled="selectedFilter.disabled" :aria-labelledby="selectedFilter.labelId" @input="updateFilter" /> </div> <div class="b-all-filters-modal__clear-button"> <bento-button variant="secondary" :disabled="isClearButtonDisabled" @click="clearFilter">{{ t('clear') }}</bento-button> </div> </div> </template> </bento-modal> </div> </template> <script lang="ts"> import { computed, defineComponent, onBeforeUpdate, onMounted, type PropType, ref, toRef, watch } from 'vue'; import BentoModal from '@/components/modal/modal.vue'; import { BentoButton } from '@/components/button'; import { type BentoFilterBarModel, BentoFilterItemType, type BentoFilterModel, type BentoSelectFilterOptions, } from '@/components/filter-bar/filter-bar.types'; import { BentoBooleanFilter, BentoCheckboxGroupFilter, BentoDateFilter, BentoDateRangeFilter, BentoInputFilter, BentoInputWithDropdownFilter, BentoRadioGroupFilter, BentoRangeFilter, BentoSelectAvatarFilter, BentoSelectCountryFilter, BentoSelectCurrencyFilter, BentoSelectFilter, BentoSelectPaymentMethodFilter, BentoSelectTagFilter, BentoToggleFilter, } from '../index'; import { type BentoDateRangePickerValue } from '@/components/date-range-picker'; import BentoSearchBar from '@/components/search-bar/search-bar.vue'; import { BentoTypography } from '@/components/typography'; import { useSearchBarFilter } from '@/components/search-bar'; import { generateUid } from '@/core/utils/ts'; import { getNextFocusableElement } from '@/utils/ts/focus-trap.utils'; import { FilterBarButton } from '../filter-bar-button'; import { AllFiltersModalEvent } from './all-filters-modal.types'; import { useI18n } from '@/utils/ts/i18n'; import AllFiltersModalButton from './components/all-filters-modal-button/all-filters-modal-button.vue'; import SlidersIcon from '@adyen/ui-assets-icons-16/vue/sliders-1'; import messages from './messages.json'; import { type BentoCheckboxGroupFilterOptions } from '../checkbox-group-filter'; type MessageSchema = (typeof messages)['en-US']; /** * A modal that acts as sort of filter manager for the BentoFilterBar. * * @example * import { BentoAllFiltersModal } from '@adyen/bento-vue2'; * * export default { * components: { BentoAllFiltersModal }, * template: ` * <bento-all-filters-modal> * ...Example code of the component * </bento-all-filters-modal> * ` * } */ export default defineComponent({ i18n: { messages }, name: 'all-filters-modal', components: { AllFiltersModalButton, BentoBooleanFilter, BentoButton, BentoCheckboxGroupFilter, BentoDateFilter, BentoDateRangeFilter, BentoInputFilter, BentoInputWithDropdownFilter, BentoModal, BentoRadioGroupFilter, BentoRangeFilter, BentoSearchBar, BentoSelectAvatarFilter, BentoSelectCurrencyFilter, BentoSelectCountryFilter, BentoSelectFilter, BentoSelectPaymentMethodFilter, BentoSelectTagFilter, BentoToggleFilter, BentoTypography, FilterBarButton, SlidersIcon, }, props: { /** * Array of filters based on {@see FilterBarModel}. * Each one of the elements in the array correspond to a filter that will * be created and rendered in the AllFiltersModal. * * Can be used with v-model or independently with the @input event. * * @example * [{ * field: 'company', * label: 'Company', * value: '200', * type: FilterType.TEXT, * }, { * field: 'name', * label: 'Name', * value: null, * type: FilterType.TEXT, * }] */ value: { type: Array as PropType<BentoFilterBarModel>, default: () => [], }, }, emits: [AllFiltersModalEvent.INPUT], setup(props, { emit }) { const filtersInModal = ref(props.value); const allFiltersModalUniqueId = generateUid('all-filters-modal'); const allFiltersModalFiltersSection = ref([]); const isModalOpen = ref(false); const selectedFilterIndex = ref(0); const selectedFilterRef = ref(null); const searchTerm = ref<string>(''); const { t, n } = useI18n<{ message: MessageSchema }>({ messages }); const filteredItems = useSearchBarFilter<BentoFilterModel>(searchTerm, toRef(filtersInModal, 'value')); const allFiltersModalFilterButtons = ref({}); onBeforeUpdate(() => { allFiltersModalFilterButtons.value = []; }); watch( () => props.value, () => { /** * Values need to be preserve when the modal is opened given that * when the "select" filter is enabled with external filtering inside the modal * the "searchTerm" gets reset and doing so resets all the unsaved values of the array */ if (isModalOpen.value) { filtersInModal.value = props.value.map((filter, index) => { return { ...filter, value: filtersInModal.value[index].value }; }); } else { filtersInModal.value = props.value; } } ); const sortAlphabetically = (array: BentoFilterBarModel) => array.sort((a, b) => (a.label > b.label ? 1 : b.label > a.label ? -1 : 0)); const activeFilters = computed(() => sortAlphabetically( filteredItems.value.filter(el => Array.isArray(el.value) ? el.value?.length && el.value?.find(item => item !== '') : el.value ) ) ); const otherFilters = computed(() => sortAlphabetically( filteredItems.value.filter(el => Array.isArray(el.value) ? el.value?.length === 0 || el.value?.every(item => item === '') : !el.value ) ) ); const isEmptyResultForSearchFilter = computed(() => searchTerm.value && filteredItems.value?.length === 0); const selectedFilter = computed(() => ({ ...filtersInModal.value[selectedFilterIndex.value], labelId: generateUid(filtersInModal.value[selectedFilterIndex.value]?.field), })); const selectOpenedFilter = () => { const openedFilter = activeFilters.value.length ? activeFilters.value[0] : otherFilters.value[0]; const openedFilterIndex = filtersInModal.value.findIndex( el => el.label === openedFilter.label && el.field === openedFilter.field ); selectedFilterIndex.value = openedFilterIndex; }; onMounted(() => { selectOpenedFilter(); }); const computeCounts = (filterValue: BentoFilterBarModel) => filterValue.map(filter => { switch (filter.type) { case BentoFilterItemType.SELECT: case BentoFilterItemType.SELECT_AVATAR: case BentoFilterItemType.SELECT_COUNTRY: case BentoFilterItemType.SELECT_CURRENCY: case BentoFilterItemType.SELECT_PAYMENT_METHOD: case BentoFilterItemType.SELECT_TAG: switch ((filter.options as BentoSelectFilterOptions)?.multiple) { case true: return { label: filter.label, field: filter.field, count: (filter.value as Array<string | number>)?.length ? (filter.value as Array<string | number>)?.length : null, }; default: return { label: filter.label, field: filter.field, count: filter.value == null ? null : 1, }; } case BentoFilterItemType.DATE_RANGE: return { label: filter.label, field: filter.field, count: (filter.value as BentoDateRangePickerValue)?.startDate != null && (filter.value as BentoDateRangePickerValue)?.endDate != null ? 1 : null, }; case BentoFilterItemType.RANGE: return { label: filter.label, field: filter.field, count: (filter.value as Array<string>)?.find(el => el !== '') ? 1 : null, }; case BentoFilterItemType.CHECKBOX_GROUP: return { label: filter.label, field: filter.field, count: (filter.value as Array<string | number>)?.length ? (filter.value as Array<string | number>)?.length : null, }; case BentoFilterItemType.TOGGLE: return { label: filter.label, field: filter.field, count: filter.value ? 1 : null, }; case BentoFilterItemType.INPUT: case BentoFilterItemType.BOOLEAN: case BentoFilterItemType.DATE: case BentoFilterItemType.RADIO_GROUP: default: return { label: filter.label, field: filter.field, count: filter.value != null ? 1 : null, }; } }); const hasValueSelected = computed(() => Array.isArray(selectedFilter.value.value) ? selectedFilter.value?.value.length && selectedFilter.value.value.find(item => item !== '') : !!filtersInModal.value[selectedFilterIndex.value].value ); /** * Hides filter title if the selected filter is Toggle. * The Toggle filter shows a checkbox next to the "title". */ const shouldShowFilterTitle = computed(() => { return selectedFilter.value.type !== BentoFilterItemType.TOGGLE; }); const isClearButtonDisabled = computed(() => !hasValueSelected.value || selectedFilter.value.disabled); const numberOfActiveFilters = computed( () => computeCounts(props.value)?.filter(({ count }) => !!count).length ); const onClickAllFiltersModal = () => { if (!isModalOpen.value) { isModalOpen.value = true; } else { isModalOpen.value = false; } }; const getFilterCount = (filter: BentoFilterModel) => { const filterCount = computeCounts(filtersInModal.value).find(el => el.field === filter.field).count; const isBentoSelectFilter = typeof filter.type === 'string' && // Type guard to avoid casting filter.type.includes('BentoSelect') && (filter.options as BentoSelectFilterOptions)?.multiple && filterCount === (filter.options as BentoSelectFilterOptions)?.listboxItems?.length; const isBentoCheckboxGroupFilter = typeof filter.type === 'string' && filter.type === BentoFilterItemType.CHECKBOX_GROUP && filterCount === (filter.options as BentoCheckboxGroupFilterOptions)?.checkboxItems?.length; if (isBentoSelectFilter || isBentoCheckboxGroupFilter) { return t('all'); } return filterCount === null ? null : n(filterCount); }; const selectFilter = (filterLabel: string, filterField: string) => { selectedFilterIndex.value = filtersInModal.value.findIndex( el => el.label === filterLabel && el.field === filterField ); }; const onSearchbarClear = () => { searchTerm.value = ''; }; const cancel = () => { isModalOpen.value = false; filtersInModal.value = props.value; onSearchbarClear(); selectOpenedFilter(); // TODO: scroll filters section to top }; const updateFilter = (updatedFilter: BentoFilterModel) => { const updatedFilters = filtersInModal.value.map(({ value, ...otherFields }, index) => { if (index === selectedFilterIndex.value) { return { value: updatedFilter.value, ...otherFields }; } return { value, ...otherFields }; }); filtersInModal.value = updatedFilters; }; const clearFilter = () => { updateFilter({ field: selectedFilter.value.field, label: selectedFilter.value.label, type: selectedFilter.value.type, value: undefined, }); }; const clearAll = () => { filtersInModal.value = props.value.map(({ value, ...otherFields }) => ({ ...otherFields, value: undefined, })); }; const applyAll = () => { emit(AllFiltersModalEvent.INPUT, filtersInModal.value); isModalOpen.value = false; }; const isApplyAllButtonDisabled = computed( () => JSON.stringify(filtersInModal.value) === JSON.stringify(props.value) ); const modalActions = computed(() => [ { title: t('applyAll'), disabled: isApplyAllButtonDisabled.value, event: applyAll, }, { title: t('cancel'), event: cancel, }, { title: t('clearAll'), disabled: !numberOfActiveFilters.value, event: clearAll, }, ]); const onKeydownRight = () => { const focusableEl = getNextFocusableElement(1, selectedFilterRef.value.$el); focusableEl.focus(); }; const onKeydownLeft = () => { allFiltersModalFilterButtons.value[selectedFilter.value.label].$el.focus(); }; return { // Values allFiltersModalUniqueId, allFiltersModalFiltersSection, allFiltersModalFilterButtons, numberOfActiveFilters, isEmptyResultForSearchFilter, isModalOpen, isClearButtonDisabled, activeFilters, otherFilters, modalActions, selectedFilter, selectedFilterRef, searchTerm, shouldShowFilterTitle, // Methods onClickAllFiltersModal, getFilterCount, cancel, selectFilter, clearFilter, updateFilter, onSearchbarClear, onKeydownRight, onKeydownLeft, // Translations t, }; }, }); </script> <style lang="scss" scoped src="./all-filters-modal.scss" />
1
+ <template> <div class="b-all-filters-modal"> <filter-bar-button :is-filter-open="isModalOpen" :applied-filters-count="numberOfActiveFilters" no-applied-state-styles @click="onClickAllFiltersModal" > {{ t('allFilters') }} <template #iconLeft> <sliders-icon aria-hidden="true" /> </template> </filter-bar-button> <bento-modal class="'b-all-filters-modal__modal'" :is-open="isModalOpen" :with-column-layout="true" size="large" :actions="modalActions" :is-dismissible="false" hide-close-button @close-modal="cancel" > <template #default> {{ t('allFilters') }} </template> <template #content-col-1> <div ref="allFiltersModalFiltersSection" class="b-all-filters-modal__filters" @keydown.right="onKeydownRight" > <bento-search-bar class="b-all-filters-modal__search-bar" :value="searchTerm" :input-field-aria-label="t('searchBar').toString()" @input="searchTerm = $event" @clear="onSearchbarClear" /> <div v-if="isEmptyResultForSearchFilter" class="b-all-filters-modal__empty-search-message"> <bento-typography el="span"> {{ t('noFiltersMatchThisSearch') }}</bento-typography> </div> <div v-if="activeFilters.length" class="b-all-filters-modal__filters-section"> <bento-typography :id="`active-filters-${allFiltersModalUniqueId}`" class="b-all-filters-modal__filters-section-title" el="span" variant="caption" > {{ t('activeFilters') }} </bento-typography> <ul class="b-all-filters-modal__filters-list" :aria-labelledby="`active-filters-${allFiltersModalUniqueId}`" > <li v-for="item in activeFilters" :key="`${item.label}-${item.field}`"> <all-filters-modal-button :ref="el => (allFiltersModalFilterButtons[item.label] = el)" :label="item.label" :applied-filters-count="getFilterCount(item)" :is-filter-open="selectedFilter.label === item.label" :disabled="item.disabled" @click="selectFilter(item.label, item.field)" /> </li> </ul> </div> <div v-if="otherFilters.length" class="b-all-filters-modal__filters-section"> <bento-typography v-if="activeFilters.length" :id="`other-filters-${allFiltersModalUniqueId}`" class="b-all-filters-modal__filters-section-title" el="span" variant="caption" > {{ t('otherFilters') }} </bento-typography> <ul class="b-all-filters-modal__filters-list" :aria-labelledby="`other-filters-${allFiltersModalUniqueId}`" > <li v-for="item in otherFilters" :key="`${item.label}-${item.field}`"> <all-filters-modal-button :ref="el => (allFiltersModalFilterButtons[item.label] = el)" :label="item.label" :disabled="item.disabled" :applied-filters-count="getFilterCount(item)" :is-filter-open="selectedFilter.label === item.label" @click="selectFilter(item.label, item.field)" /> </li> </ul> </div> </div> </template> <template #content-col-2> <div class="b-all-filters-modal__selected-filter-content" @keydown.left="onKeydownLeft"> <div v-if="shouldShowFilterTitle" class="b-all-filters-modal__selected-filter-text"> <bento-typography :id="selectedFilter.labelId" el="div" variant="title" medium> {{ selectedFilter.label }} </bento-typography> <bento-typography v-if="selectedFilter.description" el="div" variant="body" wide> {{ selectedFilter.description }} </bento-typography> </div> <div class="b-all-filters-modal__selected-filter-component"> <component :is="selectedFilter.type" ref="selectedFilterRef" :key="selectedFilter.field" :description="selectedFilter.description" :field="selectedFilter.field" :label="selectedFilter.label" :options="selectedFilter.options" :value="selectedFilter.value" :default-value="selectedFilter.defaultValue" :only-slot="true" :disabled="selectedFilter.disabled" :aria-labelledby="selectedFilter.labelId" @input="updateFilter" /> </div> <div class="b-all-filters-modal__clear-button"> <bento-button variant="secondary" :disabled="isClearButtonDisabled" @click="clearFilter">{{ t('clear') }}</bento-button> </div> </div> </template> </bento-modal> </div> </template> <script setup lang="ts"> import { computed, onBeforeUpdate, onMounted, ref, toRef, watch } from 'vue'; import BentoModal from '@/components/modal/modal.vue'; import { BentoButton } from '@/components/button'; import { type BentoFilterBarModel, BentoFilterItemType, type BentoFilterModel, type BentoSelectFilterOptions, } from '@/components/filter-bar/filter-bar.types'; import { type BentoDateRangePickerValue } from '@/components/date-range-picker'; import BentoSearchBar from '@/components/search-bar/search-bar.vue'; import { BentoTypography } from '@/components/typography'; import { useSearchBarFilter } from '@/components/search-bar'; import { generateUid } from '@/core/utils/ts'; import { getNextFocusableElement } from '@/utils/ts/focus-trap.utils'; import { FilterBarButton } from '../filter-bar-button'; import { AllFiltersModalEvent } from './all-filters-modal.types'; import { useI18n } from '@/utils/ts/i18n'; import AllFiltersModalButton from './components/all-filters-modal-button/all-filters-modal-button.vue'; import SlidersIcon from '@adyen/ui-assets-icons-16/vue/sliders-1'; import messages from './messages.json'; import { type BentoCheckboxGroupFilterOptions } from '../checkbox-group-filter'; type MessageSchema = (typeof messages)['en-US']; const props = withDefaults( defineProps<{ /** * Array of filters based on {@see FilterBarModel}. * Each one of the elements in the array correspond to a filter that will * be created and rendered in the AllFiltersModal. * * Can be used with v-model or independently with the @input event. * * @example * [{ * field: 'company', * label: 'Company', * value: '200', * type: FilterType.TEXT, * }, { * field: 'name', * label: 'Name', * value: null, * type: FilterType.TEXT, * }] */ value?: BentoFilterBarModel; }>(), { value: () => [], } ); const emit = defineEmits([AllFiltersModalEvent.INPUT]); const filtersInModal = ref<BentoFilterBarModel>(props.value); const allFiltersModalUniqueId = generateUid('all-filters-modal'); const allFiltersModalFiltersSection = ref([]); const isModalOpen = ref(false); const selectedFilterIndex = ref(0); const selectedFilterRef = ref(null); const searchTerm = ref<string>(''); const { t, n } = useI18n<{ message: MessageSchema }>({ messages }); const filteredItems = useSearchBarFilter<BentoFilterModel>(searchTerm, toRef(filtersInModal, 'value')); const allFiltersModalFilterButtons = ref({}); onBeforeUpdate(() => { allFiltersModalFilterButtons.value = []; }); watch( () => props.value, () => { /** * Values need to be preserve when the modal is opened given that * when the "select" filter is enabled with external filtering inside the modal * the "searchTerm" gets reset and doing so resets all the unsaved values of the array */ if (isModalOpen.value) { filtersInModal.value = props.value.map((filter, index) => { return { ...filter, value: filtersInModal.value[index].value }; }); } else { filtersInModal.value = props.value; } } ); const sortAlphabetically = (array: BentoFilterBarModel) => array.sort((a, b) => (a.label > b.label ? 1 : b.label > a.label ? -1 : 0)); const activeFilters = computed(() => sortAlphabetically( filteredItems.value.filter(el => Array.isArray(el.value) ? el.value?.length && el.value?.find(item => item !== '') : el.value ) ) ); const otherFilters = computed(() => sortAlphabetically( filteredItems.value.filter(el => Array.isArray(el.value) ? el.value?.length === 0 || el.value?.every(item => item === '') : !el.value ) ) ); const isEmptyResultForSearchFilter = computed(() => searchTerm.value && filteredItems.value?.length === 0); const selectedFilter = computed(() => ({ ...filtersInModal.value[selectedFilterIndex.value], labelId: generateUid(filtersInModal.value[selectedFilterIndex.value]?.field), })); const selectOpenedFilter = () => { const openedFilter = activeFilters.value.length ? activeFilters.value[0] : otherFilters.value[0]; const openedFilterIndex = filtersInModal.value.findIndex( el => el.label === openedFilter.label && el.field === openedFilter.field ); selectedFilterIndex.value = openedFilterIndex; }; onMounted(() => { selectOpenedFilter(); }); const computeCounts = (filterValue: BentoFilterBarModel) => filterValue.map(filter => { switch (filter.type) { case BentoFilterItemType.SELECT: case BentoFilterItemType.SELECT_AVATAR: case BentoFilterItemType.SELECT_COUNTRY: case BentoFilterItemType.SELECT_CURRENCY: case BentoFilterItemType.SELECT_PAYMENT_METHOD: case BentoFilterItemType.SELECT_TAG: switch ((filter.options as BentoSelectFilterOptions)?.multiple) { case true: return { label: filter.label, field: filter.field, count: (filter.value as Array<string | number>)?.length ? (filter.value as Array<string | number>)?.length : null, }; default: return { label: filter.label, field: filter.field, count: filter.value == null ? null : 1, }; } case BentoFilterItemType.DATE_RANGE: return { label: filter.label, field: filter.field, count: (filter.value as BentoDateRangePickerValue)?.startDate != null && (filter.value as BentoDateRangePickerValue)?.endDate != null ? 1 : null, }; case BentoFilterItemType.RANGE: return { label: filter.label, field: filter.field, count: (filter.value as Array<string>)?.find(el => el !== '') ? 1 : null, }; case BentoFilterItemType.CHECKBOX_GROUP: return { label: filter.label, field: filter.field, count: (filter.value as Array<string | number>)?.length ? (filter.value as Array<string | number>)?.length : null, }; case BentoFilterItemType.TOGGLE: return { label: filter.label, field: filter.field, count: filter.value ? 1 : null, }; case BentoFilterItemType.INPUT: case BentoFilterItemType.BOOLEAN: case BentoFilterItemType.DATE: case BentoFilterItemType.RADIO_GROUP: default: return { label: filter.label, field: filter.field, count: filter.value != null ? 1 : null, }; } }); const hasValueSelected = computed(() => Array.isArray(selectedFilter.value.value) ? selectedFilter.value?.value.length && selectedFilter.value.value.find(item => item !== '') : !!filtersInModal.value[selectedFilterIndex.value].value ); /** * Hides filter title if the selected filter is Toggle. * The Toggle filter shows a checkbox next to the "title". */ const shouldShowFilterTitle = computed(() => { return selectedFilter.value.type !== BentoFilterItemType.TOGGLE; }); const isClearButtonDisabled = computed(() => !hasValueSelected.value || selectedFilter.value.disabled); const numberOfActiveFilters = computed(() => computeCounts(props.value)?.filter(({ count }) => !!count).length); const onClickAllFiltersModal = () => { if (!isModalOpen.value) { isModalOpen.value = true; } else { isModalOpen.value = false; } }; const getFilterCount = (filter: BentoFilterModel) => { const filterCount = computeCounts(filtersInModal.value).find(el => el.field === filter.field).count; const isBentoSelectFilter = typeof filter.type === 'string' && // Type guard to avoid casting filter.type.includes('BentoSelect') && (filter.options as BentoSelectFilterOptions)?.multiple && filterCount === (filter.options as BentoSelectFilterOptions)?.listboxItems?.length; const isBentoCheckboxGroupFilter = typeof filter.type === 'string' && filter.type === BentoFilterItemType.CHECKBOX_GROUP && filterCount === (filter.options as BentoCheckboxGroupFilterOptions)?.checkboxItems?.length; if (isBentoSelectFilter || isBentoCheckboxGroupFilter) { return t('all'); } return filterCount === null ? null : n(filterCount); }; const selectFilter = (filterLabel: string, filterField: string) => { selectedFilterIndex.value = filtersInModal.value.findIndex( el => el.label === filterLabel && el.field === filterField ); }; const onSearchbarClear = () => { searchTerm.value = ''; }; const cancel = () => { isModalOpen.value = false; filtersInModal.value = props.value; onSearchbarClear(); selectOpenedFilter(); }; const updateFilter = (updatedFilter: BentoFilterModel) => { const updatedFilters = filtersInModal.value.map(({ value, ...otherFields }, index) => { if (index === selectedFilterIndex.value) { return { value: updatedFilter.value, ...otherFields }; } return { value, ...otherFields }; }); filtersInModal.value = updatedFilters; }; const clearFilter = () => { updateFilter({ field: selectedFilter.value.field, label: selectedFilter.value.label, type: selectedFilter.value.type, value: undefined, }); }; const clearAll = () => { filtersInModal.value = props.value.map(({ value, ...otherFields }) => ({ ...otherFields, value: undefined, })); }; const applyAll = () => { emit(AllFiltersModalEvent.INPUT, filtersInModal.value); isModalOpen.value = false; }; const isApplyAllButtonDisabled = computed( () => JSON.stringify(filtersInModal.value) === JSON.stringify(props.value) ); const modalActions = computed(() => [ { title: t('applyAll'), disabled: isApplyAllButtonDisabled.value, event: applyAll, }, { title: t('cancel'), event: cancel, }, { title: t('clearAll'), disabled: !numberOfActiveFilters.value, event: clearAll, }, ]); const onKeydownRight = () => { const focusableEl = getNextFocusableElement(1, selectedFilterRef.value.$el); focusableEl.focus(); }; const onKeydownLeft = () => { allFiltersModalFilterButtons.value[selectedFilter.value.label].$el.focus(); }; </script> <script lang="ts"> import { BentoBooleanFilter, BentoCheckboxGroupFilter, BentoDateFilter, BentoDateRangeFilter, BentoInputFilter, BentoInputWithDropdownFilter, BentoRadioGroupFilter, BentoRangeFilter, BentoSelectAvatarFilter, BentoSelectCountryFilter, BentoSelectCurrencyFilter, BentoSelectFilter, BentoSelectPaymentMethodFilter, BentoSelectTagFilter, BentoToggleFilter, } from '../index'; /** * A modal that acts as sort of filter manager for the BentoFilterBar. * * @example * import { BentoAllFiltersModal } from '@adyen/bento-vue2'; * * export default { * components: { BentoAllFiltersModal }, * template: ` * <bento-all-filters-modal> * ...Example code of the component * </bento-all-filters-modal> * ` * } */ export default { name: 'all-filters-modal', i18n: { messages }, components: { BentoBooleanFilter, BentoCheckboxGroupFilter, BentoDateFilter, BentoDateRangeFilter, BentoInputFilter, BentoInputWithDropdownFilter, BentoRadioGroupFilter, BentoRangeFilter, BentoSelectAvatarFilter, BentoSelectCountryFilter, BentoSelectCurrencyFilter, BentoSelectFilter, BentoSelectPaymentMethodFilter, BentoSelectTagFilter, BentoToggleFilter, }, }; </script> <style lang="scss" scoped src="./all-filters-modal.scss" />
@@ -0,0 +1 @@
1
+ import { type BentoTooltipPosition } from '../../../../directives/tooltip/tooltip.types'; /** * Private */ export enum BaseFilterEvent { APPLY = 'apply', CLEAR = 'clear', OPEN = 'open', CLOSE = 'close', RESET = 'reset', CANCEL = 'cancel', } /** * Public */ export interface BentoBaseFilterProps<Value, Field> { /** * Total count of applied filters */ appliedFiltersCount?: string | number; /** * Overrides the label of the popover visibility toggle button text. */ buttonLabel?: string; /** * Sets the secondary label text of the popover visibility toggle button text. */ buttonSecondaryLabel?: string; /** * Disables the filter from being interacted with. */ disabled?: boolean; /** * Default value of the filter to reset to. */ defaultValue?: Value; /** * Renders the base filter's slot content only - leaving out the button and the popover. * Used for the allFiltersModal component. */ onlySlot?: boolean; /** * Allows to handle the controls (apply/clear) from outside * of the component */ controlled?: boolean; /** * Disable the "apply" button */ disableApplyButton?: boolean; /** * Disable the secondary button action. */ disableSecondaryButton?: boolean; /** * Filter name/key referencing the actual property name in the data set. * Should be unique per filter bar. */ field: Field; /** * Label text to be displayed */ label: string; /** * Sets the size of the container to fit its content */ fitContent?: boolean; /** * Attaches a `BentoTooltip` if present. Text showed in the tooltip on hover. */ tooltipText?: string; /** * Position the tooltip should take if space is available. */ tooltipPosition?: `${BentoTooltipPosition}`; /** * The current value of the filter, used for v-model. */ value?: Value; }
@@ -0,0 +1 @@
1
+ import { type BentoCheckboxGroupItems } from '../../../checkbox/components/checkbox-group/checkbox-group.types'; import { type BentoTooltipPosition } from '@/directives/tooltip/tooltip.types'; export interface BentoCheckboxGroupFilterOptions { /** * An array of objects to create the checkboxes. Each object must have a `label` and `value`. * It can also have a `disabled` property. */ checkboxItems: BentoCheckboxGroupItems; /** * Attaches a `BentoTooltip` if present. Text showed in the tooltip on hover. */ tooltipText?: string; /** * Position the tooltip should take if space is available. */ tooltipPosition?: BentoTooltipPosition | `${BentoTooltipPosition}`; } export type BentoCheckboxGroupFilterValue = Array<number | string | null>; export interface BentoCheckboxGroupFilterProps { /** * Disables the filter from being interacted with. */ disabled?: boolean; /** * Default value of the filter to reset to. */ defaultValue?: BentoCheckboxGroupFilterValue; /** * Filter name/key referencing the actual property name in the data set. * Should be unique per filter bar. */ field: string; /** * Label text to be displayed */ label: string; /** * Renders the base filter's slot content only - leaving out the button and the popover. * Used for the allFiltersModal component. */ onlySlot?: boolean; /** * List of options that are needed to setup the filter */ options?: BentoCheckboxGroupFilterOptions; /** * Current value of the filter */ value?: BentoCheckboxGroupFilterValue; }
@@ -0,0 +1 @@
1
+ export interface BentoFilterBarButtonGroupProps { /** * Whether the button group is active. */ isActive?: boolean; /** * Whether to skip grouping the buttons. Used to simplify usage of multiple v-ifs. */ skipGrouping?: boolean; }
@@ -0,0 +1 @@
1
+ import type { BentoInputFieldProps } from '../../../input-field/input-field.types'; import type { BentoTooltipPosition } from '@/directives/tooltip/tooltip.types'; /** * @deprecated from version 2.0.0. Use BentoFilterItemType.INPUT_DROPDOWN instead. */ export interface BentoInputFilterInputAndDropdownValues { inputValue: string | null; dropdownValue?: string; } export type BentoInputFilterValue = string | null | BentoInputFilterInputAndDropdownValues; export interface BentoInputFilterOptions { /** * @deprecated BentoInputFilter "options" no longer supports "dropdown". Use the "BentoFilterItemType.INPUT_DROPDOWN" instead of "BentoFilterItemType.INPUT" */ dropdown?: any; /** * @deprecated BentoInputFilter "options" no longer supports "dropdownPosition". Use the "BentoFilterItemType.INPUT_DROPDOWN" instead of "BentoFilterItemType.INPUT" */ dropdownPosition?: any; /** * Sets a placeholder in the input field */ placeholder?: BentoInputFieldProps['placeholder']; /** * @deprecated from version 2.0.0. BentoInputFilter "options" no longer supports "variant". Use the "BentoFilterItemType.INPUT_DROPDOWN" instead of "BentoFilterItemType.INPUT" */ variant?: 'dropdown'; /** * Attaches a `BentoTooltip` if present. Text showed in the tooltip on hover. */ tooltipText?: string; /** * Position the tooltip should take if space is available. */ tooltipPosition?: BentoTooltipPosition | `${BentoTooltipPosition}`; } export interface BentoInputFilterProps { /** * Default value of the filter to reset to. */ defaultValue?: string; /** * Disables the filter from being interacted with. */ disabled?: boolean; /** * Filter name/key referencing the actual property name in the data set. * Should be unique per filter bar. */ field: string; /** * Label text to be displayed */ label: string; /** * Renders the base filter's slot content only - leaving out the button and the popover. * Used for the allFiltersModal component. */ onlySlot?: boolean; /** * List of options that are needed to setup the filter * Available options: * - placeholder: allows to set a placeholder to the search bar * - dropdown (deprecated): all the dropdown properties that can be set in the inner dropdown of the input field. @see BentoDropdown for more info. * - dropdownPosition (deprecated): allow the user to set the dropdown on the left or right side. Accepts the `start` or `end` values. * - variant (deprecated): can be `undefined` or set to `dropdown`. Enables dropdown input variant. * - tooltipPosition: The position of the tooltip attached to the filter button * - tooltipText: The content of the tooltip attached to the filter button */ options?: BentoInputFilterOptions; /** * Current value of the filter. * If the options.variant is equal to 'dropdown', the value should be of type `{ inputValue: string | null; dropdownValue?: string }`. */ value?: BentoInputFilterValue; }
@@ -0,0 +1 @@
1
+ import { type BentoRadioGroupItems, type BentoRadioGroupValue } from '@/components/radio-group/radio-group.types'; import { type BentoTooltipPosition } from '@/directives/tooltip/tooltip.types'; export interface BentoRadioGroupFilterOptions { /** * List of radio group options */ items: BentoRadioGroupItems; /** * Attaches a `BentoTooltip` if present. Text showed in the tooltip on hover. */ tooltipText?: string; /** * Position the tooltip should take if space is available. */ tooltipPosition?: BentoTooltipPosition | `${BentoTooltipPosition}`; } export type BentoRadioGroupFilterValue = BentoRadioGroupValue; export interface BentoRadioGroupFilterProps { /** * Disables the filter from being interacted with. */ disabled?: boolean; /** * Default value of the filter to reset to. */ defaultValue?: BentoRadioGroupFilterValue; /** * Filter name/key referencing the actual property name in the data set. * Should be unique per filter bar. */ field: string; /** * Label text to be displayed */ label: string; /** * List of options that are needed to setup the filter * Available options: * - items: Radio group elements containing "value" and "label" */ options: BentoRadioGroupFilterOptions; /** * Renders the base filter's slot content only - leaving out the button and the popover. * Used for the allFiltersModal component. */ onlySlot?: boolean; /** * The current value of the filter, used for v-model. */ value?: BentoRadioGroupFilterValue; }
@@ -0,0 +1 @@
1
+ import { type BentoTooltipPosition } from '../../../../directives/tooltip/tooltip.types'; export type BentoToggleFilterValue = boolean; export interface BentoToggleFilterOptions { /** * Position the tooltip should take if space is available. */ tooltipPosition?: BentoTooltipPosition | `${BentoTooltipPosition}`; /** * Attaches a `BentoTooltip` if present. Text showed in the tooltip on hover. */ tooltipText?: string; } export interface BentoToggleFilterProps { /** * Provides an optional description of the filter. Used in the "All filters" modal to add more context to the filter */ description?: string; /** * Disables the filter from being interacted with. */ disabled?: boolean; /** * Filter name/key referencing the actual property name in the data set. * Should be unique per filter bar. */ field: string; /** * Label text to be displayed */ label: string; /** * Renders the base filter's slot content only - leaving out the button and the popover. * Used for the allFiltersModal component. */ onlySlot?: boolean; /** * List of options that are needed to setup the filter * Available options: * - description: Additional information related to the toggle filter. Used in the "All filters" modal. */ options?: BentoToggleFilterOptions; /** * The current value of the filter, used for v-model. */ value?: BentoToggleFilterValue; }
@@ -0,0 +1 @@
1
+ import { type BentoAvatarListboxOptions } from '@/components/dropdown/components/variants/dropdown-avatar/dropdown-avatar.types'; import { type BentoListboxEmptyStateProps, type BentoListboxIsOptionDisabled, type BentoListboxLazyLoadType, type BentoListboxOptions, type BentoListboxSearchOptions, type BentoListboxSelectedValue, type BentoListboxVirtulisationOptions, } from '@/types/listbox'; import { type OmitStrict } from '@/types/omit-strict'; import { type VueNodeElement } from '@/types/elements'; import { type BentoCountryListboxOptions } from '@/components/dropdown/components/variants/dropdown-country/dropdown-country.types'; import { type BentoTagListboxOptions } from '@/components/dropdown/components/variants/dropdown-tag/dropdown-tag.types'; import { type BentoCountryVariant } from '@/components/country'; import { type BentoCurrencyListboxOptions } from '@/components/dropdown/components/variants/dropdown-currency/dropdown-currency.types'; import { type BentoPaymentMethodListboxOptions } from '@/components/dropdown/components/variants/dropdown-payment-method/dropdown-payment-method.types'; import { type BentoInputDropdownProps, type BentoInputFieldElementPosition, type BentoInputFieldValue, } from '@/components/input-field'; import { type BentoDatePickerFirstDayOfWeek, type BentoDatePickerIsDateDisabled, type BentoDatePickerVariant, } from '@/components/date-picker'; import { type BentoDateRangePickerGranularityConfig, type BentoDateRangePickerValue, type BentoDateRangePickerVariant, } from '@/components/date-range-picker'; import { type BentoRadioGroupFilterOptions, type BentoRadioGroupFilterValue, } from './components/radio-group-filter/radio-group-filter.types'; import { type DateRangePickerCalendarRangeSelectorItems } from '../date-range-picker/components/date-range-picker-calendar/date-range-picker-calendar.types'; import { type BentoSearchBarProps } from '@/components/search-bar/search-bar.types'; import type { BentoInputFilterOptions, BentoInputFilterValue } from './components/input-filter/input-filter.types'; import type { BentoToggleFilterOptions, BentoToggleFilterValue } from './components/toggle-filter/toggle-filter.types'; import type { BentoTooltipPosition } from '@/directives'; import { type BentoCheckboxGroupFilterOptions, type BentoCheckboxGroupFilterValue, } from './components/checkbox-group-filter/checkbox-group-filter.types'; /** * Re-export filter component types */ export type * from './components/input-filter/input-filter.types'; export type * from './components/radio-group-filter/radio-group-filter.types'; export type * from './components/toggle-filter/toggle-filter.types'; export interface BentoSelectFilterOptions { /** * Empty state props that will be used with the inner empty state component to be displayed when no search results are found. */ emptyState?: BentoListboxEmptyStateProps; /** * Option items for the list-box */ listboxItems: BentoListboxOptions; /** * Defines if the list-box should be a multi-select filter */ multiple?: boolean; /** * @deprecated from version 2.0.0. The persistent filter label now uses a counter instead of custom strings. This property is now redundant. * The translation object to handle any custom transations. e.g. perisistent filter labels. */ messages?: { [key: string]: { persistentButtonLabelPartiallySelected?: string; persistentButtonLabelAllSelected?: string; [key: string]: string; }; }; /** * @deprecated from version 2.0.0. The persistent filter label now uses a counter instead of custom strings. This property is now redundant. * The unique key to use in the messages translation object to use for the persistent button label * when the select filter has partially selected items. */ messagesPersistentButtonLabelPartiallySelectedKey?: string; /** * @deprecated from version 2.0.0. The persistent filter label now uses a counter instead of custom strings. This property is now redundant. * The unique key to use in the messages translation object to use for the persistent button label * when the select filter has all items selected. */ messagesPersistentButtonLabelAllSelectedKey?: string; /** * The event fire when the filter is opened. */ onOpen?: () => void; lazy?: { /** * Indicates whether there are more items to load */ hasMoreItems: boolean; /** * The type of `Lazy Load`. * Type "automatic" will enable infinite scrolling * Type "button" will enable lazy loading with "Show more" button */ lazyLoadType: BentoListboxLazyLoadType | `${BentoListboxLazyLoadType}`; /** * Indicates if new options are loading. */ loading: boolean; /** * The click event that fires when the user press on the "show more" button OR when the user * scrolls down to bottom of the select filter box. */ showMoreEvent?: () => void; }; /** * Toggles the loading state of the listbox. */ loading?: boolean; /** * Search options and events for the listbox filtering. */ search?: BentoListboxSearchOptions; /** * Defines if the list-box should support static categories */ staticCategories?: boolean; /** * A function that defines which option in the list-box is disabled */ isOptionDisabled?: BentoListboxIsOptionDisabled; /** * Enables virtual scrolling if set to true or by providing an object with itemHeight function. * The itemHeight function is used to calculate the height of rendered item given it's index. */ virtualScroll?: BentoListboxVirtulisationOptions; /** * Attaches a `BentoTooltip` if present. Text showed in the tooltip on hover. */ tooltipText?: string; /** * Position the tooltip should take if space is available. */ tooltipPosition?: BentoTooltipPosition | `${BentoTooltipPosition}`; /** * Enables value/label value handling. When `true`, the component expects and emits value/label * pair objects instead of primitive values. * * This affects both the `value` prop and the `input` event payload. * * - **Input (`value` prop):** Expects a `{ value: string | number, label: string }` object (or an array of them for multi-select). * - **Output (`input` event):** Emits a `{ value: string | number, label: string }` object (or an array of them) upon selection. * * This is particularly useful when a selected item's label is not available in the `items` prop, * allowing the component to display the correct label for values loaded from external sources. * @default false */ enableValueLabelPair?: boolean; /** * Enables a search bar that dynamically filters values * @default true */ dynamicFiltering?: boolean; } export type BentoSelectAvatarFilterOptions = OmitStrict<BentoSelectFilterOptions, 'listboxItems'> & { listboxItems: BentoAvatarListboxOptions; }; export type BentoSelectCountryFilterOptions = OmitStrict<BentoSelectFilterOptions, 'listboxItems'> & { listboxItems: BentoCountryListboxOptions; variant?: BentoCountryVariant | `${BentoCountryVariant}`; }; export type BentoSelectCurrencyFilterOptions = OmitStrict<BentoSelectFilterOptions, 'listboxItems'> & { listboxItems: BentoCurrencyListboxOptions; }; export type BentoSelectTagFilterOptions = OmitStrict<BentoSelectFilterOptions, 'listboxItems'> & { listboxItems: BentoTagListboxOptions; }; export type BentoSelectPaymentMethodFilterOptions = OmitStrict<BentoSelectFilterOptions, 'listboxItems'> & { listboxItems: BentoPaymentMethodListboxOptions; }; export interface BentoBooleanFilterOption { label: string; value: string | boolean; } export interface BentoBooleanFilterOptions { positiveOption?: BentoBooleanFilterOption; negativeOption?: BentoBooleanFilterOption; tooltipText?: string; tooltipPosition?: BentoTooltipPosition | `${BentoTooltipPosition}`; } export interface BentoInputWithDropdownFilterOptions { dropdown: BentoInputDropdownProps; dropdownPosition?: BentoInputFieldElementPosition | `${BentoInputFieldElementPosition}`; tooltipText?: string; tooltipPosition?: BentoTooltipPosition | `${BentoTooltipPosition}`; } export interface BentoRangeFilterOptions { tooltipText?: string; tooltipPosition?: BentoTooltipPosition | `${BentoTooltipPosition}`; } // Date export interface BentoDateFilterOptions { firstDayOfWeek?: BentoDatePickerFirstDayOfWeek; isDateDisabled?: BentoDatePickerIsDateDisabled; variant?: `${BentoDatePickerVariant}`; tooltipText?: string; tooltipPosition?: BentoTooltipPosition | `${BentoTooltipPosition}`; } export interface BentoDateRangeFilterOptions { allowTimeInput?: boolean; firstDayOfWeek?: BentoDatePickerFirstDayOfWeek; isDateDisabled?: BentoDatePickerIsDateDisabled; granularities?: Array<BentoDateRangePickerGranularityConfig>; maxRange?: number; min?: Date; max?: Date; numberOfMonths?: number; showEndDateOnOpen?: boolean; quickSelectRanges?: DateRangePickerCalendarRangeSelectorItems | boolean; variant?: `${BentoDateRangePickerVariant}`; tooltipText?: string; tooltipPosition?: BentoTooltipPosition | `${BentoTooltipPosition}`; } export interface BentoInpuWithDropdowntFilterValue { inputValue: BentoInputFieldValue; dropdownValue: BentoInputDropdownProps['value']; } export type BentoSelectFilterValue = BentoListboxSelectedValue; export type BentoBooleanFilterValue = string | null; export type BentoDateFilterValue = Date | null; export type BentoDateRangeFilterValue = BentoDateRangePickerValue | null; export type BentoFilterBarValue = | BentoInputFilterValue | BentoInpuWithDropdowntFilterValue | BentoSelectFilterValue | BentoBooleanFilterValue | BentoDateFilterValue | BentoDateRangeFilterValue | BentoRadioGroupFilterValue | BentoCheckboxGroupFilterValue | BentoToggleFilterValue; export type BentoFilterOptions = | BentoSelectFilterOptions | BentoSelectAvatarFilterOptions | BentoSelectCountryFilterOptions | BentoSelectCurrencyFilterOptions | BentoSelectPaymentMethodFilterOptions | BentoSelectTagFilterOptions | BentoDateFilterOptions | BentoDateRangeFilterOptions | BentoBooleanFilterOptions | BentoInputFilterOptions | BentoInputWithDropdownFilterOptions | BentoRadioGroupFilterOptions | BentoCheckboxGroupFilterOptions | BentoToggleFilterOptions | undefined; export enum BentoFilterEvent { // Fires upon the user interacting with the filter INPUT = 'input', // Fires on the (new) filter values being applied UPDATE = 'update', } export enum BentoFilterBarEvent { INPUT = 'input', UPDATE_FILTER_VALUES_OBJECT = 'update:filter-values-object', UPDATE_FILTER_VALUES_ARRAY = 'update:filter-values', } export enum BentoFilterItemType { /** * @deprecated Use "BentoFilterItemType.RADIO_GROUP" instead */ BOOLEAN = 'BentoBooleanFilter', RANGE = 'BentoRangeFilter', // Input INPUT = 'BentoInputFilter', INPUT_DROPDOWN = 'BentoInputWithDropdownFilter', // Select SELECT = 'BentoSelectFilter', SELECT_AVATAR = 'BentoSelectAvatarFilter', SELECT_COUNTRY = 'BentoSelectCountryFilter', SELECT_CURRENCY = 'BentoSelectCurrencyFilter', SELECT_PAYMENT_METHOD = 'BentoSelectPaymentMethodFilter', SELECT_TAG = 'BentoSelectTagFilter', // Date DATE = 'BentoDateFilter', DATE_RANGE = 'BentoDateRangeFilter', // Radio group RADIO_GROUP = 'BentoRadioGroupFilter', // Checkbox CHECKBOX_GROUP = 'BentoCheckboxGroupFilter', // Toggle TOGGLE = 'BentoToggleFilter', } export interface BentoFilterValue { field: string; value?: BentoFilterBarValue; } export interface BentoFilterModel<CustomOptions = any> extends BentoFilterValue { label: string; description?: string; options?: BentoFilterOptions | CustomOptions; disabled?: boolean; defaultValue?: BentoFilterBarValue; visible?: boolean; /** * Defines the filter to be used. * * Can be one filter defined in the {@link BentoFilterItemType} enum or a * custom filter component defined as a {@link VueNodeElement} */ type: BentoFilterItemType | VueNodeElement; /** * @deprecated This property will be removed in version 2.0, please use `config` & `filter-values` properties. */ value?: BentoFilterBarValue; } export type BentoFilterBarModel<CustomOptions = any> = Array<BentoFilterModel<CustomOptions>>; export type BentoFilterValues = Array<BentoFilterValue>; export type BentoFilterValueObject<T extends PropertyKey = string, CustomValue = BentoFilterBarValue> = Record< T, CustomValue >; export type BentoFilterBarSearchBarProps = OmitStrict<BentoSearchBarProps, 'value' | 'condensed'>; export type BentoFilterBarSearchTerm = BentoSearchBarProps['value']; export type BentoFilterBarScreenOptionsLayout = 'one-line' | 'multi-line'; export interface BentoFilterBarContainerSizeLayout { /** * @default 'one-line' */ medium?: BentoFilterBarScreenOptionsLayout; /** * @default 'multi-line' */ small?: BentoFilterBarScreenOptionsLayout; } export interface BentoFilterBarProps<CustomValue = BentoFilterBarValue | any, CustomOptions = any> { /** * Array of filters based on @see {@link BentoFilterBarModel}. * Each one of the elements in the array correspond to a filter that will * be created and rendered in the FilterBar. * * @example * [{ * field: 'company', * label: 'Company', * type: FilterType.TEXT, * }, { * field: 'name', * label: 'Name', * type: FilterType.TEXT, * }] */ config?: BentoFilterBarModel<CustomOptions>; /** * An array of the values for each filter in the filter bar. * @see {@link BentoFilterValues} * * @example * [{ * field: 'company', * value: '200', * }, { * field: 'name', * value: 'some text', * }] */ filterValues?: BentoFilterValues; /** * Defines the layout (multi-line or one-line) to be used in the filter bar for different * container sizes: * - Large (>=700px): Always multi-line * - Medium (>=430px & <700px) * - Small (<430px) * * @example * { * medium: 'multi-line', * small: 'one-line' * } */ containerSizeLayout?: BentoFilterBarContainerSizeLayout; /** * Search bar properties with which the placeholder, hint and other * "bento-search-bar" properties can be set. @see {@link BentoFilterBarSearchBarProps} */ search?: BentoFilterBarSearchBarProps; /** * Search term useful to filter all the data by text. * If the value is non-existant (null or undefined) the search bar won't be enabled. * Can be used with `.sync` */ searchTerm?: BentoFilterBarSearchTerm; /** * Will show all applied filters even if their visibility is set to false. */ showAppliedHiddenFilters?: boolean; /** * Array of filters based on @see {@link BentoFilterBarModel}. * Each one of the elements in the array correspond to a filter that will * be created and rendered in the FilterBar. * * Can be used with v-model or independently with the @input event. * * @example * [{ * field: 'company', * label: 'Company', * value: '200', * type: FilterType.TEXT, * }, { * field: 'name', * label: 'Name', * value: null, * type: FilterType.TEXT, * }] * * @deprecated from version 2.0.0. Use the config property instead. */ value?: BentoFilterBarModel; /** * An object of the values for each filter in the filter bar where key is a the field name and the value is the BentoFilterBarValue. * * @example * { * 'company': '200', * 'name': 'some text' * }, */ filterValuesObject?: BentoFilterValueObject<string, CustomValue>; }
@@ -0,0 +1 @@
1
+ export enum BentoFocusTrapArrowNavigationDirection { VERTICAL = 'vertical', HORIZONTAL = 'horizontal', } export enum BentoFocusTrapVerticalNavigationEvent { ARROW_UP = 'arrowup', ARROW_DOWN = 'arrowdown', } export enum BentoFocusTrapHorizontalNavigationEvent { ARROW_LEFT = 'arrowleft', ARROW_RIGHT = 'arrowright', } export interface BentoFocusTrapFocusableIn { node: HTMLElement; /** * tab index */ index: number; /** * true, if this node belongs to a Lock */ lockItem: boolean; /** * true, if this node is a focus-guard (system node) */ guard: boolean; }
@@ -0,0 +1 @@
1
+ import { type BentoAlertTypeOption } from '@/components/alert/alert.types'; export type BentoFormLayoutFieldTipTypeOption = `${BentoAlertTypeOption}`; export const BentoFormLayoutFieldTipTypeOptions = ['warning', 'critical', 'highlight', 'success'];
@@ -0,0 +1 @@
1
+ export type BentoLayoutGroupProportions = | '33-33-33' | '50-50' | '25-75' | '75-25' | '33-66' | '66-33' | '25' | '33' | '50' | '100'; export const BentoLayoutGroupProportions = [ '100', '50', '33', '25', '66-33', '33-66', '75-25', '25-75', '50-50', '33-33-33', ];
@@ -0,0 +1 @@
1
+ export const FORM_LAYOUT_LOADING_INJECTION_KEY = Symbol('form layout loading injection key');
@@ -0,0 +1 @@
1
+ import { type BentoLoadingButtonState } from '../button/components/loading-button/loading-button.types'; /** * @deprecated for usage with properties since version 2.0. Use the string literal instead. */ export enum BentoHeaderVariant { PAGE = 'page', COMPONENT = 'component', } export interface BentoHeaderReloadButton { title: string; event?: (event: Event) => void; state?: `${BentoLoadingButtonState}`; }
@@ -0,0 +1 @@
1
+ // eslint-disable-next-line @typescript-eslint/consistent-type-imports import { BentoMenu, type BentoMenuItem } from '@/components/menu'; import { type BentoHeaderWithViewsItem } from '../../header-with-views.types'; export interface HeaderWithViewsAllViewsProps { menuCloseOnClickOutsideOptions?: InstanceType<typeof BentoMenu>['$props']['closeOnClickOutsideOptions']; menuItems: Array<BentoHeaderWithViewsItem>; menuItemsActions: Array<BentoMenuItem>; }
@@ -0,0 +1 @@
1
+ import { type BentoHeaderWithViewsItemAdditionalConfig, type BentoHeaderWithViewsItemErrors, type BentoHeaderWithViewsItemIcon, type BentoHeaderWithViewsItemIconColor, } from '../../header-with-views.types'; type HeaderWithViewsEditFormContext = `${HeaderWithViewsEditFormContextEnum}`; export enum HeaderWithViewsEditFormContextEnum { EDIT = 'edit', CREATE = 'create', } export interface HeaderWithViewsEditFormProps { additionalFormItem?: BentoHeaderWithViewsItemAdditionalConfig; context?: HeaderWithViewsEditFormContext; disableCustomization?: boolean; errors?: BentoHeaderWithViewsItemErrors; icon?: BentoHeaderWithViewsItemIcon; iconColor?: BentoHeaderWithViewsItemIconColor; label?: string; }
@@ -0,0 +1 @@
1
+ import { type BentoButtonActionsList } from '@/components/button'; import { type VueNodeElement } from '@/types/elements'; import type { HeaderMetaProps } from '@/internal'; import BagIcon from '@adyen/ui-assets-icons-16/vue/bag'; import CarIcon from '@adyen/ui-assets-icons-16/vue/car'; import CashStackIcon from '@adyen/ui-assets-icons-16/vue/cash-stack'; import CloudIcon from '@adyen/ui-assets-icons-16/vue/cloud'; import DiamondIcon from '@adyen/ui-assets-icons-16/vue/diamond'; import EducationIcon from '@adyen/ui-assets-icons-16/vue/education'; import EmoticonHappyIcon from '@adyen/ui-assets-icons-16/vue/emoticon-happy'; import HotelIcon from '@adyen/ui-assets-icons-16/vue/gambling'; import GamingIcon from '@adyen/ui-assets-icons-16/vue/gaming'; import GiftIcon from '@adyen/ui-assets-icons-16/vue/gift'; import HandRockIcon from '@adyen/ui-assets-icons-16/vue/hand-rock'; import HashtagIcon from '@adyen/ui-assets-icons-16/vue/hashtag'; import HeartIcon from '@adyen/ui-assets-icons-16/vue/heart'; import HospitalityIcon from '@adyen/ui-assets-icons-16/vue/hospitality'; import HourglassIcon from '@adyen/ui-assets-icons-16/vue/hourglass'; import LeafIcon from '@adyen/ui-assets-icons-16/vue/leaf'; import LightningIcon from '@adyen/ui-assets-icons-16/vue/lightning'; import LockIcon from '@adyen/ui-assets-icons-16/vue/lock'; import NewsIcon from '@adyen/ui-assets-icons-16/vue/news'; import NotificationIcon from '@adyen/ui-assets-icons-16/vue/notification'; import PrizeIcon from '@adyen/ui-assets-icons-16/vue/prize'; import RetailIcon from '@adyen/ui-assets-icons-16/vue/retail'; import ShoppingCartIcon from '@adyen/ui-assets-icons-16/vue/shopping-cart'; import SleepIcon from '@adyen/ui-assets-icons-16/vue/sleep'; import StackIcon from '@adyen/ui-assets-icons-16/vue/stack'; import StarIcon from '@adyen/ui-assets-icons-16/vue/star'; import SupportIcon from '@adyen/ui-assets-icons-16/vue/support'; import TargetIcon from '@adyen/ui-assets-icons-16/vue/target'; import ThumbsDownIcon from '@adyen/ui-assets-icons-16/vue/thumb-down'; import ThumbsUpIcon from '@adyen/ui-assets-icons-16/vue/thumb-up'; import TravelBagIcon from '@adyen/ui-assets-icons-16/vue/travel-bag'; import WarningDiamondIcon from '@adyen/ui-assets-icons-16/vue/warning-diamond'; import WorldIcon from '@adyen/ui-assets-icons-16/vue/world'; /* Public */ export type BentoHeaderWithViewsItemIconColor = `${BentoHeaderWithViewsItemIconColorEnum}`; export type BentoHeaderWithViewsItemIcon = `${BentoHeaderWithViewsItemIconEnum}`; export interface BentoHeaderWithViewsItemAdditionalConfig { /** * Label for an additional view setting (e.g. a checkbox or toggle). */ label: string; /** * Boolean value indicating the state of the additional view setting (e.g. checked or toggled). */ value: boolean; } export interface BentoHeaderWithViewsItem { /** * Optional icon to display for the view item. */ icon?: BentoHeaderWithViewsItemIcon; /** * Optional color of the icon for the view item. */ iconColor?: BentoHeaderWithViewsItemIconColor; /** * Text label for the view item. */ label: string; /** * Optional boolean indicating if this view item is the default or currently active view. */ isDefault?: boolean; /** * Optional configuration for an additional view setting, such as a filter or option. */ additionalViewSetting?: BentoHeaderWithViewsItemAdditionalConfig; /** * Optional boolean indicating if this view can be edited or deleted. */ isFixed?: boolean; } export interface BentoHeaderWithViewsProps { /** * Optional list of actions (buttons) to display in the header. * The structure of each action is defined by the `BentoButtonActionsList` type. * * {@link [BentoButtonActionsList](https://adyen-bento.netlify.app/?path=/docs/components-button-button-actions--docs#variations)} */ actions?: BentoButtonActionsList; /** * Main title text for the header. This prop is required. */ title: string; /** * Optional array of view items to display as selectable views or tabs. * * Each view should have the following property: * - `icon` (BentoHeaderWithViewsItemIcon, optional): Optional icon to display. * - `iconColor` (BentoHeaderWithViewsItemIconColor, optional): Optional color of the icon. * - `label` (string): Text label for the view item. * - `isDefault` (boolean, optional): Indicates if this view item is the default or currently active view. * - `additionalViewSetting` (BentoHeaderWithViewsItemAdditionalConfig, optional): Optional configuration for an additional view setting. * - `isFixed` (boolean, optional): Indicates if this view can be edited or deleted. * @see {@link BentoHeaderWithViewsItem} */ views?: Array<BentoHeaderWithViewsItem>; /** * Optional configuration for an additional view setting, such as a filter or option. * Defined by the `BentoHeaderWithViewsItemAdditionalConfig` interface. * * @see {@link BentoHeaderWithViewsItemAdditionalConfig} */ additionalViewSetting?: BentoHeaderWithViewsItemAdditionalConfig; /** * Boolean indicating whether there are unsaved changes on the page. * This might be used to visually indicate to the user that they need to save their work. */ hasChanges: boolean; /** * String indicating whether to display a prompt, reminding the user to save their changes whenever they switch view tabs. * If null, no prompt is shown and changes will always be restored to it's previous settings on view tab change. */ skipUnsavedChangesText: string | null; /** * Boolean indicating whether icon and icon selection is allowed on view creation and edit. */ disableCustomization?: boolean; /** * Text showed in the info-icon tooltip on hover */ infoIconTooltipText?: string; /** * Meta description for the header component */ description?: string; /** * JavaScript "Date" object for when the content was last updated */ lastUpdated?: Date | number; /** * Reload button which shows next to the header's timestamp * @see HeaderMetaProps['reloadButton'] */ reloadButton?: HeaderMetaProps['reloadButton']; /** * Whether or not to automatically select a view on mount */ disableAutoSelect?: boolean; /** * The active view index. Supports two-way binding via v-model:active-view-index. * When provided, this takes precedence over `isDefault` on individual views for initial selection. * Changes from outside the component will update the active view. * Has no effect when `disableAutoSelect` is true. */ activeViewIndex?: number; } export interface BentoHeaderWithViewsItemUpdate { newViews: Array<BentoHeaderWithViewsItem>; oldViews: Array<BentoHeaderWithViewsItem>; targetViewLabel: BentoHeaderWithViewsItem['label']; } /* Private */ export enum BentoHeaderWithViewsItemIconColorEnum { BLUE = 'blue', GREEN = 'green', ORANGE = 'orange', PRIMARY = 'primary', RED = 'red', YELLOW = 'yellow', } export enum BentoHeaderWithViewsItemIconEnum { STACK = 'stack', LEAF = 'leaf', HAND_ROCK = 'hand-rock', CAR = 'car', EDUCATION = 'education', HOTEL = 'hotel', TRAVEL_BAG = 'travel-bag', LIGHTNING = 'lightning', NEWS = 'news', PRIZE = 'prize', LOCK = 'lock', BAG = 'bag', DIAMOND = 'diamond', TARGET = 'target', GIFT = 'gift', CASH_STACK = 'cash-stack', HASHTAG = 'hashtag', CLOUD = 'cloud', HOSPITALITY = 'hospitality', SHOPPING_CART = 'shopping-cart', SLEEP = 'sleep', NOTIFICATION = 'notification', THUMBS_UP = 'thumb-up', THUMBS_DOWN = 'thumb-down', STAR = 'star', WARNING_DIAMOND = 'warning-diamond', RETAIL = 'retail', EMOTICON_HAPPY = 'emoticon-happy', GAMING = 'gaming', SUPPORT = 'support', HEART = 'heart', HOURGLASS = 'hourglass', WORLD = 'world', } export const BentoHeaderWithViewsItemIconElement: Record<BentoHeaderWithViewsItemIcon, VueNodeElement> = { [BentoHeaderWithViewsItemIconEnum.STACK]: StackIcon, [BentoHeaderWithViewsItemIconEnum.LEAF]: LeafIcon, [BentoHeaderWithViewsItemIconEnum.HAND_ROCK]: HandRockIcon, [BentoHeaderWithViewsItemIconEnum.CAR]: CarIcon, [BentoHeaderWithViewsItemIconEnum.EDUCATION]: EducationIcon, [BentoHeaderWithViewsItemIconEnum.HOTEL]: HotelIcon, [BentoHeaderWithViewsItemIconEnum.TRAVEL_BAG]: TravelBagIcon, [BentoHeaderWithViewsItemIconEnum.LIGHTNING]: LightningIcon, [BentoHeaderWithViewsItemIconEnum.NEWS]: NewsIcon, [BentoHeaderWithViewsItemIconEnum.PRIZE]: PrizeIcon, [BentoHeaderWithViewsItemIconEnum.LOCK]: LockIcon, [BentoHeaderWithViewsItemIconEnum.BAG]: BagIcon, [BentoHeaderWithViewsItemIconEnum.DIAMOND]: DiamondIcon, [BentoHeaderWithViewsItemIconEnum.TARGET]: TargetIcon, [BentoHeaderWithViewsItemIconEnum.GIFT]: GiftIcon, [BentoHeaderWithViewsItemIconEnum.CASH_STACK]: CashStackIcon, [BentoHeaderWithViewsItemIconEnum.HASHTAG]: HashtagIcon, [BentoHeaderWithViewsItemIconEnum.CLOUD]: CloudIcon, [BentoHeaderWithViewsItemIconEnum.HOSPITALITY]: HospitalityIcon, [BentoHeaderWithViewsItemIconEnum.SHOPPING_CART]: ShoppingCartIcon, [BentoHeaderWithViewsItemIconEnum.SLEEP]: SleepIcon, [BentoHeaderWithViewsItemIconEnum.NOTIFICATION]: NotificationIcon, [BentoHeaderWithViewsItemIconEnum.THUMBS_UP]: ThumbsUpIcon, [BentoHeaderWithViewsItemIconEnum.THUMBS_DOWN]: ThumbsDownIcon, [BentoHeaderWithViewsItemIconEnum.STAR]: StarIcon, [BentoHeaderWithViewsItemIconEnum.WARNING_DIAMOND]: WarningDiamondIcon, [BentoHeaderWithViewsItemIconEnum.RETAIL]: RetailIcon, [BentoHeaderWithViewsItemIconEnum.EMOTICON_HAPPY]: EmoticonHappyIcon, [BentoHeaderWithViewsItemIconEnum.GAMING]: GamingIcon, [BentoHeaderWithViewsItemIconEnum.SUPPORT]: SupportIcon, [BentoHeaderWithViewsItemIconEnum.HEART]: HeartIcon, [BentoHeaderWithViewsItemIconEnum.HOURGLASS]: HourglassIcon, [BentoHeaderWithViewsItemIconEnum.WORLD]: WorldIcon, }; export interface BentoHeaderWithViewsEmits { /** * Emitted when a new view is added to the list of views. */ (e: 'add:view', value: BentoHeaderWithViewsItemUpdate): void; /** * Emitted when a view is deleted from the list of views. */ (e: 'delete:view', value: BentoHeaderWithViewsItemUpdate): void; /** * Emitted when a view is duplicated. */ (e: 'duplicate:view', value: BentoHeaderWithViewsItemUpdate): void; /** * Emitted when the user reorganizes the order of the views. * No `targetViewLabel` is emitted as the views are reordered as a whole. */ (e: 'reorder:view', value: Omit<BentoHeaderWithViewsItemUpdate, 'targetViewLabel'>): void; /** * Emitted when the user requests to restore a previously saved or default view. */ (e: 'restore:view', value: BentoHeaderWithViewsItem['label']): void; /** * Emitted when the user requests to save the current view. */ (e: 'save:view', value: BentoHeaderWithViewsItem['label']): void; /** * Emitted when the user selects a different view from the available options. */ (e: 'select:view', value: BentoHeaderWithViewsItem['label']): void; /** * Emitted when a property of a view is updated. */ (e: 'update:view', value: BentoHeaderWithViewsItemUpdate): void; /** * Emitted when the user selects the prompt checkbox and closes the modal with a valid button action. * Only works when a string is pass into the `skipUnsavedChangesText` prop. */ (e: 'update:skip-unsaved-changes', value: boolean): void; /** * Emitted when the active view index changes. Used for two-way binding with v-model:active-view-index. */ (e: 'update:active-view-index', value: number | null): void; } export type BentoHeaderWithViewsItemErrors = { /** * Object where the keys correspond to the properties of `BentoHeaderWithViewsItem`, * and the values are strings representing any validation errors for that property. */ [K in keyof BentoHeaderWithViewsItem]: string; };
@@ -0,0 +1 @@
1
+ export enum BentoImageAspectRatio { LANDSCAPE = 'landscape', P4_3 = '4:3', P3_3 = '3:3', P16_9 = '16:9', P2_1 = '2:1', PORTRAIT = 'portrait', P3_4 = '3:4', P2_3 = '2:3', P9_16 = '9:16', P1_2 = '1:2', SQUARE = 'square', P1_1 = '1:1', } export enum BentoImageObjectFit { COVER = 'cover', FILL = 'fill', CONTAIN = 'contain', } export interface BentoImageProps { /** Url to image, same as src attribute of `<img>` tag */ src: string; /** Accessible text describing the image, same as alt attribute of `<img>` tag */ alt: string; /** Aspect ratio of the image, maps to css aspect-ratio */ aspectRatio?: BentoImageAspectRatio | `${BentoImageAspectRatio}` | null; /** Object fitting strategy, maps to css object-fit */ objectFit?: BentoImageObjectFit | `${BentoImageObjectFit}` | null; }
@@ -0,0 +1 @@
1
+ import { type PopperContainerPosition } from '@/components/popper-container'; export interface BentoInfoIconProps { /** * Text showed in the info-icon tooltip on hover */ tooltipText: string; /** * Position the tooltip should take if space is available. Defaults to 'auto' */ tooltipPosition?: PopperContainerPosition | `${PopperContainerPosition}`; }
@@ -0,0 +1 @@
1
+ import { type BentoDropdownProps } from '../dropdown/dropdown.types'; import { type FormField } from '../../types/form-field'; /** * @deprecated for usage with properties since version 2.0. Use the string literal instead. */ export enum BentoInputFieldVariant { DEFAULT = 'default', PAYMENT_METHOD = 'payment-method', STATIC_VALUE = 'static-value', DROPDOWN = 'dropdown', } /** * @deprecated for usage with properties since version 2.0. Use the string literal instead. */ export enum BentoInputFieldElementPosition { START = 'start', END = 'end', } export enum BentoInputFieldStateClass { CONDENSED = 'condensed', DISABLED = 'disabled', READONLY = 'readonly', ERROR = 'error', } /** * @deprecated for usage with properties since version 2.0. Use the string literal instead. */ export enum BentoInputFieldType { SEARCH = 'search', TEXT = 'text', NUMBER = 'number', } export type BentoInputFieldValue = string | number; type DropdownProps = BentoDropdownProps; export interface BentoInputDropdownProps { /** * Text that labels the dropdown element */ 'aria-label'?: string; /** * Enables dynamic filtering */ dynamicFiltering?: DropdownProps['dynamicFiltering']; /** * List of available items */ items: DropdownProps['items']; /** * Sets the dropdown in read-only mode */ readonly?: DropdownProps['readonly']; /** * Selected value of the dropdown * @deprecated Use `model-value` instead. */ value?: DropdownProps['value']; /** * Selected value of the dropdown */ modelValue?: DropdownProps['modelValue']; } /** * @deprecated The use of these enums is no longer supported. * Use `v-on:` or `@` to listen to the events being emitted. * This enum will be removed in 2.0. */ export enum BentoInputFieldEvent { INPUT = 'input', ESCAPE_PRESSED = 'escape-pressed', UPDATE_DROPDOWN = 'update:dropdown', DROPDOWN_INPUT = 'dropdown-input', FOCUS = 'focus', CHANGE = 'change', BLUR = 'blur', KEYDOWN = 'keydown', } export interface BentoInputFieldProps extends FormField { /** * Indicates whether the element is exposed to an accessibility API. */ ariaHidden?: boolean; /** * Text that labels an the input element * * @deprecated from version 2.0.0. Use the aria-label attribute instead. */ ariaLabel?: string; /** * Reduces height of the input */ condensed?: boolean; /** * Dropdown props for the small selectable dropdown menu. * Properties supported: * * - `aria-label`: Text that labels the dropdown element * - `dynamicFiltering`: Enables dynamic filtering * - `items`: List of available items * - `readonly`: Sets the dropdown in read-only mode * - `value`: Selected value of the dropdown */ dropdown?: BentoInputDropdownProps; /** * Position of the dropdown before or after input value */ dropdownPosition?: BentoInputFieldElementPosition | `${BentoInputFieldElementPosition}`; /** * Adds error validation class for outlining input-field. * * @deprecated Use "errorMessage" prop instead */ error?: boolean; /** * Text displayed as the value of the input-field */ modelValue?: BentoInputFieldValue; /** * Renders a font numeric style with slashed zeros instead of normal ones. */ slashedZero?: boolean; /** * Static text that displayed before or after input value * Applicable to the Static Value variant */ staticValue?: string; /** * Position of Static Value before or after input value */ staticValuePosition?: BentoInputFieldElementPosition | `${BentoInputFieldElementPosition}`; /** * A string specifying the type of control to render. * All `input` types are available but only `search`, `text` and `number` are fully supported. * * If `type` is `search`, use `bento-search-bar` instead. */ type?: BentoInputFieldType | `${BentoInputFieldType}` | HTMLInputElement['type']; /** * Text displayed as the value of the input-field * @deprecated for usage with properties since version 2.0. Use `v-model` or `model-value` instead. */ value?: BentoInputFieldValue; /** * Describes the different variants for the input element */ variant?: BentoInputFieldVariant | `${BentoInputFieldVariant}`; }
@@ -0,0 +1 @@
1
+ import type { BentoInputFieldProps, BentoInputFieldValue } from '@/components/input-field/input-field.types'; import { type FormField } from '../../types/form-field'; /** * Public */ export interface BentoInputFieldPasswordValidationListItem { label?: string; key?: BentoInputFieldPasswordDefaultRuleKeys | string; // eslint-disable-next-line @typescript-eslint/no-explicit-any validate?: (value: BentoInputFieldValue, ...args: Array<any>) => boolean; // eslint-disable-next-line @typescript-eslint/no-explicit-any additionalArgs?: Array<any>; disabled?: boolean; } export type BentoInputFieldPasswordDefaultRuleKeys = | 'minLength' | 'requireLowercase' | 'requireNumeric' | 'requireSpecialCharacter' | 'requireUppercase'; export type BentoInputFieldPasswordErrorsList = Record<BentoInputFieldPasswordDefaultRuleKeys | string, string>; export interface BentoInputFieldPasswordValidation { title?: string; suggestion?: string; extendDefaultValidation?: boolean; list: Array<BentoInputFieldPasswordValidationListItem>; } export interface BentoInputFieldPasswordProps extends FormField { /** * Reduces height of the input */ condensed?: BentoInputFieldProps['condensed']; /** * The debounce time in milliseconds for running validation and emitting the `input` event after the user has stopped typing. * This helps to avoid excessive validation checks on every keystroke. Defaults to `500`. */ debounceTime?: number; /** * Performs real-time validations over the input value. Default: `false`. * Disabling the option (set to `true`) will not run pre-defined validations or custom validations defined in the `validation` field. */ disableValidation?: boolean; /** * Text displayed as the value of the input-field * @deprecated for usage with properties since version 2.0. Use `v-model` or `model-value` instead. */ value?: BentoInputFieldProps['value']; /** * Text displayed as the value of the input-field */ modelValue?: BentoInputFieldProps['modelValue']; /** * Configuration object for password field validation. * * This object defines the validation criteria and associated messages for a password field. * It includes the following properties: * * - `title`: The title displayed at the top of the validation hint. * - `suggestion`: The message displayed at the bottom of the validation hint, providing additional guidance. * - `extendDefaultValidation`: Set to true if you want to add your custom rules to the default validation instead of overriding the list. * - `list`: An array of callback functions to be executed on the value after each user input, validating the password against the specified rules. * * By default, the validation checks that: * * - The value has a minimum length of 12 characters. * - The value contains at least one lowercase letter. * - The value contains at least one uppercase letter. * - The value contains at least one numeric digit. * - The value contains at least one special character. * * */ validation?: BentoInputFieldPasswordValidation; /** * Whether the password validation hint is displayed when the input field is activated via focus or input */ withHint?: boolean; } /** * Private */ export enum BentoInputFieldPasswordType { TEXT = 'text', PASSWORD = 'password', } export enum BentoInputFieldPasswordDefaultRule { MIN_LENGTH = 'minLength', REQUIRE_LOWERCASE = 'requireLowercase', REQUIRE_NUMERIC = 'requireNumeric', REQUIRE_SPECIAL_CHARACTER = 'requireSpecialCharacter', REQUIRE_UPPERCASE = 'requireUppercase', }
@@ -0,0 +1 @@
1
+ import { type CountryCode } from 'libphonenumber-js'; import { type FormField } from '../../types/form-field'; import { type BentoInputDropdownProps } from '@/components/input-field/input-field.types'; export type BentoInputFieldPhoneNumberDropdownProps = Pick<BentoInputDropdownProps, 'readonly'>; /** * String literal with all the available country codes. */ export type BentoInputFieldPhoneNumberCountryCode = CountryCode | any; export interface BentoInputFieldPhoneNumberProps extends FormField { /** * Array of country codes to show on the dropdown */ countries?: Array<BentoInputFieldPhoneNumberCountryCode>; /** * Dropdown props for the small selectable dropdown menu. * Properties supported: * * - `readonly`: Sets the dropdown in read-only mode */ dropdown?: BentoInputFieldPhoneNumberDropdownProps; /** * If set, allows filtering on the countries dropdown */ dynamicFiltering?: boolean; /** * If true, applies error styling to the field. * @deprecated Use `errorMessage` prop instead. */ error?: boolean; /** * Selected country code */ selectedCountry?: BentoInputFieldPhoneNumberCountryCode; /** * The current value of the field. * @deprecated for usage with properties since version 2.0. Use `v-model` or `model-value` instead. */ value?: string | number; /** * The current value of the field. */ modelValue?: string | number; }
@@ -0,0 +1,52 @@
1
+ import { Canvas, Meta, Source } from '@storybook/blocks';
2
+ import * as InputTimeStories from './input-time.stories';
3
+
4
+ <Meta of={InputTimeStories} />
5
+
6
+ # Input Time
7
+
8
+ The input time component is designed for entering time values in `HH:MM:SS` format. It is an extension of the
9
+ `bento-input-field` component with built-in time character validation and auto-formatting logic.
10
+
11
+ <Canvas of={InputTimeStories.Default} />
12
+
13
+ ## Use Cases
14
+
15
+ Use this component if you need to collect a time of day value from a user.
16
+
17
+ ### Do not use
18
+
19
+ Avoid using this component for:
20
+
21
+ - Date inputs — use `bento-input-field` with an appropriate format or the `bento-date-picker` component.
22
+ - General text inputs — use `bento-input-field` instead.
23
+
24
+ ## Behaviour
25
+
26
+ ### Auto-formatting
27
+
28
+ As the user types, the component automatically inserts `:` separators to produce a well-formed `HH:MM:SS` string. For
29
+ example, typing `1` produces `1`, then `12` produces `12:`, then `1230` produces `12:30:`, and so on.
30
+
31
+ ### Key validation
32
+
33
+ Non-numeric characters (except `:` at valid positions and control keys such as `Backspace`, `ArrowLeft`, `ArrowRight`,
34
+ and `Tab`) are silently blocked via the `keydown` handler.
35
+
36
+ ## Modifiers
37
+
38
+ ### Error state
39
+
40
+ Use the `error-message` prop to display an inline error message and apply error styling:
41
+
42
+ ```html
43
+ <bento-input-time label="Start time" error-message="Invalid time format" />
44
+ ```
45
+
46
+ ### Disabled
47
+
48
+ Renders the time input in a disabled state.
49
+
50
+ ### Readonly
51
+
52
+ Renders the time input in a readonly state.