DocsRecipes

Workflow recipes

Five minimal workflows built from the same mounted demos and fixtures that keep the adapters honest.

5 recipes5 adapters

Async searchable choice

Keep the selected value and query controlled while remote options progress through loading, empty, error, and retry.

Required states

  • Controlled value and query
  • Loading results
  • Empty result
  • Error
  • Retry
React example follows below.

Mounted source: apps/grassroot/src/components/react/ComboboxDemo.tsx

ComboboxDemo.tsxts
const comboboxFixture = {
id: "combobox",
label: "Service",
defaultValue: ["ledger-api"],
options: [
{ value: "ledger-api", label: "ledger-api" },
{ value: "ledger-worker", label: "ledger-worker" },
{ value: "billing-api", label: "billing-api", disabled: true },
],
} as const;
import { Combobox } from "@grassroot/ui-react";
export default function ComboboxDemo() {
return (
<div>
<Combobox
id={comboboxFixture.id}
label={comboboxFixture.label}
defaultValue={comboboxFixture.defaultValue}
options={[...comboboxFixture.options]}
/>
</div>
);
}

Mounted source: apps/grassroot/src/components/vue/ComboboxDemo.vue

ComboboxDemo.vueplain
<script setup lang="ts">
import { ref } from "vue";
const comboboxFixture = {
id: "combobox",
label: "Service",
defaultValue: ["ledger-api"],
options: [
{ value: "ledger-api", label: "ledger-api" },
{ value: "ledger-worker", label: "ledger-worker" },
{ value: "billing-api", label: "billing-api", disabled: true },
],
} as const;
import { Combobox } from "@grassroot/ui-vue";
const value = ref(comboboxFixture.defaultValue);
</script>
<template>
<div>
<Combobox
v-model="value"
:label="comboboxFixture.label"
:options="[...comboboxFixture.options]"
/>
</div>
</template>

Mounted source: apps/grassroot/src/components/angular/ComboboxDemo.component.ts

ComboboxDemo.component.tsts
import { Component } from "@angular/core";
const comboboxFixture = {
id: "combobox",
label: "Service",
defaultValue: ["ledger-api"],
options: [
{ value: "ledger-api", label: "ledger-api" },
{ value: "ledger-worker", label: "ledger-worker" },
{ value: "billing-api", label: "billing-api", disabled: true },
],
} as const;
import { GrassrootCombobox } from "@grassroot/ui-angular";
@Component({
selector: "app-combobox-demo",
standalone: true,
imports: [GrassrootCombobox],
template: `<div>
<grassroot-combobox
[label]="fixture.label"
[options]="fixture.options"
[value]="fixture.defaultValue"
/>
</div>`,
})
export class ComboboxDemoComponent {
protected readonly fixture = comboboxFixture;
}
export default ComboboxDemoComponent;

Mounted source: apps/grassroot/src/components/svelte/ComboboxDemo.svelte

ComboboxDemo.svelteplain
<script lang="ts">
const comboboxFixture = {
id: "combobox",
label: "Service",
defaultValue: ["ledger-api"],
options: [
{ value: "ledger-api", label: "ledger-api" },
{ value: "ledger-worker", label: "ledger-worker" },
{ value: "billing-api", label: "billing-api", disabled: true },
],
} as const;
import { Combobox } from "@grassroot/ui-svelte";
</script>
<div>
<Combobox
label={comboboxFixture.label}
options={[...comboboxFixture.options]}
defaultValue={comboboxFixture.defaultValue}
/>
</div>

Mounted source: apps/grassroot/src/components/solid/ComboboxDemo.tsx

ComboboxDemo.tsxts
/** @jsxImportSource solid-js */
const comboboxFixture = {
id: "combobox",
label: "Service",
defaultValue: ["ledger-api"],
options: [
{ value: "ledger-api", label: "ledger-api" },
{ value: "ledger-worker", label: "ledger-worker" },
{ value: "billing-api", label: "billing-api", disabled: true },
],
} as const;
import { Combobox } from "@grassroot/ui-solid";
export default function ComboboxDemo() {
return (
<div>
<Combobox
label={comboboxFixture.label}
options={[...comboboxFixture.options]}
defaultValue={comboboxFixture.defaultValue}
/>
</div>
);
}

Constrained localized date

Control both the date and the open state, then make locale, direction, and valid boundaries part of the same workflow.

Required states

  • Controlled value
  • Controlled open state
  • Minimum and maximum dates
  • Locale
  • Direction
React example follows below.

Mounted source: apps/grassroot/src/components/react/DatePickerDemo.tsx

DatePickerDemo.tsxts
const datePickerFixture = {
id: "datepicker",
label: "Kickoff date",
// Pinned for the same reason as calendarFixture: the trigger's mono readout
// and the popover's today-ring must not move with the wall clock.
selectedIsoDay: "2026-06-21",
todayIsoDay: "2026-06-21",
} satisfies {
id: string;
label: string;
selectedIsoDay: string;
todayIsoDay: string;
};
/**
* Turns a fixture's pinned `yyyy-mm-dd` string into the `Date` the calendar
* adapters take.
*
* Fixtures stay plain JSON (the proof test rejects `Date` instances, and
* `date` is a forbidden field name), but Calendar/DatePicker/DateRangePicker
* are `Date`-valued — so the conversion lives here, once, instead of five
* `new Date(...)` literals drifting apart in five demo files.
*
* Parsed field-by-field into *local* midnight on purpose: `new Date("2026-06-18")`
* is parsed as UTC midnight, which renders as the 17th everywhere west of
* Greenwich, so the demo month grid would highlight a different day depending
* on the reader's timezone. Field-by-field construction pins the rendered
* year/month/day in every timezone, forever.
*/
function isoDayToDate(isoDay: string): Date {
const [year, month, day] = isoDay.split("-").map(Number);
return new Date(year!, month! - 1, day!);
}
import { DatePicker } from "@grassroot/ui-react";
export default function DatePickerDemo() {
return (
<div>
<DatePicker
id={`${datePickerFixture.id}-react`}
label={datePickerFixture.label}
defaultValue={isoDayToDate(datePickerFixture.selectedIsoDay)}
today={isoDayToDate(datePickerFixture.todayIsoDay)}
/>
</div>
);
}

Mounted source: apps/grassroot/src/components/vue/DatePickerDemo.vue

DatePickerDemo.vueplain
<script setup lang="ts">
import { ref } from "vue";
const datePickerFixture = {
id: "datepicker",
label: "Kickoff date",
// Pinned for the same reason as calendarFixture: the trigger's mono readout
// and the popover's today-ring must not move with the wall clock.
selectedIsoDay: "2026-06-21",
todayIsoDay: "2026-06-21",
} satisfies {
id: string;
label: string;
selectedIsoDay: string;
todayIsoDay: string;
};
/**
* Turns a fixture's pinned `yyyy-mm-dd` string into the `Date` the calendar
* adapters take.
*
* Fixtures stay plain JSON (the proof test rejects `Date` instances, and
* `date` is a forbidden field name), but Calendar/DatePicker/DateRangePicker
* are `Date`-valued — so the conversion lives here, once, instead of five
* `new Date(...)` literals drifting apart in five demo files.
*
* Parsed field-by-field into *local* midnight on purpose: `new Date("2026-06-18")`
* is parsed as UTC midnight, which renders as the 17th everywhere west of
* Greenwich, so the demo month grid would highlight a different day depending
* on the reader's timezone. Field-by-field construction pins the rendered
* year/month/day in every timezone, forever.
*/
function isoDayToDate(isoDay: string): Date {
const [year, month, day] = isoDay.split("-").map(Number);
return new Date(year!, month! - 1, day!);
}
import { DatePicker } from "@grassroot/ui-vue";
const selected = ref(isoDayToDate(datePickerFixture.selectedIsoDay));
const today = isoDayToDate(datePickerFixture.todayIsoDay);
</script>
<template>
<div>
<DatePicker
v-model="selected"
:id="`${datePickerFixture.id}-vue`"
:label="datePickerFixture.label"
:today="today"
/>
</div>
</template>

Mounted source: apps/grassroot/src/components/angular/DatePickerDemo.component.ts

DatePickerDemo.component.tsts
import { Component } from "@angular/core";
const datePickerFixture = {
id: "datepicker",
label: "Kickoff date",
// Pinned for the same reason as calendarFixture: the trigger's mono readout
// and the popover's today-ring must not move with the wall clock.
selectedIsoDay: "2026-06-21",
todayIsoDay: "2026-06-21",
} satisfies {
id: string;
label: string;
selectedIsoDay: string;
todayIsoDay: string;
};
/**
* Turns a fixture's pinned `yyyy-mm-dd` string into the `Date` the calendar
* adapters take.
*
* Fixtures stay plain JSON (the proof test rejects `Date` instances, and
* `date` is a forbidden field name), but Calendar/DatePicker/DateRangePicker
* are `Date`-valued — so the conversion lives here, once, instead of five
* `new Date(...)` literals drifting apart in five demo files.
*
* Parsed field-by-field into *local* midnight on purpose: `new Date("2026-06-18")`
* is parsed as UTC midnight, which renders as the 17th everywhere west of
* Greenwich, so the demo month grid would highlight a different day depending
* on the reader's timezone. Field-by-field construction pins the rendered
* year/month/day in every timezone, forever.
*/
function isoDayToDate(isoDay: string): Date {
const [year, month, day] = isoDay.split("-").map(Number);
return new Date(year!, month! - 1, day!);
}
import { GrassrootDatePicker } from "@grassroot/ui-angular";
@Component({
selector: "app-date-picker-demo",
standalone: true,
imports: [GrassrootDatePicker],
template: `<div>
<grassroot-date-picker
[id]="id"
[label]="fixture.label"
[defaultValue]="selected"
[today]="today"
/>
</div>`,
})
export class DatePickerDemoComponent {
protected readonly fixture = datePickerFixture;
protected readonly id = `${datePickerFixture.id}-angular`;
protected readonly selected = isoDayToDate(datePickerFixture.selectedIsoDay);
protected readonly today = isoDayToDate(datePickerFixture.todayIsoDay);
}
export default DatePickerDemoComponent;

Mounted source: apps/grassroot/src/components/svelte/DatePickerDemo.svelte

DatePickerDemo.svelteplain
<script lang="ts">
const datePickerFixture = {
id: "datepicker",
label: "Kickoff date",
// Pinned for the same reason as calendarFixture: the trigger's mono readout
// and the popover's today-ring must not move with the wall clock.
selectedIsoDay: "2026-06-21",
todayIsoDay: "2026-06-21",
} satisfies {
id: string;
label: string;
selectedIsoDay: string;
todayIsoDay: string;
};
/**
* Turns a fixture's pinned `yyyy-mm-dd` string into the `Date` the calendar
* adapters take.
*
* Fixtures stay plain JSON (the proof test rejects `Date` instances, and
* `date` is a forbidden field name), but Calendar/DatePicker/DateRangePicker
* are `Date`-valued — so the conversion lives here, once, instead of five
* `new Date(...)` literals drifting apart in five demo files.
*
* Parsed field-by-field into *local* midnight on purpose: `new Date("2026-06-18")`
* is parsed as UTC midnight, which renders as the 17th everywhere west of
* Greenwich, so the demo month grid would highlight a different day depending
* on the reader's timezone. Field-by-field construction pins the rendered
* year/month/day in every timezone, forever.
*/
function isoDayToDate(isoDay: string): Date {
const [year, month, day] = isoDay.split("-").map(Number);
return new Date(year!, month! - 1, day!);
}
import { DatePicker } from "@grassroot/ui-svelte";
</script>
<div>
<DatePicker
id={`${datePickerFixture.id}-svelte`}
label={datePickerFixture.label}
defaultValue={isoDayToDate(datePickerFixture.selectedIsoDay)}
today={isoDayToDate(datePickerFixture.todayIsoDay)}
/>
</div>

Mounted source: apps/grassroot/src/components/solid/DatePickerDemo.tsx

