@adyen/bento-mcp 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (424) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +24 -2
  3. package/SKILL.md +37 -7
  4. package/dist/assets/components/accordion/accordion.stories.ts +1 -1
  5. package/dist/assets/components/accordion/components/accordion-item.stories.ts +1 -1
  6. package/dist/assets/components/accordion/components/accordion-item.vue +1 -1
  7. package/dist/assets/components/action-bar/action-bar.docs.mdx +10 -3
  8. package/dist/assets/components/action-bar/action-bar.internal.types.ts +1 -0
  9. package/dist/assets/components/action-bar/action-bar.stories.ts +1 -1
  10. package/dist/assets/components/action-bar/action-bar.types.ts +1 -1
  11. package/dist/assets/components/action-bar/action-bar.vue +1 -1
  12. package/dist/assets/components/ai-tag/ai-tag.stories.ts +1 -1
  13. package/dist/assets/components/ai-tag/ai-tag.vue +1 -1
  14. package/dist/assets/components/alert/alert.stories.ts +1 -1
  15. package/dist/assets/components/alert/alert.types.ts +1 -1
  16. package/dist/assets/components/anchor-scroller/anchor-scroller.stories.ts +1 -1
  17. package/dist/assets/components/anchor-scroller/anchor-scroller.vue +1 -1
  18. package/dist/assets/components/avatar/avatar.internal.types.ts +1 -0
  19. package/dist/assets/components/avatar/avatar.stories.ts +1 -1
  20. package/dist/assets/components/avatar/avatar.types.ts +1 -1
  21. package/dist/assets/components/button/button.docs.mdx +4 -3
  22. package/dist/assets/components/button/button.stories.ts +1 -1
  23. package/dist/assets/components/button/button.types.ts +1 -1
  24. package/dist/assets/components/button/components/ai-button/ai-button.stories.ts +1 -1
  25. package/dist/assets/components/button/components/ai-button/ai-button.vue +1 -1
  26. package/dist/assets/components/button/components/button-actions/button-actions.docs.mdx +3 -0
  27. package/dist/assets/components/button/components/button-actions/button-actions.internal.types.ts +1 -0
  28. package/dist/assets/components/button/components/button-actions/button-actions.stories.ts +1 -1
  29. package/dist/assets/components/button/components/button-actions/button-actions.types.ts +1 -1
  30. package/dist/assets/components/button/components/button-actions/button-actions.vue +1 -1
  31. package/dist/assets/components/button/components/loading-button/loading-button.docs.mdx +26 -23
  32. package/dist/assets/components/button/components/loading-button/loading-button.stories.ts +1 -1
  33. package/dist/assets/components/button/components/loading-button/loading-button.vue +1 -1
  34. package/dist/assets/components/button/components/toggle-button/toggle-button.stories.ts +1 -1
  35. package/dist/assets/components/card/card.docs.mdx +18 -1
  36. package/dist/assets/components/card/card.stories.ts +1 -1
  37. package/dist/assets/components/card/card.types.ts +1 -1
  38. package/dist/assets/components/card/card.vue +1 -1
  39. package/dist/assets/components/checkbox/checkbox.stories.ts +1 -1
  40. package/dist/assets/components/checkbox/checkbox.types.ts +1 -1
  41. package/dist/assets/components/checkbox/checkbox.vue +1 -1
  42. package/dist/assets/components/checkbox/components/checkbox-group/checkbox-group.docs.mdx +9 -14
  43. package/dist/assets/components/checkbox/components/checkbox-group/checkbox-group.stories.ts +1 -1
  44. package/dist/assets/components/checkbox/components/checkbox-group/checkbox-group.types.ts +1 -1
  45. package/dist/assets/components/checkbox/components/checkbox-group/checkbox-group.vue +1 -1
  46. package/dist/assets/components/chip/chip.stories.ts +1 -1
  47. package/dist/assets/components/chip/chip.vue +1 -1
  48. package/dist/assets/components/click-outside/click-outside.stories.ts +1 -1
  49. package/dist/assets/components/code-snippet/code-snippet.types.ts +1 -1
  50. package/dist/assets/components/code-snippet/code-snippet.vue +1 -1
  51. package/dist/assets/components/country/country.stories.ts +1 -1
  52. package/dist/assets/components/country/country.types.ts +1 -1
  53. package/dist/assets/components/country/country.vue +1 -1
  54. package/dist/assets/components/currency/currency.stories.ts +1 -1
  55. package/dist/assets/components/currency/currency.vue +1 -1
  56. package/dist/assets/components/dashed-underline/dashed-underline.stories.ts +1 -1
  57. package/dist/assets/components/data-grid/components/data-grid-cell-actions/data-grid-cell-actions.types.ts +1 -1
  58. package/dist/assets/components/data-grid/components/data-grid-cell-actions/data-grid-cell-actions.vue +1 -1
  59. package/dist/assets/components/data-grid/components/data-grid-config-settings/data-grid-config-settings.vue +1 -1
  60. package/dist/assets/components/data-grid/components/data-grid-row/data-grid-row.vue +1 -1
  61. package/dist/assets/components/data-grid/components/data-grid-static-icon/data-grid-static-icon.stories.ts +1 -1
  62. package/dist/assets/components/data-grid/components/data-grid-summary-cell/data-grid-summary-cell.stories.ts +1 -1
  63. package/dist/assets/components/data-grid/components/data-grid-summary-cell/data-grid-summary-cell.vue +1 -1
  64. package/dist/assets/components/data-grid/components/data-grid-tag-with-text-cell/data-grid-tag-with-text-cell.stories.ts +1 -1
  65. package/dist/assets/components/data-grid/components/data-grid-trend-cell/data-grid-trend-cell.stories.ts +1 -1
  66. package/dist/assets/components/data-grid/components/data-grid-trend-cell/data-grid-trend-cell.vue +1 -1
  67. package/dist/assets/components/data-grid/data-grid.docs.mdx +3 -60
  68. package/dist/assets/components/data-grid/data-grid.stories.ts +1 -1
  69. package/dist/assets/components/data-grid/data-grid.vue +1 -1
  70. package/dist/assets/components/date/date.stories.ts +1 -1
  71. package/dist/assets/components/date-picker/date-picker.stories.ts +1 -1
  72. package/dist/assets/components/date-picker/date-picker.types.ts +1 -1
  73. package/dist/assets/components/date-picker/date-picker.vue +1 -1
  74. package/dist/assets/components/date-range-picker/components/date-range-picker-calendar/date-range-picker-calendar.vue +1 -1
  75. package/dist/assets/components/date-range-picker/components/date-time-input/date-time-input.types.ts +1 -1
  76. package/dist/assets/components/date-range-picker/components/date-time-input/date-time-input.vue +1 -1
  77. package/dist/assets/components/date-range-picker/date-range-picker.docs.mdx +31 -6
  78. package/dist/assets/components/date-range-picker/date-range-picker.stories.ts +1 -1
  79. package/dist/assets/components/date-range-picker/date-range-picker.types.ts +1 -1
  80. package/dist/assets/components/date-range-picker/date-range-picker.vue +1 -1
  81. package/dist/assets/components/date-time-picker/date-time-picker.stories.ts +1 -1
  82. package/dist/assets/components/date-time-picker/date-time-picker.types.ts +1 -1
  83. package/dist/assets/components/divider/divider.internal.types.ts +1 -0
  84. package/dist/assets/components/divider/divider.stories.ts +1 -1
  85. package/dist/assets/components/divider/divider.types.ts +1 -1
  86. package/dist/assets/components/divider/divider.vue +1 -1
  87. package/dist/assets/components/draggable/components/draggable-handle/draggable-handle.stories.ts +1 -1
  88. package/dist/assets/components/draggable/draggable.stories.ts +1 -1
  89. package/dist/assets/components/draggable/draggable.vue +1 -1
  90. package/dist/assets/components/drawer/drawer.stories.ts +1 -1
  91. package/dist/assets/components/dropdown/components/dropdown-base-textbox/dropdown-base-textbox.internal.types.ts +1 -0
  92. package/dist/assets/components/dropdown/components/dropdown-base-textbox/dropdown-base-textbox.types.ts +1 -1
  93. package/dist/assets/components/dropdown/components/dropdown-base-textbox/dropdown-base-textbox.vue +1 -1
  94. package/dist/assets/components/dropdown/components/dropdown-default-textbox/dropdown-default-textbox.vue +1 -1
  95. package/dist/assets/components/dropdown/components/dropdown-options-container/dropdown-options-container.vue +1 -1
  96. package/dist/assets/components/dropdown/components/dropdown-small-textbox/dropdown-small-textbox.vue +1 -1
  97. package/dist/assets/components/dropdown/components/variants/dropdown-avatar/dropdown-avatar.stories.ts +1 -1
  98. package/dist/assets/components/dropdown/components/variants/dropdown-avatar/dropdown-avatar.types.ts +1 -1
  99. package/dist/assets/components/dropdown/components/variants/dropdown-avatar/dropdown-avatar.vue +1 -1
  100. package/dist/assets/components/dropdown/components/variants/dropdown-country/dropdown-country.stories.ts +1 -1
  101. package/dist/assets/components/dropdown/components/variants/dropdown-country/dropdown-country.types.ts +1 -1
  102. package/dist/assets/components/dropdown/components/variants/dropdown-country/dropdown-country.vue +1 -1
  103. package/dist/assets/components/dropdown/components/variants/dropdown-currency/dropdown-currency.stories.ts +1 -1
  104. package/dist/assets/components/dropdown/components/variants/dropdown-currency/dropdown-currency.types.ts +1 -1
  105. package/dist/assets/components/dropdown/components/variants/dropdown-currency/dropdown-currency.vue +1 -1
  106. package/dist/assets/components/dropdown/components/variants/dropdown-payment-method/dropdown-payment-method.stories.ts +1 -1
  107. package/dist/assets/components/dropdown/components/variants/dropdown-payment-method/dropdown-payment-method.types.ts +1 -1
  108. package/dist/assets/components/dropdown/components/variants/dropdown-payment-method/dropdown-payment-method.vue +1 -1
  109. package/dist/assets/components/dropdown/components/variants/dropdown-tag/dropdown-tag.stories.ts +1 -1
  110. package/dist/assets/components/dropdown/components/variants/dropdown-tag/dropdown-tag.types.ts +1 -1
  111. package/dist/assets/components/dropdown/components/variants/dropdown-tag/dropdown-tag.vue +1 -1
  112. package/dist/assets/components/dropdown/dropdown.docs.mdx +4 -40
  113. package/dist/assets/components/dropdown/dropdown.internal.types.ts +1 -0
  114. package/dist/assets/components/dropdown/dropdown.stories.ts +1 -1
  115. package/dist/assets/components/dropdown/dropdown.types.ts +1 -1
  116. package/dist/assets/components/dropdown/dropdown.vue +1 -1
  117. package/dist/assets/components/empty-state/empty-state.stories.ts +1 -1
  118. package/dist/assets/components/empty-state/empty-state.types.ts +1 -1
  119. package/dist/assets/components/file-uploader/components/file-uploader-file-card/file-uploader-file-card.stories.ts +1 -1
  120. package/dist/assets/components/file-uploader/components/file-uploader-file-card/file-uploader-file-card.types.ts +1 -1
  121. package/dist/assets/components/file-uploader/components/file-uploader-file-card/file-uploader-file-card.vue +1 -1
  122. package/dist/assets/components/file-uploader/components/file-uploader-restrictions/file-uploader-restrictions.types.ts +1 -1
  123. package/dist/assets/components/file-uploader/components/file-uploader-restrictions/file-uploader-restrictions.vue +1 -1
  124. package/dist/assets/components/file-uploader/file-uploader.internal.types.ts +1 -0
  125. package/dist/assets/components/file-uploader/file-uploader.stories.ts +1 -1
  126. package/dist/assets/components/file-uploader/file-uploader.types.ts +1 -1
  127. package/dist/assets/components/file-uploader/file-uploader.vue +1 -1
  128. package/dist/assets/components/filter-bar/components/all-filters-modal/all-filters-modal.stories.ts +1 -1
  129. package/dist/assets/components/filter-bar/components/all-filters-modal/all-filters-modal.types.ts +1 -1
  130. package/dist/assets/components/filter-bar/components/all-filters-modal/all-filters-modal.vue +1 -1
  131. package/dist/assets/components/filter-bar/components/all-filters-modal/components/all-filters-modal-filter-selection/all-filters-modal-filter-selection.types.ts +1 -0
  132. package/dist/assets/components/filter-bar/components/all-filters-modal/components/all-filters-modal-filter-selection/all-filters-modal-filter-selection.vue +1 -0
  133. package/dist/assets/components/filter-bar/components/all-filters-modal/components/all-filters-modal-selected-filter/all-filters-modal-selected-filter.vue +1 -0
  134. package/dist/assets/components/filter-bar/components/base-filter/base-filter.internal.types.ts +1 -0
  135. package/dist/assets/components/filter-bar/components/base-filter/base-filter.types.ts +1 -1
  136. package/dist/assets/components/filter-bar/components/base-filter/base-filter.vue +1 -1
  137. package/dist/assets/components/filter-bar/components/boolean-filter/boolean-filter.vue +1 -1
  138. package/dist/assets/components/filter-bar/components/date-filter/date-filter.vue +1 -1
  139. package/dist/assets/components/filter-bar/components/date-range-filter/date-range-filter.vue +1 -1
  140. package/dist/assets/components/filter-bar/components/input-filter/input-filter.types.ts +1 -1
  141. package/dist/assets/components/filter-bar/components/input-filter/input-filter.vue +1 -1
  142. package/dist/assets/components/filter-bar/components/input-with-dropdown-filter/input-with-dropdown-filter.stories.ts +1 -1
  143. package/dist/assets/components/filter-bar/components/input-with-dropdown-filter/input-with-dropdown-filter.vue +1 -1
  144. package/dist/assets/components/filter-bar/components/select-filter/components/select-filter-button/select-filter-button.vue +1 -1
  145. package/dist/assets/components/filter-bar/components/select-filter/select-filter.vue +1 -1
  146. package/dist/assets/components/filter-bar/components/select-filter/variants/select-avatar-filter/select-avatar-filter.stories.ts +1 -1
  147. package/dist/assets/components/filter-bar/components/select-filter/variants/select-country-filter/select-country-filter.stories.ts +1 -1
  148. package/dist/assets/components/filter-bar/components/select-filter/variants/select-country-filter/select-country-filter.vue +1 -1
  149. package/dist/assets/components/filter-bar/components/toggle-filter/toggle-filter.vue +1 -1
  150. package/dist/assets/components/filter-bar/filter-bar.docs.mdx +4 -62
  151. package/dist/assets/components/filter-bar/filter-bar.stories.ts +1 -1
  152. package/dist/assets/components/filter-bar/filter-bar.types.ts +1 -1
  153. package/dist/assets/components/filter-bar/filter-bar.vue +1 -1
  154. package/dist/assets/components/form-layout/form-layout.docs.mdx +0 -23
  155. package/dist/assets/components/form-layout/form-layout.stories.ts +1 -1
  156. package/dist/assets/components/header/header.stories.ts +1 -1
  157. package/dist/assets/components/header/header.types.ts +1 -1
  158. package/dist/assets/components/header/header.vue +1 -1
  159. package/dist/assets/components/header-with-views/components/header-with-views-all-views/header-with-views-all-views.vue +1 -1
  160. package/dist/assets/components/header-with-views/components/header-with-views-item-edit-form/header-with-views-edit-form.types.ts +1 -1
  161. package/dist/assets/components/header-with-views/components/header-with-views-item-edit-form/header-with-views-edit-form.vue +1 -1
  162. package/dist/assets/components/header-with-views/header-with-views.internal.types.ts +1 -0
  163. package/dist/assets/components/header-with-views/header-with-views.stories.ts +1 -1
  164. package/dist/assets/components/header-with-views/header-with-views.types.ts +1 -1
  165. package/dist/assets/components/header-with-views/header-with-views.vue +1 -1
  166. package/dist/assets/components/image/image.stories.ts +1 -1
  167. package/dist/assets/components/info-icon/info-icon.stories.ts +1 -1
  168. package/dist/assets/components/input-field/input-field.docs.mdx +1 -13
  169. package/dist/assets/components/input-field/input-field.stories.ts +1 -1
  170. package/dist/assets/components/input-field/input-field.types.ts +1 -1
  171. package/dist/assets/components/input-field/input-field.vue +1 -1
  172. package/dist/assets/components/input-field-password/input-field-password.internal.types.ts +1 -0
  173. package/dist/assets/components/input-field-password/input-field-password.stories.ts +1 -1
  174. package/dist/assets/components/input-field-password/input-field-password.types.ts +1 -1
  175. package/dist/assets/components/input-field-password/input-field-password.vue +1 -1
  176. package/dist/assets/components/input-field-phone-number/input-field-phone-number.stories.ts +1 -1
  177. package/dist/assets/components/input-field-phone-number/input-field-phone-number.types.ts +1 -1
  178. package/dist/assets/components/input-field-phone-number/input-field-phone-number.vue +1 -1
  179. package/dist/assets/components/input-time/input-time.docs.mdx +17 -0
  180. package/dist/assets/components/input-time/input-time.stories.ts +1 -1
  181. package/dist/assets/components/input-time/input-time.types.ts +1 -1
  182. package/dist/assets/components/input-time/input-time.vue +1 -1
  183. package/dist/assets/components/inspector/inspector.stories.ts +1 -1
  184. package/dist/assets/components/inspector/inspector.vue +1 -1
  185. package/dist/assets/components/layout/components/layout-row/layout-row.stories.ts +1 -1
  186. package/dist/assets/components/layout/components/layout-row/layout-row.vue +1 -1
  187. package/dist/assets/components/layout/layout.docs.mdx +10 -0
  188. package/dist/assets/components/layout/layout.internal.types.ts +1 -0
  189. package/dist/assets/components/layout/layout.stories.ts +1 -1
  190. package/dist/assets/components/layout/layout.types.ts +1 -1
  191. package/dist/assets/components/link/link.stories.ts +1 -1
  192. package/dist/assets/components/link/link.types.ts +1 -1
  193. package/dist/assets/components/link/link.vue +1 -1
  194. package/dist/assets/components/list/list.stories.ts +1 -1
  195. package/dist/assets/components/loading-indicator/loading-indicator.stories.ts +1 -1
  196. package/dist/assets/components/menu/components/menu-item/menu-item.types.ts +1 -1
  197. package/dist/assets/components/menu/components/menu-item/menu-item.vue +1 -1
  198. package/dist/assets/components/menu/components/menu-item-list/menu-item-list.vue +1 -1
  199. package/dist/assets/components/menu/components/sub-menu-item-list/sub-menu-item-list.vue +1 -1
  200. package/dist/assets/components/menu/menu.docs.mdx +9 -0
  201. package/dist/assets/components/menu/menu.stories.ts +1 -1
  202. package/dist/assets/components/menu/menu.types.ts +1 -1
  203. package/dist/assets/components/menu/menu.vue +1 -1
  204. package/dist/assets/components/modal/components/base-modal/base-modal.vue +1 -1
  205. package/dist/assets/components/modal/components/modal-page/modal-page.vue +1 -1
  206. package/dist/assets/components/modal/modal.internal.types.ts +1 -0
  207. package/dist/assets/components/modal/modal.stories.ts +1 -1
  208. package/dist/assets/components/modal/modal.types.ts +1 -1
  209. package/dist/assets/components/modal/modal.vue +1 -1
  210. package/dist/assets/components/modal-fullscreen/components/modal-fullscreen-page.vue +1 -1
  211. package/dist/assets/components/modal-fullscreen/modal-fullscreen.stories.ts +1 -1
  212. package/dist/assets/components/modal-fullscreen/modal-fullscreen.vue +1 -1
  213. package/dist/assets/components/navigation-menu/components/navigation-menu-group/navigation-menu-group.internal.types.ts +1 -0
  214. package/dist/assets/components/navigation-menu/components/navigation-menu-group/navigation-menu-group.types.ts +1 -1
  215. package/dist/assets/components/navigation-menu/components/navigation-menu-group/navigation-menu-group.vue +1 -1
  216. package/dist/assets/components/navigation-menu/components/navigation-menu-item/navigation-menu-item.vue +1 -1
  217. package/dist/assets/components/navigation-menu/navigation-menu.internal.types.ts +1 -0
  218. package/dist/assets/components/navigation-menu/navigation-menu.stories.ts +1 -1
  219. package/dist/assets/components/navigation-menu/navigation-menu.types.ts +1 -1
  220. package/dist/assets/components/navigation-menu/navigation-menu.vue +1 -1
  221. package/dist/assets/components/pagination/components/pagination-context/pagination-context.vue +1 -1
  222. package/dist/assets/components/pagination/components/pagination-results-per-page/pagination-results-per-page.vue +1 -1
  223. package/dist/assets/components/pagination/pagination.stories.ts +1 -1
  224. package/dist/assets/components/pagination/pagination.types.ts +1 -1
  225. package/dist/assets/components/pagination/pagination.vue +1 -1
  226. package/dist/assets/components/payment-method/payment-method.stories.ts +1 -1
  227. package/dist/assets/components/payment-method/payment-method.types.ts +1 -1
  228. package/dist/assets/components/popover/popover.docs.mdx +32 -9
  229. package/dist/assets/components/popover/popover.stories.ts +1 -1
  230. package/dist/assets/components/popover/popover.types.ts +1 -1
  231. package/dist/assets/components/popover/popover.vue +1 -1
  232. package/dist/assets/components/popper-container/popper-container.types.ts +1 -1
  233. package/dist/assets/components/popper-container/popper-container.vue +1 -1
  234. package/dist/assets/components/progress-bar/progress-bar.stories.ts +1 -1
  235. package/dist/assets/components/progress-bar/progress-bar.vue +1 -1
  236. package/dist/assets/components/promo-banner/promo-banner.stories.ts +1 -1
  237. package/dist/assets/components/promo-banner/promo-banner.types.ts +1 -1
  238. package/dist/assets/components/promo-banner/promo-banner.vue +1 -1
  239. package/dist/assets/components/radio-group/components/radio-button/radio-button.vue +1 -1
  240. package/dist/assets/components/radio-group/radio-group.docs.mdx +1 -6
  241. package/dist/assets/components/radio-group/radio-group.stories.ts +1 -1
  242. package/dist/assets/components/radio-group/radio-group.types.ts +1 -1
  243. package/dist/assets/components/radio-group/radio-group.vue +1 -1
  244. package/dist/assets/components/rich-text-editor/rich-text-editor.internal.types.ts +1 -0
  245. package/dist/assets/components/rich-text-editor/rich-text-editor.stories.ts +1 -1
  246. package/dist/assets/components/rich-text-editor/rich-text-editor.types.ts +1 -1
  247. package/dist/assets/components/rich-text-editor/rich-text-editor.vue +1 -1
  248. package/dist/assets/components/search-bar/search-bar.stories.ts +1 -1
  249. package/dist/assets/components/search-bar/search-bar.vue +1 -1
  250. package/dist/assets/components/secondary-nav/components/secondary-nav-item/secondary-nav-item.vue +1 -1
  251. package/dist/assets/components/secondary-nav/secondary-nav.stories.ts +1 -1
  252. package/dist/assets/components/secondary-nav/secondary-nav.vue +1 -1
  253. package/dist/assets/components/segmented-control/components/segmented-control-button/segmented-control-button.vue +1 -1
  254. package/dist/assets/components/segmented-control/segmented-control.stories.ts +1 -1
  255. package/dist/assets/components/segmented-control/segmented-control.types.ts +1 -1
  256. package/dist/assets/components/segmented-control/segmented-control.vue +1 -1
  257. package/dist/assets/components/selection-card/components/selection-card-group/selection-card-group.docs.mdx +12 -13
  258. package/dist/assets/components/selection-card/components/selection-card-group/selection-card-group.stories.ts +1 -1
  259. package/dist/assets/components/selection-card/components/selection-card-group/selection-card-group.vue +1 -1
  260. package/dist/assets/components/selection-card/selection-card.stories.ts +1 -1
  261. package/dist/assets/components/selection-card/selection-card.types.ts +1 -1
  262. package/dist/assets/components/selection-card/selection-card.vue +1 -1
  263. package/dist/assets/components/sidepanel/sidepanel.docs.mdx +1 -1
  264. package/dist/assets/components/sidepanel/sidepanel.stories.ts +1 -1
  265. package/dist/assets/components/sidepanel/sidepanel.vue +1 -1
  266. package/dist/assets/components/status/status.stories.ts +1 -1
  267. package/dist/assets/components/status/status.types.ts +1 -1
  268. package/dist/assets/components/stepper/components/step/step.vue +1 -1
  269. package/dist/assets/components/stepper/stepper.stories.ts +1 -1
  270. package/dist/assets/components/stepper/stepper.vue +1 -1
  271. package/dist/assets/components/structured-list/structured-list.stories.ts +1 -1
  272. package/dist/assets/components/structured-list/structured-list.vue +1 -1
  273. package/dist/assets/components/summary-grid/components/summary-grid-item/summary-grid-item.types.ts +1 -1
  274. package/dist/assets/components/summary-grid/summary-grid.stories.ts +1 -1
  275. package/dist/assets/components/summary-grid/summary-grid.types.ts +1 -1
  276. package/dist/assets/components/table-of-contents/components/table-of-contents-list/table-of-contents-list.vue +1 -1
  277. package/dist/assets/components/table-of-contents/table-of-contents.internal.types.ts +1 -0
  278. package/dist/assets/components/table-of-contents/table-of-contents.stories.ts +1 -1
  279. package/dist/assets/components/table-of-contents/table-of-contents.types.ts +1 -1
  280. package/dist/assets/components/table-of-contents/table-of-contents.vue +1 -1
  281. package/dist/assets/components/tabs/components/tab.internal.types.ts +1 -0
  282. package/dist/assets/components/tabs/components/tab.types.ts +1 -1
  283. package/dist/assets/components/tabs/components/tab.vue +1 -1
  284. package/dist/assets/components/tabs/tabs.stories.ts +1 -1
  285. package/dist/assets/components/tabs/tabs.vue +1 -1
  286. package/dist/assets/components/tag/tag.docs.mdx +1 -1
  287. package/dist/assets/components/tag/tag.stories.ts +1 -1
  288. package/dist/assets/components/tag/tag.types.ts +1 -1
  289. package/dist/assets/components/tag/tag.vue +1 -1
  290. package/dist/assets/components/textarea/textarea.docs.mdx +0 -7
  291. package/dist/assets/components/textarea/textarea.stories.ts +1 -1
  292. package/dist/assets/components/textarea/textarea.types.ts +1 -1
  293. package/dist/assets/components/textarea/textarea.vue +1 -1
  294. package/dist/assets/components/timeline/components/timeline-item/timeline-item.stories.ts +1 -1
  295. package/dist/assets/components/timeline/components/timeline-item/timeline-item.vue +1 -1
  296. package/dist/assets/components/timeline/timeline.docs.mdx +0 -10
  297. package/dist/assets/components/timeline/timeline.internal.types.ts +1 -0
  298. package/dist/assets/components/timeline/timeline.stories.ts +1 -1
  299. package/dist/assets/components/timeline/timeline.types.ts +1 -1
  300. package/dist/assets/components/timeline/timeline.vue +1 -1
  301. package/dist/assets/components/toast/components/toast-item/toast-item.vue +1 -1
  302. package/dist/assets/components/toggle/toggle.stories.ts +1 -1
  303. package/dist/assets/components/toggle/toggle.types.ts +1 -1
  304. package/dist/assets/components/toggle/toggle.vue +1 -1
  305. package/dist/assets/components/tutorial/tutorial.stories.ts +1 -1
  306. package/dist/assets/components/typography/typography.stories.ts +1 -1
  307. package/dist/assets/components/typography/typography.types.ts +1 -1
  308. package/dist/assets/components/typography/typography.vue +1 -1
  309. package/dist/assets/composables/use-bento-base-filter/index.ts +1 -0
  310. package/dist/assets/composables/use-bento-base-filter/use-bento-base-filter.ts +1 -0
  311. package/dist/assets/composables/use-bento-base-filter/utils/index.ts +1 -0
  312. package/dist/assets/composables/use-bento-base-filter/utils/utils.ts +1 -0
  313. package/dist/assets/composables/use-bento-currency/index.ts +1 -0
  314. package/dist/assets/composables/use-bento-currency/use-bento-currency.stories.ts +1 -1
  315. package/dist/assets/composables/use-bento-currency/use-bento-currency.ts +1 -0
  316. package/dist/assets/composables/use-bento-delayed-hover/index.ts +1 -0
  317. package/dist/assets/composables/use-bento-delayed-hover/use-bento-delayed-hover.stories.ts +1 -1
  318. package/dist/assets/composables/use-bento-delayed-hover/use-bento-delayed-hover.ts +1 -0
  319. package/dist/assets/composables/use-bento-theme/index.ts +1 -0
  320. package/dist/assets/composables/use-bento-theme/install.ts +1 -1
  321. package/dist/assets/composables/use-bento-theme/install.types.ts +1 -1
  322. package/dist/assets/composables/use-bento-theme/use-bento-theme.ts +1 -0
  323. package/dist/assets/composables/use-bento-toast-controller/index.ts +1 -0
  324. package/dist/assets/composables/use-bento-toast-controller/toast-stack.ts +1 -0
  325. package/dist/assets/composables/use-bento-toast-controller/use-bento-toast-controller.ts +1 -0
  326. package/dist/assets/composables/use-click-outside/index.ts +1 -0
  327. package/dist/assets/composables/use-click-outside/use-click-outside.stories.ts +1 -1
  328. package/dist/assets/composables/use-click-outside/use-click-outside.ts +1 -0
  329. package/dist/assets/composables/use-form-validate/index.ts +1 -0
  330. package/dist/assets/composables/use-form-validate/use-form.ts +1 -0
  331. package/dist/assets/composables/use-form-validate/use-form.types.ts +1 -1
  332. package/dist/assets/composables.json +9 -0
  333. package/dist/assets/deprecations.md +132 -236
  334. package/dist/assets/directives/highlight/highlight.stories.ts +1 -1
  335. package/dist/assets/directives/keyboard-navigation/keyboard-navigation.stories.ts +1 -1
  336. package/dist/assets/directives/tooltip/tooltip.internal.types.ts +1 -0
  337. package/dist/assets/directives/tooltip/tooltip.stories.ts +1 -1
  338. package/dist/assets/directives/tooltip/tooltip.types.ts +1 -1
  339. package/dist/assets/icons.json +1 -0
  340. package/dist/assets/install.ts +1 -1
  341. package/dist/assets/tailwind/README.md +3 -3
  342. package/dist/assets/templates/dashboard-template/components/dashboard-section/dashboard-section.stories.ts +1 -1
  343. package/dist/assets/templates/dashboard-template/dashboard-template.stories.ts +1 -1
  344. package/dist/assets/templates/data-grid-template/data-grid-template.stories.ts +1 -1
  345. package/dist/assets/usage.json +77 -77
  346. package/dist/assets/variables.css +113 -101
  347. package/dist/main.js +228 -34
  348. package/package.json +2 -2
  349. package/dist/assets/components/internal/base-button/base-button.types.ts +0 -1
  350. package/dist/assets/components/internal/base-button/base-button.vue +0 -1
  351. package/dist/assets/components/internal/button-actions-with-menu/button-actions-with-menu.stories.ts +0 -1
  352. package/dist/assets/components/internal/button-actions-with-menu/button-actions-with-menu.types.ts +0 -1
  353. package/dist/assets/components/internal/button-actions-with-menu/button-actions-with-menu.vue +0 -1
  354. package/dist/assets/components/internal/calendar/calendar.stories.ts +0 -1
  355. package/dist/assets/components/internal/calendar/calendar.types.ts +0 -1
  356. package/dist/assets/components/internal/calendar/calendar.vue +0 -1
  357. package/dist/assets/components/internal/calendar/components/calendar-month/calendar-month.stories.ts +0 -1
  358. package/dist/assets/components/internal/calendar/components/calendar-month/calendar-month.types.ts +0 -1
  359. package/dist/assets/components/internal/calendar/components/calendar-month/calendar-month.vue +0 -1
  360. package/dist/assets/components/internal/calendar/components/calendar-month/components/calendar-month-day/__test__/calendar-month-day-stub.vue +0 -1
  361. package/dist/assets/components/internal/calendar/components/calendar-month/components/calendar-month-day/calendar-month-day.stories.ts +0 -1
  362. package/dist/assets/components/internal/calendar/components/calendar-month/components/calendar-month-day/calendar-month-day.types.ts +0 -1
  363. package/dist/assets/components/internal/calendar/components/calendar-month/components/calendar-month-day/calendar-month-day.vue +0 -1
  364. package/dist/assets/components/internal/calendar/components/calendar-month/composables/granular-min-max-date.types.ts +0 -1
  365. package/dist/assets/components/internal/calendar/components/calendar-year/calendar-year.types.ts +0 -1
  366. package/dist/assets/components/internal/calendar/components/calendar-year/calendar-year.vue +0 -1
  367. package/dist/assets/components/internal/calendar/components/calendar-year/components/calendar-year-month/__test__/calendar-year-month-stub.vue +0 -1
  368. package/dist/assets/components/internal/calendar/components/calendar-year/components/calendar-year-month/calendar-year-month.types.ts +0 -1
  369. package/dist/assets/components/internal/calendar/components/calendar-year/components/calendar-year-month/calendar-year-month.vue +0 -1
  370. package/dist/assets/components/internal/calendar/components/calendar-year-selector/calendar-year-selector.types.ts +0 -1
  371. package/dist/assets/components/internal/calendar/components/calendar-year-selector/calendar-year-selector.vue +0 -1
  372. package/dist/assets/components/internal/calendar/composables/use-granularity-adjustments.types.ts +0 -1
  373. package/dist/assets/components/internal/checkbox-input/checkbox-input.stories.ts +0 -1
  374. package/dist/assets/components/internal/checkbox-input/checkbox-input.types.ts +0 -1
  375. package/dist/assets/components/internal/checkbox-input/checkbox-input.vue +0 -1
  376. package/dist/assets/components/internal/controls-group/controls-group.vue +0 -1
  377. package/dist/assets/components/internal/copy/copy.docs.mdx +0 -43
  378. package/dist/assets/components/internal/copy/copy.stories.ts +0 -1
  379. package/dist/assets/components/internal/copy/copy.types.ts +0 -1
  380. package/dist/assets/components/internal/copy/copy.vue +0 -1
  381. package/dist/assets/components/internal/decorative-checkbox/decorative-checkbox.vue +0 -1
  382. package/dist/assets/components/internal/dialog-page/dialog-page.types.ts +0 -1
  383. package/dist/assets/components/internal/dialog-page/dialog-page.vue +0 -1
  384. package/dist/assets/components/internal/error-message/error-message.vue +0 -1
  385. package/dist/assets/components/internal/field-label/field-label.vue +0 -1
  386. package/dist/assets/components/internal/fixed-scroller/fixed-scroller.docs.mdx +0 -21
  387. package/dist/assets/components/internal/fixed-scroller/fixed-scroller.stories.ts +0 -1
  388. package/dist/assets/components/internal/fixed-scroller/fixed-scroller.vue +0 -1
  389. package/dist/assets/components/internal/footer-actions/footer-actions.vue +0 -1
  390. package/dist/assets/components/internal/grid-layout/grid-layout.types.ts +0 -1
  391. package/dist/assets/components/internal/grid-layout/grid-layout.vue +0 -1
  392. package/dist/assets/components/internal/header-meta/header-meta.types.ts +0 -1
  393. package/dist/assets/components/internal/header-meta/header-meta.vue +0 -1
  394. package/dist/assets/components/internal/info-icon-with-popover/info-icon-with-popover.vue +0 -1
  395. package/dist/assets/components/internal/listbox/components/listbox-lazy-load/listbox-lazy-load.types.ts +0 -1
  396. package/dist/assets/components/internal/listbox/components/listbox-lazy-load/listbox-lazy-load.vue +0 -1
  397. package/dist/assets/components/internal/listbox/components/listbox-multi-select/components/listbox-multi-select-options/components/listbox-multi-select-category/listbox-multi-select-category.types.ts +0 -1
  398. package/dist/assets/components/internal/listbox/components/listbox-multi-select/components/listbox-multi-select-options/components/listbox-multi-select-category/listbox-multi-select-category.vue +0 -1
  399. package/dist/assets/components/internal/listbox/components/listbox-multi-select/components/listbox-multi-select-options/components/listbox-multi-select-nested-lazy-load/listbox-multi-select-nested-lazy-load.types.ts +0 -1
  400. package/dist/assets/components/internal/listbox/components/listbox-multi-select/components/listbox-multi-select-options/components/listbox-multi-select-nested-lazy-load/listbox-multi-select-nested-lazy-load.vue +0 -1
  401. package/dist/assets/components/internal/listbox/components/listbox-multi-select/components/listbox-multi-select-options/listbox-multi-select-options.types.ts +0 -1
  402. package/dist/assets/components/internal/listbox/components/listbox-multi-select/components/listbox-multi-select-options/listbox-multi-select-options.vue +0 -1
  403. package/dist/assets/components/internal/listbox/components/listbox-multi-select/listbox-multi-select.types.ts +0 -1
  404. package/dist/assets/components/internal/listbox/components/listbox-multi-select/listbox-multi-select.vue +0 -1
  405. package/dist/assets/components/internal/listbox/components/listbox-option/listbox-option.vue +0 -1
  406. package/dist/assets/components/internal/listbox/components/listbox-option-tag/listbox-option-tag.vue +0 -1
  407. package/dist/assets/components/internal/listbox/components/listbox-single-select/components/listbox-single-select-category/listbox-single-select-category.vue +0 -1
  408. package/dist/assets/components/internal/listbox/components/listbox-single-select/listbox-single-select.vue +0 -1
  409. package/dist/assets/components/internal/listbox/components/listbox-single-select-option/listbox-single-select-option.vue +0 -1
  410. package/dist/assets/components/internal/listbox/listbox.stories.ts +0 -1
  411. package/dist/assets/components/internal/listbox/listbox.types.ts +0 -1
  412. package/dist/assets/components/internal/listbox/listbox.vue +0 -1
  413. package/dist/assets/components/internal/radio-input/radio-input.stories.ts +0 -1
  414. package/dist/assets/components/internal/radio-input/radio-input.types.ts +0 -1
  415. package/dist/assets/components/internal/radio-input/radio-input.vue +0 -1
  416. package/dist/assets/components/internal/teleport/teleport.types.ts +0 -1
  417. package/dist/assets/components/internal/teleport/teleport.vue +0 -1
  418. package/dist/assets/components/internal/tooltip/tooltip.test.vue +0 -1
  419. package/dist/assets/components/internal/tooltip/tooltip.types.ts +0 -1
  420. package/dist/assets/components/internal/tooltip/tooltip.vue +0 -1
  421. package/dist/assets/composables/use-form-validate-zod-4/use-form.types.ts +0 -1
  422. package/dist/assets/composables/use-numeric-input/use-numeric-input.types.ts +0 -1
  423. package/dist/assets/composables/use-section-navigation-keyboard/use-section-navigation-keyboard.types.ts +0 -1
  424. package/dist/assets/composables/use-virtual-list/use-virtual-list.types.ts +0 -1
