@adyen/bento-mcp 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (424) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +24 -2
  3. package/SKILL.md +37 -7
  4. package/dist/assets/components/accordion/accordion.stories.ts +1 -1
  5. package/dist/assets/components/accordion/components/accordion-item.stories.ts +1 -1
  6. package/dist/assets/components/accordion/components/accordion-item.vue +1 -1
  7. package/dist/assets/components/action-bar/action-bar.docs.mdx +10 -3
  8. package/dist/assets/components/action-bar/action-bar.internal.types.ts +1 -0
  9. package/dist/assets/components/action-bar/action-bar.stories.ts +1 -1
  10. package/dist/assets/components/action-bar/action-bar.types.ts +1 -1
  11. package/dist/assets/components/action-bar/action-bar.vue +1 -1
  12. package/dist/assets/components/ai-tag/ai-tag.stories.ts +1 -1
  13. package/dist/assets/components/ai-tag/ai-tag.vue +1 -1
  14. package/dist/assets/components/alert/alert.stories.ts +1 -1
  15. package/dist/assets/components/alert/alert.types.ts +1 -1
  16. package/dist/assets/components/anchor-scroller/anchor-scroller.stories.ts +1 -1
  17. package/dist/assets/components/anchor-scroller/anchor-scroller.vue +1 -1
  18. package/dist/assets/components/avatar/avatar.internal.types.ts +1 -0
  19. package/dist/assets/components/avatar/avatar.stories.ts +1 -1
  20. package/dist/assets/components/avatar/avatar.types.ts +1 -1
  21. package/dist/assets/components/button/button.docs.mdx +4 -3
  22. package/dist/assets/components/button/button.stories.ts +1 -1
  23. package/dist/assets/components/button/button.types.ts +1 -1
  24. package/dist/assets/components/button/components/ai-button/ai-button.stories.ts +1 -1
  25. package/dist/assets/components/button/components/ai-button/ai-button.vue +1 -1
  26. package/dist/assets/components/button/components/button-actions/button-actions.docs.mdx +3 -0
  27. package/dist/assets/components/button/components/button-actions/button-actions.internal.types.ts +1 -0
  28. package/dist/assets/components/button/components/button-actions/button-actions.stories.ts +1 -1
  29. package/dist/assets/components/button/components/button-actions/button-actions.types.ts +1 -1
  30. package/dist/assets/components/button/components/button-actions/button-actions.vue +1 -1
  31. package/dist/assets/components/button/components/loading-button/loading-button.docs.mdx +26 -23
  32. package/dist/assets/components/button/components/loading-button/loading-button.stories.ts +1 -1
  33. package/dist/assets/components/button/components/loading-button/loading-button.vue +1 -1
  34. package/dist/assets/components/button/components/toggle-button/toggle-button.stories.ts +1 -1
  35. package/dist/assets/components/card/card.docs.mdx +18 -1
  36. package/dist/assets/components/card/card.stories.ts +1 -1
  37. package/dist/assets/components/card/card.types.ts +1 -1
  38. package/dist/assets/components/card/card.vue +1 -1
  39. package/dist/assets/components/checkbox/checkbox.stories.ts +1 -1
  40. package/dist/assets/components/checkbox/checkbox.types.ts +1 -1
  41. package/dist/assets/components/checkbox/checkbox.vue +1 -1
  42. package/dist/assets/components/checkbox/components/checkbox-group/checkbox-group.docs.mdx +9 -14
  43. package/dist/assets/components/checkbox/components/checkbox-group/checkbox-group.stories.ts +1 -1
  44. package/dist/assets/components/checkbox/components/checkbox-group/checkbox-group.types.ts +1 -1
  45. package/dist/assets/components/checkbox/components/checkbox-group/checkbox-group.vue +1 -1
  46. package/dist/assets/components/chip/chip.stories.ts +1 -1
  47. package/dist/assets/components/chip/chip.vue +1 -1
  48. package/dist/assets/components/click-outside/click-outside.stories.ts +1 -1
  49. package/dist/assets/components/code-snippet/code-snippet.types.ts +1 -1
  50. package/dist/assets/components/code-snippet/code-snippet.vue +1 -1
  51. package/dist/assets/components/country/country.stories.ts +1 -1
  52. package/dist/assets/components/country/country.types.ts +1 -1
  53. package/dist/assets/components/country/country.vue +1 -1
  54. package/dist/assets/components/currency/currency.stories.ts +1 -1
  55. package/dist/assets/components/currency/currency.vue +1 -1
  56. package/dist/assets/components/dashed-underline/dashed-underline.stories.ts +1 -1
  57. package/dist/assets/components/data-grid/components/data-grid-cell-actions/data-grid-cell-actions.types.ts +1 -1
  58. package/dist/assets/components/data-grid/components/data-grid-cell-actions/data-grid-cell-actions.vue +1 -1
  59. package/dist/assets/components/data-grid/components/data-grid-config-settings/data-grid-config-settings.vue +1 -1
  60. package/dist/assets/components/data-grid/components/data-grid-row/data-grid-row.vue +1 -1
  61. package/dist/assets/components/data-grid/components/data-grid-static-icon/data-grid-static-icon.stories.ts +1 -1
  62. package/dist/assets/components/data-grid/components/data-grid-summary-cell/data-grid-summary-cell.stories.ts +1 -1
  63. package/dist/assets/components/data-grid/components/data-grid-summary-cell/data-grid-summary-cell.vue +1 -1
  64. package/dist/assets/components/data-grid/components/data-grid-tag-with-text-cell/data-grid-tag-with-text-cell.stories.ts +1 -1
  65. package/dist/assets/components/data-grid/components/data-grid-trend-cell/data-grid-trend-cell.stories.ts +1 -1
  66. package/dist/assets/components/data-grid/components/data-grid-trend-cell/data-grid-trend-cell.vue +1 -1
  67. package/dist/assets/components/data-grid/data-grid.docs.mdx +3 -60
  68. package/dist/assets/components/data-grid/data-grid.stories.ts +1 -1
  69. package/dist/assets/components/data-grid/data-grid.vue +1 -1
  70. package/dist/assets/components/date/date.stories.ts +1 -1
  71. package/dist/assets/components/date-picker/date-picker.stories.ts +1 -1
  72. package/dist/assets/components/date-picker/date-picker.types.ts +1 -1
  73. package/dist/assets/components/date-picker/date-picker.vue +1 -1
  74. package/dist/assets/components/date-range-picker/components/date-range-picker-calendar/date-range-picker-calendar.vue +1 -1
  75. package/dist/assets/components/date-range-picker/components/date-time-input/date-time-input.types.ts +1 -1
  76. package/dist/assets/components/date-range-picker/components/date-time-input/date-time-input.vue +1 -1
  77. package/dist/assets/components/date-range-picker/date-range-picker.docs.mdx +31 -6
  78. package/dist/assets/components/date-range-picker/date-range-picker.stories.ts +1 -1
  79. package/dist/assets/components/date-range-picker/date-range-picker.types.ts +1 -1
  80. package/dist/assets/components/date-range-picker/date-range-picker.vue +1 -1
  81. package/dist/assets/components/date-time-picker/date-time-picker.stories.ts +1 -1
  82. package/dist/assets/components/date-time-picker/date-time-picker.types.ts +1 -1
  83. package/dist/assets/components/divider/divider.internal.types.ts +1 -0
  84. package/dist/assets/components/divider/divider.stories.ts +1 -1
  85. package/dist/assets/components/divider/divider.types.ts +1 -1
  86. package/dist/assets/components/divider/divider.vue +1 -1
  87. package/dist/assets/components/draggable/components/draggable-handle/draggable-handle.stories.ts +1 -1
  88. package/dist/assets/components/draggable/draggable.stories.ts +1 -1
  89. package/dist/assets/components/draggable/draggable.vue +1 -1
  90. package/dist/assets/components/drawer/drawer.stories.ts +1 -1
  91. package/dist/assets/components/dropdown/components/dropdown-base-textbox/dropdown-base-textbox.internal.types.ts +1 -0
  92. package/dist/assets/components/dropdown/components/dropdown-base-textbox/dropdown-base-textbox.types.ts +1 -1
  93. package/dist/assets/components/dropdown/components/dropdown-base-textbox/dropdown-base-textbox.vue +1 -1
  94. package/dist/assets/components/dropdown/components/dropdown-default-textbox/dropdown-default-textbox.vue +1 -1
  95. package/dist/assets/components/dropdown/components/dropdown-options-container/dropdown-options-container.vue +1 -1
  96. package/dist/assets/components/dropdown/components/dropdown-small-textbox/dropdown-small-textbox.vue +1 -1
  97. package/dist/assets/components/dropdown/components/variants/dropdown-avatar/dropdown-avatar.stories.ts +1 -1
  98. package/dist/assets/components/dropdown/components/variants/dropdown-avatar/dropdown-avatar.types.ts +1 -1
  99. package/dist/assets/components/dropdown/components/variants/dropdown-avatar/dropdown-avatar.vue +1 -1
  100. package/dist/assets/components/dropdown/components/variants/dropdown-country/dropdown-country.stories.ts +1 -1
  101. package/dist/assets/components/dropdown/components/variants/dropdown-country/dropdown-country.types.ts +1 -1
  102. package/dist/assets/components/dropdown/components/variants/dropdown-country/dropdown-country.vue +1 -1
  103. package/dist/assets/components/dropdown/components/variants/dropdown-currency/dropdown-currency.stories.ts +1 -1
  104. package/dist/assets/components/dropdown/components/variants/dropdown-currency/dropdown-currency.types.ts +1 -1
  105. package/dist/assets/components/dropdown/components/variants/dropdown-currency/dropdown-currency.vue +1 -1
  106. package/dist/assets/components/dropdown/components/variants/dropdown-payment-method/dropdown-payment-method.stories.ts +1 -1
  107. package/dist/assets/components/dropdown/components/variants/dropdown-payment-method/dropdown-payment-method.types.ts +1 -1
  108. package/dist/assets/components/dropdown/components/variants/dropdown-payment-method/dropdown-payment-method.vue +1 -1
  109. package/dist/assets/components/dropdown/components/variants/dropdown-tag/dropdown-tag.stories.ts +1 -1
  110. package/dist/assets/components/dropdown/components/variants/dropdown-tag/dropdown-tag.types.ts +1 -1
  111. package/dist/assets/components/dropdown/components/variants/dropdown-tag/dropdown-tag.vue +1 -1
  112. package/dist/assets/components/dropdown/dropdown.docs.mdx +4 -40
  113. package/dist/assets/components/dropdown/dropdown.internal.types.ts +1 -0
  114. package/dist/assets/components/dropdown/dropdown.stories.ts +1 -1
  115. package/dist/assets/components/dropdown/dropdown.types.ts +1 -1
  116. package/dist/assets/components/dropdown/dropdown.vue +1 -1
  117. package/dist/assets/components/empty-state/empty-state.stories.ts +1 -1
  118. package/dist/assets/components/empty-state/empty-state.types.ts +1 -1
  119. package/dist/assets/components/file-uploader/components/file-uploader-file-card/file-uploader-file-card.stories.ts +1 -1
  120. package/dist/assets/components/file-uploader/components/file-uploader-file-card/file-uploader-file-card.types.ts +1 -1
  121. package/dist/assets/components/file-uploader/components/file-uploader-file-card/file-uploader-file-card.vue +1 -1
  122. package/dist/assets/components/file-uploader/components/file-uploader-restrictions/file-uploader-restrictions.types.ts +1 -1
  123. package/dist/assets/components/file-uploader/components/file-uploader-restrictions/file-uploader-restrictions.vue +1 -1
  124. package/dist/assets/components/file-uploader/file-uploader.internal.types.ts +1 -0
  125. package/dist/assets/components/file-uploader/file-uploader.stories.ts +1 -1
  126. package/dist/assets/components/file-uploader/file-uploader.types.ts +1 -1
  127. package/dist/assets/components/file-uploader/file-uploader.vue +1 -1
  128. package/dist/assets/components/filter-bar/components/all-filters-modal/all-filters-modal.stories.ts +1 -1
  129. package/dist/assets/components/filter-bar/components/all-filters-modal/all-filters-modal.types.ts +1 -1
  130. package/dist/assets/components/filter-bar/components/all-filters-modal/all-filters-modal.vue +1 -1
  131. package/dist/assets/components/filter-bar/components/all-filters-modal/components/all-filters-modal-filter-selection/all-filters-modal-filter-selection.types.ts +1 -0
  132. package/dist/assets/components/filter-bar/components/all-filters-modal/components/all-filters-modal-filter-selection/all-filters-modal-filter-selection.vue +1 -0
  133. package/dist/assets/components/filter-bar/components/all-filters-modal/components/all-filters-modal-selected-filter/all-filters-modal-selected-filter.vue +1 -0
  134. package/dist/assets/components/filter-bar/components/base-filter/base-filter.internal.types.ts +1 -0
  135. package/dist/assets/components/filter-bar/components/base-filter/base-filter.types.ts +1 -1
  136. package/dist/assets/components/filter-bar/components/base-filter/base-filter.vue +1 -1
  137. package/dist/assets/components/filter-bar/components/boolean-filter/boolean-filter.vue +1 -1
  138. package/dist/assets/components/filter-bar/components/date-filter/date-filter.vue +1 -1
  139. package/dist/assets/components/filter-bar/components/date-range-filter/date-range-filter.vue +1 -1
  140. package/dist/assets/components/filter-bar/components/input-filter/input-filter.types.ts +1 -1
  141. package/dist/assets/components/filter-bar/components/input-filter/input-filter.vue +1 -1
  142. package/dist/assets/components/filter-bar/components/input-with-dropdown-filter/input-with-dropdown-filter.stories.ts +1 -1
  143. package/dist/assets/components/filter-bar/components/input-with-dropdown-filter/input-with-dropdown-filter.vue +1 -1
  144. package/dist/assets/components/filter-bar/components/select-filter/components/select-filter-button/select-filter-button.vue +1 -1
  145. package/dist/assets/components/filter-bar/components/select-filter/select-filter.vue +1 -1
  146. package/dist/assets/components/filter-bar/components/select-filter/variants/select-avatar-filter/select-avatar-filter.stories.ts +1 -1
  147. package/dist/assets/components/filter-bar/components/select-filter/variants/select-country-filter/select-country-filter.stories.ts +1 -1
  148. package/dist/assets/components/filter-bar/components/select-filter/variants/select-country-filter/select-country-filter.vue +1 -1
  149. package/dist/assets/components/filter-bar/components/toggle-filter/toggle-filter.vue +1 -1
  150. package/dist/assets/components/filter-bar/filter-bar.docs.mdx +4 -62
  151. package/dist/assets/components/filter-bar/filter-bar.stories.ts +1 -1
  152. package/dist/assets/components/filter-bar/filter-bar.types.ts +1 -1
  153. package/dist/assets/components/filter-bar/filter-bar.vue +1 -1
  154. package/dist/assets/components/form-layout/form-layout.docs.mdx +0 -23
  155. package/dist/assets/components/form-layout/form-layout.stories.ts +1 -1
  156. package/dist/assets/components/header/header.stories.ts +1 -1
  157. package/dist/assets/components/header/header.types.ts +1 -1
  158. package/dist/assets/components/header/header.vue +1 -1
  159. package/dist/assets/components/header-with-views/components/header-with-views-all-views/header-with-views-all-views.vue +1 -1
  160. package/dist/assets/components/header-with-views/components/header-with-views-item-edit-form/header-with-views-edit-form.types.ts +1 -1
  161. package/dist/assets/components/header-with-views/components/header-with-views-item-edit-form/header-with-views-edit-form.vue +1 -1
  162. package/dist/assets/components/header-with-views/header-with-views.internal.types.ts +1 -0
  163. package/dist/assets/components/header-with-views/header-with-views.stories.ts +1 -1
  164. package/dist/assets/components/header-with-views/header-with-views.types.ts +1 -1
  165. package/dist/assets/components/header-with-views/header-with-views.vue +1 -1
  166. package/dist/assets/components/image/image.stories.ts +1 -1
  167. package/dist/assets/components/info-icon/info-icon.stories.ts +1 -1
  168. package/dist/assets/components/input-field/input-field.docs.mdx +1 -13
  169. package/dist/assets/components/input-field/input-field.stories.ts +1 -1
  170. package/dist/assets/components/input-field/input-field.types.ts +1 -1
  171. package/dist/assets/components/input-field/input-field.vue +1 -1
  172. package/dist/assets/components/input-field-password/input-field-password.internal.types.ts +1 -0
  173. package/dist/assets/components/input-field-password/input-field-password.stories.ts +1 -1
  174. package/dist/assets/components/input-field-password/input-field-password.types.ts +1 -1
  175. package/dist/assets/components/input-field-password/input-field-password.vue +1 -1
  176. package/dist/assets/components/input-field-phone-number/input-field-phone-number.stories.ts +1 -1
  177. package/dist/assets/components/input-field-phone-number/input-field-phone-number.types.ts +1 -1
  178. package/dist/assets/components/input-field-phone-number/input-field-phone-number.vue +1 -1
  179. package/dist/assets/components/input-time/input-time.docs.mdx +17 -0
  180. package/dist/assets/components/input-time/input-time.stories.ts +1 -1
  181. package/dist/assets/components/input-time/input-time.types.ts +1 -1
  182. package/dist/assets/components/input-time/input-time.vue +1 -1
  183. package/dist/assets/components/inspector/inspector.stories.ts +1 -1
  184. package/dist/assets/components/inspector/inspector.vue +1 -1
  185. package/dist/assets/components/layout/components/layout-row/layout-row.stories.ts +1 -1
  186. package/dist/assets/components/layout/components/layout-row/layout-row.vue +1 -1
  187. package/dist/assets/components/layout/layout.docs.mdx +10 -0
  188. package/dist/assets/components/layout/layout.internal.types.ts +1 -0
  189. package/dist/assets/components/layout/layout.stories.ts +1 -1
  190. package/dist/assets/components/layout/layout.types.ts +1 -1
  191. package/dist/assets/components/link/link.stories.ts +1 -1
  192. package/dist/assets/components/link/link.types.ts +1 -1
  193. package/dist/assets/components/link/link.vue +1 -1
  194. package/dist/assets/components/list/list.stories.ts +1 -1
  195. package/dist/assets/components/loading-indicator/loading-indicator.stories.ts +1 -1
  196. package/dist/assets/components/menu/components/menu-item/menu-item.types.ts +1 -1
  197. package/dist/assets/components/menu/components/menu-item/menu-item.vue +1 -1
  198. package/dist/assets/components/menu/components/menu-item-list/menu-item-list.vue +1 -1
  199. package/dist/assets/components/menu/components/sub-menu-item-list/sub-menu-item-list.vue +1 -1
  200. package/dist/assets/components/menu/menu.docs.mdx +9 -0
  201. package/dist/assets/components/menu/menu.stories.ts +1 -1
  202. package/dist/assets/components/menu/menu.types.ts +1 -1
  203. package/dist/assets/components/menu/menu.vue +1 -1
  204. package/dist/assets/components/modal/components/base-modal/base-modal.vue +1 -1
  205. package/dist/assets/components/modal/components/modal-page/modal-page.vue +1 -1
  206. package/dist/assets/components/modal/modal.internal.types.ts +1 -0
  207. package/dist/assets/components/modal/modal.stories.ts +1 -1
  208. package/dist/assets/components/modal/modal.types.ts +1 -1
  209. package/dist/assets/components/modal/modal.vue +1 -1
  210. package/dist/assets/components/modal-fullscreen/components/modal-fullscreen-page.vue +1 -1
  211. package/dist/assets/components/modal-fullscreen/modal-fullscreen.stories.ts +1 -1
  212. package/dist/assets/components/modal-fullscreen/modal-fullscreen.vue +1 -1
  213. package/dist/assets/components/navigation-menu/components/navigation-menu-group/navigation-menu-group.internal.types.ts +1 -0
  214. package/dist/assets/components/navigation-menu/components/navigation-menu-group/navigation-menu-group.types.ts +1 -1
  215. package/dist/assets/components/navigation-menu/components/navigation-menu-group/navigation-menu-group.vue +1 -1
  216. package/dist/assets/components/navigation-menu/components/navigation-menu-item/navigation-menu-item.vue +1 -1
  217. package/dist/assets/components/navigation-menu/navigation-menu.internal.types.ts +1 -0
  218. package/dist/assets/components/navigation-menu/navigation-menu.stories.ts +1 -1
  219. package/dist/assets/components/navigation-menu/navigation-menu.types.ts +1 -1
  220. package/dist/assets/components/navigation-menu/navigation-menu.vue +1 -1
  221. package/dist/assets/components/pagination/components/pagination-context/pagination-context.vue +1 -1
  222. package/dist/assets/components/pagination/components/pagination-results-per-page/pagination-results-per-page.vue +1 -1
  223. package/dist/assets/components/pagination/pagination.stories.ts +1 -1
  224. package/dist/assets/components/pagination/pagination.types.ts +1 -1
  225. package/dist/assets/components/pagination/pagination.vue +1 -1
  226. package/dist/assets/components/payment-method/payment-method.stories.ts +1 -1
  227. package/dist/assets/components/payment-method/payment-method.types.ts +1 -1
  228. package/dist/assets/components/popover/popover.docs.mdx +32 -9
  229. package/dist/assets/components/popover/popover.stories.ts +1 -1
  230. package/dist/assets/components/popover/popover.types.ts +1 -1
  231. package/dist/assets/components/popover/popover.vue +1 -1
  232. package/dist/assets/components/popper-container/popper-container.types.ts +1 -1
  233. package/dist/assets/components/popper-container/popper-container.vue +1 -1
  234. package/dist/assets/components/progress-bar/progress-bar.stories.ts +1 -1
  235. package/dist/assets/components/progress-bar/progress-bar.vue +1 -1
  236. package/dist/assets/components/promo-banner/promo-banner.stories.ts +1 -1
  237. package/dist/assets/components/promo-banner/promo-banner.types.ts +1 -1
  238. package/dist/assets/components/promo-banner/promo-banner.vue +1 -1
  239. package/dist/assets/components/radio-group/components/radio-button/radio-button.vue +1 -1
  240. package/dist/assets/components/radio-group/radio-group.docs.mdx +1 -6
  241. package/dist/assets/components/radio-group/radio-group.stories.ts +1 -1
  242. package/dist/assets/components/radio-group/radio-group.types.ts +1 -1
  243. package/dist/assets/components/radio-group/radio-group.vue +1 -1
  244. package/dist/assets/components/rich-text-editor/rich-text-editor.internal.types.ts +1 -0
  245. package/dist/assets/components/rich-text-editor/rich-text-editor.stories.ts +1 -1
  246. package/dist/assets/components/rich-text-editor/rich-text-editor.types.ts +1 -1
  247. package/dist/assets/components/rich-text-editor/rich-text-editor.vue +1 -1
  248. package/dist/assets/components/search-bar/search-bar.stories.ts +1 -1
  249. package/dist/assets/components/search-bar/search-bar.vue +1 -1
  250. package/dist/assets/components/secondary-nav/components/secondary-nav-item/secondary-nav-item.vue +1 -1
  251. package/dist/assets/components/secondary-nav/secondary-nav.stories.ts +1 -1
  252. package/dist/assets/components/secondary-nav/secondary-nav.vue +1 -1
  253. package/dist/assets/components/segmented-control/components/segmented-control-button/segmented-control-button.vue +1 -1
  254. package/dist/assets/components/segmented-control/segmented-control.stories.ts +1 -1
  255. package/dist/assets/components/segmented-control/segmented-control.types.ts +1 -1
  256. package/dist/assets/components/segmented-control/segmented-control.vue +1 -1
  257. package/dist/assets/components/selection-card/components/selection-card-group/selection-card-group.docs.mdx +12 -13
  258. package/dist/assets/components/selection-card/components/selection-card-group/selection-card-group.stories.ts +1 -1
  259. package/dist/assets/components/selection-card/components/selection-card-group/selection-card-group.vue +1 -1
  260. package/dist/assets/components/selection-card/selection-card.stories.ts +1 -1
  261. package/dist/assets/components/selection-card/selection-card.types.ts +1 -1
  262. package/dist/assets/components/selection-card/selection-card.vue +1 -1
  263. package/dist/assets/components/sidepanel/sidepanel.docs.mdx +1 -1
  264. package/dist/assets/components/sidepanel/sidepanel.stories.ts +1 -1
  265. package/dist/assets/components/sidepanel/sidepanel.vue +1 -1
  266. package/dist/assets/components/status/status.stories.ts +1 -1
  267. package/dist/assets/components/status/status.types.ts +1 -1
  268. package/dist/assets/components/stepper/components/step/step.vue +1 -1
  269. package/dist/assets/components/stepper/stepper.stories.ts +1 -1
  270. package/dist/assets/components/stepper/stepper.vue +1 -1
  271. package/dist/assets/components/structured-list/structured-list.stories.ts +1 -1
  272. package/dist/assets/components/structured-list/structured-list.vue +1 -1
  273. package/dist/assets/components/summary-grid/components/summary-grid-item/summary-grid-item.types.ts +1 -1
  274. package/dist/assets/components/summary-grid/summary-grid.stories.ts +1 -1
  275. package/dist/assets/components/summary-grid/summary-grid.types.ts +1 -1
  276. package/dist/assets/components/table-of-contents/components/table-of-contents-list/table-of-contents-list.vue +1 -1
  277. package/dist/assets/components/table-of-contents/table-of-contents.internal.types.ts +1 -0
  278. package/dist/assets/components/table-of-contents/table-of-contents.stories.ts +1 -1
  279. package/dist/assets/components/table-of-contents/table-of-contents.types.ts +1 -1
  280. package/dist/assets/components/table-of-contents/table-of-contents.vue +1 -1
  281. package/dist/assets/components/tabs/components/tab.internal.types.ts +1 -0
  282. package/dist/assets/components/tabs/components/tab.types.ts +1 -1
  283. package/dist/assets/components/tabs/components/tab.vue +1 -1
  284. package/dist/assets/components/tabs/tabs.stories.ts +1 -1
  285. package/dist/assets/components/tabs/tabs.vue +1 -1
  286. package/dist/assets/components/tag/tag.docs.mdx +1 -1
  287. package/dist/assets/components/tag/tag.stories.ts +1 -1
  288. package/dist/assets/components/tag/tag.types.ts +1 -1
  289. package/dist/assets/components/tag/tag.vue +1 -1
  290. package/dist/assets/components/textarea/textarea.docs.mdx +0 -7
  291. package/dist/assets/components/textarea/textarea.stories.ts +1 -1
  292. package/dist/assets/components/textarea/textarea.types.ts +1 -1
  293. package/dist/assets/components/textarea/textarea.vue +1 -1
  294. package/dist/assets/components/timeline/components/timeline-item/timeline-item.stories.ts +1 -1
  295. package/dist/assets/components/timeline/components/timeline-item/timeline-item.vue +1 -1
  296. package/dist/assets/components/timeline/timeline.docs.mdx +0 -10
  297. package/dist/assets/components/timeline/timeline.internal.types.ts +1 -0
  298. package/dist/assets/components/timeline/timeline.stories.ts +1 -1
  299. package/dist/assets/components/timeline/timeline.types.ts +1 -1
  300. package/dist/assets/components/timeline/timeline.vue +1 -1
  301. package/dist/assets/components/toast/components/toast-item/toast-item.vue +1 -1
  302. package/dist/assets/components/toggle/toggle.stories.ts +1 -1
  303. package/dist/assets/components/toggle/toggle.types.ts +1 -1
  304. package/dist/assets/components/toggle/toggle.vue +1 -1
  305. package/dist/assets/components/tutorial/tutorial.stories.ts +1 -1
  306. package/dist/assets/components/typography/typography.stories.ts +1 -1
  307. package/dist/assets/components/typography/typography.types.ts +1 -1
  308. package/dist/assets/components/typography/typography.vue +1 -1
  309. package/dist/assets/composables/use-bento-base-filter/index.ts +1 -0
  310. package/dist/assets/composables/use-bento-base-filter/use-bento-base-filter.ts +1 -0
  311. package/dist/assets/composables/use-bento-base-filter/utils/index.ts +1 -0
  312. package/dist/assets/composables/use-bento-base-filter/utils/utils.ts +1 -0
  313. package/dist/assets/composables/use-bento-currency/index.ts +1 -0
  314. package/dist/assets/composables/use-bento-currency/use-bento-currency.stories.ts +1 -1
  315. package/dist/assets/composables/use-bento-currency/use-bento-currency.ts +1 -0
  316. package/dist/assets/composables/use-bento-delayed-hover/index.ts +1 -0
  317. package/dist/assets/composables/use-bento-delayed-hover/use-bento-delayed-hover.stories.ts +1 -1
  318. package/dist/assets/composables/use-bento-delayed-hover/use-bento-delayed-hover.ts +1 -0
  319. package/dist/assets/composables/use-bento-theme/index.ts +1 -0
  320. package/dist/assets/composables/use-bento-theme/install.ts +1 -1
  321. package/dist/assets/composables/use-bento-theme/install.types.ts +1 -1
  322. package/dist/assets/composables/use-bento-theme/use-bento-theme.ts +1 -0
  323. package/dist/assets/composables/use-bento-toast-controller/index.ts +1 -0
  324. package/dist/assets/composables/use-bento-toast-controller/toast-stack.ts +1 -0
  325. package/dist/assets/composables/use-bento-toast-controller/use-bento-toast-controller.ts +1 -0
  326. package/dist/assets/composables/use-click-outside/index.ts +1 -0
  327. package/dist/assets/composables/use-click-outside/use-click-outside.stories.ts +1 -1
  328. package/dist/assets/composables/use-click-outside/use-click-outside.ts +1 -0
  329. package/dist/assets/composables/use-form-validate/index.ts +1 -0
  330. package/dist/assets/composables/use-form-validate/use-form.ts +1 -0
  331. package/dist/assets/composables/use-form-validate/use-form.types.ts +1 -1
  332. package/dist/assets/composables.json +9 -0
  333. package/dist/assets/deprecations.md +132 -236
  334. package/dist/assets/directives/highlight/highlight.stories.ts +1 -1
  335. package/dist/assets/directives/keyboard-navigation/keyboard-navigation.stories.ts +1 -1
  336. package/dist/assets/directives/tooltip/tooltip.internal.types.ts +1 -0
  337. package/dist/assets/directives/tooltip/tooltip.stories.ts +1 -1
  338. package/dist/assets/directives/tooltip/tooltip.types.ts +1 -1
  339. package/dist/assets/icons.json +1 -0
  340. package/dist/assets/install.ts +1 -1
  341. package/dist/assets/tailwind/README.md +3 -3
  342. package/dist/assets/templates/dashboard-template/components/dashboard-section/dashboard-section.stories.ts +1 -1
  343. package/dist/assets/templates/dashboard-template/dashboard-template.stories.ts +1 -1
  344. package/dist/assets/templates/data-grid-template/data-grid-template.stories.ts +1 -1
  345. package/dist/assets/usage.json +77 -77
  346. package/dist/assets/variables.css +113 -101
  347. package/dist/main.js +228 -34
  348. package/package.json +2 -2
  349. package/dist/assets/components/internal/base-button/base-button.types.ts +0 -1
  350. package/dist/assets/components/internal/base-button/base-button.vue +0 -1
  351. package/dist/assets/components/internal/button-actions-with-menu/button-actions-with-menu.stories.ts +0 -1
  352. package/dist/assets/components/internal/button-actions-with-menu/button-actions-with-menu.types.ts +0 -1
  353. package/dist/assets/components/internal/button-actions-with-menu/button-actions-with-menu.vue +0 -1
  354. package/dist/assets/components/internal/calendar/calendar.stories.ts +0 -1
  355. package/dist/assets/components/internal/calendar/calendar.types.ts +0 -1
  356. package/dist/assets/components/internal/calendar/calendar.vue +0 -1
  357. package/dist/assets/components/internal/calendar/components/calendar-month/calendar-month.stories.ts +0 -1
  358. package/dist/assets/components/internal/calendar/components/calendar-month/calendar-month.types.ts +0 -1
  359. package/dist/assets/components/internal/calendar/components/calendar-month/calendar-month.vue +0 -1
  360. package/dist/assets/components/internal/calendar/components/calendar-month/components/calendar-month-day/__test__/calendar-month-day-stub.vue +0 -1
  361. package/dist/assets/components/internal/calendar/components/calendar-month/components/calendar-month-day/calendar-month-day.stories.ts +0 -1
  362. package/dist/assets/components/internal/calendar/components/calendar-month/components/calendar-month-day/calendar-month-day.types.ts +0 -1
  363. package/dist/assets/components/internal/calendar/components/calendar-month/components/calendar-month-day/calendar-month-day.vue +0 -1
  364. package/dist/assets/components/internal/calendar/components/calendar-month/composables/granular-min-max-date.types.ts +0 -1
  365. package/dist/assets/components/internal/calendar/components/calendar-year/calendar-year.types.ts +0 -1
  366. package/dist/assets/components/internal/calendar/components/calendar-year/calendar-year.vue +0 -1
  367. package/dist/assets/components/internal/calendar/components/calendar-year/components/calendar-year-month/__test__/calendar-year-month-stub.vue +0 -1
  368. package/dist/assets/components/internal/calendar/components/calendar-year/components/calendar-year-month/calendar-year-month.types.ts +0 -1
  369. package/dist/assets/components/internal/calendar/components/calendar-year/components/calendar-year-month/calendar-year-month.vue +0 -1
  370. package/dist/assets/components/internal/calendar/components/calendar-year-selector/calendar-year-selector.types.ts +0 -1
  371. package/dist/assets/components/internal/calendar/components/calendar-year-selector/calendar-year-selector.vue +0 -1
  372. package/dist/assets/components/internal/calendar/composables/use-granularity-adjustments.types.ts +0 -1
  373. package/dist/assets/components/internal/checkbox-input/checkbox-input.stories.ts +0 -1
  374. package/dist/assets/components/internal/checkbox-input/checkbox-input.types.ts +0 -1
  375. package/dist/assets/components/internal/checkbox-input/checkbox-input.vue +0 -1
  376. package/dist/assets/components/internal/controls-group/controls-group.vue +0 -1
  377. package/dist/assets/components/internal/copy/copy.docs.mdx +0 -43
  378. package/dist/assets/components/internal/copy/copy.stories.ts +0 -1
  379. package/dist/assets/components/internal/copy/copy.types.ts +0 -1
  380. package/dist/assets/components/internal/copy/copy.vue +0 -1
  381. package/dist/assets/components/internal/decorative-checkbox/decorative-checkbox.vue +0 -1
  382. package/dist/assets/components/internal/dialog-page/dialog-page.types.ts +0 -1
  383. package/dist/assets/components/internal/dialog-page/dialog-page.vue +0 -1
  384. package/dist/assets/components/internal/error-message/error-message.vue +0 -1
  385. package/dist/assets/components/internal/field-label/field-label.vue +0 -1
  386. package/dist/assets/components/internal/fixed-scroller/fixed-scroller.docs.mdx +0 -21
  387. package/dist/assets/components/internal/fixed-scroller/fixed-scroller.stories.ts +0 -1
  388. package/dist/assets/components/internal/fixed-scroller/fixed-scroller.vue +0 -1
  389. package/dist/assets/components/internal/footer-actions/footer-actions.vue +0 -1
  390. package/dist/assets/components/internal/grid-layout/grid-layout.types.ts +0 -1
  391. package/dist/assets/components/internal/grid-layout/grid-layout.vue +0 -1
  392. package/dist/assets/components/internal/header-meta/header-meta.types.ts +0 -1
  393. package/dist/assets/components/internal/header-meta/header-meta.vue +0 -1
  394. package/dist/assets/components/internal/info-icon-with-popover/info-icon-with-popover.vue +0 -1
  395. package/dist/assets/components/internal/listbox/components/listbox-lazy-load/listbox-lazy-load.types.ts +0 -1
  396. package/dist/assets/components/internal/listbox/components/listbox-lazy-load/listbox-lazy-load.vue +0 -1
  397. package/dist/assets/components/internal/listbox/components/listbox-multi-select/components/listbox-multi-select-options/components/listbox-multi-select-category/listbox-multi-select-category.types.ts +0 -1
  398. package/dist/assets/components/internal/listbox/components/listbox-multi-select/components/listbox-multi-select-options/components/listbox-multi-select-category/listbox-multi-select-category.vue +0 -1
  399. package/dist/assets/components/internal/listbox/components/listbox-multi-select/components/listbox-multi-select-options/components/listbox-multi-select-nested-lazy-load/listbox-multi-select-nested-lazy-load.types.ts +0 -1
  400. package/dist/assets/components/internal/listbox/components/listbox-multi-select/components/listbox-multi-select-options/components/listbox-multi-select-nested-lazy-load/listbox-multi-select-nested-lazy-load.vue +0 -1
  401. package/dist/assets/components/internal/listbox/components/listbox-multi-select/components/listbox-multi-select-options/listbox-multi-select-options.types.ts +0 -1
  402. package/dist/assets/components/internal/listbox/components/listbox-multi-select/components/listbox-multi-select-options/listbox-multi-select-options.vue +0 -1
  403. package/dist/assets/components/internal/listbox/components/listbox-multi-select/listbox-multi-select.types.ts +0 -1
  404. package/dist/assets/components/internal/listbox/components/listbox-multi-select/listbox-multi-select.vue +0 -1
  405. package/dist/assets/components/internal/listbox/components/listbox-option/listbox-option.vue +0 -1
  406. package/dist/assets/components/internal/listbox/components/listbox-option-tag/listbox-option-tag.vue +0 -1
  407. package/dist/assets/components/internal/listbox/components/listbox-single-select/components/listbox-single-select-category/listbox-single-select-category.vue +0 -1
  408. package/dist/assets/components/internal/listbox/components/listbox-single-select/listbox-single-select.vue +0 -1
  409. package/dist/assets/components/internal/listbox/components/listbox-single-select-option/listbox-single-select-option.vue +0 -1
  410. package/dist/assets/components/internal/listbox/listbox.stories.ts +0 -1
  411. package/dist/assets/components/internal/listbox/listbox.types.ts +0 -1
  412. package/dist/assets/components/internal/listbox/listbox.vue +0 -1
  413. package/dist/assets/components/internal/radio-input/radio-input.stories.ts +0 -1
  414. package/dist/assets/components/internal/radio-input/radio-input.types.ts +0 -1
  415. package/dist/assets/components/internal/radio-input/radio-input.vue +0 -1
  416. package/dist/assets/components/internal/teleport/teleport.types.ts +0 -1
  417. package/dist/assets/components/internal/teleport/teleport.vue +0 -1
  418. package/dist/assets/components/internal/tooltip/tooltip.test.vue +0 -1
  419. package/dist/assets/components/internal/tooltip/tooltip.types.ts +0 -1
  420. package/dist/assets/components/internal/tooltip/tooltip.vue +0 -1
  421. package/dist/assets/composables/use-form-validate-zod-4/use-form.types.ts +0 -1
  422. package/dist/assets/composables/use-numeric-input/use-numeric-input.types.ts +0 -1
  423. package/dist/assets/composables/use-section-navigation-keyboard/use-section-navigation-keyboard.types.ts +0 -1
  424. package/dist/assets/composables/use-virtual-list/use-virtual-list.types.ts +0 -1
