Skip to content

Commit

Permalink
feat: add dropdown mask(ZhongAnTech#986)
Browse files Browse the repository at this point in the history
  • Loading branch information
JaysonZou committed Jan 5, 2023
1 parent d02f896 commit 9c62812
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 78 deletions.
138 changes: 74 additions & 64 deletions packages/zarm/src/dropdown/Dropdown.tsx
@@ -1,10 +1,11 @@
import * as React from 'react';
import {createBEM} from "@zarm-design/bem";
import {cloneElement} from "react";
import {cloneElement, useEffect, useRef} from "react";
import { ConfigContext } from '../config-provider';
import type { HTMLProps } from '../utils/utilityTypes';
import type { BaseDropdownProps } from './interface';
import DropdownItem, {DropdownItemProps} from "./DropdownItem";
import DropdownItem, {DropdownItemProps, ItemChildrenWrap} from "./DropdownItem";
import {Popup} from "../index";

export type DropdownProps = React.PropsWithChildren<BaseDropdownProps & HTMLProps>;

Expand All @@ -14,81 +15,90 @@ interface CompoundedComponent
}

const Dropdown = React.forwardRef<HTMLDivElement, DropdownProps>((props, ref) => {
const containerRef = React.createRef<HTMLDivElement>();
const popupRef = React.useRef();

const { className, children, onChange, activeKey, defaultActiveKey, ...restProps } = props;
const { prefixCls } = React.useContext(ConfigContext);
const bem = createBEM('dropdown', { prefixCls });

const [selectedKey, setSelectedKey] = React.useState(defaultActiveKey);
const navRef = useRef<HTMLDivElement>(null)
const contentRef = useRef<HTMLDivElement>(null)

const onTriggerClick = (dropdownItem: React.ReactElement<DropdownItemProps, typeof DropdownItem>, index: number) => {
const { itemKey } = dropdownItem.props;
if (selectedKey === itemKey) {
setSelectedKey('');
} else {
setSelectedKey(dropdownItem.props.itemKey);
// 计算 navs 的 top 值
const [top, setTop] = React.useState<number>()
useEffect(() => {
const container = containerRef.current;
if (!container) return
if (selectedKey) {
const rect = container.getBoundingClientRect()
setTop(rect.bottom)
}
onChange?.(index);
};

// const getSelected = (index: number, itemKey: string | number) => {
// if (!activeKey) {
// if (!defaultActiveKey && index === 0) {
// return true;
// }
// return defaultActiveKey === itemKey;
// }
// return activeKey === itemKey;
// };

const arrowRender = (isSelected: boolean) => <div className={bem('arrow', [{
active: isSelected
}])} />;
}, [selectedKey])

const renderTrigger = (dropdownItem: React.ReactElement<DropdownItemProps, typeof DropdownItem>, index: number) => {
const isSelected = selectedKey === dropdownItem.props.itemKey;
return (
<li key={index} className={bem('trigger', [
{ active: isSelected }
])} onClick={() => onTriggerClick(dropdownItem, index)}>
{dropdownItem.props.title}
{arrowRender(isSelected)}
</li>
);
};

const triggersRender = React.Children.map(children, renderTrigger);
const changeActive = (key: string | null) => {
if (selectedKey === key) {
setSelectedKey(null)
} else {
setSelectedKey(key)
}
onChange?.(key)
}

const contentRender = React.Children.map(
children,
(element: React.ReactElement<DropdownItemProps, typeof DropdownItem>, index: number) => {
if (!React.isValidElement(element)) return null;
const itemKey = element.props.itemKey || index;
// let selected = getSelected(index, itemKey);
// if (!activeKey) {
// selected = selectedKey === itemKey;
// if (!selectedKey && index === 0) {
// selected = true;
// }
// }
const selected = selectedKey === itemKey;
return cloneElement(element, {
key: index,
title: element.props.title,
itemKey,
// style: element.props.style,
selected,
});
const items: React.ReactElement<DropdownItemProps, typeof DropdownItem>[] = []
const navs = React.Children.map(props.children, (child: React.ReactElement<DropdownItemProps, typeof DropdownItem>) => {
if (React.isValidElement(child)) {
const childProps = {
...child.props,
onClick: () => {
changeActive(child.props.itemKey as string)
},
active: child.props.itemKey === selectedKey,
arrow:
child.props.arrow === undefined ? props.arrow : child.props.arrow,
}
items.push(child)
return cloneElement(child, childProps)
}
);
return child
})

return (
<div ref={ref} className={bem([className])} {...restProps}>
<ul className={bem('trigger-list')}>
{triggersRender}
</ul>
{contentRender}
<div
className={bem([className])}
{...restProps}
ref={containerRef}
>
<div className={bem('trigger-list')} ref={navRef}>
{navs}
</div>
);
<Popup
visible={!!selectedKey}
onMaskClick={() => changeActive(null)}
style={{ top }}
maskStyle={{ top }}
direction="top"
className={bem('popup')}
forceRender
ref={popupRef}
>
<div ref={contentRef}>
{items.map(item => {
const isActive = item.props.itemKey === selectedKey
return (
<ItemChildrenWrap
key={item.props.itemKey}
active={isActive}
>
{item.props.children}
</ItemChildrenWrap>
)
})}
</div>
</Popup>
</div>
)
}) as CompoundedComponent;

Dropdown.displayName = 'Dropdown';
Expand Down
51 changes: 45 additions & 6 deletions packages/zarm/src/dropdown/DropdownItem.tsx
@@ -1,5 +1,7 @@
import * as React from 'react';
import { createBEM } from '@zarm-design/bem';
import {ArrowDown} from "@zarm-design/icons";
import classNames from "classnames";
import { ConfigContext } from '../config-provider';
import type { BaseDropdownItemProps } from './interface';

Expand All @@ -10,24 +12,61 @@ export interface DropdownItemProps extends BaseDropdownItemProps {
const DropdownItem = React.forwardRef<HTMLLIElement, DropdownItemProps>((props, ref) => {
const dropdownItemRef = (ref as any) || React.createRef<HTMLDivElement>();
const {
className,
title,
arrow,
children,
selected,
active,
onClick,
...restProps
} = props;
const { prefixCls } = React.useContext(ConfigContext);
const bem = createBEM('dropdown', { prefixCls });

const cls = bem('trigger', [
{
active,
},
]);

return (
<div className={cls} onClick={onClick} ref={dropdownItemRef}>
<div className={`${cls}-title`}>
<span className={`${cls}-title-text`}>{props.title}</span>
<span
className={classNames(`${cls}-title-arrow`, {
[`${cls}-title-arrow-active`]: props.active,
})}
>
{props.arrow === undefined ? <ArrowDown /> : props.arrow}
</span>
</div>
</div>
)
})

type DropdownItemChildrenWrapProps = {
onClick?: () => void
} & Pick<
DropdownItemProps,
'active' | 'children'
>

export const ItemChildrenWrap = React.forwardRef<HTMLLIElement, DropdownItemChildrenWrapProps>((props, ref) => {
const contentRef = (ref as any) || React.createRef<HTMLDivElement>();
const {
children,
active,
...restProps
} = props;
const { prefixCls } = React.useContext(ConfigContext);
const bem = createBEM('dropdown', { prefixCls });

const cls = bem('content', [
{
active: selected,
active,
},
]);

return (
<div ref={dropdownItemRef} className={cls} {...restProps}>
<div ref={contentRef} className={cls} {...restProps}>
{children}
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion packages/zarm/src/dropdown/demo.md
Expand Up @@ -4,7 +4,7 @@

```jsx
import { useState } from 'react';
import { Dropdown, List } from 'zarm';
import { Dropdown, List, Button } from 'zarm';

const Demo = () => {
const [activeKey, setActiveKey] = useState('home');
Expand Down
4 changes: 3 additions & 1 deletion packages/zarm/src/dropdown/interface.ts
Expand Up @@ -5,12 +5,14 @@ export interface BaseDropdownProps {
defaultActiveKey?: number | string;
onChange?: (activeKey: number | string) => void;
arrow?: React.ReactNode;
mask?: boolean;
}

export interface BaseDropdownItemProps {
itemKey?: string | number;
title?: React.ReactNode;
arrow?: React.ReactNode;
children?: React.ReactNode;
selected?: boolean;
active?: boolean;
onClick: any
}
18 changes: 12 additions & 6 deletions packages/zarm/src/dropdown/style/component.scss
Expand Up @@ -21,6 +21,7 @@
transition-duration: 0.2s;
overflow: hidden;
cursor: pointer;
position: relative;

@include e(trigger-list) {
width: 100%;
Expand Down Expand Up @@ -60,20 +61,25 @@
}
}

@include e(popup) {
//position: fixed;
//overflow: hidden;
//width: 100%;
//right: 0;
//bottom: 0;
//left: 0;
}

@include e(content) {
flex: 1;
display: none;
width: 100%;
min-height: var(--height);
transition-duration: 0.2s;
transition-property: height bottom;

@include m(active) {
border-top: 1px solid var(--za-border-color);
display: block;
}
}

@include e(dropdown-mask) {
width: 100%;
height: 400px;
}
}

0 comments on commit 9c62812

Please sign in to comment.