@aiaiai-pt/design-system 0.40.0 → 0.42.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/renderer/MetabaseEmbedWidget.svelte +76 -0
- package/components/renderer/MetabaseEmbedWidget.svelte.d.ts +4 -0
- package/components/renderer/StatGridWidget.svelte +17 -1
- package/components/renderer/registry.ts +11 -8
- package/components/renderer/resolve-data.ts +3 -0
- package/components/renderer/types.d.ts +1 -1
- package/components/renderer/types.ts +6 -0
- package/package.json +2 -1
- package/components/renderer/DonutChartWidget.svelte +0 -11
- package/components/renderer/DonutChartWidget.svelte.d.ts +0 -4
- package/components/renderer/LineChartWidget.svelte +0 -12
- package/components/renderer/LineChartWidget.svelte.d.ts +0 -4
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { WidgetProps } from "./types";
|
|
3
|
+
|
|
4
|
+
// `embed` widget (#517, atelier) — a Metabase analytics embed on the unified
|
|
5
|
+
// dashboard/portal `Block[]` grid. PRESENTATION-ONLY: the DS never mints or
|
|
6
|
+
// holds a Metabase URL; the HOST resolves the signed/proxy URL and passes it
|
|
7
|
+
// as `props.src` (an `/internal/metabase-embed/…` route or a signed URL), and
|
|
8
|
+
// this widget renders an `<iframe>`. Presentational kind → `resolveData`
|
|
9
|
+
// short-circuits it (no DS fetch); the widget owns its props.
|
|
10
|
+
//
|
|
11
|
+
// props.src — the iframe URL (host-resolved). Absent ⇒ placeholder.
|
|
12
|
+
// props.title — accessible iframe title / caption.
|
|
13
|
+
// props.height — CSS height (e.g. "400px"); defaults to 400px.
|
|
14
|
+
// props.width — "full" | "half" (grid-agnostic max-width hint).
|
|
15
|
+
// props.embed_type — "dashboard" | "question" (informational; caption).
|
|
16
|
+
let { props }: WidgetProps = $props();
|
|
17
|
+
|
|
18
|
+
const src = $derived(typeof props.src === "string" ? props.src : "");
|
|
19
|
+
const title = $derived(typeof props.title === "string" && props.title ? props.title : "Metabase embed");
|
|
20
|
+
const height = $derived(typeof props.height === "string" && props.height ? props.height : "400px");
|
|
21
|
+
const half = $derived(props.width === "half");
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<figure class="mb-embed" class:half data-widget="metabase-embed">
|
|
25
|
+
{#if src}
|
|
26
|
+
<iframe {src} {title} style:height loading="lazy" referrerpolicy="strict-origin-when-cross-origin"
|
|
27
|
+
></iframe>
|
|
28
|
+
{#if props.title}<figcaption>{props.title}</figcaption>{/if}
|
|
29
|
+
{:else}
|
|
30
|
+
<div class="mb-embed-empty" style:height>
|
|
31
|
+
<p>Metabase embed not configured — set the dashboard/question id.</p>
|
|
32
|
+
</div>
|
|
33
|
+
{/if}
|
|
34
|
+
</figure>
|
|
35
|
+
|
|
36
|
+
<style>
|
|
37
|
+
.mb-embed {
|
|
38
|
+
margin: 0;
|
|
39
|
+
display: flex;
|
|
40
|
+
flex-direction: column;
|
|
41
|
+
gap: var(--space-sm);
|
|
42
|
+
inline-size: 100%;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.mb-embed.half {
|
|
46
|
+
max-inline-size: 50%;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.mb-embed iframe {
|
|
50
|
+
inline-size: 100%;
|
|
51
|
+
border: var(--elevation-border);
|
|
52
|
+
border-radius: var(--radius-md);
|
|
53
|
+
background: var(--color-surface);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.mb-embed figcaption {
|
|
57
|
+
font-size: var(--type-caption-size);
|
|
58
|
+
color: var(--color-text-muted);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.mb-embed-empty {
|
|
62
|
+
display: grid;
|
|
63
|
+
place-items: center;
|
|
64
|
+
inline-size: 100%;
|
|
65
|
+
border: var(--border-width) dashed var(--color-border);
|
|
66
|
+
border-radius: var(--radius-md);
|
|
67
|
+
background: var(--color-surface-secondary);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.mb-embed-empty p {
|
|
71
|
+
margin: 0;
|
|
72
|
+
padding: var(--space-md);
|
|
73
|
+
font-size: var(--type-body-sm-size);
|
|
74
|
+
color: var(--color-text-muted);
|
|
75
|
+
}
|
|
76
|
+
</style>
|
|
@@ -14,6 +14,11 @@
|
|
|
14
14
|
label?: string;
|
|
15
15
|
value?: string;
|
|
16
16
|
variant?: string;
|
|
17
|
+
/** #517 — optional Phosphor icon NAME (kebab-case, e.g. "chart-bar"),
|
|
18
|
+
* rendered via the Phosphor web font. Lets a dashboard KPI block carry its
|
|
19
|
+
* per-card icon through the DS widget, so KPI renders via `resolveWidget`
|
|
20
|
+
* like every other block (no host-side StatCard special-case). */
|
|
21
|
+
icon?: string;
|
|
17
22
|
}
|
|
18
23
|
|
|
19
24
|
const view = $derived((data ?? {}) as Record<string, unknown>);
|
|
@@ -24,10 +29,21 @@
|
|
|
24
29
|
? props.stats
|
|
25
30
|
: []) as Stat[],
|
|
26
31
|
);
|
|
32
|
+
|
|
33
|
+
// Phosphor web-font class names are kebab-case (`ph ph-chart-bar`); keep only
|
|
34
|
+
// the safe charset so a bad name can't leak into `class` (defensive — attribute
|
|
35
|
+
// values are escaped, but this keeps the class well-formed).
|
|
36
|
+
const iconClass = (name: string): string => `ph ph-${name.replace(/[^a-z0-9-]/gi, "")}`;
|
|
27
37
|
</script>
|
|
28
38
|
|
|
29
39
|
<StatGrid>
|
|
30
40
|
{#each stats as stat (stat.label)}
|
|
31
|
-
<
|
|
41
|
+
{#snippet cardIcon()}<i class={iconClass(stat.icon ?? "")} aria-hidden="true"></i>{/snippet}
|
|
42
|
+
<StatCard
|
|
43
|
+
label={stat.label}
|
|
44
|
+
value={stat.value}
|
|
45
|
+
variant={stat.variant}
|
|
46
|
+
icon={stat.icon ? cardIcon : undefined}
|
|
47
|
+
/>
|
|
32
48
|
{/each}
|
|
33
49
|
</StatGrid>
|
|
@@ -22,9 +22,8 @@ import {
|
|
|
22
22
|
} from "./dispatch";
|
|
23
23
|
import type { Block, OntologySchema, WidgetKind, WidgetProps } from "./types";
|
|
24
24
|
|
|
25
|
-
import DonutChartWidget from "./DonutChartWidget.svelte";
|
|
26
25
|
import EChartWidget from "./EChartWidget.svelte";
|
|
27
|
-
import
|
|
26
|
+
import MetabaseEmbedWidget from "./MetabaseEmbedWidget.svelte";
|
|
28
27
|
import ResultsChartWidget from "./ResultsChartWidget.svelte";
|
|
29
28
|
import StatGridWidget from "./StatGridWidget.svelte";
|
|
30
29
|
|
|
@@ -67,15 +66,19 @@ export function byKind(
|
|
|
67
66
|
* appended in call order. Exported (readonly view) for introspection / testing.
|
|
68
67
|
*/
|
|
69
68
|
const _entries: RegistryEntry<WidgetComponent>[] = [
|
|
70
|
-
// DS base: the
|
|
69
|
+
// DS base: the clean widgets shipped by this package.
|
|
71
70
|
byKind("stat-grid", "kpi", StatGridWidget),
|
|
72
71
|
byTypeOnKind("results-chart", "aggregate", ResultsChartWidget),
|
|
72
|
+
// #176 follow-on — ONE ECharts widget renders any chart spec (supersedes ADR
|
|
73
|
+
// 0003 #4). The per-kind `line-chart` / `donut-chart` keys were a stopgap;
|
|
74
|
+
// in the declarative model the kind is PER-SERIES (a series carries its own
|
|
75
|
+
// bar/line/area/scatter/pie type), so EChartWidget renders every shape and
|
|
76
|
+
// the kind-specific registry keys + wrapper components are retired.
|
|
77
|
+
// `results-chart` stays as the SSR / no-JS a11y fallback key.
|
|
73
78
|
byTypeOnKind("chart", "aggregate", EChartWidget),
|
|
74
|
-
// #
|
|
75
|
-
//
|
|
76
|
-
|
|
77
|
-
byTypeOnKind("line-chart", "aggregate", LineChartWidget),
|
|
78
|
-
byTypeOnKind("donut-chart", "aggregate", DonutChartWidget),
|
|
79
|
+
// #517 (atelier) — Metabase embed on the unified Block[] grid. Presentation-
|
|
80
|
+
// only (renders `props.src`); one kind-generic entry, no `type` ranking.
|
|
81
|
+
byKind("metabase-embed", "embed", MetabaseEmbedWidget),
|
|
79
82
|
];
|
|
80
83
|
|
|
81
84
|
/**
|
|
@@ -35,6 +35,9 @@ export const PRESENTATIONAL_KINDS: ReadonlySet<WidgetKind> =
|
|
|
35
35
|
new Set<WidgetKind>([
|
|
36
36
|
"hero",
|
|
37
37
|
"lookup",
|
|
38
|
+
// #517 — the Metabase embed renders from a host-resolved `props.src`; it has
|
|
39
|
+
// no DS-fetched data path, so short-circuit it like the other content kinds.
|
|
40
|
+
"embed",
|
|
38
41
|
"feature-grid",
|
|
39
42
|
"cta",
|
|
40
43
|
"media-text",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* here the descriptors are SEEDED (same staged pattern #70/#72 used).
|
|
14
14
|
*/
|
|
15
15
|
/** The bounded, governed widget kinds (spec §5 catalog). */
|
|
16
|
-
export type WidgetKind = "list" | "detail" | "form" | "map" | "calendar" | "kpi" | "content" | "vote" | "status" | "forms" | "hero" | "lookup" | "feed" | "filters" | "actions" | "aggregate" | "subscriptions" | "consent" | "deliveries" | "owned-list" | "feature-grid" | "cta" | "media-text" | "steps" | "testimonial" | "faq" | "logo-strip" | "media-gallery";
|
|
16
|
+
export type WidgetKind = "list" | "detail" | "form" | "map" | "calendar" | "kpi" | "content" | "embed" | "vote" | "status" | "forms" | "hero" | "lookup" | "feed" | "filters" | "actions" | "aggregate" | "subscriptions" | "consent" | "deliveries" | "owned-list" | "feature-grid" | "cta" | "media-text" | "steps" | "testimonial" | "faq" | "logo-strip" | "media-gallery";
|
|
17
17
|
/** The feed sub-views a `feed`/`filters` binding may toggle/scope (#308). */
|
|
18
18
|
export type FeedView = "list" | "map";
|
|
19
19
|
/** Declarative pointer — "I render <kind> of <entity|action>, scoped by filter". */
|
|
@@ -22,6 +22,12 @@ export type WidgetKind =
|
|
|
22
22
|
| "calendar"
|
|
23
23
|
| "kpi"
|
|
24
24
|
| "content"
|
|
25
|
+
// #517 (atelier) — a Metabase analytics embed. Presentational: the DS
|
|
26
|
+
// `MetabaseEmbedWidget` renders an `<iframe>` from a HOST-resolved `props.src`
|
|
27
|
+
// (the signed/proxy URL), so the DS stays presentation-only and carries no
|
|
28
|
+
// metabase auth. Lets a dashboard/portal `Block[]` hold embeds uniformly
|
|
29
|
+
// (resolved via `resolveWidget`, not a host special-case).
|
|
30
|
+
| "embed"
|
|
25
31
|
| "vote"
|
|
26
32
|
| "status"
|
|
27
33
|
| "forms"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiaiai-pt/design-system",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.42.0",
|
|
4
4
|
"description": "Design system tokens and Svelte components for aiaiai products",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -75,6 +75,7 @@
|
|
|
75
75
|
"@sveltejs/package": "^2.5.7",
|
|
76
76
|
"date-fns": "^4.1.0",
|
|
77
77
|
"echarts": "^5.6.0",
|
|
78
|
+
"jsdom": "^29.1.1",
|
|
78
79
|
"ol": "^10.8.0",
|
|
79
80
|
"svelte": "^5.55.3",
|
|
80
81
|
"svelte-check": "^4.4.6",
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import EChartWidget from "./EChartWidget.svelte";
|
|
3
|
-
import type { WidgetProps } from "./types";
|
|
4
|
-
|
|
5
|
-
// `donut-chart` registry key (#176 Tier 1). Thin wrapper binding the ECharts
|
|
6
|
-
// `kind` to a ring pie — the registry maps this key here. Same aggregate data
|
|
7
|
-
// path + a11y data table as the bar/line.
|
|
8
|
-
let p: WidgetProps = $props();
|
|
9
|
-
</script>
|
|
10
|
-
|
|
11
|
-
<EChartWidget {...p} kind="donut" />
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import EChartWidget from "./EChartWidget.svelte";
|
|
3
|
-
import type { WidgetProps } from "./types";
|
|
4
|
-
|
|
5
|
-
// `line-chart` registry key (#176 Tier 1). Thin wrapper that binds the ECharts
|
|
6
|
-
// `kind` the dispatcher can't pass — the registry maps this key to this
|
|
7
|
-
// component, which renders the shared EChart widget as a line. Same aggregate
|
|
8
|
-
// data path + a11y data table as the bar.
|
|
9
|
-
let p: WidgetProps = $props();
|
|
10
|
-
</script>
|
|
11
|
-
|
|
12
|
-
<EChartWidget {...p} kind="line" />
|