@adyen/bento-mcp 0.0.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (410) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +21 -0
  3. package/README.md +113 -0
  4. package/dist/assets/components/accordion/accordion.docs.mdx +77 -0
  5. package/dist/assets/components/accordion/accordion.stories.ts +1 -0
  6. package/dist/assets/components/accordion/accordion.vue +1 -0
  7. package/dist/assets/components/accordion/components/accordion-item.stories.ts +1 -0
  8. package/dist/assets/components/accordion/components/accordion-item.vue +1 -0
  9. package/dist/assets/components/action-bar/action-bar.docs.mdx +103 -0
  10. package/dist/assets/components/action-bar/action-bar.stories.ts +1 -0
  11. package/dist/assets/components/action-bar/action-bar.vue +1 -0
  12. package/dist/assets/components/alert/alert.docs.mdx +106 -0
  13. package/dist/assets/components/alert/alert.stories.ts +1 -0
  14. package/dist/assets/components/alert/alert.vue +1 -0
  15. package/dist/assets/components/avatar/avatar.docs.mdx +44 -0
  16. package/dist/assets/components/avatar/avatar.stories.ts +1 -0
  17. package/dist/assets/components/avatar/avatar.vue +1 -0
  18. package/dist/assets/components/avatar/components/avatar-image/avatar-image.vue +1 -0
  19. package/dist/assets/components/button/button.docs.mdx +137 -0
  20. package/dist/assets/components/button/button.stories.ts +1 -0
  21. package/dist/assets/components/button/button.vue +1 -0
  22. package/dist/assets/components/button/components/ai-button/ai-button.docs.mdx +40 -0
  23. package/dist/assets/components/button/components/ai-button/ai-button.stories.ts +1 -0
  24. package/dist/assets/components/button/components/ai-button/ai-button.vue +1 -0
  25. package/dist/assets/components/button/components/button-actions/button-actions.docs.mdx +169 -0
  26. package/dist/assets/components/button/components/button-actions/button-actions.stories.ts +1 -0
  27. package/dist/assets/components/button/components/button-actions/button-actions.vue +1 -0
  28. package/dist/assets/components/button/components/loading-button/loading-button.docs.mdx +106 -0
  29. package/dist/assets/components/button/components/loading-button/loading-button.stories.ts +1 -0
  30. package/dist/assets/components/button/components/loading-button/loading-button.vue +1 -0
  31. package/dist/assets/components/button/components/toggle-button/toggle-button.docs.mdx +81 -0
  32. package/dist/assets/components/button/components/toggle-button/toggle-button.stories.ts +1 -0
  33. package/dist/assets/components/button/components/toggle-button/toggle-button.vue +1 -0
  34. package/dist/assets/components/card/card.docs.mdx +166 -0
  35. package/dist/assets/components/card/card.stories.ts +1 -0
  36. package/dist/assets/components/card/card.vue +1 -0
  37. package/dist/assets/components/checkbox/checkbox.docs.mdx +116 -0
  38. package/dist/assets/components/checkbox/checkbox.stories.ts +1 -0
  39. package/dist/assets/components/checkbox/checkbox.vue +1 -0
  40. package/dist/assets/components/checkbox/components/checkbox-group/checkbox-group.docs.mdx +134 -0
  41. package/dist/assets/components/checkbox/components/checkbox-group/checkbox-group.stories.ts +1 -0
  42. package/dist/assets/components/checkbox/components/checkbox-group/checkbox-group.vue +1 -0
  43. package/dist/assets/components/chip/chip.docs.mdx +51 -0
  44. package/dist/assets/components/chip/chip.stories.ts +1 -0
  45. package/dist/assets/components/chip/chip.vue +1 -0
  46. package/dist/assets/components/click-outside/click-outside.docs.mdx +30 -0
  47. package/dist/assets/components/click-outside/click-outside.stories.ts +1 -0
  48. package/dist/assets/components/code-snippet/code-snippet.docs.mdx +73 -0
  49. package/dist/assets/components/code-snippet/code-snippet.stories.ts +1 -0
  50. package/dist/assets/components/code-snippet/code-snippet.vue +1 -0
  51. package/dist/assets/components/country/country.docs.mdx +61 -0
  52. package/dist/assets/components/country/country.stories.ts +1 -0
  53. package/dist/assets/components/country/country.vue +1 -0
  54. package/dist/assets/components/currency/currency.docs.mdx +38 -0
  55. package/dist/assets/components/currency/currency.stories.ts +1 -0
  56. package/dist/assets/components/currency/currency.vue +1 -0
  57. package/dist/assets/components/dashed-underline/dashed-underline.docs.mdx +57 -0
  58. package/dist/assets/components/dashed-underline/dashed-underline.stories.ts +1 -0
  59. package/dist/assets/components/dashed-underline/dashed-underline.vue +1 -0
  60. package/dist/assets/components/data-grid/components/data-grid-cell/data-grid-cell.vue +1 -0
  61. package/dist/assets/components/data-grid/components/data-grid-cell-actions/data-grid-cell-actions.vue +1 -0
  62. package/dist/assets/components/data-grid/components/data-grid-columns/data-grid-columns.vue +1 -0
  63. package/dist/assets/components/data-grid/components/data-grid-config-settings/components/data-grid-config-settings-column-row/data-grid-config-settings-column-row.vue +1 -0
  64. package/dist/assets/components/data-grid/components/data-grid-config-settings/data-grid-config-settings.vue +1 -0
  65. package/dist/assets/components/data-grid/components/data-grid-lazy-load/data-grid-lazy-load.vue +1 -0
  66. package/dist/assets/components/data-grid/components/data-grid-row/data-grid-row.vue +1 -0
  67. package/dist/assets/components/data-grid/components/data-grid-static-icon/data-grid-static-icon.docs.mdx +61 -0
  68. package/dist/assets/components/data-grid/components/data-grid-static-icon/data-grid-static-icon.stories.ts +1 -0
  69. package/dist/assets/components/data-grid/components/data-grid-static-icon/data-grid-static-icon.vue +1 -0
  70. package/dist/assets/components/data-grid/components/data-grid-summary-cell/data-grid-summary-cell.docs.mdx +56 -0
  71. package/dist/assets/components/data-grid/components/data-grid-summary-cell/data-grid-summary-cell.stories.ts +1 -0
  72. package/dist/assets/components/data-grid/components/data-grid-summary-cell/data-grid-summary-cell.vue +1 -0
  73. package/dist/assets/components/data-grid/components/data-grid-trend-cell/data-grid-trend-cell.docs.mdx +48 -0
  74. package/dist/assets/components/data-grid/components/data-grid-trend-cell/data-grid-trend-cell.stories.ts +1 -0
  75. package/dist/assets/components/data-grid/components/data-grid-trend-cell/data-grid-trend-cell.vue +1 -0
  76. package/dist/assets/components/data-grid/data-grid.docs.mdx +951 -0
  77. package/dist/assets/components/data-grid/data-grid.stories.ts +1 -0
  78. package/dist/assets/components/data-grid/data-grid.vue +1 -0
  79. package/dist/assets/components/date/date.docs.mdx +49 -0
  80. package/dist/assets/components/date/date.stories.ts +1 -0
  81. package/dist/assets/components/date/date.vue +1 -0
  82. package/dist/assets/components/date-picker/date-picker.docs.mdx +69 -0
  83. package/dist/assets/components/date-picker/date-picker.stories.ts +1 -0
  84. package/dist/assets/components/date-picker/date-picker.vue +1 -0
  85. package/dist/assets/components/date-range-picker/components/date-range-picker-calendar/date-range-picker-calendar.vue +1 -0
  86. package/dist/assets/components/date-range-picker/date-range-picker.docs.mdx +168 -0
  87. package/dist/assets/components/date-range-picker/date-range-picker.stories.ts +1 -0
  88. package/dist/assets/components/date-range-picker/date-range-picker.vue +1 -0
  89. package/dist/assets/components/divider/divider.docs.mdx +27 -0
  90. package/dist/assets/components/divider/divider.stories.ts +1 -0
  91. package/dist/assets/components/divider/divider.vue +1 -0
  92. package/dist/assets/components/dropdown/components/dropdown-base-textbox/dropdown-base-textbox.vue +1 -0
  93. package/dist/assets/components/dropdown/components/dropdown-default-textbox/dropdown-default-textbox.vue +1 -0
  94. package/dist/assets/components/dropdown/components/dropdown-options-container/dropdown-options-container.vue +1 -0
  95. package/dist/assets/components/dropdown/components/dropdown-small-textbox/dropdown-small-textbox.vue +1 -0
  96. package/dist/assets/components/dropdown/components/variants/dropdown-avatar/dropdown-avatar.stories.ts +1 -0
  97. package/dist/assets/components/dropdown/components/variants/dropdown-avatar/dropdown-avatar.vue +1 -0
  98. package/dist/assets/components/dropdown/components/variants/dropdown-country/dropdown-country.stories.ts +1 -0
  99. package/dist/assets/components/dropdown/components/variants/dropdown-country/dropdown-country.vue +1 -0
  100. package/dist/assets/components/dropdown/components/variants/dropdown-currency/dropdown-currency.stories.ts +1 -0
  101. package/dist/assets/components/dropdown/components/variants/dropdown-currency/dropdown-currency.vue +1 -0
  102. package/dist/assets/components/dropdown/components/variants/dropdown-payment-method/dropdown-payment-method.stories.ts +1 -0
  103. package/dist/assets/components/dropdown/components/variants/dropdown-payment-method/dropdown-payment-method.vue +1 -0
  104. package/dist/assets/components/dropdown/components/variants/dropdown-tag/dropdown-tag.stories.ts +1 -0
  105. package/dist/assets/components/dropdown/components/variants/dropdown-tag/dropdown-tag.vue +1 -0
  106. package/dist/assets/components/dropdown/dropdown.docs.mdx +420 -0
  107. package/dist/assets/components/dropdown/dropdown.stories.ts +1 -0
  108. package/dist/assets/components/dropdown/dropdown.vue +1 -0
  109. package/dist/assets/components/empty-state/empty-state.docs.mdx +115 -0
  110. package/dist/assets/components/empty-state/empty-state.stories.ts +1 -0
  111. package/dist/assets/components/empty-state/empty-state.vue +1 -0
  112. package/dist/assets/components/file-uploader/components/file-uploader-file-card/components/file-uploader-file-card-animation/file-uploader-file-card-animation.vue +1 -0
  113. package/dist/assets/components/file-uploader/components/file-uploader-file-card/file-uploader-file-card.stories.ts +1 -0
  114. package/dist/assets/components/file-uploader/components/file-uploader-file-card/file-uploader-file-card.vue +1 -0
  115. package/dist/assets/components/file-uploader/components/file-uploader-restrictions/file-uploader-restrictions.stories.ts +1 -0
  116. package/dist/assets/components/file-uploader/components/file-uploader-restrictions/file-uploader-restrictions.vue +1 -0
  117. package/dist/assets/components/file-uploader/file-uploader.docs.mdx +117 -0
  118. package/dist/assets/components/file-uploader/file-uploader.stories.ts +1 -0
  119. package/dist/assets/components/file-uploader/file-uploader.vue +1 -0
  120. package/dist/assets/components/filter-bar/components/all-filters-modal/all-filters-modal.stories.ts +1 -0
  121. package/dist/assets/components/filter-bar/components/all-filters-modal/all-filters-modal.vue +1 -0
  122. package/dist/assets/components/filter-bar/components/all-filters-modal/components/all-filters-modal-button/all-filters-modal-button.vue +1 -0
  123. package/dist/assets/components/filter-bar/components/base-filter/base-filter.docs.mdx +90 -0
  124. package/dist/assets/components/filter-bar/components/base-filter/base-filter.stories.ts +1 -0
  125. package/dist/assets/components/filter-bar/components/base-filter/base-filter.vue +1 -0
  126. package/dist/assets/components/filter-bar/components/base-filter-button/base-filter-button.vue +1 -0
  127. package/dist/assets/components/filter-bar/components/boolean-filter/boolean-filter.vue +1 -0
  128. package/dist/assets/components/filter-bar/components/checkbox-group-filter/checkbox-group-filter.stories.ts +1 -0
  129. package/dist/assets/components/filter-bar/components/checkbox-group-filter/checkbox-group-filter.vue +1 -0
  130. package/dist/assets/components/filter-bar/components/date-filter/date-filter.stories.ts +1 -0
  131. package/dist/assets/components/filter-bar/components/date-filter/date-filter.vue +1 -0
  132. package/dist/assets/components/filter-bar/components/date-range-filter/date-range-filter.stories.ts +1 -0
  133. package/dist/assets/components/filter-bar/components/date-range-filter/date-range-filter.vue +1 -0
  134. package/dist/assets/components/filter-bar/components/filter-bar-button/filter-bar-button.vue +1 -0
  135. package/dist/assets/components/filter-bar/components/filter-bar-button-group/filter-bar-button-group.vue +1 -0
  136. package/dist/assets/components/filter-bar/components/input-filter/input-filter.docs.mdx +18 -0
  137. package/dist/assets/components/filter-bar/components/input-filter/input-filter.stories.ts +1 -0
  138. package/dist/assets/components/filter-bar/components/input-filter/input-filter.vue +1 -0
  139. package/dist/assets/components/filter-bar/components/input-with-dropdown-filter/input-with-dropdown-filter.docs.mdx +18 -0
  140. package/dist/assets/components/filter-bar/components/input-with-dropdown-filter/input-with-dropdown-filter.stories.ts +1 -0
  141. package/dist/assets/components/filter-bar/components/input-with-dropdown-filter/input-with-dropdown-filter.vue +1 -0
  142. package/dist/assets/components/filter-bar/components/radio-group-filter/radio-group-filter.stories.ts +1 -0
  143. package/dist/assets/components/filter-bar/components/radio-group-filter/radio-group-filter.vue +1 -0
  144. package/dist/assets/components/filter-bar/components/range-filter/range-filter.vue +1 -0
  145. package/dist/assets/components/filter-bar/components/select-filter/components/select-filter-button/select-filter-button.vue +1 -0
  146. package/dist/assets/components/filter-bar/components/select-filter/components/select-filter-dropdown/select-filter-dropdown.vue +1 -0
  147. package/dist/assets/components/filter-bar/components/select-filter/select-filter.stories.ts +1 -0
  148. package/dist/assets/components/filter-bar/components/select-filter/select-filter.vue +1 -0
  149. package/dist/assets/components/filter-bar/components/select-filter/variants/select-avatar-filter/select-avatar-filter.stories.ts +1 -0
  150. package/dist/assets/components/filter-bar/components/select-filter/variants/select-avatar-filter/select-avatar-filter.vue +1 -0
  151. package/dist/assets/components/filter-bar/components/select-filter/variants/select-country-filter/select-country-filter.stories.ts +1 -0
  152. package/dist/assets/components/filter-bar/components/select-filter/variants/select-country-filter/select-country-filter.vue +1 -0
  153. package/dist/assets/components/filter-bar/components/select-filter/variants/select-currency-filter/select-currency-filter.stories.ts +1 -0
  154. package/dist/assets/components/filter-bar/components/select-filter/variants/select-currency-filter/select-currency-filter.vue +1 -0
  155. package/dist/assets/components/filter-bar/components/select-filter/variants/select-payment-method-filter/select-payment-method-filter.stories.ts +1 -0
  156. package/dist/assets/components/filter-bar/components/select-filter/variants/select-payment-method-filter/select-payment-method-filter.vue +1 -0
  157. package/dist/assets/components/filter-bar/components/select-filter/variants/select-tag-filter/select-tag-filter.stories.ts +1 -0
  158. package/dist/assets/components/filter-bar/components/select-filter/variants/select-tag-filter/select-tag-filter.vue +1 -0
  159. package/dist/assets/components/filter-bar/components/toggle-filter/toggle-filter.stories.ts +1 -0
  160. package/dist/assets/components/filter-bar/components/toggle-filter/toggle-filter.vue +1 -0
  161. package/dist/assets/components/filter-bar/filter-bar.docs.mdx +1005 -0
  162. package/dist/assets/components/filter-bar/filter-bar.stories.ts +1 -0
  163. package/dist/assets/components/filter-bar/filter-bar.vue +1 -0
  164. package/dist/assets/components/focus-trap/focus-trap.docs.mdx +10 -0
  165. package/dist/assets/components/focus-trap/focus-trap.stories.ts +1 -0
  166. package/dist/assets/components/focus-trap/focus-trap.vue +1 -0
  167. package/dist/assets/components/form-layout/components/form-layout-field-tip/form-layout-field-tip.vue +1 -0
  168. package/dist/assets/components/form-layout/components/form-layout-group/form-layout-group.vue +1 -0
  169. package/dist/assets/components/form-layout/components/form-layout-title/form-layout-title.vue +1 -0
  170. package/dist/assets/components/form-layout/form-layout.docs.mdx +452 -0
  171. package/dist/assets/components/form-layout/form-layout.stories.ts +1 -0
  172. package/dist/assets/components/form-layout/form-layout.vue +1 -0
  173. package/dist/assets/components/header/header.docs.mdx +85 -0
  174. package/dist/assets/components/header/header.stories.ts +1 -0
  175. package/dist/assets/components/header/header.vue +1 -0
  176. package/dist/assets/components/header-with-views/components/header-with-views-all-views/header-with-views-all-views.vue +1 -0
  177. package/dist/assets/components/header-with-views/components/header-with-views-item-edit-form/header-with-views-edit-form.vue +1 -0
  178. package/dist/assets/components/header-with-views/header-with-views.docs.mdx +103 -0
  179. package/dist/assets/components/header-with-views/header-with-views.stories.ts +1 -0
  180. package/dist/assets/components/header-with-views/header-with-views.vue +1 -0
  181. package/dist/assets/components/image/image.docs.mdx +34 -0
  182. package/dist/assets/components/image/image.stories.ts +1 -0
  183. package/dist/assets/components/image/image.vue +1 -0
  184. package/dist/assets/components/info-icon/info-icon.docs.mdx +57 -0
  185. package/dist/assets/components/info-icon/info-icon.stories.ts +1 -0
  186. package/dist/assets/components/info-icon/info-icon.vue +1 -0
  187. package/dist/assets/components/input-field/input-field.docs.mdx +141 -0
  188. package/dist/assets/components/input-field/input-field.stories.ts +1 -0
  189. package/dist/assets/components/input-field/input-field.vue +1 -0
  190. package/dist/assets/components/input-field-password/input-field-password.docs.mdx +292 -0
  191. package/dist/assets/components/input-field-password/input-field-password.stories.ts +1 -0
  192. package/dist/assets/components/input-field-password/input-field-password.vue +1 -0
  193. package/dist/assets/components/input-field-phone-number/input-field-phone-number.docs.mdx +72 -0
  194. package/dist/assets/components/input-field-phone-number/input-field-phone-number.stories.ts +1 -0
  195. package/dist/assets/components/input-field-phone-number/input-field-phone-number.vue +1 -0
  196. package/dist/assets/components/inspector/inspector.docs.mdx +118 -0
  197. package/dist/assets/components/inspector/inspector.stories.ts +1 -0
  198. package/dist/assets/components/inspector/inspector.vue +1 -0
  199. package/dist/assets/components/internal/base-button/base-button.vue +1 -0
  200. package/dist/assets/components/internal/button-actions-with-menu/button-actions-with-menu.stories.ts +1 -0
  201. package/dist/assets/components/internal/button-actions-with-menu/button-actions-with-menu.vue +1 -0
  202. package/dist/assets/components/internal/calendar/calendar.stories.ts +1 -0
  203. package/dist/assets/components/internal/calendar/calendar.vue +1 -0
  204. package/dist/assets/components/internal/calendar/components/calendar-month/calendar-month.stories.ts +1 -0
  205. package/dist/assets/components/internal/calendar/components/calendar-month/calendar-month.vue +1 -0
  206. package/dist/assets/components/internal/calendar/components/calendar-month/components/calendar-month-day/__test__/calendar-month-day-stub.vue +1 -0
  207. package/dist/assets/components/internal/calendar/components/calendar-month/components/calendar-month-day/calendar-month-day.stories.ts +1 -0
  208. package/dist/assets/components/internal/calendar/components/calendar-month/components/calendar-month-day/calendar-month-day.vue +1 -0
  209. package/dist/assets/components/internal/calendar/components/calendar-year/calendar-year.vue +1 -0
  210. package/dist/assets/components/internal/calendar/components/calendar-year/components/calendar-year-month/__test__/calendar-year-month-stub.vue +1 -0
  211. package/dist/assets/components/internal/calendar/components/calendar-year/components/calendar-year-month/calendar-year-month.vue +1 -0
  212. package/dist/assets/components/internal/checkbox-input/checkbox-input.stories.ts +1 -0
  213. package/dist/assets/components/internal/checkbox-input/checkbox-input.vue +1 -0
  214. package/dist/assets/components/internal/controls-group/controls-group.vue +1 -0
  215. package/dist/assets/components/internal/copy/copy.docs.mdx +43 -0
  216. package/dist/assets/components/internal/copy/copy.stories.ts +1 -0
  217. package/dist/assets/components/internal/copy/copy.vue +1 -0
  218. package/dist/assets/components/internal/decorative-checkbox/decorative-checkbox.vue +1 -0
  219. package/dist/assets/components/internal/dialog-page/dialog-page.vue +1 -0
  220. package/dist/assets/components/internal/error-message/error-message.vue +1 -0
  221. package/dist/assets/components/internal/field-label/field-label.vue +1 -0
  222. package/dist/assets/components/internal/fixed-scroller/fixed-scroller.docs.mdx +21 -0
  223. package/dist/assets/components/internal/fixed-scroller/fixed-scroller.stories.ts +1 -0
  224. package/dist/assets/components/internal/fixed-scroller/fixed-scroller.vue +1 -0
  225. package/dist/assets/components/internal/footer-actions/footer-actions.vue +1 -0
  226. package/dist/assets/components/internal/grid-layout/grid-layout.vue +1 -0
  227. package/dist/assets/components/internal/header-meta/header-meta.vue +1 -0
  228. package/dist/assets/components/internal/info-icon-with-popover/info-icon-with-popover.vue +1 -0
  229. package/dist/assets/components/internal/listbox/components/listbox-multi-select/components/listbox-multi-select-options/components/listbox-multi-select-category/listbox-multi-select-category.vue +1 -0
  230. package/dist/assets/components/internal/listbox/components/listbox-multi-select/components/listbox-multi-select-options/listbox-multi-select-options.vue +1 -0
  231. package/dist/assets/components/internal/listbox/components/listbox-multi-select/listbox-multi-select.vue +1 -0
  232. package/dist/assets/components/internal/listbox/components/listbox-option/listbox-option.vue +1 -0
  233. package/dist/assets/components/internal/listbox/components/listbox-option-tag/listbox-option-tag.vue +1 -0
  234. package/dist/assets/components/internal/listbox/components/listbox-single-select/components/listbox-single-select-category/listbox-single-select-category.vue +1 -0
  235. package/dist/assets/components/internal/listbox/components/listbox-single-select/listbox-single-select.vue +1 -0
  236. package/dist/assets/components/internal/listbox/components/listbox-single-select-option/listbox-single-select-option.vue +1 -0
  237. package/dist/assets/components/internal/listbox/listbox.stories.ts +1 -0
  238. package/dist/assets/components/internal/listbox/listbox.vue +1 -0
  239. package/dist/assets/components/internal/radio-input/radio-input.stories.ts +1 -0
  240. package/dist/assets/components/internal/radio-input/radio-input.vue +1 -0
  241. package/dist/assets/components/internal/teleport/teleport.vue +1 -0
  242. package/dist/assets/components/internal/tooltip/tooltip.test.vue +1 -0
  243. package/dist/assets/components/internal/tooltip/tooltip.vue +1 -0
  244. package/dist/assets/components/layout/components/layout-row/layout-row.stories.ts +1 -0
  245. package/dist/assets/components/layout/components/layout-row/layout-row.vue +1 -0
  246. package/dist/assets/components/layout/layout.docs.mdx +108 -0
  247. package/dist/assets/components/layout/layout.stories.ts +1 -0
  248. package/dist/assets/components/layout/layout.vue +1 -0
  249. package/dist/assets/components/link/link.docs.mdx +119 -0
  250. package/dist/assets/components/link/link.stories.ts +1 -0
  251. package/dist/assets/components/link/link.vue +1 -0
  252. package/dist/assets/components/loading-indicator/loading-indicator.docs.mdx +65 -0
  253. package/dist/assets/components/loading-indicator/loading-indicator.stories.ts +1 -0
  254. package/dist/assets/components/loading-indicator/loading-indicator.vue +1 -0
  255. package/dist/assets/components/menu/components/menu-item/menu-item.vue +1 -0
  256. package/dist/assets/components/menu/components/menu-item-list/menu-item-list.vue +1 -0
  257. package/dist/assets/components/menu/components/menu-list-container/menu-list-container.vue +1 -0
  258. package/dist/assets/components/menu/components/sub-menu-item-list/sub-menu-item-list.vue +1 -0
  259. package/dist/assets/components/menu/menu.docs.mdx +121 -0
  260. package/dist/assets/components/menu/menu.stories.ts +1 -0
  261. package/dist/assets/components/menu/menu.vue +1 -0
  262. package/dist/assets/components/modal/components/base-modal/base-modal.vue +1 -0
  263. package/dist/assets/components/modal/components/modal-default/modal-default.vue +1 -0
  264. package/dist/assets/components/modal/components/modal-dialog/modal-dialog.vue +1 -0
  265. package/dist/assets/components/modal/components/modal-page/modal-page.vue +1 -0
  266. package/dist/assets/components/modal/modal.docs.mdx +215 -0
  267. package/dist/assets/components/modal/modal.stories.ts +1 -0
  268. package/dist/assets/components/modal/modal.vue +1 -0
  269. package/dist/assets/components/modal-fullscreen/components/modal-fullscreen-page.vue +1 -0
  270. package/dist/assets/components/modal-fullscreen/modal-fullscreen.docs.mdx +217 -0
  271. package/dist/assets/components/modal-fullscreen/modal-fullscreen.stories.ts +1 -0
  272. package/dist/assets/components/modal-fullscreen/modal-fullscreen.vue +1 -0
  273. package/dist/assets/components/pagination/components/pagination-context/pagination-context.vue +1 -0
  274. package/dist/assets/components/pagination/components/pagination-controls/pagination-controls.vue +1 -0
  275. package/dist/assets/components/pagination/components/pagination-results-per-page/pagination-results-per-page.vue +1 -0
  276. package/dist/assets/components/pagination/pagination.docs.mdx +116 -0
  277. package/dist/assets/components/pagination/pagination.stories.ts +1 -0
  278. package/dist/assets/components/pagination/pagination.vue +1 -0
  279. package/dist/assets/components/payment-method/payment-method.docs.mdx +34 -0
  280. package/dist/assets/components/payment-method/payment-method.stories.ts +1 -0
  281. package/dist/assets/components/payment-method/payment-method.vue +1 -0
  282. package/dist/assets/components/popover/components/popover-dismiss-button/popover-dismiss-button.vue +1 -0
  283. package/dist/assets/components/popover/components/popover-image/popover-image.vue +1 -0
  284. package/dist/assets/components/popover/components/popover-title/popover-title.vue +1 -0
  285. package/dist/assets/components/popover/popover.docs.mdx +89 -0
  286. package/dist/assets/components/popover/popover.stories.ts +1 -0
  287. package/dist/assets/components/popover/popover.vue +1 -0
  288. package/dist/assets/components/popper-container/popper-container.docs.mdx +11 -0
  289. package/dist/assets/components/popper-container/popper-container.stories.ts +1 -0
  290. package/dist/assets/components/popper-container/popper-container.vue +1 -0
  291. package/dist/assets/components/promo-banner/promo-banner.docs.mdx +94 -0
  292. package/dist/assets/components/promo-banner/promo-banner.stories.ts +1 -0
  293. package/dist/assets/components/promo-banner/promo-banner.vue +1 -0
  294. package/dist/assets/components/radio-group/components/radio-button/radio-button.docs.mdx +76 -0
  295. package/dist/assets/components/radio-group/components/radio-button/radio-button.stories.ts +1 -0
  296. package/dist/assets/components/radio-group/components/radio-button/radio-button.vue +1 -0
  297. package/dist/assets/components/radio-group/radio-group.docs.mdx +154 -0
  298. package/dist/assets/components/radio-group/radio-group.stories.ts +1 -0
  299. package/dist/assets/components/radio-group/radio-group.vue +1 -0
  300. package/dist/assets/components/rich-text-editor/components/rich-text-editor-content-area/rich-text-editor-content-area.vue +1 -0
  301. package/dist/assets/components/rich-text-editor/components/rich-text-editor-renderer/components/rich-text-editor-code-block/rich-text-editor-code-block.vue +1 -0
  302. package/dist/assets/components/rich-text-editor/components/rich-text-editor-renderer/components/rich-text-editor-inline-code/rich-text-editor-inline-code.vue +1 -0
  303. package/dist/assets/components/rich-text-editor/components/rich-text-editor-renderer/components/rich-text-editor-link/rich-text-editor-link.vue +1 -0
  304. package/dist/assets/components/rich-text-editor/components/rich-text-editor-renderer/components/rich-text-editor-list-item/rich-text-editor-list-item.vue +1 -0
  305. package/dist/assets/components/rich-text-editor/components/rich-text-editor-renderer/components/rich-text-editor-table/rich-text-editor-table.vue +1 -0
  306. package/dist/assets/components/rich-text-editor/rich-text-editor.docs.mdx +52 -0
  307. package/dist/assets/components/rich-text-editor/rich-text-editor.stories.ts +1 -0
  308. package/dist/assets/components/rich-text-editor/rich-text-editor.vue +1 -0
  309. package/dist/assets/components/search-bar/search-bar.docs.mdx +117 -0
  310. package/dist/assets/components/search-bar/search-bar.stories.ts +1 -0
  311. package/dist/assets/components/search-bar/search-bar.vue +1 -0
  312. package/dist/assets/components/secondary-nav/components/secondary-nav-category/secondary-nav-category.vue +1 -0
  313. package/dist/assets/components/secondary-nav/components/secondary-nav-item/secondary-nav-item.vue +1 -0
  314. package/dist/assets/components/secondary-nav/secondary-nav.docs.mdx +40 -0
  315. package/dist/assets/components/secondary-nav/secondary-nav.stories.ts +1 -0
  316. package/dist/assets/components/secondary-nav/secondary-nav.vue +1 -0
  317. package/dist/assets/components/segmented-control/components/segmented-control-button/segmented-control-button.vue +1 -0
  318. package/dist/assets/components/segmented-control/segmented-control.docs.mdx +90 -0
  319. package/dist/assets/components/segmented-control/segmented-control.stories.ts +1 -0
  320. package/dist/assets/components/segmented-control/segmented-control.vue +1 -0
  321. package/dist/assets/components/selection-card/components/selection-card-group/selection-card-group.docs.mdx +119 -0
  322. package/dist/assets/components/selection-card/components/selection-card-group/selection-card-group.stories.ts +1 -0
  323. package/dist/assets/components/selection-card/components/selection-card-group/selection-card-group.vue +1 -0
  324. package/dist/assets/components/selection-card/selection-card.docs.mdx +108 -0
  325. package/dist/assets/components/selection-card/selection-card.stories.ts +1 -0
  326. package/dist/assets/components/selection-card/selection-card.vue +1 -0
  327. package/dist/assets/components/sidepanel/components/sidepanel-page.stories.ts +1 -0
  328. package/dist/assets/components/sidepanel/components/sidepanel-page.vue +1 -0
  329. package/dist/assets/components/sidepanel/sidepanel.docs.mdx +129 -0
  330. package/dist/assets/components/sidepanel/sidepanel.stories.ts +1 -0
  331. package/dist/assets/components/sidepanel/sidepanel.vue +1 -0
  332. package/dist/assets/components/status/status.docs.mdx +62 -0
  333. package/dist/assets/components/status/status.stories.ts +1 -0
  334. package/dist/assets/components/status/status.vue +1 -0
  335. package/dist/assets/components/stepper/components/step/step.stories.ts +1 -0
  336. package/dist/assets/components/stepper/components/step/step.vue +1 -0
  337. package/dist/assets/components/stepper/stepper.docs.mdx +127 -0
  338. package/dist/assets/components/stepper/stepper.stories.ts +1 -0
  339. package/dist/assets/components/stepper/stepper.vue +1 -0
  340. package/dist/assets/components/structured-list/components/structered-list-item/structured-list-item.stories.ts +1 -0
  341. package/dist/assets/components/structured-list/components/structered-list-item/structured-list-item.vue +1 -0
  342. package/dist/assets/components/structured-list/structured-list.docs.mdx +68 -0
  343. package/dist/assets/components/structured-list/structured-list.stories.ts +1 -0
  344. package/dist/assets/components/structured-list/structured-list.vue +1 -0
  345. package/dist/assets/components/summary-grid/components/summary-grid-item/summary-grid-item.vue +1 -0
  346. package/dist/assets/components/summary-grid/components/summary-grid-item-amount/summary-grid-item-amount.stories.ts +1 -0
  347. package/dist/assets/components/summary-grid/components/summary-grid-item-amount/summary-grid-item-amount.vue +1 -0
  348. package/dist/assets/components/summary-grid/components/summary-grid-item-custom/summary-grid-item-custom.stories.ts +1 -0
  349. package/dist/assets/components/summary-grid/components/summary-grid-item-custom/summary-grid-item-custom.vue +1 -0
  350. package/dist/assets/components/summary-grid/components/summary-grid-item-numeric/summary-grid-item-numeric.stories.ts +1 -0
  351. package/dist/assets/components/summary-grid/components/summary-grid-item-numeric/summary-grid-item-numeric.vue +1 -0
  352. package/dist/assets/components/summary-grid/components/summary-grid-item-percentage/summary-grid-item-percentage.stories.ts +1 -0
  353. package/dist/assets/components/summary-grid/components/summary-grid-item-percentage/summary-grid-item-percentage.vue +1 -0
  354. package/dist/assets/components/summary-grid/components/summary-grid-item-text/summary-grid-item-text.stories.ts +1 -0
  355. package/dist/assets/components/summary-grid/components/summary-grid-item-text/summary-grid-item-text.vue +1 -0
  356. package/dist/assets/components/summary-grid/summary-grid.docs.mdx +152 -0
  357. package/dist/assets/components/summary-grid/summary-grid.stories.ts +1 -0
  358. package/dist/assets/components/summary-grid/summary-grid.vue +1 -0
  359. package/dist/assets/components/tabs/components/tab.vue +1 -0
  360. package/dist/assets/components/tabs/tabs.docs.mdx +98 -0
  361. package/dist/assets/components/tabs/tabs.stories.ts +1 -0
  362. package/dist/assets/components/tabs/tabs.vue +1 -0
  363. package/dist/assets/components/tag/tag.docs.mdx +137 -0
  364. package/dist/assets/components/tag/tag.stories.ts +1 -0
  365. package/dist/assets/components/tag/tag.vue +1 -0
  366. package/dist/assets/components/textarea/textarea.docs.mdx +53 -0
  367. package/dist/assets/components/textarea/textarea.stories.ts +1 -0
  368. package/dist/assets/components/textarea/textarea.vue +1 -0
  369. package/dist/assets/components/timeline/components/timeline-item/timeline-item.stories.ts +1 -0
  370. package/dist/assets/components/timeline/components/timeline-item/timeline-item.vue +1 -0
  371. package/dist/assets/components/timeline/timeline.docs.mdx +131 -0
  372. package/dist/assets/components/timeline/timeline.stories.ts +1 -0
  373. package/dist/assets/components/timeline/timeline.vue +1 -0
  374. package/dist/assets/components/toast/components/toast-item/toast-item.vue +1 -0
  375. package/dist/assets/components/toast/components/toast-shadow-stack/toast-shadow-stack.vue +1 -0
  376. package/dist/assets/components/toast/toast.docs.mdx +151 -0
  377. package/dist/assets/components/toast/toast.stories.ts +1 -0
  378. package/dist/assets/components/toast/toast.vue +1 -0
  379. package/dist/assets/components/toggle/toggle.docs.mdx +81 -0
  380. package/dist/assets/components/toggle/toggle.stories.ts +1 -0
  381. package/dist/assets/components/toggle/toggle.vue +1 -0
  382. package/dist/assets/components/tutorial/components/tutorial-popover/components/tutorial-indicator/tutorial-indicator.vue +1 -0
  383. package/dist/assets/components/tutorial/components/tutorial-popover/tutorial-popover.vue +1 -0
  384. package/dist/assets/components/tutorial/tutorial.docs.mdx +85 -0
  385. package/dist/assets/components/tutorial/tutorial.stories.ts +1 -0
  386. package/dist/assets/components/tutorial/tutorial.vue +1 -0
  387. package/dist/assets/components/typography/typography.docs.mdx +124 -0
  388. package/dist/assets/components/typography/typography.stories.ts +1 -0
  389. package/dist/assets/components/typography/typography.vue +1 -0
  390. package/dist/assets/composables/use-bento-currency/use-bento-currency.docs.mdx +28 -0
  391. package/dist/assets/composables/use-bento-currency/use-bento-currency.stories.ts +1 -0
  392. package/dist/assets/composables/use-click-outside/use-click-outside.docs.mdx +10 -0
  393. package/dist/assets/composables/use-click-outside/use-click-outside.stories.ts +1 -0
  394. package/dist/assets/directives/highlight/highlight.docs.mdx +64 -0
  395. package/dist/assets/directives/highlight/highlight.stories.ts +1 -0
  396. package/dist/assets/directives/keyboard-navigation/keyboard-navigation.docs.mdx +41 -0
  397. package/dist/assets/directives/keyboard-navigation/keyboard-navigation.stories.ts +1 -0
  398. package/dist/assets/directives/tooltip/tooltip.docs.mdx +86 -0
  399. package/dist/assets/directives/tooltip/tooltip.stories.ts +1 -0
  400. package/dist/assets/index.ts +1 -0
  401. package/dist/assets/templates/dashboard-template/components/dashboard-section/dashboard-section.stories.ts +1 -0
  402. package/dist/assets/templates/dashboard-template/components/dashboard-section/dashboard-section.vue +1 -0
  403. package/dist/assets/templates/dashboard-template/dashboard-template.docs.mdx +148 -0
  404. package/dist/assets/templates/dashboard-template/dashboard-template.stories.ts +1 -0
  405. package/dist/assets/templates/dashboard-template/dashboard-template.vue +1 -0
  406. package/dist/assets/templates/data-grid-template/data-grid-template.docs.mdx +117 -0
  407. package/dist/assets/templates/data-grid-template/data-grid-template.stories.ts +1 -0
  408. package/dist/assets/templates/data-grid-template/data-grid-template.vue +1 -0
  409. package/dist/main.js +30031 -0
  410. package/package.json +45 -3
