Skip to content

Commit

Permalink
feat(dropdown): set active status on Button or IconButton when visible (
Browse files Browse the repository at this point in the history
#1697)

* feat(dropdown): set active status on Button or IconButton when visible

* docs(dropdown): add story of dropdown

Co-authored-by: maxin <maxin@growingio.com>
  • Loading branch information
nnmax and maxin authored Dec 20, 2021
1 parent 65c6a32 commit 33d72d5
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 45 deletions.
4 changes: 3 additions & 1 deletion src/button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ const Button = WithRef<HTMLButtonElement, ButtonProps>((props, ref) => {
);
});

Button.displayName = 'Button';
export const BUTTON_DISPLAY_NAME = 'Button';

Button.displayName = BUTTON_DISPLAY_NAME;

Button.defaultProps = {
type: 'primary',
Expand Down
4 changes: 3 additions & 1 deletion src/button/IconButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ const IconButton = React.forwardRef<HTMLButtonElement, IconButtonProps>((props,
return <Button ref={ref} className={classes} prefix={children} {...restProps} />;
});

IconButton.displayName = 'IconButton';
export const ICON_BUTTON_DISPLAY_NAME = 'IconButton';

IconButton.displayName = ICON_BUTTON_DISPLAY_NAME;

IconButton.defaultProps = {
type: 'primary',
Expand Down
24 changes: 18 additions & 6 deletions src/dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import React, { cloneElement, Children, forwardRef } from 'react';
import classnames from 'classnames';
import { usePrefixCls } from '@gio-design/utils';
import { isFunction, isUndefined, noop } from 'lodash';
import { get, isFunction, isUndefined, noop } from 'lodash';
import DropdownProps from './interface';
import useControlledState from '../utils/hooks/useControlledState';
import Popover from '../popover';
import { BUTTON_DISPLAY_NAME } from '../button/Button';
import { ICON_BUTTON_DISPLAY_NAME } from '../button/IconButton';

export const Dropdown = forwardRef<HTMLDivElement, DropdownProps>((props, ref) => {
const {
Expand All @@ -25,18 +27,28 @@ export const Dropdown = forwardRef<HTMLDivElement, DropdownProps>((props, ref) =

const getDropdownTrigger = () => {
const child = Children.only(children);
return cloneElement(child as React.ReactElement, {
const childProps: {
className?: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onClick?: (...args: any) => void;
active?: boolean;
} = {
className: classnames(
{
'dropdown-active': controlledVisible,
},
(child as React.ReactElement).props.className
child.props.className
),
onClick: (...arg: any) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onClick: (...args: any) => {
setControlledVisible(!controlledVisible);
(child as React.ReactElement).props.onClick?.(...arg);
child.props.onClick?.(...args);
},
});
};
if ([BUTTON_DISPLAY_NAME, ICON_BUTTON_DISPLAY_NAME].includes(get(child.type, 'displayName'))) {
childProps.active = child.props.active ?? controlledVisible;
}
return cloneElement(child, childProps);
};

const withoutPadding = (contentNode: DropdownProps['content']) => {
Expand Down
111 changes: 82 additions & 29 deletions src/dropdown/demos/Dropdown.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint-disable no-console */
import React, { useState } from 'react';
import { Meta, Story } from '@storybook/react/types-6-0';
import { Meta } from '@storybook/react/types-6-0';
import {
DownloadOutlined,
EmailOutlined,
Expand All @@ -13,7 +14,7 @@ import {
MoreOutlined,
} from '@gio-design/icons';
import Docs from './DropdownPage';
import Dropdown, { DropdownProps } from '../index';
import Dropdown from '../index';
import '../style';
import Button from '../../button';
import List, { Item } from '../../list';
Expand Down Expand Up @@ -127,32 +128,84 @@ export const Demo = () => {
</>
);
};
const Template: Story<DropdownProps> = (args) => <Dropdown {...args} data-testid="template-dropdown" />;
export const Default = Template.bind({});
Default.args = {
content: (
<List>
<Item prefix={<ReloadOutlined size="14px" />} value="1">
刷新数据
</Item>
<Item prefix={<FullScreenOutlined size="14px" />} value="2">
进入全屏
</Item>
<Item prefix={<DownloadOutlined size="14px" />} value="3">
下载 PDF
</Item>
<Divider style={{ margin: '0 0 4px' }} />
<CascaderItem label="邮件推送" value="4" prefix={<EmailOutlined size="14px" />}>

export const Default = () => (
<>
<p>Dropdown 在非受控模式下,会在 content 外包一层 div,并且监听该 div 的 onClick 事件,以实现自动隐藏和显示。</p>
<p>
如果触发元素为 Button 或者 IconButton,Dropdown 在展开的时候会添加一个 active 参数,如果不需要,可以在 Button 或者
IconButton 中设置 active={`{false}`} 来强制覆盖。
</p>
<Dropdown
data-testid="template-dropdown"
content={
<List onChange={(changedValue, options) => console.log(changedValue, options)}>
<List.Item prefix={<ReloadOutlined size="14px" />} value="1">
刷新数据
</List.Item>
<List.Item prefix={<FullScreenOutlined size="14px" />} value="2">
进入全屏
</List.Item>
<List.Item prefix={<DownloadOutlined size="14px" />} value="3">
下载 PDF
</List.Item>
<PopConfirm
title="您确认要删除吗?"
trigger="click"
getContainer={() => document.body}
onContentClick={(event) => event.stopPropagation()}
>
{/** 需要包裹一层 div,因为 List.Item onClick 的参数跟 PopConfirm 里监听 trigger onClick 的参数不一致,List.Item onClick 返回的参数为 value 而不是 event */}
<div role="none" onClick={(event) => event.stopPropagation()}>
<List.Item value="delete" prefix={<DeleteOutlined size="14px" />}>
删除
</List.Item>
</div>
</PopConfirm>
<Divider style={{ margin: '0 0 4px' }} />
<CascaderItem
label="邮件推送"
value="4"
prefix={<EmailOutlined size="14px" />}
// 阻止 Dropdown 自动关闭
onClick={(_, event) => event.stopPropagation()}
>
<List>
<List.Item value="4-1" label="创建邮件推送" prefix={<PlusOutlined size="14px" />} />
<List.Item value="4-1" label="邮件推送管理" prefix={<SettingOutlined size="14px" />} />
</List>
</CascaderItem>
</List>
}
// 如果在 Table 中使用,并监听了 rowClick 事件,则需要使用此参数
onContentClick={(event) => event.stopPropagation()}
>
<Button type="secondary" prefix={<MoreHorizonalOutlined />}>
更多
</Button>
</Dropdown>
</>
);

export const Disabled = () => (
<>
<h4>不可用的菜单项,和分割线。</h4>
<p>如果不想 Dropdown 自动隐藏,可以在 ListItem 上添加 onClick 监听器,阻止事件冒泡。</p>
<Dropdown
content={
<List>
<Item value="4-1" label="创建邮件推送" prefix={<PlusOutlined size="14px" />} />
<Item value="4-1" label="邮件推送管理" prefix={<SettingOutlined size="14px" />} />
<List.Item value="Apple">Apple</List.Item>
<List.Item value="Orange">Orange</List.Item>
<Divider style={{ margin: 0 }} />
<List.Item value="Banana" disabled>
Banana
</List.Item>
</List>
</CascaderItem>
</List>
),
children: (
<Button type="secondary" prefix={<MoreHorizonalOutlined />}>
更多
</Button>
),
};
}
>
<Button type="secondary" prefix={<MoreHorizonalOutlined />}>
更多
</Button>
</Dropdown>
</>
);
21 changes: 20 additions & 1 deletion src/dropdown/demos/DropdownPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,22 @@ export default function DropdownPage() {
<ul>
<li>样式变化:新版dropdown等下拉菜单拥有更圆润的边框。</li>
<li>APi变动:包装的Popover,弹出内容更名为content,children包装触发器。</li>
<li>Dropdown 封装自popover</li>
<li>Dropdown 封装自 popover</li>
<li>
Dropdown 在非受控模式下,会在 content 外包一层 div,并且监听该 div 的 onClick 事件,以实现自动隐藏和显示。
</li>
<li>
如果需要阻止 Dropdown 自动隐藏,可以使用 List.Item 或者 CascaderItem 的 onClick 回调函数,调用
event.stopPropagation() 来实现
</li>
<li>
在 Table 中使用 Dropdown,如果 该 Table 有 rowClick 监听事件,则可以使用 Dropdown 的 onContentClick 回调函数的
event.stopPropagation() 停止事件传递
</li>
<li>
如果触发元素为 Button 或者 IconButton,Dropdown 在展开的时候会添加一个 active 参数,如果不需要,可以在 Button
或者 IconButton 中设置 active={`{false}`} 来强制覆盖。
</li>
</ul>
<Heading>{formatMessage({ defaultMessage: '代码演示' })}</Heading>
<Subheading>{formatMessage({ defaultMessage: '样例' })}</Subheading>
Expand All @@ -24,6 +39,10 @@ export default function DropdownPage() {
<Canvas>
<Story id="upgraded-dropdown--default" />
</Canvas>
<Subheading>{formatMessage({ defaultMessage: '不可用的菜单项,和分割线' })}</Subheading>
<Canvas>
<Story id="upgraded-dropdown--disabled" />
</Canvas>

<Heading>{formatMessage({ defaultMessage: '参数说明' })}</Heading>
<ArgsTable of={Dropdown} />
Expand Down
4 changes: 3 additions & 1 deletion src/dropdown/interface.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { PopoverProps } from '../popover';

type DropdownProps = PopoverProps;
interface DropdownProps extends Omit<PopoverProps, 'children'> {
children: React.ReactElement;
}

export default DropdownProps;
14 changes: 8 additions & 6 deletions src/list/inner/baseItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,15 @@ const InnerBaseItem = WithRef<HTMLLIElement, BaseItemProps & Omit<DOMAttributes<
/** =================== events =================== */

const handleOnClick = (event: React.MouseEvent<HTMLLIElement>) => {
if (!mergedDisabled) {
/** cascader click 从上级来 */
if (model !== 'cascader') {
contextOnClick?.(value, event);
}
onClick?.(value, event);
if (mergedDisabled) {
event.stopPropagation();
return;
}
/** cascader click 从上级来 */
if (model !== 'cascader') {
contextOnClick?.(value, event);
}
onClick?.(value, event);
};
const content = children ?? label;

Expand Down

1 comment on commit 33d72d5

@vercel
Copy link

@vercel vercel bot commented on 33d72d5 Dec 20, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.