mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-14 00:26:51 +08:00
init: the first public commit for AFFiNE
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
/* eslint-disable no-restricted-imports */
|
||||
export { autocompleteClasses } from '@mui/material/Autocomplete';
|
||||
export { useAutocomplete } from '@mui/material';
|
||||
@@ -0,0 +1,98 @@
|
||||
import type {
|
||||
FC,
|
||||
MouseEventHandler,
|
||||
CSSProperties,
|
||||
PropsWithChildren,
|
||||
} from 'react';
|
||||
import { styled } from '../styled';
|
||||
import { buttonStatus } from './constants';
|
||||
import { cx } from '../clsx';
|
||||
|
||||
/* Temporary solution, needs to be adjusted */
|
||||
const SIZE_SMALL = 'small' as const;
|
||||
const SIZE_MIDDLE = 'middle' as const;
|
||||
const SIZE_LARGE = 'large' as const;
|
||||
|
||||
const SIZE_CONFIG = {
|
||||
[SIZE_SMALL]: {
|
||||
iconSize: '14px',
|
||||
areaSize: '20px',
|
||||
},
|
||||
[SIZE_MIDDLE]: {
|
||||
iconSize: '20px',
|
||||
areaSize: '32px',
|
||||
},
|
||||
[SIZE_LARGE]: {
|
||||
iconSize: '24px',
|
||||
areaSize: '36px',
|
||||
},
|
||||
} as const;
|
||||
|
||||
type SizeType = keyof typeof SIZE_CONFIG;
|
||||
|
||||
interface IconButtonProps {
|
||||
onClick?: MouseEventHandler;
|
||||
disabled?: boolean;
|
||||
style?: CSSProperties;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const IconButton: FC<PropsWithChildren<IconButtonProps>> = ({
|
||||
children,
|
||||
disabled,
|
||||
onClick,
|
||||
className,
|
||||
...props
|
||||
}) => {
|
||||
return (
|
||||
<Container
|
||||
{...props}
|
||||
onClick={disabled ? undefined : onClick}
|
||||
disabled={disabled}
|
||||
className={cx({ [buttonStatus.disabled]: disabled }, className)}
|
||||
>
|
||||
{children}
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
const Container = styled('button')(({ theme }) => {
|
||||
return {
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: '32px',
|
||||
height: '32px',
|
||||
backgroundColor: theme.affine.palette.white,
|
||||
color: theme.affine.palette.icons,
|
||||
padding: theme.affine.spacing.iconPadding,
|
||||
borderRadius: '5px',
|
||||
|
||||
'& svg': {
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
|
||||
'&:hover': {
|
||||
backgroundColor: theme.affine.palette.hover,
|
||||
},
|
||||
|
||||
[`&${buttonStatus.hover}`]: {
|
||||
backgroundColor: theme.affine.palette.hover,
|
||||
},
|
||||
|
||||
'&:focus': {
|
||||
color: theme.affine.palette.primary,
|
||||
},
|
||||
[`&.${buttonStatus.focus}`]: {
|
||||
color: theme.affine.palette.primary,
|
||||
},
|
||||
|
||||
[`&${buttonStatus.disabled}`]: {
|
||||
cursor: 'not-allowed',
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
import ButtonUnstyled, {
|
||||
buttonUnstyledClasses,
|
||||
} from '@mui/base/ButtonUnstyled';
|
||||
import { styled } from '../styled';
|
||||
|
||||
export const BaseButton = styled(ButtonUnstyled)`
|
||||
fontFamily: 'Helvetica,Arial,"Microsoft Yahei",SimHei,sans-serif',
|
||||
font-size: 0.875rem;
|
||||
border-radius: 8px;
|
||||
padding: 4px 8px;
|
||||
transition: all 150ms ease;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
|
||||
&:hover {
|
||||
}
|
||||
|
||||
&.${buttonUnstyledClasses.active} {
|
||||
}
|
||||
|
||||
&.${buttonUnstyledClasses.focusVisible} {
|
||||
box-shadow: 0 4px 20px 0 rgba(61, 71, 82, 0.1),
|
||||
0 0 0 5px rgba(0, 127, 255, 0.5);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&.${buttonUnstyledClasses.disabled} {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,6 @@
|
||||
export const buttonStatus = {
|
||||
hover: '.hover',
|
||||
focus: '.focus',
|
||||
active: '.focus',
|
||||
disabled: '.disabled',
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
export { BaseButton } from './base-button';
|
||||
export { ListButton } from './list-button';
|
||||
export { IconButton } from './IconButton';
|
||||
@@ -0,0 +1,60 @@
|
||||
import React from 'react';
|
||||
import clsx from 'clsx';
|
||||
import style9 from 'style9';
|
||||
|
||||
import { BaseButton } from './base-button';
|
||||
import { SvgIconProps } from '../svg-icon';
|
||||
|
||||
const styles = style9.create({
|
||||
item: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
width: '220px',
|
||||
paddingLeft: '22px',
|
||||
paddingTop: '4px',
|
||||
paddingBottom: '4px',
|
||||
marginTop: '6px',
|
||||
marginBottom: '6px',
|
||||
borderRadius: '5px',
|
||||
color: '#98ACBD',
|
||||
},
|
||||
item_hover: {
|
||||
backgroundColor: 'rgba(152, 172, 189, 0.1)',
|
||||
},
|
||||
item_text: {
|
||||
fontSize: '15px',
|
||||
lineHeight: '17px',
|
||||
textAlign: 'justify',
|
||||
letterSpacing: '1.5px',
|
||||
marginLeft: '21px',
|
||||
color: '#4C6275',
|
||||
},
|
||||
});
|
||||
|
||||
type ListButtonProps = {
|
||||
className?: string;
|
||||
onClick: () => void;
|
||||
onMouseOver?: () => void;
|
||||
content?: string;
|
||||
children?: () => JSX.Element;
|
||||
hover?: boolean;
|
||||
icon?: React.FC<SvgIconProps>;
|
||||
};
|
||||
|
||||
export const ListButton = (props: ListButtonProps) => {
|
||||
const MenuIcon = props.icon;
|
||||
return (
|
||||
<BaseButton
|
||||
onClick={props.onClick}
|
||||
onMouseOver={props.onMouseOver}
|
||||
className={clsx(
|
||||
styles('item', { item_hover: props.hover }),
|
||||
props.className
|
||||
)}
|
||||
>
|
||||
{MenuIcon && <MenuIcon sx={{ width: 20, height: 20 }} />}
|
||||
<span className={styles('item_text')}>{props.content}</span>
|
||||
{props.children}
|
||||
</BaseButton>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,187 @@
|
||||
import { useRef, useState, ReactElement } from 'react';
|
||||
import {
|
||||
MuiGrow as Grow,
|
||||
MuiPopper as Popper,
|
||||
MuiPopperPlacementType as PopperPlacementType,
|
||||
} from '../mui';
|
||||
import { styled } from '../styled';
|
||||
import { ArrowRightIcon } from '@toeverything/components/icons';
|
||||
import { Divider } from '../divider';
|
||||
|
||||
export interface CascaderItemProps {
|
||||
title: string;
|
||||
shortcut?: string;
|
||||
callback?: () => void;
|
||||
subItems?: CascaderItemProps[];
|
||||
children?: ReactElement | Array<never>;
|
||||
icon?: ReactElement;
|
||||
isDivide?: boolean;
|
||||
}
|
||||
|
||||
interface ItemProps extends CascaderItemProps {
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
interface CascaderProps {
|
||||
items: ItemProps[];
|
||||
anchorEl: Element;
|
||||
placement: PopperPlacementType;
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
children?: ReactElement | never[];
|
||||
icon?: ReactElement;
|
||||
}
|
||||
|
||||
function CascaderItem(props: ItemProps) {
|
||||
const { title, subItems, callback, onClose, children, icon, isDivide } =
|
||||
props;
|
||||
const item_ref = useRef(null);
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
if (isDivide) {
|
||||
return <Divider></Divider>;
|
||||
}
|
||||
|
||||
const on_click_item = () => {
|
||||
if ((subItems && subItems.length > 0) || children) {
|
||||
return;
|
||||
}
|
||||
callback && callback();
|
||||
onClose();
|
||||
};
|
||||
return (
|
||||
<CascaderMenuItem
|
||||
ref={item_ref}
|
||||
onMouseEnter={() => setOpen(true)}
|
||||
onMouseLeave={() => setOpen(false)}
|
||||
onClick={() => on_click_item()}
|
||||
>
|
||||
<IconContainer>{icon}</IconContainer>
|
||||
<div style={{ display: 'flex', flexGrow: 1 }}>
|
||||
<div
|
||||
style={{
|
||||
flexGrow: 1,
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</div>
|
||||
{children || (subItems && subItems.length > 0) ? (
|
||||
<Triangle>
|
||||
<ArrowRightIcon />
|
||||
</Triangle>
|
||||
) : null}
|
||||
</div>
|
||||
{((subItems && subItems.length > 0) || children) && (
|
||||
<Cascader
|
||||
items={subItems}
|
||||
anchorEl={item_ref.current}
|
||||
placement="right-start"
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
children={children}
|
||||
/>
|
||||
)}
|
||||
</CascaderMenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
export function Cascader(props: CascaderProps) {
|
||||
const { items, anchorEl, placement, open, onClose, children } = props;
|
||||
return (
|
||||
<Popper
|
||||
open={open}
|
||||
anchorEl={anchorEl}
|
||||
role={undefined}
|
||||
placement={placement}
|
||||
transition
|
||||
style={{ zIndex: 1000 }}
|
||||
onMouseLeave={onClose}
|
||||
>
|
||||
{({ TransitionProps }) => (
|
||||
<Grow
|
||||
{...TransitionProps}
|
||||
style={{
|
||||
transformOrigin: 'left bottom',
|
||||
}}
|
||||
>
|
||||
<MenuPaper>
|
||||
{children ? children : null}
|
||||
<ul>
|
||||
{items.map(item => {
|
||||
return (
|
||||
<CascaderItem
|
||||
key={item.title}
|
||||
title={item.title}
|
||||
shortcut={item.shortcut}
|
||||
callback={item.callback}
|
||||
subItems={item.subItems || []}
|
||||
onClose={onClose}
|
||||
children={item.children}
|
||||
icon={item.icon}
|
||||
isDivide={item.isDivide}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</MenuPaper>
|
||||
</Grow>
|
||||
)}
|
||||
</Popper>
|
||||
);
|
||||
}
|
||||
|
||||
const MenuPaper = styled('div')(({ theme }) => ({
|
||||
fontFamily: 'PingFang SC',
|
||||
background: '#FFF',
|
||||
boxShadow: '0px 1px 10px rgba(152, 172, 189, 0.6)',
|
||||
borderRadius: '10px 0px 10px 10px',
|
||||
color: '#4C6275',
|
||||
fontWeight: '400',
|
||||
padding: '4px 8px',
|
||||
}));
|
||||
|
||||
const CascaderMenuItem = styled('li')(({ theme }) => ({
|
||||
fontWeight: '400 !important',
|
||||
width: '268px',
|
||||
height: '32px',
|
||||
lineHeight: '32px',
|
||||
display: 'flex',
|
||||
fontSize: '14px',
|
||||
borderRadius: '5px',
|
||||
cursor: 'pointer',
|
||||
'&:hover': {
|
||||
backgroundColor: '#F5F7F8',
|
||||
},
|
||||
}));
|
||||
|
||||
const IconContainer = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
height: '32px',
|
||||
margin: '0 8px',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
lineHeight: '32px',
|
||||
fontSize: '20px',
|
||||
'&, & > svg': {
|
||||
width: '20px',
|
||||
},
|
||||
'& > svg': {
|
||||
height: '20px',
|
||||
},
|
||||
}));
|
||||
|
||||
const Triangle = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
height: '32px',
|
||||
marginRight: '8px',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
lineHeight: '32px',
|
||||
fontSize: '20px',
|
||||
'&, & > svg': {
|
||||
width: '20px',
|
||||
},
|
||||
'& > svg': {
|
||||
height: '20px',
|
||||
},
|
||||
}));
|
||||
@@ -0,0 +1 @@
|
||||
export * from './Cascader';
|
||||
@@ -0,0 +1,28 @@
|
||||
import * as React from 'react';
|
||||
/* eslint-disable no-restricted-imports */
|
||||
import MuiCheckbox, { type CheckboxProps } from '@mui/material/Checkbox';
|
||||
import {
|
||||
CheckBoxUncheckIcon,
|
||||
CheckBoxCheckIcon,
|
||||
} from '@toeverything/components/icons';
|
||||
import { styled } from '../styled';
|
||||
|
||||
export { CheckboxProps };
|
||||
|
||||
export const Checkbox = (props: CheckboxProps) => {
|
||||
return (
|
||||
<StyledCheckbox
|
||||
icon={<CheckBoxUncheckIcon />}
|
||||
checkedIcon={<CheckBoxCheckIcon />}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const StyledCheckbox = styled(MuiCheckbox)`
|
||||
padding: 0;
|
||||
color: #b9cad5;
|
||||
&.Mui-checked {
|
||||
color: #b9cad5;
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,2 @@
|
||||
export { Checkbox } from './Checkbox';
|
||||
export type { CheckboxProps } from './Checkbox';
|
||||
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Caution! this module must not include unstyled components import from `@mui/base`, otherwise, it will break the ClassNameGenerator.
|
||||
* ❌ import { ... } from '@mui/base';
|
||||
* ✅ import { ... } from '@mui/base/utils'; // must be specific base module
|
||||
*
|
||||
* Issue: https://github.com/mui/material-ui/issues/30011#issuecomment-1024993401
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export { unstable_ClassNameGenerator } from '@mui/base/className';
|
||||
@@ -0,0 +1,21 @@
|
||||
import { styled } from '../styled';
|
||||
|
||||
interface ClickableProps {
|
||||
active?: boolean;
|
||||
}
|
||||
|
||||
export const Clickable = styled('div')<ClickableProps>(({ theme, active }) => {
|
||||
return {
|
||||
...theme.affine.typography.sm,
|
||||
backgroundColor: active
|
||||
? theme.affine.palette.hover
|
||||
: theme.affine.palette.white,
|
||||
cursor: 'pointer',
|
||||
color: theme.affine.palette.menu,
|
||||
borderRadius: '5px',
|
||||
|
||||
'&:hover': {
|
||||
backgroundColor: theme.affine.palette.hover,
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
export { Clickable } from './Clickable';
|
||||
@@ -0,0 +1,2 @@
|
||||
import clsx from 'clsx';
|
||||
export const cx = clsx;
|
||||
@@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Calendar as ReactCalendar,
|
||||
type CalendarProps,
|
||||
} from 'react-date-range';
|
||||
import { useTheme } from '../theme';
|
||||
|
||||
export { CalendarProps };
|
||||
|
||||
export const Calendar = (props: CalendarProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return <ReactCalendar color={theme.affine.palette.primary} {...props} />;
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
import {
|
||||
DateRange as ReactDateRange,
|
||||
type DateRangeProps,
|
||||
type Range,
|
||||
} from 'react-date-range';
|
||||
import { useTheme } from '../theme';
|
||||
|
||||
export { DateRangeProps, Range };
|
||||
|
||||
export const DateRange = (props: DateRangeProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<ReactDateRange
|
||||
color={theme.affine.palette.primary}
|
||||
rangeColors={[theme.affine.palette.primary]}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
import 'react-date-range/dist/styles.css'; // main css file
|
||||
import 'react-date-range/dist/theme/default.css'; // theme css file
|
||||
|
||||
// export { DateRange } from 'react-date-range';
|
||||
export { DateRange } from './DateRange';
|
||||
export { Calendar } from './Calendar';
|
||||
|
||||
export type { DateRangeProps, Range } from './DateRange';
|
||||
export type { CalendarProps } from './Calendar';
|
||||
|
||||
// export const NewDateRange = DateRange;
|
||||
@@ -0,0 +1,52 @@
|
||||
import type { FC, ReactNode, CSSProperties } from 'react';
|
||||
import { styled } from '../styled';
|
||||
import { MuiDivider } from '../mui';
|
||||
import type { MuiDividerProps } from '../mui';
|
||||
|
||||
interface DividerProps {
|
||||
orientation?: 'horizontal' | 'vertical';
|
||||
textAlign?: 'center' | 'start' | 'end';
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
}
|
||||
|
||||
const _textAlignMap: Record<
|
||||
DividerProps['textAlign'],
|
||||
MuiDividerProps['textAlign']
|
||||
> = {
|
||||
center: 'center',
|
||||
start: 'left',
|
||||
end: 'right',
|
||||
};
|
||||
|
||||
export const Divider: FC<DividerProps> = ({
|
||||
orientation = 'horizontal',
|
||||
textAlign = 'center',
|
||||
children,
|
||||
}) => {
|
||||
return (
|
||||
<StyledMuiDivider
|
||||
orientation={orientation}
|
||||
textAlign={_textAlignMap[textAlign]}
|
||||
variant="fullWidth"
|
||||
>
|
||||
{children}
|
||||
</StyledMuiDivider>
|
||||
);
|
||||
};
|
||||
|
||||
const StyledMuiDivider = styled(MuiDivider)<Pick<DividerProps, 'orientation'>>(
|
||||
({ orientation }) => {
|
||||
if (orientation === 'horizontal') {
|
||||
return {
|
||||
marginTop: '6px',
|
||||
marginBottom: '6px',
|
||||
};
|
||||
}
|
||||
return {
|
||||
marginLeft: '6px',
|
||||
marginRight: '6px',
|
||||
};
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1 @@
|
||||
export * from './Divider';
|
||||
@@ -0,0 +1,62 @@
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
|
||||
type Equal<X, Y, TrueValue, FalseValue> =
|
||||
// prettier-ignore
|
||||
(<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2)
|
||||
? TrueValue
|
||||
: FalseValue;
|
||||
|
||||
type ExtractEvent<K extends keyof DocumentEventMap, E> = Equal<
|
||||
DocumentEventMap[K],
|
||||
E,
|
||||
K,
|
||||
never
|
||||
>;
|
||||
|
||||
type MouseEventName = {
|
||||
[K in keyof DocumentEventMap]: ExtractEvent<K, MouseEvent>;
|
||||
}[keyof DocumentEventMap];
|
||||
|
||||
type TouchEventName = {
|
||||
[K in keyof DocumentEventMap]: ExtractEvent<K, TouchEvent>;
|
||||
}[keyof DocumentEventMap];
|
||||
|
||||
type PointerEventName = {
|
||||
[K in keyof DocumentEventMap]: ExtractEvent<K, PointerEvent>;
|
||||
}[keyof DocumentEventMap];
|
||||
|
||||
interface EventAwayProps {
|
||||
eventName: MouseEventName | TouchEventName | PointerEventName;
|
||||
anchor: HTMLElement | HTMLElement[];
|
||||
}
|
||||
|
||||
export const useEventAway = ({ eventName, anchor }: EventAwayProps) => {
|
||||
const [away, set_away] = useState(true);
|
||||
const anchors = useRef<HTMLElement[]>([]);
|
||||
anchors.current = Array.isArray(anchor) ? anchor : [anchor];
|
||||
|
||||
useEffect(() => {
|
||||
const listener = (event: MouseEvent | TouchEvent | PointerEvent) => {
|
||||
if (event.composedPath) {
|
||||
const composed_path = event.composedPath();
|
||||
const found = composed_path.find(node =>
|
||||
anchors.current.includes(node as HTMLElement)
|
||||
);
|
||||
set_away(!found);
|
||||
} else {
|
||||
const found = anchors.current.find(anchor =>
|
||||
anchor.contains(event.target as HTMLElement)
|
||||
);
|
||||
set_away(!found);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener(eventName, listener);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener(eventName, listener);
|
||||
};
|
||||
}, [eventName]);
|
||||
|
||||
return { away };
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
// Base abstract feature for all UI components
|
||||
export { Theme, useTheme, withTheme, ThemeProvider } from './theme';
|
||||
export { styled } from './styled';
|
||||
export type { SxProps } from './styled';
|
||||
|
||||
export * from './mui';
|
||||
export * from './svg-icon';
|
||||
|
||||
export { StaticDatePicker } from '@mui/x-date-pickers/StaticDatePicker';
|
||||
export { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns';
|
||||
export { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
|
||||
export {
|
||||
PickersDay,
|
||||
type PickersDayProps,
|
||||
} from '@mui/x-date-pickers/PickersDay';
|
||||
|
||||
// Components
|
||||
export { BaseButton, ListButton, IconButton } from './button';
|
||||
export { TransitionsModal } from './model';
|
||||
export { Popover, PopoverContainer } from './popover';
|
||||
export type { PopoverProps } from './popover';
|
||||
export { Popper } from './popper';
|
||||
export type { PopperProps, PopperHandler } from './popper';
|
||||
|
||||
export { OldSelect, Select, Option } from './select';
|
||||
export { message } from './message';
|
||||
export { Input } from './input';
|
||||
export type { InputProps } from './input';
|
||||
export { Tooltip } from './tooltip';
|
||||
export { usePatchNodes } from './patch-elements';
|
||||
export type { PatchNode, UnPatchNode } from './patch-elements';
|
||||
export { Tag } from './tag';
|
||||
export type { TagProps } from './tag';
|
||||
export { Divider } from './divider';
|
||||
export { autocompleteClasses, useAutocomplete } from './autocomplete';
|
||||
export { Slider } from './slider';
|
||||
export { Typography } from './typography';
|
||||
export { ListItem, ListIcon } from './list';
|
||||
export { Clickable } from './clickable';
|
||||
export { DateRange, Calendar } from './date';
|
||||
export type { DateRangeProps, Range, CalendarProps } from './date';
|
||||
export { Radio } from './radio';
|
||||
export type { RadioProps } from './radio';
|
||||
export { Checkbox } from './checkbox';
|
||||
export type { CheckboxProps } from './checkbox';
|
||||
export * from './cascader';
|
||||
export { Switch } from './switch';
|
||||
export type { SwitchProps } from './switch';
|
||||
/* types */
|
||||
export type { SvgIconProps } from './svg-icon';
|
||||
|
||||
export * from './clsx';
|
||||
@@ -0,0 +1,64 @@
|
||||
import { forwardRef, type ForwardedRef } from 'react';
|
||||
/* eslint-disable no-restricted-imports */
|
||||
import InputUnstyled, {
|
||||
inputUnstyledClasses,
|
||||
type InputUnstyledProps,
|
||||
} from '@mui/base/InputUnstyled';
|
||||
import { styled } from '../styled';
|
||||
|
||||
/**
|
||||
* Input is extend by mui InputUnstyled
|
||||
*
|
||||
* InputUnstyled Demos:
|
||||
*
|
||||
* - [Input](https://mui.com/base/react-input/)
|
||||
*
|
||||
* InputUnstyled API:
|
||||
*
|
||||
* - [InputUnstyled API](https://mui.com/base/api/input-unstyled/)
|
||||
*
|
||||
* **/
|
||||
export type InputProps = InputUnstyledProps;
|
||||
|
||||
export const Input = forwardRef(function CustomInput(
|
||||
props: InputUnstyledProps,
|
||||
ref: ForwardedRef<HTMLDivElement>
|
||||
) {
|
||||
const { components, ...other } = props;
|
||||
return (
|
||||
<InputUnstyled
|
||||
components={{
|
||||
Root: StyledInputRoot,
|
||||
Input: StyledInputElement,
|
||||
...components,
|
||||
}}
|
||||
{...other}
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const StyledInputRoot = styled('div')(({ theme }) => ({
|
||||
height: '32px',
|
||||
display: 'flex',
|
||||
border: `1px solid ${theme.affine.palette.borderColor}`,
|
||||
borderRadius: '10px',
|
||||
color: `${theme.affine.palette.secondaryText}`,
|
||||
padding: '0 12px',
|
||||
fontSize: '14px',
|
||||
lineHeight: '1.5',
|
||||
transition: 'border .1s',
|
||||
[`&.${inputUnstyledClasses.focused}`]: {
|
||||
borderColor: `${theme.affine.palette.primary}`,
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledInputElement = styled('input')(({ theme }) => ({
|
||||
fontSize: '14px',
|
||||
lineHeight: '1.5',
|
||||
color: `${theme.affine.palette.secondaryText}`,
|
||||
flexGrow: 1,
|
||||
'&::placeholder': {
|
||||
color: `${theme.affine.palette.borderColor}`,
|
||||
},
|
||||
}));
|
||||
@@ -0,0 +1,2 @@
|
||||
export { Input } from './Input';
|
||||
export type { InputProps } from './Input';
|
||||
@@ -0,0 +1,12 @@
|
||||
import { styled } from '../styled';
|
||||
|
||||
export const ListIcon = styled('div')(({ theme }) => {
|
||||
return {
|
||||
color: theme.affine.palette.icons,
|
||||
height: '20px',
|
||||
|
||||
'& svg': {
|
||||
fontSize: '20px',
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import type { FC, PropsWithChildren, CSSProperties } from 'react';
|
||||
import { Clickable } from '../clickable';
|
||||
import { styled } from '../styled';
|
||||
|
||||
interface ListItemProps {
|
||||
active?: boolean;
|
||||
onClick?: () => void;
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
}
|
||||
|
||||
export const ListItem: FC<PropsWithChildren<ListItemProps>> = ({
|
||||
active,
|
||||
children,
|
||||
onClick,
|
||||
className,
|
||||
style,
|
||||
}) => {
|
||||
return (
|
||||
<Container
|
||||
active={active}
|
||||
onClick={onClick}
|
||||
className={className}
|
||||
style={style}
|
||||
>
|
||||
{children}
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
const Container = styled(Clickable)({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: '6px 12px',
|
||||
});
|
||||
@@ -0,0 +1,2 @@
|
||||
export { ListItem } from './ListItem';
|
||||
export { ListIcon } from './ListIcon';
|
||||
@@ -0,0 +1,48 @@
|
||||
import type { FC, ReactNode } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { styled } from '../styled';
|
||||
|
||||
import type { ContainerProps } from './types';
|
||||
|
||||
interface ShowProps {
|
||||
Container: FC<ContainerProps>;
|
||||
/**
|
||||
* 自动关闭延时,单位毫秒。设为 0 时,不自动关闭。默认 2000
|
||||
*/
|
||||
duration?: number;
|
||||
content: ReactNode;
|
||||
}
|
||||
|
||||
export const show = ({ Container, duration = 2000, content }: ShowProps) => {
|
||||
const root_element = document.createElement('div');
|
||||
document.body.appendChild(root_element);
|
||||
|
||||
function close() {
|
||||
document.body.removeChild(root_element);
|
||||
}
|
||||
|
||||
const react_root = createRoot(root_element);
|
||||
|
||||
react_root.render(
|
||||
<PortalContainer>
|
||||
<Container content={content} duration={duration} close={close} />
|
||||
</PortalContainer>
|
||||
);
|
||||
|
||||
if (duration > 0) {
|
||||
setTimeout(() => {
|
||||
close();
|
||||
}, duration);
|
||||
}
|
||||
|
||||
return close;
|
||||
};
|
||||
|
||||
const PortalContainer = styled('div')({
|
||||
position: 'fixed',
|
||||
top: '100px',
|
||||
left: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
backgroundColor: '#fff',
|
||||
zIndex: 100,
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
import { show } from './base';
|
||||
import { Success } from './success';
|
||||
|
||||
interface SuccessProps {
|
||||
/**
|
||||
* 自动关闭延时,单位毫秒。默认2000
|
||||
*/
|
||||
duration?: number;
|
||||
content: ReactNode;
|
||||
}
|
||||
|
||||
export const message = {
|
||||
success({ duration, content }: SuccessProps) {
|
||||
return show({ Container: Success, duration, content });
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
import type { FC } from 'react';
|
||||
import { styled } from '../styled';
|
||||
import type { ContainerProps } from './types';
|
||||
|
||||
export const Success: FC<ContainerProps> = ({ content }) => {
|
||||
return <Container>{content}</Container>;
|
||||
};
|
||||
|
||||
const Container = styled('div')({
|
||||
maxWidth: '200px',
|
||||
backgroundColor: 'rgba(64, 223, 155)',
|
||||
borderRadius: '4px',
|
||||
padding: '8px',
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export interface ContainerProps {
|
||||
content: ReactNode;
|
||||
duration: number;
|
||||
close: () => void;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { styled } from '@mui/system';
|
||||
import ModalUnstyled from '@mui/base/ModalUnstyled';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import Fade from '@mui/material/Fade';
|
||||
|
||||
import { MuiClickAwayListener } from '../mui';
|
||||
|
||||
const Modal = styled(ModalUnstyled)`
|
||||
position: fixed;
|
||||
z-index: 1300;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
type TransitionsModalProps = {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
children: JSX.Element;
|
||||
};
|
||||
|
||||
export const TransitionsModal = (props: TransitionsModalProps) => {
|
||||
return (
|
||||
<Modal
|
||||
aria-labelledby="transition-modal-title"
|
||||
aria-describedby="transition-modal-description"
|
||||
open={props.open}
|
||||
onClose={props.onClose}
|
||||
closeAfterTransition
|
||||
>
|
||||
<MuiClickAwayListener onClickAway={props.onClose}>
|
||||
<Fade in={props.open} timeout={300}>
|
||||
{props.children}
|
||||
</Fade>
|
||||
</MuiClickAwayListener>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,240 @@
|
||||
/**
|
||||
* There are many places that rely on Mui in the current project, which are gathered in this file
|
||||
*/
|
||||
|
||||
/* eslint-disable no-restricted-imports */
|
||||
|
||||
import {
|
||||
Box,
|
||||
Collapse,
|
||||
IconButton,
|
||||
CircularProgress,
|
||||
Divider,
|
||||
Typography,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Avatar,
|
||||
Popover,
|
||||
TextField,
|
||||
Modal,
|
||||
Button,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemText,
|
||||
Tooltip,
|
||||
tooltipClasses,
|
||||
Tabs,
|
||||
Tab,
|
||||
OutlinedInput,
|
||||
InputAdornment,
|
||||
Grow,
|
||||
Paper,
|
||||
MenuList,
|
||||
ClickAwayListener,
|
||||
Popper,
|
||||
Select,
|
||||
Switch,
|
||||
Grid,
|
||||
Container,
|
||||
Snackbar,
|
||||
InputBase,
|
||||
FormControlLabel,
|
||||
Checkbox,
|
||||
Input,
|
||||
Radio,
|
||||
Zoom,
|
||||
} from '@mui/material';
|
||||
|
||||
import type {
|
||||
PopoverProps,
|
||||
PopperPlacementType,
|
||||
SelectChangeEvent,
|
||||
TooltipProps,
|
||||
DividerProps,
|
||||
} from '@mui/material';
|
||||
import { ListItemButton } from '@mui/material';
|
||||
|
||||
export { alpha } from '@mui/system';
|
||||
|
||||
/**
|
||||
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
||||
*/
|
||||
export type MuiPopoverProps = PopoverProps;
|
||||
/**
|
||||
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
||||
*/
|
||||
export type MuiPopperPlacementType = PopperPlacementType;
|
||||
/**
|
||||
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
||||
*/
|
||||
export type MuiSelectChangeEvent<T = string> = SelectChangeEvent<T>;
|
||||
/**
|
||||
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
||||
*/
|
||||
export type MuiTooltipProps = TooltipProps;
|
||||
/**
|
||||
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
||||
*/
|
||||
export type MuiDividerProps = DividerProps;
|
||||
|
||||
/**
|
||||
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
||||
*/
|
||||
export const MuiBox = Box;
|
||||
/**
|
||||
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
||||
*/
|
||||
export const MuiCollapse = Collapse;
|
||||
/**
|
||||
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
||||
*/
|
||||
export const MuiIconButton = IconButton;
|
||||
/**
|
||||
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
||||
*/
|
||||
export const MuiCircularProgress = CircularProgress;
|
||||
/**
|
||||
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
||||
*/
|
||||
export const MuiDivider = Divider;
|
||||
/**
|
||||
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
||||
*/
|
||||
export const MuiTypography = Typography;
|
||||
/**
|
||||
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
||||
*/
|
||||
export const MuiMenu = Menu;
|
||||
/**
|
||||
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
||||
*/
|
||||
export const MuiMenuItem = MenuItem;
|
||||
/**
|
||||
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
||||
*/
|
||||
export const MuiAvatar = Avatar;
|
||||
/**
|
||||
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
||||
*/
|
||||
export const MuiPopover = Popover;
|
||||
/**
|
||||
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
||||
*/
|
||||
export const MuiTextField = TextField;
|
||||
/**
|
||||
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
||||
*/
|
||||
export const MuiModal = Modal;
|
||||
/**
|
||||
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
||||
*/
|
||||
export const MuiButton = Button;
|
||||
/**
|
||||
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
||||
*/
|
||||
export const MuiList = List;
|
||||
/**
|
||||
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
||||
*/
|
||||
export const MuiListItem = ListItem;
|
||||
/**
|
||||
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
||||
*/
|
||||
export const MuiListItemText = ListItemText;
|
||||
/**
|
||||
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
||||
*/
|
||||
export const MuiListItemButton = ListItemButton;
|
||||
/**
|
||||
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
||||
*/
|
||||
export const MuiTooltip = Tooltip;
|
||||
/**
|
||||
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
||||
*/
|
||||
export const MuiTabs = Tabs;
|
||||
/**
|
||||
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
||||
*/
|
||||
export const MuiTab = Tab;
|
||||
/**
|
||||
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
||||
*/
|
||||
export const MuiOutlinedInput = OutlinedInput;
|
||||
/**
|
||||
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
||||
*/
|
||||
export const MuiInputAdornment = InputAdornment;
|
||||
/**
|
||||
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
||||
*/
|
||||
export const MuiGrow = Grow;
|
||||
/**
|
||||
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
||||
*/
|
||||
export const MuiPaper = Paper;
|
||||
/**
|
||||
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
||||
*/
|
||||
export const MuiMenuList = MenuList;
|
||||
|
||||
export const MuiClickAwayListener = ClickAwayListener;
|
||||
/**
|
||||
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
||||
*/
|
||||
export const MuiPopper = Popper;
|
||||
/**
|
||||
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
||||
*/
|
||||
export const MuiSelect = Select;
|
||||
/**
|
||||
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
||||
*/
|
||||
export const MuiSwitch = Switch;
|
||||
/**
|
||||
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
||||
*/
|
||||
export const MuiGrid = Grid;
|
||||
/**
|
||||
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
||||
*/
|
||||
export const MuiContainer = Container;
|
||||
/**
|
||||
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
||||
*/
|
||||
export const muiTooltipClasses = tooltipClasses;
|
||||
|
||||
/**
|
||||
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
||||
*/
|
||||
export const MuiSnackbar = Snackbar;
|
||||
|
||||
/**
|
||||
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
||||
*/
|
||||
export const MuiInputBase = InputBase;
|
||||
|
||||
/**
|
||||
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
||||
*/
|
||||
export const MuiFormControlLabel = FormControlLabel;
|
||||
|
||||
/**
|
||||
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
||||
*/
|
||||
export const MuiCheckbox = Checkbox;
|
||||
|
||||
/**
|
||||
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
||||
*/
|
||||
export const MuiInput = Input;
|
||||
|
||||
/**
|
||||
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
||||
*/
|
||||
export const MuiZoom = Zoom;
|
||||
|
||||
/**
|
||||
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
||||
*/
|
||||
export const MuiRadio = Radio;
|
||||
@@ -0,0 +1,39 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { useState, Fragment } from 'react';
|
||||
import { has } from '@toeverything/utils';
|
||||
|
||||
export type UnPatchNode = () => void;
|
||||
export type PatchNode = (key: string, node: ReactNode) => UnPatchNode;
|
||||
|
||||
export const usePatchNodes = () => {
|
||||
const [nodes, set_nodes] = useState<Record<string, ReactNode>>({});
|
||||
|
||||
const patch_node: PatchNode = (key: string, node: ReactNode) => {
|
||||
set_nodes(oldNodes => ({ ...oldNodes, [key]: node }));
|
||||
return () => {
|
||||
set_nodes(oldNodes => {
|
||||
const nodes = { ...oldNodes };
|
||||
delete nodes[key];
|
||||
return nodes;
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
const has_node = (key: string) => {
|
||||
return has(nodes, key);
|
||||
};
|
||||
|
||||
const patched_nodes = (
|
||||
<>
|
||||
{Object.entries(nodes).map(([key, node]) => {
|
||||
return <Fragment key={key}>{node}</Fragment>;
|
||||
})}
|
||||
</>
|
||||
);
|
||||
|
||||
return {
|
||||
patch: patch_node,
|
||||
has: has_node,
|
||||
patchedNodes: patched_nodes,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
import type { MuiPopperPlacementType as PopperPlacementType } from '../mui';
|
||||
import React, { forwardRef, type PropsWithChildren } from 'react';
|
||||
import { type PopperHandler, Popper } from '../popper';
|
||||
import { PopoverContainer } from './container';
|
||||
import type { PopoverProps, PopoverDirection } from './interface';
|
||||
|
||||
const placementToContainerDirection: Record<
|
||||
PopperPlacementType,
|
||||
PopoverDirection
|
||||
> = {
|
||||
top: 'none',
|
||||
'top-start': 'left-bottom',
|
||||
'top-end': 'right-bottom',
|
||||
right: 'none',
|
||||
'right-start': 'left-top',
|
||||
'right-end': 'left-bottom',
|
||||
bottom: 'none',
|
||||
'bottom-start': 'left-top',
|
||||
'bottom-end': 'right-top',
|
||||
left: 'none',
|
||||
'left-start': 'right-top',
|
||||
'left-end': 'right-bottom',
|
||||
auto: 'none',
|
||||
'auto-start': 'none',
|
||||
'auto-end': 'none',
|
||||
};
|
||||
|
||||
export const Popover = forwardRef<
|
||||
PopperHandler,
|
||||
PropsWithChildren<PopoverProps>
|
||||
>((props, ref) => {
|
||||
const { popoverDirection, placement, content, children, style } = props;
|
||||
return (
|
||||
<Popper
|
||||
{...props}
|
||||
ref={ref}
|
||||
content={
|
||||
<PopoverContainer
|
||||
style={style}
|
||||
direction={
|
||||
popoverDirection ||
|
||||
placementToContainerDirection[placement]
|
||||
}
|
||||
>
|
||||
{content}
|
||||
</PopoverContainer>
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</Popper>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
import { styled } from '../styled';
|
||||
import { PopoverContainerProps } from './interface';
|
||||
|
||||
const border_radius_map: Record<PopoverContainerProps['direction'], string> = {
|
||||
none: '10px',
|
||||
'left-top': '0 10px 10px 10px',
|
||||
'left-bottom': '10px 10px 10px 0',
|
||||
'right-top': '10px 0 10px 10px',
|
||||
'right-bottom': '10px 10px 0 10px',
|
||||
};
|
||||
|
||||
export const PopoverContainer = styled('div')<
|
||||
Pick<PopoverContainerProps, 'direction'>
|
||||
>(({ theme, direction, style }) => {
|
||||
const shadow = theme.affine.shadows.shadowSxDownLg;
|
||||
const white = theme.affine.palette.white;
|
||||
|
||||
const border_radius =
|
||||
border_radius_map[direction] || border_radius_map['left-top'];
|
||||
return {
|
||||
boxShadow: shadow,
|
||||
borderRadius: border_radius,
|
||||
padding: '8px 4px',
|
||||
backgroundColor: white,
|
||||
...style,
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './Popover';
|
||||
export * from './interface';
|
||||
export { PopoverContainer } from './container';
|
||||
@@ -0,0 +1,23 @@
|
||||
import type { ReactNode, CSSProperties } from 'react';
|
||||
import { PopperProps } from '../popper';
|
||||
|
||||
export type PopoverDirection =
|
||||
| 'none'
|
||||
| 'left-top'
|
||||
| 'left-bottom'
|
||||
| 'right-top'
|
||||
| 'right-bottom';
|
||||
|
||||
export interface PopoverContainerProps {
|
||||
children?: ReactNode;
|
||||
/**
|
||||
* The pop-up window points to. The pop-up window has three rounded corners, one is a right angle, and the right angle is the direction of the pop-up window.
|
||||
*/
|
||||
direction?: PopoverDirection;
|
||||
style?: CSSProperties;
|
||||
}
|
||||
export type PopoverProps = {
|
||||
// Popover border-radius style will auto set by placement, popoverDirection can custom set it
|
||||
popoverDirection?: PopoverDirection;
|
||||
style?: CSSProperties;
|
||||
} & PopperProps;
|
||||
@@ -0,0 +1,92 @@
|
||||
import React, { forwardRef } from 'react';
|
||||
import { PopperArrowProps } from './interface';
|
||||
import { styled } from '../styled';
|
||||
|
||||
export const PopperArrow = forwardRef<HTMLElement, PopperArrowProps>(
|
||||
({ placement }, ref) => {
|
||||
return <StyledArrow placement={placement} ref={ref} />;
|
||||
}
|
||||
);
|
||||
|
||||
const getArrowStyle = (placement: PopperArrowProps['placement']) => {
|
||||
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 #98ACBD 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: `#98ACBD 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 #98ACBD`,
|
||||
},
|
||||
};
|
||||
}
|
||||
if (placement.indexOf('right') === 0) {
|
||||
return {
|
||||
left: 0,
|
||||
marginLeft: '-0.9em',
|
||||
height: '3em',
|
||||
width: '1em',
|
||||
'&::before': {
|
||||
borderWidth: '1em 1em 1em 0',
|
||||
borderColor: `transparent #98ACBD 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),
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,186 @@
|
||||
import React, {
|
||||
forwardRef,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
|
||||
import {
|
||||
MuiPopper,
|
||||
MuiClickAwayListener as ClickAwayListener,
|
||||
MuiGrow as Grow,
|
||||
} from '../mui';
|
||||
import { styled } from '../styled';
|
||||
|
||||
import { PopperProps, PopperHandler, VirtualElement } from './interface';
|
||||
import { useTheme } from '../theme';
|
||||
import { PopperArrow } from './PopoverArrow';
|
||||
export const Popper = forwardRef<PopperHandler, PropsWithChildren<PopperProps>>(
|
||||
(
|
||||
{
|
||||
children,
|
||||
content,
|
||||
anchor: propsAnchor,
|
||||
placement = 'top-start',
|
||||
defaultVisible = false,
|
||||
container,
|
||||
keepMounted = false,
|
||||
visible: propsVisible,
|
||||
trigger = 'hover',
|
||||
pointerEnterDelay = 100,
|
||||
pointerLeaveDelay = 100,
|
||||
onVisibleChange,
|
||||
popoverStyle,
|
||||
popoverClassName,
|
||||
anchorStyle,
|
||||
anchorClassName,
|
||||
zIndex,
|
||||
offset = [0, 5],
|
||||
showArrow = false,
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const [anchorEl, setAnchorEl] = useState<VirtualElement>(null);
|
||||
const [visible, setVisible] = useState(defaultVisible);
|
||||
const [arrowRef, setArrowRef] = useState<HTMLElement>(null);
|
||||
|
||||
const pointerLeaveTimer = useRef<number>();
|
||||
const pointerEnterTimer = useRef<number>();
|
||||
|
||||
const visibleControlledByParent = typeof propsVisible !== 'undefined';
|
||||
const isAnchorCustom = typeof propsAnchor !== '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 theme = useTheme();
|
||||
|
||||
const onPointerEnterHandler = () => {
|
||||
if (!hasHoverTrigger || visibleControlledByParent) {
|
||||
return;
|
||||
}
|
||||
window.clearTimeout(pointerLeaveTimer.current);
|
||||
|
||||
pointerEnterTimer.current = window.setTimeout(() => {
|
||||
setVisible(true);
|
||||
}, pointerEnterDelay);
|
||||
};
|
||||
|
||||
const onPointerLeaveHandler = () => {
|
||||
if (!hasHoverTrigger || visibleControlledByParent) {
|
||||
return;
|
||||
}
|
||||
window.clearTimeout(pointerEnterTimer.current);
|
||||
pointerLeaveTimer.current = window.setTimeout(() => {
|
||||
setVisible(false);
|
||||
}, pointerLeaveDelay);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
onVisibleChange?.(visible);
|
||||
}, [visible, onVisibleChange]);
|
||||
|
||||
useImperativeHandle(ref, () => {
|
||||
return {
|
||||
setVisible: (visible: boolean) => {
|
||||
!visibleControlledByParent && setVisible(visible);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<ClickAwayListener
|
||||
onClickAway={() => {
|
||||
setVisible(false);
|
||||
}}
|
||||
>
|
||||
<Container>
|
||||
{isAnchorCustom ? null : (
|
||||
<div
|
||||
ref={(dom: HTMLDivElement) => setAnchorEl(dom)}
|
||||
onClick={() => {
|
||||
if (
|
||||
!hasClickTrigger ||
|
||||
visibleControlledByParent
|
||||
) {
|
||||
return;
|
||||
}
|
||||
setVisible(!visible);
|
||||
}}
|
||||
onPointerEnter={onPointerEnterHandler}
|
||||
onPointerLeave={onPointerLeaveHandler}
|
||||
style={anchorStyle}
|
||||
className={anchorClassName}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
<MuiPopper
|
||||
open={
|
||||
visibleControlledByParent ? propsVisible : visible
|
||||
}
|
||||
sx={{ zIndex: zIndex || theme.affine.zIndex.popover }}
|
||||
anchorEl={isAnchorCustom ? propsAnchor : anchorEl}
|
||||
placement={placement}
|
||||
container={container}
|
||||
keepMounted={keepMounted}
|
||||
transition
|
||||
modifiers={[
|
||||
{
|
||||
name: 'offset',
|
||||
options: {
|
||||
offset,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'arrow',
|
||||
enabled: showArrow,
|
||||
options: {
|
||||
element: arrowRef,
|
||||
},
|
||||
},
|
||||
]}
|
||||
>
|
||||
{({ TransitionProps }) => (
|
||||
<Grow {...TransitionProps}>
|
||||
<div
|
||||
onPointerEnter={onPointerEnterHandler}
|
||||
onPointerLeave={onPointerLeaveHandler}
|
||||
style={popoverStyle}
|
||||
className={popoverClassName}
|
||||
>
|
||||
{showArrow && (
|
||||
<PopperArrow
|
||||
placement={placement}
|
||||
ref={setArrowRef}
|
||||
/>
|
||||
)}
|
||||
{content}
|
||||
</div>
|
||||
</Grow>
|
||||
)}
|
||||
</MuiPopper>
|
||||
</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',
|
||||
});
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './Popper';
|
||||
export * from './interface';
|
||||
@@ -0,0 +1,70 @@
|
||||
import type { CSSProperties, ReactNode } from 'react';
|
||||
import type { MuiPopperPlacementType as PopperPlacementType } from '../mui';
|
||||
|
||||
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;
|
||||
// Position of Popover
|
||||
placement?: PopperPlacementType;
|
||||
|
||||
// The popover will pop up based on the anchor position
|
||||
// And if this parameter is passed, children will not be rendered
|
||||
anchor?: VirtualElement | (() => VirtualElement);
|
||||
|
||||
// Whether the default is implicit
|
||||
defaultVisible?: boolean;
|
||||
|
||||
// Used to manually control the visibility of the Popover
|
||||
visible?: boolean;
|
||||
|
||||
// A HTML element or function that returns one. The container will have the portal children appended to it.
|
||||
// By default, it uses the body of the top-level document object, so it's simply document.body most of the time.
|
||||
container?: HTMLElement;
|
||||
|
||||
// Always keep the children in the DOM. This prop can be useful in SEO situation or when you want to maximize the responsiveness of the Popper
|
||||
keepMounted?: 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 style
|
||||
anchorStyle?: CSSProperties;
|
||||
|
||||
// Anchor class name
|
||||
anchorClassName?: string;
|
||||
|
||||
// Popover z-index
|
||||
zIndex?: number;
|
||||
|
||||
offset?: [number, number];
|
||||
|
||||
showArrow?: boolean;
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
import * as React from 'react';
|
||||
/* eslint-disable no-restricted-imports */
|
||||
import MuiRadio, { type RadioProps } from '@mui/material/Radio';
|
||||
import { styled } from '../styled';
|
||||
|
||||
// TODO: sync online icon
|
||||
// import {
|
||||
// SingleSelectBoxUncheckIcon,
|
||||
// SingleSelectBoxCheckIcon,
|
||||
// } from '@toeverything/components/icons';
|
||||
|
||||
export { RadioProps };
|
||||
|
||||
export const Radio = (props: RadioProps) => {
|
||||
return (
|
||||
<StyledRadio
|
||||
// icon={<SingleSelectBoxUncheckIcon />}
|
||||
// checkedIcon={<SingleSelectBoxCheckIcon />}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const StyledRadio = styled(MuiRadio)`
|
||||
padding: 0;
|
||||
color: #b9cad5;
|
||||
&.Mui-checked {
|
||||
color: #b9cad5;
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,2 @@
|
||||
export { Radio } from './Radio';
|
||||
export type { RadioProps } from './Radio';
|
||||
@@ -0,0 +1,48 @@
|
||||
import { useCallback } from 'react';
|
||||
import { styled } from '../styled';
|
||||
import type { FC, CSSProperties, ChangeEvent } from 'react';
|
||||
|
||||
/**
|
||||
* WARNING: This component is about to be deprecated, use Select replace
|
||||
*
|
||||
* **/
|
||||
const StyledSelect = styled('select')({
|
||||
border: '1px solid #aaa',
|
||||
});
|
||||
|
||||
interface Props {
|
||||
value: string;
|
||||
options?: Array<{ label?: string; value?: string; key?: string }>;
|
||||
onChange: (value: string) => void;
|
||||
extraStyle?: CSSProperties;
|
||||
}
|
||||
|
||||
export const OldSelect: FC<Props> = ({
|
||||
value,
|
||||
options,
|
||||
onChange,
|
||||
extraStyle,
|
||||
}: Props) => {
|
||||
const onSelectChange = useCallback(
|
||||
(e: ChangeEvent<HTMLSelectElement>) => {
|
||||
onChange(e.target.value);
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledSelect
|
||||
style={extraStyle}
|
||||
value={value}
|
||||
onChange={onSelectChange}
|
||||
>
|
||||
{options?.map(option => {
|
||||
return (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</StyledSelect>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
/* eslint-disable no-restricted-imports */
|
||||
import OptionUnstyled, {
|
||||
optionUnstyledClasses,
|
||||
} from '@mui/base/OptionUnstyled';
|
||||
|
||||
import { styled } from '../styled';
|
||||
|
||||
/**
|
||||
* Option is extend by mui OptionUnstyled
|
||||
*
|
||||
* OptionUnstyled API:
|
||||
*
|
||||
* - [SelectUnstyled API](https://mui.com/zh/base/api/option-unstyled/)
|
||||
*
|
||||
* **/
|
||||
|
||||
export const Option = styled(OptionUnstyled)(({ theme }) => ({
|
||||
height: '32px',
|
||||
listStyle: 'none',
|
||||
padding: '0 12px',
|
||||
borderRadius: '5px',
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
'&:last-of-type': {
|
||||
borderBottom: 'none',
|
||||
},
|
||||
|
||||
[`&.${optionUnstyledClasses.selected}`]: {
|
||||
backgroundColor: '#F5F7F8',
|
||||
},
|
||||
|
||||
[`&.${optionUnstyledClasses.highlighted}`]: {
|
||||
backgroundColor: '#F5F7F8',
|
||||
},
|
||||
|
||||
[`&.${optionUnstyledClasses.highlighted}.${optionUnstyledClasses.selected}`]:
|
||||
{},
|
||||
|
||||
[`&.${optionUnstyledClasses.disabled}`]: {},
|
||||
|
||||
[`&:hover:not(.${optionUnstyledClasses.disabled})`]: {
|
||||
backgroundColor: '#F5F7F8',
|
||||
},
|
||||
}));
|
||||
@@ -0,0 +1,47 @@
|
||||
import { forwardRef, type ForwardedRef } from 'react';
|
||||
/* eslint-disable no-restricted-imports */
|
||||
import OptionGroupUnstyled, {
|
||||
OptionGroupUnstyledProps,
|
||||
} from '@mui/base/OptionGroupUnstyled';
|
||||
import { styled } from '../styled';
|
||||
|
||||
const StyledGroupRoot = styled('li')`
|
||||
list-style: none;
|
||||
`;
|
||||
|
||||
const StyledGroupHeader = styled('span')`
|
||||
display: block;
|
||||
padding: 15px 0 5px 10px;
|
||||
font-size: 0.75em;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05rem;
|
||||
color: #6f7e8c;
|
||||
`;
|
||||
|
||||
const StyledGroupOptions = styled('ul')`
|
||||
list-style: none;
|
||||
margin-left: 0;
|
||||
padding: 0;
|
||||
|
||||
> li {
|
||||
padding-left: 20px;
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* TODO: The designer has not yet given a style for OptionGroup, the sample style is used here
|
||||
* **/
|
||||
export const OptionGroup = forwardRef(function CustomOptionGroup(
|
||||
props: OptionGroupUnstyledProps,
|
||||
ref: ForwardedRef<HTMLLIElement>
|
||||
) {
|
||||
const components: OptionGroupUnstyledProps['components'] = {
|
||||
Root: StyledGroupRoot,
|
||||
Label: StyledGroupHeader,
|
||||
List: StyledGroupOptions,
|
||||
...props.components,
|
||||
};
|
||||
|
||||
return <OptionGroupUnstyled {...props} ref={ref} components={components} />;
|
||||
});
|
||||
@@ -0,0 +1,129 @@
|
||||
import {
|
||||
forwardRef,
|
||||
type CSSProperties,
|
||||
type ForwardedRef,
|
||||
type RefAttributes,
|
||||
type ReactNode,
|
||||
} from 'react';
|
||||
/* eslint-disable no-restricted-imports */
|
||||
import SelectUnstyled, {
|
||||
type SelectUnstyledProps,
|
||||
selectUnstyledClasses,
|
||||
} from '@mui/base/SelectUnstyled';
|
||||
/* eslint-disable no-restricted-imports */
|
||||
import PopperUnstyled from '@mui/base/PopperUnstyled';
|
||||
import { styled } from '../styled';
|
||||
|
||||
type ExtendSelectProps = {
|
||||
// Width is always used custom, it will be set to root button and popover
|
||||
width?: number | string;
|
||||
style?: CSSProperties;
|
||||
listboxStyle?: CSSProperties;
|
||||
placeholder?: ReactNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* Select is extend by mui SelectUnstyled
|
||||
*
|
||||
* SelectUnstyled Demos:
|
||||
*
|
||||
* - [SelectUnstyled](https://mui.com/zh/base/react-select/)
|
||||
*
|
||||
* SelectUnstyled API:
|
||||
*
|
||||
* - [SelectUnstyled API](https://mui.com/zh/base/api/select-unstyled/)
|
||||
*
|
||||
* **/
|
||||
export const Select = forwardRef(function CustomSelect<TValue>(
|
||||
props: ExtendSelectProps & SelectUnstyledProps<TValue>,
|
||||
ref: ForwardedRef<HTMLUListElement>
|
||||
) {
|
||||
const { width = '100%', style, listboxStyle, placeholder } = props;
|
||||
const components: SelectUnstyledProps<TValue>['components'] = {
|
||||
// Root: generateStyledRoot({ width, ...style }),
|
||||
Root: forwardRef((rootProps, rootRef) => (
|
||||
<StyledRoot
|
||||
ref={rootRef}
|
||||
{...rootProps}
|
||||
style={{
|
||||
width,
|
||||
...style,
|
||||
}}
|
||||
>
|
||||
{rootProps.children || (
|
||||
<StyledPlaceholder>{placeholder}</StyledPlaceholder>
|
||||
)}
|
||||
</StyledRoot>
|
||||
)),
|
||||
Listbox: forwardRef((listboxProps, listboxRef) => (
|
||||
<StyledListbox
|
||||
ref={listboxRef}
|
||||
{...listboxProps}
|
||||
style={{ width, ...listboxStyle }}
|
||||
/>
|
||||
)),
|
||||
Popper: StyledPopper,
|
||||
...props.components,
|
||||
};
|
||||
|
||||
return <SelectUnstyled {...props} ref={ref} components={components} />;
|
||||
}) as <TValue>(
|
||||
props: ExtendSelectProps &
|
||||
SelectUnstyledProps<TValue> &
|
||||
RefAttributes<HTMLUListElement>
|
||||
) => JSX.Element;
|
||||
|
||||
const StyledRoot = styled('div')(({ theme }) => ({
|
||||
height: '32px',
|
||||
border: `1px solid ${theme.affine.palette.borderColor}`,
|
||||
borderRadius: '10px',
|
||||
color: theme.affine.palette.secondaryText,
|
||||
padding: '0 12px',
|
||||
fontSize: '14px',
|
||||
transition: 'border .1s',
|
||||
textAlign: 'left',
|
||||
paddingLeft: '12px',
|
||||
paddingRight: ' 30px',
|
||||
background: '#fff',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
position: 'relative',
|
||||
|
||||
'&:hover': {},
|
||||
|
||||
[`&.${selectUnstyledClasses.focusVisible}`]: {},
|
||||
|
||||
[`&.${selectUnstyledClasses.expanded}`]: {
|
||||
borderColor: `${theme.affine.palette.primary}`,
|
||||
'&::after': {
|
||||
content: '"▴"',
|
||||
},
|
||||
},
|
||||
'&::after': {
|
||||
content: '"▾"',
|
||||
position: ' absolute',
|
||||
top: '0',
|
||||
bottom: '0',
|
||||
right: '12px',
|
||||
margin: 'auto',
|
||||
lineHeight: '32px',
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledListbox = styled('ul')(({ theme }) => ({
|
||||
fontSize: '14px',
|
||||
padding: '8px 4px',
|
||||
color: theme.affine.palette.secondaryText,
|
||||
background: '#fff',
|
||||
borderRadius: '10px',
|
||||
overflow: 'auto',
|
||||
boxShadow: theme.affine.shadows.shadowSxDownLg,
|
||||
}));
|
||||
|
||||
const StyledPopper = styled(PopperUnstyled)`
|
||||
z-index: 1;
|
||||
`;
|
||||
|
||||
const StyledPlaceholder = styled('div')(({ theme }) => ({
|
||||
color: `${theme.affine.palette.borderColor}`,
|
||||
}));
|
||||
@@ -0,0 +1,4 @@
|
||||
export { Select } from './Select';
|
||||
export { Option } from './Option';
|
||||
export { OptionGroup } from './OptionGroup';
|
||||
export { OldSelect } from './OldSelect';
|
||||
@@ -0,0 +1,115 @@
|
||||
import type { FC } from 'react';
|
||||
import { SliderUnstyled, sliderUnstyledClasses } from '@mui/base';
|
||||
import type { SliderUnstyledProps } from '@mui/base';
|
||||
import { alpha } from '@mui/system';
|
||||
import { styled } from '../styled';
|
||||
|
||||
interface SliderProps {
|
||||
defaultValue?: SliderUnstyledProps['defaultValue'];
|
||||
step?: number;
|
||||
min?: number;
|
||||
max?: number;
|
||||
marks?: SliderUnstyledProps['marks'];
|
||||
value?: SliderUnstyledProps['value'];
|
||||
onChange?: SliderUnstyledProps['onChange'];
|
||||
}
|
||||
|
||||
export const Slider: FC<SliderProps> = props => {
|
||||
return <StyledSlider {...props} />;
|
||||
};
|
||||
|
||||
const StyledSlider = styled(SliderUnstyled)(
|
||||
({ theme }) => `
|
||||
color: ${theme.affine.palette.primaryText};
|
||||
height: 2px;
|
||||
width: 100%;
|
||||
padding: 13px 0;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
touch-action: none;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
opacity: 0.75;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.${sliderUnstyledClasses.disabled} {
|
||||
pointer-events: none;
|
||||
cursor: default;
|
||||
color: #bdbdbd;
|
||||
}
|
||||
|
||||
& .${sliderUnstyledClasses.rail} {
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
border-radius: 2px;
|
||||
background-color: ${theme.affine.palette.tagHover};
|
||||
opacity: 0.38;
|
||||
}
|
||||
|
||||
& .${sliderUnstyledClasses.track} {
|
||||
display: block;
|
||||
position: absolute;
|
||||
height: 1px;
|
||||
border-radius: 2px;
|
||||
background-color: currentColor;
|
||||
}
|
||||
|
||||
& .${sliderUnstyledClasses.thumb} {
|
||||
position: absolute;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
margin-left: -6px;
|
||||
margin-top: -5px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 50%;
|
||||
outline: 0;
|
||||
background-color: currentColor;
|
||||
top: 50%;
|
||||
transform: translateY(10%);
|
||||
|
||||
:hover,
|
||||
&.${sliderUnstyledClasses.focusVisible} {
|
||||
box-shadow: 0 0 0 0.25rem ${alpha(
|
||||
theme.palette.mode === 'light' ? '#1976d2' : '#90caf9',
|
||||
0.15
|
||||
)};
|
||||
}
|
||||
|
||||
&.${sliderUnstyledClasses.active} {
|
||||
box-shadow: 0 0 0 0.25rem ${alpha(
|
||||
theme.palette.mode === 'light' ? '#1976d2' : '#90caf9',
|
||||
0.3
|
||||
)};
|
||||
}
|
||||
}
|
||||
|
||||
& .${sliderUnstyledClasses.mark} {
|
||||
position: absolute;
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
border-radius: 50%;
|
||||
background-color: ${theme.affine.palette.tagHover};
|
||||
top: 50%;
|
||||
transform: translate(-50%, -40%);
|
||||
}
|
||||
|
||||
& .${sliderUnstyledClasses.markActive} {
|
||||
background-color: ${theme.affine.palette.primaryText};
|
||||
}
|
||||
|
||||
& .${sliderUnstyledClasses.valueLabel} {
|
||||
font-family: IBM Plex Sans;
|
||||
font-size: 14px;
|
||||
display: block;
|
||||
position: relative;
|
||||
top: -1.6em;
|
||||
text-align: center;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
`
|
||||
);
|
||||
@@ -0,0 +1 @@
|
||||
export { Slider } from './Slider';
|
||||
@@ -0,0 +1,73 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { styled as muiStyled } from '@mui/material/styles';
|
||||
import { ReactHTML, ReactSVG } from 'react';
|
||||
import isPropValid from '@emotion/is-prop-valid';
|
||||
export type { SxProps } from '@mui/system';
|
||||
|
||||
// Props that will be passed to DOM
|
||||
const ALLOW_LIST_PROPS: string[] = [];
|
||||
|
||||
const isSimpleElement = (
|
||||
component: unknown
|
||||
): component is keyof ReactHTML | keyof ReactSVG => {
|
||||
// TODO: improve this
|
||||
return typeof component === 'string';
|
||||
};
|
||||
|
||||
// See https://emotion.sh/docs/typescript#forwarding-props
|
||||
const isValidProp = (prop: string): boolean =>
|
||||
isPropValid(prop) || ALLOW_LIST_PROPS.includes(prop);
|
||||
|
||||
/**
|
||||
* Forward prop except transient prop
|
||||
*
|
||||
* Support [Transient props](https://styled-components.com/docs/api#transient-props)
|
||||
* See https://github.com/styled-components/styled-components/pull/3052
|
||||
*/
|
||||
const isNotTransientProp = (prop: string): boolean => !prop.startsWith('$'); // || prop !== 'as';
|
||||
|
||||
/**
|
||||
* Workaround for the [Unknown Prop Warning](https://reactjs.org/warnings/unknown-prop.html) when passing custom props to styled components
|
||||
*
|
||||
* > React does not recognize the `xxxxxx` prop on a DOM element.
|
||||
* If you intentionally want it to appear in the DOM as a custom attribute, spell it as lowercase `xxxxx` instead.
|
||||
* If you accidentally passed it from a parent component,
|
||||
* remove it from the DOM element.
|
||||
*
|
||||
* For a detailed discussion, see https://github.com/emotion-js/emotion/issues/655
|
||||
*
|
||||
* We simulate the behavior of the [styled-components](https://github.com/styled-components).
|
||||
*
|
||||
* > If the styled target is a simple element (e.g. styled.div),
|
||||
* styled-components passes through any known HTML attribute to the DOM.
|
||||
* If it is a custom React component (e.g. styled(MyComponent)),
|
||||
* styled-components passes through all props.
|
||||
*
|
||||
* See https://styled-components.com/docs/basics#passed-props
|
||||
*/
|
||||
export const styled: typeof muiStyled = (
|
||||
component: Parameters<typeof muiStyled>[0],
|
||||
options?: Parameters<typeof muiStyled>[1]
|
||||
): ReturnType<typeof muiStyled> => {
|
||||
// @ts-ignore Fix https://github.com/nrwl/nx/issues/4508
|
||||
if (options?.shouldForwardProp) {
|
||||
// Explicitly declared, no need handle
|
||||
return muiStyled(component, options);
|
||||
}
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
|
||||
if (!isSimpleElement(component)) {
|
||||
// Is custom React component, pass props except transient props through
|
||||
// @ts-ignore Fix https://github.com/nrwl/nx/issues/4508
|
||||
options.shouldForwardProp = isNotTransientProp;
|
||||
|
||||
return muiStyled(component, options);
|
||||
}
|
||||
|
||||
// Is simple element, pass only valid props
|
||||
// @ts-ignore Fix https://github.com/nrwl/nx/issues/4508
|
||||
options.shouldForwardProp = isValidProp;
|
||||
return muiStyled(component, options);
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
/* eslint-disable no-restricted-imports */
|
||||
|
||||
import type { SvgIconProps as MuiSvgIconProps } from '@mui/material';
|
||||
|
||||
export { SvgIcon } from '@mui/material';
|
||||
export interface SvgIconProps extends Omit<MuiSvgIconProps, 'color'> {
|
||||
color?: string;
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
import type { CSSProperties } from 'react';
|
||||
import { styled } from '../styled';
|
||||
import { useSwitch, UseSwitchParameters } from '@mui/base/SwitchUnstyled';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
/**
|
||||
* Switch is extend by mui SwitchUnstyled
|
||||
*
|
||||
* SwitchUnstyled Demos:
|
||||
* - [SwitchUnstyled](https://mui.com/zh/base/react-switch/)
|
||||
*
|
||||
* SwitchUnstyled API:
|
||||
* - [SwitchUnstyled API](https://mui.com/zh/base/api/switch-unstyled/)
|
||||
*
|
||||
* Add some props to use in business:
|
||||
* - `checkedLabel` and `uncheckedLabel` is used to show switch label, and you can write label custom by props `label`
|
||||
* - `width` is used to limit the wrapper, it should always set if `checkedLabel` and `uncheckedLabel` is different,otherwise wrapper will shake when label change
|
||||
*
|
||||
* Easy Demo:
|
||||
* ```jsx
|
||||
* <Switch checkedLabel="ON" uncheckedLabel="OFF" width={50} />
|
||||
* ```
|
||||
*
|
||||
* **/
|
||||
export type SwitchProps = {
|
||||
label?: (params: { checked: boolean; disabled: boolean }) => ReactNode;
|
||||
checkedLabel?: string;
|
||||
uncheckedLabel?: string;
|
||||
style?: CSSProperties;
|
||||
width?: number | string;
|
||||
} & UseSwitchParameters;
|
||||
|
||||
export const Switch = (props: SwitchProps) => {
|
||||
const { label, checkedLabel, uncheckedLabel, style, width } = props;
|
||||
const { getInputProps, checked, disabled } = useSwitch(props);
|
||||
|
||||
return (
|
||||
<StyledWrapper style={{ ...style, width }}>
|
||||
<StyledLabel checked={checked} disable={disabled}>
|
||||
{checked ? checkedLabel : uncheckedLabel}
|
||||
{label?.({ checked, disabled })}
|
||||
</StyledLabel>
|
||||
<StyledRoot>
|
||||
<StyleTrack checked={checked}>
|
||||
<StyledThumb checked={checked} disable={disabled} />
|
||||
</StyleTrack>
|
||||
<StyledSwitchInput {...getInputProps()} />
|
||||
</StyledRoot>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
const StyledRoot = styled('span')`
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 22px;
|
||||
height: 12px;
|
||||
`;
|
||||
|
||||
const StyledSwitchInput = styled('input')`
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
z-index: 1;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
const StyledThumb = styled('span')<{
|
||||
disable: boolean;
|
||||
checked: boolean;
|
||||
}>(({ theme, checked, disable }) => {
|
||||
return {
|
||||
position: 'absolute',
|
||||
display: 'block',
|
||||
backgroundColor: checked ? '#fff' : theme.affine.palette.primary,
|
||||
width: '8px',
|
||||
height: '8px',
|
||||
borderRadius: '2px',
|
||||
top: '2px',
|
||||
left: '2px',
|
||||
transform: checked ? 'translateX(10px)' : '',
|
||||
transition:
|
||||
'transform 0.15s cubic-bezier(0.4, 0, 0.2, 1), background-color .15s',
|
||||
};
|
||||
});
|
||||
|
||||
const StyleTrack = styled('span')<{ checked: boolean }>(
|
||||
({ theme, checked }) => ({
|
||||
backgroundColor: checked ? theme.affine.palette.primary : '#fff',
|
||||
border: `1px solid ${theme.affine.palette.primary}`,
|
||||
borderRadius: '3px',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'block',
|
||||
transition: 'background-color .15s, border-color .15s',
|
||||
})
|
||||
);
|
||||
|
||||
const StyledWrapper = styled('label')`
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
const StyledLabel = styled('div')<{ disable: boolean; checked: boolean }>(
|
||||
({ theme, disable, checked }) => {
|
||||
return {
|
||||
display: 'inline-flex',
|
||||
color: theme.affine.palette.primary,
|
||||
fontSize: '12px',
|
||||
marginRight: '5px',
|
||||
};
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,2 @@
|
||||
export { Switch } from './Switch';
|
||||
export type { SwitchProps } from './Switch';
|
||||
@@ -0,0 +1,57 @@
|
||||
import type {
|
||||
FC,
|
||||
ChangeEventHandler,
|
||||
PropsWithChildren,
|
||||
CSSProperties,
|
||||
ReactNode,
|
||||
} from 'react';
|
||||
|
||||
import { MouseEventHandler } from 'react';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import { StyledTag } from './style';
|
||||
|
||||
export interface TagProps {
|
||||
style?: CSSProperties;
|
||||
onClick?: MouseEventHandler;
|
||||
closeable?: boolean;
|
||||
onClose?: () => void;
|
||||
startElement?: ReactNode;
|
||||
endElement?: ReactNode;
|
||||
}
|
||||
|
||||
export const Tag: FC<PropsWithChildren<TagProps>> = ({
|
||||
onClick,
|
||||
style,
|
||||
children,
|
||||
closeable,
|
||||
onClose,
|
||||
startElement,
|
||||
endElement,
|
||||
}) => {
|
||||
return (
|
||||
<StyledTag
|
||||
className={`affine-tag ${closeable ? 'affine-tag--closeable' : ''}`}
|
||||
style={{
|
||||
...style,
|
||||
}}
|
||||
onClick={onClick}
|
||||
>
|
||||
<div className="affine-tag__wrapper">
|
||||
{startElement}
|
||||
<div className="affine-tag__content">{children}</div>
|
||||
{endElement}
|
||||
{closeable && (
|
||||
<div
|
||||
className="affine-tag__closeWrapper"
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
onClose?.();
|
||||
}}
|
||||
>
|
||||
<CloseIcon style={{ fontSize: 12 }} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</StyledTag>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
export { Tag } from './Tag';
|
||||
export type { TagProps } from './Tag';
|
||||
@@ -0,0 +1,53 @@
|
||||
import { styled } from '../styled';
|
||||
|
||||
export const StyledTag = styled('div')`
|
||||
height: 20px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
border-radius: 11px;
|
||||
padding: 0 8px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
|
||||
.affine-tag__wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-content: center;
|
||||
}
|
||||
.affine-tag__content {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
line-height: 20px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.affine-tag__closeWrapper {
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 5px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&.affine-tag--closeable {
|
||||
&:hover {
|
||||
.affine-tag__wrapper {
|
||||
width: calc(100% - 8px);
|
||||
}
|
||||
.affine-tag__closeWrapper {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,5 @@
|
||||
# Theme
|
||||
|
||||
## Size
|
||||
|
||||
xs(extra-small) < sm(small) < md(middle) < lg(large) < xl(extra-large)
|
||||
@@ -0,0 +1,231 @@
|
||||
export default {
|
||||
inherit: 'inherit',
|
||||
transparent: 'transparent',
|
||||
black: '#000',
|
||||
white: '#fff',
|
||||
primary: '#3E6FDB',
|
||||
danger: '#f87171',
|
||||
success: '#4ade80',
|
||||
warning: '#facc15',
|
||||
info: '#7dd3fc',
|
||||
'slate-50': '#f8fafc',
|
||||
'slate-100': '#f1f5f9',
|
||||
'slate-200': '#e2e8f0',
|
||||
'slate-300': '#cbd5e1',
|
||||
'slate-400': '#94a3b8',
|
||||
'slate-500': '#64748b',
|
||||
'slate-600': '#475569',
|
||||
'slate-700': '#334155',
|
||||
'slate-800': '#1e293b',
|
||||
'slate-900': '#0f172a',
|
||||
'gray-50': '#f9fafb',
|
||||
'gray-100': '#f3f4f6',
|
||||
'gray-200': '#e5e7eb',
|
||||
'gray-300': '#d1d5db',
|
||||
'gray-400': '#9ca3af',
|
||||
'gray-500': '#6b7280',
|
||||
'gray-600': '#4b5563',
|
||||
'gray-700': '#374151',
|
||||
'gray-800': '#1f2937',
|
||||
'gray-900': '#111827',
|
||||
'zinc-50': '#fafafa',
|
||||
'zinc-100': '#f4f4f5',
|
||||
'zinc-200': '#e4e4e7',
|
||||
'zinc-300': '#d4d4d8',
|
||||
'zinc-400': '#a1a1aa',
|
||||
'zinc-500': '#71717a',
|
||||
'zinc-600': '#52525b',
|
||||
'zinc-700': '#3f3f46',
|
||||
'zinc-800': '#27272a',
|
||||
'zinc-900': '#18181b',
|
||||
'neutral-50': '#fafafa',
|
||||
'neutral-100': '#f5f5f5',
|
||||
'neutral-200': '#e5e5e5',
|
||||
'neutral-300': '#d4d4d4',
|
||||
'neutral-400': '#a3a3a3',
|
||||
'neutral-500': '#737373',
|
||||
'neutral-600': '#525252',
|
||||
'neutral-700': '#404040',
|
||||
'neutral-800': '#262626',
|
||||
'neutral-900': '#171717',
|
||||
'stone-50': '#fafaf9',
|
||||
'stone-100': '#f5f5f4',
|
||||
'stone-200': '#e7e5e4',
|
||||
'stone-300': '#d6d3d1',
|
||||
'stone-400': '#a8a29e',
|
||||
'stone-500': '#78716c',
|
||||
'stone-600': '#57534e',
|
||||
'stone-700': '#44403c',
|
||||
'stone-800': '#292524',
|
||||
'stone-900': '#1c1917',
|
||||
'red-50': '#fef2f2',
|
||||
'red-100': '#fee2e2',
|
||||
'red-200': '#fecaca',
|
||||
'red-300': '#fca5a5',
|
||||
'red-400': '#f87171',
|
||||
'red-500': '#ef4444',
|
||||
'red-600': '#dc2626',
|
||||
'red-700': '#b91c1c',
|
||||
'red-800': '#991b1b',
|
||||
'red-900': '#7f1d1d',
|
||||
'orange-50': '#fff7ed',
|
||||
'orange-100': '#ffedd5',
|
||||
'orange-200': '#fed7aa',
|
||||
'orange-300': '#fdba74',
|
||||
'orange-400': '#fb923c',
|
||||
'orange-500': '#f97316',
|
||||
'orange-600': '#ea580c',
|
||||
'orange-700': '#c2410c',
|
||||
'orange-800': '#9a3412',
|
||||
'orange-900': '#7c2d12',
|
||||
'amber-50': '#fffbeb',
|
||||
'amber-100': '#fef3c7',
|
||||
'amber-200': '#fde68a',
|
||||
'amber-300': '#fcd34d',
|
||||
'amber-400': '#fbbf24',
|
||||
'amber-500': '#f59e0b',
|
||||
'amber-600': '#d97706',
|
||||
'amber-700': '#b45309',
|
||||
'amber-800': '#92400e',
|
||||
'amber-900': '#78350f',
|
||||
'yellow-50': '#fefce8',
|
||||
'yellow-100': '#fef9c3',
|
||||
'yellow-200': '#fef08a',
|
||||
'yellow-300': '#fde047',
|
||||
'yellow-400': '#facc15',
|
||||
'yellow-500': '#eab308',
|
||||
'yellow-600': '#ca8a04',
|
||||
'yellow-700': '#a16207',
|
||||
'yellow-800': '#854d0e',
|
||||
'yellow-900': '#713f12',
|
||||
'lime-50': '#f7fee7',
|
||||
'lime-100': '#ecfccb',
|
||||
'lime-200': '#d9f99d',
|
||||
'lime-300': '#bef264',
|
||||
'lime-400': '#a3e635',
|
||||
'lime-500': '#84cc16',
|
||||
'lime-600': '#65a30d',
|
||||
'lime-700': '#4d7c0f',
|
||||
'lime-800': '#3f6212',
|
||||
'lime-900': '#365314',
|
||||
'green-50': '#f0fdf4',
|
||||
'green-100': '#dcfce7',
|
||||
'green-200': '#bbf7d0',
|
||||
'green-300': '#86efac',
|
||||
'green-400': '#4ade80',
|
||||
'green-500': '#22c55e',
|
||||
'green-600': '#16a34a',
|
||||
'green-700': '#15803d',
|
||||
'green-800': '#166534',
|
||||
'green-900': '#14532d',
|
||||
'emerald-50': '#ecfdf5',
|
||||
'emerald-100': '#d1fae5',
|
||||
'emerald-200': '#a7f3d0',
|
||||
'emerald-300': '#6ee7b7',
|
||||
'emerald-400': '#34d399',
|
||||
'emerald-500': '#10b981',
|
||||
'emerald-600': '#059669',
|
||||
'emerald-700': '#047857',
|
||||
'emerald-800': '#065f46',
|
||||
'emerald-900': '#064e3b',
|
||||
'teal-50': '#f0fdfa',
|
||||
'teal-100': '#ccfbf1',
|
||||
'teal-200': '#99f6e4',
|
||||
'teal-300': '#5eead4',
|
||||
'teal-400': '#2dd4bf',
|
||||
'teal-500': '#14b8a6',
|
||||
'teal-600': '#0d9488',
|
||||
'teal-700': '#0f766e',
|
||||
'teal-800': '#115e59',
|
||||
'teal-900': '#134e4a',
|
||||
'cyan-50': '#ecfeff',
|
||||
'cyan-100': '#cffafe',
|
||||
'cyan-200': '#a5f3fc',
|
||||
'cyan-300': '#67e8f9',
|
||||
'cyan-400': '#22d3ee',
|
||||
'cyan-500': '#06b6d4',
|
||||
'cyan-600': '#0891b2',
|
||||
'cyan-700': '#0e7490',
|
||||
'cyan-800': '#155e75',
|
||||
'cyan-900': '#164e63',
|
||||
'sky-50': '#f0f9ff',
|
||||
'sky-100': '#e0f2fe',
|
||||
'sky-200': '#bae6fd',
|
||||
'sky-300': '#7dd3fc',
|
||||
'sky-400': '#38bdf8',
|
||||
'sky-500': '#0ea5e9',
|
||||
'sky-600': '#0284c7',
|
||||
'sky-700': '#0369a1',
|
||||
'sky-800': '#075985',
|
||||
'sky-900': '#0c4a6e',
|
||||
'blue-50': '#eff6ff',
|
||||
'blue-100': '#dbeafe',
|
||||
'blue-200': '#bfdbfe',
|
||||
'blue-300': '#93c5fd',
|
||||
'blue-400': '#60a5fa',
|
||||
'blue-500': '#3b82f6',
|
||||
'blue-600': '#2563eb',
|
||||
'blue-700': '#1d4ed8',
|
||||
'blue-800': '#1e40af',
|
||||
'blue-900': '#1e3a8a',
|
||||
'indigo-50': '#eef2ff',
|
||||
'indigo-100': '#e0e7ff',
|
||||
'indigo-200': '#c7d2fe',
|
||||
'indigo-300': '#a5b4fc',
|
||||
'indigo-400': '#818cf8',
|
||||
'indigo-500': '#6366f1',
|
||||
'indigo-600': '#4f46e5',
|
||||
'indigo-700': '#4338ca',
|
||||
'indigo-800': '#3730a3',
|
||||
'indigo-900': '#312e81',
|
||||
'violet-50': '#f5f3ff',
|
||||
'violet-100': '#ede9fe',
|
||||
'violet-200': '#ddd6fe',
|
||||
'violet-300': '#c4b5fd',
|
||||
'violet-400': '#a78bfa',
|
||||
'violet-500': '#8b5cf6',
|
||||
'violet-600': '#7c3aed',
|
||||
'violet-700': '#6d28d9',
|
||||
'violet-800': '#5b21b6',
|
||||
'violet-900': '#4c1d95',
|
||||
'purple-50': '#faf5ff',
|
||||
'purple-100': '#f3e8ff',
|
||||
'purple-200': '#e9d5ff',
|
||||
'purple-300': '#d8b4fe',
|
||||
'purple-400': '#c084fc',
|
||||
'purple-500': '#a855f7',
|
||||
'purple-600': '#9333ea',
|
||||
'purple-700': '#7e22ce',
|
||||
'purple-800': '#6b21a8',
|
||||
'purple-900': '#581c87',
|
||||
'fuchsia-50': '#fdf4ff',
|
||||
'fuchsia-100': '#fae8ff',
|
||||
'fuchsia-200': '#f5d0fe',
|
||||
'fuchsia-300': '#f0abfc',
|
||||
'fuchsia-400': '#e879f9',
|
||||
'fuchsia-500': '#d946ef',
|
||||
'fuchsia-600': '#c026d3',
|
||||
'fuchsia-700': '#a21caf',
|
||||
'fuchsia-800': '#86198f',
|
||||
'fuchsia-900': '#701a75',
|
||||
'pink-50': '#fdf2f8',
|
||||
'pink-100': '#fce7f3',
|
||||
'pink-200': '#fbcfe8',
|
||||
'pink-300': '#f9a8d4',
|
||||
'pink-400': '#f472b6',
|
||||
'pink-500': '#ec4899',
|
||||
'pink-600': '#db2777',
|
||||
'pink-700': '#be185d',
|
||||
'pink-800': '#9d174d',
|
||||
'pink-900': '#831843',
|
||||
'rose-50': '#fff1f2',
|
||||
'rose-100': '#ffe4e6',
|
||||
'rose-200': '#fecdd3',
|
||||
'rose-300': '#fda4af',
|
||||
'rose-400': '#fb7185',
|
||||
'rose-500': '#f43f5e',
|
||||
'rose-600': '#e11d48',
|
||||
'rose-700': '#be123c',
|
||||
'rose-800': '#9f1239',
|
||||
'rose-900': '#881337',
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Theme as theme } from './theme';
|
||||
export { ThemeProvider, useTheme, withTheme } from './utils';
|
||||
|
||||
/**
|
||||
* @deprecated use useTheme / withTheme instead
|
||||
*/
|
||||
export const Theme = theme;
|
||||
@@ -0,0 +1,253 @@
|
||||
import ColorObject from './color';
|
||||
// import { ThemeOptions } from '@mui/material/styles';
|
||||
/**
|
||||
* @deprecated Please use the new {@link ThemeOptions} type.
|
||||
*/
|
||||
interface ThemeOptionsLegacy {
|
||||
palette: Palette;
|
||||
typography: Typography;
|
||||
shadows?: Shadows;
|
||||
border?: StringWithNone;
|
||||
spacing?: Spacing;
|
||||
shape?: Shape;
|
||||
}
|
||||
|
||||
interface Shape {
|
||||
xsBorderRadius?: string;
|
||||
borderRadius?: string;
|
||||
smBorderRadius?: string;
|
||||
lgBorderRadius?: string;
|
||||
}
|
||||
interface Spacing {
|
||||
xsSpacing: string;
|
||||
smSpacing?: string;
|
||||
main?: string;
|
||||
lgSpacing?: string;
|
||||
}
|
||||
interface Palette {
|
||||
primary?: Action;
|
||||
success?: Action;
|
||||
info?: Action;
|
||||
error?: Action;
|
||||
warning?: Action;
|
||||
text?: Action;
|
||||
}
|
||||
interface Action {
|
||||
main: string;
|
||||
active?: string;
|
||||
hover?: string;
|
||||
hoverOpacity?: number;
|
||||
selected?: string;
|
||||
selectedOpacity?: number;
|
||||
disabled?: string;
|
||||
disabledOpacity?: number;
|
||||
disabledBackground?: string;
|
||||
focus?: string;
|
||||
focusOpacity?: number;
|
||||
activatedOpacity?: number;
|
||||
}
|
||||
interface Typography {
|
||||
fontSize?: string;
|
||||
fontFamily?: string;
|
||||
xsFontSize?: string;
|
||||
lgFontSize?: string;
|
||||
fontWeight?: number;
|
||||
xsFontWeight?: number;
|
||||
lineHeight?: string;
|
||||
lgFontWeight?: number;
|
||||
button?: Font;
|
||||
body1?: Font;
|
||||
body2?: Font;
|
||||
h1?: Font;
|
||||
h2?: Font;
|
||||
h3?: Font;
|
||||
h4?: Font;
|
||||
h5?: Font;
|
||||
page?: Font;
|
||||
quote?: Font;
|
||||
callout?: Font;
|
||||
}
|
||||
|
||||
interface Shadows {
|
||||
none: 'none';
|
||||
shadowSxDownLg: string;
|
||||
}
|
||||
|
||||
type StringWithNone = [
|
||||
'none',
|
||||
string?,
|
||||
string?,
|
||||
string?,
|
||||
string?,
|
||||
string?,
|
||||
string?,
|
||||
string?,
|
||||
string?,
|
||||
string?,
|
||||
string?,
|
||||
string?,
|
||||
string?,
|
||||
string?,
|
||||
string?,
|
||||
string?,
|
||||
string?,
|
||||
string?,
|
||||
string?,
|
||||
string?,
|
||||
string?,
|
||||
string?,
|
||||
string?,
|
||||
string?,
|
||||
string?
|
||||
];
|
||||
interface Font {
|
||||
fontSize?: string;
|
||||
lineHeight?: string;
|
||||
fontFamily?: string;
|
||||
fontWeight?: number;
|
||||
}
|
||||
|
||||
export const Theme = {
|
||||
palette: {
|
||||
/**
|
||||
* figma: white
|
||||
*/
|
||||
white: '#ffffff',
|
||||
/**
|
||||
* figma: icons
|
||||
*/
|
||||
icons: '#98ACBD',
|
||||
/**
|
||||
* figma: T-menu
|
||||
*/
|
||||
menu: '#4C6275',
|
||||
/** separator line color for menus and icons */
|
||||
menuSeparator: '#E0E6EB',
|
||||
/**
|
||||
* figma: hover
|
||||
*/
|
||||
hover: '#F5F7F8',
|
||||
/**
|
||||
* figma: tag hover
|
||||
*/
|
||||
tagHover: '#E0E6EB',
|
||||
borderColor: '#E0E6EB',
|
||||
placeholderColor: '#E0E6EB',
|
||||
/**
|
||||
* figma: brand color
|
||||
*/
|
||||
primary: '#3E6FDB',
|
||||
/**
|
||||
* figma: operation failed
|
||||
*/
|
||||
error: '#F1675E',
|
||||
/**
|
||||
* figma: warning color
|
||||
*/
|
||||
warning: '#F1BE5E',
|
||||
/**
|
||||
* figma: operation succeeded
|
||||
*/
|
||||
success: '#40DF9B',
|
||||
/**
|
||||
* figma: text
|
||||
*/
|
||||
primaryText: '#3A4C5C',
|
||||
/**
|
||||
* figma: subtext color
|
||||
*/
|
||||
secondaryText: '#4C6275',
|
||||
/**
|
||||
* selected
|
||||
*/
|
||||
textSelected: 'rgba(152, 172, 189, 0.1)',
|
||||
/**
|
||||
* text hover
|
||||
*/
|
||||
textHover: '#ECF1FB',
|
||||
},
|
||||
typography: {
|
||||
button: {
|
||||
fontSize: '16px',
|
||||
},
|
||||
body1: {
|
||||
fontSize: '16px',
|
||||
lineHeight: '22px',
|
||||
},
|
||||
h1: {
|
||||
fontSize: '28px',
|
||||
lineHeight: '40px',
|
||||
},
|
||||
h2: {
|
||||
fontSize: '24px',
|
||||
lineHeight: '34px',
|
||||
},
|
||||
h3: {
|
||||
fontSize: '20px',
|
||||
lineHeight: '28px',
|
||||
},
|
||||
h4: {
|
||||
fontSize: '16px',
|
||||
lineHeight: '22px',
|
||||
},
|
||||
page: {
|
||||
fontSize: '36px',
|
||||
lineHeight: '44px',
|
||||
},
|
||||
callout: {
|
||||
fontSize: '36px',
|
||||
lineHeight: '54px',
|
||||
},
|
||||
quote: {
|
||||
fontSize: '36px',
|
||||
lineHeight: '54px',
|
||||
},
|
||||
tooltip: {
|
||||
fontSize: '12px',
|
||||
lineHeight: '18px',
|
||||
},
|
||||
sm: {
|
||||
fontSize: '14px',
|
||||
lineHeight: '20px',
|
||||
},
|
||||
xs: {
|
||||
fontSize: '12px',
|
||||
lineHeight: '18px',
|
||||
},
|
||||
base: {
|
||||
fontSize: '16px',
|
||||
lineHeight: '22px',
|
||||
},
|
||||
articleTitle: {
|
||||
fontSize: '36px',
|
||||
lineHeight: '54px',
|
||||
},
|
||||
},
|
||||
shadows: {
|
||||
none: 'none',
|
||||
shadowSxDownLg: '0px 1px 10px rgba(152, 172, 189, 0.6)',
|
||||
},
|
||||
border: ['none'],
|
||||
spacing: {
|
||||
xsSpacing: '4px',
|
||||
smSpacing: '12px',
|
||||
main: '16px',
|
||||
lgSpacing: '20px',
|
||||
iconPadding: '6px',
|
||||
},
|
||||
shape: {
|
||||
borderRadius: '10px',
|
||||
xsBorderRadius: '4px',
|
||||
smBorderRadius: '8px',
|
||||
lgBorderRadius: '20px',
|
||||
},
|
||||
zIndex: {
|
||||
header: 100,
|
||||
popover: 200,
|
||||
tooltip: 300,
|
||||
modal: 400,
|
||||
message: 500,
|
||||
},
|
||||
} as const;
|
||||
|
||||
export type ThemeOptions = typeof Theme;
|
||||
@@ -0,0 +1,39 @@
|
||||
import type { FC, PropsWithChildren, ReactNode } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import {
|
||||
createTheme,
|
||||
ThemeProvider as MuiThemeProvider,
|
||||
useTheme as muiUseTheme,
|
||||
Theme as MuiTheme,
|
||||
} from '@mui/material/styles';
|
||||
import type { ThemeOptions as AffineThemeOptions } from './theme';
|
||||
import { Theme } from './theme';
|
||||
|
||||
declare module '@mui/material/styles' {
|
||||
interface Theme {
|
||||
affine: AffineThemeOptions;
|
||||
}
|
||||
// allow configuration using `createTheme`
|
||||
interface ThemeOptions {
|
||||
affine: AffineThemeOptions;
|
||||
}
|
||||
}
|
||||
|
||||
const theme = createTheme({
|
||||
affine: Theme,
|
||||
});
|
||||
|
||||
export const ThemeProvider: FC<{ children?: ReactNode }> = ({ children }) => {
|
||||
return <MuiThemeProvider theme={theme}>{children}</MuiThemeProvider>;
|
||||
};
|
||||
|
||||
export const useTheme = () => muiUseTheme();
|
||||
|
||||
export const withTheme = <T,>(
|
||||
Component: FC<T & { theme: MuiTheme }>
|
||||
): FC<T> => {
|
||||
return props => {
|
||||
const theme = useTheme();
|
||||
return <Component {...props} theme={theme} />;
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
import React, {
|
||||
forwardRef,
|
||||
type PropsWithChildren,
|
||||
type CSSProperties,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { type PopperHandler, type PopperProps, Popper } from '../popper';
|
||||
import type { TooltipProps } from './interface';
|
||||
import { useTheme } from '../theme';
|
||||
|
||||
const useTooltipStyle = (): CSSProperties => {
|
||||
const theme = useTheme();
|
||||
return {
|
||||
backgroundColor: theme.affine.palette.icons,
|
||||
color: theme.affine.palette.white,
|
||||
...theme.affine.typography.tooltip,
|
||||
padding: '4px 8px',
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
};
|
||||
};
|
||||
|
||||
export const Tooltip = forwardRef<
|
||||
PopperHandler,
|
||||
PropsWithChildren<PopperProps & TooltipProps>
|
||||
>((props, ref) => {
|
||||
const style = useTooltipStyle();
|
||||
return (
|
||||
<Popper
|
||||
ref={ref}
|
||||
popoverStyle={style}
|
||||
placement="top"
|
||||
showArrow
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
export * from './Tooltip';
|
||||
@@ -0,0 +1,3 @@
|
||||
export type TooltipProps = {
|
||||
showArrow?: boolean;
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
import { styled } from '../styled';
|
||||
import type { ThemeOptions } from '../theme/theme';
|
||||
|
||||
type TypographyType = keyof ThemeOptions['typography'];
|
||||
|
||||
interface TypographyProps {
|
||||
/**
|
||||
* Binding with typography in theme.
|
||||
* Default: sm
|
||||
*/
|
||||
type?: TypographyType;
|
||||
}
|
||||
|
||||
export const Typography = styled('span')<TypographyProps>(({ theme, type }) => {
|
||||
const config = theme.affine.typography[type] || theme.affine.typography.sm;
|
||||
return config;
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
export { Typography } from './Typo';
|
||||
Reference in New Issue
Block a user