feat(theme-editor): support to config Header's color and Settings button's color (#2263)

* feat: add color options to theme editor

* feat: add default theme

* refactor: optimize

* feat: support to change color of UI settings

* fix: fix menu background color

* fix: fix color of UI settings

* feat: support to set alpha

* refactor: migrate style to a file

* feat: support colorBgSettingsHover and colorBorderSettingsHover

* feat: adapt settings color

* fix: should be reset together

* feat: compat old theme
This commit is contained in:
被雨水过滤的空气-Rain 2023-07-21 10:38:56 +08:00 committed by GitHub
parent 7c45663cd1
commit 4f5ec0a581
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
53 changed files with 2138 additions and 196 deletions

View File

@ -38,6 +38,9 @@ const CSSVariableProvider = ({ children }) => {
document.body.style.setProperty('--colorBgScrollBarHover', colorBgScrollBarHover);
document.body.style.setProperty('--colorBgScrollBarActive', colorBgScrollBarActive);
document.body.style.setProperty('--colorBgDrawer', colorBgDrawer);
document.body.style.setProperty('--colorSettings', token.colorSettings);
document.body.style.setProperty('--colorBgSettingsHover', token.colorBgSettingsHover);
document.body.style.setProperty('--colorBorderSettingsHover', token.colorBorderSettingsHover);
// 设置登录页面的背景色
document.body.style.setProperty('background-color', token.colorBgContainer);
@ -65,6 +68,9 @@ const CSSVariableProvider = ({ children }) => {
token.colorPrimaryText,
token.colorPrimaryTextActive,
token.colorPrimaryTextHover,
token.colorSettings,
token.colorBgSettingsHover,
token.colorBorderSettingsHover,
]);
return children;

View File

@ -0,0 +1,22 @@
import { ThemeConfig } from './type';
const defaultTheme: ThemeConfig = {
name: '',
token: {
// 顶部导航栏
colorPrimaryHeader: '#001529',
colorBgHeader: '#001529',
colorBgHeaderMenuHover: '#ffffff1a',
colorBgHeaderMenuActive: '#ffffff1a',
colorTextHeaderMenu: '#ffffffa6',
colorTextHeaderMenuHover: '#ffffff',
colorTextHeaderMenuActive: '#ffffff',
// UI 配置组件
colorSettings: '#F18B62',
colorBgSettingsHover: 'rgba(241, 139, 98, 0.06)',
colorBorderSettingsHover: 'rgba(241, 139, 98, 0.3)',
},
};
export default defaultTheme;

View File

@ -1,8 +1,8 @@
import { ConfigProvider, theme as antdTheme, type ThemeConfig as Config } from 'antd';
import { ConfigProvider, theme as antdTheme } from 'antd';
import _ from 'lodash';
import React, { createContext, useCallback, useMemo, useRef } from 'react';
type ThemeConfig = Config & { name?: string; algorithm?: string | Config['algorithm'] };
import defaultTheme from './defaultTheme';
import { ThemeConfig } from './type';
interface ThemeItem {
id: number;
@ -27,8 +27,8 @@ export const useGlobalTheme = () => {
return React.useContext(GlobalThemeContext);
};
export const GlobalThemeProvider = ({ children, theme: defaultTheme }) => {
const [theme, setTheme] = React.useState<ThemeConfig>(defaultTheme || { name: 'Custom theme' });
export const GlobalThemeProvider = ({ children, theme: themeFromProps }) => {
const [theme, setTheme] = React.useState<ThemeConfig>(themeFromProps || defaultTheme);
const currentSettingThemeRef = useRef<ThemeConfig>(null);
const currentEditingThemeRef = useRef<ThemeItem>(null);
@ -83,3 +83,6 @@ export const GlobalThemeProvider = ({ children, theme: defaultTheme }) => {
};
export { default as AntdAppProvider } from './AntdAppProvider';
export * from './type';
export { defaultTheme };

View File

@ -0,0 +1,36 @@
import { MappingAlgorithm } from 'antd/es/config-provider/context';
import { OverrideToken } from 'antd/es/theme/interface';
import { AliasToken } from 'antd/es/theme/internal';
export interface CustomToken extends AliasToken {
/** 顶部导航栏主色 */
colorPrimaryHeader: string;
/** 导航栏背景色 */
colorBgHeader: string;
/** 导航栏菜单背景色悬浮态 */
colorBgHeaderMenuHover: string;
/** 导航栏菜单背景色激活态 */
colorBgHeaderMenuActive: string;
/** 导航栏菜单文本色 */
colorTextHeaderMenu: string;
/** 导航栏菜单文本色悬浮态 */
colorTextHeaderMenuHover: string;
/** 导航栏菜单文本色激活态 */
colorTextHeaderMenuActive: string;
/** UI 配置色 */
colorSettings: string;
/** 鼠标悬浮时显示的背景色 */
colorBgSettingsHover: string;
/** 鼠标悬浮时显示的边框色 */
colorBorderSettingsHover: string;
}
export interface ThemeConfig {
name?: string;
token?: Partial<CustomToken>;
components?: OverrideToken;
algorithm?: MappingAlgorithm | MappingAlgorithm[];
hashed?: boolean;
inherit?: boolean;
}

View File

@ -19,6 +19,7 @@ import {
useDocumentTitle,
useRequest,
useSystemSettings,
useToken,
} from '../../../';
import { Plugin } from '../../../application/Plugin';
import { useCollectionManager } from '../../../collection-manager';
@ -153,6 +154,7 @@ export const InternalAdminLayout = (props: any) => {
const result = useSystemSettings();
const { service } = useCollectionManager();
const params = useParams<any>();
const { token } = useToken();
return (
<Layout>
@ -161,10 +163,12 @@ export const InternalAdminLayout = (props: any) => {
.ant-menu.ant-menu-dark .ant-menu-item-selected,
.ant-menu-submenu-popup.ant-menu-dark .ant-menu-item-selected,
.ant-menu-submenu-horizontal.ant-menu-submenu-selected {
background-color: rgba(255, 255, 255, 0.1);
background-color: ${token.colorBgHeaderMenuActive};
color: ${token.colorTextHeaderMenuActive};
}
.ant-menu-dark.ant-menu-horizontal > .ant-menu-item:hover {
background-color: rgba(255, 255, 255, 0.1);
background-color: ${token.colorBgHeaderMenuHover};
color: ${token.colorTextHeaderMenuHover};
}
position: fixed;
@ -174,6 +178,15 @@ export const InternalAdminLayout = (props: any) => {
line-height: var(--nb-header-height);
padding: 0;
z-index: 100;
background-color: ${token.colorBgHeader};
.ant-menu {
background-color: transparent;
}
.ant-menu-item {
color: ${token.colorTextHeaderMenu};
}
`}
>
<div

View File

@ -1,3 +1,15 @@
import { theme } from 'antd';
const { useToken } = theme;
import { CustomToken } from '../../../../global-theme';
const { useToken: useAntdToken } = theme;
interface Result extends ReturnType<typeof useAntdToken> {
token: CustomToken;
}
const useToken = () => {
const result = useAntdToken();
return result as Result;
};
export { useToken };

View File

@ -2,6 +2,7 @@ import type { CSSInterpolation, CSSObject } from '@ant-design/cssinjs';
import { useStyleRegister } from '@ant-design/cssinjs';
import { merge } from '@formily/shared';
import type { ComponentTokenMap, GlobalToken } from 'antd/es/theme/interface';
import { CustomToken } from '../../../global-theme';
import { useConfig, usePrefixCls, useToken } from './hooks';
export type OverrideComponent = keyof ComponentTokenMap | string;
@ -62,7 +63,7 @@ export type UseComponentStyleResult = {
export const genStyleHook = <ComponentName extends OverrideComponent>(
component: ComponentName,
styleFn: (token: TokenWithCommonCls<GlobalToken>, props: any, info: StyleInfo) => CSSInterpolation,
styleFn: (token: TokenWithCommonCls<CustomToken>, props: any, info: StyleInfo) => CSSInterpolation,
) => {
return (props?: any): UseComponentStyleResult => {
const { theme, token, hashId } = useToken();
@ -80,7 +81,7 @@ export const genStyleHook = <ComponentName extends OverrideComponent>(
},
() => {
const componentCls = `.${prefixCls}`;
const mergedToken: TokenWithCommonCls<GlobalToken> = merge(token, {
const mergedToken: TokenWithCommonCls<CustomToken> = merge(token, {
componentCls,
prefixCls,
iconCls: `.${iconPrefixCls}`,

View File

@ -0,0 +1,57 @@
import { genStyleHook } from '../__builtins__';
const useStyles = genStyleHook('nb-action', (token) => {
const { componentCls } = token;
return {
[componentCls]: {
'.renderButton': {
position: 'relative',
'&:hover': { '> .general-schema-designer': { display: 'block' } },
'&.nb-action-link': {
'> .general-schema-designer': {
top: '-10px',
bottom: '-10px',
left: '-10px',
right: '-10px',
},
},
'> .general-schema-designer': {
position: 'absolute',
zIndex: 999,
top: '0',
bottom: '0',
left: '0',
right: '0',
display: 'none',
background: 'var(--colorBgSettingsHover)',
border: '0',
pointerEvents: 'none',
'> .general-schema-designer-icons': {
position: 'absolute',
right: '2px',
top: '2px',
lineHeight: '16px',
pointerEvents: 'all',
'.ant-space-item': {
backgroundColor: token.colorSettings,
color: '#fff',
lineHeight: '16px',
width: '16px',
paddingLeft: '1px',
alignSelf: 'stretch',
},
},
},
},
'.popover': {
display: 'flex',
justifyContent: 'flex-end',
width: '100%',
},
},
};
});
export default useStyles;

View File

@ -1,4 +1,3 @@
import { css } from '@emotion/css';
import { observer, RecursionField, useField, useFieldSchema, useForm } from '@formily/react';
import { App, Button, Popover } from 'antd';
import classnames from 'classnames';
@ -17,56 +16,13 @@ import { ActionDrawer } from './Action.Drawer';
import { ActionLink } from './Action.Link';
import { ActionModal } from './Action.Modal';
import { ActionPage } from './Action.Page';
import useStyles from './Action.style';
import { ActionContextProvider } from './context';
import { useA } from './hooks';
import { ComposedAction } from './types';
import { linkageAction } from './utils';
import { lodash } from '@nocobase/utils';
export const actionDesignerCss = css`
position: relative;
&:hover {
> .general-schema-designer {
display: block;
}
}
&.nb-action-link {
> .general-schema-designer {
top: -10px;
bottom: -10px;
left: -10px;
right: -10px;
}
}
> .general-schema-designer {
position: absolute;
z-index: 999;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: none;
background: rgba(241, 139, 98, 0.06);
border: 0;
pointer-events: none;
> .general-schema-designer-icons {
position: absolute;
right: 2px;
top: 2px;
line-height: 16px;
pointer-events: all;
.ant-space-item {
background-color: #f18b62;
color: #fff;
line-height: 16px;
width: 16px;
padding-left: 1px;
align-self: stretch;
}
}
}
`;
export const Action: ComposedAction = observer(
(props: any) => {
const {
@ -81,6 +37,7 @@ export const Action: ComposedAction = observer(
title,
...others
} = props;
const { wrapSSR, componentCls, hashId } = useStyles();
const { t } = useTranslation();
const { onClick } = useProps(props);
const [visible, setVisible] = useState(false);
@ -148,7 +105,7 @@ export const Action: ComposedAction = observer(
}
}}
component={tarComponent || Button}
className={classnames(actionDesignerCss, className)}
className={classnames('renderButton', className)}
type={props.type === 'danger' ? undefined : props.type}
>
{actionTitle}
@ -157,23 +114,25 @@ export const Action: ComposedAction = observer(
);
};
return (
<ActionContextProvider
button={renderButton()}
visible={visible}
setVisible={setVisible}
formValueChanged={formValueChanged}
setFormValueChanged={setFormValueChanged}
openMode={openMode}
openSize={openSize}
containerRefKey={containerRefKey}
fieldSchema={fieldSchema}
>
{popover && <RecursionField basePath={field.address} onlyRenderProperties schema={fieldSchema} />}
{!popover && renderButton()}
{!popover && <div onClick={(e) => e.stopPropagation()}>{props.children}</div>}
{element}
</ActionContextProvider>
return wrapSSR(
<div className={`${componentCls} ${hashId}`}>
<ActionContextProvider
button={renderButton()}
visible={visible}
setVisible={setVisible}
formValueChanged={formValueChanged}
setFormValueChanged={setFormValueChanged}
openMode={openMode}
openSize={openSize}
containerRefKey={containerRefKey}
fieldSchema={fieldSchema}
>
{popover && <RecursionField basePath={field.address} onlyRenderProperties schema={fieldSchema} />}
{!popover && renderButton()}
{!popover && <div onClick={(e) => e.stopPropagation()}>{props.children}</div>}
{element}
</ActionContextProvider>
</div>,
);
},
{ displayName: 'Action' },
@ -201,17 +160,7 @@ Action.Popover = observer(
Action.Popover.Footer = observer(
(props) => {
return (
<div
className={css`
display: flex;
justify-content: flex-end;
width: 100%;
`}
>
{props.children}
</div>
);
return <div className="popover">{props.children}</div>;
},
{ displayName: 'Action.Popover.Footer' },
);

View File

@ -10,7 +10,7 @@ const useStyles = genStyleHook('nb-association-filter-item', (token) => {
'&:hover': { '> .general-schema-designer': { display: 'block' } },
'&.nb-form-item:hover': {
'> .general-schema-designer': {
background: 'rgba(241, 139, 98, 0.06) !important',
background: 'var(--colorBgSettingsHover) !important',
border: '0 !important',
top: `-${token.sizeXXS}px !important`,
bottom: `-${token.sizeXXS}px !important`,
@ -26,7 +26,7 @@ const useStyles = genStyleHook('nb-association-filter-item', (token) => {
left: '0',
right: '0',
display: 'none',
border: '2px solid rgba(241, 139, 98, 0.3)',
border: '2px solid var(--colorBorderSettingsHover)',
pointerEvents: 'none',
'> .general-schema-designer-icons': {
position: 'absolute',
@ -35,7 +35,7 @@ const useStyles = genStyleHook('nb-association-filter-item', (token) => {
lineHeight: '16px',
pointerEvents: 'all',
'.ant-space-item': {
backgroundColor: '#f18b62',
backgroundColor: 'var(--colorSettings)',
color: '#fff',
lineHeight: '16px',
width: '16px',

View File

@ -39,7 +39,7 @@ export const AssociationFilter = (props) => {
}
&.nb-form-item:hover {
> .general-schema-designer {
background: rgba(241, 139, 98, 0.06) !important;
background: var(--colorBgSettingsHover) !important;
border: 0 !important;
top: -5px !important;
bottom: -5px !important;
@ -55,7 +55,7 @@ export const AssociationFilter = (props) => {
left: 0;
right: 0;
display: none;
border: 2px solid rgba(241, 139, 98, 0.3);
border: 2px solid var(--colorBorderSettingsHover);
pointer-events: none;
> .general-schema-designer-icons {
position: absolute;
@ -64,7 +64,7 @@ export const AssociationFilter = (props) => {
line-height: 16px;
pointer-events: all;
.ant-space-item {
background-color: #f18b62;
background-color: var(--colorSettings);
color: #fff;
line-height: 16px;
width: 16px;

View File

@ -21,7 +21,7 @@ export const BlockItem: React.FC<any> = (props) => {
}
&.nb-form-item:hover {
> .general-schema-designer {
background: rgba(241, 139, 98, 0.06) !important;
background: var(--colorBgSettingsHover) !important;
border: 0 !important;
top: -5px !important;
bottom: -5px !important;
@ -37,7 +37,7 @@ export const BlockItem: React.FC<any> = (props) => {
left: 0;
right: 0;
display: none;
border: 2px solid rgba(241, 139, 98, 0.3);
border: 2px solid var(--colorBorderSettingsHover);
pointer-events: none;
> .general-schema-designer-icons {
position: absolute;
@ -46,7 +46,7 @@ export const BlockItem: React.FC<any> = (props) => {
line-height: 16px;
pointer-events: all;
.ant-space-item {
background-color: #f18b62;
background-color: var(--colorSettings);
color: #fff;
line-height: 16px;
width: 16px;

View File

@ -20,7 +20,7 @@ const actionDesignerCss = css`
left: 0;
right: 0;
display: none;
background: rgba(241, 139, 98, 0.06);
background: var(--colorBgSettingsHover);
border: 0;
top: 0;
bottom: 0;
@ -34,7 +34,7 @@ const actionDesignerCss = css`
line-height: 16px;
pointer-events: all;
.ant-space-item {
background-color: #f18b62;
background-color: var(--colorSettings);
color: #fff;
line-height: 16px;
width: 16px;

View File

@ -103,8 +103,8 @@ const SaveConditions = () => {
<Button
type={'dashed'}
className={css`
border-color: rgb(241, 139, 98);
color: rgb(241, 139, 98);
border-color: var(--colorSettings);
color: var(--colorSettings);
`}
onClick={() => {
const defaultValue = { ...form.values.filter };

View File

@ -16,8 +16,8 @@ export const SaveDefaultValue = (props) => {
return (
<Button
className={css`
border-color: rgb(241, 139, 98);
color: rgb(241, 139, 98);
border-color: var(--colorSettings);
color: var(--colorSettings);
`}
type={'dashed'}
onClick={() => {

View File

@ -34,7 +34,7 @@ const designerCss = css`
left: 0;
right: 0;
display: none;
background: rgba(241, 139, 98, 0.06);
background: var(--colorBgSettingsHover);
border: 0;
pointer-events: none;
> .general-schema-designer-icons {
@ -44,7 +44,7 @@ const designerCss = css`
line-height: 16px;
pointer-events: all;
.ant-space-item {
background-color: #f18b62;
background-color: var(--colorSettings);
color: #fff;
line-height: 16px;
width: 16px;

View File

@ -18,7 +18,7 @@ const useStyles = genStyleHook('nb-grid', (token) => {
cursor: 'col-resize',
},
'&:hover': {
'&::before': { background: 'rgba(241, 139, 98, 0.06) !important' },
'&::before': { background: 'var(--colorBgSettingsHover) !important' },
},
width: token.marginLG,
height: '100%',

View File

@ -1,3 +1,4 @@
import { TinyColor } from '@ctrl/tinycolor';
import { useDndContext, useDndMonitor, useDraggable, useDroppable } from '@dnd-kit/core';
import { RecursionField, Schema, observer, useField, useFieldSchema } from '@formily/react';
import { uid } from '@formily/shared';
@ -16,6 +17,7 @@ const breakRemoveOnGrid = (s: Schema) => s['x-component'] === 'Grid';
const breakRemoveOnRow = (s: Schema) => s['x-component'] === 'Grid.Row';
const ColDivider = (props) => {
const { token } = useToken();
const dragIdRef = useRef<string | null>(null);
const { isOver, setNodeRef } = useDroppable({
@ -28,7 +30,7 @@ const ColDivider = (props) => {
const droppableStyle = useMemo(() => {
if (!isOver) return {};
return {
backgroundColor: 'rgba(241, 139, 98, .1)',
backgroundColor: new TinyColor(token.colorSettings).setAlpha(0.1).toHex8String(),
};
}, [isOver]);
@ -161,6 +163,7 @@ const ColDivider = (props) => {
};
const RowDivider = (props) => {
const { token } = useToken();
const { isOver, setNodeRef } = useDroppable({
id: props.id,
data: props.data,
@ -176,7 +179,7 @@ const RowDivider = (props) => {
return {
zIndex: active ? 1000 : -1,
backgroundColor: 'rgba(241, 139, 98, .1)',
backgroundColor: new TinyColor(token.colorSettings).setAlpha(0.1).toHex8String(),
};
}, [active, isOver]);

View File

@ -18,7 +18,7 @@ const titleCss = css`
pointer-events: none;
position: absolute;
font-size: 12px;
background: #f18b62;
background: var(--colorSettings);
color: #fff;
padding: 0 5px;
line-height: 16px;

View File

@ -17,7 +17,7 @@ const useStyles = genStyleHook('nb-list', (token) => {
left: '0',
right: '0',
display: 'none',
background: 'rgba(241, 139, 98, 0.06)',
background: 'var(--colorBgSettingsHover)',
border: '0',
pointerEvents: 'none',
'> .general-schema-designer-icons': {
@ -27,7 +27,7 @@ const useStyles = genStyleHook('nb-list', (token) => {
lineHeight: '16px',
pointerEvents: 'all',
'.ant-space-item': {
backgroundColor: '#f18b62',
backgroundColor: 'var(--colorSettings)',
color: '#fff',
lineHeight: '16px',
width: '16px',

View File

@ -48,7 +48,7 @@ const subMenuDesignerCss = css`
left: 0;
right: 0;
display: none;
background: rgba(241, 139, 98, 0.06);
background: var(--colorBgSettingsHover);
border: 0;
pointer-events: none;
> .general-schema-designer-icons {
@ -58,7 +58,7 @@ const subMenuDesignerCss = css`
line-height: 16px;
pointer-events: all;
.ant-space-item {
background-color: #f18b62;
background-color: var(--colorSettings);
color: #fff;
line-height: 16px;
width: 16px;
@ -99,7 +99,7 @@ const designerCss = css`
left: 0;
right: 0;
display: none;
background: rgba(241, 139, 98, 0.06);
background: var(--colorBgSettingsHover);
border: 0;
pointer-events: none;
> .general-schema-designer-icons {
@ -109,7 +109,7 @@ const designerCss = css`
line-height: 16px;
pointer-events: all;
.ant-space-item {
background-color: #f18b62;
background-color: var(--colorSettings);
color: #fff;
line-height: 16px;
width: 16px;

View File

@ -30,7 +30,7 @@ export const useStyles = genStyleHook('nb-page', (token) => {
lineHeight: '16px',
pointerEvents: 'all',
'.ant-space-item': {
backgroundColor: '#f18b62',
backgroundColor: 'var(--colorSettings)',
color: '#fff',
lineHeight: '16px',
width: '16px',
@ -57,8 +57,8 @@ export const useStyles = genStyleHook('nb-page', (token) => {
},
'.addTabBtn': {
borderColor: 'rgb(241, 139, 98) !important',
color: 'rgb(241, 139, 98) !important',
borderColor: 'var(--colorSettings)',
color: 'var(--colorSettings)',
},
'.designerCss': {
@ -80,7 +80,7 @@ export const useStyles = genStyleHook('nb-page', (token) => {
left: '0',
right: '0',
display: 'none',
background: 'rgba(241, 139, 98, 0.06)',
background: 'var(--colorBgSettingsHover)',
border: '0',
pointerEvents: 'none',
'> .general-schema-designer-icons': {
@ -90,7 +90,7 @@ export const useStyles = genStyleHook('nb-page', (token) => {
lineHeight: '16px',
pointerEvents: 'all',
'.ant-space-item': {
backgroundColor: '#f18b62',
backgroundColor: 'var(--colorSettings)',
color: '#fff',
lineHeight: '16px',
width: '16px',

View File

@ -18,7 +18,7 @@ export const designerCss = css`
left: 0;
right: 0;
display: none;
background: rgba(241, 139, 98, 0.06) !important;
background: var(--colorBgSettingsHover) !important;
border: 0 !important;
top: -16px !important;
bottom: -16px !important;
@ -32,7 +32,7 @@ export const designerCss = css`
line-height: 16px;
pointer-events: all;
.ant-space-item {
background-color: #f18b62;
background-color: var(--colorSettings);
color: #fff;
line-height: 16px;
width: 16px;

View File

@ -1,4 +1,5 @@
import { DeleteOutlined, MenuOutlined } from '@ant-design/icons';
import { TinyColor } from '@ctrl/tinycolor';
import { SortableContext, useSortable } from '@dnd-kit/sortable';
import { css } from '@emotion/css';
import { ArrayField, Field } from '@formily/core';
@ -113,27 +114,33 @@ const useTableColumns = (props) => {
return tableColumns;
};
const topActiveClass = css`
& > td {
border-top: 2px solid rgba(241, 139, 98, 0.6) !important;
}
`;
const bottomActiveClass = css`
& > td {
border-bottom: 2px solid rgba(241, 139, 98, 0.6) !important;
}
`;
const SortableRow = (props) => {
const { token } = useToken();
const id = props['data-row-key']?.toString();
const { setNodeRef, isOver, active, over } = useSortable({
id,
});
const classObj = useMemo(() => {
const borderColor = new TinyColor(token.colorSettings).setAlpha(0.6).toHex8String();
return {
topActiveClass: css`
& > td {
border-top: 2px solid ${borderColor} !important;
}
`,
bottomActiveClass: css`
& > td {
border-bottom: 2px solid ${borderColor} !important;
}
`,
};
}, [token.colorSettings]);
const className =
(active?.data.current?.sortable.index ?? -1) > (over?.data.current?.sortable?.index ?? -1)
? topActiveClass
: bottomActiveClass;
? classObj.topActiveClass
: classObj.bottomActiveClass;
return (
<tr

View File

@ -1,4 +1,5 @@
import { MenuOutlined } from '@ant-design/icons';
import { TinyColor } from '@ctrl/tinycolor';
import { css } from '@emotion/css';
import { ArrayField, Field } from '@formily/core';
import {
@ -15,6 +16,7 @@ import React, { useContext, useState } from 'react';
import ReactDragListView from 'react-drag-listview';
import { DndContext } from '../..';
import { RecordIndexProvider, RecordProvider, useRequest, useSchemaInitializer } from '../../../';
import { useToken } from '../__builtins__';
const isColumnComponent = (schema: Schema) => {
return schema['x-component']?.endsWith('.Column') > -1;
@ -149,6 +151,7 @@ const useDefAction = () => {
export const TableArray: React.FC<any> = observer(
(props) => {
const { token } = useToken();
const field = useField<ArrayField>();
const columns = useTableColumns();
const {
@ -270,7 +273,7 @@ export const TableArray: React.FC<any> = observer(
await move(from, to);
}}
lineClassName={css`
border-bottom: 2px solid rgba(241, 139, 98, 0.6) !important;
border-bottom: 2px solid ${new TinyColor(token.colorSettings).setAlpha(0.6).toHex8String()} !important;
`}
>
<Table

View File

@ -18,7 +18,7 @@ export const designerCss = css`
left: 0;
right: 0;
display: none;
background: rgba(241, 139, 98, 0.06) !important;
background: var(--colorBgSettingsHover) !important;
border: 0 !important;
top: -16px !important;
bottom: -16px !important;
@ -32,7 +32,7 @@ export const designerCss = css`
line-height: 16px;
pointer-events: all;
.ant-space-item {
background-color: #f18b62;
background-color: var(--colorSettings);
color: #fff;
line-height: 16px;
width: 16px;

View File

@ -1,9 +1,8 @@
import { css } from '@emotion/css';
import { observer, RecursionField, useField, useFieldSchema } from '@formily/react';
import { TabPaneProps, Tabs as AntdTabs, TabsProps } from 'antd';
import { Tabs as AntdTabs, TabPaneProps, TabsProps } from 'antd';
import classNames from 'classnames';
import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { Icon } from '../../../icon';
import { useSchemaInitializer } from '../../../schema-initializer';
import { DndContext, SortableItem } from '../../common';
@ -68,7 +67,7 @@ const designerCss = css`
left: 0;
right: 0;
display: none;
background: rgba(241, 139, 98, 0.06);
background: var(--colorBgSettingsHover);
border: 0;
top: 0;
bottom: 0;
@ -82,7 +81,7 @@ const designerCss = css`
line-height: 16px;
pointer-events: all;
.ant-space-item {
background-color: #f18b62;
background-color: var(--colorSettings);
color: #fff;
line-height: 16px;
width: 16px;

View File

@ -1,7 +1,9 @@
import { TinyColor } from '@ctrl/tinycolor';
import { useDraggable, useDroppable } from '@dnd-kit/core';
import { cx } from '@emotion/css';
import { Schema, observer, useField, useFieldSchema } from '@formily/react';
import React, { HTMLAttributes, createContext, useContext } from 'react';
import { useToken } from '../../antd/__builtins__';
export const DraggableContext = createContext(null);
export const SortableContext = createContext(null);
@ -21,12 +23,15 @@ export const SortableProvider = (props) => {
export const Sortable = (props: any) => {
const { component, overStyle, style, children, openMode, ...others } = props;
const { token } = useToken();
const { draggable, droppable } = useContext(SortableContext);
const { isOver, setNodeRef } = droppable;
const droppableStyle = { ...style };
if (isOver && draggable?.active?.id !== droppable?.over?.id) {
droppableStyle[component === 'a' ? 'color' : 'background'] = 'rgba(241, 139, 98, .15)';
droppableStyle[component === 'a' ? 'color' : 'background'] = new TinyColor(token.colorSettings)
.setAlpha(0.15)
.toHex8String();
Object.assign(droppableStyle, overStyle);
}

View File

@ -10,7 +10,7 @@ export const DesignableSwitch = () => {
const { t } = useTranslation();
const style = {};
if (designable) {
style['backgroundColor'] = '#f18b62';
style['backgroundColor'] = 'var(--colorSettings)';
}
// 快捷键切换编辑状态

View File

@ -69,8 +69,8 @@ SchemaInitializer.Button = observer(
<Button
type={'dashed'}
style={{
borderColor: '#f18b62',
color: '#f18b62',
borderColor: 'var(--colorSettings)',
color: 'var(--colorSettings)',
...style,
}}
{...others}
@ -329,8 +329,8 @@ SchemaInitializer.ActionModal = function ActionModal(props: SchemaInitializerAct
'x-component-props': {
icon: 'PlusOutlined',
style: {
borderColor: 'rgb(241, 139, 98)',
color: 'rgb(241, 139, 98)',
borderColor: 'var(--colorSettings)',
color: 'var(--colorSettings)',
},
title: buttonText,
type: 'dashed',

View File

@ -54,8 +54,8 @@ export const TabPaneInitializers = (props?: any) => {
'x-component-props': {
icon: 'PlusOutlined',
style: {
borderColor: 'rgb(241, 139, 98)',
color: 'rgb(241, 139, 98)',
borderColor: 'var(--colorSettings)',
color: 'var(--colorSettings)',
},
type: 'dashed',
},

View File

@ -26,7 +26,7 @@ export const actionDesignerCss = css`
left: 0;
right: 0;
display: none;
background: rgba(241, 139, 98, 0.06);
background: var(--colorBgSettingsHover);
border: 0;
top: 0;
bottom: 0;
@ -40,7 +40,7 @@ export const actionDesignerCss = css`
line-height: 16px;
pointer-events: all;
.ant-space-item {
background-color: #f18b62;
background-color: var(--colorSettings);
color: #fff;
line-height: 16px;
width: 16px;

View File

@ -13,7 +13,7 @@ const titleCss = css`
pointer-events: none;
position: absolute;
font-size: 12px;
/* background: #f18b62;
/* background: var(--colorSettings);
color: #fff; */
padding: 0;
line-height: 16px;
@ -25,7 +25,7 @@ const titleCss = css`
.title-tag {
padding: 0 3px;
border-radius: 2px;
background: #f18b62;
background: var(--colorSettings);
color: #fff;
display: block;
}

View File

@ -1,3 +1,15 @@
import { theme } from 'antd';
import { CustomToken } from '../global-theme';
export const { useToken } = theme;
const { useToken: useAntdToken } = theme;
interface Result extends ReturnType<typeof useAntdToken> {
token: CustomToken;
}
const useToken = () => {
const result = useAntdToken();
return result as Result;
};
export { useToken };

View File

@ -74,11 +74,11 @@ export const useShared = () => {
'x-component': 'ArrayItems.Addition',
'x-component-props': {
className: css`
border-color: rgb(241, 139, 98);
color: rgb(241, 139, 98);
border-color: var(--colorSettings);
color: var(--colorSettings);
&.ant-btn-dashed:hover {
border-color: rgb(241, 139, 98);
color: rgb(241, 139, 98);
border-color: var(--colorSettings);
color: var(--colorSettings);
}
`,
},

View File

@ -79,11 +79,11 @@ export const useShared = () => {
'x-component': 'ArrayItems.Addition',
'x-component-props': {
className: css`
border-color: rgb(241, 139, 98);
color: rgb(241, 139, 98);
border-color: var(--colorSettings);
color: var(--colorSettings);
&.ant-btn-dashed:hover {
border-color: rgb(241, 139, 98);
color: rgb(241, 139, 98);
border-color: var(--colorSettings);
color: var(--colorSettings);
}
`,
},

View File

@ -30,8 +30,8 @@ export const ContainerDesigner = () => {
title={
<Button
style={{
borderColor: 'rgb(241, 139, 98)',
color: 'rgb(241, 139, 98)',
borderColor: 'var(--colorSettings)',
color: 'var(--colorSettings)',
width: '100%',
}}
icon={<MenuOutlined />}

View File

@ -1,9 +1,9 @@
import { GeneralSchemaDesigner, SchemaSettings, useDesignable } from '@nocobase/client';
import { MenuOutlined } from '@ant-design/icons';
import { useField, useFieldSchema } from '@formily/react';
import { SchemaSettings, useDesignable } from '@nocobase/client';
import { Button } from 'antd';
import React from 'react';
import { useTranslation } from '../../../../locale';
import { Button } from 'antd';
import { useFieldSchema, useField } from '@formily/react';
export const MenuDesigner: React.FC = (props) => {
const { t } = useTranslation();
@ -20,8 +20,8 @@ export const MenuDesigner: React.FC = (props) => {
title={
<Button
style={{
borderColor: 'rgb(241, 139, 98)',
color: 'rgb(241, 139, 98)',
borderColor: 'var(--colorSettings)',
color: 'var(--colorSettings)',
}}
icon={<MenuOutlined />}
type="dashed"

View File

@ -26,8 +26,8 @@ export const PageDesigner = (props) => {
title={
<Button
style={{
borderColor: 'rgb(241, 139, 98)',
color: 'rgb(241, 139, 98)',
borderColor: 'var(--colorSettings)',
color: 'var(--colorSettings)',
width: '100%',
}}
icon={<MenuOutlined />}

View File

@ -43,7 +43,7 @@ const commonDesignerCSS = css`
line-height: 16px;
pointer-events: all;
.ant-space-item {
background-color: #f18b62;
background-color: var(--colorSettings);
color: #fff;
line-height: 16px;
width: 16px;

View File

@ -16,6 +16,7 @@
},
"devDependencies": {
"@nocobase/server": "0.11.0-alpha.1",
"@nocobase/test": "0.11.0-alpha.1"
"@nocobase/test": "0.11.0-alpha.1",
"@ctrl/tinycolor": "^3.6.0"
}
}

View File

@ -57,6 +57,20 @@ const useControlledTheme: UseControlledTheme = ({ theme: customTheme, defaultThe
const handleResetTheme = (path: string[]) => {
let newConfig = { ...theme.config };
newConfig = deepUpdateObj(newConfig, path, getValueByPath(themeRef.current?.config, path));
if (path[1] === 'colorSettings') {
newConfig = deepUpdateObj(
newConfig,
['token', 'colorBgSettingsHover'],
getValueByPath(themeRef.current?.config, ['token', 'colorBgSettingsHover']),
);
newConfig = deepUpdateObj(
newConfig,
['token', 'colorBorderSettingsHover'],
getValueByPath(themeRef.current?.config, ['token', 'colorBorderSettingsHover']),
);
}
handleSetTheme({ ...theme, config: newConfig }, path);
};

View File

@ -2,7 +2,7 @@ import type { AliasToken } from '../interface';
import type { TokenTree } from './interface';
import { seedRelatedAlias, seedRelatedMap } from './TokenRelation';
const category: TokenTree<keyof AliasToken> = [
const category: TokenTree<keyof AliasToken | string> = [
{
name: '颜色',
nameEn: 'Color',
@ -15,7 +15,8 @@ const category: TokenTree<keyof AliasToken> = [
name: '品牌色',
nameEn: 'Brand Color',
desc: '品牌色是体现产品特性和传播理念最直观的视觉元素之一。在你完成品牌主色的选取之后,我们会自动帮你生成一套完整的色板,并赋予它们有效的设计语义。',
descEn: '',
descEn:
'Brand color is one of the most direct visual elements to reflect the characteristics and communication of the product. After you have selected the brand color, we will automatically generate a complete color palette and assign it effective design semantics.',
seedToken: ['colorPrimary'],
mapToken: seedRelatedMap.colorPrimary,
aliasToken: seedRelatedAlias.colorPrimary,
@ -25,8 +26,9 @@ const category: TokenTree<keyof AliasToken> = [
type: 'Color',
name: '成功色',
nameEn: 'Success Color',
desc: '',
descEn: '',
desc: '用于表示操作成功的 Token 序列,如 Result、Progress 等组件会使用该组梯度变量。',
descEn:
'Used to represent the token sequence of operation success, such as Result, Progress and other components will use these map tokens.',
seedToken: ['colorSuccess'],
mapToken: seedRelatedMap.colorSuccess,
aliasToken: seedRelatedAlias.colorSuccess,
@ -36,8 +38,9 @@ const category: TokenTree<keyof AliasToken> = [
type: 'Color',
name: '警戒色',
nameEn: 'Warning Color',
desc: '',
descEn: '',
desc: '用于表示操作警告的 Token 序列,如 Notification、 Alert等警告类组件或 Input 输入类等组件会使用该组梯度变量。',
descEn:
'Used to represent the warning map token, such as Notification, Alert, etc. Alert or Control component(like Input) will use these map tokens.',
seedToken: ['colorWarning'],
mapToken: seedRelatedMap.colorWarning,
aliasToken: seedRelatedAlias.colorWarning,
@ -47,8 +50,9 @@ const category: TokenTree<keyof AliasToken> = [
type: 'Color',
name: '错误色',
nameEn: 'Error Color',
desc: '',
descEn: '',
desc: '用于表示操作失败的 Token 序列如失败按钮、错误状态提示Result组件等。',
descEn:
'Used to represent the visual elements of the operation failure, such as the error Button, error Result component, etc.',
seedToken: ['colorError'],
mapToken: seedRelatedMap.colorError,
aliasToken: seedRelatedAlias.colorError,
@ -58,8 +62,9 @@ const category: TokenTree<keyof AliasToken> = [
type: 'Color',
name: '信息色',
nameEn: 'Info Color',
desc: '',
descEn: '',
desc: '用于表示操作信息的 Token 序列,如 Alert 、Tag、 Progress 等组件都有用到该组梯度变量。',
descEn:
'Used to represent the operation information of the Token sequence, such as Alert, Tag, Progress, and other components use these map tokens.',
seedToken: ['colorInfo'],
mapToken: seedRelatedMap.colorInfo,
aliasToken: seedRelatedAlias.colorInfo,
@ -70,7 +75,8 @@ const category: TokenTree<keyof AliasToken> = [
name: '中性色',
nameEn: 'Neutral Color',
desc: '中性色主要被大量的应用在界面的文字、背景、边框和填充的 4 种场景。合理地选择中性色能够令页面信息具备良好的主次关系,助力阅读体验。',
descEn: '',
descEn:
'Neutral color is mainly applied to the four scenarios of text, background, border and fill in the interface. Reasonable selection of neutral colors can make the page information have a good primary and secondary relationship, and help the reading experience.',
seedToken: ['colorTextBase', 'colorBgBase'],
mapToken: seedRelatedMap.colorTextBase?.concat(seedRelatedMap.colorBgBase ?? []),
aliasToken: seedRelatedAlias.colorTextBase?.concat(seedRelatedAlias.colorBgBase ?? []),
@ -78,6 +84,36 @@ const category: TokenTree<keyof AliasToken> = [
'你可以利用 Alias Token 来精准控制部分组件的效果。例如 Input 、InputNumber、Select 等Control 类组件都共享了相同的 controlXX token 。只需修改值,即可实现不改变 Button 的情况下,修改 Control 类组件的效果。',
mapTokenGroups: ['text', 'border', 'fill', 'background'],
},
{
key: 'headerColor',
type: 'Color',
name: '导航色',
nameEn: 'Header Color',
desc: '只会影响页面顶部的导航栏',
descEn: 'Only affects the navigation bar at the top of the page.',
// seedToken: ['colorPrimaryHeader'],
seedToken: [
'colorBgHeader',
'colorBgHeaderMenuHover',
'colorBgHeaderMenuActive',
'colorTextHeaderMenu',
'colorTextHeaderMenuHover',
'colorTextHeaderMenuActive',
],
seedTokenAlpha: true,
},
{
key: 'settingsColor',
type: 'Color',
name: 'UI 配置色',
nameEn: 'UI Settings Color',
desc: '用于更改 UI 配置组件的颜色',
descEn: 'Used to change the color of the UI Settings component.',
// seedToken: ['colorPrimarySettings'],
seedToken: ['colorSettings'],
seedTokenAlpha: true,
mapToken: ['colorBgSettingsHover', 'colorBorderSettingsHover'],
},
],
},
{

View File

@ -34,6 +34,8 @@ export type TokenGroup<T> = {
// Seed token
seedToken?: T[];
/** make seedToken can be alpha */
seedTokenAlpha?: boolean;
mapToken?: T[];
aliasToken?: T[];

View File

@ -3,7 +3,6 @@ import { Button, Checkbox, Collapse, ConfigProvider, Popover, Switch, Tooltip, T
import type { MutableTheme } from 'antd-token-previewer';
import type { ThemeConfig } from 'antd/es/config-provider/context';
import seed from 'antd/es/theme/themes/seed';
import tokenMeta from 'antd/lib/version/token-meta.json';
import classNames from 'classnames';
import type { FC } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
@ -21,6 +20,8 @@ import makeStyle from '../utils/makeStyle';
import InputNumberPlus from './InputNumberPlus';
import TokenDetail from './TokenDetail';
import TokenPreview from './TokenPreview';
import calcCustomToken from './calcCustomToken';
import tokenMeta from './token-meta.json';
const { Panel } = Collapse;
@ -230,6 +231,7 @@ export type SeedTokenProps = {
theme: MutableTheme;
tokenName: string;
disabled?: boolean;
alpha?: boolean;
};
const getSeedValue = (config: ThemeConfig, token: string) => {
@ -256,7 +258,7 @@ const seedRange: Record<string, { min: number; max: number }> = {
},
};
const SeedTokenPreview: FC<SeedTokenProps> = ({ theme, tokenName, disabled }) => {
const SeedTokenPreview: FC<SeedTokenProps> = ({ theme, tokenName, disabled, alpha }) => {
const tokenPath = ['token', tokenName];
const [tokenValue, setTokenValue] = useState(getSeedValue(theme.config, tokenName));
const locale = useLocale();
@ -267,7 +269,7 @@ const SeedTokenPreview: FC<SeedTokenProps> = ({ theme, tokenName, disabled }) =>
...theme.config,
token: {
...theme.config.token,
[tokenName]: newValue,
...calcCustomToken(tokenName, newValue),
},
},
['token', tokenName],
@ -305,7 +307,7 @@ const SeedTokenPreview: FC<SeedTokenProps> = ({ theme, tokenName, disabled }) =>
trigger="click"
placement="bottomRight"
overlayInnerStyle={{ padding: 0 }}
content={<ColorPanel color={tokenValue} onChange={handleChange} style={{ border: 'none' }} />}
content={<ColorPanel color={tokenValue} onChange={handleChange} style={{ border: 'none' }} alpha={alpha} />}
>
<div
className="token-panel-pro-token-collapse-seed-block-sample-card"
@ -643,6 +645,7 @@ const TokenContent: FC<ColorTokenContentProps> = ({
</div>
</div>
<SeedTokenPreview
alpha={!!group.seedTokenAlpha}
theme={theme}
tokenName={seedToken}
disabled={seedToken === 'colorInfo' && infoFollowPrimary}

View File

@ -1,6 +1,5 @@
import { Tooltip } from 'antd';
import type { MutableTheme } from 'antd-token-previewer';
import tokenMeta from 'antd/lib/version/token-meta.json';
import classNames from 'classnames';
import type { FC } from 'react';
import React, { useMemo } from 'react';
@ -13,6 +12,7 @@ import getDesignToken from '../utils/getDesignToken';
import getValueByPath from '../utils/getValueByPath';
import makeStyle from '../utils/makeStyle';
import { getRelatedComponents } from '../utils/statistic';
import tokenMeta from './token-meta.json';
const useStyle = makeStyle('TokenDetail', (token) => ({
'.token-panel-token-detail': {

View File

@ -0,0 +1,18 @@
import { TinyColor } from '@ctrl/tinycolor';
const calcCustomToken = (name: string, value: any) => {
if (name === 'colorSettings') {
const color = new TinyColor(value);
return {
colorSettings: value,
colorBgSettingsHover: color.setAlpha(0.06).toHex8String(),
colorBorderSettingsHover: color.setAlpha(0.3).toHex8String(),
};
}
return {
[name]: value,
};
};
export default calcCustomToken;

View File

@ -1,4 +1,4 @@
import { useAPIClient, useCurrentUserContext, useGlobalTheme, useSystemSettings } from '@nocobase/client';
import { defaultTheme, useAPIClient, useCurrentUserContext, useGlobalTheme, useSystemSettings } from '@nocobase/client';
import { error } from '@nocobase/utils/client';
import { Spin } from 'antd';
import React, { useEffect, useRef } from 'react';
@ -50,13 +50,17 @@ const InitializeTheme: React.FC = ({ children }) => {
const theme = data?.find((item) => item.id === themeId.current);
if (theme) {
setTheme(theme.config);
api.auth.setOption('theme', JSON.stringify(Object.assign({ ...theme }, { config: changeAlgorithmFromFunctionToString(theme.config) })))
api.auth.setOption(
'theme',
JSON.stringify(Object.assign({ ...theme }, { config: changeAlgorithmFromFunctionToString(theme.config) })),
);
}
} else {
setTheme({});
setTheme(defaultTheme);
api.auth.setOption('theme', null);
}
}, [
api.auth,
currentUser?.data?.data?.systemSettings?.themeId,
data,
run,

View File

@ -7,6 +7,7 @@ import { ThemeConfig, ThemeItem } from '../../types';
import { Primary } from '../antd-token-previewer';
import { useUpdateThemeSettings } from '../hooks/useUpdateThemeSettings';
import { useTranslation } from '../locale';
import compatOldTheme from '../utils/compatOldTheme';
import { useCurrentThemeId } from './InitializeTheme';
import { useThemeEditorContext } from './ThemeEditorProvider';
@ -178,7 +179,7 @@ const ThemeCard = (props: Props) => {
const handleEdit = useCallback(() => {
setCurrentSettingTheme(currentTheme);
setCurrentEditingTheme(item);
setTheme(item.config);
setTheme(compatOldTheme(item.config));
setOpen(true);
}, [item, setCurrentEditingTheme, setCurrentSettingTheme, setOpen, setTheme, currentTheme]);
@ -277,7 +278,14 @@ const ThemeCard = (props: Props) => {
const cardStyle = useMemo(() => {
if (getCurrentEditingTheme()?.id === item.id) {
return { cursor: 'default', width: 240, height: 240, overflow: 'hidden', outline: '1px solid #f18b62', ...style };
return {
cursor: 'default',
width: 240,
height: 240,
overflow: 'hidden',
outline: '1px solid var(--colorSettings)',
...style,
};
}
return { cursor: 'default', width: 240, height: 240, overflow: 'hidden', ...style };
}, [getCurrentEditingTheme, item.id, style]);

View File

@ -1,8 +1,9 @@
import { PlusOutlined } from '@ant-design/icons';
import { useGlobalTheme, useToken } from '@nocobase/client';
import { defaultTheme, useGlobalTheme, useToken } from '@nocobase/client';
import { App, Button, Space } from 'antd';
import React, { useCallback } from 'react';
import { useTranslation } from '../locale';
import compatOldTheme from '../utils/compatOldTheme';
import { useThemeEditorContext } from './ThemeEditorProvider';
const ToEditTheme = () => {
@ -33,7 +34,7 @@ const ToEditTheme = () => {
type={'primary'}
onClick={() => {
setCurrentSettingTheme(theme);
setTheme({});
setTheme(compatOldTheme(defaultTheme));
setOpen(true);
m.destroy();
}}
@ -52,8 +53,8 @@ const ToEditTheme = () => {
width: 240,
height: 240,
borderRadius: token.borderRadiusLG,
borderColor: '#f18b62',
color: '#f18b62',
borderColor: 'var(--colorSettings)',
color: 'var(--colorSettings)',
}}
icon={<PlusOutlined />}
onClick={handleClick}

View File

@ -0,0 +1,12 @@
import { ThemeConfig, defaultTheme } from '@nocobase/client';
// 兼容旧主题
function compatOldTheme(theme: ThemeConfig) {
if (!theme.token.colorSettings) {
theme.token = { ...theme.token, ...defaultTheme.token };
}
return theme;
}
export default compatOldTheme;

View File

@ -1,4 +1,4 @@
import type { ThemeConfig as Config } from 'antd';
import type { ThemeConfig as Config } from '@nocobase/client';
import type { ReactElement } from 'react';
export type ThemeConfig = Config & { name?: string };