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('--colorBgScrollBarHover', colorBgScrollBarHover);
document.body.style.setProperty('--colorBgScrollBarActive', colorBgScrollBarActive); document.body.style.setProperty('--colorBgScrollBarActive', colorBgScrollBarActive);
document.body.style.setProperty('--colorBgDrawer', colorBgDrawer); 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); document.body.style.setProperty('background-color', token.colorBgContainer);
@ -65,6 +68,9 @@ const CSSVariableProvider = ({ children }) => {
token.colorPrimaryText, token.colorPrimaryText,
token.colorPrimaryTextActive, token.colorPrimaryTextActive,
token.colorPrimaryTextHover, token.colorPrimaryTextHover,
token.colorSettings,
token.colorBgSettingsHover,
token.colorBorderSettingsHover,
]); ]);
return children; 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 _ from 'lodash';
import React, { createContext, useCallback, useMemo, useRef } from 'react'; import React, { createContext, useCallback, useMemo, useRef } from 'react';
import defaultTheme from './defaultTheme';
type ThemeConfig = Config & { name?: string; algorithm?: string | Config['algorithm'] }; import { ThemeConfig } from './type';
interface ThemeItem { interface ThemeItem {
id: number; id: number;
@ -27,8 +27,8 @@ export const useGlobalTheme = () => {
return React.useContext(GlobalThemeContext); return React.useContext(GlobalThemeContext);
}; };
export const GlobalThemeProvider = ({ children, theme: defaultTheme }) => { export const GlobalThemeProvider = ({ children, theme: themeFromProps }) => {
const [theme, setTheme] = React.useState<ThemeConfig>(defaultTheme || { name: 'Custom theme' }); const [theme, setTheme] = React.useState<ThemeConfig>(themeFromProps || defaultTheme);
const currentSettingThemeRef = useRef<ThemeConfig>(null); const currentSettingThemeRef = useRef<ThemeConfig>(null);
const currentEditingThemeRef = useRef<ThemeItem>(null); const currentEditingThemeRef = useRef<ThemeItem>(null);
@ -83,3 +83,6 @@ export const GlobalThemeProvider = ({ children, theme: defaultTheme }) => {
}; };
export { default as AntdAppProvider } from './AntdAppProvider'; 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, useDocumentTitle,
useRequest, useRequest,
useSystemSettings, useSystemSettings,
useToken,
} from '../../../'; } from '../../../';
import { Plugin } from '../../../application/Plugin'; import { Plugin } from '../../../application/Plugin';
import { useCollectionManager } from '../../../collection-manager'; import { useCollectionManager } from '../../../collection-manager';
@ -153,6 +154,7 @@ export const InternalAdminLayout = (props: any) => {
const result = useSystemSettings(); const result = useSystemSettings();
const { service } = useCollectionManager(); const { service } = useCollectionManager();
const params = useParams<any>(); const params = useParams<any>();
const { token } = useToken();
return ( return (
<Layout> <Layout>
@ -161,10 +163,12 @@ export const InternalAdminLayout = (props: any) => {
.ant-menu.ant-menu-dark .ant-menu-item-selected, .ant-menu.ant-menu-dark .ant-menu-item-selected,
.ant-menu-submenu-popup.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 { .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 { .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; position: fixed;
@ -174,6 +178,15 @@ export const InternalAdminLayout = (props: any) => {
line-height: var(--nb-header-height); line-height: var(--nb-header-height);
padding: 0; padding: 0;
z-index: 100; z-index: 100;
background-color: ${token.colorBgHeader};
.ant-menu {
background-color: transparent;
}
.ant-menu-item {
color: ${token.colorTextHeaderMenu};
}
`} `}
> >
<div <div

View File

@ -1,3 +1,15 @@
import { theme } from 'antd'; 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 }; export { useToken };

View File

@ -2,6 +2,7 @@ import type { CSSInterpolation, CSSObject } from '@ant-design/cssinjs';
import { useStyleRegister } from '@ant-design/cssinjs'; import { useStyleRegister } from '@ant-design/cssinjs';
import { merge } from '@formily/shared'; import { merge } from '@formily/shared';
import type { ComponentTokenMap, GlobalToken } from 'antd/es/theme/interface'; import type { ComponentTokenMap, GlobalToken } from 'antd/es/theme/interface';
import { CustomToken } from '../../../global-theme';
import { useConfig, usePrefixCls, useToken } from './hooks'; import { useConfig, usePrefixCls, useToken } from './hooks';
export type OverrideComponent = keyof ComponentTokenMap | string; export type OverrideComponent = keyof ComponentTokenMap | string;
@ -62,7 +63,7 @@ export type UseComponentStyleResult = {
export const genStyleHook = <ComponentName extends OverrideComponent>( export const genStyleHook = <ComponentName extends OverrideComponent>(
component: ComponentName, component: ComponentName,
styleFn: (token: TokenWithCommonCls<GlobalToken>, props: any, info: StyleInfo) => CSSInterpolation, styleFn: (token: TokenWithCommonCls<CustomToken>, props: any, info: StyleInfo) => CSSInterpolation,
) => { ) => {
return (props?: any): UseComponentStyleResult => { return (props?: any): UseComponentStyleResult => {
const { theme, token, hashId } = useToken(); const { theme, token, hashId } = useToken();
@ -80,7 +81,7 @@ export const genStyleHook = <ComponentName extends OverrideComponent>(
}, },
() => { () => {
const componentCls = `.${prefixCls}`; const componentCls = `.${prefixCls}`;
const mergedToken: TokenWithCommonCls<GlobalToken> = merge(token, { const mergedToken: TokenWithCommonCls<CustomToken> = merge(token, {
componentCls, componentCls,
prefixCls, prefixCls,
iconCls: `.${iconPrefixCls}`, 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 { observer, RecursionField, useField, useFieldSchema, useForm } from '@formily/react';
import { App, Button, Popover } from 'antd'; import { App, Button, Popover } from 'antd';
import classnames from 'classnames'; import classnames from 'classnames';
@ -17,56 +16,13 @@ import { ActionDrawer } from './Action.Drawer';
import { ActionLink } from './Action.Link'; import { ActionLink } from './Action.Link';
import { ActionModal } from './Action.Modal'; import { ActionModal } from './Action.Modal';
import { ActionPage } from './Action.Page'; import { ActionPage } from './Action.Page';
import useStyles from './Action.style';
import { ActionContextProvider } from './context'; import { ActionContextProvider } from './context';
import { useA } from './hooks'; import { useA } from './hooks';
import { ComposedAction } from './types'; import { ComposedAction } from './types';
import { linkageAction } from './utils'; import { linkageAction } from './utils';
import { lodash } from '@nocobase/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( export const Action: ComposedAction = observer(
(props: any) => { (props: any) => {
const { const {
@ -81,6 +37,7 @@ export const Action: ComposedAction = observer(
title, title,
...others ...others
} = props; } = props;
const { wrapSSR, componentCls, hashId } = useStyles();
const { t } = useTranslation(); const { t } = useTranslation();
const { onClick } = useProps(props); const { onClick } = useProps(props);
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
@ -148,7 +105,7 @@ export const Action: ComposedAction = observer(
} }
}} }}
component={tarComponent || Button} component={tarComponent || Button}
className={classnames(actionDesignerCss, className)} className={classnames('renderButton', className)}
type={props.type === 'danger' ? undefined : props.type} type={props.type === 'danger' ? undefined : props.type}
> >
{actionTitle} {actionTitle}
@ -157,23 +114,25 @@ export const Action: ComposedAction = observer(
); );
}; };
return ( return wrapSSR(
<ActionContextProvider <div className={`${componentCls} ${hashId}`}>
button={renderButton()} <ActionContextProvider
visible={visible} button={renderButton()}
setVisible={setVisible} visible={visible}
formValueChanged={formValueChanged} setVisible={setVisible}
setFormValueChanged={setFormValueChanged} formValueChanged={formValueChanged}
openMode={openMode} setFormValueChanged={setFormValueChanged}
openSize={openSize} openMode={openMode}
containerRefKey={containerRefKey} openSize={openSize}
fieldSchema={fieldSchema} containerRefKey={containerRefKey}
> fieldSchema={fieldSchema}
{popover && <RecursionField basePath={field.address} onlyRenderProperties schema={fieldSchema} />} >
{!popover && renderButton()} {popover && <RecursionField basePath={field.address} onlyRenderProperties schema={fieldSchema} />}
{!popover && <div onClick={(e) => e.stopPropagation()}>{props.children}</div>} {!popover && renderButton()}
{element} {!popover && <div onClick={(e) => e.stopPropagation()}>{props.children}</div>}
</ActionContextProvider> {element}
</ActionContextProvider>
</div>,
); );
}, },
{ displayName: 'Action' }, { displayName: 'Action' },
@ -201,17 +160,7 @@ Action.Popover = observer(
Action.Popover.Footer = observer( Action.Popover.Footer = observer(
(props) => { (props) => {
return ( return <div className="popover">{props.children}</div>;
<div
className={css`
display: flex;
justify-content: flex-end;
width: 100%;
`}
>
{props.children}
</div>
);
}, },
{ displayName: 'Action.Popover.Footer' }, { displayName: 'Action.Popover.Footer' },
); );

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,5 @@
import { DeleteOutlined, MenuOutlined } from '@ant-design/icons'; import { DeleteOutlined, MenuOutlined } from '@ant-design/icons';
import { TinyColor } from '@ctrl/tinycolor';
import { SortableContext, useSortable } from '@dnd-kit/sortable'; import { SortableContext, useSortable } from '@dnd-kit/sortable';
import { css } from '@emotion/css'; import { css } from '@emotion/css';
import { ArrayField, Field } from '@formily/core'; import { ArrayField, Field } from '@formily/core';
@ -113,27 +114,33 @@ const useTableColumns = (props) => {
return tableColumns; 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 SortableRow = (props) => {
const { token } = useToken();
const id = props['data-row-key']?.toString(); const id = props['data-row-key']?.toString();
const { setNodeRef, isOver, active, over } = useSortable({ const { setNodeRef, isOver, active, over } = useSortable({
id, 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 = const className =
(active?.data.current?.sortable.index ?? -1) > (over?.data.current?.sortable?.index ?? -1) (active?.data.current?.sortable.index ?? -1) > (over?.data.current?.sortable?.index ?? -1)
? topActiveClass ? classObj.topActiveClass
: bottomActiveClass; : classObj.bottomActiveClass;
return ( return (
<tr <tr

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,3 +1,15 @@
import { theme } from 'antd'; 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': 'ArrayItems.Addition',
'x-component-props': { 'x-component-props': {
className: css` className: css`
border-color: rgb(241, 139, 98); border-color: var(--colorSettings);
color: rgb(241, 139, 98); color: var(--colorSettings);
&.ant-btn-dashed:hover { &.ant-btn-dashed:hover {
border-color: rgb(241, 139, 98); border-color: var(--colorSettings);
color: rgb(241, 139, 98); color: var(--colorSettings);
} }
`, `,
}, },

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -16,6 +16,7 @@
}, },
"devDependencies": { "devDependencies": {
"@nocobase/server": "0.11.0-alpha.1", "@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[]) => { const handleResetTheme = (path: string[]) => {
let newConfig = { ...theme.config }; let newConfig = { ...theme.config };
newConfig = deepUpdateObj(newConfig, path, getValueByPath(themeRef.current?.config, path)); 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); handleSetTheme({ ...theme, config: newConfig }, path);
}; };

View File

@ -2,7 +2,7 @@ import type { AliasToken } from '../interface';
import type { TokenTree } from './interface'; import type { TokenTree } from './interface';
import { seedRelatedAlias, seedRelatedMap } from './TokenRelation'; import { seedRelatedAlias, seedRelatedMap } from './TokenRelation';
const category: TokenTree<keyof AliasToken> = [ const category: TokenTree<keyof AliasToken | string> = [
{ {
name: '颜色', name: '颜色',
nameEn: 'Color', nameEn: 'Color',
@ -15,7 +15,8 @@ const category: TokenTree<keyof AliasToken> = [
name: '品牌色', name: '品牌色',
nameEn: 'Brand Color', nameEn: 'Brand Color',
desc: '品牌色是体现产品特性和传播理念最直观的视觉元素之一。在你完成品牌主色的选取之后,我们会自动帮你生成一套完整的色板,并赋予它们有效的设计语义。', 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'], seedToken: ['colorPrimary'],
mapToken: seedRelatedMap.colorPrimary, mapToken: seedRelatedMap.colorPrimary,
aliasToken: seedRelatedAlias.colorPrimary, aliasToken: seedRelatedAlias.colorPrimary,
@ -25,8 +26,9 @@ const category: TokenTree<keyof AliasToken> = [
type: 'Color', type: 'Color',
name: '成功色', name: '成功色',
nameEn: 'Success Color', nameEn: 'Success Color',
desc: '', desc: '用于表示操作成功的 Token 序列,如 Result、Progress 等组件会使用该组梯度变量。',
descEn: '', descEn:
'Used to represent the token sequence of operation success, such as Result, Progress and other components will use these map tokens.',
seedToken: ['colorSuccess'], seedToken: ['colorSuccess'],
mapToken: seedRelatedMap.colorSuccess, mapToken: seedRelatedMap.colorSuccess,
aliasToken: seedRelatedAlias.colorSuccess, aliasToken: seedRelatedAlias.colorSuccess,
@ -36,8 +38,9 @@ const category: TokenTree<keyof AliasToken> = [
type: 'Color', type: 'Color',
name: '警戒色', name: '警戒色',
nameEn: 'Warning Color', nameEn: 'Warning Color',
desc: '', desc: '用于表示操作警告的 Token 序列,如 Notification、 Alert等警告类组件或 Input 输入类等组件会使用该组梯度变量。',
descEn: '', 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'], seedToken: ['colorWarning'],
mapToken: seedRelatedMap.colorWarning, mapToken: seedRelatedMap.colorWarning,
aliasToken: seedRelatedAlias.colorWarning, aliasToken: seedRelatedAlias.colorWarning,
@ -47,8 +50,9 @@ const category: TokenTree<keyof AliasToken> = [
type: 'Color', type: 'Color',
name: '错误色', name: '错误色',
nameEn: 'Error Color', nameEn: 'Error Color',
desc: '', desc: '用于表示操作失败的 Token 序列如失败按钮、错误状态提示Result组件等。',
descEn: '', descEn:
'Used to represent the visual elements of the operation failure, such as the error Button, error Result component, etc.',
seedToken: ['colorError'], seedToken: ['colorError'],
mapToken: seedRelatedMap.colorError, mapToken: seedRelatedMap.colorError,
aliasToken: seedRelatedAlias.colorError, aliasToken: seedRelatedAlias.colorError,
@ -58,8 +62,9 @@ const category: TokenTree<keyof AliasToken> = [
type: 'Color', type: 'Color',
name: '信息色', name: '信息色',
nameEn: 'Info Color', nameEn: 'Info Color',
desc: '', desc: '用于表示操作信息的 Token 序列,如 Alert 、Tag、 Progress 等组件都有用到该组梯度变量。',
descEn: '', 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'], seedToken: ['colorInfo'],
mapToken: seedRelatedMap.colorInfo, mapToken: seedRelatedMap.colorInfo,
aliasToken: seedRelatedAlias.colorInfo, aliasToken: seedRelatedAlias.colorInfo,
@ -70,7 +75,8 @@ const category: TokenTree<keyof AliasToken> = [
name: '中性色', name: '中性色',
nameEn: 'Neutral Color', nameEn: 'Neutral Color',
desc: '中性色主要被大量的应用在界面的文字、背景、边框和填充的 4 种场景。合理地选择中性色能够令页面信息具备良好的主次关系,助力阅读体验。', 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'], seedToken: ['colorTextBase', 'colorBgBase'],
mapToken: seedRelatedMap.colorTextBase?.concat(seedRelatedMap.colorBgBase ?? []), mapToken: seedRelatedMap.colorTextBase?.concat(seedRelatedMap.colorBgBase ?? []),
aliasToken: seedRelatedAlias.colorTextBase?.concat(seedRelatedAlias.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 类组件的效果。', '你可以利用 Alias Token 来精准控制部分组件的效果。例如 Input 、InputNumber、Select 等Control 类组件都共享了相同的 controlXX token 。只需修改值,即可实现不改变 Button 的情况下,修改 Control 类组件的效果。',
mapTokenGroups: ['text', 'border', 'fill', 'background'], 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 // Seed token
seedToken?: T[]; seedToken?: T[];
/** make seedToken can be alpha */
seedTokenAlpha?: boolean;
mapToken?: T[]; mapToken?: T[];
aliasToken?: 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 { MutableTheme } from 'antd-token-previewer';
import type { ThemeConfig } from 'antd/es/config-provider/context'; import type { ThemeConfig } from 'antd/es/config-provider/context';
import seed from 'antd/es/theme/themes/seed'; import seed from 'antd/es/theme/themes/seed';
import tokenMeta from 'antd/lib/version/token-meta.json';
import classNames from 'classnames'; import classNames from 'classnames';
import type { FC } from 'react'; import type { FC } from 'react';
import React, { useEffect, useMemo, useState } from 'react'; import React, { useEffect, useMemo, useState } from 'react';
@ -21,6 +20,8 @@ import makeStyle from '../utils/makeStyle';
import InputNumberPlus from './InputNumberPlus'; import InputNumberPlus from './InputNumberPlus';
import TokenDetail from './TokenDetail'; import TokenDetail from './TokenDetail';
import TokenPreview from './TokenPreview'; import TokenPreview from './TokenPreview';
import calcCustomToken from './calcCustomToken';
import tokenMeta from './token-meta.json';
const { Panel } = Collapse; const { Panel } = Collapse;
@ -230,6 +231,7 @@ export type SeedTokenProps = {
theme: MutableTheme; theme: MutableTheme;
tokenName: string; tokenName: string;
disabled?: boolean; disabled?: boolean;
alpha?: boolean;
}; };
const getSeedValue = (config: ThemeConfig, token: string) => { 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 tokenPath = ['token', tokenName];
const [tokenValue, setTokenValue] = useState(getSeedValue(theme.config, tokenName)); const [tokenValue, setTokenValue] = useState(getSeedValue(theme.config, tokenName));
const locale = useLocale(); const locale = useLocale();
@ -267,7 +269,7 @@ const SeedTokenPreview: FC<SeedTokenProps> = ({ theme, tokenName, disabled }) =>
...theme.config, ...theme.config,
token: { token: {
...theme.config.token, ...theme.config.token,
[tokenName]: newValue, ...calcCustomToken(tokenName, newValue),
}, },
}, },
['token', tokenName], ['token', tokenName],
@ -305,7 +307,7 @@ const SeedTokenPreview: FC<SeedTokenProps> = ({ theme, tokenName, disabled }) =>
trigger="click" trigger="click"
placement="bottomRight" placement="bottomRight"
overlayInnerStyle={{ padding: 0 }} overlayInnerStyle={{ padding: 0 }}
content={<ColorPanel color={tokenValue} onChange={handleChange} style={{ border: 'none' }} />} content={<ColorPanel color={tokenValue} onChange={handleChange} style={{ border: 'none' }} alpha={alpha} />}
> >
<div <div
className="token-panel-pro-token-collapse-seed-block-sample-card" className="token-panel-pro-token-collapse-seed-block-sample-card"
@ -643,6 +645,7 @@ const TokenContent: FC<ColorTokenContentProps> = ({
</div> </div>
</div> </div>
<SeedTokenPreview <SeedTokenPreview
alpha={!!group.seedTokenAlpha}
theme={theme} theme={theme}
tokenName={seedToken} tokenName={seedToken}
disabled={seedToken === 'colorInfo' && infoFollowPrimary} disabled={seedToken === 'colorInfo' && infoFollowPrimary}

View File

@ -1,6 +1,5 @@
import { Tooltip } from 'antd'; import { Tooltip } from 'antd';
import type { MutableTheme } from 'antd-token-previewer'; import type { MutableTheme } from 'antd-token-previewer';
import tokenMeta from 'antd/lib/version/token-meta.json';
import classNames from 'classnames'; import classNames from 'classnames';
import type { FC } from 'react'; import type { FC } from 'react';
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
@ -13,6 +12,7 @@ import getDesignToken from '../utils/getDesignToken';
import getValueByPath from '../utils/getValueByPath'; import getValueByPath from '../utils/getValueByPath';
import makeStyle from '../utils/makeStyle'; import makeStyle from '../utils/makeStyle';
import { getRelatedComponents } from '../utils/statistic'; import { getRelatedComponents } from '../utils/statistic';
import tokenMeta from './token-meta.json';
const useStyle = makeStyle('TokenDetail', (token) => ({ const useStyle = makeStyle('TokenDetail', (token) => ({
'.token-panel-token-detail': { '.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 { error } from '@nocobase/utils/client';
import { Spin } from 'antd'; import { Spin } from 'antd';
import React, { useEffect, useRef } from 'react'; import React, { useEffect, useRef } from 'react';
@ -50,13 +50,17 @@ const InitializeTheme: React.FC = ({ children }) => {
const theme = data?.find((item) => item.id === themeId.current); const theme = data?.find((item) => item.id === themeId.current);
if (theme) { if (theme) {
setTheme(theme.config); 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 { } else {
setTheme({}); setTheme(defaultTheme);
api.auth.setOption('theme', null); api.auth.setOption('theme', null);
} }
}, [ }, [
api.auth,
currentUser?.data?.data?.systemSettings?.themeId, currentUser?.data?.data?.systemSettings?.themeId,
data, data,
run, run,

View File

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

View File

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