Skip to content

Commit 9613142

Browse files
committed
feat(ui): add dropdown component
1 parent 0875de2 commit 9613142

42 files changed

Lines changed: 1301 additions & 127 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/site/src/app/components/route/DemoBox.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,26 @@ import { useAsync, useImmer } from '@react-devui/ui/hooks';
66
import { copy, getClassName } from '@react-devui/ui/utils';
77

88
import './DemoBox.scss';
9+
import { toString } from './utils';
910

1011
export interface AppDemoBoxProps {
1112
id: string;
1213
renderer: React.ReactNode;
1314
title: string;
14-
description: string;
15-
tsx: string;
15+
description: number[];
16+
tsx: number[];
1617
scss?: string;
17-
tsxSource: string;
18+
tsxSource: number[];
1819
scssSource?: string;
1920
}
2021

2122
export function AppDemoBox(props: AppDemoBoxProps) {
2223
// eslint-disable-next-line @typescript-eslint/no-unused-vars
23-
const { id, renderer, title, description, tsx, scss, tsxSource, scssSource } = props;
24+
const { id, renderer, title, scss, scssSource } = props;
25+
26+
const description = toString(props.description);
27+
const tsx = toString(props.tsx);
28+
const tsxSource = toString(props.tsxSource);
2429

2530
const asyncCapture = useAsync();
2631

packages/site/src/app/components/route/RouteArticle.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,22 @@ import { useTranslation } from 'react-i18next';
33
import { DTooltip, DIcon, DAnchor, DAnchorLink } from '@react-devui/ui';
44

55
import './RouteArticle.scss';
6+
import { toString } from './utils';
67

78
export interface AppRouteArticleProps {
89
title: string;
910
subtitle: string;
10-
description: string;
11-
api: string;
11+
description: number[];
12+
api: number[];
1213
demos: React.ReactNode;
1314
links: Array<{ href: string; title: string }>;
1415
}
1516

1617
export function AppRouteArticle(props: AppRouteArticleProps) {
17-
const { title, subtitle, description, api, demos, links } = props;
18+
const { title, subtitle, demos, links } = props;
19+
20+
const description = toString(props.description);
21+
const api = toString(props.api);
1822

1923
const { t, i18n } = useTranslation();
2024

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function toString(arr: number[]) {
2+
return new TextDecoder().decode(Uint8Array.from(arr));
3+
}

packages/ui/src/components/_popup/Popup.tsx

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export interface DTriggerRenderProps {
1717
onFocus?: React.FocusEventHandler<HTMLElement>;
1818
onBlur?: React.FocusEventHandler<HTMLElement>;
1919
onClick?: React.MouseEventHandler<HTMLElement>;
20+
[key: `data-${string}popup-trigger`]: string;
2021
}
2122

2223
export interface DPopupRef {
@@ -40,8 +41,11 @@ export interface DPopupProps extends React.HTMLAttributes<HTMLDivElement> {
4041
dDestroy?: boolean;
4142
dMouseEnterDelay?: number;
4243
dMouseLeaveDelay?: number;
43-
dCustomPopup?: (popupEl: HTMLElement, triggerEl: HTMLElement) => { top: number; left: number; stateList: DTransitionStateList };
44-
onTrigger?: (visible: boolean) => void;
44+
dCustomPopup?: (
45+
popupEl: HTMLElement,
46+
triggerEl: HTMLElement
47+
) => { top: number; left: number; stateList: DTransitionStateList; arrowPosition?: React.CSSProperties };
48+
onVisibleChange?: (visible: boolean) => void;
4549
afterVisibleChange?: (visible: boolean) => void;
4650
}
4751

@@ -62,7 +66,7 @@ export const DPopup = React.forwardRef<DPopupRef, DPopupProps>((props, ref) => {
6266
dMouseEnterDelay = 150,
6367
dMouseLeaveDelay = 200,
6468
dCustomPopup,
65-
onTrigger,
69+
onVisibleChange,
6670
afterVisibleChange,
6771
className,
6872
children,
@@ -89,10 +93,11 @@ export const DPopup = React.forwardRef<DPopupRef, DPopupProps>((props, ref) => {
8993

9094
const asyncCapture = useAsync();
9195
const [popupPositionStyle, setPopupPositionStyle] = useImmer<React.CSSProperties>({});
96+
const [arrowPosition, setArrowStyle] = useImmer<React.CSSProperties | undefined>(undefined);
9297
const [zIndex, setZIndex] = useImmer(1000);
9398
const id = useId();
9499

95-
const [visible, changeVisible] = useTwoWayBinding(false, dVisible, onTrigger);
100+
const [visible, changeVisible] = useTwoWayBinding(false, dVisible, onVisibleChange);
96101

97102
const [autoPlacement, setAutoPlacement] = useImmer<DPlacement>(dPlacement);
98103

@@ -219,7 +224,8 @@ export const DPopup = React.forwardRef<DPopupRef, DPopupProps>((props, ref) => {
219224
'leave-to': { transform: 'scale(0)', opacity: '0', transition: 'transform 0.1s ease-in, opacity 0.1s ease-in', transformOrigin },
220225
};
221226
} else {
222-
const { top, left, stateList } = dCustomPopup(popupEl, triggerRef.current);
227+
const { top, left, stateList, arrowPosition } = dCustomPopup(popupEl, triggerRef.current);
228+
setArrowStyle(arrowPosition);
223229
setPopupPositionStyle({
224230
position: fixed ? 'fixed' : 'absolute',
225231
top,
@@ -237,6 +243,7 @@ export const DPopup = React.forwardRef<DPopupRef, DPopupProps>((props, ref) => {
237243
dDistance,
238244
dPlacement,
239245
popupEl,
246+
setArrowStyle,
240247
setAutoPlacement,
241248
setPopupPositionStyle,
242249
triggerRef,
@@ -433,7 +440,7 @@ export const DPopup = React.forwardRef<DPopupRef, DPopupProps>((props, ref) => {
433440
);
434441

435442
const triggerRenderProps = useMemo<DTriggerRenderProps>(() => {
436-
const _triggerRenderProps: DTriggerRenderProps = { [`data-${dPrefix}popup-trigger`]: id };
443+
const _triggerRenderProps: DTriggerRenderProps = { [`data-${dPrefix}popup-trigger`]: String(id) };
437444
if (dTrigger === 'hover') {
438445
_triggerRenderProps.onMouseEnter = () => {
439446
dataRef.current.clearTid && dataRef.current.clearTid();
@@ -506,7 +513,14 @@ export const DPopup = React.forwardRef<DPopupRef, DPopupProps>((props, ref) => {
506513
onBlur={handleBlur}
507514
onClick={handleClick}
508515
>
509-
{dArrow && <div className={`${dPrefix}popup__arrow`}></div>}
516+
{dArrow && (
517+
<div
518+
className={getClassName(`${dPrefix}popup__arrow`, {
519+
'is-custom': arrowPosition,
520+
})}
521+
style={arrowPosition}
522+
></div>
523+
)}
510524
{dPopupContent}
511525
</div>,
512526
containerRef.current

packages/ui/src/components/_transition/transition.tsx

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -165,14 +165,14 @@ export const DTransition = React.forwardRef<DTransitionRef, DTransitionProps>((p
165165
});
166166

167167
export interface DCollapseTransitionProps extends DTransitionProps {
168-
dDirection?: 'width' | 'height';
168+
dDirection?: 'horizontal' | 'vertical';
169169
dDuring?: number;
170170
dTimingFunction?: string | { enter: string; leave: string };
171171
dSpace?: number | string;
172172
}
173173

174174
export const DCollapseTransition = React.forwardRef<DTransitionRef, DCollapseTransitionProps>((props, ref) => {
175-
const { dEl, dCallbackList, dDirection = 'height', dTimingFunction, dDuring = 300, dSpace = 0, ...restProps } = props;
175+
const { dEl, dCallbackList, dDirection = 'vertical', dTimingFunction, dDuring = 300, dSpace = 0, ...restProps } = props;
176176

177177
const enterTimeFunction = dTimingFunction ? (isString(dTimingFunction) ? dTimingFunction : dTimingFunction.enter) : 'linear';
178178
const leaveTimeFunction = dTimingFunction ? (isString(dTimingFunction) ? dTimingFunction : dTimingFunction.leave) : 'linear';
@@ -182,6 +182,8 @@ export const DCollapseTransition = React.forwardRef<DTransitionRef, DCollapseTra
182182
const space = isNumber(dSpace) ? dSpace + 'px' : dSpace;
183183
const opacity = shouldHidden ? '0' : '1';
184184

185+
const attribute = dDirection === 'horizontal' ? 'width' : 'height';
186+
185187
return (
186188
<DTransition
187189
{...restProps}
@@ -191,30 +193,30 @@ export const DCollapseTransition = React.forwardRef<DTransitionRef, DCollapseTra
191193
if (dEl) {
192194
const rect = dEl.getBoundingClientRect();
193195
// handle nested
194-
if (rect[dDirection] === 0) {
196+
if (rect[attribute] === 0) {
195197
return undefined;
196198
}
197199

198-
const size = rect[dDirection] + 'px';
200+
const size = rect[attribute] + 'px';
199201

200202
return {
201-
'enter-from': { [dDirection]: space, opacity },
203+
'enter-from': { [attribute]: space, opacity },
202204
'enter-active': { overflow: 'hidden' },
203205
'enter-to': {
204-
[dDirection]: size,
205-
transition: `${dDirection} ${dDuring}ms ${enterTimeFunction}, opacity ${dDuring}ms ${enterTimeFunction}`,
206+
[attribute]: size,
207+
transition: `${attribute} ${dDuring}ms ${enterTimeFunction}, opacity ${dDuring}ms ${enterTimeFunction}`,
206208
},
207-
'leave-from': { [dDirection]: size },
209+
'leave-from': { [attribute]: size },
208210
'leave-active': { overflow: 'hidden' },
209211
'leave-to': {
210-
[dDirection]: space,
212+
[attribute]: space,
211213
opacity,
212-
transition: `${dDirection} ${dDuring}ms ${leaveTimeFunction}, opacity ${dDuring}ms ${leaveTimeFunction}`,
214+
transition: `${attribute} ${dDuring}ms ${leaveTimeFunction}, opacity ${dDuring}ms ${leaveTimeFunction}`,
213215
},
214216
};
215217
}
216218
}}
217-
dEndStyle={shouldHidden ? undefined : { leave: { [dDirection]: space } }}
219+
dEndStyle={shouldHidden ? undefined : { leave: { [attribute]: space } }}
218220
/>
219221
);
220222
});

packages/ui/src/components/button/Button.tsx

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export interface DButtonProps extends React.ButtonHTMLAttributes<HTMLButtonEleme
1717
dShape?: 'circle' | 'round';
1818
dSize?: 'smaller' | 'larger';
1919
dIcon?: React.ReactNode;
20-
dIconLeft?: boolean;
20+
dIconRight?: boolean;
2121
}
2222

2323
export const DButton = React.forwardRef<DButtonRef, DButtonProps>((props, ref) => {
@@ -29,7 +29,7 @@ export const DButton = React.forwardRef<DButtonRef, DButtonProps>((props, ref) =
2929
dShape,
3030
dSize,
3131
dIcon,
32-
dIconLeft = true,
32+
dIconRight = false,
3333
className,
3434
disabled,
3535
children,
@@ -39,7 +39,7 @@ export const DButton = React.forwardRef<DButtonRef, DButtonProps>((props, ref) =
3939

4040
//#region Context
4141
const dPrefix = useDPrefixConfig();
42-
const [{ buttonGroupType, buttonGroupColor, buttonGroupSize }] = useCustomContext(DButtonGroupContext);
42+
const [{ buttonGroupType, buttonGroupColor, buttonGroupSize, buttonGroupDisabled }] = useCustomContext(DButtonGroupContext);
4343
//#endregion
4444

4545
//#region Ref
@@ -54,9 +54,9 @@ export const DButton = React.forwardRef<DButtonRef, DButtonProps>((props, ref) =
5454

5555
const handleClick = useCallback(
5656
(e) => {
57-
if (!dLoading) {
58-
onClick?.(e);
57+
onClick?.(e);
5958

59+
if (!dLoading) {
6060
if (type === 'primary' || type === 'secondary' || type === 'outline' || type === 'dashed') {
6161
wave.next([e.currentTarget, `var(--${dPrefix}color-${color})`]);
6262
}
@@ -75,7 +75,7 @@ export const DButton = React.forwardRef<DButtonRef, DButtonProps>((props, ref) =
7575
<DCollapseTransition
7676
dEl={loadingEl}
7777
dVisible={dLoading}
78-
dDirection="width"
78+
dDirection="horizontal"
7979
dRender={(hidden) => (
8080
<button
8181
{...restProps}
@@ -87,21 +87,21 @@ export const DButton = React.forwardRef<DButtonRef, DButtonProps>((props, ref) =
8787
'is-only-icon': !children,
8888
'is-loading': dLoading,
8989
})}
90-
disabled={disabled}
91-
aria-disabled={disabled}
90+
disabled={buttonGroupDisabled || disabled}
91+
aria-disabled={buttonGroupDisabled || disabled}
9292
onClick={handleClick}
9393
>
94-
{!dIconLeft && children}
94+
{dIconRight && children}
9595
{dIcon ? (
96-
<span className={getClassName(`${dPrefix}button__icon`, { 'is-right': !dIconLeft })}>{dLoading ? loadingIcon : dIcon}</span>
96+
<span className={getClassName(`${dPrefix}button__icon`, { 'is-right': dIconRight })}>{dLoading ? loadingIcon : dIcon}</span>
9797
) : (
9898
!hidden && (
99-
<span ref={loadingRef} className={getClassName(`${dPrefix}button__icon`, { 'is-right': !dIconLeft })}>
99+
<span ref={loadingRef} className={getClassName(`${dPrefix}button__icon`, { 'is-right': dIconRight })}>
100100
{loadingIcon}
101101
</span>
102102
)
103103
)}
104-
{dIconLeft && children}
104+
{!dIconRight && children}
105105
</button>
106106
)}
107107
/>

packages/ui/src/components/button/ButtonGroup.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,27 @@ export interface DButtonGroupContextData {
99
buttonGroupType: DButtonProps['dType'];
1010
buttonGroupColor: DButtonProps['dColor'];
1111
buttonGroupSize: DButtonProps['dSize'];
12+
buttonGroupDisabled: boolean;
1213
}
1314
export const DButtonGroupContext = React.createContext<DButtonGroupContextData | null>(null);
1415

1516
export interface DButtonGroupProps extends React.HTMLAttributes<HTMLDivElement> {
1617
dType?: DButtonProps['dType'];
1718
dColor?: DButtonProps['dColor'];
1819
dSize?: DButtonProps['dSize'];
20+
dDisabled?: boolean;
1921
}
2022

2123
export function DButtonGroup(props: DButtonGroupProps) {
22-
const { dType = 'secondary', dColor = 'primary', dSize, className, children, ...restProps } = useDComponentConfig('button-group', props);
24+
const {
25+
dType = 'secondary',
26+
dColor = 'primary',
27+
dDisabled = false,
28+
dSize,
29+
className,
30+
children,
31+
...restProps
32+
} = useDComponentConfig('button-group', props);
2333

2434
//#region Context
2535
const dPrefix = useDPrefixConfig();
@@ -30,8 +40,9 @@ export function DButtonGroup(props: DButtonGroupProps) {
3040
buttonGroupType: dType,
3141
buttonGroupColor: dColor,
3242
buttonGroupSize: dSize,
43+
buttonGroupDisabled: dDisabled,
3344
}),
34-
[dType, dColor, dSize]
45+
[dType, dColor, dSize, dDisabled]
3546
);
3647

3748
return (

packages/ui/src/components/button/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ Extend `React.ButtonHTMLAttributes<HTMLButtonElement>`.
2525
| dShape | Set the button shape | 'circle' \| 'round' | - |
2626
| dSize | Set button size | 'smaller' \| 'larger' | - |
2727
| dIcon | Set the icon of the button | React.ReactNode | - |
28-
| dIconLeft | Settings icon on the left | boolean | true |
28+
| dIconRight | Settings icon on the right | boolean | false |
2929
<!-- prettier-ignore-end -->
3030

3131
### DButtonRef
@@ -44,4 +44,5 @@ Extend `React.HTMLAttributes<HTMLDivElement>`.
4444
| dType | Set the shape of the buttons in the button group | Reference DButtonProps['dType'] | 'secondary' |
4545
| dColor | Set the color of the buttons in the button group | Reference DButtonProps['dColor'] | 'primary' |
4646
| dSize | Set the size of the buttons in the button group | Reference DButtonProps['dSize'] | - |
47+
| buttonGroupDisabled | Disable the buttons in the button group | boolean | false |
4748
<!-- prettier-ignore-end -->

packages/ui/src/components/button/README.zh-Hant.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ title: 按钮
2424
| dShape | 设置按钮形状 | 'circle' \| 'round' | - |
2525
| dSize | 设置按钮大小 | 'smaller' \| 'larger' | - |
2626
| dIcon | 设置按钮的图标 | React.ReactNode | - |
27-
| dIconLeft | 设置图标在左侧 | boolean | true |
27+
| dIconRight | 设置图标在右侧 | boolean | false |
2828
<!-- prettier-ignore-end -->
2929

3030
### DButtonRef
@@ -43,4 +43,5 @@ export type DButtonRef = HTMLButtonElement;
4343
| dType | 设置按钮组中按钮的形态 | 参考 DButtonProps['dType'] | 'secondary' |
4444
| dColor | 设置按钮组中按钮的颜色 | 参考 DButtonProps['dColor'] | 'primary' |
4545
| dSize | 设置按钮组中按钮的大小 | 参考 DButtonProps['dSize'] | - |
46+
| buttonGroupDisabled | 禁用按钮组中的按钮 | boolean | false |
4647
<!-- prettier-ignore-end -->

packages/ui/src/components/button/demos/3.Icon.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export default function Demo() {
3333
<br />
3434
<br />
3535
<DButton dIcon={searchIcon}>Search</DButton>
36-
<DButton dIcon={searchIcon} dIconLeft={false}>
36+
<DButton dIcon={searchIcon} dIconRight>
3737
Search
3838
</DButton>
3939
<DButton dType="secondary" dIcon={searchIcon}>

0 commit comments

Comments
 (0)