Skip to content

Commit

Permalink
feat(Table): table actions column added (#61)
Browse files Browse the repository at this point in the history
* feat(Table): table actions column added

---------

Co-authored-by: alx-chernigin <[email protected]>
  • Loading branch information
kvestus and alx-chernigin authored Oct 22, 2024
1 parent f161495 commit 9ff4a42
Show file tree
Hide file tree
Showing 22 changed files with 525 additions and 0 deletions.
34 changes: 34 additions & 0 deletions src/components/ActionsCell/ActionsCell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react';

import type {Row} from '@tanstack/react-table';

import type {TableActionsSettings} from '../../types/RowActions';
import {RowActions} from '../RowActions/RowActions';

export const ACTIONS_COLUMN_ID = '_actions';

interface ActionsCellProps<TValue extends unknown> extends TableActionsSettings<TValue> {
row: Row<TValue>;
}

export const ActionsCell = <TValue extends unknown>({
getRowActions,
renderRowActions,
rowActionsSize,
row,
}: ActionsCellProps<TValue>) => {
const {original: item, index} = row;

if (renderRowActions) {
return renderRowActions({row});
}

return (
<RowActions<TValue>
item={item}
index={index}
getRowActions={getRowActions}
rowActionsSize={rowActionsSize}
/>
);
};
1 change: 1 addition & 0 deletions src/components/ActionsCell/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {ActionsCell} from './ActionsCell';
3 changes: 3 additions & 0 deletions src/components/RowActions/RowActions.classname.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import {block} from '../../utils';

export const b = block('row-actions');
18 changes: 18 additions & 0 deletions src/components/RowActions/RowActions.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
@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);
}

&__popup-menu-item {
@include mixins.max-text-width(250px);
}
}
98 changes: 98 additions & 0 deletions src/components/RowActions/RowActions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import React from 'react';

import {Ellipsis} from '@gravity-ui/icons';
import type {PopupPlacement} from '@gravity-ui/uikit';
import {Button, Icon, Popup, useUniqId} from '@gravity-ui/uikit';

import type {TableActionsSettings} from '../../types/RowActions';
import {RowActionsMenu} from '../RowActionsMenu';

import {b} from './RowActions.classname';
import i18n from './i18n';

import './RowActions.scss';

type RowActionsProps<TValue> = Pick<
TableActionsSettings<TValue>,
'getRowActions' | 'rowActionsSize'
> & {
item: TValue;
index: number;
};

const DEFAULT_PLACEMENT: PopupPlacement = ['bottom-end', 'top-end', 'auto'];

export const RowActions = <TValue extends unknown>({
index: rowIndex,
item,
getRowActions,
rowActionsSize,
}: RowActionsProps<TValue>) => {
const [isPopupOpen, setIsPopupOpen] = React.useState(false);
const anchorRef = React.useRef<HTMLButtonElement>(null);
const rowId = useUniqId();

const buttonExtraProps = React.useMemo(
() => ({
'aria-label': i18n('label-actions'),
'aria-expanded': isPopupOpen,
'aria-controls': rowId,
}),
[isPopupOpen, rowId],
);
const handleButtonClick = React.useCallback(
(event: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement, MouseEvent>) => {
setIsPopupOpen((value) => !value);
event.stopPropagation();
},
[],
);

if (getRowActions === undefined) {
return null;
}

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();
}}
>
<RowActionsMenu
item={item}
actions={actions}
size={rowActionsSize}
onMenuItemClick={() => setIsPopupOpen(false)}
className={b('popup-menu')}
itemClassName={b('popup-menu-item')}
/>
</div>
</Popup>
<Button
view="flat-secondary"
className={b('actions-button')}
onClick={handleButtonClick}
size={rowActionsSize}
ref={anchorRef}
extraProps={buttonExtraProps}
>
<Icon data={Ellipsis} />
</Button>
</div>
);
};
23 changes: 23 additions & 0 deletions src/components/RowActions/__stories__/RowActions.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type {Meta, StoryObj} from '@storybook/react';

import {RowActionsColumnStory} from './stories/RowActionsColumnStory';
import {RowActionsWithActionsColumnStory} from './stories/RowActionsWithActionsColumnStory';
import {RowActionsWithCustomRendering} from './stories/RowActionsWithCustomRendering';

const meta: Meta = {
title: 'Table actions',
};

export default meta;

export const ActionsColumn: StoryObj<typeof RowActionsColumnStory> = {
render: RowActionsColumnStory,
};

export const CustomActions: StoryObj<typeof RowActionsWithCustomRendering> = {
render: RowActionsWithCustomRendering,
};

export const SettingsWithActionsColumn: StoryObj<typeof RowActionsWithActionsColumnStory> = {
render: RowActionsWithActionsColumnStory,
};
44 changes: 44 additions & 0 deletions src/components/RowActions/__stories__/constants.tsx
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 {TableActionsSettings} from '../../../types/RowActions';
import type {Item} from '../../BaseTable/__stories__/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: () => {},
},
],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react';

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 columns: ColumnDef<Item>[] = [
selectionColumn as ColumnDef<Item>,
...baseColumns,
getActionsColumn<Item>(ACTIONS_COLUMN_ID, {
...actionsSettings,
}),
];

export const RowActionsColumnStory = () => {
const [rowSelection, setRowSelection] = React.useState<RowSelectionState>({});
const table = useTable({
columns,
data,
state: {rowSelection},
enableRowSelection: true,
enableMultiRowSelection: true,
onRowSelectionChange: setRowSelection,
});

return <Table table={table} />;
};
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} />;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
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 columns: 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 RowActionsWithCustomRendering = () => {
const [rowSelection, setRowSelection] = React.useState<RowSelectionState>({});
const table = useTable({
columns,
data,
state: {rowSelection},
enableRowSelection: true,
enableMultiRowSelection: true,
onRowSelectionChange: setRowSelection,
});

return <Table table={table} />;
};
3 changes: 3 additions & 0 deletions src/components/RowActions/i18n/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"label-actions": "Actions"
}
10 changes: 10 additions & 0 deletions src/components/RowActions/i18n/index.ts
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);
3 changes: 3 additions & 0 deletions src/components/RowActions/i18n/ru.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"label-actions": "Действия"
}
2 changes: 2 additions & 0 deletions src/components/RowActions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export {RowActions} from './RowActions';
export {ActionsCell} from '../ActionsCell/ActionsCell';
Loading

0 comments on commit 9ff4a42

Please sign in to comment.