refactor: refactor Dropdown component (#4358)

This commit is contained in:
白熱
2024-12-24 18:40:25 +08:00
committed by GitHub
parent 862e70bc56
commit 0f3082bacf
34 changed files with 542 additions and 134 deletions
+2
View File
@@ -28,11 +28,13 @@
"@vitejs/plugin-react": "^4.3.4",
"@vitejs/plugin-vue": "^5.2.1",
"@vitest/coverage-istanbul": "^2.1.8",
"autoprefixer": "^10.4.20",
"eslint-plugin-readable-tailwind": "^1.8.2",
"fs-extra": "^11.2.0",
"happy-dom": "15.0.0",
"javascript-obfuscator": "^4.1.1",
"sort-keys": "^5.1.0",
"tailwind-scrollbar": "^3.1.0",
"tailwindcss": "^3.4.16",
"tailwindcss-animate": "^1.0.7",
"vite": "^6.0.3",
+1
View File
@@ -2,6 +2,7 @@
const config = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
@@ -15,6 +15,7 @@
*/
import type { Config } from 'tailwindcss';
import scrollbar from 'tailwind-scrollbar';
import animate from 'tailwindcss-animate';
const config: Omit<Config, 'content'> = {
@@ -163,6 +164,7 @@ const config: Omit<Config, 'content'> = {
},
plugins: [
animate,
scrollbar,
],
};
@@ -16,7 +16,7 @@
import type { ITextWatermarkConfig } from '@univerjs/watermark';
import { LocaleService, useDependency } from '@univerjs/core';
import { Checkbox, ColorPicker, Dropdown, Input, InputNumber, Select } from '@univerjs/design';
import { Checkbox, ColorPicker, DropdownLegacy, Input, InputNumber, Select } from '@univerjs/design';
import { BoldSingle, FontColor, ItalicSingle } from '@univerjs/icons';
import clsx from 'clsx';
import React from 'react';
@@ -97,7 +97,7 @@ export const WatermarkTextSetting: React.FC<IWatermarkTextSettingProps> = (props
<div className={styles.watermarkTextSettingFontStylePart}>
<div className={styles.watermarkIconWrapper}>
<Dropdown
<DropdownLegacy
overlay={(
<div className={styles.watermarkColorPickerWrapper}>
<ColorPicker color={config.color} onChange={(val) => onChange({ ...config, color: val })} />
@@ -105,7 +105,7 @@ export const WatermarkTextSetting: React.FC<IWatermarkTextSettingProps> = (props
)}
>
<FontColor className={styles.watermarkIcon} extend={{ colorChannel1: config.color ?? 'rgb(var(--primary-color))' }} />
</Dropdown>
</DropdownLegacy>
</div>
<div className={clsx(styles.watermarkIconWrapper, { [styles.watermarkIconWrapperSelect]: config.bold })} onClick={() => { onChange({ ...config, bold: !config.bold }); }}>
<BoldSingle className={styles.watermarkIcon} />
@@ -14,18 +14,18 @@
* limitations under the License.
*/
import type { IProjectNode } from '../../services/unit-grid/unit-grid.service';
import { useDependency } from '@univerjs/core';
import { DropdownLegacy, Tooltip } from '@univerjs/design';
import { CheckMarkSingle, FullscreenSingle, IncreaseSingle, ZoomReduceSingle } from '@univerjs/icons';
import { ISidebarService } from '@univerjs/ui';
import {
useReactFlow,
} from '@xyflow/react';
import React, { useCallback, useEffect, useLayoutEffect } from 'react';
import { CheckMarkSingle, FullscreenSingle, IncreaseSingle, ZoomReduceSingle } from '@univerjs/icons';
import { Dropdown, Tooltip } from '@univerjs/design';
import clsx from 'clsx';
import { useDependency } from '@univerjs/core';
import { ISidebarService } from '@univerjs/ui';
import { UniDiv } from '../uni-toolbar/UniFloatToolbar';
import type { IProjectNode } from '../../services/unit-grid/unit-grid.service';
import React, { useCallback, useEffect, useLayoutEffect } from 'react';
import { IUnitGridService } from '../../services/unit-grid/unit-grid.service';
import { UniDiv } from '../uni-toolbar/UniFloatToolbar';
import styles from './index.module.less';
export const UniControlButton = (props: { tooltips: string; children?: React.ReactElement; onClick: () => void; style?: React.CSSProperties }) => {
@@ -128,7 +128,7 @@ export const UniControls = ({ zoom, onItemClick }: { zoom: number; onItemClick?:
<UniControlButton tooltips="Zoom in" onClick={onZoomInHandler}>
<IncreaseSingle />
</UniControlButton>
<Dropdown
<DropdownLegacy
placement="topLeft"
overlay={(
<div className={styles.sliderShortcuts}>
@@ -158,7 +158,7 @@ export const UniControls = ({ zoom, onItemClick }: { zoom: number; onItemClick?:
{zoomPercent}
%
</a>
</Dropdown>
</DropdownLegacy>
<UniControlButton tooltips="Zoom out" onClick={onZoomOutHandler}>
<ZoomReduceSingle />
</UniControlButton>
@@ -0,0 +1,42 @@
/**
* Copyright 2023-present DreamNum Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { Meta } from '@storybook/react';
import React from 'react';
import { Button } from '../button/Button';
import { DropdownLegacy } from './DropdownLegacy';
const meta: Meta<typeof DropdownLegacy> = {
title: 'Components / Dropdown Legacy',
component: DropdownLegacy,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
};
export default meta;
export const Playground = {
render() {
return (
<DropdownLegacy overlay={<div>overlay</div>}>
<Button>Basic</Button>
</DropdownLegacy>
);
},
};
@@ -15,14 +15,14 @@
*/
import type { ActionType, AlignType } from '@rc-component/trigger';
import RcDropdown from 'rc-dropdown';
import type Placements from 'rc-dropdown/lib/placements';
import RcDropdown from 'rc-dropdown';
import React, { useContext } from 'react';
import { ConfigContext } from '../config-provider/ConfigProvider';
import styles from './index.module.less';
export interface IDropdownProps {
export interface IDropdownLegacyProps {
/** Semantic DOM class */
className?: string;
@@ -79,7 +79,7 @@ export interface IDropdownProps {
disabled?: boolean;
}
export function Dropdown(props: IDropdownProps) {
export function DropdownLegacy(props: IDropdownLegacyProps) {
const {
className,
placement,
@@ -0,0 +1,17 @@
/**
* Copyright 2023-present DreamNum Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { DropdownLegacy, type IDropdownLegacyProps } from './DropdownLegacy';
@@ -17,12 +17,13 @@
import type { Meta } from '@storybook/react';
import React from 'react';
import { Button } from '../button/Button';
import { Dropdown } from './Dropdown';
import { DropdownOverlay } from './DropdownOverlay';
import { DropdownProvider } from './DropdownProvider';
import { DropdownTrigger } from './DropdownTrigger';
const meta: Meta<typeof Dropdown> = {
const meta: Meta<typeof DropdownProvider> = {
title: 'Components / Dropdown',
component: Dropdown,
component: DropdownProvider,
parameters: {
layout: 'centered',
},
@@ -34,9 +35,20 @@ export default meta;
export const Playground = {
render() {
return (
<Dropdown overlay={<div>overlay</div>}>
<Button>Basic</Button>
</Dropdown>
<div className="univer-relative">
<DropdownProvider>
<DropdownTrigger>
<a
className="univer-cursor-pointer univer-border univer-rounded-lg univer-border-gray-200 univer-px-4 univer-py-2 univer-border-solid hover:univer-bg-gray-100 univer-transition-all"
>
Click me
</a>
</DropdownTrigger>
<DropdownOverlay>
<div className="univer-text-blue-500">Hello Univer</div>
</DropdownOverlay>
</DropdownProvider>
</div>
);
},
};
@@ -0,0 +1,35 @@
/**
* Copyright 2023-present DreamNum Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { RefObject } from 'react';
import { createContext, useContext } from 'react';
interface IDropdownContextType {
isOpen: boolean;
setIsOpen: (value: boolean) => void;
overlayRef: RefObject<HTMLDivElement>;
triggerRef: RefObject<HTMLElement>;
}
export const DropdownContext = createContext<IDropdownContextType | null>(null);
export const useDropdown = () => {
const context = useContext(DropdownContext);
if (!context) {
throw new Error('useDropdown must be used within a DropdownProvider');
}
return context;
};
@@ -0,0 +1,90 @@
/**
* Copyright 2023-present DreamNum Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import canUseDom from 'rc-util/lib/Dom/canUseDom';
import React, { useEffect, useState } from 'react';
import { createPortal } from 'react-dom';
import clsx from '../../helper/clsx';
import { useDropdown } from './DropdownContext';
interface IDropdownOverlayProps {
children: React.ReactNode;
className?: string;
offset?: {
x?: number;
y?: number;
};
}
export function DropdownOverlay({ children, className, offset }: IDropdownOverlayProps) {
if (!canUseDom) {
return null;
}
const { isOpen, setIsOpen, overlayRef, triggerRef } = useDropdown();
const [position, setPosition] = useState({ top: 0, left: 0 });
useEffect(() => {
if (isOpen && triggerRef.current && overlayRef.current) {
const triggerRect = triggerRef.current.getBoundingClientRect();
const overlayRect = overlayRef.current.getBoundingClientRect();
const viewportWidth = document.documentElement.clientWidth;
const viewportHeight = document.documentElement.clientHeight;
let top = triggerRect.bottom + window.scrollY;
let left = triggerRect.left + window.scrollX;
if (top + overlayRect.height > viewportHeight + window.scrollY) {
top = triggerRect.top - overlayRect.height + window.scrollY;
}
if (left + overlayRect.width > viewportWidth + window.scrollX) {
left = triggerRect.right - overlayRect.width + window.scrollX;
}
top = Math.max(top, window.scrollY);
left = Math.max(left, window.scrollX);
setPosition({
top: top + (offset?.y ?? 0),
left: left + (offset?.x ?? 0),
});
}
}, [isOpen, offset?.x, offset?.y, overlayRef, triggerRef]);
if (!isOpen) return null;
return createPortal(
<div
ref={overlayRef}
className={clsx(
`
univer-fixed univer-z-50 univer-overflow-hidden univer-rounded-md univer-border univer-bg-white
univer-shadow-md univer-animate-in univer-fade-in-0 univer-zoom-in-95
`,
className
)}
style={{
top: position.top,
left: position.left,
}}
onClick={() => setIsOpen(false)}
>
{children}
</div>,
document.body
);
}
@@ -0,0 +1,49 @@
/**
* Copyright 2023-present DreamNum Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { ReactNode } from 'react';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { DropdownContext } from './DropdownContext';
interface IDropdownProviderProps {
children: ReactNode;
}
export function DropdownProvider({ children }: IDropdownProviderProps) {
const [isOpen, setIsOpen] = useState(false);
const triggerRef = useRef<HTMLDivElement>(null);
const overlayRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
const target = event.target as Node;
if (!triggerRef.current?.contains(target) && !overlayRef.current?.contains(target)) {
setIsOpen(false);
}
};
window.addEventListener('mousedown', handleClickOutside, true);
return () => window.removeEventListener('mousedown', handleClickOutside, true);
}, []);
const contextValue = useMemo(() => ({ isOpen, setIsOpen, triggerRef, overlayRef }), [isOpen]);
return (
<DropdownContext.Provider value={contextValue}>
{children}
</DropdownContext.Provider>
);
}
@@ -0,0 +1,43 @@
/**
* Copyright 2023-present DreamNum Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { ReactElement } from 'react';
import { cloneElement, isValidElement } from 'react';
import { useDropdown } from './DropdownContext';
interface IDropdownTriggerProps {
children: ReactElement;
}
export function DropdownTrigger({ children }: IDropdownTriggerProps) {
const { setIsOpen, isOpen, triggerRef } = useDropdown();
if (!isValidElement(children)) {
throw new Error('DropdownTrigger children must be a valid React element');
}
// eslint-disable-next-line react/no-clone-element
return cloneElement(children, {
ref: triggerRef,
onClick: (e: React.MouseEvent) => {
e.stopPropagation();
setIsOpen(!isOpen);
// eslint-disable-next-line ts/no-explicit-any
(children.props as React.HTMLProps<HTMLElement>)?.onClick?.(e as any);
},
// eslint-disable-next-line ts/no-explicit-any
} as any);
}
@@ -14,4 +14,6 @@
* limitations under the License.
*/
export { Dropdown, type IDropdownProps } from './Dropdown';
export { DropdownOverlay } from './DropdownOverlay';
export { DropdownProvider } from './DropdownProvider';
export { DropdownTrigger } from './DropdownTrigger';
@@ -20,7 +20,7 @@ import React, { useContext, useMemo, useRef } from 'react';
import { Button } from '../button/Button';
import { ConfigContext } from '../config-provider/ConfigProvider';
import { Dropdown } from '../dropdown/Dropdown';
import { DropdownLegacy } from '../dropdown-legacy/DropdownLegacy';
import { Tooltip } from '../tooltip/Tooltip';
import styles from './index.module.less';
@@ -176,7 +176,7 @@ export function Slider(props: ISliderProps) {
<IncreaseSingle />
</Button>
<Dropdown
<DropdownLegacy
placement="topLeft"
overlay={(
<div className={styles.sliderShortcuts}>
@@ -207,7 +207,7 @@ export function Slider(props: ISliderProps) {
{value}
%
</a>
</Dropdown>
</DropdownLegacy>
</div>
);
}
+2 -1
View File
@@ -26,7 +26,8 @@ export { ConfigContext, ConfigProvider, type IConfigProviderProps } from './comp
export { Confirm, type IConfirmProps } from './components/confirm';
export { DatePanel, DatePicker, type IDatePanelProps, type IDatePickerProps } from './components/date-picker';
export { Dialog, type IDialogProps } from './components/dialog';
export { Dropdown, type IDropdownProps } from './components/dropdown';
export { DropdownLegacy, type IDropdownLegacyProps } from './components/dropdown-legacy';
export { DropdownOverlay, DropdownProvider, DropdownTrigger } from './components/dropdown';
export { FormDualColumnLayout, FormLayout, type IFormDualColumnLayoutProps, type IFormLayoutProps } from './components/form-layout';
export { type IInputProps, type IInputWithSlotProps, Input, InputWithSlot } from './components/input';
export { type IInputNumberProps, InputNumber } from './components/input-number';
@@ -16,7 +16,7 @@
import type { IDrawingSearch } from '@univerjs/core';
import { ICommandService, LocaleService, useDependency } from '@univerjs/core';
import { Dropdown } from '@univerjs/design';
import { DropdownLegacy } from '@univerjs/design';
import { Autofill, MoreDownSingle } from '@univerjs/icons';
import clsx from 'clsx';
import React, { useState } from 'react';
@@ -79,7 +79,7 @@ export const ImagePopupMenu: React.FC<IImagePopupMenuProps> = (props: IImagePopu
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<Dropdown
<DropdownLegacy
placement="bottomLeft"
trigger={['click']}
overlay={(
@@ -109,7 +109,7 @@ export const ImagePopupMenu: React.FC<IImagePopupMenuProps> = (props: IImagePopu
/>
{showMore && <MoreDownSingle style={{ color: '#CCCCCC', fontSize: '8px', marginLeft: '8px' }} />}
</div>
</Dropdown>
</DropdownLegacy>
</div>
);
};
@@ -15,7 +15,7 @@
*/
import { ColorKit, useDependency } from '@univerjs/core';
import { Dropdown, ColorPicker as OriginColorPicker } from '@univerjs/design';
import { DropdownLegacy, ColorPicker as OriginColorPicker } from '@univerjs/design';
import { MoreDownSingle } from '@univerjs/icons';
import { ComponentManager } from '@univerjs/ui';
import React, { useMemo } from 'react';
@@ -40,7 +40,7 @@ export const ColorPicker = (props: IColorPickerProps) => {
return Icon && (!disable
? (
<Dropdown
<DropdownLegacy
overlay={(
<div className={`
${styles.cfColorPicker}
@@ -59,7 +59,7 @@ export const ColorPicker = (props: IColorPickerProps) => {
{isNeedDropdownIcon && <MoreDownSingle className={styles.iconDropdown} />}
</span>
</Dropdown>
</DropdownLegacy>
)
: <Icon className={className} extend={{ colorChannel1: colorKit.isValid ? color : 'rgb(var(--primary-color))' }} />);
};
@@ -19,10 +19,21 @@ import type { IIconSet, IIconType } from '@univerjs/sheets-conditional-formattin
import type { IStyleEditorProps } from './type';
import { get, IUniverInstanceService, LocaleService, set, Tools, UniverInstanceType, useDependency } from '@univerjs/core';
import { Checkbox, Dropdown, InputNumber, Select } from '@univerjs/design';
import { Checkbox, DropdownLegacy, InputNumber, Select } from '@univerjs/design';
import { MoreDownSingle, SlashSingle } from '@univerjs/icons';
import { CFNumberOperator, CFRuleType, CFSubRuleType, CFValueType, compareWithNumber, createDefaultValue, EMPTY_ICON_TYPE, getOppositeOperator, iconGroup, iconMap } from '@univerjs/sheets-conditional-formatting';
import {
CFNumberOperator,
CFRuleType,
CFSubRuleType,
CFValueType,
compareWithNumber,
createDefaultValue,
EMPTY_ICON_TYPE,
getOppositeOperator,
iconGroup,
iconMap,
} from '@univerjs/sheets-conditional-formatting';
import { FormulaEditor } from '@univerjs/sheets-formula-ui';
import { ILayoutService, useScrollYOverContainer, useSidebarClick } from '@univerjs/ui';
@@ -270,12 +281,12 @@ const IconSetRuleEdit = (props: {
${styles.width45}
`}
>
<Dropdown overlay={<IconItemList onClick={handleIconClick} iconId={item.iconId} iconType={item.iconType} />}>
<DropdownLegacy overlay={<IconItemList onClick={handleIconClick} iconId={item.iconId} iconType={item.iconType} />}>
<div className={styles.dropdownIcon}>
{icon ? <img src={icon} className={styles.icon} /> : <SlashSingle className={styles.icon} />}
<MoreDownSingle />
</div>
</Dropdown>
</DropdownLegacy>
</div>
{!isEnd
@@ -353,7 +364,13 @@ const IconSetRuleEdit = (props: {
${styles.width45}
`}
>
<TextInput id={index} type={item.value.type} error={error} value={item.value.value || ''} onChange={(v) => handleValueValueChange(v, index)} />
<TextInput
id={index}
type={item.value.type}
error={error}
value={item.value.value || ''}
onChange={(v) => handleValueValueChange(v, index)}
/>
</div>
</div>
@@ -417,7 +434,15 @@ export const IconSet = (props: IStyleEditorProps<unknown, IIconSet>) => {
});
return (
<div className={styles.iconWrap}>
{list.map((icon, index) => (icon ? <img className={styles.icon} key={index} src={icon} /> : <SlashSingle className={styles.icon} key={index} />))}
{list.map((icon, index) => (icon
? (
<img
className={styles.icon}
key={index}
src={icon}
/>
)
: <SlashSingle className={styles.icon} key={index} />))}
</div>
);
}, [configList]);
@@ -525,13 +550,22 @@ export const IconSet = (props: IStyleEditorProps<unknown, IIconSet>) => {
${stylesBase.mTSm}
`}
>
<Dropdown placement="bottomLeft" overlay={<IconGroupList ref={(el) => !iconGroupListEl && el && setIconGroupListEl(el)} iconType={currentIconType} onClick={handleClickIconList} />}>
<DropdownLegacy
placement="bottomLeft"
overlay={(
<IconGroupList
ref={(el) => !iconGroupListEl && el && setIconGroupListEl(el)}
iconType={currentIconType}
onClick={handleClickIconList}
/>
)}
>
<div className={styles.dropdownIcon} style={{ width: 'unset' }}>
{previewIcon}
<MoreDownSingle />
</div>
{/* <div>{previewIcon}</div> */}
</Dropdown>
</DropdownLegacy>
</div>
<div className={`
${stylesBase.mTSm}
@@ -16,7 +16,7 @@
import type { IRange, Nullable } from '@univerjs/core';
import { LocaleService, LocaleType, throttle, useDependency } from '@univerjs/core';
import { Button, Checkbox, DraggableList, Dropdown, Radio, RadioGroup } from '@univerjs/design';
import { Button, Checkbox, DraggableList, DropdownLegacy, Radio, RadioGroup } from '@univerjs/design';
import { CheckMarkSingle, DeleteEmptySingle, IncreaseSingle, MoreDownSingle, SequenceSingle } from '@univerjs/icons';
import { type IOrderRule, SheetsSortService, SortType } from '@univerjs/sheets-sort';
import React, { useCallback, useEffect, useRef, useState } from 'react';
@@ -203,7 +203,7 @@ export function SortOptionItem(props: ISortOptionItemProps) {
<SequenceSingle />
</div>
<div className={styles.customSortPanelItemColumn}>
<Dropdown
<DropdownLegacy
placement="bottomLeft"
trigger={['click']}
visible={visible}
@@ -233,7 +233,7 @@ export function SortOptionItem(props: ISortOptionItemProps) {
<span className={styles.customSortPanelItemColumnInputText}>{itemLabel}</span>
<MoreDownSingle className={styles.customSortPanelItemColumnInputDropdown} />
</div>
</Dropdown>
</DropdownLegacy>
</div>
</div>
<div className={styles.customSortPanelItemOrder}>
@@ -15,7 +15,7 @@
*/
import { BorderStyleTypes, useDependency } from '@univerjs/core';
import { ColorPicker, Dropdown, Menu, MenuItem } from '@univerjs/design';
import { ColorPicker, DropdownLegacy, Menu, MenuItem } from '@univerjs/design';
import { MoreDownSingle, PaintBucket } from '@univerjs/icons';
import { BorderStyleManagerService, type IBorderInfo } from '@univerjs/sheets';
import { ComponentManager } from '@univerjs/ui';
@@ -119,7 +119,7 @@ export function BorderPanel(props: IBorderPanelProps) {
<div className={styles.uiPluginSheetsBorderPanelStyles}>
<div>
<Dropdown
<DropdownLegacy
align={{
offset: [0, 18],
}}
@@ -135,11 +135,11 @@ export function BorderPanel(props: IBorderPanelProps) {
<MoreDownSingle />
</span>
</a>
</Dropdown>
</DropdownLegacy>
</div>
<div>
<Dropdown
<DropdownLegacy
align={{
offset: [0, 18],
}}
@@ -165,7 +165,7 @@ export function BorderPanel(props: IBorderPanelProps) {
<MoreDownSingle />
</span>
</a>
</Dropdown>
</DropdownLegacy>
</div>
</div>
</section>
@@ -16,16 +16,16 @@
import { useDependency } from '@univerjs/core';
import { Dropdown, Input } from '@univerjs/design';
import { DropdownOverlay, DropdownProvider, DropdownTrigger, Input } from '@univerjs/design';
import { IDefinedNamesService } from '@univerjs/engine-formula';
import { MoreDownSingle } from '@univerjs/icons';
import clsx from 'clsx';
import React, { useEffect } from 'react';
import React, { useEffect, useState } from 'react';
import { DefinedNameOverlay } from './DefinedNameOverlay';
import styles from './index.module.less';
export function DefinedName({ disable }: { disable: boolean }) {
const [rangeString, setRangeString] = React.useState('');
const [rangeString, setRangeString] = useState('');
const definedNamesService = useDependency(IDefinedNamesService);
useEffect(() => {
@@ -41,13 +41,34 @@ export function DefinedName({ disable }: { disable: boolean }) {
return (
<div className={styles.definedName}>
<Input className={clsx({ [styles.defineNameInputDisable]: disable })} value={rangeString} type="text" size="small" affixWrapperStyle={{ border: 'none', paddingLeft: '6px', paddingRight: '6px', height: '100%' }} />
<Input
className={clsx({ [styles.defineNameInputDisable]: disable })}
value={rangeString}
type="text"
size="small"
affixWrapperStyle={{ border: 'none', paddingLeft: '6px', paddingRight: '6px', height: '100%' }}
/>
<Dropdown overlay={<DefinedNameOverlay />}>
<div className={clsx(styles.definedNameDropDown, { [styles.definedNameDropDownDisable]: disable })}>
<MoreDownSingle />
</div>
</Dropdown>
<DropdownProvider>
<DropdownTrigger>
<a
className={clsx(`
univer-flex univer-items-center univer-justify-center univer-px-1 univer-cursor-pointer
univer-absolute univer-right-0 univer-h-full univer-transition-colors univer-duration-200
hover:univer-bg-gray-100
`,
{
'univer-text-gray-300 univer-cursor-not-allowed hover:univer-bg-transparent': disable,
})}
>
<MoreDownSingle />
</a>
</DropdownTrigger>
<DropdownOverlay className="univer-z-[1001]" offset={{ x: -75, y: 2 }}>
<DefinedNameOverlay />
</DropdownOverlay>
</DropdownProvider>
</div>
);
}
@@ -24,7 +24,6 @@ import { ISidebarService } from '@univerjs/ui';
import React, { useEffect, useState } from 'react';
import { SidebarDefinedNameOperation } from '../../commands/operations/sidebar-defined-name.operation';
import { DEFINED_NAME_CONTAINER } from './component-name';
import styles from './index.module.less';
export interface IDefinedNameOverlayProps {
@@ -97,20 +96,66 @@ export function DefinedNameOverlay(props: IDefinedNameOverlayProps) {
};
return (
<div className={styles.definedNameOverlay}>
<div className={styles.definedNameOverlayContainer}>
<div className="univer-w-[300px] univer-bg-white">
<ul
className={`
univer-max-h-[360px] univer-overflow-y-auto univer-scrollbar-thin univer-scrollbar-thumb-gray-300
univer-scrollbar-track-transparent univer-scrollbar-w-[4px] univer-m-0 univer-list-none univer-p-0
`}
>
{definedNames.map((definedName, index) => {
return (
<div key={index} className={styles.definedNameOverlayItem} onClick={() => { focusDefinedName(definedName); }}>
<div className={styles.definedNameOverlayItemName} title={definedName.name}>{definedName.name}</div>
<div className={styles.definedNameOverlayItemFormula} title={definedName.formulaOrRefString}>{definedName.formulaOrRefString}</div>
</div>
<li
key={index}
className={`
univer-px-2 univer-cursor-pointer univer-transition-colors univer-duration-200
hover:univer-bg-gray-100
`}
onClick={() => { focusDefinedName(definedName); }}
>
<div
className={`
univer-flex univer-py-1 univer-items-center univer-justify-between univer-border-b
univer-border-solid univer-border-0 univer-border-gray-200 univer-gap-2
`}
>
<div
className={`
univer-text-gray-600 univer-text-sm univer-overflow-hidden univer-text-ellipsis
univer-flex-shrink-0 univer-w-[50%] univer-whitespace-nowrap
`}
title={definedName.name}
>
{definedName.name}
</div>
<div
className={`
univer-text-gray-400 univer-text-xs univer-flex-shrink-0 univer-overflow-hidden
univer-text-ellipsis univer-w-[50%] univer-whitespace-nowrap
`}
title={definedName.formulaOrRefString}
>
{definedName.formulaOrRefString}
</div>
</div>
</li>
);
})}
</div>
<div className={styles.definedNameOverlayManager} onClick={openSlider}>
<div className={styles.definedNameOverlayManagerTitle}>{localeService.t('definedName.managerTitle')}</div>
<div className={styles.definedNameOverlayManagerContent}>{localeService.t('definedName.managerDescription')}</div>
</ul>
<div
className={`
univer-p-2 univer-cursor-pointer univer-transition-colors univer-duration-200
hover:univer-bg-gray-100
`}
onClick={openSlider}
>
<div className="univer-text-sm univer-font-semibold univer-text-gray-600 univer-mb-2">
{localeService.t('definedName.managerTitle')}
</div>
<div className="univer-text-xs univer-text-gray-400">
{localeService.t('definedName.managerDescription')}
</div>
</div>
</div>
);
@@ -13,42 +13,10 @@
box-sizing: border-box;
&-drop-down {
display: flex;
align-items: center;
justify-content: center;
padding: 0 var(--padding-xs);
cursor: pointer;
position: absolute;
right: 0;
height: 100%;
&:hover {
background-color: rgb(var(--grey-50));
color: rgb(var(--primary-color));
}
&-disable {
color: rgb(var(--grey-200));
cursor: not-allowed;
&:hover {
color: rgb(var(--grey-200));
}
}
}
&-overlay {
position: relative;
border: 1px solid rgb(var(--border-color));
width: 300px;
background-color: rgb(var(--color-white));
transform: translateX(-75px);
&-container {
position: relative;
@@ -103,7 +71,7 @@
}
&-manager {
position: absolute;
// position: absolute;
display: flex;
flex-direction: column;
overflow-y: auto;
@@ -16,7 +16,7 @@
import type { ICommandInfo, IExecutionOptions } from '@univerjs/core';
import { ICommandService, IUniverInstanceService, LocaleService, toDisposable, useDependency } from '@univerjs/core';
import { Dropdown } from '@univerjs/design';
import { DropdownLegacy } from '@univerjs/design';
import { IRenderManagerService } from '@univerjs/engine-render';
import { Autofill, CheckMarkSingle, MoreDownSingle } from '@univerjs/icons';
import clsx from 'clsx';
@@ -24,12 +24,12 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { RefillCommand } from '../../commands/commands/refill.command';
import { SetScrollOperation } from '../../commands/operations/scroll.operation';
import { useActiveWorkbook } from '../../components/hook';
import { getSheetObject } from '../../controllers/utils/component-tools';
import { IAutoFillService } from '../../services/auto-fill/auto-fill.service';
import { APPLY_TYPE } from '../../services/auto-fill/type';
import { SheetSkeletonManagerService } from '../../services/sheet-skeleton-manager.service';
import { useActiveWorkbook } from '../../components/hook';
import { ISheetSelectionRenderService } from '../../services/selection/base-selection-render.service';
import { SheetSkeletonManagerService } from '../../services/sheet-skeleton-manager.service';
import styles from './index.module.less';
export interface IAnchorPoint {
@@ -186,7 +186,7 @@ export const AutoFillPopupMenu: React.FC<{}> = () => {
onMouseLeave={handleMouseLeave}
style={{ left: `${relativeX + 2}px`, top: `${relativeY + 2}px`, position: 'absolute' }}
>
<Dropdown
<DropdownLegacy
placement="bottomLeft"
trigger={['click']}
overlay={(
@@ -221,7 +221,7 @@ export const AutoFillPopupMenu: React.FC<{}> = () => {
/>
{showMore && <MoreDownSingle style={{ color: '#CCCCCC', fontSize: '8px', marginLeft: '8px' }} />}
</div>
</Dropdown>
</DropdownLegacy>
</div>
);
};
@@ -16,7 +16,7 @@
import type { ICommandInfo, Workbook } from '@univerjs/core';
import { BooleanNumber, DisposableCollection, ICommandService, IUniverInstanceService, UniverInstanceType, useDependency } from '@univerjs/core';
import { Dropdown } from '@univerjs/design';
import { DropdownLegacy } from '@univerjs/design';
import { CheckMarkSingle, ConvertSingle, EyelashSingle } from '@univerjs/icons';
import {
InsertSheetMutation,
@@ -135,7 +135,7 @@ export function SheetBarMenu(props: ISheetBarMenuProps) {
}, [setupStatusUpdate, sheetBarService, statusInit, workbook]);
return (
<Dropdown
<DropdownLegacy
placement="topLeft"
trigger={['click']}
overlay={(
@@ -176,6 +176,6 @@ export function SheetBarMenu(props: ISheetBarMenuProps) {
<ConvertSingle />
</SheetBarButton>
</div>
</Dropdown>
</DropdownLegacy>
);
}
@@ -19,7 +19,7 @@ import type { IUniverUIConfig } from '@univerjs/ui';
import type { IBaseSheetBarProps } from './SheetBarItem';
import type { IScrollState } from './utils/slide-tab-bar';
import { ICommandService, IConfigService, IPermissionService, LocaleService, nameCharacterCheck, Quantity, useDependency } from '@univerjs/core';
import { Dropdown } from '@univerjs/design';
import { DropdownLegacy } from '@univerjs/design';
import { LockSingle } from '@univerjs/icons';
import {
@@ -412,7 +412,7 @@ export function SheetBarTabs() {
};
return (
<Dropdown
<DropdownLegacy
className={styles.slideTabItemDropdown}
visible={visible}
align={{ offset }}
@@ -441,6 +441,6 @@ export function SheetBarTabs() {
))}
</div>
</div>
</Dropdown>
</DropdownLegacy>
);
}
@@ -16,7 +16,7 @@
import type { IDrawingSearch } from '@univerjs/core';
import { ICommandService, LocaleService, useDependency } from '@univerjs/core';
import { Dropdown } from '@univerjs/design';
import { DropdownLegacy } from '@univerjs/design';
import { Autofill, MoreDownSingle } from '@univerjs/icons';
import clsx from 'clsx';
import React, { useState } from 'react';
@@ -79,7 +79,7 @@ export const SlideImagePopupMenu: React.FC<IImagePopupMenuProps> = (props: IImag
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<Dropdown
<DropdownLegacy
placement="bottomLeft"
trigger={['click']}
overlay={(
@@ -109,7 +109,7 @@ export const SlideImagePopupMenu: React.FC<IImagePopupMenuProps> = (props: IImag
/>
{showMore && <MoreDownSingle style={{ color: '#CCCCCC', fontSize: '8px', marginLeft: '8px' }} />}
</div>
</Dropdown>
</DropdownLegacy>
</div>
);
};
@@ -14,15 +14,15 @@
* limitations under the License.
*/
import React from 'react';
import type { Nullable } from '@univerjs/core';
import { ICommandService, LocaleService, useDependency } from '@univerjs/core';
import clsx from 'clsx';
import { ColorPicker, Dropdown } from '@univerjs/design';
import { MoreDownSingle, PaintBucket } from '@univerjs/icons';
import type { Rect } from '@univerjs/engine-render';
import { ICommandService, LocaleService, useDependency } from '@univerjs/core';
import { ColorPicker, DropdownLegacy } from '@univerjs/design';
import { MoreDownSingle, PaintBucket } from '@univerjs/icons';
import clsx from 'clsx';
import React from 'react';
import { UpdateSlideElementOperation } from '../../commands/operations/update-element.operation';
import { CanvasView } from '../../controllers/canvas-view';
import styles from './index.module.less';
@@ -84,7 +84,7 @@ export default function ArrangePanel(props: IProps) {
</div>
<div className={styles.imageCommonPanelRow}>
<div className={clsx(styles.imageCommonPanelColumn, styles.imageCommonPanelSpan2)}>
<Dropdown
<DropdownLegacy
align={{
offset: [0, 18],
}}
@@ -103,7 +103,7 @@ export default function ArrangePanel(props: IProps) {
<MoreDownSingle />
</span>
</a>
</Dropdown>
</DropdownLegacy>
</div>
</div>
</div>
@@ -18,7 +18,7 @@ import type { IAddCommentCommandParams, IThreadComment, IUpdateCommentCommandPar
import type { IThreadCommentEditorInstance } from '../thread-comment-editor';
import { generateRandomId, useDependency } from '@univerjs/core';
import { ICommandService, type IUser, LocaleService, type UniverInstanceType, UserManagerService } from '@univerjs/core';
import { Dropdown, Menu, MenuItem, Tooltip } from '@univerjs/design';
import { DropdownLegacy, Menu, MenuItem, Tooltip } from '@univerjs/design';
import { DeleteSingle, MoreHorizontalSingle, ReplyToCommentSingle, ResolvedSingle, SolveSingle } from '@univerjs/icons';
import { AddCommentCommand, DeleteCommentCommand, DeleteCommentTreeCommand, getDT, ResolveCommentCommand, ThreadCommentModel, UpdateCommentCommand } from '@univerjs/thread-comment';
import { useObservable } from '@univerjs/ui';
@@ -117,7 +117,7 @@ const ThreadCommentItem = (props: IThreadCommentItemProps) => {
)}
{isCommentBySelf && !isMock && !resolved
? (
<Dropdown
<DropdownLegacy
overlay={(
<Menu>
<MenuItem key="edit" onClick={() => onEditingChange?.(true)}>{localeService.t('threadCommentUI.item.edit')}</MenuItem>
@@ -128,7 +128,7 @@ const ThreadCommentItem = (props: IThreadCommentItemProps) => {
<div className={styles.threadCommentIcon}>
<MoreHorizontalSingle />
</div>
</Dropdown>
</DropdownLegacy>
)
: null}
</div>
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import type { IDropdownProps } from '@univerjs/design';
import type { IDropdownLegacyProps } from '@univerjs/design';
import type { Ref } from 'react';
import type { IDisplayMenuItem, IMenuItem, IMenuSelectorItem, IValueOption } from '../../../services/menu/menu';
import { ICommandService, LocaleService, useDependency } from '@univerjs/core';
@@ -33,7 +33,7 @@ import { useToolbarItemStatus } from './hook';
import styles from './index.module.less';
import { DropdownWrapper, TooltipWrapper } from './TooltipButtonWrapper';
export const ToolbarItem = forwardRef((props: IDisplayMenuItem<IMenuItem> & { align?: IDropdownProps['align'] }, ref: Ref<any>) => {
export const ToolbarItem = forwardRef((props: IDisplayMenuItem<IMenuItem> & { align?: IDropdownLegacyProps['align'] }, ref: Ref<any>) => {
const { align } = props;
const localeService = useDependency(LocaleService);
@@ -14,8 +14,8 @@
* limitations under the License.
*/
import type { IDropdownProps, ITooltipProps, NullableTooltipRef } from '@univerjs/design';
import { Dropdown, Tooltip } from '@univerjs/design';
import type { IDropdownLegacyProps, ITooltipProps, NullableTooltipRef } from '@univerjs/design';
import { DropdownLegacy, Tooltip } from '@univerjs/design';
import React, { createContext, forwardRef, useContext, useMemo, useState } from 'react';
const TooltipWrapperContext = createContext({
@@ -64,7 +64,7 @@ export const TooltipWrapper = forwardRef<NullableTooltipRef, ITooltipProps>((pro
);
});
export function DropdownWrapper(props: IDropdownProps) {
export function DropdownWrapper(props: IDropdownLegacyProps) {
const { children, ...dropdownProps } = props;
const { setDropdownVisible } = useContext(TooltipWrapperContext);
@@ -74,13 +74,13 @@ export function DropdownWrapper(props: IDropdownProps) {
}
return (
<Dropdown
<DropdownLegacy
onVisibleChange={handleVisibleChange}
{...dropdownProps}
>
<div onClick={(e) => e.stopPropagation()}>
{children}
</div>
</Dropdown>
</DropdownLegacy>
);
}
+44
View File
@@ -119,6 +119,9 @@ importers:
'@vitest/coverage-istanbul':
specifier: ^2.1.8
version: 2.1.8(vitest@2.1.8(@types/node@22.10.1)(happy-dom@15.0.0)(jsdom@24.1.1)(less@4.2.1)(sass@1.77.5)(terser@5.31.6))
autoprefixer:
specifier: ^10.4.20
version: 10.4.20(postcss@8.4.49)
eslint-plugin-readable-tailwind:
specifier: ^1.8.2
version: 1.8.2(eslint@9.16.0(jiti@1.21.6))(tailwindcss@3.4.16)
@@ -134,6 +137,9 @@ importers:
sort-keys:
specifier: ^5.1.0
version: 5.1.0
tailwind-scrollbar:
specifier: ^3.1.0
version: 3.1.0(tailwindcss@3.4.16)
tailwindcss:
specifier: ^3.4.16
version: 3.4.16
@@ -5745,6 +5751,13 @@ packages:
atomically@2.0.3:
resolution: {integrity: sha512-kU6FmrwZ3Lx7/7y3hPS5QnbJfaohcIul5fGqf7ok+4KklIEk9tJ0C2IQPdacSbVUWv6zVHXEBWoWd6NrVMT7Cw==}
autoprefixer@10.4.20:
resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==}
engines: {node: ^10 || ^12 || >=14}
hasBin: true
peerDependencies:
postcss: ^8.1.0
available-typed-arrays@1.0.7:
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
engines: {node: '>= 0.4'}
@@ -7272,6 +7285,9 @@ packages:
resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
engines: {node: '>= 6'}
fraction.js@4.3.7:
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
franc-min@6.2.0:
resolution: {integrity: sha512-1uDIEUSlUZgvJa2AKYR/dmJC66v/PvGQ9mWfI9nOr/kPpMFyvswK0gPXOwpYJYiYD008PpHLkGfG58SPjQJFxw==}
@@ -8564,6 +8580,10 @@ packages:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'}
normalize-range@0.1.2:
resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==}
engines: {node: '>=0.10.0'}
npm-run-path@4.0.1:
resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
engines: {node: '>=8'}
@@ -9904,6 +9924,12 @@ packages:
tailwind-merge@2.5.5:
resolution: {integrity: sha512-0LXunzzAZzo0tEPxV3I297ffKZPlKDrjj7NXphC8V5ak9yHC5zRmxnOe2m/Rd/7ivsOMJe3JZ2JVocoDdQTRBA==}
tailwind-scrollbar@3.1.0:
resolution: {integrity: sha512-pmrtDIZeHyu2idTejfV59SbaJyvp1VRjYxAjZBH0jnyrPRo6HL1kD5Glz8VPagasqr6oAx6M05+Tuw429Z8jxg==}
engines: {node: '>=12.13.0'}
peerDependencies:
tailwindcss: 3.x
tailwindcss-animate@1.0.7:
resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==}
peerDependencies:
@@ -13073,6 +13099,16 @@ snapshots:
stubborn-fs: 1.2.5
when-exit: 2.1.3
autoprefixer@10.4.20(postcss@8.4.49):
dependencies:
browserslist: 4.24.2
caniuse-lite: 1.0.30001675
fraction.js: 4.3.7
normalize-range: 0.1.2
picocolors: 1.1.1
postcss: 8.4.49
postcss-value-parser: 4.2.0
available-typed-arrays@1.0.7:
dependencies:
possible-typed-array-names: 1.0.0
@@ -14847,6 +14883,8 @@ snapshots:
combined-stream: 1.0.8
mime-types: 2.1.35
fraction.js@4.3.7: {}
franc-min@6.2.0:
dependencies:
trigram-utils: 2.0.1
@@ -16314,6 +16352,8 @@ snapshots:
normalize-path@3.0.0: {}
normalize-range@0.1.2: {}
npm-run-path@4.0.1:
dependencies:
path-key: 3.1.1
@@ -17844,6 +17884,10 @@ snapshots:
tailwind-merge@2.5.5: {}
tailwind-scrollbar@3.1.0(tailwindcss@3.4.16):
dependencies:
tailwindcss: 3.4.16
tailwindcss-animate@1.0.7(tailwindcss@3.4.16):
dependencies:
tailwindcss: 3.4.16