From db286bb7fa833d79779233c2889ea25e6705cb32 Mon Sep 17 00:00:00 2001 From: Maksim Sitnikov Date: Fri, 14 Jun 2024 15:20:25 +0300 Subject: [PATCH 01/14] Add tooltip for RTL icon in component sandbox (#215) * Add tooltip for RTL icon in component sandbox * Some fixes * Fixes after review --------- Co-authored-by: Maksim Sitnikov --- public/locales/en/component.json | 6 +- public/locales/ru/component.json | 7 +- src/components/SandboxBlock/SandboxBlock.tsx | 85 ++++++++++++-------- 3 files changed, 62 insertions(+), 36 deletions(-) diff --git a/public/locales/en/component.json b/public/locales/en/component.json index 02c37c4bd986..eb8b0099d881 100644 --- a/public/locales/en/component.json +++ b/public/locales/en/component.json @@ -3,5 +3,9 @@ "title": "Components", "searchPlaceholder": "Search by component name", "color-input_validation-format-error": "Incorrect format", - "maintainers": "Maintainers:" + "maintainers": "Maintainers:", + "theme": "Switch theme", + "rtlOn": "Switch RTL on", + "rtlOff": "Switch RTL off", + "rtlNotSupported": "RTL not supported" } diff --git a/public/locales/ru/component.json b/public/locales/ru/component.json index d78680c31347..dc452bcf66e3 100644 --- a/public/locales/ru/component.json +++ b/public/locales/ru/component.json @@ -2,5 +2,10 @@ "actions_openInFigma": "Открыть в Figma", "title": "Компоненты", "searchPlaceholder": "Поиск по названию", - "color-input_validation-format-error": "Неверный формат" + "color-input_validation-format-error": "Неверный формат", + "maintainers": "Maintainers:", + "theme": "Переключить тему", + "rtlOn": "Включить RTL", + "rtlOff": "Выключить RTL", + "rtlNotSupported": "RTL не поддерживается" } diff --git a/src/components/SandboxBlock/SandboxBlock.tsx b/src/components/SandboxBlock/SandboxBlock.tsx index 8e7b9d4b35b5..4c8b6609d186 100644 --- a/src/components/SandboxBlock/SandboxBlock.tsx +++ b/src/components/SandboxBlock/SandboxBlock.tsx @@ -1,4 +1,9 @@ -import {ChevronsCollapseUpRight, ChevronsExpandUpRight, TextAlignRight} from '@gravity-ui/icons'; +import { + ChevronsCollapseUpRight, + ChevronsExpandUpRight, + TextAlignLeft, + TextAlignRight, +} from '@gravity-ui/icons'; import { Col, Direction, @@ -13,6 +18,7 @@ import { Theme, Tooltip, } from '@gravity-ui/uikit'; +import {useTranslation} from 'next-i18next'; import React from 'react'; import themeIcon from '../../assets/icons/theme.svg'; @@ -29,6 +35,8 @@ const SandboxBlock: React.FC = ({ sandboxConfig, isSupportRTL, }) => { + const {t} = useTranslation('component'); + const [props, setProps] = React.useState({}); const [isIframeLoaded, setIsIframeLoaded] = React.useState(false); @@ -178,6 +186,10 @@ const SandboxBlock: React.FC = ({ } }, [isIframeLoaded, props, iframeTheme, iframeDirection]); + const isRtl = iframeDirection === 'rtl'; + + const rtlIcon = isRtl ? TextAlignRight : TextAlignLeft; + return (
@@ -193,39 +205,44 @@ const SandboxBlock: React.FC = ({
-
{ - setIframeTheme(iframeTheme === 'dark' ? 'light' : 'dark'); - }} - > - -
-
{ - if (isSupportRTL) { - setIframeDirection( - iframeDirection === 'ltr' ? 'rtl' : 'ltr', - ); - } - }} - > - {isSupportRTL && ( - - )} - {!isSupportRTL && ( - - - - )} -
+ +
{ + setIframeTheme(iframeTheme === 'dark' ? 'light' : 'dark'); + }} + > + +
+
+ + {isSupportRTL && ( + +
{ + setIframeDirection(isRtl ? 'ltr' : 'rtl'); + }} + > + +
+
+ )} + {!isSupportRTL && ( + +
+ +
+
+ )}
Date: Fri, 7 Jun 2024 15:01:38 +0300 Subject: [PATCH 02/14] feat: base types and logic --- src/components/Theme/Colors/index.tsx | 14 +++ src/components/Theme/constants.ts | 56 +++++++++ src/components/Theme/types.ts | 74 ++++++++++++ src/components/Theme/utils.ts | 158 ++++++++++++++++++++++++++ src/pages/theme.tsx | 14 +++ 5 files changed, 316 insertions(+) create mode 100644 src/components/Theme/Colors/index.tsx create mode 100644 src/components/Theme/constants.ts create mode 100644 src/components/Theme/types.ts create mode 100644 src/components/Theme/utils.ts create mode 100644 src/pages/theme.tsx diff --git a/src/components/Theme/Colors/index.tsx b/src/components/Theme/Colors/index.tsx new file mode 100644 index 000000000000..6335d4eab1ae --- /dev/null +++ b/src/components/Theme/Colors/index.tsx @@ -0,0 +1,14 @@ +import {Col, Grid, Row} from '@gravity-ui/page-constructor'; +import React from 'react'; + +export const Colors = () => { + return ( + + + +

Colors

+ +
+
+ ); +}; diff --git a/src/components/Theme/constants.ts b/src/components/Theme/constants.ts new file mode 100644 index 000000000000..4aa70f4861eb --- /dev/null +++ b/src/components/Theme/constants.ts @@ -0,0 +1,56 @@ +import type {ThemeOptions} from './types'; + +export const DEFAULT_PALLETE: ThemeOptions['pallette'] = { + light: { + brand: '#eee', + orange: '#FFA629', + green: '#63E587', + yellow: '#FFBE5C', + red: '#FF6B00', + blue: '#1DA3EE', + 'cool-grey': '#808895', + purple: '#6100FF', + }, + dark: { + brand: '#aaa', + orange: 'rgba(200,100,100,.20)', + green: 'rgba(200,100,100,.20)', + yellow: 'rgba(200,100,100,.20)', + red: 'rgba(200,100,100,.20)', + blue: 'rgba(200,100,100,.20)', + 'cool-grey': 'rgba(200,100,100,.20)', + purple: 'rgba(200,100,100,.20)', + }, +}; + +export const DEFAULT_THEME: ThemeOptions = { + pallette: DEFAULT_PALLETE, + colors: { + light: { + background: 'private.blue.550', // Ссылка на токен + hoveredBrand: 'private.yellow.200', + brandText: '#63E587', // Просто кастомный цвет + hcBrandText: '', + brandLine: '', + selectionBackground: '', + hoveredSelectionBackground: '', + link: '', + hoveredLink: '', + visitedLink: '', + }, + dark: { + background: '', + hoveredBrand: '', + brandText: '', + hcBrandText: '', + brandLine: '', + selectionBackground: '', + hoveredSelectionBackground: '', + link: '', + hoveredLink: '', + visitedLink: '', + }, + }, + borders: {}, + typography: {}, +}; diff --git a/src/components/Theme/types.ts b/src/components/Theme/types.ts new file mode 100644 index 000000000000..9300f6e11538 --- /dev/null +++ b/src/components/Theme/types.ts @@ -0,0 +1,74 @@ +export type ThemeVariant = 'light' | 'dark'; + +export type PalletteOptions = { + brand: string; + [key: string]: string; +}; + +export type ColorsOptions = { + background: string; + hoveredBrand: string; + brandText: string; + hcBrandText: string; + brandLine: string; + selectionBackground: string; + hoveredSelectionBackground: string; + link: string; + hoveredLink: string; + visitedLink: string; +}; + +export type BordersOptions = {}; + +export type TypographyOptions = {}; + +export interface ThemeOptions { + /** Параметры solid-цветов, от которых рассчитываются private-цвета */ + pallette: Record; + /** Утилитарные цвета, использующиеся в компонентах (background, link, brand-text и тд) */ + colors: Record; + borders: BordersOptions; + typography: TypographyOptions; +} + +// Про 550 | solid подумать... Нужно ли хранить его тут? +const PRIVATE_COLOR_VARIABLES = [ + 1000, + 950, + 900, + 850, + 800, + 750, + 700, + 650, + 600, + 'solid', + 500, + 450, + 400, + 350, + 300, + 250, + 200, + 150, + 100, + 50, +] as const; + +type PrivateColorVariable = (typeof PRIVATE_COLOR_VARIABLES)[number]; + +export type PrivateColors = Record; + +type PalleteToken = { + /** Title that will using in UI */ + title: string; + /** Auto-generated private colors for each theme variant */ + privateColors: Record; +}; + +export type PalleteTokens = Record; + +export interface ThemeWizardState extends ThemeOptions { + /** Mapping color tokens to their information (title and private colors) */ + palletteTokens: PalleteTokens; +} diff --git a/src/components/Theme/utils.ts b/src/components/Theme/utils.ts new file mode 100644 index 000000000000..e09b65a0aea0 --- /dev/null +++ b/src/components/Theme/utils.ts @@ -0,0 +1,158 @@ +import kebabCase from 'lodash/kebabCase'; +import lowerCase from 'lodash/lowerCase'; + +import type { + PalleteTokens, + PrivateColors, + ThemeOptions, + ThemeVariant, + ThemeWizardState, +} from './types'; + +function createColorToken(title: string) { + // TODO: Может стоит добавить slugify для поддержки ru? + return kebabCase(title); +} + +function createTitleFromToken(token: string) { + return lowerCase(token); +} + +function createPrivateColors(solidColor: string): PrivateColors { + return { + 50: '', + 100: '', + 150: '', + 200: '', + 250: '', + 300: '', + 350: '', + 400: '', + 450: '', + 500: '', + solid: solidColor, + 600: '', + 650: '', + 700: '', + 750: '', + 800: '', + 850: '', + 900: '', + 950: '', + 1000: '', + }; +} + +function createPalleteTokens(pallette: ThemeOptions['pallette']): PalleteTokens { + const tokens = Object.keys(pallette.light); + + return tokens.reduce( + (acc, token) => ({ + ...acc, + [token]: { + title: createTitleFromToken(token), + privateColors: { + light: pallette.light[token] + ? createPrivateColors(pallette.light[token]) + : undefined, + dark: pallette.dark[token] + ? createPrivateColors(pallette.dark[token]) + : undefined, + }, + }, + }), + {}, + ); +} + +export function updateColor( + themeState: ThemeWizardState, + params: { + title: string; + theme: ThemeVariant; + value: string; + }, +): ThemeWizardState { + const newThemeState = {...themeState}; + const token = createColorToken(params.title); + + if (params.theme === 'light') { + if (!newThemeState.pallette.light[token]) { + newThemeState.pallette.light[token] = ''; + } + + newThemeState.pallette.light[token] = params.value; + } + + if (params.theme === 'dark') { + if (!newThemeState.pallette.dark[token]) { + newThemeState.pallette.dark[token] = ''; + } + + newThemeState.pallette.dark[token] = params.value; + } + + const privateColors = createPrivateColors(params.value); + + newThemeState.palletteTokens[token] = { + ...newThemeState.palletteTokens[token], + title: params.title, + privateColors: { + light: + params.theme === 'light' + ? privateColors + : newThemeState.palletteTokens[token].privateColors?.light, + dark: + params.theme === 'dark' + ? privateColors + : newThemeState.palletteTokens[token].privateColors?.dark, + }, + }; + + return themeState; +} + +export function addColor( + themeState: ThemeWizardState, + params: { + title: string; + colors?: Partial>; + }, +): ThemeWizardState { + const newThemeState = {...themeState}; + const token = createColorToken(params.title); + + if (!themeState.pallette.dark[token]) { + newThemeState.pallette.dark[token] = ''; + } + + if (!themeState.pallette.light[token]) { + newThemeState.pallette.light[token] = ''; + } + + if (params.colors?.dark) { + newThemeState.pallette.dark[token] = params.colors.dark; + } + + if (params.colors?.light) { + newThemeState.pallette.light[token] = params.colors.light; + } + + newThemeState.palletteTokens[token] = { + ...newThemeState.palletteTokens[token], + title: params.title, + privateColors: { + light: params.colors?.light ? createPrivateColors(params.colors.light) : undefined, + dark: params.colors?.dark ? createPrivateColors(params.colors.dark) : undefined, + }, + }; + + return themeState; +} + +export function initThemeWizard(theme: ThemeOptions): ThemeWizardState { + return { + ...theme, + palletteTokens: createPalleteTokens(theme.pallette), + }; +} diff --git a/src/pages/theme.tsx b/src/pages/theme.tsx new file mode 100644 index 000000000000..9c8c80feda75 --- /dev/null +++ b/src/pages/theme.tsx @@ -0,0 +1,14 @@ +import React from 'react'; + +import {Layout} from '../components/Layout/Layout'; +import {Colors} from '../components/Theme/Colors'; + +export const ThemePage = () => { + return ( + + + + ); +}; + +export default ThemePage; From 4d42fa735463e1fdbbf0ceb0714c2922d30ac20f Mon Sep 17 00:00:00 2001 From: Daniil Gaponov Date: Sun, 9 Jun 2024 19:49:16 +0300 Subject: [PATCH 03/14] feat: basic palette in colors tab --- .../ColorsTab/BasicPalette/BasicPalette.tsx | 32 +++ .../ColorsTab/BasicPalette/PaletteColors.tsx | 30 +++ .../BasicPalette/ThemePaletteCard.scss | 22 ++ .../BasicPalette/ThemePaletteCard.tsx | 32 +++ .../Theme/{Colors => ColorsTab}/index.tsx | 5 +- src/components/Theme/Theme.tsx | 20 ++ .../Theme/ThemeCreator/ThemeCreator.tsx | 29 +++ .../Theme/ThemeCreator/ThemeCreatorContext.ts | 18 ++ src/components/Theme/ThemeCreator/index.ts | 2 + src/components/Theme/constants.ts | 14 +- src/components/Theme/hooks/useThemeCreator.ts | 42 ++++ src/components/Theme/types.ts | 15 +- src/components/Theme/utils.ts | 220 ++++++++++++++---- src/pages/theme.tsx | 4 +- 14 files changed, 427 insertions(+), 58 deletions(-) create mode 100644 src/components/Theme/ColorsTab/BasicPalette/BasicPalette.tsx create mode 100644 src/components/Theme/ColorsTab/BasicPalette/PaletteColors.tsx create mode 100644 src/components/Theme/ColorsTab/BasicPalette/ThemePaletteCard.scss create mode 100644 src/components/Theme/ColorsTab/BasicPalette/ThemePaletteCard.tsx rename src/components/Theme/{Colors => ColorsTab}/index.tsx (69%) create mode 100644 src/components/Theme/Theme.tsx create mode 100644 src/components/Theme/ThemeCreator/ThemeCreator.tsx create mode 100644 src/components/Theme/ThemeCreator/ThemeCreatorContext.ts create mode 100644 src/components/Theme/ThemeCreator/index.ts create mode 100644 src/components/Theme/hooks/useThemeCreator.ts diff --git a/src/components/Theme/ColorsTab/BasicPalette/BasicPalette.tsx b/src/components/Theme/ColorsTab/BasicPalette/BasicPalette.tsx new file mode 100644 index 000000000000..f9d74db78833 --- /dev/null +++ b/src/components/Theme/ColorsTab/BasicPalette/BasicPalette.tsx @@ -0,0 +1,32 @@ +import {Col, Flex} from '@gravity-ui/uikit'; +import React from 'react'; + +import {useThemeCreator} from '../../hooks/useThemeCreator'; + +import {PaletteColors} from './PaletteColors'; +import {ThemePaletteCard} from './ThemePaletteCard'; + +export const BasicPalette = () => { + const {palette, addColor} = useThemeCreator(); + + return ( + + +

Basic Palette

+
+ + + + + + + + + + + + + +
+ ); +}; diff --git a/src/components/Theme/ColorsTab/BasicPalette/PaletteColors.tsx b/src/components/Theme/ColorsTab/BasicPalette/PaletteColors.tsx new file mode 100644 index 000000000000..c64ba6743366 --- /dev/null +++ b/src/components/Theme/ColorsTab/BasicPalette/PaletteColors.tsx @@ -0,0 +1,30 @@ +import {Plus} from '@gravity-ui/icons'; +import {Button, Flex, Icon, Text} from '@gravity-ui/uikit'; +import React from 'react'; + +import {Palette} from '../../types'; + +interface PaletteColorsProps { + palette: Palette; + onAddColorClick: () => void; +} + +export const PaletteColors: React.FC = ({palette, onAddColorClick}) => { + return ( + + + Support Colors for various cases and states + + + {palette.map(({title}) => ( + + {title} + + ))} + + + + ); +}; diff --git a/src/components/Theme/ColorsTab/BasicPalette/ThemePaletteCard.scss b/src/components/Theme/ColorsTab/BasicPalette/ThemePaletteCard.scss new file mode 100644 index 000000000000..f73578e1a514 --- /dev/null +++ b/src/components/Theme/ColorsTab/BasicPalette/ThemePaletteCard.scss @@ -0,0 +1,22 @@ +@use '../../../../variables.scss'; + +$block: '.#{variables.$ns}theme-palette-card'; + +#{$block} { + padding: 30px var(--g-spacing-8); + border-radius: var(--g-spacing-4); + border: 1px solid var(--g-color-line-generic); + + &_light { + background: #fff; + color: var(--g-color-text-brand-contrast); + } + + &_dark { + background: #2c2c2c; + } + + &__title { + color: var(--g-color-base-background); + } +} diff --git a/src/components/Theme/ColorsTab/BasicPalette/ThemePaletteCard.tsx b/src/components/Theme/ColorsTab/BasicPalette/ThemePaletteCard.tsx new file mode 100644 index 000000000000..89890087e0b0 --- /dev/null +++ b/src/components/Theme/ColorsTab/BasicPalette/ThemePaletteCard.tsx @@ -0,0 +1,32 @@ +import {Moon, Sun} from '@gravity-ui/icons'; +import {Flex, Icon, Text} from '@gravity-ui/uikit'; +import React from 'react'; + +import {block} from '../../../../utils'; +import {Palette, ThemeVariant} from '../../types'; + +import './ThemePaletteCard.scss'; + +const b = block('theme-palette-card'); + +interface ThemePaletteCardProps { + theme: ThemeVariant; + palette: Palette; + onUpdate?: (colorTitle: string, value: string) => void; +} + +export const ThemePaletteCard: React.FC = ({theme, palette}) => { + return ( + + + + {theme === 'dark' ? 'Dark theme' : 'Light theme'} + + + {palette.map(({title, colors}) => ( +
{colors[theme]}
+ ))} +
+
+ ); +}; diff --git a/src/components/Theme/Colors/index.tsx b/src/components/Theme/ColorsTab/index.tsx similarity index 69% rename from src/components/Theme/Colors/index.tsx rename to src/components/Theme/ColorsTab/index.tsx index 6335d4eab1ae..ef2f8c8f3e27 100644 --- a/src/components/Theme/Colors/index.tsx +++ b/src/components/Theme/ColorsTab/index.tsx @@ -1,7 +1,9 @@ import {Col, Grid, Row} from '@gravity-ui/page-constructor'; import React from 'react'; -export const Colors = () => { +import {BasicPalette} from './BasicPalette/BasicPalette'; + +export const ColorsTab = () => { return ( @@ -9,6 +11,7 @@ export const Colors = () => {

Colors

+
); }; diff --git a/src/components/Theme/Theme.tsx b/src/components/Theme/Theme.tsx new file mode 100644 index 000000000000..026553a2f713 --- /dev/null +++ b/src/components/Theme/Theme.tsx @@ -0,0 +1,20 @@ +import {Col, Grid, Row} from '@gravity-ui/page-constructor'; +import React from 'react'; + +import {ColorsTab} from './ColorsTab'; +import {ThemeCreator} from './ThemeCreator'; +import {DEFAULT_THEME} from './constants'; + +export const Theme = () => { + return ( + + + + + + + + + + ); +}; diff --git a/src/components/Theme/ThemeCreator/ThemeCreator.tsx b/src/components/Theme/ThemeCreator/ThemeCreator.tsx new file mode 100644 index 000000000000..d0c05ce8e5fd --- /dev/null +++ b/src/components/Theme/ThemeCreator/ThemeCreator.tsx @@ -0,0 +1,29 @@ +import React from 'react'; + +import {ThemeOptions} from '../types'; +import {initThemeWizard} from '../utils'; + +import {ThemeCreatorContextProvider} from './ThemeCreatorContext'; + +interface ThemeCreatorProps extends React.PropsWithChildren { + theme: ThemeOptions; +} + +export const ThemeCreator: React.FC = ({theme, children}) => { + const [state, updateState] = React.useState(() => initThemeWizard(theme)); + + React.useEffect(() => { + updateState(initThemeWizard(theme)); + }, [theme]); + + return ( + + {children} + + ); +}; diff --git a/src/components/Theme/ThemeCreator/ThemeCreatorContext.ts b/src/components/Theme/ThemeCreator/ThemeCreatorContext.ts new file mode 100644 index 000000000000..e4f6dd7c4846 --- /dev/null +++ b/src/components/Theme/ThemeCreator/ThemeCreatorContext.ts @@ -0,0 +1,18 @@ +import noop from 'lodash/noop'; +import React from 'react'; + +import {DEFAULT_THEME} from '../constants'; +import type {ThemeWizardState} from '../types'; +import {initThemeWizard} from '../utils'; + +interface ThemeCreatorContextType { + state: ThemeWizardState; + updateState: (val: ThemeWizardState) => void; +} + +export const ThemeCreatorContext = React.createContext({ + state: initThemeWizard(DEFAULT_THEME), + updateState: noop, +}); + +export const ThemeCreatorContextProvider = ThemeCreatorContext.Provider; diff --git a/src/components/Theme/ThemeCreator/index.ts b/src/components/Theme/ThemeCreator/index.ts new file mode 100644 index 000000000000..1e0fe2bdfed6 --- /dev/null +++ b/src/components/Theme/ThemeCreator/index.ts @@ -0,0 +1,2 @@ +export {ThemeCreator} from './ThemeCreator'; +export {ThemeCreatorContext, ThemeCreatorContextProvider} from './ThemeCreatorContext'; diff --git a/src/components/Theme/constants.ts b/src/components/Theme/constants.ts index 4aa70f4861eb..eec6196bda5c 100644 --- a/src/components/Theme/constants.ts +++ b/src/components/Theme/constants.ts @@ -1,7 +1,13 @@ import type {ThemeOptions} from './types'; -export const DEFAULT_PALLETE: ThemeOptions['pallette'] = { +export const THEME_COLOR_VARIABLE_PREFIX = '--g-color'; + +export const DEFAULT_NEW_COLOR_TITLE = 'New color'; + +export const DEFAULT_PALETTE: ThemeOptions['palette'] = { light: { + white: '', // Дефолтно заданы + black: '', // Дефолтно заданы brand: '#eee', orange: '#FFA629', green: '#63E587', @@ -12,6 +18,8 @@ export const DEFAULT_PALLETE: ThemeOptions['pallette'] = { purple: '#6100FF', }, dark: { + white: '', // Дефолтно заданы + black: '', // Дефолтно заданы brand: '#aaa', orange: 'rgba(200,100,100,.20)', green: 'rgba(200,100,100,.20)', @@ -24,12 +32,12 @@ export const DEFAULT_PALLETE: ThemeOptions['pallette'] = { }; export const DEFAULT_THEME: ThemeOptions = { - pallette: DEFAULT_PALLETE, + palette: DEFAULT_PALETTE, colors: { light: { background: 'private.blue.550', // Ссылка на токен hoveredBrand: 'private.yellow.200', - brandText: '#63E587', // Просто кастомный цвет + brandText: '', hcBrandText: '', brandLine: '', selectionBackground: '', diff --git a/src/components/Theme/hooks/useThemeCreator.ts b/src/components/Theme/hooks/useThemeCreator.ts new file mode 100644 index 000000000000..69d6df5f29e0 --- /dev/null +++ b/src/components/Theme/hooks/useThemeCreator.ts @@ -0,0 +1,42 @@ +import React from 'react'; + +import {ThemeCreatorContext} from '../ThemeCreator'; +import {addColorToTheme, getThemePalette, removeColorFromTheme, updateColorInTheme} from '../utils'; +import type {AddColorToThemeParams, UpdateColorInThemeParams} from '../utils'; + +export const useThemeCreator = () => { + const {state, updateState} = React.useContext(ThemeCreatorContext); + + const addColor = React.useCallback( + (params: AddColorToThemeParams) => { + const newState = addColorToTheme(state, params); + updateState(newState); + }, + [state], + ); + + const updateColor = React.useCallback( + (params: UpdateColorInThemeParams) => { + const newState = updateColorInTheme(state, params); + updateState(newState); + }, + [state], + ); + + const removeColor = React.useCallback( + (colorTitle: string) => { + const newState = removeColorFromTheme(state, colorTitle); + updateState(newState); + }, + [state], + ); + + const palette = React.useMemo(() => getThemePalette(state), [state]); + + return { + addColor, + updateColor, + removeColor, + palette, + }; +}; diff --git a/src/components/Theme/types.ts b/src/components/Theme/types.ts index 9300f6e11538..3f253714bf52 100644 --- a/src/components/Theme/types.ts +++ b/src/components/Theme/types.ts @@ -1,6 +1,6 @@ export type ThemeVariant = 'light' | 'dark'; -export type PalletteOptions = { +export type PaletteOptions = { brand: string; [key: string]: string; }; @@ -24,7 +24,7 @@ export type TypographyOptions = {}; export interface ThemeOptions { /** Параметры solid-цветов, от которых рассчитываются private-цвета */ - pallette: Record; + palette: Record; /** Утилитарные цвета, использующиеся в компонентах (background, link, brand-text и тд) */ colors: Record; borders: BordersOptions; @@ -59,16 +59,21 @@ type PrivateColorVariable = (typeof PRIVATE_COLOR_VARIABLES)[number]; export type PrivateColors = Record; -type PalleteToken = { +type PaletteToken = { /** Title that will using in UI */ title: string; /** Auto-generated private colors for each theme variant */ privateColors: Record; }; -export type PalleteTokens = Record; +export type PaletteTokens = Record; export interface ThemeWizardState extends ThemeOptions { /** Mapping color tokens to their information (title and private colors) */ - palletteTokens: PalleteTokens; + paletteTokens: PaletteTokens; } + +export type Palette = { + title: string; + colors: Record; +}[]; diff --git a/src/components/Theme/utils.ts b/src/components/Theme/utils.ts index e09b65a0aea0..e055481f37c1 100644 --- a/src/components/Theme/utils.ts +++ b/src/components/Theme/utils.ts @@ -1,8 +1,10 @@ import kebabCase from 'lodash/kebabCase'; import lowerCase from 'lodash/lowerCase'; +import {DEFAULT_NEW_COLOR_TITLE, THEME_COLOR_VARIABLE_PREFIX} from './constants'; import type { - PalleteTokens, + Palette, + PaletteTokens, PrivateColors, ThemeOptions, ThemeVariant, @@ -10,7 +12,6 @@ import type { } from './types'; function createColorToken(title: string) { - // TODO: Может стоит добавить slugify для поддержки ru? return kebabCase(title); } @@ -18,6 +19,30 @@ function createTitleFromToken(token: string) { return lowerCase(token); } +function createPrivateColorToken(mainColorToken: string, privateColorCode: string) { + return `private.${mainColorToken}.${privateColorCode}`; +} + +function createPrivateColorTitle(mainColorToken: string, privateColorCode: string) { + return `${THEME_COLOR_VARIABLE_PREFIX}-${mainColorToken}-${privateColorCode}`; +} + +function createNewColorTitle(currentPaletteTokens: PaletteTokens) { + let i = 0; + + // eslint-disable-next-line no-constant-condition + while (true) { + const title = i === 0 ? DEFAULT_NEW_COLOR_TITLE : `${DEFAULT_NEW_COLOR_TITLE} ${i}`; + const token = createColorToken(title); + + if (!currentPaletteTokens[token]) { + return title; + } + + i++; + } +} + function createPrivateColors(solidColor: string): PrivateColors { return { 50: '', @@ -43,20 +68,20 @@ function createPrivateColors(solidColor: string): PrivateColors { }; } -function createPalleteTokens(pallette: ThemeOptions['pallette']): PalleteTokens { - const tokens = Object.keys(pallette.light); +function createPalleteTokens(palette: ThemeOptions['palette']): PaletteTokens { + const tokens = Object.keys(palette.light); - return tokens.reduce( + return tokens.reduce( (acc, token) => ({ ...acc, [token]: { title: createTitleFromToken(token), privateColors: { - light: pallette.light[token] - ? createPrivateColors(pallette.light[token]) + light: palette.light[token] + ? createPrivateColors(palette.light[token]) : undefined, - dark: pallette.dark[token] - ? createPrivateColors(pallette.dark[token]) + dark: palette.dark[token] + ? createPrivateColors(palette.dark[token]) : undefined, }, }, @@ -65,94 +90,195 @@ function createPalleteTokens(pallette: ThemeOptions['pallette']): PalleteTokens ); } -export function updateColor( +export type UpdateColorInThemeParams = { + /** The title of the color to update. */ + title: string; + /** The theme variant to update. */ + theme: ThemeVariant; + /** The new value of the color. */ + value: string; +}; + +/** + * Updates a color in the given theme state. + * + * @param {ThemeWizardState} themeState - The current state of the theme. + * @param {UpdateColorInThemeParams} params - The parameters for the color update. + * @returns {ThemeWizardState} The updated theme state. + */ +export function updateColorInTheme( themeState: ThemeWizardState, - params: { - title: string; - theme: ThemeVariant; - value: string; - }, + params: UpdateColorInThemeParams, ): ThemeWizardState { const newThemeState = {...themeState}; const token = createColorToken(params.title); if (params.theme === 'light') { - if (!newThemeState.pallette.light[token]) { - newThemeState.pallette.light[token] = ''; + if (!newThemeState.palette.light[token]) { + newThemeState.palette.light[token] = ''; } - newThemeState.pallette.light[token] = params.value; + newThemeState.palette.light[token] = params.value; } if (params.theme === 'dark') { - if (!newThemeState.pallette.dark[token]) { - newThemeState.pallette.dark[token] = ''; + if (!newThemeState.palette.dark[token]) { + newThemeState.palette.dark[token] = ''; } - newThemeState.pallette.dark[token] = params.value; + newThemeState.palette.dark[token] = params.value; } const privateColors = createPrivateColors(params.value); - newThemeState.palletteTokens[token] = { - ...newThemeState.palletteTokens[token], + newThemeState.paletteTokens[token] = { + ...newThemeState.paletteTokens[token], title: params.title, privateColors: { light: params.theme === 'light' ? privateColors - : newThemeState.palletteTokens[token].privateColors?.light, + : newThemeState.paletteTokens[token]?.privateColors?.light, dark: params.theme === 'dark' ? privateColors - : newThemeState.palletteTokens[token].privateColors?.dark, + : newThemeState.paletteTokens[token]?.privateColors?.dark, }, }; - return themeState; + return newThemeState; } -export function addColor( +export type AddColorToThemeParams = + | { + title?: string; + colors?: Partial>; + } + | undefined; + +/** + * Adds a new color to the given theme state. + * + * @param {ThemeWizardState} themeState - The current state of the theme. + * @param {AddColorToThemeParams} params - The parameters of the adding color. + * @returns {ThemeWizardState} The updated theme state with the new color added. + */ +export function addColorToTheme( themeState: ThemeWizardState, - params: { - title: string; - colors?: Partial>; - }, + params: AddColorToThemeParams, ): ThemeWizardState { const newThemeState = {...themeState}; - const token = createColorToken(params.title); + const title = params?.title ?? createNewColorTitle(themeState.paletteTokens); + const token = createColorToken(title); - if (!themeState.pallette.dark[token]) { - newThemeState.pallette.dark[token] = ''; + if (!themeState.palette.dark[token]) { + newThemeState.palette.dark[token] = ''; } - if (!themeState.pallette.light[token]) { - newThemeState.pallette.light[token] = ''; + if (!themeState.palette.light[token]) { + newThemeState.palette.light[token] = ''; } - if (params.colors?.dark) { - newThemeState.pallette.dark[token] = params.colors.dark; + if (params?.colors?.dark) { + newThemeState.palette.dark[token] = params.colors.dark; } - if (params.colors?.light) { - newThemeState.pallette.light[token] = params.colors.light; + if (params?.colors?.light) { + newThemeState.palette.light[token] = params.colors.light; } - newThemeState.palletteTokens[token] = { - ...newThemeState.palletteTokens[token], - title: params.title, + newThemeState.paletteTokens[token] = { + ...newThemeState.paletteTokens[token], + title, privateColors: { - light: params.colors?.light ? createPrivateColors(params.colors.light) : undefined, - dark: params.colors?.dark ? createPrivateColors(params.colors.dark) : undefined, + light: params?.colors?.light ? createPrivateColors(params.colors.light) : undefined, + dark: params?.colors?.dark ? createPrivateColors(params.colors.dark) : undefined, }, }; - return themeState; + return newThemeState; +} + +export function removeColorFromTheme( + themeState: ThemeWizardState, + colorTitle: string, +): ThemeWizardState { + const newThemeState = {...themeState}; + const token = createColorToken(colorTitle); + + delete newThemeState.palette.dark[token]; + delete newThemeState.palette.light[token]; + delete newThemeState.paletteTokens[token]; + + return newThemeState; +} + +export type ThemeColorOption = { + token: string; + title: string; + privateColors: { + token: string; + title: string; + value: string; + }[]; +}; + +/** + * Generates theme color options from the given palette tokens and theme variant. + * + * @param {Object} params - The parameters for generating theme color options. + * @param {PaletteTokens} params.paletteTokens - The palette tokens to generate options from. + * @param {ThemeVariant} params.themeVariant - The theme variant to filter private colors (light, dark). + * @returns {ThemeColorOption[]} The generated theme color options. + */ +export function getThemeColorOptions({ + paletteTokens, + themeVariant, +}: { + paletteTokens: PaletteTokens; + themeVariant: ThemeVariant; +}) { + const colorTokens = Object.keys(paletteTokens); + + return colorTokens.reduce((acc, token) => { + if (paletteTokens[token]?.privateColors[themeVariant]) { + return [ + ...acc, + { + token, + title: paletteTokens[token].title, + privateColors: Object.entries( + paletteTokens[token].privateColors[themeVariant], + ).map(([privateColorCode, value]) => ({ + token: createPrivateColorToken(token, privateColorCode), + title: createPrivateColorTitle(token, privateColorCode), + value, + })), + }, + ]; + } + + return acc; + }, []); +} + +export function getThemePalette(theme: ThemeWizardState): Palette { + const colorTokens = Object.keys(theme.paletteTokens); + + return colorTokens.map((token) => { + return { + title: theme.paletteTokens[token]?.title || '', + colors: { + light: theme.palette.light[token], + dark: theme.palette.dark[token], + }, + }; + }); } export function initThemeWizard(theme: ThemeOptions): ThemeWizardState { return { ...theme, - palletteTokens: createPalleteTokens(theme.pallette), + paletteTokens: createPalleteTokens(theme.palette), }; } diff --git a/src/pages/theme.tsx b/src/pages/theme.tsx index 9c8c80feda75..6d1568a7249a 100644 --- a/src/pages/theme.tsx +++ b/src/pages/theme.tsx @@ -1,12 +1,12 @@ import React from 'react'; import {Layout} from '../components/Layout/Layout'; -import {Colors} from '../components/Theme/Colors'; +import {Theme} from '../components/Theme/Theme'; export const ThemePage = () => { return ( - + ); }; From 001a3cd4eb737ea735f7994329c50f08dd768a09 Mon Sep 17 00:00:00 2001 From: Daniil Gaponov Date: Mon, 10 Jun 2024 00:20:14 +0300 Subject: [PATCH 04/14] feat: CRUD for basic palette --- .../ColorPickerInput/ColorPickerInput.scss | 4 +- .../ColorsTab/BasicPalette/BasicPalette.tsx | 17 +-- .../ColorsTab/BasicPalette/PaletteColors.scss | 19 ++++ .../ColorsTab/BasicPalette/PaletteColors.tsx | 102 +++++++++++++++--- .../BasicPalette/ThemePaletteCard.scss | 5 + .../BasicPalette/ThemePaletteCard.tsx | 43 +++++--- src/components/Theme/constants.ts | 44 ++++---- src/components/Theme/hooks/useThemeCreator.ts | 19 +++- src/components/Theme/types.ts | 28 ++--- src/components/Theme/utils.ts | 42 +++++++- src/pages/{theme.tsx => _theme.tsx} | 0 11 files changed, 242 insertions(+), 81 deletions(-) create mode 100644 src/components/Theme/ColorsTab/BasicPalette/PaletteColors.scss rename src/pages/{theme.tsx => _theme.tsx} (100%) diff --git a/src/components/ColorPickerInput/ColorPickerInput.scss b/src/components/ColorPickerInput/ColorPickerInput.scss index dbd6883958f7..dceed4a3060f 100644 --- a/src/components/ColorPickerInput/ColorPickerInput.scss +++ b/src/components/ColorPickerInput/ColorPickerInput.scss @@ -15,6 +15,8 @@ $block: '.#{variables.$ns}color-picker'; width: 100%; height: 0; opacity: 0; - border: 1px solid transparent; + padding: 0; + margin: 0; + border: 0; } } diff --git a/src/components/Theme/ColorsTab/BasicPalette/BasicPalette.tsx b/src/components/Theme/ColorsTab/BasicPalette/BasicPalette.tsx index f9d74db78833..829cdee16817 100644 --- a/src/components/Theme/ColorsTab/BasicPalette/BasicPalette.tsx +++ b/src/components/Theme/ColorsTab/BasicPalette/BasicPalette.tsx @@ -7,23 +7,28 @@ import {PaletteColors} from './PaletteColors'; import {ThemePaletteCard} from './ThemePaletteCard'; export const BasicPalette = () => { - const {palette, addColor} = useThemeCreator(); + const {palette, addColor, removeColor, updateColor, renameColor} = useThemeCreator(); return (

Basic Palette

- - - + + + - + - + diff --git a/src/components/Theme/ColorsTab/BasicPalette/PaletteColors.scss b/src/components/Theme/ColorsTab/BasicPalette/PaletteColors.scss new file mode 100644 index 000000000000..c68d249201c7 --- /dev/null +++ b/src/components/Theme/ColorsTab/BasicPalette/PaletteColors.scss @@ -0,0 +1,19 @@ +@use '../../../../variables.scss'; + +$block: '.#{variables.$ns}palette-colors'; + +#{$block} { + --g-button-border-radius: var(--g-spacing-2); + --g-text-input-border-radius: var(--g-spacing-2); + + padding-top: var(--g-spacing-4); + + &__title { + margin-bottom: var(--g-spacing-4); + } + + &__add-button { + margin-top: var(--g-spacing-4); + width: min-content; + } +} diff --git a/src/components/Theme/ColorsTab/BasicPalette/PaletteColors.tsx b/src/components/Theme/ColorsTab/BasicPalette/PaletteColors.tsx index c64ba6743366..1d1847b7655c 100644 --- a/src/components/Theme/ColorsTab/BasicPalette/PaletteColors.tsx +++ b/src/components/Theme/ColorsTab/BasicPalette/PaletteColors.tsx @@ -1,28 +1,104 @@ -import {Plus} from '@gravity-ui/icons'; -import {Button, Flex, Icon, Text} from '@gravity-ui/uikit'; +import {Plus, TrashBin} from '@gravity-ui/icons'; +import {Button, Flex, Icon, Text, TextInput, sp} from '@gravity-ui/uikit'; +import debounce from 'lodash/debounce'; import React from 'react'; +import {block} from '../../../../utils'; import {Palette} from '../../types'; +import './PaletteColors.scss'; + +const b = block('palette-colors'); + +interface PaletteColorEditorProps { + paletteColorData: Palette[0]; + onUpdateTitle: (oldTitle: string, newTitle: string) => void; + onDelete: (title: string) => void; +} + +const PaletteColorEditor: React.FC = ({ + onDelete, + onUpdateTitle, + paletteColorData, +}) => { + const {title, isCustom} = paletteColorData; + + const [localTitle, setLocalTitle] = React.useState(title); + + React.useEffect(() => { + setLocalTitle(title); + }, [title]); + + const handleDelete = React.useCallback(() => onDelete(title), [onDelete, title]); + + const updateTitle = React.useCallback( + (newTitle: string) => onUpdateTitle(title, newTitle), + [title, onUpdateTitle], + ); + + const debouncedUpdateTitle = React.useMemo(() => debounce(updateTitle, 500), [updateTitle]); + + const handleUpdateTitle = React.useCallback( + (newTitle: string) => { + setLocalTitle(newTitle); + debouncedUpdateTitle(newTitle); + }, + [debouncedUpdateTitle], + ); + + if (!isCustom) { + return ( +
+ {title} +
+ ); + } + + return ( + + + + + ); +}; + interface PaletteColorsProps { palette: Palette; onAddColorClick: () => void; + onDeleteColor: (title: string) => void; + onUpdateColorTitle: (oldTitle: string, newTitle: string) => void; } -export const PaletteColors: React.FC = ({palette, onAddColorClick}) => { +export const PaletteColors: React.FC = ({ + palette, + onAddColorClick, + onDeleteColor, + onUpdateColorTitle, +}) => { return ( - - - Support Colors for various cases and states + + + Support Colors for various cases and +
states
- - {palette.map(({title}) => ( - - {title} - + + {palette.map((paletteColorData) => ( + ))} - diff --git a/src/components/Theme/ColorsTab/BasicPalette/ThemePaletteCard.scss b/src/components/Theme/ColorsTab/BasicPalette/ThemePaletteCard.scss index f73578e1a514..54ff587aafb0 100644 --- a/src/components/Theme/ColorsTab/BasicPalette/ThemePaletteCard.scss +++ b/src/components/Theme/ColorsTab/BasicPalette/ThemePaletteCard.scss @@ -19,4 +19,9 @@ $block: '.#{variables.$ns}theme-palette-card'; &__title { color: var(--g-color-base-background); } + + &__theme-root { + --g-color-base-background: transparent; + --g-button-border-radius: var(--g-spacing-2); + } } diff --git a/src/components/Theme/ColorsTab/BasicPalette/ThemePaletteCard.tsx b/src/components/Theme/ColorsTab/BasicPalette/ThemePaletteCard.tsx index 89890087e0b0..385583729875 100644 --- a/src/components/Theme/ColorsTab/BasicPalette/ThemePaletteCard.tsx +++ b/src/components/Theme/ColorsTab/BasicPalette/ThemePaletteCard.tsx @@ -1,8 +1,9 @@ import {Moon, Sun} from '@gravity-ui/icons'; -import {Flex, Icon, Text} from '@gravity-ui/uikit'; +import {Flex, Icon, Text, ThemeProvider} from '@gravity-ui/uikit'; import React from 'react'; import {block} from '../../../../utils'; +import {ColorPickerInput} from '../../../ColorPickerInput/ColorPickerInput'; import {Palette, ThemeVariant} from '../../types'; import './ThemePaletteCard.scss'; @@ -12,21 +13,37 @@ const b = block('theme-palette-card'); interface ThemePaletteCardProps { theme: ThemeVariant; palette: Palette; - onUpdate?: (colorTitle: string, value: string) => void; + onUpdate: (params: {title: string; theme: ThemeVariant; value: string}) => void; } -export const ThemePaletteCard: React.FC = ({theme, palette}) => { +export const ThemePaletteCard: React.FC = ({theme, palette, onUpdate}) => { + const createChangeHandler = React.useCallback( + (title: string) => (value: string) => { + onUpdate({title, theme, value}); + }, + [onUpdate, theme], + ); + return ( - - - - {theme === 'dark' ? 'Dark theme' : 'Light theme'} - - - {palette.map(({title, colors}) => ( -
{colors[theme]}
- ))} + + + + + + {theme === 'dark' ? 'Dark theme' : 'Light theme'} + + + + {palette.map(({title, colors}) => ( + + ))} + -
+ ); }; diff --git a/src/components/Theme/constants.ts b/src/components/Theme/constants.ts index eec6196bda5c..2fbb6072f9a0 100644 --- a/src/components/Theme/constants.ts +++ b/src/components/Theme/constants.ts @@ -6,36 +6,38 @@ export const DEFAULT_NEW_COLOR_TITLE = 'New color'; export const DEFAULT_PALETTE: ThemeOptions['palette'] = { light: { - white: '', // Дефолтно заданы - black: '', // Дефолтно заданы - brand: '#eee', - orange: '#FFA629', - green: '#63E587', - yellow: '#FFBE5C', - red: '#FF6B00', - blue: '#1DA3EE', - 'cool-grey': '#808895', - purple: '#6100FF', + white: 'rgb(255, 255, 255)', + black: 'rgb(0, 0, 0)', + brand: 'rgb(82, 130, 255)', + orange: 'rgb(255, 119, 0)', + green: 'rgb(59, 201, 53)', + yellow: 'rgb(255, 219, 77)', + red: 'rgb(255, 4, 0)', + blue: 'rgb(82, 130, 255)', + 'cool-grey': 'rgb(107, 132, 153)', + purple: 'rgb(143, 82, 204)', }, dark: { - white: '', // Дефолтно заданы - black: '', // Дефолтно заданы - brand: '#aaa', - orange: 'rgba(200,100,100,.20)', - green: 'rgba(200,100,100,.20)', - yellow: 'rgba(200,100,100,.20)', - red: 'rgba(200,100,100,.20)', - blue: 'rgba(200,100,100,.20)', - 'cool-grey': 'rgba(200,100,100,.20)', - purple: 'rgba(200,100,100,.20)', + white: 'rgb(255, 255, 255)', + black: 'rgb(0, 0, 0)', + brand: 'rgb(75, 113, 214)', + orange: 'rgb(200, 99, 12)', + green: 'rgb(91, 181, 87)', + yellow: 'rgb(255, 203, 0)', + red: 'rgb(232, 73, 69)', + blue: 'rgb(82, 130, 255)', + 'cool-grey': 'rgb(96, 128, 156)', + purple: 'rgb(143, 82, 204)', }, }; +export const DEFAULT_PALETTE_TOKENS = new Set(Object.keys(DEFAULT_PALETTE.light)); + export const DEFAULT_THEME: ThemeOptions = { palette: DEFAULT_PALETTE, colors: { light: { - background: 'private.blue.550', // Ссылка на токен + background: 'private.blue.550', hoveredBrand: 'private.yellow.200', brandText: '', hcBrandText: '', diff --git a/src/components/Theme/hooks/useThemeCreator.ts b/src/components/Theme/hooks/useThemeCreator.ts index 69d6df5f29e0..8b5debf7d1eb 100644 --- a/src/components/Theme/hooks/useThemeCreator.ts +++ b/src/components/Theme/hooks/useThemeCreator.ts @@ -1,14 +1,20 @@ import React from 'react'; import {ThemeCreatorContext} from '../ThemeCreator'; -import {addColorToTheme, getThemePalette, removeColorFromTheme, updateColorInTheme} from '../utils'; +import { + addColorToTheme, + getThemePalette, + removeColorFromTheme, + renameColorInTheme, + updateColorInTheme, +} from '../utils'; import type {AddColorToThemeParams, UpdateColorInThemeParams} from '../utils'; export const useThemeCreator = () => { const {state, updateState} = React.useContext(ThemeCreatorContext); const addColor = React.useCallback( - (params: AddColorToThemeParams) => { + (params?: AddColorToThemeParams) => { const newState = addColorToTheme(state, params); updateState(newState); }, @@ -31,12 +37,21 @@ export const useThemeCreator = () => { [state], ); + const renameColor = React.useCallback( + (oldTitle: string, newTitle: string) => { + const newState = renameColorInTheme(state, oldTitle, newTitle); + updateState(newState); + }, + [state], + ); + const palette = React.useMemo(() => getThemePalette(state), [state]); return { addColor, updateColor, removeColor, + renameColor, palette, }; }; diff --git a/src/components/Theme/types.ts b/src/components/Theme/types.ts index 3f253714bf52..896b9b8893f3 100644 --- a/src/components/Theme/types.ts +++ b/src/components/Theme/types.ts @@ -23,35 +23,16 @@ export type BordersOptions = {}; export type TypographyOptions = {}; export interface ThemeOptions { - /** Параметры solid-цветов, от которых рассчитываются private-цвета */ + /** Values of solid colors, from which private colors are calculated */ palette: Record; - /** Утилитарные цвета, использующиеся в компонентах (background, link, brand-text и тд) */ + /** Utility colors that used in components (background, link, brand-text, etc.) */ colors: Record; borders: BordersOptions; typography: TypographyOptions; } -// Про 550 | solid подумать... Нужно ли хранить его тут? const PRIVATE_COLOR_VARIABLES = [ - 1000, - 950, - 900, - 850, - 800, - 750, - 700, - 650, - 600, - 'solid', - 500, - 450, - 400, - 350, - 300, - 250, - 200, - 150, - 100, + 1000, 950, 900, 850, 800, 750, 700, 650, 600, 550, 500, 450, 400, 350, 300, 250, 200, 150, 100, 50, ] as const; @@ -62,6 +43,8 @@ export type PrivateColors = Record; type PaletteToken = { /** Title that will using in UI */ title: string; + /** Is color manually created */ + isCustom?: boolean; /** Auto-generated private colors for each theme variant */ privateColors: Record; }; @@ -75,5 +58,6 @@ export interface ThemeWizardState extends ThemeOptions { export type Palette = { title: string; + isCustom?: boolean; colors: Record; }[]; diff --git a/src/components/Theme/utils.ts b/src/components/Theme/utils.ts index e055481f37c1..8d8c8188fb35 100644 --- a/src/components/Theme/utils.ts +++ b/src/components/Theme/utils.ts @@ -1,7 +1,12 @@ +import capitalize from 'lodash/capitalize'; import kebabCase from 'lodash/kebabCase'; import lowerCase from 'lodash/lowerCase'; -import {DEFAULT_NEW_COLOR_TITLE, THEME_COLOR_VARIABLE_PREFIX} from './constants'; +import { + DEFAULT_NEW_COLOR_TITLE, + DEFAULT_PALETTE_TOKENS, + THEME_COLOR_VARIABLE_PREFIX, +} from './constants'; import type { Palette, PaletteTokens, @@ -16,7 +21,7 @@ function createColorToken(title: string) { } function createTitleFromToken(token: string) { - return lowerCase(token); + return capitalize(lowerCase(token)); } function createPrivateColorToken(mainColorToken: string, privateColorCode: string) { @@ -27,6 +32,10 @@ function createPrivateColorTitle(mainColorToken: string, privateColorCode: strin return `${THEME_COLOR_VARIABLE_PREFIX}-${mainColorToken}-${privateColorCode}`; } +function isManuallyCreatedToken(token: string) { + return !DEFAULT_PALETTE_TOKENS.has(token); +} + function createNewColorTitle(currentPaletteTokens: PaletteTokens) { let i = 0; @@ -55,7 +64,7 @@ function createPrivateColors(solidColor: string): PrivateColors { 400: '', 450: '', 500: '', - solid: solidColor, + 550: solidColor, 600: '', 650: '', 700: '', @@ -194,6 +203,7 @@ export function addColorToTheme( light: params?.colors?.light ? createPrivateColors(params.colors.light) : undefined, dark: params?.colors?.dark ? createPrivateColors(params.colors.dark) : undefined, }, + isCustom: true, }; return newThemeState; @@ -213,6 +223,31 @@ export function removeColorFromTheme( return newThemeState; } +export function renameColorInTheme( + themeState: ThemeWizardState, + oldTitle: string, + newTitle: string, +): ThemeWizardState { + const newThemeState = {...themeState}; + const oldToken = createColorToken(oldTitle); + const newToken = createColorToken(newTitle); + + if (newThemeState.paletteTokens[oldToken]) { + newThemeState.paletteTokens[newToken] = { + ...newThemeState.paletteTokens[oldToken], + title: newTitle, + }; + newThemeState.palette.dark[newToken] = newThemeState.palette.dark[oldToken]; + newThemeState.palette.light[newToken] = newThemeState.palette.dark[oldToken]; + } + + delete newThemeState.palette.dark[oldToken]; + delete newThemeState.palette.light[oldToken]; + delete newThemeState.paletteTokens[oldToken]; + + return newThemeState; +} + export type ThemeColorOption = { token: string; title: string; @@ -272,6 +307,7 @@ export function getThemePalette(theme: ThemeWizardState): Palette { light: theme.palette.light[token], dark: theme.palette.dark[token], }, + isCustom: isManuallyCreatedToken(token), }; }); } diff --git a/src/pages/theme.tsx b/src/pages/_theme.tsx similarity index 100% rename from src/pages/theme.tsx rename to src/pages/_theme.tsx From bded43525faf0f928a5845b3c1594cbe4cb1c57a Mon Sep 17 00:00:00 2001 From: Daniil Gaponov Date: Tue, 11 Jun 2024 23:51:37 +0300 Subject: [PATCH 05/14] fix: palette tokens order --- .../ColorPickerInput/ColorPickerInput.tsx | 18 +++++--- .../BasicPalette/ThemePaletteCard.tsx | 2 +- src/components/Theme/constants.ts | 46 +++++++++++-------- src/components/Theme/types.ts | 29 +++++++----- src/components/Theme/utils.ts | 24 ++++++++-- 5 files changed, 74 insertions(+), 45 deletions(-) diff --git a/src/components/ColorPickerInput/ColorPickerInput.tsx b/src/components/ColorPickerInput/ColorPickerInput.tsx index d42d419eb12e..4c52ed5d488f 100644 --- a/src/components/ColorPickerInput/ColorPickerInput.tsx +++ b/src/components/ColorPickerInput/ColorPickerInput.tsx @@ -16,7 +16,7 @@ export interface ColorPickerInputProps { defaultValue: string; name?: string; value?: string; - onChange?: (color: string) => void; + onChange: (color: string) => void; errorMessage?: string; } @@ -40,7 +40,7 @@ export const ColorPickerInput = ({ const onChange: ChangeEventHandler = useCallback( (event) => { const newValue = event.target.value.replaceAll(' ', ''); - onChangeExternal?.(newValue); + onChangeExternal(newValue); setInputValue(newValue); setValidationError(undefined); @@ -64,12 +64,16 @@ export const ColorPickerInput = ({ [onChangeExternal], ); - const onNativeInputChange: ChangeEventHandler = useCallback((e) => { - const newValue = e.target.value.toUpperCase(); + const onNativeInputChange: ChangeEventHandler = useCallback( + (e) => { + const newValue = e.target.value.toUpperCase(); - setColor(newValue); - setInputValue(newValue); - }, []); + setColor(newValue); + setInputValue(newValue); + onChangeExternal(newValue); + }, + [onChangeExternal], + ); const onBlur = useCallback(() => { if ( diff --git a/src/components/Theme/ColorsTab/BasicPalette/ThemePaletteCard.tsx b/src/components/Theme/ColorsTab/BasicPalette/ThemePaletteCard.tsx index 385583729875..4c2af2d60fe3 100644 --- a/src/components/Theme/ColorsTab/BasicPalette/ThemePaletteCard.tsx +++ b/src/components/Theme/ColorsTab/BasicPalette/ThemePaletteCard.tsx @@ -38,7 +38,7 @@ export const ThemePaletteCard: React.FC = ({theme, palett ))} diff --git a/src/components/Theme/constants.ts b/src/components/Theme/constants.ts index 2fbb6072f9a0..14e77bb948ae 100644 --- a/src/components/Theme/constants.ts +++ b/src/components/Theme/constants.ts @@ -37,28 +37,34 @@ export const DEFAULT_THEME: ThemeOptions = { palette: DEFAULT_PALETTE, colors: { light: { - background: 'private.blue.550', - hoveredBrand: 'private.yellow.200', - brandText: '', - hcBrandText: '', - brandLine: '', - selectionBackground: '', - hoveredSelectionBackground: '', - link: '', - hoveredLink: '', - visitedLink: '', + 'base-background': 'private.white.1000-solid', + 'base-brand': 'private.yellow.550-solid', + 'base-brand-hover': 'private.yellow.600-solid', + 'base-selection': 'private.yellow.200', + 'base-selection-hover': 'private.yellow.300', + 'line-brand': 'private.yellow.600-solid', + 'text-brand': 'private.yellow.700-solid', + 'text-brand-heavy': 'private.orange.700-solid', + 'text-brand-contrast': 'private.black.850', + 'text-link': 'private.yellow.650-solid', + 'text-link-hover': 'private.orange.650-solid', + 'text-link-visited': 'private.purple.550-solid', + 'text-link-visited-hover': 'private.purple.800-solid', }, dark: { - background: '', - hoveredBrand: '', - brandText: '', - hcBrandText: '', - brandLine: '', - selectionBackground: '', - hoveredSelectionBackground: '', - link: '', - hoveredLink: '', - visitedLink: '', + 'base-background': 'rgb(34, 29, 34)', + 'base-brand': 'private-yellow-550-solid', + 'base-brand-hover': 'private-yellow-650-solid', + 'base-selection': 'private-yellow-150', + 'base-selection-hover': 'private-yellow-200', + 'line-brand': 'private-yellow-600-solid', + 'text-brand': 'private-yellow-600-solid', + 'text-brand-heavy': 'private-yellow-700-solid', + 'text-brand-contrast': 'private-black-900', + 'text-link': 'private-yellow-550-solid', + 'text-link-hover': 'private-orange-550-solid', + 'text-link-visited': 'private-purple-700-solid', + 'text-link-visited-hover': 'private-purple-850-solid', }, }, borders: {}, diff --git a/src/components/Theme/types.ts b/src/components/Theme/types.ts index 896b9b8893f3..2463c53b3185 100644 --- a/src/components/Theme/types.ts +++ b/src/components/Theme/types.ts @@ -6,16 +6,19 @@ export type PaletteOptions = { }; export type ColorsOptions = { - background: string; - hoveredBrand: string; - brandText: string; - hcBrandText: string; - brandLine: string; - selectionBackground: string; - hoveredSelectionBackground: string; - link: string; - hoveredLink: string; - visitedLink: string; + 'base-background': string; + 'base-brand': string; + 'base-brand-hover': string; + 'base-selection': string; + 'base-selection-hover': string; + 'line-brand': string; + 'text-brand': string; + 'text-brand-heavy': string; + 'text-brand-contrast': string; + 'text-link': string; + 'text-link-hover': string; + 'text-link-visited': string; + 'text-link-visited-hover': string; }; export type BordersOptions = {}; @@ -36,7 +39,7 @@ const PRIVATE_COLOR_VARIABLES = [ 50, ] as const; -type PrivateColorVariable = (typeof PRIVATE_COLOR_VARIABLES)[number]; +type PrivateColorVariable = typeof PRIVATE_COLOR_VARIABLES[number]; export type PrivateColors = Record; @@ -49,11 +52,13 @@ type PaletteToken = { privateColors: Record; }; -export type PaletteTokens = Record; +export type PaletteTokens = Record; export interface ThemeWizardState extends ThemeOptions { /** Mapping color tokens to their information (title and private colors) */ paletteTokens: PaletteTokens; + /** All available palette tokens in theme */ + tokens: string[]; } export type Palette = { diff --git a/src/components/Theme/utils.ts b/src/components/Theme/utils.ts index 8d8c8188fb35..dbc130c529c8 100644 --- a/src/components/Theme/utils.ts +++ b/src/components/Theme/utils.ts @@ -155,6 +155,11 @@ export function updateColorInTheme( }, }; + const isNewToken = !themeState.paletteTokens[token]; + if (isNewToken) { + newThemeState.tokens.push(token); + } + return newThemeState; } @@ -206,6 +211,8 @@ export function addColorToTheme( isCustom: true, }; + newThemeState.tokens.push(token); + return newThemeState; } @@ -220,6 +227,8 @@ export function removeColorFromTheme( delete newThemeState.palette.light[token]; delete newThemeState.paletteTokens[token]; + newThemeState.tokens = newThemeState.tokens.filter((t) => t !== token); + return newThemeState; } @@ -241,6 +250,10 @@ export function renameColorInTheme( newThemeState.palette.light[newToken] = newThemeState.palette.dark[oldToken]; } + newThemeState.tokens = newThemeState.tokens.map((token) => + token === oldToken ? newToken : token, + ); + delete newThemeState.palette.dark[oldToken]; delete newThemeState.palette.light[oldToken]; delete newThemeState.paletteTokens[oldToken]; @@ -283,7 +296,7 @@ export function getThemeColorOptions({ token, title: paletteTokens[token].title, privateColors: Object.entries( - paletteTokens[token].privateColors[themeVariant], + paletteTokens[token].privateColors[themeVariant]!, ).map(([privateColorCode, value]) => ({ token: createPrivateColorToken(token, privateColorCode), title: createPrivateColorTitle(token, privateColorCode), @@ -298,9 +311,7 @@ export function getThemeColorOptions({ } export function getThemePalette(theme: ThemeWizardState): Palette { - const colorTokens = Object.keys(theme.paletteTokens); - - return colorTokens.map((token) => { + return theme.tokens.map((token) => { return { title: theme.paletteTokens[token]?.title || '', colors: { @@ -313,8 +324,11 @@ export function getThemePalette(theme: ThemeWizardState): Palette { } export function initThemeWizard(theme: ThemeOptions): ThemeWizardState { + const paletteTokens = createPalleteTokens(theme.palette); + return { ...theme, - paletteTokens: createPalleteTokens(theme.palette), + paletteTokens, + tokens: Object.keys(paletteTokens), }; } From 9a50199e6aebbe5b91acd5d19a55ac9a929b9dcc Mon Sep 17 00:00:00 2001 From: Daniil Gaponov Date: Wed, 12 Jun 2024 10:22:07 +0300 Subject: [PATCH 06/14] feat: add private colors generator --- package-lock.json | 13 + package.json | 2 + src/components/Theme/constants.ts | 34 +-- .../Theme/privateColors/constants.ts | 289 ++++++++++++++++++ src/components/Theme/privateColors/index.ts | 1 + src/components/Theme/privateColors/utils.ts | 98 ++++++ src/components/Theme/types.ts | 9 +- src/components/Theme/utils.ts | 92 ++++-- 8 files changed, 483 insertions(+), 55 deletions(-) create mode 100644 src/components/Theme/privateColors/constants.ts create mode 100644 src/components/Theme/privateColors/index.ts create mode 100644 src/components/Theme/privateColors/utils.ts diff --git a/package-lock.json b/package-lock.json index eaf503119263..d75bf519114a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "bem-cn-lite": "^4.1.0", + "chroma-js": "^2.4.2", "husky": "^8.0.3", "i18next": "^23.8.3", "javascript-time-ago": "^2.5.9", @@ -49,6 +50,7 @@ "@gravity-ui/stylelint-config": "^2.0.0", "@gravity-ui/tsconfig": "^1.0.0", "@svgr/webpack": "^6.5.1", + "@types/chroma-js": "^2.4.4", "@types/jest": "^29.2.4", "@types/lodash": "^4.14.197", "@types/micromatch": "^4.0.7", @@ -3657,6 +3659,12 @@ "integrity": "sha512-0WR3b8eaISjEW7RpZnclONaLFDf7buaowRHdqLp4vLj54AsSAYWfh3DRbfiYJY9XDxMgx1B4sE1Afw2PGpuHOA==", "dev": true }, + "node_modules/@types/chroma-js": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/chroma-js/-/chroma-js-2.4.4.tgz", + "integrity": "sha512-/DTccpHTaKomqussrn+ciEvfW4k6NAHzNzs/sts1TCqg333qNxOhy8TNIoQCmbGG3Tl8KdEhkGAssb1n3mTXiQ==", + "dev": true + }, "node_modules/@types/cookie": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", @@ -5180,6 +5188,11 @@ "node": ">= 6" } }, + "node_modules/chroma-js": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-2.4.2.tgz", + "integrity": "sha512-U9eDw6+wt7V8z5NncY2jJfZa+hUH8XEj8FQHgFJTrUFnJfXYf4Ml4adI2vXZOjqRDpFWtYVWypDfZwnJ+HIR4A==" + }, "node_modules/chrome-trace-event": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", diff --git a/package.json b/package.json index a0988c0518e0..ba7386641557 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "bem-cn-lite": "^4.1.0", + "chroma-js": "^2.4.2", "husky": "^8.0.3", "i18next": "^23.8.3", "javascript-time-ago": "^2.5.9", @@ -45,6 +46,7 @@ "@gravity-ui/stylelint-config": "^2.0.0", "@gravity-ui/tsconfig": "^1.0.0", "@svgr/webpack": "^6.5.1", + "@types/chroma-js": "^2.4.4", "@types/jest": "^29.2.4", "@types/lodash": "^4.14.197", "@types/micromatch": "^4.0.7", diff --git a/src/components/Theme/constants.ts b/src/components/Theme/constants.ts index 14e77bb948ae..18066f68afad 100644 --- a/src/components/Theme/constants.ts +++ b/src/components/Theme/constants.ts @@ -22,10 +22,10 @@ export const DEFAULT_PALETTE: ThemeOptions['palette'] = { black: 'rgb(0, 0, 0)', brand: 'rgb(75, 113, 214)', orange: 'rgb(200, 99, 12)', - green: 'rgb(91, 181, 87)', - yellow: 'rgb(255, 203, 0)', - red: 'rgb(232, 73, 69)', - blue: 'rgb(82, 130, 255)', + green: 'rgb(77, 176, 155)', + yellow: 'rgb(255, 190, 92)', + red: 'rgb(229, 50, 93)', + blue: 'rgb(54, 151, 241)', 'cool-grey': 'rgb(96, 128, 156)', purple: 'rgb(143, 82, 204)', }, @@ -37,7 +37,7 @@ export const DEFAULT_THEME: ThemeOptions = { palette: DEFAULT_PALETTE, colors: { light: { - 'base-background': 'private.white.1000-solid', + 'base-background': 'rgb(255,255,255)', 'base-brand': 'private.yellow.550-solid', 'base-brand-hover': 'private.yellow.600-solid', 'base-selection': 'private.yellow.200', @@ -53,18 +53,18 @@ export const DEFAULT_THEME: ThemeOptions = { }, dark: { 'base-background': 'rgb(34, 29, 34)', - 'base-brand': 'private-yellow-550-solid', - 'base-brand-hover': 'private-yellow-650-solid', - 'base-selection': 'private-yellow-150', - 'base-selection-hover': 'private-yellow-200', - 'line-brand': 'private-yellow-600-solid', - 'text-brand': 'private-yellow-600-solid', - 'text-brand-heavy': 'private-yellow-700-solid', - 'text-brand-contrast': 'private-black-900', - 'text-link': 'private-yellow-550-solid', - 'text-link-hover': 'private-orange-550-solid', - 'text-link-visited': 'private-purple-700-solid', - 'text-link-visited-hover': 'private-purple-850-solid', + 'base-brand': 'private.yellow.550-solid', + 'base-brand-hover': 'private.yellow.650-solid', + 'base-selection': 'private.yellow.150', + 'base-selection-hover': 'private.yellow.200', + 'line-brand': 'private.yellow.600-solid', + 'text-brand': 'private.yellow.600-solid', + 'text-brand-heavy': 'private.yellow.700-solid', + 'text-brand-contrast': 'private.black.900', + 'text-link': 'private.yellow.550-solid', + 'text-link-hover': 'private.orange.550-solid', + 'text-link-visited': 'private.purple.700-solid', + 'text-link-visited-hover': 'private.purple.850-solid', }, }, borders: {}, diff --git a/src/components/Theme/privateColors/constants.ts b/src/components/Theme/privateColors/constants.ts new file mode 100644 index 000000000000..16569307cb00 --- /dev/null +++ b/src/components/Theme/privateColors/constants.ts @@ -0,0 +1,289 @@ +export const themeXd = { + light: { + white: { + privateSolidVariables: [], + privateVariables: [ + 950, 900, 850, 800, 750, 700, 650, 600, 550, 500, 450, 400, 350, 300, 250, 200, 150, + 100, 70, 50, + ], + colorsMap: { + 50: {a: 0.05, c: 1}, + 70: {a: 0.07, c: 1}, + 100: {a: 0.1, c: 1}, + 150: {a: 0.15, c: 1}, + 200: {a: 0.2, c: 1}, + 250: {a: 0.25, c: 1}, + 300: {a: 0.3, c: 1}, + 350: {a: 0.35, c: 1}, + 400: {a: 0.4, c: 1}, + 450: {a: 0.45, c: 1}, + 500: {a: 0.5, c: 1}, + 550: {a: 0.55, c: 1}, + 600: {a: 0.6, c: 1}, + 650: {a: 0.65, c: 1}, + 700: {a: 0.7, c: 1}, + 750: {a: 0.75, c: 1}, + 800: {a: 0.8, c: 1}, + 850: {a: 0.85, c: 1}, + 900: {a: 0.9, c: 1}, + 950: {a: 0.95, c: 1}, + 1000: {a: 1, c: 1}, + }, + }, + black: { + privateSolidVariables: [ + 950, 900, 850, 800, 750, 700, 650, 600, 550, 500, 450, 400, 350, 300, 250, 200, 150, + 100, 50, + ], + privateVariables: [ + 950, 900, 850, 800, 750, 700, 650, 600, 550, 500, 450, 400, 350, 300, 250, 200, 150, + 100, 50, + ], + colorsMap: { + 50: {a: 0.05, c: -1}, + 70: {a: 0.07, c: -1}, + 100: {a: 0.1, c: -1}, + 150: {a: 0.15, c: -1}, + 200: {a: 0.2, c: -1}, + 250: {a: 0.25, c: -1}, + 300: {a: 0.3, c: -1}, + 350: {a: 0.35, c: -1}, + 400: {a: 0.4, c: -1}, + 450: {a: 0.45, c: -1}, + 500: {a: 0.5, c: -1}, + 550: {a: 0.55, c: -1}, + 600: {a: 0.6, c: -1}, + 650: {a: 0.65, c: -1}, + 700: {a: 0.7, c: -1}, + 750: {a: 0.75, c: -1}, + 800: {a: 0.8, c: -1}, + 850: {a: 0.85, c: -1}, + 900: {a: 0.9, c: -1}, + 950: {a: 0.95, c: -1}, + 1000: {a: 1, c: -1}, + }, + }, + }, + dark: { + white: { + privateSolidVariables: [ + 950, 900, 850, 800, 750, 700, 650, 600, 550, 500, 450, 400, 350, 300, 250, 200, 150, + 100, 70, 50, + ], + privateVariables: [ + 950, 900, 850, 800, 750, 700, 650, 600, 550, 500, 450, 400, 350, 300, 250, 200, 150, + 100, 70, 50, + ], + colorsMap: { + 50: {a: 0.05, c: -1}, + 70: {a: 0.07, c: -1}, + 100: {a: 0.1, c: -1}, + 150: {a: 0.15, c: -1}, + 200: {a: 0.2, c: -1}, + 250: {a: 0.25, c: -1}, + 300: {a: 0.3, c: -1}, + 350: {a: 0.35, c: -1}, + 400: {a: 0.4, c: -1}, + 450: {a: 0.45, c: -1}, + 500: {a: 0.5, c: -1}, + 550: {a: 0.55, c: -1}, + 600: {a: 0.6, c: -1}, + 650: {a: 0.65, c: -1}, + 700: {a: 0.7, c: -1}, + 750: {a: 0.75, c: -1}, + 800: {a: 0.8, c: -1}, + 850: {a: 0.85, c: -1}, + 900: {a: 0.9, c: -1}, + 950: {a: 0.95, c: -1}, + 1000: {a: 1, c: -1}, + }, + }, + black: { + privateSolidVariables: [], + privateVariables: [ + 950, 900, 850, 800, 750, 700, 650, 600, 550, 500, 450, 400, 350, 300, 250, 200, 150, + 100, 50, 20, + ], + colorsMap: { + 20: {a: 0.02, c: 1}, + 50: {a: 0.05, c: 1}, + 100: {a: 0.1, c: 1}, + 150: {a: 0.15, c: 1}, + 200: {a: 0.2, c: 1}, + 250: {a: 0.25, c: 1}, + 300: {a: 0.3, c: 1}, + 350: {a: 0.35, c: 1}, + 400: {a: 0.4, c: 1}, + 450: {a: 0.45, c: 1}, + 500: {a: 0.5, c: 1}, + 550: {a: 0.55, c: 1}, + 600: {a: 0.6, c: 1}, + 650: {a: 0.65, c: 1}, + 700: {a: 0.7, c: 1}, + 750: {a: 0.75, c: 1}, + 800: {a: 0.8, c: 1}, + 850: {a: 0.85, c: 1}, + 900: {a: 0.9, c: 1}, + 950: {a: 0.95, c: 1}, + 1000: {a: 1, c: 1}, + }, + }, + 'White Opaque': { + privateVariables: [150], + colorsMap: { + 50: {a: 0.05, c: 1}, + 70: {a: 0.07, c: 1}, + 100: {a: 0.1, c: 1}, + 150: {a: 0.15, c: 1}, + 200: {a: 0.2, c: 1}, + 250: {a: 0.25, c: 1}, + 300: {a: 0.3, c: 1}, + 350: {a: 0.35, c: 1}, + 400: {a: 0.4, c: 1}, + 450: {a: 0.45, c: 1}, + 500: {a: 0.5, c: 1}, + 550: {a: 0.55, c: 1}, + 600: {a: 0.6, c: 1}, + 650: {a: 0.65, c: 1}, + 700: {a: 0.7, c: 1}, + 750: {a: 0.75, c: 1}, + 800: {a: 0.8, c: 1}, + 850: {a: 0.85, c: 1}, + 900: {a: 0.9, c: 1}, + 950: {a: 0.95, c: 1}, + 1000: {a: 1, c: 1}, + }, + }, + }, + 'light-hc': { + white: { + privateSolidVariables: [], + privateVariables: [ + 950, 900, 850, 800, 750, 700, 650, 600, 550, 500, 450, 400, 350, 300, 250, 200, 150, + 100, 70, 50, + ], + colorsMap: { + 50: {a: 0.05, c: 1}, + 70: {a: 0.07, c: 1}, + 100: {a: 0.1, c: 1}, + 150: {a: 0.15, c: 1}, + 200: {a: 0.2, c: 1}, + 250: {a: 0.25, c: 1}, + 300: {a: 0.3, c: 1}, + 350: {a: 0.35, c: 1}, + 400: {a: 0.4, c: 1}, + 450: {a: 0.45, c: 1}, + 500: {a: 0.5, c: 1}, + 550: {a: 0.55, c: 1}, + 600: {a: 0.6, c: 1}, + 650: {a: 0.65, c: 1}, + 700: {a: 0.7, c: 1}, + 750: {a: 0.75, c: 1}, + 800: {a: 0.8, c: 1}, + 850: {a: 0.85, c: 1}, + 900: {a: 0.9, c: 1}, + 950: {a: 0.95, c: 1}, + 1000: {a: 1, c: 1}, + }, + }, + Black: { + privateSolidVariables: [ + 950, 900, 850, 800, 750, 700, 650, 600, 550, 500, 450, 400, 350, 300, 250, 200, 150, + 100, 50, 20, + ], + privateVariables: [ + 950, 900, 850, 800, 750, 700, 650, 600, 550, 500, 450, 400, 350, 300, 250, 200, 150, + 100, 70, 50, + ], + colorsMap: { + 20: {a: 0.02, c: 1}, + 50: {a: 0.05, c: 1}, + 70: {a: 0.07, c: 1}, + 100: {a: 0.1, c: 1}, + 150: {a: 0.15, c: 1}, + 200: {a: 0.2, c: 1}, + 250: {a: 0.25, c: 1}, + 300: {a: 0.3, c: 1}, + 350: {a: 0.35, c: 1}, + 400: {a: 0.4, c: 1}, + 450: {a: 0.45, c: 1}, + 500: {a: 0.5, c: 1}, + 550: {a: 0.55, c: 1}, + 600: {a: 0.6, c: 1}, + 650: {a: 0.65, c: 1}, + 700: {a: 0.7, c: 1}, + 750: {a: 0.75, c: 1}, + 800: {a: 0.8, c: 1}, + 850: {a: 0.85, c: 1}, + 900: {a: 0.9, c: 1}, + 950: {a: 0.95, c: 1}, + 1000: {a: 1, c: 1}, + }, + }, + }, + 'dark-hc': { + white: { + privateSolidVariables: [ + 950, 900, 850, 800, 750, 700, 650, 600, 550, 500, 450, 400, 350, 300, 250, 200, 150, + 100, 70, 50, + ], + privateVariables: [ + 950, 900, 850, 800, 750, 700, 650, 600, 550, 500, 450, 400, 350, 300, 250, 200, 150, + 100, 70, 50, + ], + colorsMap: { + 50: {a: 0.05, c: 1}, + 70: {a: 0.07, c: 1}, + 100: {a: 0.1, c: 1}, + 150: {a: 0.15, c: 1}, + 200: {a: 0.2, c: 1}, + 250: {a: 0.25, c: 1}, + 300: {a: 0.3, c: 1}, + 350: {a: 0.35, c: 1}, + 400: {a: 0.4, c: 1}, + 450: {a: 0.45, c: 1}, + 500: {a: 0.5, c: 1}, + 550: {a: 0.55, c: 1}, + 600: {a: 0.6, c: 1}, + 650: {a: 0.65, c: 1}, + 700: {a: 0.7, c: 1}, + 750: {a: 0.75, c: 1}, + 800: {a: 0.8, c: 1}, + 850: {a: 0.85, c: 1}, + 900: {a: 0.9, c: 1}, + 950: {a: 0.95, c: 1}, + 1000: {a: 1, c: 1}, + }, + }, + black: { + privateSolidVariables: [], + privateVariables: [ + 950, 900, 850, 800, 750, 700, 650, 600, 550, 500, 450, 400, 350, 300, 250, 200, 150, + 100, 50, 20, + ], + colorsMap: { + 20: {a: 0.02, c: 1}, + 50: {a: 0.05, c: 1}, + 100: {a: 0.1, c: 1}, + 150: {a: 0.15, c: 1}, + 200: {a: 0.2, c: 1}, + 250: {a: 0.25, c: 1}, + 300: {a: 0.3, c: 1}, + 350: {a: 0.35, c: 1}, + 400: {a: 0.4, c: 1}, + 450: {a: 0.45, c: 1}, + 500: {a: 0.5, c: 1}, + 550: {a: 0.55, c: 1}, + 600: {a: 0.6, c: 1}, + 650: {a: 0.65, c: 1}, + 700: {a: 0.7, c: 1}, + 750: {a: 0.75, c: 1}, + 800: {a: 0.8, c: 1}, + 850: {a: 0.85, c: 1}, + 900: {a: 0.9, c: 1}, + 950: {a: 0.95, c: 1}, + 1000: {a: 1, c: 1}, + }, + }, + }, +}; diff --git a/src/components/Theme/privateColors/index.ts b/src/components/Theme/privateColors/index.ts new file mode 100644 index 000000000000..af32a8c8b0d4 --- /dev/null +++ b/src/components/Theme/privateColors/index.ts @@ -0,0 +1 @@ +export {generatePrivateColors} from './utils'; diff --git a/src/components/Theme/privateColors/utils.ts b/src/components/Theme/privateColors/utils.ts new file mode 100644 index 000000000000..06769cbf6f87 --- /dev/null +++ b/src/components/Theme/privateColors/utils.ts @@ -0,0 +1,98 @@ +import chroma from 'chroma-js'; + +import {themeXd} from './constants'; + +const privateSolidVariables = [ + 1000, 950, 900, 850, 800, 750, 700, 650, 600, 500, 450, 400, 350, 300, 250, 200, 150, 100, 50, +]; +const privateVariables = [500, 450, 400, 350, 300, 250, 200, 150, 100, 50]; +const colorsMap = { + 50: {a: 0.1, c: -1}, + 100: {a: 0.15, c: -1}, + 150: {a: 0.2, c: -1}, + 200: {a: 0.3, c: -1}, + 250: {a: 0.4, c: -1}, + 300: {a: 0.5, c: -1}, + 350: {a: 0.6, c: -1}, + 400: {a: 0.7, c: -1}, + 450: {a: 0.8, c: -1}, + 500: {a: 0.9, c: -1}, + 550: {a: 1, c: 1}, + 600: {a: 0.9, c: 1}, + 650: {a: 0.8, c: 1}, + 700: {a: 0.7, c: 1}, + 750: {a: 0.6, c: 1}, + 800: {a: 0.5, c: 1}, + 850: {a: 0.4, c: 1}, + 900: {a: 0.3, c: 1}, + 950: {a: 0.2, c: 1}, + 1000: {a: 0.15, c: 1}, +}; + +type Theme = 'light' | 'dark'; + +type GeneratePrivateColorsArgs = { + theme: Theme; + colorToken: string; + colorValue: string; + lightBg: string; + darkBg: string; +}; + +export const generatePrivateColors = ({ + theme, + colorToken, + colorValue, + lightBg, + darkBg, +}: GeneratePrivateColorsArgs) => { + const privateColors: Record = {}; + + if (!chroma.valid(colorValue)) { + throw Error('Not valid color for chroma'); + } + + let colorsMapInternal = colorsMap; + + if (colorToken === 'white' || colorToken === 'black') { + colorsMapInternal = themeXd[theme][colorToken].colorsMap; + } + + const pallete = Object.entries(colorsMapInternal).reduce((res, [key, {a, c}]) => { + const solidColor = chroma.mix(colorValue, c > 0 ? darkBg : lightBg, 1 - a, 'rgb').css(); + + const alphaColor = chroma(colorValue).alpha(a).css(); + + res[key] = [solidColor, alphaColor]; + + return res; + }, {} as Record); + + let privateSolidVariablesInternal = privateSolidVariables; + let privateVariablesInternal = privateVariables; + + if (colorToken === 'white' || colorToken === 'black') { + privateSolidVariablesInternal = themeXd[theme][colorToken].privateSolidVariables; + privateVariablesInternal = themeXd[theme][colorToken].privateVariables; + } + + // Set 550 Solid Color + privateColors['550-solid'] = chroma(colorValue).css(); + + // Set 50-1000 Solid Colors, except 550 Solid Color + privateSolidVariablesInternal.forEach((varName) => { + privateColors[`${varName}-solid`] = chroma(pallete[varName][0]).css(); + }); + + // Set 50-500 Colors + privateVariablesInternal.forEach((varName) => { + privateColors[`${varName}`] = chroma(pallete[varName][1]).css(); + }); + + if (theme === 'dark' && colorToken === 'white') { + const updatedColor = chroma(pallete[150][0]).alpha(0.95).css(); + privateColors['white-opaque-150'] = chroma(updatedColor).css(); + } + + return privateColors; +}; diff --git a/src/components/Theme/types.ts b/src/components/Theme/types.ts index 2463c53b3185..50885a7fd27c 100644 --- a/src/components/Theme/types.ts +++ b/src/components/Theme/types.ts @@ -34,14 +34,7 @@ export interface ThemeOptions { typography: TypographyOptions; } -const PRIVATE_COLOR_VARIABLES = [ - 1000, 950, 900, 850, 800, 750, 700, 650, 600, 550, 500, 450, 400, 350, 300, 250, 200, 150, 100, - 50, -] as const; - -type PrivateColorVariable = typeof PRIVATE_COLOR_VARIABLES[number]; - -export type PrivateColors = Record; +export type PrivateColors = Record; type PaletteToken = { /** Title that will using in UI */ diff --git a/src/components/Theme/utils.ts b/src/components/Theme/utils.ts index dbc130c529c8..72e5d83b4880 100644 --- a/src/components/Theme/utils.ts +++ b/src/components/Theme/utils.ts @@ -7,6 +7,7 @@ import { DEFAULT_PALETTE_TOKENS, THEME_COLOR_VARIABLE_PREFIX, } from './constants'; +import {generatePrivateColors} from './privateColors'; import type { Palette, PaletteTokens, @@ -52,32 +53,34 @@ function createNewColorTitle(currentPaletteTokens: PaletteTokens) { } } -function createPrivateColors(solidColor: string): PrivateColors { - return { - 50: '', - 100: '', - 150: '', - 200: '', - 250: '', - 300: '', - 350: '', - 400: '', - 450: '', - 500: '', - 550: solidColor, - 600: '', - 650: '', - 700: '', - 750: '', - 800: '', - 850: '', - 900: '', - 950: '', - 1000: '', - }; +function createPrivateColors({ + themeVariant, + colorToken, + colorValue, + theme, +}: { + colorToken: string; + colorValue: string; + themeVariant: ThemeVariant; + theme: ThemeOptions; +}): PrivateColors { + return generatePrivateColors({ + theme: themeVariant, + colorToken, + colorValue, + lightBg: + themeVariant === 'light' + ? theme.colors.light['base-background'] + : theme.colors.dark['base-background'], + darkBg: + themeVariant === 'dark' + ? theme.colors.dark['base-background'] + : theme.colors.light['base-background'], + }); } -function createPalleteTokens(palette: ThemeOptions['palette']): PaletteTokens { +function createPalleteTokens(theme: ThemeOptions): PaletteTokens { + const {palette} = theme; const tokens = Object.keys(palette.light); return tokens.reduce( @@ -87,10 +90,20 @@ function createPalleteTokens(palette: ThemeOptions['palette']): PaletteTokens { title: createTitleFromToken(token), privateColors: { light: palette.light[token] - ? createPrivateColors(palette.light[token]) + ? createPrivateColors({ + colorToken: token, + colorValue: palette.light[token], + theme, + themeVariant: 'light', + }) : undefined, dark: palette.dark[token] - ? createPrivateColors(palette.dark[token]) + ? createPrivateColors({ + colorToken: token, + colorValue: palette.dark[token], + theme, + themeVariant: 'dark', + }) : undefined, }, }, @@ -138,7 +151,12 @@ export function updateColorInTheme( newThemeState.palette.dark[token] = params.value; } - const privateColors = createPrivateColors(params.value); + const privateColors = createPrivateColors({ + colorToken: token, + colorValue: params.value, + theme: newThemeState, + themeVariant: params.theme, + }); newThemeState.paletteTokens[token] = { ...newThemeState.paletteTokens[token], @@ -205,8 +223,22 @@ export function addColorToTheme( ...newThemeState.paletteTokens[token], title, privateColors: { - light: params?.colors?.light ? createPrivateColors(params.colors.light) : undefined, - dark: params?.colors?.dark ? createPrivateColors(params.colors.dark) : undefined, + light: params?.colors?.light + ? createPrivateColors({ + colorToken: token, + colorValue: params.colors.light, + theme: newThemeState, + themeVariant: 'light', + }) + : undefined, + dark: params?.colors?.dark + ? createPrivateColors({ + colorToken: token, + colorValue: params.colors.dark, + theme: newThemeState, + themeVariant: 'dark', + }) + : undefined, }, isCustom: true, }; @@ -324,7 +356,7 @@ export function getThemePalette(theme: ThemeWizardState): Palette { } export function initThemeWizard(theme: ThemeOptions): ThemeWizardState { - const paletteTokens = createPalleteTokens(theme.palette); + const paletteTokens = createPalleteTokens(theme); return { ...theme, From a73e3d3e62391880561a5eeb0ad42fec2ab6a3cf Mon Sep 17 00:00:00 2001 From: Daniil Gaponov Date: Wed, 12 Jun 2024 14:32:15 +0300 Subject: [PATCH 07/14] feat: add main settings --- .../ColorPickerInput/ColorPickerInput.scss | 2 + .../ColorPickerInput/ColorPickerInput.tsx | 4 +- .../BasicPalette/ThemePaletteCard.scss | 4 +- .../BasicPalette/ThemePaletteCard.tsx | 2 +- .../ColorsTab/MainSettings/MainSettings.scss | 15 ++++++ .../ColorsTab/MainSettings/MainSettings.tsx | 53 +++++++++++++++++++ src/components/Theme/ColorsTab/index.tsx | 14 ++--- src/components/Theme/hooks/useThemeColor.ts | 26 +++++++++ src/components/ThemePicker/ThemePicker.scss | 12 +++++ src/components/ThemePicker/ThemePicker.tsx | 41 ++++++++++++++ src/components/ThemePicker/index.ts | 1 + 11 files changed, 163 insertions(+), 11 deletions(-) create mode 100644 src/components/Theme/ColorsTab/MainSettings/MainSettings.scss create mode 100644 src/components/Theme/ColorsTab/MainSettings/MainSettings.tsx create mode 100644 src/components/Theme/hooks/useThemeColor.ts create mode 100644 src/components/ThemePicker/ThemePicker.scss create mode 100644 src/components/ThemePicker/ThemePicker.tsx create mode 100644 src/components/ThemePicker/index.ts diff --git a/src/components/ColorPickerInput/ColorPickerInput.scss b/src/components/ColorPickerInput/ColorPickerInput.scss index dceed4a3060f..f814eff10d08 100644 --- a/src/components/ColorPickerInput/ColorPickerInput.scss +++ b/src/components/ColorPickerInput/ColorPickerInput.scss @@ -3,6 +3,8 @@ $block: '.#{variables.$ns}color-picker'; #{$block} { + --g-border-radius-xl: 8px; + &__preview { width: 16px; height: 16px; diff --git a/src/components/ColorPickerInput/ColorPickerInput.tsx b/src/components/ColorPickerInput/ColorPickerInput.tsx index 4c52ed5d488f..b57a1cadab0d 100644 --- a/src/components/ColorPickerInput/ColorPickerInput.tsx +++ b/src/components/ColorPickerInput/ColorPickerInput.tsx @@ -18,6 +18,7 @@ export interface ColorPickerInputProps { value?: string; onChange: (color: string) => void; errorMessage?: string; + size?: TextInputProps['size']; } export const ColorPickerInput = ({ @@ -26,6 +27,7 @@ export const ColorPickerInput = ({ onChange: onChangeExternal, defaultValue, errorMessage, + size = 'l', }: ColorPickerInputProps) => { const {t} = useTranslation('component'); @@ -95,7 +97,7 @@ export const ColorPickerInput = ({ errorMessage={errorMessage || t('color-input_validation-format-error')} validationState={validationError} view="normal" - size="l" + size={size} onChange={onChange} startContent={} endContent={ diff --git a/src/components/Theme/ColorsTab/BasicPalette/ThemePaletteCard.scss b/src/components/Theme/ColorsTab/BasicPalette/ThemePaletteCard.scss index 54ff587aafb0..352e8043e507 100644 --- a/src/components/Theme/ColorsTab/BasicPalette/ThemePaletteCard.scss +++ b/src/components/Theme/ColorsTab/BasicPalette/ThemePaletteCard.scss @@ -4,7 +4,7 @@ $block: '.#{variables.$ns}theme-palette-card'; #{$block} { padding: 30px var(--g-spacing-8); - border-radius: var(--g-spacing-4); + border-radius: 16px; border: 1px solid var(--g-color-line-generic); &_light { @@ -22,6 +22,6 @@ $block: '.#{variables.$ns}theme-palette-card'; &__theme-root { --g-color-base-background: transparent; - --g-button-border-radius: var(--g-spacing-2); + --g-button-border-radius: 8px; } } diff --git a/src/components/Theme/ColorsTab/BasicPalette/ThemePaletteCard.tsx b/src/components/Theme/ColorsTab/BasicPalette/ThemePaletteCard.tsx index 4c2af2d60fe3..0a3bc804444e 100644 --- a/src/components/Theme/ColorsTab/BasicPalette/ThemePaletteCard.tsx +++ b/src/components/Theme/ColorsTab/BasicPalette/ThemePaletteCard.tsx @@ -25,7 +25,7 @@ export const ThemePaletteCard: React.FC = ({theme, palett ); return ( - + diff --git a/src/components/Theme/ColorsTab/MainSettings/MainSettings.scss b/src/components/Theme/ColorsTab/MainSettings/MainSettings.scss new file mode 100644 index 000000000000..fb7ee397b525 --- /dev/null +++ b/src/components/Theme/ColorsTab/MainSettings/MainSettings.scss @@ -0,0 +1,15 @@ +@use '../../../../variables.scss'; + +$block: '.#{variables.$ns}main-settings'; + +#{$block} { + &__row { + --gc-form-row-label-width: 400px; + --gc-form-row-field-height: 44px; + margin-block-end: 24px; + + .gc-form-row__right { + width: 400px; + } + } +} diff --git a/src/components/Theme/ColorsTab/MainSettings/MainSettings.tsx b/src/components/Theme/ColorsTab/MainSettings/MainSettings.tsx new file mode 100644 index 000000000000..16cac24ee9d3 --- /dev/null +++ b/src/components/Theme/ColorsTab/MainSettings/MainSettings.tsx @@ -0,0 +1,53 @@ +import {FormRow} from '@gravity-ui/components'; +import {Flex, Text} from '@gravity-ui/uikit'; +import React from 'react'; + +import {block} from '../../../../utils'; +import {ColorPickerInput} from '../../../ColorPickerInput/ColorPickerInput'; +import {ThemePicker} from '../../../ThemePicker'; +import {useThemeColor} from '../../hooks/useThemeColor'; +import type {ThemeVariant} from '../../types'; + +import './MainSettings.scss'; + +const b = block('main-settings'); + +export const MainSettings = () => { + const [theme, setTheme] = React.useState('light'); + const [backgroundColor, setBackgroundColor] = useThemeColor({name: 'base-background', theme}); + const [brandColor, setBrandColor] = useThemeColor({name: 'base-brand', theme}); + + return ( + + +

Custom Brand Palette

+
+ + + Theme
} className={b('row')}> + + + Page Background} + className={b('row')} + > + + + Brand Color} className={b('row')}> + + +
+
+
+ ); +}; diff --git a/src/components/Theme/ColorsTab/index.tsx b/src/components/Theme/ColorsTab/index.tsx index ef2f8c8f3e27..3da5f07ce72b 100644 --- a/src/components/Theme/ColorsTab/index.tsx +++ b/src/components/Theme/ColorsTab/index.tsx @@ -1,17 +1,17 @@ -import {Col, Grid, Row} from '@gravity-ui/page-constructor'; +import {Grid} from '@gravity-ui/page-constructor'; +import {Flex} from '@gravity-ui/uikit'; import React from 'react'; import {BasicPalette} from './BasicPalette/BasicPalette'; +import {MainSettings} from './MainSettings/MainSettings'; export const ColorsTab = () => { return ( - - -

Colors

- -
- + + + +
); }; diff --git a/src/components/Theme/hooks/useThemeColor.ts b/src/components/Theme/hooks/useThemeColor.ts new file mode 100644 index 000000000000..12d1124eb749 --- /dev/null +++ b/src/components/Theme/hooks/useThemeColor.ts @@ -0,0 +1,26 @@ +import React from 'react'; + +import {ThemeCreatorContext} from '../ThemeCreator'; +import type {ColorsOptions, ThemeVariant} from '../types'; + +type UseThemeColorParams = { + name: keyof ColorsOptions; + theme: ThemeVariant; +}; + +export const useThemeColor = ({name, theme}: UseThemeColorParams) => { + const {state, updateState} = React.useContext(ThemeCreatorContext); + + const value = React.useMemo(() => state.colors[theme][name], [name, theme]); + + const updateValue = React.useCallback( + (newValue: string) => { + const newState = {...state}; + newState.colors[theme][name] = newValue; + updateState(newState); + }, + [name, theme, state], + ); + + return [value, updateValue] as const; +}; diff --git a/src/components/ThemePicker/ThemePicker.scss b/src/components/ThemePicker/ThemePicker.scss new file mode 100644 index 000000000000..e958f3212bb6 --- /dev/null +++ b/src/components/ThemePicker/ThemePicker.scss @@ -0,0 +1,12 @@ +@use '~@gravity-ui/page-constructor/styles/variables.scss' as pcVariables; +@use '../../variables.scss'; + +$block: '.#{variables.$ns}theme-picker'; + +#{$block} { + --g-border-radius-xl: 8px; + + &__option { + margin-top: 13px; + } +} diff --git a/src/components/ThemePicker/ThemePicker.tsx b/src/components/ThemePicker/ThemePicker.tsx new file mode 100644 index 000000000000..735c701c89ec --- /dev/null +++ b/src/components/ThemePicker/ThemePicker.tsx @@ -0,0 +1,41 @@ +import {Moon, Sun} from '@gravity-ui/icons'; +import {Flex, Icon, RadioButton, Text} from '@gravity-ui/uikit'; +import React from 'react'; + +import {block} from '../../utils'; + +import './ThemePicker.scss'; + +const b = block('theme-picker'); + +type ThemeVariant = 'light' | 'dark'; + +interface ThemePickerProps { + value: ThemeVariant; + onUpdate: (value: ThemeVariant) => void; +} + +export const ThemePicker: React.FC = ({value, onUpdate}) => { + return ( + className={b()} size="xl" value={value} onUpdate={onUpdate}> + + + Light +
+ } + /> + + + Dark + + } + /> + + ); +}; diff --git a/src/components/ThemePicker/index.ts b/src/components/ThemePicker/index.ts new file mode 100644 index 000000000000..9abdd97d808f --- /dev/null +++ b/src/components/ThemePicker/index.ts @@ -0,0 +1 @@ +export {ThemePicker} from './ThemePicker'; From e2743aed9e4224b7139cf69728cde912362e5e9c Mon Sep 17 00:00:00 2001 From: Daniil Gaponov Date: Thu, 13 Jun 2024 18:39:38 +0300 Subject: [PATCH 08/14] feat: private color select and advanced brand palette --- .../ColorPickerInput/ColorPickerInput.scss | 3 - .../ColorPickerInput/ColorPickerInput.tsx | 4 +- .../ColorPickerInput/ColorPreview.tsx | 15 --- src/components/ColorPreview/ColorPreview.scss | 35 ++++++ src/components/ColorPreview/ColorPreview.tsx | 22 ++++ .../PrivateColorSelect.scss | 14 +++ .../PrivateColorSelect/PrivateColorSelect.tsx | 71 ++++++++++++ .../PrivateColorSelectPopupContent.scss | 45 ++++++++ .../PrivateColorSelectPopupContent.tsx | 105 ++++++++++++++++++ src/components/PrivateColorSelect/types.ts | 9 ++ .../AdvancedBrandPalette.scss | 15 +++ .../AdvancedBrandPalette.tsx | 81 ++++++++++++++ .../ColorsTab/BasicPalette/BasicPalette.tsx | 15 ++- .../ColorsTab/MainSettings/MainSettings.tsx | 3 +- src/components/Theme/ColorsTab/index.tsx | 2 + src/components/Theme/constants.ts | 14 +-- src/components/Theme/hooks/useThemeColor.ts | 11 +- src/components/Theme/hooks/useThemeCreator.ts | 8 ++ .../Theme/hooks/useThemePaletteColor.ts | 30 +++++ src/components/Theme/types.ts | 1 - src/components/Theme/utils.ts | 51 +++++++-- 21 files changed, 513 insertions(+), 41 deletions(-) delete mode 100644 src/components/ColorPickerInput/ColorPreview.tsx create mode 100644 src/components/ColorPreview/ColorPreview.scss create mode 100644 src/components/ColorPreview/ColorPreview.tsx create mode 100644 src/components/PrivateColorSelect/PrivateColorSelect.scss create mode 100644 src/components/PrivateColorSelect/PrivateColorSelect.tsx create mode 100644 src/components/PrivateColorSelect/PrivateColorSelectPopupContent.scss create mode 100644 src/components/PrivateColorSelect/PrivateColorSelectPopupContent.tsx create mode 100644 src/components/PrivateColorSelect/types.ts create mode 100644 src/components/Theme/ColorsTab/AdvancedBrandPalette/AdvancedBrandPalette.scss create mode 100644 src/components/Theme/ColorsTab/AdvancedBrandPalette/AdvancedBrandPalette.tsx create mode 100644 src/components/Theme/hooks/useThemePaletteColor.ts diff --git a/src/components/ColorPickerInput/ColorPickerInput.scss b/src/components/ColorPickerInput/ColorPickerInput.scss index f814eff10d08..d47a3c473a50 100644 --- a/src/components/ColorPickerInput/ColorPickerInput.scss +++ b/src/components/ColorPickerInput/ColorPickerInput.scss @@ -6,11 +6,8 @@ $block: '.#{variables.$ns}color-picker'; --g-border-radius-xl: 8px; &__preview { - width: 16px; - height: 16px; margin-inline-start: var(--g-spacing-2); margin-inline-end: var(--g-spacing-1); - border-radius: var(--g-border-radius-xs); } &__input { diff --git a/src/components/ColorPickerInput/ColorPickerInput.tsx b/src/components/ColorPickerInput/ColorPickerInput.tsx index b57a1cadab0d..5b7a2ef03087 100644 --- a/src/components/ColorPickerInput/ColorPickerInput.tsx +++ b/src/components/ColorPickerInput/ColorPickerInput.tsx @@ -4,9 +4,9 @@ import {useTranslation} from 'next-i18next'; import React, {ChangeEventHandler, useCallback, useRef, useState} from 'react'; import {block} from '../../utils'; +import {ColorPreview} from '../ColorPreview/ColorPreview'; import './ColorPickerInput.scss'; -import {ColorPreview} from './ColorPreview'; import {NativeColorPicker} from './NativeColorPicker'; import {hexRegexp, parseRgbStringToHex, rgbRegexp, rgbaRegexp} from './utils'; @@ -99,7 +99,7 @@ export const ColorPickerInput = ({ view="normal" size={size} onChange={onChange} - startContent={} + startContent={} endContent={ + } + controlProps={{ + readOnly: true, + }} + onFocus={togglePopup} + /> + + + + + + ); +}; diff --git a/src/components/PrivateColorSelect/PrivateColorSelectPopupContent.scss b/src/components/PrivateColorSelect/PrivateColorSelectPopupContent.scss new file mode 100644 index 000000000000..8c9cab62b8b4 --- /dev/null +++ b/src/components/PrivateColorSelect/PrivateColorSelectPopupContent.scss @@ -0,0 +1,45 @@ +@use '~@gravity-ui/page-constructor/styles/variables.scss' as pcVariables; +@use '../../variables.scss'; + +$block: '.#{variables.$ns}private-colors-select-popup'; + +#{$block} { + display: flex; + width: 465px; + height: 472px; + border-radius: 8px; + + background-color: #383438; + + &__left, + &__right { + padding: var(--g-spacing-2) var(--g-spacing-2) 0; + height: 100%; + overflow: auto; + } + + &__left { + min-width: 150px; + border-right: 1px solid var(--g-color-line-generic); + } + + &__right { + flex-grow: 1; + } + + &__colors-list { + &-item { + --g-color-base-selection: rgba(255, 190, 92, 0.2); + border-radius: var(--g-border-radius-m); + cursor: pointer; + margin-bottom: 2px; + } + } + + &__color-item { + display: flex; + align-items: center; + gap: var(--g-spacing-1); + padding: 5px var(--g-spacing-2); + } +} diff --git a/src/components/PrivateColorSelect/PrivateColorSelectPopupContent.tsx b/src/components/PrivateColorSelect/PrivateColorSelectPopupContent.tsx new file mode 100644 index 000000000000..32f2541da3c3 --- /dev/null +++ b/src/components/PrivateColorSelect/PrivateColorSelectPopupContent.tsx @@ -0,0 +1,105 @@ +import {List, Text} from '@gravity-ui/uikit'; +import React from 'react'; + +import {block} from '../../utils'; +import {ColorPreview} from '../ColorPreview/ColorPreview'; +import {parsePrivateColorToken} from '../Theme/utils'; + +import './PrivateColorSelectPopupContent.scss'; +import type {BaseColor, ColorGroup} from './types'; + +const b = block('private-colors-select-popup'); + +type ColorItemProps = { + color: string; + title: string; +}; + +const ColorItem: React.FC = ({title, color}) => { + return ( +
+ + {title} +
+ ); +}; + +interface ColorsListProps { + colors: BaseColor[]; + value?: string; + onSelect: (value: string) => void; +} + +const ColorsList: React.FC = ({colors, value, onSelect}) => { + const selectedIndex = React.useMemo( + () => colors.findIndex((item) => item.token === value), + [colors, value], + ); + + const handleSelect = React.useCallback( + (item: BaseColor) => { + onSelect(item.token); + }, + [onSelect], + ); + + const renderItem = React.useCallback( + (item: BaseColor) => , + [], + ); + + return ( + + items={colors} + filterable={false} + virtualized={false} + selectedItemIndex={selectedIndex} + onItemClick={handleSelect} + renderItem={renderItem} + className={b('colors-list')} + itemClassName={b('colors-list-item')} + /> + ); +}; + +interface PrivateColorSelectPopupContentProps { + groups: ColorGroup[]; + value?: string; + onChange: (token: string) => void; +} + +export const PrivateColorSelectPopupContent: React.FC = ({ + value, + groups, + onChange, +}) => { + const [currentGroupToken, setCurrentGroupToken] = React.useState(() => + value ? parsePrivateColorToken(value)?.mainColorToken : undefined, + ); + + React.useEffect(() => { + const mainColorToken = value ? parsePrivateColorToken(value)?.mainColorToken : undefined; + + if (mainColorToken) { + setCurrentGroupToken(mainColorToken); + } + }, [value]); + + const groupToken = currentGroupToken || groups[0].token; + + const groupPrivateColors = React.useMemo( + () => groups.find(({token}) => token === groupToken)?.privateColors || [], + [groups, groupToken], + ); + + return ( +
+
+ +
+
+ +
+
+ ); +}; diff --git a/src/components/PrivateColorSelect/types.ts b/src/components/PrivateColorSelect/types.ts new file mode 100644 index 000000000000..920d8052f6bd --- /dev/null +++ b/src/components/PrivateColorSelect/types.ts @@ -0,0 +1,9 @@ +export type BaseColor = { + token: string; + title: string; + color: string; +}; + +export type ColorGroup = BaseColor & { + privateColors: BaseColor[]; +}; diff --git a/src/components/Theme/ColorsTab/AdvancedBrandPalette/AdvancedBrandPalette.scss b/src/components/Theme/ColorsTab/AdvancedBrandPalette/AdvancedBrandPalette.scss new file mode 100644 index 000000000000..65de195d6e36 --- /dev/null +++ b/src/components/Theme/ColorsTab/AdvancedBrandPalette/AdvancedBrandPalette.scss @@ -0,0 +1,15 @@ +@use '../../../../variables.scss'; + +$block: '.#{variables.$ns}advanced-brand-palette'; + +#{$block} { + &__row { + --gc-form-row-label-width: 400px; + --gc-form-row-field-height: 44px; + margin-block-end: 24px; + + .gc-form-row__right { + width: 400px; + } + } +} diff --git a/src/components/Theme/ColorsTab/AdvancedBrandPalette/AdvancedBrandPalette.tsx b/src/components/Theme/ColorsTab/AdvancedBrandPalette/AdvancedBrandPalette.tsx new file mode 100644 index 000000000000..ab06aafbb726 --- /dev/null +++ b/src/components/Theme/ColorsTab/AdvancedBrandPalette/AdvancedBrandPalette.tsx @@ -0,0 +1,81 @@ +import {FormRow} from '@gravity-ui/components'; +import {Flex, Text} from '@gravity-ui/uikit'; +import React from 'react'; + +import {block} from '../../../../utils'; +import {PrivateColorSelect} from '../../../PrivateColorSelect/PrivateColorSelect'; +import {ThemePicker} from '../../../ThemePicker'; +import {useThemeColor} from '../../hooks/useThemeColor'; +import {useThemeCreator} from '../../hooks/useThemeCreator'; +import type {ThemeVariant} from '../../types'; + +import './AdvancedBrandPalette.scss'; + +const b = block('advanced-brand-palette'); + +export const AdvancedBrandPalette = () => { + const [theme, setTheme] = React.useState('light'); + const {getThemePrivateColorOptions} = useThemeCreator(); + + const colorGroups = React.useMemo( + () => getThemePrivateColorOptions(theme), + [getThemePrivateColorOptions, theme], + ); + + const [hoveredBrandColor, setHoveredBrandColor] = useThemeColor({ + name: 'base-brand-hover', + theme, + }); + + const [textBrandColor, setTextBrandColor] = useThemeColor({ + name: 'text-brand', + theme, + }); + + const [lineBrandColor, setLineBrandColor] = useThemeColor({ + name: 'line-brand', + theme, + }); + + return ( + + +

