@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 @@
1
+ import { computed, ref } from 'vue'; import { isVue2 } from 'vue-demi'; import { storybookDocsParameter } from '@/utils/ts/storybook'; import type { Meta, StoryObj } from '@storybook/vue'; import { BentoButton, BentoLoadingButton, BentoLoadingButtonState } from '@/components/button'; import { BentoInputField } from '@/components/input-field'; import { BentoDropdown } from '@/components/dropdown'; import { BentoDatePicker } from '@/components/date-picker'; import { BentoTextarea } from '@/components/textarea'; import { BentoCheckboxGroup } from '@/components/checkbox'; import { type BentoCheckboxGroupItems } from '@/components/checkbox/components/checkbox-group/checkbox-group.types'; import { type BentoListboxOptions } from '@/types/listbox'; import { BentoRadioGroup } from '@/components/radio-group'; import { BentoSelectionCardGroup } from '@/components/selection-card/components/selection-card-group'; import { BentoFileUploader } from '@/components/file-uploader'; import { type BentoRadioGroupItems } from '@/components/radio-group/radio-group.types'; import { BentoFormLayoutFieldTip, BentoFormLayoutGroup, BentoFormLayoutTitle } from './components'; import { toTypedSchema, useForm } from '@/composables/use-form-validate/use-form'; import { BentoAlert } from '@/components/alert'; import { BentoLink } from '@/components/link'; import BentoFormLayout from './form-layout.vue'; import ChevronLeftIcon from '@adyen/ui-assets-icons-16/vue/chevron-left'; import SaveIcon from '@adyen/ui-assets-icons-16/vue/save'; import { BentoFormLayoutFieldTipTypeOptions } from './components/form-layout-field-tip/form-layout-tip.types'; import * as z from 'zod'; import BentoFormLayoutFieldTipExample from './__tests__/form-layout-field-tip-example.vue?raw'; import BentoFormLayoutDefaultExample from './__tests__/form-layout-default-example.vue?raw'; export default { title: 'Form layout', component: BentoFormLayout, argTypes: { default: { name: 'default', defaultValue: '', description: 'Can include various common form components: `bento-input-field`, `bento-dropdown`, `bento-date-picker`, `bento-checkbox-group`, etc.', table: { type: { summary: 'VNode[]' }, }, control: { disable: true, }, }, 'error-message': { name: 'error-message', defaultValue: '', description: 'Can include any HTMLElement', table: { type: { summary: 'VNode[]' }, }, control: { type: 'text', }, }, }, } as Meta<typeof BentoFormLayout>; type Story = StoryObj<typeof BentoFormLayout>; const DROPDOWN_ITEMS: BentoListboxOptions = [ { label: 'Cat', value: 'cat' }, { label: 'Dog', value: 'dog' }, ]; const CHECKBOX_GROUP_ITEMS: BentoCheckboxGroupItems = [ { value: 1, label: 'Label' }, { value: 2, label: 'Label' }, { value: 3, label: 'Label' }, ]; const RADIO_GROUP_ITEMS: BentoRadioGroupItems = [ { value: 1, label: 'Label' }, { value: 2, label: 'Label' }, { value: 3, label: 'Label' }, ]; export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoDropdown, BentoCheckboxGroup, BentoButton, BentoLoadingButton, BentoDatePicker, BentoInputField, BentoFileUploader, BentoFormLayout, BentoFormLayoutFieldTip, BentoFormLayoutGroup, BentoFormLayoutTitle, BentoRadioGroup, BentoTextarea, BentoSelectionCardGroup, }, props: Object.keys(argTypes), template: ` <bento-form-layout v-bind="args"> <template #error-message>{{ errorMessage }}</template> <bento-form-layout-title el="h1"> Section title <template #description> Optional section description </template> </bento-form-layout-title> <bento-input-field>Input field</bento-input-field> <bento-dropdown label="Dropdown" :items="dropdownItems" placeholder="Select an animal" /> <bento-date-picker label="Date picker" /> <bento-textarea label="Textarea" :character-limit="400" /> <bento-checkbox-group label="Checkbox group" :items="checkboxGroupItems" :hide-label="false" /> <bento-radio-group label="Radio group" :items="radioGroupItems" :hide-label="false" /> <bento-selection-card-group label="Selection card group" :items="selectionCardGroupItems" :hide-label="false" variant="radio" /> <bento-file-uploader label="File upload" description="Custom suppporting text" accept=".png,.jpeg,.pdf" :max-size="5242880" /> <bento-form-layout-field-tip> <bento-dropdown label="Dropdown" :items="dropdownItems" placeholder="Select an animal" /> <template #message>This is a small tip that can be used in small spaces</template> </bento-form-layout-field-tip> <bento-form-layout-title el="h2" subsection> Subsection title </bento-form-layout-title> <bento-form-layout-group> <bento-input-field>Input field (100%)</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="33-33-33"> <bento-input-field>Input field (33%)</bento-input-field> <bento-input-field>Input field (33%)</bento-input-field> <bento-input-field>Input field (33%)</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="50-50"> <bento-input-field>Input field (50%)</bento-input-field> <bento-input-field>Input field (50%)</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="25-75"> <bento-input-field>Input field (25%)</bento-input-field> <bento-input-field>Input field (75%)</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="75-25"> <bento-input-field>Input field (75%)</bento-input-field> <bento-input-field>Input field (25%)</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="33-66"> <bento-input-field>Input field (33%)</bento-input-field> <bento-input-field>Input field (66%)</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="66-33"> <bento-input-field>Input field (66%)</bento-input-field> <bento-input-field>Input field (33%)</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="25"> <bento-input-field>Input field (25%)</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="33"> <bento-input-field>Input field (33%)</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="50"> <bento-input-field>Input field (50%)</bento-input-field> </bento-form-layout-group> <template #actions-left> <bento-button variant="secondary"> Action </bento-button> <bento-button variant="secondary"> Action </bento-button> </template> <template #actions-right> <bento-button variant="secondary"> Action </bento-button> <bento-loading-button> Action </bento-loading-button> </template> </bento-form-layout> `, setup(props) { return { args: isVue2 ? props : _args, errorMessage: 'There is an error. Correct the highlighted field.', dropdownItems: [...DROPDOWN_ITEMS], checkboxGroupItems: [...CHECKBOX_GROUP_ITEMS], radioGroupItems: [...RADIO_GROUP_ITEMS], selectionCardGroupItems: [ { title: 'Card title', value: '1', description: 'Description', }, { title: 'Card title', value: '2', description: 'Description', }, { title: 'Card title', value: '3', description: 'Description', }, { title: 'Card title', value: '4', description: 'Description', }, ], }; }, }), parameters: storybookDocsParameter(BentoFormLayoutDefaultExample), }; export const WithTip: Story = { render: (_args, { argTypes }) => ({ components: { BentoDropdown, BentoFormLayout, BentoFormLayoutFieldTip, }, props: Object.keys(argTypes), template: ` <bento-form-layout v-bind="args"> <bento-form-layout-field-tip :type="args.type" :id="args.id ? args.id : undefined"> <bento-dropdown label="Dropdown" :items="dropdownItems" placeholder="Select an animal" :aria-describedby="args.id" /> <template #message>This is a small tip that can be used in small spaces</template> </bento-form-layout-field-tip> </bento-form-layout> `, setup(props) { return { args: isVue2 ? props : _args, dropdownItems: [...DROPDOWN_ITEMS], }; }, }), argTypes: { type: { options: BentoFormLayoutFieldTipTypeOptions, description: 'Type of a alert; set a distinctive icon and color. Defaults to highlight.', control: { type: 'select', }, table: { category: 'Form Layout Field Tip props', type: { summary: 'string' } }, }, id: { description: 'Unique identifier to the form field. Used for accessibility.', table: { category: 'Form Layout Field Tip props', type: { summary: 'string' } }, }, }, args: { id: 'dropdown_field_with_tip', type: 'highlight' }, parameters: storybookDocsParameter(BentoFormLayoutFieldTipExample), }; const groupCode = ` <bento-form-layout> <bento-form-layout-group> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="50-50"> <bento-input-field>Input field</bento-input-field> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="25-75"> <bento-input-field>Input field</bento-input-field> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="75-25"> <bento-input-field>Input field</bento-input-field> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="33-66"> <bento-input-field>Input field</bento-input-field> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="66-33"> <bento-input-field>Input field</bento-input-field> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="25"> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="33"> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="50"> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="33-33-33"> <bento-input-field>Input field</bento-input-field> <bento-input-field>Input field</bento-input-field> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> </bento-form-layout> `; export const Group: Story = { render: (_args, { argTypes }) => ({ components: { BentoAlert, BentoInputField, BentoFormLayout, BentoFormLayoutGroup, }, props: Object.keys(argTypes), template: ` <div> <bento-alert style="margin-bottom: 20px"> <template #description> The <code>bento-form-layout-group</code> <i>is not</i> a dynamic component and does not support changing it's content/default slot. If this functionality is needed, set the <code>v-if</code> statement <b>on</b> the component itself or around/outside it. </template> </bento-alert> <bento-form-layout v-bind="args"> <bento-form-layout-group> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="50"> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="50-50"> <bento-input-field>Input field</bento-input-field> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="25"> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="25-75"> <bento-input-field>Input field</bento-input-field> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="75-25"> <bento-input-field>Input field</bento-input-field> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="33"> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="33-66"> <bento-input-field>Input field</bento-input-field> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="66-33"> <bento-input-field>Input field</bento-input-field> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> <bento-form-layout-group layout="33-33-33"> <bento-input-field>Input field</bento-input-field> <bento-input-field>Input field</bento-input-field> <bento-input-field>Input field</bento-input-field> </bento-form-layout-group> </bento-form-layout> </div> `, setup(props) { return { args: isVue2 ? props : _args, dropdownItems: [...DROPDOWN_ITEMS], }; }, }), parameters: storybookDocsParameter(groupCode), }; const loadingFormCode = ` <bento-form-layout loading> <bento-form-layout-title el="h1"> Form request loading time <template #description> Form fields state while waiting for request to be processed </template> </bento-form-layout-title> <bento-input-field required model-value="Input field">Input field</bento-input-field> <template #actions-right> <bento-loading-button state="loading">Loading</bento-loading-button> </template> </bento-form-layout> `; export const LoadingForm: Story = { render: (_args, { argTypes }) => ({ components: { BentoButton, BentoCheckboxGroup, BentoDatePicker, BentoDropdown, BentoFormLayout, BentoFormLayoutGroup, BentoFormLayoutTitle, BentoInputField, BentoLoadingButton, BentoRadioGroup, BentoTextarea, ChevronLeftIcon, SaveIcon, }, props: Object.keys(argTypes), template: ` <bento-form-layout loading> <bento-form-layout-title el="h1"> Form request loading time <template #description> Form fields state while waiting for request to be processed </template> </bento-form-layout-title> <bento-date-picker label="Date picker" optional /> <bento-textarea label="Textarea" :characterLimit="400" model-value="Text area value..." required /> <bento-form-layout-group layout="25-75"> <bento-input-field model-value="Input value" required>Input field</bento-input-field> <bento-dropdown label="Dropdown" :items="[{ label: 'Dropdown value', value: 1 }]" :model-value="1" optional /> </bento-form-layout-group> <bento-radio-group label="Radio group" modelValue="no" optional :items="[ { label: 'No', value: 'no' }, { label: 'Yes', value: 'yes' }, ]" /> <bento-checkbox-group label="Checkbox group" optional :modelValue="['one', 'two']" :items="[ { label: 'One', value: 'one' }, { label: 'Two', value: 'two' }, ]" /> <template v-slot:actions-left> <bento-button variant="secondary"> Back <template v-slot:iconLeft> <chevron-left-icon /> </template> </bento-button> <bento-button variant="secondary"> Save for later <template v-slot:iconLeft> <save-icon /> </template> </bento-button> </template> <template v-slot:actions-right> <bento-loading-button state="loading">Validating</bento-loading-button> </template> </bento-form-layout> `, setup(props) { return { args: isVue2 ? props : _args, }; }, }), parameters: storybookDocsParameter(loadingFormCode), }; const validateCode = ` <template> <bento-form-layout :loading="isLoading"> <template #error-message v-if="errors"> There is an error. Correct the highlighted field. <ul v-if="errorArray.length"> <li v-for="(error, index) in errorArray" :key="index"> <bento-link to="#input-field-name" is-not-routing>{{ error }}</bento-link> </li> </ul> </template> <bento-form-layout-title el="h1"> Form validation <template #description> Form validation using useForm </template> </bento-form-layout-title> <bento-input-field id="input-field-name" :model-value="values.name" :error-message="errors?.name" @update:model-value="name => values.name = name" > Name </bento-input-field> <template #actions-right> <bento-loading-button :state="loadingButtonState" @click="handleSubmit"> Submit form </bento-loading-button> </template> </bento-form-layout> </template> <script setup lang="ts"> import { ref } from 'vue' import { // Components BentoFormLayout, BentoInputField, BentoLoadingButton, // Composables useForm, toTypedSchema, } from '@adyen/bento-vue2' import { z } from 'zod' const isLoading = ref(false); const loadingButtonState = ref<\`${BentoLoadingButtonState}\`>('start'); const { values, errors, submitForm } = useForm({ validationSchema: toTypedSchema( z.object({ name: z .string() .min(3, { message: 'Name is required' }) .startsWith('Jane') .max(4, { message: 'Name cannot be longer than 4 characters' }) .default('Joe'), }) ) }); const errorArray = computed(() => Object.values(errors.value)); const handleSubmit = submitForm(() => { isLoading.value = true; loadingButtonState.value = 'loading'; // Fake request time setTimeout(() => { isLoading.value = false; loadingButtonState.value = 'success'; }, 2000); }); </script> `; export const Validate: Story = { render: (_args, { argTypes }) => ({ components: { BentoInputField, BentoFormLayout, BentoFormLayoutGroup, BentoFormLayoutTitle, BentoLoadingButton, BentoLink, }, props: Object.keys(argTypes), template: ` <bento-form-layout v-bind="args" :loading="isLoading"> <template #error-message v-if="errors"> There is an error. Correct the highlighted field. <ul v-if="errorArray.length"> <li v-for="(error, index) in errorArray" :key="index"> <bento-link to="#input-field-name" is-not-routing>{{ error }}</bento-link> </li> </ul> </template> <bento-form-layout-title el="h1"> Form validation <template #description> Form validation using useForm </template> </bento-form-layout-title> <bento-input-field id="input-field-name" :model-value="values.name" :error-message="errors?.name" @update:model-value="name => values.name = name" > Name </bento-input-field> <template #actions-right> <bento-loading-button :state="loadingButtonState" @click="handleSubmit"> Submit form </bento-loading-button> </template> </bento-form-layout> `, setup(props) { const isLoading = ref(false); const loadingButtonState = ref<`${BentoLoadingButtonState}`>('start'); const validationSchema = toTypedSchema( z.object({ name: z .string() .min(3, { message: 'Name is required' }) .startsWith('Jane') .max(4, { message: 'Name cannot be longer than 4 characters' }) .default('Joe'), }) ); const { values, errors, submitForm } = useForm({ validationSchema, }); const handleSubmit = submitForm(() => { isLoading.value = true; loadingButtonState.value = 'loading'; // Fake request time setTimeout(() => { isLoading.value = false; loadingButtonState.value = 'success'; }, 2000); }); const errorArray = computed(() => Object.values(errors.value)); return { args: isVue2 ? props : _args, values, errors, errorArray, loadingButtonState, isLoading, // Methods handleSubmit, }; }, }), parameters: storybookDocsParameter(validateCode), };
@@ -0,0 +1 @@
1
+ <template> <component :is="formLayoutElement" class="b-form-layout" role="form" @submit="emit('submit', $event)"> <bento-alert v-if="hasSlot('error-message')" class="b-form-layout__error-message" type="critical"> <template #description> <slot name="error-message" /> </template> </bento-alert> <slot /> <div v-if="hasActions" class="b-form-layout__actions-container"> <div class="b-form-layout__actions"> <slot name="actions-left" /> </div> <div class="b-form-layout__actions"> <slot name="actions-right" /> </div> </div> </component> </template> <script setup lang="ts"> import { computed, provide, toRef, useSlots } from 'vue'; import { BentoAlert } from '@/components/alert'; import { useHasSlot } from '@/composables'; import { FORM_LAYOUT_LOADING_INJECTION_KEY } from './form-layout.types'; const slots = useSlots(); const hasSlot = useHasSlot(slots); const emit = defineEmits<{ /** * Emits the submit event from the form */ (e: 'submit', event: SubmitEvent): void; }>(); const props = defineProps({ /** * Renders the form layout as an HTML `form` */ form: { type: Boolean, default: false }, /** * Sets the state of the form and its fields to * a read only status. */ loading: { type: Boolean, default: false }, }); const formLayoutElement = computed(() => (props.form ? 'form' : 'div')); const hasActions = computed(() => hasSlot('actions-left') || hasSlot('actions-right')); provide(FORM_LAYOUT_LOADING_INJECTION_KEY, toRef(props, 'loading')); </script> <script lang="ts"> /** * Form layout component is a pre-designed layout that serves * as a starting point for creating a form. * * It can include various common form components: * input field, dropdown, date picker, checkbox group, radio group, * textarea, etc. * * Form layout is context agnostic and can be dropped in different * places, for example in modals, side panels or pages. * * @example * import { BentoFormLayout, BentoFormLayoutFieldTip } from '@adyen/bento-vue2'; * * export default { * components: { BentoFormLayout, BentoFormLayoutFieldTip }, * template: ` * <bento-form-layout> * <template #error-message>There is an error. Correct the highlighted field.</template> * * <bento-input-field>Input field</bento-input-field> * * <bento-form-layout-field-tip> * <bento-dropdown label="Dropdown" :items="dropdownItems" placeholder="Select an animal" /> * <template #message>This is a small tip that can be used in small spaces</template> * </bento-form-layout-field-tip> * * <bento-form-layout-title subsection> * Section title * <template #description>Optional section description</template> * </bento-form-layout-title> * </bento-form-layout> * ` * } */ export default {}; </script> <style lang="scss" scoped src="./form-layout.scss" />
@@ -0,0 +1,85 @@
1
+ import { Canvas, Story, Meta } from '@storybook/blocks';
2
+ import * as HeaderStories from './header.stories';
3
+
4
+ # Header
5
+
6
+ Header sits either on top of a page or above another component, contains title, description hidden in a tooltip, an
7
+ optional description, a set of actions and a timestamp.
8
+
9
+ - Use for displaying the main page or component title
10
+ - `header` can only be used once on a page or once per component, when using the component variant.
11
+ - ⚠️ Don't change the `font-weight`, `font-size` or `color`.
12
+ - ⚠️ Avoid the usage of unpredictable content like user names or account names.
13
+ - Use tooltip to describe the page or the component
14
+
15
+ <Meta title="Header" of={HeaderStories} />
16
+
17
+ <Canvas withSource="closed">
18
+ <Story of={HeaderStories.EverythingBagel} />
19
+ </Canvas>
20
+
21
+ ## Variants
22
+
23
+ There are two variants of the component, a page header and a component header.
24
+
25
+ ### Page header
26
+
27
+ Page header should always be the first content component on a page. Together with the top bar (if exists within the
28
+ specific application), they help users orientate themselves.
29
+
30
+ ### Component header
31
+
32
+ Component header should be used in cases where a page header already exists, but there is a component somewhere on the
33
+ page that needs its own header. A common case is a mini data grid sitting in-between other components. Then the
34
+ component variant can be added to it.
35
+
36
+ The only compulsory element of a header is title. Everything else can be turned off.
37
+
38
+ ### Actions
39
+
40
+ Buttons section should contain one primary and one secondary button, and a more options menu for any potential
41
+ additional actions. These are all passed as a `BentoButtonActionsList` to the component.
42
+
43
+ ### Info icon content
44
+
45
+ If a page or component description and link exists, it’s displayed inside of a popover next to the title. Users can
46
+ either choose to use the `infoIconTooltipText` property or `infoIconTooltipContent` slot to set the info icon content.
47
+
48
+ <Canvas withSource="closed">
49
+ <Story of={HeaderStories.WithInfoIconSlot} />
50
+ </Canvas>
51
+
52
+ ### Description
53
+
54
+ The `description` prop can be used to add a short description under the header's title.
55
+
56
+ ### Last updated
57
+
58
+ The `lastUpdated` props can be passed to tell user when the data on a page/component was last updated. This will show as
59
+ a timestamp under the header's title.
60
+
61
+ ### Reload button
62
+
63
+ An optional button rendering next to the timestamp that receives a custom `BentoHeaderReloadButton` object:
64
+
65
+ - `title`: the button label;
66
+ - `state`: marks the state of the button, a string that can take up one of the values listed in
67
+ `BentoLoadingButtonState`;
68
+ - `event`: a custom event handler to be executed whenever the button is clicked.
69
+
70
+ This button only renders if the timestamp is shown.
71
+
72
+ ## Accessibility
73
+
74
+ **Keyboard**
75
+
76
+ - User can use Tab key to move focus to different interactive elements of the header in a logical order.
77
+ - For more information about the individual subcomponents making up the header, please consult their documentation:
78
+ tooltip, button, menu.
79
+
80
+ **Screenreader**
81
+
82
+ - User can use Tab key to move focus to different interactive elements of the header in a logical order.
83
+ - User hears each element identify itself and describe its purpose
84
+ - For more information about the individual subcomponents making up the header, please consult their documentation:
85
+ tooltip, button, menu.
@@ -0,0 +1 @@
1
+ import { BentoButtonActions } from '@/components/button'; import { BentoHeaderVariant } from './header.types'; import { reactive, ref, watch } from 'vue'; import { isVue2 } from 'vue-demi'; import { storybookDocsParameter } from '@/utils/ts/storybook'; import { BentoLink } from '@/components/link'; import type { Meta, StoryObj } from '@storybook/vue'; import BentoHeader from './header.vue'; import PlusIcon from '@adyen/ui-assets-icons-16/vue/plus'; const meta: Meta = { title: 'Header', component: BentoHeader, argTypes: { variant: { options: Object.values(BentoHeaderVariant), mapping: BentoHeaderVariant, defaultValue: BentoHeaderVariant.PAGE, control: { type: 'radio', }, }, lastUpdated: { control: { type: 'date', }, }, withReloadButton: { control: { type: 'boolean', }, }, unsetDate: { table: { category: 'Storybook controls', }, description: 'Control to hide date, because date control cannot be unset in Storybook. Not a prop.', control: { type: 'boolean', }, }, // Slots description: { control: { disable: true }, description: 'Slot for the description, displayed below the title. Use this slot for rich content like links or formatted text.', table: { type: { summary: 'VNode[]' }, }, }, }, }; const HEADER_DESCRIPTION = "This page shows how Adyen's AI boosts your payment performance and revenue. Monitor your authorization rate uplift and discover data-driven opportunities for further optimization."; const DEFAULT_HEADER_PROPS = { title: 'Title', variant: 'page', }; const EVERYTHING_HEADER_PROPS = { ...DEFAULT_HEADER_PROPS, description: HEADER_DESCRIPTION, actions: [ { title: 'Primary action', event: () => undefined }, { title: 'Secondary action', icon: PlusIcon, event: () => undefined }, { title: 'Tertiary action', icon: PlusIcon, event: () => undefined }, { title: 'Another action', event: () => undefined }, ], lastUpdated: new Date(), infoIconTooltipText: 'Title Tooltip Text', }; export default meta; type Story = StoryObj<typeof BentoHeader>; export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoHeader, BentoButtonActions }, props: Object.keys(argTypes), setup(props) { const date = ref(null); const reloadButtonText = ref('Reload'); const reloadButtonState = ref('start'); const reloadButton = reactive({ title: reloadButtonText, state: reloadButtonState, event: async () => { if (reloadButtonState.value === 'start') { reloadButtonText.value = 'Loading'; reloadButtonState.value = 'loading'; setTimeout(() => { reloadButtonText.value = 'Reload'; reloadButtonState.value = 'start'; }, 2000); } }, }); watch( () => props.unsetDate, (val: boolean) => { date.value = val ? null : props.lastUpdated; }, { immediate: true } ); watch( () => props.lastUpdated, val => { date.value = props.unsetDate !== true && val; }, { immediate: true } ); return { args: isVue2 ? props : _args, date, reloadButton, }; }, template: ` <bento-header v-bind="args" :reloadButton="reloadButton" :last-updated="date" /> `, }), args: DEFAULT_HEADER_PROPS, argTypes: { icon: { table: { disable: true } } }, }; const everythingBagelCode = ` <bento-header title="Title" variant="page" :actions="[ { title: 'Primary action', event: () => undefined }, { title: 'Secondary action', icon: PlusIcon, event: () => undefined }, { title: 'Tertiary action', icon: PlusIcon, event: () => undefined }, { title: 'Another action', event: () => undefined }, ]" description="${HEADER_DESCRIPTION}" :lastUpdated="new Date()" infoIconTooltipText="Title Tooltip Text" :reloadButton="{ title: 'Reload', state: 'start', event: async () => { // Your loading button logic }, }" /> `; export const EverythingBagel: Story = { ...Default, args: EVERYTHING_HEADER_PROPS, parameters: storybookDocsParameter(everythingBagelCode), }; const withInfoIconSlotCode = ` <bento-header> <template #infoIconTooltipContent> Description and/or <bento-link to="/">link</bento-link> </template> </bento-header> `; export const WithInfoIconSlot: Story = { ...Default, render: (_args, { argTypes }) => ({ components: { BentoHeader, BentoButtonActions, BentoLink }, props: Object.keys(argTypes), setup(props) { const date = new Date(); return { args: isVue2 ? props : _args, date, }; }, template: ` <bento-header v-bind="args" :last-updated="date"> <template #infoIconTooltipContent> Description and/or <bento-link to="/">link</bento-link> </template> </bento-header> `, }), args: { ...Default.args, }, parameters: storybookDocsParameter(withInfoIconSlotCode), };
@@ -0,0 +1 @@
1
+ <template> <header class="b-header" :class="conditionalClasses"> <div class="b-header__wrapper"> <div class="b-header__title"> <div> <bento-typography v-bind="typography" class="b-header__text">{{ title }}</bento-typography> <info-icon-with-popover v-if="infoIconTooltipText || hasSlot('infoIconTooltipContent')" class="b-header__tooltip" :popover-text="infoIconTooltipText" > <template v-if="hasSlot('infoIconTooltipContent')" #default ><slot name="infoIconTooltipContent" /></template> </info-icon-with-popover> </div> </div> <header-meta v-if="description || hasSlot('description') || lastUpdated" class="b-header__meta-container" :description="description" :last-updated="lastUpdated" :reload-button="reloadButton" > <template v-if="hasSlot('description')" #description><slot name="description" /></template> </header-meta> <button-actions-with-menu v-if="actions" :actions="actions" :displayed-actions="2" class="b-header__actions" /> </div> </header> </template> <script lang="ts"> import { computed, defineComponent, type PropType } from 'vue'; import { InfoIconWithPopover } from '@/internal'; import { type BentoButtonActionsList } from '@/components/button'; import { useHasSlot } from '@/composables'; import { type BentoHeaderReloadButton, BentoHeaderVariant } from './header.types'; import { BentoTypography, BentoTypographyElement, BentoTypographyVariant } from '@/components/typography'; import { HeaderMeta } from '@/internal/header-meta'; import ButtonActionsWithMenu from '../internal/button-actions-with-menu/button-actions-with-menu.vue'; export default defineComponent({ name: 'bento-header', components: { BentoTypography, ButtonActionsWithMenu, InfoIconWithPopover, HeaderMeta, }, props: { /** * Array of BentoButtonActionsList to show to the right of the title */ actions: { type: Array as PropType<BentoButtonActionsList>, default: null, }, /** * Text showed under the title */ description: { type: String, default: null }, /** * Text showed in the info-icon tooltip on hover */ infoIconTooltipText: { type: InfoIconWithPopover.props.popoverText.type, default: null }, /** * Popover position of the info icon text. */ infoIconPopoverPosition: { type: InfoIconWithPopover.props.popoverPosition.type, default: InfoIconWithPopover.props.popoverPosition.default, validator: InfoIconWithPopover.props.popoverPosition.validator, }, /** * JavaScript "Date" object for when the content was last updated */ lastUpdated: { type: [Date, Number], default: null }, /** * Variant represents type of Header * @values page, component */ variant: { type: String as PropType<BentoHeaderVariant | `${BentoHeaderVariant}`>, default: BentoHeaderVariant.PAGE, validator: (value: BentoHeaderVariant) => Object.values(BentoHeaderVariant).includes(value), }, /** * Text used as a title */ title: { type: String, required: true }, /** * Reload button which shows next to the header's timestamp * {@see BentoHeaderReloadButton} */ reloadButton: { type: Object as PropType<BentoHeaderReloadButton>, default: null, }, }, emits: ['reload'], setup(props, { slots }) { const conditionalClasses = computed(() => ({ 'b-header--component': props.variant === BentoHeaderVariant.COMPONENT, })); const typography = computed(() => ({ el: props.variant === BentoHeaderVariant.PAGE ? BentoTypographyElement.H1 : BentoTypographyElement.H2, variant: BentoTypographyVariant.TITLE, medium: props.variant === BentoHeaderVariant.COMPONENT, large: props.variant === BentoHeaderVariant.PAGE, })); return { hasSlot: useHasSlot(slots), conditionalClasses, typography, }; }, }); </script> <style lang="scss" scoped src="./header.scss" />
@@ -0,0 +1 @@
1
+ <template> <div ref="allViewsRef" class="b-header-with-views-all-views" data-testid="header-with-views-all-views" @keydown.esc.stop.prevent="closeMenu" > <bento-toggle-button ref="allViewsMenuToggleRef" aria-haspopup="true" :aria-expanded="showMenu" :aria-controls="showMenu ? menuPopoverId : null" variant="tertiary" :toggled="showMenu" @click="toggleShowMenu" > <template #iconLeft> <menu-icon :svg-title="t('toggleAllViews')" /> </template> </bento-toggle-button> <bento-popover v-if="allViewsMenuToggleRef" :id="menuPopoverId" ref="allViewsMenuPopoverRef" class="b-header-with-views-all-views__popover" :open="showMenu" :target-element="allViewsMenuToggleRef" :fixed-positioning="true" :overflow-visible="true" position="bottom-end" without-space > <output v-if="draggedViewIndex !== null && draggingOverViewIndex !== null" class="b-header-with-views-all-views__popover-status" > {{ t('movingDraggedToPosition', { dragged: menuItems[draggedViewIndex].label, draggingOver: menuItems[draggingOverViewIndex].label, }) }} </output> <ul ref="menuRef" class="b-header-with-views-all-views__popover-menu"> <li v-for="(item, index) in menuItems" ref="menuItemRefs" :key="index" data-drag-item class="b-header-with-views-all-views__popover-menu-item" :class="conditionalDraggableItemClass(index)" @pointerdown="handleDragStart(index, $event)" > <div class="b-header-with-views-all-views__popover-menu-item-content"> <bento-button data-drag-trigger class="b-header-with-views-all-views__popover-menu-item-button b-header-with-views-all-views__popover-menu-item-button-reorder" variant="tertiary" :aria-pressed="isItemDragged(index)" @keydown.enter.native="handleDragKeyboardToggle(index, $event)" @keydown.space.native="handleDragKeyboardToggle(index, $event)" @keydown.down.native="handleDragKeyboardMove(UseDragAndDropViewsDirectionEnum.DOWN, $event)" @keydown.up.native="handleDragKeyboardMove(UseDragAndDropViewsDirectionEnum.UP, $event)" @keydown.esc.native="handleDragStop" > <template #iconLeft> <grab-icon :svg-title="t('reorderView', { label: item.label })" /> </template> </bento-button> <bento-button data-view-select class="b-header-with-views-all-views__popover-menu-item-button b-header-with-views-all-views__popover-menu-item-button-select" variant="tertiary" @click="emit('select:view', index)" > <template #iconLeft> <span class="b-header-with-views-all-views__popover-menu-item-button-icon" :class="`b-header-with-views-all-views__popover-menu-item-button-icon--${item.iconColor}`" aria-hidden="true" > <component :is="BentoHeaderWithViewsItemIconElement[item.icon]" /> </span> </template> <bento-typography el="span" variant="body" class="b-header-with-views-all-views__popover-menu-item-button-text" > {{ item.label }} </bento-typography> </bento-button> <bento-menu ref="editViewMenuRefs" data-view-menu class="b-header-with-views-all-views__popover-menu-item-button" :button="{ variant: 'tertiary', condensed: true }" :close-on-click-outside-options="menuCloseOnClickOutsideOptions" :data="menuItemsActions" menu-position="right-start" @open="emit('open:view-menu', index)" @close="emit('close:view-menu', index)" @keydown.tab.native.capture="closeViewMenuNatively(index)" > <template #iconLeft> <options-horizontal-icon :svg-title="t('openViewOptions', { label: item.label })" /> </template> </bento-menu> </div> </li> </ul> </bento-popover> </div> </template> <script setup lang="ts"> import { BentoButton, BentoToggleButton } from '@/components/button'; import { BentoHeaderWithViewsItemIconElement } from '@/components/header-with-views/header-with-views.types'; import { BentoMenu } from '@/components/menu'; import { BentoPopover } from '@/components/popover'; import { BentoTypography } from '@/components/typography'; import { useClickOutside } from '@/composables'; import { generateUid } from '@/core/utils/ts'; import { useI18n } from '@/utils/ts/i18n'; import GrabIcon from '@adyen/ui-assets-icons-16/vue/grab'; import MenuIcon from '@adyen/ui-assets-icons-16/vue/menu'; import OptionsHorizontalIcon from '@adyen/ui-assets-icons-16/vue/options-horizontal'; import { nextTick, ref, toRef } from 'vue'; import { useDragAndDropViews, UseDragAndDropViewsDirectionEnum } from '../../composables'; import { type HeaderWithViewsAllViewsProps } from './header-with-views-all-views.types'; import messages from './messages.json'; type MessageSchema = (typeof messages)['en-US']; const props = withDefaults(defineProps<HeaderWithViewsAllViewsProps>(), {}); const emit = defineEmits<{ /** * Emitted when the "all views" menu is requested to be closed. */ (e: 'close:all-views'): void; /** * Emitted when a specific view's action menu is requested to be closed. */ (e: 'close:view-menu', index: number): void; /** * Emitted when the "all views" menu is requested to be opened. */ (e: 'open:all-views'): void; /** * Emitted when a specific view's action menu is requested to be opened. */ (e: 'open:view-menu', index: number): void; /** * Emitted when a view item is selected from the "all views" menu. */ (e: 'select:view', index: number): void; /** * Emitted when the order of views has been changed due to drag and drop. */ (e: 'reorder:view', newViews: HeaderWithViewsAllViewsProps['menuItems']): void; }>(); const showMenu = ref(false); const allViewsMenuToggleRef = ref(null); const allViewsMenuPopoverRef = ref(null); const allViewsRef = ref(null); const menuRef = ref(null); const menuItemRefs = ref<Array<HTMLElement>>(null); const editViewMenuRefs = ref(null); const menuPopoverId = generateUid('menu-popover'); const { t } = useI18n<{ message: MessageSchema }>({ messages }); useClickOutside(allViewsMenuPopoverRef, () => closeMenu(), { ignore: [allViewsMenuToggleRef, ...(props.menuCloseOnClickOutsideOptions?.ignore ?? [])], }); const openMenu = () => { showMenu.value = true; emit('open:all-views'); }; const closeMenu = async () => { if (!showMenu.value) { return; } showMenu.value = false; emit('close:all-views'); await nextTick(); allViewsMenuToggleRef.value.$el.focus(); }; const toggleShowMenu = (e: MouseEvent) => { e.stopPropagation(); if (!showMenu.value) { openMenu(); } else { closeMenu(); } }; const { draggedViewIndex, draggingOverViewIndex, isItemDragged, handleDragKeyboardMove, handleDragKeyboardToggle, handleDragStart, handleDragStop, } = useDragAndDropViews({ items: toRef(props, 'menuItems'), itemRefs: menuItemRefs, onDrop: value => emit('reorder:view', value), }); const conditionalDraggableItemClass = (index: number) => ({ ['b-header-with-views-all-views__popover-menu-item--dragging']: isItemDragged(index), }); const closeViewMenuNatively = (index: number) => { // skip built-in refocus on the menu button when menu is closed // enables tab to flow as intended by HTML spec editViewMenuRefs.value[index].closeMenu(false); }; </script> <script lang="ts"> /** * Internal component to handle the logic for the list of all views available. */ export default { i18n: { messages }, }; </script> <style lang="scss" scoped src="./header-with-views-all-views.scss" />
@@ -0,0 +1 @@
1
+ <template> <form class="b-header-with-views-edit-form"> <field-label>{{ t('title') }}</field-label> <div class="b-header-with-views-edit-form__icon-choice-alert" role="alert">{{ computedAlert }}</div> <div class="b-header-with-views-edit-form__icon-selector"> <bento-toggle-button ref="iconSelectorButtonRef" variant="secondary" :aria-expanded="showIconSelectorPopover" :aria-controls="showIconSelectorPopover ? iconPopoverId : null" :disabled="props.disableCustomization" :toggled="showIconSelectorPopover" @click="showIconSelectorPopover = !showIconSelectorPopover" > <template #iconLeft> <component :is="BentoHeaderWithViewsItemIconElement[iconValue]" :class="conditionalIconClass" :svg-title="t('viewIcons')" /> </template> </bento-toggle-button> <bento-popover v-if="iconSelectorButtonRef && !props.disableCustomization" :id="iconPopoverId" ref="iconPopoverRef" class="b-header-with-views-edit-form__icon-selector-popover" :target-element="iconSelectorButtonRef" :open="showIconSelectorPopover" :fallback-position="['bottom-end']" :fixed-positioning="true" :overflow-visible="true" position="bottom-end" @dismiss="showIconSelectorPopover = false" @keydown.escape.native.capture.stop.prevent="showIconSelectorPopover = false" > <div class="b-header-with-views-edit-form__icon-panel"> <div class="b-header-with-views-edit-form__icon-color-options"> <div v-for="(color, index) in iconColorOptions" :key="index" class="b-header-with-views-edit-form__icon-options-wrapper" > <bento-button class="b-header-with-views-edit-form__icon-color-options-button" :class="`b-header-with-views-edit-form__icon-color--${color}`" :aria-selected="iconColorValue === color" variant="tertiary" @click="iconColorValue = color" > <template #iconLeft> <circle-filled-icon :svg-title="t('selectIconColor', { color })" ></circle-filled-icon> </template> </bento-button> </div> </div> <bento-divider /> <div class="b-header-with-views-edit-form__icon-options"> <bento-button v-for="(icon, index) in iconOptions" :key="index" class="b-header-with-views-edit-form__icon-options-button" :aria-selected="iconValue === icon" variant="tertiary" @click="iconValue = icon" > <template #iconLeft> <component :is="BentoHeaderWithViewsItemIconElement[icon]" :svg-title="t('selectIcon', { icon })" /> </template> </bento-button> </div> </div> </bento-popover> <bento-input-field ref="inputFieldRef" class="b-header-with-views-edit-form__icon-selector-input" :aria-label="t('viewLabel')" :error-message="errors.label" :model-value="labelValue" @keydown.enter.prevent="emit('keydown:enter', computedFormValues)" @update:model-value="handleInput" @blur="emit('blur:label', labelValue)" ></bento-input-field> </div> <bento-checkbox v-if="additionalFormItem" :model-value="additionalFormItemValue" class="b-header-with-views-edit-form__additional-config" @update:model-value="handleAdditionalFormItemChange" > {{ additionalFormItem.label }} </bento-checkbox> <bento-button-actions class="b-header-with-views-edit-form__actions" :actions="computedActions" layout="buttons-end" /> </form> </template> <script setup lang="ts"> import { BentoButton, BentoButtonActions, type BentoButtonActionsList, BentoToggleButton, } from '@/components/button'; import { BentoCheckbox } from '@/components/checkbox'; import { BentoDivider } from '@/components/divider'; import { BentoInputField } from '@/components/input-field'; import { BentoPopover } from '@/components/popover'; import { useClickOutside } from '@/composables'; import { generateUid } from '@/core/utils/ts'; import { FieldLabel } from '@/internal'; import { debounce } from '@/utils/ts/debounce'; import { useI18n } from '@/utils/ts/i18n'; import CircleFilledIcon from '@adyen/ui-assets-icons-16/vue/circle-filled'; import { onKeyStroke } from '@vueuse/core'; import { computed, nextTick, onMounted, ref, watch } from 'vue'; import { type BentoHeaderWithViewsItem, BentoHeaderWithViewsItemIconColorEnum, BentoHeaderWithViewsItemIconElement, BentoHeaderWithViewsItemIconEnum, } from '../../header-with-views.types'; import { HeaderWithViewsEditFormContextEnum, type HeaderWithViewsEditFormProps, } from './header-with-views-edit-form.types'; import messages from './messages.json'; type MessageSchema = (typeof messages)['en-US']; const props = withDefaults(defineProps<HeaderWithViewsEditFormProps>(), { additionalFormItem: null, context: HeaderWithViewsEditFormContextEnum.CREATE, disableCustomization: false, errors: null, label: '', icon: BentoHeaderWithViewsItemIconEnum.STACK, iconColor: BentoHeaderWithViewsItemIconColorEnum.PRIMARY, }); const emit = defineEmits<{ /** * Emitted when form is canceled. */ (e: 'cancel'): void; /** * Emitted when the item is updated. * Typically used to sync changes to the parent component. */ (e: 'update', value: BentoHeaderWithViewsItem): void; /** * Emitted when the item is requested to be deleted. */ (e: 'delete', value: BentoHeaderWithViewsItem): void; /** * Emitted when the label of the item is modified via input. */ (e: 'input:label', value: BentoHeaderWithViewsItem['label']): void; /** * Emitted when the label input loses focus (blur event). */ (e: 'blur:label', value: BentoHeaderWithViewsItem['label']): void; /** * Emitted when the Enter key is pressed while interacting with the item. */ (e: 'keydown:enter', value: BentoHeaderWithViewsItem): void; }>(); // form inputs const labelValue = ref(props.label); const iconValue = ref(props.icon); const iconColorValue = ref(props.iconColor); const additionalFormItemValue = ref<boolean>(props.additionalFormItem?.value ?? false); const isFormDirty = ref(false); // refs const iconSelectorButtonRef = ref(null); const iconPopoverRef = ref(null); const inputFieldRef = ref(null); const iconPopoverId = generateUid('iconPopover'); // view controls const showIconSelectorPopover = ref(false); const iconOptions = computed(() => Object.values(BentoHeaderWithViewsItemIconEnum)); const iconColorOptions = computed(() => Object.values(BentoHeaderWithViewsItemIconColorEnum)); const hasErrors = computed(() => Object.values(props.errors).some(error => error.length > 0)); const computedAlert = computed( () => `${t('selected')}: ${iconValue.value} ${t('icon')}, ${t('color')} ${iconColorValue.value}` ); const computedActions = computed<BentoButtonActionsList>(() => [ ...(props.context === HeaderWithViewsEditFormContextEnum.EDIT ? // edit mode [ { title: t('save'), type: 'submit', event: () => { if (hasErrors.value) { return; } updateView(); }, }, ] : // create mode [ { title: t('create'), type: 'submit', event: () => { if (!isFormDirty.value || hasErrors.value) { return; } updateView(); }, }, ]), { title: t('cancel'), event: resetView, }, ]); const { t } = useI18n<{ message: MessageSchema }>({ messages }); const computedFormValues = computed(() => ({ iconColor: iconColorValue.value, icon: iconValue.value, label: labelValue.value, ...(props.additionalFormItem ? { additionalViewSetting: { label: props.additionalFormItem.label, value: additionalFormItemValue.value, }, } : []), })); const conditionalIconClass = computed(() => props.disableCustomization ? '' : `b-header-with-views-edit-form__icon-color--${iconColorValue.value}` ); const hideIconSelectorPopover = () => { showIconSelectorPopover.value = false; }; const updateView = () => { emit('update', computedFormValues.value); }; const resetView = () => { iconValue.value = props.icon; iconColorValue.value = props.iconColor; labelValue.value = props.label; emit('cancel'); }; const handleInput = debounce((value: string) => { labelValue.value = value; isFormDirty.value = true; }, 200); const handleAdditionalFormItemChange = (value: boolean) => { additionalFormItemValue.value = value; isFormDirty.value = true; }; useClickOutside(iconPopoverRef, hideIconSelectorPopover, { ignore: [iconSelectorButtonRef], }); onKeyStroke('Escape', hideIconSelectorPopover, { target: iconPopoverRef, eventName: 'keydown', }); watch(labelValue, value => emit('input:label', value)); onMounted(async () => { // force focus to start from the input field await nextTick(); inputFieldRef.value.$refs.inputFieldElement.focus(); }); </script> <script lang="ts"> /** * Internal component to which presents a form for handling the logic of editing and creating a view. */ export default { i18n: { messages }, }; </script> <style lang="scss" scoped src="./header-with-views-edit-form.scss" />
@@ -0,0 +1,103 @@
1
+ import { Canvas, Meta } from '@storybook/blocks';
2
+ import * as HeaderWithViewsStories from './header-with-views.stories';
3
+
4
+ <Meta of={HeaderWithViewsStories} />
5
+
6
+ # HeaderWithViews
7
+
8
+ HeaderWithViews displays tabs which allow users to save and quickly retrieve configurations tailored to specific tasks.
9
+ Eg: managing daily transactions, auditing, or analyzing specific data sets. User may define the columns, filters, and
10
+ search queries they need and manage these views with the provided tabs.
11
+
12
+ <Canvas of={HeaderWithViewsStories.Default} />
13
+
14
+ ## Use Cases
15
+
16
+ Use a HeaderWithViews if:
17
+
18
+ - Use when your users use a single page or dashboard for multiple tasks
19
+ - Use when your users need to save configurations for quick and easy access.
20
+
21
+ ## Variations
22
+
23
+ ### Without views
24
+
25
+ When there are no views provided in the `views` property, it renders an empty state with only the `Create view` button.
26
+
27
+ The user can provide an empty state or a "default" view.
28
+
29
+ 1. Listen to the emit `add:view`.
30
+ 2. On `add:view`, add the emitted value as a view config onto your locally stores view.
31
+
32
+ ### With views
33
+
34
+ 1. Listen to the emit `select:view`.
35
+ 2. On `select:view`, update your view to the emitted label. Each label should be unique.
36
+ 3. Listen to `add:view`, `delete:view`, `duplicate:view`, `update:view`.
37
+ 4. On emission of any of the above events, update your locally stored views. The component performs an optimistic update
38
+ whereby, it updates itself first before emission. The idea should be similar to a `v-model` of an input.
39
+ 5. Listen to `reorder:view`.
40
+ 6. On emission, update the order of your views that you have passed into the prop.
41
+
42
+ ## Modifiers
43
+
44
+ ### Controlled Active View
45
+
46
+ The `activeViewIndex` prop supports two-way binding, allowing you to control and track the currently active view from
47
+ outside the component.
48
+
49
+ - **Vue 3**: Use `v-model:active-view-index="activeViewIndex"`
50
+ - **Vue 2**: Use `:active-view-index.sync="activeViewIndex"` or
51
+ `:active-view-index="activeViewIndex" @update:active-view-index="activeViewIndex = $event"`
52
+
53
+ This is useful when you need to:
54
+
55
+ - Programmatically switch views (e.g., via external buttons or navigation)
56
+ - Persist the active view state (e.g., in URL query params or local storage)
57
+ - Synchronize the active view with other parts of your application
58
+
59
+ <Canvas of={HeaderWithViewsStories.ControlledActiveView} />
60
+
61
+ ### With Unsaved changes
62
+
63
+ When a user updates the view with new changes such as filters, columns changes etc. The view will get a blue dot to
64
+ indicate that there are unsaved changes.
65
+
66
+ To save the view a user will hover the view, select the more button to reveal the option to save the changes.
67
+
68
+ 1. To enable this feature, set `hasChanges: true`.
69
+ 2. With this enabled, a modal may be displayed (with the option to disable the mdoal as long as the browser is opened)
70
+ whenever the user switches view, warning users that their changes could be reverted if the decide to proceed without
71
+ saving their changes.
72
+ 3. Listen for the `update:skip-unsaved-changes` event. This event is emitted when the user decides to skip the unsaved
73
+ changes notification. The emission happens _only_ if the `skipUnsavedChangesText` property receives a string value.
74
+ Its purpose is to ensure that the user's preference regarding continuous prompts for saving changes when switching
75
+ view tabs is consistently honored.
76
+ 4. Listen to `restore:view` and `save:view`.
77
+ 5. On emission of either event, either revert the view or update it.
78
+ 6. Set the property `hasChanges` to `false` so that it no longer prompts the modal. This not controlled by
79
+ `BentoHeaderWithViews`.
80
+ 7. _Important_: When an active view with unsaved changes is deleted, listen to the `delete:view` event and set
81
+ `hasChanges: false` when the active view is the deleted view. This has to be done from the user side as there is no
82
+ additional event emitted for `hasChanges` changes.
83
+
84
+ ## Accessibility
85
+
86
+ ### Keyboard interaction
87
+
88
+ | Keyboard interaction | Output |
89
+ | -------------------- | ------------------------------------------------------------------------------------------------------------------- |
90
+ | `Tab` | Focus moves to next navigatable element, either the next tab in the view bar or the next item in the all views menu |
91
+ | `Arrow Up/Down` | Focus moves to next item in the view menu or reorder an item during drag-and-drop |
92
+ | `Enter/Space` | Select a view, open the view menu, select a menu option or initiate keyboard drag-and-drop |
93
+ | `Escape` | Closes the topmost popver or exit drag-and-drop |
94
+
95
+ ### Roles, states and properties
96
+
97
+ | ARIA attribute | is automatic? | fallback used | manual override |
98
+ | -------------- | -------------------------------------------------------------------------- | ------------- | --------------- |
99
+ | group | Yes. This is inferred as the view container consists of a group of buttons | - | - |
100
+
101
+ ## Resources
102
+
103
+ - [Figma link](https://www.figma.com/design/uLabwF3243jdMDsNSP7U9I/Bento---Components?node-id=38116-133408&m=dev)
@@ -0,0 +1 @@
1
+ import { storybookDocsParameter, withToastDecorator } from '@/utils/ts/storybook'; import { action } from '@storybook/addon-actions'; import type { Meta, StoryObj } from '@storybook/vue'; import { isVue2 } from 'vue-demi'; import { ref, watch } from 'vue'; import { BentoHeaderWithViewsItemIconColorEnum } from './header-with-views.types'; import BentoHeaderWithViews from './header-with-views.vue'; import { BentoInputField } from '@/components/input-field'; import HeaderWithViewsControlledActiveViewExample from './__tests__/header-with-views-controlled-active-view-example.vue?raw'; const meta: Meta = { title: 'Header with views', component: BentoHeaderWithViews, decorators: [withToastDecorator], argTypes: { // Events 'add:view': { table: { type: { summary: '(value: BentoHeaderWithViewsItemUpdate) => void' }, }, control: { disable: true }, }, 'delete:view': { table: { type: { summary: '(value: BentoHeaderWithViewsItemUpdate) => void' }, }, control: { disable: true }, }, 'duplicate:view': { table: { type: { summary: '(value: BentoHeaderWithViewsItemUpdate) => void' }, }, control: { disable: true }, }, 'reorder:view': { table: { type: { summary: '(value: Omit<BentoHeaderWithViewsItemUpdate, "targetViewLabel">) => void' }, }, control: { disable: true }, }, 'restore:view': { table: { type: { summary: '(value: string) => void' }, }, control: { disable: true }, }, 'save:view': { table: { type: { summary: '(value: string) => void' }, }, control: { disable: true }, }, 'select:view': { table: { type: { summary: '(value: string) => void' }, }, control: { disable: true }, }, 'update:view': { table: { type: { summary: '(value: BentoHeaderWithViewsItemUpdate) => void' }, }, control: { disable: true }, }, 'update:active-view-index': { table: { type: { summary: '(value: number | null) => void' }, }, control: { disable: true }, }, // Slots infoIconTooltipContent: { description: 'Content to be displayed as a tooltip next to the header. This replaces the description property if present', control: 'text', table: { type: { summary: 'VNode[]' }, }, }, }, }; export default meta; type Story = StoryObj<typeof BentoHeaderWithViews>; const defaultCode = ` <template> <bento-header-with-views :actions="actions" description="Description" title="Page Title" :views="views" :additionalViewSetting='{ "label":"Include date filter (relative)", "value":false }' hasChanges skipUnsavedChangesText="Don’t show this message again" @add:view="handleViews" @delete:view="handleViews" @duplicate:view="handleViews" @reorder:view="handleViews" @restore:view="handleView" @save:view="handleView" @select:view="handleView" @update:view="handleViews" @update:skip-unsaved-changes="handleSkipUnsavedChanges" @update:active-view-index="handleActiveViewIndex" /> </template> <script setup lang="ts"> import { BentoHeaderWithViews } from '@adyen/bento-vue2' const handleViews = ({ newViews }) => void; const handleView = (label) => void; const handleSkipUnsavedChanges = (value) => void; const handleActiveViewIndex = (index) => void; const views = [ { icon: 'stack', iconColor: 'primary', label: 'A very long value' }, { icon: 'stack', iconColor: 'primary', label: 'Fixed view', isFixed: true, }, { icon: 'stack', iconColor: 'primary', label: 'View', } ]; const actions = [ {"title":"Primary action"}, {"title":"Secondary action"}, {"title":"Tertiary action"}, {"title":"Another action"} ] </script> `; export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoHeaderWithViews }, props: Object.keys(argTypes), template: ` <bento-header-with-views v-bind="args" @add:view="addView" @delete:view="deleteView" @duplicate:view="duplicateView" @reorder:view="reorderView" @restore:view="restoreView" @save:view="saveView" @select:view="selectView" @update:view="updateView" @update:skip-unsaved-changes="updateSkipUnsavedChanges" @update:active-view-index="updateActiveViewIndex" /> `, setup(props) { const addView = value => action('add:view')(value); const deleteView = value => action('delete:view')(value); const duplicateView = value => action('duplicate:view')(value); const reorderView = value => action('reorder:view')(value); const restoreView = value => action('restore:view')(value); const saveView = value => action('save:view')(value); const selectView = value => action('select:view')(value); const updateView = value => action('update:view')(value); const updateSkipUnsavedChanges = value => action('update:skip-unsaved-changes')(value); const updateActiveViewIndex = value => action('update:active-view-index')(value); return { args: isVue2 ? props : _args, addView, deleteView, duplicateView, reorderView, restoreView, saveView, selectView, updateView, updateSkipUnsavedChanges, updateActiveViewIndex, }; }, }), args: { hasChanges: true, skipUnsavedChangesText: 'Don’t show this message again', actions: [ { title: 'Primary action', event: () => undefined }, { title: 'Secondary action', event: () => undefined }, { title: 'Tertiary action', event: () => undefined }, { title: 'Another action', event: () => undefined }, ], additionalViewSetting: { label: 'Include date filter (relative)', value: false, }, title: 'Page Title', views: [ { icon: 'stack', iconColor: BentoHeaderWithViewsItemIconColorEnum.PRIMARY, label: 'A very long value', }, { icon: 'stack', iconColor: BentoHeaderWithViewsItemIconColorEnum.PRIMARY, label: 'Fixed view', isFixed: true, }, { icon: 'stack', iconColor: BentoHeaderWithViewsItemIconColorEnum.PRIMARY, label: 'View', }, ], infoIconTooltipText: 'Info icon tooltip text', lastUpdated: new Date(), description: "This page shows how Adyen's AI boosts your payment performance and revenue. Monitor your authorization rate uplift and discover data-driven opportunities for further optimization.", reloadButton: { title: 'Reload', state: 'start', event: action('reloadButtonEvent'), }, }, parameters: storybookDocsParameter(defaultCode), }; export const ControlledActiveView: Story = { render: (_args, { argTypes }) => ({ components: { BentoHeaderWithViews, BentoInputField }, props: Object.keys(argTypes), template: ` <div> <bento-header-with-views v-bind="args" :active-view-index="activeViewIndex" @update:active-view-index="onActiveViewIndexChange" /> <div style="margin-top: var(--b-spacer-080); max-width: 200px;"> <bento-input-field :model-value="activeViewIndex" @update:model-value="onInputChange" type="number" label="Active view index" /> </div> </div> `, setup(props) { const activeViewIndex = ref(props.activeViewIndex); const onActiveViewIndexChange = (value: number) => { activeViewIndex.value = value; action('update:active-view-index')(value); }; const onInputChange = (value: string) => { const numValue = Number.parseInt(value, 10); activeViewIndex.value = numValue; }; watch( () => props.activeViewIndex, index => { activeViewIndex.value = index; } ); return { args: isVue2 ? props : _args, activeViewIndex, onActiveViewIndexChange, onInputChange, }; }, }), args: { hasChanges: false, skipUnsavedChangesText: null, title: 'Page Title', views: [ { icon: 'stack', iconColor: BentoHeaderWithViewsItemIconColorEnum.BLUE, label: 'First View', }, { icon: 'leaf', iconColor: BentoHeaderWithViewsItemIconColorEnum.GREEN, label: 'Second View', }, { icon: 'star', iconColor: BentoHeaderWithViewsItemIconColorEnum.YELLOW, label: 'Third View', }, ], }, parameters: storybookDocsParameter(HeaderWithViewsControlledActiveViewExample), };
@@ -0,0 +1 @@
1
+ <template> <div ref="hostRef" class="b-header-with-views"> <div class="b-header-with-views__container" :class="conditionalContainerClass"> <div class="b-header-with-views__title"> <bento-typography el="h2" variant="title">{{ title }}</bento-typography> <info-icon-with-popover v-if="infoIconTooltipText || hasSlot('infoIconTooltipContent')" :popover-text="infoIconTooltipText" class="b-header-with-views__title-icon" > <template v-if="hasSlot('infoIconTooltipContent')" #default> <slot name="infoIconTooltipContent" /> </template> </info-icon-with-popover> </div> <fieldset ref="viewsRef" class="b-header-with-views__bar" :aria-label="t('viewListLabel')"> <div v-for="(view, index) in computedViews" :key="index" ref="viewRefs" class="b-header-with-views__bar-tab" :class="isViewActive(index) ? 'b-header-with-views__bar-tab--active' : null" > <bento-button class="b-header-with-views__bar-tab-button" variant="tertiary-with-background" :aria-current="currentActiveView === index ? 'true' : 'false'" @click="handleViewSelect(index)" @keydown.enter.native="handleViewSelect(index)" @keydown.space.native="handleViewSelect(index)" > <template #iconLeft> <span class="b-header-with-views__bar-tab-button-icon" :class="`b-header-with-views__bar-tab-button-icon--${view.iconColor}`" aria-hidden="true" > <component :is="BentoHeaderWithViewsItemIconElement[view.icon]" /> </span> </template> <bento-typography el="span" variant="body" stronger class="b-header-with-views__bar-tab-button-text" > {{ view.label }} </bento-typography> <template v-if="isViewUnsaved(index)" #iconRight> <span class="b-header-with-views__bar-tab-button--unsaved"> <dot-icon :svg-title="t('withUnsavedChanges')" /> </span> </template> </bento-button> <bento-menu v-if="currentActiveView === index" ref="editViewMenuRefs" class="b-header-with-views__bar-tab-menu" :button="{ variant: 'tertiary' }" :data="getViewMenuData(index)" :close-on-click-outside-options="{ ignore: [`#${editViewPopoverId}`], }" :menu-fixed-positioning="true" @open="showViewMenuPopover(index)" @close="hideViewMenuPopover()" @keydown.tab.native.capture="handleTabEditViewPopover" > <template #iconLeft> <options-horizontal-icon :svg-title="t('openViewOptions', { label: view.label })" /> </template> </bento-menu> </div> <div ref="viewControlRef" class="b-header-with-views__bar-control"> <!-- All views --> <header-with-views-all-views v-if="computedViews.length" ref="viewControlAllViewsRef" :menu-items="computedViews" :menu-items-actions="getViewMenuData(activeViewMenuPopover)" :menu-close-on-click-outside-options="{ ignore: [`#${editViewPopoverId}`] }" @close:view-menu="hideViewMenuPopover()" @close:all-views="showAllViewsPopover = false" @open:all-views="showAllViewsPopover = true" @open:view-menu="index => showViewMenuPopover(index)" @reorder:view="views => handleReorder(views)" @select:view="index => handleViewSelect(index)" /> <bento-toggle-button ref="createViewButtonRef" :aria-expanded="showCreateViewPopover" :aria-controls="showCreateViewPopover ? createViewPopoverId : null" :toggled="showCreateViewPopover" variant="tertiary" @click="showCreateViewPopover = !showCreateViewPopover" > <template #iconLeft> <plus-icon :svg-title="t('createView')" :aria-hidden="computedViews.length === 0" /> </template> <template v-if="computedViews.length === 0" #default>{{ t('createView') }}</template> </bento-toggle-button> </div> </fieldset> <header-meta v-if="hasMetaSection" class="b-header-with-views__meta-container" :description="description" :last-updated="lastUpdated" :reload-button="reloadButton" > <template v-if="hasSlot('description')" #description><slot name="description" /></template> </header-meta> <button-actions-with-menu v-if="actions" :actions="actions" :actions-layout="isCompact ? 'buttons-start' : 'buttons-end'" :displayed-actions="2" :disable-responsive-behavior="true" :hide-displayed-actions-breakpoint="STACKED_LAYOUT_BREAKPOINT" class="b-header-with-views__actions" /> </div> <!-- Create view --> <bento-popover v-if="activeViewMenuPopover === null && createViewButtonRef" :id="createViewPopoverId" ref="createViewPopoverRef" :dismissible="true" :fixed-positioning="true" :target-element="createViewButtonRef" :title="t('createView')" :open="showCreateViewPopover" :overflow-visible="true" position="bottom-start" @dismiss="hideCreateViewPopover" @keydown.esc.native.capture.stop.prevent="hideCreateViewPopover" > <header-with-views-edit-form context="create" :errors="errors" :additional-form-item="additionalViewSetting" :disable-customization="disableCustomization" @blur:label="value => validateLabel(value, activeViewMenuPopover)" @input:label="value => validateLabel(value, activeViewMenuPopover)" @keydown:enter="handleKeydownEnterViewItem" @update="handleCreateViewItem" @cancel="hideCreateViewPopover" > </header-with-views-edit-form> </bento-popover> <!-- Edit view --> <bento-popover v-if="activeViewMenuPopover !== null && editViewTargetElementRef" :id="editViewPopoverId" ref="editViewPopoverRef" :disable-focus-trap="false" :dismissible="true" :fixed-positioning="true" :target-element="editViewTargetElementRef" :title="t('editDetails')" :open="activeEditViewPopover !== null" :overflow-visible="true" position="right-start" @dismiss="setActiveEditViewPopover()" @keydown.esc.native.capture.stop.prevent="setActiveEditViewPopover()" > <header-with-views-edit-form v-if="computedViews[activeEditViewPopover]" context="edit" :additional-form-item="computedViews[activeEditViewPopover].additionalViewSetting" :errors="errors" :icon="computedViews[activeEditViewPopover].icon" :icon-color="computedViews[activeEditViewPopover].iconColor" :label="computedViews[activeEditViewPopover].label" :disable-customization="disableCustomization" @blur:label="value => validateLabel(value, activeViewMenuPopover)" @input:label="value => validateLabel(value, activeViewMenuPopover)" @keydown:enter="handleKeydownEnterViewItem" @update="handleUpdateViewItem" @cancel="hideEditViewPopover" > </header-with-views-edit-form> </bento-popover> <!-- Unsaved views prompt --> <bento-modal v-if="showUnsavedModalWarning" :actions="computedUnsavedViewModalActions" :is-dismissible="false" :is-open="showUnsavedModalWarning" @close-modal="showUnsavedModalWarning = false" > <template #default>{{ t('unsavedChangesTitle', { label: computedViews[currentActiveView].label }) }}</template> <template #content> <div class="b-header-with-views__modal-description"> <bento-typography>{{ t('unsavedChangesDescription') }}</bento-typography> <bento-checkbox v-if="skipUnsavedChangesText" :model-value="skipUnsavedChangesReminder" @update:model-value="value => (skipUnsavedChangesReminder = value)" > {{ skipUnsavedChangesText }} </bento-checkbox> </div> </template> </bento-modal> <!-- Delete views prompt --> <bento-modal v-if="showDeleteModalWarning" :actions="computedDeleteViewModalActions" :is-open="showDeleteModalWarning" :is-dismissible="false" destructive-actions @click.native.capture="handleClickModal" @close-modal="showDeleteModalWarning = false" > <template #default> {{ t('deleteViewTitle', { label: computedViewToBeDeletedLabel }) }} </template> <template #content> <div class="b-header-with-views__modal-description"> <bento-typography>{{ t('deleteViewDescription', { label: computedViewToBeDeletedLabel }) }}</bento-typography> </div> </template> </bento-modal> </div> </template> <script setup lang="ts"> import { BentoButton, BentoToggleButton } from '@/components/button'; import { BentoCheckbox } from '@/components/checkbox'; import { BentoMenu, type BentoMenuItem } from '@/components/menu'; import BentoModal from '@/components/modal/modal.vue'; import { BentoPopover } from '@/components/popover'; import { BentoTypography } from '@/components/typography'; import { HeaderMeta } from '@/internal'; import { useBentoToastController, useClickOutside, useHasSlot } from '@/composables'; import { generateUid } from '@/core/utils/ts'; import ButtonActionsWithMenu from '@/internal/button-actions-with-menu/button-actions-with-menu.vue'; import { InfoIconWithPopover } from '@/internal/info-icon-with-popover'; import { useI18n } from '@/utils/ts/i18n'; import BinIcon from '@adyen/ui-assets-icons-16/vue/bin'; import DotIcon from '@adyen/ui-assets-icons-16/vue/dot'; import DownloadIcon from '@adyen/ui-assets-icons-16/vue/download'; import EditIcon from '@adyen/ui-assets-icons-16/vue/edit-2'; import HeartIcon from '@adyen/ui-assets-icons-16/vue/heart'; import OptionsHorizontalIcon from '@adyen/ui-assets-icons-16/vue/options-horizontal'; import PlusIcon from '@adyen/ui-assets-icons-16/vue/plus'; import PlusMultipleIcon from '@adyen/ui-assets-icons-16/vue/plus-multiple'; import RefreshIcon from '@adyen/ui-assets-icons-16/vue/refresh'; import { useElementSize } from '@vueuse/core'; import { computed, nextTick, onMounted, ref, useSlots, watch } from 'vue'; import { HeaderWithViewsAllViews, HeaderWithViewsEditForm } from './components'; import { useEditViews, useResponsiveViews } from './composables'; import { type BentoHeaderWithViewsEmits, type BentoHeaderWithViewsItem, BentoHeaderWithViewsItemIconElement, type BentoHeaderWithViewsProps, } from './header-with-views.types'; import messages from './messages.json'; type MessageSchema = (typeof messages)['en-US']; const props = withDefaults(defineProps<BentoHeaderWithViewsProps>(), { actions: () => [], additionalViewSetting: null, title: null, description: null, disableCustomization: false, lastUpdated: null, reloadButton: null, infoIconTooltipText: null, views: () => [], disableAutoSelect: false, activeViewIndex: undefined, }); const emit = defineEmits<BentoHeaderWithViewsEmits>(); const STACKED_LAYOUT_BREAKPOINT = 573; const { t } = useI18n<{ message: MessageSchema }>({ messages }); const slots = useSlots(); const hasSlot = useHasSlot(slots); // refs const createViewButtonRef = ref(null); const createViewPopoverRef = ref(null); const editViewPopoverRef = ref(null); const editViewMenuRefs = ref([]); const editViewTargetElementRef = ref(null); // used as a mount for the edit form popover const hostRef = ref(null); const viewsRef = ref<HTMLElement>(null); // container for all views const viewRefs = ref<Array<HTMLDivElement>>([]); // individual view const viewControlRef = ref(null); // all views menu and create view button container const viewControlAllViewsRef = ref(null); // all views menu // controls const showCreateViewPopover = ref<boolean>(false); // control the create view popover const showAllViewsPopover = ref<boolean>(false); // control when menu button is shown const showUnsavedModalWarning = ref<boolean>(false); const showDeleteModalWarning = ref<boolean>(false); const activeEditViewPopover = ref<number>(null); // control the edit view popover const activeViewMenuPopover = ref<number>(null); // control when menu items are shown const internalActiveView = ref<number>(null); // control which view the user is currently on const nextActiveView = ref<number>(null); // control which view is does the user want to navigate to, used for caching unsaved view navigation const viewToBeDeleted = ref<number>(null); // cache which view is marked for deletion // Computed property to handle two-way binding for activeViewIndex const currentActiveView = computed({ get: () => internalActiveView.value, set: (value: number | null) => { internalActiveView.value = value; emit('update:active-view-index', value); }, }); // models const skipUnsavedChangesReminder = ref<boolean>(false); const createViewPopoverId = generateUid('createViewPopover'); const editViewPopoverId = generateUid('editViewPopover'); useResponsiveViews({ container: viewsRef, children: viewRefs, offset: viewControlRef, activeViewIndex: currentActiveView, }); const { computedViews, clearError, duplicateView, errors, setViews, updateView, validateLabel } = useEditViews({ additionalViewSetting: props.additionalViewSetting, views: props.views, }); const hideCreateViewPopover = async (updateFocus = true) => { showCreateViewPopover.value = false; if (updateFocus) { await nextTick(); createViewButtonRef.value.$el.focus(); } clearError(); }; const hideEditViewPopover = () => { const currentMenuRef = showAllViewsPopover.value ? viewControlAllViewsRef.value.$refs.editViewMenuRefs[activeEditViewPopover.value] : editViewMenuRefs.value[0]; currentMenuRef?.closeMenu(); }; useClickOutside(createViewPopoverRef, () => hideCreateViewPopover(false), { ignore: [createViewButtonRef], }); useClickOutside(editViewPopoverRef, () => setActiveEditViewPopover(), { ignore: [editViewTargetElementRef], }); const { width: hostWidth } = useElementSize(hostRef); const { addToast } = useBentoToastController(); const computedUnsavedViewModalActions = computed(() => [ { title: t('saveChanges'), event: handleSaveChanges, }, { title: t('leave'), event: handleRestoreChanges, }, ]); const computedDeleteViewModalActions = computed(() => [ { title: t('deleteViewAction'), event: async () => { showDeleteModalWarning.value = false; const updatedViewObj = updateView(null, viewToBeDeleted.value); emit('delete:view', updatedViewObj); hideViewMenuPopover(); // if a view that is placed higher in the order is deleted, ignore if (currentActiveView.value < viewToBeDeleted.value) { return; } // if a view that is placed lower in the order is deleted, // we need to shift the index to maintain the current view if (currentActiveView.value > viewToBeDeleted.value) { currentActiveView.value -= 1; } else if (currentActiveView.value === viewToBeDeleted.value) { if (updatedViewObj.newViews.length === 0) { // there are no more views currentActiveView.value = null; } else if (currentActiveView.value >= updatedViewObj.newViews.length) { // shift it to the left currentActiveView.value -= 1; } // else, keep it at the same index } viewToBeDeleted.value = null; }, }, { title: t('cancel'), event: () => { showDeleteModalWarning.value = false; }, }, ]); const computedViewToBeDeletedLabel = computed(() => computedViews.value[viewToBeDeleted.value]?.label); const hasMetaSection = computed(() => props.description || hasSlot('description') || props.lastUpdated); const isCompact = computed(() => hostWidth.value <= STACKED_LAYOUT_BREAKPOINT); const conditionalContainerClass = computed(() => ({ 'b-header-with-views__container--is-compact': isCompact.value, 'b-header-with-views__container--has-meta': hasMetaSection.value, })); watch([currentActiveView, computedViews], ([newIndex, newViews], [oldIndex, oldViews]) => { // skip if the labels are the same or no active view const isViewUpdated = newIndex === oldIndex && newViews.length === oldViews.length; const isViewDeletedWithChangedIndex = newViews[newIndex]?.label === oldViews[oldIndex]?.label; if (isViewUpdated || isViewDeletedWithChangedIndex) { return; } // allow empty label // occurs when the user removes all views emit('select:view', newViews[newIndex]?.label ?? null); }); // whenever the view menu popover changes, we reset the edit popover because it is never opened by default watch(activeViewMenuPopover, () => { setActiveEditViewPopover(); }); watch(showAllViewsPopover, value => { if (value) { return; } // if show all view popover is closed, close children as well hideViewMenuPopover(); }); // set the popover to open next to the edit button watch(activeEditViewPopover, value => { const editButtonIndex = isViewUnsaved(value) ? 2 : 0; // EDGE CASE: // Since edit button can be clicked while in all views popover or while it is in the bar, // we will not support the case where edit popover is opened and the screen is resize until the view menu is hidden. // Edit popover will still be displayed but the position does not have to follow the edit button as it is hidden. if (showAllViewsPopover.value) { const menuListItemRef = viewControlAllViewsRef.value.$refs.editViewMenuRefs[activeViewMenuPopover.value]?.$refs ?.menuItemListRef; editViewTargetElementRef.value = viewControlAllViewsRef.value.$refs.editViewMenuRefs[activeViewMenuPopover.value]?.$refs?.menuItemListRef ?.$refs?.menuItemRef?.[editButtonIndex] ?? null; if (!value) { menuListItemRef?.focusItem(); clearError(); } return; } const menuListItemRef = editViewMenuRefs.value.find(ref => ref.$refs.menuItemListRef)?.$refs?.menuItemListRef; editViewTargetElementRef.value = menuListItemRef?.$refs?.menuItemRef?.[editButtonIndex] ?? null; if (!value) { menuListItemRef?.focusItem(); clearError(); } }); // Watch for external changes to the activeViewIndex prop watch( () => props.activeViewIndex, newValue => { if (newValue !== undefined && newValue >= 0 && newValue < computedViews.value.length) { currentActiveView.value = newValue; } } ); onMounted(() => { if ( props.activeViewIndex !== undefined && props.activeViewIndex >= 0 && props.activeViewIndex < computedViews.value.length ) { currentActiveView.value = props.activeViewIndex; } else if (!props.disableAutoSelect) { const defaultView = computedViews.value.findIndex(view => view.isDefault); currentActiveView.value = defaultView >= 0 ? defaultView : 0; } }); const getViewMenuData = (index: number): Array<BentoMenuItem> => [ ...(isViewUnsaved(index) ? [ { text: t('saveChanges'), handler: handleSaveChanges, icon: DownloadIcon, }, { addDivider: true, text: t('restoreChanges'), handler: handleRestoreChanges, icon: RefreshIcon, }, ] : []), ...(!computedViews.value[index]?.isFixed ? [ { text: t('edit'), handler: () => { setActiveEditViewPopover(activeViewMenuPopover.value); }, icon: EditIcon, skipClose: true, }, ] : []), { text: t('setAsDefault'), handler: () => { const updatedViewObj = updateView({ isDefault: true }, activeViewMenuPopover.value); emit('update:view', updatedViewObj); addToast({ text: t('setAsDefaultToast', { label: updatedViewObj.targetViewLabel }), }); hideViewMenuPopover(); }, icon: HeartIcon, }, { text: t('duplicate'), handler: () => { const updatedViewObj = duplicateView(activeViewMenuPopover.value); emit('duplicate:view', updatedViewObj); hideViewMenuPopover(); // switch to newly duplicated view handleViewSelect(updatedViewObj.newViews.length - 1); }, icon: PlusMultipleIcon, }, ...(!computedViews.value[index]?.isFixed ? [ { text: t('delete'), handler: () => { viewToBeDeleted.value = activeViewMenuPopover.value; showDeleteModalWarning.value = true; }, critical: true, icon: BinIcon, }, ] : []), ]; const showViewMenuPopover = (index: number) => { activeViewMenuPopover.value = index; }; const hideViewMenuPopover = () => { activeViewMenuPopover.value = null; setActiveEditViewPopover(); }; const setActiveEditViewPopover = (index?: number) => { if (index === activeEditViewPopover.value) { activeEditViewPopover.value = null; } else { activeEditViewPopover.value = index ?? null; } }; const handleCreateViewItem = (value: BentoHeaderWithViewsItem) => { const updatedViewObj = updateView(value); emit('add:view', updatedViewObj); hideCreateViewPopover(); // switch to new view on add handleViewSelect(updatedViewObj.newViews.findIndex(view => updatedViewObj.targetViewLabel === view.label)); addToast({ text: t('createViewToast', { label: updatedViewObj.targetViewLabel }), }); }; const handleUpdateViewItem = (value: BentoHeaderWithViewsItem) => { emit('update:view', updateView(value, activeEditViewPopover.value)); hideEditViewPopover(); }; const handleKeydownEnterViewItem = (value: BentoHeaderWithViewsItem) => { validateLabel(value.label, activeViewMenuPopover.value); if (Object.values(errors).some(error => error.length > 0)) { return; } if (activeEditViewPopover.value) { handleUpdateViewItem(value); return; } handleCreateViewItem(value); }; const handleViewSelect = (index: number) => { // If there's no currently active view, or if there are no unsaved changes, // just switch to the new view. if (currentActiveView.value === null || !props.hasChanges || props.skipUnsavedChangesText === null) { // If switching from an existing view, emit restore event. if (currentActiveView.value !== null) { emit('restore:view', computedViews.value[currentActiveView.value].label); } currentActiveView.value = index; return; } nextActiveView.value = index; showUnsavedModalWarning.value = true; }; const handleReorder = (reorderedViews: Array<BentoHeaderWithViewsItem>) => { const currentActiveLabel = currentActiveView.value !== null ? computedViews.value[currentActiveView.value]?.label : null; const result = setViews(reorderedViews); if (currentActiveLabel) { const newIndex = result.newViews.findIndex(view => view.label === currentActiveLabel); if (newIndex !== -1) { currentActiveView.value = newIndex; } } emit('reorder:view', result); }; const handleClickModal = (event: MouseEvent) => { if ((event.target as HTMLElement).closest('button')) { return; } event.stopPropagation(); event.preventDefault(); }; const handleTabEditViewPopover = () => { // skip built-in refocus on the menu button when menu is closed // enables tab to flow as intended by HTML spec editViewMenuRefs.value[0].closeMenu(false); }; const handleSaveChanges = () => { const label = computedViews.value[currentActiveView.value].label; emit('save:view', label); if (showUnsavedModalWarning.value) { emit('update:skip-unsaved-changes', skipUnsavedChangesReminder.value); // fired when user saves the view while navigating to a different tab currentActiveView.value = nextActiveView.value; nextActiveView.value = null; showUnsavedModalWarning.value = false; } else { // fired when user saves the view via the menu hideViewMenuPopover(); } addToast({ text: t('saveChangesToast', { label }), action: { handler: () => emit('restore:view', label), text: t('undo'), }, }); }; const handleRestoreChanges = () => { emit('restore:view', computedViews.value[currentActiveView.value].label); if (showUnsavedModalWarning.value) { emit('update:skip-unsaved-changes', skipUnsavedChangesReminder.value); // fired when user restore the view while navigating to a different tab currentActiveView.value = nextActiveView.value; nextActiveView.value = null; showUnsavedModalWarning.value = false; } else { // fired when user restore the view via the menu hideViewMenuPopover(); } }; const isViewActive = (index: number) => currentActiveView.value === index; const isViewUnsaved = (index: number) => isViewActive(index) && props.hasChanges; </script> <script lang="ts"> /** * Header with views allow users to save and quickly retrieve configurations tailored to specific tasks. * For example, managing daily transactions, auditing, or analyzing specific data sets. * It also allows the user to define the columns, filters, and search queries they need. * * @example * import { BentoHeaderWithViews } from '@adyen/bento-vue2'; * * export default { * components: { BentoHeaderWithViews }, * template: ` * <bento-header-with-views * :actions="[ * { title: 'Primary action', event: () => undefined }, * { title: 'Secondary action', event: () => undefined }, * { title: 'Tertiary action', event: () => undefined }, * { title: 'Another action', event: () => undefined } * ]" * description="Page Description" * title="Page Title" * :views="[ * { icon: 'stack', iconColor: 'blue', label: 'A very long value' }, * { icon: 'stack', iconColor: 'primary', label: 'Title 1' }, * { icon: 'stack', iconColor: 'primary', label: 'Title 2' } * ]" * :additionalViewSetting="{ * label: 'Include date filter (relative)', * value: false * }" * hasChanges * /> * ` * }; */ export default { i18n: { messages }, }; </script> <style lang="scss" scoped src="./header-with-views.scss" />
@@ -0,0 +1,34 @@
1
+ import { Canvas, Meta } from '@storybook/blocks';
2
+ import * as ImageStories from './image.stories';
3
+
4
+ <Meta of={ImageStories} />
5
+
6
+ # Image
7
+
8
+ `BentoImage` is used for resizing images to certain aspect ratios and fit strategies.
9
+
10
+ <Canvas of={ImageStories.Default} />
11
+
12
+ ## Use Cases
13
+
14
+ - Use when you need to have an image resized to a cetain aspect ratio.
15
+
16
+ ### Do not use
17
+
18
+ - When just diplaying an image. It will not provide any extra benefit over the HTML `<img>` tag
19
+
20
+ ## Setting proportions
21
+
22
+ The `BentoImage` provides presets for setting an aspect ratio for the image like `landscape`, `portait` or `square`.
23
+ Additionally there is more granular control using numbers, ranging from `4:3` to `16:9` to `1:1`.
24
+
25
+ When setting an `aspect-ratio` it is important to define a streategy for how the image will fit in the container with
26
+ the new ratio, for which you can specify an `object-fit` property of `cover`, `fill` or `contain`.
27
+
28
+ Since these are wrappers around css properties, you can find more info on
29
+ [aspect-ratio](https://developer.mozilla.org/en-US/docs/Web/CSS/aspect-ratio) and
30
+ [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) online.
31
+
32
+ ## Accessibility
33
+
34
+ The image will receive the `alt` attribute as its as its textual replacement.
@@ -0,0 +1 @@
1
+ import { storybookDocsParameter } from '@/utils/ts/storybook'; import type { StoryObj } from '@storybook/vue'; import BentoImage from './image.vue'; import { mockImage } from './const'; import { BentoImageAspectRatio, BentoImageObjectFit } from './image.types'; import { isVue2 } from 'vue-demi'; export default { title: 'Image', component: BentoImage, argTypes: { aspectRatio: { options: Object.values(BentoImageAspectRatio), mapping: BentoImageAspectRatio, control: { type: 'select', }, }, objectFit: { options: Object.values(BentoImageObjectFit), mapping: BentoImageObjectFit, control: { type: 'select', }, }, }, }; type Story = StoryObj<typeof BentoImage>; const DEFAULT_IMAGE_PROPS = { src: mockImage, alt: 'Mock image', aspectRatio: 'landscape', objectFit: 'cover', }; const DefaultTemplate: Story = { render: (_args, { argTypes }) => ({ components: { BentoImage }, props: Object.keys(argTypes), template: ` <bento-image v-bind="args" style="width:200px" /> `, setup(props) { return { args: isVue2 ? props : _args, }; }, }), }; const defaultCode = ` <bento-image src="${DEFAULT_IMAGE_PROPS.src}" alt="${DEFAULT_IMAGE_PROPS.alt}" aspect-ratio="${DEFAULT_IMAGE_PROPS.aspectRatio}" object-fit="${DEFAULT_IMAGE_PROPS.objectFit}" /> `; export const Default = { ...DefaultTemplate, args: { ...DEFAULT_IMAGE_PROPS, }, parameters: storybookDocsParameter(defaultCode), }; Default.argTypes = { icon: { table: { disable: true } } };
@@ -0,0 +1 @@
1
+ <template> <img :src="src" :alt="alt" :style="imageStyles" /> </template> <script lang="ts" setup> import { computed } from 'vue'; import { BentoImageAspectRatio, type BentoImageProps } from './image.types'; const props = withDefaults(defineProps<BentoImageProps>(), { aspectRatio: null, objectFit: null, }); const convertAspectRatio = (aspectRatio: BentoImageAspectRatio | `${BentoImageAspectRatio}`) => { let parsedAspectRatio: BentoImageAspectRatio | `${BentoImageAspectRatio}` = BentoImageAspectRatio.P16_9; switch (aspectRatio) { case BentoImageAspectRatio.LANDSCAPE: parsedAspectRatio = BentoImageAspectRatio.P16_9; break; case BentoImageAspectRatio.PORTRAIT: parsedAspectRatio = BentoImageAspectRatio.P3_4; break; case BentoImageAspectRatio.SQUARE: parsedAspectRatio = BentoImageAspectRatio.P1_1; break; default: parsedAspectRatio = aspectRatio; break; } return `${parsedAspectRatio}`.replace(':', '/'); }; const imageStyles = computed(() => ({ aspectRatio: convertAspectRatio(props.aspectRatio), objectFit: props.objectFit, })); </script> <script lang="ts"> /** * Image component used for resizing images to certain aspect ratios and fit strategies. * * @example * import { BentoImage } from '@adyen/bento-vue2'; * * export default { * components: { BentoImage }, * template: ` * <bento-image * :src="imageSrc" * alt="alternative text" * aspect-ratio="landscape" * object-fit="cover" * * /> * ` * } */ export default { name: 'bento-image', }; </script>
@@ -0,0 +1,57 @@
1
+ import { Canvas, Meta, Story } from '@storybook/blocks';
2
+ import * as InfoIconStories from './info-icon.stories';
3
+
4
+ <Meta of={InfoIconStories} />
5
+
6
+ # Info Icon
7
+
8
+ <Canvas withSource="closed">
9
+ <Story of={InfoIconStories.Default} />
10
+ </Canvas>
11
+
12
+ A BentoInfoIcon that renders an informational icon with a tooltip text.
13
+
14
+ When a user hovers over the icon, the tooltip text displays, providing further information about the UI element or
15
+ feature.
16
+
17
+ ## Use Cases
18
+
19
+ - Show additional information for any component or element on the page
20
+
21
+ ## Modifiers
22
+
23
+ ### Tooltip Position
24
+
25
+ This property positions the tooltip relative to the trigger element. It's of type `PopperContainerPosition` and it
26
+ should take, one of the following values:
27
+
28
+ - `auto`: default value, positions the tooltip wherever there is space available
29
+ - `top`: displays the tooltip to the top of the trigger element.
30
+ - `right`: displays the tooltip to the right of the trigger element.
31
+ - `bottom`: displays the tooltip to the bottom of the trigger element.
32
+ - `left`: displays the tooltip to the left of the trigger element.
33
+
34
+ Note that the tooltip will only be displayed in the correct position if there is space available.
35
+
36
+ ## Accessibility
37
+
38
+ ### Keyboard interaction
39
+
40
+ The `bento-info-icon` is reachable through keyboard navigation in the logical navigation flow of the page. The following
41
+ keys trigger the following actions:
42
+
43
+ | Keyboard interaction | Action |
44
+ | -------------------- | -------------------------------------------------------------------- |
45
+ | `Tab` | Moves focus to the button or away from it, showing or hiding tooltip |
46
+
47
+ ### Aria attributes
48
+
49
+ Adhering to the [WAI-ARIA 1.2 standard](https://www.w3.org/TR/wai-aria-1.2/) we apply the following:
50
+
51
+ | ARIA attribute | is automatic? | fallback used | manual override |
52
+ | -------------- | ------------------------------------------ | ------------- | --------------- |
53
+ | `label` | Yes, based on `tooltipText` property value | - | - |
54
+
55
+ ## Resources
56
+
57
+ - [Figma link](https://www.figma.com/file/uLabwF3243jdMDsNSP7U9I/branch/xxUamzpDM5NlukATVD9JFT/Bento---Components?node-id=4550%3A101263&mode=dev)
@@ -0,0 +1 @@
1
+ import BentoInfoIcon from './info-icon.vue'; import { storybookDocsParameter } from '@/utils/ts/storybook'; import { isVue2 } from 'vue-demi'; import { type Meta, type StoryObj } from '@storybook/vue'; import { PopperContainerPosition } from '@/components/popper-container'; export default { title: 'Info Icon', component: BentoInfoIcon, argTypes: { tooltipPosition: { options: Object.values(PopperContainerPosition), mapping: PopperContainerPosition, defaultValue: PopperContainerPosition.AUTO, control: { type: 'select', }, }, }, } as Meta; type Story = StoryObj<typeof BentoInfoIcon>; const DEFAULT_INFO_ICON_PROPS = { tooltipText: 'Default Icon Tooltip Text', }; const defaultCode = ` <template> <bento-info-icon tooltip-text="${DEFAULT_INFO_ICON_PROPS.tooltipText}" /> </template> <script setup lang="ts"> import { BentoInfoIcon } from '@adyen/bento-vue2'; </scrip> `; export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoInfoIcon }, props: Object.keys(argTypes), template: ` <bento-info-icon v-bind="args"/> `, setup(props) { return { args: isVue2 ? props : _args, }; }, }), args: { ...DEFAULT_INFO_ICON_PROPS }, parameters: storybookDocsParameter(defaultCode), };
@@ -0,0 +1 @@
1
+ <template> <span v-bento-tooltip-directive:[tooltipPosition]="tooltipText" :aria-label="tooltipText" class="b-info-icon" @mouseenter="emit('mouseenter', $event)" > <info-icon :svg-title="tooltipText" class="b-info-icon__svg" /> </span> </template> <script setup lang="ts"> import { PopperContainerPosition } from '@/components/popper-container'; import { BentoTooltipDirective as vBentoTooltipDirective } from '@/directives'; import { type BentoInfoIconProps } from './info-icon.types'; import InfoIcon from '@adyen/ui-assets-icons-16/vue/info'; withDefaults(defineProps<BentoInfoIconProps>(), { tooltipPosition: PopperContainerPosition.AUTO, }); const emit = defineEmits<{ /** * Emitted when the mouse pointer enters the component. */ (e: 'mouseenter', event: MouseEvent): void; }>(); </script> <script lang="ts"> /** * @example * <template> * <bento-info-icon tooltip-text="Text on hover" /> * </template> * * <script setup lang="ts"> * import { BentoInfoIcon } from '@adyen/bento-vue2'; * </scrip> */ export default { name: 'bento-info-icon', }; </script> <style lang="scss" scoped src="./info-icon.scss" />