DatePickerDemo.tsxts
/** @jsxImportSource solid-js */
const datePickerFixture = {
id: "datepicker",
label: "Kickoff date",
// Pinned for the same reason as calendarFixture: the trigger's mono readout
// and the popover's today-ring must not move with the wall clock.
selectedIsoDay: "2026-06-21",
todayIsoDay: "2026-06-21",
} satisfies {
id: string;
label: string;
selectedIsoDay: string;
todayIsoDay: string;
};
/**
* Turns a fixture's pinned `yyyy-mm-dd` string into the `Date` the calendar
* adapters take.
*
* Fixtures stay plain JSON (the proof test rejects `Date` instances, and
* `date` is a forbidden field name), but Calendar/DatePicker/DateRangePicker
* are `Date`-valued — so the conversion lives here, once, instead of five
* `new Date(...)` literals drifting apart in five demo files.
*
* Parsed field-by-field into *local* midnight on purpose: `new Date("2026-06-18")`
* is parsed as UTC midnight, which renders as the 17th everywhere west of
* Greenwich, so the demo month grid would highlight a different day depending
* on the reader's timezone. Field-by-field construction pins the rendered
* year/month/day in every timezone, forever.
*/
function isoDayToDate(isoDay: string): Date {
const [year, month, day] = isoDay.split("-").map(Number);
return new Date(year!, month! - 1, day!);
}
import { DatePicker } from "@grassroot/ui-solid";
export default function DatePickerDemo() {
return (
<div>
<DatePicker
id={`${datePickerFixture.id}-solid`}
label={datePickerFixture.label}
defaultValue={isoDayToDate(datePickerFixture.selectedIsoDay)}
today={isoDayToDate(datePickerFixture.todayIsoDay)}
/>
</div>
);
}

Guarded upload

Validate type, size, and count before committing files; disabled and rejected states stay visible and recoverable.

Required states

  • Type, size, and count limits
  • Disabled
  • Rejected file
  • Visible corrective text
  • Recovery
React example follows below.

Mounted source: apps/grassroot/src/components/react/FileDropzoneDemo.tsx

FileDropzoneDemo.tsxts
const fileDropzoneFixture = {
id: "file-dropzone",
hint: "PNG, PDF, CSV",
accept: ".png,.pdf,.csv",
multiple: true,
};
import { FileDropzone } from "@grassroot/ui-react/advanced";
export default function FileDropzoneDemo() {
const { id: _id, ...props } = fileDropzoneFixture;
return (
<div>
<FileDropzone {...props} />
</div>
);
}

Mounted source: apps/grassroot/src/components/vue/FileDropzoneDemo.vue

FileDropzoneDemo.vueplain
<script setup lang="ts">
const fileDropzoneFixture = {
id: "file-dropzone",
hint: "PNG, PDF, CSV",
accept: ".png,.pdf,.csv",
multiple: true,
};
import { FileDropzone } from "@grassroot/ui-vue";
</script>
<template>
<div>
<FileDropzone
:hint="fileDropzoneFixture.hint"
:accept="fileDropzoneFixture.accept"
:multiple="fileDropzoneFixture.multiple"
/>
</div>
</template>

Mounted source: apps/grassroot/src/components/angular/FileDropzoneDemo.component.ts

FileDropzoneDemo.component.tsts
import { Component } from "@angular/core";
const fileDropzoneFixture = {
id: "file-dropzone",
hint: "PNG, PDF, CSV",
accept: ".png,.pdf,.csv",
multiple: true,
};
import { GrassrootFileDropzone } from "@grassroot/ui-angular";
@Component({
selector: "app-file-dropzone-demo",
standalone: true,
imports: [GrassrootFileDropzone],
template: `
<div>
<grassroot-file-dropzone
[hint]="fixture.hint"
[accept]="fixture.accept"
[multiple]="fixture.multiple"
/>
</div>
`,
})
export class FileDropzoneDemoComponent {
protected readonly fixture = fileDropzoneFixture;
}
export default FileDropzoneDemoComponent;

Mounted source: apps/grassroot/src/components/svelte/FileDropzoneDemo.svelte

FileDropzoneDemo.svelteplain
<script lang="ts">
const fileDropzoneFixture = {
id: "file-dropzone",
hint: "PNG, PDF, CSV",
accept: ".png,.pdf,.csv",
multiple: true,
};
import { FileDropzone } from "@grassroot/ui-svelte";
</script>
<div>
<FileDropzone
hint={fileDropzoneFixture.hint}
accept={fileDropzoneFixture.accept}
multiple={fileDropzoneFixture.multiple}
/>
</div>

Mounted source: apps/grassroot/src/components/solid/FileDropzoneDemo.tsx

FileDropzoneDemo.tsxts
/** @jsxImportSource solid-js */
const fileDropzoneFixture = {
id: "file-dropzone",
hint: "PNG, PDF, CSV",
accept: ".png,.pdf,.csv",
multiple: true,
};
import { FileDropzone } from "@grassroot/ui-solid";
export default function FileDropzoneDemo() {
return (
<div>
<FileDropzone
hint={fileDropzoneFixture.hint}
accept={fileDropzoneFixture.accept}
multiple={fileDropzoneFixture.multiple}
/>
</div>
);
}

Remote Grid

Use the completed G6 data-source boundary so loading, empty, error, retry, and row selection are one controlled remote workflow.

Required states

  • Loading
  • Empty result
  • Error
  • Retry
  • Selection
React example follows below.

Mounted source: apps/grassroot/src/components/react/DataGridDemo.tsx

DataGridDemo.tsxts
type DataGridDemoRow = {
id: string;
service: string;
environment: "Production" | "Staging";
region: string;
version: string;
status: "Healthy" | "Monitoring" | "Degraded" | "Incident" | "Maintenance";
owner: string;
p95: number;
errorRate: string;
deployedAt: string;
};
/**
* Deterministic, anonymised deployment telemetry. The row and column counts
* deliberately exercise vertical virtualization, horizontal overflow,
* status scanning, numeric sorting, filtering, editing, and selection.
*/
const dataGridFixture = {
caption: "Production service health",
selectedIds: ["checkout-eu", "identity-us"],
columns: [
{ key: "service", header: "Service", width: 190, sortable: true, filterable: true },
{ key: "environment", header: "Environment", width: 130, sortable: true, filterable: true, filterType: "set" },
{ key: "region", header: "Region", width: 140, sortable: true, filterable: true, filterType: "set" },
{ key: "version", header: "Version", width: 120, sortable: true },
{ key: "status", header: "Status", width: 140, sortable: true, filterable: true, filterType: "set" },
{ key: "owner", header: "Owner", width: 170, sortable: true, filterable: true },
{ key: "p95", header: "p95 ms", width: 110, sortable: true, filterable: true, filterType: "number", editable: true },
{ key: "errorRate", header: "Error rate", width: 120, sortable: true },
{ key: "deployedAt", header: "Last deployed", width: 180, sortable: true },
],
rows: [
{ id: "checkout-eu", service: "Checkout API", environment: "Production", region: "eu-west-1", version: "v4.18.2", status: "Healthy", owner: "Payments", p95: 182, errorRate: "0.08%", deployedAt: "23 Aug, 09:42" },
{ id: "identity-us", service: "Identity", environment: "Production", region: "us-east-1", version: "v7.6.0", status: "Monitoring", owner: "Platform Identity", p95: 264, errorRate: "0.31%", deployedAt: "23 Aug, 09:18" },
{ id: "catalog-eu", service: "Product catalog", environment: "Production", region: "eu-west-1", version: "v12.3.1", status: "Healthy", owner: "Commerce Core", p95: 96, errorRate: "0.03%", deployedAt: "23 Aug, 08:55" },
{ id: "pricing-us", service: "Pricing engine", environment: "Production", region: "us-east-1", version: "v9.14.4", status: "Degraded", owner: "Revenue Systems", p95: 487, errorRate: "1.42%", deployedAt: "23 Aug, 08:34" },
{ id: "orders-eu", service: "Order orchestration", environment: "Production", region: "eu-central-1", version: "v5.22.0", status: "Healthy", owner: "Fulfilment", p95: 218, errorRate: "0.11%", deployedAt: "23 Aug, 08:12" },
{ id: "search-ap", service: "Search gateway", environment: "Production", region: "ap-southeast-2", version: "v11.8.3", status: "Healthy", owner: "Discovery", p95: 144, errorRate: "0.05%", deployedAt: "23 Aug, 07:49" },
{ id: "fraud-us", service: "Fraud decisioning", environment: "Production", region: "us-east-1", version: "v3.9.7", status: "Incident", owner: "Risk", p95: 732, errorRate: "4.86%", deployedAt: "23 Aug, 07:31" },
{ id: "notifications-eu", service: "Notifications", environment: "Production", region: "eu-west-1", version: "v8.2.5", status: "Healthy", owner: "Customer Comms", p95: 121, errorRate: "0.07%", deployedAt: "23 Aug, 07:06" },
{ id: "inventory-us", service: "Inventory ledger", environment: "Production", region: "us-west-2", version: "v6.17.1", status: "Monitoring", owner: "Supply Chain", p95: 305, errorRate: "0.62%", deployedAt: "23 Aug, 06:44" },
{ id: "shipping-eu", service: "Shipping quotes", environment: "Production", region: "eu-central-1", version: "v10.4.0", status: "Healthy", owner: "Logistics", p95: 198, errorRate: "0.09%", deployedAt: "23 Aug, 06:20" },
{ id: "tax-us", service: "Tax calculation", environment: "Production", region: "us-east-1", version: "v4.11.9", status: "Maintenance", owner: "Finance Platform", p95: 356, errorRate: "0.18%", deployedAt: "23 Aug, 05:58" },
{ id: "accounts-ap", service: "Customer accounts", environment: "Production", region: "ap-southeast-1", version: "v13.0.2", status: "Healthy", owner: "Customer Platform", p95: 173, errorRate: "0.06%", deployedAt: "23 Aug, 05:35" },
{ id: "recommendations-eu", service: "Recommendations", environment: "Production", region: "eu-west-1", version: "v2.26.6", status: "Degraded", owner: "Personalisation", p95: 541, errorRate: "1.08%", deployedAt: "23 Aug, 05:11" },
{ id: "media-us", service: "Media pipeline", environment: "Production", region: "us-west-2", version: "v7.12.8", status: "Healthy", owner: "Content Systems", p95: 238, errorRate: "0.14%", deployedAt: "23 Aug, 04:47" },
{ id: "webhooks-eu", service: "Partner webhooks", environment: "Production", region: "eu-central-1", version: "v5.7.3", status: "Monitoring", owner: "Integrations", p95: 279, errorRate: "0.44%", deployedAt: "23 Aug, 04:23" },
{ id: "ledger-us", service: "Payments ledger", environment: "Production", region: "us-east-1", version: "v14.2.0", status: "Healthy", owner: "Payments", p95: 167, errorRate: "0.02%", deployedAt: "23 Aug, 03:59" },
{ id: "checkout-stg", service: "Checkout API", environment: "Staging", region: "eu-west-1", version: "v4.19.0-rc.2", status: "Monitoring", owner: "Payments", p95: 211, errorRate: "0.23%", deployedAt: "23 Aug, 03:36" },
{ id: "identity-stg", service: "Identity", environment: "Staging", region: "us-east-1", version: "v7.7.0-rc.1", status: "Healthy", owner: "Platform Identity", p95: 195, errorRate: "0.09%", deployedAt: "23 Aug, 03:12" },
{ id: "catalog-stg", service: "Product catalog", environment: "Staging", region: "eu-west-1", version: "v12.4.0-beta.3", status: "Healthy", owner: "Commerce Core", p95: 104, errorRate: "0.04%", deployedAt: "23 Aug, 02:48" },
{ id: "pricing-stg", service: "Pricing engine", environment: "Staging", region: "us-east-1", version: "v9.15.0-rc.4", status: "Healthy", owner: "Revenue Systems", p95: 226, errorRate: "0.12%", deployedAt: "23 Aug, 02:24" },
{ id: "orders-stg", service: "Order orchestration", environment: "Staging", region: "eu-central-1", version: "v5.23.0-beta.1", status: "Maintenance", owner: "Fulfilment", p95: 318, errorRate: "0.20%", deployedAt: "23 Aug, 02:01" },
{ id: "search-stg", service: "Search gateway", environment: "Staging", region: "ap-southeast-2", version: "v11.9.0-rc.2", status: "Healthy", owner: "Discovery", p95: 133, errorRate: "0.03%", deployedAt: "23 Aug, 01:38" },
{ id: "fraud-stg", service: "Fraud decisioning", environment: "Staging", region: "us-east-1", version: "v3.10.0-beta.5", status: "Monitoring", owner: "Risk", p95: 289, errorRate: "0.51%", deployedAt: "23 Aug, 01:14" },
{ id: "notifications-stg", service: "Notifications", environment: "Staging", region: "eu-west-1", version: "v8.3.0-rc.1", status: "Healthy", owner: "Customer Comms", p95: 118, errorRate: "0.05%", deployedAt: "23 Aug, 00:50" },
{ id: "inventory-stg", service: "Inventory ledger", environment: "Staging", region: "us-west-2", version: "v6.18.0-beta.2", status: "Degraded", owner: "Supply Chain", p95: 463, errorRate: "1.17%", deployedAt: "23 Aug, 00:27" },
{ id: "shipping-stg", service: "Shipping quotes", environment: "Staging", region: "eu-central-1", version: "v10.5.0-rc.3", status: "Healthy", owner: "Logistics", p95: 176, errorRate: "0.08%", deployedAt: "22 Aug, 23:53" },
{ id: "tax-stg", service: "Tax calculation", environment: "Staging", region: "us-east-1", version: "v4.12.0-beta.1", status: "Healthy", owner: "Finance Platform", p95: 207, errorRate: "0.10%", deployedAt: "22 Aug, 23:29" },
{ id: "accounts-stg", service: "Customer accounts", environment: "Staging", region: "ap-southeast-1", version: "v13.1.0-rc.2", status: "Monitoring", owner: "Customer Platform", p95: 247, errorRate: "0.38%", deployedAt: "22 Aug, 23:05" },
{ id: "recommendations-stg", service: "Recommendations", environment: "Staging", region: "eu-west-1", version: "v2.27.0-beta.4", status: "Healthy", owner: "Personalisation", p95: 201, errorRate: "0.15%", deployedAt: "22 Aug, 22:41" },
{ id: "webhooks-stg", service: "Partner webhooks", environment: "Staging", region: "eu-central-1", version: "v5.8.0-rc.1", status: "Healthy", owner: "Integrations", p95: 188, errorRate: "0.07%", deployedAt: "22 Aug, 22:17" },
] satisfies readonly DataGridDemoRow[],
} as const;
import { DataGrid } from "@grassroot/ui-react";
export default function DataGridDemo() {
return (
<DataGrid
rowData={dataGridFixture.rows}
columnDefs={dataGridFixture.columns}
getRowId={(row) => row.id}
caption={dataGridFixture.caption}
selectionMode="multiple"
defaultSelectedIds={dataGridFixture.selectedIds}
showQuickFilter
showExportButton
/>
);
}

