mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-10-22 10:02:41 +00:00
style(insights): give panels their own surface in dark mode (#15552)
* style(insights): give panels the Click UI chartWidget surface in dark mode In dark mode the panels drew the shared `surface-primary` (#0d0d0d) against a page of the same colour, so the cards read as one flat sheet with only a faint border separating them. Click UI treats a dashboard widget as its own surface, a step lighter than the page behind it. Add the `dashboards.chartWidget` surface and stroke as theme tokens and apply them to the Insights panels under `dark:` only, so light mode keeps the shared surface/border tokens untouched: dark #282828 surface / #323232 stroke light #ffffff surface / #e6e7e9 stroke (defined for completeness; unused) `Panel` is local to InsightsView, so this cannot leak into other pages -- all six panels (the four KPI tiles, both user tables, the conversation list, and the loading/error cards) pick it up from the one definition. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(theme): register muted text role * fix(theme): register chart widget colors --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Ravi Kumar L <ravi.lazar@clickhouse.com>
This commit is contained in:
parent
d21bffa4c6
commit
d07527ff93
10 changed files with 184 additions and 0 deletions
|
|
@ -106,6 +106,10 @@ function Panel({ children, className }: { children: React.ReactNode; className?:
|
|||
<section
|
||||
className={cn(
|
||||
'min-w-0 rounded-lg border border-border-light bg-surface-primary p-5',
|
||||
/** Dark mode only: Click UI gives dashboard widgets their own surface and
|
||||
* stroke, a step lighter than the page behind them. Light mode keeps the
|
||||
* shared surface/border tokens. */
|
||||
'dark:border-chart-widget-stroke dark:bg-chart-widget-surface',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -173,6 +173,8 @@ html {
|
|||
--surface-hover-alt: var(--gray-300);
|
||||
--surface-composer-hover: var(--gray-200);
|
||||
--surface-primary: var(--white);
|
||||
--chart-widget-surface: 255 255 255; /* Click UI dashboards.chartWidget.color.background.default #ffffff */
|
||||
--chart-widget-stroke: 230 231 233; /* Click UI dashboards.chartWidget.color.stroke.default #e6e7e9 */
|
||||
--surface-primary-alt: var(--gray-50);
|
||||
--surface-primary-contrast: var(--gray-100);
|
||||
--surface-secondary: var(--gray-50);
|
||||
|
|
@ -283,6 +285,8 @@ html {
|
|||
--surface-hover-alt: var(--gray-600);
|
||||
--surface-composer-hover: var(--gray-600);
|
||||
--surface-primary: var(--gray-900);
|
||||
--chart-widget-surface: 40 40 40; /* Click UI dashboards.chartWidget.color.background.default #282828 */
|
||||
--chart-widget-stroke: 50 50 50; /* Click UI dashboards.chartWidget.color.stroke.default #323232 */
|
||||
--surface-primary-alt: var(--gray-850);
|
||||
--surface-primary-contrast: var(--gray-850);
|
||||
--surface-secondary: var(--gray-800);
|
||||
|
|
@ -355,6 +359,8 @@ html {
|
|||
--text-tertiary: var(--gizmo-gray-500);
|
||||
--text-muted: 105 110 121; /* Click UI global.color.text.muted #696e79 */
|
||||
--surface-primary: var(--white);
|
||||
--chart-widget-surface: 255 255 255; /* Click UI dashboards.chartWidget.color.background.default #ffffff */
|
||||
--chart-widget-stroke: 230 231 233; /* Click UI dashboards.chartWidget.color.stroke.default #e6e7e9 */
|
||||
--surface-secondary: var(--gray-50);
|
||||
--surface-tertiary: var(--gray-100);
|
||||
--border-light: 0 0 0;
|
||||
|
|
@ -372,6 +378,8 @@ html {
|
|||
--text-tertiary: var(--gizmo-gray-500);
|
||||
--text-muted: 179 182 189; /* Click UI global.color.text.muted #b3b6bd */
|
||||
--surface-primary: var(--gray-900);
|
||||
--chart-widget-surface: 40 40 40; /* Click UI dashboards.chartWidget.color.background.default #282828 */
|
||||
--chart-widget-stroke: 50 50 50; /* Click UI dashboards.chartWidget.color.stroke.default #323232 */
|
||||
--surface-secondary: var(--gray-800);
|
||||
--surface-tertiary: var(--gray-700);
|
||||
--border-light: 217 217 227;
|
||||
|
|
|
|||
|
|
@ -88,6 +88,83 @@ describe('theme registry', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('derives omitted muted text from a theme that restates tertiary text', () => {
|
||||
const resolved = resolveTheme(
|
||||
{
|
||||
version: 1,
|
||||
name: 'legacy-muted-reference',
|
||||
modes: { dark: { colors: { 'rgb-text-tertiary': '120 121 122' } } },
|
||||
},
|
||||
'dark',
|
||||
);
|
||||
|
||||
expect(resolved.colors['rgb-text-muted']).toBe('120 121 122');
|
||||
});
|
||||
|
||||
it('preserves an explicit muted text color', () => {
|
||||
const resolved = resolveTheme(
|
||||
{
|
||||
version: 1,
|
||||
name: 'explicit-muted-reference',
|
||||
modes: {
|
||||
light: {
|
||||
colors: {
|
||||
'rgb-text-tertiary': '120 121 122',
|
||||
'rgb-text-muted': '90 91 92',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'light',
|
||||
);
|
||||
|
||||
expect(resolved.colors['rgb-text-muted']).toBe('90 91 92');
|
||||
});
|
||||
|
||||
it('derives omitted chart widget colors from the previous panel roles', () => {
|
||||
const resolved = resolveTheme(
|
||||
{
|
||||
version: 1,
|
||||
name: 'legacy-chart-widget-reference',
|
||||
modes: {
|
||||
dark: {
|
||||
colors: {
|
||||
'rgb-surface-primary': '20 21 22',
|
||||
'rgb-border-light': '30 31 32',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'dark',
|
||||
);
|
||||
|
||||
expect(resolved.colors['rgb-chart-widget-surface']).toBe('20 21 22');
|
||||
expect(resolved.colors['rgb-chart-widget-stroke']).toBe('30 31 32');
|
||||
});
|
||||
|
||||
it('preserves explicit chart widget colors', () => {
|
||||
const resolved = resolveTheme(
|
||||
{
|
||||
version: 1,
|
||||
name: 'explicit-chart-widget-reference',
|
||||
modes: {
|
||||
dark: {
|
||||
colors: {
|
||||
'rgb-surface-primary': '20 21 22',
|
||||
'rgb-border-light': '30 31 32',
|
||||
'rgb-chart-widget-surface': '40 41 42',
|
||||
'rgb-chart-widget-stroke': '50 51 52',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'dark',
|
||||
);
|
||||
|
||||
expect(resolved.colors['rgb-chart-widget-surface']).toBe('40 41 42');
|
||||
expect(resolved.colors['rgb-chart-widget-stroke']).toBe('50 51 52');
|
||||
});
|
||||
|
||||
it('resolves provider brand tokens and lets a theme override them', () => {
|
||||
const defaults = resolveTheme(libreChatTheme, 'light');
|
||||
expect(defaults.brands['provider-anthropic']).toBe('#d09a74');
|
||||
|
|
|
|||
|
|
@ -287,6 +287,21 @@ export function resolveTheme(theme: ThemeDefinition, mode: ThemeMode): ResolvedT
|
|||
customColors?.['rgb-text-primary'] !== undefined
|
||||
? { 'rgb-shimmer-base': customColors['rgb-text-primary'] }
|
||||
: {};
|
||||
const textMutedFallback =
|
||||
customColors?.['rgb-text-muted'] === undefined &&
|
||||
customColors?.['rgb-text-tertiary'] !== undefined
|
||||
? { 'rgb-text-muted': customColors['rgb-text-tertiary'] }
|
||||
: {};
|
||||
const chartWidgetSurfaceFallback =
|
||||
customColors?.['rgb-chart-widget-surface'] === undefined &&
|
||||
customColors?.['rgb-surface-primary'] !== undefined
|
||||
? { 'rgb-chart-widget-surface': customColors['rgb-surface-primary'] }
|
||||
: {};
|
||||
const chartWidgetStrokeFallback =
|
||||
customColors?.['rgb-chart-widget-stroke'] === undefined &&
|
||||
customColors?.['rgb-border-light'] !== undefined
|
||||
? { 'rgb-chart-widget-stroke': customColors['rgb-border-light'] }
|
||||
: {};
|
||||
|
||||
return {
|
||||
version: THEME_VERSION,
|
||||
|
|
@ -297,6 +312,9 @@ export function resolveTheme(theme: ThemeDefinition, mode: ThemeMode): ResolvedT
|
|||
...customColors,
|
||||
...composerHoverFallback,
|
||||
...shimmerBaseFallback,
|
||||
...textMutedFallback,
|
||||
...chartWidgetSurfaceFallback,
|
||||
...chartWidgetStrokeFallback,
|
||||
} as Required<IThemeRGB>,
|
||||
appearance: { ...defaultAppearance, ...definition?.appearance },
|
||||
brands: { ...defaultBrands, ...theme.brands },
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ export const darkTheme: IThemeRGB = {
|
|||
'rgb-text-secondary': '205 205 205', // #cdcdcd (gray-300)
|
||||
'rgb-text-secondary-alt': '153 150 150', // #999696 (gray-400)
|
||||
'rgb-text-tertiary': '153 150 150', // #999696 (gray-400)
|
||||
'rgb-text-muted': '179 182 189', // #b3b6bd (Click UI text.muted)
|
||||
'rgb-text-warning': '245 158 11', // #f59e0b (amber-500)
|
||||
'rgb-text-destructive': '248 113 113', // #f87171 (red-400)
|
||||
'rgb-shimmer-base': '255 255 255', // #ffffff, carried at 0.8 alpha
|
||||
|
|
@ -37,6 +38,8 @@ export const darkTheme: IThemeRGB = {
|
|||
'rgb-surface-hover-alt': '66 66 66', // #424242 (gray-600)
|
||||
'rgb-surface-composer-hover': '66 66 66', // #424242 (gray-600)
|
||||
'rgb-surface-primary': '13 13 13', // #0d0d0d (gray-900)
|
||||
'rgb-chart-widget-surface': '40 40 40', // #282828 (Click UI chart widget)
|
||||
'rgb-chart-widget-stroke': '50 50 50', // #323232 (Click UI chart widget)
|
||||
'rgb-surface-primary-alt': '23 23 23', // #171717 (gray-850)
|
||||
'rgb-surface-primary-contrast': '23 23 23', // #171717 (gray-850)
|
||||
'rgb-surface-secondary': '33 33 33', // #212121 (gray-800)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ export const defaultTheme: IThemeRGB = {
|
|||
'rgb-text-secondary': '66 66 66', // #424242 (gray-600)
|
||||
'rgb-text-secondary-alt': '89 89 89', // #595959 (gray-500)
|
||||
'rgb-text-tertiary': '89 89 89', // #595959 (gray-500)
|
||||
'rgb-text-muted': '105 110 121', // #696e79 (Click UI text.muted)
|
||||
'rgb-text-warning': '180 83 9', // #b45309 (amber-700)
|
||||
'rgb-text-destructive': '220 38 38', // #dc2626 (red-600)
|
||||
'rgb-shimmer-base': '33 33 33', // #212121 (gray-800), matching text-primary
|
||||
|
|
@ -37,6 +38,8 @@ export const defaultTheme: IThemeRGB = {
|
|||
'rgb-surface-hover-alt': '205 205 205', // #cdcdcd (gray-300)
|
||||
'rgb-surface-composer-hover': '227 227 227', // #e3e3e3 (gray-200)
|
||||
'rgb-surface-primary': '255 255 255', // #fff (white)
|
||||
'rgb-chart-widget-surface': '255 255 255', // #fff (Click UI chart widget)
|
||||
'rgb-chart-widget-stroke': '230 231 233', // #e6e7e9 (Click UI chart widget)
|
||||
'rgb-surface-primary-alt': '247 247 248', // #f7f7f8 (gray-50)
|
||||
'rgb-surface-primary-contrast': '236 236 236', // #ececec (gray-100)
|
||||
'rgb-surface-secondary': '247 247 248', // #f7f7f8 (gray-50)
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ export interface IThemeRGB {
|
|||
'rgb-text-secondary'?: string;
|
||||
'rgb-text-secondary-alt'?: string;
|
||||
'rgb-text-tertiary'?: string;
|
||||
'rgb-text-muted'?: string;
|
||||
'rgb-text-warning'?: string;
|
||||
'rgb-text-destructive'?: string;
|
||||
/** Bright and dipped stops of the in-flight label sweep (`.shimmer`). Their
|
||||
|
|
@ -38,6 +39,8 @@ export interface IThemeRGB {
|
|||
'rgb-surface-hover-alt'?: string;
|
||||
'rgb-surface-composer-hover'?: string;
|
||||
'rgb-surface-primary'?: string;
|
||||
'rgb-chart-widget-surface'?: string;
|
||||
'rgb-chart-widget-stroke'?: string;
|
||||
'rgb-surface-primary-alt'?: string;
|
||||
'rgb-surface-primary-contrast'?: string;
|
||||
'rgb-surface-secondary'?: string;
|
||||
|
|
@ -116,6 +119,7 @@ export interface IThemeVariables {
|
|||
'--text-secondary': string;
|
||||
'--text-secondary-alt': string;
|
||||
'--text-tertiary': string;
|
||||
'--text-muted': string;
|
||||
'--text-warning': string;
|
||||
'--text-destructive': string;
|
||||
'--shimmer-base': string;
|
||||
|
|
@ -135,6 +139,8 @@ export interface IThemeVariables {
|
|||
'--surface-hover-alt': string;
|
||||
'--surface-composer-hover': string;
|
||||
'--surface-primary': string;
|
||||
'--chart-widget-surface': string;
|
||||
'--chart-widget-stroke': string;
|
||||
'--surface-primary-alt': string;
|
||||
'--surface-primary-contrast': string;
|
||||
'--surface-secondary': string;
|
||||
|
|
@ -205,6 +211,7 @@ export interface IThemeColors {
|
|||
'text-secondary'?: string;
|
||||
'text-secondary-alt'?: string;
|
||||
'text-tertiary'?: string;
|
||||
'text-muted'?: string;
|
||||
'text-warning'?: string;
|
||||
'text-destructive'?: string;
|
||||
link?: string;
|
||||
|
|
@ -222,6 +229,8 @@ export interface IThemeColors {
|
|||
'surface-hover-alt'?: string;
|
||||
'surface-composer-hover'?: string;
|
||||
'surface-primary'?: string;
|
||||
'chart-widget-surface'?: string;
|
||||
'chart-widget-stroke'?: string;
|
||||
'surface-primary-alt'?: string;
|
||||
'surface-primary-contrast'?: string;
|
||||
'surface-secondary'?: string;
|
||||
|
|
|
|||
|
|
@ -14,6 +14,9 @@ const semanticProperties = [
|
|||
'--accent-primary',
|
||||
'--accent-primary-hover',
|
||||
'--text-destructive',
|
||||
'--text-muted',
|
||||
'--chart-widget-surface',
|
||||
'--chart-widget-stroke',
|
||||
'--border-destructive',
|
||||
'--status-success',
|
||||
'--status-success-subtle',
|
||||
|
|
@ -185,6 +188,48 @@ describe('applyTheme', () => {
|
|||
expect(root.style.getPropertyValue('--shimmer-base')).toBe('90 80 70');
|
||||
});
|
||||
|
||||
it('carries a legacy theme without muted text onto its tertiary text color', () => {
|
||||
const root = document.documentElement;
|
||||
|
||||
applyTheme({ 'rgb-text-tertiary': '80 81 82' }, root);
|
||||
|
||||
expect(root.style.getPropertyValue('--text-muted')).toBe('80 81 82');
|
||||
});
|
||||
|
||||
it('leaves a legacy theme that names muted text alone', () => {
|
||||
const root = document.documentElement;
|
||||
|
||||
applyTheme({ 'rgb-text-tertiary': '80 81 82', 'rgb-text-muted': '100 101 102' }, root);
|
||||
|
||||
expect(root.style.getPropertyValue('--text-muted')).toBe('100 101 102');
|
||||
});
|
||||
|
||||
it('carries legacy panel colors onto chart widgets', () => {
|
||||
const root = document.documentElement;
|
||||
|
||||
applyTheme({ 'rgb-surface-primary': '20 21 22', 'rgb-border-light': '30 31 32' }, root);
|
||||
|
||||
expect(root.style.getPropertyValue('--chart-widget-surface')).toBe('20 21 22');
|
||||
expect(root.style.getPropertyValue('--chart-widget-stroke')).toBe('30 31 32');
|
||||
});
|
||||
|
||||
it('leaves explicit chart widget colors alone', () => {
|
||||
const root = document.documentElement;
|
||||
|
||||
applyTheme(
|
||||
{
|
||||
'rgb-surface-primary': '20 21 22',
|
||||
'rgb-border-light': '30 31 32',
|
||||
'rgb-chart-widget-surface': '40 41 42',
|
||||
'rgb-chart-widget-stroke': '50 51 52',
|
||||
},
|
||||
root,
|
||||
);
|
||||
|
||||
expect(root.style.getPropertyValue('--chart-widget-surface')).toBe('40 41 42');
|
||||
expect(root.style.getPropertyValue('--chart-widget-stroke')).toBe('50 51 52');
|
||||
});
|
||||
|
||||
it('clears only properties owned by the theme module', () => {
|
||||
const root = document.documentElement;
|
||||
root.style.setProperty('--text-primary', '1 2 3');
|
||||
|
|
|
|||
|
|
@ -47,6 +47,21 @@ function mapColors(colors: IThemeRGB): Array<[string, string]> {
|
|||
variables.push(['--shimmer-base', colors['rgb-text-primary']]);
|
||||
}
|
||||
|
||||
if (colors['rgb-text-muted'] === undefined && colors['rgb-text-tertiary'] !== undefined) {
|
||||
variables.push(['--text-muted', colors['rgb-text-tertiary']]);
|
||||
}
|
||||
|
||||
if (
|
||||
colors['rgb-chart-widget-surface'] === undefined &&
|
||||
colors['rgb-surface-primary'] !== undefined
|
||||
) {
|
||||
variables.push(['--chart-widget-surface', colors['rgb-surface-primary']]);
|
||||
}
|
||||
|
||||
if (colors['rgb-chart-widget-stroke'] === undefined && colors['rgb-border-light'] !== undefined) {
|
||||
variables.push(['--chart-widget-stroke', colors['rgb-border-light']]);
|
||||
}
|
||||
|
||||
return variables;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -70,6 +70,8 @@ function createTailwindColors() {
|
|||
'text-secondary-alt': cssVar('--text-secondary-alt'),
|
||||
'text-tertiary': cssVar('--text-tertiary'),
|
||||
'text-muted': cssVar('--text-muted'),
|
||||
'chart-widget-surface': cssVar('--chart-widget-surface'),
|
||||
'chart-widget-stroke': cssVar('--chart-widget-stroke'),
|
||||
'text-warning': cssVar('--text-warning'),
|
||||
'text-destructive': cssVar('--text-destructive'),
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue