refactor(design & ui): refactor Tooltip and Ribbon (#4386)

This commit is contained in:
白熱
2024-12-30 21:08:34 +08:00
committed by GitHub
parent e3930bbdea
commit 4940fc73fc
74 changed files with 1077 additions and 540 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 75 KiB

+2
View File
@@ -28,6 +28,8 @@ import { UniverUIPlugin } from '@univerjs/ui';
import { UniverUniscriptPlugin } from '@univerjs/uniscript';
import { enUS, faIR, ruRU, zhCN } from '../locales';
import '../global.css';
// univer
const univer = new Univer({
theme: defaultTheme,
+2
View File
@@ -29,6 +29,8 @@ import { DEFAULT_DOCUMENT_DATA_SIMPLE } from '@univerjs/mockdata';
import { UniverUIPlugin } from '@univerjs/ui';
import { enUS, faIR, ruRU, zhCN } from '../locales';
import '../global.css';
/* eslint-disable node/prefer-global/process */
const IS_E2E: boolean = !!process.env.IS_E2E;
+2
View File
@@ -35,6 +35,8 @@ import { UniverSheetsMobileUIPlugin } from '@univerjs/sheets-ui';
import { UniverMobileUIPlugin } from '@univerjs/ui';
import { enUS, faIR } from '../locales';
import '../global.css';
// univer
const univer = new Univer({
theme: defaultTheme,
-3
View File
@@ -1,3 +0,0 @@
#app {
height: 100%;
}
+2 -1
View File
@@ -34,7 +34,8 @@ import { Mosaic, MosaicWindow } from 'react-mosaic-component';
import { enUS, faIR, frFR, ruRU, zhCN } from '../locales';
import 'react-mosaic-component/react-mosaic-component.css';
import './index.css';
import '../global.css';
function factory(id: string) {
return function createUniverOnContainer() {
+2 -1
View File
@@ -29,9 +29,10 @@ import { UniverSheetsNumfmtUIPlugin } from '@univerjs/sheets-numfmt-ui';
import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui';
import { UniverUIPlugin } from '@univerjs/ui';
import { UniverUniscriptPlugin } from '@univerjs/uniscript';
import { enUS, faIR, ruRU, zhCN } from '../locales';
import '../global.css';
// univer
const univer = new Univer({
theme: defaultTheme,
+2
View File
@@ -28,6 +28,8 @@ import { UniverSlidesUIPlugin } from '@univerjs/slides-ui';
import { UniverUIPlugin } from '@univerjs/ui';
import { enUS, faIR, ruRU, zhCN } from '../locales';
import '../global.css';
// univer
const univer = new Univer({
theme: greenTheme,
+2
View File
@@ -46,6 +46,8 @@ import { UniSlidesUIController } from '@univerjs/uni-slides-ui';
import { UniverUniUIPlugin } from '@univerjs/uniui';
import { enUS, faIR } from '../locales';
import '../global.css';
const LOAD_LAZY_PLUGINS_TIMEOUT = 4_000;
// univer
@@ -1,3 +1,2 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@@ -0,0 +1,34 @@
/**
* 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 { IAccessor, ICommand } from '@univerjs/core';
import { CommandType, ILocalStorageService } from '@univerjs/core';
export interface IDarkModeCommandParams {
value?: 'dark' | 'light' | 'auto';
}
export const DarkModeOperation: ICommand = {
id: 'debugger.operation.dark-mode',
type: CommandType.COMMAND,
handler: async (accessor: IAccessor, params: IDarkModeCommandParams) => {
const localStorageService = accessor.get(ILocalStorageService);
localStorageService.setItem('local.darkMode', params.value);
window.location.reload();
return true;
},
};
@@ -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 { ILocalStorageService, Inject, RxDisposable } from '@univerjs/core';
export class DarkModeController extends RxDisposable {
constructor(
@Inject(ILocalStorageService) private _localStorageService: ILocalStorageService
) {
super();
this._localStorageService.getItem('local.darkMode').then((darkMode) => {
if (darkMode === 'dark') {
document.documentElement.classList.add('univer-dark');
}
if (darkMode === 'system') {
const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
darkModeMediaQuery.addEventListener('change', (e) => {
if (e.matches) {
document.documentElement.classList.add('univer-dark');
} else {
document.documentElement.classList.remove('univer-dark');
}
});
}
});
}
}
@@ -21,6 +21,7 @@ import { CreateEmptySheetCommand, DisposeCurrentUnitCommand, DisposeUniverComman
import { ShowCellContentOperation } from '../commands/operations/cell.operation';
import { ChangeUserCommand } from '../commands/operations/change-user.operation';
import { ConfirmOperation } from '../commands/operations/confirm.operation';
import { DarkModeOperation } from '../commands/operations/dark-mode.operation';
import { DialogOperation } from '../commands/operations/dialog.operation';
import { LocaleOperation } from '../commands/operations/locale.operation';
import { MessageOperation } from '../commands/operations/message.operation';
@@ -29,10 +30,10 @@ import { OpenWatermarkPanelOperation } from '../commands/operations/open-waterma
import { SaveSnapshotOptions } from '../commands/operations/save-snapshot.operations';
import { SetEditable } from '../commands/operations/set.editable.operation';
import { SidebarOperation } from '../commands/operations/sidebar.operation';
import { ThemeOperation } from '../commands/operations/theme.operation';
import { ImageDemo } from '../components/Image';
// @ts-ignore
import VueI18nIcon from '../components/VueI18nIcon.vue';
import { TEST_EDITOR_CONTAINER_COMPONENT } from '../views/test-editor/component-name';
@@ -54,6 +55,7 @@ export class DebuggerController extends Disposable {
[
LocaleOperation,
DarkModeOperation,
ThemeOperation,
NotificationOperation,
DialogOperation,
@@ -20,6 +20,7 @@ import { CreateEmptySheetCommand, DisposeCurrentUnitCommand, DisposeUniverComman
import { ShowCellContentOperation } from '../commands/operations/cell.operation';
import { ChangeUserCommand } from '../commands/operations/change-user.operation';
import { ConfirmOperation } from '../commands/operations/confirm.operation';
import { DarkModeOperation } from '../commands/operations/dark-mode.operation';
import { DialogOperation } from '../commands/operations/dialog.operation';
import { LocaleOperation } from '../commands/operations/locale.operation';
import { MessageOperation } from '../commands/operations/message.operation';
@@ -34,6 +35,7 @@ import {
ConfirmMenuItemFactory,
CreateEmptySheetMenuItemFactory,
CreateFloatDOMMenuItemFactory,
DarkModeMenuItemFactory,
DialogMenuItemFactory,
DisposeCurrentUnitMenuItemFactory,
DisposeUniverItemFactory,
@@ -59,6 +61,10 @@ export const menuSchema: MenuSchemaType = {
order: 0,
menuItemFactory: LocaleMenuItemFactory,
},
[DarkModeOperation.id]: {
order: 0.9,
menuItemFactory: DarkModeMenuItemFactory,
},
[ThemeOperation.id]: {
order: 1,
menuItemFactory: ThemeMenuItemFactory,
@@ -25,6 +25,7 @@ import { CreateEmptySheetCommand, DisposeCurrentUnitCommand, DisposeUniverComman
import { ShowCellContentOperation } from '../commands/operations/cell.operation';
import { ChangeUserCommand, UnitRole } from '../commands/operations/change-user.operation';
import { ConfirmOperation } from '../commands/operations/confirm.operation';
import { DarkModeOperation } from '../commands/operations/dark-mode.operation';
import { DialogOperation } from '../commands/operations/dialog.operation';
import { LocaleOperation } from '../commands/operations/locale.operation';
import { MessageOperation } from '../commands/operations/message.operation';
@@ -71,6 +72,29 @@ export function LocaleMenuItemFactory(accessor: IAccessor): IMenuSelectorItem {
};
}
export function DarkModeMenuItemFactory(accessor: IAccessor): IMenuSelectorItem {
return {
id: DarkModeOperation.id,
title: '🌓',
tooltip: 'Dark Mode',
type: MenuItemType.SELECTOR,
selections: [
{
label: 'System',
value: 'auto',
},
{
label: 'Light',
value: 'light',
},
{
label: 'Dark',
value: 'dark',
},
],
};
}
export function ThemeMenuItemFactory(accessor: IAccessor): IMenuSelectorItem {
return {
id: ThemeOperation.id,
@@ -18,6 +18,7 @@ import type { Dependency } from '@univerjs/core';
import type { IUniverDebuggerConfig } from './controllers/config.schema';
import { IConfigService, Inject, Injector, merge, Plugin } from '@univerjs/core';
import { DEBUGGER_PLUGIN_CONFIG_KEY, defaultPluginConfig } from './controllers/config.schema';
import { DarkModeController } from './controllers/dark-mode.controller';
import { DebuggerController } from './controllers/debugger.controller';
import { E2EController } from './controllers/e2e/e2e.controller';
import { PerformanceMonitorController } from './controllers/performance-monitor.controller';
@@ -50,6 +51,7 @@ export class UniverDebuggerPlugin extends Plugin {
override onStarting(): void {
([
[PerformanceMonitorController],
[DarkModeController],
[DebuggerController],
[E2EController],
[UniverWatermarkMenuController],
@@ -63,6 +65,7 @@ export class UniverDebuggerPlugin extends Plugin {
}
override onRendered(): void {
this._injector.get(DarkModeController);
this._injector.get(PerformanceMonitorController);
this._injector.get(UniverWatermarkMenuController);
}
@@ -1,3 +1,2 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@@ -1,3 +1,2 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@@ -1,3 +1,2 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@@ -1,3 +1,2 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@@ -1,3 +1,2 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@@ -1,3 +1,2 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
+1
View File
@@ -68,6 +68,7 @@
"react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0"
},
"dependencies": {
"@radix-ui/react-tooltip": "^1.1.6",
"@rc-component/color-picker": "^2.0.1",
"@rc-component/trigger": "^2.2.5",
"@types/react-mentions": "^4.4.0",
@@ -18,8 +18,9 @@ import type { RefObject } from 'react';
import { createContext, useContext } from 'react';
interface IDropdownContextType {
isOpen: boolean;
setIsOpen: (value: boolean) => void;
show: boolean;
updateShow: (value: boolean) => void;
disabled: boolean;
overlayRef: RefObject<HTMLDivElement>;
triggerRef: RefObject<HTMLElement>;
}
@@ -15,12 +15,12 @@
*/
import canUseDom from 'rc-util/lib/Dom/canUseDom';
import React, { useEffect, useState } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import { createPortal } from 'react-dom';
import clsx from '../../helper/clsx';
import { clsx } from '../../helper/clsx';
import { useDropdown } from './DropdownContext';
interface IDropdownOverlayProps {
export interface IDropdownOverlayProps {
children: React.ReactNode;
className?: string;
offset?: {
@@ -34,11 +34,12 @@ export function DropdownOverlay({ children, className, offset }: IDropdownOverla
return null;
}
const { isOpen, setIsOpen, overlayRef, triggerRef } = useDropdown();
const { show, updateShow, overlayRef, triggerRef } = useDropdown();
const [mounted, setMounted] = useState(false);
const [position, setPosition] = useState({ top: 0, left: 0 });
useEffect(() => {
if (isOpen && triggerRef.current && overlayRef.current) {
if (show && triggerRef.current && overlayRef.current) {
const triggerRect = triggerRef.current.getBoundingClientRect();
const overlayRect = overlayRef.current.getBoundingClientRect();
const viewportWidth = document.documentElement.clientWidth;
@@ -62,26 +63,39 @@ export function DropdownOverlay({ children, className, offset }: IDropdownOverla
top: top + (offset?.y ?? 0),
left: left + (offset?.x ?? 0),
});
}
}, [isOpen, offset?.x, offset?.y, overlayRef, triggerRef]);
if (!isOpen) return null;
requestAnimationFrame(() => {
setMounted(true);
});
}
}, [show, offset?.x, offset?.y, overlayRef, triggerRef]);
const hideAfterClick = useCallback((e: React.MouseEvent) => {
e.stopPropagation();
updateShow(false);
}, []);
if (!show) 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
univer-fixed univer-z-[1071] univer-overflow-hidden univer-rounded-md univer-border univer-bg-white
univer-shadow-md univer-opacity-0
dark:univer-bg-gray-700
`,
{
'univer-opacity-100 univer-slide-in-from-top-2 univer-animate-in': mounted,
},
className
)}
style={{
top: position.top,
left: position.left,
}}
onClick={() => setIsOpen(false)}
onClick={hideAfterClick}
>
{children}
</div>,
@@ -19,19 +19,34 @@ import React, { useEffect, useMemo, useRef, useState } from 'react';
import { DropdownContext } from './DropdownContext';
interface IDropdownProviderProps {
visible?: boolean;
children: ReactNode;
disabled?: boolean;
onVisibleChange?: (visible: boolean) => void;
}
export function DropdownProvider({ children }: IDropdownProviderProps) {
const [isOpen, setIsOpen] = useState(false);
export function DropdownProvider({ visible, children, disabled = false, onVisibleChange }: IDropdownProviderProps) {
const triggerRef = useRef<HTMLDivElement>(null);
const overlayRef = useRef<HTMLDivElement>(null);
const [internalShow, setInternalShow] = useState(false);
const isControlled = visible !== undefined;
const show = isControlled ? visible : internalShow;
const updateShow = (newShow: boolean) => {
if (disabled) return;
if (!isControlled) {
setInternalShow(newShow);
}
onVisibleChange?.(newShow);
};
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
const target = event.target as Node;
if (!triggerRef.current?.contains(target) && !overlayRef.current?.contains(target)) {
setIsOpen(false);
updateShow(false);
}
};
@@ -39,7 +54,7 @@ export function DropdownProvider({ children }: IDropdownProviderProps) {
return () => window.removeEventListener('mousedown', handleClickOutside, true);
}, []);
const contextValue = useMemo(() => ({ isOpen, setIsOpen, triggerRef, overlayRef }), [isOpen]);
const contextValue = useMemo(() => ({ show, updateShow, disabled, triggerRef, overlayRef }), [show, disabled]);
return (
<DropdownContext.Provider value={contextValue}>
@@ -23,7 +23,7 @@ interface IDropdownTriggerProps {
}
export function DropdownTrigger({ children }: IDropdownTriggerProps) {
const { setIsOpen, isOpen, triggerRef } = useDropdown();
const { show, updateShow, triggerRef } = useDropdown();
if (!isValidElement(children)) {
throw new Error('DropdownTrigger children must be a valid React element');
@@ -34,7 +34,7 @@ export function DropdownTrigger({ children }: IDropdownTriggerProps) {
ref: triggerRef,
onClick: (e: React.MouseEvent) => {
e.stopPropagation();
setIsOpen(!isOpen);
updateShow(!show);
// eslint-disable-next-line ts/no-explicit-any
(children.props as React.HTMLProps<HTMLElement>)?.onClick?.(e as any);
},
@@ -14,6 +14,6 @@
* limitations under the License.
*/
export { DropdownOverlay } from './DropdownOverlay';
export { DropdownOverlay, type IDropdownOverlayProps } from './DropdownOverlay';
export { DropdownProvider } from './DropdownProvider';
export { DropdownTrigger } from './DropdownTrigger';
@@ -18,7 +18,7 @@ import { ErrorSingle, InfoSingle, Loading, SuccessSingle, WarningSingle } from '
import canUseDom from 'rc-util/lib/Dom/canUseDom';
import React, { useEffect, useMemo, useState } from 'react';
import { createPortal } from 'react-dom';
import clsx from '../../helper/clsx';
import { clsx } from '../../helper/clsx';
export enum MessageType {
Success = 'success',
@@ -51,9 +51,9 @@ const Message = ({ content, type = MessageType.Info }: IMessageProps) => {
<div
className={clsx(
`
univer-rounded-xl univer-p-4 univer-min-w-[320px] univer-transition-all univer-bg-white
univer-duration-300 univer-animate-in univer-fade-in univer-slide-in-from-top-4 univer-shadow-md
univer-max-w-[480px] univer-border univer-border-gray-200 univer-border-solid
univer-font-sans univer-rounded-xl univer-p-4 univer-min-w-[320px] univer-transition-all
univer-bg-white univer-duration-300 univer-animate-in univer-fade-in univer-slide-in-from-top-4
univer-shadow-md univer-max-w-[480px] univer-border univer-border-gray-200 univer-border-solid
dark:univer-border-gray-700 dark:univer-bg-gray-700
`
)}
@@ -115,6 +115,7 @@ export function Scrollbar(props: IScrollbarProps) {
return () => {
contentRef.current?.removeEventListener('scroll', handleScroll);
observer.disconnect();
};
}, []);
@@ -157,8 +157,17 @@ export function Slider(props: ISliderProps) {
<div className={styles.sliderRail}>
<div ref={sliderInnerRailRef} role="track" className={styles.sliderInnerRail}>
<Tooltip title={`${locale?.Slider.resetTo} ${resetPoint}%`}>
<a className={styles.sliderResetPoint} role="reset button" onClick={handleReset} />
<Tooltip title={`${locale?.Slider.resetTo} ${resetPoint}%`} placement="top" asChild>
<a
className={`
univer-cursor-pointer univer-absolute univer-left-1/2 univer-top-1/2
-univer-translate-x-1/2 -univer-translate-y-1/2 univer-box-border univer-w-0.5
univer-h-0.5 univer-bg-white univer-rounded-full univer-block
`}
role="button"
onClick={handleReset}
/>
</Tooltip>
<div
@@ -35,7 +35,7 @@ describe('Slider', () => {
const { container } = render(<Slider value={value} resetPoint={30} onChange={handleChange} />);
fireEvent.click(container.querySelector('[role=reset button]')!);
fireEvent.click(container.querySelector('[role=button]')!);
expect(value).toBe(30);
});
@@ -35,21 +35,62 @@ export const Playground = {
render() {
return (
<>
<div
className="univer-box-border univer-relative univer-w-96 univer-p-4 univer-bg-gray-50 univer-h-48 univer-flex univer-justify-center univer-items-center"
>
<div className="univer-absolute univer-top-0">
<Tooltip title="hello world" placement="top">
<Button>top</Button>
</Tooltip>
</div>
<div className="univer-absolute univer-right-0">
<Tooltip title="hello world" placement="right">
<Button>right</Button>
</Tooltip>
</div>
<div className="univer-absolute univer-bottom-0">
<Tooltip title="hello world" placement="bottom">
<Button>bottom</Button>
</Tooltip>
</div>
<div className="univer-absolute univer-left-0">
<Tooltip title="hello world" placement="left">
<Button>left</Button>
</Tooltip>
</div>
</div>
<div>
<Tooltip title="hello world" placement="top">
<Button>top</Button>
<Tooltip title="这是一个提示">
<a className="px-4 py-2 bg-blue-500 text-white rounded">
悬浮显示提示
</a>
</Tooltip>
</div>
<div>
<Tooltip title="hello world" placement="bottom">
<Button>bottom</Button>
<Tooltip
title="完整的文本内容"
showIfEllipsis
placement="bottom"
>
<div className="univer-w-32 univer-truncate">
这是一段很长的文本内容,超出部分会被截断
</div>
</Tooltip>
</div>
<div>
<Tooltip showIfEllipsis title="hello world" placement="bottom">
<span style={{ overflow: 'hidden', textOverflow: 'ellipsis' }}>
showIfEllipsis: 奇奇怪怪乖乖跟琪琪去上课就开始丹江口市的是卡拉斯科独立思考独立思考
</span>
<div className="univer-relative">
<Tooltip
title="asChild"
placement="bottom"
asChild
>
<div className="univer-h-8 univer-w-8 univer-bg-blue-400 univer-absolute univer-right-0">
</div>
</Tooltip>
</div>
</>
@@ -14,75 +14,334 @@
* limitations under the License.
*/
import type { TooltipRef } from 'rc-tooltip';
import RcTooltip from 'rc-tooltip';
import React, { forwardRef, useCallback, useContext, useImperativeHandle, useRef, useState } from 'react';
import { ConfigContext } from '../config-provider/ConfigProvider';
import styles from './index.module.less';
import { placements } from './placements';
import { useIsEllipsis } from './hooks';
import canUseDom from 'rc-util/lib/Dom/canUseDom';
import React, { useEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import { clsx } from '../../helper/clsx';
export interface ITooltipProps {
/**
* Whether the tooltip is visible
* @description If not set, the tooltip will be controlled by the component itself
*/
visible?: boolean;
/**
* Whether the tooltip is a child of the trigger element
* @default false
*/
asChild?: boolean;
/**
* The content of the tooltip
* @description If not set, the tooltip will not be displayed。 Although it can be set to ReactNode, it is recommended to use string.
*/
title?: React.ReactNode;
/**
* The trigger element
*/
children: React.ReactNode;
/**
* The placement of the tooltip
* @default 'bottom'
*/
placement?: 'top' | 'bottom' | 'left' | 'right';
title: (() => React.ReactNode) | React.ReactNode;
children: React.ReactElement;
/* Tooltip only show if text is ellipsis */
/**
* Whether the tooltip is displayed when the content is ellipsis
* @default false
*/
showIfEllipsis?: boolean;
/**
* Callback when the visibility of the tooltip changes
*/
onVisibleChange?: (visible: boolean) => void;
style?: React.CSSProperties;
}
export type NullableTooltipRef = TooltipRef | null;
export function Tooltip({ visible, asChild = false, title, children, placement = 'bottom', showIfEllipsis = false, onVisibleChange }: ITooltipProps) {
if (!canUseDom || title === undefined) {
return null;
}
export const Tooltip = forwardRef<NullableTooltipRef, ITooltipProps>((props, ref) => {
const {
children,
visible,
placement = 'top',
title,
onVisibleChange,
style,
showIfEllipsis = false,
} = props;
const [mounted, setMounted] = useState(false);
const [position, setPosition] = useState({ x: 0, y: 0 });
const [actualPlacement, setActualPlacement] = useState(placement);
const triggerRef = useRef<HTMLDivElement>(null);
const tooltipRef = useRef<HTMLDivElement>(null);
const [arrowOffset, setArrowOffset] = useState(0);
const { mountContainer } = useContext(ConfigContext);
const [tooltipEl, setTooltipEl] = useState<HTMLElement>();
const tooltipRef = useRef<NullableTooltipRef>(null);
const [internalShow, setInternalShow] = useState(false);
const isControlled = visible !== undefined;
const show = isControlled ? visible : internalShow;
// FIXME: cannot set value in a render method
const refHandler = useCallback((ref: NullableTooltipRef) => {
if (ref?.nativeElement) {
setTooltipEl(ref.nativeElement);
const updateShow = (newShow: boolean) => {
if (!isControlled) {
setInternalShow(newShow);
}
tooltipRef.current = ref;
}, []);
useImperativeHandle<NullableTooltipRef, NullableTooltipRef>(ref, () => tooltipRef.current);
onVisibleChange?.(newShow);
};
const isEllipsis = useIsEllipsis(showIfEllipsis ? tooltipEl : null);
const updatePosition = () => {
if (!triggerRef.current || !tooltipRef.current) return;
return mountContainer && (
<RcTooltip
visible={(showIfEllipsis && !isEllipsis) ? false : visible}
ref={refHandler}
prefixCls={styles.tooltip}
getTooltipContainer={() => mountContainer}
overlay={<div className={styles.tooltipContent}>{typeof title === 'function' ? title() : title}</div>}
builtinPlacements={placements}
placement={placement}
mouseEnterDelay={0.2}
showArrow
destroyTooltipOnHide={false}
onVisibleChange={onVisibleChange}
overlayStyle={style}
>
{children}
</RcTooltip>
const triggerRect = triggerRef.current.getBoundingClientRect();
const tooltipRect = tooltipRef.current.getBoundingClientRect();
const scrollX = window.scrollX;
const scrollY = window.scrollY;
const MARGIN = 8;
const ARROW_PADDING = 12;
let newPlacement = placement;
let newArrowOffset = 0;
// Calculate base position
const getBasePosition = () => {
switch (newPlacement) {
case 'top':
return {
x: triggerRect.left + (triggerRect.width - tooltipRect.width) / 2,
y: triggerRect.top - tooltipRect.height - MARGIN,
};
case 'bottom':
return {
x: triggerRect.left + (triggerRect.width - tooltipRect.width) / 2,
y: triggerRect.bottom + MARGIN,
};
case 'left':
return {
x: triggerRect.left - tooltipRect.width - MARGIN,
y: triggerRect.top + (triggerRect.height - tooltipRect.height) / 2,
};
case 'right':
return {
x: triggerRect.right + MARGIN,
y: triggerRect.top + (triggerRect.height - tooltipRect.height) / 2,
};
}
};
// Check if it needs to be flipped
const shouldFlip = (pos: { x: number; y: number }) => {
switch (newPlacement) {
case 'top':
return pos.y < MARGIN;
case 'bottom':
return pos.y + tooltipRect.height > window.innerHeight - MARGIN;
case 'left':
return pos.x < MARGIN;
case 'right':
return pos.x + tooltipRect.width > window.innerWidth - MARGIN;
}
};
// Get flipped placement
const getFlippedPlacement = () => {
const map = {
top: 'bottom',
bottom: 'top',
left: 'right',
right: 'left',
} as const;
return map[newPlacement];
};
// Initial position calculation
let pos = getBasePosition();
// Check if it needs to be flipped
if (shouldFlip(pos)) {
newPlacement = getFlippedPlacement();
pos = getBasePosition();
} else {
// Edge avoidance adjustment
if (['top', 'bottom'].includes(newPlacement)) {
const minX = MARGIN;
const maxX = window.innerWidth - tooltipRect.width - MARGIN;
if (pos.x < minX) {
newArrowOffset = pos.x - minX;
pos.x = minX;
} else if (pos.x > maxX) {
newArrowOffset = pos.x - maxX;
pos.x = maxX;
}
// Limit arrow offset
const maxArrowOffset = tooltipRect.width / 2 - ARROW_PADDING;
newArrowOffset = Math.max(Math.min(newArrowOffset, maxArrowOffset), -maxArrowOffset);
} else {
const minY = MARGIN;
const maxY = window.innerHeight - tooltipRect.height - MARGIN;
if (pos.y < minY) {
newArrowOffset = pos.y - minY;
pos.y = minY;
} else if (pos.y > maxY) {
newArrowOffset = pos.y - maxY;
pos.y = maxY;
}
// Limit arrow offset
const maxArrowOffset = tooltipRect.height / 2 - ARROW_PADDING;
newArrowOffset = Math.max(Math.min(newArrowOffset, maxArrowOffset), -maxArrowOffset);
}
}
// Add scroll offset
pos.x += scrollX;
pos.y += scrollY;
setPosition(pos);
setActualPlacement(newPlacement);
setArrowOffset(newArrowOffset);
setMounted(true);
};
useEffect(() => {
if (show) {
setMounted(false);
requestAnimationFrame(() => {
updatePosition();
});
const handleUpdate = () => {
setMounted(false);
requestAnimationFrame(updatePosition);
};
window.addEventListener('scroll', handleUpdate);
window.addEventListener('resize', handleUpdate);
return () => {
window.removeEventListener('scroll', handleUpdate);
window.removeEventListener('resize', handleUpdate);
};
}
}, [show]);
useEffect(() => {
const element = triggerRef.current;
if (!element) {
return;
}
const childEl = asChild ? element : element.firstElementChild as HTMLElement;
function isContentOverflowing(element: HTMLElement): boolean {
return Math.abs(element.scrollWidth - element.clientWidth) > 1;
}
function onMouseEnter() {
if (showIfEllipsis && childEl) {
if (!isContentOverflowing(childEl)) return;
}
updateShow(true);
}
function onMouseLeave() {
updateShow(false);
setMounted(false);
}
if (isControlled && !onVisibleChange) {
return;
}
element.addEventListener('mouseenter', onMouseEnter);
element.addEventListener('mouseleave', onMouseLeave);
return () => {
element.removeEventListener('mouseenter', onMouseEnter);
element.removeEventListener('mouseleave', onMouseLeave);
};
}, [asChild, showIfEllipsis, isControlled, onVisibleChange]);
useEffect(() => {
if (isControlled) {
if (visible) {
setMounted(false);
requestAnimationFrame(() => {
updatePosition();
});
} else {
setMounted(false);
}
}
}, [visible, isControlled]);
// eslint-disable-next-line react/no-clone-element
const enhancedChild = React.cloneElement(
// eslint-disable-next-line react/no-children-only
React.Children.only(children) as React.ReactElement,
{
ref: triggerRef,
}
);
});
return (
<>
{asChild
? enhancedChild
: (
<div ref={triggerRef} className="univer-inline-block">
{children}
</div>
)}
{show && createPortal(
<div
ref={tooltipRef}
className={clsx(`
univer-pointer-events-none univer-fixed univer-z-[1100] univer-animate-in univer-fade-in-0
univer-zoom-in-95 univer-duration-200 univer-opacity-0 univer-font-sans
`, {
'univer-opacity-100': mounted,
'univer-slide-in-from-bottom-2': actualPlacement === 'top' && mounted,
'univer-slide-in-from-top-2': actualPlacement === 'bottom' && mounted,
'univer-slide-in-from-right-2': actualPlacement === 'left' && mounted,
'univer-slide-in-from-left-2': actualPlacement === 'right' && mounted,
})}
data-state={show ? 'open' : 'closed'}
style={{
left: position.x,
top: position.y,
transition: 'opacity 150ms ease-out',
}}
>
<div className="univer-relative">
<div
className={`
univer-max-w-[192px] univer-rounded-lg univer-bg-gray-700 univer-px-3 univer-py-2.5
univer-text-xs univer-text-white univer-w-fit univer-font-medium
`}
>
{title}
</div>
{/* Arrow */}
<div
className={clsx('univer-absolute univer-w-2 univer-h-2 univer-rotate-45 univer-bg-gray-700', {
'-univer-bottom-1 univer-left-1/2 -univer-translate-x-1/2': actualPlacement === 'top',
'-univer-top-1 univer-left-1/2 -univer-translate-x-1/2': actualPlacement === 'bottom',
'-univer-right-1 univer-top-1/2 -univer-translate-y-1/2': actualPlacement === 'left',
'-univer-left-1 univer-top-1/2 -univer-translate-y-1/2': actualPlacement === 'right',
})}
style={{
...(actualPlacement === 'top' && {
'--tw-translate-x': `calc(-50% + ${arrowOffset}px)`,
}),
...(actualPlacement === 'bottom' && {
'--tw-translate-x': `calc(-50% + ${arrowOffset}px)`,
}),
...(actualPlacement === 'left' && {
'--tw-translate-y': `calc(-50% + ${arrowOffset}px)`,
}),
...(actualPlacement === 'right' && {
'--tw-translate-y': `calc(-50% + ${arrowOffset}px)`,
}),
} as React.CSSProperties}
/>
</div>
</div>,
document.body
)}
</>
);
};
@@ -1,108 +0,0 @@
@tooltip-color: rgb(var(--color-white));
@tooltip-bg: rgb(var(--color-black));
@tooltip-arrow-width: 4px;
.tooltip {
position: absolute;
z-index: 1080;
pointer-events: none;
display: block;
font-size: var(--font-size-xxs);
visibility: visible;
&-no-events {
pointer-events: none;
}
&-hidden {
display: none;
}
&-placement-top {
padding: @tooltip-arrow-width 0;
}
&-placement-right {
padding: 0 @tooltip-arrow-width;
}
&-placement-bottom {
padding: @tooltip-arrow-width;
}
&-placement-left {
padding: 0 @tooltip-arrow-width;
}
&-placement-top &-arrow,
&-placement-topLeft &-arrow,
&-placement-topRight &-arrow {
bottom: 0;
margin-left: -@tooltip-arrow-width;
border-width: @tooltip-arrow-width @tooltip-arrow-width 0;
border-top-color: @tooltip-bg;
}
&-placement-top &-arrow {
left: 50%;
}
&-placement-right &-arrow {
left: 0;
margin-top: -@tooltip-arrow-width;
border-width: @tooltip-arrow-width @tooltip-arrow-width @tooltip-arrow-width 0;
border-right-color: @tooltip-bg;
}
&-placement-right &-arrow {
top: 50%;
}
&-placement-left &-arrow {
right: 0;
margin-top: -@tooltip-arrow-width;
border-width: @tooltip-arrow-width 0 @tooltip-arrow-width @tooltip-arrow-width;
border-left-color: @tooltip-bg;
}
&-placement-left &-arrow {
top: 50%;
}
&-placement-bottom &-arrow {
top: 0;
margin-left: -@tooltip-arrow-width;
border-width: 0 @tooltip-arrow-width @tooltip-arrow-width;
border-bottom-color: @tooltip-bg;
}
&-placement-bottom &-arrow {
left: 50%;
}
// Wrapper for the tooltip content
&-inner {
padding: var(--padding-xs) var(--padding-lg);
color: @tooltip-color;
text-align: left;
text-decoration: none;
background-color: @tooltip-bg;
border-radius: var(--border-radius-base);
}
// Arrows
&-arrow {
position: absolute;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
}
}
@@ -14,5 +14,4 @@
* limitations under the License.
*/
export { resizeObserverCtor } from './hooks';
export { type ITooltipProps, type NullableTooltipRef, Tooltip } from './Tooltip';
export { type ITooltipProps, Tooltip } from './Tooltip';
@@ -1,55 +0,0 @@
/**
* 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 { BuildInPlacements } from '@rc-component/trigger';
const autoAdjustOverflowTopBottom = {
shiftX: 0,
adjustY: 1,
};
const autoAdjustOverflowLeftRight = { adjustX: 1, shiftY: true };
const targetOffset = [0, 0];
export const placements: BuildInPlacements = {
left: {
points: ['cr', 'cl'],
overflow: autoAdjustOverflowLeftRight,
offset: [-4, 0],
targetOffset,
},
right: {
points: ['cl', 'cr'],
overflow: autoAdjustOverflowLeftRight,
offset: [4, 0],
targetOffset,
},
top: {
points: ['bc', 'tc'],
overflow: autoAdjustOverflowTopBottom,
offset: [0, -4],
targetOffset,
},
bottom: {
points: ['tc', 'bc'],
overflow: autoAdjustOverflowTopBottom,
offset: [0, 4],
targetOffset,
},
};
export default placements;
+1 -1
View File
@@ -17,6 +17,6 @@
import { type ClassValue, clsx as cn } from 'clsx';
import { twMerge } from 'tailwind-merge';
export default function clsx(...inputs: ClassValue[]) {
export function clsx(...inputs: ClassValue[]) {
return twMerge(cn(inputs));
}
@@ -14,9 +14,6 @@
* limitations under the License.
*/
import { useEffect, useState } from 'react';
import canUseDom from 'rc-util/lib/Dom/canUseDom';
/**
* All elements are observed by a single ResizeObserver is got greater performance than each element observed by separate ResizeObserver
* See issue https://github.com/WICG/resize-observer/issues/59#issuecomment-408098151
@@ -41,24 +38,3 @@ export function resizeObserverCtor(callback: ResizeObserverCallback) {
},
};
}
export function useIsEllipsis(element: HTMLElement | null | undefined) {
const [isEllipsis, setIsEllipsis] = useState(false);
useEffect(() => {
if (!canUseDom() || !element) {
return;
}
const resizeObserver = resizeObserverCtor(() => {
element && setIsEllipsis(element.scrollWidth > element.offsetWidth);
});
setIsEllipsis(element.scrollWidth > element.offsetWidth);
resizeObserver.observe(element);
return () => {
resizeObserver.unobserve(element);
};
}, [element]);
return isEllipsis;
}
+4 -2
View File
@@ -27,7 +27,7 @@ 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 { DropdownLegacy, type IDropdownLegacyProps } from './components/dropdown-legacy';
export { DropdownOverlay, DropdownProvider, DropdownTrigger } from './components/dropdown';
export { DropdownOverlay, DropdownProvider, DropdownTrigger, type IDropdownOverlayProps } 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';
@@ -42,7 +42,7 @@ export { type ISelectProps, Select } from './components/select';
export { type ISelectListProps, SelectList } from './components/select-list';
export { type ISegmentedProps, Segmented } from './components/segmented';
export { type ISliderProps, Slider } from './components/slider';
export { type ITooltipProps, type NullableTooltipRef, resizeObserverCtor, Tooltip } from './components/tooltip';
export { type ITooltipProps, Tooltip } from './components/tooltip';
export { filterLeafNode, findNodePathFromTree, findSubTreeFromPath, type ITreeNodeProps, type ITreeProps, mergeTreeSelected, Tree, TreeSelectionMode } from './components/tree';
export { Switch } from './components/switch';
export { type ILocale } from './locale/interface';
@@ -50,3 +50,5 @@ export { defaultTheme, greenTheme, themeInstance } from './themes';
export { DraggableList, type IDraggableListProps } from './components/draggable-list';
export { type ITextareaProps, Textarea } from './components/textarea';
export { type IMentionsProps, Mention, type MentionProps, Mentions } from './components/mentions';
export { clsx } from './helper/clsx';
export { resizeObserverCtor } from './helper/resize-observer';
-1
View File
@@ -1,3 +1,2 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@@ -1,3 +1,2 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
-1
View File
@@ -1,3 +1,2 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@@ -1,3 +1,2 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
-1
View File
@@ -1,3 +1,2 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
-1
View File
@@ -1,3 +1,2 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
-1
View File
@@ -1,3 +1,2 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@@ -1,3 +1,2 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@@ -1,3 +1,2 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@@ -1,3 +1,2 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@@ -1,3 +1,2 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
-1
View File
@@ -1,3 +1,2 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@@ -1,3 +1,2 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@@ -1,3 +1,2 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@@ -85,7 +85,7 @@ export const CellLinkPopupPure = (props: ICellLinkPopupPureProps) => {
<div className={styles.cellLinkType}>
{iconsMap[linkObj.type]}
</div>
<Tooltip showIfEllipsis title={linkObj.name}>
<Tooltip showIfEllipsis title={linkObj.name} asChild>
<span className={styles.cellLinkUrl}>{linkObj.name}</span>
</Tooltip>
</div>
-1
View File
@@ -1,3 +1,2 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
-1
View File
@@ -1,3 +1,2 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@@ -1,3 +1,2 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
-1
View File
@@ -1,3 +1,2 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@@ -192,12 +192,12 @@ export const DefinedNameContainer = () => {
</div>
<div className={styles.definedNameContainerItemFormulaOrRefString}>{definedName.formulaOrRefString}</div>
</div>
<Tooltip title={localeService.t('definedName.updateButton')} placement="top" style={{ pointerEvents: 'none' }}>
<Tooltip title={localeService.t('definedName.updateButton')} placement="top">
<div className={clsx(styles.definedNameContainerItemUpdate, styles.definedNameContainerItemShow)} onClick={() => { closeInsertOpenKeyEditor(definedName.id); }}>
<CheckMarkSingle />
</div>
</Tooltip>
<Tooltip title={localeService.t('definedName.deleteButton')} placement="top" style={{ pointerEvents: 'none' }}>
<Tooltip title={localeService.t('definedName.deleteButton')} placement="top">
<div className={clsx(styles.definedNameContainerItemDelete, styles.definedNameContainerItemShow)} onClick={() => { deleteDefinedName(definedName.id); }}>
<DeleteSingle />
</div>
@@ -96,7 +96,7 @@ export function DefinedNameOverlay(props: IDefinedNameOverlayProps) {
};
return (
<div className="univer-w-[300px] univer-bg-white">
<div className="univer-w-[300px]">
<ul
className={`
univer-max-h-[360px] univer-overflow-y-auto univer-scrollbar-thin univer-scrollbar-thumb-gray-300
@@ -109,6 +109,7 @@ export function DefinedNameOverlay(props: IDefinedNameOverlayProps) {
key={index}
className={`
univer-px-2 univer-cursor-pointer univer-transition-colors univer-duration-200
dark:hover:univer-bg-gray-600
hover:univer-bg-gray-100
`}
onClick={() => { focusDefinedName(definedName); }}
@@ -146,6 +147,7 @@ export function DefinedNameOverlay(props: IDefinedNameOverlayProps) {
<div
className={`
univer-p-2 univer-cursor-pointer univer-transition-colors univer-duration-200
dark:hover:univer-bg-gray-600
hover:univer-bg-gray-100
`}
onClick={openSlider}
@@ -308,7 +308,7 @@ export const SheetPermissionPanelList = () => {
<div className={styles.sheetPermissionListItemContent}>
<div className={styles.sheetPermissionListItemContentEdit}>
<Tooltip title={item.creator?.name}>
<Tooltip title={item.creator?.name ?? ''}>
<div>
<Avatar src={item.creator?.avatar} style={{ marginRight: 6 }} size={24} />
</div>
@@ -1,3 +1,2 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
-1
View File
@@ -1,3 +1,2 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@@ -1,3 +1,2 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
-1
View File
@@ -1,3 +1,2 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@@ -17,10 +17,10 @@
import type { ComponentType } from 'react';
import type { IMenuSchema } from '../../../services/menu/menu-manager.service';
import { LocaleService, useDependency } from '@univerjs/core';
import { MoreFunctionSingle } from '@univerjs/icons';
import clsx from 'clsx';
import { clsx } from '@univerjs/design';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { MoreFunctionSingle } from '@univerjs/icons';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { IMenuManagerService } from '../../../services/menu/menu-manager.service';
import { MenuManagerPosition, RibbonPosition } from '../../../services/menu/types';
import { ComponentContainer } from '../ComponentContainer';
@@ -40,12 +40,17 @@ export function Ribbon(props: IRibbonProps) {
const localeService = useDependency(LocaleService);
const toolbarRef = useRef<HTMLDivElement>(null);
const toolbarItemRefs = useRef<Record<string, { el: HTMLDivElement;key: string }>>({});
const toolbarItemRefs = useRef<Record<string, { el: HTMLSpanElement; key: string }>>({});
const [ribbon, setRibbon] = useState<IMenuSchema[]>([]);
const [category, setCategory] = useState<string>(RibbonPosition.START);
const [activatedTab, setActivatedTab] = useState<string>(RibbonPosition.START);
const [collapsedIds, setCollapsedIds] = useState<string[]>([]);
const handleSelectTab = useCallback((group: IMenuSchema) => {
toolbarItemRefs.current = {};
setActivatedTab(group.key);
}, []);
// subscribe to menu changes
useEffect(() => {
function getRibbon(): void {
@@ -64,22 +69,28 @@ export function Ribbon(props: IRibbonProps) {
// resize observer
useEffect(() => {
const observer = new ResizeObserver((entries) => {
const toolbar = entries[0].target;
const toolbarWidth = toolbar.clientWidth;
const toolbarItems = Object.values(toolbarItemRefs.current);
const collapsedIds: string[] = [];
let totalWidth = 90;
requestAnimationFrame(() => {
const toolbar = entries[0].target;
const toolbarWidth = toolbar.clientWidth;
const toolbarItems = Object.values(toolbarItemRefs.current);
const collapsedIds: string[] = [];
let totalWidth = 0;
for (const { el, key } of toolbarItems) {
if (!el) continue;
const allGroups = ribbon.find((group) => group.key === activatedTab)?.children ?? [];
const a = [];
totalWidth += el.clientWidth + 8;
if (totalWidth > toolbarWidth) {
collapsedIds.push(key);
for (const { el, key } of toolbarItems) {
if (!el) continue;
a.push(el);
totalWidth += el.getBoundingClientRect().width + 8;
if (totalWidth > toolbarWidth - 32 - 8 * (allGroups.length - 1)) {
collapsedIds.push(key);
}
}
}
setCollapsedIds(collapsedIds);
setCollapsedIds(collapsedIds);
});
});
if (toolbarRef.current) {
@@ -89,13 +100,10 @@ export function Ribbon(props: IRibbonProps) {
return () => {
observer.disconnect();
};
}, [ribbon]);
// Should the header when there is at least one header menu components or menu groups.
const hasHeaderMenu = useMemo(() => (headerMenuComponents && headerMenuComponents.size > 0) || ribbon.length > 1, [headerMenuComponents, ribbon]);
}, [ribbon, activatedTab]);
const activeGroup = useMemo(() => {
const allGroups = ribbon.find((group) => group.key === category)?.children ?? [];
const allGroups = ribbon.find((group) => group.key === activatedTab)?.children ?? [];
const visibleGroups: IMenuSchema[] = [];
const hiddenGroups: IMenuSchema[] = [];
@@ -115,7 +123,7 @@ export function Ribbon(props: IRibbonProps) {
visibleGroups,
hiddenGroups,
};
}, [ribbon, category, collapsedIds]);
}, [ribbon, activatedTab, collapsedIds]);
const fakeToolbarContent = useMemo(() => (
activeGroup.allGroups.map((groupItem) => (
@@ -124,13 +132,15 @@ export function Ribbon(props: IRibbonProps) {
child.item && (
<ToolbarItem
key={child.key}
ref={(ref) => {
toolbarItemRefs.current[child.key] = {
el: ref?.nativeElement as HTMLDivElement,
key: child.key,
};
}}
{...child.item}
ref={(ref) => {
if (ref?.el) {
toolbarItemRefs.current[child.key] = {
el: ref.el,
key: child.key,
};
}
}}
/>
)
))}
@@ -141,30 +151,40 @@ export function Ribbon(props: IRibbonProps) {
return (
<>
{/* header */}
{hasHeaderMenu && (
<header className={styles.headerbar}>
<div className={styles.menubar}>
{ribbon.length > 1 && ribbon.map((group) => (
<a
key={group.key}
className={clsx(styles.menubarItem, {
[styles.menubarItemActive]: group.key === category,
})}
onClick={() => {
setCategory(group.key);
}}
>
{localeService.t(group.key)}
</a>
))}
</div>
<header className="univer-relative univer-select-none">
<div
className={clsx(`
univer-animate-in univer-flex univer-gap-2 univer-justify-center univer-items-center univer-h-0
univer-transition-all univer-overflow-hidden
`, {
'univer-h-8 univer-slide-in-from-top-full': ribbon.length > 1,
})}
>
{ribbon.length > 1 && ribbon.map((group) => (
<a
key={group.key}
className={clsx(`
univer-text-gray-700 univer-text-sm univer-rounded univer-px-2 univer-py-0.5
univer-cursor-pointer univer-transition-colors univer-box-border
hover:univer-bg-gray-300
`, {
'univer-bg-primary-500 univer-text-white hover:!univer-bg-primary-500': group.key === activatedTab,
})}
onClick={() => handleSelectTab(group)}
>
{localeService.t(group.key)}
</a>
))}
</div>
{(headerMenuComponents && headerMenuComponents.size > 0) && (
<div className={styles.headerMenu}>
<ComponentContainer components={headerMenuComponents} />
</div>
</header>
)}
)}
</header>
<section role="ribbon" className={styles.toolbar}>
<section role="toolbar" className={styles.toolbar}>
<div className={styles.toolbarContainer}>
{activeGroup.visibleGroups.map((groupItem) => (groupItem.children?.length || groupItem.item) && (
<div key={groupItem.key} className={styles.toolbarGroup}>
@@ -182,7 +202,6 @@ export function Ribbon(props: IRibbonProps) {
{collapsedIds.length > 0 && (
<TooltipWrapper title={localeService.t('ribbon.more')} placement="bottom">
<DropdownWrapper
forceRender
overlay={(
<div className={styles.toolbarMoreContainer}>
{activeGroup.hiddenGroups.map((groupItem) => (
@@ -210,6 +229,7 @@ export function Ribbon(props: IRibbonProps) {
{/* fake toolbar for calculating overflow width */}
<div
aria-hidden
ref={toolbarRef}
className={styles.toolbarContainer}
style={{
@@ -217,9 +237,6 @@ export function Ribbon(props: IRibbonProps) {
top: -9999,
left: 0,
right: 0,
// top: 0,
// left: 0,
// opacity: 1,
}}
>
{fakeToolbarContent}
@@ -15,8 +15,8 @@
*/
import type { IDropdownLegacyProps } from '@univerjs/design';
import type { Ref } from 'react';
import type { IDisplayMenuItem, IMenuItem, IMenuSelectorItem, IValueOption } from '../../../services/menu/menu';
import type { ITooltipWrapperRef } from './TooltipButtonWrapper';
import { ICommandService, LocaleService, useDependency } from '@univerjs/core';
import { MoreDownSingle } from '@univerjs/icons';
import clsx from 'clsx';
@@ -33,9 +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?: IDropdownLegacyProps['align'] }, ref: Ref<any>) => {
const { align } = props;
export const ToolbarItem = forwardRef<ITooltipWrapperRef, IDisplayMenuItem<IMenuItem> & { align?: IDropdownLegacyProps['align'] }>((props, ref) => {
const localeService = useDependency(LocaleService);
const commandService = useDependency(ICommandService);
const layoutService = useDependency(ILayoutService);
@@ -109,13 +107,23 @@ export const ToolbarItem = forwardRef((props: IDisplayMenuItem<IMenuItem> & { al
if (menuType === MenuItemType.BUTTON_SELECTOR) {
return (
<div
className={clsx(styles.toolbarItemSelectButton, {
[styles.toolbarItemSelectButtonDisabled]: disabled,
[styles.toolbarItemSelectButtonActivated]: activated,
className={clsx(styles.toolbarItemSelectButton, `
univer-pr-5 univer-h-6 univer-group univer-transition-colors
hover:univer-bg-gray-100
`, {
'univer-cursor-not-allowed univer-text-gray-200 univer-pointer-events-none': disabled,
})}
data-disabled={disabled}
>
<div className={styles.toolbarItemSelectButtonLabel} onClick={handleClick}>
<div
className={clsx(styles.toolbarItemSelectButtonLabel, `
univer-transition-colors
hover:univer-bg-gray-200
`, {
'univer-bg-gray-200': activated,
})}
onClick={handleClick}
>
<CustomLabel
icon={iconToDisplay}
title={title!}
@@ -124,11 +132,10 @@ export const ToolbarItem = forwardRef((props: IDisplayMenuItem<IMenuItem> & { al
onChange={handleSelectionsValueChange}
/>
</div>
<DropdownWrapper
disabled={disabled}
align={align ?? {
targetOffset: [32, -12],
}}
offset={{ x: -24 }}
overlay={(
<Menu
overViewport="scroll"
@@ -140,13 +147,18 @@ export const ToolbarItem = forwardRef((props: IDisplayMenuItem<IMenuItem> & { al
)}
>
<div
className={clsx(styles.toolbarItemSelectButtonArrow, {
[styles.toolbarItemSelectButtonArrowDisabled]: disabled,
[styles.toolbarItemSelectButtonArrowActivated]: activated,
className={clsx(`
univer-absolute univer-top-0 univer-right-0 univer-box-border text-gray-400
univer-transition-colors univer-w-5 univer-flex univer-justify-center univer-items-center
univer-h-6
hover:univer-bg-gray-200
`, {
'univer-cursor-not-allowed univer-text-gray-400 univer-pointer-events-none': disabled,
'univer-bg-gray-200': activated,
})}
data-disabled={disabled}
>
<MoreDownSingle style={{ height: '100%' }} />
<MoreDownSingle className="univer-text-gray-400" />
</div>
</DropdownWrapper>
</div>
@@ -167,7 +179,7 @@ export const ToolbarItem = forwardRef((props: IDisplayMenuItem<IMenuItem> & { al
>
<div
className={clsx(styles.toolbarItemSelect, {
[styles.toolbarItemSelectDisabled]: disabled,
'univer-cursor-not-allowed univer-text-gray-200 univer-pointer-events-none': disabled,
[styles.toolbarItemSelectActivated]: activated,
})}
>
@@ -180,10 +192,10 @@ export const ToolbarItem = forwardRef((props: IDisplayMenuItem<IMenuItem> & { al
/>
<div
className={clsx(styles.toolbarItemSelectArrow, {
[styles.toolbarItemSelectArrowDisabled]: disabled,
'univer-cursor-not-allowed univer-text-gray-200 univer-pointer-events-none': disabled,
})}
>
<MoreDownSingle />
<MoreDownSingle className="univer-text-gray-400" />
</div>
</div>
</DropdownWrapper>
@@ -228,7 +240,11 @@ export const ToolbarItem = forwardRef((props: IDisplayMenuItem<IMenuItem> & { al
}
return !hidden && (
<TooltipWrapper ref={ref} title={tooltipTitle} placement="bottom">
<TooltipWrapper
ref={ref}
title={tooltipTitle}
placement="bottom"
>
{renderItem()}
</TooltipWrapper>
);
@@ -14,17 +14,22 @@
* limitations under the License.
*/
import type { IDropdownLegacyProps, ITooltipProps, NullableTooltipRef } from '@univerjs/design';
import { DropdownLegacy, Tooltip } from '@univerjs/design';
import React, { createContext, forwardRef, useContext, useMemo, useState } from 'react';
import type { IDropdownOverlayProps, ITooltipProps } from '@univerjs/design';
import { DropdownOverlay, DropdownProvider, DropdownTrigger, Tooltip } from '@univerjs/design';
import React, { createContext, forwardRef, useContext, useImperativeHandle, useMemo, useRef, useState } from 'react';
const TooltipWrapperContext = createContext({
dropdownVisible: false,
setDropdownVisible: (_visible: boolean) => {},
});
export const TooltipWrapper = forwardRef<NullableTooltipRef, ITooltipProps>((props, ref) => {
export interface ITooltipWrapperRef {
el: HTMLSpanElement | null;
}
export const TooltipWrapper = forwardRef<ITooltipWrapperRef, ITooltipProps>((props, ref) => {
const { children, ...tooltipProps } = props;
const spanRef = useRef<HTMLSpanElement>(null);
const [tooltipVisible, setTooltipVisible] = useState(false);
const [dropdownVisible, setDropdownVisible] = useState(false);
@@ -48,14 +53,17 @@ export const TooltipWrapper = forwardRef<NullableTooltipRef, ITooltipProps>((pro
setDropdownVisible: handleChangeDropdownVisible,
}), [dropdownVisible]);
useImperativeHandle(ref, () => ({
el: spanRef.current,
}));
return (
<Tooltip
ref={ref}
{...tooltipProps}
visible={tooltipVisible}
onVisibleChange={handleChangeTooltipVisible}
>
<span>
<span ref={spanRef}>
<TooltipWrapperContext.Provider value={contextValue}>
{children}
</TooltipWrapperContext.Provider>
@@ -64,9 +72,7 @@ export const TooltipWrapper = forwardRef<NullableTooltipRef, ITooltipProps>((pro
);
});
export function DropdownWrapper(props: IDropdownLegacyProps) {
const { children, ...dropdownProps } = props;
export function DropdownWrapper({ children, overlay, disabled, offset }: IDropdownOverlayProps & { overlay: React.ReactNode; disabled?: boolean }) {
const { setDropdownVisible } = useContext(TooltipWrapperContext);
function handleVisibleChange(visible: boolean) {
@@ -74,13 +80,18 @@ export function DropdownWrapper(props: IDropdownLegacyProps) {
}
return (
<DropdownLegacy
onVisibleChange={handleVisibleChange}
{...dropdownProps}
>
<div onClick={(e) => e.stopPropagation()}>
{children}
</div>
</DropdownLegacy>
<DropdownProvider disabled={disabled} onVisibleChange={handleVisibleChange}>
<DropdownTrigger>
<div className="univer-h-full" onClick={(e) => e.stopPropagation()}>
{children}
</div>
</DropdownTrigger>
<DropdownOverlay offset={offset}>
{/* TODO: When the new Menu Component is ready, plz remove the univer-theme class */}
<div className="univer-grid univer-gap-2 univer-theme">
{overlay}
</div>
</DropdownOverlay>
</DropdownProvider>
);
}
@@ -1,73 +1,5 @@
@toolbar-btn-size: 24px;
.headerbar {
user-select: none;
position: relative;
> .header-menu {
position: absolute;
top: 0;
right: 0;
display: flex;
box-sizing: border-box;
height: 100%;
> div {
user-select: none;
display: inline-flex;
align-items: center;
height: 28px;
margin-left: 4px;
padding: 2px 4px;
text-align: center;
border-radius: 4px;
&:hover {
background-color: rgb(var(--grey-300));
}
}
}
}
.menubar {
display: flex;
gap: var(--margin-xs);
align-items: center;
justify-content: center;
height: 32px;
&-item {
cursor: pointer;
display: block;
padding: var(--padding-xs) var(--padding-sm);
font-size: var(--font-size-xs);
font-weight: 400;
border-radius: var(--border-radius-base);
transition: all 0.1s ease-in-out;
&:hover:not(&-active) {
background: rgb(var(--grey-300));
}
&-active {
color: rgb(var(--color-white));
background: rgb(var(--primary-color));
}
}
}
.toolbar {
user-select: none;
@@ -162,12 +94,8 @@
overflow: hidden;
display: flex;
gap: var(--margin-xs);
align-items: center;
height: @toolbar-btn-size;
padding-right: 18px;
font-size: var(--font-size-lg);
border-radius: var(--border-radius-base);
@@ -192,20 +120,6 @@
background-color: rgb(var(--grey-100));
}
&:hover {
background-color: rgb(var(--grey-100));
.toolbar {
&-item {
&-select-button {
&-label {
background-color: rgb(var(--grey-100));
}
}
}
}
}
&-label {
position: relative;
z-index: 1;
@@ -215,31 +129,6 @@
height: 100%;
padding: 0 var(--padding-xs);
&:hover {
background-color: rgb(var(--grey-300)) !important;
}
}
&-arrow {
position: absolute;
top: 0;
left: 0;
display: flex;
align-items: center;
justify-content: flex-end;
box-sizing: border-box;
width: 100%;
height: 100%;
padding-right: var(--padding-xs);
color: rgb(var(--grey-400));
&:hover {
background-color: rgb(var(--grey-300));
}
}
}
@@ -262,16 +151,6 @@
&-disabled {
cursor: not-allowed;
color: rgb(var(--grey-200));
&.toolbar-item-select-activated {
color: rgba(var(--text-color), 0.25);
&:hover {
background-color: rgb(var(--grey-100));
}
}
}
&-activated {
background-color: rgb(var(--grey-100));
}
&:hover {
@@ -297,10 +176,6 @@
}
}
}
&-activated {
background-color: rgb(var(--grey-100));
}
}
}
}
-1
View File
@@ -1,3 +1,2 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
+371
View File
@@ -1152,6 +1152,9 @@ importers:
packages/design:
dependencies:
'@radix-ui/react-tooltip':
specifier: ^1.1.6
version: 1.1.6(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@rc-component/color-picker':
specifier: ^2.0.1
version: 2.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -4569,6 +4572,206 @@ packages:
'@protobufjs/utf8@1.1.0':
resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==}
'@radix-ui/primitive@1.1.1':
resolution: {integrity: sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==}
'@radix-ui/react-arrow@1.1.1':
resolution: {integrity: sha512-NaVpZfmv8SKeZbn4ijN2V3jlHA9ngBG16VnIIm22nUR0Yk8KUALyBxT3KYEUnNuch9sTE8UTsS3whzBgKOL30w==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-compose-refs@1.1.1':
resolution: {integrity: sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@radix-ui/react-context@1.1.1':
resolution: {integrity: sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@radix-ui/react-dismissable-layer@1.1.3':
resolution: {integrity: sha512-onrWn/72lQoEucDmJnr8uczSNTujT0vJnA/X5+3AkChVPowr8n1yvIKIabhWyMQeMvvmdpsvcyDqx3X1LEXCPg==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-id@1.1.0':
resolution: {integrity: sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@radix-ui/react-popper@1.2.1':
resolution: {integrity: sha512-3kn5Me69L+jv82EKRuQCXdYyf1DqHwD2U/sxoNgBGCB7K9TRc3bQamQ+5EPM9EvyPdli0W41sROd+ZU1dTCztw==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-portal@1.1.3':
resolution: {integrity: sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-presence@1.1.2':
resolution: {integrity: sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-primitive@2.0.1':
resolution: {integrity: sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-slot@1.1.1':
resolution: {integrity: sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@radix-ui/react-tooltip@1.1.6':
resolution: {integrity: sha512-TLB5D8QLExS1uDn7+wH/bjEmRurNMTzNrtq7IjaS4kjion9NtzsTGkvR5+i7yc9q01Pi2KMM2cN3f8UG4IvvXA==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-use-callback-ref@1.1.0':
resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@radix-ui/react-use-controllable-state@1.1.0':
resolution: {integrity: sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@radix-ui/react-use-escape-keydown@1.1.0':
resolution: {integrity: sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@radix-ui/react-use-layout-effect@1.1.0':
resolution: {integrity: sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@radix-ui/react-use-rect@1.1.0':
resolution: {integrity: sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@radix-ui/react-use-size@1.1.0':
resolution: {integrity: sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@radix-ui/react-visually-hidden@1.1.1':
resolution: {integrity: sha512-vVfA2IZ9q/J+gEamvj761Oq1FpWgCDaNOOIfbPVp2MVPLEomUr5+Vf7kJGwQ24YxZSlQVar7Bes8kyTo5Dshpg==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/rect@1.1.0':
resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==}
'@rc-component/color-picker@2.0.1':
resolution: {integrity: sha512-WcZYwAThV/b2GISQ8F+7650r5ZZJ043E57aVBFkQ+kSY4C6wdofXgB0hBx+GPGpIU0Z81eETNoDUJMr7oy/P8Q==}
peerDependencies:
@@ -11705,6 +11908,174 @@ snapshots:
'@protobufjs/utf8@1.1.0': {}
'@radix-ui/primitive@1.1.1': {}
'@radix-ui/react-arrow@1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.12
'@types/react-dom': 18.3.1
'@radix-ui/react-compose-refs@1.1.1(@types/react@18.3.12)(react@18.3.1)':
dependencies:
react: 18.3.1
optionalDependencies:
'@types/react': 18.3.12
'@radix-ui/react-context@1.1.1(@types/react@18.3.12)(react@18.3.1)':
dependencies:
react: 18.3.1
optionalDependencies:
'@types/react': 18.3.12
'@radix-ui/react-dismissable-layer@1.1.3(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/primitive': 1.1.1
'@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.12)(react@18.3.1)
'@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.12)(react@18.3.1)
'@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@18.3.12)(react@18.3.1)
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.12
'@types/react-dom': 18.3.1
'@radix-ui/react-id@1.1.0(@types/react@18.3.12)(react@18.3.1)':
dependencies:
'@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.12)(react@18.3.1)
react: 18.3.1
optionalDependencies:
'@types/react': 18.3.12
'@radix-ui/react-popper@1.2.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@floating-ui/react-dom': 2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-arrow': 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.12)(react@18.3.1)
'@radix-ui/react-context': 1.1.1(@types/react@18.3.12)(react@18.3.1)
'@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.12)(react@18.3.1)
'@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.12)(react@18.3.1)
'@radix-ui/react-use-rect': 1.1.0(@types/react@18.3.12)(react@18.3.1)
'@radix-ui/react-use-size': 1.1.0(@types/react@18.3.12)(react@18.3.1)
'@radix-ui/rect': 1.1.0
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.12
'@types/react-dom': 18.3.1
'@radix-ui/react-portal@1.1.3(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.12)(react@18.3.1)
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.12
'@types/react-dom': 18.3.1
'@radix-ui/react-presence@1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.12)(react@18.3.1)
'@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.12)(react@18.3.1)
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.12
'@types/react-dom': 18.3.1
'@radix-ui/react-primitive@2.0.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/react-slot': 1.1.1(@types/react@18.3.12)(react@18.3.1)
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.12
'@types/react-dom': 18.3.1
'@radix-ui/react-slot@1.1.1(@types/react@18.3.12)(react@18.3.1)':
dependencies:
'@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.12)(react@18.3.1)
react: 18.3.1
optionalDependencies:
'@types/react': 18.3.12
'@radix-ui/react-tooltip@1.1.6(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/primitive': 1.1.1
'@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.12)(react@18.3.1)
'@radix-ui/react-context': 1.1.1(@types/react@18.3.12)(react@18.3.1)
'@radix-ui/react-dismissable-layer': 1.1.3(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-id': 1.1.0(@types/react@18.3.12)(react@18.3.1)
'@radix-ui/react-popper': 1.2.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-portal': 1.1.3(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-presence': 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-slot': 1.1.1(@types/react@18.3.12)(react@18.3.1)
'@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.12)(react@18.3.1)
'@radix-ui/react-visually-hidden': 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.12
'@types/react-dom': 18.3.1
'@radix-ui/react-use-callback-ref@1.1.0(@types/react@18.3.12)(react@18.3.1)':
dependencies:
react: 18.3.1
optionalDependencies:
'@types/react': 18.3.12
'@radix-ui/react-use-controllable-state@1.1.0(@types/react@18.3.12)(react@18.3.1)':
dependencies:
'@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.12)(react@18.3.1)
react: 18.3.1
optionalDependencies:
'@types/react': 18.3.12
'@radix-ui/react-use-escape-keydown@1.1.0(@types/react@18.3.12)(react@18.3.1)':
dependencies:
'@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.12)(react@18.3.1)
react: 18.3.1
optionalDependencies:
'@types/react': 18.3.12
'@radix-ui/react-use-layout-effect@1.1.0(@types/react@18.3.12)(react@18.3.1)':
dependencies:
react: 18.3.1
optionalDependencies:
'@types/react': 18.3.12
'@radix-ui/react-use-rect@1.1.0(@types/react@18.3.12)(react@18.3.1)':
dependencies:
'@radix-ui/rect': 1.1.0
react: 18.3.1
optionalDependencies:
'@types/react': 18.3.12
'@radix-ui/react-use-size@1.1.0(@types/react@18.3.12)(react@18.3.1)':
dependencies:
'@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.12)(react@18.3.1)
react: 18.3.1
optionalDependencies:
'@types/react': 18.3.12
'@radix-ui/react-visually-hidden@1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.12
'@types/react-dom': 18.3.1
'@radix-ui/rect@1.1.0': {}
'@rc-component/color-picker@2.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@ant-design/fast-color': 2.0.6