@@ -1 +1 @@
1
- <template> <filter-bar-button-group v-if="!onlySlot" v-bento-tooltip-directive:[options?.tooltipPosition]="options?.tooltipText" :skip-grouping="!internalValue" > <filter-bar-button :aria-pressed="internalValue ? 'true' : 'false'" :active="internalValue" :disabled="disabled" @click="toggleFilter" > <div class="b-toggle-filter"> {{ label }} <checkmark-icon v-show="internalValue" aria-hidden="true" class="b-toggle-filter__checkmark" /> </div> </filter-bar-button> <filter-bar-button v-if="internalValue" :aria-label="t('resetFilter', { filterName: label })" :disabled="disabled" :active="internalValue" @click="resetFilter" > <template #iconLeft> <cross-small-icon aria-hidden="true" /> </template> </filter-bar-button> </filter-bar-button-group> <label v-else class="b-toggle-filter__only-slot" :class="onlySlotConditionalClasses"> <checkbox-input :aria-labelledby="checkboxLabelId" :aria-describedby="checkboxDescriptionId" :disabled="disabled" class="b-toggle-filter__checkbox" :model-value="internalValue" @update:model-value="onInput" ></checkbox-input> <div class="b-toggle-filter__content"> <bento-typography :id="checkboxLabelId" variant="title" medium>{{ label }}</bento-typography> <bento-typography v-if="description" :id="checkboxDescriptionId">{{ description }}</bento-typography> </div> </label> </template> <script setup lang="ts"> import { computed, ref, watch } from 'vue'; import { BentoTooltipDirective as vBentoTooltipDirective } from '@/directives/tooltip/tooltip'; import { FilterBarButtonGroup } from '../filter-bar-button-group'; import { FilterBarButton } from '../filter-bar-button'; import { type BentoToggleFilterProps, type BentoToggleFilterValue } from './toggle-filter.types'; import { CheckboxInput } from '@/internal/checkbox-input'; import { BentoTypography } from '@/components/typography'; import { generateUid } from '@/core/utils/ts'; import { useI18n } from '@/utils/ts/i18n'; import CheckmarkIcon from '@adyen/ui-assets-icons-16/vue/checkmark'; import CrossSmallIcon from '@adyen/ui-assets-icons-16/vue/cross-small'; import messages from './messages.json'; type MessageSchema = (typeof messages)['en-US']; const { t } = useI18n<{ message: MessageSchema }>({ messages }); /** * IDs used for the checkbox a11y */ const checkboxLabelId = generateUid('toggle-filter-checkbox-label'); const checkboxDescriptionId = generateUid('toggle-filter-checkbox-description'); const props = withDefaults(defineProps<BentoToggleFilterProps>(), { disabled: false, onlySlot: false, options: undefined, value: false, }); const emit = defineEmits<{ /** * Used in the All filters modal */ (e: 'input', updatedProps: BentoToggleFilterProps): void; /** * Used in the Filter Bar */ (e: 'update', updatedProps: BentoToggleFilterProps): void; }>(); /** * Toggle internal field in sync with props.value */ const internalValue = ref<BentoToggleFilterValue>(props.value); watch( () => props.value, value => { internalValue.value = value; } ); const onlySlotConditionalClasses = computed(() => ({ 'b-toggle-filter__only-slot--disabled': props.disabled, })); /** * Used only for the checkbox. * Input emitted for the `all filters` modal * @param value new checkbox value */ const onInput = (value: BentoToggleFilterValue) => { internalValue.value = value; emit('input', { ...props, value }); }; /** * Applies the filter. * Update emitted for the filter bar. Updates all props */ const updateProps = () => { emit('update', { ...props, value: internalValue.value }); }; /** * Toggles filter with the main part (left side button) */ const toggleFilter = () => { internalValue.value = !internalValue.value; updateProps(); }; /** * Resets filter with the main part (left side button). * Has no persistent filter functionality. */ const resetFilter = () => { internalValue.value = false; updateProps(); }; </script> <script lang="ts"> /** * Toggle filter. * * @example * import { BentoToggleFilter } from './toggle-filter'; * * export default { * components: { BentoToggleFilter }, * template: ` * <bento-toggle-filter * label="Toggle filter" * field="toggle-filter" * only-slot * disabled * :value="true" * :tooltip-text="Tooltip text" * tooltip-position="top" * /> * ` * } */ export default { i18n: { messages }, name: 'bento-toggle-filter', }; </script> <style lang="scss" scoped src="./toggle-filter.scss" />
1
+ <template> <filter-bar-button-group v-if="!onlySlot" v-bento-tooltip-directive:[options?.tooltipPosition]="options?.tooltipText" :skip-grouping="!internalValue" > <filter-bar-button :aria-pressed="internalValue ? 'true' : 'false'" :active="internalValue" :disabled="disabled" @click="toggleFilter" > <div class="b-toggle-filter"> {{ label }} <checkmark-icon v-show="internalValue" aria-hidden="true" class="b-toggle-filter__checkmark" /> </div> </filter-bar-button> <filter-bar-button v-if="internalValue" :aria-label="t('resetFilter', { filterName: label })" :disabled="disabled" :active="internalValue" @click="resetFilter" > <template #iconLeft> <cross-small-icon aria-hidden="true" /> </template> </filter-bar-button> </filter-bar-button-group> <label v-else class="b-toggle-filter__only-slot" :class="onlySlotConditionalClasses"> <checkbox-input :aria-labelledby="checkboxLabelId" :aria-describedby="checkboxDescriptionId" :disabled="disabled" class="b-toggle-filter__checkbox" :model-value="internalValue" @update:model-value="onInput" ></checkbox-input> <div class="b-toggle-filter__content"> <bento-typography :id="checkboxLabelId" variant="title" medium>{{ label }}</bento-typography> <bento-typography v-if="description" :id="checkboxDescriptionId">{{ description }}</bento-typography> </div> </label> </template> <script setup lang="ts"> import { computed, ref, watch } from 'vue'; import { BentoTooltipDirective as vBentoTooltipDirective } from '@/directives/tooltip/tooltip'; import { FilterBarButtonGroup } from '../filter-bar-button-group'; import { FilterBarButton } from '../filter-bar-button'; import { type BentoToggleFilterProps, type BentoToggleFilterValue } from './toggle-filter.types'; import { CheckboxInput } from '@/internal/checkbox-input'; import { BentoTypography } from '@/components/typography'; import { generateUid } from '@/utils/ts/generate-uid'; import { useI18n } from '@/utils/ts/i18n'; import CheckmarkIcon from '@adyen/ui-assets-icons-16/vue/checkmark'; import CrossSmallIcon from '@adyen/ui-assets-icons-16/vue/cross-small'; import messages from './messages.json'; type MessageSchema = (typeof messages)['en-US']; const { t } = useI18n<{ message: MessageSchema }>({ messages }); /** * IDs used for the checkbox a11y */ const checkboxLabelId = generateUid('toggle-filter-checkbox-label'); const checkboxDescriptionId = generateUid('toggle-filter-checkbox-description'); const props = withDefaults(defineProps<BentoToggleFilterProps>(), { disabled: false, onlySlot: false, options: undefined, value: false, }); const emit = defineEmits<{ /** * Used in the All filters modal */ (e: 'input', updatedProps: BentoToggleFilterProps): void; /** * Used in the Filter Bar */ (e: 'update', updatedProps: BentoToggleFilterProps): void; }>(); /** * Toggle internal field in sync with props.value */ const internalValue = ref<BentoToggleFilterValue>(props.value); watch( () => props.value, value => { internalValue.value = value; } ); const onlySlotConditionalClasses = computed(() => ({ 'b-toggle-filter__only-slot--disabled': props.disabled, })); /** * Used only for the checkbox. * Input emitted for the `all filters` modal * @param value new checkbox value */ const onInput = (value: BentoToggleFilterValue) => { internalValue.value = value; emit('input', { ...props, value }); }; /** * Applies the filter. * Update emitted for the filter bar. Updates all props */ const updateProps = () => { emit('update', { ...props, value: internalValue.value }); }; /** * Toggles filter with the main part (left side button) */ const toggleFilter = () => { internalValue.value = !internalValue.value; updateProps(); }; /** * Resets filter with the main part (left side button). * Has no persistent filter functionality. */ const resetFilter = () => { internalValue.value = false; updateProps(); }; </script> <script lang="ts"> /** * Toggle filter. * * @example * import { BentoToggleFilter } from './toggle-filter'; * * export default { * components: { BentoToggleFilter }, * template: ` * <bento-toggle-filter * label="Toggle filter" * field="toggle-filter" * only-slot * disabled * :value="true" * :tooltip-text="Tooltip text" * tooltip-position="top" * /> * ` * } */ export default { i18n: { messages }, name: 'bento-toggle-filter', }; </script> <style lang="scss" scoped src="./toggle-filter.scss" />
@@ -13,68 +13,6 @@ import FilterBarConfigFilterValuesObjectExample from './__tests__/filter-bar-con
13
13
 
