From 680c405bca3bb497374572b4f9754ed77f3eb19b Mon Sep 17 00:00:00 2001 From: yunhui <2448510326@qq.com> Date: Mon, 25 Nov 2024 22:54:58 +0800 Subject: [PATCH 1/3] feat: add new Actions component --- components/actions/ActionMenu.tsx | 64 ++++++++++++++++++ components/actions/index.tsx | 108 ++++++++++++++++++++++++++++++ components/actions/interface.ts | 100 +++++++++++++++++++++++++++ components/actions/style/index.ts | 82 +++++++++++++++++++++++ components/index.ts | 3 + components/theme/components.ts | 2 + components/x-provider/context.ts | 2 + 7 files changed, 361 insertions(+) create mode 100644 components/actions/ActionMenu.tsx create mode 100644 components/actions/index.tsx create mode 100644 components/actions/interface.ts create mode 100644 components/actions/style/index.ts diff --git a/components/actions/ActionMenu.tsx b/components/actions/ActionMenu.tsx new file mode 100644 index 00000000..3c02d93f --- /dev/null +++ b/components/actions/ActionMenu.tsx @@ -0,0 +1,64 @@ +import { EllipsisOutlined } from '@ant-design/icons'; +import { Dropdown, MenuProps } from 'antd'; +import React from 'react'; +import { useXProviderContext } from '../x-provider'; +import { ActionItemType, ActionsProps, SubItemType } from './interface'; + +export const findItem = (keyPath: string[], items: ActionItemType[]): ActionItemType | null => { + const keyToFind = keyPath[0]; // Get the first key from the keyPath + + for (const item of items) { + if (item.key === keyToFind) { + // If the item is found and this is the last key in the path + if (keyPath.length === 1) return item; + + // If it is a SubItemType, recurse to find in its children + if ('children' in item) { + return findItem(keyPath.slice(1), item.children!); + } + } + } + + return null; +}; + +const ActionMenu = (props: { item: SubItemType } & Pick) => { + const { onClick: onMenuClick } = props; + const item = props.item; + const { children = [], triggerSubMenuAction = 'hover' } = item; + const { getPrefixCls } = useXProviderContext(); + const prefixCls = getPrefixCls('actions', props.prefixCls); + const icon = item?.icon ?? ; + + const menuProps: MenuProps = { + items: children, + + onClick: ({ key, keyPath, domEvent }) => { + onMenuClick?.({ + key, + keyPath: [item.key, ...keyPath], + domEvent, + item: findItem(keyPath, children)!, + }); + }, + }; + + return ( + +
+
{icon}
+
+
+ ); +}; + +if (process.env.NODE_ENV === 'production') { + ActionMenu.displayName = 'ActionMenu'; +} + +export default ActionMenu; diff --git a/components/actions/index.tsx b/components/actions/index.tsx new file mode 100644 index 00000000..3be22ef6 --- /dev/null +++ b/components/actions/index.tsx @@ -0,0 +1,108 @@ +import { Tooltip, TooltipProps } from 'antd'; +import classnames from 'classnames'; +import React from 'react'; +import { ReactNode } from 'react'; +import useXComponentConfig from '../_util/hooks/use-x-component-config'; +import { useXProviderContext } from '../x-provider'; +import ActionMenu from './ActionMenu'; +import { ActionItemType, ActionsProps, ItemType } from './interface'; +import useStyle from './style'; + +export { ActionsProps } from './interface'; + +const Actions = (props: ActionsProps) => { + const { + prefixCls: customizePrefixCls, + rootClassName = {}, + style = {}, + variant = 'borderless', + block = false, + onClick, + items = [], + } = props; + // ============================ PrefixCls ============================ + const { getPrefixCls } = useXProviderContext(); + const prefixCls = getPrefixCls('actions', customizePrefixCls); + + // ======================= Component Config ======================= + const contextConfig = useXComponentConfig('actions'); + + // ============================ Styles ============================ + const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls); + + const mergedCls = classnames( + prefixCls, + contextConfig.className, + rootClassName, + cssVarCls, + hashId, + ); + + const mergedStyle = { + ...style, + ...contextConfig.style, + }; + + const getTooltipNode = (node: ReactNode, title?: string, tooltipProps?: TooltipProps) => { + if (title) { + return ( + + {node} + + ); + } + return node; + }; + + const handleItemClick = ( + key: string, + item: ActionItemType, + domEvent: React.MouseEvent, + ) => { + if (item.onClick) { + item.onClick(); + return; + } + onClick?.({ + key, + item, + keyPath: [key], + domEvent, + }); + }; + + const renderSingleItem = (item: ItemType) => { + const { icon, label, key } = item; + + return ( +
handleItemClick(key, item, domEvent)} + key={key} + > + {getTooltipNode(
{icon}
, label)} +
+ ); + }; + + return wrapCSSVar( +
+
+ {items.map((item) => { + if ('children' in item) { + return ( + + ); + } + return renderSingleItem(item); + })} +
+
, + ); +}; + +if (process.env.NODE_ENV === 'production') { + Actions.displayName = 'Actions'; +} + +export default Actions; diff --git a/components/actions/interface.ts b/components/actions/interface.ts new file mode 100644 index 00000000..cdb1038c --- /dev/null +++ b/components/actions/interface.ts @@ -0,0 +1,100 @@ +import type { MenuItemProps, MenuProps } from 'antd'; +import type { ReactNode } from 'react'; + +export interface ActionsProps { + /** + * @desc 包含多个操作项的列表 + * @descEN A list containing multiple action items. + */ + items: ActionItemType[]; + /** + * @desc 根节点样式类 + * @descEN Root node style class. + */ + rootClassName?: string; + /** + * @desc 子操作项是否占据一行 + * @descEN Whether the child action items occupy a line. + * @default false + */ + block?: boolean; + /** + * @desc Item 操作项被点击时的回调函数。 + * @descEN Callback function when an action item is clicked. + */ + onClick?: (menuInfo: { + item: ActionItemType; + key: string; + keyPath: string[]; + domEvent: React.MouseEvent | React.KeyboardEvent; + }) => void; + /** + * @desc 根节点样式 + * @descEN Style for the root node. + */ + style?: React.CSSProperties; + /** + * @desc 变体 + * @descEN Variant. + * @default 'border' + */ + variant?: 'borderless' | 'border'; + /** + * @desc 样式类名的前缀。 + * @descEN Prefix for style class names. + */ + prefixCls?: string; +} + +export interface ItemType extends Pick { + /** + * @desc 自定义操作的显示标签 + * @descEN Display label for the custom action. + */ + label?: string; + /** + * @desc 自定义操作的唯一标识 + * @descEN Unique identifier for the custom action. + */ + key: string; + /** + * @desc 自定义操作的图标 + * @descEN Icon for the custom action. + */ + icon?: ReactNode; + /** + * @desc 点击自定义操作按钮时的回调函数 + * @descEN Callback function when the custom action button is clicked. + */ + onClick?: () => void; +} + +export interface SubItemType { + /** + * @desc 自定义操作的唯一标识 + * @descEN Unique identifier for the custom action. + */ + key: string; + /** + * @desc 自定义操作的显示标签 + * @descEN Display label for the custom action. + */ + label?: string; + /** + * @desc 自定义操作的图标 + * @descEN Icon for the custom action. + */ + icon?: ReactNode; + /** + * @desc 子操作项 + * @descEN Child action items. + */ + children?: ActionItemType[]; + triggerSubMenuAction?: MenuProps['triggerSubMenuAction']; + /** + * @desc 点击自定义操作按钮时的回调函数 + * @descEN Callback function when the custom action button is clicked. + */ + onClick?: () => void; +} +export type ActionItemType = ItemType | SubItemType; diff --git a/components/actions/style/index.ts b/components/actions/style/index.ts new file mode 100644 index 00000000..e1a55beb --- /dev/null +++ b/components/actions/style/index.ts @@ -0,0 +1,82 @@ +import { mergeToken } from '@ant-design/cssinjs-utils'; +import type { FullToken, GenerateStyle, GetDefaultToken } from '../../theme/cssinjs-utils'; +import { genStyleHooks } from '../../theme/genStyleUtils'; + +// biome-ignore lint/suspicious/noEmptyInterface: ComponentToken need to be empty by default +export interface ComponentToken {} + +export interface ActionsToken extends FullToken<'Prompts'> {} + +const genActionsStyle: GenerateStyle = (token) => { + const { componentCls, calc } = token; + + return { + [componentCls]: { + [`${componentCls}-list`]: { + display: 'inline-flex', + flexDirection: 'row', + gap: token.paddingXS, + color: '#8c8c8c', + + '&-item, &-sub-item': { + cursor: 'pointer', + padding: token.paddingXXS, + borderRadius: token.borderRadius, + height: token.controlHeightSM, + width: token.controlHeightSM, + boxSizing: 'border-box', + display: 'inline-flex', + alignItems: 'center', + justifyContent: 'center', + + '&-icon': { + display: 'inline-flex', + alignItems: 'center', + justifyContent: 'center', + fontSize: token.fontSize, + width: '100%', + height: '100%', + }, + + '&:hover': { + background: '#F6F6F6', + }, + }, + }, + '& .border': { + padding: `${token.paddingXS} ${token.paddingSM}`, + gap: token.paddingSM, + borderRadius: calc(token.borderRadiusLG).mul(1.5).equal(), + backgroundColor: '#F7F7F7', + color: token.colorTextSecondary, + + [`${componentCls}-list-item, ${componentCls}-list-sub-item`]: { + padding: 0, + lineHeight: token.lineHeight, + + '&-icon': { + fontSize: token.fontSizeLG, + }, + + '&:hover': { + opacity: 0.8, + }, + }, + }, + '& .block': { + display: 'flex', + }, + }, + }; +}; + +export const prepareComponentToken: GetDefaultToken<'Prompts'> = () => ({}); + +export default genStyleHooks( + 'Actions', + (token) => { + const compToken = mergeToken(token, {}); + return [genActionsStyle(compToken)]; + }, + prepareComponentToken, +); diff --git a/components/index.ts b/components/index.ts index 83918a7a..b54e2f2c 100644 --- a/components/index.ts +++ b/components/index.ts @@ -35,3 +35,6 @@ export { default as XStream } from './x-stream'; export type { XStreamOptions } from './x-stream'; export { default as XRequest } from './x-request'; + +export { default as Actions } from './actions'; +export type { ActionsProps } from './actions'; diff --git a/components/theme/components.ts b/components/theme/components.ts index 4c9f82eb..164615b0 100644 --- a/components/theme/components.ts +++ b/components/theme/components.ts @@ -1,3 +1,4 @@ +import type { ComponentToken as ActionsComponentToken } from '../actions/style'; import type { ComponentToken as AttachmentsToken } from '../attachments/style'; import type { ComponentToken as BubbleComponentToken } from '../bubble/style'; import type { ComponentToken as ConversationsComponentToken } from '../conversations/style'; @@ -16,4 +17,5 @@ export interface ComponentTokenMap { Suggestion?: SuggestionComponentToken; ThoughtChain?: ThoughtChainComponentToken; Welcome?: WelcomeComponentToken; + Actions?: ActionsComponentToken; } diff --git a/components/x-provider/context.ts b/components/x-provider/context.ts index 88863309..3f8a4fa8 100644 --- a/components/x-provider/context.ts +++ b/components/x-provider/context.ts @@ -1,6 +1,7 @@ import React from 'react'; import type { AnyObject } from '../_util/type'; +import type { ActionsProps } from '../actions'; import { AttachmentsProps } from '../attachments'; import type { BubbleProps } from '../bubble'; import type { ConversationsProps } from '../conversations'; @@ -33,6 +34,7 @@ export interface XComponentsConfig { thoughtChain?: ComponentStyleConfig; attachments?: ComponentStyleConfig; welcome?: ComponentStyleConfig; + actions?: ComponentStyleConfig; } export interface XProviderProps extends XComponentsConfig { From b491639848e82436448fb12039775b1db46606f8 Mon Sep 17 00:00:00 2001 From: yunhui <2448510326@qq.com> Date: Mon, 25 Nov 2024 22:55:43 +0800 Subject: [PATCH 2/3] test: add Actions base test case --- .../__snapshots__/demo.test.tsx.snap | 185 ++++++++++++++++++ .../actions/__tests__/actionMenu.test.tsx | 44 +++++ components/actions/__tests__/actions.test.tsx | 55 ++++++ 3 files changed, 284 insertions(+) create mode 100644 components/actions/__tests__/__snapshots__/demo.test.tsx.snap create mode 100644 components/actions/__tests__/actionMenu.test.tsx create mode 100644 components/actions/__tests__/actions.test.tsx diff --git a/components/actions/__tests__/__snapshots__/demo.test.tsx.snap b/components/actions/__tests__/__snapshots__/demo.test.tsx.snap new file mode 100644 index 00000000..047c62eb --- /dev/null +++ b/components/actions/__tests__/__snapshots__/demo.test.tsx.snap @@ -0,0 +1,185 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders components/actions/demo/basic.tsx correctly 1`] = ` +
+
+
+
+ + + +
+
+
+
+ + + +
+
+
+
+`; + +exports[`renders components/actions/demo/sub.tsx correctly 1`] = ` +
+
+
+
+ + + +
+
+
+
+ + + +
+
+
+
+ + + +
+
+
+
+ + + +
+
+
+
+`; diff --git a/components/actions/__tests__/actionMenu.test.tsx b/components/actions/__tests__/actionMenu.test.tsx new file mode 100644 index 00000000..14395aca --- /dev/null +++ b/components/actions/__tests__/actionMenu.test.tsx @@ -0,0 +1,44 @@ +import { fireEvent, render } from '@testing-library/react'; +import React from 'react'; +import ActionMenu, { findItem } from '../ActionMenu'; // Adjust the import according to your file structure +import { ActionItemType } from '../interface'; + +describe('findItem function', () => { + const items: ActionItemType[] = [ + { key: '1', label: 'Action 1' }, + { + key: '2', + label: 'Action 2', + children: [ + { key: '2-1', label: 'Sub Action 1' }, + { key: '2-2', label: 'Sub Action 2' }, + ], + }, + { key: '3', label: 'Action 3' }, + ]; + + it('should return the item if it exists at the root level', () => { + const result = findItem(['1'], items); + expect(result).toEqual(items[0]); + }); + + it('should return the item if it exists at a deeper level', () => { + const result = findItem(['2', '2-1'], items); + expect(result).toEqual(items[1].children![0]); + }); + + it('should return null if the item does not exist', () => { + const result = findItem(['4'], items); + expect(result).toBeNull(); + }); + + it('should return null when searching a non-existent sub-item', () => { + const result = findItem(['2', '2-3'], items); + expect(result).toBeNull(); + }); + + it('should handle an empty keyPath gracefully', () => { + const result = findItem([], items); + expect(result).toBeNull(); + }); +}); diff --git a/components/actions/__tests__/actions.test.tsx b/components/actions/__tests__/actions.test.tsx new file mode 100644 index 00000000..81e657ab --- /dev/null +++ b/components/actions/__tests__/actions.test.tsx @@ -0,0 +1,55 @@ +import { fireEvent, render, waitFor } from '@testing-library/react'; +import React from 'react'; +import Actions, { ActionsProps } from '../index'; // Adjust the import according to your file structure + +describe('Actions Component', () => { + const consoleSpy = jest.spyOn(console, 'log'); // 监视 console.log + const mockOnClick = jest.fn(); + const items = [ + { key: '1', label: 'Action 1', icon: icon1 }, + { + key: '2', + label: 'Action 2', + icon: icon2, + onClick: () => console.log('Action 2 clicked'), + }, + { + key: 'sub', + children: [{ key: 'sub-1', label: 'Sub Action 1', icon: ⚙️ }], + }, + ]; + + it('renders correctly', () => { + const { getByText } = render(); + + expect(getByText('icon1')).toBeInTheDocument(); + expect(getByText('icon2')).toBeInTheDocument(); + }); + + it('calls onClick when an action item is clicked', () => { + const onClick: ActionsProps['onClick'] = ({ keyPath }) => { + console.log(`You clicked ${keyPath.join(',')}`); + }; + const { getByText } = render(); + + fireEvent.click(getByText('icon1')); + expect(consoleSpy).toHaveBeenCalledWith('You clicked 1'); + }); + + it('calls individual item onClick if provided', () => { + const consoleSpy = jest.spyOn(console, 'log'); + const { getByText } = render(); + + fireEvent.click(getByText('icon2')); + expect(consoleSpy).toHaveBeenCalledWith('Action 2 clicked'); + consoleSpy.mockRestore(); + }); + + it('renders sub-menu items', async () => { + const { getByText, container } = render(); + + fireEvent.mouseOver(container.querySelector('.ant-dropdown-trigger')!); // Assuming the dropdown opens on hover + + await waitFor(() => expect(getByText('Sub Action 1')).toBeInTheDocument()); + }); +}); From 13f1209ff4f66e9f1e1ad57a8f572e25a738e499 Mon Sep 17 00:00:00 2001 From: yunhui <2448510326@qq.com> Date: Mon, 25 Nov 2024 22:56:12 +0800 Subject: [PATCH 3/3] docs: add Actions document --- components/actions/demo/basic.md | 7 ++++ components/actions/demo/basic.tsx | 26 ++++++++++++ components/actions/demo/sub.md | 7 ++++ components/actions/demo/sub.tsx | 54 +++++++++++++++++++++++++ components/actions/demo/variant.md | 7 ++++ components/actions/demo/variant.tsx | 26 ++++++++++++ components/actions/index.en-US.md | 62 ++++++++++++++++++++++++++++ components/actions/index.zh-CN.md | 63 +++++++++++++++++++++++++++++ 8 files changed, 252 insertions(+) create mode 100644 components/actions/demo/basic.md create mode 100644 components/actions/demo/basic.tsx create mode 100644 components/actions/demo/sub.md create mode 100644 components/actions/demo/sub.tsx create mode 100644 components/actions/demo/variant.md create mode 100644 components/actions/demo/variant.tsx create mode 100644 components/actions/index.en-US.md create mode 100644 components/actions/index.zh-CN.md diff --git a/components/actions/demo/basic.md b/components/actions/demo/basic.md new file mode 100644 index 00000000..673339b1 --- /dev/null +++ b/components/actions/demo/basic.md @@ -0,0 +1,7 @@ +## zh-CN + +基础用法。 + +## en-US + +Basic usage. diff --git a/components/actions/demo/basic.tsx b/components/actions/demo/basic.tsx new file mode 100644 index 00000000..79f2f95b --- /dev/null +++ b/components/actions/demo/basic.tsx @@ -0,0 +1,26 @@ +import { CopyOutlined, RedoOutlined } from '@ant-design/icons'; +import { Actions, ActionsProps } from '@ant-design/x'; +import { message } from 'antd'; +import React from 'react'; + +const actionItems = [ + { + key: 'retry', + icon: , + label: '重试', + }, + { + key: 'copy', + icon: , + label: '复制', + }, +]; + +const Demo: React.FC = () => { + const onClick: ActionsProps['onClick'] = ({ key, keyPath, item }) => { + message.success(`you click ${keyPath.join(',')}`); + }; + return ; +}; + +export default Demo; diff --git a/components/actions/demo/sub.md b/components/actions/demo/sub.md new file mode 100644 index 00000000..2645dbac --- /dev/null +++ b/components/actions/demo/sub.md @@ -0,0 +1,7 @@ +## zh-CN + +更多菜单项。 + +## en-US + +Basic usage. diff --git a/components/actions/demo/sub.tsx b/components/actions/demo/sub.tsx new file mode 100644 index 00000000..643b6ce5 --- /dev/null +++ b/components/actions/demo/sub.tsx @@ -0,0 +1,54 @@ +import { + CopyOutlined, + DeleteOutlined, + RedoOutlined, + ReloadOutlined, + ShareAltOutlined, +} from '@ant-design/icons'; +import { Actions, ActionsProps } from '@ant-design/x'; +import { message } from 'antd'; +import React from 'react'; + +const actionItems: ActionsProps['items'] = [ + { + key: 'retry', + label: '重试', + icon: , + }, + { + key: 'copy', + label: '复制', + icon: , + }, + { + key: 'more', + // icon: , // 不传使用默认的icon + children: [ + { + key: 'share', + label: '分享', + icon: , + children: [ + { key: 'qq', label: 'QQ' }, + { key: 'wechat', label: '微信' }, + ], + }, + { key: 'import', label: '引用' }, + { key: 'delete', label: '删除', icon: , onClick: () => {}, danger: true }, + ], + }, + { + key: 'clear', + label: '清空', + icon: , + }, +]; + +const Demo: React.FC = () => { + const onClick: ActionsProps['onClick'] = ({ key, keyPath, item }) => { + message.success(`you click ${keyPath}`); + }; + return ; +}; + +export default Demo; diff --git a/components/actions/demo/variant.md b/components/actions/demo/variant.md new file mode 100644 index 00000000..05c7a86a --- /dev/null +++ b/components/actions/demo/variant.md @@ -0,0 +1,7 @@ +## zh-CN + +使用`variant`切换变体。 + +## en-US + +Use `variant` to switch variants. diff --git a/components/actions/demo/variant.tsx b/components/actions/demo/variant.tsx new file mode 100644 index 00000000..0357caf0 --- /dev/null +++ b/components/actions/demo/variant.tsx @@ -0,0 +1,26 @@ +import { CopyOutlined, RedoOutlined } from '@ant-design/icons'; +import { Actions, ActionsProps } from '@ant-design/x'; +import { message } from 'antd'; +import React from 'react'; + +const actionItems = [ + { + key: 'retry', + icon: , + label: '重试', + }, + { + key: 'copy', + icon: , + label: '复制', + }, +]; + +const Demo: React.FC = () => { + const onClick: ActionsProps['onClick'] = ({ key, keyPath, item }) => { + message.success(`you click ${keyPath.join(',')}`); + }; + return ; +}; + +export default Demo; diff --git a/components/actions/index.en-US.md b/components/actions/index.en-US.md new file mode 100644 index 00000000..f3f1b8ac --- /dev/null +++ b/components/actions/index.en-US.md @@ -0,0 +1,62 @@ +--- +category: Components +group: + title: Common + order: 0 +title: Actions +description: Used for quickly configuring required action buttons or features in some AI scenarios. +cover: https://mdn.alipayobjects.com/huamei_iwk9zp/afts/img/A*1ysXSqEnAckAAAAAAAAAAAAADgCCAQ/original +coverDark: https://mdn.alipayobjects.com/huamei_iwk9zp/afts/img/A*EkYUTotf-eYAAAAAAAAAAAAADgCCAQ/original +demo: + cols: 1 +--- + +## When to Use + +The Actions component is used for quickly configuring required action buttons or features in some AI scenarios. + +## Examples + + +Basic +More Menu Items +Using Variants + +## API + +Common props ref:[Common props](/docs/react/common-props) + +### Actions + +| Property | Description | Type | Default | Version | +| --- | --- | --- | --- | --- | +| items | A list containing multiple action items | ActionItemType[] | - | - | +| rootClassName | Style class for the root node | string | - | - | +| onClick | Callback function when an action item is clicked | `function({ item, key, keyPath, selectedKeys, domEvent })` | - | - | +| style | Style for the root node | React.CSSProperties | - | - | +| variant | Variant | `'borderless' \| 'border'` | 'border' | - | +| prefixCls | Prefix for style class names | string | - | - | + +### ItemType + +| Property | Description | Type | Default | Version | +| --- | --- | --- | --- | --- | +| label | The display label for the custom action | string | - | - | +| key | The unique identifier for the custom action | string | - | - | +| icon | The icon for the custom action | ReactNode | - | - | +| onClick | Callback function when the custom action button is clicked | () => void | - | - | + +### SubItemType + +| Property | Description | Type | Default | Version | +| --- | --- | --- | --- | --- | +| key | The unique identifier for the custom action | string | - | - | +| label | The display label for the custom action | string | - | - | +| icon | The icon for the custom action | ReactNode | - | - | +| children | Sub action items | ActionItemType[] | - | - | +| triggerSubMenuAction | Action to trigger the sub-menu | `hover \| click` | - | - | +| onClick | Callback function when the custom action button is clicked | () => void | - | - | + +### ActionItemType + +> type `ActionItemType` = `ItemType` | `SubItemType` diff --git a/components/actions/index.zh-CN.md b/components/actions/index.zh-CN.md new file mode 100644 index 00000000..f0087cc0 --- /dev/null +++ b/components/actions/index.zh-CN.md @@ -0,0 +1,63 @@ +--- +category: Components +group: + title: 通用 + order: 0 +title: Actions +subtitle: 操作栏 +description: 用于快速配置一些 AI 场景下所需要的操作按钮/功能。 +cover: https://mdn.alipayobjects.com/huamei_iwk9zp/afts/img/A*1ysXSqEnAckAAAAAAAAAAAAADgCCAQ/original +coverDark: https://mdn.alipayobjects.com/huamei_iwk9zp/afts/img/A*EkYUTotf-eYAAAAAAAAAAAAADgCCAQ/original +demo: + cols: 1 +--- + +## 何时使用 + +Actions 组件用于快速配置一些 AI 场景下所需要的操作按钮/功能 + +## 代码演示 + + +基本 +更多菜单项 +使用变体 + +## API + +通用属性参考:[通用属性](/docs/react/common-props) + +### Actions + +| 属性 | 说明 | 类型 | 默认值 | 版本 | +| --- | --- | --- | --- | --- | +| items | 包含多个操作项的列表 | ActionItemType[] | - | - | +| rootClassName | 根节点样式类 | string | - | - | +| onClick | Item 操作项被点击时的回调函数 | `function({ item, key, keyPath, selectedKeys, domEvent })` | - | - | +| style | 根节点样式 | React.CSSProperties | - | - | +| variant | 变体 | `'borderless' \| 'border'` | 'border' | - | +| prefixCls | 样式类名的前缀 | string | - | - | + +### ItemType + +| 属性 | 说明 | 类型 | 默认值 | 版本 | +| ------- | ------------------------------ | ---------- | ------ | ---- | +| label | 自定义操作的显示标签 | string | - | - | +| key | 自定义操作的唯一标识 | string | - | - | +| icon | 自定义操作的图标 | ReactNode | - | - | +| onClick | 点击自定义操作按钮时的回调函数 | () => void | - | - | + +### SubItemType + +| 属性 | 说明 | 类型 | 默认值 | 版本 | +| -------------------- | ------------------------------ | ---------------- | ------ | ---- | +| key | 自定义操作的唯一标识 | string | - | - | +| label | 自定义操作的显示标签 | string | - | - | +| icon | 自定义操作的图标 | ReactNode | - | - | +| children | 子操作项 | ActionItemType[] | - | - | +| triggerSubMenuAction | 触发子菜单的操作 | `hover \| click` | - | - | +| onClick | 点击自定义操作按钮时的回调函数 | () => void | - | - | + +### ActionItemType + +> type `ActionItemType` = `ItemType` | `SubItemType`