Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions docs/docs/reference/project-files/themes.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,19 @@ dark:
color-sequential-1: "hsl(180deg 40% 30%)"
color-sequential-5: "hsl(180deg 50% 50%)"
color-sequential-9: "hsl(180deg 60% 70%)"
```

```yaml
# Example: Theme with KPI delta colors (green for positive, red for negative)
type: theme
light:
primary: "#6366f1"
secondary: "#8b5cf6"
kpi-positive: "#16a34a" # Green for positive deltas (default: gray)
kpi-negative: "#dc2626" # Red for negative deltas (default: red)
dark:
primary: "#818cf8"
secondary: "#a78bfa"
kpi-positive: "#4ade80" # Brighter green for dark mode
kpi-negative: "#f87171" # Brighter red for dark mode
```
8 changes: 8 additions & 0 deletions runtime/ai/instructions/data/resources/theme.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ light:
surface: "#ffffff" # Elevated surfaces, panels
card: "#f1f5f9" # Card backgrounds

# KPI delta colors (optional - controls comparison/change value colors)
# kpi-positive: "#16a34a" # Green for positive deltas (defaults to gray)
# kpi-negative: "#dc2626" # Red for negative deltas (defaults to red)

# Qualitative palette for categorical data (optional, 24 colors)
# Used for bar charts, pie charts, legend colors by category
color-qualitative-1: "#6366f1" # Indigo
Expand Down Expand Up @@ -127,6 +131,10 @@ dark:
surface: "#1e293b" # Elevated surfaces
card: "#334155" # Card backgrounds

# KPI delta colors (optional)
# kpi-positive: "#4ade80" # Green for positive deltas (brighter for dark mode)
# kpi-negative: "#f87171" # Red for negative deltas (brighter for dark mode)

# Qualitative palette (adjusted for dark mode visibility)
color-qualitative-1: "#818cf8"
color-qualitative-2: "#a78bfa"
Expand Down
4 changes: 4 additions & 0 deletions runtime/parser/parse_theme.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ var allowedCSSVariables = map[string]bool{
"destructive": true,
"destructive-foreground": true,

// KPI delta coloring
"kpi-positive": true,
"kpi-negative": true,

// Popover
"popover": true,
"popover-accent": true,
Expand Down
18 changes: 18 additions & 0 deletions runtime/parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2695,6 +2695,24 @@ dark:
},
},
},
{
name: "kpi semantic variables are accepted",
yaml: `
type: theme
light:
kpi-positive: "#16a34a"
kpi-negative: "#dc2626"
`,
expectError: false,
expectedSpec: &runtimev1.ThemeSpec{
Light: &runtimev1.ThemeColors{
Variables: map[string]string{
"kpi-positive": "#16a34a",
"kpi-negative": "#dc2626",
},
},
},
},
}

for _, tt := range tests {
Expand Down
6 changes: 6 additions & 0 deletions web-common/src/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@
--destructive: var(--color-red-600);
--destructive-foreground: var(--color-red-50);

--kpi-positive: var(--fg-secondary);
--kpi-negative: var(--color-red-500);

--popover: var(--white);
--popover-accent: var(--surface-hover);
--popover-foreground: var(--color-neutral-950);
Expand Down Expand Up @@ -351,6 +354,9 @@
);
--destructive-foreground: var(--color-red-light-50);

--kpi-positive: var(--fg-secondary);
--kpi-negative: var(--color-red-500);

--popover: var(--color-rill-gray-dark-400);
--popover-accent: var(--surface-hover);
--popover-foreground: var(--color-neutral-light-50);
Expand Down
11 changes: 7 additions & 4 deletions web-common/src/components/data-types/MeasureChange.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,22 @@

let isNull = false;
let isValueNegative = false;
let isValuePositive = false;

$: if (isPercDiff(value)) {
isNull = true;
}

// Determine if the value is negative for coloring purposes
$: if (value !== null && value !== undefined) {
if (typeof value === "number") {
isValueNegative = value < 0;
isValuePositive = value > 0;
} else if (typeof value === "object" && "neg" in value) {
// For NumberParts, check if it has a negative sign
isValueNegative = value.neg === "-";
isValuePositive = !isValueNegative && value.int !== "0";
} else if (typeof value === "string") {
// For strings, check if it starts with a minus sign
isValueNegative = value.startsWith("-");
isValuePositive = !isValueNegative && value !== "0";
}
}
</script>
Expand All @@ -37,9 +38,11 @@
: ''}"
>
{#if isValueNegative}
<span class="text-red-500">
<span class="text-kpi-negative">
{value}
</span>
{:else if isValuePositive}
<span class="text-kpi-positive">{value}</span>
{:else}
<span class="text-fg-secondary">{value}</span>
{/if}
Expand Down
9 changes: 8 additions & 1 deletion web-common/src/components/data-types/PercentageChange.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
export let assembled = true;

let diffIsNegative = false;
let diffIsPositive = false;
let intValue: string;
let negSign = "";
let posSign = "";
Expand Down Expand Up @@ -48,6 +49,7 @@
intValue = Math.round(intPart + fracPart).toString();

diffIsNegative = value?.neg === "-";
diffIsPositive = !diffIsNegative && !value?.approxZero && +value.int !== 0;
negSign = diffIsNegative && !value?.approxZero ? "-" : "";
approxSign = value?.approxZero ? "~" : "";
posSign = !diffIsNegative && !approxSign && showPosSign ? "+" : "";
Expand All @@ -58,6 +60,7 @@
// but this whole thing is a mess and needs to be cleaned up.

diffIsNegative = value < 0;
diffIsPositive = value > 0;
intValue = Math.round(100 * value).toString();
approxSign = Math.abs(value) < 0.005 ? "~" : "";
posSign = !diffIsNegative && !approxSign && showPosSign ? "+" : "";
Expand All @@ -78,7 +81,11 @@
{#if isNoData}
<span class="text-fg-secondary">-</span>
{:else if value !== null && assembled}
<span class:text-destructive={diffIsNegative}>
<span
class="text-fg-secondary"
class:text-kpi-negative={diffIsNegative}
class:text-kpi-positive={diffIsPositive}
>
{approxSign}{negSign}{posSign}{intValue}{suffix}<span class="opacity-50"
>%</span
>
Expand Down
18 changes: 14 additions & 4 deletions web-common/src/features/canvas/components/kpi/KPI.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,15 @@
: null,
percent: comparisonPercChange,
} as const;
$: isDeltaPositive =
computedValues.delta !== null && computedValues.delta > 0;
$: isDeltaNegative =
computedValues.delta !== null && computedValues.delta < 0;
$: comparisonDeltaColorClass = isDeltaPositive
? "text-kpi-positive"
: isDeltaNegative
? "text-kpi-negative"
: "text-fg-secondary";

// Get value based on hover type
function getValueForType(type: typeof hoveredValue) {
Expand Down Expand Up @@ -243,7 +252,7 @@

{#if comparisonOptions?.includes("delta")}
<span
class="comparison-value"
class="comparison-value {comparisonDeltaColorClass}"
class:ui-copy-disabled-faint={computedValues.delta === null}
class:italic={computedValues.delta === null}
class:text-sm={computedValues.delta === null}
Expand All @@ -255,7 +264,9 @@
onblur={handleLeaveOrBlur}
>
{#if computedValues.delta != null}
{getFormattedDiff(computedValues.delta)}
<span class={comparisonDeltaColorClass}
>{getFormattedDiff(computedValues.delta)}</span
>
{:else}
no change
{/if}
Expand All @@ -264,8 +275,7 @@

{#if comparisonOptions?.includes("percent_change") && computedValues.percent != null && !measureIsPercentage}
<span
class="w-fit font-semibold text-fg-disabled"
class:text-red-500={computedValues.percent < 0}
class="w-fit font-semibold {comparisonDeltaColorClass}"
role="button"
tabindex="0"
onmouseover={() => handleHoverOrFocus("percent")}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,13 @@
? value - comparisonValue
: 0;
$: noChange = !comparisonValue;
$: isComparisonPositive = diff >= 0;
$: isComparisonPositive = diff > 0;
$: isComparisonNegative = diff < 0;
$: comparisonDeltaColorClass = isComparisonPositive
? "text-kpi-positive"
: isComparisonNegative
? "text-kpi-negative"
: "text-fg-secondary";

$: formattedDiff = `${isComparisonPositive ? "+" : ""}${measureValueFormatter(
diff,
Expand Down Expand Up @@ -239,7 +245,7 @@
{#if comparisonValue != null}
<div
role="complementary"
class="w-fit max-w-full overflow-hidden text-ellipsis text-fg-secondary"
class="w-fit max-w-full overflow-hidden text-ellipsis {comparisonDeltaColorClass}"
class:font-semibold={isComparisonPositive}
onmouseenter={() => {
tooltipValue =
Expand Down Expand Up @@ -282,8 +288,7 @@
copyValue =
measureValueFormatterUnabridged(value) ?? "no data";
}}
class="w-fit text-fg-secondary"
class:text-red-500={!isComparisonPositive}
class="w-fit {comparisonDeltaColorClass}"
>
<WithTween
value={comparisonPercChange}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,8 +290,10 @@
: null}
customStyle={deltaAbsMap[measureName] !== null &&
deltaAbsMap[measureName] < 0
? "text-red-500"
: ""}
? "text-kpi-negative"
: deltaAbsMap[measureName] !== null && deltaAbsMap[measureName] > 0
? "text-kpi-positive"
: ""}
truncate={true}
/>
</LeaderboardCell>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
{#if value !== null && value !== undefined}
<span
class="pointer-events-none {value > 0
? 'text-fg-secondary'
: 'text-destructive'}"
? 'text-kpi-positive'
: value < 0
? 'text-kpi-negative'
: 'text-fg-secondary'}"
>
{formattedValue}
</span>
Expand Down
12 changes: 11 additions & 1 deletion web-common/src/features/file-explorer/new-files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,12 @@ light:
# Note: The theme system also auto-generates fg-secondary, fg-tertiary, fg-muted, and fg-disabled from fg-primary using opacity percentages if not explicitly set
fg-primary: "#000000" # Black text