Advanced Brand Palette

+
+ + + Theme} className={b('row')}> + + + Hovered Brand Color} + className={b('row')} + > + + + Brand Text} className={b('row')}> + + + Brand Line Color} + className={b('row')} + > + + + + +
+ ); +}; diff --git a/src/components/Theme/ColorsTab/BasicPalette/BasicPalette.tsx b/src/components/Theme/ColorsTab/BasicPalette/BasicPalette.tsx index 829cdee16817..40d1fe815e50 100644 --- a/src/components/Theme/ColorsTab/BasicPalette/BasicPalette.tsx +++ b/src/components/Theme/ColorsTab/BasicPalette/BasicPalette.tsx @@ -6,8 +6,21 @@ import {useThemeCreator} from '../../hooks/useThemeCreator'; import {PaletteColors} from './PaletteColors'; import {ThemePaletteCard} from './ThemePaletteCard'; +const hiddenColors = new Set(['white', 'black', 'brand']); + export const BasicPalette = () => { - const {palette, addColor, removeColor, updateColor, renameColor} = useThemeCreator(); + const { + palette: origPalette, + addColor, + removeColor, + updateColor, + renameColor, + } = useThemeCreator(); + + const palette = React.useMemo( + () => origPalette.filter(({title}) => !hiddenColors.has(title.toLowerCase())), + [origPalette], + ); return ( diff --git a/src/components/Theme/ColorsTab/MainSettings/MainSettings.tsx b/src/components/Theme/ColorsTab/MainSettings/MainSettings.tsx index 16cac24ee9d3..d2157a14c7aa 100644 --- a/src/components/Theme/ColorsTab/MainSettings/MainSettings.tsx +++ b/src/components/Theme/ColorsTab/MainSettings/MainSettings.tsx @@ -6,6 +6,7 @@ import {block} from '../../../../utils'; import {ColorPickerInput} from '../../../ColorPickerInput/ColorPickerInput'; import {ThemePicker} from '../../../ThemePicker'; import {useThemeColor} from '../../hooks/useThemeColor'; +import {useThemePaletteColor} from '../../hooks/useThemePaletteColor'; import type {ThemeVariant} from '../../types'; import './MainSettings.scss'; @@ -15,7 +16,7 @@ const b = block('main-settings'); export const MainSettings = () => { const [theme, setTheme] = React.useState('light'); const [backgroundColor, setBackgroundColor] = useThemeColor({name: 'base-background', theme}); - const [brandColor, setBrandColor] = useThemeColor({name: 'base-brand', theme}); + const [brandColor, setBrandColor] = useThemePaletteColor({token: 'brand', theme}); return ( diff --git a/src/components/Theme/ColorsTab/index.tsx b/src/components/Theme/ColorsTab/index.tsx index 3da5f07ce72b..7c76645845b7 100644 --- a/src/components/Theme/ColorsTab/index.tsx +++ b/src/components/Theme/ColorsTab/index.tsx @@ -2,6 +2,7 @@ import {Grid} from '@gravity-ui/page-constructor'; import {Flex} from '@gravity-ui/uikit'; import React from 'react'; +import {AdvancedBrandPalette} from './AdvancedBrandPalette/AdvancedBrandPalette'; import {BasicPalette} from './BasicPalette/BasicPalette'; import {MainSettings} from './MainSettings/MainSettings'; @@ -11,6 +12,7 @@ export const ColorsTab = () => { + ); diff --git a/src/components/Theme/constants.ts b/src/components/Theme/constants.ts index 18066f68afad..7ce55e5660e5 100644 --- a/src/components/Theme/constants.ts +++ b/src/components/Theme/constants.ts @@ -22,10 +22,10 @@ export const DEFAULT_PALETTE: ThemeOptions['palette'] = { black: 'rgb(0, 0, 0)', brand: 'rgb(75, 113, 214)', orange: 'rgb(200, 99, 12)', - green: 'rgb(77, 176, 155)', - yellow: 'rgb(255, 190, 92)', - red: 'rgb(229, 50, 93)', - blue: 'rgb(54, 151, 241)', + green: 'rgb(91, 181, 87)', + yellow: 'rgb(255, 203, 0)', + red: 'rgb(232, 73, 69)', + blue: 'rgb(82, 130, 255)', 'cool-grey': 'rgb(96, 128, 156)', purple: 'rgb(143, 82, 204)', }, @@ -38,8 +38,7 @@ export const DEFAULT_THEME: ThemeOptions = { colors: { light: { 'base-background': 'rgb(255,255,255)', - 'base-brand': 'private.yellow.550-solid', - 'base-brand-hover': 'private.yellow.600-solid', + 'base-brand-hover': 'private.brand.600-solid', 'base-selection': 'private.yellow.200', 'base-selection-hover': 'private.yellow.300', 'line-brand': 'private.yellow.600-solid', @@ -53,8 +52,7 @@ export const DEFAULT_THEME: ThemeOptions = { }, dark: { 'base-background': 'rgb(34, 29, 34)', - 'base-brand': 'private.yellow.550-solid', - 'base-brand-hover': 'private.yellow.650-solid', + 'base-brand-hover': 'private.brand.650-solid', 'base-selection': 'private.yellow.150', 'base-selection-hover': 'private.yellow.200', 'line-brand': 'private.yellow.600-solid', diff --git a/src/components/Theme/hooks/useThemeColor.ts b/src/components/Theme/hooks/useThemeColor.ts index 12d1124eb749..ea7283b4c724 100644 --- a/src/components/Theme/hooks/useThemeColor.ts +++ b/src/components/Theme/hooks/useThemeColor.ts @@ -2,6 +2,7 @@ import React from 'react'; import {ThemeCreatorContext} from '../ThemeCreator'; import type {ColorsOptions, ThemeVariant} from '../types'; +import {changeColorInTheme} from '../utils'; type UseThemeColorParams = { name: keyof ColorsOptions; @@ -11,12 +12,16 @@ type UseThemeColorParams = { export const useThemeColor = ({name, theme}: UseThemeColorParams) => { const {state, updateState} = React.useContext(ThemeCreatorContext); - const value = React.useMemo(() => state.colors[theme][name], [name, theme]); + const value = React.useMemo(() => state.colors[theme][name], [state, name, theme]); const updateValue = React.useCallback( (newValue: string) => { - const newState = {...state}; - newState.colors[theme][name] = newValue; + const newState = changeColorInTheme({ + themeState: state, + themeVariant: theme, + name, + value: newValue, + }); updateState(newState); }, [name, theme, state], diff --git a/src/components/Theme/hooks/useThemeCreator.ts b/src/components/Theme/hooks/useThemeCreator.ts index 8b5debf7d1eb..cc112489832f 100644 --- a/src/components/Theme/hooks/useThemeCreator.ts +++ b/src/components/Theme/hooks/useThemeCreator.ts @@ -1,8 +1,10 @@ import React from 'react'; import {ThemeCreatorContext} from '../ThemeCreator'; +import {ThemeVariant} from '../types'; import { addColorToTheme, + getThemeColorOptions, getThemePalette, removeColorFromTheme, renameColorInTheme, @@ -47,11 +49,17 @@ export const useThemeCreator = () => { const palette = React.useMemo(() => getThemePalette(state), [state]); + const getThemePrivateColorOptions = React.useCallback( + (themeVariant: ThemeVariant) => getThemeColorOptions({themeState: state, themeVariant}), + [state], + ); + return { addColor, updateColor, removeColor, renameColor, palette, + getThemePrivateColorOptions, }; }; diff --git a/src/components/Theme/hooks/useThemePaletteColor.ts b/src/components/Theme/hooks/useThemePaletteColor.ts new file mode 100644 index 000000000000..e5b5ddb7efe8 --- /dev/null +++ b/src/components/Theme/hooks/useThemePaletteColor.ts @@ -0,0 +1,30 @@ +import React from 'react'; + +import {ThemeCreatorContext} from '../ThemeCreator'; +import type {ThemeVariant} from '../types'; +import {updateColorInTheme} from '../utils'; + +type UseThemePaletteColorParams = { + token: string; + theme: ThemeVariant; +}; + +export const useThemePaletteColor = ({token, theme}: UseThemePaletteColorParams) => { + const {state, updateState} = React.useContext(ThemeCreatorContext); + + const value = React.useMemo(() => state.palette[theme][token], [state, token, theme]); + + const updateValue = React.useCallback( + (newValue: string) => { + const newState = updateColorInTheme(state, { + theme, + title: token, + value: newValue, + }); + updateState(newState); + }, + [token, theme, state], + ); + + return [value, updateValue] as const; +}; diff --git a/src/components/Theme/types.ts b/src/components/Theme/types.ts index 50885a7fd27c..a4674f1390b3 100644 --- a/src/components/Theme/types.ts +++ b/src/components/Theme/types.ts @@ -7,7 +7,6 @@ export type PaletteOptions = { export type ColorsOptions = { 'base-background': string; - 'base-brand': string; 'base-brand-hover': string; 'base-selection': string; 'base-selection-hover': string; diff --git a/src/components/Theme/utils.ts b/src/components/Theme/utils.ts index 72e5d83b4880..12f1fe7191f5 100644 --- a/src/components/Theme/utils.ts +++ b/src/components/Theme/utils.ts @@ -9,6 +9,7 @@ import { } from './constants'; import {generatePrivateColors} from './privateColors'; import type { + ColorsOptions, Palette, PaletteTokens, PrivateColors, @@ -29,6 +30,19 @@ function createPrivateColorToken(mainColorToken: string, privateColorCode: strin return `private.${mainColorToken}.${privateColorCode}`; } +export function parsePrivateColorToken(privateColorToken: string) { + const parts = privateColorToken.split('.'); + + if (parts.length !== 3 || parts[0] !== 'private') { + return undefined; + } + + return { + mainColorToken: parts[1], + privateColorCode: parts[2], + }; +} + function createPrivateColorTitle(mainColorToken: string, privateColorCode: string) { return `${THEME_COLOR_VARIABLE_PREFIX}-${mainColorToken}-${privateColorCode}`; } @@ -296,10 +310,11 @@ export function renameColorInTheme( export type ThemeColorOption = { token: string; title: string; + color: string; privateColors: { token: string; title: string; - value: string; + color: string; }[]; }; @@ -312,27 +327,28 @@ export type ThemeColorOption = { * @returns {ThemeColorOption[]} The generated theme color options. */ export function getThemeColorOptions({ - paletteTokens, + themeState, themeVariant, }: { - paletteTokens: PaletteTokens; + themeState: ThemeWizardState; themeVariant: ThemeVariant; }) { - const colorTokens = Object.keys(paletteTokens); + const {tokens, paletteTokens, palette} = themeState; - return colorTokens.reduce((acc, token) => { + return tokens.reduce((acc, token) => { if (paletteTokens[token]?.privateColors[themeVariant]) { return [ ...acc, { token, + color: palette[themeVariant][token], title: paletteTokens[token].title, privateColors: Object.entries( paletteTokens[token].privateColors[themeVariant]!, - ).map(([privateColorCode, value]) => ({ + ).map(([privateColorCode, color]) => ({ token: createPrivateColorToken(token, privateColorCode), title: createPrivateColorTitle(token, privateColorCode), - value, + color, })), }, ]; @@ -342,6 +358,27 @@ export function getThemeColorOptions({ }, []); } +export function changeColorInTheme({ + themeState, + themeVariant, + name, + value, +}: { + themeState: ThemeWizardState; + themeVariant: ThemeVariant; + name: keyof ColorsOptions; + value: string; +}): ThemeWizardState { + const newState = {...themeState}; + newState.colors[themeVariant][name] = value; + + if (name === 'base-background') { + newState.paletteTokens = createPalleteTokens(newState); + } + + return newState; +} + export function getThemePalette(theme: ThemeWizardState): Palette { return theme.tokens.map((token) => { return { From 03fabf6bda47cee39e781d27308e028c3e95ed87 Mon Sep 17 00:00:00 2001 From: Daniil Gaponov Date: Thu, 13 Jun 2024 22:38:07 +0300 Subject: [PATCH 09/14] feat: advanced mode switch --- .../AdvancedBrandPalette.tsx | 81 ------------------- .../ColorsTab/MainSettings/MainSettings.scss | 7 ++ .../ColorsTab/MainSettings/MainSettings.tsx | 22 ++++- .../PrivateColorsSettings.scss} | 2 +- .../PrivateColorsSettings.tsx | 78 ++++++++++++++++++ src/components/Theme/ColorsTab/index.tsx | 76 ++++++++++++++++- 6 files changed, 178 insertions(+), 88 deletions(-) delete mode 100644 src/components/Theme/ColorsTab/AdvancedBrandPalette/AdvancedBrandPalette.tsx rename src/components/Theme/ColorsTab/{AdvancedBrandPalette/AdvancedBrandPalette.scss => PrivateColorsSettings/PrivateColorsSettings.scss} (82%) create mode 100644 src/components/Theme/ColorsTab/PrivateColorsSettings/PrivateColorsSettings.tsx diff --git a/src/components/Theme/ColorsTab/AdvancedBrandPalette/AdvancedBrandPalette.tsx b/src/components/Theme/ColorsTab/AdvancedBrandPalette/AdvancedBrandPalette.tsx deleted file mode 100644 index ab06aafbb726..000000000000 --- a/src/components/Theme/ColorsTab/AdvancedBrandPalette/AdvancedBrandPalette.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import {FormRow} from '@gravity-ui/components'; -import {Flex, Text} from '@gravity-ui/uikit'; -import React from 'react'; - -import {block} from '../../../../utils'; -import {PrivateColorSelect} from '../../../PrivateColorSelect/PrivateColorSelect'; -import {ThemePicker} from '../../../ThemePicker'; -import {useThemeColor} from '../../hooks/useThemeColor'; -import {useThemeCreator} from '../../hooks/useThemeCreator'; -import type {ThemeVariant} from '../../types'; - -import './AdvancedBrandPalette.scss'; - -const b = block('advanced-brand-palette'); - -export const AdvancedBrandPalette = () => { - const [theme, setTheme] = React.useState('light'); - const {getThemePrivateColorOptions} = useThemeCreator(); - - const colorGroups = React.useMemo( - () => getThemePrivateColorOptions(theme), - [getThemePrivateColorOptions, theme], - ); - - const [hoveredBrandColor, setHoveredBrandColor] = useThemeColor({ - name: 'base-brand-hover', - theme, - }); - - const [textBrandColor, setTextBrandColor] = useThemeColor({ - name: 'text-brand', - theme, - }); - - const [lineBrandColor, setLineBrandColor] = useThemeColor({ - name: 'line-brand', - theme, - }); - - return ( - - -

Advanced Brand Palette

-
- - - Theme} className={b('row')}> - - - Hovered Brand Color} - className={b('row')} - > - - - Brand Text} className={b('row')}> - - - Brand Line Color} - className={b('row')} - > - - - - -
- ); -}; diff --git a/src/components/Theme/ColorsTab/MainSettings/MainSettings.scss b/src/components/Theme/ColorsTab/MainSettings/MainSettings.scss index fb7ee397b525..615f8313f0e5 100644 --- a/src/components/Theme/ColorsTab/MainSettings/MainSettings.scss +++ b/src/components/Theme/ColorsTab/MainSettings/MainSettings.scss @@ -3,6 +3,9 @@ $block: '.#{variables.$ns}main-settings'; #{$block} { + --g-button-border-radius: var(--g-spacing-2); + --g-text-input-border-radius: var(--g-spacing-2); + &__row { --gc-form-row-label-width: 400px; --gc-form-row-field-height: 44px; @@ -12,4 +15,8 @@ $block: '.#{variables.$ns}main-settings'; width: 400px; } } + + &__switch-button { + width: min-content; + } } diff --git a/src/components/Theme/ColorsTab/MainSettings/MainSettings.tsx b/src/components/Theme/ColorsTab/MainSettings/MainSettings.tsx index d2157a14c7aa..f73184ddb737 100644 --- a/src/components/Theme/ColorsTab/MainSettings/MainSettings.tsx +++ b/src/components/Theme/ColorsTab/MainSettings/MainSettings.tsx @@ -1,5 +1,6 @@ import {FormRow} from '@gravity-ui/components'; -import {Flex, Text} from '@gravity-ui/uikit'; +import {Sliders} from '@gravity-ui/icons'; +import {Button, Flex, Icon, Text} from '@gravity-ui/uikit'; import React from 'react'; import {block} from '../../../../utils'; @@ -13,7 +14,15 @@ import './MainSettings.scss'; const b = block('main-settings'); -export const MainSettings = () => { +interface MainSettingsProps { + advancedModeEnabled: boolean; + toggleAdvancedMode: () => void; +} + +export const MainSettings: React.FC = ({ + advancedModeEnabled, + toggleAdvancedMode, +}) => { const [theme, setTheme] = React.useState('light'); const [backgroundColor, setBackgroundColor] = useThemeColor({name: 'base-background', theme}); const [brandColor, setBrandColor] = useThemePaletteColor({token: 'brand', theme}); @@ -47,6 +56,15 @@ export const MainSettings = () => { size="xl" /> +
diff --git a/src/components/Theme/ColorsTab/AdvancedBrandPalette/AdvancedBrandPalette.scss b/src/components/Theme/ColorsTab/PrivateColorsSettings/PrivateColorsSettings.scss similarity index 82% rename from src/components/Theme/ColorsTab/AdvancedBrandPalette/AdvancedBrandPalette.scss rename to src/components/Theme/ColorsTab/PrivateColorsSettings/PrivateColorsSettings.scss index 65de195d6e36..2265f1adce1e 100644 --- a/src/components/Theme/ColorsTab/AdvancedBrandPalette/AdvancedBrandPalette.scss +++ b/src/components/Theme/ColorsTab/PrivateColorsSettings/PrivateColorsSettings.scss @@ -1,6 +1,6 @@ @use '../../../../variables.scss'; -$block: '.#{variables.$ns}advanced-brand-palette'; +$block: '.#{variables.$ns}private-colors-settings'; #{$block} { &__row { diff --git a/src/components/Theme/ColorsTab/PrivateColorsSettings/PrivateColorsSettings.tsx b/src/components/Theme/ColorsTab/PrivateColorsSettings/PrivateColorsSettings.tsx new file mode 100644 index 000000000000..31ee28a1deb5 --- /dev/null +++ b/src/components/Theme/ColorsTab/PrivateColorsSettings/PrivateColorsSettings.tsx @@ -0,0 +1,78 @@ +import {FormRow} from '@gravity-ui/components'; +import {Flex, Text} from '@gravity-ui/uikit'; +import React from 'react'; + +import {block} from '../../../../utils'; +import {PrivateColorSelect} from '../../../PrivateColorSelect/PrivateColorSelect'; +import {ThemePicker} from '../../../ThemePicker'; +import {useThemeColor} from '../../hooks/useThemeColor'; +import {useThemeCreator} from '../../hooks/useThemeCreator'; +import type {ColorsOptions, ThemeVariant} from '../../types'; +import {ThemeColorOption} from '../../utils'; + +import './PrivateColorsSettings.scss'; + +const b = block('private-colors-settings'); + +interface PrivateColorEditorProps { + name: keyof ColorsOptions; + theme: ThemeVariant; + colorGroups: ThemeColorOption[]; +} + +const PrivateColorEditor: React.FC = ({name, theme, colorGroups}) => { + const [color, setColor] = useThemeColor({ + name, + theme, + }); + + return ; +}; + +export type EditableColorOption = { + title: string; + name: keyof ColorsOptions; +}; + +interface PrivateColorsSettingsProps { + title: string; + options: EditableColorOption[]; +} + +export const PrivateColorsSettings: React.FC = ({title, options}) => { + const [theme, setTheme] = React.useState('light'); + const {getThemePrivateColorOptions} = useThemeCreator(); + + const colorGroups = React.useMemo( + () => getThemePrivateColorOptions(theme), + [getThemePrivateColorOptions, theme], + ); + + return ( + + +

{title}

+
+ + + Theme} className={b('row')}> + + + {options.map((option) => ( + {option.title}} + className={b('row')} + > + + + ))} + + +
+ ); +}; diff --git a/src/components/Theme/ColorsTab/index.tsx b/src/components/Theme/ColorsTab/index.tsx index 7c76645845b7..cecbc6296c43 100644 --- a/src/components/Theme/ColorsTab/index.tsx +++ b/src/components/Theme/ColorsTab/index.tsx @@ -2,17 +2,85 @@ import {Grid} from '@gravity-ui/page-constructor'; import {Flex} from '@gravity-ui/uikit'; import React from 'react'; -import {AdvancedBrandPalette} from './AdvancedBrandPalette/AdvancedBrandPalette'; import {BasicPalette} from './BasicPalette/BasicPalette'; import {MainSettings} from './MainSettings/MainSettings'; +import { + EditableColorOption, + PrivateColorsSettings, +} from './PrivateColorsSettings/PrivateColorsSettings'; + +const ADVANCED_COLORS_OPTIONS: EditableColorOption[] = [ + { + title: 'Hovered Brand Color', + name: 'base-brand-hover', + }, + { + title: 'Brand Text', + name: 'text-brand', + }, + { + title: 'Higher Contrast Brand Text', + name: 'text-brand-contrast', + }, + { + title: 'Brand Line Color', + name: 'line-brand', + }, + { + title: 'Selection Background', + name: 'base-selection', + }, + { + title: 'Hovered Selection Background', + name: 'base-selection-hover', + }, +]; + +const ADDITIONAL_COLORS_OPTIONS: EditableColorOption[] = [ + { + title: 'Link', + name: 'text-link', + }, + { + title: 'Hovered Link', + name: 'text-link-hover', + }, + { + title: 'Visited Link', + name: 'text-link-visited', + }, + { + title: 'Hovered Visited Link', + name: 'text-link-visited-hover', + }, +]; export const ColorsTab = () => { + const [advancedModeEnabled, toggleAdvancedMode] = React.useReducer( + (enabled) => !enabled, + false, + ); + return ( - - - + + {advancedModeEnabled && ( + + + + + + )} ); From 9a0414d59b0665161bcd59bcbc2022893c69158f Mon Sep 17 00:00:00 2001 From: Daniil Gaponov Date: Fri, 14 Jun 2024 10:45:23 +0300 Subject: [PATCH 10/14] feat: improve private color editor --- .../ColorPickerInput/ColorPickerInput.scss | 1 + .../ColorPickerInput/ColorPickerInput.tsx | 4 + .../PrivateColorSelect.scss | 5 + .../PrivateColorSelect/PrivateColorSelect.tsx | 101 ++++++++++++++---- .../PrivateColorSelectPopupContent.tsx | 11 +- .../BasicPalette/ThemePaletteCard.scss | 4 - .../BasicPalette/ThemePaletteCard.tsx | 10 +- .../PrivateColorsSettings.tsx | 10 +- src/components/Theme/utils.ts | 20 +++- 9 files changed, 134 insertions(+), 32 deletions(-) diff --git a/src/components/ColorPickerInput/ColorPickerInput.scss b/src/components/ColorPickerInput/ColorPickerInput.scss index d47a3c473a50..a42d8a7ef183 100644 --- a/src/components/ColorPickerInput/ColorPickerInput.scss +++ b/src/components/ColorPickerInput/ColorPickerInput.scss @@ -4,6 +4,7 @@ $block: '.#{variables.$ns}color-picker'; #{$block} { --g-border-radius-xl: 8px; + flex-grow: 1; &__preview { margin-inline-start: var(--g-spacing-2); diff --git a/src/components/ColorPickerInput/ColorPickerInput.tsx b/src/components/ColorPickerInput/ColorPickerInput.tsx index 5b7a2ef03087..06d5c36dbe8f 100644 --- a/src/components/ColorPickerInput/ColorPickerInput.tsx +++ b/src/components/ColorPickerInput/ColorPickerInput.tsx @@ -39,6 +39,10 @@ export const ColorPickerInput = ({ const managedValue = value || inputValue; + React.useEffect(() => { + setColor(defaultValue); + }, [defaultValue]); + const onChange: ChangeEventHandler = useCallback( (event) => { const newValue = event.target.value.replaceAll(' ', ''); diff --git a/src/components/PrivateColorSelect/PrivateColorSelect.scss b/src/components/PrivateColorSelect/PrivateColorSelect.scss index b40eb467efd8..6faf39f13daa 100644 --- a/src/components/PrivateColorSelect/PrivateColorSelect.scss +++ b/src/components/PrivateColorSelect/PrivateColorSelect.scss @@ -6,9 +6,14 @@ $block: '.#{variables.$ns}private-colors-select'; #{$block} { --g-button-border-radius: var(--g-spacing-2); --g-text-input-border-radius: var(--g-spacing-2); + --g-color-base-selection: var(--g-color-private-yellow-150); &__preview { margin-inline-start: var(--g-spacing-2); margin-inline-end: var(--g-spacing-1); } + + &__customize-button { + flex-shrink: 0; + } } diff --git a/src/components/PrivateColorSelect/PrivateColorSelect.tsx b/src/components/PrivateColorSelect/PrivateColorSelect.tsx index 9a43a4daf666..5cec93a91b33 100644 --- a/src/components/PrivateColorSelect/PrivateColorSelect.tsx +++ b/src/components/PrivateColorSelect/PrivateColorSelect.tsx @@ -1,9 +1,11 @@ -import {ChevronDown, PencilToLine} from '@gravity-ui/icons'; -import {Button, Flex, Icon, Popup, TextInput} from '@gravity-ui/uikit'; +import {ChevronDown, DiamondExclamation, PencilToLine} from '@gravity-ui/icons'; +import {ActionTooltip, Button, Flex, Icon, Popup, TextInput} from '@gravity-ui/uikit'; import React from 'react'; import {block} from '../../utils'; +import {ColorPickerInput} from '../ColorPickerInput/ColorPickerInput'; import {ColorPreview} from '../ColorPreview/ColorPreview'; +import {createPrivateColorTitle, isPrivateColorToken, parsePrivateColorToken} from '../Theme/utils'; import './PrivateColorSelect.scss'; import {PrivateColorSelectPopupContent} from './PrivateColorSelectPopupContent'; @@ -13,6 +15,7 @@ const b = block('private-colors-select'); interface PrivateColorSelectProps { value?: string; + defaultValue: string; onChange: (color: string) => void; groups: ColorGroup[]; } @@ -20,10 +23,40 @@ interface PrivateColorSelectProps { export const PrivateColorSelect: React.FC = ({ groups, value, + defaultValue, onChange, }) => { const containerRef = React.useRef(null); - const [open, setOpen] = React.useState(false); + const [showPopup, toggleShowPopup] = React.useReducer((prev) => !prev, false); + const isCustomValue = !isPrivateColorToken(value); + const isValueDiffersFromDefault = defaultValue && defaultValue !== value; + + const defaultValueToken = React.useMemo(() => { + if (!defaultValue) { + return ''; + } + + const result = parsePrivateColorToken(defaultValue); + if (result) { + return createPrivateColorTitle(result.mainColorToken, result.privateColorCode); + } + + return ''; + }, [defaultValue]); + + const resetToDefault = React.useCallback(() => { + if (defaultValue) { + onChange(defaultValue); + } + }, [onChange, defaultValue]); + + const switchMode = React.useCallback(() => { + if (isCustomValue) { + onChange(defaultValue); + } else { + onChange(''); + } + }, [isCustomValue, onChange, defaultValue]); const privateColor = React.useMemo(() => { const colorGroup = value @@ -35,34 +68,56 @@ export const PrivateColorSelect: React.FC = ({ : undefined; }, [groups, value]); - const togglePopup = React.useCallback(() => setOpen((prev) => !prev), []); - const handleClosePopup = React.useCallback(() => setOpen(false), []); - return ( - + ) : ( + + } + endContent={ + + + {isValueDiffersFromDefault && ( + + + + )} + + } + controlProps={{ + readOnly: true, + }} + onFocus={toggleShowPopup} + /> + )} + - } - controlProps={{ - readOnly: true, - }} - onFocus={togglePopup} - /> - diff --git a/src/components/PrivateColorSelect/PrivateColorSelectPopupContent.tsx b/src/components/PrivateColorSelect/PrivateColorSelectPopupContent.tsx index 32f2541da3c3..15857372d395 100644 --- a/src/components/PrivateColorSelect/PrivateColorSelectPopupContent.tsx +++ b/src/components/PrivateColorSelect/PrivateColorSelectPopupContent.tsx @@ -73,6 +73,8 @@ export const PrivateColorSelectPopupContent: React.FC { + const colorsRef = React.useRef(null); + const [currentGroupToken, setCurrentGroupToken] = React.useState(() => value ? parsePrivateColorToken(value)?.mainColorToken : undefined, ); @@ -87,6 +89,13 @@ export const PrivateColorSelectPopupContent: React.FC { + colorsRef.current?.scrollTo({ + top: 0, + behavior: 'smooth', + }); + }, [groupToken]); + const groupPrivateColors = React.useMemo( () => groups.find(({token}) => token === groupToken)?.privateColors || [], [groups, groupToken], @@ -97,7 +106,7 @@ export const PrivateColorSelectPopupContent: React.FC
-
+
diff --git a/src/components/Theme/ColorsTab/BasicPalette/ThemePaletteCard.scss b/src/components/Theme/ColorsTab/BasicPalette/ThemePaletteCard.scss index 352e8043e507..d41977702765 100644 --- a/src/components/Theme/ColorsTab/BasicPalette/ThemePaletteCard.scss +++ b/src/components/Theme/ColorsTab/BasicPalette/ThemePaletteCard.scss @@ -12,10 +12,6 @@ $block: '.#{variables.$ns}theme-palette-card'; color: var(--g-color-text-brand-contrast); } - &_dark { - background: #2c2c2c; - } - &__title { color: var(--g-color-base-background); } diff --git a/src/components/Theme/ColorsTab/BasicPalette/ThemePaletteCard.tsx b/src/components/Theme/ColorsTab/BasicPalette/ThemePaletteCard.tsx index 0a3bc804444e..5c50255e67cf 100644 --- a/src/components/Theme/ColorsTab/BasicPalette/ThemePaletteCard.tsx +++ b/src/components/Theme/ColorsTab/BasicPalette/ThemePaletteCard.tsx @@ -4,6 +4,7 @@ import React from 'react'; import {block} from '../../../../utils'; import {ColorPickerInput} from '../../../ColorPickerInput/ColorPickerInput'; +import {useThemeColor} from '../../hooks/useThemeColor'; import {Palette, ThemeVariant} from '../../types'; import './ThemePaletteCard.scss'; @@ -17,6 +18,8 @@ interface ThemePaletteCardProps { } export const ThemePaletteCard: React.FC = ({theme, palette, onUpdate}) => { + const [backgroundColor] = useThemeColor({name: 'base-background', theme}); + const createChangeHandler = React.useCallback( (title: string) => (value: string) => { onUpdate({title, theme, value}); @@ -26,7 +29,12 @@ export const ThemePaletteCard: React.FC = ({theme, palett return ( - + diff --git a/src/components/Theme/ColorsTab/PrivateColorsSettings/PrivateColorsSettings.tsx b/src/components/Theme/ColorsTab/PrivateColorsSettings/PrivateColorsSettings.tsx index 31ee28a1deb5..647a4edf780e 100644 --- a/src/components/Theme/ColorsTab/PrivateColorsSettings/PrivateColorsSettings.tsx +++ b/src/components/Theme/ColorsTab/PrivateColorsSettings/PrivateColorsSettings.tsx @@ -5,6 +5,7 @@ import React from 'react'; import {block} from '../../../../utils'; import {PrivateColorSelect} from '../../../PrivateColorSelect/PrivateColorSelect'; import {ThemePicker} from '../../../ThemePicker'; +import {DEFAULT_THEME} from '../../constants'; import {useThemeColor} from '../../hooks/useThemeColor'; import {useThemeCreator} from '../../hooks/useThemeCreator'; import type {ColorsOptions, ThemeVariant} from '../../types'; @@ -26,7 +27,14 @@ const PrivateColorEditor: React.FC = ({name, theme, col theme, }); - return ; + return ( + + ); }; export type EditableColorOption = { diff --git a/src/components/Theme/utils.ts b/src/components/Theme/utils.ts index 12f1fe7191f5..263b7676366a 100644 --- a/src/components/Theme/utils.ts +++ b/src/components/Theme/utils.ts @@ -1,4 +1,5 @@ import capitalize from 'lodash/capitalize'; +import cloneDeep from 'lodash/cloneDeep'; import kebabCase from 'lodash/kebabCase'; import lowerCase from 'lodash/lowerCase'; @@ -30,6 +31,20 @@ function createPrivateColorToken(mainColorToken: string, privateColorCode: strin return `private.${mainColorToken}.${privateColorCode}`; } +export function isPrivateColorToken(privateColorToken?: string) { + if (!privateColorToken) { + return false; + } + + const parts = privateColorToken.split('.'); + + if (parts.length !== 3 || parts[0] !== 'private') { + return false; + } + + return true; +} + export function parsePrivateColorToken(privateColorToken: string) { const parts = privateColorToken.split('.'); @@ -43,7 +58,7 @@ export function parsePrivateColorToken(privateColorToken: string) { }; } -function createPrivateColorTitle(mainColorToken: string, privateColorCode: string) { +export function createPrivateColorTitle(mainColorToken: string, privateColorCode: string) { return `${THEME_COLOR_VARIABLE_PREFIX}-${mainColorToken}-${privateColorCode}`; } @@ -392,7 +407,8 @@ export function getThemePalette(theme: ThemeWizardState): Palette { }); } -export function initThemeWizard(theme: ThemeOptions): ThemeWizardState { +export function initThemeWizard(inputTheme: ThemeOptions): ThemeWizardState { + const theme = cloneDeep(inputTheme); const paletteTokens = createPalleteTokens(theme); return { From b86d412596b118656246593af95b67410e70e01b Mon Sep 17 00:00:00 2001 From: Daniil Gaponov Date: Fri, 14 Jun 2024 10:52:41 +0300 Subject: [PATCH 11/14] fix: styles of form row --- .../Theme/ColorsTab/MainSettings/MainSettings.scss | 2 +- .../PrivateColorsSettings/PrivateColorsSettings.scss | 2 +- src/components/Theme/constants.ts | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/Theme/ColorsTab/MainSettings/MainSettings.scss b/src/components/Theme/ColorsTab/MainSettings/MainSettings.scss index 615f8313f0e5..72dbb448bcf3 100644 --- a/src/components/Theme/ColorsTab/MainSettings/MainSettings.scss +++ b/src/components/Theme/ColorsTab/MainSettings/MainSettings.scss @@ -6,7 +6,7 @@ $block: '.#{variables.$ns}main-settings'; --g-button-border-radius: var(--g-spacing-2); --g-text-input-border-radius: var(--g-spacing-2); - &__row { + & &__row { --gc-form-row-label-width: 400px; --gc-form-row-field-height: 44px; margin-block-end: 24px; diff --git a/src/components/Theme/ColorsTab/PrivateColorsSettings/PrivateColorsSettings.scss b/src/components/Theme/ColorsTab/PrivateColorsSettings/PrivateColorsSettings.scss index 2265f1adce1e..bb05bee41bbd 100644 --- a/src/components/Theme/ColorsTab/PrivateColorsSettings/PrivateColorsSettings.scss +++ b/src/components/Theme/ColorsTab/PrivateColorsSettings/PrivateColorsSettings.scss @@ -3,7 +3,7 @@ $block: '.#{variables.$ns}private-colors-settings'; #{$block} { - &__row { + & &__row { --gc-form-row-label-width: 400px; --gc-form-row-field-height: 44px; margin-block-end: 24px; diff --git a/src/components/Theme/constants.ts b/src/components/Theme/constants.ts index 7ce55e5660e5..ba88cea603a2 100644 --- a/src/components/Theme/constants.ts +++ b/src/components/Theme/constants.ts @@ -41,9 +41,9 @@ export const DEFAULT_THEME: ThemeOptions = { 'base-brand-hover': 'private.brand.600-solid', 'base-selection': 'private.yellow.200', 'base-selection-hover': 'private.yellow.300', - 'line-brand': 'private.yellow.600-solid', - 'text-brand': 'private.yellow.700-solid', - 'text-brand-heavy': 'private.orange.700-solid', + 'line-brand': 'private.brand.600-solid', + 'text-brand': 'private.brand.700-solid', + 'text-brand-heavy': 'private.brand.700-solid', 'text-brand-contrast': 'private.black.850', 'text-link': 'private.yellow.650-solid', 'text-link-hover': 'private.orange.650-solid', @@ -55,9 +55,9 @@ export const DEFAULT_THEME: ThemeOptions = { 'base-brand-hover': 'private.brand.650-solid', 'base-selection': 'private.yellow.150', 'base-selection-hover': 'private.yellow.200', - 'line-brand': 'private.yellow.600-solid', - 'text-brand': 'private.yellow.600-solid', - 'text-brand-heavy': 'private.yellow.700-solid', + 'line-brand': 'private.brand.600-solid', + 'text-brand': 'private.brand.600-solid', + 'text-brand-heavy': 'private.brand.700-solid', 'text-brand-contrast': 'private.black.900', 'text-link': 'private.yellow.550-solid', 'text-link-hover': 'private.orange.550-solid', From 386084331225d788ce0a5ae76ee5e029150e983b Mon Sep 17 00:00:00 2001 From: Daniil Gaponov Date: Sat, 15 Jun 2024 22:39:53 +0300 Subject: [PATCH 12/14] feat: base theme export --- .../ColorPickerInput/ColorPickerInput.scss | 12 ++- .../ColorPickerInput/ColorPickerInput.tsx | 1 + .../PrivateColorSelect/PrivateColorSelect.tsx | 37 +-------- .../Theme/ThemeCreator/ThemeCreator.tsx | 6 +- src/components/Theme/types.ts | 2 + src/components/Theme/utils.ts | 83 ++++++++++++++++++- 6 files changed, 102 insertions(+), 39 deletions(-) diff --git a/src/components/ColorPickerInput/ColorPickerInput.scss b/src/components/ColorPickerInput/ColorPickerInput.scss index a42d8a7ef183..68d19b24298a 100644 --- a/src/components/ColorPickerInput/ColorPickerInput.scss +++ b/src/components/ColorPickerInput/ColorPickerInput.scss @@ -5,6 +5,11 @@ $block: '.#{variables.$ns}color-picker'; #{$block} { --g-border-radius-xl: 8px; flex-grow: 1; + position: relative; + + &__text-input { + z-index: 1; + } &__preview { margin-inline-start: var(--g-spacing-2); @@ -12,11 +17,14 @@ $block: '.#{variables.$ns}color-picker'; } &__input { - width: 100%; - height: 0; + width: 35px; opacity: 0; padding: 0; margin: 0; border: 0; + position: absolute; + bottom: 0; + right: 0; + z-index: 0; } } diff --git a/src/components/ColorPickerInput/ColorPickerInput.tsx b/src/components/ColorPickerInput/ColorPickerInput.tsx index 06d5c36dbe8f..1f458709bbd3 100644 --- a/src/components/ColorPickerInput/ColorPickerInput.tsx +++ b/src/components/ColorPickerInput/ColorPickerInput.tsx @@ -95,6 +95,7 @@ export const ColorPickerInput = ({ return ( = ({ const containerRef = React.useRef(null); const [showPopup, toggleShowPopup] = React.useReducer((prev) => !prev, false); const isCustomValue = !isPrivateColorToken(value); - const isValueDiffersFromDefault = defaultValue && defaultValue !== value; - - const defaultValueToken = React.useMemo(() => { - if (!defaultValue) { - return ''; - } - - const result = parsePrivateColorToken(defaultValue); - if (result) { - return createPrivateColorTitle(result.mainColorToken, result.privateColorCode); - } - - return ''; - }, [defaultValue]); - - const resetToDefault = React.useCallback(() => { - if (defaultValue) { - onChange(defaultValue); - } - }, [onChange, defaultValue]); const switchMode = React.useCallback(() => { if (isCustomValue) { @@ -85,17 +65,6 @@ export const PrivateColorSelect: React.FC = ({ - {isValueDiffersFromDefault && ( - - - - )} } controlProps={{ diff --git a/src/components/Theme/ThemeCreator/ThemeCreator.tsx b/src/components/Theme/ThemeCreator/ThemeCreator.tsx index d0c05ce8e5fd..90127174ccc5 100644 --- a/src/components/Theme/ThemeCreator/ThemeCreator.tsx +++ b/src/components/Theme/ThemeCreator/ThemeCreator.tsx @@ -1,7 +1,7 @@ import React from 'react'; import {ThemeOptions} from '../types'; -import {initThemeWizard} from '../utils'; +import {exportTheme, initThemeWizard} from '../utils'; import {ThemeCreatorContextProvider} from './ThemeCreatorContext'; @@ -16,6 +16,10 @@ export const ThemeCreator: React.FC = ({theme, children}) => updateState(initThemeWizard(theme)); }, [theme]); + React.useEffect(() => { + console.log(exportTheme(state, 'scss')); + }, [state]); + return ( ({ token: createPrivateColorToken(token, privateColorCode), - title: createPrivateColorTitle(token, privateColorCode), + title: createPrivateColorCssVariable(token, privateColorCode), color, })), }, @@ -417,3 +434,65 @@ export function initThemeWizard(inputTheme: ThemeOptions): ThemeWizardState { tokens: Object.keys(paletteTokens), }; } + +type ExportType = 'scss' | 'json'; + +export function exportTheme(themeState: ThemeWizardState, exportType: ExportType = 'scss'): string { + if (exportType === 'json') { + throw new Error('Not implemented'); + } + + const {paletteTokens} = themeState; + + const prepareThemeVariables = (themeVariant: ThemeVariant) => { + let cssVariables = ''; + const privateColors: Record = {}; + + themeState.tokens.forEach((token) => { + // Dont export colors that are equals to default + if (DEFAULT_PALETTE[themeVariant][token] === themeState.palette[themeVariant][token]) { + return; + } + + if (paletteTokens[token]?.privateColors[themeVariant]) { + Object.entries(paletteTokens[token].privateColors[themeVariant]).forEach( + ([privateColorCode, color]) => { + privateColors[createPrivateColorToken(token, privateColorCode)] = color; + cssVariables += `${createPrivateColorCssVariable( + token, + privateColorCode, + )}: ${color};\n`; + }, + ); + cssVariables += '\n'; + } + }); + + cssVariables += '\n'; + + Object.entries(themeState.colors[themeVariant]).forEach( + ([colorName, colorOrPrivateToken]) => { + // Dont export colors that are equals to default + if ( + DEFAULT_THEME.colors[themeVariant][colorName as ColorOption] === + colorOrPrivateToken + ) { + return; + } + + const color = isPrivateColorToken(colorOrPrivateToken) + ? `var(${createPrivateColorCssVariableFromToken(colorOrPrivateToken)})` + : colorOrPrivateToken; + + cssVariables += `${createUtilityColorCssVariable(colorName)}: ${color};\n`; + }, + ); + + return cssVariables; + }; + + let result = ''; + result += '// Light\n' + prepareThemeVariables('light'); + result += '\n// Dark\n' + prepareThemeVariables('dark'); + return result; +} From 2f5b3d9737f5622395624cfc63206ac5f0d63af2 Mon Sep 17 00:00:00 2001 From: Daniil Gaponov Date: Sun, 16 Jun 2024 12:38:05 +0300 Subject: [PATCH 13/14] refactoring: move components to ui and utils to lib --- src/components/Theme/Theme.tsx | 10 +- .../Theme/ThemeCreator/ThemeCreator.tsx | 33 --- .../Theme/ThemeCreator/ThemeCreatorContext.ts | 18 -- src/components/Theme/ThemeCreator/index.ts | 2 - src/components/Theme/hooks/index.ts | 4 + src/components/Theme/hooks/useThemeColor.ts | 31 --- src/components/Theme/hooks/useThemeCreator.ts | 65 +----- src/components/Theme/hooks/useThemePalette.ts | 36 ++++ .../Theme/hooks/useThemePaletteColor.ts | 30 --- .../hooks/useThemePrivateColorOptions.ts | 15 ++ .../Theme/hooks/useThemeUtilityColor.ts | 30 +++ src/components/Theme/{ => lib}/constants.ts | 0 .../{ => lib}/privateColors/constants.ts | 0 .../Theme/{ => lib}/privateColors/index.ts | 0 .../Theme/{ => lib}/privateColors/utils.ts | 0 .../Theme/lib/themeCreatorContext.ts | 32 +++ .../Theme/lib/themeCreatorExport.ts | 74 +++++++ .../Theme/lib/themeCreatorImport.ts | 1 + .../{utils.ts => lib/themeCreatorUtils.ts} | 196 +++++++----------- src/components/Theme/{ => lib}/types.ts | 2 +- .../BasicPalette/BasicPalette.tsx | 11 +- .../BasicPalette/PaletteColors.scss | 0 .../BasicPalette/PaletteColors.tsx | 4 +- .../BasicPalette/ThemePaletteCard.scss | 0 .../BasicPalette/ThemePaletteCard.tsx | 6 +- .../Theme/{ => ui}/ColorsTab/index.tsx | 9 +- .../MainSettings/MainSettings.scss | 0 .../MainSettings/MainSettings.tsx | 12 +- .../PrivateColorSelect.scss | 2 +- .../PrivateColorSelect/PrivateColorSelect.tsx | 8 +- .../PrivateColorSelectPopupContent.scss | 2 +- .../PrivateColorSelectPopupContent.tsx | 6 +- .../Theme/ui/PrivateColorSelect/index.ts | 1 + .../ui}/PrivateColorSelect/types.ts | 0 .../PrivateColorsSettings.scss | 0 .../PrivateColorsSettings.tsx | 24 +-- .../Theme/ui/PrivateColorsSettings/index.ts | 2 + .../Theme/ui/ThemeCreatorContextProvider.tsx | 148 +++++++++++++ .../ui}/ThemePicker/ThemePicker.scss | 2 +- .../ui}/ThemePicker/ThemePicker.tsx | 5 +- .../{ => Theme/ui}/ThemePicker/index.ts | 0 41 files changed, 466 insertions(+), 355 deletions(-) delete mode 100644 src/components/Theme/ThemeCreator/ThemeCreator.tsx delete mode 100644 src/components/Theme/ThemeCreator/ThemeCreatorContext.ts delete mode 100644 src/components/Theme/ThemeCreator/index.ts create mode 100644 src/components/Theme/hooks/index.ts delete mode 100644 src/components/Theme/hooks/useThemeColor.ts create mode 100644 src/components/Theme/hooks/useThemePalette.ts delete mode 100644 src/components/Theme/hooks/useThemePaletteColor.ts create mode 100644 src/components/Theme/hooks/useThemePrivateColorOptions.ts create mode 100644 src/components/Theme/hooks/useThemeUtilityColor.ts rename src/components/Theme/{ => lib}/constants.ts (100%) rename src/components/Theme/{ => lib}/privateColors/constants.ts (100%) rename src/components/Theme/{ => lib}/privateColors/index.ts (100%) rename src/components/Theme/{ => lib}/privateColors/utils.ts (100%) create mode 100644 src/components/Theme/lib/themeCreatorContext.ts create mode 100644 src/components/Theme/lib/themeCreatorExport.ts create mode 100644 src/components/Theme/lib/themeCreatorImport.ts rename src/components/Theme/{utils.ts => lib/themeCreatorUtils.ts} (69%) rename src/components/Theme/{ => lib}/types.ts (96%) rename src/components/Theme/{ColorsTab => ui}/BasicPalette/BasicPalette.tsx (86%) rename src/components/Theme/{ColorsTab => ui}/BasicPalette/PaletteColors.scss (100%) rename src/components/Theme/{ColorsTab => ui}/BasicPalette/PaletteColors.tsx (96%) rename src/components/Theme/{ColorsTab => ui}/BasicPalette/ThemePaletteCard.scss (100%) rename src/components/Theme/{ColorsTab => ui}/BasicPalette/ThemePaletteCard.tsx (90%) rename src/components/Theme/{ => ui}/ColorsTab/index.tsx (89%) rename src/components/Theme/{ColorsTab => ui}/MainSettings/MainSettings.scss (100%) rename src/components/Theme/{ColorsTab => ui}/MainSettings/MainSettings.tsx (88%) rename src/components/{ => Theme/ui}/PrivateColorSelect/PrivateColorSelect.scss (93%) rename src/components/{ => Theme/ui}/PrivateColorSelect/PrivateColorSelect.tsx (92%) rename src/components/{ => Theme/ui}/PrivateColorSelect/PrivateColorSelectPopupContent.scss (96%) rename src/components/{ => Theme/ui}/PrivateColorSelect/PrivateColorSelectPopupContent.tsx (94%) create mode 100644 src/components/Theme/ui/PrivateColorSelect/index.ts rename src/components/{ => Theme/ui}/PrivateColorSelect/types.ts (100%) rename src/components/Theme/{ColorsTab => ui}/PrivateColorsSettings/PrivateColorsSettings.scss (100%) rename src/components/Theme/{ColorsTab => ui}/PrivateColorsSettings/PrivateColorsSettings.tsx (74%) create mode 100644 src/components/Theme/ui/PrivateColorsSettings/index.ts create mode 100644 src/components/Theme/ui/ThemeCreatorContextProvider.tsx rename src/components/{ => Theme/ui}/ThemePicker/ThemePicker.scss (85%) rename src/components/{ => Theme/ui}/ThemePicker/ThemePicker.tsx (92%) rename src/components/{ => Theme/ui}/ThemePicker/index.ts (100%) diff --git a/src/components/Theme/Theme.tsx b/src/components/Theme/Theme.tsx index 026553a2f713..f233209e552f 100644 --- a/src/components/Theme/Theme.tsx +++ b/src/components/Theme/Theme.tsx @@ -1,13 +1,13 @@ import {Col, Grid, Row} from '@gravity-ui/page-constructor'; import React from 'react'; -import {ColorsTab} from './ColorsTab'; -import {ThemeCreator} from './ThemeCreator'; -import {DEFAULT_THEME} from './constants'; +import {DEFAULT_THEME} from './lib/constants'; +import {ColorsTab} from './ui/ColorsTab'; +import {ThemeCreatorContextProvider} from './ui/ThemeCreatorContextProvider'; export const Theme = () => { return ( - + @@ -15,6 +15,6 @@ export const Theme = () => { - + ); }; diff --git a/src/components/Theme/ThemeCreator/ThemeCreator.tsx b/src/components/Theme/ThemeCreator/ThemeCreator.tsx deleted file mode 100644 index 90127174ccc5..000000000000 --- a/src/components/Theme/ThemeCreator/ThemeCreator.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; - -import {ThemeOptions} from '../types'; -import {exportTheme, initThemeWizard} from '../utils'; - -import {ThemeCreatorContextProvider} from './ThemeCreatorContext'; - -interface ThemeCreatorProps extends React.PropsWithChildren { - theme: ThemeOptions; -} - -export const ThemeCreator: React.FC = ({theme, children}) => { - const [state, updateState] = React.useState(() => initThemeWizard(theme)); - - React.useEffect(() => { - updateState(initThemeWizard(theme)); - }, [theme]); - - React.useEffect(() => { - console.log(exportTheme(state, 'scss')); - }, [state]); - - return ( - - {children} - - ); -}; diff --git a/src/components/Theme/ThemeCreator/ThemeCreatorContext.ts b/src/components/Theme/ThemeCreator/ThemeCreatorContext.ts deleted file mode 100644 index e4f6dd7c4846..000000000000 --- a/src/components/Theme/ThemeCreator/ThemeCreatorContext.ts +++ /dev/null @@ -1,18 +0,0 @@ -import noop from 'lodash/noop'; -import React from 'react'; - -import {DEFAULT_THEME} from '../constants'; -import type {ThemeWizardState} from '../types'; -import {initThemeWizard} from '../utils'; - -interface ThemeCreatorContextType { - state: ThemeWizardState; - updateState: (val: ThemeWizardState) => void; -} - -export const ThemeCreatorContext = React.createContext({ - state: initThemeWizard(DEFAULT_THEME), - updateState: noop, -}); - -export const ThemeCreatorContextProvider = ThemeCreatorContext.Provider; diff --git a/src/components/Theme/ThemeCreator/index.ts b/src/components/Theme/ThemeCreator/index.ts deleted file mode 100644 index 1e0fe2bdfed6..000000000000 --- a/src/components/Theme/ThemeCreator/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export {ThemeCreator} from './ThemeCreator'; -export {ThemeCreatorContext, ThemeCreatorContextProvider} from './ThemeCreatorContext'; diff --git a/src/components/Theme/hooks/index.ts b/src/components/Theme/hooks/index.ts new file mode 100644 index 000000000000..e68e06b27c8e --- /dev/null +++ b/src/components/Theme/hooks/index.ts @@ -0,0 +1,4 @@ +export {useThemeCreator, useThemeCreatorMethods} from './useThemeCreator'; +export {useThemePalette, useThemePaletteColor} from './useThemePalette'; +export {useThemeUtilityColor} from './useThemeUtilityColor'; +export {useThemePrivateColorOptions} from './useThemePrivateColorOptions'; diff --git a/src/components/Theme/hooks/useThemeColor.ts b/src/components/Theme/hooks/useThemeColor.ts deleted file mode 100644 index ea7283b4c724..000000000000 --- a/src/components/Theme/hooks/useThemeColor.ts +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react'; - -import {ThemeCreatorContext} from '../ThemeCreator'; -import type {ColorsOptions, ThemeVariant} from '../types'; -import {changeColorInTheme} from '../utils'; - -type UseThemeColorParams = { - name: keyof ColorsOptions; - theme: ThemeVariant; -}; - -export const useThemeColor = ({name, theme}: UseThemeColorParams) => { - const {state, updateState} = React.useContext(ThemeCreatorContext); - - const value = React.useMemo(() => state.colors[theme][name], [state, name, theme]); - - const updateValue = React.useCallback( - (newValue: string) => { - const newState = changeColorInTheme({ - themeState: state, - themeVariant: theme, - name, - value: newValue, - }); - updateState(newState); - }, - [name, theme, state], - ); - - return [value, updateValue] as const; -}; diff --git a/src/components/Theme/hooks/useThemeCreator.ts b/src/components/Theme/hooks/useThemeCreator.ts index cc112489832f..c7e62a37b0c3 100644 --- a/src/components/Theme/hooks/useThemeCreator.ts +++ b/src/components/Theme/hooks/useThemeCreator.ts @@ -1,65 +1,6 @@ import React from 'react'; -import {ThemeCreatorContext} from '../ThemeCreator'; -import {ThemeVariant} from '../types'; -import { - addColorToTheme, - getThemeColorOptions, - getThemePalette, - removeColorFromTheme, - renameColorInTheme, - updateColorInTheme, -} from '../utils'; -import type {AddColorToThemeParams, UpdateColorInThemeParams} from '../utils'; +import {ThemeCreatorContext, ThemeCreatorMethodsContext} from '../lib/themeCreatorContext'; -export const useThemeCreator = () => { - const {state, updateState} = React.useContext(ThemeCreatorContext); - - const addColor = React.useCallback( - (params?: AddColorToThemeParams) => { - const newState = addColorToTheme(state, params); - updateState(newState); - }, - [state], - ); - - const updateColor = React.useCallback( - (params: UpdateColorInThemeParams) => { - const newState = updateColorInTheme(state, params); - updateState(newState); - }, - [state], - ); - - const removeColor = React.useCallback( - (colorTitle: string) => { - const newState = removeColorFromTheme(state, colorTitle); - updateState(newState); - }, - [state], - ); - - const renameColor = React.useCallback( - (oldTitle: string, newTitle: string) => { - const newState = renameColorInTheme(state, oldTitle, newTitle); - updateState(newState); - }, - [state], - ); - - const palette = React.useMemo(() => getThemePalette(state), [state]); - - const getThemePrivateColorOptions = React.useCallback( - (themeVariant: ThemeVariant) => getThemeColorOptions({themeState: state, themeVariant}), - [state], - ); - - return { - addColor, - updateColor, - removeColor, - renameColor, - palette, - getThemePrivateColorOptions, - }; -}; +export const useThemeCreator = () => React.useContext(ThemeCreatorContext); +export const useThemeCreatorMethods = () => React.useContext(ThemeCreatorMethodsContext); diff --git a/src/components/Theme/hooks/useThemePalette.ts b/src/components/Theme/hooks/useThemePalette.ts new file mode 100644 index 000000000000..71ffd9536640 --- /dev/null +++ b/src/components/Theme/hooks/useThemePalette.ts @@ -0,0 +1,36 @@ +import React from 'react'; + +import {getThemePalette} from '../lib/themeCreatorUtils'; +import type {ThemeVariant} from '../lib/types'; + +import {useThemeCreator, useThemeCreatorMethods} from './useThemeCreator'; + +export const useThemePalette = () => { + const themeState = useThemeCreator(); + return React.useMemo(() => getThemePalette(themeState), [themeState]); +}; + +type UseThemePaletteColorParams = { + token: string; + theme: ThemeVariant; +}; + +export const useThemePaletteColor = ({token, theme}: UseThemePaletteColorParams) => { + const themeState = useThemeCreator(); + const {updateColor} = useThemeCreatorMethods(); + + const value = React.useMemo(() => themeState.palette[theme][token], [themeState, token, theme]); + + const updateValue = React.useCallback( + (newValue: string) => { + updateColor({ + theme, + title: token, + value: newValue, + }); + }, + [token, theme, updateColor], + ); + + return [value, updateValue] as const; +}; diff --git a/src/components/Theme/hooks/useThemePaletteColor.ts b/src/components/Theme/hooks/useThemePaletteColor.ts deleted file mode 100644 index e5b5ddb7efe8..000000000000 --- a/src/components/Theme/hooks/useThemePaletteColor.ts +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react'; - -import {ThemeCreatorContext} from '../ThemeCreator'; -import type {ThemeVariant} from '../types'; -import {updateColorInTheme} from '../utils'; - -type UseThemePaletteColorParams = { - token: string; - theme: ThemeVariant; -}; - -export const useThemePaletteColor = ({token, theme}: UseThemePaletteColorParams) => { - const {state, updateState} = React.useContext(ThemeCreatorContext); - - const value = React.useMemo(() => state.palette[theme][token], [state, token, theme]); - - const updateValue = React.useCallback( - (newValue: string) => { - const newState = updateColorInTheme(state, { - theme, - title: token, - value: newValue, - }); - updateState(newState); - }, - [token, theme, state], - ); - - return [value, updateValue] as const; -}; diff --git a/src/components/Theme/hooks/useThemePrivateColorOptions.ts b/src/components/Theme/hooks/useThemePrivateColorOptions.ts new file mode 100644 index 000000000000..6aa706b024cb --- /dev/null +++ b/src/components/Theme/hooks/useThemePrivateColorOptions.ts @@ -0,0 +1,15 @@ +import React from 'react'; + +import {getThemeColorOptions} from '../lib/themeCreatorUtils'; +import {ThemeVariant} from '../lib/types'; + +import {useThemeCreator} from './useThemeCreator'; + +export const useThemePrivateColorOptions = (themeVariant: ThemeVariant) => { + const themeState = useThemeCreator(); + + return React.useMemo( + () => getThemeColorOptions({themeState, themeVariant}), + [themeState, themeVariant], + ); +}; diff --git a/src/components/Theme/hooks/useThemeUtilityColor.ts b/src/components/Theme/hooks/useThemeUtilityColor.ts new file mode 100644 index 000000000000..2160797535e9 --- /dev/null +++ b/src/components/Theme/hooks/useThemeUtilityColor.ts @@ -0,0 +1,30 @@ +import React from 'react'; + +import type {ColorsOptions, ThemeVariant} from '../lib/types'; + +import {useThemeCreator, useThemeCreatorMethods} from './useThemeCreator'; + +type UseThemeColorParams = { + name: keyof ColorsOptions; + theme: ThemeVariant; +}; + +export const useThemeUtilityColor = ({name, theme}: UseThemeColorParams) => { + const themeState = useThemeCreator(); + const {changeUtilityColor} = useThemeCreatorMethods(); + + const value = React.useMemo(() => themeState.colors[theme][name], [themeState, name, theme]); + + const updateValue = React.useCallback( + (newValue: string) => { + changeUtilityColor({ + themeVariant: theme, + name, + value: newValue, + }); + }, + [name, theme, changeUtilityColor], + ); + + return [value, updateValue] as const; +}; diff --git a/src/components/Theme/constants.ts b/src/components/Theme/lib/constants.ts similarity index 100% rename from src/components/Theme/constants.ts rename to src/components/Theme/lib/constants.ts diff --git a/src/components/Theme/privateColors/constants.ts b/src/components/Theme/lib/privateColors/constants.ts similarity index 100% rename from src/components/Theme/privateColors/constants.ts rename to src/components/Theme/lib/privateColors/constants.ts diff --git a/src/components/Theme/privateColors/index.ts b/src/components/Theme/lib/privateColors/index.ts similarity index 100% rename from src/components/Theme/privateColors/index.ts rename to src/components/Theme/lib/privateColors/index.ts diff --git a/src/components/Theme/privateColors/utils.ts b/src/components/Theme/lib/privateColors/utils.ts similarity index 100% rename from src/components/Theme/privateColors/utils.ts rename to src/components/Theme/lib/privateColors/utils.ts diff --git a/src/components/Theme/lib/themeCreatorContext.ts b/src/components/Theme/lib/themeCreatorContext.ts new file mode 100644 index 000000000000..ce12acbfdeb9 --- /dev/null +++ b/src/components/Theme/lib/themeCreatorContext.ts @@ -0,0 +1,32 @@ +import noop from 'lodash/noop'; +import {createContext} from 'react'; + +import {DEFAULT_THEME} from './constants'; +import {initThemeCreator} from './themeCreatorUtils'; +import type { + AddColorToThemeParams, + ChangeUtilityColorInThemeParams, + RenameColorInThemeParams, + UpdateColorInThemeParams, +} from './themeCreatorUtils'; +import type {ThemeCreatorState} from './types'; + +export const ThemeCreatorContext = createContext( + initThemeCreator(DEFAULT_THEME), +); + +export interface ThemeCreatorMethodsContextType { + addColor: (params?: AddColorToThemeParams) => void; + updateColor: (params: UpdateColorInThemeParams) => void; + removeColor: (title: string) => void; + renameColor: (params: RenameColorInThemeParams) => void; + changeUtilityColor: (params: ChangeUtilityColorInThemeParams) => void; +} + +export const ThemeCreatorMethodsContext = createContext({ + addColor: noop, + updateColor: noop, + removeColor: noop, + renameColor: noop, + changeUtilityColor: noop, +}); diff --git a/src/components/Theme/lib/themeCreatorExport.ts b/src/components/Theme/lib/themeCreatorExport.ts new file mode 100644 index 000000000000..87db752bb4d4 --- /dev/null +++ b/src/components/Theme/lib/themeCreatorExport.ts @@ -0,0 +1,74 @@ +import {DEFAULT_PALETTE, DEFAULT_THEME} from './constants'; +import { + createPrivateColorCssVariable, + createPrivateColorCssVariableFromToken, + createPrivateColorToken, + createUtilityColorCssVariable, + isPrivateColorToken, +} from './themeCreatorUtils'; +import type {ColorOption, ThemeCreatorState, ThemeVariant} from './types'; + +type ExportType = 'scss' | 'json'; + +export function exportTheme( + themeState: ThemeCreatorState, + exportType: ExportType = 'scss', +): string { + if (exportType === 'json') { + throw new Error('Not implemented'); + } + + const {paletteTokens} = themeState; + + const prepareThemeVariables = (themeVariant: ThemeVariant) => { + let cssVariables = ''; + const privateColors: Record = {}; + + themeState.tokens.forEach((token) => { + // Dont export colors that are equals to default + if (DEFAULT_PALETTE[themeVariant][token] === themeState.palette[themeVariant][token]) { + return; + } + + if (paletteTokens[token]?.privateColors[themeVariant]) { + Object.entries(paletteTokens[token].privateColors[themeVariant]).forEach( + ([privateColorCode, color]) => { + privateColors[createPrivateColorToken(token, privateColorCode)] = color; + cssVariables += `${createPrivateColorCssVariable( + token, + privateColorCode, + )}: ${color};\n`; + }, + ); + cssVariables += '\n'; + } + }); + + cssVariables += '\n'; + + Object.entries(themeState.colors[themeVariant]).forEach( + ([colorName, colorOrPrivateToken]) => { + // Dont export colors that are equals to default + if ( + DEFAULT_THEME.colors[themeVariant][colorName as ColorOption] === + colorOrPrivateToken + ) { + return; + } + + const color = isPrivateColorToken(colorOrPrivateToken) + ? `var(${createPrivateColorCssVariableFromToken(colorOrPrivateToken)})` + : colorOrPrivateToken; + + cssVariables += `${createUtilityColorCssVariable(colorName)}: ${color};\n`; + }, + ); + + return cssVariables; + }; + + let result = ''; + result += '// Light\n' + prepareThemeVariables('light'); + result += '\n// Dark\n' + prepareThemeVariables('dark'); + return result; +} diff --git a/src/components/Theme/lib/themeCreatorImport.ts b/src/components/Theme/lib/themeCreatorImport.ts new file mode 100644 index 000000000000..fb5eeccd3c56 --- /dev/null +++ b/src/components/Theme/lib/themeCreatorImport.ts @@ -0,0 +1 @@ +// TODO implement import logic diff --git a/src/components/Theme/utils.ts b/src/components/Theme/lib/themeCreatorUtils.ts similarity index 69% rename from src/components/Theme/utils.ts rename to src/components/Theme/lib/themeCreatorUtils.ts index f83e4083baa8..e3456196854a 100644 --- a/src/components/Theme/utils.ts +++ b/src/components/Theme/lib/themeCreatorUtils.ts @@ -5,21 +5,18 @@ import lowerCase from 'lodash/lowerCase'; import { DEFAULT_NEW_COLOR_TITLE, - DEFAULT_PALETTE, DEFAULT_PALETTE_TOKENS, - DEFAULT_THEME, THEME_COLOR_VARIABLE_PREFIX, } from './constants'; import {generatePrivateColors} from './privateColors'; import type { - ColorOption, ColorsOptions, Palette, PaletteTokens, PrivateColors, + ThemeCreatorState, ThemeOptions, ThemeVariant, - ThemeWizardState, } from './types'; function createColorToken(title: string) { @@ -30,7 +27,7 @@ function createTitleFromToken(token: string) { return capitalize(lowerCase(token)); } -function createPrivateColorToken(mainColorToken: string, privateColorCode: string) { +export function createPrivateColorToken(mainColorToken: string, privateColorCode: string) { return `private.${mainColorToken}.${privateColorCode}`; } @@ -61,11 +58,11 @@ export function parsePrivateColorToken(privateColorToken: string) { }; } -function createPrivateColorCssVariable(mainColorToken: string, privateColorCode: string) { +export function createPrivateColorCssVariable(mainColorToken: string, privateColorCode: string) { return `${THEME_COLOR_VARIABLE_PREFIX}-${mainColorToken}-${privateColorCode}`; } -function createPrivateColorCssVariableFromToken(privateColorToken: string) { +export function createPrivateColorCssVariableFromToken(privateColorToken: string) { const result = parsePrivateColorToken(privateColorToken); if (result) { @@ -75,7 +72,7 @@ function createPrivateColorCssVariableFromToken(privateColorToken: string) { return ''; } -function createUtilityColorCssVariable(colorName: string) { +export function createUtilityColorCssVariable(colorName: string) { return `${THEME_COLOR_VARIABLE_PREFIX}-${colorName}`; } @@ -170,14 +167,14 @@ export type UpdateColorInThemeParams = { /** * Updates a color in the given theme state. * - * @param {ThemeWizardState} themeState - The current state of the theme. + * @param {ThemeCreatorState} themeState - The current state of the theme. * @param {UpdateColorInThemeParams} params - The parameters for the color update. - * @returns {ThemeWizardState} The updated theme state. + * @returns {ThemeCreatorState} The updated theme state. */ export function updateColorInTheme( - themeState: ThemeWizardState, + themeState: ThemeCreatorState, params: UpdateColorInThemeParams, -): ThemeWizardState { +): ThemeCreatorState { const newThemeState = {...themeState}; const token = createColorToken(params.title); @@ -237,67 +234,83 @@ export type AddColorToThemeParams = /** * Adds a new color to the given theme state. * - * @param {ThemeWizardState} themeState - The current state of the theme. + * @param {ThemeCreatorState} themeState - The current state of the theme. * @param {AddColorToThemeParams} params - The parameters of the adding color. - * @returns {ThemeWizardState} The updated theme state with the new color added. + * @returns {ThemeCreatorState} The updated theme state with the new color added. */ export function addColorToTheme( - themeState: ThemeWizardState, + themeState: ThemeCreatorState, params: AddColorToThemeParams, -): ThemeWizardState { +): ThemeCreatorState { const newThemeState = {...themeState}; const title = params?.title ?? createNewColorTitle(themeState.paletteTokens); const token = createColorToken(title); if (!themeState.palette.dark[token]) { - newThemeState.palette.dark[token] = ''; + newThemeState.palette.dark = { + ...newThemeState.palette.dark, + [token]: '', + }; } if (!themeState.palette.light[token]) { - newThemeState.palette.light[token] = ''; + newThemeState.palette.light = { + ...newThemeState.palette.light, + [token]: '', + }; } if (params?.colors?.dark) { - newThemeState.palette.dark[token] = params.colors.dark; + newThemeState.palette.dark = { + ...newThemeState.palette.dark, + [token]: params.colors.dark, + }; } if (params?.colors?.light) { - newThemeState.palette.light[token] = params.colors.light; + newThemeState.palette.light = { + ...newThemeState.palette.light, + [token]: params.colors.light, + }; } - newThemeState.paletteTokens[token] = { - ...newThemeState.paletteTokens[token], - title, - privateColors: { - light: params?.colors?.light - ? createPrivateColors({ - colorToken: token, - colorValue: params.colors.light, - theme: newThemeState, - themeVariant: 'light', - }) - : undefined, - dark: params?.colors?.dark - ? createPrivateColors({ - colorToken: token, - colorValue: params.colors.dark, - theme: newThemeState, - themeVariant: 'dark', - }) - : undefined, + newThemeState.paletteTokens = { + ...newThemeState.paletteTokens, + [token]: { + ...newThemeState.paletteTokens[token], + title, + privateColors: { + light: params?.colors?.light + ? createPrivateColors({ + colorToken: token, + colorValue: params.colors.light, + theme: newThemeState, + themeVariant: 'light', + }) + : undefined, + dark: params?.colors?.dark + ? createPrivateColors({ + colorToken: token, + colorValue: params.colors.dark, + theme: newThemeState, + themeVariant: 'dark', + }) + : undefined, + }, + isCustom: true, }, - isCustom: true, }; - newThemeState.tokens.push(token); + newThemeState.tokens = [...newThemeState.tokens, token]; return newThemeState; } export function removeColorFromTheme( - themeState: ThemeWizardState, + themeState: ThemeCreatorState, colorTitle: string, -): ThemeWizardState { +): ThemeCreatorState { + console.log('call remove color', colorTitle); const newThemeState = {...themeState}; const token = createColorToken(colorTitle); @@ -310,11 +323,15 @@ export function removeColorFromTheme( return newThemeState; } +export type RenameColorInThemeParams = { + oldTitle: string; + newTitle: string; +}; + export function renameColorInTheme( - themeState: ThemeWizardState, - oldTitle: string, - newTitle: string, -): ThemeWizardState { + themeState: ThemeCreatorState, + {oldTitle, newTitle}: RenameColorInThemeParams, +): ThemeCreatorState { const newThemeState = {...themeState}; const oldToken = createColorToken(oldTitle); const newToken = createColorToken(newTitle); @@ -362,7 +379,7 @@ export function getThemeColorOptions({ themeState, themeVariant, }: { - themeState: ThemeWizardState; + themeState: ThemeCreatorState; themeVariant: ThemeVariant; }) { const {tokens, paletteTokens, palette} = themeState; @@ -390,17 +407,16 @@ export function getThemeColorOptions({ }, []); } -export function changeColorInTheme({ - themeState, - themeVariant, - name, - value, -}: { - themeState: ThemeWizardState; +export type ChangeUtilityColorInThemeParams = { themeVariant: ThemeVariant; name: keyof ColorsOptions; value: string; -}): ThemeWizardState { +}; + +export function changeUtilityColorInTheme( + themeState: ThemeCreatorState, + {themeVariant, name, value}: ChangeUtilityColorInThemeParams, +): ThemeCreatorState { const newState = {...themeState}; newState.colors[themeVariant][name] = value; @@ -411,7 +427,7 @@ export function changeColorInTheme({ return newState; } -export function getThemePalette(theme: ThemeWizardState): Palette { +export function getThemePalette(theme: ThemeCreatorState): Palette { return theme.tokens.map((token) => { return { title: theme.paletteTokens[token]?.title || '', @@ -424,7 +440,7 @@ export function getThemePalette(theme: ThemeWizardState): Palette { }); } -export function initThemeWizard(inputTheme: ThemeOptions): ThemeWizardState { +export function initThemeCreator(inputTheme: ThemeOptions): ThemeCreatorState { const theme = cloneDeep(inputTheme); const paletteTokens = createPalleteTokens(theme); @@ -434,65 +450,3 @@ export function initThemeWizard(inputTheme: ThemeOptions): ThemeWizardState { tokens: Object.keys(paletteTokens), }; } - -type ExportType = 'scss' | 'json'; - -export function exportTheme(themeState: ThemeWizardState, exportType: ExportType = 'scss'): string { - if (exportType === 'json') { - throw new Error('Not implemented'); - } - - const {paletteTokens} = themeState; - - const prepareThemeVariables = (themeVariant: ThemeVariant) => { - let cssVariables = ''; - const privateColors: Record = {}; - - themeState.tokens.forEach((token) => { - // Dont export colors that are equals to default - if (DEFAULT_PALETTE[themeVariant][token] === themeState.palette[themeVariant][token]) { - return; - } - - if (paletteTokens[token]?.privateColors[themeVariant]) { - Object.entries(paletteTokens[token].privateColors[themeVariant]).forEach( - ([privateColorCode, color]) => { - privateColors[createPrivateColorToken(token, privateColorCode)] = color; - cssVariables += `${createPrivateColorCssVariable( - token, - privateColorCode, - )}: ${color};\n`; - }, - ); - cssVariables += '\n'; - } - }); - - cssVariables += '\n'; - - Object.entries(themeState.colors[themeVariant]).forEach( - ([colorName, colorOrPrivateToken]) => { - // Dont export colors that are equals to default - if ( - DEFAULT_THEME.colors[themeVariant][colorName as ColorOption] === - colorOrPrivateToken - ) { - return; - } - - const color = isPrivateColorToken(colorOrPrivateToken) - ? `var(${createPrivateColorCssVariableFromToken(colorOrPrivateToken)})` - : colorOrPrivateToken; - - cssVariables += `${createUtilityColorCssVariable(colorName)}: ${color};\n`; - }, - ); - - return cssVariables; - }; - - let result = ''; - result += '// Light\n' + prepareThemeVariables('light'); - result += '\n// Dark\n' + prepareThemeVariables('dark'); - return result; -} diff --git a/src/components/Theme/types.ts b/src/components/Theme/lib/types.ts similarity index 96% rename from src/components/Theme/types.ts rename to src/components/Theme/lib/types.ts index 33f1b6ed170d..5fb4fd2a2c60 100644 --- a/src/components/Theme/types.ts +++ b/src/components/Theme/lib/types.ts @@ -48,7 +48,7 @@ type PaletteToken = { export type PaletteTokens = Record; -export interface ThemeWizardState extends ThemeOptions { +export interface ThemeCreatorState extends ThemeOptions { /** Mapping color tokens to their information (title and private colors) */ paletteTokens: PaletteTokens; /** All available palette tokens in theme */ diff --git a/src/components/Theme/ColorsTab/BasicPalette/BasicPalette.tsx b/src/components/Theme/ui/BasicPalette/BasicPalette.tsx similarity index 86% rename from src/components/Theme/ColorsTab/BasicPalette/BasicPalette.tsx rename to src/components/Theme/ui/BasicPalette/BasicPalette.tsx index 40d1fe815e50..8872e0b03a5d 100644 --- a/src/components/Theme/ColorsTab/BasicPalette/BasicPalette.tsx +++ b/src/components/Theme/ui/BasicPalette/BasicPalette.tsx @@ -1,7 +1,7 @@ import {Col, Flex} from '@gravity-ui/uikit'; import React from 'react'; -import {useThemeCreator} from '../../hooks/useThemeCreator'; +import {useThemeCreatorMethods, useThemePalette} from '../../hooks'; import {PaletteColors} from './PaletteColors'; import {ThemePaletteCard} from './ThemePaletteCard'; @@ -9,13 +9,8 @@ import {ThemePaletteCard} from './ThemePaletteCard'; const hiddenColors = new Set(['white', 'black', 'brand']); export const BasicPalette = () => { - const { - palette: origPalette, - addColor, - removeColor, - updateColor, - renameColor, - } = useThemeCreator(); + const {addColor, removeColor, updateColor, renameColor} = useThemeCreatorMethods(); + const origPalette = useThemePalette(); const palette = React.useMemo( () => origPalette.filter(({title}) => !hiddenColors.has(title.toLowerCase())), diff --git a/src/components/Theme/ColorsTab/BasicPalette/PaletteColors.scss b/src/components/Theme/ui/BasicPalette/PaletteColors.scss similarity index 100% rename from src/components/Theme/ColorsTab/BasicPalette/PaletteColors.scss rename to src/components/Theme/ui/BasicPalette/PaletteColors.scss diff --git a/src/components/Theme/ColorsTab/BasicPalette/PaletteColors.tsx b/src/components/Theme/ui/BasicPalette/PaletteColors.tsx similarity index 96% rename from src/components/Theme/ColorsTab/BasicPalette/PaletteColors.tsx rename to src/components/Theme/ui/BasicPalette/PaletteColors.tsx index 1d1847b7655c..66b90a85c56c 100644 --- a/src/components/Theme/ColorsTab/BasicPalette/PaletteColors.tsx +++ b/src/components/Theme/ui/BasicPalette/PaletteColors.tsx @@ -4,7 +4,7 @@ import debounce from 'lodash/debounce'; import React from 'react'; import {block} from '../../../../utils'; -import {Palette} from '../../types'; +import {Palette} from '../../lib/types'; import './PaletteColors.scss'; @@ -68,7 +68,7 @@ interface PaletteColorsProps { palette: Palette; onAddColorClick: () => void; onDeleteColor: (title: string) => void; - onUpdateColorTitle: (oldTitle: string, newTitle: string) => void; + onUpdateColorTitle: (params: {oldTitle: string; newTitle: string}) => void; } export const PaletteColors: React.FC = ({ diff --git a/src/components/Theme/ColorsTab/BasicPalette/ThemePaletteCard.scss b/src/components/Theme/ui/BasicPalette/ThemePaletteCard.scss similarity index 100% rename from src/components/Theme/ColorsTab/BasicPalette/ThemePaletteCard.scss rename to src/components/Theme/ui/BasicPalette/ThemePaletteCard.scss diff --git a/src/components/Theme/ColorsTab/BasicPalette/ThemePaletteCard.tsx b/src/components/Theme/ui/BasicPalette/ThemePaletteCard.tsx similarity index 90% rename from src/components/Theme/ColorsTab/BasicPalette/ThemePaletteCard.tsx rename to src/components/Theme/ui/BasicPalette/ThemePaletteCard.tsx index 5c50255e67cf..2cd260fd8d8d 100644 --- a/src/components/Theme/ColorsTab/BasicPalette/ThemePaletteCard.tsx +++ b/src/components/Theme/ui/BasicPalette/ThemePaletteCard.tsx @@ -4,8 +4,8 @@ import React from 'react'; import {block} from '../../../../utils'; import {ColorPickerInput} from '../../../ColorPickerInput/ColorPickerInput'; -import {useThemeColor} from '../../hooks/useThemeColor'; -import {Palette, ThemeVariant} from '../../types'; +import {useThemeUtilityColor} from '../../hooks'; +import {Palette, ThemeVariant} from '../../lib/types'; import './ThemePaletteCard.scss'; @@ -18,7 +18,7 @@ interface ThemePaletteCardProps { } export const ThemePaletteCard: React.FC = ({theme, palette, onUpdate}) => { - const [backgroundColor] = useThemeColor({name: 'base-background', theme}); + const [backgroundColor] = useThemeUtilityColor({name: 'base-background', theme}); const createChangeHandler = React.useCallback( (title: string) => (value: string) => { diff --git a/src/components/Theme/ColorsTab/index.tsx b/src/components/Theme/ui/ColorsTab/index.tsx similarity index 89% rename from src/components/Theme/ColorsTab/index.tsx rename to src/components/Theme/ui/ColorsTab/index.tsx index cecbc6296c43..afd3525c9331 100644 --- a/src/components/Theme/ColorsTab/index.tsx +++ b/src/components/Theme/ui/ColorsTab/index.tsx @@ -2,12 +2,9 @@ import {Grid} from '@gravity-ui/page-constructor'; import {Flex} from '@gravity-ui/uikit'; import React from 'react'; -import {BasicPalette} from './BasicPalette/BasicPalette'; -import {MainSettings} from './MainSettings/MainSettings'; -import { - EditableColorOption, - PrivateColorsSettings, -} from './PrivateColorsSettings/PrivateColorsSettings'; +import {BasicPalette} from '../BasicPalette/BasicPalette'; +import {MainSettings} from '../MainSettings/MainSettings'; +import {EditableColorOption, PrivateColorsSettings} from '../PrivateColorsSettings'; const ADVANCED_COLORS_OPTIONS: EditableColorOption[] = [ { diff --git a/src/components/Theme/ColorsTab/MainSettings/MainSettings.scss b/src/components/Theme/ui/MainSettings/MainSettings.scss similarity index 100% rename from src/components/Theme/ColorsTab/MainSettings/MainSettings.scss rename to src/components/Theme/ui/MainSettings/MainSettings.scss diff --git a/src/components/Theme/ColorsTab/MainSettings/MainSettings.tsx b/src/components/Theme/ui/MainSettings/MainSettings.tsx similarity index 88% rename from src/components/Theme/ColorsTab/MainSettings/MainSettings.tsx rename to src/components/Theme/ui/MainSettings/MainSettings.tsx index f73184ddb737..ab41ff54f72a 100644 --- a/src/components/Theme/ColorsTab/MainSettings/MainSettings.tsx +++ b/src/components/Theme/ui/MainSettings/MainSettings.tsx @@ -5,10 +5,9 @@ import React from 'react'; import {block} from '../../../../utils'; import {ColorPickerInput} from '../../../ColorPickerInput/ColorPickerInput'; -import {ThemePicker} from '../../../ThemePicker'; -import {useThemeColor} from '../../hooks/useThemeColor'; -import {useThemePaletteColor} from '../../hooks/useThemePaletteColor'; -import type {ThemeVariant} from '../../types'; +import {useThemePaletteColor, useThemeUtilityColor} from '../../hooks'; +import type {ThemeVariant} from '../../lib/types'; +import {ThemePicker} from '../ThemePicker'; import './MainSettings.scss'; @@ -24,7 +23,10 @@ export const MainSettings: React.FC = ({ toggleAdvancedMode, }) => { const [theme, setTheme] = React.useState('light'); - const [backgroundColor, setBackgroundColor] = useThemeColor({name: 'base-background', theme}); + const [backgroundColor, setBackgroundColor] = useThemeUtilityColor({ + name: 'base-background', + theme, + }); const [brandColor, setBrandColor] = useThemePaletteColor({token: 'brand', theme}); return ( diff --git a/src/components/PrivateColorSelect/PrivateColorSelect.scss b/src/components/Theme/ui/PrivateColorSelect/PrivateColorSelect.scss similarity index 93% rename from src/components/PrivateColorSelect/PrivateColorSelect.scss rename to src/components/Theme/ui/PrivateColorSelect/PrivateColorSelect.scss index 6faf39f13daa..07bb5b698d0c 100644 --- a/src/components/PrivateColorSelect/PrivateColorSelect.scss +++ b/src/components/Theme/ui/PrivateColorSelect/PrivateColorSelect.scss @@ -1,5 +1,5 @@ @use '~@gravity-ui/page-constructor/styles/variables.scss' as pcVariables; -@use '../../variables.scss'; +@use '../../../../variables.scss'; $block: '.#{variables.$ns}private-colors-select'; diff --git a/src/components/PrivateColorSelect/PrivateColorSelect.tsx b/src/components/Theme/ui/PrivateColorSelect/PrivateColorSelect.tsx similarity index 92% rename from src/components/PrivateColorSelect/PrivateColorSelect.tsx rename to src/components/Theme/ui/PrivateColorSelect/PrivateColorSelect.tsx index c0b515d2086a..e953bd923f97 100644 --- a/src/components/PrivateColorSelect/PrivateColorSelect.tsx +++ b/src/components/Theme/ui/PrivateColorSelect/PrivateColorSelect.tsx @@ -2,10 +2,10 @@ import {ChevronDown, PencilToLine} from '@gravity-ui/icons'; import {Button, Flex, Icon, Popup, TextInput} from '@gravity-ui/uikit'; import React from 'react'; -import {block} from '../../utils'; -import {ColorPickerInput} from '../ColorPickerInput/ColorPickerInput'; -import {ColorPreview} from '../ColorPreview/ColorPreview'; -import {isPrivateColorToken} from '../Theme/utils'; +import {block} from '../../../../utils'; +import {ColorPickerInput} from '../../../ColorPickerInput/ColorPickerInput'; +import {ColorPreview} from '../../../ColorPreview/ColorPreview'; +import {isPrivateColorToken} from '../../lib/themeCreatorUtils'; import './PrivateColorSelect.scss'; import {PrivateColorSelectPopupContent} from './PrivateColorSelectPopupContent'; diff --git a/src/components/PrivateColorSelect/PrivateColorSelectPopupContent.scss b/src/components/Theme/ui/PrivateColorSelect/PrivateColorSelectPopupContent.scss similarity index 96% rename from src/components/PrivateColorSelect/PrivateColorSelectPopupContent.scss rename to src/components/Theme/ui/PrivateColorSelect/PrivateColorSelectPopupContent.scss index 8c9cab62b8b4..77efb8dd9304 100644 --- a/src/components/PrivateColorSelect/PrivateColorSelectPopupContent.scss +++ b/src/components/Theme/ui/PrivateColorSelect/PrivateColorSelectPopupContent.scss @@ -1,5 +1,5 @@ @use '~@gravity-ui/page-constructor/styles/variables.scss' as pcVariables; -@use '../../variables.scss'; +@use '../../../../variables.scss'; $block: '.#{variables.$ns}private-colors-select-popup'; diff --git a/src/components/PrivateColorSelect/PrivateColorSelectPopupContent.tsx b/src/components/Theme/ui/PrivateColorSelect/PrivateColorSelectPopupContent.tsx similarity index 94% rename from src/components/PrivateColorSelect/PrivateColorSelectPopupContent.tsx rename to src/components/Theme/ui/PrivateColorSelect/PrivateColorSelectPopupContent.tsx index 15857372d395..d5c8f4841c00 100644 --- a/src/components/PrivateColorSelect/PrivateColorSelectPopupContent.tsx +++ b/src/components/Theme/ui/PrivateColorSelect/PrivateColorSelectPopupContent.tsx @@ -1,9 +1,9 @@ import {List, Text} from '@gravity-ui/uikit'; import React from 'react'; -import {block} from '../../utils'; -import {ColorPreview} from '../ColorPreview/ColorPreview'; -import {parsePrivateColorToken} from '../Theme/utils'; +import {block} from '../../../../utils'; +import {ColorPreview} from '../../../ColorPreview/ColorPreview'; +import {parsePrivateColorToken} from '../../lib/themeCreatorUtils'; import './PrivateColorSelectPopupContent.scss'; import type {BaseColor, ColorGroup} from './types'; diff --git a/src/components/Theme/ui/PrivateColorSelect/index.ts b/src/components/Theme/ui/PrivateColorSelect/index.ts new file mode 100644 index 000000000000..80cd714d3529 --- /dev/null +++ b/src/components/Theme/ui/PrivateColorSelect/index.ts @@ -0,0 +1 @@ +export {PrivateColorSelect} from './PrivateColorSelect'; diff --git a/src/components/PrivateColorSelect/types.ts b/src/components/Theme/ui/PrivateColorSelect/types.ts similarity index 100% rename from src/components/PrivateColorSelect/types.ts rename to src/components/Theme/ui/PrivateColorSelect/types.ts diff --git a/src/components/Theme/ColorsTab/PrivateColorsSettings/PrivateColorsSettings.scss b/src/components/Theme/ui/PrivateColorsSettings/PrivateColorsSettings.scss similarity index 100% rename from src/components/Theme/ColorsTab/PrivateColorsSettings/PrivateColorsSettings.scss rename to src/components/Theme/ui/PrivateColorsSettings/PrivateColorsSettings.scss diff --git a/src/components/Theme/ColorsTab/PrivateColorsSettings/PrivateColorsSettings.tsx b/src/components/Theme/ui/PrivateColorsSettings/PrivateColorsSettings.tsx similarity index 74% rename from src/components/Theme/ColorsTab/PrivateColorsSettings/PrivateColorsSettings.tsx rename to src/components/Theme/ui/PrivateColorsSettings/PrivateColorsSettings.tsx index 647a4edf780e..61efa468d65c 100644 --- a/src/components/Theme/ColorsTab/PrivateColorsSettings/PrivateColorsSettings.tsx +++ b/src/components/Theme/ui/PrivateColorsSettings/PrivateColorsSettings.tsx @@ -3,13 +3,12 @@ import {Flex, Text} from '@gravity-ui/uikit'; import React from 'react'; import {block} from '../../../../utils'; -import {PrivateColorSelect} from '../../../PrivateColorSelect/PrivateColorSelect'; -import {ThemePicker} from '../../../ThemePicker'; -import {DEFAULT_THEME} from '../../constants'; -import {useThemeColor} from '../../hooks/useThemeColor'; -import {useThemeCreator} from '../../hooks/useThemeCreator'; -import type {ColorsOptions, ThemeVariant} from '../../types'; -import {ThemeColorOption} from '../../utils'; +import {useThemePrivateColorOptions, useThemeUtilityColor} from '../../hooks'; +import {DEFAULT_THEME} from '../../lib/constants'; +import {ThemeColorOption} from '../../lib/themeCreatorUtils'; +import type {ColorsOptions, ThemeVariant} from '../../lib/types'; +import {PrivateColorSelect} from '../PrivateColorSelect'; +import {ThemePicker} from '../ThemePicker'; import './PrivateColorsSettings.scss'; @@ -22,7 +21,7 @@ interface PrivateColorEditorProps { } const PrivateColorEditor: React.FC = ({name, theme, colorGroups}) => { - const [color, setColor] = useThemeColor({ + const [color, setColor] = useThemeUtilityColor({ name, theme, }); @@ -49,12 +48,7 @@ interface PrivateColorsSettingsProps { export const PrivateColorsSettings: React.FC = ({title, options}) => { const [theme, setTheme] = React.useState('light'); - const {getThemePrivateColorOptions} = useThemeCreator(); - - const colorGroups = React.useMemo( - () => getThemePrivateColorOptions(theme), - [getThemePrivateColorOptions, theme], - ); + const themePrivateColorOptions = useThemePrivateColorOptions(theme); return ( @@ -75,7 +69,7 @@ export const PrivateColorsSettings: React.FC = ({tit ))} diff --git a/src/components/Theme/ui/PrivateColorsSettings/index.ts b/src/components/Theme/ui/PrivateColorsSettings/index.ts new file mode 100644 index 000000000000..821d74e0cad7 --- /dev/null +++ b/src/components/Theme/ui/PrivateColorsSettings/index.ts @@ -0,0 +1,2 @@ +export {PrivateColorsSettings} from './PrivateColorsSettings'; +export type {EditableColorOption} from './PrivateColorsSettings'; diff --git a/src/components/Theme/ui/ThemeCreatorContextProvider.tsx b/src/components/Theme/ui/ThemeCreatorContextProvider.tsx new file mode 100644 index 000000000000..a786c6e58156 --- /dev/null +++ b/src/components/Theme/ui/ThemeCreatorContextProvider.tsx @@ -0,0 +1,148 @@ +import React from 'react'; + +import {ThemeCreatorContext, ThemeCreatorMethodsContext} from '../lib/themeCreatorContext'; +import type {ThemeCreatorMethodsContextType} from '../lib/themeCreatorContext'; +import type { + AddColorToThemeParams, + ChangeUtilityColorInThemeParams, + RenameColorInThemeParams, + UpdateColorInThemeParams, +} from '../lib/themeCreatorUtils'; +import { + addColorToTheme, + changeUtilityColorInTheme, + initThemeCreator, + removeColorFromTheme, + renameColorInTheme, + updateColorInTheme, +} from '../lib/themeCreatorUtils'; +import type {ThemeCreatorState, ThemeOptions} from '../lib/types'; + +type ThemeCreatorAction = + | { + type: 'addColor'; + payload?: AddColorToThemeParams; + } + | { + type: 'updateColor'; + payload: UpdateColorInThemeParams; + } + | { + type: 'removeColor'; + payload: string; + } + | { + type: 'renameColor'; + payload: RenameColorInThemeParams; + } + | { + type: 'changeUtilityColor'; + payload: ChangeUtilityColorInThemeParams; + } + | { + type: 'reinitialize'; + payload: ThemeOptions; + }; + +const themeCreatorReducer = ( + prevState: ThemeCreatorState, + action: ThemeCreatorAction, +): ThemeCreatorState => { + switch (action.type) { + case 'addColor': + return addColorToTheme(prevState, action.payload); + case 'removeColor': + return removeColorFromTheme(prevState, action.payload); + case 'renameColor': + return renameColorInTheme(prevState, action.payload); + case 'updateColor': + return updateColorInTheme(prevState, action.payload); + case 'changeUtilityColor': + return changeUtilityColorInTheme(prevState, action.payload); + case 'reinitialize': + return initThemeCreator(action.payload); + default: + return prevState; + } +}; + +interface ThemeCreatorProps extends React.PropsWithChildren { + initialTheme: ThemeOptions; +} + +export const ThemeCreatorContextProvider: React.FC = ({ + initialTheme, + children, +}) => { + const [themeCreator, dispatchThemeCreator] = React.useReducer( + themeCreatorReducer, + undefined, + () => initThemeCreator(initialTheme), + ); + + React.useEffect(() => { + dispatchThemeCreator({ + type: 'reinitialize', + payload: initialTheme, + }); + }, [initialTheme]); + + const addColor = React.useCallback((payload) => { + dispatchThemeCreator({ + type: 'addColor', + payload, + }); + }, []); + + const renameColor = React.useCallback( + (payload) => { + dispatchThemeCreator({ + type: 'renameColor', + payload, + }); + }, + [], + ); + + const removeColor = React.useCallback( + (payload) => { + dispatchThemeCreator({ + type: 'removeColor', + payload, + }); + }, + [], + ); + + const updateColor = React.useCallback( + (payload) => { + dispatchThemeCreator({ + type: 'updateColor', + payload, + }); + }, + [], + ); + + const changeUtilityColor = React.useCallback< + ThemeCreatorMethodsContextType['changeUtilityColor'] + >((payload) => { + dispatchThemeCreator({ + type: 'changeUtilityColor', + payload, + }); + }, []); + + const methods = React.useMemo( + () => ({addColor, renameColor, removeColor, updateColor, changeUtilityColor}), + [addColor, renameColor, removeColor, updateColor, changeUtilityColor], + ); + + return ( + + + {children} + + + ); +}; diff --git a/src/components/ThemePicker/ThemePicker.scss b/src/components/Theme/ui/ThemePicker/ThemePicker.scss similarity index 85% rename from src/components/ThemePicker/ThemePicker.scss rename to src/components/Theme/ui/ThemePicker/ThemePicker.scss index e958f3212bb6..5d8f8af41067 100644 --- a/src/components/ThemePicker/ThemePicker.scss +++ b/src/components/Theme/ui/ThemePicker/ThemePicker.scss @@ -1,5 +1,5 @@ @use '~@gravity-ui/page-constructor/styles/variables.scss' as pcVariables; -@use '../../variables.scss'; +@use '../../../../variables.scss'; $block: '.#{variables.$ns}theme-picker'; diff --git a/src/components/ThemePicker/ThemePicker.tsx b/src/components/Theme/ui/ThemePicker/ThemePicker.tsx similarity index 92% rename from src/components/ThemePicker/ThemePicker.tsx rename to src/components/Theme/ui/ThemePicker/ThemePicker.tsx index 735c701c89ec..4edf8c1c8adb 100644 --- a/src/components/ThemePicker/ThemePicker.tsx +++ b/src/components/Theme/ui/ThemePicker/ThemePicker.tsx @@ -2,14 +2,13 @@ import {Moon, Sun} from '@gravity-ui/icons'; import {Flex, Icon, RadioButton, Text} from '@gravity-ui/uikit'; import React from 'react'; -import {block} from '../../utils'; +import {block} from '../../../../utils'; +import type {ThemeVariant} from '../../lib/types'; import './ThemePicker.scss'; const b = block('theme-picker'); -type ThemeVariant = 'light' | 'dark'; - interface ThemePickerProps { value: ThemeVariant; onUpdate: (value: ThemeVariant) => void; diff --git a/src/components/ThemePicker/index.ts b/src/components/Theme/ui/ThemePicker/index.ts similarity index 100% rename from src/components/ThemePicker/index.ts rename to src/components/Theme/ui/ThemePicker/index.ts From 08e3b8c04ed055d030fd8405b2144ad7e4589c39 Mon Sep 17 00:00:00 2001 From: Daniil Gaponov Date: Sun, 16 Jun 2024 12:47:44 +0300 Subject: [PATCH 14/14] chore: fix styles --- src/components/Theme/lib/themeCreatorExport.ts | 2 +- src/components/Theme/ui/BasicPalette/PaletteColors.scss | 2 +- src/components/Theme/ui/BasicPalette/PaletteColors.tsx | 4 ++-- src/components/Theme/ui/MainSettings/MainSettings.scss | 4 ++-- .../Theme/ui/PrivateColorSelect/PrivateColorSelect.scss | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/Theme/lib/themeCreatorExport.ts b/src/components/Theme/lib/themeCreatorExport.ts index 87db752bb4d4..514d04f7d39f 100644 --- a/src/components/Theme/lib/themeCreatorExport.ts +++ b/src/components/Theme/lib/themeCreatorExport.ts @@ -31,7 +31,7 @@ export function exportTheme( } if (paletteTokens[token]?.privateColors[themeVariant]) { - Object.entries(paletteTokens[token].privateColors[themeVariant]).forEach( + Object.entries(paletteTokens[token].privateColors[themeVariant]!).forEach( ([privateColorCode, color]) => { privateColors[createPrivateColorToken(token, privateColorCode)] = color; cssVariables += `${createPrivateColorCssVariable( diff --git a/src/components/Theme/ui/BasicPalette/PaletteColors.scss b/src/components/Theme/ui/BasicPalette/PaletteColors.scss index c68d249201c7..a11fa32855f6 100644 --- a/src/components/Theme/ui/BasicPalette/PaletteColors.scss +++ b/src/components/Theme/ui/BasicPalette/PaletteColors.scss @@ -4,7 +4,7 @@ $block: '.#{variables.$ns}palette-colors'; #{$block} { --g-button-border-radius: var(--g-spacing-2); - --g-text-input-border-radius: var(--g-spacing-2); + --g-text-input-border-radius: 8px; padding-top: var(--g-spacing-4); diff --git a/src/components/Theme/ui/BasicPalette/PaletteColors.tsx b/src/components/Theme/ui/BasicPalette/PaletteColors.tsx index 66b90a85c56c..68baac0c1068 100644 --- a/src/components/Theme/ui/BasicPalette/PaletteColors.tsx +++ b/src/components/Theme/ui/BasicPalette/PaletteColors.tsx @@ -12,7 +12,7 @@ const b = block('palette-colors'); interface PaletteColorEditorProps { paletteColorData: Palette[0]; - onUpdateTitle: (oldTitle: string, newTitle: string) => void; + onUpdateTitle: (params: {oldTitle: string; newTitle: string}) => void; onDelete: (title: string) => void; } @@ -32,7 +32,7 @@ const PaletteColorEditor: React.FC = ({ const handleDelete = React.useCallback(() => onDelete(title), [onDelete, title]); const updateTitle = React.useCallback( - (newTitle: string) => onUpdateTitle(title, newTitle), + (newTitle: string) => onUpdateTitle({oldTitle: title, newTitle}), [title, onUpdateTitle], ); diff --git a/src/components/Theme/ui/MainSettings/MainSettings.scss b/src/components/Theme/ui/MainSettings/MainSettings.scss index 72dbb448bcf3..d1fb9f11be1f 100644 --- a/src/components/Theme/ui/MainSettings/MainSettings.scss +++ b/src/components/Theme/ui/MainSettings/MainSettings.scss @@ -3,8 +3,8 @@ $block: '.#{variables.$ns}main-settings'; #{$block} { - --g-button-border-radius: var(--g-spacing-2); - --g-text-input-border-radius: var(--g-spacing-2); + --g-button-border-radius: 8px; + --g-text-input-border-radius: 8px; & &__row { --gc-form-row-label-width: 400px; diff --git a/src/components/Theme/ui/PrivateColorSelect/PrivateColorSelect.scss b/src/components/Theme/ui/PrivateColorSelect/PrivateColorSelect.scss index 07bb5b698d0c..1c3353052e42 100644 --- a/src/components/Theme/ui/PrivateColorSelect/PrivateColorSelect.scss +++ b/src/components/Theme/ui/PrivateColorSelect/PrivateColorSelect.scss @@ -4,8 +4,8 @@ $block: '.#{variables.$ns}private-colors-select'; #{$block} { - --g-button-border-radius: var(--g-spacing-2); - --g-text-input-border-radius: var(--g-spacing-2); + --g-button-border-radius: 8px; + --g-text-input-border-radius: 8px; --g-color-base-selection: var(--g-color-private-yellow-150); &__preview {