@@ -1 +1 @@
1
- <template> <bento-typography el="div" class="b-layout-row" :class="conditionalRowClass"> <div v-if="hasSlot('default')"> <slot /> </div> <div v-else-if="hasSlot('col-1') && hasSlot('col-2')" ref="row" :class="conditionalRowWithColumnsClass"> <div ref="col1" :class="conditionalColumnClass.col1"> <slot name="col-1"></slot> </div> <div ref="col2" :class="conditionalColumnClass.col2"> <slot name="col-2"></slot> </div> </div> </bento-typography> </template> <script setup lang="ts"> import { computed, inject, nextTick, onMounted, ref, type Ref, useSlots, watch } from 'vue'; import { BentoTypography } from '@/components/typography'; import { useHasSlot } from '@/composables'; import { BentoLayoutRowColumnLayoutOptions, type BentoLayoutRowProps, type BentoLayoutSize, } from '../../layout.types'; import { useMediaQuery } from '@vueuse/core'; import { LAYOUT_SIZE_INJECTION_KEY } from '@/components/layout/layout.keys'; const props = withDefaults(defineProps<BentoLayoutRowProps>(), { columnsLayout: '6-6', }); const slots = useSlots(); const hasSlot = useHasSlot(slots); const size = inject<Ref<BentoLayoutSize>>(LAYOUT_SIZE_INJECTION_KEY, ref('full-width')); const row = ref<HTMLElement>(null); const col1 = ref<HTMLElement>(null); const col2 = ref<HTMLElement>(null); const isFullWidth = computed(() => size.value === 'full-width'); const isValidColumnLayout = computed(() => BentoLayoutRowColumnLayoutOptions.includes(props.columnsLayout)); const conditionalRowClass = computed(() => `b-layout-row--${size.value}`); const conditionalRowWithColumnsClass = computed(() => ({ 'b-layout-row__column-wrapper': isFullWidth.value, })); const conditionalColumnClass = computed(() => { const columns = isValidColumnLayout.value ? props.columnsLayout.split('-') : [6, 6]; const getColumnModifier = index => (isFullWidth.value ? columns[index] : size.value); return { col1: `b-layout-row__column--${getColumnModifier(0)}`, col2: `b-layout-row__column--${getColumnModifier(1)}`, }; }); watch( useMediaQuery('(min-width: 768px)'), async isLargeScreen => { await nextTick(); const areColumnsInTheRightOrder = row.value?.firstChild === col1.value; if (isLargeScreen && !areColumnsInTheRightOrder) { row.value?.prepend(col1.value); } else if (!isLargeScreen && (props.columnsLayout === '3-9' || props.columnsLayout === '4-8')) { row.value?.prepend(col2.value); } }, { immediate: true } ); onMounted(() => { if (!isFullWidth.value && (hasSlot('col-1') || hasSlot('col-2'))) { throw new Error( 'Column slots can only be used with the `full-width` size. Please check documentation for more info.' ); } if (!isValidColumnLayout.value) { throw new Error( `${props.columnsLayout} is not a valid column layout. Please refer to the documentation for the list of available column layouts.` ); } }); </script> <script lang="ts"> /** * Page row is subcomponent of Layout meant to be used to generate the row layout. * * @example * import { BentoLayout, BentoLayoutRow } from '@adyen/bento-vue2'; * * export default { * components: { BentoLayout, BentoLayoutRow }, * template: ` * <bento-layout> * <bento-layout-row columns-layout="3-9"> * <template #col-1>Narrow 3-9</template> * <template #col-2>Wide 3-9</template> * </bento-layout-row> * <bento-layout-row> * <template #col-1>6-6</template> * <template #col-2>6-6</template> * </bento-layout-row> * </bento-layout> * ` * } */ export default {}; </script> <style lang="scss" scoped src="./layout-row.scss" />
1
+ <template> <bento-typography el="div" class="b-layout-row" :class="conditionalRowClass"> <div v-if="hasSlot('default')" class="b-layout-row__wrapper"> <slot /> </div> <div v-else-if="hasSlot('col-1') && hasSlot('col-2')" ref="row" :class="conditionalRowWithColumnsClass"> <div ref="col1" :class="conditionalColumnClass.col1"> <slot name="col-1"></slot> </div> <div ref="col2" :class="conditionalColumnClass.col2"> <slot name="col-2"></slot> </div> </div> </bento-typography> </template> <script setup lang="ts"> import { computed, inject, nextTick, onMounted, ref, type Ref, useSlots, watch } from 'vue'; import { BentoTypography } from '@/components/typography'; import { useHasSlot } from '@/composables'; import { BentoLayoutRowColumnLayoutOptions } from '../../layout.internal.types'; import { type BentoLayoutRowProps, type BentoLayoutSize } from '../../layout.types'; import { useMediaQuery } from '@vueuse/core'; import { LAYOUT_SIZE_INJECTION_KEY } from '@/components/layout/layout.keys'; const props = withDefaults(defineProps<BentoLayoutRowProps>(), { columnsLayout: '6-6', fullHeight: false, }); const slots = useSlots(); const hasSlot = useHasSlot(slots); const size = inject<Ref<BentoLayoutSize>>(LAYOUT_SIZE_INJECTION_KEY, ref('full-width')); const row = ref<HTMLElement>(null); const col1 = ref<HTMLElement>(null); const col2 = ref<HTMLElement>(null); const isFullWidth = computed(() => size.value === 'full-width'); const isValidColumnLayout = computed(() => BentoLayoutRowColumnLayoutOptions.includes(props.columnsLayout)); const conditionalRowClass = computed(() => ({ [`b-layout-row--${size.value}`]: true, 'b-layout-row--full-height': props.fullHeight, })); const conditionalRowWithColumnsClass = computed(() => ({ 'b-layout-row__column-wrapper': isFullWidth.value, })); const conditionalColumnClass = computed(() => { const columns = isValidColumnLayout.value ? props.columnsLayout.split('-') : [6, 6]; const getColumnModifier = index => (isFullWidth.value ? columns[index] : size.value); return { col1: `b-layout-row__column--${getColumnModifier(0)}`, col2: `b-layout-row__column--${getColumnModifier(1)}`, }; }); watch( useMediaQuery('(min-width: 768px)'), async isLargeScreen => { await nextTick(); const areColumnsInTheRightOrder = row.value?.firstChild === col1.value; if (isLargeScreen && !areColumnsInTheRightOrder) { row.value?.prepend(col1.value); } else if (!isLargeScreen && (props.columnsLayout === '3-9' || props.columnsLayout === '4-8')) { row.value?.prepend(col2.value); } }, { immediate: true } ); onMounted(() => { if (!isFullWidth.value && (hasSlot('col-1') || hasSlot('col-2'))) { throw new Error( 'Column slots can only be used with the `full-width` size. Please check documentation for more info.' ); } if (!isValidColumnLayout.value) { throw new Error( `${props.columnsLayout} is not a valid column layout. Please refer to the documentation for the list of available column layouts.` ); } }); </script> <script lang="ts"> /** * Page row is subcomponent of Layout meant to be used to generate the row layout. * * @example * import { BentoLayout, BentoLayoutRow } from '@adyen/bento-vue2'; * * export default { * components: { BentoLayout, BentoLayoutRow }, * template: ` * <bento-layout> * <bento-layout-row columns-layout="3-9"> * <template #col-1>Narrow 3-9</template> * <template #col-2>Wide 3-9</template> * </bento-layout-row> * <bento-layout-row> * <template #col-1>6-6</template> * <template #col-2>6-6</template> * </bento-layout-row> * </bento-layout> * ` * } */ export default {}; </script> <style lang="scss" scoped src="./layout-row.scss" />
@@ -1,5 +1,6 @@
1
1
  import { Canvas, Meta } from '@storybook/blocks';
2
2
  import * as Layout from './layout.stories';
3
+ import * as LayoutRow from './components/layout-row/layout-row.stories';
3
4
 
4
5
  <Meta of={Layout} />
5
6
 
@@ -77,6 +78,15 @@ As mentioned above, there are five layouts available: `6-6`, `3-9`, `9-3`, `4-8`
77
78
  Breaking up the content into two columns only works when using the `full-width` size. If you're using the `narrow` or
78
79
  `half-width` size, or passing the content in the `default` slot, the content will always span 12 columns.
79
80
 
