@kopai/ui 0.0.5 → 0.2.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.
Files changed (125) hide show
  1. package/README.md +137 -0
  2. package/dist/index.cjs +5069 -3
  3. package/dist/index.d.cts +301 -3
  4. package/dist/index.d.cts.map +1 -1
  5. package/dist/index.d.mts +302 -3
  6. package/dist/index.d.mts.map +1 -1
  7. package/dist/index.mjs +5010 -3
  8. package/dist/index.mjs.map +1 -1
  9. package/package.json +25 -7
  10. package/src/components/KeyboardShortcuts/KeyboardShortcutsProvider.tsx +113 -0
  11. package/src/components/KeyboardShortcuts/ShortcutsHelpDialog.tsx +82 -0
  12. package/src/components/KeyboardShortcuts/context.ts +23 -0
  13. package/src/components/KeyboardShortcuts/index.ts +8 -0
  14. package/src/components/KeyboardShortcuts/types.ts +11 -0
  15. package/src/components/dashboard/Badge/Badge.stories.tsx +29 -0
  16. package/src/components/dashboard/Badge/index.tsx +32 -0
  17. package/src/components/dashboard/Button/Button.stories.tsx +107 -0
  18. package/src/components/dashboard/Button/index.tsx +63 -0
  19. package/src/components/dashboard/Card/Card.stories.tsx +81 -0
  20. package/src/components/dashboard/Card/index.tsx +58 -0
  21. package/src/components/dashboard/Chart/Chart.stories.tsx +48 -0
  22. package/src/components/dashboard/Chart/index.tsx +74 -0
  23. package/src/components/dashboard/DatePicker/DatePicker.stories.tsx +33 -0
  24. package/src/components/dashboard/DatePicker/index.tsx +41 -0
  25. package/src/components/dashboard/Divider/Divider.stories.tsx +17 -0
  26. package/src/components/dashboard/Divider/index.tsx +49 -0
  27. package/src/components/dashboard/Empty/Empty.stories.tsx +48 -0
  28. package/src/components/dashboard/Empty/index.tsx +46 -0
  29. package/src/components/dashboard/Grid/Grid.stories.tsx +52 -0
  30. package/src/components/dashboard/Grid/index.tsx +26 -0
  31. package/src/components/dashboard/Heading/Heading.stories.tsx +25 -0
  32. package/src/components/dashboard/Heading/index.tsx +27 -0
  33. package/src/components/dashboard/List/List.stories.tsx +37 -0
  34. package/src/components/dashboard/List/index.tsx +24 -0
  35. package/src/components/dashboard/Metric/Metric.stories.tsx +65 -0
  36. package/src/components/dashboard/Metric/index.tsx +36 -0
  37. package/src/components/dashboard/Stack/Stack.stories.tsx +61 -0
  38. package/src/components/dashboard/Stack/index.tsx +33 -0
  39. package/src/components/dashboard/Table/Table.stories.tsx +38 -0
  40. package/src/components/dashboard/Table/index.tsx +104 -0
  41. package/src/components/dashboard/Text/Text.stories.tsx +53 -0
  42. package/src/components/dashboard/Text/index.tsx +18 -0
  43. package/src/components/dashboard/index.ts +46 -0
  44. package/src/components/index.ts +17 -0
  45. package/src/components/observability/LogTimeline/LogDetailPane/AttributesTab.tsx +56 -0
  46. package/src/components/observability/LogTimeline/LogDetailPane/JsonTreeView.tsx +139 -0
  47. package/src/components/observability/LogTimeline/LogDetailPane/index.tsx +271 -0
  48. package/src/components/observability/LogTimeline/LogFilter.stories.tsx +66 -0
  49. package/src/components/observability/LogTimeline/LogFilter.test.tsx +696 -0
  50. package/src/components/observability/LogTimeline/LogFilter.tsx +674 -0
  51. package/src/components/observability/LogTimeline/LogRow.tsx +174 -0
  52. package/src/components/observability/LogTimeline/LogTimeline.stories.tsx +154 -0
  53. package/src/components/observability/LogTimeline/index.tsx +542 -0
  54. package/src/components/observability/LogTimeline/shortcuts.ts +18 -0
  55. package/src/components/observability/MetricHistogram/MetricHistogram.stories.tsx +20 -0
  56. package/src/components/observability/MetricHistogram/index.tsx +303 -0
  57. package/src/components/observability/MetricStat/MetricStat.stories.tsx +30 -0
  58. package/src/components/observability/MetricStat/index.tsx +281 -0
  59. package/src/components/observability/MetricTable/MetricTable.stories.tsx +20 -0
  60. package/src/components/observability/MetricTable/index.tsx +194 -0
  61. package/src/components/observability/MetricTimeSeries/MetricTimeSeries.stories.tsx +28 -0
  62. package/src/components/observability/MetricTimeSeries/index.tsx +462 -0
  63. package/src/components/observability/RawDataTable/RawDataTable.stories.tsx +27 -0
  64. package/src/components/observability/RawDataTable/index.tsx +131 -0
  65. package/src/components/observability/ServiceList/ServiceList.stories.tsx +20 -0
  66. package/src/components/observability/ServiceList/index.tsx +60 -0
  67. package/src/components/observability/ServiceList/shortcuts.ts +6 -0
  68. package/src/components/observability/TabBar/TabBar.stories.tsx +34 -0
  69. package/src/components/observability/TabBar/index.tsx +46 -0
  70. package/src/components/observability/TraceDetail/TraceDetail.stories.tsx +51 -0
  71. package/src/components/observability/TraceDetail/index.tsx +53 -0
  72. package/src/components/observability/TraceSearch/TraceSearch.stories.tsx +49 -0
  73. package/src/components/observability/TraceSearch/index.tsx +292 -0
  74. package/src/components/observability/TraceTimeline/DetailPane/AttributesTab.tsx +152 -0
  75. package/src/components/observability/TraceTimeline/DetailPane/EventsTab.tsx +128 -0
  76. package/src/components/observability/TraceTimeline/DetailPane/LinksTab.tsx +210 -0
  77. package/src/components/observability/TraceTimeline/DetailPane/index.tsx +174 -0
  78. package/src/components/observability/TraceTimeline/SpanRow.tsx +173 -0
  79. package/src/components/observability/TraceTimeline/TimelineBar.tsx +41 -0
  80. package/src/components/observability/TraceTimeline/Tooltip.tsx +42 -0
  81. package/src/components/observability/TraceTimeline/TraceHeader.tsx +88 -0
  82. package/src/components/observability/TraceTimeline/TraceTimeline.stories.tsx +25 -0
  83. package/src/components/observability/TraceTimeline/index.tsx +478 -0
  84. package/src/components/observability/TraceTimeline/shortcuts.ts +16 -0
  85. package/src/components/observability/__fixtures__/logs.ts +476 -0
  86. package/src/components/observability/__fixtures__/metrics.ts +216 -0
  87. package/src/components/observability/__fixtures__/raw-table.ts +204 -0
  88. package/src/components/observability/__fixtures__/services.ts +8 -0
  89. package/src/components/observability/__fixtures__/trace-summaries.ts +81 -0
  90. package/src/components/observability/__fixtures__/traces.ts +396 -0
  91. package/src/components/observability/index.ts +66 -0
  92. package/src/components/observability/renderers/OtelMetricDiscovery.tsx +77 -0
  93. package/src/components/observability/renderers/OtelMetricHistogram.tsx +29 -0
  94. package/src/components/observability/renderers/OtelMetricStat.tsx +44 -0
  95. package/src/components/observability/renderers/OtelMetricTable.tsx +29 -0
  96. package/src/components/observability/renderers/OtelMetricTimeSeries.tsx +30 -0
  97. package/src/components/observability/renderers/index.ts +5 -0
  98. package/src/components/observability/shared/LoadingSkeleton.tsx +43 -0
  99. package/src/components/observability/types.ts +113 -0
  100. package/src/components/observability/utils/attributes.ts +17 -0
  101. package/src/components/observability/utils/colors.ts +29 -0
  102. package/src/components/observability/utils/flatten-tree.ts +53 -0
  103. package/src/components/observability/utils/lttb.ts +121 -0
  104. package/src/components/observability/utils/time.ts +46 -0
  105. package/src/hooks/use-kopai-data.test.ts +296 -0
  106. package/src/hooks/use-kopai-data.ts +64 -0
  107. package/src/hooks/use-live-logs.test.ts +193 -0
  108. package/src/hooks/use-live-logs.ts +113 -0
  109. package/src/index.ts +15 -0
  110. package/src/lib/__snapshots__/generate-prompt-instructions.test.ts.snap +33 -0
  111. package/src/lib/catalog.ts +165 -0
  112. package/src/lib/component-catalog.test.ts +357 -0
  113. package/src/lib/component-catalog.ts +171 -0
  114. package/src/lib/dashboard-datasource.ts +76 -0
  115. package/src/lib/generate-prompt-instructions.test.ts +27 -0
  116. package/src/lib/generate-prompt-instructions.ts +185 -0
  117. package/src/lib/log-buffer.test.ts +88 -0
  118. package/src/lib/log-buffer.ts +62 -0
  119. package/src/lib/observability-catalog.ts +143 -0
  120. package/src/lib/renderer.test.tsx +693 -0
  121. package/src/lib/renderer.tsx +276 -0
  122. package/src/pages/observability.tsx +825 -0
  123. package/src/providers/kopai-provider.tsx +51 -0
  124. package/src/styles/globals.css +46 -0
  125. package/src/vite-env.d.ts +1 -0