# KPI delta colors (optional - controls comparison/change value colors)
# kpi-positive defaults to gray (same as fg-secondary) — uncomment and set to customize
# kpi-negative defaults to red — uncomment and set to customize
# kpi-positive: "#16a34a" # Green for positive deltas
# kpi-negative: "#dc2626" # Red for negative deltas

# Qualitative palette (for categorical data - all 24 colors)
color-qualitative-1: "#6366f1" # Indigo
color-qualitative-2: "#8b5cf6" # Purple
Expand Down Expand Up @@ -316,7 +322,11 @@ dark:

# Note: The theme system also auto-generates fg-secondary, fg-tertiary, fg-muted, and fg-disabled from fg-primary using opacity percentages if not explicitly set
fg-primary: "#ffffff" # White text


# KPI delta colors (optional - controls comparison/change value colors)
# kpi-positive: "#4ade80" # Green for positive deltas (brighter for dark mode)
# kpi-negative: "#f87171" # Red for negative deltas (brighter for dark mode)

# Qualitative palette (adjusted for dark mode visibility - all 24 colors)
color-qualitative-1: "#818cf8" # Indigo
color-qualitative-2: "#a78bfa" # Purple
Expand Down
4 changes: 4 additions & 0 deletions web-common/src/features/themes/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,10 @@ type Colors = {
destructive?: Color;
"destructive-foreground"?: Color;

// KPI delta coloring
"kpi-positive"?: Color;
"kpi-negative"?: Color;

// Popover
popover?: Color;
"popover-accent"?: Color;
Expand Down
4 changes: 4 additions & 0 deletions web-common/tailwind.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,10 @@ export default {
foreground: oklabString("measure-foreground"),
border: oklabString("measure-border"),
},
kpi: {
positive: oklabString("kpi-positive"),
negative: oklabString("kpi-negative"),
},
tooltip: oklabString("tooltip"),
"theme-secondary": {
DEFAULT: oklabString("color-theme-secondary-500"),
Expand Down
Loading