add base theme

This commit is contained in:
Ashley Harrison 2025-10-02 16:19:31 +01:00
parent d28b8a4417
commit cf356d1054
No known key found for this signature in database
GPG Key ID: FFB870B48A9457E0
2 changed files with 100 additions and 34 deletions

View File

@ -93,6 +93,7 @@ export { DataTransformerID } from '../transformations/transformers/ids';
export { mergeTransformer } from '../transformations/transformers/merge';
export { getThemeById } from '../themes/registry';
export * as experimentalThemeDefinitions from '../themes/themeDefinitions';
export { GrafanaEdition } from '../types/config';
export { SIPrefix } from '../valueFormats/symbolFormatters';

View File

@ -1,14 +1,14 @@
import { css } from '@emotion/css';
import { useState } from 'react';
import { useId, useState } from 'react';
import { AppEvents, createTheme, GrafanaTheme2 } from '@grafana/data';
import { experimentalThemeDefinitions } from '@grafana/data/internal';
import { t } from '@grafana/i18n';
import { useChromeHeaderHeight } from '@grafana/runtime';
import { CodeEditor, Stack, useStyles2 } from '@grafana/ui';
import { CodeEditor, Combobox, Field, Stack, useStyles2 } from '@grafana/ui';
import { ThemeDemo } from '@grafana/ui/internal';
import { Page } from 'app/core/components/Page/Page';
import tron from '../../../../packages/grafana-data/src/themes/themeDefinitions/tron';
import appEvents from '../../core/app_events';
import { HOME_NAV_ID } from '../../core/reducers/navModel';
import { getNavModel } from '../../core/selectors/navModel';
@ -17,6 +17,39 @@ import { useSelector } from '../../types/store';
import schema from './schema.generated.json';
const themeOptions = [
{
label: 'Dark',
value: JSON.stringify(
{
name: 'Dark',
colors: {
mode: 'dark',
},
},
null,
2
),
},
{
label: 'Light',
value: JSON.stringify(
{
name: 'Light',
colors: {
mode: 'light',
},
},
null,
2
),
},
...Object.values(experimentalThemeDefinitions).map((theme) => ({
label: theme.name,
value: JSON.stringify(theme, null, 2),
})),
];
export default function ThemePlayground() {
const navIndex = useSelector((state) => state.navIndex);
const homeNav = getNavModel(navIndex, HOME_NAV_ID).main;
@ -24,16 +57,22 @@ export default function ThemePlayground() {
text: t('theme-playground.title', 'Theme playground'),
parentItem: homeNav,
};
const [theme, setTheme] = useState(tron);
const baseId = useId();
const [baseTheme, setBaseTheme] = useState(themeOptions[0].value);
const [theme, setTheme] = useState(createTheme(JSON.parse(baseTheme)));
const chromeHeaderHeight = useChromeHeaderHeight();
const styles = useStyles2(getStyles, chromeHeaderHeight);
const onBlur = (value: string) => {
try {
const themeInput = JSON.parse(value);
setTheme(themeInput);
try {
setTheme(createTheme(themeInput));
} catch (error) {
appEvents.emit(AppEvents.alertError, ['Failed to create theme', error]);
}
} catch (error) {
appEvents.emit(AppEvents.alertError, ['Failed to modify theme', error]);
appEvents.emit(AppEvents.alertError, ['Failed to parse JSON', error]);
}
};
@ -49,33 +88,51 @@ export default function ThemePlayground() {
xs: 'column',
md: 'row',
}}
columnGap={2}
rowGap={1}
height="100%"
>
<CodeEditor
width="100%"
value={JSON.stringify(theme, null, 2)}
language="json"
showLineNumbers={true}
showMiniMap={true}
containerStyles={styles.codeEditor}
onBlur={onBlur}
onBeforeEditorMount={(monaco) => {
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
validate: true,
schemas: [
{
uri: 'theme-schema',
fileMatch: ['*'],
schema,
},
],
});
}}
monacoOptions={{
alwaysConsumeMouseWheel: true,
}}
/>
<ThemeProvider value={createTheme(theme)}>
<div className={styles.left}>
<Field noMargin label={t('theme-playground.label-base-theme', 'Base theme')}>
<Combobox
value={baseTheme}
onChange={(option) => {
setBaseTheme(option.value);
onBlur(option.value);
}}
options={themeOptions}
id={baseId}
/>
</Field>
<CodeEditor
width="100%"
value={baseTheme}
language="json"
showLineNumbers={true}
showMiniMap={true}
containerStyles={styles.codeEditor}
onBlur={onBlur}
onBeforeEditorMount={(monaco) => {
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
validate: true,
schemas: [
{
uri: 'theme-schema',
fileMatch: ['*'],
schema,
},
],
});
}}
monacoOptions={{
alwaysConsumeMouseWheel: true,
minimap: {
enabled: false,
},
}}
/>
</div>
<ThemeProvider value={theme}>
<ThemeDemo />
</ThemeProvider>
</Stack>
@ -84,16 +141,24 @@ export default function ThemePlayground() {
}
const getStyles = (theme: GrafanaTheme2, chromeHeaderHeight: number | undefined) => ({
codeEditor: css({
left: css({
background: theme.colors.background.primary,
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(1),
height: '40vh',
minWidth: '300px',
padding: theme.spacing(2, 0),
position: 'sticky',
top: chromeHeaderHeight ? `calc(${chromeHeaderHeight}px + ${theme.spacing(4)})` : 0,
top: chromeHeaderHeight ?? 0,
width: '100%',
zIndex: theme.zIndex.activePanel,
[theme.breakpoints.up('md')]: {
height: `calc(90vh - ${chromeHeaderHeight ?? 0}px - ${theme.spacing(2)})`,
width: '70%',
},
zIndex: theme.zIndex.activePanel,
}),
codeEditor: css({
flex: 1,
}),
});