14
14
  Filter bar allows users to filter the content so that only the information they're interested in is displayed.
15
15
 
16
- ## Table of contents
17
-
18
- - [Filter Bar](#filter-bar)
19
- - [Table of contents](#table-of-contents)
20
- - [Behavior](#behavior)
21
- - [Search bar](#search-bar)
22
- - [Filter configuration and Filter values](#filter-configuration-and-filter-values)
23
- - [Applying a filter](#applying-a-filter)
24
- - [Clearing a filter](#clearing-a-filter)
25
- - [Clear all](#clear-all)
26
- - [Persistent filters](#persistent-filters)
27
- - [Resetting a filter](#resetting-a-filter)
28
- - [Reset all](#reset-all)
29
- - [Use Cases](#use-cases)
30
- - [Do not use](#do-not-use)
31
- - [Variations](#variations)
32
- - [Available filter configurations](#available-filter-configurations)
33
- - [Input filter](#input-filter)
34
- - [Options](#options)
35
- - [Configuration Interface](#configuration-interface)
36
- - [Input with dropdown filter](#input-with-dropdown-filter)
37
- - [Options](#options-1)
38
- - [Configuration Interface](#configuration-interface-1)
39
- - [Value Interface](#value-interface)
40
- - [Range filter](#range-filter)
41
- - [Configuration Interface](#configuration-interface-2)
42
- - [Value Interface](#value-interface-1)
43
- - [~~Boolean filter~~ (`DEPRECATED`)](#boolean-filter-deprecated)
44
- - [Configuration Interface](#configuration-interface-3)
45
- - [Filter value Interface](#filter-value-interface)
46
- - [Custom boolean options](#custom-boolean-options)
47
- - [Select filter](#select-filter)
48
- - [Options](#options-2)
49
- - [Configuration Interface](#configuration-interface-4)
50
- - [Value Interface](#value-interface-2)
51
- - [External filtering](#external-filtering)
52
- - [External filtering and Lazy Loading simultaneously](#external-filtering-and-lazy-loading-simultaneously)
53
- - [Nested multi-select filter](#nested-multi-select-filter)
54
- - [Category Selection & Select All Behavior](#category-selection--select-all-behavior)
55
- - [Date filter](#date-filter)
56
- - [Options](#options-3)
57
- - [Configuration Interface](#configuration-interface-5)
58
- - [Value Interface](#value-interface-3)
59
- - [Date range filter](#date-range-filter)
60
- - [Options](#options-4)
61
- - [Configuration Interface](#configuration-interface-6)
62
- - [Value Interface](#value-interface-4)
63
- - [Radio group filter](#radio-group-filter)
64
- - [Configuration Interface](#configuration-interface-7)
65
- - [Checkbox group filter](#checkbox-group-filter)
66
- - [Options](#options-5)
67
- - [Configuration Interface](#configuration-interface-7)
68
- - [Value Interface](#value-interface-5)
69
- - [Custom filters](#custom-filters)
70
- - [Creating a custom filter](#creating-a-custom-filter)
71
- - [Custom filter component](#custom-filter-component)
72
- - [Connecting internal values and events](#connecting-internal-values-and-events)
73
- - [Configure fields in `bento-filter-bar`](#configure-fields-in-bento-filter-bar)
74
- - [Responsiveness](#responsiveness)
75
- - [Accessibility 🚧](#accessibility-)
76
- - [Roles, States, and Properties](#roles-states-and-properties)
77
-
78
16
  ## Behavior
79
17
 
80
18
  By default filter bar displays the most common filters for that particular page/component.
@@ -711,6 +649,9 @@ Example of selected categories with Lazy load:
711
649
  array of predefined date range options for the user to select. Please see more info
712
650
  [here](../?path=/docs/components-date-picker-range-dates--docs#quick-select-ranges).
713
651
  - `showEndDateOnOpen: boolean`: Toggles whether the picker will open on the end date's month
652
+ - `timeInterval: number`: Limits the selectable times to a fixed interval, in minutes, turning the time inputs into
653
+ searchable dropdowns. Requires `allowTimeInput`. Please see more info
654
+ [here](../?path=/docs/components-date-picker-range-dates--docs#predefined-times).
714
655
 
715
656
  #### Configuration Interface
716
657
 
@@ -728,6 +669,7 @@ Example of selected categories with Lazy load:
728
669
  numberOfMonths?: number;
729
670
  quickSelectRanges: { label: string, value: string, data: { startDate: Date, endDate?: Date, isRelativeToNow?: boolean } }[];
730
671
  showEndDateOnOpen?: boolean;
672
+ timeInterval?: number;
731
673
  }; // i.e. BentoDateRangeFilterOptions
732
674
  }
733
675
  ```
@@ -1 +1 @@
1
- import { computed, onMounted, ref } from 'vue'; import { action } from '@storybook/addon-actions'; import { type BentoDateRangeFilterOptions, type BentoFilterBarContainerSizeLayout, type BentoFilterBarModel, type BentoFilterBarSearchBarProps, type BentoFilterBarValue, BentoFilterItemType, type BentoFilterValueObject, type BentoFilterValues, } from './filter-bar.types'; import { getPokemonFromApi, storybookDocsParameter, withToastDecorator } from '@/utils/ts/storybook'; import { type BentoListboxOptions } from '@/types/listbox'; import { BentoCountryCode } from '@/components/country/country.types'; import { BentoCurrencyISOCode } from '@/components/currency/currency.types'; import { isVue2 } from 'vue-demi'; import { type Meta, type StoryObj } from '@storybook/vue'; import { BentoToggle } from '@/components/toggle'; import BentoFilterBar from './filter-bar.vue'; import { endOfWeek } from 'date-fns/endOfWeek'; import { startOfWeek } from 'date-fns/startOfWeek'; import { subDays } from 'date-fns/subDays'; import { subWeeks } from 'date-fns/subWeeks'; import { subMonths } from 'date-fns/subMonths'; import { startOfMonth } from 'date-fns/startOfMonth'; import { endOfDay, startOfDay, subHours, subMinutes } from 'date-fns'; // Examples import FilterBarCustomFilterSubComponentSingleValueExample from './__tests__/filter-bar-custom-filter-sub-component-single-value-example.vue'; import FilterBarCustomFilterSubComponentMultiValueExample from './__tests__/filter-bar-custom-filter-sub-component-multiple-value-example.vue'; import FilterBarCustomFilterSubComponentWithPopoverExample from './__tests__/filter-bar-custom-filter-sub-component-with-popover-example.vue'; import FilterBarCustomFiltersExample from './__tests__/filter-bar-custom-filters-example.vue?raw'; import FilterBarDefaultExample from './__tests__/filter-bar-default-example.vue?raw'; // Generate array from 1 to # // { label: 'Option #', value: # }, const DEFAULT_OPTIONS: BentoListboxOptions = new Array(10) .fill(0) .map((_, index) => ({ label: `Option ${index + 1}`, value: index + 1 })); // To be used by as the current time across all date filters const CURRENT_DATE_AND_TIME = new Date(); const meta: Meta = { title: 'Filter bar', component: BentoFilterBar, decorators: [withToastDecorator], argTypes: { // Props searchTerm: { name: 'searchTerm', defaultValue: undefined, description: "Search term useful to filter all the data by text. If the value is non-existant (`null` or `undefined`) the search bar won't be enabled.", control: { type: 'text', }, }, // Events input: { name: 'input', defaultValue: '', description: 'Triggered whenever the filters are updated.', table: { type: { summary: '([{ field, label, value, type }]) => void' } }, control: { disable: true }, }, // Search Events 'search-input': { name: 'search-input', defaultValue: '', description: "Triggered whenever the filter's search is updated.", table: { type: { summary: '(newSearchTerm) => void' } }, control: { disable: true }, }, 'search-clear': { name: 'search-clear', defaultValue: '', description: "Triggered whenever the filter's search is cleared.", table: { type: { summary: '() => void' } }, control: { disable: true }, }, 'update:search-term': { name: 'update:search-term', defaultValue: '', description: "Triggered whenever the filter's search is updated to sync the model.", table: { type: { summary: '(newSearchTerm) => void' } }, control: { disable: true }, }, }, }; export default meta; type Story = StoryObj<typeof BentoFilterBar>; const FILTER_VALUES: BentoFilterValues = [ { field: 'input', value: undefined, }, { field: 'boolean', value: 'yes', }, { field: 'input-with-dropdown', value: undefined, }, { field: 'hidden-input', value: 'Active value of a hidden filter', }, { field: 'merchant', value: 2, }, { field: 'company', value: [1, 3, 5], }, { field: 'nested-select', value: ['amsterdam', 'den-haag'], }, { field: 'currency', value: [], }, { field: 'range', value: ['', ''], }, { field: 'date', value: CURRENT_DATE_AND_TIME, }, { field: 'date-range', value: { startDate: subDays(startOfDay(CURRENT_DATE_AND_TIME), 2), endDate: endOfDay(CURRENT_DATE_AND_TIME) }, }, { field: 'granular-date-range', value: { startDate: subDays(CURRENT_DATE_AND_TIME, 2), endDate: CURRENT_DATE_AND_TIME }, }, { field: 'date-range-month', value: { startDate: startOfMonth(subMonths(CURRENT_DATE_AND_TIME, 2)), endDate: startOfMonth(CURRENT_DATE_AND_TIME), }, }, { field: 'paymentMethod', value: undefined, }, { field: 'checkbox-group', value: [], }, { field: 'toggle', value: true, }, ]; const FILTERS: BentoFilterBarModel = [ { field: 'input', label: 'Input filter', type: BentoFilterItemType.INPUT, options: { placeholder: 'Input filter placeholder', tooltipPosition: 'bottom', tooltipText: 'Input filter tooltip text', }, visible: true, }, { field: 'input-with-dropdown', label: 'Input with dropdown filter', type: BentoFilterItemType.INPUT_DROPDOWN, visible: true, options: { dropdown: { items: [ { label: 'EUR', value: 'EUR' }, { label: 'USD', value: 'USD' }, ], modelValue: 'EUR', dynamicFiltering: true, }, }, }, { field: 'hidden-input', label: 'Hidden input filter', description: 'This is a hidden input filter only visible through the modal', type: BentoFilterItemType.INPUT, }, { field: 'country', label: 'Country/Region', options: { listboxItems: [ { label: 'Option 1', value: BentoCountryCode.NL, }, { label: 'Option 2', value: BentoCountryCode.BE, }, ], multiple: true, variant: 'capital', }, type: BentoFilterItemType.SELECT_COUNTRY, }, { field: 'avatar', label: 'Avatar', options: { listboxItems: [ { label: 'Avatar 1', value: 'avatar-1', }, { label: 'Avatar 2', value: 'avatar-2', }, ], }, type: BentoFilterItemType.SELECT_AVATAR, }, { field: 'merchant', label: 'Merchant', options: { listboxItems: DEFAULT_OPTIONS, enableValueLabelPair: true, isOptionDisabled: option => option.value === 3, }, type: BentoFilterItemType.SELECT, visible: true, }, { field: 'company', label: 'Company', options: { listboxItems: DEFAULT_OPTIONS, enableValueLabelPair: true, isOptionDisabled: option => option.value === 5, multiple: true, }, type: BentoFilterItemType.SELECT, visible: true, }, { field: 'nested-select', label: 'Nested select', options: { listboxItems: [ { label: 'Belgium', description: 'Country/Region', value: 'belgium', }, { label: 'Netherlands', description: 'Country/Region', value: 'netherlands', items: [ { label: 'Amsterdam', description: 'City', value: 'amsterdam', }, { label: 'Den Haag', description: 'City', value: 'den-haag', }, ], }, ], multiple: true, }, type: BentoFilterItemType.SELECT, visible: true, }, { field: 'currency', label: 'Currency select', options: { listboxItems: [ { label: 'Euro', value: BentoCurrencyISOCode.EUR, }, { label: 'US Dollars', value: BentoCurrencyISOCode.USD, }, ], multiple: true, }, type: BentoFilterItemType.SELECT_CURRENCY, }, { field: 'range', label: 'Range', type: BentoFilterItemType.RANGE, visible: true, }, { field: 'date', label: 'Date', type: BentoFilterItemType.DATE, visible: true, }, { field: 'date-range', label: 'Date range', options: { allowTimeInput: true, numberOfMonths: 1, quickSelectRanges: [ { label: 'Last 15 min', value: 'lastFifteenMin', data: { startDate: subMinutes(CURRENT_DATE_AND_TIME, 15), isRelativeToNow: true, }, }, { label: 'Last 24 hours', value: 'lastTwentyFourHours', data: { startDate: subHours(CURRENT_DATE_AND_TIME, 24), isRelativeToNow: true, }, }, { label: 'Last three days', value: 'lastThreeDays', data: { startDate: subDays(startOfDay(CURRENT_DATE_AND_TIME), 2), }, }, { label: 'Last week', value: 'lastWeek', data: { startDate: subWeeks(startOfDay(CURRENT_DATE_AND_TIME), 1), }, }, ], } as BentoDateRangeFilterOptions, type: BentoFilterItemType.DATE_RANGE, visible: true, }, { field: 'granular-date-range', label: 'Granular date range', options: { allowTimeInput: true, numberOfMonths: 2, granularities: [{ type: 'daily' }, { type: 'weekly' }, { type: 'monthly', disabled: true }], min: subWeeks(startOfWeek(CURRENT_DATE_AND_TIME, { weekStartsOn: 1 }), 12), max: endOfWeek(CURRENT_DATE_AND_TIME, { weekStartsOn: 1 }), } as BentoDateRangeFilterOptions, type: BentoFilterItemType.DATE_RANGE, visible: true, }, { field: 'date-range-month', label: 'Date range (month)', options: { numberOfMonths: 2, variant: 'month', } as BentoDateRangeFilterOptions, type: BentoFilterItemType.DATE_RANGE, visible: true, }, { field: 'paymentMethod', label: 'Payment method', options: { listboxItems: [ { label: 'iDEAL', value: 'ideal' }, { label: 'Visa', value: 'visa' }, ], multiple: true, }, type: BentoFilterItemType.SELECT_PAYMENT_METHOD, }, { field: 'tag', label: 'Tag', options: { listboxItems: [ { label: 'Offline', value: 'unique-id-1', variant: 'red' }, { label: 'Online', value: 'unique-id-2', variant: 'green' }, { label: 'Standby', value: 'unique-id-3', variant: 'orange' }, { label: 'Busy', value: 'unique-id-4', variant: 'blue' }, ], multiple: true, }, type: BentoFilterItemType.SELECT_TAG, }, { field: 'radio-group', label: 'Radio group', options: { items: [ { label: 'Option 1', value: 'option-1' }, { label: 'Option 2', value: 'option-2' }, { label: 'Option 3', value: 'option-3' }, ], }, visible: false, type: BentoFilterItemType.RADIO_GROUP, }, { field: 'checkbox-group', label: 'Checkbox group', options: { checkboxItems: [ { label: 'Item 1', value: 'unique-id-1' }, { label: 'Item 2', value: 'unique-id-2' }, { label: 'Item 3', value: 'unique-id-3' }, { label: 'Item with a very long name', value: 'unique-id-4' }, ], }, type: BentoFilterItemType.CHECKBOX_GROUP, visible: true, }, { field: 'toggle', label: 'Toggle', type: BentoFilterItemType.TOGGLE, visible: true, description: 'Optional description for the toggle filter', options: { tooltipPosition: 'bottom', tooltipText: 'Toggle filter tooltip text', }, }, ]; const FILTERS_WITH_DEFAULTS: BentoFilterBarModel = [ { field: 'input', label: 'Input filter', defaultValue: 'hello', type: BentoFilterItemType.INPUT, }, { field: 'input-with-dropdown', label: 'Input with dropdown filter', type: BentoFilterItemType.INPUT_DROPDOWN, visible: true, defaultValue: { inputValue: 'Peristent input filter', dropdownValue: 'EUR', }, options: { dropdown: { items: [ { label: 'EUR', value: 'EUR' }, { label: 'USD', value: 'USD' }, ], }, }, }, { field: 'range', label: 'Range', defaultValue: ['1', '4'], type: BentoFilterItemType.RANGE, }, { field: 'merchant', label: 'Merchants', defaultValue: ['merchant_1'], options: { listboxItems: [ { label: 'Merchant 1', value: 'merchant_1', }, { label: 'Merchant 2', value: 'merchant_2', }, { label: 'Merchant 3', value: 'merchant_3', }, ], multiple: true, enableValueLabelPair: true, }, type: BentoFilterItemType.SELECT, }, { field: 'thing', label: 'Things', defaultValue: 'merchant_1', options: { listboxItems: [ { label: 'Thing 1', value: 'merchant_1', }, { label: 'Thing 2', value: 'merchant_2', }, { label: 'Thing 3', value: 'merchant_3', }, ], }, type: BentoFilterItemType.SELECT, }, { field: 'country', label: 'Country/Region', options: { listboxItems: [ { label: 'Option 1', value: BentoCountryCode.NL, }, { label: 'Option 2', value: BentoCountryCode.BE, }, ], multiple: true, variant: 'capital', }, type: BentoFilterItemType.SELECT_COUNTRY, }, { field: 'radio-group', label: 'Radio group', defaultValue: 'option-1', options: { items: [ { label: 'Option 1', value: 'option-1' }, { label: 'Option 2', value: 'option-2' }, { label: 'Option 3', value: 'option-3' }, ], }, visible: false, type: BentoFilterItemType.RADIO_GROUP, }, { field: 'checkbox-group', label: 'Checkbox group', defaultValue: ['unique-id-1'], options: { checkboxItems: [ { label: 'Item 1', value: 'unique-id-1' }, { label: 'Item 2', value: 'unique-id-2' }, { label: 'Item 3', value: 'unique-id-3' }, { label: 'Item 4', value: 'unique-id-4' }, ], }, type: BentoFilterItemType.CHECKBOX_GROUP, }, { field: 'date-range', label: 'Date range', defaultValue: { startDate: subDays(startOfDay(CURRENT_DATE_AND_TIME), 2), endDate: endOfDay(CURRENT_DATE_AND_TIME), }, options: { numberOfMonths: 1, quickSelectRanges: [ { label: 'Last three days', value: 'lastThreeDays', data: { startDate: subDays(startOfDay(CURRENT_DATE_AND_TIME), 2), }, }, { label: 'Last week', value: 'lastWeek', data: { startDate: subWeeks(startOfDay(CURRENT_DATE_AND_TIME), 1), }, }, ], } as BentoDateRangeFilterOptions, type: BentoFilterItemType.DATE_RANGE, }, ]; const defaultFilterTemplate = storybookDocsParameter(FilterBarDefaultExample); export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoFilterBar }, props: Object.keys(argTypes), setup(props) { const args = isVue2 ? props : _args; const filtersConfig = ref<BentoFilterBarModel>(args.config); const filterValues = ref<BentoFilterValues>(args.filterValues); const filterValuesUpdate = (newFilterValues: BentoFilterValues) => { filterValues.value = newFilterValues; action('update:filter-values')(newFilterValues); }; const filterValuesChanged = (newFilterValues: BentoFilterValues) => { action('input')(newFilterValues); }; const searchTermField = computed(() => args.searchTerm); const searchInput = (newSearchTerm: string) => action('search-input')(newSearchTerm); const searchClear = () => action('search-clear')(); const searchTermSync = (newSearchTerm: string) => action('update:search-term')(newSearchTerm); return { // Props args, // Values filtersConfig, filterValues, searchTermField, // Events filterValuesChanged, filterValuesUpdate, searchInput, searchClear, searchTermSync, }; }, template: ` <div style="padding-top: 24px"> <bento-filter-bar v-bind="args" :config="filtersConfig" :search="{width: '800px'}" :searchTerm="searchTermField" :filter-values="filterValues" @input="filterValuesChanged" @update:filter-values="filterValuesUpdate" @update:search-term="searchTermSync" @search-input="searchInput" @search-clear="searchClear" /> </div> `, }), args: { config: FILTERS, filterValues: FILTER_VALUES, containerSizeLayout: { large: 'multi-line', medium: 'one-line', small: 'multi-line', } satisfies BentoFilterBarContainerSizeLayout, search: { inputFieldAriaLabel: 'Input field aria label', debounceTime: 500, hint: 'Hint', placeholder: 'Placeholder content', } satisfies BentoFilterBarSearchBarProps, searchTerm: 'something', }, parameters: defaultFilterTemplate, }; const persistentFilterTemplate = storybookDocsParameter(` <bento-filter-bar :config="[ { field: 'input', label: 'Input filter', defaultValue: 'hello', type: BentoFilterItemType.INPUT, }, { field: 'range', label: 'Range', defaultValue: ['1', '4'], type: BentoFilterItemType.RANGE, }, { field: 'merchant', label: 'Merchants', defaultValue: ['merchant_1'], options: { listboxItems: [ { label: 'Merchant 1', value: 'merchant_1', }, { label: 'Merchant 2', value: 'merchant_2', }, { label: 'Merchant 3', value: 'merchant_3', }, ], multiple: true, }, type: BentoFilterItemType.SELECT, }, { field: 'thing', label: 'Things', defaultValue: 'merchant_1', options: { listboxItems: [ { label: 'Thing 1', value: 'merchant_1', }, { label: 'Thing 2', value: 'merchant_2', }, { label: 'Thing 3', value: 'merchant_3', }, ], }, type: BentoFilterItemType.SELECT, }, { field: 'country', label: 'Country/Region', options: { listboxItems: [ { label: 'Option 1', value: BentoCountryCode.NL, }, { label: 'Option 2', value: BentoCountryCode.BE, }, ], multiple: true, variant: 'capital', }, type: BentoFilterItemType.SELECT_COUNTRY, }, ]" :filter-values="[ { field: 'input', value: 'non-default value', }, ]" :search="{ inputFieldAriaLabel: 'Input field aria label', debounceTime: 500, hint: 'Hint', placeholder: 'Placeholder content', }" :search-term.sync="searchTerm" @input="onFilter" @search-input="onSearchInput" @search-clear="onSearchClear" /> `); export const Persistent: Story = { ...Default, args: { config: FILTERS_WITH_DEFAULTS, filterValues: [ { field: 'input', value: 'non-default value', }, { field: 'checkbox-group', value: ['unique-id-2'], }, ], searchTerm: null, }, parameters: persistentFilterTemplate, }; const externalFilteringTemplate = storybookDocsParameter(` <template> <bento-filter-bar :config="filtersConfig" :filter-values.sync="filterValues" /> </template> <script setup lang="ts"> import { ref, computed, onMounted } from 'vue'; import { BentoFilterBar, BentoFilterItemType, BentoFilterBarModel, BentoFilterValues, BentoSelectFilterOptions, } from '@adyen/bento-vue2'; // --- Mock API for demonstration --- const getPokemonFromApi = async (searchTerm?: string) => { // In a real application, this would be an API call. // We're mocking it with a timeout and static data. console.log(\`Searching for: \${searchTerm}\`); await new Promise(resolve => setTimeout(resolve, 1000)); const allPokemon = [ { label: 'Bulbasaur', value: 'bulbasaur' }, { label: 'Ivysaur', value: 'ivysaur' }, { label: 'Venusaur', value: 'venusaur' }, { label: 'Charmander', value: 'charmander' }, { label: 'Charmeleon', value: 'charmeleon' }, { label: 'Charizard', value: 'charizard' }, ]; const items = searchTerm ? allPokemon.filter(p => p.label.toLowerCase().includes(searchTerm.toLowerCase())) : allPokemon; return { items }; }; // --- End Mock API --- const filterValues = ref<BentoFilterValues>([{ field: 'pokemon', value: 'bulbasaur' }]); const apiItems = ref([]); const isLoading = ref(false); const getAndSetPokemonFromApi = async (val?: string) => { isLoading.value = true; const { items } = await getPokemonFromApi(val); apiItems.value = items; isLoading.value = false; }; onMounted(() => { getAndSetPokemonFromApi(); }); const filtersConfig = computed<BentoFilterBarModel>(() => [ { field: 'name', label: 'Name', type: BentoFilterItemType.INPUT }, { field: 'pokemon', label: 'Pokemon', options: { isOptionDisabled: option => ['ivysaur', 'charmeleon'].includes(option.value as string), listboxItems: apiItems.value, search: { debounceTime: 500, searchEvent: getAndSetPokemonFromApi }, loading: isLoading.value, }, type: BentoFilterItemType.SELECT, }, ]); </script> `); export const ExternalFilteringSelectFilter: Story = { render: (_args, { argTypes }) => ({ components: { BentoFilterBar, }, props: Object.keys(argTypes), setup(props) { const apiItems = ref([]); const filterValues = ref<BentoFilterValues>([{ field: 'pokemon', value: 'bulbasaur' }]); const isLoading = ref(false); const getAndSetPokemonFromApi = async (val?: string) => { isLoading.value = true; await new Promise(resolve => setTimeout(resolve, 1000)); const { items } = await getPokemonFromApi(val); apiItems.value = items; isLoading.value = false; }; const search = { debounceTime: 500, searchEvent: getAndSetPokemonFromApi, }; onMounted(() => { getAndSetPokemonFromApi(); }); const filtersConfig = computed(() => [ { field: 'name', label: 'Name', type: BentoFilterItemType.INPUT, }, { field: 'pokemon', label: 'Pokemon', options: { isOptionDisabled: option => ['ivysaur', 'charmeleon'].includes(option.value as string), listboxItems: apiItems.value, search, loading: isLoading.value, }, type: BentoFilterItemType.SELECT, visible: false, }, ]); return { args: isVue2 ? props : _args, // Values filtersConfig, filterValues, }; }, template: ` <div style="padding-top: 24px"> <bento-filter-bar v-bind="args" :config="filtersConfig" :filter-values.sync="filterValues" /> </div> `, }), args: { config: FILTERS, searchTerm: null }, parameters: externalFilteringTemplate, }; const SearchAndLazyLoadTemplate = storybookDocsParameter(` <template> <bento-filter-bar :config="filtersConfig" :filter-values.sync="filterValues" /> </template> <script setup lang="ts"> import { ref, computed, onMounted } from 'vue'; import { BentoFilterBar, BentoFilterItemType, BentoFilterBarModel, BentoFilterValues, BentoSelectFilterOptions, BentoListboxOptions, } from '@adyen/bento-vue2'; // --- Mock API for demonstration --- const ALL_ITEMS = Array.from({ length: 100 }, (_, i) => ({ label: \`Item \${i + 1}\`, value: i + 1, })); const fetchItems = (searchTerm: string, page: number, pageSize: number = 20) => { console.log(\`Fetching items with searchTerm: "\${searchTerm}", page: \${page}\`); return new Promise<{ items: BentoListboxOptions; hasMore: boolean }>(resolve => { setTimeout(() => { const filtered = ALL_ITEMS.filter(item => item.label.toLowerCase().includes(searchTerm.toLowerCase()) ); const start = (page - 1) * pageSize; const end = start + pageSize; const pageItems = filtered.slice(start, end); resolve({ items: pageItems, hasMore: end < filtered.length, }); }, 500); }); }; // --- End Mock API --- const filterValues = ref<BentoFilterValues>([]); // State for the select filter const listboxItems = ref<BentoListboxOptions>([]); const isLoading = ref(false); const hasMoreItems = ref(true); const currentPage = ref(1); const currentSearchTerm = ref(''); /** * Handles the search event from the filter. * This should reset pagination and fetch a new list of items. */ const onSearch = async (searchTerm: string) => { currentSearchTerm.value = searchTerm; currentPage.value = 1; isLoading.value = true; const { items, hasMore } = await fetchItems(searchTerm, currentPage.value); listboxItems.value = items; hasMoreItems.value = hasMore; isLoading.value = false; }; /** * Handles the lazy load event. * This should fetch the next page of items and append them. */ const onLazyLoad = async () => { if (isLoading.value || !hasMoreItems.value) { return; } currentPage.value++; isLoading.value = true; const { items, hasMore } = await fetchItems(currentSearchTerm.value, currentPage.value); // Append new items to the existing list listboxItems.value = [...listboxItems.value, ...items]; hasMoreItems.value = hasMore; isLoading.value = false; }; // Load initial data when the component mounts onMounted(() => { onSearch(''); }); const filtersConfig = computed<BentoFilterBarModel>(() => [ { field: 'lazy-select', label: 'Lazy Load Select', type: BentoFilterItemType.SELECT, options: { listboxItems: listboxItems.value, multiple: true, // External filtering configuration search: { searchEvent: onSearch, }, // Lazy loading configuration lazy: { hasMoreItems: hasMoreItems.value, lazyLoadType: 'automatic', // or 'button' loading: isLoading.value, showMoreEvent: onLazyLoad, }, }, }, ]); </script> `); export const ExternalFilteringAndLazyLoadingSelectFilter: Story = { ...ExternalFilteringSelectFilter, render: (_args, { argTypes }) => ({ components: { BentoFilterBar, }, props: Object.keys(argTypes), setup(props) { const args = isVue2 ? props : _args; const filterValues = ref<BentoFilterValues>([]); const lazyLoadingPageSize = 20; // State for the select filter const listboxItems = ref<BentoListboxOptions>([]); const isSearchLoading = ref(false); const isLazyLoading = ref(false); const hasMoreItems = ref(true); const currentPage = ref(1); const currentSearchTerm = ref(''); /** * Handles the search event from the filter. * This should reset pagination and fetch a new list of items. */ const onSearch = async (searchTerm: string) => { currentSearchTerm.value = searchTerm; currentPage.value = 1; isSearchLoading.value = true; const { items, hasMore } = await getPokemonFromApi(searchTerm, currentPage.value, lazyLoadingPageSize); listboxItems.value = items; hasMoreItems.value = hasMore; isSearchLoading.value = false; }; /** * Handles the lazy load event. * This should fetch the next page of items and append them. */ const onLazyLoad = async () => { if (isLazyLoading.value || !hasMoreItems.value) { return; } currentPage.value++; isLazyLoading.value = true; const { items, hasMore } = await getPokemonFromApi( currentSearchTerm.value, currentPage.value, lazyLoadingPageSize ); // Append new items to the existing list listboxItems.value = [...listboxItems.value, ...items]; hasMoreItems.value = hasMore; isLazyLoading.value = false; }; // Load initial data when the component mounts onMounted(() => { onSearch(''); }); const filtersConfig = computed<BentoFilterBarModel>(() => [ { field: 'lazy-select', label: 'External filtering and lazy loading', type: BentoFilterItemType.SELECT, options: { listboxItems: listboxItems.value, multiple: true, loading: isSearchLoading.value, // External filtering configuration search: { searchEvent: onSearch, }, // Lazy loading configuration lazy: { hasMoreItems: hasMoreItems.value, lazyLoadType: 'automatic', // or 'button' loading: isLazyLoading.value, showMoreEvent: onLazyLoad, }, }, }, ]); return { args, filtersConfig, filterValues, }; }, template: ` <div style="padding-top: 24px"> <bento-filter-bar v-bind="args" :config="filtersConfig" :filter-values.sync="filterValues" /> </div> `, }), args: { ...ExternalFilteringSelectFilter.args, }, parameters: SearchAndLazyLoadTemplate, }; export const CustomFilters: Story = { render: (_args, { argTypes }) => ({ components: { BentoFilterBar, BentoToggle, FilterBarCustomFilterSubComponentSingleValueExample, FilterBarCustomFilterSubComponentMultiValueExample, }, props: Object.keys(argTypes), template: ` <div style="padding-top: 24px"> <bento-filter-bar :config="filtersConfig" :filter-values-object="filterValues" @update:filter-values-object="filterValuesChanged" /> </div> `, setup() { interface CustomFilterOptions { required?: boolean; optional?: boolean; disabled?: boolean; } const filtersConfig: BentoFilterBarModel<CustomFilterOptions> = [ { field: 'input', label: 'Input filter', type: BentoFilterItemType.INPUT, options: { placeholder: 'Input filter placeholder', }, visible: true, }, { field: 'custom-single', label: 'Custom single value filter', type: FilterBarCustomFilterSubComponentSingleValueExample, visible: true, }, { field: 'custom-multi', label: 'Custom multi value filter', type: FilterBarCustomFilterSubComponentMultiValueExample, options: { // ICustomFilterOptions disabled: false, optional: false, required: false, }, visible: false, }, { field: 'custom-popover', label: 'Custom filter with popover', type: FilterBarCustomFilterSubComponentWithPopoverExample, visible: true, }, ]; /** * Custom filter values */ type FilterKeys = 'input' | 'custom-single' | 'custom-multi' | 'custom-popover'; type CustomFilterValues = | BentoFilterBarValue | boolean | { // ICustomFilterValues first: boolean; second: boolean; } | undefined; const filterValues = ref<BentoFilterValueObject<FilterKeys, CustomFilterValues>>({ input: undefined, 'custom-single': undefined, 'custom-multi': { first: false, second: false, }, 'custom-popover': undefined, }); const filterValuesChanged = (newFilterValues: BentoFilterValueObject<FilterKeys, CustomFilterValues>) => { filterValues.value = newFilterValues; action('input')(newFilterValues); }; return { filtersConfig, filterValues, filterValuesChanged, }; }, }), args: false, parameters: storybookDocsParameter(FilterBarCustomFiltersExample), };
1
+ import { computed, onMounted, ref } from 'vue'; import { action } from '@storybook/addon-actions'; import { type BentoDateRangeFilterOptions, type BentoFilterBarContainerSizeLayout, type BentoFilterBarModel, type BentoFilterBarSearchBarProps, type BentoFilterBarValue, BentoFilterItemType, type BentoFilterValueObject, type BentoFilterValues, } from './filter-bar.types'; import { getPokemonFromApi, storybookDocsParameter, withToastDecorator } from '@/utils/ts/storybook'; import { type BentoListboxOptions } from '@/types/listbox'; import { BentoCountryCode } from '@/components/country/country.types'; import { BentoCurrencyISOCode } from '@/components/currency/currency.types'; import { isVue2 } from 'vue-demi'; import { type Meta, type StoryObj } from '@storybook/vue'; import { BentoToggle } from '@/components/toggle'; import BentoFilterBar from './filter-bar.vue'; import { endOfWeek } from 'date-fns/endOfWeek'; import { startOfWeek } from 'date-fns/startOfWeek'; import { subDays } from 'date-fns/subDays'; import { subWeeks } from 'date-fns/subWeeks'; import { subMonths } from 'date-fns/subMonths'; import { startOfMonth } from 'date-fns/startOfMonth'; import { endOfDay, startOfDay, subHours, subMinutes } from 'date-fns'; // Examples import FilterBarCustomFilterSubComponentSingleValueExample from './__tests__/filter-bar-custom-filter-sub-component-single-value-example.vue'; import FilterBarCustomFilterSubComponentMultiValueExample from './__tests__/filter-bar-custom-filter-sub-component-multiple-value-example.vue'; import FilterBarCustomFilterSubComponentWithPopoverExample from './__tests__/filter-bar-custom-filter-sub-component-with-popover-example.vue'; const FilterBarCustomFiltersExample = "<template>\n <bento-filter-bar\n :config=\"filtersConfig\"\n :filter-values-object=\"filterValues\"\n @update:filter-values-object=\"filterValuesChanged\"\n />\n</template>\n\n<script setup lang=\"ts\">\n import { ref } from 'vue';\n import FilterBarCustomFilterSubComponentSingleValueExample from './filter-bar-custom-filter-sub-component-single-value-example.vue';\n import FilterBarCustomFilterSubComponentMultipleValueExample from './filter-bar-custom-filter-sub-component-multiple-value-example.vue';\n import FilterBarCustomFilterSubComponentWithPopoverExample from './filter-bar-custom-filter-sub-component-with-popover-example.vue';\n import {\n BentoFilterBar,\n type BentoFilterBarModel,\n type BentoFilterBarValue,\n BentoFilterItemType,\n type BentoFilterValueObject,\n } from '@adyen/bento-vue2';\n\n interface ICustomFilterValues {\n first: boolean;\n second: boolean;\n }\n\n interface ICustomFilterOptions {\n required?: boolean;\n optional?: boolean;\n disabled?: boolean;\n }\n\n const filtersConfig = ref<BentoFilterBarModel<ICustomFilterOptions>>([\n {\n field: 'input',\n label: 'Input filter',\n type: BentoFilterItemType.INPUT,\n options: {\n placeholder: 'Input filter placeholder',\n },\n visible: true,\n },\n\n {\n field: 'custom-single',\n label: 'Custom single value filter',\n type: FilterBarCustomFilterSubComponentSingleValueExample,\n visible: true,\n },\n\n {\n field: 'custom-multi',\n label: 'Custom multi value filter',\n type: FilterBarCustomFilterSubComponentMultipleValueExample,\n options: {\n // ICustomFilterOptions\n disabled: false,\n optional: false,\n required: false,\n },\n visible: true,\n },\n\n {\n field: 'custom-popover',\n label: 'Custom filter with popover',\n type: FilterBarCustomFilterSubComponentWithPopoverExample,\n visible: true,\n },\n ]);\n\n type FilterKeys = 'input' | 'custom-single' | 'custom-multi' | 'custom-popover';\n type CustomFilterValues = BentoFilterBarValue | boolean | ICustomFilterValues | undefined;\n\n const filterValues = ref<BentoFilterValueObject<FilterKeys, CustomFilterValues>>({\n input: '',\n 'custom-single': undefined,\n 'custom-multi': {\n first: false,\n second: false,\n },\n 'custom-popover': undefined,\n });\n\n function filterValuesChanged(newFilterValues: BentoFilterValueObject<FilterKeys, CustomFilterValues>) {\n filterValues.value = newFilterValues;\n }\n</script>\n"; const FilterBarDefaultExample = "<template>\n <bento-filter-bar\n :search-term=\"searchTerm\"\n :config=\"config\"\n :container-size-layout=\"containerSizeLayout\"\n :filter-values=\"filterValues\"\n @input=\"onFilter\"\n @update:search-term=\"searchTerm = $event\"\n @search-input=\"onSearchInput\"\n @search-clear=\"onSearchClear\"\n />\n</template>\n\n<script setup lang=\"ts\">\n import { ref } from 'vue';\n import { BentoFilterBar, BentoFilterItemType } from '@adyen/bento-vue2';\n import type { BentoFilterValues } from '@adyen/bento-vue2';\n\n const searchTerm = ref<string | undefined>(undefined);\n const options = [\n { label: 'Option 1', value: 1 },\n { label: 'Option 2', value: 2 },\n { label: 'Option 3', value: 3 },\n ];\n\n const config = [\n {\n field: 'name',\n label: 'Name',\n type: BentoFilterItemType.INPUT,\n visible: true,\n },\n {\n field: 'merchant',\n label: 'Merchant',\n options: {\n listboxItems: options,\n },\n type: BentoFilterItemType.SELECT,\n },\n {\n field: 'company',\n label: 'Company',\n options: {\n listboxItems: options,\n isOptionDisabled: option => option.value === 5,\n multiple: true,\n },\n type: BentoFilterItemType.SELECT,\n },\n ];\n\n const containerSizeLayout = {\n large: 'multi-line',\n medium: 'one-line',\n small: 'multi-line',\n };\n\n const filterValues = ref<BentoFilterValues>([\n {\n field: 'merchant',\n value: 2,\n },\n {\n field: 'company',\n value: [1, 3, 5],\n },\n ]);\n\n function onFilter() {\n /* handle filter change */\n }\n function onSearchInput() {\n /* handle search input */\n }\n function onSearchClear() {\n /* handle search clear */\n }\n</script>\n"; const FilterBarExternalFiltering = "<template>\n <bento-filter-bar :config=\"filters\" :filter-values=\"filterValues\" @update:filter-values=\"filterValues = $event\" />\n</template>\n\n<script setup lang=\"ts\">\n import {\n BentoFilterBar,\n type BentoFilterBarModel,\n BentoFilterItemType,\n type BentoFilterValues,\n type BentoListboxOptions,\n } from '@adyen/bento-vue2';\n import { computed, onMounted, ref } from 'vue';\n\n const allPokemon: BentoListboxOptions = [\n { label: 'Bulbasaur', value: 'bulbasaur' },\n { label: 'Ivysaur', value: 'ivysaur' },\n { label: 'Venusaur', value: 'venusaur' },\n { label: 'Charmander', value: 'charmander' },\n { label: 'Charmeleon', value: 'charmeleon' },\n { label: 'Charizard', value: 'charizard' },\n ];\n const filterValues = ref<BentoFilterValues>([{ field: 'pokemon', value: 'bulbasaur' }]);\n const isLoading = ref(false);\n const items = ref<BentoListboxOptions>([]);\n\n const searchPokemon = async (searchTerm = '') => {\n isLoading.value = true;\n await new Promise(resolve => setTimeout(resolve, 1000));\n items.value = allPokemon.filter(item => item.label.toLowerCase().includes(searchTerm.toLowerCase()));\n isLoading.value = false;\n };\n const filters = computed<BentoFilterBarModel>(() => [\n { field: 'name', label: 'Name', type: BentoFilterItemType.INPUT },\n {\n field: 'pokemon',\n label: 'Pokemon',\n options: {\n isOptionDisabled: option => ['ivysaur', 'charmeleon'].includes(option.value as string),\n listboxItems: items.value,\n loading: isLoading.value,\n search: { debounceTime: 500, searchEvent: searchPokemon },\n },\n type: BentoFilterItemType.SELECT,\n visible: false,\n },\n ]);\n\n onMounted(() => searchPokemon());\n</script>\n"; const FilterBarExternalFilteringAndLazyLoading = "<template>\n <bento-filter-bar :config=\"filters\" :filter-values=\"filterValues\" @update:filter-values=\"filterValues = $event\" />\n</template>\n\n<script setup lang=\"ts\">\n import {\n BentoFilterBar,\n type BentoFilterBarModel,\n BentoFilterItemType,\n type BentoFilterValues,\n type BentoListboxOptions,\n } from '@adyen/bento-vue2';\n import { computed, onMounted, ref } from 'vue';\n\n const allItems: BentoListboxOptions = Array.from({ length: 100 }, (_, index) => ({\n label: `Item ${index + 1}`,\n value: index + 1,\n }));\n const currentPage = ref(1);\n const currentSearchTerm = ref('');\n const filterValues = ref<BentoFilterValues>([]);\n const hasMoreItems = ref(true);\n const isLazyLoading = ref(false);\n const isSearchLoading = ref(false);\n const items = ref<BentoListboxOptions>([]);\n const pageSize = 20;\n\n const fetchItems = async (searchTerm: string, page: number) => {\n await new Promise(resolve => setTimeout(resolve, 500));\n const filtered = allItems.filter(item => item.label.toLowerCase().includes(searchTerm.toLowerCase()));\n const end = page * pageSize;\n return { hasMore: end < filtered.length, items: filtered.slice(end - pageSize, end) };\n };\n const search = async (searchTerm: string) => {\n currentPage.value = 1;\n currentSearchTerm.value = searchTerm;\n isSearchLoading.value = true;\n const result = await fetchItems(searchTerm, currentPage.value);\n items.value = result.items;\n hasMoreItems.value = result.hasMore;\n isSearchLoading.value = false;\n };\n const loadMore = async () => {\n if (isLazyLoading.value || !hasMoreItems.value) {\n return;\n }\n isLazyLoading.value = true;\n const result = await fetchItems(currentSearchTerm.value, ++currentPage.value);\n items.value = [...items.value, ...result.items];\n hasMoreItems.value = result.hasMore;\n isLazyLoading.value = false;\n };\n const filters = computed<BentoFilterBarModel>(() => [\n {\n field: 'lazy-select',\n label: 'External filtering and lazy loading',\n options: {\n lazy: {\n hasMoreItems: hasMoreItems.value,\n lazyLoadType: 'automatic',\n loading: isLazyLoading.value,\n showMoreEvent: loadMore,\n },\n listboxItems: items.value,\n loading: isSearchLoading.value,\n multiple: true,\n search: { searchEvent: search },\n },\n type: BentoFilterItemType.SELECT,\n },\n ]);\n\n onMounted(() => search(''));\n</script>\n"; const FilterBarPersistent = "<template>\n <bento-filter-bar :config=\"filters\" :filter-values=\"filterValues\" @update:filter-values=\"filterValues = $event\" />\n</template>\n\n<script setup lang=\"ts\">\n import {\n BentoFilterBar,\n type BentoFilterBarModel,\n BentoFilterItemType,\n type BentoFilterValues,\n } from '@adyen/bento-vue2';\n import { endOfDay } from 'date-fns/endOfDay';\n import { startOfDay } from 'date-fns/startOfDay';\n import { subDays } from 'date-fns/subDays';\n import { subWeeks } from 'date-fns/subWeeks';\n import { ref } from 'vue';\n\n const now = new Date();\n const filters: BentoFilterBarModel = [\n {\n defaultValue: 'hello',\n field: 'input',\n label: 'Input filter',\n type: BentoFilterItemType.INPUT,\n },\n {\n defaultValue: { dropdownValue: 'EUR', inputValue: 'Persistent input filter' },\n field: 'input-with-dropdown',\n label: 'Input with dropdown filter',\n options: {\n dropdown: {\n items: [\n { label: 'EUR', value: 'EUR' },\n { label: 'USD', value: 'USD' },\n ],\n },\n },\n type: BentoFilterItemType.INPUT_DROPDOWN,\n visible: true,\n },\n {\n defaultValue: ['1', '4'],\n field: 'range',\n label: 'Range',\n type: BentoFilterItemType.RANGE,\n },\n {\n defaultValue: ['merchant_1'],\n field: 'merchant',\n label: 'Merchants',\n options: {\n enableValueLabelPair: true,\n listboxItems: [\n { label: 'Merchant 1', value: 'merchant_1' },\n { label: 'Merchant 2', value: 'merchant_2' },\n { label: 'Merchant 3', value: 'merchant_3' },\n ],\n multiple: true,\n },\n type: BentoFilterItemType.SELECT,\n },\n {\n defaultValue: 'merchant_1',\n field: 'thing',\n label: 'Things',\n options: {\n listboxItems: [\n { label: 'Thing 1', value: 'merchant_1' },\n { label: 'Thing 2', value: 'merchant_2' },\n { label: 'Thing 3', value: 'merchant_3' },\n ],\n },\n type: BentoFilterItemType.SELECT,\n },\n {\n field: 'country',\n label: 'Country/Region',\n options: {\n listboxItems: [{ value: 'NL' }, { value: 'BE' }],\n multiple: true,\n variant: 'capital',\n },\n type: BentoFilterItemType.SELECT_COUNTRY,\n },\n {\n defaultValue: 'option-1',\n field: 'radio-group',\n label: 'Radio group',\n options: {\n items: [\n { label: 'Option 1', value: 'option-1' },\n { label: 'Option 2', value: 'option-2' },\n { label: 'Option 3', value: 'option-3' },\n ],\n },\n type: BentoFilterItemType.RADIO_GROUP,\n visible: false,\n },\n {\n defaultValue: ['unique-id-1'],\n field: 'checkbox-group',\n label: 'Checkbox group',\n options: {\n checkboxItems: [\n { label: 'Item 1', value: 'unique-id-1' },\n { label: 'Item 2', value: 'unique-id-2' },\n { label: 'Item 3', value: 'unique-id-3' },\n { label: 'Item 4', value: 'unique-id-4' },\n ],\n },\n type: BentoFilterItemType.CHECKBOX_GROUP,\n },\n {\n defaultValue: {\n startDate: subDays(startOfDay(now), 2),\n endDate: endOfDay(now),\n },\n field: 'date-range',\n label: 'Date range',\n options: {\n numberOfMonths: 1,\n quickSelectRanges: [\n {\n data: { startDate: subDays(startOfDay(now), 2) },\n label: 'Last three days',\n value: 'lastThreeDays',\n },\n {\n data: { startDate: subWeeks(startOfDay(now), 1) },\n label: 'Last week',\n value: 'lastWeek',\n },\n ],\n },\n type: BentoFilterItemType.DATE_RANGE,\n },\n ];\n const filterValues = ref<BentoFilterValues>([\n { field: 'input', value: 'non-default value' },\n { field: 'checkbox-group', value: ['unique-id-2'] },\n ]);\n</script>\n"; // Generate array from 1 to # // { label: 'Option #', value: # }, const DEFAULT_OPTIONS: BentoListboxOptions = new Array(10) .fill(0) .map((_, index) => ({ label: `Option ${index + 1}`, value: index + 1 })); // To be used by as the current time across all date filters const CURRENT_DATE_AND_TIME = new Date(); const meta: Meta = { title: 'Filter bar', component: BentoFilterBar, decorators: [withToastDecorator], argTypes: { // Props searchTerm: { name: 'searchTerm', defaultValue: undefined, description: "Search term useful to filter all the data by text. If the value is non-existant (`null` or `undefined`) the search bar won't be enabled.", control: { type: 'text', }, }, // Events input: { name: 'input', defaultValue: '', description: 'Triggered whenever the filters are updated.', table: { type: { summary: '([{ field, label, value, type }]) => void' } }, control: { disable: true }, }, // Search Events 'search-input': { name: 'search-input', defaultValue: '', description: "Triggered whenever the filter's search is updated.", table: { type: { summary: '(newSearchTerm) => void' } }, control: { disable: true }, }, 'search-clear': { name: 'search-clear', defaultValue: '', description: "Triggered whenever the filter's search is cleared.", table: { type: { summary: '() => void' } }, control: { disable: true }, }, 'update:search-term': { name: 'update:search-term', defaultValue: '', description: "Triggered whenever the filter's search is updated to sync the model.", table: { type: { summary: '(newSearchTerm) => void' } }, control: { disable: true }, }, }, }; export default meta; type Story = StoryObj<typeof BentoFilterBar>; const FILTER_VALUES: BentoFilterValues = [ { field: 'input', value: undefined, }, { field: 'boolean', value: 'yes', }, { field: 'input-with-dropdown', value: undefined, }, { field: 'hidden-input', value: 'Active value of a hidden filter', }, { field: 'merchant', value: 2, }, { field: 'company', value: [1, 3, 5], }, { field: 'nested-select', value: ['amsterdam', 'den-haag'], }, { field: 'currency', value: [], }, { field: 'range', value: ['', ''], }, { field: 'date', value: CURRENT_DATE_AND_TIME, }, { field: 'date-range', value: { startDate: subDays(startOfDay(CURRENT_DATE_AND_TIME), 2), endDate: endOfDay(CURRENT_DATE_AND_TIME) }, }, { field: 'granular-date-range', value: { startDate: subDays(CURRENT_DATE_AND_TIME, 2), endDate: CURRENT_DATE_AND_TIME }, }, { field: 'date-range-month', value: { startDate: startOfMonth(subMonths(CURRENT_DATE_AND_TIME, 2)), endDate: startOfMonth(CURRENT_DATE_AND_TIME), }, }, { field: 'paymentMethod', value: undefined, }, { field: 'checkbox-group', value: [], }, { field: 'toggle', value: true, }, ]; const FILTERS: BentoFilterBarModel = [ { field: 'input', label: 'Input filter', type: BentoFilterItemType.INPUT, options: { placeholder: 'Input filter placeholder', tooltipPosition: 'bottom', tooltipText: 'Input filter tooltip text', }, visible: true, }, { field: 'input-with-dropdown', label: 'Input with dropdown filter', type: BentoFilterItemType.INPUT_DROPDOWN, visible: true, options: { dropdown: { items: [ { label: 'EUR', value: 'EUR' }, { label: 'USD', value: 'USD' }, ], modelValue: 'EUR', dynamicFiltering: true, }, }, }, { field: 'hidden-input', label: 'Hidden input filter', description: 'This is a hidden input filter only visible through the modal', type: BentoFilterItemType.INPUT, }, { field: 'country', label: 'Country/Region', options: { listboxItems: [ { value: BentoCountryCode.NL, }, { value: BentoCountryCode.BE, }, ], multiple: true, variant: 'capital', }, type: BentoFilterItemType.SELECT_COUNTRY, }, { field: 'avatar', label: 'Avatar', options: { listboxItems: [ { label: 'Avatar 1', value: 'avatar-1', }, { label: 'Avatar 2', value: 'avatar-2', }, ], }, type: BentoFilterItemType.SELECT_AVATAR, }, { field: 'merchant', label: 'Merchant', options: { listboxItems: DEFAULT_OPTIONS, enableValueLabelPair: true, isOptionDisabled: option => option.value === 3, }, type: BentoFilterItemType.SELECT, visible: true, }, { field: 'company', label: 'Company', options: { listboxItems: DEFAULT_OPTIONS, enableValueLabelPair: true, isOptionDisabled: option => option.value === 5, multiple: true, }, type: BentoFilterItemType.SELECT, visible: true, }, { field: 'nested-select', label: 'Nested select', options: { listboxItems: [ { label: 'Belgium', description: 'Country/Region', value: 'belgium', }, { label: 'Netherlands', description: 'Country/Region', value: 'netherlands', items: [ { label: 'Amsterdam', description: 'City', value: 'amsterdam', }, { label: 'Den Haag', description: 'City', value: 'den-haag', }, ], }, ], multiple: true, }, type: BentoFilterItemType.SELECT, visible: true, }, { field: 'currency', label: 'Currency select', options: { listboxItems: [ { label: 'Euro', value: BentoCurrencyISOCode.EUR, }, { label: 'US Dollars', value: BentoCurrencyISOCode.USD, }, ], multiple: true, }, type: BentoFilterItemType.SELECT_CURRENCY, }, { field: 'range', label: 'Range', type: BentoFilterItemType.RANGE, visible: true, }, { field: 'date', label: 'Date', type: BentoFilterItemType.DATE, visible: true, }, { field: 'date-range', label: 'Date range', options: { allowTimeInput: true, numberOfMonths: 1, quickSelectRanges: [ { label: 'Last 15 min', value: 'lastFifteenMin', data: { startDate: subMinutes(CURRENT_DATE_AND_TIME, 15), isRelativeToNow: true, }, }, { label: 'Last 24 hours', value: 'lastTwentyFourHours', data: { startDate: subHours(CURRENT_DATE_AND_TIME, 24), isRelativeToNow: true, }, }, { label: 'Last three days', value: 'lastThreeDays', data: { startDate: subDays(startOfDay(CURRENT_DATE_AND_TIME), 2), }, }, { label: 'Last week', value: 'lastWeek', data: { startDate: subWeeks(startOfDay(CURRENT_DATE_AND_TIME), 1), }, }, ], } as BentoDateRangeFilterOptions, type: BentoFilterItemType.DATE_RANGE, visible: true, }, { field: 'granular-date-range', label: 'Granular date range', options: { allowTimeInput: true, numberOfMonths: 2, granularities: [{ type: 'daily' }, { type: 'weekly' }, { type: 'monthly', disabled: true }], min: subWeeks(startOfWeek(CURRENT_DATE_AND_TIME, { weekStartsOn: 1 }), 12), max: endOfWeek(CURRENT_DATE_AND_TIME, { weekStartsOn: 1 }), } as BentoDateRangeFilterOptions, type: BentoFilterItemType.DATE_RANGE, visible: true, }, { field: 'date-range-month', label: 'Date range (month)', options: { numberOfMonths: 2, variant: 'month', } as BentoDateRangeFilterOptions, type: BentoFilterItemType.DATE_RANGE, visible: true, }, { field: 'paymentMethod', label: 'Payment method', options: { listboxItems: [ { label: 'iDEAL', value: 'ideal' }, { label: 'Visa', value: 'visa' }, ], multiple: true, }, type: BentoFilterItemType.SELECT_PAYMENT_METHOD, }, { field: 'tag', label: 'Tag', options: { listboxItems: [ { label: 'Offline', value: 'unique-id-1', variant: 'red' }, { label: 'Online', value: 'unique-id-2', variant: 'green' }, { label: 'Standby', value: 'unique-id-3', variant: 'orange' }, { label: 'Busy', value: 'unique-id-4', variant: 'blue' }, ], multiple: true, }, type: BentoFilterItemType.SELECT_TAG, }, { field: 'radio-group', label: 'Radio group', options: { items: [ { label: 'Option 1', value: 'option-1' }, { label: 'Option 2', value: 'option-2' }, { label: 'Option 3', value: 'option-3' }, ], }, visible: false, type: BentoFilterItemType.RADIO_GROUP, }, { field: 'checkbox-group', label: 'Checkbox group', options: { checkboxItems: [ { label: 'Item 1', value: 'unique-id-1' }, { label: 'Item 2', value: 'unique-id-2' }, { label: 'Item 3', value: 'unique-id-3' }, { label: 'Item with a very long name', value: 'unique-id-4' }, ], }, type: BentoFilterItemType.CHECKBOX_GROUP, visible: true, }, { field: 'toggle', label: 'Toggle', type: BentoFilterItemType.TOGGLE, visible: true, description: 'Optional description for the toggle filter', options: { tooltipPosition: 'bottom', tooltipText: 'Toggle filter tooltip text', }, }, ]; const FILTERS_WITH_DEFAULTS: BentoFilterBarModel = [ { field: 'input', label: 'Input filter', defaultValue: 'hello', type: BentoFilterItemType.INPUT, }, { field: 'input-with-dropdown', label: 'Input with dropdown filter', type: BentoFilterItemType.INPUT_DROPDOWN, visible: true, defaultValue: { inputValue: 'Peristent input filter', dropdownValue: 'EUR', }, options: { dropdown: { items: [ { label: 'EUR', value: 'EUR' }, { label: 'USD', value: 'USD' }, ], }, }, }, { field: 'range', label: 'Range', defaultValue: ['1', '4'], type: BentoFilterItemType.RANGE, }, { field: 'merchant', label: 'Merchants', defaultValue: ['merchant_1'], options: { listboxItems: [ { label: 'Merchant 1', value: 'merchant_1', }, { label: 'Merchant 2', value: 'merchant_2', }, { label: 'Merchant 3', value: 'merchant_3', }, ], multiple: true, enableValueLabelPair: true, }, type: BentoFilterItemType.SELECT, }, { field: 'thing', label: 'Things', defaultValue: 'merchant_1', options: { listboxItems: [ { label: 'Thing 1', value: 'merchant_1', }, { label: 'Thing 2', value: 'merchant_2', }, { label: 'Thing 3', value: 'merchant_3', }, ], }, type: BentoFilterItemType.SELECT, }, { field: 'country', label: 'Country/Region', options: { listboxItems: [ { value: BentoCountryCode.NL, }, { value: BentoCountryCode.BE, }, ], multiple: true, variant: 'capital', }, type: BentoFilterItemType.SELECT_COUNTRY, }, { field: 'radio-group', label: 'Radio group', defaultValue: 'option-1', options: { items: [ { label: 'Option 1', value: 'option-1' }, { label: 'Option 2', value: 'option-2' }, { label: 'Option 3', value: 'option-3' }, ], }, visible: false, type: BentoFilterItemType.RADIO_GROUP, }, { field: 'checkbox-group', label: 'Checkbox group', defaultValue: ['unique-id-1'], options: { checkboxItems: [ { label: 'Item 1', value: 'unique-id-1' }, { label: 'Item 2', value: 'unique-id-2' }, { label: 'Item 3', value: 'unique-id-3' }, { label: 'Item 4', value: 'unique-id-4' }, ], }, type: BentoFilterItemType.CHECKBOX_GROUP, }, { field: 'date-range', label: 'Date range', defaultValue: { startDate: subDays(startOfDay(CURRENT_DATE_AND_TIME), 2), endDate: endOfDay(CURRENT_DATE_AND_TIME), }, options: { numberOfMonths: 1, quickSelectRanges: [ { label: 'Last three days', value: 'lastThreeDays', data: { startDate: subDays(startOfDay(CURRENT_DATE_AND_TIME), 2), }, }, { label: 'Last week', value: 'lastWeek', data: { startDate: subWeeks(startOfDay(CURRENT_DATE_AND_TIME), 1), }, }, ], } as BentoDateRangeFilterOptions, type: BentoFilterItemType.DATE_RANGE, }, ]; const defaultFilterTemplate = storybookDocsParameter(FilterBarDefaultExample); export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoFilterBar }, props: Object.keys(argTypes), setup(props) { const args = isVue2 ? props : _args; const filtersConfig = ref<BentoFilterBarModel>(args.config); const filterValues = ref<BentoFilterValues>(args.filterValues); const filterValuesUpdate = (newFilterValues: BentoFilterValues) => { filterValues.value = newFilterValues; action('update:filter-values')(newFilterValues); }; const filterValuesChanged = (newFilterValues: BentoFilterValues) => { action('input')(newFilterValues); }; const searchTermField = computed(() => args.searchTerm); const searchInput = (newSearchTerm: string) => action('search-input')(newSearchTerm); const searchClear = () => action('search-clear')(); const searchTermSync = (newSearchTerm: string) => action('update:search-term')(newSearchTerm); return { // Props args, // Values filtersConfig, filterValues, searchTermField, // Events filterValuesChanged, filterValuesUpdate, searchInput, searchClear, searchTermSync, }; }, template: ` <div style="padding-top: 24px"> <bento-filter-bar v-bind="args" :config="filtersConfig" :search="{width: '800px'}" :searchTerm="searchTermField" :filter-values="filterValues" @input="filterValuesChanged" @update:filter-values="filterValuesUpdate" @update:search-term="searchTermSync" @search-input="searchInput" @search-clear="searchClear" /> </div> `, }), args: { config: FILTERS, filterValues: FILTER_VALUES, containerSizeLayout: { large: 'multi-line', medium: 'one-line', small: 'multi-line', } satisfies BentoFilterBarContainerSizeLayout, search: { inputFieldAriaLabel: 'Input field aria label', debounceTime: 500, hint: 'Hint', placeholder: 'Placeholder content', } satisfies BentoFilterBarSearchBarProps, searchTerm: 'something', }, parameters: defaultFilterTemplate, }; export const Persistent: Story = { ...Default, args: { config: FILTERS_WITH_DEFAULTS, filterValues: [ { field: 'input', value: 'non-default value', }, { field: 'checkbox-group', value: ['unique-id-2'], }, ], searchTerm: null, }, parameters: storybookDocsParameter(FilterBarPersistent), }; export const ExternalFilteringSelectFilter: Story = { render: (_args, { argTypes }) => ({ components: { BentoFilterBar, }, props: Object.keys(argTypes), setup(props) { const apiItems = ref([]); const filterValues = ref<BentoFilterValues>([{ field: 'pokemon', value: 'bulbasaur' }]); const isLoading = ref(false); const getAndSetPokemonFromApi = async (val?: string) => { isLoading.value = true; await new Promise(resolve => setTimeout(resolve, 1000)); const { items } = await getPokemonFromApi(val); apiItems.value = items; isLoading.value = false; }; const search = { debounceTime: 500, searchEvent: getAndSetPokemonFromApi, }; onMounted(() => { getAndSetPokemonFromApi(); }); const filtersConfig = computed(() => [ { field: 'name', label: 'Name', type: BentoFilterItemType.INPUT, }, { field: 'pokemon', label: 'Pokemon', options: { isOptionDisabled: option => ['ivysaur', 'charmeleon'].includes(option.value as string), listboxItems: apiItems.value, search, loading: isLoading.value, }, type: BentoFilterItemType.SELECT, visible: false, }, ]); return { args: isVue2 ? props : _args, // Values filtersConfig, filterValues, }; }, template: ` <div style="padding-top: 24px"> <bento-filter-bar v-bind="args" :config="filtersConfig" :filter-values.sync="filterValues" /> </div> `, }), args: { config: FILTERS, searchTerm: null }, parameters: storybookDocsParameter(FilterBarExternalFiltering), }; export const ExternalFilteringAndLazyLoadingSelectFilter: Story = { ...ExternalFilteringSelectFilter, render: (_args, { argTypes }) => ({ components: { BentoFilterBar, }, props: Object.keys(argTypes), setup(props) { const args = isVue2 ? props : _args; const filterValues = ref<BentoFilterValues>([]); const lazyLoadingPageSize = 20; // State for the select filter const listboxItems = ref<BentoListboxOptions>([]); const isSearchLoading = ref(false); const isLazyLoading = ref(false); const hasMoreItems = ref(true); const currentPage = ref(1); const currentSearchTerm = ref(''); /** * Handles the search event from the filter. * This should reset pagination and fetch a new list of items. */ const onSearch = async (searchTerm: string) => { currentSearchTerm.value = searchTerm; currentPage.value = 1; isSearchLoading.value = true; const { items, hasMore } = await getPokemonFromApi(searchTerm, currentPage.value, lazyLoadingPageSize); listboxItems.value = items; hasMoreItems.value = hasMore; isSearchLoading.value = false; }; /** * Handles the lazy load event. * This should fetch the next page of items and append them. */ const onLazyLoad = async () => { if (isLazyLoading.value || !hasMoreItems.value) { return; } currentPage.value++; isLazyLoading.value = true; const { items, hasMore } = await getPokemonFromApi( currentSearchTerm.value, currentPage.value, lazyLoadingPageSize ); // Append new items to the existing list listboxItems.value = [...listboxItems.value, ...items]; hasMoreItems.value = hasMore; isLazyLoading.value = false; }; // Load initial data when the component mounts onMounted(() => { onSearch(''); }); const filtersConfig = computed<BentoFilterBarModel>(() => [ { field: 'lazy-select', label: 'External filtering and lazy loading', type: BentoFilterItemType.SELECT, options: { listboxItems: listboxItems.value, multiple: true, loading: isSearchLoading.value, // External filtering configuration search: { searchEvent: onSearch, }, // Lazy loading configuration lazy: { hasMoreItems: hasMoreItems.value, lazyLoadType: 'automatic', // or 'button' loading: isLazyLoading.value, showMoreEvent: onLazyLoad, }, }, }, ]); return { args, filtersConfig, filterValues, }; }, template: ` <div style="padding-top: 24px"> <bento-filter-bar v-bind="args" :config="filtersConfig" :filter-values.sync="filterValues" /> </div> `, }), args: { ...ExternalFilteringSelectFilter.args, }, parameters: storybookDocsParameter(FilterBarExternalFilteringAndLazyLoading), }; export const CustomFilters: Story = { render: (_args, { argTypes }) => ({ components: { BentoFilterBar, BentoToggle, FilterBarCustomFilterSubComponentSingleValueExample, FilterBarCustomFilterSubComponentMultiValueExample, }, props: Object.keys(argTypes), template: ` <div style="padding-top: 24px"> <bento-filter-bar :config="filtersConfig" :filter-values-object="filterValues" @update:filter-values-object="filterValuesChanged" /> </div> `, setup() { interface CustomFilterOptions { required?: boolean; optional?: boolean; disabled?: boolean; } const filtersConfig: BentoFilterBarModel<CustomFilterOptions> = [ { field: 'input', label: 'Input filter', type: BentoFilterItemType.INPUT, options: { placeholder: 'Input filter placeholder', }, visible: true, }, { field: 'custom-single', label: 'Custom single value filter', type: FilterBarCustomFilterSubComponentSingleValueExample, visible: true, }, { field: 'custom-multi', label: 'Custom multi value filter', type: FilterBarCustomFilterSubComponentMultiValueExample, options: { // ICustomFilterOptions disabled: false, optional: false, required: false, }, visible: false, }, { field: 'custom-popover', label: 'Custom filter with popover', type: FilterBarCustomFilterSubComponentWithPopoverExample, visible: true, }, ]; /** * Custom filter values */ type FilterKeys = 'input' | 'custom-single' | 'custom-multi' | 'custom-popover'; type CustomFilterValues = | BentoFilterBarValue | boolean | { // ICustomFilterValues first: boolean; second: boolean; } | undefined; const filterValues = ref<BentoFilterValueObject<FilterKeys, CustomFilterValues>>({ input: undefined, 'custom-single': undefined, 'custom-multi': { first: false, second: false, }, 'custom-popover': undefined, }); const filterValuesChanged = (newFilterValues: BentoFilterValueObject<FilterKeys, CustomFilterValues>) => { filterValues.value = newFilterValues; action('input')(newFilterValues); }; return { filtersConfig, filterValues, filterValuesChanged, }; }, }), args: false, parameters: storybookDocsParameter(FilterBarCustomFiltersExample), };
@@ -1 +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, type BentoDatePickerYearRange, } from '@/components/date-picker'; import { type BentoDateRangePickerGranularityConfig, type BentoDateRangePickerValue, type BentoDateRangePickerVariant, type BentoDateRangePickerYearRange, } 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 Since v2.0.0. Do not use. The persistent filter label now uses a counter instead of custom strings. This property is now redundant. * The translation object to handle any custom translations. e.g. persistent filter labels. */ messages?: { [key: string]: { persistentButtonLabelPartiallySelected?: string; persistentButtonLabelAllSelected?: string; [key: string]: string; }; }; /** * @deprecated Since v2.0.0. Do not use. 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 Since v2.0.0. Do not use. 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; yearRange?: BentoDatePickerYearRange; variant?: `${BentoDatePickerVariant}`; tooltipText?: string; tooltipPosition?: BentoTooltipPosition | `${BentoTooltipPosition}`; } export interface BentoDateRangeFilterOptions { allowTimeInput?: boolean; firstDayOfWeek?: BentoDatePickerFirstDayOfWeek; isDateDisabled?: BentoDatePickerIsDateDisabled; yearRange?: BentoDateRangePickerYearRange; 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 Since v2.0.0. 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 Since v2.0.0. Use `config` and `filter-values` instead. */ 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'> & { /** * CSS width value applied to the search box element (e.g. `'200px'`, `'50%'`). */ width?: string; }; export type BentoFilterBarSearchTerm = BentoSearchBarProps['value']; export type BentoFilterBarScreenOptionsLayout = 'one-line' | 'multi-line'; export interface BentoFilterBarContainerSizeLayout { /** * @default 'multi-line' */ large?: BentoFilterBarScreenOptionsLayout; /** * @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) * - Medium (>=430px & <700px) * - Small (<430px) * * @example * { * large: 'multi-line', * 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 Since v2.0.0. Use `config` 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>; }
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, type BentoDatePickerYearRange, } from '@/components/date-picker'; import { type BentoDateRangePickerGranularityConfig, type BentoDateRangePickerValue, type BentoDateRangePickerVariant, type BentoDateRangePickerYearRange, } 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; /** * 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; yearRange?: BentoDatePickerYearRange; variant?: `${BentoDatePickerVariant}`; tooltipText?: string; tooltipPosition?: BentoTooltipPosition | `${BentoTooltipPosition}`; } export interface BentoDateRangeFilterOptions { allowTimeInput?: boolean; firstDayOfWeek?: BentoDatePickerFirstDayOfWeek; isDateDisabled?: BentoDatePickerIsDateDisabled; yearRange?: BentoDateRangePickerYearRange; granularities?: Array<BentoDateRangePickerGranularityConfig>; maxRange?: number; min?: Date; max?: Date; numberOfMonths?: number; showEndDateOnOpen?: boolean; quickSelectRanges?: DateRangePickerCalendarRangeSelectorItems | boolean; timeInterval?: number; variant?: `${BentoDateRangePickerVariant}`; tooltipText?: string; tooltipPosition?: BentoTooltipPosition | `${BentoTooltipPosition}`; } export interface BentoInpuWithDropdowntFilterValue { inputValue: BentoInputFieldValue; dropdownValue: BentoInputDropdownProps['modelValue']; } 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 Since v3.0.0. 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> { field: BentoFilterValue['field']; 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; } /** * A filter model combined with its current value. */ export type BentoFilterBarFilterWithValue = BentoFilterModel & BentoFilterValue; /** * An array of filter models combined with their current values. */ export type BentoFilterBarFiltersWithValues = Array<BentoFilterBarFilterWithValue>; 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'> & { /** * CSS width value applied to the search box element (e.g. `'200px'`, `'50%'`). */ width?: string; }; export type BentoFilterBarSearchTerm = BentoSearchBarProps['value']; export type BentoFilterBarScreenOptionsLayout = 'one-line' | 'multi-line'; export interface BentoFilterBarContainerSizeLayout { /** * @default 'multi-line' */ large?: BentoFilterBarScreenOptionsLayout; /** * @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) * - Medium (>=430px & <700px) * - Small (<430px) * * @example * { * large: 'multi-line', * 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; /** * 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>; }
@@ -1 +1 @@
1
- <template> <div ref="filterBarRef" class="b-filter-bar" :class="rootElementConditionalClasses"> <div v-if="!showSearchbarInsideContainer && isSearchBarEnabled" class="b-filter-bar__search-external-container"> <bento-search-bar class="b-filter-bar__search-bar" v-bind="searchBarProps" :input-field-aria-label="searchBarAriaLabel" :value="searchTerm" condensed :style="searchBarWidthStyle" @input="onSearchbarInput" @clear="onSearchbarClear" /> </div> <component :is="screenLayoutComponent" :class="containerConditionalClasses" condensed> <div :class="contentConditionalClasses"> <div v-if="showSearchbarInsideContainer && isSearchBarEnabled" class="b-filter-bar__search-bar-container" :style="searchBarWidthStyle" > <bento-search-bar class="b-filter-bar__search-bar" v-bind="searchBarProps" :input-field-aria-label="searchBarAriaLabel" :value="searchTerm" condensed @input="onSearchbarInput" @clear="onSearchbarClear" /> </div> <template v-if="persistentFilters.length"> <component :is="filter.type" v-for="filter in persistentFilters" :key="filter.field" :field="filter.field" :label="filter.label" :disabled="filter?.disabled" :options="filter.options" :value="filter.value" :default-value="filter.defaultValue" @update="updateFilterHandler" /> </template> <component :is="filter.type" v-for="filter in regularFilters" :key="filter.field" :field="filter.field" :label="filter.label" :disabled="filter?.disabled" :options="filter.options" :value="filter.value" @update="updateFilterHandler" /> <!-- All filters button --> <all-filters-modal v-if="hasHiddenFilters" class="b-filter-bar__all-filters-modal" :value="dynamicFilters" @input="updateFilterHandler" /> <!-- Clear all filters button --> <filter-bar-button v-if="isClearAllVisible" class="b-filter-bar__clear-all" @click="clearAllFilters"> {{ t('clearFilters') }} <template #iconLeft> <refresh-icon aria-hidden="true" /> </template> </filter-bar-button> <!-- Reset all filters button --> <filter-bar-button v-if="isResetAllVisible" class="b-filter-bar__reset-all" @click="resetAllFilters"> {{ t('resetFilters') }} <template #iconLeft> <refresh-icon aria-hidden="true" /> </template> </filter-bar-button> </div> </component> </div> </template> <script setup lang="ts"> import { computed, ref } from 'vue'; import { FilterBarButton } from './components/filter-bar-button'; import { type BentoFilterBarContainerSizeLayout, BentoFilterBarEvent, type BentoFilterBarModel, type BentoFilterBarProps, type BentoFilterModel, } from './filter-bar.types'; import BentoSearchBar from '@/components/search-bar/search-bar.vue'; import { BentoSearchBarEvent } from '@/components/search-bar/search-bar.types'; import { useBentoToastController } from '@/composables'; import { useI18n } from '@/utils/ts/i18n'; import { useContainerSizeOptionsLayout } from './composables/use-container-size-options-layout/use-container-size-options-layout'; import AllFiltersModal from './components/all-filters-modal/all-filters-modal.vue'; import RefreshIcon from '@adyen/ui-assets-icons-16/vue/refresh'; import messages from './messages.json'; type MessageSchema = (typeof messages)['en-US']; // TODO: setup Vue3 generics in Bento 2.0.0 const props = withDefaults(defineProps<BentoFilterBarProps>(), { config: () => [], filterValues: () => [], containerSizeLayout: null, search: undefined, searchTerm: undefined, value: () => [], showAppliedHiddenFilters: false, filterValuesObject: undefined, }); const emit = defineEmits([ BentoFilterBarEvent.INPUT, BentoFilterBarEvent.UPDATE_FILTER_VALUES_OBJECT, BentoFilterBarEvent.UPDATE_FILTER_VALUES_ARRAY, BentoSearchBarEvent.CLEAR, BentoSearchBarEvent.INPUT, BentoSearchBarEvent.UPDATE, ]); const { t } = useI18n<{ message: MessageSchema }>({ messages }); const filterBarRef = ref<HTMLDivElement>(null); const valueAndFilterValuesPropsMerged = computed<BentoFilterBarModel>(() => (props.config?.length > 0 ? props.config : props.value)?.map(filterModel => { let filterValue = props.filterValues.find(({ field }) => field === filterModel.field)?.value; if (props.filterValuesObject && filterModel.field in props.filterValuesObject) { filterValue = props.filterValuesObject[filterModel.field]; } if (filterValue === undefined) { // If the filter is using the deprecated `config` value prop use that // But if that is also not set then check if a default value exists set it to that if it is defined. filterValue = filterModel?.value === undefined && filterModel?.defaultValue !== undefined ? filterModel?.defaultValue : filterModel?.value; } return { ...filterModel, value: filterValue !== undefined ? filterValue : filterModel?.value }; }) ); const { addToast } = useBentoToastController(); /** * Container size layout. * Allows to change the layout from multi-line to one-line for each container size type: * - large: >=700 * - medium: >=430 & <700 * - small: <430 */ const DEFAULT_CONTAINER_SIZE_LAYOUT: Required<BentoFilterBarContainerSizeLayout> = { large: 'multi-line', medium: 'one-line', small: 'multi-line', }; const mergedContainerSizeLayout = computed<Required<BentoFilterBarContainerSizeLayout>>(() => ({ ...DEFAULT_CONTAINER_SIZE_LAYOUT, ...props.containerSizeLayout, })); const { screenLayoutComponent, showSearchbarInsideContainer, isOneLineLayout, isLargeLayout, rootElementConditionalClasses, } = useContainerSizeOptionsLayout(filterBarRef, mergedContainerSizeLayout); /** * Classes */ /** * Handles the classes of the swappable component ('div' or 'fixed-scroller) */ const containerConditionalClasses = computed(() => ({ 'b-filter-bar__fixed-scroller': isOneLineLayout.value, 'b-filter-bar__container': !isOneLineLayout.value, // Multi-line })); /** * Handles the classes of the container inside the swappable component. */ const contentConditionalClasses = computed(() => ({ 'b-filter-bar__scroller-content': isOneLineLayout.value, 'b-filter-bar__filters': !isOneLineLayout.value, // Multi-line })); /** * Search bar pops */ const searchBarAriaLabel = computed(() => props.search?.inputFieldAriaLabel || (t('searchBar') as string)); const searchBarProps = computed(() => { if (!props.search) { return undefined; } const { debounceTime, disabled, inputFieldAriaLabel, hint, placeholder } = props.search; return { debounceTime, disabled, inputFieldAriaLabel, hint, placeholder }; }); const searchBarWidthStyle = computed(() => props.search?.width && isLargeLayout.value ? { flexBasis: props.search.width, maxWidth: props.search.width } : undefined ); const hasHiddenFilters = computed(() => { // Has props with visible set const hasVisibleSet = valueAndFilterValuesPropsMerged.value.some( ({ visible }) => visible !== undefined && visible !== null ); return hasVisibleSet ? valueAndFilterValuesPropsMerged.value.some(({ visible }) => !visible) : false; }); // Persistent filters i.e. filters with default value const persistentFilters = computed(() => valueAndFilterValuesPropsMerged.value.filter(({ defaultValue }) => defaultValue !== undefined) ); // Dynamic filters i.e. filters without default value const dynamicFilters = computed(() => valueAndFilterValuesPropsMerged.value.filter(({ defaultValue }) => defaultValue === undefined) ); // Show non-persistent & visible filters const regularFilters = computed(() => { const hasVisibleSet = valueAndFilterValuesPropsMerged.value.some( ({ visible }) => visible !== undefined && visible !== null ); // If no filter has the `visible` prop set then all are visible, otherwise search for the "visible: true" elements return hasVisibleSet ? valueAndFilterValuesPropsMerged.value.filter( ({ defaultValue, visible, value }) => // eslint-disable-next-line eqeqeq (defaultValue === undefined && visible) || (props.showAppliedHiddenFilters && value != undefined) ) : valueAndFilterValuesPropsMerged.value.filter( ({ defaultValue, value }) => // eslint-disable-next-line eqeqeq defaultValue === undefined || (props.showAppliedHiddenFilters && value != undefined) ); }); // Enable search bar. // Soft equality to check for "null" and "undefined" // eslint-disable-next-line eqeqeq const isSearchBarEnabled = computed(() => props.searchTerm != undefined); const hasActiveFilters = computed(() => valueAndFilterValuesPropsMerged.value .filter(({ defaultValue }) => defaultValue === undefined || defaultValue === null) // Disabling strict equality to check for both undefined and null // eslint-disable-next-line eqeqeq .some(({ value }) => value != undefined) ); // Show the "Clear all" button (true) if at least one filter is active (contains a value) // and there are not persistent filters present. const isClearAllVisible = computed(() => persistentFilters.value.length === 0 && hasActiveFilters.value); const isResetAllVisible = computed(() => { if (persistentFilters.value.length === 0) { return false; } const areAllValuesEqualToDefaults = valueAndFilterValuesPropsMerged.value .filter(({ defaultValue }) => defaultValue !== undefined || defaultValue !== null) .every(({ value, defaultValue }) => JSON.stringify(value) === JSON.stringify(defaultValue)); return !areAllValuesEqualToDefaults || hasActiveFilters.value; }); const updateAllFilters = (newFilters: BentoFilterBarModel) => { emit(BentoFilterBarEvent.INPUT, newFilters); emit( BentoFilterBarEvent.UPDATE_FILTER_VALUES_ARRAY, newFilters ?.filter(({ value }) => value !== null || value !== undefined) .map(({ field, value }) => ({ field, value })) ); emit( BentoFilterBarEvent.UPDATE_FILTER_VALUES_OBJECT, newFilters ?.filter(({ value }) => value !== null || value !== undefined) .reduce((acc, filter) => { acc[filter.field] = filter.value; return acc; }, {}) ); }; const updateFilterHandler = (updatedFilters: BentoFilterModel | BentoFilterBarModel) => { const filtersArray = Array.isArray(updatedFilters) ? updatedFilters : [updatedFilters]; const updatedFiltersMap = new Map(filtersArray.map(filter => [filter.field, filter.value])); const newFilters = valueAndFilterValuesPropsMerged.value.map(filter => { if (updatedFiltersMap.has(filter.field)) { return { ...filter, value: updatedFiltersMap.get(filter.field) }; } return filter; }); updateAllFilters(newFilters); }; const clearAllFilters = () => { const previousFilters = valueAndFilterValuesPropsMerged.value.slice(); const clearedFilters = valueAndFilterValuesPropsMerged.value.map(({ value, ...otherFields }) => ({ ...otherFields, value: undefined, })); addToast({ text: t('filtersHaveBeenCleared') as string, action: { handler: () => updateAllFilters(previousFilters), text: t('undo') as string, }, }); updateAllFilters(clearedFilters); }; const resetAllFilters = () => { const previousFilters = valueAndFilterValuesPropsMerged.value.slice(); const resetFilters = valueAndFilterValuesPropsMerged.value.map(({ defaultValue, ...otherFields }) => ({ ...otherFields, defaultValue, value: defaultValue, })); addToast({ text: t('filtersHaveBeenReset') as string, action: { handler: () => updateAllFilters(previousFilters), text: t('undo') as string, }, }); updateAllFilters(resetFilters); }; const onSearchbarInput = (newSearchTerm: string) => { emit(BentoSearchBarEvent.INPUT, newSearchTerm); emit(BentoSearchBarEvent.UPDATE, newSearchTerm); }; const onSearchbarClear = () => { emit(BentoSearchBarEvent.CLEAR); }; // Expose methods for BentoDataGrid to access defineExpose({ isClearAllVisible, clearAllFilters, isResetAllVisible, resetAllFilters }); </script> <script lang="ts"> import { BentoBooleanFilter, BentoCheckboxGroupFilter, BentoDateFilter, BentoDateRangeFilter, BentoInputFilter, BentoInputWithDropdownFilter, BentoRadioGroupFilter, BentoRangeFilter, BentoSelectAvatarFilter, BentoSelectCountryFilter, BentoSelectCurrencyFilter, BentoSelectFilter, BentoSelectPaymentMethodFilter, BentoSelectTagFilter, BentoToggleFilter, } from './components'; import { BentoTeleport, FixedScroller } from '@/internal'; /** * The filter bar filters item lists and tables according to various filter criteria. * * @example * import { BentoFilterBar } from '@adyen/bento-vue2' * * export default { * components: { BentoFilterBar }, * data: () => ({ * filters: [{ * field: 'name', * label: 'Name', * value: null, * options: { ... }, * type: FilterType.TEXT, * }] * }), * template: ` * <bento-filter-bar * v-model="filter" * /> * ` * } */ export default { i18n: { messages }, name: 'bento-filter-bar', components: { BentoBooleanFilter, BentoCheckboxGroupFilter, BentoDateFilter, BentoDateRangeFilter, BentoInputFilter, BentoInputWithDropdownFilter, BentoRadioGroupFilter, BentoRangeFilter, BentoSelectAvatarFilter, BentoSelectCountryFilter, BentoSelectCurrencyFilter, BentoSelectFilter, BentoSelectPaymentMethodFilter, BentoSelectTagFilter, BentoTeleport, BentoToggleFilter, FixedScroller, }, }; </script> <style lang="scss" scoped src="./filter-bar.scss" />
1
+ <template> <div ref="filterBarRef" class="b-filter-bar" :class="rootElementConditionalClasses"> <div v-if="!showSearchbarInsideContainer && isSearchBarEnabled" class="b-filter-bar__search-external-container"> <bento-search-bar class="b-filter-bar__search-bar" v-bind="searchBarProps" :input-field-aria-label="searchBarAriaLabel" :value="searchTerm" condensed :style="searchBarWidthStyle" @input="onSearchbarInput" @clear="onSearchbarClear" /> </div> <component :is="screenLayoutComponent" :class="containerConditionalClasses" condensed> <div :class="contentConditionalClasses"> <div v-if="showSearchbarInsideContainer && isSearchBarEnabled" class="b-filter-bar__search-bar-container" :style="searchBarWidthStyle" > <bento-search-bar class="b-filter-bar__search-bar" v-bind="searchBarProps" :input-field-aria-label="searchBarAriaLabel" :value="searchTerm" condensed @input="onSearchbarInput" @clear="onSearchbarClear" /> </div> <template v-if="persistentFilters.length"> <component :is="filter.type" v-for="filter in persistentFilters" :key="filter.field" :field="filter.field" :label="filter.label" :disabled="filter?.disabled" :options="filter.options" :value="filter.value" :default-value="filter.defaultValue" @update="updateFilterHandler" /> </template> <component :is="filter.type" v-for="filter in regularFilters" :key="filter.field" :field="filter.field" :label="filter.label" :disabled="filter?.disabled" :options="filter.options" :value="filter.value" @update="updateFilterHandler" /> <!-- All filters button --> <all-filters-modal v-if="hasHiddenFilters" class="b-filter-bar__all-filters-modal" :value="filtersInAllFiltersModal" @input="updateFilterHandler" /> <!-- Clear all filters button --> <filter-bar-button v-if="isClearAllVisible" class="b-filter-bar__clear-all" @click="clearAllFilters"> {{ t('clearFilters') }} <template #iconLeft> <refresh-icon aria-hidden="true" /> </template> </filter-bar-button> <!-- Reset all filters button --> <filter-bar-button v-if="isResetAllVisible" class="b-filter-bar__reset-all" @click="resetAllFilters"> {{ t('resetFilters') }} <template #iconLeft> <refresh-icon aria-hidden="true" /> </template> </filter-bar-button> </div> </component> </div> </template> <script setup lang="ts"> import { computed, ref } from 'vue'; import { FilterBarButton } from './components/filter-bar-button'; import { type BentoFilterBarContainerSizeLayout, BentoFilterBarEvent, type BentoFilterBarFiltersWithValues, type BentoFilterBarFilterWithValue, type BentoFilterBarProps, } from './filter-bar.types'; import BentoSearchBar from '@/components/search-bar/search-bar.vue'; import { BentoSearchBarEvent } from '@/components/search-bar/search-bar.types'; import { useBentoToastController } from '@/composables'; import { useI18n } from '@/utils/ts/i18n'; import { useContainerSizeOptionsLayout } from './composables/use-container-size-options-layout/use-container-size-options-layout'; import AllFiltersModal from './components/all-filters-modal/all-filters-modal.vue'; import RefreshIcon from '@adyen/ui-assets-icons-16/vue/refresh'; import messages from './messages.json'; type MessageSchema = (typeof messages)['en-US']; // TODO: setup Vue3 generics in Bento 2.0.0 const props = withDefaults(defineProps<BentoFilterBarProps>(), { config: () => [], filterValues: () => [], containerSizeLayout: null, search: undefined, searchTerm: undefined, showAppliedHiddenFilters: false, filterValuesObject: undefined, }); const emit = defineEmits([ BentoFilterBarEvent.INPUT, BentoFilterBarEvent.UPDATE_FILTER_VALUES_OBJECT, BentoFilterBarEvent.UPDATE_FILTER_VALUES_ARRAY, BentoSearchBarEvent.CLEAR, BentoSearchBarEvent.INPUT, BentoSearchBarEvent.UPDATE, ]); const { t } = useI18n<{ message: MessageSchema }>({ messages }); const filterBarRef = ref<HTMLDivElement>(null); const valueAndFilterValuesPropsMerged = computed( () => props.config?.map(filterModel => { let filterValue = props.filterValues.find(({ field }) => field === filterModel.field)?.value; if (props.filterValuesObject && filterModel.field in props.filterValuesObject) { filterValue = props.filterValuesObject[filterModel.field]; } if (filterValue === undefined) { filterValue = filterModel?.defaultValue; } return { ...filterModel, value: filterValue }; }) as BentoFilterBarFiltersWithValues ); const { addToast } = useBentoToastController(); /** * Container size layout. * Allows to change the layout from multi-line to one-line for each container size type: * - large: >=700 * - medium: >=430 & <700 * - small: <430 */ const DEFAULT_CONTAINER_SIZE_LAYOUT: Required<BentoFilterBarContainerSizeLayout> = { large: 'multi-line', medium: 'one-line', small: 'multi-line', }; const mergedContainerSizeLayout = computed<Required<BentoFilterBarContainerSizeLayout>>(() => ({ ...DEFAULT_CONTAINER_SIZE_LAYOUT, ...props.containerSizeLayout, })); const { screenLayoutComponent, showSearchbarInsideContainer, isOneLineLayout, isLargeLayout, rootElementConditionalClasses, } = useContainerSizeOptionsLayout(filterBarRef, mergedContainerSizeLayout); /** * Classes */ /** * Handles the classes of the swappable component ('div' or 'fixed-scroller) */ const containerConditionalClasses = computed(() => ({ 'b-filter-bar__fixed-scroller': isOneLineLayout.value, 'b-filter-bar__container': !isOneLineLayout.value, // Multi-line })); /** * Handles the classes of the container inside the swappable component. */ const contentConditionalClasses = computed(() => ({ 'b-filter-bar__scroller-content': isOneLineLayout.value, 'b-filter-bar__filters': !isOneLineLayout.value, // Multi-line })); /** * Search bar pops */ const searchBarAriaLabel = computed(() => props.search?.inputFieldAriaLabel || (t('searchBar') as string)); const searchBarProps = computed(() => { if (!props.search) { return undefined; } const { debounceTime, disabled, inputFieldAriaLabel, hint, placeholder } = props.search; return { debounceTime, disabled, inputFieldAriaLabel, hint, placeholder }; }); const searchBarWidthStyle = computed(() => props.search?.width && isLargeLayout.value ? { flexBasis: props.search.width, maxWidth: props.search.width } : undefined ); const hasVisibleSet = computed(() => valueAndFilterValuesPropsMerged.value.some(({ visible }) => visible !== undefined && visible !== null) ); const hasHiddenFilters = computed(() => hasVisibleSet.value ? valueAndFilterValuesPropsMerged.value.some(({ visible }) => !visible) : false ); // Persistent filters i.e. filters with default value const persistentFilters = computed(() => valueAndFilterValuesPropsMerged.value.filter( ({ defaultValue, visible }) => defaultValue !== undefined && visible !== false ) ); const filtersInAllFiltersModal = computed(() => valueAndFilterValuesPropsMerged.value.filter( ({ defaultValue, visible }) => defaultValue === undefined || visible === false ) ); const hasPersistentFilters = computed(() => valueAndFilterValuesPropsMerged.value.some(({ defaultValue }) => defaultValue !== undefined) ); // Show non-persistent & visible filters const regularFilters = computed(() => { // If no filter has the `visible` prop set then all are visible, otherwise search for the "visible: true" elements return hasVisibleSet.value ? valueAndFilterValuesPropsMerged.value.filter( ({ defaultValue, visible, value }) => defaultValue === undefined && (visible || (props.showAppliedHiddenFilters && value !== undefined && value !== null)) ) : valueAndFilterValuesPropsMerged.value.filter(({ defaultValue }) => defaultValue === undefined); }); // Enable search bar. // Soft equality to check for "null" and "undefined" // eslint-disable-next-line eqeqeq const isSearchBarEnabled = computed(() => props.searchTerm != undefined); const hasActiveFilters = computed(() => valueAndFilterValuesPropsMerged.value .filter(({ defaultValue }) => defaultValue === undefined || defaultValue === null) // Disabling strict equality to check for both undefined and null // eslint-disable-next-line eqeqeq .some(({ value }) => value != undefined) ); // Show the "Clear all" button (true) if at least one filter is active (contains a value) // and there are not persistent filters present. const isClearAllVisible = computed(() => !hasPersistentFilters.value && hasActiveFilters.value); const isResetAllVisible = computed(() => { if (!hasPersistentFilters.value) { return false; } const areAllValuesEqualToDefaults = valueAndFilterValuesPropsMerged.value .filter(({ defaultValue }) => defaultValue !== undefined || defaultValue !== null) .every(({ value, defaultValue }) => JSON.stringify(value) === JSON.stringify(defaultValue)); return !areAllValuesEqualToDefaults || hasActiveFilters.value; }); const updateAllFilters = (newFilters: BentoFilterBarFiltersWithValues) => { emit(BentoFilterBarEvent.INPUT, newFilters); emit( BentoFilterBarEvent.UPDATE_FILTER_VALUES_ARRAY, newFilters ?.filter(({ value }) => value !== null || value !== undefined) .map(({ field, value }) => ({ field, value })) ); emit( BentoFilterBarEvent.UPDATE_FILTER_VALUES_OBJECT, newFilters ?.filter(({ value }) => value !== null || value !== undefined) .reduce((acc, filter) => { acc[filter.field] = filter.value; return acc; }, {}) ); }; const updateFilterHandler = (updatedFilters: BentoFilterBarFilterWithValue | BentoFilterBarFiltersWithValues) => { const filtersArray = Array.isArray(updatedFilters) ? updatedFilters : [updatedFilters]; const updatedFiltersMap = new Map(filtersArray.map(filter => [filter.field, filter.value])); const newFilters = valueAndFilterValuesPropsMerged.value.map(filter => { if (updatedFiltersMap.has(filter.field)) { return { ...filter, value: updatedFiltersMap.get(filter.field) }; } return filter; }); updateAllFilters(newFilters); }; const clearAllFilters = () => { const previousFilters = valueAndFilterValuesPropsMerged.value.slice(); const clearedFilters = valueAndFilterValuesPropsMerged.value.map(({ value, ...otherFields }) => ({ ...otherFields, value: undefined, })); addToast({ text: t('filtersHaveBeenCleared') as string, action: { handler: () => updateAllFilters(previousFilters), text: t('undo') as string, }, }); updateAllFilters(clearedFilters); }; const resetAllFilters = () => { const previousFilters = valueAndFilterValuesPropsMerged.value.slice(); const resetFilters = valueAndFilterValuesPropsMerged.value.map(({ defaultValue, ...otherFields }) => ({ ...otherFields, defaultValue, value: defaultValue, })); addToast({ text: t('filtersHaveBeenReset') as string, action: { handler: () => updateAllFilters(previousFilters), text: t('undo') as string, }, }); updateAllFilters(resetFilters); }; const onSearchbarInput = (newSearchTerm: string) => { emit(BentoSearchBarEvent.INPUT, newSearchTerm); emit(BentoSearchBarEvent.UPDATE, newSearchTerm); }; const onSearchbarClear = () => { emit(BentoSearchBarEvent.CLEAR); }; // Expose methods for BentoDataGrid to access defineExpose({ isClearAllVisible, clearAllFilters, isResetAllVisible, resetAllFilters }); </script> <script lang="ts"> import { BentoBooleanFilter, BentoCheckboxGroupFilter, BentoDateFilter, BentoDateRangeFilter, BentoInputFilter, BentoInputWithDropdownFilter, BentoRadioGroupFilter, BentoRangeFilter, BentoSelectAvatarFilter, BentoSelectCountryFilter, BentoSelectCurrencyFilter, BentoSelectFilter, BentoSelectPaymentMethodFilter, BentoSelectTagFilter, BentoToggleFilter, } from './components'; import { BentoTeleport, FixedScroller } from '@/internal'; /** * The filter bar filters item lists and tables according to various filter criteria. * * @example * import { BentoFilterBar } from '@adyen/bento-vue2' * * export default { * components: { BentoFilterBar }, * data: () => ({ * filtersConfig: [{ * field: 'name', * label: 'Name', * options: { ... }, * type: FilterType.TEXT, * }], * filterValues: [{ * field: 'name', * value: null, * }], * }), * template: ` * <bento-filter-bar * :config="filtersConfig" * :filter-values="filterValues" * @update:filter-values="filterValues = $event" * /> * ` * } */ export default { i18n: { messages }, name: 'bento-filter-bar', components: { BentoBooleanFilter, BentoCheckboxGroupFilter, BentoDateFilter, BentoDateRangeFilter, BentoInputFilter, BentoInputWithDropdownFilter, BentoRadioGroupFilter, BentoRangeFilter, BentoSelectAvatarFilter, BentoSelectCountryFilter, BentoSelectCurrencyFilter, BentoSelectFilter, BentoSelectPaymentMethodFilter, BentoSelectTagFilter, BentoTeleport, BentoToggleFilter, FixedScroller, }, }; </script> <style lang="scss" scoped src="./filter-bar.scss" />
@@ -11,29 +11,6 @@ The `bento-form-layout` component is a pre-designed layout that serves as a star
11
11
  Form layout is context agnostic and can be dropped in different places, for example in modals, side panels or pages. For
12
12
  page-specific guidance, such as multi-page forms and how to place a form in a page.
13
13
 
14
- ## Table of contents
15
-
16
- - [Use Cases](#use-cases)
17
- - [Do not use](#do-not-use)
18
- - [Variations](#variations)
19
- - [Sections and sub sections](#sections-and-sub-sections)
20
- - [Section title](#section-title)
21
- - [Subsection title](#subsection-title)
22
- - [Modifiers](#modifiers)
23
- - [Render as an HTML "form"](#render-as-an-html-form)
24
- - [Field with tip](#field-with-tip)
25
- - [Type](#type)
26
- - [Group](#group)
27
- - [Dynamic default slot content](#dynamic-default-slot-content)
28
- - [Group-level validation](#group-level-validation)
29
- - [Form actions](#form-actions)
30
- - [Loading](#loading)
31
- - [Validation](#validation)
32
- - [Common use cases](#common-use-cases)
33
- - [Field depends on another field or external reactive data](#field-depends-on-another-field-or-external-reactive-data)
34
- - [Error message depends on external reactive data](#error-message-depends-on-external-reactive-data)
35
- - [Migrating from vue 2 to vue 3](#migrating-from-vue-2-to-vue-3)
36
-
37
14
  <Canvas of={FormLayoutStories.Default} />
38
15
 
39
16
  ## Use Cases
@@ -1 +1 @@
1
- import { computed, ref } from 'vue'; import { isVue2 } from 'vue-demi'; import { storybookDocsParameter } from '@/utils/ts/storybook'; import type { Meta, StoryObj } from '@storybook/vue'; import { BentoButton, BentoLoadingButton, type BentoLoadingButtonState } from '@/components/button'; import { BentoInputField } from '@/components/input-field'; import { BentoDropdown } from '@/components/dropdown'; import { BentoDatePicker } from '@/components/date-picker'; import { BentoTextarea } from '@/components/textarea'; import { BentoCheckboxGroup } from '@/components/checkbox'; import { type BentoCheckboxGroupItems } from '@/components/checkbox/components/checkbox-group/checkbox-group.types'; import { type BentoListboxOptions } from '@/types/listbox'; import { BentoRadioGroup } from '@/components/radio-group'; import { BentoSelectionCardGroup } from '@/components/selection-card/components/selection-card-group'; import { BentoFileUploader } from '@/components/file-uploader'; import { type BentoRadioGroupItems } from '@/components/radio-group/radio-group.types'; import { BentoFormLayoutFieldTip, BentoFormLayoutGroup, BentoFormLayoutTitle } from './components'; import { toTypedSchema, useForm } from '@/composables/use-form-validate'; import { BentoAlert } from '@/components/alert'; import { BentoLink } from '@/components/link'; import BentoFormLayout from './form-layout.vue'; import ChevronLeftIcon from '@adyen/ui-assets-icons-16/vue/chevron-left'; import SaveIcon from '@adyen/ui-assets-icons-16/vue/save'; import { BentoFormLayoutFieldTipTypeOptions } from './components/form-layout-field-tip/form-layout-tip.types'; import * as z from 'zod'; import BentoFormLayoutFieldTipExample from './__tests__/form-layout-field-tip-example.vue?raw'; import BentoFormLayoutDefaultExample from './__tests__/form-layout-default-example.vue?raw'; import BentoFormLayoutValidateExample from './__tests__/form-layout-validate-example.vue?raw'; export default { title: 'Form layout', component: BentoFormLayout, argTypes: { default: { name: 'default', defaultValue: '', description: 'Can include various common form components: `bento-input-field`, `bento-dropdown`, `bento-date-picker`, `bento-checkbox-group`, etc.', table: { type: { summary: 'VNode[]' }, }, control: { disable: true, }, }, 'error-message': { name: 'error-message', defaultValue: '', description: 'Can include any HTMLElement', table: { type: { summary: 'VNode[]' }, }, control: { type: 'text', }, }, }, } as Meta<typeof BentoFormLayout>; type Story = StoryObj<typeof BentoFormLayout>; const DROPDOWN_ITEMS: BentoListboxOptions = [ { label: 'Cat', value: 'cat' }, { label: 'Dog', value: 'dog' }, ]; const CHECKBOX_GROUP_ITEMS: BentoCheckboxGroupItems = [ { value: 1, label: 'Label' }, { value: 2, label: 'Label' }, { value: 3, label: 'Label' }, ]; const RADIO_GROUP_ITEMS: BentoRadioGroupItems = [ { value: 1, label: 'Label' }, { value: 2, label: 'Label' }, { value: 3, label: 'Label' }, ]; export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoDropdown, BentoCheckboxGroup, BentoButton, BentoLoadingButton, BentoDatePicker, BentoInputField, BentoFileUploader, BentoFormLayout, BentoFormLayoutFieldTip, BentoFormLayoutGroup, BentoFormLayoutTitle, BentoRadioGroup, BentoTextarea, BentoSelectionCardGroup, }, props: Object.keys(argTypes), template: ` <bento-form-layout v-bind="args"> <template #error-message>{{ errorMessage }}</template> <bento-form-layout-title el="h1"> Section title <template #description> Optional section description </template> </bento-form-layout-title> <bento-input-field>Input field</bento-input-field> <bento-dropdown label="Dropdown" :items="dropdownItems" placeholder="Select an animal" /> <bento-date-picker label="Date picker" /> <bento-textarea label="Textarea" :character-limit="400" /> <bento-checkbox-group label="Checkbox group" :items="checkboxGroupItems" :hide-label="false" /> <bento-radio-group label="Radio group" :items="radioGroupItems" :hide-label="false" /> <bento-selection-card-group label="Selection card group" :items="selectionCardGroupItems" :hide-label="false" variant="radio" /> <bento-file-uploader label="File upload" description="Custom suppporting text" accept=".png,.jpeg,.pdf" :max-size="5242880" /> <bento-form-layout-field-tip> <bento-dropdown label="Dropdown" :items="dropdownItems" placeholder="Select an animal" /> <template #message>This is a small tip that can be used in small spaces</template> </bento-form-layout-field-tip> <bento-form-layout-title el="h2" subsection> Subsection title </bento-form-layout-title> <bento-form-layout-group> <bento-input-field>Input field (100%)</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="33-33-33"> <bento-input-field>Input field (33%)</bento-input-field> <bento-input-field>Input field (33%)</bento-input-field> <bento-input-field>Input field (33%)</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="50-50"> <bento-input-field>Input field (50%)</bento-input-field> <bento-input-field>Input field (50%)</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="25-75"> <bento-input-field>Input field (25%)</bento-input-field> <bento-input-field>Input field (75%)</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="75-25"> <bento-input-field>Input field (75%)</bento-input-field> <bento-input-field>Input field (25%)</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="33-66"> <bento-input-field>Input field (33%)</bento-input-field> <bento-input-field>Input field (66%)</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="66-33"> <bento-input-field>Input field (66%)</bento-input-field> <bento-input-field>Input field (33%)</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="25"> <bento-input-field>Input field (25%)</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="33"> <bento-input-field>Input field (33%)</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="50"> <bento-input-field>Input field (50%)</bento-input-field> </bento-form-layout-group> <template #actions-left> <bento-button variant="secondary"> Action </bento-button> <bento-button variant="secondary"> Action </bento-button> </template> <template #actions-right> <bento-button variant="secondary"> Action </bento-button> <bento-loading-button> Action </bento-loading-button> </template> </bento-form-layout> `, setup(props) { return { args: isVue2 ? props : _args, errorMessage: 'There is an error. Correct the highlighted field.', dropdownItems: [...DROPDOWN_ITEMS], checkboxGroupItems: [...CHECKBOX_GROUP_ITEMS], radioGroupItems: [...RADIO_GROUP_ITEMS], selectionCardGroupItems: [ { title: 'Card title', value: '1', description: 'Description', }, { title: 'Card title', value: '2', description: 'Description', }, { title: 'Card title', value: '3', description: 'Description', }, { title: 'Card title', value: '4', description: 'Description', }, ], }; }, }), parameters: storybookDocsParameter(BentoFormLayoutDefaultExample), }; export const WithTip: Story = { render: (_args, { argTypes }) => ({ components: { BentoDropdown, BentoFormLayout, BentoFormLayoutFieldTip, }, props: Object.keys(argTypes), template: ` <bento-form-layout v-bind="args"> <bento-form-layout-field-tip :type="args.type" :id="args.id ? args.id : undefined"> <bento-dropdown label="Dropdown" :items="dropdownItems" placeholder="Select an animal" :aria-describedby="args.id" /> <template #message>This is a small tip that can be used in small spaces</template> </bento-form-layout-field-tip> </bento-form-layout> `, setup(props) { return { args: isVue2 ? props : _args, dropdownItems: [...DROPDOWN_ITEMS], }; }, }), argTypes: { type: { options: BentoFormLayoutFieldTipTypeOptions, description: 'Type of a alert; set a distinctive icon and color. Defaults to highlight.', control: { type: 'select', }, table: { category: 'Form Layout Field Tip props', type: { summary: 'string' } }, }, id: { description: 'Unique identifier to the form field. Used for accessibility.', table: { category: 'Form Layout Field Tip props', type: { summary: 'string' } }, }, }, args: { id: 'dropdown_field_with_tip', type: 'highlight' }, parameters: storybookDocsParameter(BentoFormLayoutFieldTipExample), }; const groupCode = ` <bento-form-layout> <bento-form-layout-group> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="50-50"> <bento-input-field>Input field</bento-input-field> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="25-75"> <bento-input-field>Input field</bento-input-field> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="75-25"> <bento-input-field>Input field</bento-input-field> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="33-66"> <bento-input-field>Input field</bento-input-field> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="66-33"> <bento-input-field>Input field</bento-input-field> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="25"> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="33"> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="50"> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="33-33-33"> <bento-input-field>Input field</bento-input-field> <bento-input-field>Input field</bento-input-field> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> </bento-form-layout> `; export const Group: Story = { render: (_args, { argTypes }) => ({ components: { BentoAlert, BentoInputField, BentoFormLayout, BentoFormLayoutGroup, }, props: Object.keys(argTypes), template: ` <div> <bento-alert style="margin-bottom: 20px"> <template #description> The <code>bento-form-layout-group</code> <i>is not</i> a dynamic component and does not support changing it's content/default slot. If this functionality is needed, set the <code>v-if</code> statement <b>on</b> the component itself or around/outside it. </template> </bento-alert> <bento-form-layout v-bind="args"> <bento-form-layout-group> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="50"> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="50-50"> <bento-input-field>Input field</bento-input-field> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="25"> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="25-75"> <bento-input-field>Input field</bento-input-field> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="75-25"> <bento-input-field>Input field</bento-input-field> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="33"> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="33-66"> <bento-input-field>Input field</bento-input-field> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="66-33"> <bento-input-field>Input field</bento-input-field> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="33-33-33"> <bento-input-field>Input field</bento-input-field> <bento-input-field>Input field</bento-input-field> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> </bento-form-layout> </div> `, setup(props) { return { args: isVue2 ? props : _args, dropdownItems: [...DROPDOWN_ITEMS], }; }, }), parameters: storybookDocsParameter(groupCode), }; const loadingFormCode = ` <bento-form-layout loading> <bento-form-layout-title el="h1"> Form request loading time <template #description> Form fields state while waiting for request to be processed </template> </bento-form-layout-title> <bento-input-field required model-value="Input field">Input field</bento-input-field> <template #actions-right> <bento-loading-button state="loading">Loading</bento-loading-button> </template> </bento-form-layout> `; export const LoadingForm: Story = { render: (_args, { argTypes }) => ({ components: { BentoButton, BentoCheckboxGroup, BentoDatePicker, BentoDropdown, BentoFormLayout, BentoFormLayoutGroup, BentoFormLayoutTitle, BentoInputField, BentoLoadingButton, BentoRadioGroup, BentoTextarea, ChevronLeftIcon, SaveIcon, }, props: Object.keys(argTypes), template: ` <bento-form-layout loading> <bento-form-layout-title el="h1"> Form request loading time <template #description> Form fields state while waiting for request to be processed </template> </bento-form-layout-title> <bento-date-picker label="Date picker" optional /> <bento-textarea label="Textarea" :characterLimit="400" model-value="Text area value..." required /> <bento-form-layout-group layout="25-75"> <bento-input-field model-value="Input value" required>Input field</bento-input-field> <bento-dropdown label="Dropdown" :items="[{ label: 'Dropdown value', value: 1 }]" :model-value="1" optional /> </bento-form-layout-group> <bento-radio-group label="Radio group" modelValue="no" optional :items="[ { label: 'No', value: 'no' }, { label: 'Yes', value: 'yes' }, ]" /> <bento-checkbox-group label="Checkbox group" optional :modelValue="['one', 'two']" :items="[ { label: 'One', value: 'one' }, { label: 'Two', value: 'two' }, ]" /> <template v-slot:actions-left> <bento-button variant="secondary"> Back <template v-slot:iconLeft> <chevron-left-icon /> </template> </bento-button> <bento-button variant="secondary"> Save for later <template v-slot:iconLeft> <save-icon /> </template> </bento-button> </template> <template v-slot:actions-right> <bento-loading-button state="loading">Validating</bento-loading-button> </template> </bento-form-layout> `, setup(props) { return { args: isVue2 ? props : _args, }; }, }), parameters: storybookDocsParameter(loadingFormCode), }; export const Validate: Story = { render: (_args, { argTypes }) => ({ components: { BentoInputField, BentoFormLayout, BentoFormLayoutGroup, BentoFormLayoutTitle, BentoLoadingButton, BentoLink, }, props: Object.keys(argTypes), template: ` <bento-form-layout v-bind="args" :loading="isLoading"> <template #error-message v-if="errorArray.length"> There is an error. Correct the highlighted field. <ul> <li v-for="(error, index) in errorArray" :key="index"> <bento-link to="#input-field-name" is-not-routing>{{ error }}</bento-link> </li> </ul> </template> <bento-form-layout-title el="h1"> Form validation <template #description> Form validation using useForm </template> </bento-form-layout-title> <bento-input-field id="input-field-name" :model-value="values.name" :error-message="errors.name" @update:model-value="name => { if (values) { values.name = String(name); } }" > Name </bento-input-field> <bento-form-layout-group layout="50-50" :error-message="errors.email || errors.phone"> <bento-input-field id="input-field-email" :model-value="values.email" :error-message="errors.email" @update:model-value="email => { if (values) { values.email = String(email); } }" > Email </bento-input-field> <bento-input-field id="input-field-phone" :model-value="values.phone" :error-message="errors.phone" @update:model-value="phone => { if (values) { values.phone = String(phone); } }" > Phone </bento-input-field> </bento-form-layout-group> <template #actions-right> <bento-loading-button :state="loadingButtonState" @click="handleSubmit"> Submit form </bento-loading-button> </template> </bento-form-layout> `, setup(props) { const isLoading = ref(false); const loadingButtonState = ref<`${BentoLoadingButtonState}`>('start'); const validationSchema = toTypedSchema( z .object({ name: z .string() .min(3, { message: 'Name is required' }) .startsWith('Jane') .max(4, { message: 'Name cannot be longer than 4 characters' }) .default('Joe'), email: z.string().default(''), phone: z.string().default(''), }) .superRefine((data, ctx) => { if (data.email.length === 0 && data.phone.length === 0) { const issue = { code: z.ZodIssueCode.custom, message: 'At least one of Email or Phone is required', }; ctx.addIssue({ ...issue, path: ['email'] }); } }) ); const { values, errors, submitForm } = useForm({ validationSchema, }); const handleSubmit = submitForm(() => { isLoading.value = true; loadingButtonState.value = 'loading'; // Fake request time setTimeout(() => { isLoading.value = false; loadingButtonState.value = 'success'; }, 2000); }); const errorArray = computed(() => [...new Set(Object.values(errors.value))]); return { args: isVue2 ? props : _args, values, errors, errorArray, loadingButtonState, isLoading, // Methods handleSubmit, }; }, }), parameters: storybookDocsParameter(BentoFormLayoutValidateExample), };
1
+ import { computed, ref } from 'vue'; import { isVue2 } from 'vue-demi'; import { storybookDocsParameter } from '@/utils/ts/storybook'; import type { Meta, StoryObj } from '@storybook/vue'; import { BentoButton, BentoLoadingButton, type BentoLoadingButtonState } from '@/components/button'; import { BentoInputField } from '@/components/input-field'; import { BentoDropdown } from '@/components/dropdown'; import { BentoDatePicker } from '@/components/date-picker'; import { BentoTextarea } from '@/components/textarea'; import { BentoCheckboxGroup } from '@/components/checkbox'; import { type BentoCheckboxGroupItems } from '@/components/checkbox/components/checkbox-group/checkbox-group.types'; import { type BentoListboxOptions } from '@/types/listbox'; import { BentoRadioGroup } from '@/components/radio-group'; import { BentoSelectionCardGroup } from '@/components/selection-card/components/selection-card-group'; import { BentoFileUploader } from '@/components/file-uploader'; import { type BentoRadioGroupItems } from '@/components/radio-group/radio-group.types'; import { BentoFormLayoutFieldTip, BentoFormLayoutGroup, BentoFormLayoutTitle } from './components'; import { toTypedSchema, useForm } from '@/composables/use-form-validate'; import { BentoAlert } from '@/components/alert'; import { BentoLink } from '@/components/link'; import BentoFormLayout from './form-layout.vue'; import ChevronLeftIcon from '@adyen/ui-assets-icons-16/vue/chevron-left'; import SaveIcon from '@adyen/ui-assets-icons-16/vue/save'; import { BentoFormLayoutFieldTipTypeOptions } from './components/form-layout-field-tip/form-layout-tip.types'; import * as z from 'zod'; const BentoFormLayoutFieldTipExample = "<template>\n <bento-form-layout>\n <bento-form-layout-field-tip id=\"dropdown_field_with_tip\" type=\"highlight\">\n <bento-dropdown\n label=\"Dropdown\"\n :items=\"dropdownItems\"\n placeholder=\"Select an animal\"\n aria-describedby=\"dropdown_field_with_tip\"\n />\n <template #message>This is a small tip that can be used in small spaces</template>\n </bento-form-layout-field-tip>\n </bento-form-layout>\n</template>\n\n<script setup lang=\"ts\">\n import { BentoFormLayout, BentoFormLayoutFieldTip, type BentoListboxOptions } from '@adyen/bento-vue2';\n\n const dropdownItems: BentoListboxOptions = [\n { label: 'Cat', value: 'cat' },\n { label: 'Dog', value: 'dog' },\n ];\n</script>\n"; const BentoFormLayoutDefaultExample = "<template>\n <bento-form-layout>\n <template #error-message>{{ errorMessage }}</template>\n\n <bento-form-layout-title el=\"h1\">\n Section title\n <template #description> Optional section description </template>\n </bento-form-layout-title>\n\n <bento-input-field>Input field</bento-input-field>\n\n <bento-dropdown label=\"Dropdown\" :items=\"dropdownItems\" placeholder=\"Select an animal\" />\n\n <bento-date-picker label=\"Date picker\" />\n\n <bento-textarea label=\"Textarea\" :character-limit=\"400\" />\n\n <bento-checkbox-group label=\"Checkbox group\" :items=\"checkboxGroupItems\" :hide-label=\"false\" />\n\n <bento-radio-group label=\"Radio group\" :items=\"radioGroupItems\" :hide-label=\"false\" />\n\n <bento-selection-card-group\n label=\"Selection card group\"\n :items=\"selectionCardGroupItems\"\n :hide-label=\"false\"\n variant=\"radio\"\n />\n\n <bento-file-uploader\n label=\"File upload\"\n description=\"Custom suppporting text\"\n accept=\".png,.jpeg,.pdf\"\n :max-size=\"5242880\"\n />\n\n <bento-form-layout-field-tip>\n <bento-dropdown label=\"Dropdown\" :items=\"dropdownItems\" placeholder=\"Select an animal\" />\n <template #message>This is a small tip that can be used in small spaces</template>\n </bento-form-layout-field-tip>\n\n <bento-form-layout-title el=\"h2\" subsection> Subsection title </bento-form-layout-title>\n\n <bento-form-layout-group>\n <bento-input-field>Input field (100%)</bento-input-field>\n </bento-form-layout-group>\n\n <bento-form-layout-group layout=\"33-33-33\">\n <bento-input-field>Input field (33%)</bento-input-field>\n <bento-input-field>Input field (33%)</bento-input-field>\n <bento-input-field>Input field (33%)</bento-input-field>\n </bento-form-layout-group>\n\n <bento-form-layout-group layout=\"50-50\">\n <bento-input-field>Input field (50%)</bento-input-field>\n <bento-input-field>Input field (50%)</bento-input-field>\n </bento-form-layout-group>\n\n <bento-form-layout-group layout=\"25-75\">\n <bento-input-field>Input field (25%)</bento-input-field>\n <bento-input-field>Input field (75%)</bento-input-field>\n </bento-form-layout-group>\n\n <bento-form-layout-group layout=\"75-25\">\n <bento-input-field>Input field (75%)</bento-input-field>\n <bento-input-field>Input field (25%)</bento-input-field>\n </bento-form-layout-group>\n\n <bento-form-layout-group layout=\"33-66\">\n <bento-input-field>Input field (33%)</bento-input-field>\n <bento-input-field>Input field (66%)</bento-input-field>\n </bento-form-layout-group>\n\n <bento-form-layout-group layout=\"66-33\">\n <bento-input-field>Input field (66%)</bento-input-field>\n <bento-input-field>Input field (33%)</bento-input-field>\n </bento-form-layout-group>\n\n <bento-form-layout-group layout=\"25\">\n <bento-input-field>Input field (25%)</bento-input-field>\n </bento-form-layout-group>\n\n <bento-form-layout-group layout=\"33\">\n <bento-input-field>Input field (33%)</bento-input-field>\n </bento-form-layout-group>\n\n <bento-form-layout-group layout=\"50\">\n <bento-input-field>Input field (50%)</bento-input-field>\n </bento-form-layout-group>\n\n <template #actions-left>\n <bento-button variant=\"secondary\"> Action </bento-button>\n <bento-button variant=\"secondary\"> Action </bento-button>\n </template>\n <template #actions-right>\n <bento-button variant=\"secondary\"> Action </bento-button>\n <bento-loading-button> Action </bento-loading-button>\n </template>\n </bento-form-layout>\n</template>\n\n<script setup lang=\"ts\">\n import {\n BentoButton,\n BentoCheckboxGroup,\n type BentoCheckboxGroupItems,\n BentoDatePicker,\n BentoDropdown,\n BentoFileUploader,\n BentoFormLayout,\n BentoFormLayoutFieldTip,\n BentoFormLayoutGroup,\n BentoFormLayoutTitle,\n BentoInputField,\n type BentoListboxOptions,\n BentoLoadingButton,\n BentoRadioGroup,\n type BentoRadioGroupItems,\n BentoSelectionCardGroup,\n type BentoSelectionCardItem,\n BentoTextarea,\n } from '@adyen/bento-vue2';\n\n const errorMessage = 'There is an error. Correct the highlighted field.';\n\n const dropdownItems: BentoListboxOptions = [\n { label: 'Cat', value: 'cat' },\n { label: 'Dog', value: 'dog' },\n ];\n\n const checkboxGroupItems: BentoCheckboxGroupItems = [\n { value: 1, label: 'Label' },\n { value: 2, label: 'Label' },\n { value: 3, label: 'Label' },\n ];\n\n const radioGroupItems: BentoRadioGroupItems = [\n { value: 1, label: 'Label' },\n { value: 2, label: 'Label' },\n { value: 3, label: 'Label' },\n ];\n\n const selectionCardGroupItems: Array<BentoSelectionCardItem> = [\n {\n title: 'Card title',\n value: 1,\n description: 'Description',\n },\n {\n title: 'Card title',\n value: 2,\n description: 'Description',\n },\n {\n title: 'Card title',\n value: 3,\n description: 'Description',\n },\n {\n title: 'Card title',\n value: 4,\n description: 'Description',\n },\n ];\n</script>\n"; const BentoFormLayoutValidateExample = "<template>\n <bento-form-layout :loading=\"isLoading\">\n <template v-if=\"errorArray.length\" #error-message>\n There is an error. Correct the highlighted field.\n <ul>\n <li v-for=\"(error, index) in errorArray\" :key=\"index\">\n <bento-link to=\"#input-field-name\" is-not-routing>{{ error }}</bento-link>\n </li>\n </ul>\n </template>\n\n <bento-form-layout-title el=\"h1\">\n Form validation\n\n <template #description> Form validation using useForm </template>\n </bento-form-layout-title>\n\n <bento-input-field\n id=\"input-field-name\"\n :model-value=\"values.name\"\n :error-message=\"errors.name\"\n @update:model-value=\"\n name => {\n if (values) {\n values.name = String(name);\n }\n }\n \"\n >\n Name\n </bento-input-field>\n\n <bento-form-layout-group layout=\"50-50\" :error-message=\"errors.email || errors.phone\">\n <bento-input-field\n id=\"input-field-email\"\n :model-value=\"values.email\"\n :error-message=\"errors.email\"\n @update:model-value=\"\n email => {\n if (values) {\n values.email = String(email);\n }\n }\n \"\n >\n Email\n </bento-input-field>\n\n <bento-input-field\n id=\"input-field-phone\"\n :model-value=\"values.phone\"\n :error-message=\"errors.phone\"\n @update:model-value=\"\n phone => {\n if (values) {\n values.phone = String(phone);\n }\n }\n \"\n >\n Phone\n </bento-input-field>\n </bento-form-layout-group>\n\n <template #actions-right>\n <bento-loading-button :state=\"loadingButtonState\" @click=\"handleSubmit\"> Submit form </bento-loading-button>\n </template>\n </bento-form-layout>\n</template>\n\n<script setup lang=\"ts\">\n import { computed, ref } from 'vue';\n import {\n BentoFormLayout,\n BentoFormLayoutGroup,\n BentoFormLayoutTitle,\n BentoInputField,\n BentoLink,\n BentoLoadingButton,\n type BentoLoadingButtonState,\n toTypedSchema,\n useForm,\n } from '@adyen/bento-vue2';\n import { z } from 'zod';\n\n const isLoading = ref(false);\n const loadingButtonState = ref<`${BentoLoadingButtonState}`>('start');\n\n const { values, errors, submitForm } = useForm({\n validationSchema: toTypedSchema(\n z\n .object({\n name: z\n .string()\n .min(3, { message: 'Name is required' })\n .startsWith('Jane')\n .max(4, { message: 'Name cannot be longer than 4 characters' })\n .default('Joe'),\n email: z.string().default(''),\n phone: z.string().default(''),\n })\n .superRefine((data, ctx) => {\n if (data.email.length === 0 && data.phone.length === 0) {\n const issue = {\n code: z.ZodIssueCode.custom,\n message: 'At least one of Email or Phone is required',\n };\n\n ctx.addIssue({ ...issue, path: ['email'] });\n }\n })\n ),\n });\n\n const errorArray = computed(() => [...new Set(Object.values(errors.value))]);\n\n const handleSubmit = submitForm(() => {\n isLoading.value = true;\n loadingButtonState.value = 'loading';\n\n // Fake request time\n setTimeout(() => {\n isLoading.value = false;\n loadingButtonState.value = 'success';\n }, 2000);\n });\n</script>\n"; const FormLayoutGroup = "<template>\n <bento-form-layout>\n <bento-form-layout-group>\n <bento-input-field>Input field</bento-input-field>\n </bento-form-layout-group>\n\n <bento-form-layout-group layout=\"50-50\">\n <bento-input-field>Input field</bento-input-field>\n <bento-input-field>Input field</bento-input-field>\n </bento-form-layout-group>\n\n <bento-form-layout-group layout=\"25-75\">\n <bento-input-field>Input field</bento-input-field>\n <bento-input-field>Input field</bento-input-field>\n </bento-form-layout-group>\n\n <bento-form-layout-group layout=\"75-25\">\n <bento-input-field>Input field</bento-input-field>\n <bento-input-field>Input field</bento-input-field>\n </bento-form-layout-group>\n\n <bento-form-layout-group layout=\"33-66\">\n <bento-input-field>Input field</bento-input-field>\n <bento-input-field>Input field</bento-input-field>\n </bento-form-layout-group>\n\n <bento-form-layout-group layout=\"66-33\">\n <bento-input-field>Input field</bento-input-field>\n <bento-input-field>Input field</bento-input-field>\n </bento-form-layout-group>\n\n <bento-form-layout-group layout=\"25\">\n <bento-input-field>Input field</bento-input-field>\n </bento-form-layout-group>\n\n <bento-form-layout-group layout=\"33\">\n <bento-input-field>Input field</bento-input-field>\n </bento-form-layout-group>\n\n <bento-form-layout-group layout=\"50\">\n <bento-input-field>Input field</bento-input-field>\n </bento-form-layout-group>\n\n <bento-form-layout-group layout=\"33-33-33\">\n <bento-input-field>Input field</bento-input-field>\n <bento-input-field>Input field</bento-input-field>\n <bento-input-field>Input field</bento-input-field>\n </bento-form-layout-group>\n </bento-form-layout>\n</template>\n\n<script setup lang=\"ts\">\n import { BentoFormLayout, BentoFormLayoutGroup, BentoInputField } from '@adyen/bento-vue2';\n</script>\n"; const FormLayoutLoading = "<template>\n <bento-form-layout loading>\n <bento-form-layout-title el=\"h1\">\n Form request loading time\n <template #description>Form fields state while waiting for request to be processed</template>\n </bento-form-layout-title>\n\n <bento-date-picker label=\"Date picker\" optional />\n <bento-textarea :character-limit=\"400\" label=\"Textarea\" model-value=\"Text area value...\" required />\n\n <bento-form-layout-group layout=\"25-75\">\n <bento-input-field model-value=\"Input value\" required>Input field</bento-input-field>\n <bento-dropdown :items=\"dropdownItems\" label=\"Dropdown\" :model-value=\"1\" optional />\n </bento-form-layout-group>\n\n <bento-radio-group :items=\"radioItems\" label=\"Radio group\" model-value=\"no\" optional />\n <bento-checkbox-group :items=\"checkboxItems\" label=\"Checkbox group\" :model-value=\"['one', 'two']\" optional />\n\n <template #actions-left>\n <bento-button variant=\"secondary\">\n Back\n <template #iconLeft><chevron-left-icon /></template>\n </bento-button>\n <bento-button variant=\"secondary\">\n Save for later\n <template #iconLeft><save-icon /></template>\n </bento-button>\n </template>\n <template #actions-right>\n <bento-loading-button state=\"loading\">Validating</bento-loading-button>\n </template>\n </bento-form-layout>\n</template>\n\n<script setup lang=\"ts\">\n import {\n BentoButton,\n BentoCheckboxGroup,\n type BentoCheckboxGroupItems,\n BentoDatePicker,\n BentoDropdown,\n BentoFormLayout,\n BentoFormLayoutGroup,\n BentoFormLayoutTitle,\n BentoInputField,\n type BentoListboxOptions,\n BentoLoadingButton,\n BentoRadioGroup,\n type BentoRadioGroupItems,\n BentoTextarea,\n } from '@adyen/bento-vue2';\n import ChevronLeftIcon from '@adyen/ui-assets-icons-16/vue/chevron-left';\n import SaveIcon from '@adyen/ui-assets-icons-16/vue/save';\n\n const checkboxItems: BentoCheckboxGroupItems = [\n { label: 'One', value: 'one' },\n { label: 'Two', value: 'two' },\n ];\n const dropdownItems: BentoListboxOptions = [{ label: 'Dropdown value', value: 1 }];\n const radioItems: BentoRadioGroupItems = [\n { label: 'No', value: 'no' },\n { label: 'Yes', value: 'yes' },\n ];\n</script>\n"; export default { title: 'Form layout', component: BentoFormLayout, argTypes: { default: { name: 'default', defaultValue: '', description: 'Can include various common form components: `bento-input-field`, `bento-dropdown`, `bento-date-picker`, `bento-checkbox-group`, etc.', table: { type: { summary: 'VNode[]' }, }, control: { disable: true, }, }, 'error-message': { name: 'error-message', defaultValue: '', description: 'Can include any HTMLElement', table: { type: { summary: 'VNode[]' }, }, control: { type: 'text', }, }, }, } as Meta<typeof BentoFormLayout>; type Story = StoryObj<typeof BentoFormLayout>; const DROPDOWN_ITEMS: BentoListboxOptions = [ { label: 'Cat', value: 'cat' }, { label: 'Dog', value: 'dog' }, ]; const CHECKBOX_GROUP_ITEMS: BentoCheckboxGroupItems = [ { value: 1, label: 'Label' }, { value: 2, label: 'Label' }, { value: 3, label: 'Label' }, ]; const RADIO_GROUP_ITEMS: BentoRadioGroupItems = [ { value: 1, label: 'Label' }, { value: 2, label: 'Label' }, { value: 3, label: 'Label' }, ]; export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoDropdown, BentoCheckboxGroup, BentoButton, BentoLoadingButton, BentoDatePicker, BentoInputField, BentoFileUploader, BentoFormLayout, BentoFormLayoutFieldTip, BentoFormLayoutGroup, BentoFormLayoutTitle, BentoRadioGroup, BentoTextarea, BentoSelectionCardGroup, }, props: Object.keys(argTypes), template: ` <bento-form-layout v-bind="args"> <template #error-message>{{ errorMessage }}</template> <bento-form-layout-title el="h1"> Section title <template #description> Optional section description </template> </bento-form-layout-title> <bento-input-field>Input field</bento-input-field> <bento-dropdown label="Dropdown" :items="dropdownItems" placeholder="Select an animal" /> <bento-date-picker label="Date picker" /> <bento-textarea label="Textarea" :character-limit="400" /> <bento-checkbox-group label="Checkbox group" :items="checkboxGroupItems" :hide-label="false" /> <bento-radio-group label="Radio group" :items="radioGroupItems" :hide-label="false" /> <bento-selection-card-group label="Selection card group" :items="selectionCardGroupItems" :hide-label="false" variant="radio" /> <bento-file-uploader label="File upload" description="Custom suppporting text" accept=".png,.jpeg,.pdf" :max-size="5242880" /> <bento-form-layout-field-tip> <bento-dropdown label="Dropdown" :items="dropdownItems" placeholder="Select an animal" /> <template #message>This is a small tip that can be used in small spaces</template> </bento-form-layout-field-tip> <bento-form-layout-title el="h2" subsection> Subsection title </bento-form-layout-title> <bento-form-layout-group> <bento-input-field>Input field (100%)</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="33-33-33"> <bento-input-field>Input field (33%)</bento-input-field> <bento-input-field>Input field (33%)</bento-input-field> <bento-input-field>Input field (33%)</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="50-50"> <bento-input-field>Input field (50%)</bento-input-field> <bento-input-field>Input field (50%)</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="25-75"> <bento-input-field>Input field (25%)</bento-input-field> <bento-input-field>Input field (75%)</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="75-25"> <bento-input-field>Input field (75%)</bento-input-field> <bento-input-field>Input field (25%)</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="33-66"> <bento-input-field>Input field (33%)</bento-input-field> <bento-input-field>Input field (66%)</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="66-33"> <bento-input-field>Input field (66%)</bento-input-field> <bento-input-field>Input field (33%)</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="25"> <bento-input-field>Input field (25%)</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="33"> <bento-input-field>Input field (33%)</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="50"> <bento-input-field>Input field (50%)</bento-input-field> </bento-form-layout-group> <template #actions-left> <bento-button variant="secondary"> Action </bento-button> <bento-button variant="secondary"> Action </bento-button> </template> <template #actions-right> <bento-button variant="secondary"> Action </bento-button> <bento-loading-button> Action </bento-loading-button> </template> </bento-form-layout> `, setup(props) { return { args: isVue2 ? props : _args, errorMessage: 'There is an error. Correct the highlighted field.', dropdownItems: [...DROPDOWN_ITEMS], checkboxGroupItems: [...CHECKBOX_GROUP_ITEMS], radioGroupItems: [...RADIO_GROUP_ITEMS], selectionCardGroupItems: [ { title: 'Card title', value: '1', description: 'Description', }, { title: 'Card title', value: '2', description: 'Description', }, { title: 'Card title', value: '3', description: 'Description', }, { title: 'Card title', value: '4', description: 'Description', }, ], }; }, }), parameters: storybookDocsParameter(BentoFormLayoutDefaultExample), }; export const WithTip: Story = { render: (_args, { argTypes }) => ({ components: { BentoDropdown, BentoFormLayout, BentoFormLayoutFieldTip, }, props: Object.keys(argTypes), template: ` <bento-form-layout v-bind="args"> <bento-form-layout-field-tip :type="args.type" :id="args.id ? args.id : undefined"> <bento-dropdown label="Dropdown" :items="dropdownItems" placeholder="Select an animal" :aria-describedby="args.id" /> <template #message>This is a small tip that can be used in small spaces</template> </bento-form-layout-field-tip> </bento-form-layout> `, setup(props) { return { args: isVue2 ? props : _args, dropdownItems: [...DROPDOWN_ITEMS], }; }, }), argTypes: { type: { options: BentoFormLayoutFieldTipTypeOptions, description: 'Type of a alert; set a distinctive icon and color. Defaults to highlight.', control: { type: 'select', }, table: { category: 'Form Layout Field Tip props', type: { summary: 'string' } }, }, id: { description: 'Unique identifier to the form field. Used for accessibility.', table: { category: 'Form Layout Field Tip props', type: { summary: 'string' } }, }, }, args: { id: 'dropdown_field_with_tip', type: 'highlight' }, parameters: storybookDocsParameter(BentoFormLayoutFieldTipExample), }; export const Group: Story = { render: (_args, { argTypes }) => ({ components: { BentoAlert, BentoInputField, BentoFormLayout, BentoFormLayoutGroup, }, props: Object.keys(argTypes), template: ` <div> <bento-alert style="margin-bottom: 20px"> <template #description> The <code>bento-form-layout-group</code> <i>is not</i> a dynamic component and does not support changing it's content/default slot. If this functionality is needed, set the <code>v-if</code> statement <b>on</b> the component itself or around/outside it. </template> </bento-alert> <bento-form-layout v-bind="args"> <bento-form-layout-group> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="50"> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="50-50"> <bento-input-field>Input field</bento-input-field> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="25"> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="25-75"> <bento-input-field>Input field</bento-input-field> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="75-25"> <bento-input-field>Input field</bento-input-field> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="33"> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="33-66"> <bento-input-field>Input field</bento-input-field> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="66-33"> <bento-input-field>Input field</bento-input-field> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="33-33-33"> <bento-input-field>Input field</bento-input-field> <bento-input-field>Input field</bento-input-field> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> </bento-form-layout> </div> `, setup(props) { return { args: isVue2 ? props : _args, dropdownItems: [...DROPDOWN_ITEMS], }; }, }), parameters: storybookDocsParameter(FormLayoutGroup), }; export const LoadingForm: Story = { render: (_args, { argTypes }) => ({ components: { BentoButton, BentoCheckboxGroup, BentoDatePicker, BentoDropdown, BentoFormLayout, BentoFormLayoutGroup, BentoFormLayoutTitle, BentoInputField, BentoLoadingButton, BentoRadioGroup, BentoTextarea, ChevronLeftIcon, SaveIcon, }, props: Object.keys(argTypes), template: ` <bento-form-layout loading> <bento-form-layout-title el="h1"> Form request loading time <template #description> Form fields state while waiting for request to be processed </template> </bento-form-layout-title> <bento-date-picker label="Date picker" optional /> <bento-textarea label="Textarea" :characterLimit="400" model-value="Text area value..." required /> <bento-form-layout-group layout="25-75"> <bento-input-field model-value="Input value" required>Input field</bento-input-field> <bento-dropdown label="Dropdown" :items="[{ label: 'Dropdown value', value: 1 }]" :model-value="1" optional /> </bento-form-layout-group> <bento-radio-group label="Radio group" modelValue="no" optional :items="[ { label: 'No', value: 'no' }, { label: 'Yes', value: 'yes' }, ]" /> <bento-checkbox-group label="Checkbox group" optional :modelValue="['one', 'two']" :items="[ { label: 'One', value: 'one' }, { label: 'Two', value: 'two' }, ]" /> <template v-slot:actions-left> <bento-button variant="secondary"> Back <template v-slot:iconLeft> <chevron-left-icon /> </template> </bento-button> <bento-button variant="secondary"> Save for later <template v-slot:iconLeft> <save-icon /> </template> </bento-button> </template> <template v-slot:actions-right> <bento-loading-button state="loading">Validating</bento-loading-button> </template> </bento-form-layout> `, setup(props) { return { args: isVue2 ? props : _args, }; }, }), parameters: storybookDocsParameter(FormLayoutLoading), }; export const Validate: Story = { render: (_args, { argTypes }) => ({ components: { BentoInputField, BentoFormLayout, BentoFormLayoutGroup, BentoFormLayoutTitle, BentoLoadingButton, BentoLink, }, props: Object.keys(argTypes), template: ` <bento-form-layout v-bind="args" :loading="isLoading"> <template #error-message v-if="errorArray.length"> There is an error. Correct the highlighted field. <ul> <li v-for="(error, index) in errorArray" :key="index"> <bento-link to="#input-field-name" is-not-routing>{{ error }}</bento-link> </li> </ul> </template> <bento-form-layout-title el="h1"> Form validation <template #description> Form validation using useForm </template> </bento-form-layout-title> <bento-input-field id="input-field-name" :model-value="values.name" :error-message="errors.name" @update:model-value="name => { if (values) { values.name = String(name); } }" > Name </bento-input-field> <bento-form-layout-group layout="50-50" :error-message="errors.email || errors.phone"> <bento-input-field id="input-field-email" :model-value="values.email" :error-message="errors.email" @update:model-value="email => { if (values) { values.email = String(email); } }" > Email </bento-input-field> <bento-input-field id="input-field-phone" :model-value="values.phone" :error-message="errors.phone" @update:model-value="phone => { if (values) { values.phone = String(phone); } }" > Phone </bento-input-field> </bento-form-layout-group> <template #actions-right> <bento-loading-button :state="loadingButtonState" @click="handleSubmit"> Submit form </bento-loading-button> </template> </bento-form-layout> `, setup(props) { const isLoading = ref(false); const loadingButtonState = ref<`${BentoLoadingButtonState}`>('start'); const validationSchema = toTypedSchema( z .object({ name: z .string() .min(3, { message: 'Name is required' }) .startsWith('Jane') .max(4, { message: 'Name cannot be longer than 4 characters' }) .default('Joe'), email: z.string().default(''), phone: z.string().default(''), }) .superRefine((data, ctx) => { if (data.email.length === 0 && data.phone.length === 0) { const issue = { code: z.ZodIssueCode.custom, message: 'At least one of Email or Phone is required', }; ctx.addIssue({ ...issue, path: ['email'] }); } }) ); const { values, errors, submitForm } = useForm({ validationSchema, }); const handleSubmit = submitForm(() => { isLoading.value = true; loadingButtonState.value = 'loading'; // Fake request time setTimeout(() => { isLoading.value = false; loadingButtonState.value = 'success'; }, 2000); }); const errorArray = computed(() => [...new Set(Object.values(errors.value))]); return { args: isVue2 ? props : _args, values, errors, errorArray, loadingButtonState, isLoading, // Methods handleSubmit, }; }, }), parameters: storybookDocsParameter(BentoFormLayoutValidateExample), };