Mounted source: apps/grassroot/src/components/vue/DataGridDemo.vue

DataGridDemo.vueplain
<script setup lang="ts">
type DataGridDemoRow = {
id: string;
service: string;
environment: "Production" | "Staging";
region: string;
version: string;
status: "Healthy" | "Monitoring" | "Degraded" | "Incident" | "Maintenance";
owner: string;
p95: number;
errorRate: string;
deployedAt: string;
};
/**
* Deterministic, anonymised deployment telemetry. The row and column counts
* deliberately exercise vertical virtualization, horizontal overflow,
* status scanning, numeric sorting, filtering, editing, and selection.
*/
const dataGridFixture = {
caption: "Production service health",
selectedIds: ["checkout-eu", "identity-us"],
columns: [
{ key: "service", header: "Service", width: 190, sortable: true, filterable: true },
{ key: "environment", header: "Environment", width: 130, sortable: true, filterable: true, filterType: "set" },
{ key: "region", header: "Region", width: 140, sortable: true, filterable: true, filterType: "set" },
{ key: "version", header: "Version", width: 120, sortable: true },
{ key: "status", header: "Status", width: 140, sortable: true, filterable: true, filterType: "set" },
{ key: "owner", header: "Owner", width: 170, sortable: true, filterable: true },
{ key: "p95", header: "p95 ms", width: 110, sortable: true, filterable: true, filterType: "number", editable: true },
{ key: "errorRate", header: "Error rate", width: 120, sortable: true },
{ key: "deployedAt", header: "Last deployed", width: 180, sortable: true },
],
rows: [
{ id: "checkout-eu", service: "Checkout API", environment: "Production", region: "eu-west-1", version: "v4.18.2", status: "Healthy", owner: "Payments", p95: 182, errorRate: "0.08%", deployedAt: "23 Aug, 09:42" },
{ id: "identity-us", service: "Identity", environment: "Production", region: "us-east-1", version: "v7.6.0", status: "Monitoring", owner: "Platform Identity", p95: 264, errorRate: "0.31%", deployedAt: "23 Aug, 09:18" },
{ id: "catalog-eu", service: "Product catalog", environment: "Production", region: "eu-west-1", version: "v12.3.1", status: "Healthy", owner: "Commerce Core", p95: 96, errorRate: "0.03%", deployedAt: "23 Aug, 08:55" },
{ id: "pricing-us", service: "Pricing engine", environment: "Production", region: "us-east-1", version: "v9.14.4", status: "Degraded", owner: "Revenue Systems", p95: 487, errorRate: "1.42%", deployedAt: "23 Aug, 08:34" },
{ id: "orders-eu", service: "Order orchestration", environment: "Production", region: "eu-central-1", version: "v5.22.0", status: "Healthy", owner: "Fulfilment", p95: 218, errorRate: "0.11%", deployedAt: "23 Aug, 08:12" },
{ id: "search-ap", service: "Search gateway", environment: "Production", region: "ap-southeast-2", version: "v11.8.3", status: "Healthy", owner: "Discovery", p95: 144, errorRate: "0.05%", deployedAt: "23 Aug, 07:49" },
{ id: "fraud-us", service: "Fraud decisioning", environment: "Production", region: "us-east-1", version: "v3.9.7", status: "Incident", owner: "Risk", p95: 732, errorRate: "4.86%", deployedAt: "23 Aug, 07:31" },
{ id: "notifications-eu", service: "Notifications", environment: "Production", region: "eu-west-1", version: "v8.2.5", status: "Healthy", owner: "Customer Comms", p95: 121, errorRate: "0.07%", deployedAt: "23 Aug, 07:06" },
{ id: "inventory-us", service: "Inventory ledger", environment: "Production", region: "us-west-2", version: "v6.17.1", status: "Monitoring", owner: "Supply Chain", p95: 305, errorRate: "0.62%", deployedAt: "23 Aug, 06:44" },
{ id: "shipping-eu", service: "Shipping quotes", environment: "Production", region: "eu-central-1", version: "v10.4.0", status: "Healthy", owner: "Logistics", p95: 198, errorRate: "0.09%", deployedAt: "23 Aug, 06:20" },
{ id: "tax-us", service: "Tax calculation", environment: "Production", region: "us-east-1", version: "v4.11.9", status: "Maintenance", owner: "Finance Platform", p95: 356, errorRate: "0.18%", deployedAt: "23 Aug, 05:58" },
{ id: "accounts-ap", service: "Customer accounts", environment: "Production", region: "ap-southeast-1", version: "v13.0.2", status: "Healthy", owner: "Customer Platform", p95: 173, errorRate: "0.06%", deployedAt: "23 Aug, 05:35" },
{ id: "recommendations-eu", service: "Recommendations", environment: "Production", region: "eu-west-1", version: "v2.26.6", status: "Degraded", owner: "Personalisation", p95: 541, errorRate: "1.08%", deployedAt: "23 Aug, 05:11" },
{ id: "media-us", service: "Media pipeline", environment: "Production", region: "us-west-2", version: "v7.12.8", status: "Healthy", owner: "Content Systems", p95: 238, errorRate: "0.14%", deployedAt: "23 Aug, 04:47" },
{ id: "webhooks-eu", service: "Partner webhooks", environment: "Production", region: "eu-central-1", version: "v5.7.3", status: "Monitoring", owner: "Integrations", p95: 279, errorRate: "0.44%", deployedAt: "23 Aug, 04:23" },
{ id: "ledger-us", service: "Payments ledger", environment: "Production", region: "us-east-1", version: "v14.2.0", status: "Healthy", owner: "Payments", p95: 167, errorRate: "0.02%", deployedAt: "23 Aug, 03:59" },
{ id: "checkout-stg", service: "Checkout API", environment: "Staging", region: "eu-west-1", version: "v4.19.0-rc.2", status: "Monitoring", owner: "Payments", p95: 211, errorRate: "0.23%", deployedAt: "23 Aug, 03:36" },
{ id: "identity-stg", service: "Identity", environment: "Staging", region: "us-east-1", version: "v7.7.0-rc.1", status: "Healthy", owner: "Platform Identity", p95: 195, errorRate: "0.09%", deployedAt: "23 Aug, 03:12" },
{ id: "catalog-stg", service: "Product catalog", environment: "Staging", region: "eu-west-1", version: "v12.4.0-beta.3", status: "Healthy", owner: "Commerce Core", p95: 104, errorRate: "0.04%", deployedAt: "23 Aug, 02:48" },
{ id: "pricing-stg", service: "Pricing engine", environment: "Staging", region: "us-east-1", version: "v9.15.0-rc.4", status: "Healthy", owner: "Revenue Systems", p95: 226, errorRate: "0.12%", deployedAt: "23 Aug, 02:24" },
{ id: "orders-stg", service: "Order orchestration", environment: "Staging", region: "eu-central-1", version: "v5.23.0-beta.1", status: "Maintenance", owner: "Fulfilment", p95: 318, errorRate: "0.20%", deployedAt: "23 Aug, 02:01" },
{ id: "search-stg", service: "Search gateway", environment: "Staging", region: "ap-southeast-2", version: "v11.9.0-rc.2", status: "Healthy", owner: "Discovery", p95: 133, errorRate: "0.03%", deployedAt: "23 Aug, 01:38" },
{ id: "fraud-stg", service: "Fraud decisioning", environment: "Staging", region: "us-east-1", version: "v3.10.0-beta.5", status: "Monitoring", owner: "Risk", p95: 289, errorRate: "0.51%", deployedAt: "23 Aug, 01:14" },
{ id: "notifications-stg", service: "Notifications", environment: "Staging", region: "eu-west-1", version: "v8.3.0-rc.1", status: "Healthy", owner: "Customer Comms", p95: 118, errorRate: "0.05%", deployedAt: "23 Aug, 00:50" },
{ id: "inventory-stg", service: "Inventory ledger", environment: "Staging", region: "us-west-2", version: "v6.18.0-beta.2", status: "Degraded", owner: "Supply Chain", p95: 463, errorRate: "1.17%", deployedAt: "23 Aug, 00:27" },
{ id: "shipping-stg", service: "Shipping quotes", environment: "Staging", region: "eu-central-1", version: "v10.5.0-rc.3", status: "Healthy", owner: "Logistics", p95: 176, errorRate: "0.08%", deployedAt: "22 Aug, 23:53" },
{ id: "tax-stg", service: "Tax calculation", environment: "Staging", region: "us-east-1", version: "v4.12.0-beta.1", status: "Healthy", owner: "Finance Platform", p95: 207, errorRate: "0.10%", deployedAt: "22 Aug, 23:29" },
{ id: "accounts-stg", service: "Customer accounts", environment: "Staging", region: "ap-southeast-1", version: "v13.1.0-rc.2", status: "Monitoring", owner: "Customer Platform", p95: 247, errorRate: "0.38%", deployedAt: "22 Aug, 23:05" },
{ id: "recommendations-stg", service: "Recommendations", environment: "Staging", region: "eu-west-1", version: "v2.27.0-beta.4", status: "Healthy", owner: "Personalisation", p95: 201, errorRate: "0.15%", deployedAt: "22 Aug, 22:41" },
{ id: "webhooks-stg", service: "Partner webhooks", environment: "Staging", region: "eu-central-1", version: "v5.8.0-rc.1", status: "Healthy", owner: "Integrations", p95: 188, errorRate: "0.07%", deployedAt: "22 Aug, 22:17" },
] satisfies readonly DataGridDemoRow[],
} as const;
import { DataGrid } from "@grassroot/ui-vue";
const getRowId = (row: unknown) => (row as DataGridDemoRow).id;
</script>
<template>
<DataGrid
:row-data="dataGridFixture.rows"
:column-defs="dataGridFixture.columns"
:get-row-id="getRowId"
:caption="dataGridFixture.caption"
selection-mode="multiple"
:default-selected-ids="dataGridFixture.selectedIds"
:show-quick-filter="true"
:show-export-button="true"
/>
</template>

Mounted source: apps/grassroot/src/components/angular/DataGridDemo.component.ts

