import * as React from 'react'
import type { StoryFn, StoryObj, Meta } from '@storybook/react-webpack5'
import { Grid } from '.'
import type { Column, GridProps, GridActionsMenuFullProps } from '../../types'
import { GridCellDefault, GridCellDropdownMenu } from '../../components'
import {
ButtonPrimary,
EmptyState,
ListItem,
ListItemDivider,
ListItemInfo,
Menu,
} from '@planview/pv-uikit'
import { size } from '@planview/pv-utilities'
import { Copy, Cut, DotsVertical, Trash } from '@planview/pv-icons'
export default {
title: 'pv-grid/Components/Grid',
tags: ['autodocs'],
component: Grid,
parameters: {
badges: ['intl'],
},
args: {
rowHeight: 'medium',
selectionMode: 'multi',
filterMode: 'default',
sortMode: 'internal',
},
argTypes: {
loading: {
control: { type: 'radio' },
options: [false, true, 3, 6],
table: {
category: 'Status',
},
},
emptyContent: {
table: {
category: 'Status',
},
},
columns: {
control: false,
},
columnGroups: {
control: { type: 'radio' },
options: ['without groups', 'with groups'],
mapping: {
'without groups': [],
'with groups': [
{
id: 'top-group',
label: 'Group',
children: ['title', 'price'],
},
],
},
},
rows: {
control: false,
},
ref: {
control: false,
description:
'This will return an api for interacting with the grid externally.',
table: {
category: 'Advanced',
},
},
defaultSort: {
control: false,
table: {
category: 'Sorting',
},
},
sort: {
control: false,
table: {
category: 'Sorting',
},
},
sortMode: {
table: {
category: 'Sorting',
},
},
multiColumnSort: {
table: {
category: 'Sorting',
},
},
onSortChange: {
table: {
category: 'Sorting',
},
},
selection: {
table: {
category: 'Selection',
},
},
onSelectionChange: {
table: {
category: 'Selection',
},
},
selectionMode: {
table: {
category: 'Selection',
},
},
filter: {
table: {
category: 'Filtering',
},
},
filterMode: {
table: {
category: 'Filtering',
},
},
filteredIds: {
control: { type: 'radio' },
options: ['not filtered', 'filtered', 'no matches'],
mapping: {
'not filtered': undefined,
filtered: ['1002'],
'no matches': [],
},
table: {
category: 'Filtering',
},
},
preferencesAdapter: {
control: false,
table: {
category: 'Advanced',
},
},
expandedRows: {
control: false,
table: {
category: 'Tree',
},
},
onExpandedRowsChange: {
table: {
category: 'Tree',
},
},
actionsMenu: {
control: { type: 'radio' },
options: ['no actions menu', 'actions menu', 'actions menu async'],
mapping: {
'no actions menu': undefined,
'actions menu': () => (
<>
} label="Cut" />
} label="Copy" />
} label="Delete" />
>
),
'actions menu async': {
Menu({ row, menuProps }: GridActionsMenuFullProps) {
const [loading, setLoading] = React.useState(true)
React.useEffect(() => {
const t = setTimeout(() => setLoading(false), 500)
return () => clearTimeout(t)
}, [])
return (
)
},
},
},
table: {
category: 'Context Menu',
},
},
enableColumnVisibilityMenu: {
control: { type: 'boolean' },
table: {
category: 'Context Menu',
},
},
rowDrag: {
control: false,
table: {
category: 'Drag and Drop',
},
},
onCellChange: {
control: false,
table: {
category: 'Editing',
},
},
},
} satisfies Meta
type Row = {
id: string
title: string
price?: number
}
const rows = [
{ id: '1001', title: 'Journey to the Center of the Earth', price: 11.99 },
{
id: '1002',
title: 'Twenty Thousand Leagues Under the Sea',
price: 35.65,
},
{ id: '1003', title: 'Around the World In Eighty Days', price: 29.98 },
]
const currencyFormatter = new Intl.NumberFormat(undefined, {
style: 'currency',
currency: 'USD',
})
export const Default: StoryFn> = ({
columns: _columns,
rows: _rows,
...rest
}) => {
const columns = React.useMemo[]>(
() => [
{
id: 'id',
label: 'ID',
width: 100,
},
{
id: 'title',
label: 'Title',
width: 400,
},
{
id: 'price',
label: 'Price',
width: 200,
header: {
align: 'right',
},
cell: {
label: ({ value }: { value: number }) =>
currencyFormatter.format(value),
Renderer: ({ label, tabIndex }) => (
),
},
},
],
[]
)
return
}
Default.parameters = {
docs: {
source: {
code: `
type Book = {
id: number
title: string
price: number
}
const rows = React.useMemo(() => [
{ id: '1001', title: 'Journey to the Center of the Earth', price: 11.99 },
{
id: '1002',
title: 'Twenty Thousand Leagues Under the Sea',
price: 35.65,
},
{ id: '1003', title: 'Around the World In Eighty Days', price: 29.98 },
], [])
const columns = React.useMemo[]>(
() => [
{
id: 'id',
label: 'ID',
width: 100,
},
{
id: 'title',
label: 'Title',
width: 400,
},
{
id: 'price',
label: 'Price',
width: 200,
header: {
align: 'right',
},
cell: {
label: ({ value }: { value: number }) =>
currencyFormatter.format(value),
Renderer: ({ label, tabIndex }) => (
),
},
},
],
[]
)
return
`,
},
},
}
export const LoadingState: StoryFn> = ({
columns: _columns,
rows: _rows,
...rest
}) => {
const columns = React.useMemo[]>(
() => [
{
id: 'id',
label: 'ID',
width: 100,
},
{
id: 'title',
label: 'Title',
width: 400,
},
{
id: 'price',
label: 'Price',
width: 200,
header: {
align: 'right',
},
cell: {
label: ({ value }: { value: number }) =>
currencyFormatter.format(value),
Renderer: ({ label, tabIndex }) => (
),
},
},
],
[]
)
return
}
LoadingState.args = {
loading: 3,
}
export const LocaleSorting: StoryObj> = {
args: {
columns: [
{
id: 'title',
label: 'Title (Natural sort)',
sortStrategy: 'natural',
width: 200,
},
{
id: 'title-alt',
label: 'Title (Fast sort)',
cell: { value: ({ row }) => row.title },
sortStrategy: 'fast',
width: 200,
},
],
rows: [
{ id: '1', title: 'Alpha 20' },
{ id: '2', title: 'alpha 2' },
{ id: '3', title: 'alpha 100' },
{ id: '4', title: 'älpha 11' }, // spell-checker: disable-line
{ id: '5', title: 'Beta 99' },
],
selectionMode: 'none',
defaultSort: [{ columnId: 'title', direction: 'asc' }],
},
}
export const StickyColumns: StoryObj> = {
args: {
columns: [
{
id: 'id',
label: 'ID',
width: 100,
sticky: 'left',
},
{
id: 'title',
label: 'Title',
width: 400,
},
{
id: 'desc',
label: 'Description',
width: 200,
cell: {
value: () => 'N/A',
},
},
{
id: 'extra',
label: 'Extra',
width: 200,
cell: {
value: () => 'N/A',
},
},
{
id: 'price',
label: 'Price',
width: 200,
header: {
align: 'right',
},
cell: {
label: ({ value }: { value: number }) =>
currencyFormatter.format(value),
Renderer: ({ label, tabIndex }) => (
),
},
},
{
id: 'actions',
label: '',
width: size.small,
resizable: false,
movable: false,
sortable: false,
sticky: 'right',
cell: {
Renderer({ tabIndex }) {
return (
}}
>
)
},
},
},
],
rows,
},
}
export const NoData: StoryObj> = {
args: {
columns: [
{
id: 'id',
label: 'ID',
width: 100,
},
{
id: 'title',
label: 'Title',
width: 400,
},
{
id: 'price',
label: 'Price',
width: 200,
header: {
align: 'right',
},
},
{
id: 'extra',
label: 'Extra',
width: 400,
},
],
rows: [],
selectionMode: 'none',
defaultSort: [{ columnId: 'title', direction: 'asc' }],
emptyContent: (
}
callToAction={Create Issue}
/>
),
},
decorators: [(Story) => {Story()}
],
}
export const ActionsMenu: StoryFn> = ({
columns: _columns,
rows: _rows,
...rest
}) => {
const columns = React.useMemo[]>(
() => [
{
id: 'id',
label: 'ID',
width: 100,
},
{
id: 'title',
label: 'Title',
width: 400,
},
{
id: 'price',
label: 'Price',
width: 200,
header: {
align: 'right',
},
cell: {
label: ({ value }: { value: number }) =>
currencyFormatter.format(value),
Renderer: ({ label, tabIndex }) => (
),
},
},
],
[]
)
return
}
ActionsMenu.args = {
actionsMenu: 'actions menu' as any,
}
ActionsMenu.parameters = {
docs: {
source: {
code: `
(
<>
} label="Cut" />
} label="Copy" />
} label="Delete" />
>
)}
/>
`,
},
},
}
ActionsMenu.tags = ['hidden']
export const ActionsMenuAsync: StoryFn> = ({
columns: _columns,
rows: _rows,
...rest
}) => {
const columns = React.useMemo[]>(
() => [
{
id: 'id',
label: 'ID',
width: 100,
},
{
id: 'title',
label: 'Title',
width: 400,
},
{
id: 'price',
label: 'Price',
width: 200,
header: {
align: 'right',
},
cell: {
label: ({ value }: { value: number }) =>
currencyFormatter.format(value),
Renderer: ({ label, tabIndex }) => (
),
},
},
],
[]
)
return
}
ActionsMenuAsync.args = {
actionsMenu: 'actions menu async' as any,
}
ActionsMenuAsync.parameters = {
docs: {
source: {
code: `
) {
const [loading, setLoading] = React.useState(true)
React.useEffect(() => {
const t = setTimeout(() => setLoading(false), 500)
return () => clearTimeout(t)
}, [])
return (
)
},
}}
/>
`,
},
},
}
ActionsMenuAsync.tags = ['hidden']
export const Footer: StoryFn> = ({
columns: _columns,
rows: _rows,
...rest
}) => {
const columns = React.useMemo[]>(
() => [
{
id: 'id',
label: 'ID',
width: 100,
},
{
id: 'title',
label: 'Title',
width: 400,
},
{
id: 'price',
label: 'Price',
width: 200,
header: {
align: 'right',
},
footer: {
aggregation: 'sum',
label: (value) =>
typeof value === 'number'
? currencyFormatter.format(value)
: '',
},
cell: {
label: ({ value }: { value: number }) =>
currencyFormatter.format(value),
Renderer: ({ label, tabIndex }) => (
),
},
},
],
[]
)
return
}
Footer.tags = ['hidden']
export const ShowHideColumns: StoryFn> = ({
columns: _columns,
rows: _rows,
...rest
}) => {
const columns = React.useMemo[]>(
() => [
{
id: 'id',
label: 'ID',
width: 100,
hideable: false,
},
{
id: 'title',
label: 'Title',
width: 400,
},
{
id: 'price',
label: 'Price',
width: 200,
hidden: true,
header: {
align: 'right',
},
cell: {
label: ({ value }: { value: number }) =>
currencyFormatter.format(value),
Renderer: ({ label, tabIndex }) => (
),
},
},
],
[]
)
return (
)
}
ShowHideColumns.tags = ['hidden']