@@ -0,0 +1,420 @@
1
+ import { Canvas, Story, Source, Meta } from '@storybook/blocks';
2
+ import * as DropdownStories from './dropdown.stories';
3
+ import * as DropdownAvatarStories from './components/variants/dropdown-avatar/dropdown-avatar.stories.ts';
4
+ import * as DropdownCountryStories from './components/variants/dropdown-country/dropdown-country.stories.ts';
5
+ import * as DropdownCurrencyStories from './components/variants/dropdown-currency/dropdown-currency.stories.ts';
6
+ import * as DropdownPaymentMethodStories from './components/variants/dropdown-payment-method/dropdown-payment-method.stories.ts';
7
+ import * as DropdownTagStories from './components/variants/dropdown-tag/dropdown-tag.stories.ts';
8
+
9
+ <Meta title="Dropdown" of={DropdownStories} />
10
+
11
+ # Dropdown
12
+
13
+ A dropdown shows a selected option or multiple options. Use a dropdown when you want users to select from a pre-defined
14
+ list of options.
15
+
16
+ <Canvas sourceState="hidden">
17
+ <Story of={DropdownStories.Default} />
18
+ </Canvas>
19
+
20
+ ## Table of contents
21
+
22
+ - [Use Cases](#use-cases)
23
+ - [Variants](#variants)
24
+ - [Avatar](#avatar)
25
+ - [Country](#country)
26
+ - [Currency](#currency)
27
+ - [Payment method](#payment-method)
28
+ - [Tag](#tag)
29
+ - [Custom slots](#custom-slots)
30
+ - [Modifiers](#modifiers)
31
+ - [Display value](#display-value)
32
+ - [Condensed](#condensed)
33
+ - [Disabling options](#disabling-options)
34
+ - [Dynamic filtering](#dynamic-filtering)
35
+ - [Empty state](#empty-state)
36
+ - [Error state](#error-state)
37
+ - [Items with description](#items-with-description)
38
+ - [External filtering](#external-filtering)
39
+ - [Lazy loading](#lazy-loading)
40
+ - [External filtering and Lazy loading simultaneously](#external-filtering-and-lazy-loading-simultaneously)
41
+ - [Multi select](#multi-select)
42
+ - [Nested multi-select](#nested-multi-select)
43
+ - [Static categories](#static-categories)
44
+ - [Readonly](#readonly)
45
+ - [Tooltip](#tooltip)
46
+ - [Accessibility](#accessibility)
47
+ - [Resources](#resources)
48
+
49
+ ## Use Cases
50
+
51
+ Use a Dropdown if you need to:
52
+
53
+ - Allow the user to select a country of residence.
54
+ - Select multiple payment methods.
55
+
56
+ ### Do not use
57
+
58
+ If there are less than 4 options - use a group of radio buttons or checkboxes instead.
59
+
60
+ ## Variants
61
+
62
+ All the variants listed below accept all the same properties as the default `bento-dropdown` plus the properties listed
63
+ in the description of the variant.
64
+
65
+ ### Avatar
66
+
67
+ <Canvas of={DropdownAvatarStories.Default} />
68
+
69
+ NOTE: this uses the `bento-dropdown-avatar` component, so you can pass a `src` string with each item in the `items`
70
+ array.
71
+
72
+ ### Country
73
+
74
+ <Canvas of={DropdownCountryStories.Default} />
75
+
76
+ NOTE: this uses the `bento-dropdown-country` component, so alongside all other `bento-dropdown` props it additionally
77
+ accepts a country `variant` property which prepopulates the dropdown item labels.
78
+
79
+ e.g. when `variant` is set to `'name'`, then the dropdown will display a list of country names.
80
+
81
+ ### Currency
82
+
83
+ <Canvas of={DropdownCurrencyStories.Default} />
84
+
85
+ NOTE: this uses the `bento-dropdown-country` component.
86
+
87
+ The `value` property within each option in `items` property array should translate to a currency code in
88
+ ([ISO 4217 format](https://en.wikipedia.org/wiki/ISO_4217)).
89
+
90
+ ### Payment method
91
+
92
+ <Canvas of={DropdownPaymentMethodStories.Default} />
93
+
94
+ NOTE: this uses the `bento-dropdown-payment-method` component, so the `value` property within each option in `items`
95
+ property array can take one of the values listed in the exported `IconsEnum` enum from the
96
+ `@adyen/ui-assets-payment-method-icons` passed as a string.
97
+
98
+ ### Tag
99
+
100
+ <Canvas of={DropdownTagStories.Default} />
101
+
102
+ ### Custom slots
103
+
104
+ If your dropdown items don't match any of the variants listed above, you can pass custom slots as the dropdown's options
105
+ via the default slot.
106
+
107
+ <Canvas of={DropdownStories.Slots} />
108
+
109
+ ## Modifiers
110
+
111
+ ### Display value
112
+
113
+ Each item in the dropdown can optionally be customized in the way it display when selected. To enable this feature, pass
114
+ a `display-value` property to the item's object in the items array.
115
+
116
+ <Canvas sourceState="hidden">
117
+ <Story of={DropdownStories.CustomDisplayValue} />
118
+ </Canvas>
119
+
120
+ ### Condensed
121
+
122
+ Condensed variant is meant to be used in places where regular variant is too big, for example, in small areas (i.e.
123
+ filter bar search) or condensed versions (i.e. condensed b-data-grid) of other components. It should not be used in
124
+ normal page content, for example in forms or settings - use regular variant in those instead. To use this variant, pass
125
+ the `condensed` prop to the `bento-dropdown` when using it.
126
+
127
+ ### Disabling options
128
+
129
+ The prop `isOptionDisabled` allows you to specify which options of the `items` array you want to render in a disabled
130
+ state. This prop takes in a function which runs over each dropdown option to dertermine if option should be disabled
131
+ from interaction. An example of usage would be:
132
+
133
+ ```html
134
+ <template>
135
+ <bento-dropdown v-model="selectedValue" :items="apiItems" :is-option-disabled="isOptionDisabled" />
136
+ </template>
137
+ <script setup lang="ts">
138
+ const apiItems = ref(props.items);
139
+ const selectedValue = ref(props.value);
140
+
141
+ const clickedAction = (selectedOption: BentoListboxSelectedValue) => {
142
+ action('input')(selectedOption);
143
+ };
144
+
145
+ const isOptionDisabled = (option: BentoListboxOptionItem) => [3, 5, 7].includes(option.value as number);
146
+ </script>
147
+ ```
148
+
149
+ ### Dynamic filtering
150
+
151
+ This option can be enabled to allow the user to filter the dropdown options by typing into a value.
152
+
153
+ ### Empty state
154
+
155
+ It's possible to customise the empty state message by using the `emptyState` property.
156
+
157
+ Here's an example:
158
+
159
+ ```html
160
+ <template>
161
+ <bento-dropdown v-model="selectedValue" :items="apiItems" :search="search" :empty-state="emptyState" />
162
+ </template>
163
+ <script setup lang="ts">
164
+ const apiItems = ref(props.items);
165
+ const selectedValue = ref(props.value);
166
+
167
+ const emptyState = computed<BentoDropdownEmptyStateProps>(() => ({
168
+ title: 'Unable to retrieve items',
169
+ action: {
170
+ title: 'Refresh the page to try again',
171
+ event: () => {
172
+ // Refresh page logic
173
+ },
174
+ },
175
+ }));
176
+
177
+ const search = {
178
+ debounceTime: 2000,
179
+ searchEvent: async (val: string) => {
180
+ const { data } = await fetch('dropdown-items-api');
181
+ apiItems.value = data;
182
+ },
183
+ };
184
+ </script>
185
+ ```
186
+
187
+ ### Error state
188
+
189
+ There are two properties that control the error state of `dropdown`:
190
+
191
+ - `errorMessage`: renders `dropdown` in an error state and displays an error message beneath the field
192
+ - `error`: renders `dropdown` with an error state
193
+
194
+ `error` is deprecated and will be removed in the near future, so please use `errorMessage`.
195
+
196
+ ### Items with description
197
+
198
+ Each item in the droepdown can optionally dispay a description. To enable this feature, pass a `description` field to
199
+ the item's object in the `items` array.
200
+
201
+ <Canvas sourceState="hidden">
202
+ <Story of={DropdownStories.WithDescription} />
203
+ </Canvas>
204
+
205
+ ### External filtering
206
+
207
+ <Canvas sourceState="hidden">
208
+ <Story of={DropdownStories.externalFiltering} />
209
+ </Canvas>
210
+
211
+ Instead of relying on client-side filtering, you can configure the dropdown to fetch filtered results from an external
212
+ source, such as an API, as the user types. This is particularly useful for large datasets.
213
+
214
+ To implement external filtering, you'll use a combination of props:
215
+
216
+ - **`dynamic-filtering`**: This must be set to `true` to enable the user to type into the dropdown.
217
+ - **`search`**: This object configures the external search behavior. It has two properties:
218
+ - `searchEvent`: A function that is triggered with the search term as its argument. This is where you should place
219
+ your logic to call an external API.
220
+ - `debounceTime` (optional): The delay in milliseconds after the user stops typing before the `searchEvent` is
221
+ triggered. The default is `300ms`.
222
+ - **`items`**: This is the array of options displayed in the dropdown. You should dynamically update this prop with the
223
+ data you receive from your API call.
224
+ - **`component-loading`**: Set this boolean prop to `true` while waiting for the API response. This will display a
225
+ loading indicator inside the dropdown, providing feedback to the user.
226
+
227
+ The typical flow is as follows:
228
+
229
+ 1. A user starts typing in the dropdown.
230
+ 2. After the `debounceTime` has passed, the `searchEvent` function is called with the current search term.
231
+ 3. In your `searchEvent` implementation, you set a loading state variable (bound to `component-loading`) to `true`.
232
+ 4. You make your API request using the search term.
233
+ 5. Once the request is complete, you update your `items` array with the results from the API.
234
+ 6. Finally, you set the loading state variable back to `false`.
235
+
236
+ ### Lazy loading
237
+
238
+ <Canvas sourceState="hidden">
239
+ <Story of={DropdownStories.LazyLoading} />
240
+ </Canvas>
241
+
242
+ The `bento-dropdown` component supports lazy loading, which is useful for handling large datasets by loading items in
243
+ batches as the user interacts with the dropdown. This can be done either automatically on scroll or by clicking a
244
+ button.
245
+
246
+ To enable lazy loading, you need to configure the following props:
247
+
248
+ - **`lazy-load-type`**: This determines how new items are loaded.
249
+ - `'automatic'`: The `show-more` event is emitted when the user scrolls to the bottom of the options list.
250
+ - `'button'`: A "Show more" button is displayed at the bottom of the list. Clicking it emits the `show-more` event.
251
+ - `'none'` (default): Lazy loading is disabled.
252
+ - **`has-more-items`**: A boolean that you should set to `true` as long as there are more items to be loaded from your
253
+ data source. Once all items are loaded, set it to `false` to hide the loader/button.
254
+ - **`loading`**: A boolean that should be set to `true` while you are fetching the next batch of items. This will
255
+ display a loading indicator to the user.
256
+ - **`@show-more`**: An event that is emitted when the user triggers the lazy load (either by scrolling or clicking the
257
+ button). You should listen for this event to fetch and append the next set of items to your `items` array.
258
+
259
+ ### External filtering and Lazy loading simultaneously
260
+
261
+ <Canvas sourceState="hidden">
262
+ <Story of={DropdownStories.ExternalFilteringAndLazyLoading} />
263
+ </Canvas>
264
+
265
+ You can combine external filtering with lazy loading to create a powerful and efficient search experience for very large
266
+ datasets. This allows users to search for a specific term and then lazy-load more results that match that term.
267
+
268
+ To achieve this, you'll combine the props from both features. The key is to manage the state for both searching and lazy
269
+ loading.
270
+
271
+ - Use `component-loading` for the initial search loading state.
272
+ - Use `loading` for the subsequent lazy-loading state.
273
+ - When a new search is performed, you must reset your pagination and load the first page of results for the new search
274
+ term.
275
+
276
+ #### Pre-set values that are not in the initial loaded list.
277
+
278
+ Some times the `bento-dropdown` field comes with pre-selected values that are not present on the initial load. This can
279
+ be done in three ways:
280
+
281
+ - (**Recommended**) By **always** setting a `value` / `label` pair as selected value and setting the prop
282
+ `enable-value-label-pair` as `true`:
283
+
284
+ <Source
285
+ format={'html'}
286
+ dark
287
+ code={`
288
+ <bento-dropdown
289
+ :value="[{ label: 'Option 1', value: 1}]"
290
+ :items="[]"
291
+ enable-value-label-pair
292
+ />
293
+ `}
294
+ />
295
+
296
+ - By only providing the `value` of the selected options and all selected options in the _initial load_. The value will
297
+ be used as `label`
298
+
299
+ <Source
300
+ format={'html'}
301
+ dark
302
+ code={`
303
+ <bento-dropdown
304
+ :value="[100]"
305
+ :items="[{ value: 1, label: 'Option 1'}]"
306
+ />
307
+ `}
308
+ />
309
+
310
+ - By providing the `value` and appending the selected options as part of the `item` property after the _initial load_.
311
+
312
+ <Source
313
+ format={'html'}
314
+ dark
315
+ code={`
316
+ <bento-dropdown
317
+ :value="[100]"
318
+ :items="[
319
+ { value: 1, label: 'Option 1'},
320
+ { value: 100, label: 'Option 100'}
321
+ ]"
322
+ />
323
+ `}
324
+ />
325
+
326
+ ### Multi-select
327
+
328
+ To allow the user to select more than one item at a time, set the `multiple` property. This property will add checkboxes
329
+ to the items and allow to select multiple items at the same time.
330
+
331
+ <Canvas sourceState="hidden">
332
+ <Story of={DropdownStories.MultiSelect} />
333
+ </Canvas>
334
+
335
+ ### Nested multi-select
336
+
337
+ The `bento-dropdown` multi-select variant comes with a nested functionality, which allows to group multiple child
338
+ options under a selectable parent category. This allows to create categories of options and select all the items inside
339
+ the category or the options of the category individually.
340
+
341
+ To enable this feature, pass a data structure in which each item in the `items` array which has a category will contain
342
+ an `items` array itself.
343
+
344
+ Categories and their options _must_ contain a 'description' that helps identifying the options and categories when
345
+ searching for values in `dynamicFiltering` mode.
346
+
347
+ <Canvas sourceState="hidden">
348
+ <Story of={DropdownStories.NestedMultiSelect} />
349
+ </Canvas>
350
+
351
+ ### Static categories
352
+
353
+ Static categories (not to be confused with the Nested options categories) add an additional level of organisation to the
354
+ dropdown list. They're static, meaning they cannot be interacted with or searched for. They also don't appear in
355
+ selected section.
356
+
357
+ To display them the data structure must have 1 level of nested options (same as in Nested Options) and use the property
358
+ `static-categories`. Static categories can be used along with `multiple` to enable multi selection.
359
+
360
+ <Canvas sourceState="hidden">
361
+ <Story of={DropdownStories.StaticCategories} />
362
+ </Canvas>
363
+
364
+ ### Readonly
365
+
366
+ Setting the `readonly` property renders the `dropdown` component in a readonly state. In terms of functionality, read
367
+ only states are focusable and submittable in a form. They're considered to be 'active' from an application's point of
368
+ view.
369
+
370
+ ### Tooltip
371
+
372
+ A tooltip with extra information for the field can be rendered by passing the desired information to the `tooltipText`
373
+ prop.
374
+
375
+ ## Accessibility
376
+
377
+ ### Keyboard interaction
378
+
379
+ Being an interactive element, the dropdown is reachable through keyboard navigation in the logical navigation flow of
380
+ the page. The following keys trigger the following actions:
381
+
382
+ Dropdown `combobox` in focus:
383
+
384
+ | Keyboard interaction | Action |
385
+ | -------------------- | ----------------------------------------------------- |
386
+ | `Tab` | Moves focus to the dropdown selector or away from it. |
387
+ | `Space` | Opens / closes dropdown |
388
+ | `Enter` | Opens / closes dropdown |
389
+ | `Escape` | Closes dropdown |
390
+ | `Arrow down` | Move down the dropdown options list |
391
+
392
+ Dropdown `listbox` in focus:
393
+
394
+ | Keyboard interaction | Action |
395
+ | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
396
+ | `Escape` | Closes popup. |
397
+ | `Enter` | Select option in dropdown and cloes popup. |
398
+ | `Down Arrow` | Moves focus to and selects the next option. If focus is on the last option, either returns focus to the combobox or does nothing. |
399
+ | `Up Arrow` | Moves focus to and selects the previous option. If focus is on the first option, either returns focus to the combobox or does nothing. |
400
+ | `Up Arrow` | Moves focus to and selects the previous option. If focus is on the first option, either returns focus to the combobox or does nothing. |
401
+
402
+ ### Roles, states and properties
403
+
404
+ Adhering to the [WAI-ARIA 1.2](https://www.w3.org/TR/wai-aria-1.2/) standard we follow the
405
+ [combobox pattern](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/#wai-ariaroles,states,andproperties) documentation
406
+ provided.
407
+
408
+ ### Roles, states and properties
409
+
410
+ | Role | Attribute | Element | Usage |
411
+ | -------- | ---------------------- | --------------- | ----------------------------------------------------------------------------------------------- |
412
+ | combobox | | `input` / `div` | Indicates the element has an associated popup. The popup may be a `listbox` or `tree` |
413
+ | | `aria-label` | | The input requires an accessible name that will be assigned to it |
414
+ | | `aria-readonly="true"` | | Set only when `readonly` is set to `true` |
415
+ | listbox | | | Allows the user to select one or more items from a list of choices (single and multi select) |
416
+ | tree | | | Allows the user to select one or more items from a hierarchically organised collection (nested) |
417
+
418
+ ## Resources
419
+
420
+ - [Figma link](https://www.figma.com/file/uLabwF3243jdMDsNSP7U9I/Bento---Components?type=design&node-id=964-18157&t=VN4tRotGCnkexZI4-0)
@@ -0,0 +1 @@
1
+ import { action } from '@storybook/addon-actions'; import { computed, onMounted, ref, watch } from 'vue'; import { isVue2 } from 'vue-demi'; import { type BentoListboxOptionItem, type BentoListboxOptions, type BentoListboxSelectedValue } from '@/types/listbox'; import { BentoDropdownEvent, BentoDropdownLazyLoadType, type BentoDropdownSearchOptions, } from '@/components/dropdown/dropdown.types'; import { BentoTag, BentoTagVariant } from '@/components/tag'; import { useCountryName, useCountryPhoneCode } from '@/components/country'; import { storybookDocsParameter } from '@/utils/ts/storybook'; import type { Meta, StoryObj } from '@storybook/vue'; import BentoDropdown from './dropdown.vue'; import DropdownWithDescriptionExample from './__tests__/dropdown-with-description-example.vue?raw'; import DropdownWithCustomSlotsExample from './__tests__/dropdown-with-custom-slots-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 })); const DEFAULT_DESCRIPTION = 'Choose an option'; const DEFAULT_LABEL = 'Label'; const DEFAULT_PLACEHOLDER = 'Select an option'; const DEFAULT_PROPS = { label: DEFAULT_LABEL, description: DEFAULT_DESCRIPTION, placeholder: DEFAULT_PLACEHOLDER, lazyLoadType: 'none', enableValueLabelPair: false, }; const COUNTRY_OPTIONS = ['NL', 'BE', 'DE'].map(code => ({ label: `${useCountryName(code, 'en-US').value} ${useCountryPhoneCode(code).value}`, value: code, displayValue: `${code} (${useCountryPhoneCode(code).value})`, })); const showMoreOptions = () => action(BentoDropdownEvent.SHOW_MORE)(); const meta: Meta = { title: 'Dropdown', component: BentoDropdown, argTypes: { 'show-more': { name: 'show-more', table: { type: { summary: '() => void' }, }, control: { disable: true }, }, size: { table: { disable: true } }, // Slots default: { description: 'Custom slots for the items and display value', table: { category: 'Slots', type: { summary: 'VNode[]' }, }, control: { type: 'text' }, }, description: { description: 'Custom slot for the description', table: { category: 'Slots', type: { summary: 'VNode[]' }, }, control: { type: 'text' }, }, 'display-value': { description: 'Custom slot for the display value when the display value is different than the items', table: { category: 'Slots', type: { summary: 'VNode[]' }, }, control: { type: 'text' }, }, innerSlot: { // Hide non-existent slot inaccurately created by storybook table: { disable: true }, }, // Values lazyLoadType: { options: Object.values(BentoDropdownLazyLoadType), mapping: BentoDropdownLazyLoadType, control: { type: 'select', }, }, error: { control: { type: 'boolean' }, table: { category: 'Deprecated' }, }, input: { description: 'Deprecated since version 2.0. Use `v-model` or `update:model-value` instead', table: { category: 'Deprecated' }, control: { disable: true }, }, value: { control: { type: 'text' }, table: { category: 'Deprecated' }, description: 'Deprecated for usage with properties since version 2.0. Use `v-model` or `model-value` property instead', }, }, }; export default meta; type Story = StoryObj<typeof BentoDropdown>; const defaultDocs = ` <template> <bento-dropdown v-model="modelValue" aria-label="Descriptive definition of what the dropdown is for" description="Description of the field" :condensed="false" :disabled="false" :empty-state="emptyState" dynamic-filtering error-message="This is an error message" :multiple="false" :items="items" :is-option-disabled="isOptionDisabled" label="Dropdown input label" :optional="!isRequired" tooltip-text="Label tooltip text" placeholder="Dropdown placeholder text" :required="isRequired" enable-value-label-pair /> </template> <script setup lang="ts"> import { BentoDropdown, BentoDropdownEmptyStateProps, BentoListboxOptionItem, BentoListboxOptions, BentoListboxSelectedValue, } from '@adyen/bento-vue2'; import { computed, ref } from 'vue'; const modelValue = ref<BentoListboxSelectedValue>({ label: 'Option 1', value: 1 }); const items: BentoListboxOptions = new Array(10) .fill(0) .map((_, index) => ({ label: \`Option \${index + 1}\`, value: index + 1 })); // "required" and "optional" are mutually exclusive const isRequired = ref(false); const emptyState = computed<BentoDropdownEmptyStateProps>(() => ({ title: 'Unable to retrieve items', action: { title: 'Refresh the page to try again', event: () => { // Refresh page logic }, }, })); // Disable individual options const isOptionDisabled = (option: BentoListboxOptionItem) => option.value === 3; </script> `; export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoDropdown }, props: Object.keys(argTypes), setup(props) { const args = isVue2 ? props : _args; const selectedValue = ref(args.modelValue); const isOptionDisabled = (option: BentoListboxOptionItem) => option.value === 3; const onUpdateModelValue = (newSelectedValue: BentoListboxSelectedValue) => { selectedValue.value = newSelectedValue; action('update:model-value')(newSelectedValue); }; return { // Values args, selectedValue, // Methods isOptionDisabled, onUpdateModelValue, showMoreOptions, }; }, template: ` <div style="max-width: 300px"> <bento-dropdown v-bind="args" :model-value="selectedValue" @update:model-value="onUpdateModelValue" :isOptionDisabled="isOptionDisabled" @show-more="showMoreOptions" /> </div> `, }), args: { ...DEFAULT_PROPS, modelValue: DEFAULT_OPTIONS[0].value, items: DEFAULT_OPTIONS, }, parameters: storybookDocsParameter(defaultDocs), }; export const MultiSelect: Story = { render: (_args, { argTypes }) => ({ components: { BentoDropdown }, props: Object.keys(argTypes), setup(props) { const args = isVue2 ? props : _args; const selectedValue = ref(args.modelValue); const isOptionDisabled = (option: BentoListboxOptionItem) => [3, 5, 7, 9].includes(option.value as number); const onUpdateModelValue = (newSelectedValue: BentoListboxSelectedValue) => { selectedValue.value = newSelectedValue; action('update:model-value')(newSelectedValue); }; return { // Values selectedValue, args, // Methods isOptionDisabled, onUpdateModelValue, showMoreOptions, }; }, template: ` <div style="max-width: 300px"> <bento-dropdown v-bind="args" :model-value="selectedValue" @update:model-value="onUpdateModelValue" :isOptionDisabled="isOptionDisabled" @show-more="showMoreOptions" /> </div> `, }), args: { ...DEFAULT_PROPS, description: 'Choose one or more options', modelValue: [1, 3, 100], multiple: true, items: DEFAULT_OPTIONS, }, parameters: storybookDocsParameter(` <template> <bento-dropdown v-model="modelValue" aria-label="Descriptive definition of what the dropdown is for" description="Description of the field" :condensed="false" :disabled="false" :empty-state="emptyState" dynamic-filtering error-message="This is an error message" :multiple="true" :items="items" :is-option-disabled="isOptionDisabled" label="Dropdown input label" :optional="!isRequired" tooltip-text="Label tooltip text" placeholder="Dropdown placeholder text" :required="isRequired" enable-value-label-pair @update:model-value="onUpdateModelValue" /> </template> <script setup lang="ts"> import { BentoDropdown, BentoDropdownEmptyStateProps, BentoListboxOptionItem, BentoListboxOptions, BentoListboxSelectedValue, } from '@adyen/bento-vue2'; import { computed, ref } from 'vue'; const modelValue = ref<BentoListboxSelectedValue>([ { label: 'Option 1', value: 1 } ]); const items: BentoListboxOptions = new Array(10) .fill(0) .map((_, index) => ({ label: \`Option \${index + 1}\`, value: index + 1 })); // "required" and "optional" are mutually exclusive const isRequired = ref(false); const emptyState = computed<BentoDropdownEmptyStateProps>(() => ({ title: 'Unable to retrieve items', action: { title: 'Refresh the page to try again', event: () => { // Refresh page logic }, }, })); // Disable individual options const isOptionDisabled = (option: BentoListboxOptionItem) => option.value === 3; const onUpdateModelValue = (newSelectedValue: BentoListboxSelectedValue) => { modelValue.value = newSelectedValue; }; </script> `), }; const TAG_LIST = Object.values(BentoTagVariant); const TAG_OPTIONS: BentoListboxOptions = new Array(TAG_LIST.length) .fill(0) .map((_, index) => ({ label: `Option ${index + 1}`, value: TAG_LIST[index] })); export const Slots: Story = { render: (_args, { argTypes }) => ({ components: { BentoDropdown, BentoTag }, props: Object.keys(argTypes), setup(props) { const selectedValue = ref(props.value); const onUpdateModelValue = (newSelectedValue: BentoListboxSelectedValue) => { selectedValue.value = newSelectedValue; action('update:model-value')(newSelectedValue); }; return { // Values args: isVue2 ? props : _args, selectedValue, // Methods onUpdateModelValue, showMoreOptions, }; }, template: ` <div style="max-width: 300px"> <bento-dropdown v-bind="args" :model-value="selectedValue" @update:model-value="onUpdateModelValue" v-slot="{label, value}" @show-more="showMoreOptions" > <bento-tag :variant="value" :label="label" /> </bento-dropdown> </div> `, }), args: { ...DEFAULT_PROPS, modelValue: TAG_OPTIONS[1].value, items: TAG_OPTIONS, }, parameters: storybookDocsParameter(DropdownWithCustomSlotsExample), }; export const WithDescription: Story = { ...Default, args: { ...DEFAULT_PROPS, modelValue: [1, 2, 3], multiple: true, items: DEFAULT_OPTIONS.map(item => ({ ...item, description: `Description ${item.value}`, })), }, parameters: storybookDocsParameter(DropdownWithDescriptionExample), }; export const NestedMultiSelect: Story = { render: (_args, { argTypes }) => ({ components: { BentoDropdown }, props: Object.keys(argTypes), setup(props) { const args = isVue2 ? props : _args; const selectedValue = ref(args.modelValue); const isOptionDisabled = (option: BentoListboxOptionItem) => [4, 5].includes(option.value as number); const onUpdateModelValue = (newSelectedValue: BentoListboxSelectedValue) => { selectedValue.value = newSelectedValue; action('update:model-value')(newSelectedValue); }; return { // Values args, selectedValue, // Methods isOptionDisabled, onUpdateModelValue, }; }, template: ` <div style="max-width: 300px"> <bento-dropdown v-bind="args" :model-value="selectedValue" @update:model-value="onUpdateModelValue" :isOptionDisabled="isOptionDisabled" /> </div> `, }), args: { ...DEFAULT_PROPS, description: 'Choose one or more options', modelValue: [2, 3, 100], multiple: true, items: [ { label: 'Option 1', description: 'Uncategorized', value: 1, }, { label: 'Category 1', description: 'C1', value: 'c1', items: [ { label: 'Option 2', description: 'C1-O2', value: 2, }, { label: 'Option 3', description: 'C1-O3', value: 3, }, ], }, { label: 'Category 2', description: 'C2', value: 'c2', items: [ { label: 'Option 4', description: 'C2-O4', value: 4, }, { label: 'Option 5', description: 'C2-O5', value: 5, }, ], }, ] as BentoListboxOptions, }, }; const externalFilteringTemplate = storybookDocsParameter(` <template> <bento-dropdown v-model="selectedValue" :items="internalList" :search="search" :component-loading="isSearching" dynamic-filtering multiple has-more-items lazy-load-type="automatic" label="Dropdown with external filtering" enable-value-label-pair /> </template> <script setup lang="ts"> import { ref, computed } from 'vue'; import { BentoDropdown, BentoListboxOptions, BentoListboxSelectedValueLabelPair, BentoDropdownSearchOptions, } from '@adyen/bento-vue2'; const initialItems: BentoListboxOptions = [ { label: 'Option 1', value: 1 }, { label: 'Option 2', value: 2 }, { label: 'Option 3', value: 3 }, { label: 'Option 4', value: 4 }, { label: 'Option 5', value: 5 }, ]; const selectedValue = ref<Array<BentoListboxSelectedValueLabelPair>>([]); const internalList = ref<BentoListboxOptions>(initialItems); const isSearching = ref(false); const searchForValue = (searchString: string) => { // This is where you would typically make an API call // to fetch data based on the searchString. // For this example, we'll simulate it. if (!searchString) { internalList.value = initialItems; return; } const newItems = new Array(5).fill(0).map((_, index) => ({ label: \`\${searchString} \${index + 1}\`, value: \`\${searchString}-\${index + 1}\`, })); internalList.value = newItems; }; const search = computed<BentoDropdownSearchOptions>(() => ({ debounceTime: 500, searchEvent: async (value: string) => { isSearching.value = true; // Simulate API call delay await new Promise(resolve => setTimeout(resolve, 1000)); searchForValue(value); isSearching.value = false; }, })); </script> `); export const externalFiltering: Story = { render: (_args, { argTypes }) => ({ components: { BentoDropdown }, props: Object.keys(argTypes), setup(props) { const args = isVue2 ? props : _args; const selectedValue = ref(args.modelValue); const internalList = ref<BentoListboxOptions>(args.items); const isSearching = ref(false); const searchForValue = (searchString: string) => { // This is where you would typically make an API call // to fetch data based on the searchString. // For this example, we'll simulate it. if (!searchString) { internalList.value = args.items; return; } const newItems = new Array(5).fill(0).map((_, index) => ({ label: `${searchString} ${index + 1}`, value: `${searchString}-${index + 1}`, })); internalList.value = newItems; }; const search = computed<BentoDropdownSearchOptions>(() => ({ debounceTime: args.search.debounceTime, searchEvent: async value => { isSearching.value = true; await new Promise(resolve => setTimeout(resolve, 1000)); searchForValue(value); action('searchEvent')(value); isSearching.value = false; }, })); const onUpdateModelValue = (newSelectedValue: BentoListboxSelectedValue) => { selectedValue.value = newSelectedValue; action('update:model-value')(newSelectedValue); }; return { args, selectedValue, internalList, isSearching, search, onUpdateModelValue, }; }, template: ` <div style="max-width: 300px"> <bento-dropdown v-bind="args" :model-value="selectedValue" :items="internalList" :search="search" :component-loading="isSearching" @update:model-value="onUpdateModelValue" /> </div> `, }), args: { ...DEFAULT_PROPS, items: DEFAULT_OPTIONS, label: 'Dropdown with external filtering', description: 'Type to simulate fetching new items.', dynamicFiltering: true, multiple: true, modelValue: [], search: { debounceTime: 500, }, }, parameters: externalFilteringTemplate, }; const lazyLoadingTemplate = storybookDocsParameter(` <template> <bento-dropdown v-model="selectedValue" :items="listboxItems" :loading="isLoading" :has-more-items="hasMoreItems" :lazy-load-type="lazyLoadType" label="Lazy Loading Dropdown" multiple enable-value-label-pair @show-more="onLazyLoad" /> </template> <script setup lang="ts"> import { ref, onMounted } from 'vue'; import { BentoDropdown, BentoListboxOptions, BentoListboxSelectedValueLabelPair, BentoDropdownLazyLoadType, } from '@adyen/bento-vue2'; // --- Mock API for demonstration --- const ALL_ITEMS = Array.from({ length: 100 }, (_, i) => ({ label: \`Item \${i + 1}\`, value: i + 1, })); const PAGE_SIZE = 20; const fetchItems = (page: number) => { console.log(\`Fetching page: \${page}\`); return new Promise<{ items: BentoListboxOptions; hasMore: boolean }>(resolve => { setTimeout(() => { const start = (page - 1) * PAGE_SIZE; const end = start + PAGE_SIZE; const pageItems = ALL_ITEMS.slice(start, end); resolve({ items: pageItems, hasMore: end < ALL_ITEMS.length, }); }, 500); }); }; // --- End Mock API --- const selectedValue = ref<Array<BentoListboxSelectedValueLabelPair>>([]); const listboxItems = ref<BentoListboxOptions>([]); const isLoading = ref(false); const hasMoreItems = ref(true); const currentPage = ref(1); const lazyLoadType = ref<BentoDropdownLazyLoadType>('automatic'); // or 'button' const onLazyLoad = async () => { if (isLoading.value || !hasMoreItems.value) { return; } currentPage.value++; isLoading.value = true; const { items, hasMore } = await fetchItems(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(async () => { isLoading.value = true; const { items, hasMore } = await fetchItems(currentPage.value); listboxItems.value = items; hasMoreItems.value = hasMore; isLoading.value = false; }); </script> `); export const LazyLoading: Story = { render: (_args, { argTypes }) => ({ components: { BentoDropdown }, props: Object.keys(argTypes), setup(props) { const args = isVue2 ? props : _args; const selectedValue = ref<BentoListboxSelectedValue>([]); const listboxItems = ref<BentoListboxOptions>([]); const isLoading = ref(false); const hasMoreItems = ref(true); const currentPage = ref(1); const allItems = Array.from({ length: 100 }, (_, i) => ({ label: `Item ${i + 1}`, value: i + 1, })); const pageSize = 20; const fetchItems = (page: number) => { return new Promise<{ items: BentoListboxOptions; hasMore: boolean }>(resolve => { setTimeout(() => { const start = (page - 1) * pageSize; const end = start + pageSize; const pageItems = allItems.slice(start, end); resolve({ items: pageItems, hasMore: end < allItems.length, }); }, 500); }); }; const onLazyLoad = async () => { if (isLoading.value || !hasMoreItems.value) { return; } currentPage.value++; isLoading.value = true; const { items, hasMore } = await fetchItems(currentPage.value); listboxItems.value = [...listboxItems.value, ...items]; hasMoreItems.value = hasMore; isLoading.value = false; action('show-more')(); }; const onUpdateModelValue = (newSelectedValue: BentoListboxSelectedValue) => { selectedValue.value = newSelectedValue; action('update:model-value')(newSelectedValue); }; const loadInitial = async () => { currentPage.value = 1; isLoading.value = true; const { items, hasMore } = await fetchItems(currentPage.value); listboxItems.value = items; hasMoreItems.value = hasMore; isLoading.value = false; }; watch(() => args.lazyLoadType, loadInitial, { immediate: true }); return { args, selectedValue, listboxItems, isLoading, hasMoreItems, onLazyLoad, onUpdateModelValue, }; }, template: ` <div style="max-width: 300px"> <bento-dropdown v-bind="args" :items="listboxItems" :model-value="selectedValue" :loading="isLoading" :has-more-items="hasMoreItems" @update:model-value="onUpdateModelValue" @show-more="onLazyLoad" /> </div> `, }), args: { ...DEFAULT_PROPS, label: 'Lazy Loading', description: 'Scroll down or click the button to load more items.', multiple: true, lazyLoadType: 'automatic', }, parameters: lazyLoadingTemplate, }; const externalFilteringAndLazyLoadingTemplate = storybookDocsParameter(` <template> <bento-dropdown v-model="selectedValue" :items="listboxItems" :component-loading="isComponentLoading" :loading="isLazyLoading" :has-more-items="hasMoreItems" :search="search" :lazy-load-type="lazyLoadType" label="External Filtering & Lazy Loading" dynamic-filtering multiple enable-value-label-pair @show-more="onLazyLoad" /> </template> <script setup lang="ts"> import { ref, computed, onMounted } from 'vue'; import { BentoDropdown, BentoListboxOptions, BentoListboxSelectedValueLabelPair, BentoDropdownSearchOptions, BentoDropdownLazyLoadType, } from '@adyen/bento-vue2'; // --- Mock API for demonstration --- const ALL_ITEMS = Array.from({ length: 1000 }, (_, i) => ({ label: \`Item \${i + 1}\`, value: i + 1, })); const PAGE_SIZE = 20; const fetchItems = (searchTerm: string, page: number) => { 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) * PAGE_SIZE; const end = start + PAGE_SIZE; const pageItems = filtered.slice(start, end); resolve({ items: pageItems, hasMore: end < filtered.length, }); }, 500); }); }; // --- End Mock API --- const selectedValue = ref<Array<BentoListboxSelectedValueLabelPair>>([]); const listboxItems = ref<BentoListboxOptions>([]); const isComponentLoading = ref(false); const isLazyLoading = ref(false); const hasMoreItems = ref(true); const currentPage = ref(1); const currentSearchTerm = ref(''); const lazyLoadType = ref<BentoDropdownLazyLoadType>('automatic'); const onSearch = async (searchTerm: string) => { currentSearchTerm.value = searchTerm; currentPage.value = 1; isComponentLoading.value = true; const { items, hasMore } = await fetchItems(searchTerm, currentPage.value); listboxItems.value = items; hasMoreItems.value = hasMore; isComponentLoading.value = false; }; const onLazyLoad = async () => { if (isLazyLoading.value || !hasMoreItems.value) { return; } currentPage.value++; isLazyLoading.value = true; const { items, hasMore } = await fetchItems(currentSearchTerm.value, currentPage.value); listboxItems.value = [...listboxItems.value, ...items]; hasMoreItems.value = hasMore; isLazyLoading.value = false; }; const search = computed<BentoDropdownSearchOptions>(() => ({ searchEvent: onSearch, })); onMounted(() => { onSearch(''); }); </script> `); export const ExternalFilteringAndLazyLoading: Story = { render: (_args, { argTypes }) => ({ components: { BentoDropdown }, props: Object.keys(argTypes), setup(props) { const args = isVue2 ? props : _args; const selectedValue = ref<BentoListboxSelectedValue>([{ label: 'Item 2000', value: 2000 }]); const listboxItems = ref<BentoListboxOptions>([]); const isComponentLoading = ref(false); const isLazyLoading = ref(false); const hasMoreItems = ref(true); const currentPage = ref(1); const currentSearchTerm = ref(''); const allItems = Array.from({ length: 1000 }, (_, i) => ({ label: `Item ${i + 1}`, value: i + 1, })); const pageSize = 20; const fetchItems = (searchTerm: string, page: number) => { return new Promise<{ items: BentoListboxOptions; hasMore: boolean }>(resolve => { setTimeout(() => { const filtered = allItems.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); }); }; const onSearch = async (searchTerm: string) => { action('searchEvent')(searchTerm); currentSearchTerm.value = searchTerm; currentPage.value = 1; isComponentLoading.value = true; const { items, hasMore } = await fetchItems(searchTerm, currentPage.value); listboxItems.value = items; hasMoreItems.value = hasMore; isComponentLoading.value = false; }; const onLazyLoad = async () => { if (isLazyLoading.value || !hasMoreItems.value) { return; } currentPage.value++; isLazyLoading.value = true; const { items, hasMore } = await fetchItems(currentSearchTerm.value, currentPage.value); listboxItems.value = [...listboxItems.value, ...items]; hasMoreItems.value = hasMore; isLazyLoading.value = false; action('show-more')(); }; const onUpdateModelValue = (newSelectedValue: BentoListboxSelectedValue) => { selectedValue.value = newSelectedValue; action('update:model-value')(newSelectedValue); }; const search = computed<BentoDropdownSearchOptions>(() => ({ searchEvent: onSearch, debounceTime: args.search?.debounceTime, })); onMounted(() => { onSearch(''); }); return { args, selectedValue, listboxItems, isComponentLoading, isLazyLoading, hasMoreItems, search, onLazyLoad, onUpdateModelValue, }; }, template: ` <div style="max-width: 300px"> <bento-dropdown v-bind="args" :items="listboxItems" :model-value="selectedValue" :component-loading="isComponentLoading" :loading="isLazyLoading" :has-more-items="hasMoreItems" :search="search" @update:model-value="onUpdateModelValue" @show-more="onLazyLoad" /> </div> `, }), args: { ...DEFAULT_PROPS, label: 'External Filtering & Lazy Loading', description: 'Search for items and scroll to load more results.', dynamicFiltering: true, multiple: true, lazyLoadType: 'automatic', search: { debounceTime: 500, }, }, parameters: externalFilteringAndLazyLoadingTemplate, }; export const StaticCategories: Story = { render: (_args, { argTypes }) => ({ components: { BentoDropdown }, props: Object.keys(argTypes), setup(props) { const selectedValue = ref(props.value); const onUpdateModelValue = (newSelectedValue: BentoListboxSelectedValue) => { selectedValue.value = newSelectedValue; action('update:model-value')(newSelectedValue); }; return { // Values selectedValue, args: isVue2 ? props : _args, // Methods onUpdateModelValue, }; }, template: ` <div style="max-width: 300px"> <bento-dropdown v-bind="args" :model-value="selectedValue" @update:model-value="onUpdateModelValue" /> </div> `, }), args: { ...DEFAULT_PROPS, description: 'Select an option from a category', modelValue: '', multiple: false, staticCategories: true, items: [ { label: 'Carnivores', value: 'carnivores', items: [ { label: 'T-Rex', value: 'trex', }, { label: 'Velociraptor', value: 'velociraptor', }, ], }, { label: 'Herbivores', value: 'herbivores', items: [ { label: 'Iguanodon', value: 'iguanodon', }, { label: 'Barney', value: 'barney', }, ], }, { label: 'Aquatic', value: 'aquatic', items: [ { label: 'Mosasaurus', value: 'mosasaurus', }, { label: 'Nessie', value: 'nessie', }, ], }, ] as BentoListboxOptions, }, }; export const VirtualScrolling: Story = { ...Default, args: { ...Default.args, items: new Array(1000).fill(0).map((_, index) => ({ label: `Option ${index + 1}`, value: index + 1 })), virtualScroll: true, }, }; export const CustomDisplayValue: Story = { render: (_args, { argTypes }) => ({ components: { BentoDropdown }, props: Object.keys(argTypes), setup(props) { const args = isVue2 ? props : _args; const selectedValue = ref(args.modelValue); const onUpdateModelValue = (newSelectedValue: BentoListboxSelectedValue) => { selectedValue.value = newSelectedValue; action('update:model-value')(newSelectedValue); }; return { // Values args, selectedValue, // Methods onUpdateModelValue, }; }, template: ` <div style="max-width: 300px"> <bento-dropdown v-bind="args" :model-value="selectedValue" @update:model-value="onUpdateModelValue" /> </div> `, }), args: { ...DEFAULT_PROPS, modelValue: COUNTRY_OPTIONS[0].value, items: COUNTRY_OPTIONS, }, parameters: storybookDocsParameter(` <bento-dropdown :items="[ {label: "Netherlands +31", displayValue: "NL +31", value: "NL"}, {label: "Belgium +32", displayValue: "BE +32", value: "BE"}, {label: "Germany +49", displayValue: "DE +49", value: "DE"}, ]" :model-value="selectedValue" @update:model-value="onUpdateModelValue" /> `), };
@@ -0,0 +1 @@
1
+ <!-- TODO: Remove aria props as these are part of JSX elements /vue/types/jsx.d.ts --> <!-- eslint-disable vue/attribute-hyphenation --> <template> <div v-bento-click-outside-directive="clickOutsideDropdown" class="b-dropdown" :class="conditionalClasses"> <field-label v-if="label" :id="labelId" :condensed="condensed" :label="label" :tooltip-text="tooltipText" :optional="optional" :required="required" /> <div ref="inputContainerRefWrapper" v-bento-keyboard-navigation-directive v-on="textboxKeyboardNavigationListeners" > <component :is="computedSize" ref="inputContainerRef" :condensed="condensed" :value="searchTerm" :additional-items-selected="additionalItemsSelected" :aria-controls="dropdownOptionsContainerId" :ariaExpanded="isDropdownOpen" :aria-labelledby="label ? labelId : null" :aria-describedby="ariaDescribedBy" :aria-label="computedAriaLabel" :aria-readonly="isReadOnly" :readonly="isReadOnly" :required="required" :disabled="disabled" :display-value="displayValue" :dynamic-filtering="dynamicFiltering" :is-invalid="!!errorMessage || error" :multiple="multiple" :open="isDropdownOpen" :selected-value-item="!isInputComponent ? selectedValueItem : undefined" :show-slot-content-in-multiple="showSlotContentInMultiple" @input="searchTerm = $event" @open="toggleDropdown" @close="toggleDropdown" > <template v-for="(_, innerSlot) of slots" #[innerSlot]="scope"> <slot :name="innerSlot" v-bind="scope" /> </template> </component> </div> <bento-dropdown-options-container v-if="inputContainerRef" :id="dropdownOptionsContainerId" ref="optionsContainerRef" :aria-label="computedAriaLabel" :component-loading="componentLoading" :empty-state="emptyState" :debounce-time="search?.debounceTime" :disabled="disabled" :is-option-disabled="isOptionDisabled" :items="filteredItems" :multiple="multiple" :static-categories="staticCategories" :open="isDropdownOpen" :selected-value="cachedSelectedListboxOptions" :searching="isSearching" :target-element="inputContainerRefWrapper" :loading="loading" :has-more-items="hasMoreItems" :lazy-load-type="lazyLoadType" :virtual-scroll="virtualScroll" v-on="listboxListeners" > <template v-for="(_, innerSlot) of slots" #[innerSlot]="scope"> <slot :name="innerSlot" v-bind="scope" /> </template> </bento-dropdown-options-container> <error-message v-if="!!errorMessage" :id="errorId" :error-message="errorMessage" class="b-dropdown__error-message" /> <bento-typography v-if="description || hasSlot('description')" :id="descriptionId" class="b-dropdown__description" :class="descriptionConditionalClasses" el="span" > <slot name="description"> {{ description }} </slot> </bento-typography> </div> </template> <script setup lang="ts"> import { computed, defineComponent, inject, nextTick, type Ref, ref, toRef, toRefs, useAttrs, useSlots, watch, } from 'vue'; // Components import { BentoDropdownOptionsContainer } from './components/dropdown-options-container'; import { BentoTypography } from '@/components/typography'; import { DropdownInputDefault, DropdownInputSmall } from './components'; import { BentoListbox, ErrorMessage, FieldLabel, useCachedSelectedValues, useMultiLevelItems } from '@/internal'; // Utils import { deprecate } from '@/utils/ts/deprecate'; import { generateUid } from '@/core/utils/ts'; import { useAriaLabel } from '@/utils/ts/aria-label'; import { useI18n } from '@/utils/ts/i18n'; import { useFormFieldEmits } from '@/utils/ts/form-field/form-field'; // Composables import { useDisplayValue, useListboxKeyboardNavigation, useTextboxKeyboardNavigation } from './composables'; import { useVisibleDomOptions } from '@/internal/listbox/composables/use-visible-dom-options/use-visible-dom-options'; import { useFormLayoutFieldLoading } from '@/composables/use-form-layout-loading/use-form-layout-loading'; import { useSearchBarFilter } from '@/components/search-bar/useSearchBarDataFilter'; // Directives import { BentoClickOutsideDirective as vBentoClickOutsideDirective } from '@/directives/click-outside'; import { BentoKeyboardNavigationDirective as vBentoKeyboardNavigationDirective } from '@/directives'; // Types import { BentoDropdownEvent, type BentoDropdownProps, BentoDropdownSize } from './dropdown.types'; import { BentoListboxEvent, type BentoListboxItemRole, type BentoListboxMultiSelectValue, type BentoListboxOptions, type BentoListboxSelectedValue, type BentoListboxSelectedValueLabelPair, } from '@/types/listbox'; import messages from './messages.json'; import { useHasSlot } from '@/composables'; import { INPUT_FIELD_COMPONENT_INJECTION_KEY } from '@/components/input-field/input-field.keys'; type MessageSchema = (typeof messages)['en-US']; const attrs = useAttrs(); const slots = useSlots(); const hasSlot = useHasSlot(slots); const props = withDefaults(defineProps<BentoDropdownProps>(), { ariaLabel: null, condensed: false, emptyState: undefined, disabled: false, dynamicFiltering: false, error: false, errorMessage: null, description: null, hasMoreItems: false, isOptionDisabled: undefined, items: () => [], label: null, loading: false, componentLoading: false, lazyLoadType: BentoListbox.props.lazyLoadType.default, multiple: false, optional: false, placeholder: null, readonly: false, required: false, search: undefined, showSlotContentInMultiple: false, size: null, staticCategories: false, tooltipText: null, value: null, modelValue: null, virtualScroll: false, enableValueLabelPair: false, }); const emit = defineEmits<{ /** * Emitted when an option is clicked. Updates the selected option from the "value" property linked to the v-model. * @deprecated since version 2.0. Use `v-model` or `update:model-value instead. */ (e: 'input', selectedValue: BentoListboxSelectedValue | any); // eslint-disable-line @typescript-eslint/no-explicit-any /** * Emitted when an option is clicked. Updates the selected option from the "modelValue" property linked to the v-model. */ (e: 'update:model-value', selectedValue: BentoListboxSelectedValue | any); // eslint-disable-line @typescript-eslint/no-explicit-any /** * Emitted when: * - `lazyLoad` is `automatic` and the scroll meets the end of the list * - `lazyload` is `button` and the button is clicked */ (e: 'show-more'); /** * Emitted when the dropdown is opened. */ (e: 'open'); }>(); const { t } = useI18n<{ message: MessageSchema }>({ messages }); const { emitValue } = useFormFieldEmits<BentoListboxSelectedValue>(emit); const inputContainerRef = ref(null); const inputContainerRefWrapper = ref(null); const optionsContainerRef: Ref<InstanceType<typeof BentoDropdownOptionsContainer>> = ref(null); const isDropdownOpen = ref(false); const dropdownOptionsContainerId = generateUid('bento-dropdown-options-container'); const labelId = generateUid('dropdown-label'); const descriptionId = generateUid('dropdown-description'); const errorId = generateUid('dropdown-error'); const { isReadOnly } = useFormLayoutFieldLoading(toRef(props, 'readonly')); const dropdownValue = computed(() => props.modelValue ?? props.value); const { cachedSelectedListboxOptions, cachedListboxSelectedValues, setCachedValues } = useCachedSelectedValues( toRef(props, 'items'), dropdownValue, toRef(props, 'multiple'), toRef(props, 'enableValueLabelPair') ); const ariaLabelAttribute = ref(attrs['aria-label']); const ariaDescribedByAttribute = ref(attrs['aria-describedby']); const { ariaLabel, label } = toRefs(props); // vue-i18n issue with t() used in script - direct usage in template works // requires either createI18n({ legacy: false }) or to be wrapped in computed() const computedAriaLabelFallbackMessage = computed(() => t('ariaLabelFallback')); const computedAriaLabel = useAriaLabel({ // TODO: take ariaLabel ref away when aria-label prop is removed ariaLabel: ariaLabel.value || (ariaLabelAttribute as Ref<string>), label, defaultFallback: computedAriaLabelFallbackMessage, }); // Inject input field component key to change the dropdown size const isInputComponent = props.size === BentoDropdownSize.SMALL || inject(INPUT_FIELD_COMPONENT_INJECTION_KEY, false); const computedSize = computed(() => (isInputComponent ? BentoDropdownSize.SMALL : BentoDropdownSize.DEFAULT)); const conditionalClasses = computed(() => ({ 'b-dropdown--condensed': props.condensed, })); // Description styles const descriptionConditionalClasses = computed(() => ({ 'b-dropdown__description--error': !!props.errorMessage, })); // Filtering const searchTerm = ref(''); const items = toRef(props, 'items'); const staticCategories = toRef(props, 'staticCategories'); const filteredItems = useSearchBarFilter(searchTerm, items, props?.search?.searchEvent, staticCategories); const isSearching = computed(() => props.dynamicFiltering && !!searchTerm.value); const displayValue = useDisplayValue({ items, placeholder: toRef(props, 'placeholder'), isMultiSelect: toRef(props, 'multiple'), selectedValues: cachedSelectedListboxOptions, }); const selectedValueItem = computed(() => { if (props?.multiple && Array.isArray(dropdownValue.value)) { return props.items.filter( ({ value }) => props?.value && (props?.value as BentoListboxMultiSelectValue).includes(value) ); } if (!props.multiple && props.staticCategories) { // Find the category that holds the selected value const category = props.items.find(categoryItem => categoryItem.items.some(item => item.value === dropdownValue.value) ); // Find the specific item within the category const selectedCategoryItem = category ? category.items.find(item => item.value === dropdownValue.value) : null; return selectedCategoryItem; } return props.items.find(({ value }) => value === dropdownValue.value); }); const selectedValueIndex = computed(() => { const index = filteredItems.value.findIndex(({ value }) => { return props.enableValueLabelPair ? (dropdownValue.value as BentoListboxSelectedValueLabelPair).value === value : dropdownValue.value === value; }); return index > -1 ? index : 0; }); const { isMultiLevelSelect, flattenedItemsList } = useMultiLevelItems(items); const isTreePattern = computed(() => isMultiLevelSelect.value && !props.staticCategories); const computedOptionRole = computed<BentoListboxItemRole>(() => (isTreePattern.value ? 'treeitem' : 'option')); const additionalItemsSelected = computed(() => { if (props.multiple) { const selectedItemsCount = Array.isArray(dropdownValue.value) ? dropdownValue.value.length : 0; const total = isMultiLevelSelect.value ? flattenedItemsList.value.length : items.value.length; if (selectedItemsCount === 0) { return null; } // Display count always - show "all" only when is not external filtering (hasMoreItems = true) if (!props.hasMoreItems && total > 0 && total === selectedItemsCount) { return t('all'); } return selectedItemsCount; } return null; }); // ARIA const ariaDescribedBy = computed( () => [ props.description ? descriptionId : null, ariaDescribedByAttribute.value, props.errorMessage ? errorId : null, ] .filter(Boolean) .join(' ') || null ); const listboxRef = computed(() => optionsContainerRef.value?.$el as HTMLDivElement); const { visibleDomOptions, updateVisibleDomOptions } = useVisibleDomOptions( listboxRef, filteredItems, computedOptionRole ); const openDropdown = () => { if (isReadOnly.value) { return; } isDropdownOpen.value = true; nextTick(() => { updateVisibleDomOptions(); }); }; const closeDropdown = () => { isDropdownOpen.value = false; }; const toggleDropdown = () => { if (isReadOnly.value) { return; } isDropdownOpen.value = !isDropdownOpen.value; nextTick(() => { updateVisibleDomOptions(); }); // Clear search if (isSearching.value) { searchTerm.value = ''; } }; const clickOutsideDropdown = () => { if (isSearching.value) { searchTerm.value = ''; } if (isDropdownOpen.value) { isDropdownOpen.value = false; // Reset the internal dropdown options state optionsContainerRef.value.onOutsideDropdownClick(); } }; const onOptionSelected = (selectedValue: BentoListboxOptions) => { // Clear and close the dropdown on single selection if (!props.multiple) { searchTerm.value = ''; isDropdownOpen.value = false; focus(); } setCachedValues(selectedValue); /** * Emit the value/label pair objects if enableValueLabelPair is enabled, * otherwise just emit the list of selected values (number or string) */ emitValue(cachedListboxSelectedValues.value); }; const emitShowMoreEvent = () => { emit(BentoDropdownEvent.SHOW_MORE); }; const textboxKeyboardNavigationListeners = useTextboxKeyboardNavigation( { inputContainerRef, optionsContainerRef, selectedValueIndex, isDropdownOpen, isDynamicFltering: toRef(props, 'dynamicFiltering'), items: visibleDomOptions, isMultiple: props.multiple, }, toggleDropdown, openDropdown, closeDropdown ); const listboxKeyboardNavigationListeners = useListboxKeyboardNavigation( { inputContainerRef, isDropdownOpen, }, closeDropdown ); const listboxListeners = { [BentoListboxEvent.CLOSE_DROPDOWN]: closeDropdown, [BentoListboxEvent.SELECT]: onOptionSelected, [BentoListboxEvent.SHOW_MORE]: emitShowMoreEvent, ...listboxKeyboardNavigationListeners, }; if (props.error) { deprecate( 'BentoDropdown "error" property', `Use the BentoDropdown "errorMessage" property instead to indicate that there is a modifier error. Having the field as "undefined" will hide/remove the error. <bento-dropdown errorMessage="Error message text" />`, '2.0.0' ); } if (props.size) { deprecate('BentoDropdown "size" property', `Do not use. Only 'default' size should be used.`, '2.0.0'); } if (props.value) { deprecate( 'BentoDropdown "value" property', `The use of "value" prop in "BentoDropdown" is no longer supported. Use the "v-model" or "model-value" property instead.`, '2.0.0' ); } const focus = () => { inputContainerRef.value.focus(); }; watch( () => isDropdownOpen.value, (isOpen: boolean) => { if (isOpen) { emit('open'); } } ); defineExpose({ focus, }); </script> <script lang="ts"> /** * A Dropdown shows a selected option's. * Use a dropdown when you want users to select options * from a list of pre-defined options. * * @example * import { BentoDropdown } from '@adyen/bento-vue2'; * import type { BentoDropdownOptions } from '@adyen/bento-vue2' * * export default { * components: { BentoDropdown }, * template: ` * <bento-dropdown * v-model="selectedValue" * :disabled="false" * :multiple="true" * :isOptionsDisabled="option => option.value === 2" * :items="options" * /> * `, * setup() { * const selectedValue = ref(2) // Default value * const options: BentoDropdownOptions = [ * { label: 'Option 1', value: 1 }, * { label: 'Option 2', value: 2 }, * ]; * return { options, selectedValue }; * } * } */ export default defineComponent({ i18n: { messages }, name: 'b-dropdown', components: { DropdownInputDefault, DropdownInputSmall, }, model: { prop: 'modelValue' }, }); </script> <style lang="scss" scoped src="./dropdown.scss" />
@@ -0,0 +1,115 @@
1
+ import { Canvas, Meta } from '@storybook/blocks';
2
+ import * as EmptyStateStories from './empty-state.stories';
3
+
4
+ <Meta of={EmptyStateStories} />
5
+
6
+ # Empty state
7
+
8
+ Empty states are moments in the user experience when there is nothing to display.
9
+
10
+ This component can be used to provide:
11
+
12
+ - Information about system status
13
+ - Contextual learning cues
14
+ - Direct pathways for key tasks
15
+
16
+ <Canvas of={EmptyStateStories.Default} />
17
+
18
+ ## Use Cases
19
+
20
+ There are seven empty state types commonly used in our interface:
21
+
22
+ - **First touch**: When the user is onboarding or starting to use a product area for the first time.
23
+
24
+ - **No results found**: When the user’s search request does not deliver any results, either due to search parameters
25
+ or because the information doesn’t exist.
26
+
27
+ - **Page not available (access restricted)**: When the user’s role does not allow them access to an area in the
28
+ interface.
29
+
30
+ - **Wrong environment**: When the user can access multiple environments and clicks into an area that is not available
31
+ on one of those environments.
32
+
33
+ - **All done**: When the user has read, removed, or completed all items and there is nothing more to do.
34
+
35
+ - **Planned maintenance**: A static page displayed when the website or system is temporarily unavailable.
36
+
37
+ ## Variations
38
+
39
+ ### Full-page
40
+
41
+ To be used in scenarios where the empty state is the primary focus of the page.
42
+
43
+ _Examples: First touch, maintenance and page access._
44
+
45
+ ### Embedded
46
+
47
+ To be used only when the page contains one large component (multiple components will use the basic variant).
48
+
49
+ _Examples: Within the data grid when there are no filter results._
50
+
51
+ ### Basic
52
+
53
+ To be used when multiple empty states can occur in a single page because the information is collected from different
54
+ backend calls.
55
+
56
+ _Example: dashboards, temporarily unavailable or broken._
57
+
58
+ ### Condensed
59
+
60
+ To be used in components with limited space.
61
+
62
+ _Example: When there are no results to show when searching within filters._
63
+
64
+ ## Modifiers
65
+
66
+ ### Image
67
+
68
+ The illustration shown by the empty state `full-page` / `embedded` variants. You can refer to
69
+ [this list of illustrations](https://www.figma.com/file/qSUFsdW9fjMev5nHHBfToj/Bento---Illustrations---Empty-states),
70
+ which is mapped to following values:
71
+
72
+ - `1-generic-use`
73
+ - `2-generic-use`
74
+ - `3-generic-use`
75
+ - `4-generic-use`
76
+ - `adding-payment-methods`
77
+ - `adyen-giving`
78
+ - `internal-error`
79
+ - `no-results-found`
80
+ - `notifications-cleared`
81
+ - `page-not-found`
82
+ - `planned-maintenance`
83
+ - `referrals`
84
+ - `upload-files`
85
+ - `wrong-environment`
86
+
87
+ ## Accessibility
88
+
89
+ Illustrations are considered decorative, they should be skipped by screen readers. Users should be able to:
90
+
91
+ - hear the component's label and description, and the purpose is clear;
92
+ - identify the component's role as an empty state.
93
+
94
+ ### Keyboard interaction
95
+
96
+ A list of keyboard interactions is provided in the table below:
97
+
98
+ | Keyboard interaction | Output |
99
+ | -------------------- | -------------------------- |
100
+ | `Tab` | Moves focus to the action. |
101
+ | `Space`/`Enter` | Activates the action. |
102
+
103
+ ### Roles, states and properties
104
+
105
+ | ARIA attribute | is automatic? | fallback used | manual override |
106
+ | ---------------- | --------------------------------------------------------- | ------------- | --------------------------------------------------------------- |
107
+ | `alt` | Yes, set to `""` since the image is only decorative. | - | - |
108
+ | `role="heading"` | Yes, through the semantic heading elements. | - | The user can pass a different element through `headingEl` prop. |
109
+ | `role="button"` | Yes, through the semantic `button` element in the action. | - | - |
110
+
111
+ ## Resources
112
+
113
+ - [Figma link](https://www.figma.com/file/uLabwF3243jdMDsNSP7U9I/Bento---Components?type=design&node-id=16462-6288&mode=design&t=q79EnMp4k59VirFh-0)
114
+ - [Illustrations](https://www.figma.com/file/qSUFsdW9fjMev5nHHBfToj/Bento---Illustrations---Empty-states)
115
+ - [WAI Images (decorative)](https://www.w3.org/WAI/tutorials/images/decorative/)
@@ -0,0 +1 @@
1
+ import { action } from '@storybook/addon-actions'; import type { Meta, StoryObj } from '@storybook/vue'; import { isVue2 } from 'vue-demi'; import { storybookDocsParameter } from '@/utils/ts/storybook'; import BentoEmptyState from './empty-state.vue'; import { BentoEmptyStateImage, BentoEmptyStateVariant } from './empty-state.types'; const meta: Meta = { title: 'Empty state', component: BentoEmptyState, argTypes: { // Slots default: { name: 'default', type: { name: 'string', required: false }, description: 'Default slot content', control: 'text', table: { type: { summary: 'VNode[]' } }, }, // Enum props variant: { options: Object.values(BentoEmptyStateVariant), mapping: BentoEmptyStateVariant, }, image: { options: Object.values(BentoEmptyStateImage), mapping: BentoEmptyStateImage, }, }, }; export default meta; type Story = StoryObj<typeof BentoEmptyState>; const DEFAULT_BUTTON_PROPS = { title: 'No results were found', default: 'Try a different term or reset search filters', // content slot variant: 'full-page', image: 'no-results-found', action: { title: 'Reset filters', event: () => action('action clicked')(), }, }; const defaultCode = ` <bento-empty-state title="No results were found" image="no-results-found" variant="full-page" :action="{ title: 'Reset filters', event: () => {} }" > Try a different term or reset search filters </bento-empty-state> `; export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoEmptyState }, props: Object.keys(argTypes), template: ` <bento-empty-state v-bind="args"> {{ args.default }} </bento-empty-state> `, setup(props) { return { args: isVue2 ? props : _args }; }, }), args: DEFAULT_BUTTON_PROPS, parameters: storybookDocsParameter(defaultCode), };