DataGridDemo.component.tsts
import { Component } from "@angular/core";
type DataGridDemoRow = {
id: string;
service: string;
environment: "Production" | "Staging";
region: string;
version: string;
status: "Healthy" | "Monitoring" | "Degraded" | "Incident" | "Maintenance";
owner: string;
p95: number;
errorRate: string;
deployedAt: string;
};
/**
* Deterministic, anonymised deployment telemetry. The row and column counts
* deliberately exercise vertical virtualization, horizontal overflow,
* status scanning, numeric sorting, filtering, editing, and selection.
*/
const dataGridFixture = {
caption: "Production service health",
selectedIds: ["checkout-eu", "identity-us"],
columns: [
{ key: "service", header: "Service", width: 190, sortable: true, filterable: true },
{ key: "environment", header: "Environment", width: 130, sortable: true, filterable: true, filterType: "set" },
{ key: "region", header: "Region", width: 140, sortable: true, filterable: true, filterType: "set" },
{ key: "version", header: "Version", width: 120, sortable: true },
{ key: "status", header: "Status", width: 140, sortable: true, filterable: true, filterType: "set" },
{ key: "owner", header: "Owner", width: 170, sortable: true, filterable: true },
{ key: "p95", header: "p95 ms", width: 110, sortable: true, filterable: true, filterType: "number", editable: true },
{ key: "errorRate", header: "Error rate", width: 120, sortable: true },
{ key: "deployedAt", header: "Last deployed", width: 180, sortable: true },
],
rows: [
{ id: "checkout-eu", service: "Checkout API", environment: "Production", region: "eu-west-1", version: "v4.18.2", status: "Healthy", owner: "Payments", p95: 182, errorRate: "0.08%", deployedAt: "23 Aug, 09:42" },
{ id: "identity-us", service: "Identity", environment: "Production", region: "us-east-1", version: "v7.6.0", status: "Monitoring", owner: "Platform Identity", p95: 264, errorRate: "0.31%", deployedAt: "23 Aug, 09:18" },
{ id: "catalog-eu", service: "Product catalog", environment: "Production", region: "eu-west-1", version: "v12.3.1", status: "Healthy", owner: "Commerce Core", p95: 96, errorRate: "0.03%", deployedAt: "23 Aug, 08:55" },
{ id: "pricing-us", service: "Pricing engine", environment: "Production", region: "us-east-1", version: "v9.14.4", status: "Degraded", owner: "Revenue Systems", p95: 487, errorRate: "1.42%", deployedAt: "23 Aug, 08:34" },
{ id: "orders-eu", service: "Order orchestration", environment: "Production", region: "eu-central-1", version: "v5.22.0", status: "Healthy", owner: "Fulfilment", p95: 218, errorRate: "0.11%", deployedAt: "23 Aug, 08:12" },
{ id: "search-ap", service: "Search gateway", environment: "Production", region: "ap-southeast-2", version: "v11.8.3", status: "Healthy", owner: "Discovery", p95: 144, errorRate: "0.05%", deployedAt: "23 Aug, 07:49" },
{ id: "fraud-us", service: "Fraud decisioning", environment: "Production", region: "us-east-1", version: "v3.9.7", status: "Incident", owner: "Risk", p95: 732, errorRate: "4.86%", deployedAt: "23 Aug, 07:31" },
{ id: "notifications-eu", service: "Notifications", environment: "Production", region: "eu-west-1", version: "v8.2.5", status: "Healthy", owner: "Customer Comms", p95: 121, errorRate: "0.07%", deployedAt: "23 Aug, 07:06" },
{ id: "inventory-us", service: "Inventory ledger", environment: "Production", region: "us-west-2", version: "v6.17.1", status: "Monitoring", owner: "Supply Chain", p95: 305, errorRate: "0.62%", deployedAt: "23 Aug, 06:44" },
{ id: "shipping-eu", service: "Shipping quotes", environment: "Production", region: "eu-central-1", version: "v10.4.0", status: "Healthy", owner: "Logistics", p95: 198, errorRate: "0.09%", deployedAt: "23 Aug, 06:20" },
{ id: "tax-us", service: "Tax calculation", environment: "Production", region: "us-east-1", version: "v4.11.9", status: "Maintenance", owner: "Finance Platform", p95: 356, errorRate: "0.18%", deployedAt: "23 Aug, 05:58" },
{ id: "accounts-ap", service: "Customer accounts", environment: "Production", region: "ap-southeast-1", version: "v13.0.2", status: "Healthy", owner: "Customer Platform", p95: 173, errorRate: "0.06%", deployedAt: "23 Aug, 05:35" },
{ id: "recommendations-eu", service: "Recommendations", environment: "Production", region: "eu-west-1", version: "v2.26.6", status: "Degraded", owner: "Personalisation", p95: 541, errorRate: "1.08%", deployedAt: "23 Aug, 05:11" },
{ id: "media-us", service: "Media pipeline", environment: "Production", region: "us-west-2", version: "v7.12.8", status: "Healthy", owner: "Content Systems", p95: 238, errorRate: "0.14%", deployedAt: "23 Aug, 04:47" },
{ id: "webhooks-eu", service: "Partner webhooks", environment: "Production", region: "eu-central-1", version: "v5.7.3", status: "Monitoring", owner: "Integrations", p95: 279, errorRate: "0.44%", deployedAt: "23 Aug, 04:23" },
{ id: "ledger-us", service: "Payments ledger", environment: "Production", region: "us-east-1", version: "v14.2.0", status: "Healthy", owner: "Payments", p95: 167, errorRate: "0.02%", deployedAt: "23 Aug, 03:59" },
{ id: "checkout-stg", service: "Checkout API", environment: "Staging", region: "eu-west-1", version: "v4.19.0-rc.2", status: "Monitoring", owner: "Payments", p95: 211, errorRate: "0.23%", deployedAt: "23 Aug, 03:36" },
{ id: "identity-stg", service: "Identity", environment: "Staging", region: "us-east-1", version: "v7.7.0-rc.1", status: "Healthy", owner: "Platform Identity", p95: 195, errorRate: "0.09%", deployedAt: "23 Aug, 03:12" },
{ id: "catalog-stg", service: "Product catalog", environment: "Staging", region: "eu-west-1", version: "v12.4.0-beta.3", status: "Healthy", owner: "Commerce Core", p95: 104, errorRate: "0.04%", deployedAt: "23 Aug, 02:48" },
{ id: "pricing-stg", service: "Pricing engine", environment: "Staging", region: "us-east-1", version: "v9.15.0-rc.4", status: "Healthy", owner: "Revenue Systems", p95: 226, errorRate: "0.12%", deployedAt: "23 Aug, 02:24" },
{ id: "orders-stg", service: "Order orchestration", environment: "Staging", region: "eu-central-1", version: "v5.23.0-beta.1", status: "Maintenance", owner: "Fulfilment", p95: 318, errorRate: "0.20%", deployedAt: "23 Aug, 02:01" },
{ id: "search-stg", service: "Search gateway", environment: "Staging", region: "ap-southeast-2", version: "v11.9.0-rc.2", status: "Healthy", owner: "Discovery", p95: 133, errorRate: "0.03%", deployedAt: "23 Aug, 01:38" },
{ id: "fraud-stg", service: "Fraud decisioning", environment: "Staging", region: "us-east-1", version: "v3.10.0-beta.5", status: "Monitoring", owner: "Risk", p95: 289, errorRate: "0.51%", deployedAt: "23 Aug, 01:14" },
{ id: "notifications-stg", service: "Notifications", environment: "Staging", region: "eu-west-1", version: "v8.3.0-rc.1", status: "Healthy", owner: "Customer Comms", p95: 118, errorRate: "0.05%", deployedAt: "23 Aug, 00:50" },
{ id: "inventory-stg", service: "Inventory ledger", environment: "Staging", region: "us-west-2", version: "v6.18.0-beta.2", status: "Degraded", owner: "Supply Chain", p95: 463, errorRate: "1.17%", deployedAt: "23 Aug, 00:27" },
{ id: "shipping-stg", service: "Shipping quotes", environment: "Staging", region: "eu-central-1", version: "v10.5.0-rc.3", status: "Healthy", owner: "Logistics", p95: 176, errorRate: "0.08%", deployedAt: "22 Aug, 23:53" },
{ id: "tax-stg", service: "Tax calculation", environment: "Staging", region: "us-east-1", version: "v4.12.0-beta.1", status: "Healthy", owner: "Finance Platform", p95: 207, errorRate: "0.10%", deployedAt: "22 Aug, 23:29" },
{ id: "accounts-stg", service: "Customer accounts", environment: "Staging", region: "ap-southeast-1", version: "v13.1.0-rc.2", status: "Monitoring", owner: "Customer Platform", p95: 247, errorRate: "0.38%", deployedAt: "22 Aug, 23:05" },
{ id: "recommendations-stg", service: "Recommendations", environment: "Staging", region: "eu-west-1", version: "v2.27.0-beta.4", status: "Healthy", owner: "Personalisation", p95: 201, errorRate: "0.15%", deployedAt: "22 Aug, 22:41" },
{ id: "webhooks-stg", service: "Partner webhooks", environment: "Staging", region: "eu-central-1", version: "v5.8.0-rc.1", status: "Healthy", owner: "Integrations", p95: 188, errorRate: "0.07%", deployedAt: "22 Aug, 22:17" },
] satisfies readonly DataGridDemoRow[],
} as const;
import { GrassrootDataGrid } from "@grassroot/ui-angular";
@Component({
selector: "app-data-grid-demo",
standalone: true,
imports: [GrassrootDataGrid],
host: { style: "display: contents" },
template: `
<grassroot-data-grid
[rowData]="fixture.rows"
[columnDefs]="fixture.columns"
[getRowId]="getRowId"
[caption]="fixture.caption"
selectionMode="multiple"
[defaultSelectedIds]="fixture.selectedIds"
[showQuickFilter]="true"
[showExportButton]="true"
/>
`,
})
export class DataGridDemoComponent {
protected readonly fixture = dataGridFixture;
protected readonly getRowId = (row: unknown) => (row as DataGridDemoRow).id;
}
export default DataGridDemoComponent;

Mounted source: apps/grassroot/src/components/svelte/DataGridDemo.svelte

