@adyen/bento-mcp 0.1.0 → 0.1.2

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 (58) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +11 -66
  3. package/dist/assets/components/action-bar/action-bar.stories.ts +1 -1
  4. package/dist/assets/components/action-bar/action-bar.vue +1 -1
  5. package/dist/assets/components/ai-tag/ai-tag.docs.mdx +75 -0
  6. package/dist/assets/components/ai-tag/ai-tag.stories.ts +1 -0
  7. package/dist/assets/components/ai-tag/ai-tag.vue +1 -0
  8. package/dist/assets/components/anchor-scroller/anchor-scroller.docs.mdx +55 -0
  9. package/dist/assets/components/anchor-scroller/anchor-scroller.stories.ts +1 -0
  10. package/dist/assets/components/anchor-scroller/anchor-scroller.vue +1 -0
  11. package/dist/assets/components/anchor-scroller/components/anchor-scroller-list/anchor-scroller-list.vue +1 -0
  12. package/dist/assets/components/card/card.docs.mdx +3 -3
  13. package/dist/assets/components/data-grid/components/data-grid-cell-actions/data-grid-cell-actions.vue +1 -1
  14. package/dist/assets/components/data-grid/components/data-grid-tag-with-text-cell/data-grid-tag-with-text-cell.docs.mdx +35 -0
  15. package/dist/assets/components/data-grid/components/data-grid-tag-with-text-cell/data-grid-tag-with-text-cell.stories.ts +1 -0
  16. package/dist/assets/components/data-grid/components/data-grid-tag-with-text-cell/data-grid-tag-with-text-cell.vue +1 -0
  17. package/dist/assets/components/data-grid/data-grid.docs.mdx +29 -9
  18. package/dist/assets/components/data-grid/data-grid.stories.ts +1 -1
  19. package/dist/assets/components/data-grid/data-grid.vue +1 -1
  20. package/dist/assets/components/date-picker/date-picker.vue +1 -1
  21. package/dist/assets/components/date-range-picker/components/date-range-picker-calendar/date-range-picker-calendar.vue +1 -1
  22. package/dist/assets/components/date-range-picker/date-range-picker.vue +1 -1
  23. package/dist/assets/components/draggable-handle/draggable-handle.docs.mdx +61 -0
  24. package/dist/assets/components/draggable-handle/draggable-handle.stories.ts +1 -0
  25. package/dist/assets/components/draggable-handle/draggable-handle.vue +1 -0
  26. package/dist/assets/components/dropdown/components/dropdown-base-textbox/dropdown-base-textbox.vue +1 -1
  27. package/dist/assets/components/dropdown/components/dropdown-default-textbox/dropdown-default-textbox.vue +1 -1
  28. package/dist/assets/components/dropdown/components/dropdown-small-textbox/dropdown-small-textbox.vue +1 -1
  29. package/dist/assets/components/dropdown/dropdown.docs.mdx +3 -3
  30. package/dist/assets/components/dropdown/dropdown.vue +1 -1
  31. package/dist/assets/components/file-uploader/components/file-uploader-file-card/file-uploader-file-card.vue +1 -1
  32. package/dist/assets/components/file-uploader/file-uploader.vue +1 -1
  33. package/dist/assets/components/filter-bar/components/date-range-filter/date-range-filter.vue +1 -1
  34. package/dist/assets/components/filter-bar/filter-bar.docs.mdx +1 -1
  35. package/dist/assets/components/form-layout/components/form-layout-title/form-layout-title.vue +1 -1
  36. package/dist/assets/components/form-layout/form-layout.docs.mdx +2 -2
  37. package/dist/assets/components/inspector/inspector.docs.mdx +2 -2
  38. package/dist/assets/components/internal/copy/copy.docs.mdx +2 -2
  39. package/dist/assets/components/internal/dialog-page/dialog-page.vue +1 -1
  40. package/dist/assets/components/internal/fixed-scroller/fixed-scroller.vue +1 -1
  41. package/dist/assets/components/internal/listbox/components/listbox-multi-select/components/listbox-multi-select-options/listbox-multi-select-options.vue +1 -1
  42. package/dist/assets/components/layout/layout.docs.mdx +8 -8
  43. package/dist/assets/components/layout/layout.vue +1 -1
  44. package/dist/assets/components/modal/components/base-modal/base-modal.vue +1 -1
  45. package/dist/assets/components/modal/modal.docs.mdx +4 -4
  46. package/dist/assets/components/modal-fullscreen/modal-fullscreen.docs.mdx +5 -5
  47. package/dist/assets/components/modal-fullscreen/modal-fullscreen.vue +1 -1
  48. package/dist/assets/components/secondary-nav/components/secondary-nav-item/secondary-nav-item.vue +1 -1
  49. package/dist/assets/components/sidepanel/sidepanel.docs.mdx +27 -27
  50. package/dist/assets/components/sidepanel/sidepanel.vue +1 -1
  51. package/dist/assets/components/stepper/stepper.docs.mdx +8 -1
  52. package/dist/assets/components/stepper/stepper.stories.ts +1 -1
  53. package/dist/assets/components/summary-grid/summary-grid.docs.mdx +1 -1
  54. package/dist/assets/components/tabs/tabs.docs.mdx +1 -1
  55. package/dist/assets/components/toast/toast.docs.mdx +10 -10
  56. package/dist/assets/index.ts +1 -1
  57. package/dist/main.js +501 -427
  58. package/package.json +5 -4
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.2 (2026-03-11)
4
+
5
+ This was a version bump only for mcp to align it with other projects, there were no code changes.
6
+
7
+
8
+ ## 0.1.1 (2026-03-04)
9
+
10
+ ### Bug Fixes
11
+
12
+ - updated `@modelcontextprotocol/sdk` to fix `CVE-2026-25536`, `CVE-2026-25536` ([15f9b8fd1](https://github.com/Adyen/bento/commit/15f9b8fd1))
13
+
14
+ ### Documentation
15
+
16
+ - updated MCP docs with standard config for models ([471b7fa80](https://github.com/Adyen/bento/commit/471b7fa80))
17
+
18
+ ### ❤️ Thank You
19
+
20
+ - daver
21
+
22
+
3
23
  ## 0.1.0 (2026-02-25)
4
24
 
5
25
  ### Features
package/README.md CHANGED
@@ -5,25 +5,15 @@ models (LLMs) to interact with Bento's codebase, documentation, and components.
5
5
 
6
6
  ## Features
7
7
 
8
- - **Changelog Access**: Query and retrieve information from the Bento Vue2 changelog.
9
8
  - **Component Context**: Provide component definitions and usage guidelines to LLMs.
9
+ - **Changelog Access**: (Planned) Query and retrieve information from the Bento changelog.
10
10
  - **Design Tokens**: (Planned) Access design token values and documentation.
11
11
 
12
12
  ## Usage
13
13
 
14
- You can use this server as a tool with various AI assistant clients that support the Model Context Protocol. The tool
15
- command to use is `bento-mcp`.
14
+ You can use this server as a tool with various AI assistant clients that support the Model Context Protocol.
16
15
 
17
- ### With Gemini
18
-
19
- You can use the `bento-mcp` server as a tool with the `gemini-cli` by passing the command in the `-t` flag.
20
-
21
- ```bash
22
- gemini -t "bento-mcp" "What props does the button component have?"
23
- ```
24
-
25
- Alternatively, you can configure the tool in a JSON file. For example, you can create a `.gemini-cli-config.json` file
26
- in your home directory:
16
+ To configure the MCP server, add the following to your client's settings file:
27
17
 
28
18
  ```json
29
19
  {
@@ -37,61 +27,16 @@ in your home directory:
37
27
  }
38
28
  ```
39
29
 
40
- With this configuration, you can invoke the tool by its name:
30
+ The location of this file may vary depending on your client and setup. For project-level and global configuration, you
31
+ can typically use:
41
32
 
42
- ```bash
43
- gemini -t "bento" "What props does the button component have?"
44
- ```
33
+ | Client | Local Path | Global Path |
34
+ | -------- | ----------------------------- | ------------------------------------- |
35
+ | Claude | `.claude/settings.local.json` | `~/.claude/settings.json` |
36
+ | Gemini | `.gemini/settings.json` | `~/.gemini/settings.json` |
37
+ | Windsurf | `-` | `~/.codeium/windsurf/mcp_config.json` |
45
38
 
46
- ### With Claude Desktop (macOS)
47
-
48
- To configure the `bento-mcp` tool for the Claude desktop client on macOS, you can edit the configuration file located at
49
- `~/Library/Application Support/Claude/claude_desktop_config.json`.
50
-
51
- Add a `bento` server entry that points to the `bento-mcp` command:
52
-
53
- ```json
54
- {
55
- "mcpServers": {
56
- "bento": {
57
- "type": "stdio",
58
- "command": "npx",
59
- "args": ["@adyen/bento-mcp@latest"]
60
- }
61
- }
62
- }
63
- ```
64
-
65
- ### With Windsurf
66
-
67
- #### Installation
68
-
69
- You can install the package globally using NPM:
70
-
71
- ```bash
72
- npm install -g @adyen/bento-mcp
73
- ```
74
-
75
- This will make the `bento-mcp` command available in your shell.
76
-
77
- You can pass the tool command directly to the `windsurf` client:
78
-
79
- ```bash
80
- windsurf --tool "bento-mcp" "What props does the button component have?"
81
- ```
82
-
83
- You can also configure tools in a JSON file. For instance, create a `~/.windsurf/config.json` file:
84
-
85
- ```json
86
- {
87
- "tools": [
88
- {
89
- "alias": "bento",
90
- "command": "bento-mcp"
91
- }
92
- ]
93
- }
94
- ```
39
+ Once configured, you can invoke the tool by its name (`bento`).
95
40
 
96
41
  ## Available Tools
97
42
 
@@ -1 +1 @@
1
- import { action } from '@storybook/addon-actions'; import { isVue2, ref } from 'vue-demi'; import { storybookDocsParameter } from '@/utils/ts/storybook'; import BentoActionBar from './action-bar.vue'; import { BentoButton } from '@/components/button'; import type { Meta, StoryObj } from '@storybook/vue'; import BinIcon from '@adyen/ui-assets-icons-16/vue/bin'; import EditIcon from '@adyen/ui-assets-icons-16/vue/edit-1'; const meta: Meta = { title: 'Action bar', component: BentoActionBar, argTypes: { variant: { control: { type: 'select', }, }, }, args: { variant: 'default', }, parameters: { layout: 'fullscreen', }, }; export default meta; type Story = StoryObj<typeof BentoActionBar>; const DEFAULT_ACTIONS = [ { title: 'Assign to...', disabled: true, event: () => true, }, { title: 'Snooze', event: () => true, }, { title: 'Edit', icon: EditIcon, event: () => true, }, { title: 'Delete', critical: true, icon: BinIcon, event: () => true, }, { title: 'Extra option', event: () => true, }, ]; const DEFAULT_PROPS = { actions: DEFAULT_ACTIONS, selectionLabel: 'selected', itemCounter: 3 }; const defaultCode = ` <template> <bento-action-bar :actions="[ { title: 'Assign to...', event: () => true, }, { title: 'Snooze', event: () => true, }, { title: 'Edit', icon: EditIcon, event: () => true, }, { title: 'Delete', critical: true, icon: BinIcon, event: () => true, }, { title: 'Extra option', event: () => true, } ]" @close="closeAction" :item-counter="3" /> <script setup> const closeAction = () => { // unselect selected items and reset itemCounter } </script> </template>`; export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoActionBar, BentoButton }, props: Object.keys(argTypes), template: ` <div style="height:140px"> <bento-button @click='toggleActionBar = !toggleActionBar'>Toggle action bar</bento-button> <bento-action-bar v-if='toggleActionBar' v-bind="args" @close="closeAction" @cancel="cancelAction" @save="saveAction" /> </div> `, setup(props) { const closeAction = () => action('close')(); const cancelAction = () => action('cancel')(); const saveAction = () => action('save')(); const toggleActionBar = ref(true); return { // Values args: isVue2 ? props : _args, toggleActionBar, // Events closeAction, cancelAction, saveAction, }; }, }), args: { ...DEFAULT_PROPS, }, parameters: storybookDocsParameter(defaultCode), };
1
+ import { action } from '@storybook/addon-actions'; import { isVue2, ref } from 'vue-demi'; import { storybookDocsParameter } from '@/utils/ts/storybook'; import BentoActionBar from './action-bar.vue'; import { BentoButton } from '@/components/button'; import type { Meta, StoryObj } from '@storybook/vue'; import BinIcon from '@adyen/ui-assets-icons-16/vue/bin'; import EditIcon from '@adyen/ui-assets-icons-16/vue/edit-1'; const meta: Meta = { title: 'Action bar', component: BentoActionBar, argTypes: { variant: { options: ['default', 'confirmation'], control: { type: 'select', }, }, }, parameters: { layout: 'fullscreen', }, }; export default meta; type Story = StoryObj<typeof BentoActionBar>; const DEFAULT_ACTIONS = [ { title: 'Assign to...', disabled: true, event: () => true, }, { title: 'Snooze', event: () => true, }, { title: 'Edit', icon: EditIcon, event: () => true, }, { title: 'Delete', critical: true, icon: BinIcon, event: () => true, }, { title: 'Extra option', event: () => true, }, ]; const DEFAULT_PROPS = { actions: DEFAULT_ACTIONS, selectionLabel: 'selected', itemCounter: 3 }; const defaultCode = ` <template> <bento-action-bar :actions="[ { title: 'Assign to...', event: () => true, }, { title: 'Snooze', event: () => true, }, { title: 'Edit', icon: EditIcon, event: () => true, }, { title: 'Delete', critical: true, icon: BinIcon, event: () => true, }, { title: 'Extra option', event: () => true, } ]" @close="closeAction" :item-counter="3" /> <script setup> const closeAction = () => { // unselect selected items and reset itemCounter } </script> </template>`; export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoActionBar, BentoButton }, props: Object.keys(argTypes), template: ` <div style="height:140px"> <bento-button @click='toggleActionBar = !toggleActionBar'>Toggle action bar</bento-button> <bento-action-bar v-if='toggleActionBar' v-bind="args" @close="closeAction" @cancel="cancelAction" @save="saveAction" /> </div> `, setup(props) { const closeAction = () => action('close')(); const cancelAction = () => action('cancel')(); const saveAction = () => action('save')(); const toggleActionBar = ref(true); return { // Values args: isVue2 ? props : _args, toggleActionBar, // Events closeAction, cancelAction, saveAction, }; }, }), args: { ...DEFAULT_PROPS, }, parameters: storybookDocsParameter(defaultCode), };
@@ -1 +1 @@
1
- <template> <Transition name="b-action-bar__animation" appear> <section class="b-action-bar" :class="conditionalClasses" :aria-label="t('actionBar')"> <div ref="actionBar" class="b-action-bar__container"> <div v-if="!isConfirmationVariant" class="b-action-bar__close-button"> <bento-button variant="tertiary" inverse :aria-label="t('close')" @click="emit('close')"> <template #iconLeft><cross-icon :aria-hidden="true" /></template> </bento-button> </div> <bento-typography el="span" stronger class="b-action-bar__selection"> {{ n(itemCounter) }} {{ isConfirmationVariant ? tc('unsavedChanges', itemCounter) : label }} </bento-typography> <bento-button-actions v-if="isConfirmationVariant" :actions="confirmationActions" class="b-action-bar__actions" inverse /> <button-actions-with-menu v-else :actions="actionsInternal" class="b-action-bar__actions" :displayed-actions="internalDisplayedActions" menu-position="top-end" disable-responsive-behavior inverse /> </div> </section> </Transition> </template> <script setup lang="ts"> import { computed, nextTick, type PropType, provide, ref, toRefs, watch } from 'vue'; import { BentoButton, BentoButtonActions, type BentoButtonActionsList } from '@/components/button'; import { BentoTypography } from '@/components/typography'; import { type BentoActionBarVariant } from '@/components/action-bar/action-bar.types'; import ButtonActionsWithMenu from '@/components/internal/button-actions-with-menu/button-actions-with-menu.vue'; import CrossIcon from '@adyen/ui-assets-icons-16/vue/cross'; import { useI18n } from '@/utils/ts/i18n'; import { useWindowSize } from '@vueuse/core'; import messages from './messages.json'; import { POPOVER_OFFSET_INJECTION_KEY } from '@/components/popover/popover.keys'; type MessageSchema = (typeof messages)['en-US']; // Constants const AVERAGE_BUTTON_SIZE = 100; const MENU_OFFSET = 7; const RESPONSIVE_BREAKPOINT = 500; const SPACING = 32; const props = defineProps({ /** * List of actions that will be used to render the buttons. * Required and used for the `default` variant. * * Each object in the list should have a `title`, an `event` and, * optionally, an `icon`. * * The component will display 4 actions and the hide the rest inside a menu. * * @see BentoButton for a list of all the other props supported by each button action. */ actions: { type: Array as PropType<BentoButtonActionsList>, default: () => [], }, /** * How many actions should be visible outside the menu, cannot be more than 4 */ visibleActions: { type: Number, default: 4, validator: (value: number) => value > 0 && value <= 4, }, /** * Use when there are sticky elements at the bottom, like sticky pagination */ extraBottomSpacing: { type: Boolean, default: false, }, /** * Show how many items are selected */ itemCounter: { type: Number, required: true, }, /** * Label for the selection, e.g. 'selected'/'changed' */ selectionLabel: { type: String, default: null, }, /** * Variant for the action bar * @values default, confirmation */ variant: { type: String as PropType<BentoActionBarVariant>, default: 'default', }, }); const emit = defineEmits<{ /** * Event emitted when the user click the close button */ (e: 'close'): void; (e: 'cancel'): void; (e: 'save'): void; }>(); const { t, tc, n } = useI18n<{ message: MessageSchema }>({ messages }); const label = computed(() => (props.selectionLabel ? props.selectionLabel : t('selected'))); // Refs const actionBar = ref<HTMLDivElement>(null); const { extraBottomSpacing, visibleActions } = toRefs(props); const { width } = useWindowSize(); const internalDisplayedActions = ref(null); const internalVisibleActions = ref(null); const isConfirmationVariant = computed(() => props.variant === 'confirmation'); watch( () => visibleActions.value, value => { internalVisibleActions.value = value <= 4 ? value : 4; internalDisplayedActions.value = internalVisibleActions.value; }, { immediate: true } ); // Styling const conditionalClasses = computed(() => ({ 'b-action-bar--extra-spacing': extraBottomSpacing.value, 'b-action-bar--confirmation': isConfirmationVariant.value, })); // Provide POPOVER_OFFSET_INJECTION_KEY to change the value of the popover offset provide(POPOVER_OFFSET_INJECTION_KEY, MENU_OFFSET); // Manipulate actions array to display as intended const actionsInternal = computed(() => { // All buttons should be of type secondary in the action bar const actions = props.actions.map(action => ({ ...action, variant: 'secondary', })) as BentoButtonActionsList; // Shown actions should display from left to right in action bar, instead of right to left which is the button actions default const shownActions = actions.slice(0, internalDisplayedActions.value).reverse(); const hiddenActions = actions.slice(internalDisplayedActions.value); return [...shownActions, ...hiddenActions]; }); const confirmationActions = computed<BentoButtonActionsList>(() => [ { title: t('save'), event: () => emit('save'), }, { title: t('cancel'), variant: 'secondary', event: () => emit('cancel'), }, ]); // Recursive functions to determine how many actions should be visible/hidden based on the screens width const showActions = async () => { await nextTick(); if ( width.value > actionBar.value.clientWidth + SPACING + AVERAGE_BUTTON_SIZE && internalDisplayedActions.value < internalVisibleActions.value ) { internalDisplayedActions.value += 1; showActions(); } }; const hideActions = async () => { await nextTick(); if (width.value < actionBar.value.clientWidth + SPACING && internalDisplayedActions.value > 0) { internalDisplayedActions.value -= 1; hideActions(); } }; // Screen width watcher to toggle hidden/visible actions watch( () => width.value, async (newValue, oldValue) => { await nextTick(); if (!actionBar.value) { return; } const isIncreasing = newValue > oldValue; if (width.value < RESPONSIVE_BREAKPOINT) { internalDisplayedActions.value = 0; } else if (isIncreasing) { showActions(); } else { hideActions(); } }, { immediate: true } ); </script> <script lang="ts"> /** * Action bar is a floating bar that provides users with quick access to key actions that can be applied to one or multiple items. * * @example * import { BentoActionBar } from '@adyen/bento-vue2'; * * export default { * components: { BentoActionBar }, * template: ` * <bento-action-bar * :actions='[ * { * title: 'Delete', * critical: true, * icon: BinIcon, * event: () => true, * }, * { * title: 'Edit', * icon: EditIcon, * event: () => true, * } * ]' * @close="closeAction" * :item-counter="5" * /> * ` * } */ export default { i18n: { messages }, }; </script> <style lang="scss" scoped src="./action-bar.scss" />
1
+ <template> <Transition name="b-action-bar__animation" appear> <section class="b-action-bar" :class="conditionalClasses" :aria-label="t('actionBar')"> <div ref="actionBar" class="b-action-bar__container"> <div v-if="!isConfirmationVariant" class="b-action-bar__close-button"> <bento-button variant="tertiary" inverse :aria-label="t('close')" @click="emit('close')"> <template #iconLeft><cross-icon :aria-hidden="true" /></template> </bento-button> </div> <bento-typography el="span" stronger class="b-action-bar__selection"> <template v-if="isConfirmationVariant && noItemCounter"> {{ tc('unsavedChanges', 0) }} </template> <template v-else> {{ n(itemCounter) }} {{ isConfirmationVariant ? tc('unsavedChanges', itemCounter) : label }} </template> </bento-typography> <bento-button-actions v-if="isConfirmationVariant" :actions="confirmationActions" class="b-action-bar__actions" inverse /> <button-actions-with-menu v-else :actions="actionsInternal" class="b-action-bar__actions" :displayed-actions="internalDisplayedActions" menu-position="top-end" disable-responsive-behavior inverse /> </div> </section> </Transition> </template> <script setup lang="ts"> import { computed, nextTick, type PropType, provide, ref, toRefs, watch } from 'vue'; import { BentoButton, BentoButtonActions, type BentoButtonActionsList } from '@/components/button'; import { BentoTypography } from '@/components/typography'; import { type BentoActionBarVariant } from '@/components/action-bar/action-bar.types'; import ButtonActionsWithMenu from '@/components/internal/button-actions-with-menu/button-actions-with-menu.vue'; import CrossIcon from '@adyen/ui-assets-icons-16/vue/cross'; import { useI18n } from '@/utils/ts/i18n'; import { useWindowSize } from '@vueuse/core'; import messages from './messages.json'; import { POPOVER_OFFSET_INJECTION_KEY } from '@/components/popover/popover.keys'; type MessageSchema = (typeof messages)['en-US']; // Constants const AVERAGE_BUTTON_SIZE = 100; const MENU_OFFSET = 7; const RESPONSIVE_BREAKPOINT = 500; const SPACING = 32; const props = defineProps({ /** * List of actions that will be used to render the buttons. * Required and used for the `default` variant. * * Each object in the list should have a `title`, an `event` and, * optionally, an `icon`. * * The component will display 4 actions and the hide the rest inside a menu. * * @see BentoButton for a list of all the other props supported by each button action. */ actions: { type: Array as PropType<BentoButtonActionsList>, default: () => [], }, /** * How many actions should be visible outside the menu, cannot be more than 4 */ visibleActions: { type: Number, default: 4, validator: (value: number) => value > 0 && value <= 4, }, /** * Use when there are sticky elements at the bottom, like sticky pagination */ extraBottomSpacing: { type: Boolean, default: false, }, /** * Show how many items are selected. * When omitted in the `confirmation` variant, a generic "Unsaved changes" label is displayed. */ itemCounter: { type: Number, default: null, }, /** * Label for the selection, e.g. 'selected'/'changed' */ selectionLabel: { type: String, default: null, }, /** * Variant for the action bar * @values default, confirmation */ variant: { type: String as PropType<BentoActionBarVariant>, default: 'default', }, }); const emit = defineEmits<{ /** * Event emitted when the user click the close button */ (e: 'close'): void; (e: 'cancel'): void; (e: 'save'): void; }>(); const { t, tc, n } = useI18n<{ message: MessageSchema }>({ messages }); const label = computed(() => (props.selectionLabel ? props.selectionLabel : t('selected'))); // Refs const actionBar = ref<HTMLDivElement>(null); const { extraBottomSpacing, visibleActions } = toRefs(props); const { width } = useWindowSize(); const internalDisplayedActions = ref(null); const internalVisibleActions = ref(null); const isConfirmationVariant = computed(() => props.variant === 'confirmation'); watch( () => visibleActions.value, value => { internalVisibleActions.value = value <= 4 ? value : 4; internalDisplayedActions.value = internalVisibleActions.value; }, { immediate: true } ); // Styling const conditionalClasses = computed(() => ({ 'b-action-bar--extra-spacing': extraBottomSpacing.value, 'b-action-bar--confirmation': isConfirmationVariant.value, })); // Provide POPOVER_OFFSET_INJECTION_KEY to change the value of the popover offset provide(POPOVER_OFFSET_INJECTION_KEY, MENU_OFFSET); // Manipulate actions array to display as intended const actionsInternal = computed(() => { // All buttons should be of type secondary in the action bar const actions = props.actions.map(action => ({ ...action, variant: 'secondary', })) as BentoButtonActionsList; // Shown actions should display from left to right in action bar, instead of right to left which is the button actions default const shownActions = actions.slice(0, internalDisplayedActions.value).reverse(); const hiddenActions = actions.slice(internalDisplayedActions.value); return [...shownActions, ...hiddenActions]; }); const confirmationActions = computed<BentoButtonActionsList>(() => [ { title: t('save'), event: () => emit('save'), }, { title: t('cancel'), variant: 'secondary', event: () => emit('cancel'), }, ]); const noItemCounter = computed(() => { return props.itemCounter == null || props.itemCounter < 1; }); // Recursive functions to determine how many actions should be visible/hidden based on the screens width const showActions = async () => { await nextTick(); if ( width.value > actionBar.value.clientWidth + SPACING + AVERAGE_BUTTON_SIZE && internalDisplayedActions.value < internalVisibleActions.value ) { internalDisplayedActions.value += 1; showActions(); } }; const hideActions = async () => { await nextTick(); if (width.value < actionBar.value.clientWidth + SPACING && internalDisplayedActions.value > 0) { internalDisplayedActions.value -= 1; hideActions(); } }; // Screen width watcher to toggle hidden/visible actions watch( () => width.value, async (newValue, oldValue) => { await nextTick(); if (!actionBar.value) { return; } const isIncreasing = newValue > oldValue; if (width.value < RESPONSIVE_BREAKPOINT) { internalDisplayedActions.value = 0; } else if (isIncreasing) { showActions(); } else { hideActions(); } }, { immediate: true } ); </script> <script lang="ts"> /** * Action bar is a floating bar that provides users with quick access to key actions that can be applied to one or multiple items. * * @example * import { BentoActionBar } from '@adyen/bento-vue2'; * * export default { * components: { BentoActionBar }, * template: ` * <bento-action-bar * :actions='[ * { * title: 'Delete', * critical: true, * icon: BinIcon, * event: () => true, * }, * { * title: 'Edit', * icon: EditIcon, * event: () => true, * } * ]' * @close="closeAction" * :item-counter="5" * /> * ` * } */ export default { i18n: { messages }, }; </script> <style lang="scss" scoped src="./action-bar.scss" />
@@ -0,0 +1,75 @@
1
+ import { Canvas, Meta } from '@storybook/blocks';
2
+ import * as AiTagStories from './ai-tag.stories';
3
+
4
+ <Meta of={AiTagStories} />
5
+
6
+ # AI tag
7
+
8
+ The AI tag is a component used to transparently identify content, data, or experiences that have been generated or
9
+ enhanced by artificial intelligence. Its primary purpose is to build user trust by clearly attributing AI's role.
10
+
11
+ It can be used standalone—placed next to the affected value—or embedded within other Bento components (e.g.,
12
+ b-input-field, b-card) via their designated slots. Clicking the tag opens an Explainability Popover with a short,
13
+ contextual summary of what the AI did.
14
+
15
+ <Canvas of={AiTagStories.Default} />
16
+
17
+ ## Use Cases
18
+
19
+ - **To label AI-generated content:** Use it next to text, images, or data that was fully created by an AI model (e.g., a
20
+ generated summary, a generated data prediction).
21
+ - **To label AI-enhanced content:** Use it when AI has modified or significantly assisted in creating content (e.g., "AI
22
+ suggested these edits," "AI prefilled this input fields").
23
+ - **To identify AI-powered features:** Use it when a specific feature is an AI-driven system (e.g., “AI assistant chat”)
24
+ - **To identify AI-generated pages:** Use it when an entire page is generated by AI
25
+
26
+ ### Do not use
27
+
28
+ - **For non-AI automation:** Do not use it for simple rules-based logic or standard algorithms (e.g., sorting a list
29
+ alphabetically).
30
+ - **For values that are not influenced by AI**, or where the AI role is irrelevant to the user’s decision.
31
+ - **As a decorative label** without an attached Explainability Popover.
32
+
33
+ ### Best practices
34
+
35
+ - Place the AI tag on the right side of the AI-influenced value or in the component’s designated meta/info slot (also
36
+ usually on the right side).
37
+ - Keep Explainability Popover content concise and plain-language
38
+ - Prefer actionable transparency: include a “Learn more” action when deeper context or policy details exist.
39
+
40
+ ## Placement
41
+
42
+ There are two primary ways to place the AI Tag:
43
+
44
+ - **Standalone**: AI tag is a standalone element placed directly on the right side of the AI-generated or enhanced
45
+ label/content/feature/page. Use this for freeform content.
46
+ - **Embedded**: AI tag is placed inside of an existing component, typically on the right side. This shows that the AI is
47
+ acting within or upon that specific component (e.t., an AI suggestion inside a input field, dropdown, etc).
48
+
49
+ ## Accessibility
50
+
51
+ ### Keyboard interaction
52
+
53
+ | Keyboard interaction | Action |
54
+ | ---------------------- | ---------------------------------------------------------------------------------------- |
55
+ | `Tab` | Moves focus to the AI tag trigger or away from it. |
56
+ | `Space` / `Enter` | Activates the trigger and toggles the visibility of the Explainability Popover. |
57
+ | `Esc` | Closes the Popover and returns focus to the trigger button. |
58
+ | `Tab` (inside Popover) | Cycles focus through the interactive elements within the Popover (e.g., action buttons). |
59
+
60
+ ### Roles, states and properties
61
+
62
+ The AI tag follows the [disclosure pattern](https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/) for the trigger and
63
+ the [dialog pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/) for the popover content.
64
+
65
+ | ARIA attribute | Is automatic? | Description |
66
+ | --------------- | ------------- | ---------------------------------------------------------------------------------------- |
67
+ | `role="button"` | Yes | The trigger is a semantic button. |
68
+ | `aria-expanded` | Yes | Set to `true` when the popover is visible, `false` otherwise. |
69
+ | `aria-controls` | Yes | Points to the `id` of the popover content. |
70
+ | `role="dialog"` | Yes | The popover container has the dialog role. |
71
+ | `aria-label` | Yes | The popover uses "AI explained" as its accessible label (derived from the header title). |
72
+
73
+ ## Resources
74
+
75
+ - [Figma link](https://www.figma.com/design/uLabwF3243jdMDsNSP7U9I/Bento---Components?node-id=49373-138087&m=dev)
@@ -0,0 +1 @@
1
+ import BentoAiTag from './ai-tag.vue'; import AiTagDefault from './__tests__/ai-tag-default.vue?raw'; import { action } from '@storybook/addon-actions'; import { isVue2 } from 'vue-demi'; import { storybookDocsParameter } from '@/utils/ts/storybook'; import type { Meta, StoryObj } from '@storybook/vue'; const meta: Meta = { title: 'AI tag', component: BentoAiTag, }; export default meta; type Story = StoryObj<typeof BentoAiTag>; export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoAiTag }, props: Object.keys(argTypes), template: ` <bento-ai-tag v-bind="args" @update:toggled="onToggleAction"/> `, setup(props) { const onToggleAction = (value: boolean) => action('update:toggled')(value); return { // Values args: isVue2 ? props : _args, // Events onToggleAction, }; }, }), args: { title: 'Input fields auto-filled by AI', description: 'Based on your documents uploaded, AI has auto-filled some information for you. You can change that information any time.', influencedValue: '30%', action: { title: 'Learn more', event: () => action('action-click')('Learn more clicked'), }, }, parameters: storybookDocsParameter(AiTagDefault), };
@@ -0,0 +1 @@
1
+ <template> <div ref="aiTagRef" class="b-ai-tag" @keydown.esc="closePopover"> <bento-toggle-button ref="targetElement" :toggled="toggled" :aria-controls="popoverId" variant="secondary" condensed class="b-ai-tag__trigger" @update:toggled="onToggle" > <template #iconLeft> <artificial-intelligence-ai-icon class="b-ai-tag__trigger-icon" :aria-hidden="true" /> </template> {{ t('ai') }} </bento-toggle-button> <bento-popover v-if="targetElement" :id="popoverId" :open="toggled" :target-element="targetElement" :actions="popoverActions" actions-layout="fill-container" :title="t('aiExplained')" trap-all :trap-all-options="{ onDeactivate: closePopover, allowOutsideClick: true }" fixed-positioning overflow-visible @dismiss="closePopover" > <template #headerIcon> <artificial-intelligence-ai-icon class="b-ai-tag__popover-header-icon" :aria-hidden="true" /> </template> <div class="b-ai-tag__popover-content"> <bento-typography v-if="influencedValue" variant="title" medium class="b-ai-tag__influenced-value"> {{ influencedValue }} </bento-typography> <bento-typography variant="title"> {{ title }} </bento-typography> <bento-typography> {{ description }} </bento-typography> </div> </bento-popover> </div> </template> <script setup lang="ts"> import { computed, ref, watch } from 'vue'; import { BentoPopover } from '@/components/popover'; import { BentoToggleButton } from '@/components/button'; import { BentoTypography } from '@/components/typography'; import { useClickOutside } from '@/composables'; import { useI18n } from '@/utils/ts/i18n'; import { generateUid } from '@/core/utils/ts'; import type { BentoButtonActionsList } from '@/components/button/components/button-actions/button-actions.types'; import ArtificialIntelligenceAiIcon from '@adyen/ui-assets-icons-16/vue/artificial-intelligence-ai-filled'; import type { BentoAiTagProps } from './ai-tag.types'; import messages from './messages.json'; type MessageSchema = (typeof messages)['en-US']; const props = withDefaults(defineProps<BentoAiTagProps>(), { influencedValue: undefined, toggled: false, action: undefined, }); const { t } = useI18n<{ message: MessageSchema }>({ messages }); const toggled = ref(props.toggled); const aiTagRef = ref<HTMLElement | null>(null); const targetElement = ref<InstanceType<typeof BentoToggleButton> | null>(null); const popoverId = generateUid('b-ai-tag-popover'); watch( () => props.toggled, value => { toggled.value = value; } ); useClickOutside(aiTagRef, () => { closePopover(); }); const popoverActions = computed<BentoButtonActionsList>(() => props.action ? [ { ...props.action, variant: 'secondary', }, ] : undefined ); const emit = defineEmits<{ /** * Triggered when the popover is toggled */ (e: 'update:toggled', toggled: boolean): void; }>(); const onToggle = (value: boolean) => { toggled.value = value; emit('update:toggled', value); }; const closePopover = () => { if (toggled.value) { onToggle(false); } }; </script> <script lang="ts"> /** * The AI tag is a component used to transparently identify content, data, or experiences that have been generated or * enhanced by artificial intelligence. * * @example * import { BentoAiTag } from '@adyen/bento-vue2'; * * export default { * components: { BentoAiTag }, * template: ` * <bento-ai-tag * title="Input fields auto-filled by AI" * description="Based on your documents uploaded, AI has auto-filled some information for you." * /> * ` * } */ export default { i18n: { messages }, }; </script> <style lang="scss" scoped src="./ai-tag.scss" />
@@ -0,0 +1,55 @@
1
+ import { Canvas, Meta, Controls } from '@storybook/blocks';
2
+ import * as AnchorScrollerStories from './anchor-scroller.stories';
3
+
4
+ <Meta of={AnchorScrollerStories} />
5
+
6
+ # AnchorScroller
7
+
8
+ The AnchorScroller component provides a sticky navigation bar that allows users to quickly jump to different sections
9
+ within a single page. It dynamically highlights the navigation item that corresponds to the section currently visible in
10
+ the viewport.
11
+
12
+ <Canvas of={AnchorScrollerStories.Default} />
13
+
14
+ ## Use Cases
15
+
16
+ Use the AnchorScroller component when:
17
+
18
+ - There's a long page with a lot of sections, for example on a Payment Detail Page, to allow people to quickly jump to
19
+ relevant content.
20
+
21
+ ### Do not use
22
+
23
+ Don’t use anchor scroller:
24
+
25
+ - on short pages, where the ability to jump doesn’t help user in any way.
26
+ - on text-based pages, such as Terms and Conditions, where the content doesn’t span the entire width of the page and a
27
+ Table of Contents would be more suitable.
28
+
29
+ ## Props
30
+
31
+ <Controls />
32
+
33
+ ## Accessibility
34
+
35
+ The AnchorScroller is designed with accessibility in mind.
36
+
37
+ ### Keyboard interaction
38
+
39
+ - Users can press `Tab` to focus on the navigation bar links, and then use `ArrowLeft` or `ArrowRight` to navigate
40
+ between them.
41
+ - Pressing `Home` moves focus to the first link in the navigation bar.
42
+ - Pressing `End` moves focus to the last link in the navigation bar.
43
+ - Pressing `Enter` or `Space`å on a focused link scrolls the corresponding section into view.
44
+
45
+ ### Roles, states and properties
46
+
47
+ - The navigation bar is rendered within a `<nav>` element for semantic purposes.
48
+ - An `aria-labelledby` attribute on the `<nav>` element points to the "Jump to" text, providing a label for screen
49
+ readers.
50
+ - The currently active link is visually distinguished, and in the future, `aria-current="true"` should be used to
51
+ programmatically expose the active state.
52
+
53
+ ## Resources
54
+
55
+ - [Figma link](https://www.figma.com/design/uLabwF3243jdMDsNSP7U9I/Bento---Components?node-id=48514-115166&m=dev)
@@ -0,0 +1 @@
1
+ import { storybookDocsParameter } from '@/utils/ts/storybook'; import type { Meta, StoryObj } from '@storybook/vue'; import { computed, ref } from 'vue'; import BentoAnchorScroller from './anchor-scroller.vue'; import AnchorScrollerDefaultExample from './__tests__/anchor-scroller-default-example.vue?raw'; import BentoTypography from '../typography/typography.vue'; import { action } from '@storybook/addon-actions'; const meta: Meta = { title: 'Anchor Scroller', component: BentoAnchorScroller, argTypes: { // Slots default: { description: 'Content to be displayed. This enables the anchor scroller to be sticky when scrolling.', control: 'text', table: { type: { summary: 'VNode[]' }, }, }, // Props activeIndex: { table: { type: { summary: 'number' }, }, }, items: { table: { type: { summary: "BentoAnchorScrollerProps['items']" }, }, }, // Events 'update:active-index': { table: { type: { summary: '(index: number) => void' }, }, }, }, }; export default meta; type Story = StoryObj<typeof BentoAnchorScroller>; const ITEMS = [ { title: 'Overview', elementRef: null }, { title: 'Disabled', elementRef: null, disabled: true }, { title: 'Configuration', elementRef: null }, { title: 'Usage', elementRef: null }, ]; export const Default: Story = { render: args => ({ components: { BentoAnchorScroller, BentoTypography }, template: ` <bento-anchor-scroller v-bind="args" :items="computedItems" @update:active-index="updateActiveIndex" > <section ref="section1" style="height: 500px; border: 1px dashed #ccc; margin-bottom: 32px; padding: 24px; border-radius: 8px" > <bento-typography el="h2" variant="title">Overview</bento-typography> <bento-typography>This is the content for the first section.</bento-typography> </section> <section ref="section2" style="height: 400px; border: 1px dashed #ccc; margin-bottom: 32px; padding: 24px; border-radius: 8px" > <bento-typography el="h2" variant="title">Configuration</bento-typography> <bento-typography>This section is shorter.</bento-typography> </section> <section ref="section3" style="height: 600px; border: 1px dashed #ccc; margin-bottom: 32px; padding: 24px; border-radius: 8px" > <bento-typography el="h2" variant="title">Usage</bento-typography> <bento-typography>This is a tall section to demonstrate scrolling.</bento-typography> </section> </bento-anchor-scroller> `, setup() { const section1 = ref<HTMLElement | null>(null); const section2 = ref<HTMLElement | null>(null); const section3 = ref<HTMLElement | null>(null); const sectionDisabled = ref<HTMLElement | null>(null); const sectionRefs = [section1, sectionDisabled, section2, section3]; const computedItems = computed(() => { return (args.items || []).map((item, index) => ({ ...item, // Keep whatever title/disabled state is currently in the Storybook UI elementRef: sectionRefs[index]?.value || null, // Inject the real DOM node! })); }); const updateActiveIndex = value => action('update:active-index')(value); return { args, computedItems, section1, section2, section3, updateActiveIndex }; }, }), args: { items: ITEMS, activeIndex: 0, }, parameters: storybookDocsParameter(AnchorScrollerDefaultExample), };
@@ -0,0 +1 @@
1
+ <template> <div v-if="computedItems.length" class="b-anchor-scroller"> <nav ref="navigationRef" class="b-anchor-scroller__navigation" :class="navigationConditionalClasses" :aria-labelledby="navLabelId" > <fixed-scroller condensed centered> <div class="b-anchor-scroller__navigation-panel"> <bento-typography :id="navLabelId" el="span" class="b-anchor-scroller__navigation-panel-label" stronger > {{ t('jumpTo') }} </bento-typography> <anchor-scroller-list :items="computedItems" :active-index="currentActiveIndex" :scroll-offset="bottom" /> </div> </fixed-scroller> </nav> <slot /> </div> </template> <script setup lang="ts"> import { computed, ref, toRef, watch } from 'vue'; import { useElementBounding, watchDebounced } from '@vueuse/core'; import BentoTypography from '@/components/typography/typography.vue'; import { FixedScroller } from '@/internal'; import { type BentoAnchorScrollerItem, type BentoAnchorScrollerProps } from './anchor-scroller.types'; import { AnchorScrollerList } from './components/anchor-scroller-list'; import { useI18n } from '@/utils/ts/i18n'; import { generateUid } from '@/core/utils/ts'; import { unrefElement } from '@/directives/click-outside/utils'; import messages from './messages.json'; import { useScrollSpy } from './composables'; import { printDevelopmentWarning } from '@/utils/ts/print-development-warning'; type MessageSchema = (typeof messages)['en-US']; const { t } = useI18n<{ message: MessageSchema }>({ messages }); const props = withDefaults(defineProps<BentoAnchorScrollerProps>(), { activeIndex: undefined, }); const emit = defineEmits<{ /** * Emits update event when the active index is changed */ (e: 'update:active-index', value: number): void; }>(); const navigationRef = ref(null); const computedItems = computed<Array<BentoAnchorScrollerItem>>(() => props.items.map(item => ({ ...item }))); // Skip and cache only items that are enabled const enabledItems = computed(() => props.items .map((item, originalIndex) => ({ element: unrefElement(item.elementRef), originalIndex, disabled: item.disabled, })) .filter( (item): item is { element: Element; originalIndex: number; disabled: boolean } => !item.disabled && !!item.element ) ); // Extract strictly the elements for the ScrollSpy to watch const scrollToTargets = computed(() => enabledItems.value.map(c => c.element)); const navigationConditionalClasses = computed(() => ({ 'b-anchor-scroller__navigation--pinned': top.value <= 1, })); const currentActiveIndex = computed(() => { const candidate = enabledItems.value[relativeActiveIndex.value]; return candidate ? candidate.originalIndex : 0; }); const { activeIndex: relativeActiveIndex } = useScrollSpy(scrollToTargets, { default: toRef(props, 'activeIndex'), }); const { top, bottom } = useElementBounding(navigationRef); watchDebounced( currentActiveIndex, val => { if (Number.isFinite(val)) { emit('update:active-index', val); } }, { debounce: 250, // ensure that if you are scrolling through multiple sections quickly, it fires only once. } ); watch( () => props.activeIndex, val => { if (Number.isFinite(val) && computedItems.value[val].disabled) { printDevelopmentWarning(`[bento-anchor-scroller] activeIndex should not be a disabled item`); } } ); const navLabelId = generateUid('bento-anchor-scroller-label'); </script> <script lang="ts"> /** * A navigation component that tracks page scroll and allows jumping to sections. * * @example * import { BentoAnchorScroller } from '@adyen/bento-vue2'; * import { ref } from 'vue'; * * export default { * components: { BentoAnchorScroller }, * template: ` * <bento-anchor-scroller :items="items"> * <section ref="section1">Section 1</section> * <section ref="section2">Section 2</section> * </bento-anchor-scroller> * `, * setup() { * const section1 = ref(null); * const section2 = ref(null); * const items = ref([ * { title: 'Section 1', elementRef: section1 }, * { title: 'Section 2', elementRef: section2 }, * ]); * return { * items, * section1, * section2, * } * } * } */ export default { i18n: { messages }, name: 'bento-anchor-scroller', }; </script> <style lang="scss" scoped src="./anchor-scroller.scss" />
@@ -0,0 +1 @@
1
+ <template> <ul class="b-anchor-scroller-list" @keydown="onKeydownNavigation"> <li v-for="({ title, disabled, elementRef }, index) in items" :key="index" class="b-anchor-scroller-list__item"> <bento-button ref="listItemRefs" :aria-current="activeIndex === index ? 'location' : null" class="b-anchor-scroller-list__item-button" :class="{ 'b-anchor-scroller-list__item-button--active': activeIndex === index, 'b-anchor-scroller-list__item-button--disabled': disabled, }" :aria-disabled="disabled" :tabindex="activeIndex === index ? null : '-1'" variant="tertiary-with-background" @click="scrollToSection(elementRef, disabled)" @keydown.space.native.prevent="scrollToSection(elementRef, disabled)" @keydown.enter.native.prevent="scrollToSection(elementRef, disabled)" > {{ title }} </bento-button> </li> </ul> </template> <script setup lang="ts"> import BentoButton from '@/components/button/button.vue'; import { unrefElement } from '@/directives/click-outside/utils'; import { printDevelopmentWarning } from '@/utils/ts/print-development-warning'; import { type BentoAnchorScrollerItem } from '../../anchor-scroller.types'; import { type AnchorScrollerListProps } from './anchor-scroller-list.types'; import { computed, ref, watch } from 'vue'; import { useCycleList } from '@vueuse/core'; const props = withDefaults(defineProps<AnchorScrollerListProps>(), { activeIndex: undefined, scrollOffset: undefined, }); const listItemRefs = ref([]); const listIndices = computed(() => props.items .map((item, index) => ({ disabled: item.disabled, originalIndex: index })) .filter(({ disabled }) => !disabled) .map(({ originalIndex }) => originalIndex) ); const { state: focusedIndex, next, prev, } = useCycleList(listIndices, { initialValue: props.activeIndex ?? listIndices.value[0], }); watch(focusedIndex, async newIndex => { const targetComponent = listItemRefs.value[newIndex]; const el = unrefElement(targetComponent); // Handles Component or DOM node if (el && typeof el.focus === 'function') { el.focus(); } }); const scrollToSection = (elementRef: BentoAnchorScrollerItem['elementRef'], disabled?: boolean) => { if (disabled) { return; } const el = unrefElement(elementRef); if (!el) { printDevelopmentWarning(`[bento-anchor-scroller] element ref not found`); return; } if (!el.style.scrollMarginTop && props.scrollOffset) { const currentScrollMargin = window.getComputedStyle(el).scrollMarginTop; if (currentScrollMargin === '0px' || currentScrollMargin === '') { el.style.scrollMarginTop = `${props.scrollOffset}px`; } } if (!el.hasAttribute('tabindex')) { el.setAttribute('tabindex', '-1'); } el.scrollIntoView({ behavior: 'smooth', block: 'start', inline: 'nearest', }); el.focus({ preventScroll: true }); }; const onKeydownNavigation = (e: KeyboardEvent) => { switch (e.key) { case 'ArrowRight': e.preventDefault(); next(); break; case 'ArrowLeft': e.preventDefault(); prev(); break; case 'Home': e.preventDefault(); focusedIndex.value = 0; break; case 'End': e.preventDefault(); focusedIndex.value = listIndices.value.length - 1; break; } }; </script> <style lang="scss" scoped src="./anchor-scroller-list.scss" />
@@ -65,7 +65,7 @@ This creates a confusing and inaccessible experience for users of assistive tech
65
65
 
66
66
  When expandable `bento-card` is used, then it's rendered by default with opened state.
67
67
 
68
- You can control the opened/closed behaviour of expandable `bento-card` with 'closed' property.
68
+ You can control the opened/closed behavior of expandable `bento-card` with 'closed' property.
69
69
 
70
70
  <Canvas of={CardStories.ExpandableClosed} />
71
71
 
@@ -135,9 +135,9 @@ const cardButtonActions = [{
135
135
  `}
136
136
  />
137
137
 
138
- > **Important note:** Actions as links will still be rendered as buttons meaning that native link behaviour like right
138
+ > **Important note:** Actions as links will still be rendered as buttons meaning that native link behavior like right
139
139
  > clicking to open in new tab won't work. The Bento team is currently working on a solution to provide native link
140
- > behaviour to the actions.
140
+ > behavior to the actions.
141
141
 
142
142
  ## Accessibility
143
143
 
@@ -1 +1 @@
1
- <template> <Transition name="b-data-grid-cell-actions__animation" appear> <div class="b-data-grid-cell-actions"> <div v-if="showGradientElement && !isTouchDevice" class="b-data-grid-cell-actions__background-gradient" :class="conditionalCellActionsBackgroundGradientClasses" ></div> <div class="b-data-grid-cell-actions__actions" :class="conditionalCellActionsClasses" role="group"> <div v-for="(button, index) in computedCellActions" :key="index" class="b-data-grid-cell-actions__action" > <bento-menu v-if="button.menuData" :data="transformMenuEvents(button.menuData)" :button="{ condensed: true, variant: 'secondary', disabled: button.disabled, }" :disable-arrow-down-opening-menu="disableArrowDownOpeningMenu" :teleport="teleportMenu" @open="emit('menu-open')" @close="emit('menu-close')" @menu-item-click="emit('menu-item-click')" > <template v-if="shouldShowMenuIconLeft(button)" #iconLeft> <options-vertical-icon v-if="button?.variant === 'more'" :svg-title="getButtonTitle(button, true)" /> <component :is="button.iconLeft" v-else :svg-title="getButtonTitle(button, true)" /> </template> <template v-if="!button?.isShort" #default> {{ getButtonTitle(button) }} </template> </bento-menu> <copy v-else-if="button.variant === 'copy'" :text="getCopyText(button)" :button-text="getButtonTitle(button)" :button="{ condensed: true, variant: 'secondary', }" /> <bento-button v-else condensed variant="secondary" :critical="button.critical" :disabled="button.disabled" :link="button.link" v-on="generateButtonEvents(button)" > <template v-if="button.iconLeft || button.variant === 'edit'" #iconLeft> <edit1-icon v-if="button?.variant === 'edit'" :svg-title="getButtonTitle(button, true)" /> <component :is="button.iconLeft" v-else :svg-title="button.title" /> </template> <template v-if="!button?.isShort" #default> {{ getButtonTitle(button) }} </template> <template v-if="button.iconRight || button.variant === 'open'" #iconRight> <external-link-icon v-if="button?.variant === 'open'" :svg-title="getButtonTitle(button, true)" /> <component :is="button.iconRight" v-else :svg-title="button.title" /> </template> </bento-button> </div> </div> </div> </Transition> </template> <script setup lang="ts"> import { computed, inject, type PropType, ref, type Ref } from 'vue'; import { BentoButton } from '@/components/button'; import { BentoMenu, type BentoMenuItem } from '@/components/menu'; import { Copy } from '@/internal/copy'; import { printDevelopmentWarning } from '@/utils/ts/print-development-warning'; import { type BentoDatagridDataItem } from '@/components/data-grid/data-grid.types'; import { DATA_GRID_TOUCH_DEVICE } from '@/components/data-grid/data-grid.keys'; import Edit1Icon from '@adyen/ui-assets-icons-16/vue/edit-1'; import ExternalLinkIcon from '@adyen/ui-assets-icons-16/vue/external-link'; import OptionsVerticalIcon from '@adyen/ui-assets-icons-16/vue/options-vertical'; // Types import type { BentoColumnCellAction, BentoColumnCellActionMenuItem, BentoColumnCellActions, BentoColumnCellActionsByCellDataFn, BentoColumnCellActionsProp, BentoDataGridRowActionsProp, } from './data-grid-cell-actions.types'; // Translations import messages from './messages.json'; import { useI18n } from '@/utils/ts/i18n'; type MessageSchema = (typeof messages)['en-US']; const { t } = useI18n<{ message: MessageSchema }>({ messages }); const props = defineProps({ /** * Column definitions using 'key' to relate to data object. */ cellActions: { type: [Array, Function] as PropType<BentoColumnCellActionsProp | BentoDataGridRowActionsProp>, required: true, validator: (value: BentoColumnCellActions) => value?.length <= 2, }, /** * The column field key. */ columnKey: { type: String, default: undefined, }, /** * Disables any opening of the cell actions menus using the down arrow key. */ disableArrowDownOpeningMenu: { type: Boolean, default: false, }, /** * Set to true if the datagrid has been scrolled and the right side has overflowing space. */ hasRightOverflow: { type: Boolean, default: false, }, /** * Set to true if the row has a darker background when zebra rows is enabled. */ hasZebraBackground: { type: Boolean, default: false, }, /** * Set to true if the row is selected. */ isSelected: { type: Boolean, default: false, }, /** * Set to true if the row is highlighted. */ isHighlighted: { type: Boolean, default: false, }, /** * Set if set to true will teleport the action menu component */ teleportMenu: { type: Boolean, default: false, }, /** * The row data item object. */ rowDataItem: { type: Object as PropType<BentoDatagridDataItem>, required: true, }, }); const emit = defineEmits<{ /** * Emits when a cell actions menu is opened */ (e: 'menu-open'): void; /** * Emits when a cell actions menu is closed */ (e: 'menu-close'): void; /** * Emits when a cell actions menu button closes due to a menu item click */ (e: 'menu-item-click'): void; }>(); const isTouchDevice: Ref<boolean> = inject(DATA_GRID_TOUCH_DEVICE, ref(false)); // Helper to get text for menu items, always returning a string const getMenuItemText = (action: BentoColumnCellAction | BentoColumnCellActionMenuItem) => { // If it's already a menu item with a text property, use it if ('text' in action && action.text !== undefined) { return action.text; } // Otherwise, derive from variant or title switch ((action as BentoColumnCellAction).variant) { case 'copy': return t('copy'); case 'edit': return t('edit'); case 'open': return t('open'); case 'more': return t('more'); default: return (action as BentoColumnCellAction).title; } }; const getMenuItemIcon = (action: BentoColumnCellAction) => { switch (action.variant) { case 'copy': return Copy; case 'edit': return Edit1Icon; case 'open': return ExternalLinkIcon; default: return undefined; } }; const isRowActions = computed(() => props.columnKey === undefined); const conditionalCellActionsBackgroundGradientClasses = computed(() => ({ 'b-data-grid-cell-actions__background-gradient--with-row-actions-and-overflow': isRowActions.value && props.hasRightOverflow, 'b-data-grid-cell-actions__background-gradient--zebra': props.hasZebraBackground, 'b-data-grid-cell-actions__background-gradient--highlighted': props.isHighlighted, 'b-data-grid-cell-actions__background-gradient--selected': props.isSelected, })); const conditionalCellActionsClasses = computed(() => ({ 'b-data-grid-cell-actions__actions--with-row-actions': isRowActions.value, 'b-data-grid-cell-actions__actions--zebra': props.hasZebraBackground, 'b-data-grid-cell-actions__actions--highlighted': props.isHighlighted, 'b-data-grid-cell-actions__actions--selected': props.isSelected, })); const showGradientElement = computed(() => !isRowActions.value || (isRowActions.value && props.hasRightOverflow)); /** * Checks if the passed object is a cell actions function that returns an cell actions array. * @param cellActions - a cell actions array or function */ function isBentoColumnCellActionsByCellValueDataFn( cellActions: BentoColumnCellActionsProp ): cellActions is BentoColumnCellActionsByCellDataFn { return typeof cellActions === 'function'; } const computedCellActions = computed(() => { const cellActions = isBentoColumnCellActionsByCellValueDataFn(props.cellActions) ? props.cellActions?.(props.rowDataItem, props.columnKey) : props.cellActions; if (isTouchDevice.value && !isRowActions.value && cellActions) { const flattenedMenuItems: Array<BentoColumnCellActionMenuItem> = []; cellActions.forEach(action => { if (action.menuData) { action.menuData.forEach(subMenuItem => { flattenedMenuItems.push(subMenuItem); }); } else { flattenedMenuItems.push({ text: getMenuItemText(action), event: action.event, critical: action.critical, disabled: action.disabled, icon: getMenuItemIcon(action), }); } }); return [ { variant: 'more', isShort: true, iconLeft: OptionsVerticalIcon, menuData: transformMenuEvents(flattenedMenuItems), }, ] as BentoColumnCellActions; } // Do not allow users to provide more than 2 cell action buttons if (!isTouchDevice.value && cellActions?.length > 2) { printDevelopmentWarning( 'WARNING: BentoDataGrid Cell actions can only have a maximum of 2 actions, if you need more consider using the menuData property instead.' ); } return cellActions?.slice(0, 2); }); const getButtonTitle = computed(() => (button: BentoColumnCellAction, isForSvgTitle?: boolean) => { if ((!isForSvgTitle && button.isShort) || (isForSvgTitle && !button.isShort)) { return undefined; } switch (button.variant) { case 'copy': return t('copy'); case 'edit': return t('edit'); case 'open': return t('open'); case 'more': return t('more'); default: return button.title; } }); const generateButtonEvents = (button: BentoColumnCellAction) => button?.event !== undefined ? { click: (event: MouseEvent) => { event.stopPropagation(); button.event(props.rowDataItem, props.columnKey); }, } : {}; const generateMenuEvents = (menuDataItem: BentoColumnCellActionMenuItem): BentoMenuItem => ({ ...menuDataItem, handler: () => { if (menuDataItem?.handler && typeof menuDataItem.handler === 'function') { menuDataItem?.handler(); } if (menuDataItem?.event && typeof menuDataItem.event === 'function') { menuDataItem?.event(props.rowDataItem, props.columnKey); } }, }); const transformMenuEvents = (menuData: Array<BentoColumnCellActionMenuItem>) => menuData.map(menuDataItem => { const transformedMenuData = menuDataItem; if (transformedMenuData?.items) { transformedMenuData.items = transformMenuEvents(transformedMenuData.items); } return { ...generateMenuEvents(transformedMenuData) }; }); const getCopyText = (button: BentoColumnCellAction) => button?.getCopyData !== undefined ? button.getCopyData(props.rowDataItem, props.columnKey) : props.columnKey ? (props.rowDataItem?.[props.columnKey]?.toString() as string) : ''; const shouldShowMenuIconLeft = (button: BentoColumnCellAction) => button.iconLeft || button?.variant === 'more'; </script> <script lang="ts"> /** * Internal component to display the data-grid actions. * There are 4 different pre-defined buttons that can be used by using the `variant` property. * * The component renders buttons or a menu depending on the actions provided. * * @example * <data-grid-cell-actions * :cell-actions="[ * { variant: 'edit', event: () => {} }, * { * variant: 'more', * isShort: true, * menuData: [ * { * text: 'Cancel payment', * handler: () => {}), * critical: true, * }, * { * text: 'Transfer payment', * handler: () => {}, * }, * { * text: 'Delete payment', * handler: () => {}, * disabled: true, * }, * ], * }, * ]" * column-key="payment" * :row-data-item="rowData" * /> */ export default { i18n: { messages }, name: 'data-grid-cell-actions', }; </script> <style lang="scss" scoped src="./data-grid-cell-actions.scss" />
1
+ <template> <Transition name="b-data-grid-cell-actions__animation" appear> <div class="b-data-grid-cell-actions"> <div v-if="showGradientElement && !isTouchDevice" class="b-data-grid-cell-actions__background-gradient" :class="conditionalCellActionsBackgroundGradientClasses" ></div> <div class="b-data-grid-cell-actions__actions" :class="conditionalCellActionsClasses" role="group"> <div v-for="(button, index) in computedCellActions" :key="index" class="b-data-grid-cell-actions__action" > <bento-menu v-if="button.menuData" v-bento-tooltip-directive:[button.tooltipPosition]="button.tooltipText" :data="transformMenuEvents(button.menuData)" :button="{ condensed: true, variant: 'secondary', disabled: button.disabled, }" :disable-arrow-down-opening-menu="disableArrowDownOpeningMenu" :teleport="teleportMenu" @open="emit('menu-open')" @close="emit('menu-close')" @menu-item-click="emit('menu-item-click')" > <template v-if="shouldShowMenuIconLeft(button)" #iconLeft> <options-vertical-icon v-if="button?.variant === 'more'" :svg-title="getButtonTitle(button, true)" /> <component :is="button.iconLeft" v-else :svg-title="getButtonTitle(button, true)" /> </template> <template v-if="!button?.isShort" #default> {{ getButtonTitle(button) }} </template> </bento-menu> <copy v-else-if="button.variant === 'copy'" :text="getCopyText(button)" :button-text="getButtonTitle(button)" :button="{ condensed: true, variant: 'secondary', }" /> <bento-button v-else v-bento-tooltip-directive:[button.tooltipPosition]="button.tooltipText" condensed variant="secondary" :critical="button.critical" :disabled="button.disabled" :link="button.link" v-on="generateButtonEvents(button)" > <template v-if="button.iconLeft || button.variant === 'edit'" #iconLeft> <edit1-icon v-if="button?.variant === 'edit'" :svg-title="getButtonTitle(button, true)" /> <component :is="button.iconLeft" v-else :svg-title="button.title" /> </template> <template v-if="!button?.isShort" #default> {{ getButtonTitle(button) }} </template> <template v-if="button.iconRight || button.variant === 'open'" #iconRight> <external-link-icon v-if="button?.variant === 'open'" :svg-title="getButtonTitle(button, true)" /> <component :is="button.iconRight" v-else :svg-title="button.title" /> </template> </bento-button> </div> </div> </div> </Transition> </template> <script setup lang="ts"> import { computed, inject, type PropType, ref, type Ref } from 'vue'; import { BentoButton } from '@/components/button'; import { BentoMenu, type BentoMenuItem } from '@/components/menu'; import { Copy } from '@/internal/copy'; import { BentoTooltipDirective as vBentoTooltipDirective } from '@/directives/tooltip'; import { printDevelopmentWarning } from '@/utils/ts/print-development-warning'; import { type BentoDatagridDataItem } from '@/components/data-grid/data-grid.types'; import { DATA_GRID_TOUCH_DEVICE } from '@/components/data-grid/data-grid.keys'; import Edit1Icon from '@adyen/ui-assets-icons-16/vue/edit-1'; import ExternalLinkIcon from '@adyen/ui-assets-icons-16/vue/external-link'; import OptionsVerticalIcon from '@adyen/ui-assets-icons-16/vue/options-vertical'; // Types import type { BentoColumnCellAction, BentoColumnCellActionMenuItem, BentoColumnCellActions, BentoColumnCellActionsByCellDataFn, BentoColumnCellActionsProp, BentoDataGridRowActionsProp, } from './data-grid-cell-actions.types'; // Translations import messages from './messages.json'; import { useI18n } from '@/utils/ts/i18n'; type MessageSchema = (typeof messages)['en-US']; const { t } = useI18n<{ message: MessageSchema }>({ messages }); const props = defineProps({ /** * Column definitions using 'key' to relate to data object. */ cellActions: { type: [Array, Function] as PropType<BentoColumnCellActionsProp | BentoDataGridRowActionsProp>, required: true, validator: (value: BentoColumnCellActions) => value?.length <= 2, }, /** * The column field key. */ columnKey: { type: String, default: undefined, }, /** * Disables any opening of the cell actions menus using the down arrow key. */ disableArrowDownOpeningMenu: { type: Boolean, default: false, }, /** * Set to true if the datagrid has been scrolled and the right side has overflowing space. */ hasRightOverflow: { type: Boolean, default: false, }, /** * Set to true if the row has a darker background when zebra rows is enabled. */ hasZebraBackground: { type: Boolean, default: false, }, /** * Set to true if the row is selected. */ isSelected: { type: Boolean, default: false, }, /** * Set to true if the row is highlighted. */ isHighlighted: { type: Boolean, default: false, }, /** * Set if set to true will teleport the action menu component */ teleportMenu: { type: Boolean, default: false, }, /** * The row data item object. */ rowDataItem: { type: Object as PropType<BentoDatagridDataItem>, required: true, }, }); const emit = defineEmits<{ /** * Emits when a cell actions menu is opened */ (e: 'menu-open'): void; /** * Emits when a cell actions menu is closed */ (e: 'menu-close'): void; /** * Emits when a cell actions menu button closes due to a menu item click */ (e: 'menu-item-click'): void; }>(); const isTouchDevice: Ref<boolean> = inject(DATA_GRID_TOUCH_DEVICE, ref(false)); // Helper to get text for menu items, always returning a string const getMenuItemText = (action: BentoColumnCellAction | BentoColumnCellActionMenuItem) => { // If it's already a menu item with a text property, use it if ('text' in action && action.text !== undefined) { return action.text; } // Otherwise, derive from variant or title switch ((action as BentoColumnCellAction).variant) { case 'copy': return t('copy'); case 'edit': return t('edit'); case 'open': return t('open'); case 'more': return t('more'); default: return (action as BentoColumnCellAction).title; } }; const getMenuItemIcon = (action: BentoColumnCellAction) => { switch (action.variant) { case 'copy': return Copy; case 'edit': return Edit1Icon; case 'open': return ExternalLinkIcon; default: return undefined; } }; const isRowActions = computed(() => props.columnKey === undefined); const conditionalCellActionsBackgroundGradientClasses = computed(() => ({ 'b-data-grid-cell-actions__background-gradient--with-row-actions-and-overflow': isRowActions.value && props.hasRightOverflow, 'b-data-grid-cell-actions__background-gradient--zebra': props.hasZebraBackground, 'b-data-grid-cell-actions__background-gradient--highlighted': props.isHighlighted, 'b-data-grid-cell-actions__background-gradient--selected': props.isSelected, })); const conditionalCellActionsClasses = computed(() => ({ 'b-data-grid-cell-actions__actions--with-row-actions': isRowActions.value, 'b-data-grid-cell-actions__actions--zebra': props.hasZebraBackground, 'b-data-grid-cell-actions__actions--highlighted': props.isHighlighted, 'b-data-grid-cell-actions__actions--selected': props.isSelected, })); const showGradientElement = computed(() => !isRowActions.value || (isRowActions.value && props.hasRightOverflow)); /** * Checks if the passed object is a cell actions function that returns an cell actions array. * @param cellActions - a cell actions array or function */ function isBentoColumnCellActionsByCellValueDataFn( cellActions: BentoColumnCellActionsProp ): cellActions is BentoColumnCellActionsByCellDataFn { return typeof cellActions === 'function'; } const computedCellActions = computed(() => { const cellActions = isBentoColumnCellActionsByCellValueDataFn(props.cellActions) ? props.cellActions?.(props.rowDataItem, props.columnKey) : props.cellActions; if (isTouchDevice.value && !isRowActions.value && cellActions) { const flattenedMenuItems: Array<BentoColumnCellActionMenuItem> = []; cellActions.forEach(action => { if (action.menuData) { action.menuData.forEach(subMenuItem => { flattenedMenuItems.push(subMenuItem); }); } else { flattenedMenuItems.push({ text: getMenuItemText(action), event: action.event, critical: action.critical, disabled: action.disabled, icon: getMenuItemIcon(action), }); } }); return [ { variant: 'more', isShort: true, iconLeft: OptionsVerticalIcon, menuData: transformMenuEvents(flattenedMenuItems), }, ] as BentoColumnCellActions; } // Do not allow users to provide more than 2 cell action buttons if (!isTouchDevice.value && cellActions?.length > 2) { printDevelopmentWarning( 'WARNING: BentoDataGrid Cell actions can only have a maximum of 2 actions, if you need more consider using the menuData property instead.' ); } return cellActions?.slice(0, 2); }); const getButtonTitle = computed(() => (button: BentoColumnCellAction, isForSvgTitle?: boolean) => { if ((!isForSvgTitle && button.isShort) || (isForSvgTitle && !button.isShort)) { return undefined; } switch (button.variant) { case 'copy': return t('copy'); case 'edit': return t('edit'); case 'open': return t('open'); case 'more': return t('more'); default: return button.title; } }); const generateButtonEvents = (button: BentoColumnCellAction) => button?.event !== undefined ? { click: (event: MouseEvent) => { event.stopPropagation(); button.event(props.rowDataItem, props.columnKey); }, } : {}; const generateMenuEvents = (menuDataItem: BentoColumnCellActionMenuItem): BentoMenuItem => ({ ...menuDataItem, handler: () => { if (menuDataItem?.handler && typeof menuDataItem.handler === 'function') { menuDataItem?.handler(); } if (menuDataItem?.event && typeof menuDataItem.event === 'function') { menuDataItem?.event(props.rowDataItem, props.columnKey); } }, }); const transformMenuEvents = (menuData: Array<BentoColumnCellActionMenuItem>) => menuData.map(menuDataItem => { const transformedMenuData = menuDataItem; if (transformedMenuData?.items) { transformedMenuData.items = transformMenuEvents(transformedMenuData.items); } return { ...generateMenuEvents(transformedMenuData) }; }); const getCopyText = (button: BentoColumnCellAction) => button?.getCopyData !== undefined ? button.getCopyData(props.rowDataItem, props.columnKey) : props.columnKey ? (props.rowDataItem?.[props.columnKey]?.toString() as string) : ''; const shouldShowMenuIconLeft = (button: BentoColumnCellAction) => button.iconLeft || button?.variant === 'more'; </script> <script lang="ts"> /** * Internal component to display the data-grid actions. * There are 4 different pre-defined buttons that can be used by using the `variant` property. * * The component renders buttons or a menu depending on the actions provided. * * @example * <data-grid-cell-actions * :cell-actions="[ * { variant: 'edit', event: () => {} }, * { * variant: 'more', * isShort: true, * menuData: [ * { * text: 'Cancel payment', * handler: () => {}), * critical: true, * }, * { * text: 'Transfer payment', * handler: () => {}, * }, * { * text: 'Delete payment', * handler: () => {}, * disabled: true, * }, * ], * }, * ]" * column-key="payment" * :row-data-item="rowData" * /> */ export default { i18n: { messages }, name: 'data-grid-cell-actions', }; </script> <style lang="scss" scoped src="./data-grid-cell-actions.scss" />
@@ -0,0 +1,35 @@
1
+ import { Canvas, Meta, Controls, Source } from '@storybook/blocks';
2
+ import * as DataGridTagWithTextCellStories from './data-grid-tag-with-text-cell.stories';
3
+ import DataGridTagWithTextCellExample from '../../__tests__/data-grid-tag-with-text-cell-example.vue?raw';
4
+
5
+ <Meta of={DataGridTagWithTextCellStories} />
6
+
7
+ # Data Grid Tag With Text Cell
8
+
9
+ The `bento-data-grid-tag-with-text-cell` is a component designed to be used within cells of the `bento-data-grid`. It
10
+ displays a tag alongside a text value.
11
+
12
+ <Canvas of={DataGridTagWithTextCellStories.Default} />
13
+
14
+ ## Use Cases
15
+
16
+ Use a `bento-data-grid-tag-with-text-cell` to display categorized data with labels, such as:
17
+
18
+ - HTTP method indicators (GET, POST, PUT, DELETE)
19
+ - Status labels with descriptions
20
+ - Category tags with associated values
21
+
22
+ ### Do not use
23
+
24
+ Don't use the `bento-data-grid-tag-with-text-cell` component outside of a data grid. For standalone tags, use
25
+ `bento-tag`.
26
+
27
+ ## How to use
28
+
29
+ The component is intended to be used within a `bento-data-grid`'s item slot.
30
+
31
+ <Source dark language="html" code={DataGridTagWithTextCellExample} />
32
+
33
+ ## Props
34
+
35
+ <Controls />
@@ -0,0 +1 @@
1
+ import BentoDataGridTagWithTextCell from './data-grid-tag-with-text-cell.vue'; import DataGridTagWithTextCellDefaultExample from './__tests__/data-grid-tag-with-text-cell-default-example.vue?raw'; import { storybookDocsParameter } from '@/utils/ts/storybook'; import type { Meta, StoryObj } from '@storybook/vue'; import { isVue2 } from 'vue-demi'; const meta: Meta = { title: 'Data Grid/Components/Data Grid Tag With Text Cell', component: BentoDataGridTagWithTextCell, argTypes: { tag: { control: { type: 'object' }, }, value: { control: { type: 'text' }, }, }, }; export default meta; type Story = StoryObj<typeof BentoDataGridTagWithTextCell>; export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoDataGridTagWithTextCell }, props: Object.keys(argTypes), template: ` <bento-data-grid-tag-with-text-cell v-bind="args" /> `, setup(props) { return { // Values args: isVue2 ? props : _args, }; }, }), args: { tag: { label: 'POST', variant: 'blue' }, value: 'Sample text', condensed: false, }, parameters: storybookDocsParameter(DataGridTagWithTextCellDefaultExample), };
@@ -0,0 +1 @@
1
+ <template> <div class="b-data-grid-tag-with-text-cell"> <bento-tag v-bind="tag" /> <bento-typography el="span">{{ value }}</bento-typography> </div> </template> <script setup lang="ts"> import BentoTag from '@/components/tag/tag.vue'; import BentoTypography from '@/components/typography/typography.vue'; import { type BentoDataGridTagWithTextCellProps } from './data-grid-tag-with-text-cell.types'; defineProps<BentoDataGridTagWithTextCellProps>(); </script> <script lang="ts"> /** * The bento-data-grid-tag-with-text-cell is a component for `bento-data-grid` that displays a tag alongside a text value. * * @example * import { BentoDataGridTagWithTextCell } from '@adyen/bento-vue2'; * * export default { * components: { BentoDataGridTagWithTextCell }, * template: ` * <bento-data-grid-tag-with-text-cell :tag="{ label: 'POST', variant: 'blue' }" value="Sample text" /> * ` * } */ export default {}; </script> <style lang="scss" scoped src="./data-grid-tag-with-text-cell.scss" />
@@ -66,6 +66,7 @@ filter and sort the information.
66
66
  - [Action types](#action-types)
67
67
  - [Components](#components)
68
68
  - [Summary Cell](#summary-cell)
69
+ - [Tag with Text Cell](#tag-with-text-cell)
69
70
  - [Trend Cell](#trend-cell)
70
71
  - [Everything Bagel Example](#everything-bagel-example)
71
72
  - [Server side filtering and sorting](#server-side-filtering-and-sorting)
@@ -170,7 +171,7 @@ This can be done by using the `numeric` boolean property
170
171
 
171
172
  <Canvas of={ComponentStories.EverythingBagelExample} />
172
173
 
173
- It is possible to customise the text overflow for columns - note that this will affect on all row cells for that column.
174
+ It is possible to customize the text overflow for columns - note that this will affect on all row cells for that column.
174
175
 
175
176
  This can be done by using the properties:
176
177
 
@@ -346,7 +347,7 @@ UX rules to decide which one to set:
346
347
  #### Sticky filters
347
348
 
348
349
  By default, filters will be sticky, showing always on top of the page as the user scrolls, along with the column
349
- headers. This behaviour can be disabled by setting `stickyFilters` to false.
350
+ headers. This behavior can be disabled by setting `stickyFilters` to false.
350
351
 
351
352
  ### Pagination
352
353
 
@@ -583,7 +584,7 @@ Enabling this boolean property will add the Column wiew settings functionality.
583
584
 
584
585
  It's possible to nest content inside of each data row in the Datagrid. In order to do this you have to set the
585
586
  `hasNestedContent` to true and then use the `nested-slot` slot to render elements in the slot. This slot passes on the
586
- `BentoDatagridDataItem` object which you can use to customise the slot.
587
+ `BentoDatagridDataItem` object which you can use to customize the slot.
587
588
 
588
589
  You can also use the `shouldShowNestedContent` property to filter which rows should show the nested content slot. This
589
590
  property accepts a function of type `BentoDatagridShouldShowNestedContentFn`. i.e.
@@ -653,15 +654,17 @@ Note: For `rowActions`, the columnKey parameter is not applicable and will not b
653
654
  | ------------- | ------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
654
655
  | `variant` | `string` | Must be set to 'copy'. |
655
656
  | `isShort` | `boolean` | If set to true, will only show icon. |
656
- | `getCopyData` | `(rowDataItem: BentoDatagridDataItem, columnKey: string) => string` | A function that can be used to customise what data is copied upon the user clicking the button. |
657
+ | `getCopyData` | `(rowDataItem: BentoDatagridDataItem, columnKey: string) => string` | A function that can be used to customize what data is copied upon the user clicking the button. |
657
658
 
658
659
  ##### Edit action
659
660
 
660
- | Prop | type | value |
661
- | --------- | ----------------------------------------------------------------- | ----------------------------------------------------------------------- |
662
- | `variant` | `string` | Must be set to 'edit'. |
663
- | `isShort` | `boolean` | If set to true, will only show icon. |
664
- | `event` | `(rowDataItem: BentoDatagridDataItem, columnKey: string) => void` | Click event that will emit the row data and the column key of the cell. |
661
+ | Prop | type | value |
662
+ | ----------------- | ----------------------------------------------------------------- | ----------------------------------------------------------------------- |
663
+ | `variant` | `string` | Must be set to 'edit'. |
664
+ | `isShort` | `boolean` | If set to true, will only show icon. |
665
+ | `event` | `(rowDataItem: BentoDatagridDataItem, columnKey: string) => void` | Click event that will emit the row data and the column key of the cell. |
666
+ | `tooltipText` | `string` | Text showed in the tooltip on hover. |
667
+ | `tooltipPosition` | `BentoTooltipPosition \| string` | Position the tooltip should take if space is available. |
665
668
 
666
669
  ##### Open action
667
670
 
@@ -671,6 +674,8 @@ Note: For `rowActions`, the columnKey parameter is not applicable and will not b
671
674
  | `isShort` | `boolean` | If set to true, will only show icon. |
672
675
  | `menuData` | `BentoColumnCellActionMenuItem[]` | Can be set to an array of `BentoColumnCellActionMenuItem` to create a menu button. |
673
676
  | `menuData[menuItemIndex].event` | `(rowDataItem: BentoDatagridDataItem, columnKey: string) => void` | The menu item click event that will emit the row data and the column key of the cell. |
677
+ | `tooltipText` | `string` | Text showed in the tooltip on hover. |
678
+ | `tooltipPosition` | `BentoTooltipPosition \| string` | Position the tooltip should take if space is available. |
674
679
 
675
680
  ##### More action
676
681
 
@@ -680,6 +685,8 @@ Note: For `rowActions`, the columnKey parameter is not applicable and will not b
680
685
  | `isShort` | `boolean` | If set to true, will only show icon. |
681
686
  | `menuData` | `BentoColumnCellActionMenuItem[]` | Can be set to an array of `BentoColumnCellActionMenuItem` to create a menu button. |
682
687
  | `menuData[menuItemIndex].event` | `(rowDataItem: BentoDatagridDataItem, columnKey: string) => void` | The menu item click event that will emit the row data and the column key of the cell. |
688
+ | `tooltipText` | `string` | Text showed in the tooltip on hover. |
689
+ | `tooltipPosition` | `BentoTooltipPosition \| string` | Position the tooltip should take if space is available. |
683
690
 
684
691
  ##### Custom action
685
692
 
@@ -694,6 +701,8 @@ Note: For `rowActions`, the columnKey parameter is not applicable and will not b
694
701
  | `link` | An object containing `BentoLink` properties. | e.g. `{ to: 'https://adyen.com', external: true }` |
695
702
  | `menuData` | `BentoColumnCellActionMenuItem[]` | Can be set to an array of `BentoColumnCellActionMenuItem` to create a menu button. |
696
703
  | `menuData[menuItemIndex].event` | `(rowDataItem: BentoDatagridDataItem, columnKey: string) => void` | The menu item click event that will emit the row data and the column key of the cell. |
704
+ | `tooltipText` | `string` | Text showed in the tooltip on hover. |
705
+ | `tooltipPosition` | `BentoTooltipPosition \| string` | Position the tooltip should take if space is available. |
697
706
 
698
707
  ## Components
699
708
 
@@ -718,6 +727,17 @@ Passing a `searchTerm` (from the filter bar or global search) to the summary cel
718
727
  You can also use the `tooltipText` property to override the default tooltip text. By default, the tooltip displays
719
728
  `"{numberOfResultsInItems} results"`.
720
729
 
730
+ ### Tag With Text Cell
731
+
732
+ <Canvas of={ComponentStories.TagWithTextCell} />
733
+
734
+ The `bento-data-grid-tag-with-text-cell` is a specialized component for `bento-data-grid` that displays a tag alongside
735
+ a text value. This is useful for showing categorized data with labels, such as status indicators with descriptions.
736
+
737
+ To use it, place `bento-data-grid-tag-with-text-cell` inside a column's item slot (e.g., `#item-endpoint`). Pass a `tag`
738
+ object with [BentoTag properties](../?path=/docs/components-tag--docs#properties) and a `value` string for the text
739
+ content.
740
+
721
741
  ### Trend Cell
722
742
 
723
743
  <Canvas of={ComponentStories.TrendCell} />