@kopai/ui 0.0.5 → 0.1.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/README.md +137 -0
- package/dist/index.cjs +5069 -3
- package/dist/index.d.cts +301 -3
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +302 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +5010 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +25 -7
- package/src/components/KeyboardShortcuts/KeyboardShortcutsProvider.tsx +113 -0
- package/src/components/KeyboardShortcuts/ShortcutsHelpDialog.tsx +82 -0
- package/src/components/KeyboardShortcuts/context.ts +23 -0
- package/src/components/KeyboardShortcuts/index.ts +8 -0
- package/src/components/KeyboardShortcuts/types.ts +11 -0
- package/src/components/dashboard/Badge/Badge.stories.tsx +29 -0
- package/src/components/dashboard/Badge/index.tsx +32 -0
- package/src/components/dashboard/Button/Button.stories.tsx +107 -0
- package/src/components/dashboard/Button/index.tsx +63 -0
- package/src/components/dashboard/Card/Card.stories.tsx +81 -0
- package/src/components/dashboard/Card/index.tsx +58 -0
- package/src/components/dashboard/Chart/Chart.stories.tsx +48 -0
- package/src/components/dashboard/Chart/index.tsx +74 -0
- package/src/components/dashboard/DatePicker/DatePicker.stories.tsx +33 -0
- package/src/components/dashboard/DatePicker/index.tsx +41 -0
- package/src/components/dashboard/Divider/Divider.stories.tsx +17 -0
- package/src/components/dashboard/Divider/index.tsx +49 -0
- package/src/components/dashboard/Empty/Empty.stories.tsx +48 -0
- package/src/components/dashboard/Empty/index.tsx +46 -0
- package/src/components/dashboard/Grid/Grid.stories.tsx +52 -0
- package/src/components/dashboard/Grid/index.tsx +26 -0
- package/src/components/dashboard/Heading/Heading.stories.tsx +25 -0
- package/src/components/dashboard/Heading/index.tsx +27 -0
- package/src/components/dashboard/List/List.stories.tsx +37 -0
- package/src/components/dashboard/List/index.tsx +24 -0
- package/src/components/dashboard/Metric/Metric.stories.tsx +65 -0
- package/src/components/dashboard/Metric/index.tsx +36 -0
- package/src/components/dashboard/Stack/Stack.stories.tsx +61 -0
- package/src/components/dashboard/Stack/index.tsx +33 -0
- package/src/components/dashboard/Table/Table.stories.tsx +38 -0
- package/src/components/dashboard/Table/index.tsx +104 -0
- package/src/components/dashboard/Text/Text.stories.tsx +53 -0
- package/src/components/dashboard/Text/index.tsx +18 -0
- package/src/components/dashboard/index.ts +46 -0
- package/src/components/index.ts +17 -0
- package/src/components/observability/LogTimeline/LogDetailPane/AttributesTab.tsx +56 -0
- package/src/components/observability/LogTimeline/LogDetailPane/JsonTreeView.tsx +139 -0
- package/src/components/observability/LogTimeline/LogDetailPane/index.tsx +271 -0
- package/src/components/observability/LogTimeline/LogFilter.stories.tsx +66 -0
- package/src/components/observability/LogTimeline/LogFilter.test.tsx +696 -0
- package/src/components/observability/LogTimeline/LogFilter.tsx +674 -0
- package/src/components/observability/LogTimeline/LogRow.tsx +174 -0
- package/src/components/observability/LogTimeline/LogTimeline.stories.tsx +154 -0
- package/src/components/observability/LogTimeline/index.tsx +542 -0
- package/src/components/observability/LogTimeline/shortcuts.ts +18 -0
- package/src/components/observability/MetricHistogram/MetricHistogram.stories.tsx +20 -0
- package/src/components/observability/MetricHistogram/index.tsx +303 -0
- package/src/components/observability/MetricStat/MetricStat.stories.tsx +30 -0
- package/src/components/observability/MetricStat/index.tsx +281 -0
- package/src/components/observability/MetricTable/MetricTable.stories.tsx +20 -0
- package/src/components/observability/MetricTable/index.tsx +194 -0
- package/src/components/observability/MetricTimeSeries/MetricTimeSeries.stories.tsx +28 -0
- package/src/components/observability/MetricTimeSeries/index.tsx +462 -0
- package/src/components/observability/RawDataTable/RawDataTable.stories.tsx +27 -0
- package/src/components/observability/RawDataTable/index.tsx +131 -0
- package/src/components/observability/ServiceList/ServiceList.stories.tsx +20 -0
- package/src/components/observability/ServiceList/index.tsx +60 -0
- package/src/components/observability/ServiceList/shortcuts.ts +6 -0
- package/src/components/observability/TabBar/TabBar.stories.tsx +34 -0
- package/src/components/observability/TabBar/index.tsx +46 -0
- package/src/components/observability/TraceDetail/TraceDetail.stories.tsx +51 -0
- package/src/components/observability/TraceDetail/index.tsx +53 -0
- package/src/components/observability/TraceSearch/TraceSearch.stories.tsx +49 -0
- package/src/components/observability/TraceSearch/index.tsx +292 -0
- package/src/components/observability/TraceTimeline/DetailPane/AttributesTab.tsx +152 -0
- package/src/components/observability/TraceTimeline/DetailPane/EventsTab.tsx +128 -0
- package/src/components/observability/TraceTimeline/DetailPane/LinksTab.tsx +210 -0
- package/src/components/observability/TraceTimeline/DetailPane/index.tsx +174 -0
- package/src/components/observability/TraceTimeline/SpanRow.tsx +173 -0
- package/src/components/observability/TraceTimeline/TimelineBar.tsx +41 -0
- package/src/components/observability/TraceTimeline/Tooltip.tsx +42 -0
- package/src/components/observability/TraceTimeline/TraceHeader.tsx +88 -0
- package/src/components/observability/TraceTimeline/TraceTimeline.stories.tsx +25 -0
- package/src/components/observability/TraceTimeline/index.tsx +478 -0
- package/src/components/observability/TraceTimeline/shortcuts.ts +16 -0
- package/src/components/observability/__fixtures__/logs.ts +476 -0
- package/src/components/observability/__fixtures__/metrics.ts +216 -0
- package/src/components/observability/__fixtures__/raw-table.ts +204 -0
- package/src/components/observability/__fixtures__/services.ts +8 -0
- package/src/components/observability/__fixtures__/trace-summaries.ts +81 -0
- package/src/components/observability/__fixtures__/traces.ts +396 -0
- package/src/components/observability/index.ts +66 -0
- package/src/components/observability/renderers/OtelMetricDiscovery.tsx +77 -0
- package/src/components/observability/renderers/OtelMetricHistogram.tsx +29 -0
- package/src/components/observability/renderers/OtelMetricStat.tsx +44 -0
- package/src/components/observability/renderers/OtelMetricTable.tsx +29 -0
- package/src/components/observability/renderers/OtelMetricTimeSeries.tsx +30 -0
- package/src/components/observability/renderers/index.ts +5 -0
- package/src/components/observability/shared/LoadingSkeleton.tsx +43 -0
- package/src/components/observability/types.ts +113 -0
- package/src/components/observability/utils/attributes.ts +17 -0
- package/src/components/observability/utils/colors.ts +29 -0
- package/src/components/observability/utils/flatten-tree.ts +53 -0
- package/src/components/observability/utils/lttb.ts +121 -0
- package/src/components/observability/utils/time.ts +46 -0
- package/src/hooks/use-kopai-data.test.ts +296 -0
- package/src/hooks/use-kopai-data.ts +64 -0
- package/src/hooks/use-live-logs.test.ts +193 -0
- package/src/hooks/use-live-logs.ts +113 -0
- package/src/index.ts +15 -0
- package/src/lib/__snapshots__/generate-prompt-instructions.test.ts.snap +33 -0
- package/src/lib/catalog.ts +165 -0
- package/src/lib/component-catalog.test.ts +357 -0
- package/src/lib/component-catalog.ts +171 -0
- package/src/lib/dashboard-datasource.ts +76 -0
- package/src/lib/generate-prompt-instructions.test.ts +27 -0
- package/src/lib/generate-prompt-instructions.ts +185 -0
- package/src/lib/log-buffer.test.ts +88 -0
- package/src/lib/log-buffer.ts +62 -0
- package/src/lib/observability-catalog.ts +143 -0
- package/src/lib/renderer.test.tsx +693 -0
- package/src/lib/renderer.tsx +276 -0
- package/src/pages/observability.tsx +828 -0
- package/src/providers/kopai-provider.tsx +51 -0
- package/src/styles/globals.css +46 -0
- 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
|
+
}
|