generated from gravity-ui/package-example
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(Table): table actions column added
- Loading branch information
alx-chernigin
committed
Oct 21, 2024
1 parent
f161495
commit 3dee229
Showing
15 changed files
with
450 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
@use '@gravity-ui/uikit/styles/mixins'; | ||
@use '../variables'; | ||
|
||
$block: '.#{variables.$ns}row-actions'; | ||
|
||
#{$block} { | ||
height: 18px; | ||
display: flex; | ||
align-items: center; | ||
|
||
&-popup { | ||
&__menu { | ||
@include mixins.max-height(200px); | ||
|
||
&-item { | ||
@include mixins.max-text-width(250px); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
import React from 'react'; | ||
|
||
import {Ellipsis} from '@gravity-ui/icons'; | ||
import type {PopupPlacement} from '@gravity-ui/uikit'; | ||
import {Button, Icon, Menu, Popup, useUniqId} from '@gravity-ui/uikit'; | ||
|
||
import {block} from '../../utils'; | ||
|
||
import i18n from './i18n'; | ||
import type {TableActionConfig, TableActionGroup, TableActionsSettings} from './types'; | ||
|
||
import './RowActions.scss'; | ||
|
||
type RowActionsProps<I> = Pick<TableActionsSettings<I>, 'getRowActions' | 'rowActionsSize'> & { | ||
item: I; | ||
index: number; | ||
}; | ||
|
||
const b = block('row-actions'); | ||
const bPopup = block('row-actions-popup'); | ||
|
||
const DEFAULT_PLACEMENT: PopupPlacement = ['bottom-end', 'top-end', 'auto']; | ||
|
||
const isActionGroup = <I extends unknown>( | ||
config: TableActionConfig<I>, | ||
): config is TableActionGroup<I> => { | ||
return Array.isArray((config as TableActionGroup<I>).items); | ||
}; | ||
|
||
export const RowActions = <I extends unknown>({ | ||
index: rowIndex, | ||
item, | ||
getRowActions, | ||
rowActionsSize, | ||
}: RowActionsProps<I>) => { | ||
const [isPopupOpen, setIsPopupOpen] = React.useState(false); | ||
const anchorRef = React.useRef<HTMLButtonElement>(null); | ||
const rowId = useUniqId(); | ||
|
||
if (getRowActions === undefined) { | ||
return null; | ||
} | ||
|
||
const renderPopupMenuItem = (action: TableActionConfig<I>, index: number) => { | ||
if (isActionGroup(action)) { | ||
return ( | ||
<Menu.Group key={index} label={action.title}> | ||
{action.items.map(renderPopupMenuItem)} | ||
</Menu.Group> | ||
); | ||
} | ||
|
||
const {text, icon, handler, href, ...restProps} = action; | ||
|
||
return ( | ||
<Menu.Item | ||
key={index} | ||
onClick={(event) => { | ||
event.stopPropagation(); | ||
handler(item, index, event); | ||
|
||
setIsPopupOpen(false); | ||
}} | ||
href={typeof href === 'function' ? href(item, index) : href} | ||
iconStart={icon} | ||
className={bPopup('menu-item')} | ||
{...restProps} | ||
> | ||
{text} | ||
</Menu.Item> | ||
); | ||
}; | ||
|
||
const actions = getRowActions(item, rowIndex); | ||
|
||
if (actions.length === 0) { | ||
return null; | ||
} | ||
|
||
return ( | ||
<div className={b()}> | ||
<Popup | ||
open={isPopupOpen} | ||
anchorRef={anchorRef} | ||
placement={DEFAULT_PLACEMENT} | ||
onOutsideClick={() => setIsPopupOpen(false)} | ||
id={rowId} | ||
> | ||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */} | ||
<div | ||
onClick={(event) => { | ||
event.stopPropagation(); | ||
}} | ||
> | ||
<Menu className={bPopup('menu')} size={rowActionsSize}> | ||
{actions.map(renderPopupMenuItem)} | ||
</Menu> | ||
</div> | ||
</Popup> | ||
<Button | ||
view="flat-secondary" | ||
className={b('actions-button')} | ||
onClick={(event) => { | ||
setIsPopupOpen((value) => !value); | ||
event.stopPropagation(); | ||
}} | ||
size={rowActionsSize} | ||
ref={anchorRef} | ||
extraProps={{ | ||
'aria-label': i18n('label-actions'), | ||
'aria-expanded': isPopupOpen, | ||
'aria-controls': rowId, | ||
}} | ||
> | ||
<Icon data={Ellipsis} /> | ||
</Button> | ||
</div> | ||
); | ||
}; |
18 changes: 18 additions & 0 deletions
18
src/components/RowActions/__stories__/RowActions.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import type {Meta, StoryObj} from '@storybook/react'; | ||
|
||
import {RowActionsColumnStory} from './stories/RowActionsColumnStory'; | ||
import {RowActionsWithActionsColumnStory} from './stories/RowActionsWithActionsColumnStory'; | ||
|
||
const meta: Meta = { | ||
title: 'Table actions', | ||
}; | ||
|
||
export default meta; | ||
|
||
export const ActionsColumn: StoryObj<typeof RowActionsColumnStory> = { | ||
render: RowActionsColumnStory, | ||
}; | ||
|
||
export const SettingsWithActionsColumn: StoryObj<typeof RowActionsWithActionsColumnStory> = { | ||
render: RowActionsWithActionsColumnStory, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import React from 'react'; | ||
|
||
import {Pencil} from '@gravity-ui/icons'; | ||
import {Icon} from '@gravity-ui/uikit'; | ||
|
||
export {columns as baseColumns} from '../../BaseTable/__stories__/constants/columns'; | ||
import type {Item} from '../../BaseTable/__stories__/types'; | ||
import type {TableActionsSettings} from '../types'; | ||
|
||
export const actionsSettings: TableActionsSettings<Item> = { | ||
getRowActions: (item, index) => [ | ||
{ | ||
text: 'default', | ||
handler: () => { | ||
alert(JSON.stringify(item)); | ||
}, | ||
}, | ||
{ | ||
text: 'with icon', | ||
icon: <Icon data={Pencil} size={14} />, | ||
handler: () => {}, | ||
}, | ||
{ | ||
text: 'disabled', | ||
disabled: true, | ||
handler: () => {}, | ||
}, | ||
{ | ||
text: 'danger theme', | ||
theme: 'danger', | ||
handler: () => { | ||
alert(index); | ||
}, | ||
}, | ||
{ | ||
text: 'with href', | ||
theme: 'normal', | ||
href: 'https://gravity-ui.com', | ||
target: '_blank', | ||
rel: 'noopener noreferrer', | ||
handler: () => {}, | ||
}, | ||
], | ||
}; |
80 changes: 80 additions & 0 deletions
80
src/components/RowActions/__stories__/stories/RowActionsColumnStory.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import React from 'react'; | ||
|
||
import {Select} from '@gravity-ui/uikit'; | ||
import type {ColumnDef, RowSelectionState} from '@tanstack/react-table'; | ||
|
||
import {ACTIONS_COLUMN_ID, getActionsColumn, selectionColumn} from '../../../../constants'; | ||
import {useTable} from '../../../../hooks'; | ||
import type {Item} from '../../../BaseTable/__stories__/types'; | ||
import {generateData} from '../../../BaseTable/__stories__/utils'; | ||
import {Table} from '../../../Table/Table'; | ||
import {actionsSettings, baseColumns} from '../constants'; | ||
|
||
const data = generateData(5); | ||
|
||
const defaultColumns: ColumnDef<Item>[] = [ | ||
selectionColumn as ColumnDef<Item>, | ||
...baseColumns, | ||
getActionsColumn<Item>(ACTIONS_COLUMN_ID, { | ||
...actionsSettings, | ||
}), | ||
]; | ||
|
||
const columnsWithRenderRowActions: ColumnDef<Item>[] = [ | ||
selectionColumn as ColumnDef<Item>, | ||
...baseColumns, | ||
getActionsColumn<Item>(ACTIONS_COLUMN_ID, { | ||
...actionsSettings, | ||
renderRowActions: ({row}) => { | ||
const {index} = row; | ||
if (index % 2) { | ||
return null; | ||
} | ||
|
||
return ( | ||
<Select | ||
options={[ | ||
{value: '1', text: 'action 1', content: 'action 1'}, | ||
{value: '2', text: 'action 2', content: 'action 2'}, | ||
{value: '3', text: 'action 3', content: 'action 3'}, | ||
]} | ||
size="s" | ||
title="Actions select example" | ||
/> | ||
); | ||
}, | ||
}), | ||
]; | ||
|
||
export const RowActionsColumnStory = () => { | ||
const [rowSelectionForTable, setRowSelectionForTable] = React.useState<RowSelectionState>({}); | ||
const table = useTable({ | ||
columns: defaultColumns, | ||
data, | ||
state: {rowSelection: rowSelectionForTable}, | ||
enableRowSelection: true, | ||
enableMultiRowSelection: true, | ||
onRowSelectionChange: setRowSelectionForTable, | ||
}); | ||
|
||
const [rowSelectionForTableWithRenderRowActions, setRowSelectionForTableWithRenderRowActions] = | ||
React.useState<RowSelectionState>({}); | ||
const tableWithRenderRowActions = useTable({ | ||
columns: columnsWithRenderRowActions, | ||
data, | ||
state: {rowSelection: rowSelectionForTableWithRenderRowActions}, | ||
enableRowSelection: true, | ||
enableMultiRowSelection: true, | ||
onRowSelectionChange: setRowSelectionForTableWithRenderRowActions, | ||
}); | ||
|
||
return ( | ||
<React.Fragment> | ||
<h3>{'with getRowActions property'}</h3> | ||
<Table table={table} /> | ||
<br /> | ||
<h3>{'with renderRowActions property'}</h3> | ||
<Table table={tableWithRenderRowActions} /> | ||
</React.Fragment> | ||
); | ||
}; |
39 changes: 39 additions & 0 deletions
39
src/components/RowActions/__stories__/stories/RowActionsWithActionsColumnStory.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import React from 'react'; | ||
|
||
import type {ColumnDef, ColumnPinningState} from '@tanstack/react-table'; | ||
|
||
import {SETTINGS_WITH_ACTIONS_COLUMN_ID, getSettingsWithActionsColumn} from '../../../../constants'; | ||
import {useTable} from '../../../../hooks'; | ||
import type {Item} from '../../../BaseTable/__stories__/types'; | ||
import {generateData} from '../../../BaseTable/__stories__/utils'; | ||
import {Table} from '../../../Table/Table'; | ||
import {actionsSettings, baseColumns} from '../constants'; | ||
|
||
const data = generateData(5); | ||
|
||
const columns: ColumnDef<Item>[] = [ | ||
...baseColumns, | ||
getSettingsWithActionsColumn<Item>(SETTINGS_WITH_ACTIONS_COLUMN_ID, { | ||
actions: actionsSettings, | ||
settings: { | ||
sortable: true, | ||
filterable: true, | ||
}, | ||
}), | ||
]; | ||
|
||
export const RowActionsWithActionsColumnStory = () => { | ||
const [columnPinning, setColumnPinning] = React.useState<ColumnPinningState>({ | ||
left: [], | ||
right: [SETTINGS_WITH_ACTIONS_COLUMN_ID], | ||
}); | ||
const table = useTable({ | ||
columns, | ||
data, | ||
enableColumnPinning: true, | ||
state: {columnPinning}, | ||
onColumnPinningChange: setColumnPinning, | ||
}); | ||
|
||
return <Table table={table} />; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"label-actions": "Actions" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import {addComponentKeysets} from '@gravity-ui/uikit/i18n'; | ||
|
||
import {NAMESPACE} from '../../../utils'; | ||
|
||
import en from './en.json'; | ||
import ru from './ru.json'; | ||
|
||
const COMPONENT = `${NAMESPACE}table-actions`; | ||
|
||
export default addComponentKeysets({en, ru}, COMPONENT); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"label-actions": "Действия" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export type * from './types'; | ||
export {RowActions} from './RowActions'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import type {MenuItemProps} from '@gravity-ui/uikit'; | ||
import type {Row} from '@tanstack/react-table'; | ||
|
||
export interface TableAction<TValue> { | ||
text: string; | ||
handler: ( | ||
item: TValue, | ||
index: number, | ||
event: React.MouseEvent<HTMLDivElement | HTMLAnchorElement, MouseEvent>, | ||
) => void; | ||
href?: ((item: TValue, index: number) => string) | string; | ||
target?: string; | ||
rel?: string; | ||
disabled?: boolean; | ||
theme?: MenuItemProps['theme']; | ||
icon?: MenuItemProps['iconStart']; | ||
} | ||
export interface TableActionGroup<I> { | ||
title: string; | ||
items: TableActionConfig<I>[]; | ||
} | ||
export type TableActionConfig<TValue> = TableAction<TValue> | TableActionGroup<TValue>; | ||
/** | ||
* common sizes for Menu and Button | ||
*/ | ||
export type TableRowActionsSize = 's' | 'm' | 'l' | 'xl'; | ||
export type RenderRowActionsProps<TValue> = { | ||
item: TValue; | ||
index: number; | ||
}; | ||
export interface TableActionsSettings<TValue> { | ||
getRowActions?: (item: TValue, index: number) => TableActionConfig<TValue>[]; | ||
renderRowActions?: (props: {row: Row<TValue>}) => React.ReactNode; | ||
rowActionsSize?: TableRowActionsSize; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.