DataGridDemo.svelteplain
<script lang="ts">
type DataGridDemoRow = {
id: string;
service: string;
environment: "Production" | "Staging";
region: string;
version: string;
status: "Healthy" | "Monitoring" | "Degraded" | "Incident" | "Maintenance";
owner: string;
p95: number;
errorRate: string;
deployedAt: string;
};
/**
* Deterministic, anonymised deployment telemetry. The row and column counts
* deliberately exercise vertical virtualization, horizontal overflow,
* status scanning, numeric sorting, filtering, editing, and selection.
*/
const dataGridFixture = {
caption: "Production service health",
selectedIds: ["checkout-eu", "identity-us"],
columns: [
{ key: "service", header: "Service", width: 190, sortable: true, filterable: true },
{ key: "environment", header: "Environment", width: 130, sortable: true, filterable: true, filterType: "set" },
{ key: "region", header: "Region", width: 140, sortable: true, filterable: true, filterType: "set" },
{ key: "version", header: "Version", width: 120, sortable: true },
{ key: "status", header: "Status", width: 140, sortable: true, filterable: true, filterType: "set" },
{ key: "owner", header: "Owner", width: 170, sortable: true, filterable: true },
{ key: "p95", header: "p95 ms", width: 110, sortable: true, filterable: true, filterType: "number", editable: true },
{ key: "errorRate", header: "Error rate", width: 120, sortable: true },
{ key: "deployedAt", header: "Last deployed", width: 180, sortable: true },
],
rows: [
{ id: "checkout-eu", service: "Checkout API", environment: "Production", region: "eu-west-1", version: "v4.18.2", status: "Healthy", owner: "Payments", p95: 182, errorRate: "0.08%", deployedAt: "23 Aug, 09:42" },
{ id: "identity-us", service: "Identity", environment: "Production", region: "us-east-1", version: "v7.6.0", status: "Monitoring", owner: "Platform Identity", p95: 264, errorRate: "0.31%", deployedAt: "23 Aug, 09:18" },
{ id: "catalog-eu", service: "Product catalog", environment: "Production", region: "eu-west-1", version: "v12.3.1", status: "Healthy", owner: "Commerce Core", p95: 96, errorRate: "0.03%", deployedAt: "23 Aug, 08:55" },
{ id: "pricing-us", service: "Pricing engine", environment: "Production", region: "us-east-1", version: "v9.14.4", status: "Degraded", owner: "Revenue Systems", p95: 487, errorRate: "1.42%", deployedAt: "23 Aug, 08:34" },
{ id: "orders-eu", service: "Order orchestration", environment: "Production", region: "eu-central-1", version: "v5.22.0", status: "Healthy", owner: "Fulfilment", p95: 218, errorRate: "0.11%", deployedAt: "23 Aug, 08:12" },
{ id: "search-ap", service: "Search gateway", environment: "Production", region: "ap-southeast-2", version: "v11.8.3", status: "Healthy", owner: "Discovery", p95: 144, errorRate: "0.05%", deployedAt: "23 Aug, 07:49" },
{ id: "fraud-us", service: "Fraud decisioning", environment: "Production", region: "us-east-1", version: "v3.9.7", status: "Incident", owner: "Risk", p95: 732, errorRate: "4.86%", deployedAt: "23 Aug, 07:31" },
{ id: "notifications-eu", service: "Notifications", environment: "Production", region: "eu-west-1", version: "v8.2.5", status: "Healthy", owner: "Customer Comms", p95: 121, errorRate: "0.07%", deployedAt: "23 Aug, 07:06" },
{ id: "inventory-us", service: "Inventory ledger", environment: "Production", region: "us-west-2", version: "v6.17.1", status: "Monitoring", owner: "Supply Chain", p95: 305, errorRate: "0.62%", deployedAt: "23 Aug, 06:44" },
{ id: "shipping-eu", service: "Shipping quotes", environment: "Production", region: "eu-central-1", version: "v10.4.0", status: "Healthy", owner: "Logistics", p95: 198, errorRate: "0.09%", deployedAt: "23 Aug, 06:20" },
{ id: "tax-us", service: "Tax calculation", environment: "Production", region: "us-east-1", version: "v4.11.9", status: "Maintenance", owner: "Finance Platform", p95: 356, errorRate: "0.18%", deployedAt: "23 Aug, 05:58" },
{ id: "accounts-ap", service: "Customer accounts", environment: "Production", region: "ap-southeast-1", version: "v13.0.2", status: "Healthy", owner: "Customer Platform", p95: 173, errorRate: "0.06%", deployedAt: "23 Aug, 05:35" },
{ id: "recommendations-eu", service: "Recommendations", environment: "Production", region: "eu-west-1", version: "v2.26.6", status: "Degraded", owner: "Personalisation", p95: 541, errorRate: "1.08%", deployedAt: "23 Aug, 05:11" },
{ id: "media-us", service: "Media pipeline", environment: "Production", region: "us-west-2", version: "v7.12.8", status: "Healthy", owner: "Content Systems", p95: 238, errorRate: "0.14%", deployedAt: "23 Aug, 04:47" },
{ id: "webhooks-eu", service: "Partner webhooks", environment: "Production", region: "eu-central-1", version: "v5.7.3", status: "Monitoring", owner: "Integrations", p95: 279, errorRate: "0.44%", deployedAt: "23 Aug, 04:23" },
{ id: "ledger-us", service: "Payments ledger", environment: "Production", region: "us-east-1", version: "v14.2.0", status: "Healthy", owner: "Payments", p95: 167, errorRate: "0.02%", deployedAt: "23 Aug, 03:59" },
{ id: "checkout-stg", service: "Checkout API", environment: "Staging", region: "eu-west-1", version: "v4.19.0-rc.2", status: "Monitoring", owner: "Payments", p95: 211, errorRate: "0.23%", deployedAt: "23 Aug, 03:36" },
{ id: "identity-stg", service: "Identity", environment: "Staging", region: "us-east-1", version: "v7.7.0-rc.1", status: "Healthy", owner: "Platform Identity", p95: 195, errorRate: "0.09%", deployedAt: "23 Aug, 03:12" },
{ id: "catalog-stg", service: "Product catalog", environment: "Staging", region: "eu-west-1", version: "v12.4.0-beta.3", status: "Healthy", owner: "Commerce Core", p95: 104, errorRate: "0.04%", deployedAt: "23 Aug, 02:48" },
{ id: "pricing-stg", service: "Pricing engine", environment: "Staging", region: "us-east-1", version: "v9.15.0-rc.4", status: "Healthy", owner: "Revenue Systems", p95: 226, errorRate: "0.12%", deployedAt: "23 Aug, 02:24" },
{ id: "orders-stg", service: "Order orchestration", environment: "Staging", region: "eu-central-1", version: "v5.23.0-beta.1", status: "Maintenance", owner: "Fulfilment", p95: 318, errorRate: "0.20%", deployedAt: "23 Aug, 02:01" },
{ id: "search-stg", service: "Search gateway", environment: "Staging", region: "ap-southeast-2", version: "v11.9.0-rc.2", status: "Healthy", owner: "Discovery", p95: 133, errorRate: "0.03%", deployedAt: "23 Aug, 01:38" },
{ id: "fraud-stg", service: "Fraud decisioning", environment: "Staging", region: "us-east-1", version: "v3.10.0-beta.5", status: "Monitoring", owner: "Risk", p95: 289, errorRate: "0.51%", deployedAt: "23 Aug, 01:14" },
{ id: "notifications-stg", service: "Notifications", environment: "Staging", region: "eu-west-1", version: "v8.3.0-rc.1", status: "Healthy", owner: "Customer Comms", p95: 118, errorRate: "0.05%", deployedAt: "23 Aug, 00:50" },
{ id: "inventory-stg", service: "Inventory ledger", environment: "Staging", region: "us-west-2", version: "v6.18.0-beta.2", status: "Degraded", owner: "Supply Chain", p95: 463, errorRate: "1.17%", deployedAt: "23 Aug, 00:27" },
{ id: "shipping-stg", service: "Shipping quotes", environment: "Staging", region: "eu-central-1", version: "v10.5.0-rc.3", status: "Healthy", owner: "Logistics", p95: 176, errorRate: "0.08%", deployedAt: "22 Aug, 23:53" },
{ id: "tax-stg", service: "Tax calculation", environment: "Staging", region: "us-east-1", version: "v4.12.0-beta.1", status: "Healthy", owner: "Finance Platform", p95: 207, errorRate: "0.10%", deployedAt: "22 Aug, 23:29" },
{ id: "accounts-stg", service: "Customer accounts", environment: "Staging", region: "ap-southeast-1", version: "v13.1.0-rc.2", status: "Monitoring", owner: "Customer Platform", p95: 247, errorRate: "0.38%", deployedAt: "22 Aug, 23:05" },
{ id: "recommendations-stg", service: "Recommendations", environment: "Staging", region: "eu-west-1", version: "v2.27.0-beta.4", status: "Healthy", owner: "Personalisation", p95: 201, errorRate: "0.15%", deployedAt: "22 Aug, 22:41" },
{ id: "webhooks-stg", service: "Partner webhooks", environment: "Staging", region: "eu-central-1", version: "v5.8.0-rc.1", status: "Healthy", owner: "Integrations", p95: 188, errorRate: "0.07%", deployedAt: "22 Aug, 22:17" },
] satisfies readonly DataGridDemoRow[],
} as const;
import { DataGrid } from "@grassroot/ui-svelte";
const getRowId = (row: unknown) => (row as DataGridDemoRow).id;
</script>
<DataGrid
rowData={dataGridFixture.rows}
columnDefs={dataGridFixture.columns}
getRowId={getRowId}
caption={dataGridFixture.caption}
selectionMode="multiple"
defaultSelectedIds={dataGridFixture.selectedIds}
showQuickFilter
showExportButton
/>

Mounted source: apps/grassroot/src/components/solid/DataGridDemo.tsx

DataGridDemo.tsxts
/** @jsxImportSource solid-js */
type DataGridDemoRow = {
id: string;
service: string;
environment: "Production" | "Staging";
region: string;
version: string;
status: "Healthy" | "Monitoring" | "Degraded" | "Incident" | "Maintenance";
owner: string;
p95: number;
errorRate: string;
deployedAt: string;
};
/**
* Deterministic, anonymised deployment telemetry. The row and column counts
* deliberately exercise vertical virtualization, horizontal overflow,
* status scanning, numeric sorting, filtering, editing, and selection.
*/
const dataGridFixture = {
caption: "Production service health",
selectedIds: ["checkout-eu", "identity-us"],
columns: [
{ key: "service", header: "Service", width: 190, sortable: true, filterable: true },
{ key: "environment", header: "Environment", width: 130, sortable: true, filterable: true, filterType: "set" },
{ key: "region", header: "Region", width: 140, sortable: true, filterable: true, filterType: "set" },
{ key: "version", header: "Version", width: 120, sortable: true },
{ key: "status", header: "Status", width: 140, sortable: true, filterable: true, filterType: "set" },
{ key: "owner", header: "Owner", width: 170, sortable: true, filterable: true },
{ key: "p95", header: "p95 ms", width: 110, sortable: true, filterable: true, filterType: "number", editable: true },
{ key: "errorRate", header: "Error rate", width: 120, sortable: true },
{ key: "deployedAt", header: "Last deployed", width: 180, sortable: true },
],
rows: [
{ id: "checkout-eu", service: "Checkout API", environment: "Production", region: "eu-west-1", version: "v4.18.2", status: "Healthy", owner: "Payments", p95: 182, errorRate: "0.08%", deployedAt: "23 Aug, 09:42" },
{ id: "identity-us", service: "Identity", environment: "Production", region: "us-east-1", version: "v7.6.0", status: "Monitoring", owner: "Platform Identity", p95: 264, errorRate: "0.31%", deployedAt: "23 Aug, 09:18" },
{ id: "catalog-eu", service: "Product catalog", environment: "Production", region: "eu-west-1", version: "v12.3.1", status: "Healthy", owner: "Commerce Core", p95: 96, errorRate: "0.03%", deployedAt: "23 Aug, 08:55" },
{ id: "pricing-us", service: "Pricing engine", environment: "Production", region: "us-east-1", version: "v9.14.4", status: "Degraded", owner: "Revenue Systems", p95: 487, errorRate: "1.42%", deployedAt: "23 Aug, 08:34" },
{ id: "orders-eu", service: "Order orchestration", environment: "Production", region: "eu-central-1", version: "v5.22.0", status: "Healthy", owner: "Fulfilment", p95: 218, errorRate: "0.11%", deployedAt: "23 Aug, 08:12" },
{ id: "search-ap", service: "Search gateway", environment: "Production", region: "ap-southeast-2", version: "v11.8.3", status: "Healthy", owner: "Discovery", p95: 144, errorRate: "0.05%", deployedAt: "23 Aug, 07:49" },
{ id: "fraud-us", service: "Fraud decisioning", environment: "Production", region: "us-east-1", version: "v3.9.7", status: "Incident", owner: "Risk", p95: 732, errorRate: "4.86%", deployedAt: "23 Aug, 07:31" },
{ id: "notifications-eu", service: "Notifications", environment: "Production", region: "eu-west-1", version: "v8.2.5", status: "Healthy", owner: "Customer Comms", p95: 121, errorRate: "0.07%", deployedAt: "23 Aug, 07:06" },
{ id: "inventory-us", service: "Inventory ledger", environment: "Production", region: "us-west-2", version: "v6.17.1", status: "Monitoring", owner: "Supply Chain", p95: 305, errorRate: "0.62%", deployedAt: "23 Aug, 06:44" },
{ id: "shipping-eu", service: "Shipping quotes", environment: "Production", region: "eu-central-1", version: "v10.4.0", status: "Healthy", owner: "Logistics", p95: 198, errorRate: "0.09%", deployedAt: "23 Aug, 06:20" },
{ id: "tax-us", service: "Tax calculation", environment: "Production", region: "us-east-1", version: "v4.11.9", status: "Maintenance", owner: "Finance Platform", p95: 356, errorRate: "0.18%", deployedAt: "23 Aug, 05:58" },
{ id: "accounts-ap", service: "Customer accounts", environment: "Production", region: "ap-southeast-1", version: "v13.0.2", status: "Healthy", owner: "Customer Platform", p95: 173, errorRate: "0.06%", deployedAt: "23 Aug, 05:35" },
{ id: "recommendations-eu", service: "Recommendations", environment: "Production", region: "eu-west-1", version: "v2.26.6", status: "Degraded", owner: "Personalisation", p95: 541, errorRate: "1.08%", deployedAt: "23 Aug, 05:11" },
{ id: "media-us", service: "Media pipeline", environment: "Production", region: "us-west-2", version: "v7.12.8", status: "Healthy", owner: "Content Systems", p95: 238, errorRate: "0.14%", deployedAt: "23 Aug, 04:47" },
{ id: "webhooks-eu", service: "Partner webhooks", environment: "Production", region: "eu-central-1", version: "v5.7.3", status: "Monitoring", owner: "Integrations", p95: 279, errorRate: "0.44%", deployedAt: "23 Aug, 04:23" },
{ id: "ledger-us", service: "Payments ledger", environment: "Production", region: "us-east-1", version: "v14.2.0", status: "Healthy", owner: "Payments", p95: 167, errorRate: "0.02%", deployedAt: "23 Aug, 03:59" },
{ id: "checkout-stg", service: "Checkout API", environment: "Staging", region: "eu-west-1", version: "v4.19.0-rc.2", status: "Monitoring", owner: "Payments", p95: 211, errorRate: "0.23%", deployedAt: "23 Aug, 03:36" },
{ id: "identity-stg", service: "Identity", environment: "Staging", region: "us-east-1", version: "v7.7.0-rc.1", status: "Healthy", owner: "Platform Identity", p95: 195, errorRate: "0.09%", deployedAt: "23 Aug, 03:12" },
{ id: "catalog-stg", service: "Product catalog", environment: "Staging", region: "eu-west-1", version: "v12.4.0-beta.3", status: "Healthy", owner: "Commerce Core", p95: 104, errorRate: "0.04%", deployedAt: "23 Aug, 02:48" },
{ id: "pricing-stg", service: "Pricing engine", environment: "Staging", region: "us-east-1", version: "v9.15.0-rc.4", status: "Healthy", owner: "Revenue Systems", p95: 226, errorRate: "0.12%", deployedAt: "23 Aug, 02:24" },
{ id: "orders-stg", service: "Order orchestration", environment: "Staging", region: "eu-central-1", version: "v5.23.0-beta.1", status: "Maintenance", owner: "Fulfilment", p95: 318, errorRate: "0.20%", deployedAt: "23 Aug, 02:01" },
{ id: "search-stg", service: "Search gateway", environment: "Staging", region: "ap-southeast-2", version: "v11.9.0-rc.2", status: "Healthy", owner: "Discovery", p95: 133, errorRate: "0.03%", deployedAt: "23 Aug, 01:38" },
{ id: "fraud-stg", service: "Fraud decisioning", environment: "Staging", region: "us-east-1", version: "v3.10.0-beta.5", status: "Monitoring", owner: "Risk", p95: 289, errorRate: "0.51%", deployedAt: "23 Aug, 01:14" },
{ id: "notifications-stg", service: "Notifications", environment: "Staging", region: "eu-west-1", version: "v8.3.0-rc.1", status: "Healthy", owner: "Customer Comms", p95: 118, errorRate: "0.05%", deployedAt: "23 Aug, 00:50" },
{ id: "inventory-stg", service: "Inventory ledger", environment: "Staging", region: "us-west-2", version: "v6.18.0-beta.2", status: "Degraded", owner: "Supply Chain", p95: 463, errorRate: "1.17%", deployedAt: "23 Aug, 00:27" },
{ id: "shipping-stg", service: "Shipping quotes", environment: "Staging", region: "eu-central-1", version: "v10.5.0-rc.3", status: "Healthy", owner: "Logistics", p95: 176, errorRate: "0.08%", deployedAt: "22 Aug, 23:53" },
{ id: "tax-stg", service: "Tax calculation", environment: "Staging", region: "us-east-1", version: "v4.12.0-beta.1", status: "Healthy", owner: "Finance Platform", p95: 207, errorRate: "0.10%", deployedAt: "22 Aug, 23:29" },
{ id: "accounts-stg", service: "Customer accounts", environment: "Staging", region: "ap-southeast-1", version: "v13.1.0-rc.2", status: "Monitoring", owner: "Customer Platform", p95: 247, errorRate: "0.38%", deployedAt: "22 Aug, 23:05" },
{ id: "recommendations-stg", service: "Recommendations", environment: "Staging", region: "eu-west-1", version: "v2.27.0-beta.4", status: "Healthy", owner: "Personalisation", p95: 201, errorRate: "0.15%", deployedAt: "22 Aug, 22:41" },
{ id: "webhooks-stg", service: "Partner webhooks", environment: "Staging", region: "eu-central-1", version: "v5.8.0-rc.1", status: "Healthy", owner: "Integrations", p95: 188, errorRate: "0.07%", deployedAt: "22 Aug, 22:17" },
] satisfies readonly DataGridDemoRow[],
} as const;
import { DataGrid } from "@grassroot/ui-solid";
const getRowId = (row: unknown) => (row as DataGridDemoRow).id;
export default function DataGridDemo() {
return (
<DataGrid
rowData={dataGridFixture.rows}
columnDefs={dataGridFixture.columns}
getRowId={getRowId}
caption={dataGridFixture.caption}
selectionMode="multiple"
defaultSelectedIds={dataGridFixture.selectedIds}
showQuickFilter
showExportButton
/>
);
}

Chart workbench

Use the mounted LineChart workbench for selection, brush and resettable zoom, replacement data, SVG/CSV export, and its accessible table.

Required states

  • Point selection
  • Brush drag
  • Reset zoom
  • Replacement source
  • SVG/CSV and accessible table
React example follows below.

Mounted source: apps/grassroot/src/components/react/ChartDemoShell.tsx

ChartDemoShell.tsxts
import { useEffect, useLayoutEffect, useMemo, useRef, useState, type ReactNode } from "react";
import { attachDemoChartMachine, type DemoChartMachine } from "../../site/chart-demo-machine";
import { createChartDataSession } from "@grassroot/ui-headless-core";
import type { ChartXDomain } from "@grassroot/ui-machines/chart-shell";
import {
applyShellControl,
bumpReplay,
capabilitiesFor,
defaultState,
downloadLineChartCsv,
downloadLineChartSvg,
failingLineChartWorkbenchSource,
formatLineChartDomain,
isLineChartWorkbench,
lineChartWorkbenchSnapshot,
shellControlRows,
toChartProps,
type ChartDemoShellState,
} from "../../site/chart-demo-shell";
export function ChartDemoShell({
name,
children,
}: {
name: string;
children: (props: Record<string, unknown>) => ReactNode;
}) {
const caps = useMemo(() => capabilitiesFor(name), [name]);
const [state, setState] = useState<ChartDemoShellState>(() => defaultState(caps));
const [optionsOpen, setOptionsOpen] = useState(false);
const stateRef = useRef(state);
stateRef.current = state;
const plotRef = useRef<HTMLDivElement>(null);
const machineRef = useRef<DemoChartMachine | null>(null);
const rows = shellControlRows(caps, state);
const chartProps = toChartProps(state, caps);
const workbench = isLineChartWorkbench(name);
const [series, setSeries] = useState(lineChartWorkbenchSnapshot);
const [status, setStatus] = useState<"loading" | "ready" | "error">("ready");
const [selectedIds, setSelectedIds] = useState<readonly string[]>([]);
const [brushDomain, setBrushDomain] = useState<ChartXDomain>(null);
const [zoomDomain, setZoomDomain] = useState<ChartXDomain>(null);
const dataSession = useRef<ReturnType<typeof createChartDataSession> | null>(null);
useEffect(() => {
if (!workbench) return;
const session = createChartDataSession({ onData: setSeries, onStatus: setStatus });
dataSession.current = session;
session.replace(Promise.resolve(lineChartWorkbenchSnapshot()));
return () => {
session.dispose();
dataSession.current = null;
};
}, [workbench]);
useLayoutEffect(() => {
const plot = plotRef.current;
if (!plot || !caps.machine || workbench) return;
const session = attachDemoChartMachine(plot, () => stateRef.current);
machineRef.current = session;
return () => {
session.destroy();
machineRef.current = null;
};
}, [caps.machine, name, workbench]);
useLayoutEffect(() => {
machineRef.current?.syncInput();
}, [state.selection]);
// Replay restarts the entrance. The entrance is a CSS animation applied on
// mount, so bumping `replayToken` alone changed a prop and nothing else —
// the animation had already run to completion and CSS will not replay a
// running-then-finished animation without being told. Strip the class, force
// a reflow so the removal is committed, then put it back: the standard
// restart idiom, and cheaper than remounting the chart, which would also
// throw away the reader's hidden series and selection.
useLayoutEffect(() => {
if (state.replayToken === 0 || state.motion === "off") return;
const scene = plotRef.current?.querySelector<SVGElement>(".vs-chart-enter-fade");
if (!scene) return;
scene.classList.remove("vs-chart-enter-fade");
void scene.getBoundingClientRect();
scene.classList.add("vs-chart-enter-fade");
}, [state.replayToken, state.motion]);
if (workbench) {
const workbenchProps = {
...chartProps,
series,
selectionMode: "multiple" as const,
selectedIds,
onSelectedIdsChange: setSelectedIds,
brushDomain,
onBrushDomainChange: setBrushDomain,
zoomDomain,
onZoomDomainChange: setZoomDomain,
zoomOnBrush: true,
};
const replacePending = () => dataSession.current?.replace(failingLineChartWorkbenchSource());
const retry = () => dataSession.current?.replace(Promise.resolve(lineChartWorkbenchSnapshot()));
return (
<div className="gr-demo-shell" data-demo-shell data-chart-workbench>
<div className="gr-demo-shell__controls">
<div className="gr-demo-variant"><span className="gr-demo-variant__label">Point selection</span><output data-workbench-selection>{selectedIds.length} selected</output><button type="button" onClick={() => setSelectedIds([])}>Clear selection</button></div>
<div className="gr-demo-variant"><span className="gr-demo-variant__label">Brush drag</span><output data-workbench-brush>{formatLineChartDomain(brushDomain)}</output></div>
<div className="gr-demo-variant"><span className="gr-demo-variant__label">Reset zoom</span><output data-workbench-zoom>{formatLineChartDomain(zoomDomain)}</output><button type="button" onClick={() => { setBrushDomain(null); setZoomDomain(null); }}>Reset zoom</button></div>
<div className="gr-demo-variant"><span className="gr-demo-variant__label">Pending source</span><button type="button" onClick={replacePending}>Swap source</button></div>
<div className="gr-demo-variant"><span className="gr-demo-variant__label">Export SVG/CSV</span><button type="button" onClick={() => plotRef.current && downloadLineChartSvg(plotRef.current)}>Export SVG</button><button type="button" onClick={() => downloadLineChartCsv(series)}>Export CSV</button></div>
</div>
{status === "loading" ? <div role="status">Loading chart data</div> : null}
{status === "error" ? <div role="alert">Unable to load chart data. <button type="button" onClick={retry}>Retry</button></div> : null}
<div className="gr-demo-shell__chart" ref={plotRef}>{children(workbenchProps)}</div>
</div>
);
}
if (!caps.any) return children({});
return (
<div
className="gr-demo-shell"
data-demo-shell
data-options-open={optionsOpen ? "true" : "false"}
>
<div className="gr-demo-shell__controls">
{rows.map((row) => (
<div className="gr-demo-variant" key={row.id}>
<span className="gr-demo-variant__label">{row.label}</span>
<div className="gr-demo-toggles" role="group" aria-label={row.label}>
{row.options.map((option) => (
<button
key={option.value}
type="button"
aria-pressed={row.value === option.value}
onClick={() =>
setState((current) => applyShellControl(current, row.id, option.value))
}
>
{option.label}
</button>
))}
{row.action ? (
<button type="button" onClick={() => setState(bumpReplay)}>
{row.action.label}
</button>
) : null}
</div>
</div>
))}
</div>
<button
className="gr-demo-shell__options-toggle"
type="button"
aria-expanded={optionsOpen}
onClick={() => setOptionsOpen((open) => !open)}
>
Chart options
</button>
<div className="gr-demo-shell__chart" ref={plotRef}>
{children(chartProps)}
</div>
</div>
);
}

Mounted source: apps/grassroot/src/components/vue/ChartDemoShell.vue

ChartDemoShell.vueplain
<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref, watch, type Component } from "vue";
import { attachDemoChartMachine, type DemoChartMachine } from "../../site/chart-demo-machine";
import { createChartDataSession } from "@grassroot/ui-headless-core";
import type { ChartSeries } from "@grassroot/chart-spec";
import type { ChartXDomain } from "@grassroot/ui-machines/chart-shell";
import {
applyShellControl,
bumpReplay,
capabilitiesFor,
defaultState,
downloadLineChartCsv,
downloadLineChartSvg,
failingLineChartWorkbenchSource,
formatLineChartDomain,
isLineChartWorkbench,
lineChartWorkbenchSnapshot,
shellControlRows,
toChartProps,
} from "../../site/chart-demo-shell";
const props = defineProps<{ name: string; demo: Component }>();
const caps = computed(() => capabilitiesFor(props.name));
const state = ref(defaultState(caps.value));
const optionsOpen = ref(false);
const plot = ref<HTMLElement | null>(null);
let session: DemoChartMachine | null = null;
watch(
() => props.name,
(name) => {
state.value = defaultState(capabilitiesFor(name));
},
);
const rows = computed(() => shellControlRows(caps.value, state.value));
const chartProps = computed(() => toChartProps(state.value, caps.value));
const workbench = computed(() => isLineChartWorkbench(props.name));
const series = ref<readonly ChartSeries[]>(lineChartWorkbenchSnapshot());
const status = ref<"loading" | "ready" | "error">("ready");
const selectedIds = ref<readonly string[]>([]);
const brushDomain = ref<ChartXDomain>(null);
const zoomDomain = ref<ChartXDomain>(null);
const workbenchProps = computed(() => ({
...chartProps.value,
series: series.value,
selectionMode: "multiple" as const,
selectedIds: selectedIds.value,
onSelectedIdsChange: (ids: readonly string[]) => { selectedIds.value = ids; },
brushDomain: brushDomain.value,
onBrushDomainChange: (domain: ChartXDomain) => { brushDomain.value = domain; },
zoomDomain: zoomDomain.value,
onZoomDomainChange: (domain: ChartXDomain) => { zoomDomain.value = domain; },
zoomOnBrush: true,
}));
let dataSession: ReturnType<typeof createChartDataSession> | null = null;
onMounted(() => {
if (!workbench.value) return;
dataSession = createChartDataSession({
onData: (snapshot) => { series.value = snapshot; },
onStatus: (next) => { status.value = next; },
});
dataSession.replace(Promise.resolve(lineChartWorkbenchSnapshot()));
});
function replacePending() { dataSession?.replace(failingLineChartWorkbenchSource()); }
function retry() { dataSession?.replace(Promise.resolve(lineChartWorkbenchSnapshot())); }
function resetZoom() { brushDomain.value = null; zoomDomain.value = null; }
function setChoice(rowId: string, value: string) {
state.value = applyShellControl(state.value, rowId, value);
}
function runAction() {
state.value = bumpReplay(state.value);
}
watch([plot, () => caps.value.machine, () => props.name], ([host, machine]) => {
session?.destroy();
session = null;
if (!host || !machine || workbench.value) return;
session = attachDemoChartMachine(host, () => state.value);
session.syncInput();
});
watch(
() => state.value.selection,
() => session?.syncInput(),
);
onBeforeUnmount(() => {
session?.destroy();
session = null;
dataSession?.dispose();
dataSession = null;
});
</script>
<template>
<div v-if="workbench" class="gr-demo-shell" data-demo-shell data-chart-workbench>
<div class="gr-demo-shell__controls">
<div class="gr-demo-variant"><span class="gr-demo-variant__label">Point selection</span><output data-workbench-selection>{{ selectedIds.length }} selected</output><button type="button" @click="selectedIds = []">Clear selection</button></div>
<div class="gr-demo-variant"><span class="gr-demo-variant__label">Brush drag</span><output data-workbench-brush>{{ formatLineChartDomain(brushDomain) }}</output></div>
<div class="gr-demo-variant"><span class="gr-demo-variant__label">Reset zoom</span><output data-workbench-zoom>{{ formatLineChartDomain(zoomDomain) }}</output><button type="button" @click="resetZoom">Reset zoom</button></div>
<div class="gr-demo-variant"><span class="gr-demo-variant__label">Pending source</span><button type="button" @click="replacePending">Swap source</button></div>
<div class="gr-demo-variant"><span class="gr-demo-variant__label">Export SVG/CSV</span><button type="button" @click="plot && downloadLineChartSvg(plot)">Export SVG</button><button type="button" @click="downloadLineChartCsv(series)">Export CSV</button></div>
</div>
<div v-if="status === 'loading'" role="status">Loading chart data</div>
<div v-if="status === 'error'" role="alert">Unable to load chart data. <button type="button" @click="retry">Retry</button></div>
<div class="gr-demo-shell__chart" ref="plot">
<component :is="demo" v-bind="workbenchProps" />
</div>
</div>
<div
v-else
:class="caps.any ? 'gr-demo-shell' : undefined"
:data-demo-shell="caps.any ? '' : undefined"
:data-options-open="caps.any ? String(optionsOpen) : undefined"
>
<template v-if="caps.any">
<div class="gr-demo-shell__controls">
<div v-for="row in rows" :key="row.id" class="gr-demo-variant">
<span class="gr-demo-variant__label">{{ row.label }}</span>
<div class="gr-demo-toggles" role="group" :aria-label="row.label">
<button
v-for="option in row.options"
:key="option.value"
type="button"
:aria-pressed="row.value === option.value"
@click="setChoice(row.id, option.value)"
>
{{ option.label }}
</button>
<button v-if="row.action" type="button" @click="runAction()">
{{ row.action.label }}
</button>
</div>
</div>
</div>
<button
class="gr-demo-shell__options-toggle"
type="button"
:aria-expanded="optionsOpen"
@click="optionsOpen = !optionsOpen"
>
Chart options
</button>
<div class="gr-demo-shell__chart" ref="plot">
<component :is="demo" v-bind="chartProps" />
</div>
</template>
<component v-else :is="demo" />
</div>
</template>

Mounted source: apps/grassroot/src/components/angular/ChartDemoShell.component.ts

