diff --git a/core/src/user-components/tags/bigvalue/BigValue.svelte b/core/src/user-components/tags/bigvalue/BigValue.svelte index 390204714a..dceb01ea61 100644 --- a/core/src/user-components/tags/bigvalue/BigValue.svelte +++ b/core/src/user-components/tags/bigvalue/BigValue.svelte @@ -55,6 +55,7 @@ const comparison = $derived(model.resolvedComparison); // Coerce booleans in case they came from variable interpolation as strings const comparison_delta = $derived(coerceBoolean(comparison?.delta) ?? true); + const comparison_chip = $derived(coerceBoolean(comparison?.chip) ?? false); const sparkline = $derived(model.resolvedSparkline); const sparkline_type = $derived((sparkline?.type ?? 'line') as 'line' | 'area' | 'bar'); const sparkline_color = $derived(sparkline?.color); @@ -81,9 +82,11 @@ ); const down_is_good = $derived(coerceBoolean(comparison?.down_is_good) ?? false); const neutralRange = $derived(comparison?.neutral_range ?? [0, 0]); + const isCard = $derived(props.card ?? false); const max_width = $derived(props.max_width ?? 'none'); const min_width = $derived(props.min_width ?? 'auto'); const title_class = $derived(props.title_class); + const subtitle_class = $derived(props.subtitle_class); const value_class = $derived(props.value_class); const text_size = $derived(props.text_size); const comparison_class = $derived(undefined); @@ -148,11 +151,13 @@ // Use resolved props from model (handles variable interpolation) const resolvedTitle = $derived(model.resolvedTitle); + const resolvedSubtitle = $derived(model.resolvedSubtitle); const info = $derived(model.resolvedInfo); const info_link = $derived(model.resolvedInfo_link); const info_link_title = $derived(model.resolvedInfo_link_title); const title = $derived(resolvedTitle || (props.value ? valueProcessed.displayAlias : '')); + const subtitle = $derived(resolvedSubtitle || ''); // Extract data points from the single-row query results const resultRow = $derived.by(() => { @@ -280,7 +285,13 @@ {/if} + {#if subtitle} + + {subtitle} + + {/if} diff --git a/core/src/user-components/tags/bigvalue/BigValueModel.svelte.ts b/core/src/user-components/tags/bigvalue/BigValueModel.svelte.ts index ee208dcd12..371c6eae38 100644 --- a/core/src/user-components/tags/bigvalue/BigValueModel.svelte.ts +++ b/core/src/user-components/tags/bigvalue/BigValueModel.svelte.ts @@ -59,6 +59,7 @@ export class BigValueModel extends UserComponentModel { if (this.metricCompiled) return this.metricCompiled.displayLabel; return explicit; }); + readonly resolvedSubtitle = $derived(this.resolveText(this.attributes.subtitle)); readonly resolvedInfo = $derived(this.resolveText(this.attributes.info)); readonly resolvedInfo_link = $derived(this.resolveText(this.attributes.info_link)); readonly resolvedInfo_link_title = $derived(this.resolveText(this.attributes.info_link_title)); diff --git a/core/src/user-components/tags/bigvalue/bigvalue.test.ts b/core/src/user-components/tags/bigvalue/bigvalue.test.ts index 12c77c961e..a7583b636a 100644 --- a/core/src/user-components/tags/bigvalue/bigvalue.test.ts +++ b/core/src/user-components/tags/bigvalue/bigvalue.test.ts @@ -1,4 +1,5 @@ import { describe, it, expect } from 'vitest'; +import { schema } from './schema'; import { buildBigValueSQL, type BigValueSQLAttrs } from './build-bigvalue-sql'; import { orderCompatibleWithSingleValue } from '../../validators'; import { @@ -602,3 +603,12 @@ describe('order × value compatibility (author-time guard for the GROUP BY ALL s ).toEqual([]); }); }); + +describe('big_value schema attributes', () => { + it('supports subtitle, card, and comparison.chip', () => { + expect(schema.attributes.title).toBeDefined(); + expect(schema.attributes.subtitle).toBeDefined(); + expect(schema.attributes.card).toBeDefined(); + expect(schema.attributes.comparison).toBeDefined(); + }); +}); diff --git a/core/src/user-components/tags/bigvalue/schema.ts b/core/src/user-components/tags/bigvalue/schema.ts index 64cbf4110c..337e899301 100644 --- a/core/src/user-components/tags/bigvalue/schema.ts +++ b/core/src/user-components/tags/bigvalue/schema.ts @@ -63,6 +63,13 @@ const comparisonSchema = baseComparisonSchema 'Range [min, max] for neutral values. Use null for infinity (e.g., [null, 0] means anything ≤ 0 is neutral)' ), { supportsVariables: true } + ), + chip: setZodMetadata( + booleanVariableSchema + .optional() + .default(false) + .describe('Whether to display the comparison delta as a chip / pill badge'), + { supportsVariables: true } ) }) .optional(); @@ -205,6 +212,21 @@ const attributes = { supportsVariables: true, variableContext: 'text' }, + subtitle: { + type: String, + required: false, + description: 'Subtitle displayed below the title', + affectsQuery: false, + supportsVariables: true, + variableContext: 'text' + }, + card: { + type: Boolean, + required: false, + default: false, + description: 'Whether to style the component as an individual card with border and padding', + affectsQuery: false + }, max_width: { type: String, required: false, @@ -233,6 +255,12 @@ const attributes = { description: 'Additional CSS classes for the title', affectsQuery: false }, + subtitle_class: { + type: String, + required: false, + description: 'Additional CSS classes for the subtitle', + affectsQuery: false + }, value_class: { type: String, required: false,