diff --git a/website/plasma-theme-builder/package.json b/website/plasma-theme-builder/package.json index bcefc57b7d..0239f9f2f7 100644 --- a/website/plasma-theme-builder/package.json +++ b/website/plasma-theme-builder/package.json @@ -23,6 +23,7 @@ "prettier": "^2.6.2", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-router-dom": "^6.26.1", "react-scripts": "4.0.0", "styled-components": "^5.3.3" }, diff --git a/website/plasma-theme-builder/public/index.html b/website/plasma-theme-builder/public/index.html index 4671d8a003..323a7a520a 100644 --- a/website/plasma-theme-builder/public/index.html +++ b/website/plasma-theme-builder/public/index.html @@ -10,6 +10,8 @@ /> + + diff --git a/website/plasma-theme-builder/src/GlobalStyle.tsx b/website/plasma-theme-builder/src/GlobalStyle.tsx index 29945fc5f7..7e20f59771 100644 --- a/website/plasma-theme-builder/src/GlobalStyle.tsx +++ b/website/plasma-theme-builder/src/GlobalStyle.tsx @@ -1,10 +1,9 @@ -import React from 'react'; +import React, { useState, createContext, useContext } from 'react'; import { createGlobalStyle } from 'styled-components'; -import { plasma } from '@salutejs/plasma-typo'; +import { plasma, ruler, sage, mage, soulmate } from '@salutejs/plasma-typo'; import { dark } from '@salutejs/plasma-tokens-b2c/themes'; import { text } from '@salutejs/plasma-tokens-b2c'; import { b2c } from '@salutejs/plasma-tokens-b2c/typo'; -import type { ThemeMode } from '@salutejs/plasma-tokens-utils'; import { SBSansTextMono } from './components/mixins'; @@ -24,18 +23,46 @@ const DocumentStyle = createGlobalStyle` `; const DarkThemeStyle = createGlobalStyle(dark); -const TypoStyle = createGlobalStyle(plasma); const OldTypoStyle = createGlobalStyle(b2c); -export const GlobalStyle = () => { - const theme: ThemeMode = 'dark'; +const archetypeMap = { + plasma: createGlobalStyle(plasma), + ruler: createGlobalStyle(ruler), + sage: createGlobalStyle(sage), + mage: createGlobalStyle(mage), + soulmate: createGlobalStyle(soulmate), +}; + +export type Archetype = keyof typeof archetypeMap; + +const ThemeContext = createContext<{ + toggleArchetype: (value: Archetype) => void; +}>({ + toggleArchetype: () => {}, +}); + +export const useTheme = () => useContext(ThemeContext); + +export const GlobalStyle = ({ children }: any) => { + const [archetype, setArchetype] = useState('plasma'); + + const toggleArchetype = (value: Archetype) => { + setArchetype(value); + }; + + const TypoStyle = archetypeMap[archetype]; return ( - <> + - {theme === 'dark' && } + - > + {children} + ); }; diff --git a/website/plasma-theme-builder/src/_new/examples/buildAndModifyMockTheme.ts b/website/plasma-theme-builder/src/_new/examples/buildAndModifyMockTheme.ts index 8c356e1568..80b5ba974e 100644 --- a/website/plasma-theme-builder/src/_new/examples/buildAndModifyMockTheme.ts +++ b/website/plasma-theme-builder/src/_new/examples/buildAndModifyMockTheme.ts @@ -263,13 +263,13 @@ export const buildAndModifyMockTheme = async () => { }, }); - const onlyGradientTokens = mockTheme.getTokens('gradient'); - console.log('onlyGradientTokens', onlyGradientTokens); + const allTokens = mockTheme.getTokens(); + console.log('allTokens', allTokens); mockTheme.setTokenValue('shadow.name', 'shadow', 'android', { color: 'red', elevation: 0 }); - const allTokens = mockTheme.getTokens(); - console.log('allTokens', allTokens); + const onlyGradientTokens = mockTheme.getTokens('gradient'); + console.log('onlyGradientTokens', onlyGradientTokens); const value = mockTheme.getTokenValue('shape.name', 'shape', 'ios'); const values = mockTheme.getTokenValue('shape.name', 'shape'); diff --git a/website/plasma-theme-builder/src/components/App.tsx b/website/plasma-theme-builder/src/components/App.tsx index 6b96c6fc88..a0fd727ff1 100644 --- a/website/plasma-theme-builder/src/components/App.tsx +++ b/website/plasma-theme-builder/src/components/App.tsx @@ -1,5 +1,6 @@ -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import styled, { css } from 'styled-components'; +import { Route, Routes, useNavigate } from 'react-router-dom'; import { Footer } from './Footer/Footer'; import { Main } from './Main/Main'; @@ -12,6 +13,14 @@ import { useDefaultThemeData, useFetchTheme } from '../hooks'; import { multipleMediaQuery } from './mixins'; import { getURLParams, pushHistoryState } from '../utils'; import type { Theme as ThemeType } from '../types'; +// import { useDefaultThemeData, useFetchTheme } from '../hooks'; +// import { multipleMediaQuery } from './mixins'; +// import { getURLParams, pushHistoryState } from '../utils'; +// import type { Theme as ThemeType } from '../types'; +import { ToneGenerator } from './ColorGenerator/ToneGenerator'; +import { GrayscaleGenerator } from './ColorGenerator/GrayscaleGenerator'; +import { FontFamilyGenerator } from './ColorGenerator/FontFamilyGenerator'; +import { TokensEditor } from './_new/TokensEditor'; const StyledRoot = styled.div` min-width: 35rem; @@ -31,82 +40,66 @@ const StyledRoot = styled.div` `; const PAGE_TYPE = { - MAIN: 'MAIN', - GENERATOR: 'GENERATOR', - THEME: 'THEME', - PULL_REQUEST: 'PULL_REQUEST', - ERROR: 'ERROR', + TONE: 'TONE', + GRAYSCALE: 'GRAYSCALE', + FONT_FAMILY: 'FONT_FAMILY', + TOKENS_EDITOR: 'TOKENS_EDITOR', } as const; type PageType = typeof PAGE_TYPE[keyof typeof PAGE_TYPE]; const App = () => { - const [state, setState] = useState(PAGE_TYPE.MAIN); - const [data, setData] = useState(); - const [token, setToken] = useState(); - const defaultData = useDefaultThemeData(); + const [state, setState] = useState(PAGE_TYPE.TONE); + // const [data, setData] = useState(); + // const [token, setToken] = useState(); + // const defaultData = useDefaultThemeData(); + + const navigate = useNavigate(); const [themeName, branchName] = getURLParams(['theme', 'branch']); const [themeData, errorMessage] = useFetchTheme(themeName, branchName); - useEffect(() => { - if (!token) { - return; - } - - if (themeData) { - setState(PAGE_TYPE.THEME); - setData(themeData); - return; - } - - if (errorMessage) { - setState(PAGE_TYPE.ERROR); - } - }, [themeData, token, errorMessage]); - - const onSetToken = useCallback((value: string) => { - setToken(value); - }, []); - - const onMain = useCallback(() => { - setState(PAGE_TYPE.MAIN); - pushHistoryState('./'); - }, []); - - const onGenerateTheme = useCallback(() => { - setState(PAGE_TYPE.GENERATOR); - pushHistoryState('./'); - }, []); - - const onPreviewTheme = useCallback((data: ThemeType) => { - setState(PAGE_TYPE.THEME); - setData(data); - }, []); - - const onPullRequest = useCallback((data: ThemeType) => { - setState(PAGE_TYPE.PULL_REQUEST); - setData(data); - }, []); + const onGrayScale = () => { + setState(PAGE_TYPE.GRAYSCALE); + }; + + const onTokensEditor = () => { + setState(PAGE_TYPE.TOKENS_EDITOR); + }; + + const onTone = () => { + setState(PAGE_TYPE.TONE); + }; + + const onFontFamily = () => { + setState(PAGE_TYPE.FONT_FAMILY); + }; return ( - - {state === PAGE_TYPE.MAIN && } - {state === PAGE_TYPE.GENERATOR && } - {state === PAGE_TYPE.THEME && ( - - )} - {state === PAGE_TYPE.PULL_REQUEST && } - {state === PAGE_TYPE.ERROR && } - - + + } /> + { + navigate('/new'); + }} + /> + } + /> + {/* {state === PAGE_TYPE.TONE && } + {state === PAGE_TYPE.GRAYSCALE && ( + + )} + {state === PAGE_TYPE.FONT_FAMILY && ( + + )} + {state === PAGE_TYPE.TOKENS_EDITOR && ( + + )} */} + ); }; diff --git a/website/plasma-theme-builder/src/components/ColorGenerator/FontFamilyGenerator.tsx b/website/plasma-theme-builder/src/components/ColorGenerator/FontFamilyGenerator.tsx new file mode 100644 index 0000000000..09614c53b6 --- /dev/null +++ b/website/plasma-theme-builder/src/components/ColorGenerator/FontFamilyGenerator.tsx @@ -0,0 +1,606 @@ +import React, { ChangeEvent, UIEvent, useEffect, useRef, useState } from 'react'; +import styled, { createGlobalStyle } from 'styled-components'; + +import { textPrimary } from '@salutejs/plasma-tokens-b2c/new'; + +import { Container as ContainerBase, Col, Row } from '@salutejs/plasma-b2c'; +import { + h1, + h1Bold, + h2, + h2Bold, + h3, + h3Bold, + h4, + h4Bold, + h5, + h5Bold, + bodyL, + bodyLBold, + bodyM, + bodyMBold, + bodyS, + bodySBold, + bodyXS, + bodyXSBold, + bodyXXS, + bodyXXSBold, +} from '@salutejs/plasma-typo'; + +import { Header } from './Header'; +import { RadioboxButton } from './RadioboxButton'; +import { Archetype, useTheme } from '../../GlobalStyle'; + +const NoScroll = createGlobalStyle` + html, body { + overscroll-behavior: none; + } +`; + +const Container = styled.div` + position: relative; + + width: 100%; + height: 100vh; + box-sizing: border-box; + background-color: #000; +`; + +const Wrapper = styled.div` + position: absolute; + inset: 0.125rem; + border-radius: 0.5rem; + + overflow: scroll; + + ::-webkit-scrollbar { + display: none; + } + scrollbar-width: none; +`; + +const ColorTone = styled.div` + position: absolute; + inset: 0; + + background: linear-gradient(90deg, #080808 0%, #080808 25.03%, #080808 49.72%, #080808 74.76%, #080808 100%); + + padding: 2rem; + color: #ffffff17; + font-size: 4rem; +`; + +const ScrollableHiddenContainer = styled.div` + overflow-y: scroll; + scroll-snap-type: y mandatory; + scrollbar-width: none; + opacity: 0; + + position: absolute; + inset: 0; +`; + +const HiddenColorTone = styled.div` + position: relative; + height: 100vh; + + scroll-snap-align: center; + opacity: 1; + + background: transparent; +`; + +const Grid = styled(ContainerBase)` + position: absolute; +`; + +const ColorToneDisplay = styled.div` + position: relative; + top: 6.125rem; +`; + +const ColorToneDescription = styled.div` + width: 16.25rem; + margin-bottom: 1.5rem; + + color: ${textPrimary}; + + font-family: var(--plasma-typo-text-s-font-family); + font-size: var(--plasma-typo-text-s-font-size); + font-weight: var(--plasma-typo-text-s-font-weight); + line-height: var(--plasma-typo-text-s-line-height); +`; + +const ColorToneName = styled.div` + color: ${textPrimary}; + + font-family: var(--plasma-typo-dspl-m-font-family); + font-size: var(--plasma-typo-dspl-m-font-size); + font-weight: var(--plasma-typo-dspl-m-font-weight); + line-height: var(--plasma-typo-dspl-m-line-height); + + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +`; + +const HelperBlock = styled.div` + flex: 1; + max-width: calc(100% / var(--plasma_private-grid-cols-count) * var(--plasma_private-grid-col-largeM-offset, 16)); +`; + +const ColorToneName2 = styled.div` + margin-top: 0.75rem; + + color: ${textPrimary}; + + font-family: var(--plasma-typo-body-m-font-family); + font-size: var(--plasma-typo-body-m-font-size); + font-weight: var(--plasma-typo-body-m-font-weight); + line-height: var(--plasma-typo-body-m-line-height); +`; + +const StyledHeader = styled(Header)``; + +const TypographyPreview = styled.div` + position: absolute; + left: 3.375rem; + bottom: 2.875rem; +`; + +const TypographyPreviewBlock = styled.div` + margin-top: 2rem; + + display: flex; + flex-direction: column; + gap: 0.5rem; +`; + +const TypographyLabel = styled.div` + color: rgba(255, 255, 255, 0.28); + + ${bodyXXS}; +`; + +const TypographySeparator = styled.span` + margin: 0 -0.625rem; +`; + +/** HEADER 1 */ +const HeaderH1Wrapper = styled.div` + color: rgba(255, 255, 255, 0.28); + + ${h1}; + + display: flex; + gap: 0.5rem; +`; +const HeaderH1Regular = styled.span` + color: rgba(255, 255, 255, 0.96); + + ${h1}; +`; +const HeaderH1Bold = styled.span` + color: rgba(255, 255, 255, 0.96); + + ${h1Bold}; +`; +const BodyLWrapper = styled.div` + color: rgba(255, 255, 255, 0.28); + + ${bodyL}; + + display: flex; + gap: 0.5rem; +`; +const BodyLRegular = styled.span` + color: rgba(255, 255, 255, 0.8); + + ${bodyL}; +`; +const BodyLBold = styled.span` + color: rgba(255, 255, 255, 0.8); + + ${bodyLBold}; +`; + +/** HEADER 2 */ +const HeaderH2Wrapper = styled.div` + color: rgba(255, 255, 255, 0.28); + + ${h2}; + + display: flex; + gap: 0.5rem; +`; +const HeaderH2Regular = styled.span` + color: rgba(255, 255, 255, 0.96); + + ${h2}; +`; +const HeaderH2Bold = styled.span` + color: rgba(255, 255, 255, 0.96); + + ${h2Bold}; +`; +const BodyMWrapper = styled.div` + color: rgba(255, 255, 255, 0.28); + + ${bodyM}; + + display: flex; + gap: 0.5rem; +`; +const BodyMRegular = styled.span` + color: rgba(255, 255, 255, 0.8); + + ${bodyM}; +`; +const BodyMBold = styled.span` + color: rgba(255, 255, 255, 0.8); + + ${bodyMBold}; +`; + +/** HEADER 3 */ +const HeaderH3Wrapper = styled.div` + color: rgba(255, 255, 255, 0.28); + + ${h3}; + + display: flex; + gap: 0.5rem; +`; +const HeaderH3Regular = styled.span` + color: rgba(255, 255, 255, 0.96); + + ${h3}; +`; +const HeaderH3Bold = styled.span` + color: rgba(255, 255, 255, 0.96); + + ${h3Bold}; +`; +const BodySWrapper = styled.div` + color: rgba(255, 255, 255, 0.28); + + ${bodyS}; + + display: flex; + gap: 0.5rem; +`; +const BodySRegular = styled.span` + color: rgba(255, 255, 255, 0.8); + + ${bodyS}; +`; +const BodySBold = styled.span` + color: rgba(255, 255, 255, 0.8); + + ${bodySBold}; +`; + +/** HEADER 4 */ +const HeaderH4Wrapper = styled.div` + color: rgba(255, 255, 255, 0.28); + + ${h4}; + + display: flex; + gap: 0.5rem; +`; +const HeaderH4Regular = styled.span` + color: rgba(255, 255, 255, 0.96); + + ${h4}; +`; +const HeaderH4Bold = styled.span` + color: rgba(255, 255, 255, 0.96); + + ${h4Bold}; +`; +const BodyXSWrapper = styled.div` + color: rgba(255, 255, 255, 0.28); + + ${bodyXS}; + + display: flex; + gap: 0.5rem; +`; +const BodyXSRegular = styled.span` + color: rgba(255, 255, 255, 0.8); + + ${bodyXS}; +`; +const BodyXSBold = styled.span` + color: rgba(255, 255, 255, 0.8); + + ${bodyXSBold}; +`; + +/** HEADER 5 */ +const HeaderH5Wrapper = styled.div` + color: rgba(255, 255, 255, 0.28); + + ${h5}; + + display: flex; + gap: 0.5rem; +`; +const HeaderH5Regular = styled.span` + color: rgba(255, 255, 255, 0.96); + + ${h5}; +`; +const HeaderH5Bold = styled.span` + color: rgba(255, 255, 255, 0.96); + + ${h5Bold}; +`; +const BodyXXSWrapper = styled.div` + color: rgba(255, 255, 255, 0.28); + + ${bodyXXS}; + + display: flex; + gap: 0.5rem; +`; +const BodyXXSRegular = styled.span` + color: rgba(255, 255, 255, 0.8); + + ${bodyXXS}; +`; +const BodyXXSBold = styled.span` + color: rgba(255, 255, 255, 0.8); + + ${bodyXXSBold}; +`; + +const fontFamilyMap: { + archetype: Archetype; + name: string; + displayFontFamily: string; + textFontFamily: string; +}[] = [ + { + archetype: 'ruler', + name: 'Правитель', + displayFontFamily: 'SB Sans Display', + textFontFamily: 'SB Sans Text', + }, + { + archetype: 'sage', + name: 'Мудрец', + displayFontFamily: 'SB Serif Display', + textFontFamily: 'SB Sans Text', + }, + { + archetype: 'mage', + name: 'Маг', + displayFontFamily: 'SB Sans Display', + textFontFamily: 'SB Sans Text', + }, + { + archetype: 'soulmate', + name: 'Душа компании', + displayFontFamily: 'SB Sans Display', + textFontFamily: 'SB Sans Text', + }, + { + archetype: 'soulmate', + name: 'Длинное название', + displayFontFamily: 'SB Sans Display SB Sans Display SB Sans Display SB Sans Display SB Sans Display', + textFontFamily: 'SB Sans Text', + }, + { + archetype: 'soulmate', + name: 'Короткое название', + displayFontFamily: 'SB Sans', + textFontFamily: 'SB Sans Text', + }, +]; + +interface FontFamilyGeneratorProps { + onNextPage?: () => void; + onPreviousPage?: () => void; +} + +export const FontFamilyGenerator = (props: FontFamilyGeneratorProps) => { + const hiddenContainerRef = useRef(null); + const thumbRef = useRef(null); + + const { toggleArchetype } = useTheme(); + + const sliderMin = 0; + const sliderMax = fontFamilyMap.length - 1; + + const [colorIndexValue, setColorIndexValue] = useState(0); + + useEffect(() => { + const scaledColorValue = ((colorIndexValue - sliderMin) / (sliderMax - sliderMin)).toFixed(3); + + if (thumbRef.current) { + thumbRef.current.style.transform = `translateY(calc(${scaledColorValue} * calc(var(--private-gradient-scroll-height) - 0.25rem)))`; + } + }, [colorIndexValue, sliderMax]); + + const updateSliderValue = (selectColorIndex: number, withAutoScroll = true) => { + setColorIndexValue(selectColorIndex); + toggleArchetype(fontFamilyMap[selectColorIndex].archetype); + + if (withAutoScroll && hiddenContainerRef.current) { + hiddenContainerRef.current.scrollTo({ + top: hiddenContainerRef.current.offsetHeight * selectColorIndex, + }); + } + }; + + const onScrollHiddenContainer = (event: UIEvent) => { + const selectColorIndex = Math.round(event.currentTarget.scrollTop / event.currentTarget.offsetHeight); + const withAutoScroll = false; + + updateSliderValue(selectColorIndex, withAutoScroll); + }; + + const handleOptionChange = (event: ChangeEvent) => { + updateSliderValue(Number(event.target.value)); + }; + + return ( + + + + {fontFamilyMap.map(() => ( + + ))} + + + + + Bold + & + Regular + headerH1 + + + Bold + & + Regular + bodyL + + + + + + Bold + & + Regular + headerH2 + + + Bold + & + Regular + BodyM + + + + + + Bold + & + Regular + headerH3 + + + Bold + & + Regular + BodyS + + + + + + Bold + & + Regular + headerH4 + + + Bold + & + Regular + BodyXS + + + + + + Bold + & + Regular + headerH5 + + + Bold + & + Regular + BodyXXS + + + + + + + + + + Определяет шрифты для заголовков и текста + + + + + + + + + + {fontFamilyMap[colorIndexValue].displayFontFamily} + {fontFamilyMap[colorIndexValue].textFontFamily} + + + + + + + + {fontFamilyMap.map((item, index) => ( + { + props.onNextPage?.(); + }} + /> + ))} + + + + + + + {fontFamilyMap.map(() => ( + + ))} + + + + + ); +}; diff --git a/website/plasma-theme-builder/src/components/ColorGenerator/GradientVerticalSlider.tsx b/website/plasma-theme-builder/src/components/ColorGenerator/GradientVerticalSlider.tsx new file mode 100644 index 0000000000..b5a6e6bcbc --- /dev/null +++ b/website/plasma-theme-builder/src/components/ColorGenerator/GradientVerticalSlider.tsx @@ -0,0 +1,126 @@ +import React, { ChangeEvent, RefObject } from 'react'; +import styled, { css } from 'styled-components'; + +import { IconArrowsMoveVertical } from '@salutejs/plasma-icons'; +import { BackgroundGradientSaturation } from './types'; + +const getGradientColorPart = (colors: string[]) => + colors.map((color: string, index: number) => `${color} ${(100 / colors.length) * index}%`).join(','); + +const Root = styled.div<{ colors: string[] }>` + position: absolute; + z-index: 999; + + left: 1.5rem; + top: 6.125rem; + + --private-gradient-scroll-height: calc(100vh - 6.125rem - 4.375rem); + height: var(--private-gradient-scroll-height); + width: 0.25rem; + border-radius: 0.125rem; + + background: linear-gradient(180deg, ${({ colors }) => getGradientColorPart(colors)}); +`; + +const thumbStyle = css` + border-radius: 0.5rem; + height: 0.25rem; + width: 0.25rem; + outline: 0.5rem solid #fff; +`; + +const Slider = styled.input` + cursor: pointer; + appearance: none; + background: transparent; + margin: 0; + opacity: 0; + + position: absolute; + left: 0.75rem; + + transform: rotate(90deg); + transform-origin: 0 0; + + padding: 0 0.5rem; + top: -0.5rem; + height: 1.25rem; + width: var(--private-gradient-scroll-height); + + :focus { + outline: none; + } + + ::-webkit-slider-thumb { + appearance: none; + position: relative; + + ${thumbStyle} + } + + ::-moz-range-thumb { + border: none; + border-radius: 0; + background: transparent; + + ${thumbStyle} + } +`; + +const Thumb = styled.div` + position: absolute; + + ${thumbStyle} + + transition: transform 0.5s cubic-bezier(0.33, 1, 0.68, 1); + will-change: transform; +`; + +const GradientShadow = styled.div<{ colors: string[] }>` + position: absolute; + width: 0.25rem; + left: -0.375rem; + top: 1.5rem; + bottom: 1.5rem; + + border-radius: 0.125rem; + + filter: blur(32px); + opacity: 0.6; + + background: linear-gradient(180deg, ${({ colors }) => getGradientColorPart(colors)}); +`; + +const HintIcon = styled(IconArrowsMoveVertical)` + position: absolute; + bottom: -1.5rem; + left: 50%; + transform: translateX(-50%); +`; + +interface GradientVerticalSliderProps { + value: number; + onChange: (event: ChangeEvent) => void; + min: number; + max: number; + gradients: BackgroundGradientSaturation[]; + thumbRef: RefObject; +} + +export const GradientVerticalSlider = ({ + value, + gradients, + min, + max, + thumbRef, + onChange, +}: GradientVerticalSliderProps) => { + return ( + item[500])}> + item[500])} /> + + + + + ); +}; diff --git a/website/plasma-theme-builder/src/components/ColorGenerator/GrayscaleGenerator.tsx b/website/plasma-theme-builder/src/components/ColorGenerator/GrayscaleGenerator.tsx new file mode 100644 index 0000000000..2636575bf0 --- /dev/null +++ b/website/plasma-theme-builder/src/components/ColorGenerator/GrayscaleGenerator.tsx @@ -0,0 +1,271 @@ +import React, { ChangeEvent, UIEvent, useEffect, useRef, useState } from 'react'; +import styled, { createGlobalStyle } from 'styled-components'; + +import { onLightTextPrimary, onLightTextSecondary } from '@salutejs/plasma-tokens-b2c/new'; +import { Container as ContainerBase, Col, Row } from '@salutejs/plasma-b2c'; +import { general } from '@salutejs/plasma-colors'; +import { upperFirstLetter } from '@salutejs/plasma-tokens-utils'; + +import { Header } from './Header'; +import { BackgroundGradientSaturation } from './types'; +import { RadioboxButton } from './RadioboxButton'; + +const NoScroll = createGlobalStyle` + html, body { + overscroll-behavior: none; + } +`; + +const Container = styled.div` + position: relative; + + width: 100%; + height: 100vh; + box-sizing: border-box; + background-color: #000; +`; + +const Wrapper = styled.div` + position: absolute; + inset: 0.125rem; + border-radius: 0.5rem; + + overflow: scroll; + + ::-webkit-scrollbar { + display: none; + } + scrollbar-width: none; + + .transitionOpacity { + opacity: 1; + } +`; + +const ColorTone = styled.div<{ colors: BackgroundGradientSaturation }>` + position: absolute; + inset: 0; + + opacity: 0; + transition: opacity 0.3s ease-in-out; + + /* stylelint-disable */ + background: linear-gradient( + 90deg, + ${({ colors }) => colors[1000]} 5%, + ${({ colors }) => colors[800]} 30%, + ${({ colors }) => colors[500]} 50%, + ${({ colors }) => colors[250]} 70%, + ${({ colors }) => colors[100]} 95% + ); + + padding: 2rem; + color: #ffffff17; + font-size: 4rem; +`; + +const ScrollableHiddenContainer = styled.div` + overflow-y: scroll; + scroll-snap-type: y mandatory; + scroll-bar-width: none; + opacity: 0; + + position: absolute; + inset: 0; +`; + +const HiddenColorTone = styled.div` + position: relative; + height: 100vh; + + scroll-snap-align: center; + opacity: 1; + + background: transparent; +`; + +const Grid = styled(ContainerBase)` + position: absolute; +`; + +const ColorToneDisplay = styled.div` + position: relative; + top: 6.125rem; + height: 15.25rem; +`; + +const ColorToneDescription = styled.div` + width: 16.25rem; + margin-bottom: 1.5rem; + + color: ${onLightTextSecondary}; + + font-family: var(--plasma-typo-text-s-font-family); + font-size: var(--plasma-typo-text-s-font-size); + font-weight: var(--plasma-typo-text-s-font-weight); + line-height: var(--plasma-typo-text-s-line-height); +`; + +const ColorToneName = styled.div` + color: ${onLightTextPrimary}; + + font-family: var(--plasma-typo-dspl-m-font-family); + font-size: var(--plasma-typo-dspl-m-font-size); + font-weight: var(--plasma-typo-dspl-m-font-weight); + line-height: var(--plasma-typo-dspl-m-line-height); +`; + +const grayShadeIndex = Math.max(Object.entries(general).length - 2, 1); + +const toneColorsName = Object.keys(general).slice(grayShadeIndex).reverse(); + +const gradients = Object.entries(general) + .map(([_, saturation]) => ({ + 1000: saturation['1000'], + 800: saturation['800'], + 500: saturation['500'], + 250: saturation['250'], + 100: saturation['100'], + })) + .slice(grayShadeIndex) + .reverse(); + +const formatColorToneName = (value: string) => { + return upperFirstLetter(value).replace(/([a-z])([A-Z])/g, '$1 $2'); +}; + +interface GrayscaleGeneratorProps { + onNextPage?: () => void; + onPreviousPage?: () => void; +} + +export const GrayscaleGenerator = (props: GrayscaleGeneratorProps) => { + const colorRefs = useRef([]); + const hiddenContainerRef = useRef(null); + const thumbRef = useRef(null); + + const sliderMin = 0; + const sliderMax = gradients.length - 1; + + const [colorIndexValue, setColorIndexValue] = useState(0); + + useEffect(() => { + colorRefs.current?.[0]?.classList.add('transitionOpacity'); + }, []); + + useEffect(() => { + const scaledColorValue = ((colorIndexValue - sliderMin) / (sliderMax - sliderMin)).toFixed(3); + + if (thumbRef.current) { + thumbRef.current.style.transform = `translateY(calc(${scaledColorValue} * calc(var(--private-gradient-scroll-height) - 0.25rem)))`; + } + }, [colorIndexValue, sliderMax]); + + const getColorRefs = (element: HTMLDivElement, index: number) => { + if (colorRefs.current) { + colorRefs.current[index] = element; + } + }; + + const updateTransitionOpacityClass = (selectColorIndex: number) => { + if (selectColorIndex < colorIndexValue) { + for (let index = sliderMax; index > selectColorIndex; index--) { + colorRefs.current?.[index]?.classList.remove('transitionOpacity'); + } + + return; + } + + for (let index = sliderMin; index <= selectColorIndex; index++) { + colorRefs.current?.[index]?.classList.add('transitionOpacity'); + } + }; + + const updateSliderValue = (selectColorIndex: number, withAutoScroll = true) => { + updateTransitionOpacityClass(selectColorIndex); + setColorIndexValue(selectColorIndex); + + if (withAutoScroll && hiddenContainerRef.current) { + hiddenContainerRef.current.scrollTo({ + top: hiddenContainerRef.current.offsetHeight * selectColorIndex, + }); + } + }; + + const onScrollHiddenContainer = (event: UIEvent) => { + const selectColorIndex = Math.round(event.currentTarget.scrollTop / event.currentTarget.offsetHeight); + const withAutoScroll = false; + + updateSliderValue(selectColorIndex, withAutoScroll); + }; + + const handleOptionChange = (event: ChangeEvent) => { + updateSliderValue(Number(event.target.value)); + }; + + return ( + + + + {gradients.map((colors, index) => ( + getColorRefs(element, index)} colors={colors} /> + ))} + + + + + + Определяет оттенок серого для текста и фона + {formatColorToneName(toneColorsName[colorIndexValue])} + + + + + + + { + props.onNextPage?.(); + }} + /> + { + props.onNextPage?.(); + }} + /> + + + + + + + {gradients.map(() => ( + + ))} + + + + + ); +}; diff --git a/website/plasma-theme-builder/src/components/ColorGenerator/Header.tsx b/website/plasma-theme-builder/src/components/ColorGenerator/Header.tsx new file mode 100644 index 0000000000..05ad19658b --- /dev/null +++ b/website/plasma-theme-builder/src/components/ColorGenerator/Header.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import styled from 'styled-components'; + +import { textPrimary } from '@salutejs/plasma-tokens-b2c/new'; + +const HeaderArrow = styled.div` + width: 1.25rem; + height: 1.25rem; + + display: flex; + justify-content: center; + align-items: center; + + opacity: 0.56; +`; + +const HeaderWrapper = styled.div` + display: flex; + + top: 1.875rem; + left: 1rem; + gap: 1.125rem; + + position: fixed; + align-items: center; + text-decoration: none; + + cursor: pointer; + z-index: 999; + + color: ${textPrimary}; + + &:hover ${HeaderArrow} { + opacity: 1; + } +`; + +const HeaderTextBold = styled.div` + font-size: var(--plasma-typo-body-m-font-size); + line-height: var(--plasma-typo-body-m-line-height); + font-weight: var(--plasma-typo-body-m-bold-font-weight); +`; + +interface HeaderProps { + text?: string; + onClick?: () => void; +} + +export const Header = ({ text, onClick, ...rest }: HeaderProps) => { + return ( + + + + + + + {text} + + ); +}; diff --git a/website/plasma-theme-builder/src/components/ColorGenerator/RadioboxButton.tsx b/website/plasma-theme-builder/src/components/ColorGenerator/RadioboxButton.tsx new file mode 100644 index 0000000000..e8600ccfac --- /dev/null +++ b/website/plasma-theme-builder/src/components/ColorGenerator/RadioboxButton.tsx @@ -0,0 +1,78 @@ +import React, { ChangeEvent } from 'react'; +import styled from 'styled-components'; + +import { IconArrowRight } from '@salutejs/plasma-icons'; +import { IconButton } from '@salutejs/plasma-b2c'; + +const Root = styled.div` + display: flex; + align-items: center; + gap: 0.25rem; + + height: 3.5rem; +`; + +const Label = styled.label<{ view: 'black' | 'white' }>` + cursor: pointer; + display: block; + flex: 1; + padding: 1rem 0; + + color: ${({ view }) => (view === 'white' ? '#FFFFFF' : '#080808')}; + + font-family: var(--plasma-typo-text-l-font-family); + font-size: 16px; + font-style: normal; + font-weight: bold; + line-height: 20px; +`; + +const Input = styled.input<{ view: 'black' | 'white' }>` + display: none; + + &:checked + ${Label} { + flex: unset; + + border-radius: 2.5rem; + padding: 1rem 2.375rem; + border: 0.125rem solid ${({ view }) => (view === 'white' ? '#FFFFFF' : '#000000')}; + } +`; + +const ContinueButton = styled(IconButton)` + position: relative; +`; + +interface RadioboxButtonProps { + label: string; + id: string; + value?: number; + checked?: boolean; + view: 'black' | 'white'; + onChange?: (event: ChangeEvent) => void; + onClick?: (event: React.MouseEvent) => void; +} + +export const RadioboxButton = ({ value, checked, label, view, id, onChange, onClick }: RadioboxButtonProps) => { + return ( + + + + {label} + + {checked && ( + + + + )} + + ); +}; diff --git a/website/plasma-theme-builder/src/components/ColorGenerator/ToneGenerator.tsx b/website/plasma-theme-builder/src/components/ColorGenerator/ToneGenerator.tsx new file mode 100644 index 0000000000..3ae80a6360 --- /dev/null +++ b/website/plasma-theme-builder/src/components/ColorGenerator/ToneGenerator.tsx @@ -0,0 +1,329 @@ +import React, { ChangeEvent, UIEvent, useEffect, useRef, useState } from 'react'; +import styled, { createGlobalStyle } from 'styled-components'; + +import { textPrimary, onLightTextPrimary, onLightTextSecondary } from '@salutejs/plasma-tokens-b2c/new'; +import { Container as ContainerBase, Col, Row, Button } from '@salutejs/plasma-b2c'; +import { general } from '@salutejs/plasma-colors'; +import { IconArrowRight } from '@salutejs/plasma-icons'; +import { getHSLARawColor, upperFirstLetter } from '@salutejs/plasma-tokens-utils'; + +import { GradientVerticalSlider } from './GradientVerticalSlider'; +import { Header } from './Header'; +import { BackgroundGradientSaturation } from './types'; + +const NoScroll = createGlobalStyle` + html, body { + overscroll-behavior: none; + } +`; + +const Container = styled.div` + position: relative; + + width: 100%; + height: 100vh; + box-sizing: border-box; + background-color: #000; +`; + +const Wrapper = styled.div` + position: absolute; + inset: 0.125rem; + border-radius: 0.5rem; + + overflow: scroll; + + ::-webkit-scrollbar { + display: none; + } + scrollbar-width: none; + + .transitionOpacity { + opacity: 1; + } +`; + +const ColorTone = styled.div<{ colors: BackgroundGradientSaturation }>` + position: absolute; + inset: 0; + + opacity: 0; + transition: opacity 0.3s ease-in-out; + + /* stylelint-disable */ + background: linear-gradient( + 90deg, + ${({ colors }) => colors[1000]} 5%, + ${({ colors }) => colors[800]} 30%, + ${({ colors }) => colors[500]} 50%, + ${({ colors }) => colors[250]} 70%, + ${({ colors }) => colors[100]} 95% + ); + + padding: 2rem; + color: #ffffff17; + font-size: 4rem; +`; + +const ScrollableHiddenContainer = styled.div` + overflow-y: scroll; + scroll-snap-type: y mandatory; + scroll-bar-width: none; + opacity: 0; + + position: absolute; + inset: 0; +`; + +const HiddenColorTone = styled.div` + position: relative; + height: 100vh; + + scroll-snap-align: center; + opacity: 1; + + background: transparent; +`; + +const ColorBox = styled.div` + position: absolute; + z-index: 999; + + left: 3.375rem; + bottom: 3.875rem; + + width: 11rem; + + color: ${textPrimary}; +`; + +const InputLabel = styled.div` + margin-bottom: 1rem; + + font-family: var(--plasma-typo-body-s-font-family); + font-size: var(--plasma-typo-body-s-font-size); + font-weight: var(--plasma-typo-body-s-font-weight); + line-height: var(--plasma-typo-body-s-line-height); +`; + +const InputHex = styled.input` + width: inherit; + outline: none; + border: none; + background: transparent; + color: inherit; + + caret-color: ${textPrimary}; + + ::placeholder { + opacity: 0.28; + } + + font-family: var(--plasma-typo-h1-font-family); + font-size: var(--plasma-typo-h1-font-size); + font-weight: var(--plasma-typo-h1-font-weight); + line-height: var(--plasma-typo-h1-line-height); +`; + +const Grid = styled(ContainerBase)` + position: absolute; +`; + +const ColorToneDisplay = styled.div` + position: relative; + top: 6.125rem; + height: 15.25rem; +`; + +const ColorToneDescription = styled.div` + width: 16.25rem; + margin-bottom: 1.5rem; + + color: ${onLightTextSecondary}; + + font-family: var(--plasma-typo-text-s-font-family); + font-size: var(--plasma-typo-text-s-font-size); + font-weight: var(--plasma-typo-text-s-font-weight); + line-height: var(--plasma-typo-text-s-line-height); +`; + +const ColorToneName = styled.div` + color: ${onLightTextPrimary}; + + font-family: var(--plasma-typo-dspl-m-font-family); + font-size: var(--plasma-typo-dspl-m-font-size); + font-weight: var(--plasma-typo-dspl-m-font-weight); + line-height: var(--plasma-typo-dspl-m-line-height); +`; + +const ContinueButton = styled(Button)` + position: relative; + z-index: 999; + top: 13rem; +`; + +const toneColorsName = Object.keys(general).slice(2, -2); + +const gradients = Object.entries(general) + .map(([_, saturation]) => ({ + 1000: saturation['1000'], + 800: saturation['800'], + 500: saturation['500'], + 250: saturation['250'], + 100: saturation['100'], + })) + .slice(2, -2); + +const findNearestIndex = (inputNumber: number, numbers: number[]) => { + return numbers.reduce((closestIndex, curr, index, arr) => { + return Math.abs(curr - inputNumber) < Math.abs(arr[closestIndex] - inputNumber) ? index : closestIndex; + }, 0); +}; + +const formatColorToneName = (value: string) => { + return upperFirstLetter(value).replace(/([a-z])([A-Z])/g, '$1 $2'); +}; + +interface ToneGeneratorProps { + onNextPage?: () => void; + onPreviousPage?: () => void; +} + +export const ToneGenerator = (props: ToneGeneratorProps) => { + const colorRefs = useRef([]); + const hiddenContainerRef = useRef(null); + const thumbRef = useRef(null); + + const sliderMin = 0; + const sliderMax = gradients.length - 1; + + const [colorHex, setColorHex] = useState(''); + const [colorIndexValue, setColorIndexValue] = useState(0); + + useEffect(() => { + colorRefs.current?.[0]?.classList.add('transitionOpacity'); + }, []); + + useEffect(() => { + const scaledColorValue = ((colorIndexValue - sliderMin) / (sliderMax - sliderMin)).toFixed(3); + + if (thumbRef.current) { + thumbRef.current.style.transform = `translateY(calc(${scaledColorValue} * calc(var(--private-gradient-scroll-height) - 0.25rem)))`; + } + }, [colorIndexValue, sliderMax]); + + const getColorRefs = (element: HTMLDivElement, index: number) => { + if (colorRefs.current) { + colorRefs.current[index] = element; + } + }; + + const updateTransitionOpacityClass = (selectColorIndex: number) => { + if (selectColorIndex < colorIndexValue) { + for (let index = sliderMax; index > selectColorIndex; index--) { + colorRefs.current?.[index]?.classList.remove('transitionOpacity'); + } + + return; + } + + for (let index = sliderMin; index <= selectColorIndex; index++) { + colorRefs.current?.[index]?.classList.add('transitionOpacity'); + } + }; + + const updateSliderValue = (selectColorIndex: number, withAutoScroll = true) => { + updateTransitionOpacityClass(selectColorIndex); + setColorIndexValue(selectColorIndex); + + if (withAutoScroll && hiddenContainerRef.current) { + hiddenContainerRef.current.scrollTo({ + top: hiddenContainerRef.current.offsetHeight * selectColorIndex, + }); + } + }; + + const onChangeSliderValue = (event: ChangeEvent) => { + const selectColorIndex = Number(event.target.value); + + updateSliderValue(selectColorIndex); + }; + + const onScrollHiddenContainer = (event: UIEvent) => { + const selectColorIndex = Math.round(event.currentTarget.scrollTop / event.currentTarget.offsetHeight); + const withAutoScroll = false; + + updateSliderValue(selectColorIndex, withAutoScroll); + }; + + const onChangeColorHex = (event: ChangeEvent) => { + const value = event.target.value; + const hue = getHSLARawColor(value).color[0]; + const huesArray = gradients.map((item) => getHSLARawColor(item['500']).color[0]); + const nearestIndex = findNearestIndex(hue, huesArray); + + updateSliderValue(nearestIndex); + setColorHex(value); + }; + + return ( + + + + {gradients.map((colors, index) => ( + getColorRefs(element, index)} colors={colors} /> + ))} + + + Подобрать ближайший по hex-коду + e.preventDefault()} + value={colorHex} + onChange={onChangeColorHex} + placeholder="#HEX" + /> + + + + + + + Влияет на оттенок акцентного цвета темы + {formatColorToneName(toneColorsName[colorIndexValue])} + + + + + + { + props.onNextPage?.(); + }} + contentRight={} + size="l" + stretching="filled" + text="Продолжить" + pin="circle-circle" + view="black" + /> + + + + + + {gradients.map(() => ( + + ))} + + + + + ); +}; diff --git a/website/plasma-theme-builder/src/components/ColorGenerator/types.ts b/website/plasma-theme-builder/src/components/ColorGenerator/types.ts new file mode 100644 index 0000000000..f5e4236024 --- /dev/null +++ b/website/plasma-theme-builder/src/components/ColorGenerator/types.ts @@ -0,0 +1,7 @@ +export interface BackgroundGradientSaturation { + 1000: string; + 800: string; + 500: string; + 250: string; + 100: string; +} diff --git a/website/plasma-theme-builder/src/components/_new/TokensEditor.tsx b/website/plasma-theme-builder/src/components/_new/TokensEditor.tsx new file mode 100644 index 0000000000..24f5e1b669 --- /dev/null +++ b/website/plasma-theme-builder/src/components/_new/TokensEditor.tsx @@ -0,0 +1,350 @@ +import React, { useMemo, useState } from 'react'; +import styled, { createGlobalStyle } from 'styled-components'; +import { useNavigate } from 'react-router-dom'; + +import { TextField, TabItem, Tabs, Switch, Button } from '@salutejs/plasma-b2c'; + +import { getHEXAColor, getRestoredColorFromPalette, Grayscale, ThemeConfig } from '@salutejs/plasma-tokens-utils'; +import { buildDefaultTheme, ColorToken } from '../../_new'; +import { getRGBAColor } from '../../utils'; +import { Header } from '../ColorGenerator/Header'; + +const NoScroll = createGlobalStyle` + html, body { + overscroll-behavior: none; + } +`; + +const Container = styled.div` + position: relative; + + width: 100%; + height: 100vh; + box-sizing: border-box; + background-color: #000; +`; + +const Wrapper = styled.div` + position: relative; + top: 6rem; + left: 3rem; + bottom: 3rem; + right: 3rem; + border-radius: 0.5rem; + height: calc(100vh - 9rem); + width: calc(100% - 6rem); + + overflow: hidden; + + display: flex; + flex-direction: column; + + ::-webkit-scrollbar { + display: none; + } + scrollbar-width: none; +`; + +interface FontFamilyGeneratorProps { + onNextPage?: () => void; + onPreviousPage?: () => void; +} + +const tokensTypes = [ + { + label: 'Цвета', + value: 'color', + inner: [ + { + label: 'Темная', + value: 'dark', + }, + { + label: 'Светлая', + value: 'light', + }, + ], + }, + { + label: 'Градиенты', + value: 'gradient', + inner: [ + { + label: 'Темная', + value: 'dark', + }, + { + label: 'Светлая', + value: 'light', + }, + ], + }, + { + label: 'Форма', + value: 'shape', + inner: [ + { + label: 'Скругление', + value: 'round', + }, + ], + }, + { + label: 'Тени', + value: 'Shadow', + inner: [ + { + label: 'Вверх', + value: 'up', + }, + { + label: 'Вниз', + value: 'down', + }, + ], + }, + { + label: 'Типографика', + value: 'typography', + disabled: true, + }, + { + label: 'Семейство шрифтов', + value: 'fontFamily', + disabled: true, + }, +]; + +const platformTypes = [ + { + label: 'Web', + value: 'web', + }, + { + label: 'Android', + value: 'android', + disabled: true, + }, + { + label: 'iOS', + value: 'ios', + disabled: true, + }, + { + label: 'React-Native', + value: 'reactNative', + disabled: true, + }, +]; + +export const buildDefaultThemeWithUserConfig = () => { + const userConfig: ThemeConfig = { + name: 'default', + accentColor: { + dark: '[general.green.500]', + light: '[general.green.600]', + }, + grayscale: { + dark: Grayscale.gray, + light: Grayscale.gray, + }, + }; + + return buildDefaultTheme(userConfig); +}; + +const ColorTokenPreview = ({ + token, + onClick, +}: { + token: ColorToken; + onClick: (event?: React.MouseEvent) => void; +}) => { + const value = token.getValue('web'); + const newValue = getRestoredColorFromPalette(value); + + return ( + + + {token.getDisplayName()} + + + + {getRGBAColor(newValue)} + + {getHEXAColor(newValue)} + + ); +}; + +const TokenEditor = ({ token }: { token: ColorToken }) => { + const tokenName = token.getDisplayName(); + const tokenDescription = token.getDescription(); + const tokenEnabled = token.getEnabled(); + const tokenValue = token.getValue('web'); + + return ( + + + + + + {platformTypes.map(({ label, disabled, value }, i) => ( + + {label} + + ))} + + + + + { + const hackButton = event.currentTarget.parentNode?.parentNode?.parentNode?.parentNode + ?.parentNode?.children[0] as HTMLButtonElement; + + hackButton?.click(); + }} + /> + + + ); +}; + +export const TokensEditor = (props: FontFamilyGeneratorProps) => { + const [theme, setTheme] = useState(buildDefaultThemeWithUserConfig); + const [index, setIndex] = useState(0); + const [tokenEditorIndex, setTokenEditorIndex] = useState(undefined); + + const [colorMode, setColorMode] = useState('dark'); + + const navigate = useNavigate(); + + const colorTokens = useMemo(() => theme.getTokens('color'), [theme]); + + const groupedColorTokens = useMemo( + () => [ + { + data: colorTokens.filter((item) => item.getTags()[0] === colorMode && item.getTags()[2] === 'default'), + group: 'default', + }, + { + data: colorTokens.filter( + (item) => + item.getTags()[0] === colorMode && + (item.getTags()[2] === 'dark' || item.getTags()[2] === 'on-dark'), + ), + group: 'onDark', + }, + { + data: colorTokens.filter( + (item) => + item.getTags()[0] === colorMode && + (item.getTags()[2] === 'light' || item.getTags()[2] === 'on-light'), + ), + group: 'onLight', + }, + { + data: colorTokens.filter((item) => item.getTags()[0] === colorMode && item.getTags()[2] === 'inverse'), + group: 'inverse', + }, + ], + [colorMode, colorTokens], + ); + + const onClickColorTokenPreview = (index: string) => { + setTokenEditorIndex((prevIndex) => (prevIndex === index ? undefined : index)); + }; + + return ( + + + + + + {tokensTypes.map(({ label, value, disabled }, i) => ( + setIndex(i)} + > + {label} + + ))} + + + + {tokensTypes[index].inner?.map(({ label, value }, i) => ( + setColorMode(value)} + > + {label} + + ))} + + + + {groupedColorTokens.map((item, groupIndex) => ( + + {item.group} + {item.data.map((token, index) => ( + + onClickColorTokenPreview(`${groupIndex}_${index}`)} + /> + {tokenEditorIndex === `${groupIndex}_${index}` && } + + ))} + + ))} + + + + + ); +}; diff --git a/website/plasma-theme-builder/src/components/mixins.ts b/website/plasma-theme-builder/src/components/mixins.ts index 438def56c9..3acc6d64e9 100644 --- a/website/plasma-theme-builder/src/components/mixins.ts +++ b/website/plasma-theme-builder/src/components/mixins.ts @@ -1,8 +1,9 @@ import { FlattenSimpleInterpolation, css, keyframes } from 'styled-components'; + import { Breakpoint, mediaQuery } from '@salutejs/plasma-b2c'; export const SBSansTextMono = css` - font-family: 'SB Sans Text Mono', Helvetica, Arial, sans-serif; + font-family: var(--plasma-typo-text-l-font-family), Helvetica, Arial, sans-serif; `; export const iconButtonFade = css` diff --git a/website/plasma-theme-builder/src/index.tsx b/website/plasma-theme-builder/src/index.tsx index 729b515bfc..981d94b5b0 100644 --- a/website/plasma-theme-builder/src/index.tsx +++ b/website/plasma-theme-builder/src/index.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { createRoot } from 'react-dom/client'; +import { BrowserRouter } from 'react-router-dom'; import App from './components/App'; import { GlobalStyle } from './GlobalStyle'; @@ -13,8 +14,10 @@ if (!rootElement) { const root = createRoot(rootElement); root.render( - <> - - - >, + + + + + {/* */} + , );