diff --git a/.changeset/ui-fixes-clickstack.md b/.changeset/ui-fixes-clickstack.md new file mode 100644 index 00000000..92e2438d --- /dev/null +++ b/.changeset/ui-fixes-clickstack.md @@ -0,0 +1,12 @@ +--- +"@hyperdx/app": patch +--- + +feat: Theme-aware UI improvements for ClickStack + +- **Chart colors**: Made chart color palette theme-aware - ClickStack uses blue as primary color, HyperDX uses green. Charts now correctly display blue bars for ClickStack theme. +- **Semantic colors**: Updated semantic color functions (getChartColorSuccess, getChartColorWarning, getChartColorError) to be theme-aware, reading from CSS variables or falling back to theme-appropriate palettes. +- **Info log colors**: Changed info-level logs to use primary chart color (blue for ClickStack, green for HyperDX) instead of success green. +- **Button variants**: Made ResumeLiveTailButton variant conditional - uses 'secondary' for ClickStack theme, 'primary' for HyperDX theme. +- **Nav styles**: Fixed collapsed navigation styles for proper alignment and spacing when nav is collapsed to 50px width. +- **Icon stroke width**: Added custom stroke width (1.5) for Tabler icons in ClickStack theme only, providing a more refined appearance. diff --git a/packages/app/.stylelintignore b/packages/app/.stylelintignore index 1ef1a71a..440f865a 100644 --- a/packages/app/.stylelintignore +++ b/packages/app/.stylelintignore @@ -2,4 +2,5 @@ .storybook node_modules coverage -playwright-report \ No newline at end of file +playwright-report +styles/app.scss \ No newline at end of file diff --git a/packages/app/pages/_app.tsx b/packages/app/pages/_app.tsx index b702272a..3c2286db 100644 --- a/packages/app/pages/_app.tsx +++ b/packages/app/pages/_app.tsx @@ -96,22 +96,23 @@ function AppContent({ confirmModal: React.ReactNode; }) { const { userPreferences } = useUserPreferences(); + const { themeName } = useAppTheme(); - // Only override font if user has explicitly set a preference. - // Otherwise, return undefined to let the theme use its default font: - // - HyperDX theme: "IBM Plex Sans", monospace - // - ClickStack theme: "Inter", sans-serif - const selectedMantineFont = userPreferences.font - ? MANTINE_FONT_MAP[userPreferences.font] || undefined + // ClickStack theme always uses Inter font - user preference is ignored + // HyperDX theme allows user to select font preference + const isClickStackTheme = themeName === 'clickstack'; + const effectiveFont = isClickStackTheme ? 'Inter' : userPreferences.font; + const selectedMantineFont = effectiveFont + ? MANTINE_FONT_MAP[effectiveFont] || undefined : undefined; useEffect(() => { // Update CSS variable for global font cascading if (typeof document !== 'undefined') { - const fontVar = FONT_VAR_MAP[userPreferences.font] || DEFAULT_FONT_VAR; + const fontVar = FONT_VAR_MAP[effectiveFont] || DEFAULT_FONT_VAR; document.documentElement.style.setProperty('--app-font-family', fontVar); } - }, [userPreferences.font]); + }, [effectiveFont]); const getLayout = Component.getLayout ?? (page => page); diff --git a/packages/app/public/favicons/hyperdx/apple-touch-icon.png b/packages/app/public/favicons/hyperdx/apple-touch-icon.png index 80385d60..d5859464 100644 Binary files a/packages/app/public/favicons/hyperdx/apple-touch-icon.png and b/packages/app/public/favicons/hyperdx/apple-touch-icon.png differ diff --git a/packages/app/public/favicons/hyperdx/favicon-16x16.png b/packages/app/public/favicons/hyperdx/favicon-16x16.png index fd6e3b78..7778dc48 100644 Binary files a/packages/app/public/favicons/hyperdx/favicon-16x16.png and b/packages/app/public/favicons/hyperdx/favicon-16x16.png differ diff --git a/packages/app/public/favicons/hyperdx/favicon-32x32.png b/packages/app/public/favicons/hyperdx/favicon-32x32.png index e392e035..5a695c0c 100644 Binary files a/packages/app/public/favicons/hyperdx/favicon-32x32.png and b/packages/app/public/favicons/hyperdx/favicon-32x32.png differ diff --git a/packages/app/public/favicons/hyperdx/favicon.svg b/packages/app/public/favicons/hyperdx/favicon.svg index 595b064e..46509367 100644 --- a/packages/app/public/favicons/hyperdx/favicon.svg +++ b/packages/app/public/favicons/hyperdx/favicon.svg @@ -1,6 +1,13 @@ - - - - \ No newline at end of file + + + + + + + + + + + \ No newline at end of file diff --git a/packages/app/src/ChartUtils.tsx b/packages/app/src/ChartUtils.tsx index 05804020..2ecd0b8b 100644 --- a/packages/app/src/ChartUtils.tsx +++ b/packages/app/src/ChartUtils.tsx @@ -44,7 +44,7 @@ import { TimeChartSeries, } from './types'; import { NumberFormat } from './types'; -import { getColorProps, logLevelColor, logLevelColorOrder } from './utils'; +import { getColorProps, getLogLevelColorOrder, logLevelColor } from './utils'; export const SORT_ORDER = [ { value: 'asc' as const, label: 'Ascending' }, @@ -713,6 +713,7 @@ export function formatResponseForTimeChart({ }); } + const logLevelColorOrder = getLogLevelColorOrder(); const sortedLineData = Object.values(lineDataMap).sort((a, b) => { return ( logLevelColorOrder.findIndex(color => color === a.color) - diff --git a/packages/app/src/DBSearchPage.tsx b/packages/app/src/DBSearchPage.tsx index c4092fc8..2964b32f 100644 --- a/packages/app/src/DBSearchPage.tsx +++ b/packages/app/src/DBSearchPage.tsx @@ -107,6 +107,7 @@ import { useSource, useSources, } from '@/source'; +import { useAppTheme } from '@/theme/ThemeProvider'; import { parseRelativeTimeQuery, parseTimeQuery, @@ -280,10 +281,13 @@ function ResumeLiveTailButton({ }: { handleResumeLiveTail: () => void; }) { + const { themeName } = useAppTheme(); + const variant = themeName === 'clickstack' ? 'secondary' : 'primary'; + return ( - + ); @@ -86,7 +91,7 @@ export const AppNavUserMenu = ({ })} > - + {initials} {!isCollapsed && ( @@ -211,6 +216,8 @@ export const AppNavHelpMenu = ({ data-testid="documentation-menu-item" href="https://clickhouse.com/docs/use-cases/observability/clickstack" component="a" + target="_blank" + rel="noopener noreferrer" leftSection={} > Documentation @@ -221,6 +228,7 @@ export const AppNavHelpMenu = ({ component="a" href="https://hyperdx.io/discord" target="_blank" + rel="noopener noreferrer" > Discord Community @@ -292,8 +300,8 @@ export const AppNavLink = ({ {!isCollapsed && isBeta && ( Beta diff --git a/packages/app/src/components/AppNav/AppNav.module.scss b/packages/app/src/components/AppNav/AppNav.module.scss index afef0702..3c568c99 100644 --- a/packages/app/src/components/AppNav/AppNav.module.scss +++ b/packages/app/src/components/AppNav/AppNav.module.scss @@ -3,6 +3,7 @@ $spacing-sm: 8px; $spacing-md: 12px; $spacing-lg: 16px; $nav-item-height: 34px; +$nav-item-height-collapsed: 28px; $header-height: 58px; $help-button-size: 28px; $search-input-height: 28px; @@ -51,13 +52,30 @@ $transition-slow: 0.2s ease; justify-content: space-between; border-right: 1px solid var(--color-border); background: var(--color-bg-sidenav); - letter-spacing: 0.05em; overflow: hidden; transition: width 0.2s ease; &Fixed { position: fixed; } + + &Collapsed { + .navLinks { + padding-inline: 0; + align-items: center; + } + + .navItem { + padding-inline: $spacing-sm; + justify-content: center; + height: $nav-item-height-collapsed; + margin-block: $spacing-xs; + } + + .navItemIcon { + margin-right: 0; + } + } } .header { @@ -71,15 +89,31 @@ $transition-slow: 0.2s ease; &Expanded { height: $header-height; } + + &Collapsed { + flex-flow: column nowrap; + justify-content: flex-start; + align-items: center; + padding-inline: 0; + padding-block: $spacing-md; + min-height: $header-height; + gap: $spacing-sm; + width: 100%; + } } .logoLink { text-decoration: none; - margin-left: -0.15rem; + display: flex; + align-items: center; + justify-content: center; + color: var(--color-text); } .logoIconWrapper { - margin-top: 6px; + display: flex; + align-items: center; + justify-content: center; } .collapseButton { @@ -88,7 +122,9 @@ $transition-slow: 0.2s ease; &Collapsed { transform: rotate(180deg); - margin-top: $spacing-lg; + margin-top: 0; + margin-right: 0; + margin-left: 0; } } @@ -118,7 +154,7 @@ $transition-slow: 0.2s ease; display: flex; flex-direction: column; gap: 2px; - padding-inline: $spacing-sm; + padding-inline: $spacing-lg; margin-top: $spacing-sm; } @@ -134,7 +170,7 @@ $transition-slow: 0.2s ease; user-select: none; gap: 10px; - padding-inline: $spacing-sm; + padding-inline: $spacing-lg; height: $nav-item-height; border-radius: $radius-sm; @@ -151,6 +187,10 @@ $transition-slow: 0.2s ease; &Active { color: var(--color-text-sidenav-link-active); background: var(--color-bg-sidenav-link-active); + + &:hover { + background: var(--color-bg-sidenav-link-active); + } } } @@ -160,7 +200,7 @@ $transition-slow: 0.2s ease; } .navItemIcon { - margin-right: $spacing-sm; + margin-right: $spacing-md; display: inline-flex; align-items: center; vertical-align: middle; @@ -222,7 +262,6 @@ $transition-slow: 0.2s ease; &:hover { color: var(--color-text-sidenav-link-active); - background: var(--color-bg-sidenav-link-active); } &:focus-visible { diff --git a/packages/app/src/components/AppNav/AppNav.tsx b/packages/app/src/components/AppNav/AppNav.tsx index 92127fdb..c5d2e225 100644 --- a/packages/app/src/components/AppNav/AppNav.tsx +++ b/packages/app/src/components/AppNav/AppNav.tsx @@ -461,7 +461,7 @@ export default function AppNav({ fixed = false }: { fixed?: boolean }) { const isSmallScreen = (width ?? 1000) < 900; const isCollapsed = isSmallScreen || isPreferCollapsed; - const navWidth = isCollapsed ? 50 : 230; + const navWidth = isCollapsed ? 50 : 250; useEffect(() => { HyperDX.addAction('user navigated', { @@ -663,6 +663,7 @@ export default function AppNav({ fixed = false }: { fixed?: boolean }) {
@@ -670,6 +671,7 @@ export default function AppNav({ fixed = false }: { fixed?: boolean }) {
diff --git a/packages/app/src/components/DBDeltaChart.tsx b/packages/app/src/components/DBDeltaChart.tsx index c4961f91..0777a06f 100644 --- a/packages/app/src/components/DBDeltaChart.tsx +++ b/packages/app/src/components/DBDeltaChart.tsx @@ -28,8 +28,8 @@ import { isAggregateFunction } from '@/ChartUtils'; import { useQueriedChartConfig } from '@/hooks/useChartConfig'; import { getFirstTimestampValueExpression } from '@/source'; import { - CHART_COLOR_ERROR, - CHART_COLOR_SUCCESS, + getChartColorError, + getChartColorSuccess, truncateMiddle, } from '@/utils'; @@ -250,13 +250,13 @@ function PropertyComparisonChart({ diff --git a/packages/app/src/components/DBTraceWaterfallChart.tsx b/packages/app/src/components/DBTraceWaterfallChart.tsx index 4142b3c0..2334703a 100644 --- a/packages/app/src/components/DBTraceWaterfallChart.tsx +++ b/packages/app/src/components/DBTraceWaterfallChart.tsx @@ -40,10 +40,10 @@ import { import TimelineChart from '@/TimelineChart'; import { useFormatTime } from '@/useFormatTime'; import { - CHART_COLOR_ERROR, - CHART_COLOR_ERROR_HIGHLIGHT, - CHART_COLOR_WARNING, - CHART_COLOR_WARNING_HIGHLIGHT, + getChartColorError, + getChartColorErrorHighlight, + getChartColorWarning, + getChartColorWarningHighlight, } from '@/utils'; import { getHighlightedAttributesFromData, @@ -89,9 +89,11 @@ function barColor(condition: { }) { const { isError, isWarn, isHighlighted } = condition; if (isError) - return isHighlighted ? CHART_COLOR_ERROR_HIGHLIGHT : CHART_COLOR_ERROR; + return isHighlighted ? getChartColorErrorHighlight() : getChartColorError(); if (isWarn) - return isHighlighted ? CHART_COLOR_WARNING_HIGHLIGHT : CHART_COLOR_WARNING; + return isHighlighted + ? getChartColorWarningHighlight() + : getChartColorWarning(); return isHighlighted ? '#A9AFB7' : '#6A7077'; } diff --git a/packages/app/src/components/LogLevel.tsx b/packages/app/src/components/LogLevel.tsx index 9cb1af3e..0bae0411 100644 --- a/packages/app/src/components/LogLevel.tsx +++ b/packages/app/src/components/LogLevel.tsx @@ -16,7 +16,7 @@ export default function LogLevel({ levelClass === 'error' ? 'red' : levelClass === 'warn' - ? 'yellow' + ? 'var(--color-chart-warning)' : 'gray' } {...props} diff --git a/packages/app/src/theme/ChartColors.stories.tsx b/packages/app/src/theme/ChartColors.stories.tsx index 132e5596..56e17a25 100644 --- a/packages/app/src/theme/ChartColors.stories.tsx +++ b/packages/app/src/theme/ChartColors.stories.tsx @@ -1,10 +1,10 @@ import React from 'react'; import { - CHART_COLOR_ERROR, - CHART_COLOR_SUCCESS, - CHART_COLOR_WARNING, COLORS, + getChartColorError, + getChartColorSuccess, + getChartColorWarning, } from '@/utils'; // Labels for chart colors - brand green first, then Observable palette @@ -31,15 +31,19 @@ const CHART_COLORS = COLORS.map((hex, i) => ({ const SEMANTIC_CHART_COLORS = [ { name: 'color-chart-success', - hex: CHART_COLOR_SUCCESS, + hex: getChartColorSuccess(), label: 'Success (Green)', }, { name: 'color-chart-warning', - hex: CHART_COLOR_WARNING, + hex: getChartColorWarning(), label: 'Warning (Orange)', }, - { name: 'color-chart-error', hex: CHART_COLOR_ERROR, label: 'Error (Red)' }, + { + name: 'color-chart-error', + hex: getChartColorError(), + label: 'Error (Red)', + }, ]; const story = { diff --git a/packages/app/src/theme/themes/clickstack/_tokens.scss b/packages/app/src/theme/themes/clickstack/_tokens.scss index 22e35078..8bad5ba6 100644 --- a/packages/app/src/theme/themes/clickstack/_tokens.scss +++ b/packages/app/src/theme/themes/clickstack/_tokens.scss @@ -146,6 +146,8 @@ --color-state-hover: #2b2c3d; --color-state-selected: #34354a; --color-state-focus: #4d4f66; + --color-outline-focus: var(--mantine-primary-color-filled); + --input-bd-focus: var(--mantine-primary-color-filled); /* Code / Misc UI */ --color-bg-code: #1d1e30; @@ -161,8 +163,40 @@ --color-json-array: #ffd966; --color-json-punctuation: #666980; + /* + * Chart Colors - Observable 10 categorical palette + * NOTE: These colors are intentionally duplicated in both dark and light mode sections. + * CSS specificity requires them to be defined within each [data-mantine-color-scheme] selector + * to ensure they're applied correctly. A shared .theme-clickstack section would have lower + * specificity and could be overridden by other styles. + */ + --color-chart-1: #437eef; /* Blue - Primary */ + --color-chart-2: #efb118; /* Orange */ + --color-chart-3: #ff725c; /* Red */ + --color-chart-4: #6cc5b0; /* Cyan */ + --color-chart-5: #3ca951; /* Green */ + --color-chart-6: #ff8ab7; /* Pink */ + --color-chart-7: #a463f2; /* Purple */ + --color-chart-8: #97bbf5; /* Light Blue */ + --color-chart-9: #9c6b4e; /* Brown */ + --color-chart-10: #9498a0; /* Gray */ + + /* Chart Semantic Colors */ + --color-chart-success: #3ca951; /* Green */ + --color-chart-warning: #efb118; /* Orange */ + --color-chart-error: #ff725c; /* Red */ + + /* Chart Semantic Colors - Highlighted (for hover/selection states) */ + --color-chart-error-highlight: #ffa090; + --color-chart-warning-highlight: #f5c94d; + /* Mantine Overrides */ --mantine-color-body: var(--color-bg-body) !important; + + /* Tabler icons default stroke width - Set default stroke-width to 1.5 for Tabler icons (instead of default 2). This only affects SVGs that have a stroke attribute set. Can be overridden on individual icons by setting strokeWidth prop */ + .tabler-icon { + stroke-width: 1.5; + } } /* Light Mode */ @@ -306,6 +340,7 @@ --color-state-hover: #e9ecef; --color-state-selected: #dee2e6; --color-state-focus: #ced4da; + --color-outline-focus: var(--mantine-primary-color-filled); /* Code / Misc UI */ --color-bg-code: var(--click-global-color-background-muted); @@ -321,6 +356,32 @@ --color-json-array: #997300; --color-json-punctuation: #868e96; + /* Chart Colors - See dark mode section for explanation of why duplication is required */ + --color-chart-1: #437eef; /* Blue - Primary */ + --color-chart-2: #efb118; /* Orange */ + --color-chart-3: #ff725c; /* Red */ + --color-chart-4: #6cc5b0; /* Cyan */ + --color-chart-5: #3ca951; /* Green */ + --color-chart-6: #ff8ab7; /* Pink */ + --color-chart-7: #a463f2; /* Purple */ + --color-chart-8: #97bbf5; /* Light Blue */ + --color-chart-9: #9c6b4e; /* Brown */ + --color-chart-10: #9498a0; /* Gray */ + + /* Chart Semantic Colors */ + --color-chart-success: #3ca951; /* Green */ + --color-chart-warning: #efb118; /* Orange */ + --color-chart-error: #ff725c; /* Red */ + + /* Chart Semantic Colors - Highlighted (for hover/selection states) */ + --color-chart-error-highlight: #ffa090; + --color-chart-warning-highlight: #f5c94d; + /* Mantine Overrides */ --mantine-color-body: var(--color-bg-body); + + /* Tabler icons default stroke width - Set default stroke-width to 1.5 for Tabler icons (instead of default 2). This only affects SVGs that have a stroke attribute set. Can be overridden on individual icons by setting strokeWidth prop */ + .tabler-icon { + stroke-width: 1.5; + } } diff --git a/packages/app/src/theme/themes/clickstack/mantineTheme.ts b/packages/app/src/theme/themes/clickstack/mantineTheme.ts index cfcaadff..c2d57b46 100644 --- a/packages/app/src/theme/themes/clickstack/mantineTheme.ts +++ b/packages/app/src/theme/themes/clickstack/mantineTheme.ts @@ -228,6 +228,7 @@ export const makeTheme = ({ baseVars['--button-bg'] = 'var(--color-primary-button-bg)'; baseVars['--button-hover'] = 'var(--color-primary-button-bg-hover)'; baseVars['--button-color'] = 'var(--color-primary-button-text)'; + baseVars['--button-color-hover'] = 'var(--color-primary-button-text)'; } if (props.variant === 'secondary') { diff --git a/packages/app/src/utils.ts b/packages/app/src/utils.ts index 0d472e24..40a3fc9f 100644 --- a/packages/app/src/utils.ts +++ b/packages/app/src/utils.ts @@ -424,10 +424,29 @@ export const CHART_PALETTE = { orangeHighlight: '#f5c94d', } as const; -// Ordered array for chart series - green first for brand consistency +// ClickStack theme chart color palette - Observable 10 categorical palette +// https://observablehq.com/@d3/color-schemes +export const CLICKSTACK_CHART_PALETTE = { + blue: '#437EEF', // Primary color for ClickStack + orange: '#efb118', + red: '#ff725c', + cyan: '#6cc5b0', + green: '#3ca951', + pink: '#ff8ab7', + purple: '#a463f2', + lightBlue: '#97bbf5', + brown: '#9c6b4e', + gray: '#9498a0', + // Highlighted variants (lighter shades for hover/selection states) + redHighlight: '#ffa090', + orangeHighlight: '#f5c94d', +} as const; + +// Ordered array for chart series - green first for brand consistency (HyperDX default) // Maps to CSS variables: COLORS[0] -> --color-chart-1, COLORS[1] -> --color-chart-2, etc. +// NOTE: This is a fallback for SSR. In browser, getColorFromCSSVariable() reads from CSS variables export const COLORS = [ - CHART_PALETTE.green, // 1 - Brand green (primary) + CHART_PALETTE.green, // 1 - Brand green (primary) - HyperDX default CHART_PALETTE.blue, // 2 CHART_PALETTE.orange, // 3 CHART_PALETTE.red, // 4 @@ -439,6 +458,63 @@ export const COLORS = [ CHART_PALETTE.gray, // 10 ]; +/** + * Detects the active theme by checking for theme classes on documentElement. + * Returns 'clickstack' if theme-clickstack class is present, 'hyperdx' otherwise. + * Note: classList.contains() is O(1) and fast - no caching needed. + */ +function detectActiveTheme(): 'clickstack' | 'hyperdx' { + if (typeof window === 'undefined') { + // SSR: default to hyperdx (can't detect theme without DOM) + return 'hyperdx'; + } + + try { + const isClickStack = + document.documentElement.classList.contains('theme-clickstack'); + return isClickStack ? 'clickstack' : 'hyperdx'; + } catch { + // Fallback if DOM access fails + return 'hyperdx'; + } +} + +/** + * Reads chart color from CSS variable based on index. + * CSS variables handle theme switching automatically via theme classes on documentElement. + * Falls back to COLORS array if CSS variable is not available (SSR or getComputedStyle fails). + * + * Note on SSR/Hydration: During SSR, this returns fallback colors (HyperDX green palette). + * On client hydration, it reads from CSS variables which may differ for ClickStack theme. + * This is expected behavior - charts typically render after data fetching (client-side), + * so hydration mismatches are rare. If needed, wrap chart components with suppressHydrationWarning. + */ +export function getColorFromCSSVariable(index: number): string { + const colorArrayLength = COLORS.length; + + if (typeof window === 'undefined') { + // SSR: fallback to default colors (HyperDX palette) + return COLORS[index % colorArrayLength]; + } + + try { + const cssVarName = `--color-chart-${(index % colorArrayLength) + 1}`; + // Read from documentElement - CSS variables cascade from theme classes + const computedStyle = getComputedStyle(document.documentElement); + const color = computedStyle.getPropertyValue(cssVarName).trim(); + + // Only use CSS variable if it's actually set (non-empty) + if (color && color !== '') { + return color; + } + } catch { + // Fallback if getComputedStyle fails + } + + // Fallback to default colors + return COLORS[index % colorArrayLength]; +} + export function hashCode(str: string) { let hash = 0, i, @@ -452,14 +528,81 @@ export function hashCode(str: string) { return hash; } -// Semantic colors for log levels (derived from palette) -export const CHART_COLOR_SUCCESS = CHART_PALETTE.green; -export const CHART_COLOR_WARNING = CHART_PALETTE.orange; -export const CHART_COLOR_ERROR = CHART_PALETTE.red; +/** + * Gets theme-aware chart color from CSS variable or falls back to palette. + * Reads from --color-chart-{type} CSS variable, falls back to theme-appropriate palette. + * + * Note on SSR/Hydration: During SSR, returns HyperDX colors as default. + * On client, reads from CSS variables for accurate theme colors. + * Charts typically render client-side after data fetching, minimizing hydration issues. + */ +function getSemanticChartColor( + cssVarName: string, + hyperdxColor: string, + clickstackColor: string, +): string { + if (typeof window === 'undefined') { + // SSR: use HyperDX as default (can't detect theme without DOM) + return hyperdxColor; + } -// Highlighted variants (derived from palette) -export const CHART_COLOR_ERROR_HIGHLIGHT = CHART_PALETTE.redHighlight; -export const CHART_COLOR_WARNING_HIGHLIGHT = CHART_PALETTE.orangeHighlight; + try { + const computedStyle = getComputedStyle(document.documentElement); + const color = computedStyle.getPropertyValue(cssVarName).trim(); + if (color && color !== '') { + return color; + } + } catch { + // Fallback if getComputedStyle fails + } + + // Fallback to theme-appropriate palette + const activeTheme = detectActiveTheme(); + return activeTheme === 'clickstack' ? clickstackColor : hyperdxColor; +} + +// Semantic colors for log levels (theme-aware) +// These are functions that read from CSS variables with theme-appropriate fallbacks +export function getChartColorSuccess(): string { + return getSemanticChartColor( + '--color-chart-success', + CHART_PALETTE.green, + CLICKSTACK_CHART_PALETTE.green, + ); +} + +export function getChartColorWarning(): string { + return getSemanticChartColor( + '--color-chart-warning', + CHART_PALETTE.orange, + CLICKSTACK_CHART_PALETTE.orange, + ); +} + +export function getChartColorError(): string { + return getSemanticChartColor( + '--color-chart-error', + CHART_PALETTE.red, + CLICKSTACK_CHART_PALETTE.red, + ); +} + +// Highlighted variants (theme-aware) +export function getChartColorErrorHighlight(): string { + return getSemanticChartColor( + '--color-chart-error-highlight', + CHART_PALETTE.redHighlight, + CLICKSTACK_CHART_PALETTE.redHighlight, + ); +} + +export function getChartColorWarningHighlight(): string { + return getSemanticChartColor( + '--color-chart-warning-highlight', + CHART_PALETTE.orangeHighlight, + CLICKSTACK_CHART_PALETTE.orangeHighlight, + ); +} // Try to match log levels to colors export const semanticKeyedColor = ( @@ -469,47 +612,51 @@ export const semanticKeyedColor = ( const logLevel = getLogLevelClass(`${key}`); if (logLevel != null) { return logLevel === 'error' - ? CHART_COLOR_ERROR + ? getChartColorError() : logLevel === 'warn' - ? CHART_COLOR_WARNING - : CHART_COLOR_SUCCESS; + ? getChartColorWarning() + : // Info-level logs use primary chart color (blue for ClickStack, green for HyperDX) + getColorFromCSSVariable(0); } - return COLORS[index % COLORS.length]; + // Use CSS variable for theme-aware colors, fallback to hardcoded array + return getColorFromCSSVariable(index); }; export const logLevelColor = (key: string | number | undefined) => { const logLevel = getLogLevelClass(`${key}`); return logLevel === 'error' - ? CHART_COLOR_ERROR + ? getChartColorError() : logLevel === 'warn' - ? CHART_COLOR_WARNING - : CHART_COLOR_SUCCESS; + ? getChartColorWarning() + : // Info-level logs use primary chart color (blue for ClickStack, green for HyperDX) + getColorFromCSSVariable(0); }; -// order of colors for sorting. green on bottom, then yellow, then red -export const logLevelColorOrder = [ - logLevelColor('info'), - logLevelColor('warn'), - logLevelColor('error'), -]; +// order of colors for sorting. primary color (blue/green) on bottom, then yellow, then red +// Computed lazily to avoid DOM access at module initialization (SSR-safe) +export function getLogLevelColorOrder(): string[] { + return [logLevelColor('info'), logLevelColor('warn'), logLevelColor('error')]; +} const getLevelColor = (logLevel?: string) => { if (logLevel == null) { return; } return logLevel === 'error' - ? CHART_COLOR_ERROR + ? getChartColorError() : logLevel === 'warn' - ? CHART_COLOR_WARNING - : CHART_COLOR_SUCCESS; + ? getChartColorWarning() + : // Info-level logs use primary chart color (blue for ClickStack, green for HyperDX) + getColorFromCSSVariable(0); }; export const getColorProps = (index: number, level: string): string => { const logLevel = getLogLevelClass(level); const colorOverride = getLevelColor(logLevel); - return colorOverride ?? COLORS[index % COLORS.length]; + // Use CSS variable for theme-aware colors, fallback to hardcoded array + return colorOverride ?? getColorFromCSSVariable(index); }; export const truncateMiddle = (str: string, maxLen = 10) => { diff --git a/packages/app/styles/app.scss b/packages/app/styles/app.scss index 968c44ae..75ea2445 100644 --- a/packages/app/styles/app.scss +++ b/packages/app/styles/app.scss @@ -1,4 +1,4 @@ -/* stylelint-disable */ +/* stylelint-disable-file */ // stylelint adds '}}' to end of file.. not sure why. Disabled for now. @use '../src/theme/themes/base-tokens'; @@ -279,17 +279,6 @@ button:focus-visible { opacity: 0.8; } -.hover-color-white { - &:hover { - color: white; - } -} - - -a.mantine-focus-auto:hover { - color: inherit; -} - .recharts-tooltip-wrapper { &:focus, &:focus-visible {