Skip to content

Commit

Permalink
🔧 chore: add sutido editor base components
Browse files Browse the repository at this point in the history
  • Loading branch information
倏昱 committed Apr 3, 2023
1 parent 9c9df9b commit c87cd31
Show file tree
Hide file tree
Showing 32 changed files with 1,727 additions and 1 deletion.
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,14 @@
"prettier-plugin-organize-imports": "^3",
"prettier-plugin-packagejson": "^2",
"query-string": "^8.1.0",
"re-resizable": "^6.9.9",
"react-layout-kit": "^1.6.0",
"react-rnd": "^10.4.1",
"semantic-release": "^19",
"semantic-release-config-gitmoji": "^1",
"styled-components": "^5.3.9",
"stylelint": "^15.4.0",
"typescript": "^5.0.0"
"typescript": "^5.0.0",
"use-merge-value": "^1.2.0"
}
}
94 changes: 94 additions & 0 deletions src/components/EditorComponent/ActionIcon/ActionIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import type { ButtonProps, TooltipProps } from 'antd';
import { Button, Tooltip } from 'antd';
import type { CSSProperties, FC } from 'react';
import { ConfigProvider } from '../ConfigProvider';
import { cx, getPrefixCls, useToken } from '@/components/theme';
import { useStyles } from './style';

/**
* @title 动作图标属性
* @description 继承自 `Button` 组件所有属性,除了 `title` 和 `size`
*/
export interface ActionIconProps extends Omit<ButtonProps, 'title' | 'size'> {
/**
* @title 鼠标类型
*/
cursor?: CSSProperties['cursor'];
/**
* @title 动作提示
*/
title?: TooltipProps['title'];
/**
* @title 提示位置
*/
placement?: TooltipProps['placement'];
/**
* @title 图标
*/
icon: ButtonProps['icon'];
/**
* @title 点击回调
*/
onClick?: ButtonProps['onClick'];
/**
* @title 图标尺寸
*/
size?: 'default' | 'large' | number;
}

const ActionIcon: FC<ActionIconProps> = ({
placement,
title,
icon,
cursor,
onClick,
className,
size,
prefixCls: customPrefixCls,
...restProps
}) => {
const prefixCls = getPrefixCls('actionicon', customPrefixCls);
const { styles } = useStyles({ size, prefixCls });

const token = useToken();

const Icon = (
<Button
icon={icon}
className={cx(className, styles.container)}
type={'text'}
style={{ cursor }}
size={'small'}
{...restProps}
onClick={onClick}
/>
);

return (
<ConfigProvider
componentToken={{
Button: {
colorText: token.colorTextTertiary,
// TODO: Token 的提供不太合理,需要改造
colorBgTextHover: token.colorFillSecondary,
colorBgTextActive: token.colorFill,
},
}}
>
{!title ? (
Icon
) : (
<Tooltip
showArrow={false}
align={{ offset: [0, -5] }}
overlayClassName={styles.tooltip}
title={title}
placement={placement}
>
{Icon}
</Tooltip>
)}
</ConfigProvider>
);
};
export default ActionIcon;
33 changes: 33 additions & 0 deletions src/components/EditorComponent/ActionIcon/Icons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { DeleteFilled, EditFilled } from '@ant-design/icons';
import type { FC } from 'react';

import type { ActionIconProps } from './ActionIcon';
import ActionIcon from './ActionIcon';

export type IconsProps = Omit<ActionIconProps, 'icon'>;

const HandleIcon = (
<svg viewBox="0 0 20 20" width="16" fill="currentColor">
<path d="M7 2a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 2zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 8zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 14zm6-8a2 2 0 1 0-.001-4.001A2 2 0 0 0 13 6zm0 2a2 2 0 1 0 .001 4.001A2 2 0 0 0 13 8zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 13 14z" />
</svg>
);

const CollapseIcon = (
<svg width="10" viewBox="0 0 70 41" fill="currentColor">
<path d="M30.76 39.2402C31.885 40.3638 33.41 40.995 35 40.995C36.59 40.995 38.115 40.3638 39.24 39.2402L68.24 10.2402C69.2998 9.10284 69.8768 7.59846 69.8494 6.04406C69.822 4.48965 69.1923 3.00657 68.093 1.90726C66.9937 0.807959 65.5106 0.178263 63.9562 0.150837C62.4018 0.123411 60.8974 0.700397 59.76 1.76024L35 26.5102L10.24 1.76024C9.10259 0.700397 7.59822 0.123411 6.04381 0.150837C4.4894 0.178263 3.00632 0.807959 1.90702 1.90726C0.807714 3.00657 0.178019 4.48965 0.150593 6.04406C0.123167 7.59846 0.700153 9.10284 1.75999 10.2402L30.76 39.2402Z" />
</svg>
);

export const CollapseAction: FC<IconsProps> = (props) => (
<ActionIcon icon={CollapseIcon} {...props} />
);

export const HandleAction: FC<IconsProps> = (props) => <ActionIcon icon={HandleIcon} {...props} />;

export const DeleteAction: FC<IconsProps> = (props) => (
<ActionIcon icon={<DeleteFilled />} {...props} />
);

export const EditAction: FC<IconsProps> = (props) => (
<ActionIcon icon={<EditFilled />} {...props} />
);
8 changes: 8 additions & 0 deletions src/components/EditorComponent/ActionIcon/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import ActionIcon from './ActionIcon';

export { default as ActionIcon } from './ActionIcon';
export type { ActionIconProps } from './ActionIcon';
export * from './Icons';
// 内部使用统一图标语义
export type { IconsProps } from './Icons';
export default ActionIcon;
27 changes: 27 additions & 0 deletions src/components/EditorComponent/ActionIcon/style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { createStyles, css, cx } from '@/components/theme';
export const useStyles = createStyles(({ token }, { size, className, prefixCls }) => {
const sizeBoundary =
typeof size === 'number'
? css`
width: ${size}px !important;
height: ${size}px !important;
`
: '';

const button = css`
display: flex;
align-items: center;
justify-content: center;
&:hover {
color: ${token.colorText} !important;
}
`;

return {
container: cx(prefixCls, button, sizeBoundary, className),
tooltip: css`
pointer-events: none;
`,
};
});
108 changes: 108 additions & 0 deletions src/components/EditorComponent/CollapseTitle/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { CaretRightFilled } from '@ant-design/icons';
import { Divider } from 'antd';
import type { CSSProperties, FC, ReactNode } from 'react';
import { Flexbox } from 'react-layout-kit';
import useMergedState from 'use-merge-value';
import { getPrefixCls } from '@/components/theme';
import { ConfigProvider } from '../ConfigProvider';
import { useStyles } from './style';

interface CollapseTitleProps {
/**
* 默认展开
*/
defaultExpand?: boolean;
/**
* 受控展开
*/
expand?: boolean;
/**
* 标题
*/
title?: string;
/**
* 展开回调
* @param expand
* @returns
*/
onExpandChange?: (expand: boolean) => void;
/**
* 前缀
*/
prefixCls?: string;
/**
* 类名
*/
className?: string;
/**
* 扩展部位渲染
* @param expand
* @returns
*/
extra?: (expand: boolean) => ReactNode;
/**
* 样式
*/
style?: CSSProperties;
/**
* 内容
*/
children?: ReactNode;
}

const CollapseTitle: FC<CollapseTitleProps> = ({
defaultExpand = false,
expand,
onExpandChange,
prefixCls: customizePrefixCls,
title,
children,
className,
extra,
}) => {
const prefixCls = getPrefixCls('collapse-title', customizePrefixCls);

const [showPanel, setCollapsed] = useMergedState(defaultExpand, {
value: expand,
onChange: onExpandChange,
});

const toggleCollapse = () => {
setCollapsed(!showPanel);
};

const { styles } = useStyles({ showPanel, prefixCls, className });
return (
<ConfigProvider>
<Flexbox className={styles.container}>
<Flexbox
direction={'horizontal'}
distribution={'space-between'}
align={'center'}
className={styles.header}
onClick={showPanel ? undefined : toggleCollapse}
>
<Flexbox
direction={'horizontal'}
gap={4}
align={'center'}
onClick={showPanel ? toggleCollapse : undefined}
className={styles.title}
>
<CaretRightFilled style={{ fontSize: 10 }} rotate={showPanel ? 90 : 0} />
<div>{title}</div>
</Flexbox>
{extra && extra(showPanel)}
</Flexbox>
{showPanel ? (
<>
<Divider style={{ margin: '4px 0 12px' }} dashed />
{children}
</>
) : null}
</Flexbox>
</ConfigProvider>
);
};

export default CollapseTitle;
44 changes: 44 additions & 0 deletions src/components/EditorComponent/CollapseTitle/style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { createStyles, css, cx } from '@/components/theme';

export const useStyles = createStyles(({ token, stylish }, { prefixCls, className, showPanel }) => {
return {
container: cx(
prefixCls,
className,
showPanel
? css`
padding: 6px 8px;
background: ${token.colorFillQuaternary};
border-radius: 4px;
`
: '',
),

header: cx(
showPanel ? `${prefixCls}-expand` : `${prefixCls}-collapsed`,
showPanel
? ''
: css`
padding: 6px 8px;
user-select: none;
border-radius: 4px;
${stylish.containerBgL2}
`,
),

title: cx(
css`
height: 24px;
`,
showPanel
? css`
margin-left: -4px;
padding: 4px;
border-radius: 4px;
user-select: none;
${stylish.containerBgHover}
`
: '',
),
};
});
55 changes: 55 additions & 0 deletions src/components/EditorComponent/ConfigProvider/AppContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { ThemeConfig } from 'antd/es/config-provider/context';
import type { FC, PropsWithChildren } from 'react';
import { GetAntdThemeConfig, STUDIO_UI_PREFIX } from '@/components/theme';

import { OverrideAntdGlobalStyles } from '../override';
import {
createStudioAntdTheme,
getStudioStylish,
getStudioToken,
ThemeAppearance,
ThemeMode,
ThemeProvider,
} from '@/components/theme';

/**
* @title 应用容器属性
*/
export interface AppContainerProps {
/**
* @title 主题外观
*/
appearance?: ThemeAppearance;
/**
* @title 主题模式
* @enum ['light', 'dark']
* @enumNames ['亮色', '暗色']
* @default 'light'
*/
themeMode?: ThemeMode;
/**
* @title 主题配置
* @description 可以传入一个对象或者函数来生成主题配置
*/
theme?: ThemeConfig | GetAntdThemeConfig;
}

export const AppContainer: FC<PropsWithChildren<AppContainerProps>> = ({
children,
theme,
appearance,
themeMode,
}) => (
<ThemeProvider
prefixCls={STUDIO_UI_PREFIX}
appearance={appearance}
themeMode={themeMode}
// 以下都是自定义主题
theme={theme ?? createStudioAntdTheme}
customToken={getStudioToken}
customStylish={getStudioStylish}
>
<OverrideAntdGlobalStyles />
{children}
</ThemeProvider>
);
Loading

0 comments on commit c87cd31

Please sign in to comment.