mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 12:28:42 +00:00
feat: modify pivot style & add operation menu to pivot item (#1726)
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { CssBaseline } from '@mui/material';
|
||||
import {
|
||||
alpha,
|
||||
createTheme as createMuiTheme,
|
||||
css,
|
||||
keyframes,
|
||||
@@ -11,7 +12,7 @@ import { useMemo } from 'react';
|
||||
|
||||
import type { AffineTheme } from './types';
|
||||
|
||||
export { css, keyframes, styled };
|
||||
export { alpha, css, keyframes, styled };
|
||||
|
||||
export const ThemeProvider = ({
|
||||
theme,
|
||||
|
||||
@@ -6,12 +6,13 @@ export type IconMenuProps = PropsWithChildren<{
|
||||
isDir?: boolean;
|
||||
icon?: ReactElement;
|
||||
iconSize?: [number, number];
|
||||
disabled?: boolean;
|
||||
}> &
|
||||
HTMLAttributes<HTMLButtonElement>;
|
||||
|
||||
export const MenuItem = forwardRef<HTMLButtonElement, IconMenuProps>(
|
||||
({ isDir = false, icon, iconSize, children, ...props }, ref) => {
|
||||
const [iconWidth, iconHeight] = iconSize || [16, 16];
|
||||
const [iconWidth, iconHeight] = iconSize || [20, 20];
|
||||
return (
|
||||
<StyledMenuItem ref={ref} {...props}>
|
||||
{icon &&
|
||||
@@ -19,7 +20,7 @@ export const MenuItem = forwardRef<HTMLButtonElement, IconMenuProps>(
|
||||
width: iconWidth,
|
||||
height: iconHeight,
|
||||
style: {
|
||||
marginRight: 14,
|
||||
marginRight: 12,
|
||||
...icon.props?.style,
|
||||
},
|
||||
})}
|
||||
|
||||
20
packages/component/src/ui/menu/PureMenu.tsx
Normal file
20
packages/component/src/ui/menu/PureMenu.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { CSSProperties } from 'react';
|
||||
|
||||
import type { PurePopperProps } from '../popper';
|
||||
import { PurePopper } from '../popper';
|
||||
import { StyledMenuWrapper } from './styles';
|
||||
|
||||
export const PureMenu = ({
|
||||
children,
|
||||
placement,
|
||||
width,
|
||||
...otherProps
|
||||
}: PurePopperProps & { width?: CSSProperties['width'] }) => {
|
||||
return (
|
||||
<PurePopper placement={placement} {...otherProps}>
|
||||
<StyledMenuWrapper width={width} placement={placement}>
|
||||
{children}
|
||||
</StyledMenuWrapper>
|
||||
</PurePopper>
|
||||
);
|
||||
};
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './Menu';
|
||||
// export { StyledMenuItem as MenuItem } from './styles';
|
||||
export * from './MenuItem';
|
||||
export * from './PureMenu';
|
||||
|
||||
@@ -28,7 +28,8 @@ export const StyledArrow = styled(ArrowRightSmallIcon)({
|
||||
|
||||
export const StyledMenuItem = styled('button')<{
|
||||
isDir?: boolean;
|
||||
}>(({ theme, isDir = false }) => {
|
||||
disabled?: boolean;
|
||||
}>(({ theme, isDir = false, disabled = false }) => {
|
||||
return {
|
||||
width: '100%',
|
||||
borderRadius: '5px',
|
||||
@@ -39,10 +40,25 @@ export const StyledMenuItem = styled('button')<{
|
||||
cursor: isDir ? 'pointer' : '',
|
||||
position: 'relative',
|
||||
backgroundColor: 'transparent',
|
||||
color: theme.colors.textColor,
|
||||
':hover': {
|
||||
color: theme.colors.primaryColor,
|
||||
backgroundColor: theme.colors.hoverBackground,
|
||||
color: disabled ? theme.colors.disableColor : theme.colors.textColor,
|
||||
svg: {
|
||||
color: disabled ? theme.colors.disableColor : theme.colors.iconColor,
|
||||
},
|
||||
...(disabled
|
||||
? {
|
||||
cursor: 'not-allowed',
|
||||
pointerEvents: 'none',
|
||||
}
|
||||
: {}),
|
||||
|
||||
':hover': disabled
|
||||
? {}
|
||||
: {
|
||||
color: theme.colors.primaryColor,
|
||||
backgroundColor: theme.colors.hoverBackground,
|
||||
svg: {
|
||||
color: theme.colors.primaryColor,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -11,7 +11,7 @@ export const PopperArrow = forwardRef<HTMLElement, PopperArrowProps>(
|
||||
);
|
||||
|
||||
const getArrowStyle = (
|
||||
placement: PopperArrowProps['placement'],
|
||||
placement: PopperArrowProps['placement'] = 'bottom',
|
||||
backgroundColor: CSSProperties['backgroundColor']
|
||||
) => {
|
||||
if (placement.indexOf('bottom') === 0) {
|
||||
@@ -72,7 +72,7 @@ const getArrowStyle = (
|
||||
};
|
||||
|
||||
const StyledArrow = styled('span')<{
|
||||
placement: PopperArrowProps['placement'];
|
||||
placement?: PopperArrowProps['placement'];
|
||||
}>(({ placement, theme }) => {
|
||||
return {
|
||||
position: 'absolute',
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import ClickAwayListener from '@mui/base/ClickAwayListener';
|
||||
import PopperUnstyled from '@mui/base/PopperUnstyled';
|
||||
import Grow from '@mui/material/Grow';
|
||||
import type { CSSProperties, PointerEvent } from 'react';
|
||||
import {
|
||||
cloneElement,
|
||||
useEffect,
|
||||
@@ -33,6 +34,8 @@ export const Popper = ({
|
||||
popperHandlerRef,
|
||||
onClick,
|
||||
onClickAway,
|
||||
onPointerEnter,
|
||||
onPointerLeave,
|
||||
...popperProps
|
||||
}: PopperProps) => {
|
||||
const [anchorEl, setAnchorEl] = useState<VirtualElement>();
|
||||
@@ -58,7 +61,8 @@ export const Popper = ({
|
||||
);
|
||||
}, [trigger]);
|
||||
|
||||
const onPointerEnterHandler = () => {
|
||||
const onPointerEnterHandler = (e: PointerEvent<HTMLDivElement>) => {
|
||||
onPointerEnter?.(e);
|
||||
if (!hasHoverTrigger || visibleControlledByParent) {
|
||||
return;
|
||||
}
|
||||
@@ -69,7 +73,9 @@ export const Popper = ({
|
||||
}, pointerEnterDelay);
|
||||
};
|
||||
|
||||
const onPointerLeaveHandler = () => {
|
||||
const onPointerLeaveHandler = (e: PointerEvent<HTMLDivElement>) => {
|
||||
onPointerLeave?.(e);
|
||||
|
||||
if (!hasHoverTrigger || visibleControlledByParent) {
|
||||
return;
|
||||
}
|
||||
@@ -151,7 +157,7 @@ export const Popper = ({
|
||||
onPointerLeave={onPointerLeaveHandler}
|
||||
style={popoverStyle}
|
||||
className={popoverClassName}
|
||||
onClick={e => {
|
||||
onClick={() => {
|
||||
if (hasClickTrigger && !visibleControlledByParent) {
|
||||
setVisible(false);
|
||||
}
|
||||
@@ -178,11 +184,11 @@ const Container = styled('div')({
|
||||
display: 'contents',
|
||||
});
|
||||
|
||||
const BasicStyledPopper = styled(PopperUnstyled, {
|
||||
export const BasicStyledPopper = styled(PopperUnstyled, {
|
||||
shouldForwardProp: (propName: string) =>
|
||||
!['zIndex'].some(name => name === propName),
|
||||
})<{
|
||||
zIndex?: number;
|
||||
zIndex?: CSSProperties['zIndex'];
|
||||
}>(({ zIndex, theme }) => {
|
||||
return {
|
||||
zIndex: zIndex ?? theme.zIndex.popover,
|
||||
|
||||
67
packages/component/src/ui/popper/PurePopper.tsx
Normal file
67
packages/component/src/ui/popper/PurePopper.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import type { PopperUnstyledProps } from '@mui/base/PopperUnstyled';
|
||||
import Grow from '@mui/material/Grow';
|
||||
import type { CSSProperties, PropsWithChildren } from 'react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { PopperArrow } from './PopoverArrow';
|
||||
import { BasicStyledPopper } from './Popper';
|
||||
import { PopperWrapper } from './styles';
|
||||
|
||||
export type PurePopperProps = {
|
||||
zIndex?: CSSProperties['zIndex'];
|
||||
|
||||
offset?: [number, number];
|
||||
|
||||
showArrow?: boolean;
|
||||
} & PopperUnstyledProps &
|
||||
PropsWithChildren;
|
||||
|
||||
export const PurePopper = (props: PurePopperProps) => {
|
||||
const {
|
||||
children,
|
||||
zIndex,
|
||||
offset,
|
||||
showArrow = false,
|
||||
modifiers = [],
|
||||
placement,
|
||||
...otherProps
|
||||
} = props;
|
||||
const [arrowRef, setArrowRef] = useState<HTMLElement | null>();
|
||||
|
||||
// @ts-ignore
|
||||
return (
|
||||
<BasicStyledPopper
|
||||
zIndex={zIndex}
|
||||
transition
|
||||
modifiers={[
|
||||
{
|
||||
name: 'offset',
|
||||
options: {
|
||||
offset,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'arrow',
|
||||
enabled: showArrow,
|
||||
options: {
|
||||
element: arrowRef,
|
||||
},
|
||||
},
|
||||
...modifiers,
|
||||
]}
|
||||
placement={placement}
|
||||
{...otherProps}
|
||||
>
|
||||
{({ TransitionProps }) => (
|
||||
<Grow {...TransitionProps}>
|
||||
<PopperWrapper>
|
||||
{showArrow && (
|
||||
<PopperArrow placement={placement} ref={setArrowRef} />
|
||||
)}
|
||||
{children}
|
||||
</PopperWrapper>
|
||||
</Grow>
|
||||
)}
|
||||
</BasicStyledPopper>
|
||||
);
|
||||
};
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './interface';
|
||||
export * from './Popper';
|
||||
export * from './PurePopper';
|
||||
|
||||
@@ -13,7 +13,7 @@ export type PopperHandler = {
|
||||
};
|
||||
|
||||
export type PopperArrowProps = {
|
||||
placement: PopperPlacementType;
|
||||
placement?: PopperPlacementType;
|
||||
};
|
||||
|
||||
export type PopperProps = {
|
||||
|
||||
7
packages/component/src/ui/popper/styles.ts
Normal file
7
packages/component/src/ui/popper/styles.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { styled } from '../../styles';
|
||||
|
||||
export const PopperWrapper = styled('div')(() => {
|
||||
return {
|
||||
position: 'relative',
|
||||
};
|
||||
});
|
||||
@@ -1,13 +1,18 @@
|
||||
import { useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useDrag, useDrop } from 'react-dnd';
|
||||
|
||||
import {
|
||||
StyledCollapse,
|
||||
StyledNodeLine,
|
||||
StyledTreeNodeContainer,
|
||||
StyledTreeNodeItem,
|
||||
StyledTreeNodeWrapper,
|
||||
} from './styles';
|
||||
import type { Node, NodeLIneProps, TreeNodeProps } from './types';
|
||||
import type {
|
||||
Node,
|
||||
NodeLIneProps,
|
||||
TreeNodeItemProps,
|
||||
TreeNodeProps,
|
||||
} from './types';
|
||||
|
||||
const NodeLine = <N,>({
|
||||
node,
|
||||
@@ -39,30 +44,21 @@ const NodeLine = <N,>({
|
||||
|
||||
return <StyledNodeLine ref={drop} show={isOver && allowDrop} isTop={isTop} />;
|
||||
};
|
||||
|
||||
export const TreeNode = <N,>({
|
||||
const TreeNodeItem = <N,>({
|
||||
node,
|
||||
index,
|
||||
allDrop = true,
|
||||
allowDrop,
|
||||
collapsed,
|
||||
setCollapsed,
|
||||
...otherProps
|
||||
}: TreeNodeProps<N>) => {
|
||||
const { onAdd, onDelete, onDrop, indent } = otherProps;
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
|
||||
const [{ isDragging }, drag] = useDrag(() => ({
|
||||
type: 'node',
|
||||
item: node,
|
||||
collect: monitor => ({
|
||||
isDragging: monitor.isDragging(),
|
||||
}),
|
||||
}));
|
||||
}: TreeNodeItemProps<N>) => {
|
||||
const { onAdd, onDelete, onDrop } = otherProps;
|
||||
|
||||
const [{ canDrop, isOver }, drop] = useDrop(
|
||||
() => ({
|
||||
accept: 'node',
|
||||
drop: (item: Node<N>, monitor) => {
|
||||
const didDrop = monitor.didDrop();
|
||||
if (didDrop || item.id === node.id || !allDrop) {
|
||||
if (didDrop || item.id === node.id || !allowDrop) {
|
||||
return;
|
||||
}
|
||||
onDrop?.(item, node, {
|
||||
@@ -73,42 +69,75 @@ export const TreeNode = <N,>({
|
||||
},
|
||||
collect: monitor => ({
|
||||
isOver: monitor.isOver(),
|
||||
canDrop: monitor.canDrop(),
|
||||
canDrop: monitor.canDrop() && allowDrop,
|
||||
}),
|
||||
}),
|
||||
[onDrop, allDrop]
|
||||
[onDrop, allowDrop]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOver && canDrop) {
|
||||
setCollapsed(false);
|
||||
}
|
||||
}, [isOver, canDrop]);
|
||||
|
||||
return (
|
||||
<div ref={drop}>
|
||||
{node.render?.(node, {
|
||||
isOver: !!(isOver && canDrop),
|
||||
onAdd: () => onAdd?.(node),
|
||||
onDelete: () => onDelete?.(node),
|
||||
collapsed,
|
||||
setCollapsed,
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const TreeNode = <N,>({
|
||||
node,
|
||||
index,
|
||||
allowDrop = true,
|
||||
...otherProps
|
||||
}: TreeNodeProps<N>) => {
|
||||
const { indent } = otherProps;
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
|
||||
const [{ isDragging }, drag] = useDrag(() => ({
|
||||
type: 'node',
|
||||
item: node,
|
||||
collect: monitor => ({
|
||||
isDragging: monitor.isDragging(),
|
||||
}),
|
||||
}));
|
||||
|
||||
return (
|
||||
<StyledTreeNodeContainer ref={drag} isDragging={isDragging}>
|
||||
<StyledTreeNodeItem
|
||||
ref={drop}
|
||||
isOver={isOver && !isDragging}
|
||||
canDrop={canDrop}
|
||||
>
|
||||
<StyledTreeNodeWrapper>
|
||||
{index === 0 && (
|
||||
<NodeLine
|
||||
node={node}
|
||||
{...otherProps}
|
||||
allowDrop={!isDragging && allDrop}
|
||||
allowDrop={!isDragging && allowDrop}
|
||||
isTop={true}
|
||||
/>
|
||||
)}
|
||||
{node.render?.(node, {
|
||||
onAdd: () => onAdd?.(node),
|
||||
onDelete: () => onDelete?.(node),
|
||||
collapsed,
|
||||
setCollapsed,
|
||||
})}
|
||||
<TreeNodeItem
|
||||
node={node}
|
||||
index={index}
|
||||
allowDrop={allowDrop}
|
||||
collapsed={collapsed}
|
||||
setCollapsed={setCollapsed}
|
||||
{...otherProps}
|
||||
/>
|
||||
{(!node.children?.length || collapsed) && (
|
||||
<NodeLine
|
||||
node={node}
|
||||
{...otherProps}
|
||||
allowDrop={!isDragging && allDrop}
|
||||
allowDrop={!isDragging && allowDrop}
|
||||
/>
|
||||
)}
|
||||
</StyledTreeNodeItem>
|
||||
|
||||
</StyledTreeNodeWrapper>
|
||||
<StyledCollapse in={!collapsed} indent={indent}>
|
||||
{node.children &&
|
||||
node.children.map((childNode, index) => (
|
||||
@@ -116,7 +145,7 @@ export const TreeNode = <N,>({
|
||||
key={childNode.id}
|
||||
node={childNode}
|
||||
index={index}
|
||||
allDrop={isDragging ? false : allDrop}
|
||||
allowDrop={isDragging ? false : allowDrop}
|
||||
{...otherProps}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import MuiCollapse from '@mui/material/Collapse';
|
||||
import type { CSSProperties } from 'react';
|
||||
|
||||
import { styled } from '../../styles';
|
||||
import { alpha, styled } from '../../styles';
|
||||
|
||||
export const StyledCollapse = styled(MuiCollapse)<{
|
||||
indent?: CSSProperties['paddingLeft'];
|
||||
@@ -10,12 +10,8 @@ export const StyledCollapse = styled(MuiCollapse)<{
|
||||
paddingLeft: indent,
|
||||
};
|
||||
});
|
||||
export const StyledTreeNodeItem = styled('div')<{
|
||||
isOver?: boolean;
|
||||
canDrop?: boolean;
|
||||
}>(({ isOver, canDrop, theme }) => {
|
||||
export const StyledTreeNodeWrapper = styled('div')(() => {
|
||||
return {
|
||||
background: isOver && canDrop ? theme.colors.hoverBackground : '',
|
||||
position: 'relative',
|
||||
};
|
||||
});
|
||||
@@ -23,6 +19,7 @@ export const StyledTreeNodeContainer = styled('div')<{ isDragging: boolean }>(
|
||||
({ isDragging, theme }) => {
|
||||
return {
|
||||
background: isDragging ? theme.colors.hoverBackground : '',
|
||||
// opacity: isDragging ? 0.4 : 1,
|
||||
};
|
||||
}
|
||||
);
|
||||
@@ -32,11 +29,14 @@ export const StyledNodeLine = styled('div')<{ show: boolean; isTop?: boolean }>(
|
||||
return {
|
||||
position: 'absolute',
|
||||
left: '0',
|
||||
...(isTop ? { top: '0' } : { bottom: '0' }),
|
||||
...(isTop ? { top: '-1px' } : { bottom: '-1px' }),
|
||||
width: '100%',
|
||||
paddingTop: '3px',
|
||||
borderBottom: '3px solid',
|
||||
paddingTop: '2x',
|
||||
borderTop: '2px solid',
|
||||
borderColor: show ? theme.colors.primaryColor : 'transparent',
|
||||
boxShadow: show
|
||||
? `0px 0px 8px ${alpha(theme.colors.primaryColor, 0.35)}`
|
||||
: 'none',
|
||||
zIndex: 1,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ export type Node<N> = {
|
||||
render?: (
|
||||
node: Node<N>,
|
||||
eventsAndStatus: {
|
||||
isOver: boolean;
|
||||
onAdd: () => void;
|
||||
onDelete: () => void;
|
||||
collapsed: boolean;
|
||||
@@ -33,9 +34,14 @@ type CommonProps<N> = {
|
||||
export type TreeNodeProps<N> = {
|
||||
node: Node<N>;
|
||||
index: number;
|
||||
allDrop?: boolean;
|
||||
allowDrop?: boolean;
|
||||
} & CommonProps<N>;
|
||||
|
||||
export type TreeNodeItemProps<N> = {
|
||||
collapsed: boolean;
|
||||
setCollapsed: (collapsed: boolean) => void;
|
||||
} & TreeNodeProps<N>;
|
||||
|
||||
export type TreeViewProps<N> = {
|
||||
data: Node<N>[];
|
||||
} & CommonProps<N>;
|
||||
|
||||
Reference in New Issue
Block a user