refactor(core): remove all MUI related components and utilities (#4941)

This commit is contained in:
Cats Juice
2023-11-20 10:51:28 +08:00
committed by GitHub
parent 4ef1f4c046
commit 57d42bf491
59 changed files with 335 additions and 2520 deletions

View File

@@ -27,9 +27,6 @@
"@emotion/react": "^11.11.1",
"@emotion/server": "^11.11.0",
"@emotion/styled": "^11.11.0",
"@mui/base": "5.0.0-beta.19",
"@mui/icons-material": "^5.14.14",
"@mui/material": "^5.14.14",
"@popperjs/core": "^2.11.8",
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-collapsible": "^1.0.3",

View File

@@ -1,4 +1,3 @@
import { Skeleton } from '@mui/material';
import { assignInlineVars } from '@vanilla-extract/dynamic';
import clsx from 'clsx';
import { useAtom, useAtomValue } from 'jotai';
@@ -6,6 +5,7 @@ import { debounce } from 'lodash-es';
import type { PropsWithChildren, ReactElement } from 'react';
import { useEffect, useRef, useState } from 'react';
import { Skeleton } from '../../ui/skeleton';
import { fallbackHeaderStyle, fallbackStyle } from './fallback.css';
import {
floatingMaxWidth,

View File

@@ -1,7 +1,6 @@
import { EditorContainer } from '@blocksuite/editor';
import { assertExists } from '@blocksuite/global/utils';
import type { Page } from '@blocksuite/store';
import { Skeleton } from '@mui/material';
import clsx from 'clsx';
import { use } from 'foxact/use';
import type { CSSProperties, ReactElement } from 'react';
@@ -17,6 +16,7 @@ import {
import type { FallbackProps } from 'react-error-boundary';
import { ErrorBoundary } from 'react-error-boundary';
import { Skeleton } from '../../ui/skeleton';
import {
blockSuiteEditorHeaderStyle,
blockSuiteEditorStyle,

View File

@@ -2,7 +2,6 @@ import { WorkspaceFlavour } from '@affine/env/workspace';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import type { RootWorkspaceMetadata } from '@affine/workspace/atom';
import { CollaborationIcon, SettingsIcon } from '@blocksuite/icons';
import { Skeleton } from '@mui/material';
import { Avatar } from '@toeverything/components/avatar';
import { Divider } from '@toeverything/components/divider';
import { Tooltip } from '@toeverything/components/tooltip';
@@ -12,6 +11,7 @@ import { getBlockSuiteWorkspaceAtom } from '@toeverything/infra/__internal__/wor
import { useAtomValue } from 'jotai/react';
import { useCallback } from 'react';
import { Skeleton } from '../../../ui/skeleton';
import {
StyledCard,
StyledIconContainer,

View File

@@ -1,13 +0,0 @@
import { Skeleton } from '@mui/material';
import { memo } from 'react';
export const ListSkeleton = memo(function ListItemSkeleton() {
return (
<>
<Skeleton animation="wave" height={40} />
<Skeleton animation="wave" height={40} />
<Skeleton animation="wave" height={40} />
<Skeleton animation="wave" height={40} />
</>
);
});

View File

@@ -1,4 +1,3 @@
import { useMediaQuery, useTheme } from '@mui/material';
import clsx from 'clsx';
import {
type BaseSyntheticEvent,
@@ -8,12 +7,6 @@ import {
import * as styles from './page-list.css';
export const useIsSmallDevices = () => {
const theme = useTheme();
const isSmallDevices = useMediaQuery(theme.breakpoints.down(900));
return isSmallDevices;
};
export function isToday(date: Date): boolean {
const today = new Date();
return (

View File

@@ -1,5 +1,4 @@
import { Skeleton } from '@mui/material';
import { Skeleton } from '../../ui/skeleton';
import { SettingHeader } from './setting-header';
import { SettingRow } from './setting-row';
import { SettingWrapper } from './wrapper';

View File

@@ -1,12 +1,11 @@
import { Skeleton } from '@mui/material';
import { FlexWrapper } from '../../ui/layout';
import { Skeleton } from '../../ui/skeleton';
export const WorkspaceListItemSkeleton = () => {
return (
<FlexWrapper
alignItems="center"
style={{ padding: '0 8px', height: 30, marginBottom: 4 }}
style={{ padding: '0 24px', height: 30, marginBottom: 4 }}
>
<Skeleton
variant="circular"
@@ -14,7 +13,12 @@ export const WorkspaceListItemSkeleton = () => {
height={14}
style={{ marginRight: 10 }}
/>
<Skeleton variant="rectangular" height={16} style={{ flexGrow: 1 }} />
<Skeleton
variant="rectangular"
height={16}
width={0}
style={{ flexGrow: 1 }}
/>
</FlexWrapper>
);
};

View File

@@ -2,8 +2,6 @@ import { lightCssVariables } from '@toeverything/theme';
import type { ComplexStyleRule } from '@vanilla-extract/css';
import { globalStyle, style } from '@vanilla-extract/css';
import { breakpoints } from '../../styles/mui-theme';
export const appStyle = style({
width: '100%',
position: 'relative',
@@ -134,10 +132,10 @@ export const toolStyle = style({
flexDirection: 'column',
gap: '12px',
'@media': {
[breakpoints.down('md', true)]: {
'screen and (max-width: 960px)': {
right: 'calc((100vw - 640px) * 3 / 19 + 14px)',
},
[breakpoints.down('sm', true)]: {
'screen and (max-width: 640px)': {
right: '5px',
bottom: '5px',
},
@@ -149,10 +147,10 @@ export const toolStyle = style({
'&[data-in-trash-page="true"]': {
bottom: '70px',
'@media': {
[breakpoints.down('md', true)]: {
'screen and (max-width: 960px)': {
bottom: '80px',
},
[breakpoints.down('sm', true)]: {
'screen and (max-width: 640px)': {
bottom: '85px',
},
print: {

View File

@@ -1,6 +1,4 @@
export * from './components/list-skeleton';
export * from './styles';
export * from './ui/breadcrumbs';
export * from './ui/button';
export * from './ui/checkbox';
export * from './ui/empty';
@@ -8,12 +6,8 @@ export * from './ui/input';
export * from './ui/layout';
export * from './ui/lottie/collections-icon';
export * from './ui/lottie/delete-icon';
export * from './ui/menu';
export * from './ui/mui';
export * from './ui/popper';
export * from './ui/scrollbar';
export * from './ui/shared/container';
export * from './ui/skeleton';
export * from './ui/switch';
export * from './ui/table';
export * from './ui/toast';
export * from './ui/tree-view';

View File

@@ -1,3 +1,2 @@
export * from './helper';
export * from './mui-theme';
export * from './mui-theme-provider';
export * from './styled';

View File

@@ -1,3 +0,0 @@
import { alpha, css, keyframes, styled } from '@mui/material/styles';
export { alpha, css, keyframes, styled };

View File

@@ -1,86 +0,0 @@
import type {
Breakpoint,
BreakpointsOptions,
ThemeOptions,
} from '@mui/material';
export const muiThemes = {
breakpoints: {
values: {
xs: 0,
sm: 640,
md: 960,
lg: 1280,
xl: 1920,
},
},
} satisfies ThemeOptions;
// Ported from mui
// See https://github.com/mui/material-ui/blob/eba90da5359ff9c58b02800dfe468dc6c0b95bd2/packages/mui-system/src/createTheme/createBreakpoints.js
// License under MIT
function createBreakpoints(breakpoints: BreakpointsOptions): Readonly<
Omit<BreakpointsOptions, 'up' | 'down'> & {
up: (key: Breakpoint | number, pure?: boolean) => string;
down: (key: Breakpoint | number, pure?: boolean) => string;
}
> {
const {
// The breakpoint **start** at this value.
// For instance with the first breakpoint xs: [xs, sm).
values = {
xs: 0, // phone
sm: 600, // tablet
md: 900, // small laptop
lg: 1200, // desktop
xl: 1536, // large screen
},
unit = 'px',
step = 5,
...other
} = breakpoints;
const keys = Object.keys(values) as ['xs', 'sm', 'md', 'lg', 'xl'];
function up(key: Breakpoint | number, pure = false) {
const value = typeof key === 'number' ? key : values[key];
const original = `(min-width:${value}${unit})`;
if (pure) {
return original;
}
return `@media ${original}`;
}
function down(key: Breakpoint | number, pure = false) {
const value = typeof key === 'number' ? key : values[key];
const original = `(max-width:${value - step / 100}${unit})`;
if (pure) {
return original;
}
return `@media ${original}`;
}
return {
keys,
values,
up,
down,
unit,
// between,
// only,
// not,
...other,
};
}
/**
* @example
* ```ts
* export const iconButtonStyle = style({
* [breakpoints.up('sm')]: {
* padding: '6px'
* },
* });
* ```
*/
export const breakpoints = createBreakpoints(muiThemes.breakpoints);

View File

@@ -0,0 +1,3 @@
import styled from '@emotion/styled';
export { styled };

View File

@@ -1,14 +0,0 @@
import type { BreadcrumbsProps } from '@mui/material/Breadcrumbs';
import MuiBreadcrumbs from '@mui/material/Breadcrumbs';
import type { ComponentType } from 'react';
import { styled } from '../../styles';
const StyledMuiBreadcrumbs = styled(MuiBreadcrumbs)(() => {
return {
color: 'var(--affine-text-primary-color)',
};
});
export const Breadcrumbs: ComponentType<BreadcrumbsProps> =
StyledMuiBreadcrumbs;

View File

@@ -1,60 +0,0 @@
import { styled } from '../../styles';
import type { ButtonProps } from './interface';
import { getButtonColors } from './utils';
export const LoadingContainer = styled('div')<Pick<ButtonProps, 'type'>>(({
theme,
type = 'default',
}) => {
const { color } = getButtonColors(theme, type, false);
return `
margin: 0px auto;
width: 38px;
text-align: center;
.load {
width: 8px;
height: 8px;
background-color: ${color};
border-radius: 100%;
display: inline-block;
-webkit-animation: bouncedelay 1.4s infinite ease-in-out;
animation: bouncedelay 1.4s infinite ease-in-out;
/* Prevent first frame from flickering when animation starts */
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
}
.load1 {
-webkit-animation-delay: -0.32s;
animation-delay: -0.32s;
}
.load2 {
-webkit-animation-delay: -0.16s;
animation-delay: -0.16s;
}
@-webkit-keyframes bouncedelay {
0%, 80%, 100% { -webkit-transform: scale(0) }
40% { -webkit-transform: scale(1.0) }
}
@keyframes bouncedelay {
0%, 80%, 100% {
transform: scale(0);
-webkit-transform: scale(0);
} 40% {
transform: scale(1.0);
-webkit-transform: scale(1.0);
}
}
`;
});
export const Loading = ({ type }: Pick<ButtonProps, 'type'>) => {
return (
<LoadingContainer type={type} className="load-container">
<div className="load load1"></div>
<div className="load load2"></div>
<div className="load"></div>
</LoadingContainer>
);
};

View File

@@ -1,9 +1,6 @@
import type { Theme } from '@mui/material';
import type { ButtonProps } from './interface';
export const getButtonColors = (
_theme: Theme,
type: ButtonProps['type'],
disabled: boolean,
extend?: {

View File

@@ -1,6 +0,0 @@
/**
* @deprecated
* Use @toeverything/components/menu instead, this component only used in bookmark plugin, since it support set anchor as Range
*/
export * from './menu-item';
export * from './pure-menu';

View File

@@ -1,44 +0,0 @@
import type { HTMLAttributes, PropsWithChildren, ReactElement } from 'react';
import { forwardRef } from 'react';
import {
StyledContent,
StyledEndIconWrapper,
StyledMenuItem,
StyledStartIconWrapper,
} from './styles';
export type IconMenuProps = PropsWithChildren<{
icon?: ReactElement;
endIcon?: ReactElement;
iconSize?: number;
disabled?: boolean;
active?: boolean;
disableHover?: boolean;
userFocused?: boolean;
gap?: string;
fontSize?: string;
}> &
HTMLAttributes<HTMLButtonElement>;
export const MenuItem = forwardRef<HTMLButtonElement, IconMenuProps>(
({ endIcon, icon, children, gap, fontSize, iconSize, ...props }, ref) => {
return (
<StyledMenuItem ref={ref} {...props}>
{icon && (
<StyledStartIconWrapper iconSize={iconSize} gap={gap}>
{icon}
</StyledStartIconWrapper>
)}
<StyledContent fontSize={fontSize}>{children}</StyledContent>
{endIcon && (
<StyledEndIconWrapper iconSize={iconSize} gap={gap}>
{endIcon}
</StyledEndIconWrapper>
)}
</StyledMenuItem>
);
}
);
MenuItem.displayName = 'MenuItem';
export default MenuItem;

View File

@@ -1,24 +0,0 @@
import type { CSSProperties } from 'react';
import type { PurePopperProps } from '../popper';
import { PurePopper } from '../popper';
import { StyledMenuWrapper } from './styles';
export type PureMenuProps = PurePopperProps & {
width?: CSSProperties['width'];
height?: CSSProperties['height'];
};
export const PureMenu = ({
children,
placement,
width,
...otherProps
}: PureMenuProps) => {
return (
<PurePopper placement={placement} {...otherProps}>
<StyledMenuWrapper width={width} placement={placement}>
{children}
</StyledMenuWrapper>
</PurePopper>
);
};

View File

@@ -1,115 +0,0 @@
import type { CSSProperties } from 'react';
import { displayFlex, styled, textEllipsis } from '../../styles';
import StyledPopperContainer from '../shared/container';
export const StyledMenuWrapper = styled(StyledPopperContainer, {
shouldForwardProp: propName =>
!['width', 'height'].includes(propName as string),
})<{
width?: CSSProperties['width'];
height?: CSSProperties['height'];
}>(({ width, height }) => {
return {
width,
height,
minWidth: '200px',
background: 'var(--affine-white)',
padding: '8px 4px',
fontSize: '14px',
backgroundColor: 'var(--affine-white)',
boxShadow: 'var(--affine-menu-shadow)',
userSelect: 'none',
};
});
export const StyledStartIconWrapper = styled('div')<{
gap?: CSSProperties['gap'];
iconSize?: CSSProperties['fontSize'];
}>(({ gap, iconSize }) => {
return {
display: 'flex',
marginRight: gap ? gap : '12px',
fontSize: iconSize ? iconSize : '20px',
color: 'var(--affine-icon-color)',
};
});
export const StyledEndIconWrapper = styled('div')<{
gap?: CSSProperties['gap'];
iconSize?: CSSProperties['fontSize'];
}>(({ gap, iconSize }) => {
return {
display: 'flex',
marginLeft: gap ? gap : '12px',
fontSize: iconSize ? iconSize : '20px',
color: 'var(--affine-icon-color)',
};
});
export const StyledContent = styled('div')<{
fontSize?: CSSProperties['fontSize'];
}>(({ fontSize }) => {
return {
textAlign: 'left',
flexGrow: 1,
fontSize: fontSize ? fontSize : 'var(--affine-font-base)',
...textEllipsis(1),
};
});
export const StyledMenuItem = styled('button')<{
isDir?: boolean;
disabled?: boolean;
active?: boolean;
disableHover?: boolean;
userFocused?: boolean;
}>(({
isDir = false,
disabled = false,
active = false,
disableHover = false,
userFocused = false,
}) => {
return {
width: '100%',
borderRadius: '5px',
padding: '0 14px',
fontSize: 'var(--affine-font-sm)',
height: '32px',
...displayFlex('flex-start', 'center'),
cursor: isDir ? 'pointer' : '',
position: 'relative',
backgroundColor: 'transparent',
color: disabled
? 'var(--affine-text-disable-color)'
: 'var(--affine-text-primary-color)',
svg: {
color: disabled
? 'var(--affine-text-disable-color)'
: 'var(--affine-icon-color)',
},
...(disabled
? {
cursor: 'not-allowed',
pointerEvents: 'none',
}
: {}),
':hover':
disabled || disableHover
? {}
: {
backgroundColor: 'var(--affine-hover-color)',
},
...(userFocused && !disabled
? {
backgroundColor: 'var(--affine-hover-color)',
}
: {}),
...(active && !disabled
? {
backgroundColor: 'var(--affine-hover-color)',
}
: {}),
};
});

View File

@@ -1,19 +0,0 @@
import { ClickAwayListener as MuiClickAwayListener } from '@mui/base/ClickAwayListener';
import MuiAvatar from '@mui/material/Avatar';
import MuiBreadcrumbs from '@mui/material/Breadcrumbs';
import MuiCollapse from '@mui/material/Collapse';
import MuiFade from '@mui/material/Fade';
import MuiGrow from '@mui/material/Grow';
import MuiSkeleton from '@mui/material/Skeleton';
import MuiSlide from '@mui/material/Slide';
export {
MuiAvatar,
MuiBreadcrumbs,
MuiClickAwayListener,
MuiCollapse,
MuiFade,
MuiGrow,
MuiSkeleton,
MuiSlide,
};

View File

@@ -1,3 +0,0 @@
export * from './interface';
export * from './popper';
export * from './pure-popper';

View File

@@ -1,64 +0,0 @@
import {
type PopperPlacementType,
type PopperProps as PopperUnstyledProps,
} from '@mui/base/Popper';
import type { CSSProperties, ReactElement, ReactNode, Ref } from 'react';
export type VirtualElement = {
getBoundingClientRect: () => ClientRect | DOMRect;
contextElement?: Element;
};
export type PopperHandler = {
setVisible: (visible: boolean) => void;
};
export type PopperArrowProps = {
placement?: PopperPlacementType;
};
export type PopperProps = {
// Popover content
content?: ReactNode;
// Popover trigger
children: ReactElement;
// Whether the default is implicit
defaultVisible?: boolean;
// Used to manually control the visibility of the Popover
visible?: boolean;
// TODO: support focus
trigger?: 'hover' | 'click' | 'focus' | ('click' | 'hover' | 'focus')[];
// How long does it take for the mouse to display the Popover, in milliseconds
pointerEnterDelay?: number;
// How long does it take to hide the Popover after the mouse moves out, in milliseconds
pointerLeaveDelay?: number;
// Callback fired when the component closed or open
onVisibleChange?: (visible: boolean) => void;
// Popover container style
popoverStyle?: CSSProperties;
// Popover container class name
popoverClassName?: string;
// Anchor class name
anchorClassName?: string;
// Popover z-index
zIndex?: number;
offset?: [number, number];
showArrow?: boolean;
popperHandlerRef?: Ref<PopperHandler>;
onClickAway?: () => void;
triggerContainerStyle?: CSSProperties;
} & Omit<PopperUnstyledProps, 'open' | 'content'>;

View File

@@ -1,97 +0,0 @@
import type { CSSProperties } from 'react';
import { forwardRef } from 'react';
import { styled } from '../../styles';
import type { PopperArrowProps } from './interface';
export const PopperArrow = forwardRef<HTMLElement, PopperArrowProps>(
function PopperArrow({ placement }, ref) {
return <StyledArrow placement={placement} ref={ref} />;
}
);
const getArrowStyle = (
placement: PopperArrowProps['placement'] = 'bottom',
backgroundColor: CSSProperties['backgroundColor']
) => {
if (placement.indexOf('bottom') === 0) {
return {
top: 0,
left: 0,
marginTop: '-0.9em',
width: '3em',
height: '1em',
'&::before': {
borderWidth: '0 1em 1em 1em',
borderColor: `transparent transparent ${backgroundColor} transparent`,
},
};
}
if (placement.indexOf('top') === 0) {
return {
bottom: 0,
left: 0,
marginBottom: '-0.9em',
width: '3em',
height: '1em',
'&::before': {
borderWidth: '1em 1em 0 1em',
borderColor: `${backgroundColor} transparent transparent transparent`,
},
};
}
if (placement.indexOf('left') === 0) {
return {
right: 0,
marginRight: '-0.9em',
height: '3em',
width: '1em',
'&::before': {
borderWidth: '1em 0 1em 1em',
borderColor: `transparent transparent transparent ${backgroundColor}`,
},
};
}
if (placement.indexOf('right') === 0) {
return {
left: 0,
marginLeft: '-0.9em',
height: '3em',
width: '1em',
'&::before': {
borderWidth: '1em 1em 1em 0',
borderColor: `transparent ${backgroundColor} transparent transparent`,
},
};
}
return {
display: 'none',
};
};
const StyledArrow = styled('span')<{
placement?: PopperArrowProps['placement'];
}>(({ placement }) => {
return {
position: 'absolute',
fontSize: '7px',
width: '3em',
'::before': {
content: '""',
margin: 'auto',
display: 'block',
width: 0,
height: 0,
borderStyle: 'solid',
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
},
...getArrowStyle(placement, 'var(--affine-tooltip)'),
};
});

View File

@@ -1,300 +0,0 @@
import { ClickAwayListener } from '@mui/base/ClickAwayListener';
import { Popper as PopperUnstyled } from '@mui/base/Popper';
import Grow from '@mui/material/Grow';
import type { CSSProperties, PointerEvent } from 'react';
import {
cloneElement,
useEffect,
useImperativeHandle,
useMemo,
useRef,
useState,
} from 'react';
import { styled } from '../../styles';
import type { PopperProps, VirtualElement } from './interface';
export const Popper = ({
children,
content,
anchorEl: propsAnchorEl,
placement = 'top-start',
defaultVisible = false,
visible: propsVisible,
trigger = 'hover',
pointerEnterDelay = 500,
pointerLeaveDelay = 100,
onVisibleChange,
popoverStyle,
popoverClassName,
anchorClassName,
zIndex,
offset = [0, 5],
showArrow = false,
popperHandlerRef,
onClick,
onClickAway,
onPointerEnter,
onPointerLeave,
triggerContainerStyle = {},
...popperProps
}: PopperProps) => {
const [anchorEl, setAnchorEl] = useState<VirtualElement>();
const [visible, setVisible] = useState(defaultVisible);
//const [arrowRef, setArrowRef] = useState<HTMLElement>();
const arrowRef = null;
const pointerLeaveTimer = useRef<number>();
const pointerEnterTimer = useRef<number>();
const visibleControlledByParent = typeof propsVisible !== 'undefined';
const isAnchorCustom = typeof propsAnchorEl !== 'undefined';
const hasHoverTrigger = useMemo(() => {
return (
trigger === 'hover' ||
(Array.isArray(trigger) && trigger.includes('hover'))
);
}, [trigger]);
const hasClickTrigger = useMemo(() => {
return (
trigger === 'click' ||
(Array.isArray(trigger) && trigger.includes('click'))
);
}, [trigger]);
const onPointerEnterHandler = (e: PointerEvent<HTMLDivElement>) => {
onPointerEnter?.(e);
if (!hasHoverTrigger || visibleControlledByParent) {
return;
}
window.clearTimeout(pointerLeaveTimer.current);
pointerEnterTimer.current = window.window.setTimeout(() => {
setVisible(true);
}, pointerEnterDelay);
};
const onPointerLeaveHandler = (e: PointerEvent<HTMLDivElement>) => {
onPointerLeave?.(e);
if (!hasHoverTrigger || visibleControlledByParent) {
return;
}
window.clearTimeout(pointerEnterTimer.current);
pointerLeaveTimer.current = window.window.setTimeout(() => {
setVisible(false);
}, pointerLeaveDelay);
};
useEffect(() => {
onVisibleChange?.(visible);
}, [visible, onVisibleChange]);
useImperativeHandle(popperHandlerRef, () => {
return {
setVisible: (visible: boolean) => {
!visibleControlledByParent && setVisible(visible);
},
};
});
const mergedClass = [anchorClassName, children.props.className]
.filter(Boolean)
.join(' ');
return (
<ClickAwayListener
onClickAway={() => {
if (visibleControlledByParent) {
onClickAway?.();
} else {
setVisible(false);
}
}}
>
<Container style={triggerContainerStyle}>
{cloneElement(children, {
ref: (dom: HTMLDivElement) => setAnchorEl(dom),
onClick: (e: MouseEvent) => {
children.props.onClick?.(e);
if (!hasClickTrigger || visibleControlledByParent) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
onClick?.(e);
return;
}
setVisible(!visible);
},
onPointerEnter: onPointerEnterHandler,
onPointerLeave: onPointerLeaveHandler,
...(mergedClass
? {
className: mergedClass,
}
: {}),
})}
{content && (
<BasicStyledPopper
open={visibleControlledByParent ? propsVisible : visible}
zIndex={zIndex}
anchorEl={isAnchorCustom ? propsAnchorEl : anchorEl}
placement={placement}
transition
modifiers={[
{
name: 'offset',
options: {
offset,
},
},
{
name: 'arrow',
enabled: showArrow,
options: {
element: arrowRef,
},
},
]}
{...popperProps}
>
{({ TransitionProps }) => (
<Grow {...TransitionProps}>
<div
onPointerEnter={onPointerEnterHandler}
onPointerLeave={onPointerLeaveHandler}
style={popoverStyle}
className={popoverClassName}
onClick={() => {
if (hasClickTrigger && !visibleControlledByParent) {
setVisible(false);
}
}}
>
{showArrow ? (
placement.indexOf('bottom') === 0 ? (
<div
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="11"
height="6"
viewBox="0 0 11 6"
fill="none"
>
<path
d="M6.38889 0.45C5.94444 -0.15 5.05555 -0.150001 4.61111 0.449999L0.499999 6L10.5 6L6.38889 0.45Z"
style={{ fill: 'var(--affine-tooltip)' }}
/>
</svg>
{content}
</div>
) : placement.indexOf('top') === 0 ? (
<div
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
{content}
<svg
xmlns="http://www.w3.org/2000/svg"
width="11"
height="6"
viewBox="0 0 11 6"
fill="none"
>
<path
d="M4.61111 5.55C5.05556 6.15 5.94445 6.15 6.38889 5.55L10.5 -4.76837e-07H0.5L4.61111 5.55Z"
style={{ fill: 'var(--affine-tooltip)' }}
/>
</svg>
</div>
) : placement.indexOf('left') === 0 ? (
<>
{content}
<svg
xmlns="http://www.w3.org/2000/svg"
width="6"
height="10"
viewBox="0 0 6 10"
fill="none"
>
<path
d="M5.55 5.88889C6.15 5.44444 6.15 4.55555 5.55 4.11111L-4.76837e-07 0L-4.76837e-07 10L5.55 5.88889Z"
style={{ fill: 'var(--affine-tooltip)' }}
/>
</svg>
</>
) : placement.indexOf('right') === 0 ? (
<>
<svg
xmlns="http://www.w3.org/2000/svg"
width="6"
height="10"
viewBox="0 0 6 10"
style={{ fill: 'var(--affine-tooltip)' }}
>
<path
d="M0.45 4.11111C-0.15 4.55556 -0.15 5.44445 0.45 5.88889L6 10V0L0.45 4.11111Z"
style={{ fill: 'var(--affine-tooltip)' }}
/>
</svg>
{content}
</>
) : (
<div
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
{content}
<svg
xmlns="http://www.w3.org/2000/svg"
width="11"
height="6"
viewBox="0 0 11 6"
fill="none"
>
<path
d="M4.61111 5.55C5.05556 6.15 5.94445 6.15 6.38889 5.55L10.5 -4.76837e-07H0.5L4.61111 5.55Z"
style={{ fill: 'var(--affine-tooltip)' }}
/>
</svg>
</div>
)
) : (
content
)}
</div>
</Grow>
)}
</BasicStyledPopper>
)}
</Container>
</ClickAwayListener>
);
};
// The children of ClickAwayListener must be a DOM Node to judge whether the click is outside, use node.contains
const Container = styled('div')({
display: 'contents',
});
export const BasicStyledPopper = styled(PopperUnstyled, {
shouldForwardProp: (propName: string) =>
!['zIndex'].some(name => name === propName),
})<{
zIndex?: CSSProperties['zIndex'];
}>(({ zIndex }) => {
return {
zIndex: zIndex ?? 'var(--affine-z-index-popover)',
};
});

View File

@@ -1,66 +0,0 @@
import type { PopperProps as PopperUnstyledProps } from '@mui/base/Popper';
import Grow from '@mui/material/Grow';
import type { CSSProperties, PropsWithChildren } from 'react';
import { useState } from 'react';
import { PopperArrow } from './popover-arrow';
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>();
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>
);
};

View File

@@ -1,7 +0,0 @@
import { styled } from '../../styles';
export const PopperWrapper = styled('div')(() => {
return {
position: 'relative',
};
});

View File

@@ -1,57 +0,0 @@
import type { PopperPlacementType } from '@mui/material';
import { styled } from '../../styles';
export type PopperDirection =
| 'none'
| 'left-top'
| 'left-bottom'
| 'right-top'
| 'right-bottom';
const getBorderRadius = (direction: PopperDirection, radius = '0') => {
const map: Record<PopperDirection, string> = {
none: `${radius}`,
'left-top': `0 ${radius} ${radius} ${radius}`,
'left-bottom': `${radius} ${radius} ${radius} 0`,
'right-top': `${radius} 0 ${radius} ${radius}`,
'right-bottom': `${radius} ${radius} 0 ${radius}`,
};
return map[direction];
};
export const placementToContainerDirection: Record<
PopperPlacementType,
PopperDirection
> = {
top: 'none',
'top-start': 'left-bottom',
'top-end': 'right-bottom',
right: 'none',
'right-start': 'left-top',
'right-end': 'left-bottom',
bottom: 'none',
'bottom-start': 'none',
'bottom-end': 'none',
left: 'none',
'left-start': 'right-top',
'left-end': 'right-bottom',
auto: 'none',
'auto-start': 'none',
'auto-end': 'none',
};
export const StyledPopperContainer = styled('div')<{
placement?: PopperPlacementType;
}>(({ placement = 'top' }) => {
const direction = placementToContainerDirection[placement];
const borderRadius = getBorderRadius(
direction,
'var(--affine-popover-radius)'
);
return {
borderRadius,
};
});
export default StyledPopperContainer;

View File

@@ -0,0 +1,94 @@
import { keyframes, style } from '@vanilla-extract/css';
import type { PickStringFromUnion, SkeletonProps } from './types';
// variables
const bg = 'var(--affine-placeholder-color)';
const highlight = 'rgba(255, 255, 255, 0.4)';
const defaultHeight = '32px';
const pulseKeyframes = keyframes({
'0%': { opacity: 1 },
'50%': { opacity: 0.5 },
'100%': { opacity: 1 },
});
const waveKeyframes = keyframes({
'0%': { transform: 'translateX(-100%)' },
'50%': { transform: 'translateX(100%)' },
'100%': { transform: 'translateX(100%)' },
});
export const root = style({
display: 'block',
width: '100%',
height: defaultHeight,
flexShrink: 0,
/**
* paint background in ::before,
* so that we can use opacity to control the color
**/
position: 'relative',
'::before': {
content: '',
position: 'absolute',
borderRadius: 'inherit',
inset: 0,
opacity: 0.3,
backgroundColor: bg,
},
});
export const variant: Record<string, string> = {
circular: style({
width: defaultHeight,
borderRadius: '50%',
}),
rectangular: style({
borderRadius: '0px',
}),
rounded: style({
borderRadius: '8px',
}),
text: style({
borderRadius: '4px',
height: '1.2em',
marginTop: '0.2em',
marginBottom: '0.2em',
}),
};
export const animation: Record<
PickStringFromUnion<SkeletonProps['animation']>,
string
> = {
pulse: style({
animation: `${pulseKeyframes} 2s ease-in-out 0.5s infinite`,
}),
wave: style({
position: 'relative',
overflow: 'hidden',
/* Fix bug in Safari https://bugs.webkit.org/show_bug.cgi?id=68196 */
WebkitMaskImage: '-webkit-radial-gradient(white, black)',
'::after': {
animation: `${waveKeyframes} 2s linear 0.5s infinite`,
background: `linear-gradient(
90deg,
transparent,
${highlight},
transparent
)`,
content: '',
position: 'absolute',
transform:
'translateX(-100%)' /* Avoid flash during server-side hydration */,
bottom: 0,
left: 0,
right: 0,
top: 0,
},
}),
};

View File

@@ -0,0 +1,2 @@
export * from './skeleton';
export * from './types';

View File

@@ -0,0 +1,49 @@
import clsx from 'clsx';
import * as styles from './index.css';
import type { SkeletonProps } from './types';
function getSize(size: number | string) {
return typeof size === 'number' || /^\d+$/.test(size) ? `${size}px` : size;
}
/**
*
* @returns
*/
export const Skeleton = ({
animation = 'pulse',
variant = 'text',
children,
width: _width,
height: _height,
style: _style,
className: _className,
...props
}: SkeletonProps) => {
const width = _width !== undefined ? getSize(_width) : undefined;
const height = _height !== undefined ? getSize(_height) : undefined;
const style = {
width,
height,
...(_style || {}),
};
return (
<div
className={clsx(
_className,
styles.root,
styles.variant[variant],
animation && styles.animation[animation]
)}
style={style}
{...props}
>
{children}
</div>
);
};

View File

@@ -0,0 +1,33 @@
import type { HTMLAttributes, PropsWithChildren } from 'react';
export interface SkeletonProps
extends PropsWithChildren,
HTMLAttributes<HTMLElement> {
/**
* The animation. If `false` the animation effect is disabled.
*/
animation?: 'pulse' | 'wave' | false;
/**
* The type of content that will be rendered.
* @default `'text'`
*/
variant?: 'circular' | 'rectangular' | 'rounded' | 'text' | string;
/**
* Width of the skeleton. Useful when the skeleton is inside an inline element with no width of its own.
*/
width?: number | string;
/**
* Height of the skeleton. Useful when you don't want to adapt the skeleton to a text element but for instance a card.
*/
height?: number | string;
/**
* Wrapper component. If not provided, the default element is a div.
*/
wrapper?: string;
}
export type PickStringFromUnion<T> = T extends string ? T : never;

View File

@@ -1,10 +1,3 @@
// import Table from '@mui/material/Table';
// import TableBody from '@mui/material/TableBody';
// import TableCell from '@mui/material/TableCell';
// import TableHead from '@mui/material/TableHead';
// import TableRow from '@mui/material/TableRow';
//
export * from './interface';
export * from './table';
export * from './table-body';

View File

@@ -1,32 +0,0 @@
import { useState } from 'react';
import type { TreeNodeProps } from '../types';
export const useCollapsed = ({
initialCollapsedIds = [],
disableCollapse = false,
}: {
disableCollapse?: boolean;
initialCollapsedIds?: string[];
}) => {
// TODO: should record collapsedIds in localStorage
const [collapsedIds, setCollapsedIds] =
useState<string[]>(initialCollapsedIds);
const setCollapsed: TreeNodeProps['setCollapsed'] = (id, collapsed) => {
if (disableCollapse) {
return;
}
if (collapsed) {
setCollapsedIds(ids => [...ids, id]);
} else {
setCollapsedIds(ids => ids.filter(i => i !== id));
}
};
return {
collapsedIds,
setCollapsed,
};
};
export default useCollapsed;

View File

@@ -1,63 +0,0 @@
import { useEffect, useState } from 'react';
import type { TreeViewProps } from '../types';
import { flattenIds } from '../utils';
export const useSelectWithKeyboard = <RenderProps>({
data,
enableKeyboardSelection,
onSelect,
}: Pick<
TreeViewProps<RenderProps>,
'data' | 'enableKeyboardSelection' | 'onSelect'
>) => {
const [selectedId, setSelectedId] = useState<string>();
// TODO: should record collapsedIds in localStorage
useEffect(() => {
if (!enableKeyboardSelection) {
return;
}
const flattenedIds = flattenIds<RenderProps>(data);
const handleDirectionKeyDown = (e: KeyboardEvent) => {
if (e.key !== 'ArrowDown' && e.key !== 'ArrowUp') {
return;
}
if (selectedId === undefined) {
setSelectedId(flattenedIds[0]);
return;
}
let selectedIndex = flattenedIds.indexOf(selectedId);
if (e.key === 'ArrowDown') {
selectedIndex < flattenedIds.length - 1 && selectedIndex++;
}
if (e.key === 'ArrowUp') {
selectedIndex > 0 && selectedIndex--;
}
setSelectedId(flattenedIds[selectedIndex]);
};
const handleEnterKeyDown = (e: KeyboardEvent) => {
if (e.key !== 'Enter') {
return;
}
selectedId && onSelect?.(selectedId);
};
document.addEventListener('keydown', handleDirectionKeyDown);
document.addEventListener('keydown', handleEnterKeyDown);
return () => {
document.removeEventListener('keydown', handleDirectionKeyDown);
document.removeEventListener('keydown', handleEnterKeyDown);
};
}, [data, enableKeyboardSelection, onSelect, selectedId]);
return {
selectedId,
};
};
export default useSelectWithKeyboard;

View File

@@ -1,3 +0,0 @@
export * from './tree-node';
export * from './tree-view';
export * from './types';

View File

@@ -1,44 +0,0 @@
import MuiCollapse from '@mui/material/Collapse';
import { lightTheme } from '@toeverything/theme';
import type { CSSProperties } from 'react';
import { alpha, styled } from '../../styles';
export const StyledCollapse = styled(MuiCollapse)<{
indent?: CSSProperties['paddingLeft'];
}>(({ indent = 12 }) => {
return {
paddingLeft: indent,
};
});
export const StyledTreeNodeWrapper = styled('div')(() => {
return {
position: 'relative',
};
});
export const StyledTreeNodeContainer = styled('div')<{ isDragging?: boolean }>(
({ isDragging = false }) => {
return {
background: isDragging ? 'var(--affine-hover-color)' : '',
};
}
);
export const StyledNodeLine = styled('div')<{
isOver: boolean;
isTop?: boolean;
}>(({ isOver, isTop = false }) => {
return {
position: 'absolute',
left: '0',
...(isTop ? { top: '-1px' } : { bottom: '-1px' }),
width: '100%',
paddingTop: '2x',
borderTop: '2px solid',
borderColor: isOver ? 'var(--affine-primary-color)' : 'transparent',
boxShadow: isOver
? `0px 0px 8px ${alpha(lightTheme.primaryColor, 0.35)}`
: 'none',
zIndex: 1,
};
});

View File

@@ -1,88 +0,0 @@
import { useDroppable } from '@dnd-kit/core';
import { StyledNodeLine } from './styles';
import type { NodeLIneProps, TreeNodeItemProps } from './types';
export const NodeLine = <RenderProps,>({
node,
allowDrop = true,
isTop = false,
}: NodeLIneProps<RenderProps>) => {
const { isOver, setNodeRef } = useDroppable({
id: `${node.id}-${isTop ? 'top' : 'bottom'}-line`,
disabled: !allowDrop,
data: {
node,
position: {
topLine: isTop,
bottomLine: !isTop,
internal: false,
},
},
});
return (
<StyledNodeLine
ref={setNodeRef}
isOver={isOver && allowDrop}
isTop={isTop}
/>
);
};
export const TreeNodeItemWithDnd = <RenderProps,>({
node,
allowDrop,
setCollapsed,
...otherProps
}: TreeNodeItemProps<RenderProps>) => {
const { onAdd, onDelete } = otherProps;
const { isOver, setNodeRef } = useDroppable({
id: node.id,
disabled: !allowDrop,
data: {
node,
position: {
topLine: false,
bottomLine: false,
internal: true,
},
},
});
return (
<div ref={setNodeRef}>
<TreeNodeItem
onAdd={onAdd}
onDelete={onDelete}
node={node}
allowDrop={allowDrop}
setCollapsed={setCollapsed}
isOver={isOver}
{...otherProps}
/>
</div>
);
};
export const TreeNodeItem = <RenderProps,>({
node,
collapsed,
setCollapsed,
selectedId,
isOver = false,
onAdd,
onDelete,
disableCollapse,
allowDrop = true,
}: TreeNodeItemProps<RenderProps>) => {
return node.render?.(node, {
isOver: isOver && allowDrop,
onAdd: () => onAdd?.(node.id),
onDelete: () => onDelete?.(node.id),
collapsed,
setCollapsed,
isSelected: selectedId === node.id,
disableCollapse,
});
};

View File

@@ -1,106 +0,0 @@
import { useDraggable } from '@dnd-kit/core';
import { useMemo } from 'react';
import {
StyledCollapse,
StyledTreeNodeContainer,
StyledTreeNodeWrapper,
} from './styles';
import { NodeLine, TreeNodeItem, TreeNodeItemWithDnd } from './tree-node-inner';
import type { TreeNodeProps } from './types';
export const TreeNodeWithDnd = <RenderProps,>(
props: TreeNodeProps<RenderProps>
) => {
const { draggingId, node, allowDrop } = props;
const { attributes, listeners, setNodeRef } = useDraggable({
id: props.node.id,
});
const isDragging = useMemo(
() => draggingId === node.id,
[draggingId, node.id]
);
return (
<StyledTreeNodeContainer
ref={setNodeRef}
isDragging={isDragging}
{...listeners}
{...attributes}
>
<TreeNode
{...props}
allowDrop={allowDrop === false ? allowDrop : !isDragging}
/>
</StyledTreeNodeContainer>
);
};
export const TreeNode = <RenderProps,>({
node,
index,
allowDrop = true,
...otherProps
}: TreeNodeProps<RenderProps>) => {
const { indent, enableDnd, collapsedIds } = otherProps;
const collapsed = collapsedIds.includes(node.id);
const { renderTopLine = true, renderBottomLine = true } = node;
return (
<>
<StyledTreeNodeWrapper>
{enableDnd && renderTopLine && index === 0 && (
<NodeLine
node={node}
{...otherProps}
allowDrop={allowDrop}
isTop={true}
/>
)}
{enableDnd ? (
<TreeNodeItemWithDnd
node={node}
index={index}
allowDrop={allowDrop}
collapsed={collapsed}
{...otherProps}
/>
) : (
<TreeNodeItem
node={node}
index={index}
allowDrop={allowDrop}
collapsed={collapsed}
{...otherProps}
/>
)}
{enableDnd &&
renderBottomLine &&
(!node.children?.length || collapsed) && (
<NodeLine node={node} {...otherProps} allowDrop={allowDrop} />
)}
</StyledTreeNodeWrapper>
<StyledCollapse in={!collapsed} indent={indent}>
{node.children &&
node.children.map((childNode, index) =>
enableDnd ? (
<TreeNodeWithDnd
key={childNode.id}
node={childNode}
index={index}
{...otherProps}
allowDrop={allowDrop}
/>
) : (
<TreeNode
key={childNode.id}
node={childNode}
index={index}
allowDrop={false}
{...otherProps}
/>
)
)}
</StyledCollapse>
</>
);
};

View File

@@ -1,126 +0,0 @@
import type { DragEndEvent } from '@dnd-kit/core';
import {
closestCenter,
DndContext,
DragOverlay,
PointerSensor,
useSensor,
useSensors,
} from '@dnd-kit/core';
import { useCallback, useState } from 'react';
import useCollapsed from './hooks/use-collapsed';
import useSelectWithKeyboard from './hooks/use-select-with-keyboard';
import { TreeNode, TreeNodeWithDnd } from './tree-node';
import type { Node, TreeViewProps } from './types';
import { findNode } from './utils';
export const TreeView = <RenderProps,>({
data,
enableKeyboardSelection,
onSelect,
enableDnd = true,
disableCollapse,
onDrop,
...otherProps
}: TreeViewProps<RenderProps>) => {
const sensors = useSensors(
useSensor(PointerSensor, {
activationConstraint: {
distance: 8,
},
})
);
const { selectedId } = useSelectWithKeyboard({
data,
onSelect,
enableKeyboardSelection,
});
const { collapsedIds, setCollapsed } = useCollapsed({ disableCollapse });
const [draggingId, setDraggingId] = useState<string>();
const onDragEnd = useCallback(
(e: DragEndEvent) => {
const { active, over } = e;
const position = over?.data.current?.position;
const dropId = over?.data.current?.node.id;
setDraggingId(undefined);
if (!over || !active || !position) {
return;
}
onDrop?.(active.id as string, dropId, position);
},
[onDrop]
);
const onDragMove = useCallback((e: DragEndEvent) => {
setDraggingId(e.active.id as string);
}, []);
if (enableDnd) {
const treeNodes = data.map((node, index) => (
<TreeNodeWithDnd
key={node.id}
index={index}
collapsedIds={collapsedIds}
setCollapsed={setCollapsed}
node={node}
selectedId={selectedId}
enableDnd={enableDnd}
disableCollapse={disableCollapse}
draggingId={draggingId}
{...otherProps}
/>
));
const draggingNode = (function () {
let draggingNode: Node<RenderProps> | undefined;
if (draggingId) {
draggingNode = findNode(draggingId, data);
}
if (draggingNode) {
return (
<TreeNode
node={draggingNode}
index={0}
allowDrop={false}
collapsedIds={collapsedIds}
setCollapsed={() => {}}
{...otherProps}
/>
);
}
return null;
})();
return (
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragMove={onDragMove}
onDragEnd={onDragEnd}
>
{treeNodes}
<DragOverlay>{draggingNode}</DragOverlay>
</DndContext>
);
}
return (
<>
{data.map((node, index) => (
<TreeNode
key={node.id}
index={index}
collapsedIds={collapsedIds}
setCollapsed={setCollapsed}
node={node}
selectedId={selectedId}
enableDnd={enableDnd}
disableCollapse={disableCollapse}
{...otherProps}
/>
))}
</>
);
};
export default TreeView;

View File

@@ -1,72 +0,0 @@
import type { CSSProperties, ReactNode } from 'react';
export type DropPosition = {
topLine: boolean;
bottomLine: boolean;
internal: boolean;
};
export type OnDrop = (
dragId: string,
dropId: string,
position: DropPosition
) => void;
export type Node<RenderProps = unknown> = {
id: string;
children?: Node<RenderProps>[];
render: (
node: Node<RenderProps>,
eventsAndStatus: {
isOver: boolean;
onAdd: () => void;
onDelete: () => void;
collapsed: boolean;
setCollapsed: (id: string, collapsed: boolean) => void;
isSelected: boolean;
disableCollapse?: ReactNode;
},
renderProps?: RenderProps
) => ReactNode;
renderTopLine?: boolean;
renderBottomLine?: boolean;
};
type CommonProps = {
enableDnd?: boolean;
enableKeyboardSelection?: boolean;
indent?: CSSProperties['paddingLeft'];
onAdd?: (parentId: string) => void;
onDelete?: (deleteId: string) => void;
onDrop?: OnDrop;
// Only trigger when the enableKeyboardSelection is true
onSelect?: (id: string) => void;
disableCollapse?: ReactNode;
};
export type TreeNodeProps<RenderProps = unknown> = {
node: Node<RenderProps>;
index: number;
collapsedIds: string[];
setCollapsed: (id: string, collapsed: boolean) => void;
allowDrop?: boolean;
selectedId?: string;
draggingId?: string;
} & CommonProps;
export type TreeNodeItemProps<RenderProps = unknown> = {
collapsed: boolean;
setCollapsed: (id: string, collapsed: boolean) => void;
isOver?: boolean;
} & TreeNodeProps<RenderProps>;
export type TreeViewProps<RenderProps = unknown> = {
data: Node<RenderProps>[];
initialCollapsedIds?: string[];
disableCollapse?: boolean;
} & CommonProps;
export type NodeLIneProps<RenderProps = unknown> = {
allowDrop: boolean;
isTop?: boolean;
} & Pick<TreeNodeProps<RenderProps>, 'node'>;

View File

@@ -1,37 +0,0 @@
import type { Node } from './types';
export function flattenIds<RenderProps>(arr: Node<RenderProps>[]): string[] {
const result: string[] = [];
function flatten(arr: Node<RenderProps>[]) {
for (let i = 0, len = arr.length; i < len; i++) {
const item = arr[i];
result.push(item.id);
if (Array.isArray(item.children)) {
flatten(item.children);
}
}
}
flatten(arr);
return result;
}
export function findNode<RenderProps>(
id: string,
nodes: Node<RenderProps>[]
): Node<RenderProps> | undefined {
for (let i = 0, len = nodes.length; i < len; i++) {
const node = nodes[i];
if (node.id === id) {
return node;
}
if (node.children) {
const result = findNode(id, node.children);
if (result) {
return result;
}
}
}
return undefined;
}

View File

@@ -20,12 +20,6 @@ export const productionCacheGroups = {
priority: Number.MAX_SAFE_INTEGER,
chunks: 'async' as const,
},
mui: {
name: `npm-mui`,
test: testPackageName(/[\\/]node_modules[\\/](mui|@mui)[\\/]/),
priority: 200,
enforce: true,
},
blocksuite: {
name: `npm-blocksuite`,
test: testPackageName(/[\\/]node_modules[\\/](@blocksuite)[\\/]/),

View File

@@ -40,7 +40,6 @@
"@emotion/server": "^11.11.0",
"@emotion/styled": "^11.11.0",
"@marsidev/react-turnstile": "^0.3.1",
"@mui/material": "^5.14.14",
"@radix-ui/react-collapsible": "^1.0.3",
"@radix-ui/react-dialog": "^1.0.4",
"@radix-ui/react-scroll-area": "^1.0.5",

View File

@@ -1,3 +1,4 @@
import { Skeleton } from '@affine/component';
import { Pagination } from '@affine/component/member-components';
import {
SettingHeader,
@@ -19,7 +20,6 @@ import { Trans } from '@affine/i18n';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { useMutation, useQuery } from '@affine/workspace/affine/gql';
import { ArrowRightSmallIcon } from '@blocksuite/icons';
import { Skeleton } from '@mui/material';
import { Button, IconButton } from '@toeverything/components/button';
import { Loading } from '@toeverything/components/loading';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';

View File

@@ -1,4 +1,4 @@
import { Skeleton } from '@mui/material';
import { Skeleton } from '@affine/component';
import { PlanLayout } from './layout';
import * as styles from './skeleton.css';

View File

@@ -1,10 +1,10 @@
import { MenuItem, PureMenu } from '@affine/component';
import { MuiClickAwayListener } from '@affine/component';
import type { SerializedBlock } from '@blocksuite/blocks';
import type { BaseBlockModel } from '@blocksuite/store';
import type { Page } from '@blocksuite/store';
import type { VEditor } from '@blocksuite/virgo';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { Menu, MenuItem } from '@toeverything/components/menu';
import { useEffect, useState } from 'react';
import { createPortal } from 'react-dom';
type ShortcutMap = {
[key: string]: (e: KeyboardEvent, page: Page) => void;
@@ -121,60 +121,6 @@ export type BookmarkProps = {
export const Bookmark = ({ page }: BookmarkProps) => {
const [anchor, setAnchor] = useState<Range | null>(null);
const [selectedOption, setSelectedOption] = useState<string>(
menuOptions[0].id
);
const shortcutMap = useMemo<ShortcutMap>(
() => ({
ArrowUp: () => {
const curIndex = menuOptions.findIndex(
({ id }) => id === selectedOption
);
if (menuOptions[curIndex - 1]) {
setSelectedOption(menuOptions[curIndex - 1].id);
} else if (curIndex === -1) {
setSelectedOption(menuOptions[0].id);
} else {
setSelectedOption(menuOptions[menuOptions.length - 1].id);
}
},
ArrowDown: () => {
const curIndex = menuOptions.findIndex(
({ id }) => id === selectedOption
);
if (curIndex !== -1 && menuOptions[curIndex + 1]) {
setSelectedOption(menuOptions[curIndex + 1].id);
} else {
setSelectedOption(menuOptions[0].id);
}
},
Enter: () =>
handleEnter({
page,
selectedOption,
callback: () => {
setAnchor(null);
},
}),
Escape: () => {
setAnchor(null);
},
}),
[page, selectedOption]
);
const onKeydown = useCallback(
(e: KeyboardEvent) => {
const shortcut = shortcutMap[e.key];
if (shortcut) {
e.stopPropagation();
e.preventDefault();
shortcut(e, page);
} else {
setAnchor(null);
}
},
[page, shortcutMap]
);
useEffect(() => {
const disposer = page.slots.pasted.on(pastedBlocks => {
@@ -189,56 +135,35 @@ export const Bookmark = ({ page }: BookmarkProps) => {
return () => {
disposer.dispose();
};
}, [onKeydown, page, shortcutMap]);
}, [page]);
useEffect(() => {
if (anchor) {
document.addEventListener('keydown', onKeydown, { capture: true });
} else {
// reset status and remove event
setSelectedOption(menuOptions[0].id);
document.removeEventListener('keydown', onKeydown, { capture: true });
}
const portalContainer = anchor?.startContainer.parentElement;
return () => {
document.removeEventListener('keydown', onKeydown, { capture: true });
};
}, [anchor, onKeydown]);
return anchor ? (
<MuiClickAwayListener
onClickAway={() => {
setAnchor(null);
setSelectedOption('');
}}
>
<div>
<PureMenu open={!!anchor} anchorEl={anchor} placement="bottom-start">
{menuOptions.map(({ id, label }) => {
return (
<MenuItem
key={id}
active={selectedOption === id}
onClick={() => {
handleEnter({
page,
selectedOption: id,
callback: () => {
setAnchor(null);
},
});
}}
disableHover={true}
onMouseEnter={() => {
setSelectedOption(id);
}}
>
{label}
</MenuItem>
);
})}
</PureMenu>
</div>
</MuiClickAwayListener>
) : null;
return anchor && portalContainer
? createPortal(
<Menu
rootOptions={{
defaultOpen: true,
onOpenChange: (e: boolean) => !e && setAnchor(null),
}}
items={menuOptions.map(({ id, label }) => (
<MenuItem
key={id}
onClick={() =>
handleEnter({
page,
selectedOption: id,
callback: () => setAnchor(null),
})
}
>
{label}
</MenuItem>
))}
>
<span></span>
</Menu>,
portalContainer
)
: null;
};

View File

@@ -1,4 +1,3 @@
import { MuiFade } from '@affine/component';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { CloseIcon, NewIcon, UserGuideIcon } from '@blocksuite/icons';
import { Tooltip } from '@toeverything/components/tooltip';
@@ -8,7 +7,7 @@ import { useCallback, useState } from 'react';
import { openOnboardingModalAtom, openSettingModalAtom } from '../../../atoms';
import { currentModeAtom } from '../../../atoms/mode';
import { ShortcutsModal } from '../shortcuts-modal';
import type { SettingProps } from '../../affine/setting-modal';
import { ContactIcon, HelpIcon, KeyboardIcon } from './icons';
import {
StyledAnimateWrapper,
@@ -36,113 +35,106 @@ export const HelpIsland = ({
const [spread, setShowSpread] = useState(false);
const t = useAFFiNEI18N();
const [openShortCut, setOpenShortCut] = useState(false);
const openSettingModal = useCallback(
(tab: SettingProps['activeTab']) => {
setShowSpread(false);
const openAbout = useCallback(() => {
setShowSpread(false);
setOpenSettingModalAtom({
open: true,
activeTab: 'about',
workspaceId: null,
});
}, [setOpenSettingModalAtom]);
setOpenSettingModalAtom({
open: true,
activeTab: tab,
workspaceId: null,
});
},
[setOpenSettingModalAtom]
);
const openAbout = useCallback(
() => openSettingModal('about'),
[openSettingModal]
);
const openShortcuts = useCallback(
() => openSettingModal('shortcuts'),
[openSettingModal]
);
return (
<>
<StyledIsland
spread={spread}
data-testid="help-island"
onClick={() => {
setShowSpread(!spread);
}}
inEdgelessPage={mode === 'edgeless'}
<StyledIsland
spread={spread}
data-testid="help-island"
onClick={() => {
setShowSpread(!spread);
}}
inEdgelessPage={mode === 'edgeless'}
>
<StyledAnimateWrapper
style={{ height: spread ? `${showList.length * 40 + 4}px` : 0 }}
>
<StyledAnimateWrapper
style={{ height: spread ? `${showList.length * 40 + 4}px` : 0 }}
>
{showList.includes('whatNew') && (
<Tooltip
content={t['com.affine.appUpdater.whatsNew']()}
side="left"
{showList.includes('whatNew') && (
<Tooltip content={t['com.affine.appUpdater.whatsNew']()} side="left">
<StyledIconWrapper
data-testid="right-bottom-change-log-icon"
onClick={() => {
window.open(runtimeConfig.changelogUrl, '_blank');
}}
>
<StyledIconWrapper
data-testid="right-bottom-change-log-icon"
onClick={() => {
window.open(runtimeConfig.changelogUrl, '_blank');
}}
>
<NewIcon />
</StyledIconWrapper>
</Tooltip>
)}
{showList.includes('contact') && (
<Tooltip
content={t['com.affine.helpIsland.contactUs']()}
side="left"
<NewIcon />
</StyledIconWrapper>
</Tooltip>
)}
{showList.includes('contact') && (
<Tooltip content={t['com.affine.helpIsland.contactUs']()} side="left">
<StyledIconWrapper
data-testid="right-bottom-contact-us-icon"
onClick={openAbout}
>
<StyledIconWrapper
data-testid="right-bottom-contact-us-icon"
onClick={openAbout}
>
<ContactIcon />
</StyledIconWrapper>
</Tooltip>
)}
{showList.includes('shortcuts') && (
<Tooltip
content={t['com.affine.keyboardShortcuts.title']()}
side="left"
<ContactIcon />
</StyledIconWrapper>
</Tooltip>
)}
{showList.includes('shortcuts') && (
<Tooltip
content={t['com.affine.keyboardShortcuts.title']()}
side="left"
>
<StyledIconWrapper
data-testid="shortcuts-icon"
onClick={openShortcuts}
>
<StyledIconWrapper
data-testid="shortcuts-icon"
onClick={() => {
setShowSpread(false);
setOpenShortCut(true);
}}
>
<KeyboardIcon />
</StyledIconWrapper>
</Tooltip>
)}
{showList.includes('guide') && (
<Tooltip
content={t['com.affine.helpIsland.gettingStarted']()}
side="left"
<KeyboardIcon />
</StyledIconWrapper>
</Tooltip>
)}
{showList.includes('guide') && (
<Tooltip
content={t['com.affine.helpIsland.gettingStarted']()}
side="left"
>
<StyledIconWrapper
data-testid="easy-guide"
onClick={() => {
setShowSpread(false);
setOpenOnboarding(true);
}}
>
<StyledIconWrapper
data-testid="easy-guide"
onClick={() => {
setShowSpread(false);
setOpenOnboarding(true);
}}
>
<UserGuideIcon />
</StyledIconWrapper>
</Tooltip>
)}
</StyledAnimateWrapper>
<UserGuideIcon />
</StyledIconWrapper>
</Tooltip>
)}
</StyledAnimateWrapper>
{spread ? (
<StyledTriggerWrapper spread>
<CloseIcon />
</StyledTriggerWrapper>
) : (
<Tooltip
content={t['com.affine.helpIsland.helpAndFeedback']()}
side="left"
>
<MuiFade in={!spread} data-testid="faq-icon">
<StyledTriggerWrapper>
<HelpIcon />
</StyledTriggerWrapper>
</MuiFade>
</Tooltip>
<MuiFade in={spread}>
<StyledTriggerWrapper spread>
<CloseIcon />
<StyledTriggerWrapper data-testid="faq-icon">
<HelpIcon />
</StyledTriggerWrapper>
</MuiFade>
</StyledIsland>
<ShortcutsModal
open={openShortCut}
onClose={() => setOpenShortCut(false)}
/>
</>
</Tooltip>
)}
</StyledIsland>
);
};

View File

@@ -19,8 +19,8 @@ export const StyledIsland = styled('div')<{
? 'var(--affine-background-overlay-panel-color)'
: 'var(--affine-background-primary-color)',
':hover': {
background: spread ? null : 'var(--affine-white)',
boxShadow: spread ? null : 'var(--affine-menu-shadow)',
background: spread ? undefined : 'var(--affine-white)',
boxShadow: spread ? undefined : 'var(--affine-menu-shadow)',
},
'::after': {
content: '""',
@@ -73,7 +73,7 @@ export const StyledTriggerWrapper = styled('div')<{
...displayFlex('center', 'center'),
...positionAbsolute({ left: '4px', bottom: '4px' }),
':hover': {
backgroundColor: spread ? 'var(--affine-hover-color)' : null,
backgroundColor: spread ? 'var(--affine-hover-color)' : undefined,
},
};
});

View File

@@ -1,27 +0,0 @@
export const CloseIcon = () => {
return (
<svg
width="14"
height="14"
viewBox="0 0 14 14"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M7.94 7.00014L13.4667 1.47348C13.5759 1.34594 13.633 1.18189 13.6265 1.01411C13.62 0.846324 13.5504 0.687165 13.4317 0.568435C13.313 0.449706 13.1538 0.38015 12.986 0.37367C12.8183 0.367189 12.6542 0.42426 12.5267 0.533477L7 6.06014L1.47334 0.526811C1.3478 0.401275 1.17754 0.33075 1 0.33075C0.822468 0.33075 0.652205 0.401275 0.526669 0.526811C0.401133 0.652346 0.330608 0.82261 0.330608 1.00014C0.330608 1.17768 0.401133 1.34794 0.526669 1.47348L6.06 7.00014L0.526669 12.5268C0.456881 12.5866 0.400201 12.6601 0.360186 12.7428C0.32017 12.8255 0.297683 12.9156 0.294137 13.0074C0.290591 13.0993 0.306061 13.1908 0.339577 13.2764C0.373094 13.3619 0.423932 13.4396 0.488902 13.5046C0.553872 13.5695 0.63157 13.6204 0.71712 13.6539C0.80267 13.6874 0.894225 13.7029 0.986038 13.6993C1.07785 13.6958 1.16794 13.6733 1.25065 13.6333C1.33336 13.5933 1.4069 13.5366 1.46667 13.4668L7 7.94014L12.5267 13.4668C12.6542 13.576 12.8183 13.6331 12.986 13.6266C13.1538 13.6201 13.313 13.5506 13.4317 13.4319C13.5504 13.3131 13.62 13.154 13.6265 12.9862C13.633 12.8184 13.5759 12.6543 13.4667 12.5268L7.94 7.00014Z" />
</svg>
);
};
export const KeyboardIcon = () => {
return (
<svg
width="20"
height="15"
viewBox="0 0 20 15"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M17.745 0C18.3417 0 18.914 0.237053 19.336 0.65901C19.7579 1.08097 19.995 1.65326 19.995 2.25V11.755C19.995 12.3517 19.7579 12.924 19.336 13.346C18.914 13.7679 18.3417 14.005 17.745 14.005H2.25C1.95453 14.005 1.66194 13.9468 1.38896 13.8337C1.11598 13.7207 0.867941 13.5549 0.65901 13.346C0.450078 13.1371 0.284344 12.889 0.171271 12.616C0.058198 12.3431 0 12.0505 0 11.755V2.25C0 1.65326 0.237053 1.08097 0.65901 0.65901C1.08097 0.237053 1.65326 0 2.25 0H17.745ZM17.745 1.5H2.25C2.05109 1.5 1.86032 1.57902 1.71967 1.71967C1.57902 1.86032 1.5 2.05109 1.5 2.25V11.755C1.5 12.169 1.836 12.505 2.25 12.505H17.745C17.9439 12.505 18.1347 12.426 18.2753 12.2853C18.416 12.1447 18.495 11.9539 18.495 11.755V2.25C18.495 2.05109 18.416 1.86032 18.2753 1.71967C18.1347 1.57902 17.9439 1.5 17.745 1.5ZM4.75 9.5H15.25C15.44 9.50006 15.6229 9.57224 15.7618 9.70197C15.9006 9.8317 15.9851 10.0093 15.998 10.1989C16.011 10.3885 15.9515 10.5759 15.8316 10.7233C15.7117 10.8707 15.5402 10.9671 15.352 10.993L15.25 11H4.75C4.55998 10.9999 4.37706 10.9278 4.23821 10.798C4.09936 10.6683 4.01493 10.4907 4.00197 10.3011C3.98902 10.1115 4.04852 9.92411 4.16843 9.7767C4.28835 9.62929 4.45975 9.5329 4.648 9.507L4.75 9.5H15.25H4.75ZM14.5 6C14.7652 6 15.0196 6.10536 15.2071 6.29289C15.3946 6.48043 15.5 6.73478 15.5 7C15.5 7.26522 15.3946 7.51957 15.2071 7.70711C15.0196 7.89464 14.7652 8 14.5 8C14.2348 8 13.9804 7.89464 13.7929 7.70711C13.6054 7.51957 13.5 7.26522 13.5 7C13.5 6.73478 13.6054 6.48043 13.7929 6.29289C13.9804 6.10536 14.2348 6 14.5 6ZM8.505 6C8.77022 6 9.02457 6.10536 9.21211 6.29289C9.39964 6.48043 9.505 6.73478 9.505 7C9.505 7.26522 9.39964 7.51957 9.21211 7.70711C9.02457 7.89464 8.77022 8 8.505 8C8.23978 8 7.98543 7.89464 7.79789 7.70711C7.61036 7.51957 7.505 7.26522 7.505 7C7.505 6.73478 7.61036 6.48043 7.79789 6.29289C7.98543 6.10536 8.23978 6 8.505 6ZM5.505 6C5.77022 6 6.02457 6.10536 6.21211 6.29289C6.39964 6.48043 6.505 6.73478 6.505 7C6.505 7.26522 6.39964 7.51957 6.21211 7.70711C6.02457 7.89464 5.77022 8 5.505 8C5.23978 8 4.98543 7.89464 4.79789 7.70711C4.61036 7.51957 4.505 7.26522 4.505 7C4.505 6.73478 4.61036 6.48043 4.79789 6.29289C4.98543 6.10536 5.23978 6 5.505 6ZM11.505 6C11.7702 6 12.0246 6.10536 12.2121 6.29289C12.3996 6.48043 12.505 6.73478 12.505 7C12.505 7.26522 12.3996 7.51957 12.2121 7.70711C12.0246 7.89464 11.7702 8 11.505 8C11.2398 8 10.9854 7.89464 10.7979 7.70711C10.6104 7.51957 10.505 7.26522 10.505 7C10.505 6.73478 10.6104 6.48043 10.7979 6.29289C10.9854 6.10536 11.2398 6 11.505 6ZM4 3C4.26522 3 4.51957 3.10536 4.70711 3.29289C4.89464 3.48043 5 3.73478 5 4C5 4.26522 4.89464 4.51957 4.70711 4.70711C4.51957 4.89464 4.26522 5 4 5C3.73478 5 3.48043 4.89464 3.29289 4.70711C3.10536 4.51957 3 4.26522 3 4C3 3.73478 3.10536 3.48043 3.29289 3.29289C3.48043 3.10536 3.73478 3 4 3ZM6.995 3C7.26022 3 7.51457 3.10536 7.70211 3.29289C7.88964 3.48043 7.995 3.73478 7.995 4C7.995 4.26522 7.88964 4.51957 7.70211 4.70711C7.51457 4.89464 7.26022 5 6.995 5C6.72978 5 6.47543 4.89464 6.28789 4.70711C6.10036 4.51957 5.995 4.26522 5.995 4C5.995 3.73478 6.10036 3.48043 6.28789 3.29289C6.47543 3.10536 6.72978 3 6.995 3ZM9.995 3C10.2602 3 10.5146 3.10536 10.7021 3.29289C10.8896 3.48043 10.995 3.73478 10.995 4C10.995 4.26522 10.8896 4.51957 10.7021 4.70711C10.5146 4.89464 10.2602 5 9.995 5C9.72978 5 9.47543 4.89464 9.28789 4.70711C9.10036 4.51957 8.995 4.26522 8.995 4C8.995 3.73478 9.10036 3.48043 9.28789 3.29289C9.47543 3.10536 9.72978 3 9.995 3ZM12.995 3C13.2602 3 13.5146 3.10536 13.7021 3.29289C13.8896 3.48043 13.995 3.73478 13.995 4C13.995 4.26522 13.8896 4.51957 13.7021 4.70711C13.5146 4.89464 13.2602 5 12.995 5C12.7298 5 12.4754 4.89464 12.2879 4.70711C12.1004 4.51957 11.995 4.26522 11.995 4C11.995 3.73478 12.1004 3.48043 12.2879 3.29289C12.4754 3.10536 12.7298 3 12.995 3ZM15.995 3C16.2602 3 16.5146 3.10536 16.7021 3.29289C16.8896 3.48043 16.995 3.73478 16.995 4C16.995 4.26522 16.8896 4.51957 16.7021 4.70711C16.5146 4.89464 16.2602 5 15.995 5C15.7298 5 15.4754 4.89464 15.2879 4.70711C15.1004 4.51957 14.995 4.26522 14.995 4C14.995 3.73478 15.1004 3.48043 15.2879 3.29289C15.4754 3.10536 15.7298 3 15.995 3Z" />
</svg>
);
};

View File

@@ -1,97 +0,0 @@
import { MuiClickAwayListener, MuiSlide } from '@affine/component';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { CloseIcon } from '@blocksuite/icons';
import { IconButton } from '@toeverything/components/button';
import {
type ShortcutsInfo,
useEdgelessShortcuts,
useGeneralShortcuts,
useMarkdownShortcuts,
usePageShortcuts,
} from '../../../hooks/affine/use-shortcuts';
import { KeyboardIcon } from './icons';
import * as styles from './style.css';
type ModalProps = {
open: boolean;
onClose: () => void;
};
const ShortcutsPanel = ({
shortcutsInfo,
}: {
shortcutsInfo: ShortcutsInfo;
}) => {
return (
<>
<div className={styles.subtitle}>{shortcutsInfo.title}</div>
{Object.entries(shortcutsInfo.shortcuts).map(([title, shortcuts]) => {
return (
<div className={styles.listItem} key={title}>
<span>{title}</span>
<div className={styles.keyContainer}>
{shortcuts.map(key => {
return (
<span className={styles.key} key={key}>
{key}
</span>
);
})}
</div>
</div>
);
})}
</>
);
};
export const ShortcutsModal = ({ open, onClose }: ModalProps) => {
const t = useAFFiNEI18N();
const markdownShortcutsInfo = useMarkdownShortcuts();
const pageShortcutsInfo = usePageShortcuts();
const edgelessShortcutsInfo = useEdgelessShortcuts();
const generalShortcutsInfo = useGeneralShortcuts();
return (
<MuiSlide direction="left" in={open} mountOnEnter unmountOnExit>
<div className={styles.shortcutsModal} data-testid="shortcuts-modal">
<MuiClickAwayListener
onClickAway={() => {
onClose();
}}
>
<div>
<div
className={styles.modalHeader}
style={{ marginBottom: '-28px' }}
>
<div className={styles.title}>
<KeyboardIcon />
{t['Shortcuts']()}
</div>
<IconButton
style={{
position: 'absolute',
right: 6,
top: 6,
}}
onClick={() => {
onClose();
}}
icon={<CloseIcon />}
/>
</div>
<ShortcutsPanel shortcutsInfo={generalShortcutsInfo} />
<ShortcutsPanel shortcutsInfo={pageShortcutsInfo} />
<ShortcutsPanel shortcutsInfo={edgelessShortcutsInfo} />
<ShortcutsPanel shortcutsInfo={markdownShortcutsInfo} />
</div>
</MuiClickAwayListener>
</div>
</MuiSlide>
);
};

View File

@@ -1,89 +0,0 @@
import { globalStyle, style } from '@vanilla-extract/css';
export const shortcutsModal = style({
width: '288px',
height: '74vh',
paddingBottom: '28px',
backgroundColor: 'var(--affine-white)',
boxShadow: 'var(--affine-popover-shadow)',
borderRadius: `var(--affine-popover-radius)`,
overflow: 'auto',
position: 'fixed',
right: '12px',
top: '0',
bottom: '0',
margin: 'auto',
zIndex: 'var(--affine-z-index-modal)',
});
// export const shortcutsModal = style({
// color: 'var(--affine-text-primary-color)',
// fontWeight: '500',
// fontSize: 'var(--affine-font-sm)',
// height: '44px',
// display: 'flex',
// justifyContent: 'center',
// alignItems: 'center',
// svg: {
// width: '20px',
// marginRight: '14px',
// color: 'var(--affine-primary-color)',
// },
// });
export const title = style({
color: 'var(--affine-text-primary-color)',
fontWeight: '500',
fontSize: 'var(--affine-font-sm)',
height: '44px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
});
globalStyle(`${title} svg`, {
width: '20px',
marginRight: '14px',
color: 'var(--affine-primary-color)',
});
export const subtitle = style({
fontWeight: '500',
fontSize: 'var(--affine-font-sm)',
height: '34px',
lineHeight: '36px',
marginTop: '28px',
padding: '0 16px',
});
export const modalHeader = style({
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
paddingTop: '8px 4px 0 4px',
width: '100%',
padding: '8px 16px 0 16px',
position: 'sticky',
left: '0',
top: '0',
background: 'var(--affine-white)',
transition: 'background-color 0.5s',
});
export const listItem = style({
height: '34px',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
fontSize: 'var(--affine-font-sm)',
padding: '0 16px',
});
export const keyContainer = style({
display: 'flex',
});
export const key = style({
selectors: {
'&:not(:last-child)::after': {
content: '+',
margin: '0 4px',
},
},
});

View File

@@ -1,130 +0,0 @@
import { alpha, displayFlex, styled, textEllipsis } from '@affine/component';
export const StyledListItem = styled('div')<{
active?: boolean;
disabled?: boolean;
}>(({ active, disabled }) => {
return {
height: '32px',
color: active
? 'var(--affine-primary-color)'
: 'var(--affine-text-primary-color)',
paddingLeft: '2px',
paddingRight: '2px',
borderRadius: '8px',
cursor: 'pointer',
marginBottom: '4px',
position: 'relative',
flexShrink: 0,
userSelect: 'none',
...displayFlex('flex-start', 'stretch'),
...(disabled
? {
cursor: 'not-allowed',
color: 'var(--affine-border-color)',
}
: {}),
'a > svg, div > svg': {
fontSize: '20px',
marginLeft: '14px',
marginRight: '12px',
color: active
? 'var(--affine-primary-color)'
: 'var(--affine-icon-color)',
},
':hover:not([disabled])': {
backgroundColor: 'var(--affine-hover-color)',
},
};
});
export const StyledCollapseButton = styled('button')<{
collapse: boolean;
show?: boolean;
}>(({ collapse, show = true }) => {
return {
width: '16px',
height: '100%',
...displayFlex('center', 'center'),
fontSize: '16px',
position: 'absolute',
left: '0',
top: '0',
bottom: '0',
margin: 'auto',
color: 'var(--affine-icon-color)',
opacity: '.6',
transition: 'opacity .15s ease-in-out',
display: show ? 'flex' : 'none',
svg: {
transform: `rotate(${collapse ? '0' : '-90'}deg)`,
},
':hover': {
opacity: '1',
},
':focus-visible': {
outline: '-webkit-focus-ring-color auto 1px',
},
};
});
export const StyledCollapseItem = styled('div')<{
disable?: boolean;
active?: boolean;
isOver?: boolean;
textWrap?: boolean;
}>(({ disable = false, active = false, isOver, textWrap = false }) => {
return {
width: '100%',
lineHeight: '1.5',
minHeight: '32px',
borderRadius: '8px',
...displayFlex('flex-start', 'center'),
paddingRight: '2px',
position: 'relative',
color: disable
? 'var(--affine-text-disable-color)'
: active
? 'var(--affine-primary-color)'
: 'var(--affine-text-primary-color)',
cursor: disable ? 'not-allowed' : 'pointer',
background: isOver ? alpha('var(--affine-primary-color)', 0.06) : '',
userSelect: 'none',
...(textWrap
? {
wordBreak: 'break-word',
whiteSpace: 'pre-wrap',
}
: {}),
span: {
flexGrow: '1',
textAlign: 'left',
...textEllipsis(1),
},
'> svg': {
fontSize: '20px',
marginRight: '8px',
flexShrink: '0',
color: active
? 'var(--affine-primary-color)'
: 'var(--affine-icon-color)',
},
':hover': disable
? {}
: {
backgroundColor: 'var(--affine-hover-color)',
'.operation-button': {
visibility: 'visible',
},
},
};
});
export const StyledRouteNavigationWrapper = styled('div')({
height: '32px',
width: '80px',
marginRight: '16px',
...displayFlex('space-between', 'center'),
});

View File

@@ -1,85 +0,0 @@
import { displayFlex, styled, textEllipsis } from '@affine/component';
import { Link } from '@mui/material';
import { baseTheme } from '@toeverything/theme';
export const StyledSliderBarInnerWrapper = styled('div')(() => {
return {
flexGrow: 1,
margin: '0 2px',
position: 'relative',
height: 'calc(100% - 52px * 2)',
display: 'flex',
flexDirection: 'column',
};
});
export const StyledLink = styled(Link)(() => {
return {
flexGrow: 1,
textAlign: 'left',
color: 'inherit',
...displayFlex('flex-start', 'center'),
':visited': {
color: 'inherit',
},
overflow: 'hidden',
div: {
wordBreak: 'break-all',
wordWrap: 'break-word',
whiteSpace: 'nowrap',
...textEllipsis(1),
},
userDrag: 'none',
userSelect: 'none',
appRegion: 'no-drag',
WebkitUserSelect: 'none',
WebkitUserDrag: 'none',
WebkitAppRegion: 'no-drag',
};
});
export const StyledNewPageButton = styled('button')(() => {
return {
width: '100%',
height: '52px',
...displayFlex('flex-start', 'center'),
padding: '0 16px',
svg: {
fontSize: '20px',
color: 'var(--affine-icon-color)',
marginRight: '12px',
},
':hover': {
color: 'var(--affine-primary-color)',
svg: {
color: 'var(--affine-primary-color)',
},
},
};
});
export const StyledSliderModalBackground = styled('div')<{ active: boolean }>(({
active,
}) => {
return {
transition: 'opacity .15s',
pointerEvents: active ? 'auto' : 'none',
opacity: active ? 1 : 0,
position: 'fixed',
top: 0,
left: 0,
right: active ? 0 : '100%',
bottom: 0,
zIndex: parseInt(baseTheme.zIndexModal) - 1,
background: 'var(--affine-background-modal-color)',
};
});
export const StyledScrollWrapper = styled('div')<{
showTopBorder: boolean;
}>(({ showTopBorder }) => {
return {
maxHeight: '50%',
overflowY: 'auto',
borderTop: '1px solid',
borderColor: showTopBorder ? 'var(--affine-border-color)' : 'transparent',
};
});