Improve customTheme functionalities
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
@@ -49,6 +49,7 @@ iframe {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
min-width: 34px;
|
||||
}
|
||||
|
||||
.cm-line {
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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<string | undefined>(undefined);
|
||||
const { selectedThemeName } = usePlayground();
|
||||
const [isClient, setIsClient] = useState(false);
|
||||
const [savedMessage, setSavedMessage] = useState<string>('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 (
|
||||
<AnimatePresence>
|
||||
{isClient && selectedThemeName === 'custom' && (
|
||||
{selectedThemeName === 'custom' && (
|
||||
<motion.div
|
||||
className={styles.container}
|
||||
className={`${styles.container} ${open ? styles.open : ''}`}
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: 10 }}
|
||||
>
|
||||
<div className={styles.header}>
|
||||
<div className={styles.headerLeft}>Custom Theme</div>
|
||||
<button className={styles.button} onClick={handleSave}>
|
||||
Save
|
||||
</button>
|
||||
<div className={styles.headerRight}>
|
||||
{open && (
|
||||
<button className={styles.buttonSave} onClick={handleSave}>
|
||||
{savedMessage}
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
className={styles.buttonClose}
|
||||
onClick={() => setOpen(!open)}
|
||||
>
|
||||
<Icon name={open ? 'chevronDown' : 'chevronUp'} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.editorContainer}>
|
||||
<CodeMirror
|
||||
value={customTheme}
|
||||
height="300px"
|
||||
extensions={[sass()]}
|
||||
onChange={handleChange}
|
||||
className={styles.editor}
|
||||
basicSetup={{ foldGutter: false }}
|
||||
/>
|
||||
</div>
|
||||
<CodeMirror
|
||||
value={customTheme}
|
||||
height="300px"
|
||||
extensions={[sass()]}
|
||||
onChange={onChange}
|
||||
className={styles.editor}
|
||||
basicSetup={{ foldGutter: false }}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user