diff --git a/src/components/RowActions/RowActions.scss b/src/components/RowActions/RowActions.scss new file mode 100644 index 0000000..6fc22ed --- /dev/null +++ b/src/components/RowActions/RowActions.scss @@ -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); + } + } + } +} diff --git a/src/components/RowActions/RowActions.tsx b/src/components/RowActions/RowActions.tsx new file mode 100644 index 0000000..438ac8e --- /dev/null +++ b/src/components/RowActions/RowActions.tsx @@ -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 = Pick, '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 = ( + config: TableActionConfig, +): config is TableActionGroup => { + return Array.isArray((config as TableActionGroup).items); +}; + +export const RowActions = ({ + index: rowIndex, + item, + getRowActions, + rowActionsSize, +}: RowActionsProps) => { + const [isPopupOpen, setIsPopupOpen] = React.useState(false); + const anchorRef = React.useRef(null); + const rowId = useUniqId(); + + if (getRowActions === undefined) { + return null; + } + + const renderPopupMenuItem = (action: TableActionConfig, index: number) => { + if (isActionGroup(action)) { + return ( + + {action.items.map(renderPopupMenuItem)} + + ); + } + + const {text, icon, handler, href, ...restProps} = action; + + return ( + { + event.stopPropagation(); + handler(item, index, event); + + setIsPopupOpen(false); + }} + href={typeof href === 'function' ? href(item, index) : href} + iconStart={icon} + className={bPopup('menu-item')} + {...restProps} + > + {text} + + ); + }; + + const actions = getRowActions(item, rowIndex); + + if (actions.length === 0) { + return null; + } + + return ( +
+ setIsPopupOpen(false)} + id={rowId} + > + {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */} +
{ + event.stopPropagation(); + }} + > + + {actions.map(renderPopupMenuItem)} + +
+
+ +
+ ); +}; diff --git a/src/components/RowActions/__stories__/RowActions.stories.tsx b/src/components/RowActions/__stories__/RowActions.stories.tsx new file mode 100644 index 0000000..56315aa --- /dev/null +++ b/src/components/RowActions/__stories__/RowActions.stories.tsx @@ -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 = { + render: RowActionsColumnStory, +}; + +export const SettingsWithActionsColumn: StoryObj = { + render: RowActionsWithActionsColumnStory, +}; diff --git a/src/components/RowActions/__stories__/constants.tsx b/src/components/RowActions/__stories__/constants.tsx new file mode 100644 index 0000000..5b997ce --- /dev/null +++ b/src/components/RowActions/__stories__/constants.tsx @@ -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 = { + getRowActions: (item, index) => [ + { + text: 'default', + handler: () => { + alert(JSON.stringify(item)); + }, + }, + { + text: 'with icon', + icon: , + 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: () => {}, + }, + ], +}; diff --git a/src/components/RowActions/__stories__/stories/RowActionsColumnStory.tsx b/src/components/RowActions/__stories__/stories/RowActionsColumnStory.tsx new file mode 100644 index 0000000..680be7c --- /dev/null +++ b/src/components/RowActions/__stories__/stories/RowActionsColumnStory.tsx @@ -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[] = [ + selectionColumn as ColumnDef, + ...baseColumns, + getActionsColumn(ACTIONS_COLUMN_ID, { + ...actionsSettings, + }), +]; + +const columnsWithRenderRowActions: ColumnDef[] = [ + selectionColumn as ColumnDef, + ...baseColumns, + getActionsColumn(ACTIONS_COLUMN_ID, { + ...actionsSettings, + renderRowActions: ({row}) => { + const {index} = row; + if (index % 2) { + return null; + } + + return ( +