@adyen/bento-mcp 0.1.1 → 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.
- package/CHANGELOG.md +5 -0
- package/dist/assets/components/anchor-scroller/anchor-scroller.docs.mdx +55 -0
- package/dist/assets/components/anchor-scroller/anchor-scroller.stories.ts +1 -0
- package/dist/assets/components/anchor-scroller/anchor-scroller.vue +1 -0
- package/dist/assets/components/anchor-scroller/components/anchor-scroller-list/anchor-scroller-list.vue +1 -0
- package/dist/assets/components/data-grid/components/data-grid-cell-actions/data-grid-cell-actions.vue +1 -1
- package/dist/assets/components/data-grid/data-grid.docs.mdx +13 -5
- package/dist/assets/components/data-grid/data-grid.stories.ts +1 -1
- package/dist/assets/components/date-picker/date-picker.vue +1 -1
- package/dist/assets/components/date-range-picker/date-range-picker.vue +1 -1
- package/dist/assets/components/dropdown/components/dropdown-base-textbox/dropdown-base-textbox.vue +1 -1
- package/dist/assets/components/dropdown/components/dropdown-default-textbox/dropdown-default-textbox.vue +1 -1
- package/dist/assets/components/dropdown/components/dropdown-small-textbox/dropdown-small-textbox.vue +1 -1
- package/dist/assets/components/dropdown/dropdown.vue +1 -1
- package/dist/assets/components/file-uploader/file-uploader.vue +1 -1
- package/dist/assets/components/internal/dialog-page/dialog-page.vue +1 -1
- package/dist/assets/components/internal/fixed-scroller/fixed-scroller.vue +1 -1
- package/dist/assets/components/internal/listbox/components/listbox-multi-select/components/listbox-multi-select-options/listbox-multi-select-options.vue +1 -1
- package/dist/assets/components/modal/components/base-modal/base-modal.vue +1 -1
- package/dist/assets/components/modal-fullscreen/modal-fullscreen.vue +1 -1
- package/dist/assets/components/secondary-nav/components/secondary-nav-item/secondary-nav-item.vue +1 -1
- package/dist/assets/components/sidepanel/sidepanel.vue +1 -1
- package/dist/assets/components/stepper/stepper.docs.mdx +8 -1
- package/dist/assets/components/stepper/stepper.stories.ts +1 -1
- package/dist/assets/index.ts +1 -1
- package/dist/main.js +381 -327
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -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" />
|
|
@@ -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" />
|
|
@@ -658,11 +658,13 @@ Note: For `rowActions`, the columnKey parameter is not applicable and will not b
|
|
|
658
658
|
|
|
659
659
|
##### Edit action
|
|
660
660
|
|
|
661
|
-
| Prop
|
|
662
|
-
|
|
|
663
|
-
| `variant`
|
|
664
|
-
| `isShort`
|
|
665
|
-
| `event`
|
|
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. |
|
|
666
668
|
|
|
667
669
|
##### Open action
|
|
668
670
|
|
|
@@ -672,6 +674,8 @@ Note: For `rowActions`, the columnKey parameter is not applicable and will not b
|
|
|
672
674
|
| `isShort` | `boolean` | If set to true, will only show icon. |
|
|
673
675
|
| `menuData` | `BentoColumnCellActionMenuItem[]` | Can be set to an array of `BentoColumnCellActionMenuItem` to create a menu button. |
|
|
674
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. |
|
|
675
679
|
|
|
676
680
|
##### More action
|
|
677
681
|
|
|
@@ -681,6 +685,8 @@ Note: For `rowActions`, the columnKey parameter is not applicable and will not b
|
|
|
681
685
|
| `isShort` | `boolean` | If set to true, will only show icon. |
|
|
682
686
|
| `menuData` | `BentoColumnCellActionMenuItem[]` | Can be set to an array of `BentoColumnCellActionMenuItem` to create a menu button. |
|
|
683
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. |
|
|
684
690
|
|
|
685
691
|
##### Custom action
|
|
686
692
|
|
|
@@ -695,6 +701,8 @@ Note: For `rowActions`, the columnKey parameter is not applicable and will not b
|
|
|
695
701
|
| `link` | An object containing `BentoLink` properties. | e.g. `{ to: 'https://adyen.com', external: true }` |
|
|
696
702
|
| `menuData` | `BentoColumnCellActionMenuItem[]` | Can be set to an array of `BentoColumnCellActionMenuItem` to create a menu button. |
|
|
697
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. |
|
|
698
706
|
|
|
699
707
|
## Components
|
|
700
708
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import { type BentoColumn, BentoColumnOverflow, type BentoDatagridData, type BentoDatagridDataItem, type BentoDatagridDataItemId, BentoDatagridEvent, type BentoDatagridPaginationProps, type BentoDatagridSelection, type BentoDatagridSortByColumn, BentoDatagridSortDirection, } from './data-grid.types'; import { BentoCard } from '@/components/card'; import { action } from '@storybook/addon-actions'; import { type BentoFilterBarModel, type BentoFilterBarSearchBarProps, BentoFilterItemType, type BentoFilterValues, } from '../filter-bar'; import { BentoCurrency } from '@/components/currency'; import { BentoStatus, BentoStatusVariant } from '@/components/status'; import { BentoLink } from '@/components/link'; import { BentoTypography } from '@/components/typography'; import { computed, ref, watch } from 'vue'; import { storybookDocsParameter } from '@/utils/ts/storybook'; import { BentoDate, BentoDateFormat } from '@/components/date'; import { BentoCountry, BentoCountryCode } from '@/components/country'; import type { Meta, StoryObj } from '@storybook/vue'; import BentoDataGrid from './data-grid.vue'; import { isVue2 } from 'vue-demi'; import { BentoButton } from '../button'; import { BentoDashedUnderline } from '../dashed-underline'; import { BentoDataGridSummaryCell, BentoDataGridTagWithTextCell, BentoDataGridTrendCell } from './components'; import type { BentoColumnCellAction, BentoColumnCellActionEvent, BentoColumnCellActionsProp, BentoDataGridRowActionsProp, } from './components/data-grid-cell-actions/data-grid-cell-actions.types'; // Examples import DataGridEverythingBagel from './__tests__/data-grid-everything-bagel.vue?raw'; import DataGridFitContentInCardExample from './__tests__/data-grid-fit-content-in-card-example.vue?raw'; import DataGridWithSlotsExample from './__tests__/data-grid-with-slots-example.vue?raw'; import DataGridFilteringExample from './__tests__/data-grid-filtering-example.vue?raw'; import DataGridSelectionExample from './__tests__/data-grid-selection-example.vue?raw'; import DataGridTrendCellExample from './__tests__/data-grid-trend-cell-example.vue?raw'; import DataGridTagWithTextCellExample from './__tests__/data-grid-tag-with-text-cell-example.vue?raw'; const meta: Meta = { title: 'Data Grid', component: BentoDataGrid, argTypes: { // Values lazyLoadType: { control: { type: 'select', }, }, // Filter bar props filters: { table: { type: { summary: 'BentoFilterBarModel' }, }, }, filterSearchTerm: { name: 'filterSearchTerm', defaultValue: undefined, description: "Filter bar search term useful to filter all the data by text. If the value is non-existant (`null` or `undefined`) the search bar won't be enabled.", control: { type: 'text', }, }, // Slots default: { table: { disable: true }, }, header: { name: 'header', defaultValue: '', description: 'header text', table: { type: { summary: 'VNode[]' }, }, control: { type: 'text', }, }, footer: { name: 'footer', defaultValue: '', description: 'footer text', table: { type: { summary: 'VNode[]' }, }, control: { type: 'text', }, }, 'head-{columnKey}': { name: 'head-{columnKey}', defaultValue: '', description: 'the column slot where `{columnKey}` is the name of the column key. Allows the Column header to be customized.', table: { type: { summary: 'VNode[]' }, category: 'slots', }, control: { disable: true }, }, 'item-{columnKey}': { name: 'item-{columnKey}', defaultValue: '', description: 'the data item slot where `{columnKey}` is the name of the data item column key. Allow row cells to be customized.', table: { type: { summary: 'VNode[]' }, category: 'slots', }, control: { disable: true }, }, ['pagination-slot']: { control: { type: 'text' }, description: 'Passes content to the default pagination slot', table: { type: { summary: 'VNode[]' }, }, }, // Events [BentoDatagridEvent.COLUMN_RESIZE]: { name: BentoDatagridEvent.COLUMN_RESIZE, defaultValue: '', description: 'Triggered when a column is resized. Provides a field name and the new resized width in pixels.', table: { category: 'events', type: { summary: '({ field, newResizedWidth }) => void' }, }, control: { disable: true }, }, [BentoDatagridEvent.SELECT]: { name: BentoDatagridEvent.SELECT, defaultValue: '', description: 'Triggered when a row is selected. Provides an array of data item ids. `BentoDatagridDataItemId[]`', table: { category: 'events', type: { summary: '(selectedDataItemIds) => void' }, }, control: { disable: true }, }, [BentoDatagridEvent.NAVIGATE]: { name: BentoDatagridEvent.NAVIGATE, defaultValue: '', description: `Triggered when the navigation controls in the pagination are clicked (i.e. when the arrow buttons to the rightmost of the screen or the dropdown on the right hand are updated). It indicates to which page it should go next.`, table: { category: 'events', type: { summary: '(pageNumber: number) => void' }, }, control: { disable: true }, }, [BentoDatagridEvent.ITEMS_PAGE]: { name: BentoDatagridEvent.ITEMS_PAGE, defaultValue: '', description: `Triggered when the number of items per page is changed from the left hand dropdown of the pagination. It indicates the new number of items that will be displayed.`, table: { category: 'events', type: { summary: '(itemsPerPage: number) => void' }, }, control: { disable: true }, }, [BentoDatagridEvent.SORT]: { name: BentoDatagridEvent.SORT, defaultValue: '', description: `Triggered when a column is sorted. Provides an array of \`BentoDatagridSortByColumn\` which includes the \`field\` and the sort \`direction\` (\`BentoDatagridSortDirection\`) of the column:`, table: { category: 'events', type: { summary: '(sortByDirections) => void' }, }, control: { disable: true }, }, [BentoDatagridEvent.UPDATE_COLUMNS]: { name: BentoDatagridEvent.UPDATE_COLUMNS, defaultValue: '', description: `Triggered when a the columns have been updated. Provides an updated array of columns \`BentoColumn[]\` which should be passed back into the \`columns\` prop (easily achieved using the [sync modifier](https://v2.vuejs.org/v2/guide/components-custom-events.html#sync-Modifier)) e.g. \`:columns.sync="columns"\``, table: { category: 'events', type: { summary: '(columns) => void' }, }, control: { disable: true }, }, [BentoDatagridEvent.UPDATE_CONDENSED]: { name: 'update:condensed', defaultValue: '', description: `Triggered when the config panel's condensed view toggle is interacted with. Provides the updated condensed \`boolean\`. (easily achieved using the [sync modifier](https://v2.vuejs.org/v2/guide/components-custom-events.html#sync-Modifier)) e.g. \`:condensed.sync="condensed"\``, table: { category: 'events', type: { summary: '(condensed) => void' }, }, control: { disable: true }, }, [BentoDatagridEvent.UPDATE_FILTER_VALUES]: { name: BentoDatagridEvent.UPDATE_FILTER_VALUES, defaultValue: '', description: `Triggered when a the filters in the filterbar have updated. Provides an array of filters \`BentoFilterBarModel[]\` which should be passed back into the \`filter-values\` prop (easily achieved using the [sync modifier](https://v2.vuejs.org/v2/guide/components-custom-events.html#sync-Modifier)) e.g. \`:filter-values.sync="filtersValues"\``, table: { category: 'events', type: { summary: '(filterValues) => void' }, }, control: { disable: true }, }, [BentoDatagridEvent.UPDATE_PAGINATION]: { name: BentoDatagridEvent.UPDATE_PAGINATION, defaultValue: '', description: `Triggered when a the pagination state has been updated. Provides an updated \`pagination\` properties object which should be passed back into the \`pagination\` prop (easily achieved using the [sync modifier](https://v2.vuejs.org/v2/guide/components-custom-events.html#sync-Modifier)) e.g. \`:pagination.sync="pagination"\``, table: { category: 'events', type: { summary: '(paginationProps) => void' }, }, control: { disable: true }, }, [BentoDatagridEvent.UPDATE_SELECTION]: { name: BentoDatagridEvent.UPDATE_SELECTION, defaultValue: '', description: `Triggered when a the select data rows state has been updated. Provides an array of row IDs \`BentoDatagridDataItemId[]\` which should be passed back into the \`selection\` prop (easily achieved using the [sync modifier](https://v2.vuejs.org/v2/guide/components-custom-events.html#sync-Modifier)) e.g. \`:selection.sync="selection"\``, table: { category: 'events', type: { summary: '(selection) => void' }, }, control: { disable: true }, }, [BentoDatagridEvent.UPDATE_SORT_BY]: { name: 'update:sort-by', defaultValue: '', description: `Triggered when a the sorting state has been updated. Provides an array of sorting directions \`BentoDatagridSortByColumn[]\` which should be passed back into the \`sort-by\` prop (easily achieved using the [sync modifier](https://v2.vuejs.org/v2/guide/components-custom-events.html#sync-Modifier)) e.g. \`:sort-by.sync="sortBy"\``, table: { category: 'events', type: { summary: '(sortingDirections) => void' }, }, control: { disable: true }, }, // Filter bar search events [BentoDatagridEvent.FILTER_SEARCH_INPUT]: { name: BentoDatagridEvent.FILTER_SEARCH_INPUT, defaultValue: '', description: "Triggered whenever the filter's search is updated.", table: { category: 'events', type: { summary: '(newSearchTerm) => void' } }, control: { disable: true }, }, [BentoDatagridEvent.FILTER_SEARCH_CLEAR]: { name: BentoDatagridEvent.FILTER_SEARCH_CLEAR, defaultValue: '', description: "Triggered whenever the filter's search is cleared.", table: { category: 'events', type: { summary: '() => void' }, }, control: { disable: true }, }, [BentoDatagridEvent.UPDATE_FILTER_SEARCH_TERM]: { name: BentoDatagridEvent.UPDATE_FILTER_SEARCH_TERM, defaultValue: '', description: "Triggered whenever the filter's search is updated to sync the model.", table: { category: 'events', type: { summary: '(newSearchTerm) => void' }, }, control: { disable: true }, }, [BentoDatagridEvent.ROW_CLICK]: { name: BentoDatagridEvent.ROW_CLICK, defaultValue: '', description: 'Triggered whenever a row has been clicked.', table: { category: 'events', type: { summary: '(row: BentoDatagridDataItem, event: MouseEvent) => void' }, }, }, [BentoDatagridEvent.NESTED_CONTENT_TOGGLED]: { name: BentoDatagridEvent.NESTED_CONTENT_TOGGLED, defaultValue: '', description: 'Triggered whenever a data item toggled is nested content', table: { category: 'events', type: { summary: '(dataItem: BentoDatagridDataItem) => void' }, }, control: { disable: true }, }, }, }; export default meta; type Story = StoryObj<typeof BentoDataGrid>; const DEFAULT_COLUMNS: Array<BentoColumn> = [ { field: 'name', label: 'Name', minWidth: 300, mandatory: true }, { field: 'company', label: 'Company', autoWidth: true }, { field: 'payment', label: 'Payment', minWidth: 350 }, ]; const DEFAULT_DATA: BentoDatagridData = [ { id: '03c0a799-1ced-4017-aea3-0cebb0106335', name: 'Shepherd Kane', payment: 160405, company: 'Merchant Helium', registered: '2023-06-30T13:56:09.498Z', tags: ['deserunt', 'et', 'esse'], reference: 'UnbQaUncf3eR1D8XUBqn_UnbQaUncf3eR1D8XUBqn_UnbQaUncf3eR1D8XUBqn', paymentStatus: 1, countryCode: BentoCountryCode.NL, }, { id: 'b6d4f1fd-b1f6-40a6-a443-f785c89604ef', name: 'Santos Palmer', payment: 163500, company: 'Merchant B', registered: '2023-04-28T13:11:09.498Z', tags: ['et', 'Lorem', 'sint'], reference: 'IxUZ9FsEPCZ9dFFPpI9C', paymentStatus: 2, countryCode: BentoCountryCode.BE, }, { id: '8f9e7877-1ca2-4929-9217-f1ec78709291', name: 'Joyce Johns', payment: 78500, company: 'Merchant C', registered: '2023-06-30T11:01:09.498Z', tags: ['laborum', 'enim', 'ullamco'], reference: 'y0qAQ0WDszRyqLY2WO9f', paymentStatus: 1, countryCode: BentoCountryCode.UA, }, { id: '0789a012-85a6-4657-87bb-319141938a61', name: 'Lynnette Melendez', payment: -45000, company: 'STRALOY', registered: '2019-08-01T13:56:01.498Z', tags: ['sit', 'consectetur', 'amet'], reference: '2vv5pzbRSwx8YsFoGGOC', paymentStatus: 1, countryCode: BentoCountryCode.BR, }, { id: '0b3981b6-c73b-47ad-81d1-b06f9b95d467', name: 'Wagner Mcintosh', payment: 1112, company: 'Merchant A', registered: '2022-12-30T02:12:09.498Z', tags: ['ad', 'ullamco', 'et'], reference: '54KN4jkpBuaRY5xMfe84', paymentStatus: 1, countryCode: BentoCountryCode.US, }, { id: '51d2b31d-d577-4fca-8329-a583babbbdda', name: 'Evans Zamora', payment: 1153.4, company: 'GEEKWAGON', registered: '2023-06-30T13:51:09.498Z', tags: ['consequat', 'magna', 'sit'], reference: 'Sj84kad0MGxpuozTjEk', paymentStatus: 0, countryCode: BentoCountryCode.FR, }, { id: '9bd59a6e-bd03-4477-813f-b844953b7645', name: 'Anderson Middleton', payment: 181722.23, company: 'BALOOBA', registered: '2021-03-02T01:34:09.498Z', tags: ['aliqua', 'adipisicing', 'magna'], reference: 'MtJ5Bnew0MGxpuozTjEk', paymentStatus: 1, countryCode: BentoCountryCode.IT, }, ]; const ADDITIONAL_DATA: BentoDatagridData = [ { id: '03c0a799-1ced-4017-aea3-0cebb0106335', name: 'Additional data fetched here', payment: 160405, company: 'Merchant Helium', registered: '2023-06-30T13:56:09.498Z', tags: ['deserunt', 'et', 'esse'], reference: 'UnbQaUncf3eR1D8XUBqn_UnbQaUncf3eR1D8XUBqn_UnbQaUncf3eR1D8XUBqn', paymentStatus: 1, countryCode: BentoCountryCode.NL, }, { id: 'b6d4f1fd-b1f6-40a6-a443-f785c89604ef', name: 'Santos Palmer', payment: 163500, company: 'Merchant B', registered: '2023-04-28T13:11:09.498Z', tags: ['et', 'Lorem', 'sint'], reference: 'IxUZ9FsEPCZ9dFFPpI9C', paymentStatus: 2, countryCode: BentoCountryCode.BE, }, { id: '8f9e7877-1ca2-4929-9217-f1ec78709291', name: 'Joyce Johns', payment: 78500, company: 'Merchant C', registered: '2023-06-30T11:01:09.498Z', tags: ['laborum', 'enim', 'ullamco'], reference: 'y0qAQ0WDszRyqLY2WO9f', paymentStatus: 1, countryCode: BentoCountryCode.UA, }, { id: '0789a012-85a6-4657-87bb-319141938a61', name: 'Lynnette Melendez', payment: -45000, company: 'STRALOY', registered: '2019-08-01T13:56:01.498Z', tags: ['sit', 'consectetur', 'amet'], reference: '2vv5pzbRSwx8YsFoGGOC', paymentStatus: 1, countryCode: BentoCountryCode.BR, }, { id: '0b3981b6-c73b-47ad-81d1-b06f9b95d467', name: 'Wagner Mcintosh', payment: 1112, company: 'Merchant A', registered: '2022-12-30T02:12:09.498Z', tags: ['ad', 'ullamco', 'et'], reference: '54KN4jkpBuaRY5xMfe84', paymentStatus: 1, countryCode: BentoCountryCode.US, }, { id: '51d2b31d-d577-4fca-8329-a583babbbdda', name: 'Evans Zamora', payment: 1153.4, company: 'GEEKWAGON', registered: '2023-06-30T13:51:09.498Z', tags: ['consequat', 'magna', 'sit'], reference: 'Sj84kad0MGxpuozTjEk', paymentStatus: 0, countryCode: BentoCountryCode.FR, }, { id: '9bd59a6e-bd03-4477-813f-b844953b7645', name: 'Anderson Middleton', payment: 181722.23, company: 'BALOOBA', registered: '2021-03-02T01:34:09.498Z', tags: ['aliqua', 'adipisicing', 'magna'], reference: 'MtJ5Bnew0MGxpuozTjEk', paymentStatus: 1, countryCode: BentoCountryCode.IT, }, ]; const DEFAULT_ARGS = { data: DEFAULT_DATA, columns: DEFAULT_COLUMNS, allowColumnDragAndDrop: false, pagination: {}, emptyState: { title: 'No results were found', image: 'no-results-found', variant: 'embedded', action: { title: 'Clear filters', event: action('clear'), }, description: 'Try a different term or reset search filters', }, }; // Default const DefaultCode = ` <template> <bento-data-grid :allow-row-clicks="false" :allow-column-drag-and-drop="false" :bulk-actions="undefined" :condensed="false" :columns="columns" :column-panel="false" :data="data" :empty-state="undefined" :filters="undefined" :filters-search-term="undefined" :fit-content="false" :get-data-item-id="undefined" :has-resizeable-columns="false" :has-nested-content="false" :highlighted-rows="undefined" :is-data-item-disabled="undefined" lazy-load-type="none" :loading="false" :outline="false" :no-spacing-top="false" :pagination="undefinded" :selectable-accross-pages="false" :sticky-pagination="false" :sticky-filters="false" :selectable="false" :selection="undefined" :sort-by="undefined" :tab-title="undefined" :truncate-column-headers="false" /> </template> <script setup lang="ts"> import { BentoColumn, BentoDataGrid, BentoDatagridData } from '@adyen/bento-vue2'; import { ref } from 'vue'; const data = ref<BentoDatagridData>(${JSON.stringify(DEFAULT_DATA)}); const columns = ref<BentoColumn[]>([ { field: 'name', label: 'Name', minWidth: 300, mandatory: true }, { field: 'company', label: 'Reference' }, { field: 'payment', label: 'Payment', width: 200, minWidth: 150 }, ]); </script> `; export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoDataGrid }, props: Object.keys(argTypes), template: ` <bento-data-grid v-bind="args" /> `, setup(props) { return { args: isVue2 ? props : _args }; }, }), args: DEFAULT_ARGS, parameters: storybookDocsParameter(DefaultCode), }; // Fluid Columns const FluidTemplateCode = ` <template> <bento-data-grid :data="data" :columns="columns" /> </template> <script setup lang="ts"> import { BentoColumn, BentoDataGrid, BentoDatagridData } from '@adyen/bento-vue2'; import { ref } from 'vue'; const data = ref<BentoDatagridData>(${JSON.stringify(DEFAULT_DATA)}); const columns = ref<BentoColumn[]>([ { field: 'name', label: 'Name', flex: 2, mandatory: true }, { field: 'company', label: 'Company', flex: 1 }, { field: 'payment', label: 'Payment', flex: 1, minWidth: 200 }, ]); </script> `; const rowActionsFluid: BentoDataGridRowActionsProp = (item: BentoDatagridDataItem) => { if (item.id === DEFAULT_DATA[3].id) { return [ { variant: 'edit', event: action('edit'), }, ]; } else if (item.id === DEFAULT_DATA[2].id) { return [ { variant: 'edit', event: action('edit') }, { variant: 'open', isShort: true, menuData: [ { text: 'Cancel payment', event: action('cancel payment'), critical: true, }, { text: 'Transfer payment', event: action('transfer payment'), }, { text: 'Delete payment', event: action('delete payment'), disabled: true, }, ], }, ]; } else if (item.id === DEFAULT_DATA[1].id) { return []; } return [ { variant: 'edit', event: action('edit'), }, { title: 'Delete', critical: true, event: action('Delete'), }, ]; }; export const Fluid: Story = { ...Default, args: { ...DEFAULT_ARGS, columns: [ { field: 'name', label: 'Name', flex: 2, mandatory: true }, { field: 'company', label: 'Company', flex: 1 }, { field: 'payment', label: 'Payment', flex: 1, minWidth: 200 }, ], rowActions: rowActionsFluid, }, parameters: storybookDocsParameter(FluidTemplateCode), }; // Slots export const Slots: Story = { render: (_args, { argTypes }) => ({ components: { BentoDataGrid, BentoLink, BentoTypography, }, props: Object.keys(argTypes), template: ` <bento-data-grid v-bind="args" > <template #header> <bento-typography variant="title" el="h2"> Users </bento-typography> </template> <template #head-name="{column}"> <bento-typography variant="caption"> {{ column.label }} </bento-typography> </template> <template #item-name="{item}"> <bento-typography variant="body"> {{ item.name }} </bento-typography> <template v-if="item.company === 'Merchant Helium'"> <div style="display: inline-flex; gap: 4px;"> <bento-typography v-for="itemTag in item.tags" :key="itemTag" variant="caption" :style="{ color: 'var(--b-color-label-secondary)' }" > { {{ itemTag }} } </bento-typography> </div> </template> </template> <template #item-company="{item}"> <bento-link isNotRouting to="https://www.adyen.com" :title="item.company" > {{ item.company }} </bento-link> <bento-typography v-if="item.company === 'Merchant B'" variant="caption" :style="{ color: 'var(--b-color-label-secondary)' }"> {{ item.registered }} </bento-typography> </template> <template #item-payment="{item}"> $ {{ item.payment }} </template> <template #footer> <bento-typography variant="caption"> Footer </bento-typography> </template> </bento-data-grid> `, setup(props) { return { args: isVue2 ? props : _args }; }, }), args: { ...DEFAULT_ARGS, columns: [ { field: 'name', label: 'Name', flex: 2, mandatory: true }, { field: 'company', label: 'Company', flex: 1 }, { field: 'payment', label: 'Payment', flex: 1, minWidth: 200 }, ], }, parameters: storybookDocsParameter(DataGridWithSlotsExample), }; // Sorting const useSort = () => { const sortBy = ref<Array<BentoDatagridSortByColumn>>([]); const data = ref<BentoDatagridData>(DEFAULT_DATA); const sort = (sortedColumns: Array<BentoDatagridSortByColumn>) => { data.value = sortedColumns[0].direction === BentoDatagridSortDirection.ASCENDING ? data.value.sort((a, b) => (a[sortedColumns[0].field] as string).localeCompare(b[sortedColumns[0].field] as string) ) : data.value .sort((a, b) => (a[sortedColumns[0].field] as string).localeCompare(b[sortedColumns[0].field] as string) ) .reverse(); action('sort')(sortedColumns); }; return { data, sortBy, sort }; }; const SortingTemplateCode = ` <template> <bento-data-grid :data="data" :columns="columns" @sort="sort" :sort-by.sync="sortBy" /> </template> <script setup lang="ts"> import { BentoDataGrid, type BentoDatagridSortByColumn, type BentoDatagridData, type BentoColumn, } from '@adyen/bento-vue2' // Sortable fields must have "sortable: true" const columns: BentoColumn = [ { field: 'id', label: 'ID', minWidth: 300, sortable: true, mandatory: true }, { field: 'name', label: 'Name', minWidth: 300, mandatory: true }, ] const data = ref<BentoDatagridData>([ { id: 1, name: 'First' }, { id: 2, name: 'Second' }, ]); const sortBy = ref<Array<BentoDatagridSortByColumn>>([]); const sort = (sortedColumns: Array<BentoDatagridSortByColumn>) => { data.value = sortedColumns[0].direction === BentoDatagridSortDirection.ASCENDING ? data.value.sort((a, b) => (a[sortedColumns[0].field] as number) .toString() .localeCompare(b[sortedColumns[0].field] as string) ) : data.value .sort((a, b) => (a[sortedColumns[0].field] as number) .toString() .localeCompare(b[sortedColumns[0].field] as string) ) .reverse(); } </script> `; export const Sorting: Story = { render: (_args, { argTypes }) => ({ components: { BentoDataGrid }, props: Object.keys(argTypes), template: ` <bento-data-grid :data="data" :columns="columns" :sort-by.sync="sortBy" @sort="sort" /> `, setup(props) { const { data, sortBy, sort } = useSort(); return { args: isVue2 ? props : _args, data, sortBy, sort, }; }, }), args: { ...DEFAULT_ARGS, columns: [ { field: 'name', label: 'Name', minWidth: 300, sortable: true, mandatory: true }, { field: 'company', label: 'Company', minWidth: 150, sortable: true }, { field: 'payment', label: 'Payment', width: 200, minWidth: 150 }, ], sortBy: [], }, parameters: storybookDocsParameter(SortingTemplateCode), }; export const Selection: Story = { render: (_args, { argTypes }) => ({ components: { BentoDataGrid }, props: Object.keys(argTypes), template: ` <div> <bento-data-grid v-bind="args" :data="pagedData" :pagination="pagination" :filters="filtersConfig" :filter-values="filterValues" :is-data-item-disabled="isDataItemDisabled" :selection="selection" :filter-search-term="searchTerm" @update:filter-search-term="searchTerm = $event" @update:pagination="pagination = $event" @update:filter-values="filterValues = $event" @select-all-across-pages="onSelectAllAcrossPages" @select-all-current-results="onSelectAllInCurrentResults" @deselect-all-current-results="onDeselectAllInCurrentResults" @filter-search-clear="onSearchCleared" @update:selection="onSelect" @row-click="rowClickAction" /> </div> `, setup(props) { const args = isVue2 ? props : _args; const selection = ref([]); const filtersConfig = computed<BentoFilterBarModel>(() => args?.filters ?? FILTERS); const filterValues = ref<BentoFilterValues>([]); const searchTerm = ref(''); const data = ref( Array.from({ length: 20 }, (_, i) => ({ id: `id-${i + 1}`, name: `Name ${i + 1}`, payment: (i + 1) * 100, company: `Merchant ${i + 1}`, })) ); const isDataItemDisabled = (row: BentoDatagridDataItem) => { return row.id === data.value[3].id; }; const pagination = ref<BentoDatagridPaginationProps>({ page: 1, size: 10, totalCount: 20, hasNext: true, }); const filteredData = computed<BentoDatagridData>(() => { let internalData = data.value; if (searchTerm.value) { const lowerCaseSearchTerm = searchTerm.value.toLowerCase(); const searchableFields = args.columns.map(column => column.field); internalData = internalData.filter((item: BentoDatagridDataItem) => { return searchableFields.some(field => { const value = String(item[field]); return value.toLowerCase().includes(lowerCaseSearchTerm); }); }); } return internalData.filter(item => filterValues.value.every(filter => filter.value ? String(item[filter.field]).toLowerCase().includes(String(filter.value).toLowerCase()) : true ) ); }); const pagedData = computed<BentoDatagridData>(() => { return filteredData.value.slice( (pagination.value.page - 1) * pagination.value.size, pagination.value.page * pagination.value.size ); }); const enabledIds = computed(() => data.value.filter(item => !isDataItemDisabled(item)).map(x => x.id)); const enabledFilteredIds = computed(() => filteredData.value.filter(item => !isDataItemDisabled(item)).map(x => x.id) ); const onSelect = value => { selection.value = value; action('update:selection')(value); }; const onSelectAllAcrossPages = () => { selection.value = enabledIds.value; action('select-all-across-pages')(); }; const onSelectAllInCurrentResults = () => { selection.value = [...new Set([...selection.value, ...enabledFilteredIds.value])]; action('select-all-current-results')(); }; const onDeselectAllInCurrentResults = () => { selection.value = selection.value.filter(item => !enabledFilteredIds.value.includes(item)); action('deselect-all-current-results')(); }; const onSearchCleared = () => { searchTerm.value = ''; action('search-clear')(); }; watch( filteredData, () => { pagination.value.totalCount = filteredData.value.length; }, { immediate: true } ); return { args, onSelect, onSelectAllAcrossPages, onSelectAllInCurrentResults, onDeselectAllInCurrentResults, pagination, selection, pagedData, filtersConfig, filterValues, searchTerm, rowClickAction: value => action('row-click')(value), isDataItemDisabled, onSearchCleared, }; }, }), args: { ...DEFAULT_ARGS, selectable: true, allowRowClicks: true, selectableAcrossPages: true, bulkActions: [ { title: 'Edit', event: () => true, }, { title: 'Delete', critical: true, event: () => true, }, ], }, parameters: storybookDocsParameter(DataGridSelectionExample), }; // Filtering const FILTERS: BentoFilterBarModel = [ { field: 'name', label: 'Name', type: BentoFilterItemType.INPUT, }, { field: 'company', label: 'Company', type: BentoFilterItemType.INPUT, }, ]; export const Filtering: Story = { render: (_args, { argTypes }) => ({ components: { BentoDataGrid }, props: Object.keys(argTypes), setup(props) { const args = isVue2 ? props : _args; const filtersConfig = computed<BentoFilterBarModel>(() => args?.filters ?? FILTERS); const filterValues = ref<BentoFilterValues>([]); const searchTerm = ref(''); const searchConfig = ref<BentoFilterBarSearchBarProps>({ inputFieldAriaLabel: 'Search for items', placeholder: 'Placeholder', hint: 'Hint', }); const dataSet = computed<BentoDatagridData>(() => { let filteredData = args.data; // Apply global search term filter if (searchTerm.value) { const lowerCaseSearchTerm = searchTerm.value.toLowerCase(); const searchableFields = args.columns.map(column => column.field); filteredData = filteredData.filter((item: BentoDatagridDataItem) => { return searchableFields.some(field => { const value = String(item[field]); return value.toLowerCase().includes(lowerCaseSearchTerm); }); }); } filteredData = filteredData.filter(item => filterValues.value?.every(filter => { return filter.value ? String(item[filter.field]).toLowerCase().includes(String(filter.value).toLowerCase()) : true; }) ); return filteredData; }); watch(filterValues, () => { action('filter-values:update')(filterValues.value); }); const onSearchCleared = action('search-clear'); const onSearchInput = action('search-input'); const onSearchTermUpdated = action('update:search-term'); return { // Values dataSet, filtersConfig, filterValues, searchTerm, searchConfig, // Methods onSearchCleared, onSearchInput, onSearchTermUpdated, // Vue 3 storybook fix args, }; }, template: ` <bento-data-grid v-bind="args" :data="dataSet" :filters="filtersConfig" :filter-values.sync="filterValues" :filter-search-term.sync="searchTerm" :filter-search-config="searchConfig" @filter-search-clear="onSearchCleared" @filter-search-input="onSearchInput" @update:filter-search-term="onSearchTermUpdated" /> `, }), args: DEFAULT_ARGS, parameters: storybookDocsParameter(DataGridFilteringExample), }; // No Results const NoResultsTemplateCode = ` <bento-data-grid :data="[]" :columns="[ { field: 'name', label: 'Name', minWidth: 300, mandatory: true }, { field: 'company', label: 'Reference' }, { field: 'payment', label: 'Payment', width: 200, minWidth: 150 }, ]" :empty-state="{ title: 'No results were found', image: 'no-results-found', variant: 'embedded', action: { title: 'Clear filters', event: clearFiltersFunction, }, description: 'Try a different term or reset search filters', }" /> `; export const NoResults: Story = { ...Filtering, args: { ...DEFAULT_ARGS, data: [], filters: FILTERS, filterValues: [{ field: 'company', value: 'CIA' }], }, parameters: storybookDocsParameter(NoResultsTemplateCode), }; // Pagination const PaginationTemplateCode = ` <template> <bento-data-grid :data="dataGridData" :columns="columnsConfig" :pagination.sync="pagination" @update:pagination="onUpdatePagination" /> </template> <script lang="ts" setup> const data="[{ id: '1', name: 'Jane' }, { id: '1', name: 'John' }]" const pagination = ref({ description: 'datagrid navigation', hasNext: true, size: 10 }); // With API call watch( () => pagination.value, () => { makeAPICall(pagination.value); } ); // Without API call const onUpdatePagination = (newPagination: BentoDatagridPaginationProps) => { pagination.value = { ...newPagination }; const newSetStartIndex = pagination.value.page * pagination.value.size; const amountOfItems = newSetStartIndex + pagination.value.size - 1; data.value = arrayWithFullSetOfValues.slice(newSetStartIndex, amountOfItems); }; </script> `; export const Pagination: Story = { render: (_args, { argTypes }) => ({ components: { BentoDataGrid }, props: Object.keys(argTypes), template: ` <bento-data-grid v-bind="args" :data="dataGridData" :pagination.sync="pagination" @update:pagination="updatePagination" @items-page="updateItemsPerPage" /> `, setup(props) { const args = isVue2 ? props : _args; const largeData = [ ...DEFAULT_ARGS.data, ...DEFAULT_ARGS.data.map(dataItem => ({ ...dataItem, id: `${dataItem.id}+1` })), ]; const pagination = ref(args.pagination); const dataGridData = computed(() => { const end = pagination.value.size <= largeData.length ? pagination.value.size : largeData.length; return largeData.slice(pagination.value.page - 1, end); }); const updateItemsPerPage = (itemsPerPage: number) => { action('items-page')(itemsPerPage); }; const updatePagination = (newPagination: BentoDatagridPaginationProps) => { action('navigate')(newPagination.page); action('update:pagination')(newPagination); }; return { // Values dataGridData, pagination, // Methods updateItemsPerPage, updatePagination, // Vue 3 storybook fixes args: isVue2 ? props : _args, }; }, }), args: { ...DEFAULT_ARGS, pagination: { hasNext: true, description: 'datagrid navigation', size: 10, page: 1, totalCount: DEFAULT_ARGS.data.length * 2, }, }, parameters: storybookDocsParameter(PaginationTemplateCode), }; // Payment Status (demo use case for datagrid V2) export const EverythingBagelExample: Story = { render: (_args, { argTypes }) => ({ components: { BentoDataGrid, BentoLink, BentoStatus, BentoCurrency, BentoDate, BentoCountry, BentoButton }, props: Object.keys(argTypes), template: ` <bento-data-grid v-bind="args" :data="filteredAndSortedData" :columns.sync="columns" :condensed="isCondensed" :filters="filtersConfig" :filter-values.sync="filterValues" :filter-search-term.sync="searchTerm" :sort-by="sortBy" @update:sort-by="sortBy = $event" @update:condensed="isCondensed = $event" :selection.sync="selection" :is-data-item-disabled="isDataItemDisabledForSelection" :row-actions="rowActions" selectable @row-click="onRowClick" > <template #item-paymentStatus="{item}"> <div style="display: flex"> <bento-status :variant="getPaymentStatus(item.paymentStatus)" /> <span>{{ getPaymentText(item.paymentStatus) }}</span> </div> </template> <template #item-reference="{item}"> <bento-link isNotRouting to="https://www.adyen.com" :title="item.reference" > {{ item.reference }} </bento-link> </template> <template #item-countryCode="{item}"> <bento-country :code="item.countryCode" /> </template> <template #item-payment="{item}"> <bento-currency currency="EUR" :value="item.payment" /> </template> <template #item-date="{item}"> <bento-date :date="new Date(item.registered)" :format='BentoDateFormat.FULL_DATE_TIME_WITH_PERIOD'/> </template> </bento-data-grid> `, setup(props) { const args = isVue2 ? props : _args; const columns = ref<Array<BentoColumn>>(args.columns); const filtersConfig = ref<BentoFilterBarModel>(args.filters); const filterValues = ref<BentoFilterValues>(args?.filterValues); const sortBy = ref<Array<BentoDatagridSortByColumn>>([]); const searchTerm = ref(''); const isCondensed = ref(args.condensed); const getPaymentStatus = (paymentStatus: number) => { switch (paymentStatus) { case 2: return BentoStatusVariant.GREEN; case 1: return BentoStatusVariant.YELLOW; case 0: default: return BentoStatusVariant.RED; } }; const getPaymentText = (paymentStatus: number) => { switch (paymentStatus) { case 2: return 'Paid'; case 1: return 'In progress'; case 0: default: return 'Not paid'; } }; const onRowClick = (item: BentoDatagridDataItem) => { action('row-click')(item); }; // Selection const isDataItemDisabledForSelection = (row: BentoDatagridDataItem) => row.id === DEFAULT_DATA[3].id; const selection = ref<BentoDatagridSelection>([DEFAULT_DATA[1].id]); // Filtering const filteredData = computed(() => // If any filters or search terms have been set filterValues.value?.length === 0 && searchTerm.value === '' ? args.data : args.data.filter(item => { const foundFilteredItem = filterValues.value?.length > 0 ? filterValues.value.every(filter => filter.value ? (item[filter.field] as string) .toLowerCase() .includes((filter.value as string).toLowerCase()) : true ) : true; const foundSearchFilteredItem = searchTerm.value ? (item?.company as string) .toLowerCase() .includes((searchTerm.value as string).toLowerCase()) : true; return foundFilteredItem && foundSearchFilteredItem; }) ); const filteredAndSortedData = computed(() => { if (sortBy.value?.length === 0) { return filteredData.value; } return sortBy.value[0].direction === BentoDatagridSortDirection.ASCENDING ? filteredData.value.sort((a, b) => (a[sortBy.value[0].field] as string).localeCompare(b[sortBy.value[0].field] as string) ) : filteredData.value .sort((a, b) => (a[sortBy.value[0].field] as string).localeCompare(b[sortBy.value[0].field] as string) ) .reverse(); }); const rowActions: BentoDataGridRowActionsProp = (item: BentoDatagridDataItem) => { if (item.id === DEFAULT_DATA[3].id) { return [ { variant: 'edit', event: action('edit'), }, ]; } else if (item.id === DEFAULT_DATA[2].id) { return [ { variant: 'edit', event: action('edit') }, { variant: 'open', isShort: true, menuData: [ { text: 'Cancel payment', event: action('cancel payment'), critical: true, }, { text: 'Transfer payment', event: action('transfer payment'), }, { text: 'Delete payment', event: action('delete payment'), disabled: true, }, ], }, ]; } else if (item.id === DEFAULT_DATA[1].id) { return []; } return [ { variant: 'edit', event: action('edit'), }, { title: 'Delete', critical: true, event: action('Delete'), }, ]; }; return { // Methods getPaymentStatus, getPaymentText, // Functions onRowClick, isDataItemDisabledForSelection, // Values columns, filteredAndSortedData, sortBy, filtersConfig, filterValues, searchTerm, isCondensed, selection, rowActions, // Enums BentoDateFormat, // Vue 3 storybook fix args, }; }, }), args: { ...DEFAULT_ARGS, zebraRows: true, filters: [ { field: 'company', label: 'Company', value: null, type: BentoFilterItemType.INPUT, }, { field: 'countryCode', label: 'Country/Region', value: undefined, options: { listboxItems: [ { value: BentoCountryCode.NL, }, { value: BentoCountryCode.BE, }, ], variant: 'name', }, type: BentoFilterItemType.SELECT_COUNTRY, }, ], columns: [ { field: 'company', label: 'Company', sortable: true, mandatory: true, autoWidth: true, frozen: true, minWidth: 200, }, { field: 'paymentStatus', label: 'Status', width: 180, infoIconTooltip: 'For current day' }, { field: 'reference', label: 'Reference', width: 320, overflow: BentoColumnOverflow.ELLIPSIS }, { field: 'countryCode', label: 'Country/Region', width: 320 }, { field: 'payment', label: 'Payment', width: 180, numeric: true }, { field: 'date', label: 'Execution Date', width: 200 }, ], allowRowClicks: true, allowColumnDragAndDrop: true, columnPanel: true, highlightedRows: [DEFAULT_DATA[0].id, DEFAULT_DATA[4].id], }, parameters: storybookDocsParameter(DataGridEverythingBagel), }; export const DisplayOptions = (_args, { argTypes }) => ({ components: { BentoDataGrid, BentoDate }, props: Object.keys(argTypes), template: ` <bento-data-grid v-bind="args" :columns.sync="columns" :condensed.sync="isCondensed"> <template #item-date="{item}"> <bento-date :date="new Date(item.registered)" :format='BentoDateFormat.FULL_DATE_TIME_WITH_PERIOD'/> </template> </bento-data-grid> `, setup(props) { const args = isVue2 ? props : _args; const isCondensed = ref(args.condensed); const columns = ref([ { field: 'name', label: 'Name', minWidth: 200, mandatory: true }, { field: 'reference', label: 'Reference', width: 320, overflow: BentoColumnOverflow.ELLIPSIS, frozen: true, }, { field: 'company', label: 'Company', minWidth: 150, width: 250, frozen: true }, { field: 'payment', label: 'Payment', width: 200, minWidth: 150 }, { field: 'countryCode', label: 'Country/Region', width: 280 }, { field: 'date', label: 'Execution Date', width: 300 }, ]); return { // Values columns, isCondensed, // Enums BentoDateFormat, // Vue 3 storybook fix args, }; }, }); DisplayOptions.args = { ...DEFAULT_ARGS, allowColumnDragAndDrop: true, columnPanel: true, }; DisplayOptions.parameters = storybookDocsParameter(` <bento-data-grid column-panel :data="dataArray" :columns.sync="columns" :condensed.sync="isCondensed" />`); export const GridInCard: Story = { render: (_args, { argTypes }) => ({ components: { BentoCard, BentoDataGrid, BentoDate }, props: Object.keys(argTypes), template: ` <div style="max-width: 500px;"> <bento-card> Grid in a card <template #content> <div style="height: 300px;"> <bento-data-grid v-bind="args" :columns.sync="columns" :condensed.sync="isCondensed" :pagination="pagination" fit-content > <template #item-date="{item}"> <bento-date :date="new Date(item.registered)" :format='BentoDateFormat.FULL_DATE_TIME_WITH_PERIOD'/> </template> </bento-data-grid> </div> </template> </bento-card> </div> `, setup(props) { const args = isVue2 ? props : _args; const isCondensed = ref(args.condensed); const pagination = ref({ hasNext: true, size: 20, page: 1, description: 'datagrid navigation', }); const columns = ref([ { field: 'name', label: 'Name', minWidth: 200, mandatory: true }, { field: 'reference', label: 'Reference', width: 320, overflow: BentoColumnOverflow.ELLIPSIS, }, { field: 'company', label: 'Company', minWidth: 150, width: 250 }, { field: 'payment', label: 'Payment', width: 200, minWidth: 150 }, { field: 'countryCode', label: 'Country/Region', width: 280 }, { field: 'date', label: 'Execution Date', width: 300 }, ]); return { // Values columns, isCondensed, pagination, // Enums BentoDateFormat, // Vue 3 storybook fix args, }; }, }), args: { ...DEFAULT_ARGS, columnPanel: false, }, parameters: storybookDocsParameter(DataGridFitContentInCardExample), }; // Default const lazyLoadingCode = ` // With infinite scroll <bento-data-grid :data="dataArray" :columns="[ { field: 'name', label: 'Name', minWidth: 300, mandatory: true }, { field: 'company', label: 'Reference' }, { field: 'payment', label: 'Payment', width: 200, minWidth: 150 }, ]" lazy-load-type="automatic" :loading="yourLoadingLogic" @update:load-more="yourInfiniteScrollHandler" /> // With 'load more' button <bento-data-grid :data="dataArray" :columns="[ { field: 'name', label: 'Name', minWidth: 300, mandatory: true }, { field: 'company', label: 'Reference' }, { field: 'payment', label: 'Payment', width: 200, minWidth: 150 }, ]" lazy-load-type="button" :loading="yourLoadingLogic" @update:load-more="yourInfiniteScrollHandler" />`; export const lazyLoading: Story = { render: (_args, { argTypes }) => ({ components: { BentoDataGrid }, props: Object.keys(argTypes), template: ` <div style="height: 300px;"> <bento-data-grid v-bind="args" :lazy-load-type="lazyLoadType" :loading="loading" @update:show-more="infiniteScrollHandler"/> </div> `, setup(props) { const args = isVue2 ? props : _args; const loading = ref(false); const data = ref(DEFAULT_DATA); const infiniteScrollHandler = () => { loading.value = true; setTimeout(() => { data.value.push(...ADDITIONAL_DATA); loading.value = false; }, 2000); }; return { args, loading, infiniteScrollHandler, }; }, }), args: { ...DEFAULT_ARGS, lazyLoadType: 'automatic', }, parameters: storybookDocsParameter(lazyLoadingCode), }; // Added just to be able to easily play around with the sticky headers/pagination/filters // Not really relevant as a Storybook example, so not adding storybook code example export const LongDataWithStickyFiltersAndPagination: Story = { render: (_args, { argTypes }) => ({ components: { BentoDataGrid }, props: Object.keys(argTypes), setup(props) { const args = isVue2 ? props : _args; const filtersConfig = ref<BentoFilterBarModel>([ { field: 'col1', label: 'Column 1', type: BentoFilterItemType.INPUT, }, { field: 'col2', label: 'Column 2', type: BentoFilterItemType.INPUT, }, ]); const filterValues = ref(); const searchTerm = ref(''); const isCondensed = ref(args.condensed); const columns = ref( Array.from({ length: 20 }, (_, index) => ({ field: `col${index + 1}`, label: `Column ${index + 1}`, width: 200, mandatory: index === 0, })) ); const data = ref( Array.from({ length: 50 }, (_, rowIndex) => columns.value.reduce((acc, col, colIndex) => { acc[col.field] = `Row ${rowIndex + 1} - Col ${colIndex + 1}`; return acc; }, {}) ) ); const dataSet = computed(() => // If any filters or search terms have been set filterValues.value?.length === 0 && searchTerm.value === '' ? data.value : data.value.filter(item => { const foundFilteredItem = filterValues.value?.length > 0 ? filterValues.value.every(filter => filter.value ? (item[filter.field] as string) .toLowerCase() .includes((filter.value as string).toLowerCase()) : true ) : true; const foundSearchFilteredItem = searchTerm.value ? Object.keys(item).some(key => key ? (item[key] as string) .toLowerCase() .includes((searchTerm.value as string).toLowerCase()) : true ) : true; return foundFilteredItem && foundSearchFilteredItem; }) ); const onSearchCleared = action('search-clear'); const onSearchInput = action('search-input'); const onSearchTermUpdated = action('update:search-term'); return { // Values dataSet, filtersConfig, filterValues, searchTerm, columns, isCondensed, // Methods onSearchCleared, onSearchInput, onSearchTermUpdated, // Vue 3 storybook fix args, }; }, template: ` <bento-data-grid v-bind="args" :data="dataSet" :columns.sync="columns" :condensed.sync="isCondensed" :filters="filtersConfig" :filter-values.sync="filterValues" :filter-search-term.sync="searchTerm" @filter-search-clear="onSearchCleared" @filter-search-input="onSearchInput" @update:filter-search-term="onSearchTermUpdated" /> `, }), args: { ...DEFAULT_ARGS, columnPanel: true, stickyPagination: true, stickyFilters: true }, }; // Cell actions const CellActionsTemplateCode = ` <template> <bento-data-grid :data="data" :columns="columns" /> </template> <script setup lang="ts"> import { ref } from 'vue'; import { BentoDataGrid, type BentoColumn, type BentoDatagridData, type BentoDatagridDataItem, type BentoColumnCellActionEvent, type BentoColumnCellAction, } from '@adyen/bento-vue2'; // --- Action Handlers --- const editItem: BentoColumnCellActionEvent = (rowDataItem, columnKey) => { // to be implemented }; const cancelAction: BentoColumnCellActionEvent = (rowDataItem, columnKey) => { // to be implemented }; const transferAction: BentoColumnCellActionEvent = (rowDataItem, columnKey) => { // to be implemented }; const deleteAction: BentoColumnCellActionEvent = (rowDataItem, columnKey) => { // to be implemented }; // --- Dynamic Cell Actions Function --- // This function generates actions based on the row's data. const getNameCellActions = (rowDataItem: BentoDatagridDataItem): Array<BentoColumnCellAction> => { const actions: Array<BentoColumnCellAction> = []; // Conditionally add the 'edit' action only for the first row. if (rowDataItem.id === 1) { actions.push({ variant: 'edit', event: editItem }); } // Add a 'more' menu with dynamic options. actions.push({ variant: 'more', isShort: true, menuData: [ { text: 'Cancel payment', event: cancelAction, critical: true, }, { text: 'Transfer payment', event: transferAction, }, { text: 'Delete payment', event: deleteAction, // Example: disable delete for the second row. disabled: rowDataItem.id === 2, }, ], }); return actions; }; // --- Column Definitions --- const columns: Array<BentoColumn> = [ { field: 'name', label: 'Name (Dynamic Actions)', minWidth: 300, mandatory: true, // Use a function for dynamic cell actions cellActions: getNameCellActions, }, { field: 'company', label: 'Reference', autoWidth: true }, { field: 'payment', label: 'Payment (Static Actions)', minWidth: 350, // Use a static array for cell actions cellActions: [ { variant: 'copy', getCopyData: (rowDataItem: BentoDatagridDataItem, columnKey: string) => \`\${rowDataItem[columnKey]}\`, }, { variant: 'open', event: action('open-payment') }, ], }, ]; // --- Data --- const data = ref<BentoDatagridData>([ { id: 1, name: 'First', payment: 20.9 }, { id: 2, name: 'Second', payment: 10.0 }, ]); </script> `; export const CellActions: Story = { render: (_args, { argTypes }) => ({ components: { BentoDataGrid }, props: Object.keys(argTypes), template: ` <bento-data-grid v-bind="args" :columns="columns"> <template #item-custom="{item}"> Value </template> </bento-data-grid> `, setup(props) { // --- Action Handlers --- const editItem: BentoColumnCellActionEvent = (rowDataItem, columnKey) => { action('edit')(rowDataItem, columnKey); }; const cancelAction: BentoColumnCellActionEvent = (rowDataItem, columnKey) => { action('cancel')(rowDataItem, columnKey); }; const transferAction: BentoColumnCellActionEvent = (rowDataItem, columnKey) => { action('transfer')(rowDataItem, columnKey); }; const deleteAction: BentoColumnCellActionEvent = (rowDataItem, columnKey) => { action('delete')(rowDataItem, columnKey); }; // --- Dynamic Cell Actions Function --- // This function generates actions based on the row's data. const getNameCellActions: BentoColumnCellActionsProp = rowDataItem => { const actions: Array<BentoColumnCellAction> = []; // Conditionally add the 'edit' action only for the first row. if (rowDataItem.id === DEFAULT_DATA[3].id) { actions.push({ variant: 'edit', event: editItem }); } // Add a 'more' menu with dynamic options. actions.push({ variant: 'more', isShort: true, menuData: [ { text: 'Cancel payment', event: cancelAction, critical: true, }, { text: 'Transfer payment', event: transferAction, }, { text: 'Delete payment', event: deleteAction, // Example: disable delete for the second row. disabled: rowDataItem.id === DEFAULT_DATA[2].id, }, ], }); return actions; }; // --- Column Definitions --- const columns: Array<BentoColumn> = [ { field: 'name', label: 'Name (Dynamic Actions)', minWidth: 300, mandatory: true, // Use a function for dynamic cell actions cellActions: getNameCellActions, }, { field: 'company', label: 'Reference', autoWidth: true }, { field: 'payment', label: 'Payment (Static Actions)', minWidth: 350, // Use a static array for cell actions cellActions: [ { variant: 'copy', getCopyData: (rowDataItem: BentoDatagridDataItem, columnKey: string) => `${rowDataItem[columnKey]}`, }, { variant: 'open', event: action('open-payment') }, ], }, ]; return { args: isVue2 ? props : _args, columns }; }, }), args: { ...DEFAULT_ARGS, highlightedRows: [DEFAULT_DATA[3].id], columns: [ { field: 'name', label: 'Name (Dynamic Actions)', minWidth: 300, mandatory: true, cellActions: [ () => (rowDataItem: BentoDatagridDataItem) => ({ variant: 'edit', disabled: rowDataItem.id === DEFAULT_DATA[3].id, event: action('edit'), }), { variant: 'open', menuData: [ { text: 'Cancel payment', event: action('cancel payment'), critical: true, }, { text: 'Transfer payment', event: action('transfer payment'), }, { text: 'Delete payment', event: action('delete payment'), disabled: true, }, ], }, ], }, { field: 'company', label: 'Reference', autoWidth: true }, { field: 'payment', label: 'Payment (Static Actions)', minWidth: 350, cellActions: [ { variant: 'copy', getCopyData: (rowDataItem: BentoDatagridDataItem, columnKey: string) => `$${rowDataItem[columnKey]}`, }, { variant: 'more', menuData: [ { text: 'Cancel payment', event: action('cancel payment'), critical: true, }, { text: 'Transfer payment', event: action('transfer payment'), }, { text: 'Delete payment', event: action('delete payment'), disabled: true, }, ], isShort: true, }, ], }, { field: 'custom', label: 'Custom template', minWidth: 200, cellActions: [{ variant: 'edit', event: action('edit') }], }, ], }, parameters: storybookDocsParameter(CellActionsTemplateCode), }; // NestContentSlot const NestedContentSlotTemplateCode = ` <template> <bento-data-grid :data="data" :columns="columns" has-nested-content :should-show-nested-content="filterRowsThatShowNestedContent" > <template #nested-slot="{item}"> <bento-typography>Nested content for {{ item.company }}</bento-typography> </template> </bento-data-grid> </template> <script setup lang="ts"> import { BentoDataGrid, type BentoColumn, type BentoDatagridData, type BentoDatagridShouldShowNestedContent } from '@adyen/bento-vue2' const columns: BentoColumn = [ { field: 'name', label: 'Name', minWidth: 300, mandatory: true }, { field: 'payment', label: 'Payment', minWidth: 300, mandatory: true }, ] const data = ref<BentoDatagridData>([ { id: 1, name: 'First', payment: 20.9 }, { id: 2, name: 'Second', payment: 10.0 }, ]); const filterRowsThatShowNestedContent: BentoDatagridShouldShowNestedContent = (item) => item.id === 2; </script> `; export const NestedContentSlot: Story = { render: (_args, { argTypes }) => ({ components: { BentoDataGrid, BentoLink, BentoTypography, }, props: Object.keys(argTypes), template: ` <bento-data-grid v-bind="args" > <template #nested-slot="{item}"> <div style="height: 80px; text-align: center; display: flex; align-items: center;"> <bento-typography>Nested content slot for {{ item.company }}</bento-typography> </div> </template> </bento-data-grid> `, setup(props) { return { args: isVue2 ? props : _args }; }, }), args: { ...DEFAULT_ARGS, hasNestedContent: true, zebraRows: true, shouldShowNestedContent: (item: BentoDatagridDataItem) => item.id !== DEFAULT_ARGS.data[1].id, }, parameters: storybookDocsParameter(NestedContentSlotTemplateCode), }; export const StickyHorizontalScrollbar: Story = { render: (_args, { argTypes }) => ({ components: { BentoDataGrid, BentoButton }, props: Object.keys(argTypes), setup(props) { const args = isVue2 ? props : _args; const filtersConfig = ref<BentoFilterBarModel>([ { field: 'col1', label: 'Column 1', type: BentoFilterItemType.INPUT, }, { field: 'col2', label: 'Column 2', type: BentoFilterItemType.INPUT, }, ]); const filterValues = ref(); const searchTerm = ref(''); const isCondensed = ref(args.condensed); const columns = ref( Array.from({ length: 20 }, (_, index) => ({ field: `col${index + 1}`, label: `Column ${index + 1}`, width: 200, mandatory: index === 0, })) ); const data = ref( Array.from({ length: 50 }, (_, rowIndex) => columns.value.reduce((acc, col, colIndex) => { acc[col.field] = `Row ${rowIndex + 1} - Col ${colIndex + 1}`; return acc; }, {}) ) ); const dataSet = computed(() => // If any filters or search terms have been set filterValues.value?.length === 0 && searchTerm.value === '' ? data.value : data.value.filter(item => { const foundFilteredItem = filterValues.value?.length > 0 ? filterValues.value.every(filter => filter.value ? (item[filter.field] as string) .toLowerCase() .includes((filter.value as string).toLowerCase()) : true ) : true; const foundSearchFilteredItem = searchTerm.value ? Object.keys(item).some(key => key ? (item[key] as string) .toLowerCase() .includes((searchTerm.value as string).toLowerCase()) : true ) : true; return foundFilteredItem && foundSearchFilteredItem; }) ); const onSearchCleared = action('search-clear'); const onSearchInput = action('search-input'); const onSearchTermUpdated = action('update:search-term'); return { // Values dataSet, filtersConfig, filterValues, searchTerm, columns, isCondensed, // Methods onSearchCleared, onSearchInput, onSearchTermUpdated, // Vue 3 storybook fix args, }; }, template: ` <bento-data-grid v-bind="args" :data="dataSet" :columns.sync="columns" :condensed.sync="isCondensed" :filters="filtersConfig" :filter-values.sync="filterValues" :filter-search-term.sync="searchTerm" @filter-search-clear="onSearchCleared" @filter-search-input="onSearchInput" @update:filter-search-term="onSearchTermUpdated" > <template #pagination-slot> <bento-button variant="tertiary"> Pagination slot button </bento-button> </template> </bento-data-grid> `, }), args: { ...DEFAULT_ARGS, pagination: { page: 1, hidePageSize: true }, columnPanel: true, stickyPagination: true, stickyFilters: true, stickyHorizontalScrollbar: true, }, }; const NestedRowsTemplateCode = ` <template> <bento-data-grid :data="data" :columns="columns" has-nested-rows :should-show-nested-row="filterRowsThatShowNestedContent" @nested-content-toggled="onParentRowToggle" /> </template> <script setup lang="ts"> import { BentoDataGrid, type BentoColumn, type BentoDatagridData, type BentoDatagridShouldShowNestedRowsFn } from '@adyen/bento-vue2' const columns: BentoColumn = [ { field: 'name', label: 'Name', minWidth: 300, mandatory: true }, { field: 'payment', label: 'Payment', minWidth: 300, mandatory: true }, ] const data = ref<BentoDatagridData>([ { id: 1, name: 'First', payment: 20.9 }, { id: 2, name: 'Second', payment: 10.0 }, ]); const filterRowsThatShowNestedRows: BentoDatagridShouldShowNestedRowsFn = (item) => item.id === 2; const onParentRowToggle = async (item: BentoDatagridDataItem) => { const childrenForToggledParentItem = await makeAPICallToGetChildren(item.id); dataState.value = dataState.value.map(itemToChange => { if (itemToChange.id === item.id) { return { ...itemToChange, children: childrenForToggledParentItem } } return itemToChange; }); }; </script> `; export const NestedRows: Story = { render: (_args, { argTypes }) => ({ components: { BentoDataGrid, BentoLink, BentoTypography, }, props: Object.keys(argTypes), template: ` <bento-data-grid v-bind="args" :data="dataState" @nested-content-toggled="onParentRowToggle" /> `, setup(props) { const dataState = ref<Array<BentoDatagridDataItem>>(DEFAULT_ARGS.data); const selection = ref<BentoDatagridDataItemId>(); const onParentRowToggle = (item: BentoDatagridDataItem) => { setTimeout(() => { const children = [ { id: `03c0a799-1ced-4017-aea3-111111${item.id}`, name: `Child of ${item.name} #1`, payment: 160405, company: 'Merchant Helium', registered: '2023-06-30T13:56:09.498Z', tags: ['deserunt', 'et', 'esse'], reference: 'UnbQaUncf3eR1D8XUBqn_UnbQaUncf3eR1D8XUBqn_UnbQaUncf3eR1D8XUBqn', paymentStatus: 1, countryCode: BentoCountryCode.NL, parentId: DEFAULT_ARGS.data[1].id, }, { id: `03c0a799-1ced-4017-aea3-1111113${item.id}`, name: `Child of ${item.name} #2`, payment: 163500, company: 'Merchant B', registered: '2023-04-28T13:11:09.498Z', tags: ['et', 'Lorem', 'sint'], reference: 'IxUZ9FsEPCZ9dFFPpI9C', paymentStatus: 2, countryCode: BentoCountryCode.BE, parentId: DEFAULT_ARGS.data[1].id, }, { id: `03c0a799-1ced-4017-aea3-1111114${item.id}`, name: `Child of ${item.name} #3`, payment: 78500, company: 'Merchant C', registered: '2023-06-30T11:01:09.498Z', tags: ['laborum', 'enim', 'ullamco'], reference: 'y0qAQ0WDszRyqLY2WO9f', paymentStatus: 1, countryCode: BentoCountryCode.UA, parentId: DEFAULT_ARGS.data[1].id, }, ]; dataState.value = dataState.value.map(itemToChange => { if (itemToChange.id === item.id) { return { ...itemToChange, children, }; } return itemToChange; }); }, 1000); }; return { args: isVue2 ? props : _args, dataState, selection, onParentRowToggle }; }, }), args: { ...DEFAULT_ARGS, hasNestedRows: true, zebraRows: true, shouldShowNestedRows: dataItem => { return dataItem.id !== DEFAULT_ARGS.data[1].id; }, }, parameters: storybookDocsParameter(NestedRowsTemplateCode), }; export const RowActions: Story = { render: (_args, { argTypes }) => ({ components: { BentoDataGrid }, props: Object.keys(argTypes), template: ` <bento-data-grid v-bind="args" :row-actions="rowActions" /> `, setup(props) { const rowActions: BentoDataGridRowActionsProp = (item: BentoDatagridDataItem) => { if (item.id === DEFAULT_DATA[0].id) { return [ { variant: 'edit', event: action('edit') }, { title: 'Delete', critical: true, event: action('Delete'), }, ]; } else if (item.id === DEFAULT_DATA[1].id) { return []; } else if (item.id === DEFAULT_DATA[2].id) { return [ { variant: 'edit', event: action('edit') }, { variant: 'open', isShort: true, menuData: [ { text: 'Cancel payment', event: action('cancel payment'), critical: true, }, { text: 'Transfer payment', event: action('transfer payment'), }, { text: 'Delete payment', event: action('delete payment'), disabled: true, }, ], }, ]; } return [ { variant: 'edit', event: action('edit'), }, ]; }; return { args: isVue2 ? props : _args, rowActions, }; }, }), args: { ...DEFAULT_ARGS, }, parameters: storybookDocsParameter(` <template> <bento-data-grid :data="data" :columns="columns" :row-actions="getRowActions" /> </template> <script setup lang="ts"> import { ref } from 'vue'; import { BentoDataGrid, type BentoColumn, type BentoDatagridData, type BentoDatagridDataItem, type BentoDataGridRowActionsProp } from '@adyen/bento-vue2'; const data = ref<BentoDatagridData>([ { id: '1', name: 'Shepherd Kane', company: 'Merchant Helium' }, { id: '2', name: 'Santos Palmer', company: 'Merchant B' }, { id: '3', name: 'Joyce Johns', company: 'Merchant C' }, ]); const columns = ref<BentoColumn[]>([ { field: 'name', label: 'Name' }, { field: 'company', label: 'Company' }, ]); const getRowActions: BentoDataGridRowActionsProp = (item: BentoDatagridDataItem) => { // Example: Show different actions based on the row data if (item.id === '1') { return [ { variant: 'edit', event: (item) => console.log('Editing', item) }, { title: 'Delete', critical: true, event: (item) => console.log('Deleting', item) } ]; } if (item.id === '3') { return [ { variant: 'edit', event: (item) => console.log('Editing', item) }, { variant: 'more', menuData: [ { text: 'Action 1', event: (item) => console.log('Action 1 on', item) }, { text: 'Action 2', event: (item) => console.log('Action 2 on', item), disabled: true }, ] } ]; } // Return an empty array for rows that should have no actions (e.g., row with id '2') return []; }; </script> `), }; const SummaryCellTemplateCode = ` <template> <bento-data-grid :columns="columns" :data="dataSet" :filters="filtersConfig" :filter-values.sync="filterValues" :filter-search-term="searchTerm" :filter-search-config="searchConfig" @update:filter-values="filterValues = $event" @update:filter-search-term="searchTerm = $event" @filter-search-clear="onSearchCleared" > <template #item-company="{item}"> <bento-data-grid-summary-cell :label="\`\${item.company.length} merchants\`" :items="item.company" :search-term="companyColumnSearchTerm" /> </template> </bento-data-grid> </template> <script setup lang="ts"> import { BentoColumn, BentoDataGrid, BentoDataGridSummaryCell, BentoDatagridData, BentoFilterBarModel, BentoFilterBarSearchBarProps, BentoFilterItemType, BentoFilterValues, BentoCountryCode, } from '@adyen/bento-vue2'; import { computed, ref } from 'vue'; const columns: Array<BentoColumn> = [ { field: 'name', label: 'Name', flex: 2, mandatory: true }, { field: 'company', label: 'Company', flex: 1 }, { field: 'payment', label: 'Payment', flex: 1, minWidth: 200 }, ]; /** * Data set with array of companies */ const multiData: BentoDatagridData = [ { id: '03c0a799-1ced-4017-aea3-0cebb0106335', name: 'Shepherd Kane', payment: 160405, company: ['Merchant Helium', 'Merchant A'], }, { id: '8f9e7877-1ca2-4929-9217-f1ec78709291', name: 'Joyce Johns', payment: 78500, company: ['Merchant C', 'Merchant Hedra'], }, { id: '51d2b31d-d577-4fca-8329-a583babbbdda', name: 'Evans Zamora', payment: 1153.4, company: [ 'Merchant A', 'Merchant B', 'Merchant C', 'Merchant D', 'Merchant E', 'Merchant F', 'Merchant G', 'Merchant H', 'Merchant I', 'Merchant J', ], }, ]; const filtersConfig = computed<BentoFilterBarModel>(() => [ { field: 'name', label: 'Name', type: BentoFilterItemType.INPUT, }, { field: 'company', label: 'Company', type: BentoFilterItemType.INPUT, }, ]); /** * The values of applied filters. */ const filterValues = ref<BentoFilterValues>(); /** * The current global search term that is being searched with */ const searchTerm = ref(''); /** * Global search configuration */ const searchConfig = ref<BentoFilterBarSearchBarProps>({ inputFieldAriaLabel: 'Descriptive details about the search bar', debounceTime: 1000, // in ms placeholder: 'Find merchants', }); /** * Concatenated search term using the global search and the company filter value */ const companyColumnSearchTerm = computed( () => searchTerm.value || filterValues.value?.find(filter => filter.field === 'company')?.value?.toString() || '' ); /** * Simplified computed data set that filters data depending on applied filters and global search values */ const dataSet = computed<BentoDatagridData>(() => { let filteredData = multiData; // Apply global search term filter if (searchTerm.value) { const lowerCaseSearchTerm = searchTerm.value.toLowerCase(); filteredData = filteredData.filter( item => (item.name as string).toLowerCase().includes(lowerCaseSearchTerm) || (item.company as string[]).some(company => company.toLowerCase().includes(lowerCaseSearchTerm) ) ); } // Apply filterValues filter if (filterValues.value?.length) { filteredData = filteredData.filter(item => filterValues.value?.every(filter => { if (filter.field === 'company') { return filter.value ? (item[filter.field] as string[]).some(company => (company as string) .toLowerCase() .includes((filter.value as string).toLowerCase()) ) : true; } return filter.value ? (item[filter.field] as string) .toLowerCase() .includes((filter.value as string).toLowerCase()) : true; }) ); } return filteredData; }); const onSearchCleared = () => { searchTerm.value = ''; }; </script> `; export const SummaryCell: Story = { render: (_args, { argTypes }) => ({ components: { BentoDataGrid, BentoLink, BentoTypography, BentoDashedUnderline, BentoDataGridSummaryCell, }, props: Object.keys(argTypes), template: ` <bento-data-grid v-bind="args" :data="dataSet" :filters="filtersConfig" :filter-values.sync="filterValues" :filter-search-term="searchTerm" :filter-search-config="searchConfig" @update:filter-values="filterValues = $event" @update:filter-search-term="searchTerm = $event" @filter-search-clear="onSearchCleared" > <template #item-company="{item}"> <bento-data-grid-summary-cell :label="\`\${item.company.length} merchants\`" :items="item.company" :search-term="companyColumnSearchTerm" /> </template> </bento-data-grid> `, setup(props) { /** * Data set with array of companies */ const multiData: BentoDatagridData = [ { id: '03c0a799-1ced-4017-aea3-0cebb0106335', name: 'Shepherd Kane', payment: 160405, company: ['Merchant Helium', 'Merchant A'], }, { id: 'b6d4f1fd-b1f6-40a6-a443-f785c89604ef', name: 'Santos Palmer', payment: 163500, company: ['Merchant B'], }, { id: '8f9e7877-1ca2-4929-9217-f1ec78709291', name: 'Joyce Johns', payment: 78500, company: ['Merchant C', 'Merchant Hedra'], }, { id: '0789a012-85a6-4657-87bb-319141938a61', name: 'Lynnette Melendez', payment: -45000, company: ['Merchant A', 'Merchant B', 'Merchant C'], }, { id: '0b3981b6-c73b-47ad-81d1-b06f9b95d467', name: 'Wagner Mcintosh', payment: 1112, company: ['Merchant A'], }, { id: '51d2b31d-d577-4fca-8329-a583babbbdda', name: 'Evans Zamora', payment: 1153.4, company: [ 'Merchant A', 'Merchant B', 'Merchant C', 'Merchant D', 'Merchant E', 'Merchant F', 'Merchant G', 'Merchant H', 'Merchant I', 'Merchant J', 'Merchant K', 'Merchant L', 'Merchant M', 'Merchant N', 'Merchant O', 'Merchant P', 'Merchant Q', 'Merchant R', 'Merchant S', 'Merchant T', ], }, { id: '9bd59a6e-bd03-4477-813f-b844953b7645', name: 'Anderson Middleton', payment: 181722.23, company: ['Merchant A'], }, ]; const filtersConfig = computed<BentoFilterBarModel>(() => [ { field: 'name', label: 'Name', type: BentoFilterItemType.INPUT, }, { field: 'company', label: 'Company', type: BentoFilterItemType.INPUT, }, ]); /** * The values of applied filters. */ const filterValues = ref<BentoFilterValues>(); /** * The current global search term that is being searched with */ const searchTerm = ref(''); /** * Global search configuration */ const searchConfig = ref<BentoFilterBarSearchBarProps>({ inputFieldAriaLabel: 'Descriptive details about the search bar', debounceTime: 1000, // in ms placeholder: 'Find merchants', }); /** * Concatenated search term using the global search and the company filter value */ const companyColumnSearchTerm = computed( () => searchTerm.value || filterValues.value?.find(filter => filter.field === 'company')?.value?.toString() || '' ); /** * Simplified computed data set that filters data depending on applied filters and global search values */ const dataSet = computed<BentoDatagridData>(() => { let filteredData = multiData; // Apply global search term filter if (searchTerm.value) { const lowerCaseSearchTerm = searchTerm.value.toLowerCase(); filteredData = filteredData.filter( item => (item.name as string).toLowerCase().includes(lowerCaseSearchTerm) || (item.company as Array<string>).some(company => company.toLowerCase().includes(lowerCaseSearchTerm) ) ); } // Apply filterValues filter if (filterValues.value?.length) { filteredData = filteredData.filter(item => filterValues.value?.every(filter => { if (filter.field === 'company') { return filter.value ? (item[filter.field] as Array<string>).some(company => (company as string) .toLowerCase() .includes((filter.value as string).toLowerCase()) ) : true; } return filter.value ? (item[filter.field] as string) .toLowerCase() .includes((filter.value as string).toLowerCase()) : true; }) ); } return filteredData; }); const onSearchCleared = () => { searchTerm.value = ''; }; return { args: isVue2 ? props : _args, filtersConfig, filterValues, searchTerm, searchConfig, onSearchCleared, dataSet, companyColumnSearchTerm, }; }, }), args: { ...DEFAULT_ARGS, columns: [ { field: 'name', label: 'Name', flex: 2, mandatory: true }, { field: 'company', label: 'Company', flex: 1 }, { field: 'payment', label: 'Payment', flex: 1, minWidth: 200 }, ], zebraRows: true, }, parameters: storybookDocsParameter(SummaryCellTemplateCode), }; export const TrendCell: Story = { render: (_args, { argTypes }) => ({ components: { BentoDataGrid, BentoDataGridTrendCell, }, props: Object.keys(argTypes), template: ` <bento-data-grid v-bind="args"> <template #item-performance="{ item }"> <bento-data-grid-trend-cell :value="item.performance.value" :direction="item.performance.direction" :variant="item.performance.variant" /> </template> </bento-data-grid> `, setup(props) { const columns: Array<BentoColumn> = [ { field: 'name', label: 'Name', flex: 2 }, { field: 'performance', label: 'Performance', flex: 1, numeric: true }, ]; const data: BentoDatagridData = [ { id: '1', name: 'Shepherd Kane', performance: { value: 25.5, variant: 'green', direction: 'up', }, }, { id: '2', name: 'Santos Palmer', performance: { value: 10.2, variant: 'red', direction: 'down', }, }, { id: '3', name: 'Joyce Johns', performance: { value: 0, variant: 'black', direction: 'none', }, }, { id: '4', name: 'Lynnette Melendez', performance: { value: '3.1428571429%', variant: 'green', direction: 'down', }, }, { id: '5', name: 'Wagner Mcintosh', performance: { value: '15.2%', variant: 'red', direction: 'up', }, }, { id: '6', name: 'Evans Zamora', performance: { value: '1.23', direction: 'up', }, }, { id: '7', name: 'Anderson Middleton', performance: { value: '90', direction: 'down', }, }, ]; return { args: { ...(isVue2 ? props : _args), columns, data }, }; }, }), args: { ...DEFAULT_ARGS, zebraRows: true, }, parameters: storybookDocsParameter(DataGridTrendCellExample), }; export const TagWithTextCell: Story = { render: (_args, { argTypes }) => ({ components: { BentoDataGrid, BentoDataGridTagWithTextCell, }, props: Object.keys(argTypes), template: ` <bento-data-grid v-bind="args"> <template #item-endpoint="{ item }"> <bento-data-grid-tag-with-text-cell :tag="item.endpoint.tag" :value="item.endpoint.value" /> </template> </bento-data-grid> `, setup(props) { const columns = [ { field: 'name', label: 'Name', flex: 2 }, { field: 'endpoint', label: 'Endpoint', flex: 2 }, ]; const data = [ { id: '1', name: 'Get users', endpoint: { tag: { label: 'GET', variant: 'blue' }, value: '/api/users', }, }, { id: '2', name: 'Create user', endpoint: { tag: { label: 'POST', variant: 'green' }, value: '/api/users', }, }, { id: '3', name: 'Update user', endpoint: { tag: { label: 'PUT', variant: 'yellow' }, value: '/api/users/:id', }, }, { id: '4', name: 'Delete user', endpoint: { tag: { label: 'DELETE', variant: 'red' }, value: '/api/users/:id', }, }, ]; return { args: { ...(isVue2 ? props : _args), columns, data }, }; }, }), args: { ...DEFAULT_ARGS, }, parameters: storybookDocsParameter(DataGridTagWithTextCellExample), };
|
|
1
|
+
import { type BentoColumn, BentoColumnOverflow, type BentoDatagridData, type BentoDatagridDataItem, type BentoDatagridDataItemId, BentoDatagridEvent, type BentoDatagridPaginationProps, type BentoDatagridSelection, type BentoDatagridSortByColumn, BentoDatagridSortDirection, } from './data-grid.types'; import { BentoCard } from '@/components/card'; import { action } from '@storybook/addon-actions'; import { type BentoFilterBarModel, type BentoFilterBarSearchBarProps, BentoFilterItemType, type BentoFilterValues, } from '../filter-bar'; import { BentoCurrency } from '@/components/currency'; import { BentoStatus, BentoStatusVariant } from '@/components/status'; import { BentoLink } from '@/components/link'; import { BentoTypography } from '@/components/typography'; import { computed, ref, watch } from 'vue'; import { storybookDocsParameter } from '@/utils/ts/storybook'; import { BentoDate, BentoDateFormat } from '@/components/date'; import { BentoCountry, BentoCountryCode } from '@/components/country'; import type { Meta, StoryObj } from '@storybook/vue'; import BentoDataGrid from './data-grid.vue'; import { isVue2 } from 'vue-demi'; import { BentoButton } from '../button'; import { BentoDashedUnderline } from '../dashed-underline'; import { BentoDataGridSummaryCell, BentoDataGridTagWithTextCell, BentoDataGridTrendCell } from './components'; import type { BentoColumnCellAction, BentoColumnCellActionEvent, BentoColumnCellActionsProp, BentoDataGridRowActionsProp, } from './components/data-grid-cell-actions/data-grid-cell-actions.types'; // Examples import DataGridEverythingBagel from './__tests__/data-grid-everything-bagel.vue?raw'; import DataGridCellActionsExample from './__tests__/data-grid-cell-actions-example.vue?raw'; import DataGridRowActionsExample from './__tests__/data-grid-row-actions-example.vue?raw'; import DataGridFitContentInCardExample from './__tests__/data-grid-fit-content-in-card-example.vue?raw'; import DataGridWithSlotsExample from './__tests__/data-grid-with-slots-example.vue?raw'; import DataGridFilteringExample from './__tests__/data-grid-filtering-example.vue?raw'; import DataGridSelectionExample from './__tests__/data-grid-selection-example.vue?raw'; import DataGridTrendCellExample from './__tests__/data-grid-trend-cell-example.vue?raw'; import DataGridTagWithTextCellExample from './__tests__/data-grid-tag-with-text-cell-example.vue?raw'; import { DEFAULT_DATA } from './__tests__/data-grid-example-data'; const meta: Meta = { title: 'Data Grid', component: BentoDataGrid, argTypes: { // Values lazyLoadType: { control: { type: 'select', }, }, // Filter bar props filters: { table: { type: { summary: 'BentoFilterBarModel' }, }, }, filterSearchTerm: { name: 'filterSearchTerm', defaultValue: undefined, description: "Filter bar search term useful to filter all the data by text. If the value is non-existant (`null` or `undefined`) the search bar won't be enabled.", control: { type: 'text', }, }, // Slots default: { table: { disable: true }, }, header: { name: 'header', defaultValue: '', description: 'header text', table: { type: { summary: 'VNode[]' }, }, control: { type: 'text', }, }, footer: { name: 'footer', defaultValue: '', description: 'footer text', table: { type: { summary: 'VNode[]' }, }, control: { type: 'text', }, }, 'head-{columnKey}': { name: 'head-{columnKey}', defaultValue: '', description: 'the column slot where `{columnKey}` is the name of the column key. Allows the Column header to be customized.', table: { type: { summary: 'VNode[]' }, category: 'slots', }, control: { disable: true }, }, 'item-{columnKey}': { name: 'item-{columnKey}', defaultValue: '', description: 'the data item slot where `{columnKey}` is the name of the data item column key. Allow row cells to be customized.', table: { type: { summary: 'VNode[]' }, category: 'slots', }, control: { disable: true }, }, ['pagination-slot']: { control: { type: 'text' }, description: 'Passes content to the default pagination slot', table: { type: { summary: 'VNode[]' }, }, }, // Events [BentoDatagridEvent.COLUMN_RESIZE]: { name: BentoDatagridEvent.COLUMN_RESIZE, defaultValue: '', description: 'Triggered when a column is resized. Provides a field name and the new resized width in pixels.', table: { category: 'events', type: { summary: '({ field, newResizedWidth }) => void' }, }, control: { disable: true }, }, [BentoDatagridEvent.SELECT]: { name: BentoDatagridEvent.SELECT, defaultValue: '', description: 'Triggered when a row is selected. Provides an array of data item ids. `BentoDatagridDataItemId[]`', table: { category: 'events', type: { summary: '(selectedDataItemIds) => void' }, }, control: { disable: true }, }, [BentoDatagridEvent.NAVIGATE]: { name: BentoDatagridEvent.NAVIGATE, defaultValue: '', description: `Triggered when the navigation controls in the pagination are clicked (i.e. when the arrow buttons to the rightmost of the screen or the dropdown on the right hand are updated). It indicates to which page it should go next.`, table: { category: 'events', type: { summary: '(pageNumber: number) => void' }, }, control: { disable: true }, }, [BentoDatagridEvent.ITEMS_PAGE]: { name: BentoDatagridEvent.ITEMS_PAGE, defaultValue: '', description: `Triggered when the number of items per page is changed from the left hand dropdown of the pagination. It indicates the new number of items that will be displayed.`, table: { category: 'events', type: { summary: '(itemsPerPage: number) => void' }, }, control: { disable: true }, }, [BentoDatagridEvent.SORT]: { name: BentoDatagridEvent.SORT, defaultValue: '', description: `Triggered when a column is sorted. Provides an array of \`BentoDatagridSortByColumn\` which includes the \`field\` and the sort \`direction\` (\`BentoDatagridSortDirection\`) of the column:`, table: { category: 'events', type: { summary: '(sortByDirections) => void' }, }, control: { disable: true }, }, [BentoDatagridEvent.UPDATE_COLUMNS]: { name: BentoDatagridEvent.UPDATE_COLUMNS, defaultValue: '', description: `Triggered when a the columns have been updated. Provides an updated array of columns \`BentoColumn[]\` which should be passed back into the \`columns\` prop (easily achieved using the [sync modifier](https://v2.vuejs.org/v2/guide/components-custom-events.html#sync-Modifier)) e.g. \`:columns.sync="columns"\``, table: { category: 'events', type: { summary: '(columns) => void' }, }, control: { disable: true }, }, [BentoDatagridEvent.UPDATE_CONDENSED]: { name: 'update:condensed', defaultValue: '', description: `Triggered when the config panel's condensed view toggle is interacted with. Provides the updated condensed \`boolean\`. (easily achieved using the [sync modifier](https://v2.vuejs.org/v2/guide/components-custom-events.html#sync-Modifier)) e.g. \`:condensed.sync="condensed"\``, table: { category: 'events', type: { summary: '(condensed) => void' }, }, control: { disable: true }, }, [BentoDatagridEvent.UPDATE_FILTER_VALUES]: { name: BentoDatagridEvent.UPDATE_FILTER_VALUES, defaultValue: '', description: `Triggered when a the filters in the filterbar have updated. Provides an array of filters \`BentoFilterBarModel[]\` which should be passed back into the \`filter-values\` prop (easily achieved using the [sync modifier](https://v2.vuejs.org/v2/guide/components-custom-events.html#sync-Modifier)) e.g. \`:filter-values.sync="filtersValues"\``, table: { category: 'events', type: { summary: '(filterValues) => void' }, }, control: { disable: true }, }, [BentoDatagridEvent.UPDATE_PAGINATION]: { name: BentoDatagridEvent.UPDATE_PAGINATION, defaultValue: '', description: `Triggered when a the pagination state has been updated. Provides an updated \`pagination\` properties object which should be passed back into the \`pagination\` prop (easily achieved using the [sync modifier](https://v2.vuejs.org/v2/guide/components-custom-events.html#sync-Modifier)) e.g. \`:pagination.sync="pagination"\``, table: { category: 'events', type: { summary: '(paginationProps) => void' }, }, control: { disable: true }, }, [BentoDatagridEvent.UPDATE_SELECTION]: { name: BentoDatagridEvent.UPDATE_SELECTION, defaultValue: '', description: `Triggered when a the select data rows state has been updated. Provides an array of row IDs \`BentoDatagridDataItemId[]\` which should be passed back into the \`selection\` prop (easily achieved using the [sync modifier](https://v2.vuejs.org/v2/guide/components-custom-events.html#sync-Modifier)) e.g. \`:selection.sync="selection"\``, table: { category: 'events', type: { summary: '(selection) => void' }, }, control: { disable: true }, }, [BentoDatagridEvent.UPDATE_SORT_BY]: { name: 'update:sort-by', defaultValue: '', description: `Triggered when a the sorting state has been updated. Provides an array of sorting directions \`BentoDatagridSortByColumn[]\` which should be passed back into the \`sort-by\` prop (easily achieved using the [sync modifier](https://v2.vuejs.org/v2/guide/components-custom-events.html#sync-Modifier)) e.g. \`:sort-by.sync="sortBy"\``, table: { category: 'events', type: { summary: '(sortingDirections) => void' }, }, control: { disable: true }, }, // Filter bar search events [BentoDatagridEvent.FILTER_SEARCH_INPUT]: { name: BentoDatagridEvent.FILTER_SEARCH_INPUT, defaultValue: '', description: "Triggered whenever the filter's search is updated.", table: { category: 'events', type: { summary: '(newSearchTerm) => void' } }, control: { disable: true }, }, [BentoDatagridEvent.FILTER_SEARCH_CLEAR]: { name: BentoDatagridEvent.FILTER_SEARCH_CLEAR, defaultValue: '', description: "Triggered whenever the filter's search is cleared.", table: { category: 'events', type: { summary: '() => void' }, }, control: { disable: true }, }, [BentoDatagridEvent.UPDATE_FILTER_SEARCH_TERM]: { name: BentoDatagridEvent.UPDATE_FILTER_SEARCH_TERM, defaultValue: '', description: "Triggered whenever the filter's search is updated to sync the model.", table: { category: 'events', type: { summary: '(newSearchTerm) => void' }, }, control: { disable: true }, }, [BentoDatagridEvent.ROW_CLICK]: { name: BentoDatagridEvent.ROW_CLICK, defaultValue: '', description: 'Triggered whenever a row has been clicked.', table: { category: 'events', type: { summary: '(row: BentoDatagridDataItem, event: MouseEvent) => void' }, }, }, [BentoDatagridEvent.NESTED_CONTENT_TOGGLED]: { name: BentoDatagridEvent.NESTED_CONTENT_TOGGLED, defaultValue: '', description: 'Triggered whenever a data item toggled is nested content', table: { category: 'events', type: { summary: '(dataItem: BentoDatagridDataItem) => void' }, }, control: { disable: true }, }, }, }; export default meta; type Story = StoryObj<typeof BentoDataGrid>; const DEFAULT_COLUMNS: Array<BentoColumn> = [ { field: 'name', label: 'Name', minWidth: 300, mandatory: true }, { field: 'company', label: 'Company', autoWidth: true }, { field: 'payment', label: 'Payment', minWidth: 350 }, ]; const ADDITIONAL_DATA: BentoDatagridData = [ { id: '03c0a799-1ced-4017-aea3-0cebb0106335', name: 'Additional data fetched here', payment: 160405, company: 'Merchant Helium', registered: '2023-06-30T13:56:09.498Z', tags: ['deserunt', 'et', 'esse'], reference: 'UnbQaUncf3eR1D8XUBqn_UnbQaUncf3eR1D8XUBqn_UnbQaUncf3eR1D8XUBqn', paymentStatus: 1, countryCode: BentoCountryCode.NL, }, { id: 'b6d4f1fd-b1f6-40a6-a443-f785c89604ef', name: 'Santos Palmer', payment: 163500, company: 'Merchant B', registered: '2023-04-28T13:11:09.498Z', tags: ['et', 'Lorem', 'sint'], reference: 'IxUZ9FsEPCZ9dFFPpI9C', paymentStatus: 2, countryCode: BentoCountryCode.BE, }, { id: '8f9e7877-1ca2-4929-9217-f1ec78709291', name: 'Joyce Johns', payment: 78500, company: 'Merchant C', registered: '2023-06-30T11:01:09.498Z', tags: ['laborum', 'enim', 'ullamco'], reference: 'y0qAQ0WDszRyqLY2WO9f', paymentStatus: 1, countryCode: BentoCountryCode.UA, }, { id: '0789a012-85a6-4657-87bb-319141938a61', name: 'Lynnette Melendez', payment: -45000, company: 'STRALOY', registered: '2019-08-01T13:56:01.498Z', tags: ['sit', 'consectetur', 'amet'], reference: '2vv5pzbRSwx8YsFoGGOC', paymentStatus: 1, countryCode: BentoCountryCode.BR, }, { id: '0b3981b6-c73b-47ad-81d1-b06f9b95d467', name: 'Wagner Mcintosh', payment: 1112, company: 'Merchant A', registered: '2022-12-30T02:12:09.498Z', tags: ['ad', 'ullamco', 'et'], reference: '54KN4jkpBuaRY5xMfe84', paymentStatus: 1, countryCode: BentoCountryCode.US, }, { id: '51d2b31d-d577-4fca-8329-a583babbbdda', name: 'Evans Zamora', payment: 1153.4, company: 'GEEKWAGON', registered: '2023-06-30T13:51:09.498Z', tags: ['consequat', 'magna', 'sit'], reference: 'Sj84kad0MGxpuozTjEk', paymentStatus: 0, countryCode: BentoCountryCode.FR, }, { id: '9bd59a6e-bd03-4477-813f-b844953b7645', name: 'Anderson Middleton', payment: 181722.23, company: 'BALOOBA', registered: '2021-03-02T01:34:09.498Z', tags: ['aliqua', 'adipisicing', 'magna'], reference: 'MtJ5Bnew0MGxpuozTjEk', paymentStatus: 1, countryCode: BentoCountryCode.IT, }, ]; const DEFAULT_ARGS = { data: DEFAULT_DATA, columns: DEFAULT_COLUMNS, allowColumnDragAndDrop: false, pagination: {}, emptyState: { title: 'No results were found', image: 'no-results-found', variant: 'embedded', action: { title: 'Clear filters', event: action('clear'), }, description: 'Try a different term or reset search filters', }, }; // Default const DefaultCode = ` <template> <bento-data-grid :allow-row-clicks="false" :allow-column-drag-and-drop="false" :bulk-actions="undefined" :condensed="false" :columns="columns" :column-panel="false" :data="data" :empty-state="undefined" :filters="undefined" :filters-search-term="undefined" :fit-content="false" :get-data-item-id="undefined" :has-resizeable-columns="false" :has-nested-content="false" :highlighted-rows="undefined" :is-data-item-disabled="undefined" lazy-load-type="none" :loading="false" :outline="false" :no-spacing-top="false" :pagination="undefinded" :selectable-accross-pages="false" :sticky-pagination="false" :sticky-filters="false" :selectable="false" :selection="undefined" :sort-by="undefined" :tab-title="undefined" :truncate-column-headers="false" /> </template> <script setup lang="ts"> import { BentoColumn, BentoDataGrid, BentoDatagridData } from '@adyen/bento-vue2'; import { ref } from 'vue'; const data = ref<BentoDatagridData>(${JSON.stringify(DEFAULT_DATA)}); const columns = ref<BentoColumn[]>([ { field: 'name', label: 'Name', minWidth: 300, mandatory: true }, { field: 'company', label: 'Reference' }, { field: 'payment', label: 'Payment', width: 200, minWidth: 150 }, ]); </script> `; export const Default: Story = { render: (_args, { argTypes }) => ({ components: { BentoDataGrid }, props: Object.keys(argTypes), template: ` <bento-data-grid v-bind="args" /> `, setup(props) { return { args: isVue2 ? props : _args }; }, }), args: DEFAULT_ARGS, parameters: storybookDocsParameter(DefaultCode), }; // Fluid Columns const FluidTemplateCode = ` <template> <bento-data-grid :data="data" :columns="columns" /> </template> <script setup lang="ts"> import { BentoColumn, BentoDataGrid, BentoDatagridData } from '@adyen/bento-vue2'; import { ref } from 'vue'; const data = ref<BentoDatagridData>(${JSON.stringify(DEFAULT_DATA)}); const columns = ref<BentoColumn[]>([ { field: 'name', label: 'Name', flex: 2, mandatory: true }, { field: 'company', label: 'Company', flex: 1 }, { field: 'payment', label: 'Payment', flex: 1, minWidth: 200 }, ]); </script> `; const rowActionsFluid: BentoDataGridRowActionsProp = (item: BentoDatagridDataItem) => { if (item.id === DEFAULT_DATA[3].id) { return [ { variant: 'edit', event: action('edit'), }, ]; } else if (item.id === DEFAULT_DATA[2].id) { return [ { variant: 'edit', event: action('edit') }, { variant: 'open', isShort: true, menuData: [ { text: 'Cancel payment', event: action('cancel payment'), critical: true, }, { text: 'Transfer payment', event: action('transfer payment'), }, { text: 'Delete payment', event: action('delete payment'), disabled: true, }, ], }, ]; } else if (item.id === DEFAULT_DATA[1].id) { return []; } return [ { variant: 'edit', event: action('edit'), }, { title: 'Delete', critical: true, event: action('Delete'), }, ]; }; export const Fluid: Story = { ...Default, args: { ...DEFAULT_ARGS, columns: [ { field: 'name', label: 'Name', flex: 2, mandatory: true }, { field: 'company', label: 'Company', flex: 1 }, { field: 'payment', label: 'Payment', flex: 1, minWidth: 200 }, ], rowActions: rowActionsFluid, }, parameters: storybookDocsParameter(FluidTemplateCode), }; // Slots export const Slots: Story = { render: (_args, { argTypes }) => ({ components: { BentoDataGrid, BentoLink, BentoTypography, }, props: Object.keys(argTypes), template: ` <bento-data-grid v-bind="args" > <template #header> <bento-typography variant="title" el="h2"> Users </bento-typography> </template> <template #head-name="{column}"> <bento-typography variant="caption"> {{ column.label }} </bento-typography> </template> <template #item-name="{item}"> <bento-typography variant="body"> {{ item.name }} </bento-typography> <template v-if="item.company === 'Merchant Helium'"> <div style="display: inline-flex; gap: 4px;"> <bento-typography v-for="itemTag in item.tags" :key="itemTag" variant="caption" :style="{ color: 'var(--b-color-label-secondary)' }" > { {{ itemTag }} } </bento-typography> </div> </template> </template> <template #item-company="{item}"> <bento-link isNotRouting to="https://www.adyen.com" :title="item.company" > {{ item.company }} </bento-link> <bento-typography v-if="item.company === 'Merchant B'" variant="caption" :style="{ color: 'var(--b-color-label-secondary)' }"> {{ item.registered }} </bento-typography> </template> <template #item-payment="{item}"> $ {{ item.payment }} </template> <template #footer> <bento-typography variant="caption"> Footer </bento-typography> </template> </bento-data-grid> `, setup(props) { return { args: isVue2 ? props : _args }; }, }), args: { ...DEFAULT_ARGS, columns: [ { field: 'name', label: 'Name', flex: 2, mandatory: true }, { field: 'company', label: 'Company', flex: 1 }, { field: 'payment', label: 'Payment', flex: 1, minWidth: 200 }, ], }, parameters: storybookDocsParameter(DataGridWithSlotsExample), }; // Sorting const useSort = () => { const sortBy = ref<Array<BentoDatagridSortByColumn>>([]); const data = ref<BentoDatagridData>(DEFAULT_DATA); const sort = (sortedColumns: Array<BentoDatagridSortByColumn>) => { data.value = sortedColumns[0].direction === BentoDatagridSortDirection.ASCENDING ? data.value.sort((a, b) => (a[sortedColumns[0].field] as string).localeCompare(b[sortedColumns[0].field] as string) ) : data.value .sort((a, b) => (a[sortedColumns[0].field] as string).localeCompare(b[sortedColumns[0].field] as string) ) .reverse(); action('sort')(sortedColumns); }; return { data, sortBy, sort }; }; const SortingTemplateCode = ` <template> <bento-data-grid :data="data" :columns="columns" @sort="sort" :sort-by.sync="sortBy" /> </template> <script setup lang="ts"> import { BentoDataGrid, type BentoDatagridSortByColumn, type BentoDatagridData, type BentoColumn, } from '@adyen/bento-vue2' // Sortable fields must have "sortable: true" const columns: BentoColumn = [ { field: 'id', label: 'ID', minWidth: 300, sortable: true, mandatory: true }, { field: 'name', label: 'Name', minWidth: 300, mandatory: true }, ] const data = ref<BentoDatagridData>([ { id: 1, name: 'First' }, { id: 2, name: 'Second' }, ]); const sortBy = ref<Array<BentoDatagridSortByColumn>>([]); const sort = (sortedColumns: Array<BentoDatagridSortByColumn>) => { data.value = sortedColumns[0].direction === BentoDatagridSortDirection.ASCENDING ? data.value.sort((a, b) => (a[sortedColumns[0].field] as number) .toString() .localeCompare(b[sortedColumns[0].field] as string) ) : data.value .sort((a, b) => (a[sortedColumns[0].field] as number) .toString() .localeCompare(b[sortedColumns[0].field] as string) ) .reverse(); } </script> `; export const Sorting: Story = { render: (_args, { argTypes }) => ({ components: { BentoDataGrid }, props: Object.keys(argTypes), template: ` <bento-data-grid :data="data" :columns="columns" :sort-by.sync="sortBy" @sort="sort" /> `, setup(props) { const { data, sortBy, sort } = useSort(); return { args: isVue2 ? props : _args, data, sortBy, sort, }; }, }), args: { ...DEFAULT_ARGS, columns: [ { field: 'name', label: 'Name', minWidth: 300, sortable: true, mandatory: true }, { field: 'company', label: 'Company', minWidth: 150, sortable: true }, { field: 'payment', label: 'Payment', width: 200, minWidth: 150 }, ], sortBy: [], }, parameters: storybookDocsParameter(SortingTemplateCode), }; export const Selection: Story = { render: (_args, { argTypes }) => ({ components: { BentoDataGrid }, props: Object.keys(argTypes), template: ` <div> <bento-data-grid v-bind="args" :data="pagedData" :pagination="pagination" :filters="filtersConfig" :filter-values="filterValues" :is-data-item-disabled="isDataItemDisabled" :selection="selection" :filter-search-term="searchTerm" @update:filter-search-term="searchTerm = $event" @update:pagination="pagination = $event" @update:filter-values="filterValues = $event" @select-all-across-pages="onSelectAllAcrossPages" @select-all-current-results="onSelectAllInCurrentResults" @deselect-all-current-results="onDeselectAllInCurrentResults" @filter-search-clear="onSearchCleared" @update:selection="onSelect" @row-click="rowClickAction" /> </div> `, setup(props) { const args = isVue2 ? props : _args; const selection = ref([]); const filtersConfig = computed<BentoFilterBarModel>(() => args?.filters ?? FILTERS); const filterValues = ref<BentoFilterValues>([]); const searchTerm = ref(''); const data = ref( Array.from({ length: 20 }, (_, i) => ({ id: `id-${i + 1}`, name: `Name ${i + 1}`, payment: (i + 1) * 100, company: `Merchant ${i + 1}`, })) ); const isDataItemDisabled = (row: BentoDatagridDataItem) => { return row.id === data.value[3].id; }; const pagination = ref<BentoDatagridPaginationProps>({ page: 1, size: 10, totalCount: 20, hasNext: true, }); const filteredData = computed<BentoDatagridData>(() => { let internalData = data.value; if (searchTerm.value) { const lowerCaseSearchTerm = searchTerm.value.toLowerCase(); const searchableFields = args.columns.map(column => column.field); internalData = internalData.filter((item: BentoDatagridDataItem) => { return searchableFields.some(field => { const value = String(item[field]); return value.toLowerCase().includes(lowerCaseSearchTerm); }); }); } return internalData.filter(item => filterValues.value.every(filter => filter.value ? String(item[filter.field]).toLowerCase().includes(String(filter.value).toLowerCase()) : true ) ); }); const pagedData = computed<BentoDatagridData>(() => { return filteredData.value.slice( (pagination.value.page - 1) * pagination.value.size, pagination.value.page * pagination.value.size ); }); const enabledIds = computed(() => data.value.filter(item => !isDataItemDisabled(item)).map(x => x.id)); const enabledFilteredIds = computed(() => filteredData.value.filter(item => !isDataItemDisabled(item)).map(x => x.id) ); const onSelect = value => { selection.value = value; action('update:selection')(value); }; const onSelectAllAcrossPages = () => { selection.value = enabledIds.value; action('select-all-across-pages')(); }; const onSelectAllInCurrentResults = () => { selection.value = [...new Set([...selection.value, ...enabledFilteredIds.value])]; action('select-all-current-results')(); }; const onDeselectAllInCurrentResults = () => { selection.value = selection.value.filter(item => !enabledFilteredIds.value.includes(item)); action('deselect-all-current-results')(); }; const onSearchCleared = () => { searchTerm.value = ''; action('search-clear')(); }; watch( filteredData, () => { pagination.value.totalCount = filteredData.value.length; }, { immediate: true } ); return { args, onSelect, onSelectAllAcrossPages, onSelectAllInCurrentResults, onDeselectAllInCurrentResults, pagination, selection, pagedData, filtersConfig, filterValues, searchTerm, rowClickAction: value => action('row-click')(value), isDataItemDisabled, onSearchCleared, }; }, }), args: { ...DEFAULT_ARGS, selectable: true, allowRowClicks: true, selectableAcrossPages: true, bulkActions: [ { title: 'Edit', event: () => true, }, { title: 'Delete', critical: true, event: () => true, }, ], }, parameters: storybookDocsParameter(DataGridSelectionExample), }; // Filtering const FILTERS: BentoFilterBarModel = [ { field: 'name', label: 'Name', type: BentoFilterItemType.INPUT, }, { field: 'company', label: 'Company', type: BentoFilterItemType.INPUT, }, ]; export const Filtering: Story = { render: (_args, { argTypes }) => ({ components: { BentoDataGrid }, props: Object.keys(argTypes), setup(props) { const args = isVue2 ? props : _args; const filtersConfig = computed<BentoFilterBarModel>(() => args?.filters ?? FILTERS); const filterValues = ref<BentoFilterValues>([]); const searchTerm = ref(''); const searchConfig = ref<BentoFilterBarSearchBarProps>({ inputFieldAriaLabel: 'Search for items', placeholder: 'Placeholder', hint: 'Hint', }); const dataSet = computed<BentoDatagridData>(() => { let filteredData = args.data; // Apply global search term filter if (searchTerm.value) { const lowerCaseSearchTerm = searchTerm.value.toLowerCase(); const searchableFields = args.columns.map(column => column.field); filteredData = filteredData.filter((item: BentoDatagridDataItem) => { return searchableFields.some(field => { const value = String(item[field]); return value.toLowerCase().includes(lowerCaseSearchTerm); }); }); } filteredData = filteredData.filter(item => filterValues.value?.every(filter => { return filter.value ? String(item[filter.field]).toLowerCase().includes(String(filter.value).toLowerCase()) : true; }) ); return filteredData; }); watch(filterValues, () => { action('filter-values:update')(filterValues.value); }); const onSearchCleared = action('search-clear'); const onSearchInput = action('search-input'); const onSearchTermUpdated = action('update:search-term'); return { // Values dataSet, filtersConfig, filterValues, searchTerm, searchConfig, // Methods onSearchCleared, onSearchInput, onSearchTermUpdated, // Vue 3 storybook fix args, }; }, template: ` <bento-data-grid v-bind="args" :data="dataSet" :filters="filtersConfig" :filter-values.sync="filterValues" :filter-search-term.sync="searchTerm" :filter-search-config="searchConfig" @filter-search-clear="onSearchCleared" @filter-search-input="onSearchInput" @update:filter-search-term="onSearchTermUpdated" /> `, }), args: DEFAULT_ARGS, parameters: storybookDocsParameter(DataGridFilteringExample), }; // No Results const NoResultsTemplateCode = ` <bento-data-grid :data="[]" :columns="[ { field: 'name', label: 'Name', minWidth: 300, mandatory: true }, { field: 'company', label: 'Reference' }, { field: 'payment', label: 'Payment', width: 200, minWidth: 150 }, ]" :empty-state="{ title: 'No results were found', image: 'no-results-found', variant: 'embedded', action: { title: 'Clear filters', event: clearFiltersFunction, }, description: 'Try a different term or reset search filters', }" /> `; export const NoResults: Story = { ...Filtering, args: { ...DEFAULT_ARGS, data: [], filters: FILTERS, filterValues: [{ field: 'company', value: 'CIA' }], }, parameters: storybookDocsParameter(NoResultsTemplateCode), }; // Pagination const PaginationTemplateCode = ` <template> <bento-data-grid :data="dataGridData" :columns="columnsConfig" :pagination.sync="pagination" @update:pagination="onUpdatePagination" /> </template> <script lang="ts" setup> const data="[{ id: '1', name: 'Jane' }, { id: '1', name: 'John' }]" const pagination = ref({ description: 'datagrid navigation', hasNext: true, size: 10 }); // With API call watch( () => pagination.value, () => { makeAPICall(pagination.value); } ); // Without API call const onUpdatePagination = (newPagination: BentoDatagridPaginationProps) => { pagination.value = { ...newPagination }; const newSetStartIndex = pagination.value.page * pagination.value.size; const amountOfItems = newSetStartIndex + pagination.value.size - 1; data.value = arrayWithFullSetOfValues.slice(newSetStartIndex, amountOfItems); }; </script> `; export const Pagination: Story = { render: (_args, { argTypes }) => ({ components: { BentoDataGrid }, props: Object.keys(argTypes), template: ` <bento-data-grid v-bind="args" :data="dataGridData" :pagination.sync="pagination" @update:pagination="updatePagination" @items-page="updateItemsPerPage" /> `, setup(props) { const args = isVue2 ? props : _args; const largeData = [ ...DEFAULT_ARGS.data, ...DEFAULT_ARGS.data.map(dataItem => ({ ...dataItem, id: `${dataItem.id}+1` })), ]; const pagination = ref(args.pagination); const dataGridData = computed(() => { const end = pagination.value.size <= largeData.length ? pagination.value.size : largeData.length; return largeData.slice(pagination.value.page - 1, end); }); const updateItemsPerPage = (itemsPerPage: number) => { action('items-page')(itemsPerPage); }; const updatePagination = (newPagination: BentoDatagridPaginationProps) => { action('navigate')(newPagination.page); action('update:pagination')(newPagination); }; return { // Values dataGridData, pagination, // Methods updateItemsPerPage, updatePagination, // Vue 3 storybook fixes args: isVue2 ? props : _args, }; }, }), args: { ...DEFAULT_ARGS, pagination: { hasNext: true, description: 'datagrid navigation', size: 10, page: 1, totalCount: DEFAULT_ARGS.data.length * 2, }, }, parameters: storybookDocsParameter(PaginationTemplateCode), }; // Payment Status (demo use case for datagrid V2) export const EverythingBagelExample: Story = { render: (_args, { argTypes }) => ({ components: { BentoDataGrid, BentoLink, BentoStatus, BentoCurrency, BentoDate, BentoCountry, BentoButton }, props: Object.keys(argTypes), template: ` <bento-data-grid v-bind="args" :data="filteredAndSortedData" :columns.sync="columns" :condensed="isCondensed" :filters="filtersConfig" :filter-values.sync="filterValues" :filter-search-term.sync="searchTerm" :sort-by="sortBy" @update:sort-by="sortBy = $event" @update:condensed="isCondensed = $event" :selection.sync="selection" :is-data-item-disabled="isDataItemDisabledForSelection" :row-actions="rowActions" selectable @row-click="onRowClick" > <template #item-paymentStatus="{item}"> <div style="display: flex"> <bento-status :variant="getPaymentStatus(item.paymentStatus)" /> <span>{{ getPaymentText(item.paymentStatus) }}</span> </div> </template> <template #item-reference="{item}"> <bento-link isNotRouting to="https://www.adyen.com" :title="item.reference" > {{ item.reference }} </bento-link> </template> <template #item-countryCode="{item}"> <bento-country :code="item.countryCode" /> </template> <template #item-payment="{item}"> <bento-currency currency="EUR" :value="item.payment" /> </template> <template #item-date="{item}"> <bento-date :date="new Date(item.registered)" :format='BentoDateFormat.FULL_DATE_TIME_WITH_PERIOD'/> </template> </bento-data-grid> `, setup(props) { const args = isVue2 ? props : _args; const columns = ref<Array<BentoColumn>>(args.columns); const filtersConfig = ref<BentoFilterBarModel>(args.filters); const filterValues = ref<BentoFilterValues>(args?.filterValues); const sortBy = ref<Array<BentoDatagridSortByColumn>>([]); const searchTerm = ref(''); const isCondensed = ref(args.condensed); const getPaymentStatus = (paymentStatus: number) => { switch (paymentStatus) { case 2: return BentoStatusVariant.GREEN; case 1: return BentoStatusVariant.YELLOW; case 0: default: return BentoStatusVariant.RED; } }; const getPaymentText = (paymentStatus: number) => { switch (paymentStatus) { case 2: return 'Paid'; case 1: return 'In progress'; case 0: default: return 'Not paid'; } }; const onRowClick = (item: BentoDatagridDataItem) => { action('row-click')(item); }; // Selection const isDataItemDisabledForSelection = (row: BentoDatagridDataItem) => row.id === DEFAULT_DATA[3].id; const selection = ref<BentoDatagridSelection>([DEFAULT_DATA[1].id]); // Filtering const filteredData = computed(() => // If any filters or search terms have been set filterValues.value?.length === 0 && searchTerm.value === '' ? args.data : args.data.filter(item => { const foundFilteredItem = filterValues.value?.length > 0 ? filterValues.value.every(filter => filter.value ? (item[filter.field] as string) .toLowerCase() .includes((filter.value as string).toLowerCase()) : true ) : true; const foundSearchFilteredItem = searchTerm.value ? (item?.company as string) .toLowerCase() .includes((searchTerm.value as string).toLowerCase()) : true; return foundFilteredItem && foundSearchFilteredItem; }) ); const filteredAndSortedData = computed(() => { if (sortBy.value?.length === 0) { return filteredData.value; } return sortBy.value[0].direction === BentoDatagridSortDirection.ASCENDING ? filteredData.value.sort((a, b) => (a[sortBy.value[0].field] as string).localeCompare(b[sortBy.value[0].field] as string) ) : filteredData.value .sort((a, b) => (a[sortBy.value[0].field] as string).localeCompare(b[sortBy.value[0].field] as string) ) .reverse(); }); const rowActions: BentoDataGridRowActionsProp = (item: BentoDatagridDataItem) => { if (item.id === DEFAULT_DATA[3].id) { return [ { variant: 'edit', event: action('edit'), tooltipText: 'Edit payment', }, ]; } else if (item.id === DEFAULT_DATA[2].id) { return [ { variant: 'edit', event: action('edit'), tooltipText: 'Edit payment' }, { variant: 'open', isShort: true, menuData: [ { text: 'Cancel payment', event: action('cancel payment'), critical: true, }, { text: 'Transfer payment', event: action('transfer payment'), }, { text: 'Delete payment', event: action('delete payment'), disabled: true, }, ], }, ]; } else if (item.id === DEFAULT_DATA[1].id) { return []; } return [ { variant: 'edit', event: action('edit'), tooltipText: 'Edit payment', }, { title: 'Delete', critical: true, event: action('Delete'), tooltipText: 'Delete payment', }, ]; }; return { // Methods getPaymentStatus, getPaymentText, // Functions onRowClick, isDataItemDisabledForSelection, // Values columns, filteredAndSortedData, sortBy, filtersConfig, filterValues, searchTerm, isCondensed, selection, rowActions, // Enums BentoDateFormat, // Vue 3 storybook fix args, }; }, }), args: { ...DEFAULT_ARGS, zebraRows: true, filters: [ { field: 'company', label: 'Company', value: null, type: BentoFilterItemType.INPUT, }, { field: 'countryCode', label: 'Country/Region', value: undefined, options: { listboxItems: [ { value: BentoCountryCode.NL, }, { value: BentoCountryCode.BE, }, ], variant: 'name', }, type: BentoFilterItemType.SELECT_COUNTRY, }, ], columns: [ { field: 'company', label: 'Company', sortable: true, mandatory: true, autoWidth: true, frozen: true, minWidth: 200, }, { field: 'paymentStatus', label: 'Status', width: 180, infoIconTooltip: 'For current day' }, { field: 'reference', label: 'Reference', width: 320, overflow: BentoColumnOverflow.ELLIPSIS }, { field: 'countryCode', label: 'Country/Region', width: 320 }, { field: 'payment', label: 'Payment', width: 180, numeric: true }, { field: 'date', label: 'Execution Date', width: 200 }, ], allowRowClicks: true, allowColumnDragAndDrop: true, columnPanel: true, highlightedRows: [DEFAULT_DATA[0].id, DEFAULT_DATA[4].id], }, parameters: storybookDocsParameter(DataGridEverythingBagel), }; export const DisplayOptions = (_args, { argTypes }) => ({ components: { BentoDataGrid, BentoDate }, props: Object.keys(argTypes), template: ` <bento-data-grid v-bind="args" :columns.sync="columns" :condensed.sync="isCondensed"> <template #item-date="{item}"> <bento-date :date="new Date(item.registered)" :format='BentoDateFormat.FULL_DATE_TIME_WITH_PERIOD'/> </template> </bento-data-grid> `, setup(props) { const args = isVue2 ? props : _args; const isCondensed = ref(args.condensed); const columns = ref([ { field: 'name', label: 'Name', minWidth: 200, mandatory: true }, { field: 'reference', label: 'Reference', width: 320, overflow: BentoColumnOverflow.ELLIPSIS, frozen: true, }, { field: 'company', label: 'Company', minWidth: 150, width: 250, frozen: true }, { field: 'payment', label: 'Payment', width: 200, minWidth: 150 }, { field: 'countryCode', label: 'Country/Region', width: 280 }, { field: 'date', label: 'Execution Date', width: 300 }, ]); return { // Values columns, isCondensed, // Enums BentoDateFormat, // Vue 3 storybook fix args, }; }, }); DisplayOptions.args = { ...DEFAULT_ARGS, allowColumnDragAndDrop: true, columnPanel: true, }; DisplayOptions.parameters = storybookDocsParameter(` <bento-data-grid column-panel :data="dataArray" :columns.sync="columns" :condensed.sync="isCondensed" />`); export const GridInCard: Story = { render: (_args, { argTypes }) => ({ components: { BentoCard, BentoDataGrid, BentoDate }, props: Object.keys(argTypes), template: ` <div style="max-width: 500px;"> <bento-card> Grid in a card <template #content> <div style="height: 300px;"> <bento-data-grid v-bind="args" :columns.sync="columns" :condensed.sync="isCondensed" :pagination="pagination" fit-content > <template #item-date="{item}"> <bento-date :date="new Date(item.registered)" :format='BentoDateFormat.FULL_DATE_TIME_WITH_PERIOD'/> </template> </bento-data-grid> </div> </template> </bento-card> </div> `, setup(props) { const args = isVue2 ? props : _args; const isCondensed = ref(args.condensed); const pagination = ref({ hasNext: true, size: 20, page: 1, description: 'datagrid navigation', }); const columns = ref([ { field: 'name', label: 'Name', minWidth: 200, mandatory: true }, { field: 'reference', label: 'Reference', width: 320, overflow: BentoColumnOverflow.ELLIPSIS, }, { field: 'company', label: 'Company', minWidth: 150, width: 250 }, { field: 'payment', label: 'Payment', width: 200, minWidth: 150 }, { field: 'countryCode', label: 'Country/Region', width: 280 }, { field: 'date', label: 'Execution Date', width: 300 }, ]); return { // Values columns, isCondensed, pagination, // Enums BentoDateFormat, // Vue 3 storybook fix args, }; }, }), args: { ...DEFAULT_ARGS, columnPanel: false, }, parameters: storybookDocsParameter(DataGridFitContentInCardExample), }; // Default const lazyLoadingCode = ` // With infinite scroll <bento-data-grid :data="dataArray" :columns="[ { field: 'name', label: 'Name', minWidth: 300, mandatory: true }, { field: 'company', label: 'Reference' }, { field: 'payment', label: 'Payment', width: 200, minWidth: 150 }, ]" lazy-load-type="automatic" :loading="yourLoadingLogic" @update:load-more="yourInfiniteScrollHandler" /> // With 'load more' button <bento-data-grid :data="dataArray" :columns="[ { field: 'name', label: 'Name', minWidth: 300, mandatory: true }, { field: 'company', label: 'Reference' }, { field: 'payment', label: 'Payment', width: 200, minWidth: 150 }, ]" lazy-load-type="button" :loading="yourLoadingLogic" @update:load-more="yourInfiniteScrollHandler" />`; export const lazyLoading: Story = { render: (_args, { argTypes }) => ({ components: { BentoDataGrid }, props: Object.keys(argTypes), template: ` <div style="height: 300px;"> <bento-data-grid v-bind="args" :lazy-load-type="lazyLoadType" :loading="loading" @update:show-more="infiniteScrollHandler"/> </div> `, setup(props) { const args = isVue2 ? props : _args; const loading = ref(false); const data = ref(DEFAULT_DATA); const infiniteScrollHandler = () => { loading.value = true; setTimeout(() => { data.value.push(...ADDITIONAL_DATA); loading.value = false; }, 2000); }; return { args, loading, infiniteScrollHandler, }; }, }), args: { ...DEFAULT_ARGS, lazyLoadType: 'automatic', }, parameters: storybookDocsParameter(lazyLoadingCode), }; // Added just to be able to easily play around with the sticky headers/pagination/filters // Not really relevant as a Storybook example, so not adding storybook code example export const LongDataWithStickyFiltersAndPagination: Story = { render: (_args, { argTypes }) => ({ components: { BentoDataGrid }, props: Object.keys(argTypes), setup(props) { const args = isVue2 ? props : _args; const filtersConfig = ref<BentoFilterBarModel>([ { field: 'col1', label: 'Column 1', type: BentoFilterItemType.INPUT, }, { field: 'col2', label: 'Column 2', type: BentoFilterItemType.INPUT, }, ]); const filterValues = ref(); const searchTerm = ref(''); const isCondensed = ref(args.condensed); const columns = ref( Array.from({ length: 20 }, (_, index) => ({ field: `col${index + 1}`, label: `Column ${index + 1}`, width: 200, mandatory: index === 0, })) ); const data = ref( Array.from({ length: 50 }, (_, rowIndex) => columns.value.reduce((acc, col, colIndex) => { acc[col.field] = `Row ${rowIndex + 1} - Col ${colIndex + 1}`; return acc; }, {}) ) ); const dataSet = computed(() => // If any filters or search terms have been set filterValues.value?.length === 0 && searchTerm.value === '' ? data.value : data.value.filter(item => { const foundFilteredItem = filterValues.value?.length > 0 ? filterValues.value.every(filter => filter.value ? (item[filter.field] as string) .toLowerCase() .includes((filter.value as string).toLowerCase()) : true ) : true; const foundSearchFilteredItem = searchTerm.value ? Object.keys(item).some(key => key ? (item[key] as string) .toLowerCase() .includes((searchTerm.value as string).toLowerCase()) : true ) : true; return foundFilteredItem && foundSearchFilteredItem; }) ); const onSearchCleared = action('search-clear'); const onSearchInput = action('search-input'); const onSearchTermUpdated = action('update:search-term'); return { // Values dataSet, filtersConfig, filterValues, searchTerm, columns, isCondensed, // Methods onSearchCleared, onSearchInput, onSearchTermUpdated, // Vue 3 storybook fix args, }; }, template: ` <bento-data-grid v-bind="args" :data="dataSet" :columns.sync="columns" :condensed.sync="isCondensed" :filters="filtersConfig" :filter-values.sync="filterValues" :filter-search-term.sync="searchTerm" @filter-search-clear="onSearchCleared" @filter-search-input="onSearchInput" @update:filter-search-term="onSearchTermUpdated" /> `, }), args: { ...DEFAULT_ARGS, columnPanel: true, stickyPagination: true, stickyFilters: true }, }; // Cell actions export const CellActions: Story = { render: (_args, { argTypes }) => ({ components: { BentoDataGrid }, props: Object.keys(argTypes), template: ` <bento-data-grid v-bind="args" :columns="columns"> <template #item-custom="{item}"> Value </template> </bento-data-grid> `, setup(props) { // --- Action Handlers --- const editItem: BentoColumnCellActionEvent = (rowDataItem, columnKey) => { action('edit')(rowDataItem, columnKey); }; const cancelAction: BentoColumnCellActionEvent = (rowDataItem, columnKey) => { action('cancel')(rowDataItem, columnKey); }; const transferAction: BentoColumnCellActionEvent = (rowDataItem, columnKey) => { action('transfer')(rowDataItem, columnKey); }; const deleteAction: BentoColumnCellActionEvent = (rowDataItem, columnKey) => { action('delete')(rowDataItem, columnKey); }; // --- Dynamic Cell Actions Function --- // This function generates actions based on the row's data. const getNameCellActions: BentoColumnCellActionsProp = rowDataItem => { const actions: Array<BentoColumnCellAction> = []; // Conditionally add the 'edit' action only for the first row. if (rowDataItem.id === DEFAULT_DATA[3].id) { actions.push({ variant: 'edit', event: editItem, tooltipText: 'Edit this name' }); } // Add a 'more' menu with dynamic options. actions.push({ variant: 'more', isShort: true, tooltipText: 'Additional actions', menuData: [ { text: 'Cancel payment', event: cancelAction, critical: true, }, { text: 'Transfer payment', event: transferAction, }, { text: 'Delete payment', event: deleteAction, // Example: disable delete for the second row. disabled: rowDataItem.id === DEFAULT_DATA[2].id, }, ], }); return actions; }; // --- Column Definitions --- const columns: Array<BentoColumn> = [ { field: 'name', label: 'Name (Dynamic Actions)', minWidth: 300, mandatory: true, // Use a function for dynamic cell actions cellActions: getNameCellActions, }, { field: 'company', label: 'Reference', autoWidth: true }, { field: 'payment', label: 'Payment (Static Actions)', minWidth: 350, // Use a static array for cell actions cellActions: [ { variant: 'copy', getCopyData: (rowDataItem: BentoDatagridDataItem, columnKey: string) => `${rowDataItem[columnKey]}`, }, { variant: 'open', event: action('open-payment'), tooltipText: 'Open payment details' }, ], }, ]; return { args: isVue2 ? props : _args, columns }; }, }), args: { ...DEFAULT_ARGS, highlightedRows: [DEFAULT_DATA[3].id], columns: [ { field: 'name', label: 'Name (Dynamic Actions)', minWidth: 300, mandatory: true, cellActions: [ () => (rowDataItem: BentoDatagridDataItem) => ({ variant: 'edit', disabled: rowDataItem.id === DEFAULT_DATA[3].id, event: action('edit'), tooltipText: 'Edit this name', }), { variant: 'open', tooltipText: 'Additional actions', menuData: [ { text: 'Cancel payment', event: action('cancel payment'), critical: true, }, { text: 'Transfer payment', event: action('transfer payment'), }, { text: 'Delete payment', event: action('delete payment'), disabled: true, }, ], }, ], }, { field: 'company', label: 'Reference', autoWidth: true }, { field: 'payment', label: 'Payment (Static Actions)', minWidth: 350, cellActions: [ { variant: 'copy', getCopyData: (rowDataItem: BentoDatagridDataItem, columnKey: string) => `$${rowDataItem[columnKey]}`, }, { variant: 'more', tooltipText: 'Additional actions', menuData: [ { text: 'Cancel payment', event: action('cancel payment'), critical: true, }, { text: 'Transfer payment', event: action('transfer payment'), }, { text: 'Delete payment', event: action('delete payment'), disabled: true, }, ], isShort: true, }, ], }, { field: 'custom', label: 'Custom template', minWidth: 200, cellActions: [{ variant: 'edit', event: action('edit'), tooltipText: 'Edit custom' }], }, ], }, parameters: storybookDocsParameter(DataGridCellActionsExample), }; // NestContentSlot const NestedContentSlotTemplateCode = ` <template> <bento-data-grid :data="data" :columns="columns" has-nested-content :should-show-nested-content="filterRowsThatShowNestedContent" > <template #nested-slot="{item}"> <bento-typography>Nested content for {{ item.company }}</bento-typography> </template> </bento-data-grid> </template> <script setup lang="ts"> import { BentoDataGrid, type BentoColumn, type BentoDatagridData, type BentoDatagridShouldShowNestedContent } from '@adyen/bento-vue2' const columns: BentoColumn = [ { field: 'name', label: 'Name', minWidth: 300, mandatory: true }, { field: 'payment', label: 'Payment', minWidth: 300, mandatory: true }, ] const data = ref<BentoDatagridData>([ { id: 1, name: 'First', payment: 20.9 }, { id: 2, name: 'Second', payment: 10.0 }, ]); const filterRowsThatShowNestedContent: BentoDatagridShouldShowNestedContent = (item) => item.id === 2; </script> `; export const NestedContentSlot: Story = { render: (_args, { argTypes }) => ({ components: { BentoDataGrid, BentoLink, BentoTypography, }, props: Object.keys(argTypes), template: ` <bento-data-grid v-bind="args" > <template #nested-slot="{item}"> <div style="height: 80px; text-align: center; display: flex; align-items: center;"> <bento-typography>Nested content slot for {{ item.company }}</bento-typography> </div> </template> </bento-data-grid> `, setup(props) { return { args: isVue2 ? props : _args }; }, }), args: { ...DEFAULT_ARGS, hasNestedContent: true, zebraRows: true, shouldShowNestedContent: (item: BentoDatagridDataItem) => item.id !== DEFAULT_ARGS.data[1].id, }, parameters: storybookDocsParameter(NestedContentSlotTemplateCode), }; export const StickyHorizontalScrollbar: Story = { render: (_args, { argTypes }) => ({ components: { BentoDataGrid, BentoButton }, props: Object.keys(argTypes), setup(props) { const args = isVue2 ? props : _args; const filtersConfig = ref<BentoFilterBarModel>([ { field: 'col1', label: 'Column 1', type: BentoFilterItemType.INPUT, }, { field: 'col2', label: 'Column 2', type: BentoFilterItemType.INPUT, }, ]); const filterValues = ref(); const searchTerm = ref(''); const isCondensed = ref(args.condensed); const columns = ref( Array.from({ length: 20 }, (_, index) => ({ field: `col${index + 1}`, label: `Column ${index + 1}`, width: 200, mandatory: index === 0, })) ); const data = ref( Array.from({ length: 50 }, (_, rowIndex) => columns.value.reduce((acc, col, colIndex) => { acc[col.field] = `Row ${rowIndex + 1} - Col ${colIndex + 1}`; return acc; }, {}) ) ); const dataSet = computed(() => // If any filters or search terms have been set filterValues.value?.length === 0 && searchTerm.value === '' ? data.value : data.value.filter(item => { const foundFilteredItem = filterValues.value?.length > 0 ? filterValues.value.every(filter => filter.value ? (item[filter.field] as string) .toLowerCase() .includes((filter.value as string).toLowerCase()) : true ) : true; const foundSearchFilteredItem = searchTerm.value ? Object.keys(item).some(key => key ? (item[key] as string) .toLowerCase() .includes((searchTerm.value as string).toLowerCase()) : true ) : true; return foundFilteredItem && foundSearchFilteredItem; }) ); const onSearchCleared = action('search-clear'); const onSearchInput = action('search-input'); const onSearchTermUpdated = action('update:search-term'); return { // Values dataSet, filtersConfig, filterValues, searchTerm, columns, isCondensed, // Methods onSearchCleared, onSearchInput, onSearchTermUpdated, // Vue 3 storybook fix args, }; }, template: ` <bento-data-grid v-bind="args" :data="dataSet" :columns.sync="columns" :condensed.sync="isCondensed" :filters="filtersConfig" :filter-values.sync="filterValues" :filter-search-term.sync="searchTerm" @filter-search-clear="onSearchCleared" @filter-search-input="onSearchInput" @update:filter-search-term="onSearchTermUpdated" > <template #pagination-slot> <bento-button variant="tertiary"> Pagination slot button </bento-button> </template> </bento-data-grid> `, }), args: { ...DEFAULT_ARGS, pagination: { page: 1, hidePageSize: true }, columnPanel: true, stickyPagination: true, stickyFilters: true, stickyHorizontalScrollbar: true, }, }; const NestedRowsTemplateCode = ` <template> <bento-data-grid :data="data" :columns="columns" has-nested-rows :should-show-nested-row="filterRowsThatShowNestedContent" @nested-content-toggled="onParentRowToggle" /> </template> <script setup lang="ts"> import { BentoDataGrid, type BentoColumn, type BentoDatagridData, type BentoDatagridShouldShowNestedRowsFn } from '@adyen/bento-vue2' const columns: BentoColumn = [ { field: 'name', label: 'Name', minWidth: 300, mandatory: true }, { field: 'payment', label: 'Payment', minWidth: 300, mandatory: true }, ] const data = ref<BentoDatagridData>([ { id: 1, name: 'First', payment: 20.9 }, { id: 2, name: 'Second', payment: 10.0 }, ]); const filterRowsThatShowNestedRows: BentoDatagridShouldShowNestedRowsFn = (item) => item.id === 2; const onParentRowToggle = async (item: BentoDatagridDataItem) => { const childrenForToggledParentItem = await makeAPICallToGetChildren(item.id); dataState.value = dataState.value.map(itemToChange => { if (itemToChange.id === item.id) { return { ...itemToChange, children: childrenForToggledParentItem } } return itemToChange; }); }; </script> `; export const NestedRows: Story = { render: (_args, { argTypes }) => ({ components: { BentoDataGrid, BentoLink, BentoTypography, }, props: Object.keys(argTypes), template: ` <bento-data-grid v-bind="args" :data="dataState" @nested-content-toggled="onParentRowToggle" /> `, setup(props) { const dataState = ref<Array<BentoDatagridDataItem>>(DEFAULT_ARGS.data); const selection = ref<BentoDatagridDataItemId>(); const onParentRowToggle = (item: BentoDatagridDataItem) => { setTimeout(() => { const children = [ { id: `03c0a799-1ced-4017-aea3-111111${item.id}`, name: `Child of ${item.name} #1`, payment: 160405, company: 'Merchant Helium', registered: '2023-06-30T13:56:09.498Z', tags: ['deserunt', 'et', 'esse'], reference: 'UnbQaUncf3eR1D8XUBqn_UnbQaUncf3eR1D8XUBqn_UnbQaUncf3eR1D8XUBqn', paymentStatus: 1, countryCode: BentoCountryCode.NL, parentId: DEFAULT_ARGS.data[1].id, }, { id: `03c0a799-1ced-4017-aea3-1111113${item.id}`, name: `Child of ${item.name} #2`, payment: 163500, company: 'Merchant B', registered: '2023-04-28T13:11:09.498Z', tags: ['et', 'Lorem', 'sint'], reference: 'IxUZ9FsEPCZ9dFFPpI9C', paymentStatus: 2, countryCode: BentoCountryCode.BE, parentId: DEFAULT_ARGS.data[1].id, }, { id: `03c0a799-1ced-4017-aea3-1111114${item.id}`, name: `Child of ${item.name} #3`, payment: 78500, company: 'Merchant C', registered: '2023-06-30T11:01:09.498Z', tags: ['laborum', 'enim', 'ullamco'], reference: 'y0qAQ0WDszRyqLY2WO9f', paymentStatus: 1, countryCode: BentoCountryCode.UA, parentId: DEFAULT_ARGS.data[1].id, }, ]; dataState.value = dataState.value.map(itemToChange => { if (itemToChange.id === item.id) { return { ...itemToChange, children, }; } return itemToChange; }); }, 1000); }; return { args: isVue2 ? props : _args, dataState, selection, onParentRowToggle }; }, }), args: { ...DEFAULT_ARGS, hasNestedRows: true, zebraRows: true, shouldShowNestedRows: dataItem => { return dataItem.id !== DEFAULT_ARGS.data[1].id; }, }, parameters: storybookDocsParameter(NestedRowsTemplateCode), }; export const RowActions: Story = { render: (_args, { argTypes }) => ({ components: { BentoDataGrid }, props: Object.keys(argTypes), template: ` <bento-data-grid v-bind="args" :row-actions="rowActions" /> `, setup(props) { const rowActions: BentoDataGridRowActionsProp = (item: BentoDatagridDataItem) => { if (item.id === DEFAULT_DATA[0].id) { return [ { variant: 'edit', event: action('edit'), tooltipText: 'Edit row' }, { title: 'Delete', critical: true, event: action('Delete'), tooltipText: 'Delete row', }, ]; } else if (item.id === DEFAULT_DATA[1].id) { return []; } else if (item.id === DEFAULT_DATA[2].id) { return [ { variant: 'edit', event: action('edit'), tooltipText: 'Edit row' }, { variant: 'open', isShort: true, tooltipText: 'More options', menuData: [ { text: 'Cancel payment', event: action('cancel payment'), critical: true, }, { text: 'Transfer payment', event: action('transfer payment'), }, { text: 'Delete payment', event: action('delete payment'), disabled: true, }, ], }, ]; } return [ { variant: 'edit', event: action('edit'), tooltipText: 'Edit row', }, ]; }; return { args: isVue2 ? props : _args, rowActions, }; }, }), args: { ...DEFAULT_ARGS, }, parameters: storybookDocsParameter(DataGridRowActionsExample), }; const SummaryCellTemplateCode = ` <template> <bento-data-grid :columns="columns" :data="dataSet" :filters="filtersConfig" :filter-values.sync="filterValues" :filter-search-term="searchTerm" :filter-search-config="searchConfig" @update:filter-values="filterValues = $event" @update:filter-search-term="searchTerm = $event" @filter-search-clear="onSearchCleared" > <template #item-company="{item}"> <bento-data-grid-summary-cell :label="\`\${item.company.length} merchants\`" :items="item.company" :search-term="companyColumnSearchTerm" /> </template> </bento-data-grid> </template> <script setup lang="ts"> import { BentoColumn, BentoDataGrid, BentoDataGridSummaryCell, BentoDatagridData, BentoFilterBarModel, BentoFilterBarSearchBarProps, BentoFilterItemType, BentoFilterValues, BentoCountryCode, } from '@adyen/bento-vue2'; import { computed, ref } from 'vue'; const columns: Array<BentoColumn> = [ { field: 'name', label: 'Name', flex: 2, mandatory: true }, { field: 'company', label: 'Company', flex: 1 }, { field: 'payment', label: 'Payment', flex: 1, minWidth: 200 }, ]; /** * Data set with array of companies */ const multiData: BentoDatagridData = [ { id: '03c0a799-1ced-4017-aea3-0cebb0106335', name: 'Shepherd Kane', payment: 160405, company: ['Merchant Helium', 'Merchant A'], }, { id: '8f9e7877-1ca2-4929-9217-f1ec78709291', name: 'Joyce Johns', payment: 78500, company: ['Merchant C', 'Merchant Hedra'], }, { id: '51d2b31d-d577-4fca-8329-a583babbbdda', name: 'Evans Zamora', payment: 1153.4, company: [ 'Merchant A', 'Merchant B', 'Merchant C', 'Merchant D', 'Merchant E', 'Merchant F', 'Merchant G', 'Merchant H', 'Merchant I', 'Merchant J', ], }, ]; const filtersConfig = computed<BentoFilterBarModel>(() => [ { field: 'name', label: 'Name', type: BentoFilterItemType.INPUT, }, { field: 'company', label: 'Company', type: BentoFilterItemType.INPUT, }, ]); /** * The values of applied filters. */ const filterValues = ref<BentoFilterValues>(); /** * The current global search term that is being searched with */ const searchTerm = ref(''); /** * Global search configuration */ const searchConfig = ref<BentoFilterBarSearchBarProps>({ inputFieldAriaLabel: 'Descriptive details about the search bar', debounceTime: 1000, // in ms placeholder: 'Find merchants', }); /** * Concatenated search term using the global search and the company filter value */ const companyColumnSearchTerm = computed( () => searchTerm.value || filterValues.value?.find(filter => filter.field === 'company')?.value?.toString() || '' ); /** * Simplified computed data set that filters data depending on applied filters and global search values */ const dataSet = computed<BentoDatagridData>(() => { let filteredData = multiData; // Apply global search term filter if (searchTerm.value) { const lowerCaseSearchTerm = searchTerm.value.toLowerCase(); filteredData = filteredData.filter( item => (item.name as string).toLowerCase().includes(lowerCaseSearchTerm) || (item.company as string[]).some(company => company.toLowerCase().includes(lowerCaseSearchTerm) ) ); } // Apply filterValues filter if (filterValues.value?.length) { filteredData = filteredData.filter(item => filterValues.value?.every(filter => { if (filter.field === 'company') { return filter.value ? (item[filter.field] as string[]).some(company => (company as string) .toLowerCase() .includes((filter.value as string).toLowerCase()) ) : true; } return filter.value ? (item[filter.field] as string) .toLowerCase() .includes((filter.value as string).toLowerCase()) : true; }) ); } return filteredData; }); const onSearchCleared = () => { searchTerm.value = ''; }; </script> `; export const SummaryCell: Story = { render: (_args, { argTypes }) => ({ components: { BentoDataGrid, BentoLink, BentoTypography, BentoDashedUnderline, BentoDataGridSummaryCell, }, props: Object.keys(argTypes), template: ` <bento-data-grid v-bind="args" :data="dataSet" :filters="filtersConfig" :filter-values.sync="filterValues" :filter-search-term="searchTerm" :filter-search-config="searchConfig" @update:filter-values="filterValues = $event" @update:filter-search-term="searchTerm = $event" @filter-search-clear="onSearchCleared" > <template #item-company="{item}"> <bento-data-grid-summary-cell :label="\`\${item.company.length} merchants\`" :items="item.company" :search-term="companyColumnSearchTerm" /> </template> </bento-data-grid> `, setup(props) { /** * Data set with array of companies */ const multiData: BentoDatagridData = [ { id: '03c0a799-1ced-4017-aea3-0cebb0106335', name: 'Shepherd Kane', payment: 160405, company: ['Merchant Helium', 'Merchant A'], }, { id: 'b6d4f1fd-b1f6-40a6-a443-f785c89604ef', name: 'Santos Palmer', payment: 163500, company: ['Merchant B'], }, { id: '8f9e7877-1ca2-4929-9217-f1ec78709291', name: 'Joyce Johns', payment: 78500, company: ['Merchant C', 'Merchant Hedra'], }, { id: '0789a012-85a6-4657-87bb-319141938a61', name: 'Lynnette Melendez', payment: -45000, company: ['Merchant A', 'Merchant B', 'Merchant C'], }, { id: '0b3981b6-c73b-47ad-81d1-b06f9b95d467', name: 'Wagner Mcintosh', payment: 1112, company: ['Merchant A'], }, { id: '51d2b31d-d577-4fca-8329-a583babbbdda', name: 'Evans Zamora', payment: 1153.4, company: [ 'Merchant A', 'Merchant B', 'Merchant C', 'Merchant D', 'Merchant E', 'Merchant F', 'Merchant G', 'Merchant H', 'Merchant I', 'Merchant J', 'Merchant K', 'Merchant L', 'Merchant M', 'Merchant N', 'Merchant O', 'Merchant P', 'Merchant Q', 'Merchant R', 'Merchant S', 'Merchant T', ], }, { id: '9bd59a6e-bd03-4477-813f-b844953b7645', name: 'Anderson Middleton', payment: 181722.23, company: ['Merchant A'], }, ]; const filtersConfig = computed<BentoFilterBarModel>(() => [ { field: 'name', label: 'Name', type: BentoFilterItemType.INPUT, }, { field: 'company', label: 'Company', type: BentoFilterItemType.INPUT, }, ]); /** * The values of applied filters. */ const filterValues = ref<BentoFilterValues>(); /** * The current global search term that is being searched with */ const searchTerm = ref(''); /** * Global search configuration */ const searchConfig = ref<BentoFilterBarSearchBarProps>({ inputFieldAriaLabel: 'Descriptive details about the search bar', debounceTime: 1000, // in ms placeholder: 'Find merchants', }); /** * Concatenated search term using the global search and the company filter value */ const companyColumnSearchTerm = computed( () => searchTerm.value || filterValues.value?.find(filter => filter.field === 'company')?.value?.toString() || '' ); /** * Simplified computed data set that filters data depending on applied filters and global search values */ const dataSet = computed<BentoDatagridData>(() => { let filteredData = multiData; // Apply global search term filter if (searchTerm.value) { const lowerCaseSearchTerm = searchTerm.value.toLowerCase(); filteredData = filteredData.filter( item => (item.name as string).toLowerCase().includes(lowerCaseSearchTerm) || (item.company as Array<string>).some(company => company.toLowerCase().includes(lowerCaseSearchTerm) ) ); } // Apply filterValues filter if (filterValues.value?.length) { filteredData = filteredData.filter(item => filterValues.value?.every(filter => { if (filter.field === 'company') { return filter.value ? (item[filter.field] as Array<string>).some(company => (company as string) .toLowerCase() .includes((filter.value as string).toLowerCase()) ) : true; } return filter.value ? (item[filter.field] as string) .toLowerCase() .includes((filter.value as string).toLowerCase()) : true; }) ); } return filteredData; }); const onSearchCleared = () => { searchTerm.value = ''; }; return { args: isVue2 ? props : _args, filtersConfig, filterValues, searchTerm, searchConfig, onSearchCleared, dataSet, companyColumnSearchTerm, }; }, }), args: { ...DEFAULT_ARGS, columns: [ { field: 'name', label: 'Name', flex: 2, mandatory: true }, { field: 'company', label: 'Company', flex: 1 }, { field: 'payment', label: 'Payment', flex: 1, minWidth: 200 }, ], zebraRows: true, }, parameters: storybookDocsParameter(SummaryCellTemplateCode), }; export const TrendCell: Story = { render: (_args, { argTypes }) => ({ components: { BentoDataGrid, BentoDataGridTrendCell, }, props: Object.keys(argTypes), template: ` <bento-data-grid v-bind="args"> <template #item-performance="{ item }"> <bento-data-grid-trend-cell :value="item.performance.value" :direction="item.performance.direction" :variant="item.performance.variant" /> </template> </bento-data-grid> `, setup(props) { const columns: Array<BentoColumn> = [ { field: 'name', label: 'Name', flex: 2 }, { field: 'performance', label: 'Performance', flex: 1, numeric: true }, ]; const data: BentoDatagridData = [ { id: '1', name: 'Shepherd Kane', performance: { value: 25.5, variant: 'green', direction: 'up', }, }, { id: '2', name: 'Santos Palmer', performance: { value: 10.2, variant: 'red', direction: 'down', }, }, { id: '3', name: 'Joyce Johns', performance: { value: 0, variant: 'black', direction: 'none', }, }, { id: '4', name: 'Lynnette Melendez', performance: { value: '3.1428571429%', variant: 'green', direction: 'down', }, }, { id: '5', name: 'Wagner Mcintosh', performance: { value: '15.2%', variant: 'red', direction: 'up', }, }, { id: '6', name: 'Evans Zamora', performance: { value: '1.23', direction: 'up', }, }, { id: '7', name: 'Anderson Middleton', performance: { value: '90', direction: 'down', }, }, ]; return { args: { ...(isVue2 ? props : _args), columns, data }, }; }, }), args: { ...DEFAULT_ARGS, zebraRows: true, }, parameters: storybookDocsParameter(DataGridTrendCellExample), }; export const TagWithTextCell: Story = { render: (_args, { argTypes }) => ({ components: { BentoDataGrid, BentoDataGridTagWithTextCell, }, props: Object.keys(argTypes), template: ` <bento-data-grid v-bind="args"> <template #item-endpoint="{ item }"> <bento-data-grid-tag-with-text-cell :tag="item.endpoint.tag" :value="item.endpoint.value" /> </template> </bento-data-grid> `, setup(props) { const columns = [ { field: 'name', label: 'Name', flex: 2 }, { field: 'endpoint', label: 'Endpoint', flex: 2 }, ]; const data = [ { id: '1', name: 'Get users', endpoint: { tag: { label: 'GET', variant: 'blue' }, value: '/api/users', }, }, { id: '2', name: 'Create user', endpoint: { tag: { label: 'POST', variant: 'green' }, value: '/api/users', }, }, { id: '3', name: 'Update user', endpoint: { tag: { label: 'PUT', variant: 'yellow' }, value: '/api/users/:id', }, }, { id: '4', name: 'Delete user', endpoint: { tag: { label: 'DELETE', variant: 'red' }, value: '/api/users/:id', }, }, ]; return { args: { ...(isVue2 ? props : _args), columns, data }, }; }, }), args: { ...DEFAULT_ARGS, }, parameters: storybookDocsParameter(DataGridTagWithTextCellExample), };
|