DocsRecipes
Workflow recipes
Five minimal workflows built from the same mounted demos and fixtures that keep the adapters honest.
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
Mounted source: apps/grassroot/src/components/react/ComboboxDemo.tsx
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
<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
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
<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
/** @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
Mounted source: apps/grassroot/src/components/react/DatePickerDemo.tsx
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
<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
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
<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
/** @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
Mounted source: apps/grassroot/src/components/react/FileDropzoneDemo.tsx
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
<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
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
<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
/** @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
Mounted source: apps/grassroot/src/components/react/DataGridDemo.tsx
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
<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
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
<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
/** @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
Mounted source: apps/grassroot/src/components/react/ChartDemoShell.tsx
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
<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
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
<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
/** @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({}) )} </> );}