@@ -0,0 +1,48 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Chart } from "./index.js";
3
+
4
+ const meta: Meta<typeof Chart> = {
5
+ title: "Dashboard/Chart",
6
+ component: Chart,
7
+ };
8
+ export default meta;
9
+ type Story = StoryObj<typeof Chart>;
10
+
11
+ export const Bar: Story = {
12
+ args: {
13
+ element: {
14
+ props: {
15
+ type: "bar",
16
+ dataPath: "analytics.weekly",
17
+ title: "Weekly Activity",
18
+ height: 150,
19
+ },
20
+ },
21
+ },
22
+ };
23
+
24
+ export const Line: Story = {
25
+ args: {
26
+ element: {
27
+ props: {
28
+ type: "line",
29
+ dataPath: "analytics.daily",
30
+ title: "Daily Trend",
31
+ height: 200,
32
+ },
33
+ },
34
+ },
35
+ };
36
+
37
+ export const NoTitle: Story = {
38
+ args: {
39
+ element: {
40
+ props: {
41
+ type: "area",
42
+ dataPath: "data",
43
+ title: null,
44
+ height: 120,
45
+ },
46
+ },
47
+ },
48
+ };
@@ -0,0 +1,74 @@
1
+ import { dashboardCatalog } from "../../../lib/catalog.js";
2
+ import type { CatalogueComponentProps } from "../../../lib/component-catalog.js";
3
+
4
+ export function Chart({
5
+ element,
6
+ }: CatalogueComponentProps<typeof dashboardCatalog.components.Chart>) {
7
+ const { type, dataPath, title, height } = element.props;
8
+
9
+ // Static mock data for example page
10
+ const mockData = [
11
+ { label: "Mon", value: 40 },
12
+ { label: "Tue", value: 65 },
13
+ { label: "Wed", value: 45 },
14
+ { label: "Thu", value: 80 },
15
+ { label: "Fri", value: 55 },
16
+ ];
17
+
18
+ const maxValue = Math.max(...mockData.map((d) => d.value));
19
+
20
+ return (
21
+ <div>
22
+ {title && (
23
+ <h4 style={{ margin: "0 0 16px", fontSize: 14, fontWeight: 600 }}>
24
+ {title}
25
+ </h4>
26
+ )}
27
+ <div
28
+ style={{
29
+ display: "flex",
30
+ gap: 8,
31
+ alignItems: "flex-end",
32
+ height: height || 120,
33
+ }}
34
+ >
35
+ {mockData.map((d, i) => (
36
+ <div
37
+ key={i}
38
+ style={{
39
+ flex: 1,
40
+ display: "flex",
41
+ flexDirection: "column",
42
+ alignItems: "center",
43
+ gap: 4,
44
+ }}
45
+ >
46
+ <div
47
+ style={{
48
+ width: "100%",
49
+ height: `${(d.value / maxValue) * 100}%`,
50
+ background: "hsl(var(--foreground))",
51
+ borderRadius: "4px 4px 0 0",
52
+ minHeight: 4,
53
+ }}
54
+ />
55
+ <span
56
+ style={{ fontSize: 12, color: "hsl(var(--muted-foreground))" }}
57
+ >
58
+ {d.label}
59
+ </span>
60
+ </div>
61
+ ))}
62
+ </div>
63
+ <p
64
+ style={{
65
+ margin: "8px 0 0",
66
+ fontSize: 12,
67
+ color: "hsl(var(--muted-foreground))",
68
+ }}
69
+ >
70
+ Type: {type} | Data: {dataPath}
71
+ </p>
72
+ </div>
73
+ );
74
+ }
@@ -0,0 +1,33 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { DatePicker } from "./index.js";
3
+
4
+ const meta: Meta<typeof DatePicker> = {
5
+ title: "Dashboard/DatePicker",
6
+ component: DatePicker,
7
+ };
8
+ export default meta;
9
+ type Story = StoryObj<typeof DatePicker>;
10
+
11
+ export const Default: Story = {
12
+ args: {
13
+ element: {
14
+ props: {
15
+ label: "Select Date",
16
+ bindPath: "filters.date",
17
+ placeholder: "Choose a date",
18
+ },
19
+ },
20
+ },
21
+ };
22
+
23
+ export const NoLabel: Story = {
24
+ args: {
25
+ element: {
26
+ props: {
27
+ label: null,
28
+ bindPath: "filters.startDate",
29
+ placeholder: "Start date",
30
+ },
31
+ },
32
+ },
33
+ };
@@ -0,0 +1,41 @@
1
+ import { dashboardCatalog } from "../../../lib/catalog.js";
2
+ import type { CatalogueComponentProps } from "../../../lib/component-catalog.js";
3
+
4
+ export function DatePicker({
5
+ element,
6
+ }: CatalogueComponentProps<typeof dashboardCatalog.components.DatePicker>) {
7
+ const { label, bindPath } = element.props;
8
+ const inputId = `datepicker-${bindPath}`;
9
+
10
+ return (
11
+ <div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
12
+ {label && (
13
+ <label htmlFor={inputId} style={{ fontSize: 14, fontWeight: 500 }}>
14
+ {label}
15
+ </label>
16
+ )}
17
+ <input
18
+ id={inputId}
19
+ type="date"
20
+ style={{
21
+ padding: "8px 12px",
22
+ borderRadius: "var(--radius)",
23
+ border: "1px solid hsl(var(--border))",
24
+ background: "hsl(var(--card))",
25
+ color: "hsl(var(--foreground))",
26
+ fontSize: 16,
27
+ outline: "none",
28
+ }}
29
+ />
30
+ <p
31
+ style={{
32
+ margin: "4px 0 0",
33
+ fontSize: 12,
34
+ color: "hsl(var(--muted-foreground))",
35
+ }}
36
+ >
37
+ Bound to: {bindPath}
38
+ </p>
39
+ </div>
40
+ );
41
+ }
@@ -0,0 +1,17 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Divider } from "./index.js";
3
+
4
+ const meta: Meta<typeof Divider> = {
5
+ title: "Dashboard/Divider",
6
+ component: Divider,
7
+ };
8
+ export default meta;
9
+ type Story = StoryObj<typeof Divider>;
10
+
11
+ export const Default: Story = {
12
+ args: { element: { props: { label: null } } },
13
+ };
14
+
15
+ export const WithLabel: Story = {
16
+ args: { element: { props: { label: "Section Break" } } },
17
+ };
@@ -0,0 +1,49 @@
1
+ import { dashboardCatalog } from "../../../lib/catalog.js";
2
+ import type { CatalogueComponentProps } from "../../../lib/component-catalog.js";
3
+
4
+ export function Divider({
5
+ element,
6
+ }: CatalogueComponentProps<typeof dashboardCatalog.components.Divider>) {
7
+ const { label } = element.props;
8
+
9
+ if (label) {
10
+ return (
11
+ <div
12
+ style={{
13
+ display: "flex",
14
+ alignItems: "center",
15
+ gap: 16,
16
+ margin: "16px 0",
17
+ }}
18
+ >
19
+ <hr
20
+ style={{
21
+ flex: 1,
22
+ border: "none",
23
+ borderTop: "1px solid hsl(var(--border))",
24
+ }}
25
+ />
26
+ <span style={{ fontSize: 12, color: "hsl(var(--muted-foreground))" }}>
27
+ {label}
28
+ </span>
29
+ <hr
30
+ style={{
31
+ flex: 1,
32
+ border: "none",
33
+ borderTop: "1px solid hsl(var(--border))",
34
+ }}
35
+ />
36
+ </div>
37
+ );
38
+ }
39
+
40
+ return (
41
+ <hr
42
+ style={{
43
+ border: "none",
44
+ borderTop: "1px solid hsl(var(--border))",
45
+ margin: "16px 0",
46
+ }}
47
+ />
48
+ );
49
+ }
@@ -0,0 +1,48 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Empty } from "./index.js";
3
+
4
+ const meta: Meta<typeof Empty> = {
5
+ title: "Dashboard/Empty",
6
+ component: Empty,
7
+ };
8
+ export default meta;
9
+ type Story = StoryObj<typeof Empty>;
10
+
11
+ export const Default: Story = {
12
+ args: {
13
+ element: {
14
+ props: {
15
+ title: "No Data Available",
16
+ description: "Try adjusting your filters or adding new data.",
17
+ action: "addData",
18
+ actionLabel: "Add Data",
19
+ },
20
+ },
21
+ },
22
+ };
23
+
24
+ export const NoAction: Story = {
25
+ args: {
26
+ element: {
27
+ props: {
28
+ title: "Nothing Here",
29
+ description: "Check back later.",
30
+ action: null,
31
+ actionLabel: null,
32
+ },
33
+ },
34
+ },
35
+ };
36
+
37
+ export const TitleOnly: Story = {
38
+ args: {
39
+ element: {
40
+ props: {
41
+ title: "Empty",
42
+ description: null,
43
+ action: null,
44
+ actionLabel: null,
45
+ },
46
+ },
47
+ },
48
+ };
@@ -0,0 +1,46 @@
1
+ import { dashboardCatalog } from "../../../lib/catalog.js";
2
+ import type { CatalogueComponentProps } from "../../../lib/component-catalog.js";
3
+
4
+ export function Empty({
5
+ element,
6
+ onAction,
7
+ }: CatalogueComponentProps<typeof dashboardCatalog.components.Empty> & {
8
+ onAction?: (action: string) => void;
9
+ }) {
10
+ const { title, description, action, actionLabel } = element.props;
11
+
12
+ return (
13
+ <div style={{ textAlign: "center", padding: "40px 20px" }}>
14
+ <h3 style={{ margin: "0 0 8px", fontSize: 16, fontWeight: 600 }}>
15
+ {title}
16
+ </h3>
17
+ {description && (
18
+ <p
19
+ style={{
20
+ margin: "0 0 16px",
21
+ fontSize: 14,
22
+ color: "hsl(var(--muted-foreground))",
23
+ }}
24
+ >
25
+ {description}
26
+ </p>
27
+ )}
28
+ {action && actionLabel && (
29
+ <button
30
+ onClick={() => onAction?.(action)}
31
+ style={{
32
+ padding: "8px 16px",
33
+ borderRadius: "var(--radius)",
34
+ border: "1px solid hsl(var(--border))",
35
+ background: "hsl(var(--card))",
36
+ color: "hsl(var(--foreground))",
37
+ fontSize: 14,
38
+ cursor: "pointer",
39
+ }}
40
+ >
41
+ {actionLabel}
42
+ </button>
43
+ )}
44
+ </div>
45
+ );
46
+ }
@@ -0,0 +1,52 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Grid } from "./index.js";
3
+
4
+ const meta: Meta<typeof Grid> = {
5
+ title: "Dashboard/Grid",
6
+ component: Grid,
7
+ };
8
+ export default meta;
9
+ type Story = StoryObj<typeof Grid>;
10
+
11
+ const GridItem = ({ label }: { label: string }) => (
12
+ <div
13
+ style={{
14
+ padding: 16,
15
+ background: "hsl(var(--card))",
16
+ border: "1px solid hsl(var(--border))",
17
+ borderRadius: "var(--radius)",
18
+ }}
19
+ >
20
+ {label}
21
+ </div>
22
+ );
23
+
24
+ export const TwoColumns: Story = {
25
+ render: () => (
26
+ <Grid element={{ props: { columns: 2, gap: "md" } }}>
27
+ <GridItem label="Item 1" />
28
+ <GridItem label="Item 2" />
29
+ <GridItem label="Item 3" />
30
+ <GridItem label="Item 4" />
31
+ </Grid>
32
+ ),
33
+ };
34
+
35
+ export const ThreeColumns: Story = {
36
+ render: () => (
37
+ <Grid element={{ props: { columns: 3, gap: "md" } }}>
38
+ <GridItem label="Item 1" />
39
+ <GridItem label="Item 2" />
40
+ <GridItem label="Item 3" />
41
+ </Grid>
42
+ ),
43
+ };
44
+
45
+ export const SmallGap: Story = {
46
+ render: () => (
47
+ <Grid element={{ props: { columns: 2, gap: "sm" } }}>
48
+ <GridItem label="Item 1" />
49
+ <GridItem label="Item 2" />
50
+ </Grid>
51
+ ),
52
+ };
@@ -0,0 +1,26 @@
1
+ import { dashboardCatalog } from "../../../lib/catalog.js";
2
+ import type { CatalogueComponentProps } from "../../../lib/component-catalog.js";
3
+
4
+ export function Grid({
5
+ element,
6
+ children,
7
+ }: CatalogueComponentProps<typeof dashboardCatalog.components.Grid>) {
8
+ const { columns, gap } = element.props;
9
+ const gaps: Record<string, string> = {
10
+ sm: "8px",
11
+ md: "16px",
12
+ lg: "24px",
13
+ };
14
+
15
+ return (
16
+ <div
17
+ style={{
18
+ display: "grid",
19
+ gridTemplateColumns: `repeat(${columns || 2}, 1fr)`,
20
+ gap: gaps[gap || "md"],
21
+ }}
22
+ >
23
+ {children}
24
+ </div>
25
+ );
26
+ }
@@ -0,0 +1,25 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Heading } from "./index.js";
3
+
4
+ const meta: Meta<typeof Heading> = {
5
+ title: "Dashboard/Heading",
6
+ component: Heading,
7
+ };
8
+ export default meta;
9
+ type Story = StoryObj<typeof Heading>;
10
+
11
+ export const H1: Story = {
12
+ args: { element: { props: { text: "Heading Level 1", level: "h1" } } },
13
+ };
14
+
15
+ export const H2: Story = {
16
+ args: { element: { props: { text: "Heading Level 2", level: "h2" } } },
17
+ };
18
+
19
+ export const H3: Story = {
20
+ args: { element: { props: { text: "Heading Level 3", level: "h3" } } },
21
+ };
22
+
23
+ export const H4: Story = {
24
+ args: { element: { props: { text: "Heading Level 4", level: "h4" } } },
25
+ };
@@ -0,0 +1,27 @@
1
+ import React from "react";
2
+ import { dashboardCatalog } from "../../../lib/catalog.js";
3
+ import type { CatalogueComponentProps } from "../../../lib/component-catalog.js";
4
+
5
+ export function Heading({
6
+ element,
7
+ }: CatalogueComponentProps<typeof dashboardCatalog.components.Heading>) {
8
+ const { text, level } = element.props;
9
+ const Tag = (level || "h2") as keyof React.JSX.IntrinsicElements;
10
+ const sizes: Record<string, string> = {
11
+ h1: "28px",
12
+ h2: "24px",
13
+ h3: "20px",
14
+ h4: "16px",
15
+ };
16
+ return (
17
+ <Tag
18
+ style={{
19
+ margin: "0 0 16px",
20
+ fontSize: sizes[level || "h2"],
21
+ fontWeight: 600,
22
+ }}
23
+ >
24
+ {text}
25
+ </Tag>
26
+ );
27
+ }
@@ -0,0 +1,37 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { List } from "./index.js";
3
+
4
+ const meta: Meta<typeof List> = {
5
+ title: "Dashboard/List",
6
+ component: List,
7
+ };
8
+ export default meta;
9
+ type Story = StoryObj<typeof List>;
10
+
11
+ export const Default: Story = {
12
+ render: () => (
13
+ <List element={{ props: { dataPath: "items", emptyMessage: "No items" } }}>
14
+ <div
15
+ style={{ padding: "8px 0", borderBottom: "1px solid var(--border)" }}
16
+ >
17
+ Item 1
18
+ </div>
19
+ <div
20
+ style={{ padding: "8px 0", borderBottom: "1px solid var(--border)" }}
21
+ >
22
+ Item 2
23
+ </div>
24
+ <div style={{ padding: "8px 0" }}>Item 3</div>
25
+ </List>
26
+ ),
27
+ };
28
+
29
+ export const Empty: Story = {
30
+ render: () => (
31
+ <List
32
+ element={{ props: { dataPath: "items", emptyMessage: "Nothing here" } }}
33
+ >
34
+ {null}
35
+ </List>
36
+ ),
37
+ };
@@ -0,0 +1,24 @@
1
+ import { dashboardCatalog } from "../../../lib/catalog.js";
2
+ import type { CatalogueComponentProps } from "../../../lib/component-catalog.js";
3
+
4
+ export function List({
5
+ element,
6
+ children,
7
+ }: CatalogueComponentProps<typeof dashboardCatalog.components.List>) {
8
+ const { dataPath, emptyMessage } = element.props;
9
+
10
+ return (
11
+ <div>
12
+ {children}
13
+ <p
14
+ style={{
15
+ margin: "8px 0 0",
16
+ fontSize: 12,
17
+ color: "hsl(var(--muted-foreground))",
18
+ }}
19
+ >
20
+ Data: {dataPath} {emptyMessage && `| Empty: "${emptyMessage}"`}
21
+ </p>
22
+ </div>
23
+ );
24
+ }
@@ -0,0 +1,65 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Metric } from "./index.js";
3
+
4
+ const meta: Meta<typeof Metric> = {
5
+ title: "Dashboard/Metric",
6
+ component: Metric,
7
+ };
8
+ export default meta;
9
+ type Story = StoryObj<typeof Metric>;
10
+
11
+ export const TrendUp: Story = {
12
+ args: {
13
+ element: {
14
+ props: {
15
+ label: "Total Users",
16
+ valuePath: "1,234",
17
+ format: "number",
18
+ trend: "up",
19
+ trendValue: "12%",
20
+ },
21
+ },
22
+ },
23
+ };
24
+
25
+ export const TrendDown: Story = {
26
+ args: {
27
+ element: {
28
+ props: {
29
+ label: "Bounce Rate",
30
+ valuePath: "23%",
31
+ format: "percent",
32
+ trend: "down",
33
+ trendValue: "5%",
34
+ },
35
+ },
36
+ },
37
+ };
38
+
39
+ export const Neutral: Story = {
40
+ args: {
41
+ element: {
42
+ props: {
43
+ label: "Sessions",
44
+ valuePath: "890",
45
+ format: "number",
46
+ trend: "neutral",
47
+ trendValue: "0%",
48
+ },
49
+ },
50
+ },
51
+ };
52
+
53
+ export const NoTrend: Story = {
54
+ args: {
55
+ element: {
56
+ props: {
57
+ label: "Revenue",
58
+ valuePath: "$45,678",
59
+ format: "currency",
60
+ trend: null,
61
+ trendValue: null,
62
+ },
63
+ },
64
+ },
65
+ };
@@ -0,0 +1,36 @@
1
+ import { dashboardCatalog } from "../../../lib/catalog.js";
2
+ import type { CatalogueComponentProps } from "../../../lib/component-catalog.js";
3
+
4
+ export function Metric({
5
+ element,
6
+ }: CatalogueComponentProps<typeof dashboardCatalog.components.Metric>) {
7
+ const { label, valuePath, trend, trendValue } = element.props;
8
+
9
+ // For example page, we just display the valuePath as placeholder
10
+ const displayValue = valuePath;
11
+
12
+ return (
13
+ <div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
14
+ <span style={{ fontSize: 14, color: "hsl(var(--muted-foreground))" }}>
15
+ {label}
16
+ </span>
17
+ <span style={{ fontSize: 32, fontWeight: 600 }}>{displayValue}</span>
18
+ {trendValue && (
19
+ <span
20
+ style={{
21
+ fontSize: 14,
22
+ color:
23
+ trend === "up"
24
+ ? "#22c55e"
25
+ : trend === "down"
26
+ ? "#ef4444"
27
+ : "hsl(var(--muted-foreground))",
28
+ }}
29
+ >
30
+ {trend === "up" ? "+" : trend === "down" ? "-" : ""}
31
+ {trendValue}
32
+ </span>
33
+ )}
34
+ </div>
35
+ );
36
+ }