From b81776dfaa6dffa1ea332692796fe75cd6030507 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Sun, 12 Jan 2025 15:06:54 +0000 Subject: [PATCH 1/3] First pass at custom theme Signed-off-by: Charles de Dreuille --- canon-docs/package.json | 2 + canon-docs/src/app/globals.css | 34 +++ canon-docs/src/app/layout.tsx | 3 +- .../components/CustomTheme/customTheme.tsx | 94 ++++++ .../src/components/CustomTheme/index.ts | 1 + .../components/CustomTheme/styles.module.css | 41 +++ canon-docs/src/components/Sidebar/docs.tsx | 3 +- .../src/components/Toolbar/theme-name.tsx | 5 +- canon-docs/src/mdx-components.tsx | 1 - canon-docs/src/utils/playground-context.tsx | 23 +- canon-docs/yarn.lock | 271 ++++++++++++++++++ 11 files changed, 463 insertions(+), 15 deletions(-) create mode 100644 canon-docs/src/components/CustomTheme/customTheme.tsx create mode 100644 canon-docs/src/components/CustomTheme/index.ts create mode 100644 canon-docs/src/components/CustomTheme/styles.module.css diff --git a/canon-docs/package.json b/canon-docs/package.json index 824a645f99..af440bf9d5 100644 --- a/canon-docs/package.json +++ b/canon-docs/package.json @@ -9,11 +9,13 @@ "watch-css": "node scripts/watch-css.js" }, "dependencies": { + "@codemirror/lang-sass": "^6.0.2", "@mdx-js/loader": "^3.1.0", "@mdx-js/react": "^3.1.0", "@next/mdx": "^15.1.4", "@storybook/react": "^8.4.7", "@types/mdx": "^2.0.13", + "@uiw/react-codemirror": "^4.23.7", "next": "14.2.23", "react": "^18", "react-dom": "^18", diff --git a/canon-docs/src/app/globals.css b/canon-docs/src/app/globals.css index 4b39735a22..e9710c6576 100644 --- a/canon-docs/src/app/globals.css +++ b/canon-docs/src/app/globals.css @@ -35,3 +35,37 @@ iframe { font-weight: var(--shiki-dark-font-weight) !important; text-decoration: var(--shiki-dark-text-decoration) !important; } + +.ͼ2 .cm-gutters { + background-color: var(--canon-surface-2); + border-right: 1px solid var(--canon-border-base); + transition: background-color 0.2s ease-in-out, border-color 0.2s ease-in-out; +} + +.ͼ1 .cm-lineNumbers .cm-gutterElement { + padding-right: 8px; + padding-left: 8px; + font-size: 0.875rem; + display: flex; + align-items: center; + justify-content: flex-end; +} + +.cm-line { + font-size: 0.875rem; +} + +.ͼ1 .cm-line { + padding-left: 12px; + padding-top: 1px; + padding-bottom: 1px; +} + +.cm-focused { + outline: none !important; +} + +.cm-editor { + background-color: var(--canon-surface-1); + transition: background-color 0.2s ease-in-out; +} diff --git a/canon-docs/src/app/layout.tsx b/canon-docs/src/app/layout.tsx index 6eb1856a1d..90cd6a9d0d 100644 --- a/canon-docs/src/app/layout.tsx +++ b/canon-docs/src/app/layout.tsx @@ -2,7 +2,7 @@ import type { Metadata } from 'next'; import { Sidebar } from '../components/Sidebar'; import { Toolbar } from '@/components/Toolbar'; import { Providers } from './providers'; - +import { CustomTheme } from '@/components/CustomTheme'; import styles from './page.module.css'; import './globals.css'; @@ -35,6 +35,7 @@ export default function RootLayout({ {children} + diff --git a/canon-docs/src/components/CustomTheme/customTheme.tsx b/canon-docs/src/components/CustomTheme/customTheme.tsx new file mode 100644 index 0000000000..d02f96131b --- /dev/null +++ b/canon-docs/src/components/CustomTheme/customTheme.tsx @@ -0,0 +1,94 @@ +'use client'; + +import { useEffect, useState, useCallback } from 'react'; +import CodeMirror from '@uiw/react-codemirror'; +import { sass } from '@codemirror/lang-sass'; +import styles from './styles.module.css'; +import { usePlayground } from '@/utils/playground-context'; +import { AnimatePresence, motion } from 'framer-motion'; + +export const CustomTheme = () => { + const [customTheme, setCustomTheme] = useState(undefined); + const { selectedThemeName } = usePlayground(); + const [isClient, setIsClient] = useState(false); + + useEffect(() => { + if (selectedThemeName === 'custom') { + const storedTheme = localStorage.getItem('customThemeCss') || ''; + setCustomTheme(storedTheme); + + let styleElement = document.getElementById( + 'custom-theme-style', + ) as HTMLStyleElement; + + if (!styleElement) { + styleElement = document.createElement('style'); + styleElement.id = 'custom-theme-style'; + document.head.appendChild(styleElement); + } + + styleElement.textContent = storedTheme; + } else { + const styleElement = document.getElementById( + 'custom-theme-style', + ) as HTMLStyleElement; + if (styleElement) { + styleElement.remove(); + } + } + }, [selectedThemeName]); + + useEffect(() => { + setIsClient(true); + }, []); + + const handleSave = () => { + if (customTheme) { + localStorage.setItem('customThemeCss', customTheme); + + let styleElement = document.getElementById( + 'custom-theme-style', + ) as HTMLStyleElement; + + if (!styleElement) { + styleElement = document.createElement('style'); + styleElement.id = 'custom-theme-style'; + document.head.appendChild(styleElement); + } + + styleElement.textContent = customTheme; + } + }; + + const onChange = useCallback((val: string) => { + setCustomTheme(val); + }, []); + + return ( + + {isClient && selectedThemeName === 'custom' && ( + +
+
Custom Theme
+ +
+ +
+ )} +
+ ); +}; diff --git a/canon-docs/src/components/CustomTheme/index.ts b/canon-docs/src/components/CustomTheme/index.ts new file mode 100644 index 0000000000..6805b2c087 --- /dev/null +++ b/canon-docs/src/components/CustomTheme/index.ts @@ -0,0 +1 @@ +export { CustomTheme } from './customTheme'; diff --git a/canon-docs/src/components/CustomTheme/styles.module.css b/canon-docs/src/components/CustomTheme/styles.module.css new file mode 100644 index 0000000000..17e6ef18e4 --- /dev/null +++ b/canon-docs/src/components/CustomTheme/styles.module.css @@ -0,0 +1,41 @@ +.container { + position: fixed; + bottom: 16px; + right: 16px; + width: 36%; + height: 348px; + background-color: var(--canon-surface-1); + border-radius: 0.375rem; + border: 1px solid var(--canon-border-base); + display: flex; + flex-direction: column; + overflow: hidden; + transition: background-color 0.2s ease-in-out, border-color 0.2s ease-in-out; +} + +.editor { + flex: 1; +} + +.header { + height: 46px; + flex-shrink: 0; + border-bottom: 1px solid var(--canon-border-base); + background-color: var(--canon-surface-1); + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 12px 0 16px; + transition: background-color 0.2s ease-in-out, border-color 0.2s ease-in-out; +} + +.button { + all: unset; + height: 28px; + padding: 0 8px; + color: #fff; + background-color: #000; + border-radius: 0.25rem; + cursor: pointer; + font-size: 0.875rem; +} diff --git a/canon-docs/src/components/Sidebar/docs.tsx b/canon-docs/src/components/Sidebar/docs.tsx index 13c1675c20..f207036cfa 100644 --- a/canon-docs/src/components/Sidebar/docs.tsx +++ b/canon-docs/src/components/Sidebar/docs.tsx @@ -2,8 +2,7 @@ import Link from 'next/link'; import { components, overview, layoutComponents, theme } from '@/utils/data'; -import { Box } from '../../../../packages/canon/src/components/Box'; -import { Text } from '../../../../packages/canon/src/components/Text'; +import { Box } from '../../../../packages/canon'; import { motion } from 'framer-motion'; import styles from './Sidebar.module.css'; import { usePathname } from 'next/navigation'; diff --git a/canon-docs/src/components/Toolbar/theme-name.tsx b/canon-docs/src/components/Toolbar/theme-name.tsx index 57c858dd44..07b734de34 100644 --- a/canon-docs/src/components/Toolbar/theme-name.tsx +++ b/canon-docs/src/components/Toolbar/theme-name.tsx @@ -6,8 +6,9 @@ import { Icon } from '@backstage/canon'; import { usePlayground } from '@/utils/playground-context'; const themes = [ - { name: 'Backstage Legacy', value: 'legacy' }, { name: 'Backstage Default', value: 'default' }, + { name: 'Backstage Legacy', value: 'legacy' }, + { name: 'Custom theme', value: 'custom' }, ]; export const ThemeNameSelector = () => { @@ -31,7 +32,7 @@ export const ThemeNameSelector = () => { {themes.map(({ name, value }) => ( - + diff --git a/canon-docs/src/mdx-components.tsx b/canon-docs/src/mdx-components.tsx index 308356bc67..071fa7580c 100644 --- a/canon-docs/src/mdx-components.tsx +++ b/canon-docs/src/mdx-components.tsx @@ -2,7 +2,6 @@ import React, { ReactNode } from 'react'; import type { MDXComponents } from 'mdx/types'; import Image, { ImageProps } from 'next/image'; import { CodeBlock } from '@/components/CodeBlock'; -import { Heading } from '../../packages/canon/src/components/Heading'; import { Box } from '../../packages/canon/src/components/Box'; export function useMDXComponents(components: MDXComponents): MDXComponents { diff --git a/canon-docs/src/utils/playground-context.tsx b/canon-docs/src/utils/playground-context.tsx index 198c5a9aab..721851c3e4 100644 --- a/canon-docs/src/utils/playground-context.tsx +++ b/canon-docs/src/utils/playground-context.tsx @@ -7,16 +7,19 @@ import React, { } from 'react'; import { components } from './data'; +type Theme = 'light' | 'dark'; +type ThemeName = 'legacy' | 'default' | 'custom'; + // Create a context with an empty array as the default value const PlaygroundContext = createContext<{ selectedScreenSizes: string[]; setSelectedScreenSizes: (screenSizes: string[]) => void; selectedComponents: string[]; setSelectedComponents: (components: string[]) => void; - selectedTheme: string | null; - setSelectedTheme: (theme: string) => void; - selectedThemeName: string; - setSelectedThemeName: (themeName: string) => void; + selectedTheme: Theme; + setSelectedTheme: (theme: Theme) => void; + selectedThemeName: ThemeName; + setSelectedThemeName: (themeName: ThemeName) => void; }>({ selectedScreenSizes: [], setSelectedScreenSizes: () => {}, @@ -37,12 +40,14 @@ export const PlaygroundProvider = ({ children }: { children: ReactNode }) => { const [selectedComponents, setSelectedComponents] = useState( components.map(component => component.slug), ); - const [selectedTheme, setSelectedTheme] = useState(() => { - return isBrowser ? localStorage.getItem('theme') : 'light'; - }); - const [selectedThemeName, setSelectedThemeName] = useState(() => { + const [selectedTheme, setSelectedTheme] = useState(() => { return isBrowser - ? localStorage.getItem('theme-name') || 'default' + ? (localStorage.getItem('theme') as Theme) || 'light' + : 'light'; + }); + const [selectedThemeName, setSelectedThemeName] = useState(() => { + return isBrowser + ? (localStorage.getItem('theme-name') as ThemeName) || 'default' : 'default'; }); diff --git a/canon-docs/yarn.lock b/canon-docs/yarn.lock index 9eb2b969f2..f7a3d103a4 100644 --- a/canon-docs/yarn.lock +++ b/canon-docs/yarn.lock @@ -5,6 +5,133 @@ __metadata: version: 6 cacheKey: 8 +"@babel/runtime@npm:^7.18.6": + version: 7.26.0 + resolution: "@babel/runtime@npm:7.26.0" + dependencies: + regenerator-runtime: ^0.14.0 + checksum: c8e2c0504ab271b3467a261a8f119bf2603eb857a0d71e37791f4e3fae00f681365073cc79f141ddaa90c6077c60ba56448004ad5429d07ac73532be9f7cf28a + languageName: node + linkType: hard + +"@codemirror/autocomplete@npm:^6.0.0": + version: 6.18.4 + resolution: "@codemirror/autocomplete@npm:6.18.4" + dependencies: + "@codemirror/language": ^6.0.0 + "@codemirror/state": ^6.0.0 + "@codemirror/view": ^6.17.0 + "@lezer/common": ^1.0.0 + checksum: 4216f45a17f6cfd8d33df53f940396f7d3707662570bf3a79d8d333f926e273a265fac13c362e29e3fa57ccdf444f1a047862f5f56c672cfc669c87ee975858f + languageName: node + linkType: hard + +"@codemirror/commands@npm:^6.0.0, @codemirror/commands@npm:^6.1.0": + version: 6.8.0 + resolution: "@codemirror/commands@npm:6.8.0" + dependencies: + "@codemirror/language": ^6.0.0 + "@codemirror/state": ^6.4.0 + "@codemirror/view": ^6.27.0 + "@lezer/common": ^1.1.0 + checksum: 7d819bab4830ec7b8c5dffdec4b035dfa664bfd1d2675e639e08a459df65f45be111e1b8b569b1a8a3253d5980cf2ecf4394d8a13509996cca1b65cc16d47a4e + languageName: node + linkType: hard + +"@codemirror/lang-css@npm:^6.2.0": + version: 6.3.1 + resolution: "@codemirror/lang-css@npm:6.3.1" + dependencies: + "@codemirror/autocomplete": ^6.0.0 + "@codemirror/language": ^6.0.0 + "@codemirror/state": ^6.0.0 + "@lezer/common": ^1.0.2 + "@lezer/css": ^1.1.7 + checksum: ed175d75d75bc0a059d1e60b3dcd8464d570da14fc97388439943c9c43e1e9146e37b83fe2ccaad9cd387420b7b411ea1d24ede78ecd1f2045a38acbb4dd36bc + languageName: node + linkType: hard + +"@codemirror/lang-sass@npm:^6.0.2": + version: 6.0.2 + resolution: "@codemirror/lang-sass@npm:6.0.2" + dependencies: + "@codemirror/lang-css": ^6.2.0 + "@codemirror/language": ^6.0.0 + "@codemirror/state": ^6.0.0 + "@lezer/common": ^1.0.2 + "@lezer/sass": ^1.0.0 + checksum: e7665aaab70476a952522b143fd7bd59f6c025746cbf7b542f6965f94eecac483b4afd03f6da98aaa1572e379194309b241c5264eff05c681c637aa26651b9ab + languageName: node + linkType: hard + +"@codemirror/language@npm:^6.0.0": + version: 6.10.8 + resolution: "@codemirror/language@npm:6.10.8" + dependencies: + "@codemirror/state": ^6.0.0 + "@codemirror/view": ^6.23.0 + "@lezer/common": ^1.1.0 + "@lezer/highlight": ^1.0.0 + "@lezer/lr": ^1.0.0 + style-mod: ^4.0.0 + checksum: 679b69d69faa94f028f996a7005d0c6c2a2e4cd7a7a2614f615c23d7b642c31fc1837915248e864cb1ad59a2f032d1a7a8ef486b5f9904e5f6fbe6f7d2882c38 + languageName: node + linkType: hard + +"@codemirror/lint@npm:^6.0.0": + version: 6.8.4 + resolution: "@codemirror/lint@npm:6.8.4" + dependencies: + "@codemirror/state": ^6.0.0 + "@codemirror/view": ^6.35.0 + crelt: ^1.0.5 + checksum: 640e3dd44eb167d952eb5c5b8518919ba46e164aa3471776342f7f9361e676b4627a76a9f01d51b22127b97413f2bc9b8c60299d8dfdd5fc8ad0225d42de7669 + languageName: node + linkType: hard + +"@codemirror/search@npm:^6.0.0": + version: 6.5.8 + resolution: "@codemirror/search@npm:6.5.8" + dependencies: + "@codemirror/state": ^6.0.0 + "@codemirror/view": ^6.0.0 + crelt: ^1.0.5 + checksum: 0f9633037492a7b647b606c30255ea42c4327319e643be7ea3aa2913ed8e4aa662589f457e376636521c7d4d1215fae0e8939f127db9c0790b19ae3b654c3bc4 + languageName: node + linkType: hard + +"@codemirror/state@npm:^6.0.0, @codemirror/state@npm:^6.1.1, @codemirror/state@npm:^6.4.0, @codemirror/state@npm:^6.5.0": + version: 6.5.1 + resolution: "@codemirror/state@npm:6.5.1" + dependencies: + "@marijn/find-cluster-break": ^1.0.0 + checksum: b7d6de9a87d5b55dadfadaeb6e1c991e0a91845d3cdd0bd953391f05363fcbaf21de79a4eec2816ab8c3e31293faeca82cc2bb729d080779df94b14e28ae0d8a + languageName: node + linkType: hard + +"@codemirror/theme-one-dark@npm:^6.0.0": + version: 6.1.2 + resolution: "@codemirror/theme-one-dark@npm:6.1.2" + dependencies: + "@codemirror/language": ^6.0.0 + "@codemirror/state": ^6.0.0 + "@codemirror/view": ^6.0.0 + "@lezer/highlight": ^1.0.0 + checksum: 29bc09f79534115f62658caf3d0db527fe347d058b69a8c7f580ae636827377aadd0606fd0d83dbab8d6f3b0a5df53d3253c619341b5fb93d2c8291a8efb9556 + languageName: node + linkType: hard + +"@codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.17.0, @codemirror/view@npm:^6.23.0, @codemirror/view@npm:^6.27.0, @codemirror/view@npm:^6.35.0": + version: 6.36.2 + resolution: "@codemirror/view@npm:6.36.2" + dependencies: + "@codemirror/state": ^6.5.0 + style-mod: ^4.1.0 + w3c-keyname: ^2.2.4 + checksum: a58c64b623ddc65bb864917297f3b37f8e95280deec442024c43a9513b26352c829665c5d98e4dfcae104e8ecdfdb774d94a395a29da98a919c83482d2c14152 + languageName: node + linkType: hard + "@esbuild/aix-ppc64@npm:0.24.2": version: 0.24.2 resolution: "@esbuild/aix-ppc64@npm:0.24.2" @@ -261,6 +388,60 @@ __metadata: languageName: node linkType: hard +"@lezer/common@npm:^1.0.0, @lezer/common@npm:^1.0.2, @lezer/common@npm:^1.1.0, @lezer/common@npm:^1.2.0": + version: 1.2.3 + resolution: "@lezer/common@npm:1.2.3" + checksum: 9b5f52d949adae69d077f56c0b1c2295923108c3dfb241dd9f17654ff708f3eab81ff9fa7f0d0e4a668eabdcb9d961c73e75caca87c966ca1436e30e49130fcb + languageName: node + linkType: hard + +"@lezer/css@npm:^1.1.7": + version: 1.1.9 + resolution: "@lezer/css@npm:1.1.9" + dependencies: + "@lezer/common": ^1.2.0 + "@lezer/highlight": ^1.0.0 + "@lezer/lr": ^1.0.0 + checksum: 25c63475061a3c9f87961a7f85c5f547f14fb7e81b0864675d2206999a874a0559d676145c74c6ccde39519dbc8aa33e216265f5366d08060507b6c9e875fe0f + languageName: node + linkType: hard + +"@lezer/highlight@npm:^1.0.0": + version: 1.2.1 + resolution: "@lezer/highlight@npm:1.2.1" + dependencies: + "@lezer/common": ^1.0.0 + checksum: a8822d7e37f79ff64669eb2df4a9f9d16580e88f2b276a646092e19a9bdccac304e92510e200e35869a8b1f6c27eba5972c508d347a277e9b722d582ab7a23d5 + languageName: node + linkType: hard + +"@lezer/lr@npm:^1.0.0": + version: 1.4.2 + resolution: "@lezer/lr@npm:1.4.2" + dependencies: + "@lezer/common": ^1.0.0 + checksum: 94318ad046c7dfcc8d37e26cb85b99623c39aef60aa51ec2abb30928e7a649f38fa5520f34bd5b356f1db11b6991999589f039e87c8949b0f163be3764f029d8 + languageName: node + linkType: hard + +"@lezer/sass@npm:^1.0.0": + version: 1.0.7 + resolution: "@lezer/sass@npm:1.0.7" + dependencies: + "@lezer/common": ^1.2.0 + "@lezer/highlight": ^1.0.0 + "@lezer/lr": ^1.0.0 + checksum: 32d37aa5d3143033f0b886c33a293b1d28b7eecdf2cb979cf7b4cbba0587be227dfed246c27116060ac9b2b2c0fe4b09df4b672690dd0593e876529583e7ef2a + languageName: node + linkType: hard + +"@marijn/find-cluster-break@npm:^1.0.0": + version: 1.0.2 + resolution: "@marijn/find-cluster-break@npm:1.0.2" + checksum: 0d836de25e04d58325813401ef3c2d34caf040da985a5935fcbc9d84e7b47a21bdb15f57d70c2bf0960bd29ed3dbbb1afd00cdd0fc4fafbee7fd0ffe7d508ae1 + languageName: node + linkType: hard + "@mdx-js/loader@npm:^3.1.0": version: 3.1.0 resolution: "@mdx-js/loader@npm:3.1.0" @@ -904,6 +1085,51 @@ __metadata: languageName: node linkType: hard +"@uiw/codemirror-extensions-basic-setup@npm:4.23.7": + version: 4.23.7 + resolution: "@uiw/codemirror-extensions-basic-setup@npm:4.23.7" + dependencies: + "@codemirror/autocomplete": ^6.0.0 + "@codemirror/commands": ^6.0.0 + "@codemirror/language": ^6.0.0 + "@codemirror/lint": ^6.0.0 + "@codemirror/search": ^6.0.0 + "@codemirror/state": ^6.0.0 + "@codemirror/view": ^6.0.0 + peerDependencies: + "@codemirror/autocomplete": ">=6.0.0" + "@codemirror/commands": ">=6.0.0" + "@codemirror/language": ">=6.0.0" + "@codemirror/lint": ">=6.0.0" + "@codemirror/search": ">=6.0.0" + "@codemirror/state": ">=6.0.0" + "@codemirror/view": ">=6.0.0" + checksum: 656c5f78db4c6216a9a7eeb65455f52b002b7a5426bb1b05f11b896d5dba9887c7b9ee5aee9fe369d6c7b3c69128ee842cb5c8fe029cd07c2e48ec9358039a30 + languageName: node + linkType: hard + +"@uiw/react-codemirror@npm:^4.23.7": + version: 4.23.7 + resolution: "@uiw/react-codemirror@npm:4.23.7" + dependencies: + "@babel/runtime": ^7.18.6 + "@codemirror/commands": ^6.1.0 + "@codemirror/state": ^6.1.1 + "@codemirror/theme-one-dark": ^6.0.0 + "@uiw/codemirror-extensions-basic-setup": 4.23.7 + codemirror: ^6.0.0 + peerDependencies: + "@babel/runtime": ">=7.11.0" + "@codemirror/state": ">=6.0.0" + "@codemirror/theme-one-dark": ">=6.0.0" + "@codemirror/view": ">=6.0.0" + codemirror: ">=6.0.0" + react: ">=16.8.0" + react-dom: ">=16.8.0" + checksum: 5f6318b290f479c4718129578bdccbbdf64e3b588fdd79e3d80224779219eae7e249d40e4f88c864ed6ae40ea93036042313e983a022349563698593a2bf2e6c + languageName: node + linkType: hard + "@ungap/structured-clone@npm:^1.0.0, @ungap/structured-clone@npm:^1.2.0": version: 1.2.1 resolution: "@ungap/structured-clone@npm:1.2.1" @@ -1254,6 +1480,7 @@ __metadata: version: 0.0.0-use.local resolution: "canon-docs@workspace:." dependencies: + "@codemirror/lang-sass": ^6.0.2 "@mdx-js/loader": ^3.1.0 "@mdx-js/react": ^3.1.0 "@next/mdx": ^15.1.4 @@ -1262,6 +1489,7 @@ __metadata: "@types/node": ^20 "@types/react": ^18 "@types/react-dom": ^18 + "@uiw/react-codemirror": ^4.23.7 concurrently: ^9.1.2 eslint: ^8 eslint-config-next: 14.2.23 @@ -1339,6 +1567,21 @@ __metadata: languageName: node linkType: hard +"codemirror@npm:^6.0.0": + version: 6.0.1 + resolution: "codemirror@npm:6.0.1" + dependencies: + "@codemirror/autocomplete": ^6.0.0 + "@codemirror/commands": ^6.0.0 + "@codemirror/language": ^6.0.0 + "@codemirror/lint": ^6.0.0 + "@codemirror/search": ^6.0.0 + "@codemirror/state": ^6.0.0 + "@codemirror/view": ^6.0.0 + checksum: 1a78f7077ac5801bdbff162aa0c61bf2b974603c7e9a477198c3ce50c789af674a061d7c293c58b73807eda345c2b5228c38ad2aabb9319d552d5486f785cbef + languageName: node + linkType: hard + "collapse-white-space@npm:^2.0.0": version: 2.1.0 resolution: "collapse-white-space@npm:2.1.0" @@ -1394,6 +1637,13 @@ __metadata: languageName: node linkType: hard +"crelt@npm:^1.0.5": + version: 1.0.6 + resolution: "crelt@npm:1.0.6" + checksum: dad842093371ad702afbc0531bfca2b0a8dd920b23a42f26e66dabbed9aad9acd5b9030496359545ef3937c3aced0fd4ac39f7a2d280a23ddf9eb7fdcb94a69f + languageName: node + linkType: hard + "cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2": version: 7.0.6 resolution: "cross-spawn@npm:7.0.6" @@ -4380,6 +4630,13 @@ __metadata: languageName: node linkType: hard +"regenerator-runtime@npm:^0.14.0": + version: 0.14.1 + resolution: "regenerator-runtime@npm:0.14.1" + checksum: 9f57c93277b5585d3c83b0cf76be47b473ae8c6d9142a46ce8b0291a04bb2cf902059f0f8445dcabb3fb7378e5fe4bb4ea1e008876343d42e46d3b484534ce38 + languageName: node + linkType: hard + "regex-recursion@npm:^5.1.1": version: 5.1.1 resolution: "regex-recursion@npm:5.1.1" @@ -4971,6 +5228,13 @@ __metadata: languageName: node linkType: hard +"style-mod@npm:^4.0.0, style-mod@npm:^4.1.0": + version: 4.1.2 + resolution: "style-mod@npm:4.1.2" + checksum: 7c5c3e82747f9bcf5f288d8d07f50848e4630fe5ff7bfe4d94cc87d6b6a2588227cbf21b4c792ac6406e5852293300a75e710714479a5c59a06af677f0825ef8 + languageName: node + linkType: hard + "style-to-object@npm:^1.0.0": version: 1.0.8 resolution: "style-to-object@npm:1.0.8" @@ -5331,6 +5595,13 @@ __metadata: languageName: node linkType: hard +"w3c-keyname@npm:^2.2.4": + version: 2.2.8 + resolution: "w3c-keyname@npm:2.2.8" + checksum: 95bafa4c04fa2f685a86ca1000069c1ec43ace1f8776c10f226a73296caeddd83f893db885c2c220ebeb6c52d424e3b54d7c0c1e963bbf204038ff1a944fbb07 + languageName: node + linkType: hard + "which-boxed-primitive@npm:^1.1.0, which-boxed-primitive@npm:^1.1.1": version: 1.1.1 resolution: "which-boxed-primitive@npm:1.1.1" From 7bc5aaab834c87f8a25a88446c6bd61670c62e9f Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Sun, 12 Jan 2025 15:45:27 +0000 Subject: [PATCH 2/3] Improve customTheme functionalities Signed-off-by: Charles de Dreuille --- canon-docs/src/app/globals.css | 1 + canon-docs/src/app/layout.tsx | 1 + .../components/CustomTheme/customTheme.tsx | 99 +++++++++++-------- .../components/CustomTheme/styles.module.css | 46 ++++++++- 4 files changed, 102 insertions(+), 45 deletions(-) diff --git a/canon-docs/src/app/globals.css b/canon-docs/src/app/globals.css index e9710c6576..249f05ea8c 100644 --- a/canon-docs/src/app/globals.css +++ b/canon-docs/src/app/globals.css @@ -49,6 +49,7 @@ iframe { display: flex; align-items: center; justify-content: flex-end; + min-width: 34px; } .cm-line { diff --git a/canon-docs/src/app/layout.tsx b/canon-docs/src/app/layout.tsx index 90cd6a9d0d..99b5f22739 100644 --- a/canon-docs/src/app/layout.tsx +++ b/canon-docs/src/app/layout.tsx @@ -13,6 +13,7 @@ import '/public/backstage.css'; export const metadata: Metadata = { title: 'Canon', description: 'UI library for Backstage', + metadataBase: new URL('https://canon.backstage.io'), }; export default function RootLayout({ diff --git a/canon-docs/src/components/CustomTheme/customTheme.tsx b/canon-docs/src/components/CustomTheme/customTheme.tsx index d02f96131b..b29b5458cd 100644 --- a/canon-docs/src/components/CustomTheme/customTheme.tsx +++ b/canon-docs/src/components/CustomTheme/customTheme.tsx @@ -6,28 +6,42 @@ import { sass } from '@codemirror/lang-sass'; import styles from './styles.module.css'; import { usePlayground } from '@/utils/playground-context'; import { AnimatePresence, motion } from 'framer-motion'; +import { Icon } from '../../../../packages/canon'; + +const defaultTheme = `:root { + --canon-accent: #000; +}`; export const CustomTheme = () => { + const [isClient, setIsClient] = useState(false); + const [open, setOpen] = useState(true); const [customTheme, setCustomTheme] = useState(undefined); const { selectedThemeName } = usePlayground(); - const [isClient, setIsClient] = useState(false); + const [savedMessage, setSavedMessage] = useState('Save'); + + const updateStyleElement = (theme: string) => { + let styleElement = document.getElementById( + 'custom-theme-style', + ) as HTMLStyleElement; + + if (!styleElement) { + styleElement = document.createElement('style'); + styleElement.id = 'custom-theme-style'; + document.head.appendChild(styleElement); + } + + styleElement.textContent = theme; + }; useEffect(() => { if (selectedThemeName === 'custom') { - const storedTheme = localStorage.getItem('customThemeCss') || ''; - setCustomTheme(storedTheme); - - let styleElement = document.getElementById( - 'custom-theme-style', - ) as HTMLStyleElement; - - if (!styleElement) { - styleElement = document.createElement('style'); - styleElement.id = 'custom-theme-style'; - document.head.appendChild(styleElement); + let storedTheme = localStorage.getItem('customThemeCss'); + if (!storedTheme) { + storedTheme = defaultTheme; + localStorage.setItem('customThemeCss', storedTheme); } - - styleElement.textContent = storedTheme; + setCustomTheme(storedTheme); + updateStyleElement(storedTheme); } else { const styleElement = document.getElementById( 'custom-theme-style', @@ -45,48 +59,53 @@ export const CustomTheme = () => { const handleSave = () => { if (customTheme) { localStorage.setItem('customThemeCss', customTheme); - - let styleElement = document.getElementById( - 'custom-theme-style', - ) as HTMLStyleElement; - - if (!styleElement) { - styleElement = document.createElement('style'); - styleElement.id = 'custom-theme-style'; - document.head.appendChild(styleElement); - } - - styleElement.textContent = customTheme; + updateStyleElement(customTheme); + setSavedMessage('Saved!'); + setTimeout(() => setSavedMessage('Save'), 1000); } }; - const onChange = useCallback((val: string) => { + const handleChange = useCallback((val: string) => { setCustomTheme(val); }, []); + if (isClient === false) return null; + return ( - {isClient && selectedThemeName === 'custom' && ( + {selectedThemeName === 'custom' && (
Custom Theme
- +
+ {open && ( + + )} + +
+
+
+
-
)}
diff --git a/canon-docs/src/components/CustomTheme/styles.module.css b/canon-docs/src/components/CustomTheme/styles.module.css index 17e6ef18e4..a4f51c8a1d 100644 --- a/canon-docs/src/components/CustomTheme/styles.module.css +++ b/canon-docs/src/components/CustomTheme/styles.module.css @@ -2,21 +2,32 @@ position: fixed; bottom: 16px; right: 16px; - width: 36%; - height: 348px; + width: 240px; + height: 47px; background-color: var(--canon-surface-1); border-radius: 0.375rem; border: 1px solid var(--canon-border-base); display: flex; flex-direction: column; overflow: hidden; - transition: background-color 0.2s ease-in-out, border-color 0.2s ease-in-out; + transition-property: background-color, border-color, height, width; + transition-duration: 0.2s; + transition-timing-function: ease-in-out; +} + +.open { + width: 36%; + height: 348px; } .editor { flex: 1; } +.editorContainer { + overflow: hidden; +} + .header { height: 46px; flex-shrink: 0; @@ -29,7 +40,16 @@ transition: background-color 0.2s ease-in-out, border-color 0.2s ease-in-out; } -.button { +.headerLeft { + font-size: 0.875rem; +} + +.headerRight { + display: flex; + gap: 8px; +} + +.buttonSave { all: unset; height: 28px; padding: 0 8px; @@ -37,5 +57,21 @@ background-color: #000; border-radius: 0.25rem; cursor: pointer; - font-size: 0.875rem; + font-size: 0.75rem; +} + +.buttonClose { + all: unset; + height: 28px; + padding: 0 8px; + color: #fff; + background-color: var(--canon-surface-2); + color: var(--canon-text-primary); + transition: background-color 0.2s ease-in-out; + border-radius: 0.25rem; + cursor: pointer; + font-size: 0.875rem; + display: flex; + align-items: center; + justify-content: center; } From 6de1d5fbae34fab1f3c42c1aa9eebe2a7e53458d Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Sun, 12 Jan 2025 22:28:07 +0000 Subject: [PATCH 3/3] Fix some styling Signed-off-by: Charles de Dreuille --- canon-docs/package.json | 2 ++ canon-docs/src/app/globals.css | 9 +++-- .../components/CustomTheme/customTheme.tsx | 34 +++++++++++++++++++ .../src/components/Sidebar/Sidebar.module.css | 14 ++++---- .../components/Toolbar/theme-name.module.css | 2 +- canon-docs/yarn.lock | 19 ++++++++++- 6 files changed, 65 insertions(+), 15 deletions(-) diff --git a/canon-docs/package.json b/canon-docs/package.json index af440bf9d5..9fddf08c20 100644 --- a/canon-docs/package.json +++ b/canon-docs/package.json @@ -10,11 +10,13 @@ }, "dependencies": { "@codemirror/lang-sass": "^6.0.2", + "@lezer/highlight": "^1.2.1", "@mdx-js/loader": "^3.1.0", "@mdx-js/react": "^3.1.0", "@next/mdx": "^15.1.4", "@storybook/react": "^8.4.7", "@types/mdx": "^2.0.13", + "@uiw/codemirror-themes": "^4.23.7", "@uiw/react-codemirror": "^4.23.7", "next": "14.2.23", "react": "^18", diff --git a/canon-docs/src/app/globals.css b/canon-docs/src/app/globals.css index 249f05ea8c..5efc0407e7 100644 --- a/canon-docs/src/app/globals.css +++ b/canon-docs/src/app/globals.css @@ -36,6 +36,10 @@ iframe { text-decoration: var(--shiki-dark-text-decoration) !important; } +.cm-editor { + transition: background-color 0.2s ease-in-out; +} + .ͼ2 .cm-gutters { background-color: var(--canon-surface-2); border-right: 1px solid var(--canon-border-base); @@ -65,8 +69,3 @@ iframe { .cm-focused { outline: none !important; } - -.cm-editor { - background-color: var(--canon-surface-1); - transition: background-color 0.2s ease-in-out; -} diff --git a/canon-docs/src/components/CustomTheme/customTheme.tsx b/canon-docs/src/components/CustomTheme/customTheme.tsx index b29b5458cd..3d8ed394bd 100644 --- a/canon-docs/src/components/CustomTheme/customTheme.tsx +++ b/canon-docs/src/components/CustomTheme/customTheme.tsx @@ -7,11 +7,44 @@ import styles from './styles.module.css'; import { usePlayground } from '@/utils/playground-context'; import { AnimatePresence, motion } from 'framer-motion'; import { Icon } from '../../../../packages/canon'; +import { createTheme } from '@uiw/codemirror-themes'; +import { tags as t } from '@lezer/highlight'; const defaultTheme = `:root { --canon-accent: #000; }`; +const myTheme = createTheme({ + theme: 'light', + settings: { + background: 'var(--canon-surface-1)', + backgroundImage: '', + foreground: '#6182B8', + caret: '#5d00ff', + selection: '#036dd626', + selectionMatch: '#036dd626', + lineHighlight: '#8a91991a', + gutterBackground: '#fff', + gutterForeground: '#8a919966', + }, + styles: [ + { tag: t.comment, color: '#787b8099' }, + { tag: t.variableName, color: '#0080ff' }, + { tag: [t.string, t.special(t.brace)], color: '#6182B8' }, + { tag: t.number, color: '#6182B8' }, + { tag: t.bool, color: '#6182B8' }, + { tag: t.null, color: '#6182B8' }, + { tag: t.keyword, color: '#6182B8' }, + { tag: t.operator, color: '#6182B8' }, + { tag: t.className, color: '#6182B8' }, + { tag: t.definition(t.typeName), color: '#6182B8' }, + { tag: t.typeName, color: '#6182B8' }, + { tag: t.angleBracket, color: '#6182B8' }, + { tag: t.tagName, color: '#6182B8' }, + { tag: t.attributeName, color: '#6182B8' }, + ], +}); + export const CustomTheme = () => { const [isClient, setIsClient] = useState(false); const [open, setOpen] = useState(true); @@ -104,6 +137,7 @@ export const CustomTheme = () => { onChange={handleChange} className={styles.editor} basicSetup={{ foldGutter: false }} + theme={myTheme} /> diff --git a/canon-docs/src/components/Sidebar/Sidebar.module.css b/canon-docs/src/components/Sidebar/Sidebar.module.css index 870f504d28..f3bdd3f2e7 100644 --- a/canon-docs/src/components/Sidebar/Sidebar.module.css +++ b/canon-docs/src/components/Sidebar/Sidebar.module.css @@ -35,15 +35,11 @@ position: relative; } -.menu a { - display: flex; - text-decoration: none; - height: 28px; - align-items: center; -} - .section { width: 100%; + display: flex; + flex-direction: column; + gap: 2px; } .sectionTitle { @@ -54,12 +50,14 @@ } .line { + text-decoration: none; + align-items: center; width: 100%; display: flex; flex-direction: row; justify-content: space-between; align-items: center; - height: 28px; + height: 26px; padding: 0 12px; border-radius: 4px; transition: background-color 0.2s ease-in-out; diff --git a/canon-docs/src/components/Toolbar/theme-name.module.css b/canon-docs/src/components/Toolbar/theme-name.module.css index 6f2ceb427f..c9700c571b 100644 --- a/canon-docs/src/components/Toolbar/theme-name.module.css +++ b/canon-docs/src/components/Toolbar/theme-name.module.css @@ -43,7 +43,7 @@ box-sizing: border-box; padding-block: 0.25rem; border-radius: 0.375rem; - background-color: canvas; + background-color: var(--canon-surface-1); color: var(--color-gray-900); border: 1px solid var(--canon-border-base); padding-inline: 0.25rem; diff --git a/canon-docs/yarn.lock b/canon-docs/yarn.lock index f7a3d103a4..90107a541b 100644 --- a/canon-docs/yarn.lock +++ b/canon-docs/yarn.lock @@ -406,7 +406,7 @@ __metadata: languageName: node linkType: hard -"@lezer/highlight@npm:^1.0.0": +"@lezer/highlight@npm:^1.0.0, @lezer/highlight@npm:^1.2.1": version: 1.2.1 resolution: "@lezer/highlight@npm:1.2.1" dependencies: @@ -1108,6 +1108,21 @@ __metadata: languageName: node linkType: hard +"@uiw/codemirror-themes@npm:^4.23.7": + version: 4.23.7 + resolution: "@uiw/codemirror-themes@npm:4.23.7" + dependencies: + "@codemirror/language": ^6.0.0 + "@codemirror/state": ^6.0.0 + "@codemirror/view": ^6.0.0 + peerDependencies: + "@codemirror/language": ">=6.0.0" + "@codemirror/state": ">=6.0.0" + "@codemirror/view": ">=6.0.0" + checksum: 8c2a9a7ee8df2de3f09ad1f18979c4b407f7dd03e1f6ddfe47837b9e4769b566a70259ec8c398473b6b6db79cdb8809d4acc2f2ed7851743e13ff95308f52e33 + languageName: node + linkType: hard + "@uiw/react-codemirror@npm:^4.23.7": version: 4.23.7 resolution: "@uiw/react-codemirror@npm:4.23.7" @@ -1481,6 +1496,7 @@ __metadata: resolution: "canon-docs@workspace:." dependencies: "@codemirror/lang-sass": ^6.0.2 + "@lezer/highlight": ^1.2.1 "@mdx-js/loader": ^3.1.0 "@mdx-js/react": ^3.1.0 "@next/mdx": ^15.1.4 @@ -1489,6 +1505,7 @@ __metadata: "@types/node": ^20 "@types/react": ^18 "@types/react-dom": ^18 + "@uiw/codemirror-themes": ^4.23.7 "@uiw/react-codemirror": ^4.23.7 concurrently: ^9.1.2 eslint: ^8