Merge pull request #30428 from backstage/canon-themes
Canon - Add custom theme capacity to Storybook and documentation
This commit is contained in:
+10
-3
@@ -3,10 +3,13 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "npm run build:css && next build",
|
||||
"build:css": "node scripts/build-css.js",
|
||||
"prebuild": "yarn sync:css",
|
||||
"build": "next build",
|
||||
"lint": "next lint",
|
||||
"start": "npm run build:css && next dev"
|
||||
"prestart": "yarn sync:css",
|
||||
"start": "concurrently \"yarn sync:css:watch\" \"next dev\"",
|
||||
"sync:css": "node scripts/sync-css.js",
|
||||
"sync:css:watch": "node scripts/sync-css.js --watch"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "19.1.8",
|
||||
@@ -14,6 +17,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@codemirror/lang-sass": "^6.0.2",
|
||||
"@codemirror/view": "^6.34.4",
|
||||
"@lezer/highlight": "^1.2.1",
|
||||
"@mdx-js/loader": "^3.1.0",
|
||||
"@mdx-js/react": "^3.1.0",
|
||||
@@ -25,6 +29,7 @@
|
||||
"motion": "^12.4.1",
|
||||
"next": "15.3.4",
|
||||
"next-mdx-remote-client": "^2.1.2",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"react-frame-component": "^5.2.7",
|
||||
@@ -36,6 +41,8 @@
|
||||
"@types/node": "^20",
|
||||
"@types/react": "19.1.8",
|
||||
"@types/react-dom": "19.1.6",
|
||||
"chokidar": "^3.6.0",
|
||||
"concurrently": "^8.2.2",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "15.3.4",
|
||||
"lightningcss": "^1.28.2",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
@font-face{font-family:CircularSp;font-weight:700;font-display:swap;unicode-range:U+0,U+D,U+20-7E,U+A0-17E,U+18F,U+192,U+1A0-1A1,U+1AF-1B0,U+1B5-1B6,U+1C4-1C6,U+1F1-1F3,U+1FA-1FF,U+218-21B,U+237,U+259,U+2BB-2BC,U+2C6-2C7,U+2C9,U+2D8-2DD,U+300-301,U+303,U+309,U+323,U+394,U+3A9,U+3BC,U+3C0,U+1E80-1E85,U+1E8A-1E8B,U+1EA0-1EF9,U+1FD6,U+2007-2008,U+200B,U+2010-2011,U+2013-2014,U+2018-201A,U+201C-201E,U+2020-2022,U+2026,U+2030,U+2032-2033,U+2039-203A,U+2042,U+2044,U+2051,U+2070,U+2074-2079,U+2080-2089,U+20AB-20AC,U+2113,U+2117,U+2122,U+2126,U+2160-2169,U+216C-216F,U+2190-2193,U+2196-2199,U+21A9,U+21B0-21B5,U+21C6,U+2202,U+2206,U+220F,U+2211-2212,U+2215,U+2219-221A,U+221E,U+222B,U+2248,U+2260,U+2264-2265,U+22C5,U+24C5,U+25A0-25A1,U+25AF,U+25B2-25B3,U+25CA-25CB,U+25CF,U+262E,U+2713,U+2715,U+2780-2788,U+E000,U+E002,U+F6C3,U+FB00-FB04,U+FEFF,U+FF0C,U+FF0E,U+FF1A-FF1B,U+FFFF;src:url(https://encore.scdn.co/fonts/CircularSp-Bold-602e7aefc706aa36c6ec1324b9bbc461.woff2)format("woff2"),url(https://encore.scdn.co/fonts/CircularSp-Bold-856afe2da4ba4e61239b129e2c16d633.woff)format("woff")}[data-theme=dark][data-theme-name=spotify]{--canon-font-text:CircularSp,CircularSp-Arab,CircularSp-Hebr,CircularSp-Cyrl,CircularSp-Grek,CircularSp-Deva;--canon-font-title:CircularSpTitle,CircularSp-Arab,CircularSp-Hebr,CircularSp-Cyrl,CircularSp-Grek,CircularSp-Deva;--canon-font-regular:CircularSp,CircularSp-Arab,CircularSp-Hebr,CircularSp-Cyrl,CircularSp-Grek,CircularSp-Deva;--canon-gray-1:#191919;--canon-gray-2:#242424;--canon-gray-3:#373737;--canon-gray-4:#464646;--canon-gray-5:#575757;--canon-gray-6:#7b7b7b;--canon-gray-7:#9e9e9e;--canon-gray-8:#b4b4b4;--canon-bg:#000;--canon-bg-surface-1:#191919;--canon-bg-surface-2:#242424;--canon-bg-solid:#1ed760;--canon-bg-solid-hover:#3be477;--canon-bg-solid-pressed:#1abc54;--canon-bg-solid-disabled:#0f6c30;--canon-bg-tint:#242424;--canon-bg-tint-hover:#202020;--canon-bg-tint-pressed:#292929;--canon-bg-tint-disabled:#fffc;--canon-bg-danger:#3b1219;--canon-bg-warning:#302008;--canon-bg-success:#132d21;--canon-bg-underline:#d16ecb;--canon-fg-primary:#fff;--canon-fg-secondary:#9e9e9e;--canon-fg-link:#fff;--canon-fg-link-hover:#fff;--canon-fg-disabled:#575757;--canon-fg-solid:#000;--canon-fg-solid-disabled:#083618;--canon-fg-tint:#fff;--canon-fg-tint-disabled:#575757;--canon-fg-danger:#e22b2b;--canon-fg-warning:#e36d05;--canon-fg-success:#1db954;--canon-border:var(--canon-gray-3);--canon-border-hover:#fff6;--canon-border-pressed:#ffffff80;--canon-border-disabled:#fff3;--canon-border-danger:#f87a7a;--canon-border-warning:#e36d05;--canon-border-success:#53db83;--canon-ring:#fff3}#root{padding-top:var(--header-height)}.canon-Button,.canon-IconButton{border-radius:var(--canon-radius-3);transition:background-color .2s ease-in-out,color .2s ease-in-out,box-shadow .2s ease-in-out}.canon-Button--variant-primary,.canon-IconButton--variant-primary{&:hover{box-shadow:inset 0 0 0 1px var(--canon-bg-solid);color:var(--canon-fg-primary);background-color:#0000}&:active{background-color:#1ed7601a}&:disabled{background-color:var(--canon-bg-solid-disabled);color:var(--canon-fg-solid-disabled);box-shadow:none}}.canon-Button[data-size=medium],.canon-Button[data-size=small]{padding-inline:var(--canon-space-3)}.canon-MenuTrigger{all:unset;display:inline-flex}.canon-MenuPopup{box-sizing:border-box;max-width:21.25rem}.canon-MenuSubmenuTrigger,.canon-MenuItem{height:auto;min-height:2rem}.canon-Text{font-family:var(--canon-font-text)}.canon-Heading{font-family:var(--canon-font-title)}.canon-TableRow{border-radius:var(--canon-radius-4);border:none}.canon-TableRow:hover td:first-child{border-top-left-radius:var(--canon-radius-2);border-bottom-left-radius:var(--canon-radius-2)}.canon-TableRow:hover td:last-child{border-top-right-radius:var(--canon-radius-2);border-bottom-right-radius:var(--canon-radius-2)}.canon-TableBody:before,.canon-TableBody:after{line-height:var(--canon-space-1);content:"";display:block}.canon-TableHeader .canon-TableRow{border-bottom:1px solid var(--canon-border)}.canon-TableHead{font-size:var(--canon-font-size-2);color:var(--canon-fg-secondary);font-weight:var(--canon-font-weight-regular)}.canon-ScrollAreaRoot{width:100%;height:unset}.canon-TabsIndicator{background-color:var(--canon-bg-underline)}.canon-TabsTab[data-selected]{background:radial-gradient(at bottom,#d16ecb40 0%,#0000 95%)}
|
||||
@@ -1,34 +0,0 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { bundle } = require('lightningcss');
|
||||
|
||||
const source = '../../packages/canon/src/css';
|
||||
const destination = '../public';
|
||||
|
||||
const source1 = path.join(__dirname, `${source}/styles.css`);
|
||||
const destination1 = path.join(__dirname, `${destination}/styles.css`);
|
||||
|
||||
// Function to bundle and copy the CSS file
|
||||
const bundleAndCopyFile = async (source, destination) => {
|
||||
try {
|
||||
const result = await bundle({
|
||||
filename: source,
|
||||
minify: true,
|
||||
});
|
||||
|
||||
fs.writeFileSync(destination, result.code);
|
||||
console.log('File bundled and copied successfully!');
|
||||
} catch (err) {
|
||||
console.error('Error bundling file:', err);
|
||||
}
|
||||
};
|
||||
|
||||
// Initial bundle and copy
|
||||
Promise.all([bundleAndCopyFile(source1, destination1)])
|
||||
.then(() => {
|
||||
// Add an empty line after all operations are complete - It looks better in the terminal :)
|
||||
console.log('');
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Error in processing files:', err);
|
||||
});
|
||||
@@ -0,0 +1,149 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { bundle } = require('lightningcss');
|
||||
const chokidar = require('chokidar');
|
||||
|
||||
// Configuration
|
||||
const config = {
|
||||
canonPath: '../../packages/canon',
|
||||
publicPath: '../public',
|
||||
files: [
|
||||
{
|
||||
source: 'css/styles.css',
|
||||
destination: 'theme-backstage.css',
|
||||
name: 'Main Styles',
|
||||
},
|
||||
{
|
||||
source: '.storybook/themes/spotify.css',
|
||||
destination: 'theme-spotify.css',
|
||||
name: 'Spotify Theme',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
class CSSSync {
|
||||
constructor() {
|
||||
this.canonPath = path.resolve(__dirname, config.canonPath);
|
||||
this.publicPath = path.resolve(__dirname, config.publicPath);
|
||||
this.isWatching = process.argv.includes('--watch');
|
||||
}
|
||||
|
||||
async syncFile(fileConfig) {
|
||||
const sourcePath = path.join(this.canonPath, fileConfig.source);
|
||||
const destPath = path.join(this.publicPath, fileConfig.destination);
|
||||
|
||||
try {
|
||||
// Check if source file exists
|
||||
if (!fs.existsSync(sourcePath)) {
|
||||
console.warn(`⚠️ Source file not found: ${sourcePath}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure destination directory exists
|
||||
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
||||
|
||||
// Bundle and optimize CSS
|
||||
const result = await bundle({
|
||||
filename: sourcePath,
|
||||
minify: true,
|
||||
});
|
||||
|
||||
// Write to destination
|
||||
fs.writeFileSync(destPath, result.code);
|
||||
|
||||
console.log(
|
||||
`✅ ${fileConfig.name}: ${fileConfig.source} → ${fileConfig.destination}`,
|
||||
);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(`❌ Error syncing ${fileConfig.name}:`, error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async syncAll() {
|
||||
console.log('🔄 Syncing CSS files...\n');
|
||||
|
||||
let successCount = 0;
|
||||
for (const fileConfig of config.files) {
|
||||
if (await this.syncFile(fileConfig)) {
|
||||
successCount++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(
|
||||
`\n✨ Synced ${successCount}/${config.files.length} CSS files successfully!`,
|
||||
);
|
||||
|
||||
if (successCount > 0) {
|
||||
console.log('\n📁 Available CSS files in public/:');
|
||||
config.files.forEach(file => {
|
||||
const destPath = path.join(this.publicPath, file.destination);
|
||||
if (fs.existsSync(destPath)) {
|
||||
const stats = fs.statSync(destPath);
|
||||
const size = (stats.size / 1024).toFixed(2);
|
||||
console.log(` • ${file.destination} (${size} KB)`);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
startWatching() {
|
||||
console.log('👀 Watching for CSS changes...\n');
|
||||
|
||||
// Watch all source files
|
||||
const watchPaths = config.files.map(file =>
|
||||
path.join(this.canonPath, file.source),
|
||||
);
|
||||
|
||||
const watcher = chokidar.watch(watchPaths, {
|
||||
ignored: /node_modules/,
|
||||
persistent: true,
|
||||
});
|
||||
|
||||
watcher.on('change', async filePath => {
|
||||
console.log(
|
||||
`\n🔄 Change detected: ${path.relative(this.canonPath, filePath)}`,
|
||||
);
|
||||
|
||||
// Find which file changed and sync it
|
||||
const fileConfig = config.files.find(file =>
|
||||
filePath.endsWith(file.source.replace(/\//g, path.sep)),
|
||||
);
|
||||
|
||||
if (fileConfig) {
|
||||
await this.syncFile(fileConfig);
|
||||
}
|
||||
});
|
||||
|
||||
watcher.on('error', error => console.error('❌ Watch error:', error));
|
||||
|
||||
// Handle process termination
|
||||
process.on('SIGINT', () => {
|
||||
console.log('\n👋 Stopping CSS sync...');
|
||||
watcher.close();
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
async run() {
|
||||
console.log('🎨 Canon CSS Sync Tool\n');
|
||||
console.log(`📂 Canon path: ${this.canonPath}`);
|
||||
console.log(`📂 Public path: ${this.publicPath}\n`);
|
||||
|
||||
// Initial sync
|
||||
await this.syncAll();
|
||||
|
||||
// Watch for changes if requested
|
||||
if (this.isWatching) {
|
||||
this.startWatching();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run the sync tool
|
||||
const cssSync = new CSSSync();
|
||||
cssSync.run().catch(error => {
|
||||
console.error('❌ CSS Sync failed:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -6,7 +6,8 @@ import { CustomTheme } from '@/components/CustomTheme';
|
||||
import styles from '../css/page.module.css';
|
||||
|
||||
import '../css/globals.css';
|
||||
import '/public/styles.css';
|
||||
import '/public/theme-backstage.css';
|
||||
import '/public/theme-spotify.css';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Canon',
|
||||
|
||||
@@ -7,6 +7,7 @@ import { usePlayground } from '@/utils/playground-context';
|
||||
|
||||
const themes = [
|
||||
{ name: 'Backstage', value: 'default' },
|
||||
{ name: 'Spotify', value: 'spotify' },
|
||||
{ name: 'Custom theme', value: 'custom' },
|
||||
];
|
||||
|
||||
|
||||
@@ -40,16 +40,29 @@ export const PlaygroundProvider = ({ children }: { children: ReactNode }) => {
|
||||
const [selectedComponents, setSelectedComponents] = useState<string[]>(
|
||||
components.map(component => component.slug),
|
||||
);
|
||||
const [selectedTheme, setSelectedTheme] = useState<Theme>(() => {
|
||||
return isBrowser
|
||||
? (localStorage.getItem('theme') as Theme) || 'light'
|
||||
: 'light';
|
||||
});
|
||||
const [selectedThemeName, setSelectedThemeName] = useState<ThemeName>(() => {
|
||||
return isBrowser
|
||||
? (localStorage.getItem('theme-name') as ThemeName) || 'default'
|
||||
: 'default';
|
||||
});
|
||||
const [selectedTheme, setSelectedTheme] = useState<Theme>('light');
|
||||
const [selectedThemeName, setSelectedThemeName] =
|
||||
useState<ThemeName>('default');
|
||||
|
||||
// Load saved theme from localStorage after hydration
|
||||
useEffect(() => {
|
||||
if (isBrowser) {
|
||||
const savedTheme = localStorage.getItem('theme') as Theme;
|
||||
if (savedTheme) {
|
||||
setSelectedTheme(savedTheme);
|
||||
}
|
||||
}
|
||||
}, [isBrowser]);
|
||||
|
||||
// Load saved theme name from localStorage after hydration
|
||||
useEffect(() => {
|
||||
if (isBrowser) {
|
||||
const savedThemeName = localStorage.getItem('theme-name') as ThemeName;
|
||||
if (savedThemeName) {
|
||||
setSelectedThemeName(savedThemeName);
|
||||
}
|
||||
}
|
||||
}, [isBrowser]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isBrowser) {
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// Auto-generated theme switcher utility
|
||||
|
||||
export const availableThemes = {
|
||||
default: '/styles.css',
|
||||
spotify: '/spotify-theme.css',
|
||||
} as const;
|
||||
|
||||
export type ThemeName = keyof typeof availableThemes;
|
||||
|
||||
export function switchTheme(themeName: ThemeName) {
|
||||
// Remove existing theme links
|
||||
const existingLinks = document.querySelectorAll('link[data-theme]');
|
||||
existingLinks.forEach(link => link.remove());
|
||||
|
||||
// Add new theme link
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.href = availableThemes[themeName];
|
||||
link.setAttribute('data-theme', themeName);
|
||||
document.head.appendChild(link);
|
||||
}
|
||||
|
||||
export function getCurrentTheme(): ThemeName {
|
||||
const existingLink = document.querySelector('link[data-theme]');
|
||||
if (existingLink) {
|
||||
const href = existingLink.getAttribute('href');
|
||||
for (const [name, path] of Object.entries(availableThemes)) {
|
||||
if (path === href) return name as ThemeName;
|
||||
}
|
||||
}
|
||||
return 'default';
|
||||
}
|
||||
+882
-22
File diff suppressed because it is too large
Load Diff
@@ -15,7 +15,6 @@ const config: StorybookConfig = {
|
||||
getAbsolutePath('@storybook/addon-webpack5-compiler-swc'),
|
||||
getAbsolutePath('@storybook/addon-essentials'),
|
||||
getAbsolutePath('@storybook/addon-interactions'),
|
||||
getAbsolutePath('@storybook/addon-themes'),
|
||||
],
|
||||
framework: {
|
||||
name: getAbsolutePath('@storybook/react-webpack5'),
|
||||
|
||||
@@ -1,8 +1,74 @@
|
||||
import type { Preview, ReactRenderer } from '@storybook/react';
|
||||
import { withThemeByDataAttribute } from '@storybook/addon-themes';
|
||||
import React, { useEffect } from 'react';
|
||||
import type { Decorator, Preview, ReactRenderer } from '@storybook/react';
|
||||
import { useGlobals } from '@storybook/preview-api';
|
||||
|
||||
// Default Backstage theme
|
||||
import '../src/css/styles.css';
|
||||
|
||||
// Custom themes
|
||||
import './themes/spotify.css';
|
||||
|
||||
export const withThemes: Decorator = StoryFn => {
|
||||
const [globals] = useGlobals();
|
||||
const selectedTheme = globals.themeMode || 'light';
|
||||
const selectedThemeName = globals.themeName || 'default';
|
||||
|
||||
useEffect(() => {
|
||||
const htmlElement = document.documentElement;
|
||||
|
||||
// Remove any existing theme data attributes
|
||||
htmlElement.removeAttribute('data-theme');
|
||||
htmlElement.removeAttribute('data-theme-name');
|
||||
|
||||
// Add the selected theme data attribute
|
||||
htmlElement.setAttribute('data-theme', selectedTheme);
|
||||
htmlElement.setAttribute('data-theme-name', selectedThemeName);
|
||||
|
||||
// Cleanup on unmount
|
||||
return () => {
|
||||
htmlElement.removeAttribute('data-theme');
|
||||
htmlElement.removeAttribute('data-theme-name');
|
||||
};
|
||||
}, [selectedTheme, selectedThemeName]);
|
||||
|
||||
return <StoryFn />;
|
||||
};
|
||||
|
||||
const preview: Preview = {
|
||||
globalTypes: {
|
||||
themeMode: {
|
||||
name: 'Theme Mode',
|
||||
description: 'Global theme mode for components',
|
||||
defaultValue: 'light',
|
||||
toolbar: {
|
||||
icon: 'circlehollow',
|
||||
items: [
|
||||
{ value: 'light', icon: 'circlehollow', title: 'Light' },
|
||||
{ value: 'dark', icon: 'circle', title: 'Dark' },
|
||||
],
|
||||
showName: true,
|
||||
dynamicTitle: true,
|
||||
},
|
||||
},
|
||||
themeName: {
|
||||
name: 'Theme Name',
|
||||
description: 'Global theme name for components',
|
||||
defaultValue: 'default',
|
||||
toolbar: {
|
||||
icon: 'paintbrush',
|
||||
items: [
|
||||
{ value: 'default', title: 'Default (Backstage)' },
|
||||
{ value: 'spotify', title: 'Spotify' },
|
||||
],
|
||||
showName: true,
|
||||
dynamicTitle: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
initialGlobals: {
|
||||
themeMode: 'light',
|
||||
themeName: 'default',
|
||||
},
|
||||
parameters: {
|
||||
controls: {
|
||||
matchers: {
|
||||
@@ -67,13 +133,7 @@ const preview: Preview = {
|
||||
},
|
||||
},
|
||||
decorators: [
|
||||
withThemeByDataAttribute<ReactRenderer>({
|
||||
themes: {
|
||||
Light: 'light',
|
||||
Dark: 'dark',
|
||||
},
|
||||
defaultTheme: 'Light',
|
||||
}),
|
||||
withThemes,
|
||||
Story => {
|
||||
document.body.style.backgroundColor = 'var(--canon-bg)';
|
||||
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
@font-face {
|
||||
font-family: CircularSp;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range: U+0000, U+000D, U+0020-007E, U+00A0-017E, U+018F, U+0192,
|
||||
U+01A0-01A1, U+01AF-01B0, U+01B5-01B6, U+01C4-01C6, U+01F1-01F3, U+01FA-01FF,
|
||||
U+0218-021B, U+0237, U+0259, U+02BB-02BC, U+02C6-02C7, U+02C9, U+02D8-02DD,
|
||||
U+0300-0301, U+0303, U+0309, U+0323, U+0394, U+03A9, U+03BC, U+03C0,
|
||||
U+1E80-1E85, U+1E8A-1E8B, U+1EA0-1EF9, U+1FD6, U+2007-2008, U+200B,
|
||||
U+2010-2011, U+2013-2014, U+2018-201A, U+201C-201E, U+2020-2022, U+2026,
|
||||
U+2030, U+2032-2033, U+2039-203A, U+2042, U+2044, U+2051, U+2070,
|
||||
U+2074-2079, U+2080-2089, U+20AB-20AC, U+2113, U+2117, U+2122, U+2126,
|
||||
U+2160-2169, U+216C-216F, U+2190-2193, U+2196-2199, U+21A9, U+21B0-21B5,
|
||||
U+21C6, U+2202, U+2206, U+220F, U+2211-2212, U+2215, U+2219-221A, U+221E,
|
||||
U+222B, U+2248, U+2260, U+2264-2265, U+22C5, U+24C5, U+25A0-25A1, U+25AF,
|
||||
U+25B2-25B3, U+25CA-25CB, U+25CF, U+262E, U+2713, U+2715, U+2780-2788,
|
||||
U+E000, U+E002, U+F6C3, U+FB00-FB04, U+FEFF, U+FF0C, U+FF0E, U+FF1A-FF1B,
|
||||
U+FFFF;
|
||||
src: url('https://encore.scdn.co/fonts/CircularSp-Bold-602e7aefc706aa36c6ec1324b9bbc461.woff2')
|
||||
format('woff2'),
|
||||
url('https://encore.scdn.co/fonts/CircularSp-Bold-856afe2da4ba4e61239b129e2c16d633.woff')
|
||||
format('woff');
|
||||
}
|
||||
|
||||
[data-theme='dark'][data-theme-name='spotify'] {
|
||||
--canon-font-text: CircularSp, CircularSp-Arab, CircularSp-Hebr,
|
||||
CircularSp-Cyrl, CircularSp-Grek, CircularSp-Deva;
|
||||
--canon-font-title: CircularSpTitle, CircularSp-Arab, CircularSp-Hebr,
|
||||
CircularSp-Cyrl, CircularSp-Grek, CircularSp-Deva;
|
||||
--canon-font-regular: CircularSp, CircularSp-Arab, CircularSp-Hebr,
|
||||
CircularSp-Cyrl, CircularSp-Grek, CircularSp-Deva;
|
||||
|
||||
/* Gray scale */
|
||||
--canon-gray-1: #191919;
|
||||
--canon-gray-2: #242424;
|
||||
--canon-gray-3: #373737;
|
||||
--canon-gray-4: #464646;
|
||||
--canon-gray-5: #575757;
|
||||
--canon-gray-6: #7b7b7b;
|
||||
--canon-gray-7: #9e9e9e;
|
||||
--canon-gray-8: #b4b4b4;
|
||||
|
||||
/* Background Colors */
|
||||
--canon-bg: #000000;
|
||||
--canon-bg-surface-1: #191919;
|
||||
--canon-bg-surface-2: #242424;
|
||||
--canon-bg-solid: #1ed760;
|
||||
--canon-bg-solid-hover: #3be477;
|
||||
--canon-bg-solid-pressed: #1abc54;
|
||||
--canon-bg-solid-disabled: #0f6c30;
|
||||
--canon-bg-tint: #242424;
|
||||
--canon-bg-tint-hover: #202020;
|
||||
--canon-bg-tint-pressed: #292929;
|
||||
--canon-bg-tint-disabled: rgba(255, 255, 255, 0.8);
|
||||
--canon-bg-danger: #3b1219;
|
||||
--canon-bg-warning: #302008;
|
||||
--canon-bg-success: #132d21;
|
||||
--canon-bg-underline: #d16ecb;
|
||||
|
||||
/* Foreground Colors */
|
||||
--canon-fg-primary: #ffffff;
|
||||
--canon-fg-secondary: #9e9e9e;
|
||||
--canon-fg-link: #ffffff;
|
||||
--canon-fg-link-hover: #ffffff;
|
||||
--canon-fg-disabled: #575757;
|
||||
--canon-fg-solid: #000000;
|
||||
--canon-fg-solid-disabled: #083618;
|
||||
--canon-fg-tint: #ffffff;
|
||||
--canon-fg-tint-disabled: #575757;
|
||||
--canon-fg-danger: #e22b2b;
|
||||
--canon-fg-warning: #e36d05;
|
||||
--canon-fg-success: #1db954;
|
||||
|
||||
/* Border Colors */
|
||||
--canon-border: var(--canon-gray-3);
|
||||
--canon-border-hover: rgba(255, 255, 255, 0.4);
|
||||
--canon-border-pressed: rgba(255, 255, 255, 0.5);
|
||||
--canon-border-disabled: rgba(255, 255, 255, 0.2);
|
||||
--canon-border-danger: #f87a7a;
|
||||
--canon-border-warning: #e36d05;
|
||||
--canon-border-success: #53db83;
|
||||
|
||||
/* Ring Colors */
|
||||
--canon-ring: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
#root {
|
||||
padding-top: var(--header-height);
|
||||
}
|
||||
|
||||
.canon-Button,
|
||||
.canon-IconButton {
|
||||
border-radius: var(--canon-radius-3);
|
||||
transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out,
|
||||
box-shadow 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.canon-Button--variant-primary,
|
||||
.canon-IconButton--variant-primary {
|
||||
&:hover {
|
||||
box-shadow: inset 0 0 0 1px var(--canon-bg-solid);
|
||||
background-color: transparent;
|
||||
color: var(--canon-fg-primary);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: rgba(30, 215, 96, 0.1);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background-color: var(--canon-bg-solid-disabled);
|
||||
color: var(--canon-fg-solid-disabled);
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.canon-Button[data-size='medium'],
|
||||
.canon-Button[data-size='small'] {
|
||||
padding-inline: var(--canon-space-3);
|
||||
}
|
||||
|
||||
.canon-MenuTrigger {
|
||||
all: unset;
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.canon-MenuPopup {
|
||||
box-sizing: border-box;
|
||||
max-width: 21.25rem;
|
||||
}
|
||||
|
||||
.canon-MenuSubmenuTrigger,
|
||||
.canon-MenuItem {
|
||||
height: auto;
|
||||
min-height: 2rem;
|
||||
}
|
||||
|
||||
.canon-Text {
|
||||
font-family: var(--canon-font-text);
|
||||
}
|
||||
|
||||
.canon-Heading {
|
||||
font-family: var(--canon-font-title);
|
||||
}
|
||||
|
||||
.canon-TableRow {
|
||||
border: none;
|
||||
border-radius: var(--canon-radius-4);
|
||||
}
|
||||
|
||||
.canon-TableRow:hover td:first-child {
|
||||
border-top-left-radius: var(--canon-radius-2);
|
||||
border-bottom-left-radius: var(--canon-radius-2);
|
||||
}
|
||||
|
||||
.canon-TableRow:hover td:last-child {
|
||||
border-top-right-radius: var(--canon-radius-2);
|
||||
border-bottom-right-radius: var(--canon-radius-2);
|
||||
}
|
||||
|
||||
.canon-TableBody:before,
|
||||
.canon-TableBody:after {
|
||||
line-height: var(--canon-space-1);
|
||||
content: '\200C';
|
||||
display: block;
|
||||
}
|
||||
|
||||
.canon-TableHeader .canon-TableRow {
|
||||
border-bottom: 1px solid var(--canon-border);
|
||||
}
|
||||
|
||||
.canon-TableHead {
|
||||
font-size: var(--canon-font-size-2);
|
||||
color: var(--canon-fg-secondary);
|
||||
font-weight: var(--canon-font-weight-regular);
|
||||
}
|
||||
|
||||
.canon-ScrollAreaRoot {
|
||||
width: 100%;
|
||||
/* TODO: Bring this as a fix in OS */
|
||||
height: unset;
|
||||
}
|
||||
|
||||
.canon-TabsIndicator {
|
||||
background-color: var(--canon-bg-underline);
|
||||
}
|
||||
|
||||
.canon-TabsTab[data-selected] {
|
||||
background: radial-gradient(
|
||||
ellipse at bottom,
|
||||
rgba(209, 110, 203, 0.25) 0%,
|
||||
transparent 95%
|
||||
);
|
||||
}
|
||||
@@ -52,7 +52,6 @@
|
||||
"@storybook/addon-essentials": "^8.6.12",
|
||||
"@storybook/addon-interactions": "^8.6.12",
|
||||
"@storybook/addon-styling-webpack": "^1.0.1",
|
||||
"@storybook/addon-themes": "^8.6.12",
|
||||
"@storybook/addon-webpack5-compiler-swc": "^3.0.0",
|
||||
"@storybook/blocks": "^8.6.12",
|
||||
"@storybook/react": "^8.6.12",
|
||||
|
||||
@@ -3821,7 +3821,6 @@ __metadata:
|
||||
"@storybook/addon-essentials": "npm:^8.6.12"
|
||||
"@storybook/addon-interactions": "npm:^8.6.12"
|
||||
"@storybook/addon-styling-webpack": "npm:^1.0.1"
|
||||
"@storybook/addon-themes": "npm:^8.6.12"
|
||||
"@storybook/addon-webpack5-compiler-swc": "npm:^3.0.0"
|
||||
"@storybook/blocks": "npm:^8.6.12"
|
||||
"@storybook/react": "npm:^8.6.12"
|
||||
@@ -19996,17 +19995,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@storybook/addon-themes@npm:^8.6.12":
|
||||
version: 8.6.12
|
||||
resolution: "@storybook/addon-themes@npm:8.6.12"
|
||||
dependencies:
|
||||
ts-dedent: "npm:^2.0.0"
|
||||
peerDependencies:
|
||||
storybook: ^8.6.12
|
||||
checksum: 10/2df945accd7c0546785f71e2adc4ed68d71a057bc54aaef53f16ce8fcbc5a494380d60f9dd58520fe7d93b021a1b09e3d73553766d4bf2a1d07a3d731132b939
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@storybook/addon-toolbars@npm:8.6.12":
|
||||
version: 8.6.12
|
||||
resolution: "@storybook/addon-toolbars@npm:8.6.12"
|
||||
|
||||
Reference in New Issue
Block a user