feat(core): add color picker ui to editor settings (#8053)

close AF-1306 AF-1280

![CleanShot 2024-09-03 at 00 23 10@2x](https://github.com/user-attachments/assets/46928c85-45ec-43b1-bbde-24beb7c5c580)
This commit is contained in:
JimmFly
2024-09-03 03:14:49 +00:00
parent 02f0d7aa08
commit bea3d42f40
9 changed files with 330 additions and 169 deletions

View File

@@ -1,5 +1,4 @@
import { import {
Menu,
MenuItem, MenuItem,
MenuTrigger, MenuTrigger,
RadioGroup, RadioGroup,
@@ -18,7 +17,9 @@ import {
import { useFramework, useLiveData } from '@toeverything/infra'; import { useFramework, useLiveData } from '@toeverything/infra';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { DropdownMenu } from '../menu';
import { menuTrigger, settingWrapper } from '../style.css'; import { menuTrigger, settingWrapper } from '../style.css';
import { Point } from './point';
import { EdgelessSnapshot } from './snapshot'; import { EdgelessSnapshot } from './snapshot';
enum ConnecterStyle { enum ConnecterStyle {
@@ -130,6 +131,11 @@ export const ConnectorSettings = () => {
[editorSetting] [editorSetting]
); );
const currentColor = useMemo(() => {
const color = settings.connector.stroke;
return Object.entries(LineColor).find(([, value]) => value === color);
}, [settings]);
const colorItems = useMemo(() => { const colorItems = useMemo(() => {
const { stroke } = settings.connector; const { stroke } = settings.connector;
return Object.entries(LineColor).map(([name, value]) => { return Object.entries(LineColor).map(([name, value]) => {
@@ -138,7 +144,12 @@ export const ConnectorSettings = () => {
}; };
const isSelected = stroke === value; const isSelected = stroke === value;
return ( return (
<MenuItem key={name} onSelect={handler} selected={isSelected}> <MenuItem
key={name}
onSelect={handler}
selected={isSelected}
prefix={<Point color={value} />}
>
{name} {name}
</MenuItem> </MenuItem>
); );
@@ -188,11 +199,19 @@ export const ConnectorSettings = () => {
]()} ]()}
desc={''} desc={''}
> >
<Menu items={colorItems}> {currentColor ? (
<MenuTrigger className={menuTrigger}> <DropdownMenu
{String(settings.connector.stroke)} items={colorItems}
</MenuTrigger> trigger={
</Menu> <MenuTrigger
className={menuTrigger}
prefix={<Point color={currentColor[1]} />}
>
{currentColor[0]}
</MenuTrigger>
}
/>
) : null}
</SettingRow> </SettingRow>
<SettingRow <SettingRow
name={t['com.affine.settings.editorSettings.edgeless.style']()} name={t['com.affine.settings.editorSettings.edgeless.style']()}
@@ -255,11 +274,14 @@ export const ConnectorSettings = () => {
]()} ]()}
desc={''} desc={''}
> >
<Menu items={startEndPointItems}> <DropdownMenu
<MenuTrigger className={menuTrigger}> items={startEndPointItems}
{String(settings.connector.frontEndpointStyle)} trigger={
</MenuTrigger> <MenuTrigger className={menuTrigger}>
</Menu> {String(settings.connector.frontEndpointStyle)}
</MenuTrigger>
}
/>
</SettingRow> </SettingRow>
<SettingRow <SettingRow
name={t[ name={t[
@@ -267,11 +289,14 @@ export const ConnectorSettings = () => {
]()} ]()}
desc={''} desc={''}
> >
<Menu items={endEndPointItems}> <DropdownMenu
<MenuTrigger className={menuTrigger}> items={endEndPointItems}
{String(settings.connector.rearEndpointStyle)} trigger={
</MenuTrigger> <MenuTrigger className={menuTrigger}>
</Menu> {String(settings.connector.rearEndpointStyle)}
</MenuTrigger>
}
/>
</SettingRow> </SettingRow>
</> </>
); );

View File

@@ -1,5 +1,4 @@
import { import {
Menu,
MenuItem, MenuItem,
MenuTrigger, MenuTrigger,
RadioGroup, RadioGroup,
@@ -9,6 +8,7 @@ import { SettingRow } from '@affine/component/setting-components';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import { DropdownMenu } from '../menu';
import { menuTrigger, settingWrapper } from '../style.css'; import { menuTrigger, settingWrapper } from '../style.css';
import { EdgelessSnapshot } from './snapshot'; import { EdgelessSnapshot } from './snapshot';
@@ -54,11 +54,14 @@ export const MindMapSettings = () => {
name={t['com.affine.settings.editorSettings.edgeless.style']()} name={t['com.affine.settings.editorSettings.edgeless.style']()}
desc={''} desc={''}
> >
<Menu items={<MenuItem>Style 1</MenuItem>}> <DropdownMenu
<MenuTrigger className={menuTrigger} disabled> items={<MenuItem>Style 1</MenuItem>}
Style 1 trigger={
</MenuTrigger> <MenuTrigger className={menuTrigger} disabled>
</Menu> Style 1
</MenuTrigger>
}
/>
</SettingRow> </SettingRow>
<SettingRow <SettingRow
name={t[ name={t[

View File

@@ -1,5 +1,4 @@
import { import {
Menu,
MenuItem, MenuItem,
MenuTrigger, MenuTrigger,
RadioGroup, RadioGroup,
@@ -17,7 +16,9 @@ import {
import { useFramework, useLiveData } from '@toeverything/infra'; import { useFramework, useLiveData } from '@toeverything/infra';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { DropdownMenu } from '../menu';
import { menuTrigger, settingWrapper } from '../style.css'; import { menuTrigger, settingWrapper } from '../style.css';
import { Point } from './point';
import { EdgelessSnapshot } from './snapshot'; import { EdgelessSnapshot } from './snapshot';
const CORNER_SIZE = [ const CORNER_SIZE = [
@@ -91,7 +92,12 @@ export const NoteSettings = () => {
}; };
const isSelected = background === value; const isSelected = background === value;
return ( return (
<MenuItem key={name} onSelect={handler} selected={isSelected}> <MenuItem
key={name}
onSelect={handler}
selected={isSelected}
prefix={<Point color={value} />}
>
{name} {name}
</MenuItem> </MenuItem>
); );
@@ -140,6 +146,13 @@ export const NoteSettings = () => {
}); });
}, [editorSetting, settings]); }, [editorSetting, settings]);
const currentColor = useMemo(() => {
const { background } = settings['affine:note'];
return Object.entries(NoteBackgroundColor).find(
([, value]) => value === background
);
}, [settings]);
return ( return (
<> <>
<EdgelessSnapshot <EdgelessSnapshot
@@ -153,31 +166,45 @@ export const NoteSettings = () => {
]()} ]()}
desc={''} desc={''}
> >
<Menu items={backgroundItems}> {currentColor ? (
<MenuTrigger className={menuTrigger}> <DropdownMenu
{String(settings['affine:note'].background)} items={backgroundItems}
</MenuTrigger> trigger={
</Menu> <MenuTrigger
className={menuTrigger}
prefix={<Point color={currentColor[1]} />}
>
{currentColor[0]}
</MenuTrigger>
}
/>
) : null}
</SettingRow> </SettingRow>
<SettingRow <SettingRow
name={t['com.affine.settings.editorSettings.edgeless.note.corners']()} name={t['com.affine.settings.editorSettings.edgeless.note.corners']()}
desc={''} desc={''}
> >
<Menu items={cornerItems}> <DropdownMenu
<MenuTrigger className={menuTrigger}> items={cornerItems}
{String(settings['affine:note'].edgeless.style.borderRadius)} trigger={
</MenuTrigger> <MenuTrigger className={menuTrigger}>
</Menu> {String(settings['affine:note'].edgeless.style.borderRadius)}
</MenuTrigger>
}
/>
</SettingRow> </SettingRow>
<SettingRow <SettingRow
name={t['com.affine.settings.editorSettings.edgeless.note.shadow']()} name={t['com.affine.settings.editorSettings.edgeless.note.shadow']()}
desc={''} desc={''}
> >
<Menu items={shadowItems}> <DropdownMenu
<MenuTrigger className={menuTrigger}> items={shadowItems}
{String(settings['affine:note'].edgeless.style.shadowType)} trigger={
</MenuTrigger> <MenuTrigger className={menuTrigger}>
</Menu> {String(settings['affine:note'].edgeless.style.shadowType)}
</MenuTrigger>
}
/>
</SettingRow> </SettingRow>
<SettingRow <SettingRow
name={t['com.affine.settings.editorSettings.edgeless.note.border']()} name={t['com.affine.settings.editorSettings.edgeless.note.border']()}

View File

@@ -1,4 +1,4 @@
import { Menu, MenuItem, MenuTrigger, Slider } from '@affine/component'; import { MenuItem, MenuTrigger, Slider } from '@affine/component';
import { SettingRow } from '@affine/component/setting-components'; import { SettingRow } from '@affine/component/setting-components';
import { EditorSettingService } from '@affine/core/modules/editor-settting'; import { EditorSettingService } from '@affine/core/modules/editor-settting';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
@@ -6,6 +6,7 @@ import { LineColor } from '@blocksuite/blocks';
import { useFramework, useLiveData } from '@toeverything/infra'; import { useFramework, useLiveData } from '@toeverything/infra';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { DropdownMenu } from '../menu';
import { menuTrigger } from '../style.css'; import { menuTrigger } from '../style.css';
import { EdgelessSnapshot } from './snapshot'; import { EdgelessSnapshot } from './snapshot';
@@ -39,7 +40,6 @@ export const PenSettings = () => {
}, },
[editorSetting] [editorSetting]
); );
return ( return (
<> <>
<EdgelessSnapshot <EdgelessSnapshot
@@ -51,11 +51,14 @@ export const PenSettings = () => {
name={t['com.affine.settings.editorSettings.edgeless.pen.color']()} name={t['com.affine.settings.editorSettings.edgeless.pen.color']()}
desc={''} desc={''}
> >
<Menu items={colorItems}> <DropdownMenu
<MenuTrigger className={menuTrigger}> items={colorItems}
{String(settings.brush.color)} trigger={
</MenuTrigger> <MenuTrigger className={menuTrigger}>
</Menu> {String(settings.brush.color)}
</MenuTrigger>
}
/>
</SettingRow> </SettingRow>
<SettingRow <SettingRow
name={t['com.affine.settings.editorSettings.edgeless.pen.thickness']()} name={t['com.affine.settings.editorSettings.edgeless.pen.thickness']()}

View File

@@ -0,0 +1,21 @@
import { cssVarV2 } from '@toeverything/theme/v2';
export const Point = ({
color,
size = 8,
}: {
color: string;
size?: number;
}) => {
return (
<div
style={{
width: size,
height: size,
borderRadius: '50%',
backgroundColor: `var(${color})`,
border: `1px solid ${cssVarV2('layer/insideBorder/border')}`,
}}
></div>
);
};

View File

@@ -1,5 +1,4 @@
import { import {
Menu,
MenuItem, MenuItem,
MenuTrigger, MenuTrigger,
RadioGroup, RadioGroup,
@@ -10,6 +9,7 @@ import { SettingRow } from '@affine/component/setting-components';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import { DropdownMenu } from '../menu';
import { menuTrigger, settingWrapper, shapeIndicator } from '../style.css'; import { menuTrigger, settingWrapper, shapeIndicator } from '../style.css';
import { EdgelessSnapshot } from './snapshot'; import { EdgelessSnapshot } from './snapshot';
@@ -164,11 +164,14 @@ export const ShapeSettings = () => {
]()} ]()}
desc={''} desc={''}
> >
<Menu items={<MenuItem>Yellow</MenuItem>}> <DropdownMenu
<MenuTrigger className={menuTrigger} disabled> items={<MenuItem>Yellow</MenuItem>}
Yellow trigger={
</MenuTrigger> <MenuTrigger className={menuTrigger} disabled>
</Menu> Yellow
</MenuTrigger>
}
/>
</SettingRow> </SettingRow>
<SettingRow <SettingRow
name={t[ name={t[
@@ -176,11 +179,14 @@ export const ShapeSettings = () => {
]()} ]()}
desc={''} desc={''}
> >
<Menu items={<MenuItem>Yellow</MenuItem>}> <DropdownMenu
<MenuTrigger className={menuTrigger} disabled> items={<MenuItem>Yellow</MenuItem>}
Yellow trigger={
</MenuTrigger> <MenuTrigger className={menuTrigger} disabled>
</Menu> Yellow
</MenuTrigger>
}
/>
</SettingRow> </SettingRow>
<SettingRow <SettingRow
name={t[ name={t[
@@ -217,21 +223,28 @@ export const ShapeSettings = () => {
]()} ]()}
desc={''} desc={''}
> >
<Menu items={<MenuItem>Yellow</MenuItem>}> <DropdownMenu
<MenuTrigger className={menuTrigger} disabled> items={<MenuItem>Yellow</MenuItem>}
Yellow trigger={
</MenuTrigger> <MenuTrigger className={menuTrigger} disabled>
</Menu> Yellow
</MenuTrigger>
}
/>
</SettingRow> </SettingRow>
<SettingRow <SettingRow
name={t['com.affine.settings.editorSettings.edgeless.shape.font']()} name={t['com.affine.settings.editorSettings.edgeless.shape.font']()}
desc={''} desc={''}
> >
<Menu items={<MenuItem>Inter</MenuItem>}> {' '}
<MenuTrigger className={menuTrigger} disabled> <DropdownMenu
Inter items={<MenuItem>Inter</MenuItem>}
</MenuTrigger> trigger={
</Menu> <MenuTrigger className={menuTrigger} disabled>
Inter
</MenuTrigger>
}
/>
</SettingRow> </SettingRow>
<SettingRow <SettingRow
name={t[ name={t[
@@ -239,11 +252,14 @@ export const ShapeSettings = () => {
]()} ]()}
desc={''} desc={''}
> >
<Menu items={<MenuItem>15px</MenuItem>}> <DropdownMenu
<MenuTrigger className={menuTrigger} disabled> items={<MenuItem>15px</MenuItem>}
15px trigger={
</MenuTrigger> <MenuTrigger className={menuTrigger} disabled>
</Menu> 15px
</MenuTrigger>
}
/>
</SettingRow> </SettingRow>
<SettingRow <SettingRow
name={t[ name={t[
@@ -251,11 +267,14 @@ export const ShapeSettings = () => {
]()} ]()}
desc={''} desc={''}
> >
<Menu items={<MenuItem>Regular</MenuItem>}> <DropdownMenu
<MenuTrigger className={menuTrigger} disabled> items={<MenuItem>Regular</MenuItem>}
Regular trigger={
</MenuTrigger> <MenuTrigger className={menuTrigger} disabled>
</Menu> Regular
</MenuTrigger>
}
/>
</SettingRow> </SettingRow>
<SettingRow <SettingRow
name={t[ name={t[

View File

@@ -1,5 +1,4 @@
import { import {
Menu,
MenuItem, MenuItem,
MenuTrigger, MenuTrigger,
RadioGroup, RadioGroup,
@@ -19,7 +18,9 @@ import {
import { useFramework, useLiveData } from '@toeverything/infra'; import { useFramework, useLiveData } from '@toeverything/infra';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { DropdownMenu } from '../menu';
import { menuTrigger, settingWrapper } from '../style.css'; import { menuTrigger, settingWrapper } from '../style.css';
import { Point } from './point';
import { EdgelessSnapshot } from './snapshot'; import { EdgelessSnapshot } from './snapshot';
export const TextSettings = () => { export const TextSettings = () => {
@@ -73,7 +74,12 @@ export const TextSettings = () => {
}; };
const isSelected = color === value; const isSelected = color === value;
return ( return (
<MenuItem key={name} onSelect={handler} selected={isSelected}> <MenuItem
key={name}
onSelect={handler}
selected={isSelected}
prefix={<Point color={value} />}
>
{name} {name}
</MenuItem> </MenuItem>
); );
@@ -125,6 +131,10 @@ export const TextSettings = () => {
}); });
}, [editorSetting, settings]); }, [editorSetting, settings]);
const currentColor = useMemo(() => {
const { color } = settings['affine:edgeless-text'];
return Object.entries(LineColor).find(([, value]) => value === color);
}, [settings]);
return ( return (
<> <>
<EdgelessSnapshot <EdgelessSnapshot
@@ -136,11 +146,19 @@ export const TextSettings = () => {
name={t['com.affine.settings.editorSettings.edgeless.text.color']()} name={t['com.affine.settings.editorSettings.edgeless.text.color']()}
desc={''} desc={''}
> >
<Menu items={colorItems}> {currentColor ? (
<MenuTrigger className={menuTrigger}> <DropdownMenu
{String(settings['affine:edgeless-text'].color)} items={colorItems}
</MenuTrigger> trigger={
</Menu> <MenuTrigger
className={menuTrigger}
prefix={<Point color={currentColor[1]} />}
>
{currentColor[0]}
</MenuTrigger>
}
/>
) : null}
</SettingRow> </SettingRow>
<SettingRow <SettingRow
name={t[ name={t[
@@ -148,11 +166,14 @@ export const TextSettings = () => {
]()} ]()}
desc={''} desc={''}
> >
<Menu items={fontFamilyItems}> <DropdownMenu
<MenuTrigger className={menuTrigger}> items={fontFamilyItems}
{FontFamilyMap[settings['affine:edgeless-text'].fontFamily]} trigger={
</MenuTrigger> <MenuTrigger className={menuTrigger}>
</Menu> {FontFamilyMap[settings['affine:edgeless-text'].fontFamily]}
</MenuTrigger>
}
/>
</SettingRow> </SettingRow>
<SettingRow <SettingRow
name={t[ name={t[
@@ -160,11 +181,14 @@ export const TextSettings = () => {
]()} ]()}
desc={''} desc={''}
> >
<Menu items={fontStyleItems}> <DropdownMenu
<MenuTrigger className={menuTrigger}> items={fontStyleItems}
{String(settings['affine:edgeless-text'].fontStyle)} trigger={
</MenuTrigger> <MenuTrigger className={menuTrigger}>
</Menu> {String(settings['affine:edgeless-text'].fontStyle)}
</MenuTrigger>
}
/>
</SettingRow> </SettingRow>
<SettingRow <SettingRow
name={t[ name={t[
@@ -172,11 +196,14 @@ export const TextSettings = () => {
]()} ]()}
desc={''} desc={''}
> >
<Menu items={fontWeightItems}> <DropdownMenu
<MenuTrigger className={menuTrigger}> items={fontWeightItems}
{settings['affine:edgeless-text'].fontWeight} trigger={
</MenuTrigger> <MenuTrigger className={menuTrigger}>
</Menu> {settings['affine:edgeless-text'].fontWeight}
</MenuTrigger>
}
/>
</SettingRow> </SettingRow>
<SettingRow <SettingRow
name={t['com.affine.settings.editorSettings.edgeless.text.alignment']()} name={t['com.affine.settings.editorSettings.edgeless.text.alignment']()}

View File

@@ -38,6 +38,7 @@ import {
} from 'react'; } from 'react';
import { Virtuoso } from 'react-virtuoso'; import { Virtuoso } from 'react-virtuoso';
import { DropdownMenu } from './menu';
import * as styles from './style.css'; import * as styles from './style.css';
const FontFamilySettings = () => { const FontFamilySettings = () => {
@@ -94,13 +95,18 @@ const FontFamilySettings = () => {
); );
return ( return (
<RadioGroup <SettingRow
items={radioItems} name={t['com.affine.appearanceSettings.font.title']()}
value={settings.fontFamily} desc={t['com.affine.appearanceSettings.font.description']()}
width={250} >
className={styles.settingWrapper} <RadioGroup
onChange={handleFontFamilyChange} items={radioItems}
/> value={settings.fontFamily}
width={250}
className={styles.settingWrapper}
onChange={handleFontFamilyChange}
/>
</SettingRow>
); );
}; };
@@ -303,13 +309,71 @@ const NewDocDefaultModeSettings = () => {
[editorSettingService.editorSetting] [editorSettingService.editorSetting]
); );
return ( return (
<RadioGroup <SettingRow
items={radioItems} name={t[
value={settings.newDocDefaultMode} 'com.affine.settings.editorSettings.general.default-new-doc.title'
width={250} ]()}
className={styles.settingWrapper} desc={t[
onChange={updateNewDocDefaultMode} 'com.affine.settings.editorSettings.general.default-new-doc.description'
/> ]()}
>
<RadioGroup
items={radioItems}
value={settings.newDocDefaultMode}
width={250}
className={styles.settingWrapper}
onChange={updateNewDocDefaultMode}
/>
</SettingRow>
);
};
export const DeFaultCodeBlockSettings = () => {
const t = useI18n();
return (
<>
<SettingRow
name={t[
'com.affine.settings.editorSettings.general.default-code-block.language.title'
]()}
desc={t[
'com.affine.settings.editorSettings.general.default-code-block.language.description'
]()}
>
<DropdownMenu
items={<MenuItem>Plain Text</MenuItem>}
trigger={
<MenuTrigger className={styles.menuTrigger} disabled>
Plain Text
</MenuTrigger>
}
/>
</SettingRow>
<SettingRow
name={t[
'com.affine.settings.editorSettings.general.default-code-block.wrap.title'
]()}
desc={t[
'com.affine.settings.editorSettings.general.default-code-block.wrap.description'
]()}
>
<Switch />
</SettingRow>
</>
);
};
export const SpellCheckSettings = () => {
const t = useI18n();
return (
<SettingRow
name={t['com.affine.settings.editorSettings.general.spell-check.title']()}
desc={t[
'com.affine.settings.editorSettings.general.spell-check.description'
]()}
>
<Switch />
</SettingRow>
); );
}; };
@@ -323,62 +387,11 @@ export const General = () => {
> >
<Switch /> <Switch />
</SettingRow> </SettingRow>
<SettingRow <FontFamilySettings />
name={t['com.affine.appearanceSettings.font.title']()}
desc={t['com.affine.appearanceSettings.font.description']()}
>
<FontFamilySettings />
</SettingRow>
<CustomFontFamilySettings /> <CustomFontFamilySettings />
<SettingRow <NewDocDefaultModeSettings />
name={t[ <DeFaultCodeBlockSettings />
'com.affine.settings.editorSettings.general.default-new-doc.title' <SpellCheckSettings />
]()}
desc={t[
'com.affine.settings.editorSettings.general.default-new-doc.description'
]()}
>
<NewDocDefaultModeSettings />
</SettingRow>
<SettingRow
name={t[
'com.affine.settings.editorSettings.general.default-code-block.language.title'
]()}
desc={t[
'com.affine.settings.editorSettings.general.default-code-block.language.description'
]()}
>
<Menu
contentOptions={{
className: styles.menu,
}}
items={<MenuItem>Plain Text</MenuItem>}
>
<MenuTrigger className={styles.menuTrigger} disabled>
Plain Text
</MenuTrigger>
</Menu>
</SettingRow>
<SettingRow
name={t[
'com.affine.settings.editorSettings.general.default-code-block.wrap.title'
]()}
desc={t[
'com.affine.settings.editorSettings.general.default-code-block.wrap.description'
]()}
>
<Switch />
</SettingRow>
<SettingRow
name={t[
'com.affine.settings.editorSettings.general.spell-check.title'
]()}
desc={t[
'com.affine.settings.editorSettings.general.spell-check.description'
]()}
>
<Switch />
</SettingRow>
</SettingWrapper> </SettingWrapper>
); );
}; };

View File

@@ -0,0 +1,23 @@
import { Menu } from '@affine/component';
import { type ReactNode } from 'react';
export const DropdownMenu = ({
items,
trigger,
}: {
items: ReactNode;
trigger: ReactNode;
}) => {
return (
<Menu
items={items}
contentOptions={{
style: {
width: '250px',
},
}}
>
{trigger}
</Menu>
);
};