ChartDemoShell.component.tsts
import { NgComponentOutlet } from "@angular/common";
import {
afterNextRender,
ChangeDetectionStrategy,
Component,
computed,
effect,
ElementRef,
input,
signal,
viewChild,
type OnDestroy,
type Type,
} from "@angular/core";
import { provideChartTheme } from "@grassroot/charts-angular";
import { attachDemoChartMachine, type DemoChartMachine } from "../../site/chart-demo-machine";
import { createChartDataSession } from "@grassroot/ui-headless-core";
import type { ChartSeries } from "@grassroot/chart-spec";
import type { ChartXDomain } from "@grassroot/ui-machines/chart-shell";
import {
applyShellControl,
bumpReplay,
capabilitiesFor,
defaultState,
downloadLineChartCsv,
downloadLineChartSvg,
failingLineChartWorkbenchSource,
formatLineChartDomain,
isLineChartWorkbench,
lineChartWorkbenchSnapshot,
DOCS_CHART_THEME,
shellControlRows,
toChartProps,
type ChartDemoShellState,
} from "../../site/chart-demo-shell";
@Component({
selector: "chart-demo-shell",
standalone: true,
imports: [NgComponentOutlet],
providers: [provideChartTheme(DOCS_CHART_THEME)],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
@if (workbench()) {
<div class="gr-demo-shell" data-demo-shell data-chart-workbench>
<div class="gr-demo-shell__controls">
<div class="gr-demo-variant"><span class="gr-demo-variant__label">Point selection</span><output data-workbench-selection>{{ selectedIds().length }} selected</output><button type="button" (click)="clearSelection()">Clear selection</button></div>
<div class="gr-demo-variant"><span class="gr-demo-variant__label">Brush drag</span><output data-workbench-brush>{{ formatDomain(brushDomain()) }}</output></div>
<div class="gr-demo-variant"><span class="gr-demo-variant__label">Reset zoom</span><output data-workbench-zoom>{{ formatDomain(zoomDomain()) }}</output><button type="button" (click)="resetZoom()">Reset zoom</button></div>
<div class="gr-demo-variant"><span class="gr-demo-variant__label">Pending source</span><button type="button" (click)="replacePending()">Swap source</button></div>
<div class="gr-demo-variant"><span class="gr-demo-variant__label">Export SVG/CSV</span><button type="button" (click)="exportSvg()">Export SVG</button><button type="button" (click)="exportCsv()">Export CSV</button></div>
</div>
@if (status() === "loading") { <div role="status">Loading chart data</div> }
@if (status() === "error") { <div role="alert">Unable to load chart data. <button type="button" (click)="retry()">Retry</button></div> }
<div class="gr-demo-shell__chart" #plot>
@if (demo(); as component) { <ng-container *ngComponentOutlet="component; inputs: demoInputs()" /> }
</div>
</div>
} @else if (caps().any) {
<div class="gr-demo-shell" data-demo-shell [attr.data-options-open]="optionsOpen()">
<div class="gr-demo-shell__controls">
@for (row of rows(); track row.id) {
<div class="gr-demo-variant">
<span class="gr-demo-variant__label">{{ row.label }}</span>
<div class="gr-demo-toggles" role="group" [attr.aria-label]="row.label">
@for (option of row.options; track option.value) {
<button
type="button"
[attr.aria-pressed]="row.value === option.value"
(click)="setChoice(row.id, option.value)"
>
{{ option.label }}
</button>
}
@if (row.action) {
<button type="button" (click)="runAction()">
{{ row.action.label }}
</button>
}
</div>
</div>
}
</div>
<button
class="gr-demo-shell__options-toggle"
type="button"
[attr.aria-expanded]="optionsOpen()"
(click)="optionsOpen.update((open) => !open)"
>
Chart options
</button>
<div class="gr-demo-shell__chart" #plot>
@if (demo(); as component) {
<ng-container *ngComponentOutlet="component; inputs: demoInputs()" />
}
</div>
</div>
} @else if (demo(); as component) {
<ng-container *ngComponentOutlet="component" />
}
`,
})
export class ChartDemoShellComponent implements OnDestroy {
protected readonly formatDomain = formatLineChartDomain;
readonly name = input.required<string>();
readonly demo = input<Type<unknown> | null>(null);
private readonly plot = viewChild<ElementRef<HTMLElement>>("plot");
private readonly plotReady = signal(false);
protected readonly caps = computed(() => capabilitiesFor(this.name()));
protected readonly workbench = computed(() => isLineChartWorkbench(this.name()));
protected readonly state = signal<ChartDemoShellState>(defaultState(capabilitiesFor("")));
protected readonly optionsOpen = signal(false);
protected readonly rows = computed(() => shellControlRows(this.caps(), this.state()));
protected readonly series = signal<readonly ChartSeries[]>(lineChartWorkbenchSnapshot());
protected readonly status = signal<"loading" | "ready" | "error">("ready");
protected readonly selectedIds = signal<readonly string[]>([]);
protected readonly brushDomain = signal<ChartXDomain>(null);
protected readonly zoomDomain = signal<ChartXDomain>(null);
protected readonly demoInputs = computed(() => ({ shell: this.workbench()
? { ...toChartProps(this.state(), this.caps()), series: this.series(), selectionMode: "multiple", selectedIds: this.selectedIds(), onSelectedIdsChange: (ids: readonly string[]) => this.selectedIds.set(ids), brushDomain: this.brushDomain(), onBrushDomainChange: (domain: ChartXDomain) => this.brushDomain.set(domain), zoomDomain: this.zoomDomain(), onZoomDomainChange: (domain: ChartXDomain) => this.zoomDomain.set(domain), zoomOnBrush: true }
: toChartProps(this.state(), this.caps()) }));
private session: DemoChartMachine | null = null;
private dataSession: ReturnType<typeof createChartDataSession> | null = null;
constructor() {
afterNextRender(() => this.plotReady.set(true));
effect(() => {
this.state.set(defaultState(capabilitiesFor(this.name())));
});
effect((onCleanup) => {
this.plotReady();
this.name();
this.demo();
const host = this.plot()?.nativeElement;
if (!host || !this.caps().machine || this.workbench()) return;
const session = attachDemoChartMachine(host, () => this.state());
this.session = session;
onCleanup(() => {
session.destroy();
if (this.session === session) this.session = null;
});
});
effect(() => {
this.state().selection;
this.session?.syncInput();
});
effect((onCleanup) => {
if (!this.workbench()) return;
const session = createChartDataSession({
onData: (snapshot) => this.series.set(snapshot),
onStatus: (status) => this.status.set(status),
});
this.dataSession = session;
session.replace(Promise.resolve(lineChartWorkbenchSnapshot()));
onCleanup(() => {
session.dispose();
if (this.dataSession === session) this.dataSession = null;
});
});
}
ngOnDestroy(): void {
this.session?.destroy();
this.session = null;
this.dataSession?.dispose();
this.dataSession = null;
}
protected setChoice(rowId: string, value: string): void {
this.state.update((current) => applyShellControl(current, rowId, value));
}
protected runAction(): void {
this.state.update(bumpReplay);
}
protected clearSelection(): void { this.selectedIds.set([]); }
protected resetZoom(): void { this.brushDomain.set(null); this.zoomDomain.set(null); }
protected replacePending(): void { this.dataSession?.replace(failingLineChartWorkbenchSource()); }
protected retry(): void { this.dataSession?.replace(Promise.resolve(lineChartWorkbenchSnapshot())); }
protected exportSvg(): void { const host = this.plot()?.nativeElement; if (host) downloadLineChartSvg(host); }
protected exportCsv(): void { downloadLineChartCsv(this.series()); }
}

Mounted source: apps/grassroot/src/components/svelte/ChartDemoShell.svelte

ChartDemoShell.svelteplain
<script lang="ts">
import { onMount, type Snippet } from "svelte";
import { attachDemoChartMachine, type DemoChartMachine } from "../../site/chart-demo-machine";
import { createChartDataSession } from "@grassroot/ui-headless-core";
import type { ChartSeries } from "@grassroot/chart-spec";
import type { ChartXDomain } from "@grassroot/ui-machines/chart-shell";
import {
applyShellControl,
bumpReplay,
capabilitiesFor,
defaultState,
downloadLineChartCsv,
downloadLineChartSvg,
failingLineChartWorkbenchSource,
formatLineChartDomain,
isLineChartWorkbench,
lineChartWorkbenchSnapshot,
shellControlRows,
toChartProps,
type ChartDemoShellState,
} from "../../site/chart-demo-shell";
let {
name,
children,
}: {
name: string;
children: Snippet<[Record<string, unknown>]>;
} = $props();
const caps = $derived(capabilitiesFor(name));
const workbench = $derived(isLineChartWorkbench(name));
let state = $state<ChartDemoShellState>(defaultState(capabilitiesFor(name)));
let optionsOpen = $state(false);
let plot = $state<HTMLDivElement | null>(null);
// Lifecycle bookkeeping only: making this reactive closes a feedback loop
// because the attachment effect both reads and replaces the session.
let session: DemoChartMachine | null = null;
let dataSession: ReturnType<typeof createChartDataSession> | null = null;
let series = $state<readonly ChartSeries[]>(lineChartWorkbenchSnapshot());
let status = $state<"loading" | "ready" | "error">("ready");
let selectedIds = $state<readonly string[]>([]);
let brushDomain = $state<ChartXDomain>(null);
let zoomDomain = $state<ChartXDomain>(null);
$effect(() => {
state = defaultState(caps);
});
const rows = $derived(shellControlRows(caps, state));
const chartProps = $derived(toChartProps(state, caps));
const workbenchProps = $derived({
...chartProps,
series,
selectionMode: "multiple" as const,
selectedIds,
onSelectedIdsChange: (ids: readonly string[]) => { selectedIds = ids; },
brushDomain,
onBrushDomainChange: (domain: ChartXDomain) => { brushDomain = domain; },
zoomDomain,
onZoomDomainChange: (domain: ChartXDomain) => { zoomDomain = domain; },
zoomOnBrush: true,
});
onMount(() => {
if (!workbench) return;
dataSession = createChartDataSession({
onData: (snapshot) => { series = snapshot; },
onStatus: (next) => { status = next; },
});
dataSession.replace(Promise.resolve(lineChartWorkbenchSnapshot()));
return () => dataSession?.dispose();
});
$effect(() => {
const host = plot;
const machine = caps.machine;
name;
session?.destroy();
session = null;
if (!host || !machine || workbench) return;
const next = attachDemoChartMachine(host, () => state);
session = next;
return () => {
next.destroy();
if (session === next) session = null;
};
});
$effect(() => {
state.selection;
session?.syncInput();
});
function runAction() {
state = bumpReplay(state);
}
function replacePending() { dataSession?.replace(failingLineChartWorkbenchSource()); }
function retry() { dataSession?.replace(Promise.resolve(lineChartWorkbenchSnapshot())); }
function resetZoom() { brushDomain = null; zoomDomain = null; }
</script>
{#if workbench}
<div class="gr-demo-shell" data-demo-shell data-chart-workbench>
<div class="gr-demo-shell__controls">
<div class="gr-demo-variant"><span class="gr-demo-variant__label">Point selection</span><output data-workbench-selection>{selectedIds.length} selected</output><button type="button" onclick={() => (selectedIds = [])}>Clear selection</button></div>
<div class="gr-demo-variant"><span class="gr-demo-variant__label">Brush drag</span><output data-workbench-brush>{formatLineChartDomain(brushDomain)}</output></div>
<div class="gr-demo-variant"><span class="gr-demo-variant__label">Reset zoom</span><output data-workbench-zoom>{formatLineChartDomain(zoomDomain)}</output><button type="button" onclick={resetZoom}>Reset zoom</button></div>
<div class="gr-demo-variant"><span class="gr-demo-variant__label">Pending source</span><button type="button" onclick={replacePending}>Swap source</button></div>
<div class="gr-demo-variant"><span class="gr-demo-variant__label">Export SVG/CSV</span><button type="button" onclick={() => plot && downloadLineChartSvg(plot)}>Export SVG</button><button type="button" onclick={() => downloadLineChartCsv(series)}>Export CSV</button></div>
</div>
{#if status === "loading"}<div role="status">Loading chart data</div>{/if}
{#if status === "error"}<div role="alert">Unable to load chart data. <button type="button" onclick={retry}>Retry</button></div>{/if}
<div class="gr-demo-shell__chart" bind:this={plot}>{@render children(workbenchProps)}</div>
</div>
{:else if caps.any}
<div class="gr-demo-shell" data-demo-shell data-options-open={optionsOpen ? "true" : "false"}>
<div class="gr-demo-shell__controls">
{#each rows as row (row.id)}
<div class="gr-demo-variant">
<span class="gr-demo-variant__label">{row.label}</span>
<div class="gr-demo-toggles" role="group" aria-label={row.label}>
{#each row.options as option (option.value)}
<button
type="button"
aria-pressed={row.value === option.value}
onclick={() => (state = applyShellControl(state, row.id, option.value))}
>
{option.label}
</button>
{/each}
{#if row.action}
<button type="button" onclick={() => runAction()}>{row.action.label}</button>
{/if}
</div>
</div>
{/each}
</div>
<button
class="gr-demo-shell__options-toggle"
type="button"
aria-expanded={optionsOpen}
onclick={() => (optionsOpen = !optionsOpen)}
>
Chart options
</button>
<div class="gr-demo-shell__chart" bind:this={plot}>
{@render children(chartProps)}
</div>
</div>
{:else}
{@render children({})}
{/if}

Mounted source: apps/grassroot/src/components/solid/ChartDemoShell.tsx

ChartDemoShell.tsxts
/** @jsxImportSource solid-js */
import { createEffect, createMemo, createSignal, For, onCleanup, type JSX } from "solid-js";
import { attachDemoChartMachine, type DemoChartMachine } from "../../site/chart-demo-machine";
import { createChartDataSession } from "@grassroot/ui-headless-core";
import type { ChartSeries } from "@grassroot/chart-spec";
import type { ChartXDomain } from "@grassroot/ui-machines/chart-shell";
import {
applyShellControl,
bumpReplay,
capabilitiesFor,
defaultState,
downloadLineChartCsv,
downloadLineChartSvg,
failingLineChartWorkbenchSource,
formatLineChartDomain,
isLineChartWorkbench,
lineChartWorkbenchSnapshot,
shellControlRows,
toChartProps,
} from "../../site/chart-demo-shell";
export function ChartDemoShell(props: {
name: string;
children: (chartProps: Record<string, unknown>) => JSX.Element;
}) {
const caps = createMemo(() => capabilitiesFor(props.name));
const [state, setState] = createSignal(defaultState(capabilitiesFor(props.name)));
const [optionsOpen, setOptionsOpen] = createSignal(false);
const rows = createMemo(() => shellControlRows(caps(), state()));
const chartProps = createMemo(() => toChartProps(state(), caps()));
const workbench = createMemo(() => isLineChartWorkbench(props.name));
const [plot, setPlot] = createSignal<HTMLDivElement>();
let session: DemoChartMachine | null = null;
let dataSession: ReturnType<typeof createChartDataSession> | null = null;
const [series, setSeries] = createSignal<readonly ChartSeries[]>(lineChartWorkbenchSnapshot());
const [status, setStatus] = createSignal<"loading" | "ready" | "error">("ready");
const [selectedIds, setSelectedIds] = createSignal<readonly string[]>([]);
const [brushDomain, setBrushDomain] = createSignal<ChartXDomain>(null);
const [zoomDomain, setZoomDomain] = createSignal<ChartXDomain>(null);
const workbenchProps = {
...chartProps(), get series() { return series(); }, selectionMode: "multiple" as const,
get selectedIds() { return selectedIds(); },
onSelectedIdsChange: (ids: readonly string[]) => setSelectedIds(ids),
get brushDomain() { return brushDomain(); },
onBrushDomainChange: (domain: ChartXDomain) => setBrushDomain(domain),
get zoomDomain() { return zoomDomain(); },
onZoomDomainChange: (domain: ChartXDomain) => setZoomDomain(domain), zoomOnBrush: true,
};
createEffect(() => {
const host = plot();
caps();
props.name;
session?.destroy();
session = null;
if (!host || !caps().machine || workbench()) return;
session = attachDemoChartMachine(host, () => state());
onCleanup(() => {
session?.destroy();
session = null;
});
});
createEffect(() => {
state().selection;
session?.syncInput();
});
createEffect(() => {
if (!workbench()) return;
const next = createChartDataSession({ onData: setSeries, onStatus: setStatus });
dataSession = next;
next.replace(Promise.resolve(lineChartWorkbenchSnapshot()));
onCleanup(() => {
next.dispose();
if (dataSession === next) dataSession = null;
});
});
if (workbench()) {
const replacePending = () => dataSession?.replace(failingLineChartWorkbenchSource());
const retry = () => dataSession?.replace(Promise.resolve(lineChartWorkbenchSnapshot()));
return <div class="gr-demo-shell" data-demo-shell data-chart-workbench>
<div class="gr-demo-shell__controls">
<div class="gr-demo-variant"><span class="gr-demo-variant__label">Point selection</span><output data-workbench-selection>{selectedIds().length} selected</output><button type="button" onClick={() => setSelectedIds([])}>Clear selection</button></div>
<div class="gr-demo-variant"><span class="gr-demo-variant__label">Brush drag</span><output data-workbench-brush>{formatLineChartDomain(brushDomain())}</output></div>
<div class="gr-demo-variant"><span class="gr-demo-variant__label">Reset zoom</span><output data-workbench-zoom>{formatLineChartDomain(zoomDomain())}</output><button type="button" onClick={() => { setBrushDomain(null); setZoomDomain(null); }}>Reset zoom</button></div>
<div class="gr-demo-variant"><span class="gr-demo-variant__label">Pending source</span><button type="button" onClick={replacePending}>Swap source</button></div>
<div class="gr-demo-variant"><span class="gr-demo-variant__label">Export SVG/CSV</span><button type="button" onClick={() => plot() && downloadLineChartSvg(plot()!)}>Export SVG</button><button type="button" onClick={() => downloadLineChartCsv(series())}>Export CSV</button></div>
</div>
{status() === "loading" ? <div role="status">Loading chart data</div> : null}
{status() === "error" ? <div role="alert">Unable to load chart data. <button type="button" onClick={retry}>Retry</button></div> : null}
<div class="gr-demo-shell__chart" ref={setPlot}>{props.children(workbenchProps)}</div>
</div>;
}
return (
<>
{caps().any ? (
<div
class="gr-demo-shell"
data-demo-shell
data-options-open={optionsOpen() ? "true" : "false"}
>
<div class="gr-demo-shell__controls">
<For each={rows()}>
{(row) => (
<div class="gr-demo-variant">
<span class="gr-demo-variant__label">{row.label}</span>
<div class="gr-demo-toggles" role="group" aria-label={row.label}>
<For each={row.options}>
{(option) => (
<button
type="button"
aria-pressed={row.value === option.value}
onClick={() => setState(applyShellControl(state(), row.id, option.value))}
>
{option.label}
</button>
)}
</For>
{row.action ? (
<button type="button" onClick={() => setState(bumpReplay(state()))}>
{row.action.label}
</button>
) : null}
</div>
</div>
)}
</For>
</div>
<button
class="gr-demo-shell__options-toggle"
type="button"
aria-expanded={optionsOpen()}
onClick={() => setOptionsOpen((open) => !open)}
>
Chart options
</button>
<div class="gr-demo-shell__chart" ref={setPlot}>
{props.children(chartProps())}
</div>
</div>
) : (
props.children({})
)}
</>
);
}