81
+ ## Full height
82
+
83
+ Use the `full-height` prop on `bento-layout-row` when the row should fill the remaining height of a parent layout. The
84
+ parent layout needs an explicit height and a grid row that can grow, such as `grid-template-rows: auto minmax(0, 1fr)`.
85
+
86
+ This is useful for content such as a Data Grid with `fit-content` and `sticky-pagination`.
87
+
88
+ <Canvas of={LayoutRow.FullHeight} />
89
+
80
90
  ## Nested
81
91
 
82
92
  It is possible to nest rows within rows and even layout inside of a row, but this should be avoided as much as possible,
@@ -0,0 +1 @@
1
+ export const BentoLayoutVariantOptions = ['page', 'component'] as const; export const BentoLayoutSizeOptions = ['full-width', 'narrow', 'half-width'] as const; export const BentoLayoutRowColumnLayoutOptions = ['3-9', '9-3', '4-8', '8-4', '6-6'] as const;
@@ -1 +1 @@
1
- import { computed } from 'vue'; import { isVue2 } from 'vue-demi'; import { storybookDocsParameter } from '@/utils/ts/storybook'; import type { Meta, StoryObj } from '@storybook/vue'; import { BentoLayout, BentoLayoutRow } from '@/components/layout'; import { BentoLayoutSizeOptions, BentoLayoutVariantOptions } from '@/components/layout/layout.types'; import './layout.stories.scss?module'; const meta: Meta = { title: 'Layout', component: BentoLayout, argTypes: { variant: { options: BentoLayoutVariantOptions, control: { type: 'select', }, }, size: { options: BentoLayoutSizeOptions, control: { type: 'select', }, }, }, parameters: { layout: 'fullscreen', }, }; export default meta; type Story = StoryObj<typeof BentoLayout>; const defaultCode = ` <template> <bento-layout size="half-width"> <bento-layout-row>Row 1</bento-layout-row> <bento-layout-row>Row 2</bento-layout-row> <bento-layout-row>Row 3</bento-layout-row> </bento-layout> </template> <script setup lang="ts"></script> `; export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoLayout, BentoLayoutRow, }, props: Object.keys(argTypes), template: ` <bento-layout v-bind="args"> <bento-layout-row class="b-layout-story__row"> <div>Page header</div> <div>{{ numberCols}} cols</div> </bento-layout-row> <bento-layout-row class="b-layout-story__row b-layout-story__row--content"> <div>Content</div> <div>{{ numberCols}} cols</div> </bento-layout-row> </bento-layout> `, setup(props) { const args = isVue2 ? props : _args; const colsMap = Object.freeze({ ['half-width']: 6, ['narrow']: 8, ['full-width']: 12, }); const numberCols = computed(() => colsMap[args.size]); return { args, numberCols, }; }, }), args: { size: 'half-width' }, parameters: storybookDocsParameter(defaultCode), }; const withColumnsCode = ` <template> <bento-layout size="full-width"> <bento-layout-row columns-layout="3-9"> <template #col-1>Narrow 3-9</template> <template #col-2>Wide 3-9</template> </bento-layout-row> <bento-layout-row columns-layout="4-8"> <template #col-1>Narrow 4-8</template> <template #col-2>Wide 4-8</template> </bento-layout-row> <bento-layout-row columns-layout="9-3"> <template #col-1>Wide 9-3</template> <template #col-2>Narrow 9-3</template> </bento-layout-row> <bento-layout-row columns-layout="8-4"> <template #col-1>Wide 8-4</template> <template #col-2>Narrow 8-4</template> </bento-layout-row> <bento-layout-row> <template #col-1>6-6</template> <template #col-2>6-6</template> </bento-layout-row> </bento-layout> </template> `; export const WithColumns: Story = { render: (_args, { argTypes }) => ({ components: { BentoLayout, BentoLayoutRow, }, props: Object.keys(argTypes), template: ` <bento-layout v-bind="args"> <bento-layout-row class="b-layout-story__row b-layout-story__row--thin">12 cols</bento-layout-row> <bento-layout-row columns-layout="3-9" class="b-layout-story__row-with-cells"> <template #col-1><div class="b-layout-story__cell">3 cols</div></template> <template #col-2><div class="b-layout-story__cell">9 cols</div></template> </bento-layout-row> <bento-layout-row columns-layout="9-3" class="b-layout-story__row-with-cells"> <template #col-1><div class="b-layout-story__cell">9 cols</div></template> <template #col-2><div class="b-layout-story__cell">3 cols</div></template> </bento-layout-row> <bento-layout-row columns-layout="4-8" class="b-layout-story__row-with-cells"> <template #col-1><div class="b-layout-story__cell">4 cols</div></template> <template #col-2><div class="b-layout-story__cell">8 cols</div></template> </bento-layout-row> <bento-layout-row columns-layout="8-4" class="b-layout-story__row-with-cells"> <template #col-1><div class="b-layout-story__cell">8 cols</div></template> <template #col-2><div class="b-layout-story__cell">4 cols</div></template> </bento-layout-row> <bento-layout-row columns-layout="6-6" class="b-layout-story__row-with-cells"> <template #col-1><div class="b-layout-story__cell">6 cols</div></template> <template #col-2><div class="b-layout-story__cell">6 cols</div></template> </bento-layout-row> </bento-layout> `, setup(props) { const args = isVue2 ? props : _args; return { args, }; }, }), args: { size: 'full-width' }, parameters: storybookDocsParameter(withColumnsCode), }; const nestedCode = ` <template> <bento-layout variant="page" size="full-width"> <bento-layout-row>Single column row</bento-layout-row> <bento-layout-row columns-layout="3-9"> <template #col-1>Col 1</template> <template #col-2> <bento-layout-row columns-layout="4-8"> <template #col-1>Col 2.1 (nested)</template> <template #col-2>Col 2.2 (nested)</template> </bento-layout-row> </template> </bento-layout-row> <bento-layout-row columns-layout="9-3" class="b-layout-story__row-with-cells"> <template #col-1> <bento-layout-row columns-layout="8-4"> <template #col-1>Col 1.1 (nested)</template> <template #col-2>Col 1.2 (nested)</template> </bento-layout-row> </template> <template #col-2>Col 2</template> </bento-layout-row> <bento-layout-row columns-layout="3-9"> <bento-layout size="full-width" variant="component"> <bento-layout-row>Row with nested Layout</bento-layout-row> </bento-layout> </bento-layout-row> </bento-layout> </template> `; export const Nested: Story = { render: (_args, { argTypes }) => ({ components: { BentoLayout, BentoLayoutRow, }, props: Object.keys(argTypes), template: ` <bento-layout v-bind="args"> <bento-layout-row class="b-layout-story__row b-layout-story__row--thin">Single column row</bento-layout-row> <bento-layout-row columns-layout="3-9" class="b-layout-story__row-with-cells"> <template #col-1><div class="b-layout-story__cell">Col 1</div></template> <template #col-2> <bento-layout-row columns-layout="4-8"> <template #col-1><div class="b-layout-story__cell">Col 2.1 (nested)</div></template> <template #col-2><div class="b-layout-story__cell">Col 2.2 (nested)</div></template> </bento-layout-row> </template> </bento-layout-row> <bento-layout-row columns-layout="9-3" class="b-layout-story__row-with-cells"> <template #col-1> <bento-layout-row columns-layout="8-4"> <template #col-1><div class="b-layout-story__cell">Col 1.1 (nested)</div></template> <template #col-2><div class="b-layout-story__cell">Col 1.2 (nested)</div></template> </bento-layout-row> </template> <template #col-2><div class="b-layout-story__cell">Col 2</div></template> </bento-layout-row> <bento-layout-row columns-layout="3-9" class="b-layout-story__row-with-cells"> <bento-layout size="full-width" variant="component"> <bento-layout-row class="b-layout-story__cell">Row with nested Layout</bento-layout-row> </bento-layout> </bento-layout-row> </bento-layout> `, setup(props) { const args = isVue2 ? props : _args; return { args, }; }, }), args: { size: 'full-width' }, parameters: storybookDocsParameter(nestedCode), };
1
+ import { computed } from 'vue'; import { isVue2 } from 'vue-demi'; import { storybookDocsParameter } from '@/utils/ts/storybook'; import type { Meta, StoryObj } from '@storybook/vue'; import { BentoLayout, BentoLayoutRow } from '@/components/layout'; import { BentoLayoutSizeOptions, BentoLayoutVariantOptions } from '@/components/layout/layout.internal.types'; const LayoutDefaultExample = "<template>\n <bento-layout size=\"half-width\">\n <bento-layout-row>\n <div>Page header</div>\n <div>6 cols</div>\n </bento-layout-row>\n <bento-layout-row>\n <div>Content</div>\n <div>6 cols</div>\n </bento-layout-row>\n </bento-layout>\n</template>\n\n<script setup lang=\"ts\">\n import { BentoLayout, BentoLayoutRow } from '@adyen/bento-vue2';\n</script>\n"; const LayoutNestedExample = "<template>\n <bento-layout variant=\"page\" size=\"full-width\">\n <bento-layout-row>Single column row</bento-layout-row>\n <bento-layout-row columns-layout=\"3-9\">\n <template #col-1>Col 1</template>\n <template #col-2>\n <bento-layout-row columns-layout=\"4-8\">\n <template #col-1>Col 2.1 (nested)</template>\n <template #col-2>Col 2.2 (nested)</template>\n </bento-layout-row>\n </template>\n </bento-layout-row>\n <bento-layout-row columns-layout=\"9-3\">\n <template #col-1>\n <bento-layout-row columns-layout=\"8-4\">\n <template #col-1>Col 1.1 (nested)</template>\n <template #col-2>Col 1.2 (nested)</template>\n </bento-layout-row>\n </template>\n <template #col-2>Col 2</template>\n </bento-layout-row>\n <bento-layout-row columns-layout=\"3-9\">\n <bento-layout size=\"full-width\" variant=\"component\">\n <bento-layout-row>Row with nested Layout</bento-layout-row>\n </bento-layout>\n </bento-layout-row>\n </bento-layout>\n</template>\n\n<script setup lang=\"ts\">\n import { BentoLayout, BentoLayoutRow } from '@adyen/bento-vue2';\n</script>\n"; const LayoutWithColumnsExample = "<template>\n <bento-layout size=\"full-width\">\n <bento-layout-row>12 cols</bento-layout-row>\n <bento-layout-row columns-layout=\"3-9\">\n <template #col-1>3 cols</template>\n <template #col-2>9 cols</template>\n </bento-layout-row>\n <bento-layout-row columns-layout=\"9-3\">\n <template #col-1>9 cols</template>\n <template #col-2>3 cols</template>\n </bento-layout-row>\n <bento-layout-row columns-layout=\"4-8\">\n <template #col-1>4 cols</template>\n <template #col-2>8 cols</template>\n </bento-layout-row>\n <bento-layout-row columns-layout=\"8-4\">\n <template #col-1>8 cols</template>\n <template #col-2>4 cols</template>\n </bento-layout-row>\n <bento-layout-row columns-layout=\"6-6\">\n <template #col-1>6 cols</template>\n <template #col-2>6 cols</template>\n </bento-layout-row>\n </bento-layout>\n</template>\n\n<script setup lang=\"ts\">\n import { BentoLayout, BentoLayoutRow } from '@adyen/bento-vue2';\n</script>\n"; import './layout.stories.scss?module'; const meta: Meta = { title: 'Layout', component: BentoLayout, argTypes: { variant: { options: BentoLayoutVariantOptions, control: { type: 'select', }, }, size: { options: BentoLayoutSizeOptions, control: { type: 'select', }, }, }, parameters: { layout: 'fullscreen', }, }; export default meta; type Story = StoryObj<typeof BentoLayout>; export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoLayout, BentoLayoutRow, }, props: Object.keys(argTypes), template: ` <bento-layout v-bind="args"> <bento-layout-row class="b-layout-story__row"> <div>Page header</div> <div>{{ numberCols}} cols</div> </bento-layout-row> <bento-layout-row class="b-layout-story__row b-layout-story__row--content"> <div>Content</div> <div>{{ numberCols}} cols</div> </bento-layout-row> </bento-layout> `, setup(props) { const args = isVue2 ? props : _args; const colsMap = Object.freeze({ ['half-width']: 6, ['narrow']: 8, ['full-width']: 12, }); const numberCols = computed(() => colsMap[args.size]); return { args, numberCols, }; }, }), args: { size: 'half-width' }, parameters: storybookDocsParameter(LayoutDefaultExample), }; export const WithColumns: Story = { render: (_args, { argTypes }) => ({ components: { BentoLayout, BentoLayoutRow, }, props: Object.keys(argTypes), template: ` <bento-layout v-bind="args"> <bento-layout-row class="b-layout-story__row b-layout-story__row--thin">12 cols</bento-layout-row> <bento-layout-row columns-layout="3-9" class="b-layout-story__row-with-cells"> <template #col-1><div class="b-layout-story__cell">3 cols</div></template> <template #col-2><div class="b-layout-story__cell">9 cols</div></template> </bento-layout-row> <bento-layout-row columns-layout="9-3" class="b-layout-story__row-with-cells"> <template #col-1><div class="b-layout-story__cell">9 cols</div></template> <template #col-2><div class="b-layout-story__cell">3 cols</div></template> </bento-layout-row> <bento-layout-row columns-layout="4-8" class="b-layout-story__row-with-cells"> <template #col-1><div class="b-layout-story__cell">4 cols</div></template> <template #col-2><div class="b-layout-story__cell">8 cols</div></template> </bento-layout-row> <bento-layout-row columns-layout="8-4" class="b-layout-story__row-with-cells"> <template #col-1><div class="b-layout-story__cell">8 cols</div></template> <template #col-2><div class="b-layout-story__cell">4 cols</div></template> </bento-layout-row> <bento-layout-row columns-layout="6-6" class="b-layout-story__row-with-cells"> <template #col-1><div class="b-layout-story__cell">6 cols</div></template> <template #col-2><div class="b-layout-story__cell">6 cols</div></template> </bento-layout-row> </bento-layout> `, setup(props) { const args = isVue2 ? props : _args; return { args, }; }, }), args: { size: 'full-width' }, parameters: storybookDocsParameter(LayoutWithColumnsExample), }; export const Nested: Story = { render: (_args, { argTypes }) => ({ components: { BentoLayout, BentoLayoutRow, }, props: Object.keys(argTypes), template: ` <bento-layout v-bind="args"> <bento-layout-row class="b-layout-story__row b-layout-story__row--thin">Single column row</bento-layout-row> <bento-layout-row columns-layout="3-9" class="b-layout-story__row-with-cells"> <template #col-1><div class="b-layout-story__cell">Col 1</div></template> <template #col-2> <bento-layout-row columns-layout="4-8"> <template #col-1><div class="b-layout-story__cell">Col 2.1 (nested)</div></template> <template #col-2><div class="b-layout-story__cell">Col 2.2 (nested)</div></template> </bento-layout-row> </template> </bento-layout-row> <bento-layout-row columns-layout="9-3" class="b-layout-story__row-with-cells"> <template #col-1> <bento-layout-row columns-layout="8-4"> <template #col-1><div class="b-layout-story__cell">Col 1.1 (nested)</div></template> <template #col-2><div class="b-layout-story__cell">Col 1.2 (nested)</div></template> </bento-layout-row> </template> <template #col-2><div class="b-layout-story__cell">Col 2</div></template> </bento-layout-row> <bento-layout-row columns-layout="3-9" class="b-layout-story__row-with-cells"> <bento-layout size="full-width" variant="component"> <bento-layout-row class="b-layout-story__cell">Row with nested Layout</bento-layout-row> </bento-layout> </bento-layout-row> </bento-layout> `, setup(props) { const args = isVue2 ? props : _args; return { args, }; }, }), args: { size: 'full-width' }, parameters: storybookDocsParameter(LayoutNestedExample), };
@@ -1 +1 @@
1
- /** * @private */ export const BentoLayoutVariantOptions = ['page', 'component'] as const; export const BentoLayoutSizeOptions = ['full-width', 'narrow', 'half-width'] as const; export const BentoLayoutRowColumnLayoutOptions = ['3-9', '9-3', '4-8', '8-4', '6-6'] as const; // Public export type BentoLayoutVariant = (typeof BentoLayoutVariantOptions)[number]; export type BentoLayoutSize = (typeof BentoLayoutSizeOptions)[number]; export type BentoLayoutRowColumnLayout = (typeof BentoLayoutRowColumnLayoutOptions)[number]; export interface BentoLayoutProps { /** Defines if the layout is used in a page or only a section */ variant?: BentoLayoutVariant; /** Defines the size of the page */ size?: BentoLayoutSize; } export interface BentoLayoutRowProps { /** Sets the layout of the columns when multiple columns are used */ columnsLayout?: BentoLayoutRowColumnLayout; }
1
+ import type { BentoLayoutRowColumnLayoutOptions, BentoLayoutSizeOptions, BentoLayoutVariantOptions, } from './layout.internal.types'; export type BentoLayoutVariant = (typeof BentoLayoutVariantOptions)[number]; export type BentoLayoutSize = (typeof BentoLayoutSizeOptions)[number]; export type BentoLayoutRowColumnLayout = (typeof BentoLayoutRowColumnLayoutOptions)[number]; export interface BentoLayoutProps { /** Defines if the layout is used in a page or only a section */ variant?: BentoLayoutVariant; /** Defines the size of the page */ size?: BentoLayoutSize; } export interface BentoLayoutRowProps { /** Sets the layout of the columns when multiple columns are used */ columnsLayout?: BentoLayoutRowColumnLayout; /** Makes the row's slot content fill its available height */ fullHeight?: boolean; }
@@ -1 +1 @@
1
- import BentoLink from './link.vue'; import { BentoTypography } from '@/components/typography'; import { BentoLinkVariant } from './link.types'; import { storybookDocsParameter } from '@/utils/ts/storybook'; import { type Meta, type StoryObj } from '@storybook/vue'; import { isVue2 } from 'vue-demi'; const meta: Meta<typeof BentoLink> = { title: 'Link', component: BentoLink, argTypes: { // Slots default: { name: 'default', defaultValue: '', description: 'link text', table: { type: { summary: 'VNode[]' }, }, control: { type: 'text', }, }, // Router props to: { name: 'to', defaultValue: '/', description: 'Denotes the target route of the link. When clicked, the value of the `to` prop will be passed to `router.push()` internally, so the value can be either a string or a location descriptor object.', table: { type: { summary: 'string | Location' }, }, control: { type: 'text', }, }, // Custom props variant: { options: Object.values(BentoLinkVariant), mapping: BentoLinkVariant, control: { type: 'select', }, }, target: { name: 'target', defaultValue: null, control: { type: 'text', }, }, // Emits click: { name: 'click', defaultValue: '', description: 'Click event handler.', table: { type: { summary: '($event) => void' }, }, control: { disable: true }, }, // TODO: remove this when Vue fixes extending interface issue for defineProps args: { table: { disable: true } }, }, }; export default meta; type Story = StoryObj<typeof BentoLink>; const DEFFAULT_ARGS = { to: '/', }; const EXTERNAL_HREF = 'https://www.adyen.com/'; // Default export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoLink, BentoTypography }, props: Object.keys(argTypes), template: ` <div> <bento-typography> <bento-link v-bind="args"> an interactive link </bento-link> </bento-typography> </div> `, setup: props => ({ args: isVue2 ? props : _args }), }), args: DEFFAULT_ARGS, parameters: storybookDocsParameter(` <bento-typography> <bento-link to="router/path"> an interactive link </bento-link> </bento-typography> `), }; // Quiet export const Quiet: Story = { ...Default, args: { ...DEFFAULT_ARGS, variant: 'quiet', }, parameters: storybookDocsParameter(` <bento-typography> <bento-link variant="quiet" to="router/path"> an interactive link </bento-link> </bento-typography> `), }; // External export const External: Story = { ...Default, args: { ...DEFFAULT_ARGS, external: true, to: EXTERNAL_HREF, }, parameters: storybookDocsParameter(` <bento-typography> <bento-link to="${EXTERNAL_HREF}" external> an interactive link </bento-link> </bento-typography> `), }; // isNotRouting export const isNotRouting: Story = { ...Default, args: { ...DEFFAULT_ARGS, isNotRouting: true, }, parameters: storybookDocsParameter(` <bento-typography> <bento-link to="link/path" is-not-routing> an interactive link </bento-link> </bento-typography> `), };
1
+ import BentoLink from './link.vue'; import { BentoTypography } from '@/components/typography'; import { BentoLinkVariant } from './link.types'; import { storybookDocsParameter } from '@/utils/ts/storybook'; import { type Meta, type StoryObj } from '@storybook/vue'; import { isVue2 } from 'vue-demi'; const LinkDefaultExample = "<template>\n <bento-typography>\n <bento-link to=\"/\">an interactive link</bento-link>\n </bento-typography>\n</template>\n\n<script setup lang=\"ts\">\n import { BentoLink, BentoTypography } from '@adyen/bento-vue2';\n</script>\n"; const LinkExternalExample = "<template>\n <bento-typography>\n <bento-link to=\"https://www.adyen.com/\" external>an interactive link</bento-link>\n </bento-typography>\n</template>\n\n<script setup lang=\"ts\">\n import { BentoLink, BentoTypography } from '@adyen/bento-vue2';\n</script>\n"; const LinkIsNotRoutingExample = "<template>\n <bento-typography>\n <bento-link to=\"/\" is-not-routing>an interactive link</bento-link>\n </bento-typography>\n</template>\n\n<script setup lang=\"ts\">\n import { BentoLink, BentoTypography } from '@adyen/bento-vue2';\n</script>\n"; const LinkQuietExample = "<template>\n <bento-typography>\n <bento-link variant=\"quiet\" to=\"/\">an interactive link</bento-link>\n </bento-typography>\n</template>\n\n<script setup lang=\"ts\">\n import { BentoLink, BentoTypography } from '@adyen/bento-vue2';\n</script>\n"; const meta: Meta<typeof BentoLink> = { title: 'Link', component: BentoLink, argTypes: { // Slots default: { name: 'default', defaultValue: '', description: 'link text', table: { type: { summary: 'VNode[]' }, }, control: { type: 'text', }, }, // Router props to: { name: 'to', defaultValue: '/', description: 'Denotes the target route of the link. When clicked, the value of the `to` prop will be passed to `router.push()` internally, so the value can be either a string or a location descriptor object.', table: { type: { summary: 'string | Location' }, }, control: { type: 'text', }, }, // Custom props variant: { options: Object.values(BentoLinkVariant), mapping: BentoLinkVariant, control: { type: 'select', }, }, target: { name: 'target', defaultValue: null, control: { type: 'text', }, }, // Emits click: { name: 'click', defaultValue: '', description: 'Click event handler.', table: { type: { summary: '($event) => void' }, }, control: { disable: true }, }, // TODO: remove this when Vue fixes extending interface issue for defineProps args: { table: { disable: true } }, }, }; export default meta; type Story = StoryObj<typeof BentoLink>; const DEFFAULT_ARGS = { to: '/', }; const EXTERNAL_HREF = 'https://www.adyen.com/'; // Default export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoLink, BentoTypography }, props: Object.keys(argTypes), template: ` <div> <bento-typography> <bento-link v-bind="args"> an interactive link </bento-link> </bento-typography> </div> `, setup: props => ({ args: isVue2 ? props : _args }), }), args: DEFFAULT_ARGS, parameters: storybookDocsParameter(LinkDefaultExample), }; // Quiet export const Quiet: Story = { ...Default, args: { ...DEFFAULT_ARGS, variant: 'quiet', }, parameters: storybookDocsParameter(LinkQuietExample), }; // External export const External: Story = { ...Default, args: { ...DEFFAULT_ARGS, external: true, to: EXTERNAL_HREF, }, parameters: storybookDocsParameter(LinkExternalExample), }; // isNotRouting export const isNotRouting: Story = { ...Default, args: { ...DEFFAULT_ARGS, isNotRouting: true, }, parameters: storybookDocsParameter(LinkIsNotRoutingExample), };
@@ -1 +1 @@
1
- import type { Location } from 'vue-router/types/router.d.ts'; // TODO: revert to import once fully migrated to Vue 3 // import type { RouterLinkProps } from 'vue-router/types/router.d.ts'; /** * Manually defined RouterLinkProps to ensure compatibility * with Vue Macros and Vue 2.7 / 3 monorepos. */ export interface RouterLinkProps { /** * Denotes the target route of the link. When clicked, the value of the `to` prop will be passed to * `router.push()` internally, so the value can be either a string or a location descriptor object. */ to: string | Location; /** * Setting `replace` prop will call `router.replace()` instead of `router.push()` when clicked, so the navigation will * not create a new history record. * * @default false */ replace?: boolean; /** * Setting `append` prop always appends the relative path to the current path. For example, assuming we are navigating * from `/a` to a relative link `b`, without `append` we will end up at `/b`, but with append we will end up at * `/a/b`. * * @default false */ append?: boolean; /** * Sometimes we want <RouterLink> to render as another tag, e.g <li>. Then we can use tag prop to specify which tag to * render to, and it will still listen to click events for navigation. * * @default "a" */ tag?: string; /** * Configure the active CSS class applied when the link is active. Note the default value can also be configured * globally via the `linkActiveClass` router constructor option. * * @default "router-link-active" */ activeClass?: string; /** * The default active class matching behavior is **inclusive match**. For example, `<RouterLink to="/a">` will get * this class applied as long as the current path starts with `/a/` or is `/a`. * * @default false */ exact?: boolean; /** * Allows matching only using the `path` section of the url, effectively ignoring the `query` and the `hash` sections. * * @default false */ exactPath?: boolean; /** * Configure the active CSS class applied when the link is active with exact path match. Note the default value can * also be configured globally via the `linkExactPathActiveClass` router constructor option. * * @default "router-link-exact-path-active" */ exactPathActiveClass?: string; /** * Specify the event(s) that can trigger the link navigation. * * @default 'click' */ event?: string | ReadonlyArray<string>; /** * Configure the active CSS class applied when the link is active with exact match. Note the default value can also be * configured globally via the `linkExactActiveClass` router constructor option. * * @default "router-link-exact-active" */ exactActiveClass?: string; /** * Configure the value of `aria-current` when the link is active with exact match. It must be one of the allowed * values for [aria-current](https://www.w3.org/TR/wai-aria-1.2/#aria-current) in the ARIA spec. In most cases, the * default of page should be the best fit. * * @default "page" */ ariaCurrentValue?: 'page' | 'step' | 'location' | 'date' | 'time' | 'true' | 'false'; } /** * @deprecated Since v2.0.0. Use string literal instead. */ export enum BentoLinkVariant { DEFAULT = 'default', QUIET = 'quiet', } export type BentoLinkProps = RouterLinkProps & { /** * Disable the default typography for the component. Only use this when the component is inside another typography component. */ disableTypography?: boolean; /** * Set the rendered link to a regular external anchor link and renders an external link icon. */ external?: boolean; /** * Set to true if used inside a `BentoButton` component. */ isButton?: boolean; /** * Set the rendered link to a regular external anchor link. */ isNotRouting?: boolean; /** * Sets the target of the rendered link. */ target?: string; /** * Set to true to truncate link. Doesn't work with disableTypography. */ truncate?: boolean; /** * Link variant type affecting visual style. * @values default, quiet */ variant?: BentoLinkVariant | `${BentoLinkVariant}`; };
1
+ import type { Location } from 'vue-router/types/router.d.ts'; // TODO: revert to import once fully migrated to Vue 3 // import type { RouterLinkProps } from 'vue-router/types/router.d.ts'; /** * Manually defined RouterLinkProps to ensure compatibility * with Vue Macros and Vue 2.7 / 3 monorepos. */ export interface RouterLinkProps { /** * Denotes the target route of the link. When clicked, the value of the `to` prop will be passed to * `router.push()` internally, so the value can be either a string or a location descriptor object. */ to: string | Location; /** * Setting `replace` prop will call `router.replace()` instead of `router.push()` when clicked, so the navigation will * not create a new history record. * * @default false */ replace?: boolean; /** * Setting `append` prop always appends the relative path to the current path. For example, assuming we are navigating * from `/a` to a relative link `b`, without `append` we will end up at `/b`, but with append we will end up at * `/a/b`. * * @default false */ append?: boolean; /** * Sometimes we want <RouterLink> to render as another tag, e.g <li>. Then we can use tag prop to specify which tag to * render to, and it will still listen to click events for navigation. * * @default "a" */ tag?: string; /** * Configure the active CSS class applied when the link is active. Note the default value can also be configured * globally via the `linkActiveClass` router constructor option. * * @default "router-link-active" */ activeClass?: string; /** * The default active class matching behavior is **inclusive match**. For example, `<RouterLink to="/a">` will get * this class applied as long as the current path starts with `/a/` or is `/a`. * * @default false */ exact?: boolean; /** * Allows matching only using the `path` section of the url, effectively ignoring the `query` and the `hash` sections. * * @default false */ exactPath?: boolean; /** * Configure the active CSS class applied when the link is active with exact path match. Note the default value can * also be configured globally via the `linkExactPathActiveClass` router constructor option. * * @default "router-link-exact-path-active" */ exactPathActiveClass?: string; /** * Specify the event(s) that can trigger the link navigation. * * @default 'click' */ event?: string | ReadonlyArray<string>; /** * Configure the active CSS class applied when the link is active with exact match. Note the default value can also be * configured globally via the `linkExactActiveClass` router constructor option. * * @default "router-link-exact-active" */ exactActiveClass?: string; /** * Configure the value of `aria-current` when the link is active with exact match. It must be one of the allowed * values for [aria-current](https://www.w3.org/TR/wai-aria-1.2/#aria-current) in the ARIA spec. In most cases, the * default of page should be the best fit. * * @default "page" */ ariaCurrentValue?: 'page' | 'step' | 'location' | 'date' | 'time' | 'true' | 'false'; } /** * @deprecated Since v3.0.0. Use string literal instead. */ export enum BentoLinkVariant { DEFAULT = 'default', QUIET = 'quiet', } export interface LinkProps { /** * Disable the default typography for the component. Only use this when the component is inside another typography component. */ disableTypography?: boolean; /** * Set the rendered link to a regular external anchor link and renders an external link icon. */ external?: boolean; /** * Set to true if used inside a `BentoButton` component. */ isButton?: boolean; /** * Set the rendered link to a regular external anchor link. */ isNotRouting?: boolean; /** * Sets the target of the rendered link. */ target?: string; /** * Set to true to truncate link. Doesn't work with disableTypography. */ truncate?: boolean; /** * Link variant type affecting visual style. * @values default, quiet */ variant?: BentoLinkVariant | `${BentoLinkVariant}`; } export type BentoLinkProps = RouterLinkProps & LinkProps;
@@ -1 +1 @@
1
- <template> <a v-if="external || isNotRouting" :class="conditionalClasses" v-bind="$attrs" :aria-describedby="external ? externalLinkId : null" :href="$props.to.toString()" :title="getAttributeTitle($attrs.title)" :target="computedTarget" @click="onClick" > <bento-typography v-if="!disableTypography" class="b-link__text" el="span" variant="body"> <slot /> </bento-typography> <slot v-else /> <span v-if="showExternalLinkIcon" aria-hidden="true" class="b-link__external-link-icon"> <external-link-icon /> </span> <span v-if="external" :id="externalLinkId" class="b-link--visually-hidden">{{ t('opensInNewTab') }}</span> </a> <router-link v-else v-bind="{ ...$attrs, ...$props, ...nonForwardedProps }" :class="conditionalClasses" :target="target" :to="props.to" custom @click.native="onClick" > <bento-typography v-if="!disableTypography" class="b-link__text" el="span" variant="body"> <slot /> </bento-typography> <slot v-else /> </router-link> </template> <script setup lang="ts"> import { RouterLink } from 'vue-router'; import { computed } from 'vue'; import { generateUid } from '@/core/utils/ts'; import { BentoTypography } from '@/components/typography'; import { type BentoLinkProps, BentoLinkVariant } from './link.types'; import { useI18n } from '@/utils/ts/i18n'; import type { RouterLinkSlotArgument } from 'vue-router/types/router.d.ts'; import ExternalLinkIcon from '@adyen/ui-assets-icons-16/vue/external-link'; import messages from './messages.json'; type MessageSchema = (typeof messages)['en-US']; const { t } = useI18n<{ message: MessageSchema }>({ messages }); const props = withDefaults(defineProps<BentoLinkProps>(), { target: null, variant: BentoLinkVariant.DEFAULT, }); // Props that should not be forwarded to the underlying anchor element const nonForwardedProps = { variant: undefined, isButton: undefined, truncate: undefined, disableTypography: undefined, external: undefined, isNotRouting: undefined, }; const externalLinkId = generateUid('external-link'); const getAttributeTitle = (attributesTitle: unknown) => attributesTitle as string; const conditionalClasses = computed(() => ({ 'b-link': !props.isButton, [`b-link--${BentoLinkVariant.QUIET}`]: props.variant === BentoLinkVariant.QUIET, [`b-link--truncate`]: props.truncate, })); const computedTarget = computed(() => (props.external ? (props.target ?? '_blank') : props.target)); const showExternalLinkIcon = computed(() => !(props.isButton || props.isNotRouting)); const emit = defineEmits<{ (e: 'click', clickEvent: MouseEvent): void; }>(); const onClick = (e: MouseEvent, navigateCallback?: RouterLinkSlotArgument['navigate']) => { emit('click', e); if (navigateCallback) { navigateCallback(); } }; </script> <script lang="ts"> /** * Extends the `router-link` from the `vue-router` NPM package. * * @example * import { BentoLink } from '@adyen/bento-vue2'; * * export default { * components: { BentoLink }, * template: ` * <bento-link to="/"> * link * </bento-link> * ` * } */ export default { name: 'bento-link', inheritAttrs: false, i18n: { messages }, }; </script> <style lang="scss" scoped src="@/core/components/link/link.scss" />
1
+ <template> <a v-if="external || isNotRouting" :class="conditionalClasses" v-bind="$attrs" :aria-describedby="external ? externalLinkId : null" :href="$props.to.toString()" :title="getAttributeTitle($attrs.title)" :target="computedTarget" @click="onClick" > <bento-typography v-if="!disableTypography" class="b-link__text" el="span" variant="body"> <slot /> </bento-typography> <slot v-else /> <span v-if="showExternalLinkIcon" aria-hidden="true" class="b-link__external-link-icon"> <external-link-icon /> </span> <span v-if="external" :id="externalLinkId" class="b-link--visually-hidden">{{ t('opensInNewTab') }}</span> </a> <router-link v-else v-bind="{ ...$attrs, ...$props, ...nonForwardedProps }" :class="conditionalClasses" :target="target" :to="props.to" custom @click.native="onClick" > <bento-typography v-if="!disableTypography" class="b-link__text" el="span" variant="body"> <slot /> </bento-typography> <slot v-else /> </router-link> </template> <script setup lang="ts"> import { RouterLink } from 'vue-router'; import { computed } from 'vue'; import { generateUid } from '@/utils/ts/generate-uid'; import { BentoTypography } from '@/components/typography'; import { type BentoLinkProps, BentoLinkVariant } from './link.types'; import { useI18n } from '@/utils/ts/i18n'; import type { RouterLinkSlotArgument } from 'vue-router/types/router.d.ts'; import ExternalLinkIcon from '@adyen/ui-assets-icons-16/vue/external-link'; import messages from './messages.json'; type MessageSchema = (typeof messages)['en-US']; const { t } = useI18n<{ message: MessageSchema }>({ messages }); const props = withDefaults(defineProps<BentoLinkProps>(), { target: null, variant: BentoLinkVariant.DEFAULT, }); // Props that should not be forwarded to the underlying anchor element const nonForwardedProps = { variant: undefined, isButton: undefined, truncate: undefined, disableTypography: undefined, external: undefined, isNotRouting: undefined, }; const externalLinkId = generateUid('external-link'); const getAttributeTitle = (attributesTitle: unknown) => attributesTitle as string; const conditionalClasses = computed(() => ({ 'b-link': !props.isButton, [`b-link--${BentoLinkVariant.QUIET}`]: props.variant === BentoLinkVariant.QUIET, [`b-link--truncate`]: props.truncate, })); const computedTarget = computed(() => (props.external ? (props.target ?? '_blank') : props.target)); const showExternalLinkIcon = computed(() => !(props.isButton || props.isNotRouting)); const emit = defineEmits<{ (e: 'click', clickEvent: MouseEvent): void; }>(); const onClick = (e: MouseEvent, navigateCallback?: RouterLinkSlotArgument['navigate']) => { emit('click', e); if (navigateCallback) { navigateCallback(); } }; </script> <script lang="ts"> /** * Extends the `router-link` from the `vue-router` NPM package. * * @example * import { BentoLink } from '@adyen/bento-vue2'; * * export default { * components: { BentoLink }, * template: ` * <bento-link to="/"> * link * </bento-link> * ` * } */ export default { name: 'bento-link', inheritAttrs: false, i18n: { messages }, }; </script> <style lang="scss" scoped src="./link.scss" />
@@ -1 +1 @@
1
- import { ref } from 'vue'; import { action } from '@storybook/addon-actions'; import { BentoButton } from '@/components/button'; import { BentoToggle } from '@/components/toggle'; import BentoList from './list.vue'; import BentoListItem from './components/list-item/list-item.vue'; import { storybookDocsParameter } from '@/utils/ts/storybook'; import ListDefault from './__tests__/list-default.vue?raw'; import ListEverythingBagel from './__tests__/list-everything-bagel.vue?raw'; import { isVue2 } from 'vue-demi'; import type { Meta, StoryObj } from '@storybook/vue'; const meta: Meta = { title: 'List', component: BentoList, argTypes: { type: { options: ['unordered', 'ordered'], control: { type: 'select', }, }, }, }; export default meta; type Story = StoryObj<typeof BentoList>; export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoList, BentoListItem, BentoButton, BentoToggle }, props: Object.keys(argTypes), template: ` <bento-list v-bind="args"> <bento-list-item label="Payment details" description="Updated 2 hours ago" :tag="{ label: 'Live', variant: 'green' }" show-bottom-divider > <template #start> <bento-toggle aria-label="Payment details" /> </template> <template #end> <bento-button variant="secondary">Edit</bento-button> </template> </bento-list-item> <bento-list-item label="Payout settings" description="Updated yesterday" :tag="{ label: 'Review', variant: 'orange' }" show-bottom-divider > <template #start> <bento-toggle aria-label="Payout settings" /> </template> <template #end> <bento-button variant="secondary">Edit</bento-button> </template> </bento-list-item> <bento-list-item label="Risk configuration" description="Updated 3 days ago" :tag="{ label: 'Draft', variant: 'grey' }" > <template #start> <bento-toggle aria-label="Risk configuration" /> </template> <template #end> <bento-button variant="secondary">Edit</bento-button> </template> </bento-list-item> </bento-list> `, setup(props) { return { args: isVue2 ? props : _args, }; }, }), args: { type: 'unordered', }, parameters: storybookDocsParameter(ListDefault), }; export const EverythingBagel: Story = { render: (_args, { argTypes }) => ({ components: { BentoList, BentoListItem, BentoButton, BentoToggle }, props: Object.keys(argTypes), template: ` <bento-list v-bind="args" @reorder="onReorder"> <bento-list-item v-for="item in items" :key="item.id" :label="item.label" :description="item.description" :tag="item.tag" :show-status="item.showStatus" :status-color="item.statusColor" :draggable-options="item.draggableOptions" show-bottom-divider > <template v-if="item.showToggle" #start> <bento-toggle :aria-label="item.label" /> </template> <template v-if="item.showEdit" #end> <bento-button variant="secondary">Edit</bento-button> </template> </bento-list-item> </bento-list> `, setup(props) { const items = ref([ { id: 'payment', label: 'Payment details', description: 'Updated 2 hours ago', tag: { label: 'Live', variant: 'green' }, showStatus: true, statusColor: 'green', showToggle: true, showEdit: true, }, { id: 'payout', label: 'Payout settings', description: 'Updated yesterday', tag: { label: 'Review', variant: 'orange' }, showStatus: true, statusColor: 'orange', showToggle: true, showEdit: true, }, { id: 'risk', label: 'Risk configuration', description: 'Updated 3 days ago', tag: { label: 'Draft', variant: 'grey' }, showStatus: true, }, { id: 'compliance', label: 'Compliance check', description: 'Locked by admin', draggableOptions: { disabled: true }, }, ]); const reorderAction = action('reorder'); const onReorder = ({ oldIndex, newIndex }: { oldIndex: number; newIndex: number }) => { const updated = [...items.value]; const [moved] = updated.splice(oldIndex, 1); updated.splice(newIndex, 0, moved); items.value = updated; reorderAction({ oldIndex, newIndex, order: updated.map(i => i.label) }); }; return { args: isVue2 ? props : _args, items, onReorder, }; }, }), args: { type: 'unordered', draggable: true, draggableOptions: { animation: 150 }, }, parameters: storybookDocsParameter(ListEverythingBagel), };
1
+ import { ref } from 'vue'; import { action } from '@storybook/addon-actions'; import { BentoButton } from '@/components/button'; import { BentoToggle } from '@/components/toggle'; import BentoList from './list.vue'; import BentoListItem from './components/list-item/list-item.vue'; import { storybookDocsParameter } from '@/utils/ts/storybook'; const ListDefault = "<template>\n <bento-list>\n <bento-list-item\n label=\"Payment details\"\n description=\"Updated 2 hours ago\"\n :tag=\"{ label: 'Live', variant: 'green' }\"\n show-bottom-divider\n >\n <template #start>\n <bento-toggle aria-label=\"Payment details\" />\n </template>\n <template #end>\n <bento-button variant=\"secondary\">Edit</bento-button>\n </template>\n </bento-list-item>\n\n <bento-list-item\n label=\"Payout settings\"\n description=\"Updated yesterday\"\n :tag=\"{ label: 'Review', variant: 'orange' }\"\n show-bottom-divider\n >\n <template #start>\n <bento-toggle aria-label=\"Payout settings\" />\n </template>\n <template #end>\n <bento-button variant=\"secondary\">Edit</bento-button>\n </template>\n </bento-list-item>\n\n <bento-list-item\n label=\"Risk configuration\"\n description=\"Updated 3 days ago\"\n :tag=\"{ label: 'Draft', variant: 'grey' }\"\n >\n <template #start>\n <bento-toggle aria-label=\"Risk configuration\" />\n </template>\n <template #end>\n <bento-button variant=\"secondary\">Edit</bento-button>\n </template>\n </bento-list-item>\n </bento-list>\n</template>\n\n<script setup lang=\"ts\">\n import { BentoButton, BentoList, BentoListItem, BentoToggle } from '@adyen/bento-vue2';\n</script>\n"; const ListEverythingBagel = "<template>\n <bento-list draggable :draggable-options=\"{ animation: 150 }\" @reorder=\"onReorder\">\n <bento-list-item\n v-for=\"item in items\"\n :key=\"item.id\"\n :label=\"item.label\"\n :description=\"item.description\"\n :tag=\"item.tag\"\n :show-status=\"item.showStatus\"\n :status-color=\"item.statusColor\"\n :draggable-options=\"item.draggableOptions\"\n show-bottom-divider\n >\n <template v-if=\"item.showToggle\" #start>\n <bento-toggle :aria-label=\"item.label\" />\n </template>\n <template v-if=\"item.showEdit\" #end>\n <bento-button variant=\"secondary\">Edit</bento-button>\n </template>\n </bento-list-item>\n </bento-list>\n</template>\n\n<script setup lang=\"ts\">\n import { ref } from 'vue';\n import { BentoButton, BentoList, BentoListItem, type BentoListItemProps, BentoToggle } from '@adyen/bento-vue2';\n\n interface ListItem extends BentoListItemProps {\n id: string;\n showToggle?: boolean;\n showEdit?: boolean;\n }\n\n const items = ref<Array<ListItem>>([\n {\n id: 'payment',\n label: 'Payment details',\n description: 'Updated 2 hours ago',\n tag: { label: 'Live', variant: 'green' },\n showStatus: true,\n statusColor: 'green',\n showToggle: true,\n showEdit: true,\n },\n {\n id: 'payout',\n label: 'Payout settings',\n description: 'Updated yesterday',\n tag: { label: 'Review', variant: 'orange' },\n showStatus: true,\n statusColor: 'orange',\n showToggle: true,\n showEdit: true,\n },\n {\n id: 'risk',\n label: 'Risk configuration',\n description: 'Updated 3 days ago',\n tag: { label: 'Draft', variant: 'grey' },\n showStatus: true,\n },\n {\n id: 'compliance',\n label: 'Compliance check',\n description: 'Locked by admin',\n draggableOptions: { disabled: true },\n },\n ]);\n\n const onReorder = ({ oldIndex, newIndex }: { oldIndex: number; newIndex: number }) => {\n const updated = [...items.value];\n const [moved] = updated.splice(oldIndex, 1);\n updated.splice(newIndex, 0, moved);\n items.value = updated;\n };\n</script>\n"; import { isVue2 } from 'vue-demi'; import type { Meta, StoryObj } from '@storybook/vue'; const meta: Meta = { title: 'List', component: BentoList, argTypes: { type: { options: ['unordered', 'ordered'], control: { type: 'select', }, }, }, }; export default meta; type Story = StoryObj<typeof BentoList>; export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoList, BentoListItem, BentoButton, BentoToggle }, props: Object.keys(argTypes), template: ` <bento-list v-bind="args"> <bento-list-item label="Payment details" description="Updated 2 hours ago" :tag="{ label: 'Live', variant: 'green' }" show-bottom-divider > <template #start> <bento-toggle aria-label="Payment details" /> </template> <template #end> <bento-button variant="secondary">Edit</bento-button> </template> </bento-list-item> <bento-list-item label="Payout settings" description="Updated yesterday" :tag="{ label: 'Review', variant: 'orange' }" show-bottom-divider > <template #start> <bento-toggle aria-label="Payout settings" /> </template> <template #end> <bento-button variant="secondary">Edit</bento-button> </template> </bento-list-item> <bento-list-item label="Risk configuration" description="Updated 3 days ago" :tag="{ label: 'Draft', variant: 'grey' }" > <template #start> <bento-toggle aria-label="Risk configuration" /> </template> <template #end> <bento-button variant="secondary">Edit</bento-button> </template> </bento-list-item> </bento-list> `, setup(props) { return { args: isVue2 ? props : _args, }; }, }), args: { type: 'unordered', }, parameters: storybookDocsParameter(ListDefault), }; export const EverythingBagel: Story = { render: (_args, { argTypes }) => ({ components: { BentoList, BentoListItem, BentoButton, BentoToggle }, props: Object.keys(argTypes), template: ` <bento-list v-bind="args" @reorder="onReorder"> <bento-list-item v-for="item in items" :key="item.id" :label="item.label" :description="item.description" :tag="item.tag" :show-status="item.showStatus" :status-color="item.statusColor" :draggable-options="item.draggableOptions" show-bottom-divider > <template v-if="item.showToggle" #start> <bento-toggle :aria-label="item.label" /> </template> <template v-if="item.showEdit" #end> <bento-button variant="secondary">Edit</bento-button> </template> </bento-list-item> </bento-list> `, setup(props) { const items = ref([ { id: 'payment', label: 'Payment details', description: 'Updated 2 hours ago', tag: { label: 'Live', variant: 'green' }, showStatus: true, statusColor: 'green', showToggle: true, showEdit: true, }, { id: 'payout', label: 'Payout settings', description: 'Updated yesterday', tag: { label: 'Review', variant: 'orange' }, showStatus: true, statusColor: 'orange', showToggle: true, showEdit: true, }, { id: 'risk', label: 'Risk configuration', description: 'Updated 3 days ago', tag: { label: 'Draft', variant: 'grey' }, showStatus: true, }, { id: 'compliance', label: 'Compliance check', description: 'Locked by admin', draggableOptions: { disabled: true }, }, ]); const reorderAction = action('reorder'); const onReorder = ({ oldIndex, newIndex }: { oldIndex: number; newIndex: number }) => { const updated = [...items.value]; const [moved] = updated.splice(oldIndex, 1); updated.splice(newIndex, 0, moved); items.value = updated; reorderAction({ oldIndex, newIndex, order: updated.map(i => i.label) }); }; return { args: isVue2 ? props : _args, items, onReorder, }; }, }), args: { type: 'unordered', draggable: true, draggableOptions: { animation: 150 }, }, parameters: storybookDocsParameter(ListEverythingBagel), };
@@ -1 +1 @@
1
- import { isVue2 } from 'vue-demi'; import BentoLoadingIndicator from './loading-indicator.vue'; import { storybookDocsParameter } from '@/utils/ts/storybook'; export default { title: 'Loading Indicator', component: BentoLoadingIndicator, parameters: { layout: 'centered', }, argTypes: { small: { if: { arg: 'large', truthy: false } }, large: { if: { arg: 'small', truthy: false } }, }, }; export const Default = (_args, { argTypes }) => ({ components: { BentoLoadingIndicator }, props: Object.keys(argTypes), setup: props => ({ args: isVue2 ? props : _args }), template: ` <bento-loading-indicator v-bind="args" /> `, }); const defaultCode = ` <loading-indicator /> `; Default.args = {}; Default.parameters = storybookDocsParameter(defaultCode);
1
+ import { isVue2 } from 'vue-demi'; import BentoLoadingIndicator from './loading-indicator.vue'; import { storybookDocsParameter } from '@/utils/ts/storybook'; const LoadingIndicatorDefaultExample = "<template>\n <bento-loading-indicator />\n</template>\n\n<script setup lang=\"ts\">\n import { BentoLoadingIndicator } from '@adyen/bento-vue2';\n</script>\n"; export default { title: 'Loading Indicator', component: BentoLoadingIndicator, parameters: { layout: 'centered', }, argTypes: { small: { if: { arg: 'large', truthy: false } }, large: { if: { arg: 'small', truthy: false } }, }, }; export const Default = (_args, { argTypes }) => ({ components: { BentoLoadingIndicator }, props: Object.keys(argTypes), setup: props => ({ args: isVue2 ? props : _args }), template: ` <bento-loading-indicator v-bind="args" /> `, }); Default.args = {}; Default.parameters = storybookDocsParameter(LoadingIndicatorDefaultExample);
@@ -1 +1 @@
1
- import type { VueNodeElement } from '@/types/elements'; export interface BentoMenuItem { text: string; handler: () => void; secondaryText?: string; items?: Array<BentoMenuItem>; disabled?: boolean; chevron?: boolean; icon?: VueNodeElement; critical?: boolean; indent?: boolean; addDivider?: boolean; skipClose?: boolean; }
1
+ import type { VueNodeElement } from '@/types/elements'; import type { BentoLinkProps } from '@/components/link/link.types'; export interface BentoMenuItem { text: string; handler?: () => void; link?: BentoLinkProps; secondaryText?: string; items?: Array<BentoMenuItem>; disabled?: boolean; chevron?: boolean; icon?: VueNodeElement; critical?: boolean; indent?: boolean; addDivider?: boolean; skipClose?: boolean; }
@@ -1 +1 @@
1
- <template> <base-button class="b-menu-item" role="menuitem" :class="conditionalClasses" :disabled="disabled" tabindex="-1" @click="onClick" > <template v-if="icon" #iconLeft> <component :is="icon" class="b-menu-item__icon-left" aria-hidden="true" /> </template> <template #default> <bento-typography el="span" variant="body" :stronger="isTitle" class="b-menu-item__primary-text"> {{ text }} </bento-typography> <bento-typography v-if="computedSecondaryText" el="span" variant="body" class="b-menu-item__secondary-text"> {{ computedSecondaryText }} </bento-typography> </template> <template v-if="chevron" #iconRight><chevron-right-icon aria-hidden="true" /></template> </base-button> </template> <script lang="ts"> import { BaseButton, BentoBaseButtonEvent } from '@/components/internal/base-button'; import { BentoTypography } from '@/components/typography'; import { truncate } from '@/utils/ts/truncate'; import ChevronRightIcon from '@adyen/ui-assets-icons-16/vue/chevron-right'; import { computed, defineComponent, type PropType } from 'vue'; import { type BentoMenuItem } from './menu-item.types'; /** * Menu item component to render items in a menu. For internal use only. */ export default defineComponent({ name: 'menu-item', components: { BaseButton, BentoTypography, ChevronRightIcon }, props: { /** * menu-item text that gets displayed as the main label */ text: { type: String, required: true, }, /** * menu-item action that gets executed when the MeniItem is clicked */ handler: { type: Function as PropType<BentoMenuItem['handler']>, required: true, }, /** * menu-item text that gets displayed as the secondary label */ secondaryText: { type: String, default: null, }, /** * Optional chevron icon displayed to the right of the menu-item */ chevron: { type: Boolean, default: false, }, /** * Optional icon displayed to the left of the menu-item */ icon: { // Types for Vue2 (Object) and Vue3 (Function) // TODO: Remove Object when fully migrated to Vue3 type: [Object, Function] as PropType<BentoMenuItem['icon']>, default: null, }, /** * Disables menu-item button functionality */ disabled: { type: Boolean, default: false, }, /** * Displays an indent variant od the menu-item */ indent: { type: Boolean, default: false, }, /** * Displays menu-item button as critical */ critical: { type: Boolean, default: false, }, /** * Renders the text in a bold variant */ isTitle: { type: Boolean, default: false, }, }, emits: [BentoBaseButtonEvent.CLICK], setup(props, { emit }) { const MAX_SECONDARY_TEXT_LENGTH = 10; const conditionalClasses = computed(() => ({ 'b-menu-item--critical': props.critical, 'b-menu-item--indent': props.indent, })); const computedSecondaryText = computed(() => truncate(props.secondaryText, MAX_SECONDARY_TEXT_LENGTH)); const onClick = (e: MouseEvent) => { e.stopPropagation(); props.handler(); emit(BentoBaseButtonEvent.CLICK); }; return { // Values conditionalClasses, computedSecondaryText, // Methods onClick, }; }, }); </script> <style lang="scss" scoped src="./menu-item.scss" />
1
+ <template> <base-button class="b-menu-item" role="menuitem" :class="conditionalClasses" :disabled="disabled" :link="link" tabindex="-1" @click="onClick" > <template v-if="icon" #iconLeft> <component :is="icon" class="b-menu-item__icon-left" aria-hidden="true" /> </template> <template #default> <bento-typography el="span" variant="body" :stronger="isTitle" class="b-menu-item__primary-text"> {{ text }} </bento-typography> <span v-if="isExternalLink" class="b-menu-item__external-link-icon"> <external-link-icon aria-hidden="true" /> </span> <bento-typography v-else-if="computedSecondaryText" el="span" variant="body" class="b-menu-item__secondary-text" > {{ computedSecondaryText }} </bento-typography> </template> <template v-if="chevron" #iconRight><chevron-right-icon aria-hidden="true" /></template> </base-button> </template> <script lang="ts"> import { BaseButton, BentoBaseButtonEvent } from '@/components/internal/base-button'; import { BentoTypography } from '@/components/typography'; import { truncate } from '@/utils/ts/truncate'; import ChevronRightIcon from '@adyen/ui-assets-icons-16/vue/chevron-right'; import ExternalLinkIcon from '@adyen/ui-assets-icons-16/vue/external-link'; import { computed, defineComponent, type PropType } from 'vue'; import { type BentoMenuItem } from './menu-item.types'; /** * Menu item component to render items in a menu. For internal use only. */ export default defineComponent({ name: 'menu-item', components: { BaseButton, BentoTypography, ChevronRightIcon, ExternalLinkIcon }, props: { /** * menu-item text that gets displayed as the main label */ text: { type: String, required: true, }, /** * Optional click event handler. */ handler: { type: Function as PropType<BentoMenuItem['handler']>, default: undefined, }, /** * Renders the menu-item as a link. Accepts the same props as `BentoLink`. */ link: { type: Object as PropType<BentoMenuItem['link']>, default: undefined, }, /** * menu-item text that gets displayed as the secondary label */ secondaryText: { type: String, default: null, }, /** * Optional chevron icon displayed to the right of the menu-item */ chevron: { type: Boolean, default: false, }, /** * Optional icon displayed to the left of the menu-item */ icon: { // Types for Vue2 (Object) and Vue3 (Function) // TODO: Remove Object when fully migrated to Vue3 type: [Object, Function] as PropType<BentoMenuItem['icon']>, default: null, }, /** * Disables menu-item button functionality */ disabled: { type: Boolean, default: false, }, /** * Displays an indent variant od the menu-item */ indent: { type: Boolean, default: false, }, /** * Displays menu-item button as critical */ critical: { type: Boolean, default: false, }, /** * Renders the text in a bold variant */ isTitle: { type: Boolean, default: false, }, }, emits: [BentoBaseButtonEvent.CLICK], setup(props, { emit }) { const MAX_SECONDARY_TEXT_LENGTH = 10; const conditionalClasses = computed(() => ({ 'b-menu-item--critical': props.critical, 'b-menu-item--disabled': props.disabled, 'b-menu-item--indent': props.indent, })); const computedSecondaryText = computed(() => truncate(props.secondaryText, MAX_SECONDARY_TEXT_LENGTH)); const isExternalLink = computed(() => !!props.link?.external); const onClick = (e: MouseEvent) => { e.stopPropagation(); props.handler?.(); emit(BentoBaseButtonEvent.CLICK); }; return { // Values conditionalClasses, computedSecondaryText, isExternalLink, // Methods onClick, }; }, }); </script> <style lang="scss" scoped src="./menu-item.scss" />
@@ -1 +1 @@
1
- <template> <menu-list-container ref="menuItemListWrapperRef" :style="computedStyle"> <ul ref="menuItemListRef" class="b-menu-item-list" role="menu" @keydown.down="focusNext" @keydown.up="focusPrevious" @keydown.esc="closeMenu" @keydown.tab="closeMenu" > <li v-for="(item, index) in computedData" :key="`menu-item-${index}`" ref="menuItemRef" class="b-menu-item-list__item" role="none" @keydown.right="focusRight(item, index)" @keydown.left="focusLeft()" > <menu-item :text="item.text" :handler="!item.items?.length ? item.handler : () => null" :secondary-text="item.secondaryText" :icon="item.icon" :chevron="!!item.items?.length" :disabled="item.disabled" :critical="item.critical" :indent="item.indent" :tabindex="index === subMenuFocusedIndex ? '0' : '-1'" :aria-haspopup="!!item.items?.length" :aria-expanded="showSubMenu && visibleSubMenuIndex === index" @click="menuItemClick(item, index)" /> <bento-divider v-if="item.addDivider" class="b-menu-item-list__divider" /> <sub-menu-item-list v-if="item.items?.length && showSubMenu && visibleSubMenuIndex === index" ref="subMenuItemListRef" :items="item.items" :title="item.text" @back="onBackButton" @close="closeMenu" /> </li> </ul> </menu-list-container> </template> <script lang="ts"> import { computed, defineComponent, nextTick, type PropType, ref, toRef } from 'vue'; import { isVue3 } from 'vue-demi'; import { BentoDivider } from '@/components/divider'; import { type BentoMenuItem, MenuItem } from '../menu-item'; import { BentoMenuEvent } from '../../menu.types'; import MenuListContainer from '../menu-list-container/menu-list-container.vue'; import SubMenuItemList from '../sub-menu-item-list/sub-menu-item-list.vue'; import { useDynamicMenuSize } from '../../composables'; /** * MenuItem list to be used in the Menu component. For internal use only. */ export default defineComponent({ name: 'menu-item-list', components: { BentoDivider, MenuItem, MenuListContainer, SubMenuItemList }, props: { /** * The data to be displayed in the list */ data: { type: Array as PropType<Array<BentoMenuItem>>, required: true, }, /** * Displays the menu-items of the list in the indent variant. Only applies to elements that don't have an icon to their left. */ indentItems: { type: Boolean, default: false, }, /** * If set to true, focuses the last item on the list instead of the first. Used for accessibility purposes when the menu is opened with the 'up' arrow key. */ focusLastElement: { type: Boolean, default: false, }, /** * If set to true, closes the menu when an item is clicked. */ closeMenuOnItemSelect: { type: Boolean, default: true, }, /** * The custom popover width if one has been set */ popoverWidth: { type: Number, default: null, }, }, emits: [BentoMenuEvent.CLOSE, 'menu-item-click'], setup(props, { emit, expose }) { const menuItemRef = ref(null); const menuItemListRef = ref(null); const menuItemListWrapperRef = ref(null); const subMenuItemListRef = ref(null); const showSubMenu = ref(false); const visibleSubMenuIndex = ref(null); const focusedIndex = ref(props.focusLastElement ? props.data.length - 1 : 0); const subMenuFocusedIndex = ref(0); const numberOfItems = ref(props.data.length - 1); const hasMenuItemsWithIcon = computed(() => props.data.some(el => el.icon)); const computedData = computed(() => { if (props.indentItems || hasMenuItemsWithIcon.value) { return props.data.map(el => { let indent = false; if (!el.icon) { indent = true; } return { ...el, indent, }; }); } return props.data; }); const focusItem = async () => { // vue 3 requires nextTick to be called before an item can be focused // this function does not need to be awaited itself if (isVue3) { await nextTick(); } menuItemListRef.value?.children[focusedIndex.value]?.children[0]?.focus(); }; const closeMenu = () => emit(BentoMenuEvent.CLOSE); const menuItemClick = (item: BentoMenuItem, index: number) => { if (item.items) { visibleSubMenuIndex.value = index; showSubMenu.value = true; return; } visibleSubMenuIndex.value = index; showSubMenu.value = false; if (!item.skipClose && props.closeMenuOnItemSelect !== false) { closeMenu(); } emit('menu-item-click'); }; const focusPrevious = () => { if (focusedIndex.value > 0) { focusedIndex.value -= 1; if (computedData.value[focusedIndex.value].disabled) { focusPrevious(); } } else { focusedIndex.value = numberOfItems.value; } focusItem(); }; const focusNext = () => { if (focusedIndex.value < numberOfItems.value) { focusedIndex.value += 1; if (computedData.value[focusedIndex.value].disabled) { focusNext(); } } else { focusedIndex.value = 0; } focusItem(); }; const focusRight = (item, index) => { if (item?.items?.length) { visibleSubMenuIndex.value = index; showSubMenu.value = true; } }; const focusLeft = () => { if (visibleSubMenuIndex.value && showSubMenu.value) { visibleSubMenuIndex.value = null; showSubMenu.value = false; } }; const onBackButton = () => { showSubMenu.value = false; visibleSubMenuIndex.value = null; // Focus the right element focusItem(); }; expose({ focusItem, }); const { computedStyle } = useDynamicMenuSize( subMenuItemListRef, visibleSubMenuIndex, showSubMenu, toRef(props, 'popoverWidth') ); return { // Values menuItemListRef, menuItemListWrapperRef, subMenuItemListRef, menuItemRef, computedData, computedStyle, focusedIndex, subMenuFocusedIndex, showSubMenu, visibleSubMenuIndex, // Methods menuItemClick, closeMenu, focusPrevious, focusNext, focusRight, focusLeft, onBackButton, }; }, }); </script> <style lang="scss" scoped src="./menu-item-list.scss" />
1
+ <template> <menu-list-container ref="menuItemListWrapperRef" :style="computedStyle"> <ul ref="menuItemListRef" class="b-menu-item-list" role="menu" @keydown.down="focusNext" @keydown.up="focusPrevious" @keydown.esc="closeMenu" @keydown.tab="closeMenu" > <li v-for="(item, index) in computedData" :key="`menu-item-${index}`" ref="menuItemRef" class="b-menu-item-list__item" role="none" @keydown.right="focusRight(item, index)" @keydown.left="focusLeft()" > <menu-item :text="item.text" :handler="!item.items?.length ? item.handler : () => null" :link="!item.items?.length ? item.link : undefined" :secondary-text="item.secondaryText" :icon="item.icon" :chevron="!!item.items?.length" :disabled="item.disabled" :critical="item.critical" :indent="item.indent" :tabindex="index === subMenuFocusedIndex ? '0' : '-1'" :aria-haspopup="!!item.items?.length" :aria-expanded="showSubMenu && visibleSubMenuIndex === index" @click="menuItemClick(item, index)" /> <bento-divider v-if="item.addDivider" class="b-menu-item-list__divider" /> <sub-menu-item-list v-if="item.items?.length && showSubMenu && visibleSubMenuIndex === index" ref="subMenuItemListRef" :items="item.items" :title="item.text" @back="onBackButton" @close="closeMenu" /> </li> </ul> </menu-list-container> </template> <script lang="ts"> import { computed, defineComponent, nextTick, type PropType, ref, toRef } from 'vue'; import { isVue3 } from 'vue-demi'; import { BentoDivider } from '@/components/divider'; import { type BentoMenuItem, MenuItem } from '../menu-item'; import { BentoMenuEvent } from '../../menu.types'; import MenuListContainer from '../menu-list-container/menu-list-container.vue'; import SubMenuItemList from '../sub-menu-item-list/sub-menu-item-list.vue'; import { useDynamicMenuSize } from '../../composables'; /** * MenuItem list to be used in the Menu component. For internal use only. */ export default defineComponent({ name: 'menu-item-list', components: { BentoDivider, MenuItem, MenuListContainer, SubMenuItemList }, props: { /** * The data to be displayed in the list */ data: { type: Array as PropType<Array<BentoMenuItem>>, required: true, }, /** * Displays the menu-items of the list in the indent variant. Only applies to elements that don't have an icon to their left. */ indentItems: { type: Boolean, default: false, }, /** * If set to true, focuses the last item on the list instead of the first. Used for accessibility purposes when the menu is opened with the 'up' arrow key. */ focusLastElement: { type: Boolean, default: false, }, /** * If set to true, closes the menu when an item is clicked. */ closeMenuOnItemSelect: { type: Boolean, default: true, }, /** * The custom popover width if one has been set */ popoverWidth: { type: Number, default: null, }, }, emits: [BentoMenuEvent.CLOSE, 'menu-item-click'], setup(props, { emit, expose }) { const menuItemRef = ref(null); const menuItemListRef = ref(null); const menuItemListWrapperRef = ref(null); const subMenuItemListRef = ref(null); const showSubMenu = ref(false); const visibleSubMenuIndex = ref(null); const focusedIndex = ref(props.focusLastElement ? props.data.length - 1 : 0); const subMenuFocusedIndex = ref(0); const numberOfItems = ref(props.data.length - 1); const hasMenuItemsWithIcon = computed(() => props.data.some(el => el.icon)); const computedData = computed(() => { if (props.indentItems || hasMenuItemsWithIcon.value) { return props.data.map(el => { let indent = false; if (!el.icon) { indent = true; } return { ...el, indent, }; }); } return props.data; }); const focusItem = async () => { // vue 3 requires nextTick to be called before an item can be focused // this function does not need to be awaited itself if (isVue3) { await nextTick(); } menuItemListRef.value?.children[focusedIndex.value]?.children[0]?.focus(); }; const closeMenu = () => emit(BentoMenuEvent.CLOSE); const menuItemClick = (item: BentoMenuItem, index: number) => { if (item.items) { visibleSubMenuIndex.value = index; showSubMenu.value = true; return; } visibleSubMenuIndex.value = index; showSubMenu.value = false; if (!item.skipClose && props.closeMenuOnItemSelect !== false) { closeMenu(); } emit('menu-item-click'); }; const focusPrevious = () => { if (focusedIndex.value > 0) { focusedIndex.value -= 1; if (computedData.value[focusedIndex.value].disabled) { focusPrevious(); } } else { focusedIndex.value = numberOfItems.value; } focusItem(); }; const focusNext = () => { if (focusedIndex.value < numberOfItems.value) { focusedIndex.value += 1; if (computedData.value[focusedIndex.value].disabled) { focusNext(); } } else { focusedIndex.value = 0; } focusItem(); }; const focusRight = (item, index) => { if (item?.items?.length) { visibleSubMenuIndex.value = index; showSubMenu.value = true; } }; const focusLeft = () => { if (visibleSubMenuIndex.value && showSubMenu.value) { visibleSubMenuIndex.value = null; showSubMenu.value = false; } }; const onBackButton = () => { showSubMenu.value = false; visibleSubMenuIndex.value = null; // Focus the right element focusItem(); }; expose({ focusItem, }); const { computedStyle } = useDynamicMenuSize( subMenuItemListRef, visibleSubMenuIndex, showSubMenu, toRef(props, 'popoverWidth') ); return { // Values menuItemListRef, menuItemListWrapperRef, subMenuItemListRef, menuItemRef, computedData, computedStyle, focusedIndex, subMenuFocusedIndex, showSubMenu, visibleSubMenuIndex, // Methods menuItemClick, closeMenu, focusPrevious, focusNext, focusRight, focusLeft, onBackButton, }; }, }); </script> <style lang="scss" scoped src="./menu-item-list.scss" />
@@ -1 +1 @@
1
- <template> <transition name="b-sub-menu-item-list__transition" appear> <menu-list-container class="b-sub-menu-item-list"> <ul ref="subMenuItemListRefLocal" role="menu" class="b-sub-menu-item-list__list" @keydown.down.stop="focusNext" @keydown.up.stop="focusPrevious" @keydown.esc.stop="onBackButton" > <li v-for="(item, index) in computedItems" :key="`sub-menu-item-list-item-${index}`" role="none"> <menu-item :text="item.text" :handler="item.handler" :secondary-text="item.secondaryText" :icon="item.icon" :is-title="item.isTitle" :disabled="item.disabled" :critical="item.critical" :indent="item.indent" :tabindex="index === subMenuFocusedIndex ? '0' : '-1'" @click="item.clickHandler" /> <bento-divider v-if="item.addDivider" class="b-sub-menu-item-list__divider" /> </li> </ul> </menu-list-container> </transition> </template> <script lang="ts"> import { computed, defineComponent, onMounted, type PropType, ref } from 'vue'; import { BentoDivider } from '@/components/divider'; import { type BentoMenuItem, MenuItem } from '../menu-item'; import MenuListContainer from '../menu-list-container/menu-list-container.vue'; import ChevronLeftIcon from '@adyen/ui-assets-icons-16/vue/chevron-left'; import { BentoMenuEvent } from '../../menu.types'; import messages from '../../messages.json'; /** * SubMenuItemList component to render a submenu with the sliding transition. For internal use only. */ export default defineComponent({ i18n: { messages }, name: 'sub-menu-item-list', components: { BentoDivider, MenuItem, MenuListContainer }, props: { items: { type: Array as PropType<Array<BentoMenuItem>>, required: true, }, title: { type: String, required: true, }, }, emits: [BentoMenuEvent.CLOSE, BentoMenuEvent.BACK], setup(props, { emit }) { const subMenuItemListRefLocal = ref(null); const subMenuFocusedIndex = ref(0); const numberOfItems = ref(props.items.length); const closeMenu = () => emit(BentoMenuEvent.CLOSE); const onBackButton = () => emit(BentoMenuEvent.BACK); const computedItems = computed(() => { const items = props.items?.map(el => ({ ...el, indent: !el.icon, isTitle: false, clickHandler: closeMenu, })); return [ { text: props.title, secondaryText: null, critical: false, icon: ChevronLeftIcon, isTitle: true, disabled: false, indent: false, handler: onBackButton, clickHandler: onBackButton, addDivider: true, }, ...items, ]; }); const focusItem = async () => { subMenuItemListRefLocal.value?.children?.[subMenuFocusedIndex.value]?.children?.[0]?.focus(); }; onMounted(async () => { // Using setTimeout otherwise the sliding animation doesn't work anymore. The timeout imer is the same as the transition time (0.15s) setTimeout(() => focusItem(), 150); }); const focusPrevious = () => { if (subMenuFocusedIndex.value > 0) { subMenuFocusedIndex.value -= 1; if (computedItems.value[subMenuFocusedIndex.value].disabled) { focusPrevious(); } } else { subMenuFocusedIndex.value = numberOfItems.value; } focusItem(); }; const focusNext = () => { if (subMenuFocusedIndex.value < numberOfItems.value) { subMenuFocusedIndex.value += 1; if (computedItems.value[subMenuFocusedIndex.value].disabled) { focusNext(); } } else { subMenuFocusedIndex.value = 0; } focusItem(); }; return { // Enums BentoMenuEvent, // Values subMenuItemListRefLocal, subMenuFocusedIndex, ChevronLeftIcon, computedItems, // Methods closeMenu, onBackButton, focusPrevious, focusNext, }; }, }); </script> <style lang="scss" scoped src="./sub-menu-item-list.scss" />
1
+ <template> <transition name="b-sub-menu-item-list__transition" appear> <menu-list-container class="b-sub-menu-item-list"> <ul ref="subMenuItemListRefLocal" role="menu" class="b-sub-menu-item-list__list" @keydown.down.stop="focusNext" @keydown.up.stop="focusPrevious" @keydown.esc.stop="onBackButton" > <li v-for="(item, index) in computedItems" :key="`sub-menu-item-list-item-${index}`" role="none"> <menu-item :text="item.text" :handler="item.handler" :link="item.link" :secondary-text="item.secondaryText" :icon="item.icon" :is-title="item.isTitle" :disabled="item.disabled" :critical="item.critical" :indent="item.indent" :tabindex="index === subMenuFocusedIndex ? '0' : '-1'" @click="item.clickHandler" /> <bento-divider v-if="item.addDivider" class="b-sub-menu-item-list__divider" /> </li> </ul> </menu-list-container> </transition> </template> <script lang="ts"> import { computed, defineComponent, onMounted, type PropType, ref } from 'vue'; import { BentoDivider } from '@/components/divider'; import { type BentoMenuItem, MenuItem } from '../menu-item'; import MenuListContainer from '../menu-list-container/menu-list-container.vue'; import ChevronLeftIcon from '@adyen/ui-assets-icons-16/vue/chevron-left'; import { BentoMenuEvent } from '../../menu.types'; import messages from '../../messages.json'; /** * SubMenuItemList component to render a submenu with the sliding transition. For internal use only. */ export default defineComponent({ i18n: { messages }, name: 'sub-menu-item-list', components: { BentoDivider, MenuItem, MenuListContainer }, props: { items: { type: Array as PropType<Array<BentoMenuItem>>, required: true, }, title: { type: String, required: true, }, }, emits: [BentoMenuEvent.CLOSE, BentoMenuEvent.BACK], setup(props, { emit }) { const subMenuItemListRefLocal = ref(null); const subMenuFocusedIndex = ref(0); const numberOfItems = ref(props.items.length); const closeMenu = () => emit(BentoMenuEvent.CLOSE); const onBackButton = () => emit(BentoMenuEvent.BACK); const computedItems = computed(() => { const items = props.items?.map(el => ({ ...el, indent: !el.icon, isTitle: false, clickHandler: closeMenu, })); return [ { text: props.title, secondaryText: null, critical: false, icon: ChevronLeftIcon, isTitle: true, disabled: false, indent: false, link: undefined, handler: onBackButton, clickHandler: onBackButton, addDivider: true, }, ...items, ]; }); const focusItem = async () => { subMenuItemListRefLocal.value?.children?.[subMenuFocusedIndex.value]?.children?.[0]?.focus(); }; onMounted(async () => { // Using setTimeout otherwise the sliding animation doesn't work anymore. The timeout imer is the same as the transition time (0.15s) setTimeout(() => focusItem(), 150); }); const focusPrevious = () => { if (subMenuFocusedIndex.value > 0) { subMenuFocusedIndex.value -= 1; if (computedItems.value[subMenuFocusedIndex.value].disabled) { focusPrevious(); } } else { subMenuFocusedIndex.value = numberOfItems.value; } focusItem(); }; const focusNext = () => { if (subMenuFocusedIndex.value < numberOfItems.value) { subMenuFocusedIndex.value += 1; if (computedItems.value[subMenuFocusedIndex.value].disabled) { focusNext(); } } else { subMenuFocusedIndex.value = 0; } focusItem(); }; return { // Enums BentoMenuEvent, // Values subMenuItemListRefLocal, subMenuFocusedIndex, ChevronLeftIcon, computedItems, // Methods closeMenu, onBackButton, focusPrevious, focusNext, }; }, }); </script> <style lang="scss" scoped src="./sub-menu-item-list.scss" />
@@ -24,6 +24,9 @@ styling of the menu button trigger.
24
24
 
25
25
  The `bento-menu` component offers two distinct icon configurations.
26
26
 
27
+ > **Icon Requirement:** All icon properties (`icon`, `iconLeft`, `iconRight`) strictly accept components from
28
+ > `@adyen/ui-assets-icons-16` only. Font icons, custom SVGs, and external libraries are prohibited.
29
+
27
30
  - **Icon-Only:** To use this variant, provide an icon directly to the `iconLeft` slot and **omit the default slot**.
28
31
 
29
32
  - **Menu with Text:** This variant is automatically activated when you provide content to the **default slot**. For this
@@ -62,6 +65,12 @@ field to `true` in the corresponding `menu-item` object in the data array.
62
65
  Renders a divider after the `menu-item` to separate it from other content. To enable this, set `addDivider` to `true` in
63
66
  the corresponding `menu-item` object in the data array.
64
67
 
68
+ #### Link
69
+
70
+ A menu item can be rendered as a link instead of a button. To do this, set the `link` field on the item, which is
71
+ forwarded to the internal `BentoLink` component. If `handler` is additionally provided, it will still be called
72
+ alongside link navigation. See [BentoLink](../?path=/docs/components-link--docs) for the properties it accepts.
73
+
65
74
  ## Modifiers
66
75
 
67
76
  ### Menu Item
@@ -1 +1 @@
1
- import { storybookDocsParameter } from '@/utils/ts/storybook'; import { action } from '@storybook/addon-actions'; import { isVue2, ref } from 'vue-demi'; import BentoMenu from './menu.vue'; import CodeIcon from '@adyen/ui-assets-icons-16/vue/code-2'; import PeopleIcon from '@adyen/ui-assets-icons-16/vue/4-people'; import ChevronDownIcon from '@adyen/ui-assets-icons-16/vue/chevron-down'; import PlusIcon from '@adyen/ui-assets-icons-16/vue/plus'; import { BentoMenuEvent } from './menu.types'; import { type Meta, type StoryObj } from '@storybook/vue'; const MENU_DATA = [ { text: 'Menu entry', secondaryText: 'Text', handler: () => { // Your code here action('click')('Menu 1'); }, critical: true, icon: CodeIcon, }, { text: 'Menu entry 2 extra long text', secondaryText: 'Disabled', handler: () => { // Your code here action('click')('Menu 2'); }, disabled: true, addDivider: true, }, { text: 'Menu entry 3', handler: () => { // Your code here action('click')('Menu 3'); }, icon: PeopleIcon, items: [ { text: 'Submenu entry', secondaryText: 'Subtext', handler: () => { // Your code here action('click')('Submenu 1'); }, }, { text: 'Submenu entry 2', handler: () => { // Your code here action('click')('Submenu 2'); }, }, ], }, { text: 'Menu entry 4', secondaryText: 'Text', handler: () => { // Your code here action('click')('Menu 4'); }, }, ]; const meta: Meta = { title: 'Menu', component: BentoMenu, argTypes: { 'menu-item-click': { name: 'menu-item-click', defaultValue: '', description: 'When a menu item that has no submenu is clicked', table: { type: { summary: '($event) => void' }, }, control: { disable: true }, }, }, }; export default meta; const defaultCode = ` <template> <bento-menu :data="menuData" /> </template> <script setup lang="ts"> import CodeIcon from '@adyen/ui-assets-icons-16/vue/code-2'; import PeopleIcon from '@adyen/ui-assets-icons-16/vue/4-people'; const menuData = [ { text: 'Menu entry', secondaryText: 'Text', handler: () => { // Your code here }, critical: true, icon: CodeIcon, }, { text: 'Menu entry 2 extra long text', secondaryText: 'Disabled entry', handler: () => { // Your code here }, disabled: true, }, { text: 'Menu entry 3', handler: () => { // Your code here }, icon: PeopleIcon, }, ]; </script> `; export const Default = { render: (_args, { argTypes }) => ({ components: { BentoMenu }, props: Object.keys(argTypes), template: ` <bento-menu v-bind="args" @open="open" @close="close" /> `, setup(props) { return { args: isVue2 ? props : _args, [BentoMenuEvent.OPEN]: action(BentoMenuEvent.OPEN), [BentoMenuEvent.CLOSE]: action(BentoMenuEvent.CLOSE), }; }, }), args: { data: MENU_DATA, button: { variant: 'secondary', }, }, parameters: storybookDocsParameter(defaultCode), }; const withIconAndTextCode = ` <template> <bento-menu :data="menuData"> <template #iconLeft> <plus-icon /> </template> Open menu </bento-menu> </template> <script setup lang="ts"> import CodeIcon from '@adyen/ui-assets-icons-16/vue/code-2'; import PeopleIcon from '@adyen/ui-assets-icons-16/vue/4-people'; import PlusIcon from '@adyen/ui-assets-icons-16/vue/plus'; const menuData = [ { text: 'Menu entry', secondaryText: 'Text', handler: () => { // Your code here console.log('Menu entry 1 clicked'); }, critical: true, icon: CodeIcon, }, { text: 'Menu entry 2 extra long text', secondaryText: 'Disabled entry', handler: () => { // Your code here }, disabled: true, }, { text: 'Menu entry 3', handler: () => { // Your code here console.log('Menu entry 3 clicked'); }, icon: PeopleIcon, }, ]; </script> `; export const WithIconAndText: StoryObj = { ...Default, render: (_args, { argTypes }) => ({ components: { BentoMenu, CodeIcon, PlusIcon, ChevronDownIcon }, props: Object.keys(argTypes), template: ` <bento-menu v-bind="args" @open="open" @close="close"> <template #iconLeft> <plus-icon /> </template> Open menu </bento-menu> `, setup(props) { const isOpen = ref(false); const toggleMenuOpenText = () => { isOpen.value = !isOpen.value; }; return { // Values args: isVue2 ? props : _args, isOpen, // Methods [BentoMenuEvent.OPEN]: () => { toggleMenuOpenText(); action(BentoMenuEvent.OPEN)(); }, [BentoMenuEvent.CLOSE]: () => { toggleMenuOpenText(); action(BentoMenuEvent.CLOSE)(); }, }; }, }), parameters: storybookDocsParameter(withIconAndTextCode), };
1
+ import { storybookDocsParameter } from '@/utils/ts/storybook'; import { action } from '@storybook/addon-actions'; import { isVue2, ref } from 'vue-demi'; import BentoMenu from './menu.vue'; import CodeIcon from '@adyen/ui-assets-icons-16/vue/code-2'; import PeopleIcon from '@adyen/ui-assets-icons-16/vue/4-people'; import ChevronDownIcon from '@adyen/ui-assets-icons-16/vue/chevron-down'; import PlusIcon from '@adyen/ui-assets-icons-16/vue/plus'; import PlusMultipleIcon from '@adyen/ui-assets-icons-16/vue/plus-multiple'; import HandRockIcon from '@adyen/ui-assets-icons-16/vue/hand-rock'; import { BentoMenuEvent } from './menu.types'; import { type Meta, type StoryObj } from '@storybook/vue'; const MenuDefaultExample = "<template>\n <bento-menu :data=\"menuData\" teleport />\n</template>\n<script setup lang=\"ts\">\n import { BentoMenu, type BentoMenuItem } from '@adyen/bento-vue2';\n import CodeIcon from '@adyen/ui-assets-icons-16/vue/code-2';\n import PlusMultipleIcon from '@adyen/ui-assets-icons-16/vue/plus-multiple';\n import PeopleIcon from '@adyen/ui-assets-icons-16/vue/4-people';\n import HandRockIcon from '@adyen/ui-assets-icons-16/vue/hand-rock';\n\n const menuData: Array<BentoMenuItem> = [\n {\n text: 'Menu entry',\n secondaryText: 'Text',\n handler: () => {\n // Your code here\n },\n critical: true,\n icon: CodeIcon,\n },\n {\n text: 'Menu entry 2 disabled extra long text',\n secondaryText: 'Secondary text',\n handler: () => {\n // Your code here\n },\n icon: CodeIcon,\n disabled: true,\n },\n {\n text: 'Open in a new tab',\n link: { to: 'https://www.adyen.com/', external: true },\n icon: PlusMultipleIcon,\n },\n {\n text: 'Disabled external link',\n link: { to: 'https://www.adyen.com/', external: true },\n disabled: true,\n icon: PlusMultipleIcon,\n addDivider: true,\n },\n {\n text: 'Menu entry 3',\n handler: () => {\n // Your code here\n },\n icon: PeopleIcon,\n items: [\n {\n text: 'Open in a new tab',\n link: { to: 'https://www.adyen.com/', external: true },\n },\n {\n text: 'Regular action',\n secondaryText: 'Text',\n handler: () => {\n // Your code here\n },\n icon: HandRockIcon,\n },\n ],\n },\n {\n text: 'Regular action',\n handler: () => {\n // Your code here\n },\n secondaryText: 'Text',\n icon: HandRockIcon,\n },\n ];\n</script>\n"; const MenuWithIconAndTextExample = "<template>\n <bento-menu :button=\"{ variant: 'secondary' }\" :data=\"menuData\" teleport>\n <template #iconLeft>\n <plus-icon />\n </template>\n Open menu\n </bento-menu>\n</template>\n\n<script setup lang=\"ts\">\n import { BentoMenu, type BentoMenuItem } from '@adyen/bento-vue2';\n import CodeIcon from '@adyen/ui-assets-icons-16/vue/code-2';\n import PeopleIcon from '@adyen/ui-assets-icons-16/vue/4-people';\n import HandRockIcon from '@adyen/ui-assets-icons-16/vue/hand-rock';\n import PlusIcon from '@adyen/ui-assets-icons-16/vue/plus';\n import PlusMultipleIcon from '@adyen/ui-assets-icons-16/vue/plus-multiple';\n\n const menuData: Array<BentoMenuItem> = [\n {\n text: 'Menu entry',\n secondaryText: 'Text',\n handler: () => undefined,\n critical: true,\n icon: CodeIcon,\n },\n {\n text: 'Menu entry disabled extra long text',\n secondaryText: 'Secondary text',\n icon: CodeIcon,\n disabled: true,\n },\n {\n text: 'Open in a new tab',\n link: { to: 'https://www.adyen.com/', external: true },\n icon: PlusMultipleIcon,\n },\n {\n text: 'Disabled external link',\n link: { to: 'https://www.adyen.com/', external: true },\n disabled: true,\n icon: PlusMultipleIcon,\n addDivider: true,\n },\n {\n text: 'Menu entry 3',\n icon: PeopleIcon,\n items: [\n {\n text: 'Open in a new tab',\n link: { to: 'https://www.adyen.com/', external: true },\n },\n {\n text: 'Regular action',\n secondaryText: 'Text',\n handler: () => undefined,\n icon: HandRockIcon,\n },\n ],\n },\n {\n text: 'Regular action',\n secondaryText: 'Text',\n handler: () => undefined,\n icon: HandRockIcon,\n },\n ];\n</script>\n"; const MENU_DATA = [ { text: 'Menu entry', secondaryText: 'Text', handler: () => { // Your code here action('click')('Menu entry action'); }, critical: true, icon: CodeIcon, }, { text: 'Menu entry disabled extra long text', secondaryText: 'Secondary text', handler: () => { // Your code here action('click')('Menu entry disabled extra long text action'); }, icon: CodeIcon, disabled: true, }, { text: 'Open in a new tab', link: { to: 'https://www.adyen.com/', external: true }, icon: PlusMultipleIcon, }, { text: 'Disabled external link', link: { to: 'https://www.adyen.com/', external: true }, disabled: true, icon: PlusMultipleIcon, addDivider: true, }, { text: 'Menu entry 3', handler: () => { // Your code here action('click')('Menu entry 3 action'); }, icon: PeopleIcon, items: [ { text: 'Open in a new tab', link: { to: 'https://www.adyen.com/', external: true }, }, { text: 'Regular action', secondaryText: 'Text', handler: () => { // Your code here action('click')('Submenu regular action'); }, icon: HandRockIcon, }, ], }, { text: 'Regular action', secondaryText: 'Text', handler: () => { // Your code here action('click')('Regular action'); }, icon: HandRockIcon, }, ]; const meta: Meta = { title: 'Menu', component: BentoMenu, argTypes: { 'menu-item-click': { name: 'menu-item-click', defaultValue: '', description: 'When a menu item that has no submenu is clicked', table: { type: { summary: '($event) => void' }, }, control: { disable: true }, }, }, }; export default meta; export const Default = { render: (_args, { argTypes }) => ({ components: { BentoMenu }, props: Object.keys(argTypes), template: ` <bento-menu v-bind="args" @open="open" @close="close" /> `, setup(props) { return { args: isVue2 ? props : _args, [BentoMenuEvent.OPEN]: action(BentoMenuEvent.OPEN), [BentoMenuEvent.CLOSE]: action(BentoMenuEvent.CLOSE), }; }, }), args: { data: MENU_DATA, button: { variant: 'secondary', }, teleport: true, }, parameters: storybookDocsParameter(MenuDefaultExample), }; export const WithIconAndText: StoryObj = { ...Default, render: (_args, { argTypes }) => ({ components: { BentoMenu, CodeIcon, PlusIcon, ChevronDownIcon }, props: Object.keys(argTypes), template: ` <bento-menu v-bind="args" @open="open" @close="close"> <template #iconLeft> <plus-icon /> </template> Open menu </bento-menu> `, setup(props) { const isOpen = ref(false); const toggleMenuOpenText = () => { isOpen.value = !isOpen.value; }; return { // Values args: isVue2 ? props : _args, isOpen, // Methods [BentoMenuEvent.OPEN]: () => { toggleMenuOpenText(); action(BentoMenuEvent.OPEN)(); }, [BentoMenuEvent.CLOSE]: () => { toggleMenuOpenText(); action(BentoMenuEvent.CLOSE)(); }, }; }, }), parameters: storybookDocsParameter(MenuWithIconAndTextExample), };
@@ -1 +1 @@
1
- import { type OnClickOutsideOptions } from '@/directives/click-outside/types'; import { PopperContainerPosition } from '../popper-container/popper-container.types'; import { type BentoMenuItem } from './components/menu-item/menu-item.types'; import { type HTMLAttributes } from 'vue'; import { type BentoButtonProps } from '../button/button.types'; /** * @deprecated Since v2.0.0. Use string literal instead. */ const BentoMenuPositions = { ...PopperContainerPosition }; type BentoMenuPositions = PopperContainerPosition; export type * from './components/menu-item/menu-item.types'; export enum BentoMenuEvent { BACK = 'back', CLOSE = 'close', OPEN = 'open', } export interface BentoMenuButtonProps { 'aria-label'?: HTMLAttributes['aria-label']; condensed?: boolean; disabled?: boolean; variant?: BentoButtonProps['variant']; } export interface BentoMenuProps { /** * Object containing props to change the menu button appearance. Accepts the same props present in BentoButton. */ button?: BentoMenuButtonProps; /** * Controls if the menu should close when an item is clicked. * Defaults to `true`. */ closeMenuOnItemSelect?: boolean; /** * The data to be displayed in the list */ data: Array<BentoMenuItem>; /** * Disables opening the menu when the down arrow key is pressed. */ disableArrowDownOpeningMenu?: boolean; /** * The position of where the menu should open. */ menuPosition?: BentoMenuPositions | `${BentoMenuPositions}`; /** * Instructs the menu popover to be visible outside of the containing container. * @see {@link https://floating-ui.com/docs/usefloating#strategy} */ menuFixedPositioning?: boolean; /** * Sets a custom width for the menu popover. Values are clamped between 150 - 350. */ menuWidth?: number; /** * Returns focus to the menu button after popover is closed. * Disable if focus management is delegated to the parent component. * Defaults to `true`. */ revertFocusToButton?: boolean; /** * Can be used to set the inner teleport component to render the menu's popover in the root `body` DOM node * (or `dialog` if popover is inside a dialog). */ teleport?: boolean; /** * Can be used to set the inner teleport component to render the menu's popover in the root `body` DOM node * (or `dialog` if popover is inside a dialog). * * @see {@link OnClickOutsideOptions} */ closeOnClickOutsideOptions?: OnClickOutsideOptions; }
1
+ import { type OnClickOutsideOptions } from '@/directives/click-outside/types'; import { PopperContainerPosition } from '../popper-container/popper-container.types'; import { type BentoMenuItem } from './components/menu-item/menu-item.types'; import { type HTMLAttributes } from 'vue'; import { type BentoButtonProps } from '../button/button.types'; /** * @deprecated Since v3.0.0. Use string literal instead. */ const BentoMenuPositions = { ...PopperContainerPosition }; type BentoMenuPositions = PopperContainerPosition; export type * from './components/menu-item/menu-item.types'; export enum BentoMenuEvent { BACK = 'back', CLOSE = 'close', OPEN = 'open', } export interface BentoMenuButtonProps { 'aria-label'?: HTMLAttributes['aria-label']; condensed?: boolean; disabled?: boolean; variant?: BentoButtonProps['variant']; } export interface BentoMenuProps { /** * Object containing props to change the menu button appearance. Accepts the same props present in BentoButton. */ button?: BentoMenuButtonProps; /** * Controls if the menu should close when an item is clicked. * Defaults to `true`. */ closeMenuOnItemSelect?: boolean; /** * The data to be displayed in the list */ data: Array<BentoMenuItem>; /** * Disables opening the menu when the down arrow key is pressed. */ disableArrowDownOpeningMenu?: boolean; /** * The position of where the menu should open. */ menuPosition?: BentoMenuPositions | `${BentoMenuPositions}`; /** * Instructs the menu popover to be visible outside of the containing container. * @see {@link https://floating-ui.com/docs/usefloating#strategy} */ menuFixedPositioning?: boolean; /** * Sets a custom width for the menu popover. Values are clamped between 150 - 350. */ menuWidth?: number; /** * Returns focus to the menu button after popover is closed. * Disable if focus management is delegated to the parent component. * Defaults to `true`. */ revertFocusToButton?: boolean; /** * Can be used to set the inner teleport component to render the menu's popover in the root `body` DOM node * (or `dialog` if popover is inside a dialog). */ teleport?: boolean; /** * Can be used to set the inner teleport component to render the menu's popover in the root `body` DOM node * (or `dialog` if popover is inside a dialog). * * @see {@link OnClickOutsideOptions} */ closeOnClickOutsideOptions?: OnClickOutsideOptions; }
@@ -1 +1 @@
1
- <template> <div :class="computedClasses" class="b-menu" data-testid="menu" @keydown.down="onKeyDown" @keydown.up="onKeyUp" @keydown.esc="closeMenu()" > <bento-toggle-button ref="menuButtonRef" class="b-menu__toggle-button" aria-haspopup="true" v-bind="computedButtonPropsWithDefaults" :aria-label="computedMenuButtonAriaLabel(hasSlot('default'))" :aria-controls="showMenu ? menuId : null" :toggled="showMenu" :inverse="isInverseColorSet" @click="toggleShowMenu" > <template v-if="!hasSlot('default') || hasSlot('iconLeft')" #iconLeft> <slot name="iconLeft"> <options-vertical-icon aria-hidden="true" /> </slot> </template> <template v-if="hasSlot('default')" #default> <slot /> </template> <template v-if="hasSlot('default')" #iconRight> <chevron-down-icon /> </template> </bento-toggle-button> <bento-teleport v-if="menuButtonRef" :disabled="!teleport"> <bento-popover v-if="menuButtonRef" :id="menuId" ref="popoverRef" :role="null" :open="showMenu" class="b-menu__popover" :style="computedMenuWidth" :target-element="menuButtonRef" :position="menuPosition" :fixed-positioning="menuFixedPositioning" :fallback-position="[ 'bottom-start', 'bottom-end', 'top-start', 'top-end', 'right-start', 'right-end', 'left-start', 'left-end', ]" :teleport="teleport" without-space > <menu-item-list ref="menuItemListRef" :close-menu-on-item-select="closeMenuOnItemSelect" :data="data" :focus-last-element="isLastElementFocused" :popover-width="menuWidth" @close="closeMenu" @menu-item-click="menuItemClick" /> </bento-popover> </bento-teleport> </div> </template> <script setup lang="ts"> import BentoToggleButton from '@/components/button/components/toggle-button/toggle-button.vue'; import { DIVIDER_INVERSE_INJECTION_KEY } from '@/components/divider/divider.keys'; import BentoPopover from '@/components/popover/popover.vue'; import { useClickOutside, useHasSlot } from '@/composables'; import { generateUid } from '@/core/utils/ts'; import { BentoTeleport } from '@/internal/teleport'; import { useI18n } from '@/utils/ts/i18n'; import { printDevelopmentWarning } from '@/utils/ts/print-development-warning'; import ChevronDownIcon from '@adyen/ui-assets-icons-16/vue/chevron-down'; import OptionsVerticalIcon from '@adyen/ui-assets-icons-16/vue/options-vertical'; import { computed, inject, nextTick, onMounted, provide, ref, useSlots } from 'vue'; import MenuItemList from './components/menu-item-list/menu-item-list.vue'; import { MENU_INVERSE_INJECTION_KEY } from './menu.keys'; import { clampMenuWidth } from './composables/use-dynamic-menu-size/use-dynamic-menu-size'; import { type BentoMenuButtonProps, BentoMenuEvent, type BentoMenuProps } from './menu.types'; import messages from './messages.json'; type MessageSchema = (typeof messages)['en-US']; const props = withDefaults(defineProps<BentoMenuProps>(), { button: () => ({ variant: 'secondary', }) as BentoMenuButtonProps, closeMenuOnItemSelect: true, disableArrowDownOpeningMenu: false, menuPosition: 'bottom-start', menuFixedPositioning: false, menuWidth: null, revertFocusToButton: true, teleport: false, closeOnClickOutsideOptions: undefined, }); const emit = defineEmits<{ (e: BentoMenuEvent.CLOSE): void; (e: BentoMenuEvent.OPEN): void; (e: 'menu-item-click'): void; (e: 'click-outside'): void; }>(); const slots = useSlots(); const hasSlot = useHasSlot(slots); const { t } = useI18n<{ message: MessageSchema }>({ messages }); const menuButtonRef = ref(null); const menuItemListRef = ref(null); const showMenu = ref(false); const isLastElementFocused = ref(false); const popoverRef = ref<HTMLElement>(null); const menuId = generateUid('menu'); const isInverseColorSet = inject(MENU_INVERSE_INJECTION_KEY, ref(false)); provide(DIVIDER_INVERSE_INJECTION_KEY, isInverseColorSet); const computedClasses = computed(() => ({ 'b-menu--inverse': isInverseColorSet.value, })); const computedMenuWidth = computed(() => props.menuWidth ? { width: `${clampMenuWidth(props.menuWidth)}px` } : null ); const computedMenuButtonAriaLabel = (hasDefaultSlot: boolean) => { if (props.button?.['aria-label']) { return props.button?.['aria-label']; } if (!hasDefaultSlot) { return t('menuButton'); } return undefined; }; const computedButtonPropsWithDefaults = computed<BentoMenuButtonProps>(() => { if (!props?.button) { return undefined; } return { variant: 'secondary', ...props.button, }; }); const openMenu = () => { showMenu.value = true; emit(BentoMenuEvent.OPEN); }; const closeMenu = async (revertFocus = props.revertFocusToButton) => { if (!showMenu.value) { return; } showMenu.value = false; if (revertFocus) { // ensure that menu is closed and re-rendering is completed before focusing await nextTick(); menuButtonRef.value?.$el?.focus(); } emit(BentoMenuEvent.CLOSE); }; const toggleShowMenu = (e: MouseEvent) => { e.stopPropagation(); if (!showMenu.value) { openMenu(); } else { closeMenu(); } }; const onKeyDown = (event: KeyboardEvent) => { event.preventDefault(); isLastElementFocused.value = false; if (!props.disableArrowDownOpeningMenu) { openMenu(); } }; const onKeyUp = (event: KeyboardEvent) => { event.preventDefault(); isLastElementFocused.value = true; openMenu(); }; const menuItemClick = () => { emit('menu-item-click'); }; // Ignore the events inside the Button and the Popover useClickOutside( popoverRef, () => { closeMenu(); emit('click-outside'); }, { ...props.closeOnClickOutsideOptions, ignore: [menuButtonRef, ...(props.closeOnClickOutsideOptions?.ignore ?? [])], } ); defineExpose({ /** * Control to enable programmatic closing of the menu */ closeMenu, }); onMounted(() => { if (hasSlot('iconRight')) { printDevelopmentWarning( '"bento-menu" no longer supports the "iconRight" slot. The right-aligned icon is now fixed by design.' ); } }); </script> <script lang="ts"> /** * Menu component to render a series of buttons. Csan render nested submenus. * * @example * import { BentoMenu } from '@adyen/bento-vue2'; * * export default { * components: { BentoMenu }, * template: ` * <bento-menu * :data=[ * { text: 'Menu item text', handler: () => { some code here } }, * { text: 'Another menu item text', handler: () => { some other code here }, critical: true } * ] * /> * } */ export default { i18n: { messages }, name: 'bento-menu', }; </script> <style lang="scss" scoped src="./menu.scss" />
1
+ <template> <div :class="computedClasses" class="b-menu" data-testid="menu" @keydown.down="onKeyDown" @keydown.up="onKeyUp" @keydown.esc="closeMenu()" > <bento-toggle-button ref="menuButtonRef" class="b-menu__toggle-button" aria-haspopup="true" v-bind="computedButtonPropsWithDefaults" :aria-label="computedMenuButtonAriaLabel(hasSlot('default'))" :aria-controls="showMenu ? menuId : null" :toggled="showMenu" :inverse="isInverseColorSet" @click="toggleShowMenu" > <template v-if="!hasSlot('default') || hasSlot('iconLeft')" #iconLeft> <slot name="iconLeft"> <options-vertical-icon aria-hidden="true" /> </slot> </template> <template v-if="hasSlot('default')" #default> <slot /> </template> <template v-if="hasSlot('default')" #iconRight> <chevron-down-icon /> </template> </bento-toggle-button> <bento-teleport v-if="menuButtonRef" :disabled="!teleport"> <bento-popover v-if="menuButtonRef" :id="menuId" ref="popoverRef" :role="null" :open="showMenu" class="b-menu__popover" :style="computedMenuWidth" :target-element="menuButtonRef" :position="menuPosition" :fixed-positioning="menuFixedPositioning" :fallback-position="[ 'bottom-start', 'bottom-end', 'top-start', 'top-end', 'right-start', 'right-end', 'left-start', 'left-end', ]" :teleport="teleport" without-space > <menu-item-list ref="menuItemListRef" :close-menu-on-item-select="closeMenuOnItemSelect" :data="data" :focus-last-element="isLastElementFocused" :popover-width="menuWidth" @close="closeMenu" @menu-item-click="menuItemClick" /> </bento-popover> </bento-teleport> </div> </template> <script setup lang="ts"> import BentoToggleButton from '@/components/button/components/toggle-button/toggle-button.vue'; import { DIVIDER_INVERSE_INJECTION_KEY } from '@/components/divider/divider.keys'; import BentoPopover from '@/components/popover/popover.vue'; import { useClickOutside, useHasSlot } from '@/composables'; import { generateUid } from '@/utils/ts/generate-uid'; import { BentoTeleport } from '@/internal/teleport'; import { useI18n } from '@/utils/ts/i18n'; import { printDevelopmentWarning } from '@/utils/ts/print-development-warning'; import ChevronDownIcon from '@adyen/ui-assets-icons-16/vue/chevron-down'; import OptionsVerticalIcon from '@adyen/ui-assets-icons-16/vue/options-vertical'; import { computed, inject, nextTick, onMounted, provide, ref, useSlots } from 'vue'; import MenuItemList from './components/menu-item-list/menu-item-list.vue'; import { MENU_INVERSE_INJECTION_KEY } from './menu.keys'; import { clampMenuWidth } from './composables/use-dynamic-menu-size/use-dynamic-menu-size'; import { type BentoMenuButtonProps, BentoMenuEvent, type BentoMenuProps } from './menu.types'; import messages from './messages.json'; type MessageSchema = (typeof messages)['en-US']; const props = withDefaults(defineProps<BentoMenuProps>(), { button: () => ({ variant: 'secondary', }) as BentoMenuButtonProps, closeMenuOnItemSelect: true, disableArrowDownOpeningMenu: false, menuPosition: 'bottom-start', menuFixedPositioning: false, menuWidth: null, revertFocusToButton: true, teleport: false, closeOnClickOutsideOptions: undefined, }); const emit = defineEmits<{ (e: BentoMenuEvent.CLOSE): void; (e: BentoMenuEvent.OPEN): void; (e: 'menu-item-click'): void; (e: 'click-outside'): void; }>(); const slots = useSlots(); const hasSlot = useHasSlot(slots); const { t } = useI18n<{ message: MessageSchema }>({ messages }); const menuButtonRef = ref(null); const menuItemListRef = ref(null); const showMenu = ref(false); const isLastElementFocused = ref(false); const popoverRef = ref<HTMLElement>(null); const menuId = generateUid('menu'); const isInverseColorSet = inject(MENU_INVERSE_INJECTION_KEY, ref(false)); provide(DIVIDER_INVERSE_INJECTION_KEY, isInverseColorSet); const computedClasses = computed(() => ({ 'b-menu--inverse': isInverseColorSet.value, })); const computedMenuWidth = computed(() => props.menuWidth ? { width: `${clampMenuWidth(props.menuWidth)}px` } : null ); const computedMenuButtonAriaLabel = (hasDefaultSlot: boolean) => { if (props.button?.['aria-label']) { return props.button?.['aria-label']; } if (!hasDefaultSlot) { return t('menuButton'); } return undefined; }; const computedButtonPropsWithDefaults = computed<BentoMenuButtonProps>(() => { if (!props?.button) { return undefined; } return { variant: 'secondary', ...props.button, }; }); const openMenu = () => { showMenu.value = true; emit(BentoMenuEvent.OPEN); }; const closeMenu = async (revertFocus = props.revertFocusToButton) => { if (!showMenu.value) { return; } showMenu.value = false; if (revertFocus) { // ensure that menu is closed and re-rendering is completed before focusing await nextTick(); menuButtonRef.value?.$el?.focus(); } emit(BentoMenuEvent.CLOSE); }; const toggleShowMenu = (e: MouseEvent) => { e.stopPropagation(); if (!showMenu.value) { openMenu(); } else { closeMenu(); } }; const onKeyDown = (event: KeyboardEvent) => { event.preventDefault(); isLastElementFocused.value = false; if (!props.disableArrowDownOpeningMenu) { openMenu(); } }; const onKeyUp = (event: KeyboardEvent) => { event.preventDefault(); isLastElementFocused.value = true; openMenu(); }; const menuItemClick = () => { emit('menu-item-click'); }; // Ignore the events inside the Button and the Popover useClickOutside( popoverRef, () => { closeMenu(); emit('click-outside'); }, { ...props.closeOnClickOutsideOptions, ignore: [menuButtonRef, ...(props.closeOnClickOutsideOptions?.ignore ?? [])], } ); defineExpose({ /** * Control to enable programmatic closing of the menu */ closeMenu, }); onMounted(() => { if (hasSlot('iconRight')) { printDevelopmentWarning( '"bento-menu" no longer supports the "iconRight" slot. The right-aligned icon is now fixed by design.' ); } }); </script> <script lang="ts"> /** * Menu component to render a series of buttons. Csan render nested submenus. * * @example * import { BentoMenu } from '@adyen/bento-vue2'; * * export default { * components: { BentoMenu }, * template: ` * <bento-menu * :data=[ * { text: 'Menu item text', handler: () => { some code here } }, * { text: 'Another menu item text', handler: () => { some other code here }, critical: true } * ] * /> * } */ export default { i18n: { messages }, name: 'bento-menu', }; </script> <style lang="scss" scoped src="./menu.scss" />
@@ -1 +1 @@
1
- <template> <div class="b-modal-base"> <dialog ref="modal" :role="modalRole" :aria-label="computedAriaLabel" :style="{ height: height }" class="b-modal-base__container" :class="modalConditionalClasses" :open="initiallyOpen" @animationend="closeModalAfterAnimation" @keydown.esc="internalModalActionClose" > <bento-focus-trap v-if="isOpen || initiallyOpen" :id="modalId" v-bento-click-outside-directive="closeModalOnClickOutside" > <bento-button v-if="showCloseButton" class="b-modal-base__close-button" variant="tertiary" type="button" :style="customPaddingStyles" @click="internalModalActionClose" > <cross-icon :svg-title="t('close')" /> </bento-button> <template v-if="props.activePage"> <slot /> </template> <bento-modal-page v-else :with-column-layout="withColumnLayout" :size="size" :custom-padding="customPadding" :columns-size="columnsSize" > <template v-if="hasSlot('default')" #default> <slot></slot> </template> <template v-if="hasSlot('content-col-1')" #content-col-1 ><slot name="content-col-1"></slot ></template> <template v-if="hasSlot('content-col-2')" #content-col-2 ><slot name="content-col-2"></slot ></template> <template v-if="hasSlot('content')" #content><slot name="content"></slot></template> <template v-if="hasSlot('actions')" #actions><slot name="actions"></slot></template> </bento-modal-page> </bento-focus-trap> </dialog> </div> </template> <script setup lang="ts"> import { computed, defineComponent, onMounted, type PropType, provide, ref, toRefs, useAttrs, useSlots, watch, } from 'vue'; import { BentoModalColumnLayoutVariant, type BentoModalConfigData, BentoModalRole, BentoModalSize, } from '@/components/modal/modal.types'; import { BentoButton, type BentoButtonActionsList } from '@/components/button'; import { BentoFocusTrap } from '@/components/focus-trap'; import { useHasSlot } from '@/composables'; import { useI18n } from '@/utils/ts/i18n'; import { MODAL_DIALOG_ID_INJECTION_KEY } from '@/components/modal/components/base-modal/base-modal.keys'; import { generateUid } from '@/core/utils/ts'; import { BentoClickOutsideDirective as vBentoClickOutsideDirective } from '@/directives/click-outside'; import messages from './messages.json'; import { BentoModalPage } from '@/components/modal/components/modal-page'; import CrossIcon from '@adyen/ui-assets-icons-16/vue/cross'; import { MODAL_CONFIG_INJECTION_KEY } from '../../modal.keys'; import { BentoDialogPageAnimation, type BentoDialogPageConfigData, type BentoDialogPageRegisterPage, DIALOG_PAGE_CONFIG_INJECTION_KEY, DIALOG_PAGE_REGISTER_PAGE_INJECTION_KEY, } from '@/internal'; type MessageSchema = (typeof messages)['en-US']; const LOADING_BTN_CLOSE_AFTER_SUCCESS_DELAY_TIME = 500; const { t } = useI18n<{ message: MessageSchema }>({ messages }); const slots = useSlots(); const attrs = useAttrs(); const hasSlot = useHasSlot(slots); const modal = ref<HTMLDialogElement>(null); const isClosing = ref(false); const modalId = generateUid('modal'); const emit = defineEmits<{ /** * Emits update event when page is changed internally */ (e: 'update:active-page', page: string): void; /** * Emitted when the modal is closed. */ (e: 'close-modal'); }>(); const props = defineProps({ /** * List of actions that will be used to render the buttons. * First element in the list will be rendered as "primary". * All elements after the first will be rendered as "secondary". * But you may change the color of 1-st action to red with 'critial' key * Not more than 3 action buttons can be provided * Or 2 action buttons and 1 Label for them * * Each object in the list should have a `title`, an `event` and * (optional) a Vue `icon` from the library `@adyen/ui-assets-icons-16`. * * `title`: Button's text. * * `event`: Event triggered when clicking the Button. * * `icon`: Vue Icon from the library `@adyen/ui-assets-icons-16`. * * ``` * [{ * title: 'Primary',2 * event: () => { ... }, * icon: FourPeopleIcon * }, { * title: 'Secondary', * event: () => { ... } * }] * ``` */ actions: { type: Array as PropType<BentoButtonActionsList>, default: null, }, /** * The id of the current active page * Should be the same id as the BentoModalPage to be displayed */ activePage: { type: String, default: null, }, /** * If a column layout is provided, it renders the respective left column width variant. This can be smaller than the second column, the same size as the second column or bigger than the second column. */ columnsSize: { type: String as PropType<BentoModalColumnLayoutVariant>, default: BentoModalColumnLayoutVariant.SMALLER, }, /** * If provided, manages the padding of the whole modal container */ customPadding: { type: String, default: null }, /** * If passed - sets first button color to red */ destructiveActions: { type: Boolean, default: false }, /** * Hides a cross icon on the right side of the title */ hideCloseButton: { type: Boolean, required: true }, /** * Sets the open attribute on the dialog element, use isOpen instead to toggle modal visbility. */ initiallyOpen: { type: Boolean, default: false }, /** * If set to true, it allows the modal to be closed by clicking outside of it */ isDismissible: { type: Boolean, required: true }, /** * Controls the modal's state from the outside. Set it to true to open the modal */ isOpen: { type: Boolean, required: true }, /** * Defines whether the modal will be an alert modal */ modalRole: { type: String, default: BentoModalRole.DIALOG }, /** * Sets the width of the modal */ size: { type: String as PropType<BentoModalSize | `${BentoModalSize}`>, default: BentoModalSize.SMALL, }, /** * If provided, renders the modal's content in a two-column layout an wth dividers separating its content */ withColumnLayout: { type: Boolean, default: false }, }); const showCloseButton = computed(() => props.isDismissible && !props.hideCloseButton); const pageCustomPadding = ref(props.customPadding); const customPaddingStyles = computed(() => { if (!pageCustomPadding.value) { return {}; } return { top: pageCustomPadding.value, right: pageCustomPadding.value }; }); const computedAriaLabel = computed(() => (attrs['aria-label'] as string) ?? (t('ariaLabelFallback') as string)); const hasColumnLayout = computed(() => props.withColumnLayout && props.size === BentoModalSize.LARGE); const openModal = (): void => { modal.value.showModal(); }; const emitCloseEvent = (): void => { emit('close-modal'); }; // Handle closing and closing animation for dialog element const closeModalAfterAnimation = () => { if (isClosing.value && modal.value) { modal.value.close(); if (previousPages.value[0]) { emit('update:active-page', previousPages.value[0]); } previousPages.value = []; isClosing.value = false; emitCloseEvent(); } }; const startCloseModalAnimation = () => { if (!modal.value.open) { return; } isClosing.value = true; }; /** * If the user has pressed escape, clicked outside or clicked the close icon. * Needs to be called from outside, following the "dialog.close()" pattern, * when the closing event is triggered outside of the "base-modal" * component (e.g. from a closing action). */ const internalModalActionClose = (event?: Event) => { if (event) { event.preventDefault(); } if (!props.isDismissible && props.modalRole === BentoModalRole.DIALOG) { return; } startCloseModalAnimation(); }; const closeModalOnClickOutside = () => { if (props.isDismissible) { internalModalActionClose(); } }; const modalConditionalClasses = computed(() => ({ [`b-modal-base__container--${props.size}`]: props.size, 'b-modal-base__container--hide': isClosing.value, 'b-modal-base__container--column-layout': hasColumnLayout.value, 'b-modal-base__container--animation': isAnimationActive.value, })); watch( () => [props.isOpen, modal.value], ([newIsOpen, newModalValue], [, oldModalValue]): void => { if (!newModalValue) { return; } const isFirstMutation = !oldModalValue; if (newIsOpen) { openModal(); // add the first page to the array when the modal opens if (!previousPages.value.length) { previousPages.value.push(activePage.value); } } else if (!isFirstMutation) { startCloseModalAnimation(); } } ); // If the first button is a loading button then close the modal have 0.5 seconds // if the state is changed to 'success'. watch( () => props.actions, () => { if (props.actions?.[0]?.state === 'success') { setTimeout(startCloseModalAnimation, LOADING_BTN_CLOSE_AFTER_SUCCESS_DELAY_TIME); } }, { deep: true } ); onMounted(() => { if (props.isOpen && modal.value) { openModal(); } }); /** * Expose methods through its 'ref' */ defineExpose({ internalModalActionClose, }); // Let's Children components know they are inside a Modal (dialog element) provide(MODAL_DIALOG_ID_INJECTION_KEY, modalId); // Modal pagination const { activePage, isOpen, size } = toRefs(props); const animation = ref(BentoDialogPageAnimation.TRANSITION_LEVEL_DEEPER); const pages = ref<Array<string>>([]); const previousPages = ref([]); const updatePage = page => emit('update:active-page', page); // unregister page if removed / destroyed const unregisterPage = (pageIdToRemove: string) => () => { pages.value = pages.value?.filter(pageId => pageId !== pageIdToRemove); }; // Provide data and function to register page to subcomponent const registerPage: BentoDialogPageRegisterPage = pageId => { if (pageId) { pages.value.push(pageId); } return { unregisterPage: unregisterPage(pageId) }; }; const height = ref(''); const isAnimationActive = ref(false); provide<BentoModalConfigData>(MODAL_CONFIG_INJECTION_KEY, { activePage, height, isAnimationActive, pageCustomPadding, showCloseButton, size, }); provide<BentoDialogPageConfigData>(DIALOG_PAGE_CONFIG_INJECTION_KEY, { activePage, animation, isOpen, pages, previousPages, updatePage, }); provide(DIALOG_PAGE_REGISTER_PAGE_INJECTION_KEY, registerPage); </script> <script lang="ts"> /** * Dialog container. It creates a dialog HTML element for the modal to use. * * @example * import BaseModal from './components/base-modal/base-modal.vue'; * * export default { * components: { BaseModal }, * template: ` * <base-modal * :hide-close-button="true" * :is-dismissible="false" * :is-open="isOpen" * @close-modal="isOpen = false" * > * <template #content-col-1> * // Content on the left side column * </template> * * <template #content-col-2> * // Content on the right side column * </template> * * <template v-if="actions" #actions> * <footer-actions :actions="actions" /> * </template> * </base-modal> * `, * setup() { * const isOpen = ref(true) * const footerActions = [{ * title: 'Primary action', * event: () => { ... } * }] * } * } */ export default defineComponent({ i18n: { messages }, name: 'base-modal', }); </script> <style lang="scss" scoped src="./base-modal.scss" />
1
+ <template> <div v-bind="getRootAttributes($attrs)" class="b-modal-base"> <dialog ref="modal" :role="modalRole" :aria-label="$attrs?.['aria-label'] ?? t('ariaLabelFallback')" :style="{ height: height }" class="b-modal-base__container" :class="modalConditionalClasses" :open="initiallyOpen" @animationend="closeModalAfterAnimation" @keydown.esc="internalModalActionClose" > <bento-focus-trap v-if="isOpen || initiallyOpen" :id="modalId" v-bento-click-outside-directive="closeModalOnClickOutside" > <bento-button v-if="showCloseButton" class="b-modal-base__close-button" variant="tertiary" type="button" :style="customPaddingStyles" @click="internalModalActionClose" > <cross-icon :svg-title="t('close')" /> </bento-button> <template v-if="props.activePage"> <slot /> </template> <bento-modal-page v-else :with-column-layout="withColumnLayout" :size="size" :custom-padding="customPadding" :columns-size="columnsSize" > <template v-if="hasSlot('default')" #default> <slot></slot> </template> <template v-if="hasSlot('content-col-1')" #content-col-1 ><slot name="content-col-1"></slot ></template> <template v-if="hasSlot('content-col-2')" #content-col-2 ><slot name="content-col-2"></slot ></template> <template v-if="hasSlot('content')" #content><slot name="content"></slot></template> <template v-if="hasSlot('actions')" #actions><slot name="actions"></slot></template> </bento-modal-page> </bento-focus-trap> </dialog> </div> </template> <script setup lang="ts"> import { computed, defineComponent, onMounted, type PropType, provide, ref, toRefs, useSlots, watch } from 'vue'; import { BentoModalColumnLayoutVariant, BentoModalRole, BentoModalSize } from '@/components/modal/modal.types'; import { type BentoModalConfigData } from '@/components/modal/modal.internal.types'; import { BentoButton, type BentoButtonActionsList } from '@/components/button'; import { BentoFocusTrap } from '@/components/focus-trap'; import { useAttributeRouting, useHasSlot } from '@/composables'; import { useI18n } from '@/utils/ts/i18n'; import { MODAL_DIALOG_ID_INJECTION_KEY } from '@/components/modal/components/base-modal/base-modal.keys'; import { generateUid } from '@/utils/ts/generate-uid'; import { BentoClickOutsideDirective as vBentoClickOutsideDirective } from '@/directives/click-outside'; import messages from './messages.json'; import { BentoModalPage } from '@/components/modal/components/modal-page'; import CrossIcon from '@adyen/ui-assets-icons-16/vue/cross'; import { MODAL_CONFIG_INJECTION_KEY } from '../../modal.keys'; import { BentoDialogPageAnimation, type BentoDialogPageConfigData, type BentoDialogPageRegisterPage, DIALOG_PAGE_CONFIG_INJECTION_KEY, DIALOG_PAGE_REGISTER_PAGE_INJECTION_KEY, } from '@/internal'; type MessageSchema = (typeof messages)['en-US']; const LOADING_BTN_CLOSE_AFTER_SUCCESS_DELAY_TIME = 500; const { t } = useI18n<{ message: MessageSchema }>({ messages }); const { getRootAttributes } = useAttributeRouting(); const slots = useSlots(); const hasSlot = useHasSlot(slots); const modal = ref<HTMLDialogElement>(null); const isClosing = ref(false); const modalId = generateUid('modal'); const emit = defineEmits<{ /** * Emits update event when page is changed internally */ (e: 'update:active-page', page: string): void; /** * Emitted when the modal is closed. */ (e: 'close-modal'); }>(); const props = defineProps({ /** * List of actions that will be used to render the buttons. * First element in the list will be rendered as "primary". * All elements after the first will be rendered as "secondary". * But you may change the color of 1-st action to red with 'critial' key * Not more than 3 action buttons can be provided * Or 2 action buttons and 1 Label for them * * Each object in the list should have a `title`, an `event` and * (optional) a Vue `icon` from the library `@adyen/ui-assets-icons-16`. * * `title`: Button's text. * * `event`: Event triggered when clicking the Button. * * `icon`: Vue Icon from the library `@adyen/ui-assets-icons-16`. * * ``` * [{ * title: 'Primary',2 * event: () => { ... }, * icon: FourPeopleIcon * }, { * title: 'Secondary', * event: () => { ... } * }] * ``` */ actions: { type: Array as PropType<BentoButtonActionsList>, default: null, }, /** * The id of the current active page * Should be the same id as the BentoModalPage to be displayed */ activePage: { type: String, default: null, }, /** * If a column layout is provided, it renders the respective left column width variant. This can be smaller than the second column, the same size as the second column or bigger than the second column. */ columnsSize: { type: String as PropType<BentoModalColumnLayoutVariant>, default: BentoModalColumnLayoutVariant.SMALLER, }, /** * If provided, manages the padding of the whole modal container */ customPadding: { type: String, default: null }, /** * If passed - sets first button color to red */ destructiveActions: { type: Boolean, default: false }, /** * Hides a cross icon on the right side of the title */ hideCloseButton: { type: Boolean, required: true }, /** * Sets the open attribute on the dialog element, use isOpen instead to toggle modal visbility. */ initiallyOpen: { type: Boolean, default: false }, /** * If set to true, it allows the modal to be closed by clicking outside of it */ isDismissible: { type: Boolean, required: true }, /** * Controls the modal's state from the outside. Set it to true to open the modal */ isOpen: { type: Boolean, required: true }, /** * Defines whether the modal will be an alert modal */ modalRole: { type: String, default: BentoModalRole.DIALOG }, /** * Sets the width of the modal */ size: { type: String as PropType<BentoModalSize | `${BentoModalSize}`>, default: BentoModalSize.SMALL, }, /** * If provided, renders the modal's content in a two-column layout an wth dividers separating its content */ withColumnLayout: { type: Boolean, default: false }, }); const showCloseButton = computed(() => props.isDismissible && !props.hideCloseButton); const pageCustomPadding = ref(props.customPadding); const customPaddingStyles = computed(() => { if (!pageCustomPadding.value) { return {}; } return { top: pageCustomPadding.value, right: pageCustomPadding.value }; }); const hasColumnLayout = computed(() => props.withColumnLayout && props.size === BentoModalSize.LARGE); const openModal = (): void => { modal.value.showModal(); }; const emitCloseEvent = (): void => { emit('close-modal'); }; // Handle closing and closing animation for dialog element const closeModalAfterAnimation = () => { if (isClosing.value && modal.value) { modal.value.close(); if (previousPages.value[0]) { emit('update:active-page', previousPages.value[0]); } previousPages.value = []; isClosing.value = false; emitCloseEvent(); } }; const startCloseModalAnimation = () => { if (!modal.value.open) { return; } isClosing.value = true; }; /** * If the user has pressed escape, clicked outside or clicked the close icon. * Needs to be called from outside, following the "dialog.close()" pattern, * when the closing event is triggered outside of the "base-modal" * component (e.g. from a closing action). */ const internalModalActionClose = (event?: Event) => { if (event) { event.preventDefault(); } if (!props.isDismissible && props.modalRole === BentoModalRole.DIALOG) { return; } startCloseModalAnimation(); }; const closeModalOnClickOutside = () => { if (props.isDismissible) { internalModalActionClose(); } }; const modalConditionalClasses = computed(() => ({ [`b-modal-base__container--${props.size}`]: props.size, 'b-modal-base__container--hide': isClosing.value, 'b-modal-base__container--column-layout': hasColumnLayout.value, 'b-modal-base__container--animation': isAnimationActive.value, })); watch( () => [props.isOpen, modal.value], ([newIsOpen, newModalValue], [, oldModalValue]): void => { if (!newModalValue) { return; } const isFirstMutation = !oldModalValue; if (newIsOpen) { openModal(); // add the first page to the array when the modal opens if (!previousPages.value.length) { previousPages.value.push(activePage.value); } } else if (!isFirstMutation) { startCloseModalAnimation(); } } ); // If the first button is a loading button then close the modal have 0.5 seconds // if the state is changed to 'success'. watch( () => props.actions, () => { if (props.actions?.[0]?.state === 'success') { setTimeout(startCloseModalAnimation, LOADING_BTN_CLOSE_AFTER_SUCCESS_DELAY_TIME); } }, { deep: true } ); onMounted(() => { if (props.isOpen && modal.value) { openModal(); } }); /** * Expose methods through its 'ref' */ defineExpose({ internalModalActionClose, }); // Let's Children components know they are inside a Modal (dialog element) provide(MODAL_DIALOG_ID_INJECTION_KEY, modalId); // Modal pagination const { activePage, isOpen, size } = toRefs(props); const animation = ref(BentoDialogPageAnimation.TRANSITION_LEVEL_DEEPER); const pages = ref<Array<string>>([]); const previousPages = ref([]); const updatePage = page => emit('update:active-page', page); // unregister page if removed / destroyed const unregisterPage = (pageIdToRemove: string) => () => { pages.value = pages.value?.filter(pageId => pageId !== pageIdToRemove); }; // Provide data and function to register page to subcomponent const registerPage: BentoDialogPageRegisterPage = pageId => { if (pageId) { pages.value.push(pageId); } return { unregisterPage: unregisterPage(pageId) }; }; const height = ref(''); const isAnimationActive = ref(false); provide<BentoModalConfigData>(MODAL_CONFIG_INJECTION_KEY, { activePage, height, isAnimationActive, pageCustomPadding, showCloseButton, size, }); provide<BentoDialogPageConfigData>(DIALOG_PAGE_CONFIG_INJECTION_KEY, { activePage, animation, isOpen, pages, previousPages, updatePage, }); provide(DIALOG_PAGE_REGISTER_PAGE_INJECTION_KEY, registerPage); </script> <script lang="ts"> /** * Dialog container. It creates a dialog HTML element for the modal to use. * * @example * import BaseModal from './components/base-modal/base-modal.vue'; * * export default { * components: { BaseModal }, * template: ` * <base-modal * :hide-close-button="true" * :is-dismissible="false" * :is-open="isOpen" * @close-modal="isOpen = false" * > * <template #content-col-1> * // Content on the left side column * </template> * * <template #content-col-2> * // Content on the right side column * </template> * * <template v-if="actions" #actions> * <footer-actions :actions="actions" /> * </template> * </base-modal> * `, * setup() { * const isOpen = ref(true) * const footerActions = [{ * title: 'Primary action', * event: () => { ... } * }] * } * } */ export default defineComponent({ i18n: { messages }, name: 'base-modal', inheritAttrs: false, }); </script> <style lang="scss" scoped src="./base-modal.scss" />
@@ -1 +1 @@
1
- <template> <dialog-page ref="page" class="b-modal-page" :class="modalPageConditionalClasses" :page-id="pageId" :always-show-border="hasColumnLayout" :back-button-styling="customPaddingStyles?.backButton" :previous-page="previousPage" @animationend="isAnimationActive = false" > <template v-if="hasSlot('default') || showCloseButton" #header> <div class="b-modal-page__header-container" :style="customPaddingStyles.header"> <div class="b-modal-page__header"> <bento-typography class="b-modal-page__title" el="span" variant="title" stronger> <slot v-if="hasSlot('default')" /> </bento-typography> </div> </div> </template> <template #content> <div class="b-modal-page__content" :class="modalContentConditionalClasses" :style="customPaddingStyles.content" > <bento-typography v-if="hasSlot('content') || (hasSlot('content-col-1') && hasSlot('content-col-2'))" el="div" > <grid-layout v-if="hasColumnLayout" :column-width="computedColumnWidth" :container-type="GridLayoutContainerType.NORMAL" no-gap class="b-modal-page__grid" > <template #col-1><slot name="content-col-1"></slot></template> <template #col-2><slot name="content-col-2"></slot></template> </grid-layout> <slot v-else name="content" /> </bento-typography> </div> </template> <template v-if="hasSlot('actions')" #footer> <div class="b-modal-page__actions-container" :style="customPaddingStyles.actions"> <div aria-label="modal actions" class="b-modal-page__actions"> <slot name="actions" /> </div> </div> </template> <template v-else-if="actions && actions.length > 0" #footer> <div class="b-modal-page__actions-container" :style="customPaddingStyles.actions"> <footer-actions :actions="actions" :actions-label="actionsLabel" :destructive-actions="destructiveActions" /> </div> </template> </dialog-page> </template> <script setup lang="ts"> import { computed, defineComponent, inject, nextTick, onUnmounted, ref, useSlots, watch } from 'vue'; import { BentoModalColumnLayoutVariant, type BentoModalConfigData, BentoModalSize, } from '@/components/modal/modal.types'; import { BentoTypography } from '@/components/typography'; import { GridLayout, GridLayoutContainerType } from '@/internal'; import { useHasSlot } from '@/composables'; import { FooterActions } from '@/components/internal/footer-actions'; import DialogPage from '@/internal/dialog-page/dialog-page.vue'; import { MODAL_CONFIG_INJECTION_KEY } from '../../modal.keys'; import { observeSizeOfElement } from '@/utils/ts/resize'; import type { BentoModalPageProps } from './modal-page.types'; const CLOSE_BUTTON_ICON_WIDTH = 26; const slots = useSlots(); const hasSlot = useHasSlot(slots); const page = ref<InstanceType<typeof DialogPage>>(null); const props = withDefaults(defineProps<BentoModalPageProps>(), { actions: null, actionsLabel: null, columnsSize: BentoModalColumnLayoutVariant.SMALLER, customPadding: null, destructiveActions: false, withColumnLayout: false, pageId: undefined, previousPage: undefined, }); const modalPageConditionalClasses = computed(() => ({ 'b-modal-page--custom-padding': props.customPadding, })); const modalContentConditionalClasses = computed(() => ({ 'b-modal-page__content--with-header': hasSlot('default'), 'b-modal-page__content--with-actions': hasSlot('actions') || props.actions?.length, })); // Setting custom padding styles const customPaddingStyles = computed(() => { if (!props.customPadding) { return {}; } const xPadding = { paddingLeft: props.customPadding, paddingRight: props.customPadding, }; const content = { ...xPadding, paddingTop: undefined, paddingBottom: undefined }; if (!hasSlot('default')) { content.paddingTop = props.customPadding; } if (!hasSlot('actions')) { content.paddingBottom = props.customPadding; } return { header: { ...xPadding, paddingTop: props.customPadding, paddingRight: `${parseInt(props.customPadding, 10) + CLOSE_BUTTON_ICON_WIDTH}px`, flex: 1, }, content, actions: { ...xPadding, paddingBottom: props.customPadding }, backButton: { top: props.customPadding, paddingLeft: props.customPadding }, }; }); // Column layout styles const hasColumnLayout = computed(() => props.withColumnLayout && size.value === BentoModalSize.LARGE); const computedColumnWidth = computed(() => { if (props.columnsSize === BentoModalColumnLayoutVariant.SMALLER) { return [ { small: 4, medium: 4, large: 4 }, { small: 8, medium: 8, large: 8 }, ]; } if (props.columnsSize === BentoModalColumnLayoutVariant.BIGGER) { return [ { small: 8, medium: 8, large: 8 }, { small: 4, medium: 4, large: 4 }, ]; } return [ { small: 6, medium: 6, large: 6 }, { small: 6, medium: 6, large: 6 }, ]; }); // Register page to parent component and inject data from parent component const { activePage, height, isAnimationActive, pageCustomPadding, showCloseButton, size } = inject<BentoModalConfigData>(MODAL_CONFIG_INJECTION_KEY); const isCurrentPage = computed(() => activePage.value === props.pageId || !activePage.value); // Animation // Set resize observer on mount of update the height of the dialog on resize let pageElementObserver: ResizeObserver | null = null; // Watching activePage changes to set the height of the page and set isAnimationActive watch( () => activePage.value, async (value, oldValue) => { if (isCurrentPage.value) { await nextTick(); height.value = `${page.value?.$el?.clientHeight}px`; pageCustomPadding.value = props.customPadding; // only set isAnimationActive to true if its going from two different pages and not from null to the first page isAnimationActive.value = oldValue !== undefined; pageElementObserver = observeSizeOfElement(page.value, () => { const pageHeight = `${page.value?.$el?.clientHeight}px`; if (pageHeight && pageHeight !== height.value) { height.value = pageHeight; } }); } }, { immediate: true } ); // Disconnect observer on unmounting the page onUnmounted(() => { pageElementObserver?.disconnect(); }); </script> <script lang="ts"> /** * ModalPage is used to render different pages of the Modal component * * @example * import { BentoModal, BentoModalPage } from '@adyen/bento-vue2'; * * export default { * components: { BentoModal, BentoModalPage }, * template: ` * <bento-modal active-page='page1' :is-open='true'> * <bento-modal-page * pageId='page1' * actions-label='Move to next page' * :actions="[ * { * title: 'Next page', * event: () => { * activePage.value = 'page2'; * }, * }]"> * Page 1 * <template #content> * Page 1 content * </template> * </bento-modal-page> * <bento-modal-page * pageId='page2' * :actions="[ * { * title: 'Save', * event: () => {}, * }]"> * Page 2 * <template #content> * Page 2 content * </template> * </bento-modal-page> * </bento-modal> * ` * } */ export default defineComponent({}); </script> <style lang="scss" scoped src="./modal-page.scss" />
1
+ <template> <dialog-page ref="page" class="b-modal-page" :class="modalPageConditionalClasses" :page-id="pageId" :always-show-border="hasColumnLayout" :back-button-styling="customPaddingStyles?.backButton" :previous-page="previousPage" @animationend="isAnimationActive = false" > <template v-if="hasSlot('default') || showCloseButton" #header> <div class="b-modal-page__header-container" :style="customPaddingStyles.header"> <div class="b-modal-page__header"> <bento-typography class="b-modal-page__title" el="span" variant="title" stronger> <slot v-if="hasSlot('default')" /> </bento-typography> </div> </div> </template> <template #content> <div class="b-modal-page__content" :class="modalContentConditionalClasses" :style="customPaddingStyles.content" > <bento-typography v-if="hasSlot('content') || (hasSlot('content-col-1') && hasSlot('content-col-2'))" el="div" > <grid-layout v-if="hasColumnLayout" :column-width="computedColumnWidth" :container-type="GridLayoutContainerType.NORMAL" no-gap class="b-modal-page__grid" > <template #col-1><slot name="content-col-1"></slot></template> <template #col-2><slot name="content-col-2"></slot></template> </grid-layout> <slot v-else name="content" /> </bento-typography> </div> </template> <template v-if="hasSlot('actions')" #footer> <div class="b-modal-page__actions-container" :style="customPaddingStyles.actions"> <div aria-label="modal actions" class="b-modal-page__actions"> <slot name="actions" /> </div> </div> </template> <template v-else-if="actions && actions.length > 0" #footer> <div class="b-modal-page__actions-container" :style="customPaddingStyles.actions"> <footer-actions :actions="actions" :actions-label="actionsLabel" :destructive-actions="destructiveActions" /> </div> </template> </dialog-page> </template> <script setup lang="ts"> import { computed, defineComponent, inject, nextTick, onUnmounted, ref, useSlots, watch } from 'vue'; import { BentoModalColumnLayoutVariant, BentoModalSize } from '@/components/modal/modal.types'; import { type BentoModalConfigData } from '@/components/modal/modal.internal.types'; import { BentoTypography } from '@/components/typography'; import { GridLayout, GridLayoutContainerType } from '@/internal'; import { useHasSlot } from '@/composables'; import { FooterActions } from '@/components/internal/footer-actions'; import DialogPage from '@/internal/dialog-page/dialog-page.vue'; import { MODAL_CONFIG_INJECTION_KEY } from '../../modal.keys'; import { observeSizeOfElement } from '@/utils/ts/resize'; import type { BentoModalPageProps } from './modal-page.types'; const CLOSE_BUTTON_ICON_WIDTH = 26; const slots = useSlots(); const hasSlot = useHasSlot(slots); const page = ref<InstanceType<typeof DialogPage>>(null); const props = withDefaults(defineProps<BentoModalPageProps>(), { actions: null, actionsLabel: null, columnsSize: BentoModalColumnLayoutVariant.SMALLER, customPadding: null, destructiveActions: false, withColumnLayout: false, pageId: undefined, previousPage: undefined, }); const modalPageConditionalClasses = computed(() => ({ 'b-modal-page--custom-padding': props.customPadding, })); const modalContentConditionalClasses = computed(() => ({ 'b-modal-page__content--with-header': hasSlot('default'), 'b-modal-page__content--with-actions': hasSlot('actions') || props.actions?.length, })); // Setting custom padding styles const customPaddingStyles = computed(() => { if (!props.customPadding) { return {}; } const xPadding = { paddingLeft: props.customPadding, paddingRight: props.customPadding, }; const content = { ...xPadding, paddingTop: undefined, paddingBottom: undefined }; if (!hasSlot('default')) { content.paddingTop = props.customPadding; } if (!hasSlot('actions')) { content.paddingBottom = props.customPadding; } return { header: { ...xPadding, paddingTop: props.customPadding, paddingRight: `${parseInt(props.customPadding, 10) + CLOSE_BUTTON_ICON_WIDTH}px`, flex: 1, }, content, actions: { ...xPadding, paddingBottom: props.customPadding }, backButton: { top: props.customPadding, paddingLeft: props.customPadding }, }; }); // Column layout styles const hasColumnLayout = computed(() => props.withColumnLayout && size.value === BentoModalSize.LARGE); const computedColumnWidth = computed(() => { if (props.columnsSize === BentoModalColumnLayoutVariant.SMALLER) { return [ { small: 4, medium: 4, large: 4 }, { small: 8, medium: 8, large: 8 }, ]; } if (props.columnsSize === BentoModalColumnLayoutVariant.BIGGER) { return [ { small: 8, medium: 8, large: 8 }, { small: 4, medium: 4, large: 4 }, ]; } return [ { small: 6, medium: 6, large: 6 }, { small: 6, medium: 6, large: 6 }, ]; }); // Register page to parent component and inject data from parent component const { activePage, height, isAnimationActive, pageCustomPadding, showCloseButton, size } = inject<BentoModalConfigData>(MODAL_CONFIG_INJECTION_KEY); const isCurrentPage = computed(() => activePage.value === props.pageId || !activePage.value); // Animation // Set resize observer on mount of update the height of the dialog on resize let pageElementObserver: ResizeObserver | null = null; // Watching activePage changes to set the height of the page and set isAnimationActive watch( () => activePage.value, async (value, oldValue) => { if (isCurrentPage.value) { await nextTick(); height.value = `${page.value?.$el?.clientHeight}px`; pageCustomPadding.value = props.customPadding; // only set isAnimationActive to true if its going from two different pages and not from null to the first page isAnimationActive.value = oldValue !== undefined; pageElementObserver = observeSizeOfElement(page.value, () => { const pageHeight = `${page.value?.$el?.clientHeight}px`; if (pageHeight && pageHeight !== height.value) { height.value = pageHeight; } }); } }, { immediate: true } ); // Disconnect observer on unmounting the page onUnmounted(() => { pageElementObserver?.disconnect(); }); </script> <script lang="ts"> /** * ModalPage is used to render different pages of the Modal component * * @example * import { BentoModal, BentoModalPage } from '@adyen/bento-vue2'; * * export default { * components: { BentoModal, BentoModalPage }, * template: ` * <bento-modal active-page='page1' :is-open='true'> * <bento-modal-page * pageId='page1' * actions-label='Move to next page' * :actions="[ * { * title: 'Next page', * event: () => { * activePage.value = 'page2'; * }, * }]"> * Page 1 * <template #content> * Page 1 content * </template> * </bento-modal-page> * <bento-modal-page * pageId='page2' * :actions="[ * { * title: 'Save', * event: () => {}, * }]"> * Page 2 * <template #content> * Page 2 content * </template> * </bento-modal-page> * </bento-modal> * ` * } */ export default defineComponent({}); </script> <style lang="scss" scoped src="./modal-page.scss" />
@@ -0,0 +1 @@
1
+ import { type ComputedRef, type Ref } from 'vue'; import type { BentoModalSize } from './modal.types'; export interface BentoModalConfigData { activePage: Ref<string>; height: Ref<string>; isAnimationActive: Ref<boolean>; pageCustomPadding: Ref<string>; showCloseButton: ComputedRef<boolean>; size: Ref<BentoModalSize | `${BentoModalSize}`>; }