@kopai/ui 0.0.4 → 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.
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 +30 -12
  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 +828 -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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kopai/ui",
3
- "version": "0.0.4",
3
+ "version": "0.1.0",
4
4
  "license": "Apache-2.0",
5
5
  "author": "Vladimir Adamic",
6
6
  "repository": {
@@ -18,33 +18,51 @@
18
18
  "types": "./dist/index.d.mts",
19
19
  "import": "./dist/index.mjs",
20
20
  "require": "./dist/index.cjs"
21
- }
21
+ },
22
+ "./globals.css": "./src/styles/globals.css"
22
23
  },
23
24
  "main": "./dist/index.cjs",
24
25
  "module": "./dist/index.mjs",
25
26
  "types": "./dist/index.d.mts",
26
27
  "files": [
27
- "dist"
28
+ "dist",
29
+ "src"
28
30
  ],
29
31
  "dependencies": {
30
- "@kopai/core": "0.4.0"
32
+ "@tanstack/react-query": "^5",
33
+ "@tanstack/react-virtual": "^3.11.2",
34
+ "recharts": "^2.15.4",
35
+ "@kopai/core": "0.5.0",
36
+ "@kopai/sdk": "0.2.3"
31
37
  },
32
38
  "peerDependencies": {
33
- "react": "^19.2.0",
34
- "react-dom": "^19.2.0"
39
+ "react": "^19.2.4",
40
+ "react-dom": "^19.2.4"
35
41
  },
36
42
  "devDependencies": {
37
- "@types/react": "^19.2.9",
43
+ "@storybook/addon-docs": "^8.6.14",
44
+ "@storybook/react": "^8.6.14",
45
+ "@storybook/react-vite": "^8.6.14",
46
+ "@testing-library/react": "^16.3.0",
47
+ "@types/react": "^19.2.10",
38
48
  "@types/react-dom": "^19.1.0",
39
- "react": "^19.2.0",
40
- "react-dom": "^19.2.0",
41
- "tsdown": "^0.20.1",
49
+ "@vitejs/plugin-react": "^4.5.2",
50
+ "autoprefixer": "^10.4.20",
51
+ "jsdom": "^26.1.0",
52
+ "postcss": "^8.5.3",
53
+ "react": "^19.2.4",
54
+ "react-dom": "^19.2.4",
55
+ "storybook": "^8.6.14",
56
+ "tailwindcss": "^3.4.17",
57
+ "tsdown": "^0.20.3",
58
+ "vite": "^6.3.5",
42
59
  "@kopai/tsconfig": "0.2.0"
43
60
  },
44
61
  "scripts": {
45
62
  "build": "tsdown",
46
- "dev": "tsdown --watch",
47
63
  "lint": "eslint src",
48
- "type-check": "tsc --noEmit"
64
+ "type-check": "tsc --noEmit",
65
+ "storybook": "storybook dev -p 6006",
66
+ "build-storybook": "storybook build"
49
67
  }
50
68
  }
@@ -0,0 +1,113 @@
1
+ import { useState, useCallback, useEffect, useMemo } from "react";
2
+ import type { ReactNode } from "react";
3
+ import type { ShortcutGroup, ShortcutsRegistry } from "./types.js";
4
+ import { KeyboardShortcutsContext } from "./context.js";
5
+ import { ShortcutsHelpDialog } from "./ShortcutsHelpDialog.js";
6
+
7
+ const GENERAL_GROUP: ShortcutGroup = {
8
+ name: "General",
9
+ shortcuts: [
10
+ { keys: ["Shift", "?"], description: "Toggle shortcuts help" },
11
+ { keys: ["Shift", "S"], description: "Services tab" },
12
+ { keys: ["Shift", "L"], description: "Logs tab" },
13
+ { keys: ["Shift", "M"], description: "Metrics tab" },
14
+ ],
15
+ };
16
+
17
+ interface KeyboardShortcutsProviderProps {
18
+ children: ReactNode;
19
+ onNavigateServices: () => void;
20
+ onNavigateLogs: () => void;
21
+ onNavigateMetrics: () => void;
22
+ }
23
+
24
+ export function KeyboardShortcutsProvider({
25
+ children,
26
+ onNavigateServices,
27
+ onNavigateLogs,
28
+ onNavigateMetrics,
29
+ }: KeyboardShortcutsProviderProps) {
30
+ const [registry, setRegistry] = useState<ShortcutsRegistry>(() => new Map());
31
+ const [isOpen, setIsOpen] = useState(false);
32
+
33
+ const register = useCallback((id: string, group: ShortcutGroup) => {
34
+ setRegistry((prev) => {
35
+ const next = new Map(prev);
36
+ next.set(id, group);
37
+ return next;
38
+ });
39
+ }, []);
40
+
41
+ const unregister = useCallback((id: string) => {
42
+ setRegistry((prev) => {
43
+ const next = new Map(prev);
44
+ next.delete(id);
45
+ return next;
46
+ });
47
+ }, []);
48
+
49
+ useEffect(() => {
50
+ function handleKeyDown(e: KeyboardEvent) {
51
+ const target = e.target as HTMLElement;
52
+ if (
53
+ target.tagName === "INPUT" ||
54
+ target.tagName === "TEXTAREA" ||
55
+ target.tagName === "SELECT" ||
56
+ target.isContentEditable
57
+ ) {
58
+ return;
59
+ }
60
+
61
+ if (e.shiftKey && e.key === "?") {
62
+ e.preventDefault();
63
+ setIsOpen((v) => !v);
64
+ return;
65
+ }
66
+
67
+ if (e.key === "Escape" && isOpen) {
68
+ e.preventDefault();
69
+ setIsOpen(false);
70
+ return;
71
+ }
72
+
73
+ if (e.shiftKey && e.key === "S") {
74
+ e.preventDefault();
75
+ onNavigateServices();
76
+ return;
77
+ }
78
+ if (e.shiftKey && e.key === "L") {
79
+ e.preventDefault();
80
+ onNavigateLogs();
81
+ return;
82
+ }
83
+ if (e.shiftKey && e.key === "M") {
84
+ e.preventDefault();
85
+ onNavigateMetrics();
86
+ return;
87
+ }
88
+ }
89
+
90
+ document.addEventListener("keydown", handleKeyDown);
91
+ return () => document.removeEventListener("keydown", handleKeyDown);
92
+ }, [isOpen, onNavigateServices, onNavigateLogs, onNavigateMetrics]);
93
+
94
+ const groups = useMemo(() => {
95
+ return [GENERAL_GROUP, ...registry.values()];
96
+ }, [registry]);
97
+
98
+ const contextValue = useMemo(
99
+ () => ({ register, unregister }),
100
+ [register, unregister]
101
+ );
102
+
103
+ return (
104
+ <KeyboardShortcutsContext.Provider value={contextValue}>
105
+ {children}
106
+ <ShortcutsHelpDialog
107
+ open={isOpen}
108
+ onClose={() => setIsOpen(false)}
109
+ groups={groups}
110
+ />
111
+ </KeyboardShortcutsContext.Provider>
112
+ );
113
+ }
@@ -0,0 +1,82 @@
1
+ import type { ShortcutGroup } from "./types.js";
2
+
3
+ interface ShortcutsHelpDialogProps {
4
+ open: boolean;
5
+ onClose: () => void;
6
+ groups: ShortcutGroup[];
7
+ }
8
+
9
+ export function ShortcutsHelpDialog({
10
+ open,
11
+ onClose,
12
+ groups,
13
+ }: ShortcutsHelpDialogProps) {
14
+ if (!open) return null;
15
+
16
+ const handleKeyDown = (e: React.KeyboardEvent) => {
17
+ if (e.key === "Escape") {
18
+ e.stopPropagation();
19
+ onClose();
20
+ }
21
+ };
22
+
23
+ return (
24
+ <div
25
+ className="fixed inset-0 z-50 flex items-center justify-center bg-black/60"
26
+ onClick={onClose}
27
+ onKeyDown={handleKeyDown}
28
+ >
29
+ <div
30
+ role="dialog"
31
+ aria-modal="true"
32
+ aria-label="Keyboard Shortcuts"
33
+ className="bg-zinc-900 border border-zinc-700 rounded-lg shadow-xl max-w-2xl w-full mx-4 max-h-[80vh] overflow-y-auto"
34
+ onClick={(e) => e.stopPropagation()}
35
+ >
36
+ <div className="flex items-center justify-between px-6 py-4 border-b border-zinc-700">
37
+ <h2 className="text-lg font-semibold text-zinc-100">
38
+ Keyboard Shortcuts
39
+ </h2>
40
+ <button
41
+ onClick={onClose}
42
+ aria-label="Close"
43
+ className="text-zinc-400 hover:text-zinc-200 text-xl leading-none"
44
+ >
45
+ &times;
46
+ </button>
47
+ </div>
48
+ <div className="p-6 grid grid-cols-1 sm:grid-cols-2 gap-6">
49
+ {groups.map((group) => (
50
+ <div key={group.name}>
51
+ <h3 className="text-sm font-medium text-zinc-400 mb-3">
52
+ {group.name}
53
+ </h3>
54
+ <ul className="space-y-2">
55
+ {group.shortcuts.map((shortcut) => (
56
+ <li
57
+ key={shortcut.keys.join("+")}
58
+ className="flex items-center justify-between text-sm"
59
+ >
60
+ <span className="text-zinc-300">
61
+ {shortcut.description}
62
+ </span>
63
+ <span className="flex gap-1 ml-4">
64
+ {shortcut.keys.map((key) => (
65
+ <kbd
66
+ key={key}
67
+ className="px-1.5 py-0.5 text-xs font-mono bg-zinc-800 border border-zinc-600 rounded text-zinc-300"
68
+ >
69
+ {key}
70
+ </kbd>
71
+ ))}
72
+ </span>
73
+ </li>
74
+ ))}
75
+ </ul>
76
+ </div>
77
+ ))}
78
+ </div>
79
+ </div>
80
+ </div>
81
+ );
82
+ }
@@ -0,0 +1,23 @@
1
+ import { createContext, useContext, useEffect } from "react";
2
+ import type { ShortcutGroup } from "./types.js";
3
+
4
+ interface KeyboardShortcutsContextValue {
5
+ register: (id: string, group: ShortcutGroup) => void;
6
+ unregister: (id: string) => void;
7
+ }
8
+
9
+ const noop = () => {};
10
+
11
+ export const KeyboardShortcutsContext =
12
+ createContext<KeyboardShortcutsContextValue>({
13
+ register: noop,
14
+ unregister: noop,
15
+ });
16
+
17
+ export function useRegisterShortcuts(id: string, group: ShortcutGroup) {
18
+ const { register, unregister } = useContext(KeyboardShortcutsContext);
19
+ useEffect(() => {
20
+ register(id, group);
21
+ return () => unregister(id);
22
+ }, [id, group, register, unregister]);
23
+ }
@@ -0,0 +1,8 @@
1
+ export { KeyboardShortcutsProvider } from "./KeyboardShortcutsProvider.js";
2
+ export { ShortcutsHelpDialog } from "./ShortcutsHelpDialog.js";
3
+ export { useRegisterShortcuts } from "./context.js";
4
+ export type {
5
+ KeyboardShortcut,
6
+ ShortcutGroup,
7
+ ShortcutsRegistry,
8
+ } from "./types.js";
@@ -0,0 +1,11 @@
1
+ export interface KeyboardShortcut {
2
+ keys: string[];
3
+ description: string;
4
+ }
5
+
6
+ export interface ShortcutGroup {
7
+ name: string;
8
+ shortcuts: KeyboardShortcut[];
9
+ }
10
+
11
+ export type ShortcutsRegistry = Map<string, ShortcutGroup>;
@@ -0,0 +1,29 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Badge } from "./index.js";
3
+
4
+ const meta: Meta<typeof Badge> = {
5
+ title: "Dashboard/Badge",
6
+ component: Badge,
7
+ };
8
+ export default meta;
9
+ type Story = StoryObj<typeof Badge>;
10
+
11
+ export const Default: Story = {
12
+ args: { element: { props: { text: "Default", variant: "default" } } },
13
+ };
14
+
15
+ export const Success: Story = {
16
+ args: { element: { props: { text: "Success", variant: "success" } } },
17
+ };
18
+
19
+ export const Warning: Story = {
20
+ args: { element: { props: { text: "Warning", variant: "warning" } } },
21
+ };
22
+
23
+ export const Danger: Story = {
24
+ args: { element: { props: { text: "Danger", variant: "danger" } } },
25
+ };
26
+
27
+ export const Info: Story = {
28
+ args: { element: { props: { text: "Info", variant: "info" } } },
29
+ };
@@ -0,0 +1,32 @@
1
+ import { dashboardCatalog } from "../../../lib/catalog.js";
2
+ import type { CatalogueComponentProps } from "../../../lib/component-catalog.js";
3
+
4
+ export function Badge({
5
+ element,
6
+ }: CatalogueComponentProps<typeof dashboardCatalog.components.Badge>) {
7
+ const { text, variant } = element.props;
8
+
9
+ const colors: Record<string, string> = {
10
+ default: "hsl(var(--foreground))",
11
+ success: "#22c55e",
12
+ warning: "#eab308",
13
+ danger: "#ef4444",
14
+ info: "hsl(var(--muted-foreground))",
15
+ };
16
+
17
+ return (
18
+ <span
19
+ style={{
20
+ display: "inline-block",
21
+ padding: "2px 8px",
22
+ borderRadius: 12,
23
+ fontSize: 12,
24
+ fontWeight: 500,
25
+ background: "hsl(var(--border))",
26
+ color: colors[variant || "default"],
27
+ }}
28
+ >
29
+ {text}
30
+ </span>
31
+ );
32
+ }
@@ -0,0 +1,107 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Button } from "./index.js";
3
+
4
+ const meta: Meta<typeof Button> = {
5
+ title: "Dashboard/Button",
6
+ component: Button,
7
+ };
8
+ export default meta;
9
+ type Story = StoryObj<typeof Button>;
10
+
11
+ export const Primary: Story = {
12
+ args: {
13
+ element: {
14
+ props: {
15
+ label: "Primary",
16
+ variant: "primary",
17
+ size: "md",
18
+ action: "click",
19
+ disabled: null,
20
+ },
21
+ },
22
+ },
23
+ };
24
+
25
+ export const Secondary: Story = {
26
+ args: {
27
+ element: {
28
+ props: {
29
+ label: "Secondary",
30
+ variant: "secondary",
31
+ size: "md",
32
+ action: "click",
33
+ disabled: null,
34
+ },
35
+ },
36
+ },
37
+ };
38
+
39
+ export const Danger: Story = {
40
+ args: {
41
+ element: {
42
+ props: {
43
+ label: "Danger",
44
+ variant: "danger",
45
+ size: "md",
46
+ action: "click",
47
+ disabled: null,
48
+ },
49
+ },
50
+ },
51
+ };
52
+
53
+ export const Ghost: Story = {
54
+ args: {
55
+ element: {
56
+ props: {
57
+ label: "Ghost",
58
+ variant: "ghost",
59
+ size: "md",
60
+ action: "click",
61
+ disabled: null,
62
+ },
63
+ },
64
+ },
65
+ };
66
+
67
+ export const Disabled: Story = {
68
+ args: {
69
+ element: {
70
+ props: {
71
+ label: "Disabled",
72
+ variant: "primary",
73
+ size: "md",
74
+ action: "click",
75
+ disabled: true,
76
+ },
77
+ },
78
+ },
79
+ };
80
+
81
+ export const Small: Story = {
82
+ args: {
83
+ element: {
84
+ props: {
85
+ label: "Small",
86
+ variant: "primary",
87
+ size: "sm",
88
+ action: "click",
89
+ disabled: null,
90
+ },
91
+ },
92
+ },
93
+ };
94
+
95
+ export const Large: Story = {
96
+ args: {
97
+ element: {
98
+ props: {
99
+ label: "Large",
100
+ variant: "primary",
101
+ size: "lg",
102
+ action: "click",
103
+ disabled: null,
104
+ },
105
+ },
106
+ },
107
+ };
@@ -0,0 +1,63 @@
1
+ import { dashboardCatalog } from "../../../lib/catalog.js";
2
+ import type { CatalogueComponentProps } from "../../../lib/component-catalog.js";
3
+
4
+ export function Button({
5
+ element,
6
+ }: CatalogueComponentProps<typeof dashboardCatalog.components.Button>) {
7
+ const { label, variant, size, action, disabled } = element.props;
8
+
9
+ const variants: Record<
10
+ string,
11
+ { bg: string; color: string; border: string }
12
+ > = {
13
+ primary: {
14
+ bg: "hsl(var(--foreground))",
15
+ color: "hsl(var(--background))",
16
+ border: "hsl(var(--foreground))",
17
+ },
18
+ secondary: {
19
+ bg: "hsl(var(--card))",
20
+ color: "hsl(var(--foreground))",
21
+ border: "hsl(var(--border))",
22
+ },
23
+ danger: {
24
+ bg: "#ef4444",
25
+ color: "white",
26
+ border: "#ef4444",
27
+ },
28
+ ghost: {
29
+ bg: "transparent",
30
+ color: "hsl(var(--foreground))",
31
+ border: "transparent",
32
+ },
33
+ };
34
+
35
+ const sizes: Record<string, { padding: string; fontSize: number }> = {
36
+ sm: { padding: "6px 12px", fontSize: 12 },
37
+ md: { padding: "8px 16px", fontSize: 14 },
38
+ lg: { padding: "12px 24px", fontSize: 16 },
39
+ };
40
+
41
+ const v = variants[variant || "primary"] ?? variants["primary"]!;
42
+ const s = sizes[size || "md"] ?? sizes["md"]!;
43
+
44
+ return (
45
+ <button
46
+ disabled={disabled || false}
47
+ onClick={() => console.log("Action:", action)}
48
+ style={{
49
+ padding: s.padding,
50
+ fontSize: s.fontSize,
51
+ fontWeight: 500,
52
+ borderRadius: "var(--radius)",
53
+ border: `1px solid ${v.border}`,
54
+ background: v.bg,
55
+ color: v.color,
56
+ cursor: disabled ? "not-allowed" : "pointer",
57
+ opacity: disabled ? 0.5 : 1,
58
+ }}
59
+ >
60
+ {label}
61
+ </button>
62
+ );
63
+ }
@@ -0,0 +1,81 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Card } from "./index.js";
3
+
4
+ const meta: Meta<typeof Card> = {
5
+ title: "Dashboard/Card",
6
+ component: Card,
7
+ };
8
+ export default meta;
9
+ type Story = StoryObj<typeof Card>;
10
+
11
+ export const Default: Story = {
12
+ render: () => (
13
+ <Card
14
+ element={{
15
+ key: "card",
16
+ type: "Card",
17
+ children: [],
18
+ parentKey: "",
19
+ props: {
20
+ title: "Card Title",
21
+ description: "A description",
22
+ padding: "md",
23
+ },
24
+ }}
25
+ hasData={false}
26
+ >
27
+ <p style={{ margin: 0 }}>Card content goes here</p>
28
+ </Card>
29
+ ),
30
+ };
31
+
32
+ export const TitleOnly: Story = {
33
+ render: () => (
34
+ <Card
35
+ element={{
36
+ key: "card",
37
+ type: "Card",
38
+ children: [],
39
+ parentKey: "",
40
+ props: { title: "Title Only", description: null, padding: null },
41
+ }}
42
+ hasData={false}
43
+ >
44
+ <p style={{ margin: 0 }}>Content</p>
45
+ </Card>
46
+ ),
47
+ };
48
+
49
+ export const NoHeader: Story = {
50
+ render: () => (
51
+ <Card
52
+ element={{
53
+ key: "card",
54
+ type: "Card",
55
+ children: [],
56
+ parentKey: "",
57
+ props: { title: null, description: null, padding: "lg" },
58
+ }}
59
+ hasData={false}
60
+ >
61
+ <p style={{ margin: 0 }}>Card without header</p>
62
+ </Card>
63
+ ),
64
+ };
65
+
66
+ export const SmallPadding: Story = {
67
+ render: () => (
68
+ <Card
69
+ element={{
70
+ key: "card",
71
+ type: "Card",
72
+ children: [],
73
+ parentKey: "",
74
+ props: { title: "Compact", description: null, padding: "sm" },
75
+ }}
76
+ hasData={false}
77
+ >
78
+ <p style={{ margin: 0 }}>Small padding</p>
79
+ </Card>
80
+ ),
81
+ };
@@ -0,0 +1,58 @@
1
+ import { dashboardCatalog } from "../../../lib/catalog.js";
2
+ import type { RendererComponentProps } from "../../../lib/renderer.js";
3
+
4
+ export function Card({
5
+ element,
6
+ children,
7
+ }: RendererComponentProps<typeof dashboardCatalog.components.Card>) {
8
+ const { title, description, padding } = element.props as {
9
+ title?: string | null;
10
+ description?: string | null;
11
+ padding?: string | null;
12
+ };
13
+
14
+ const paddings: Record<string, string> = {
15
+ sm: "12px",
16
+ md: "16px",
17
+ lg: "24px",
18
+ };
19
+
20
+ return (
21
+ <div
22
+ style={{
23
+ background: "hsl(var(--card))",
24
+ border: "1px solid hsl(var(--border))",
25
+ borderRadius: "var(--radius)",
26
+ }}
27
+ >
28
+ {(title || description) && (
29
+ <div
30
+ style={{
31
+ padding: "16px 20px",
32
+ borderBottom: "1px solid hsl(var(--border))",
33
+ }}
34
+ >
35
+ {title && (
36
+ <h3 style={{ margin: 0, fontSize: 16, fontWeight: 600 }}>
37
+ {title}
38
+ </h3>
39
+ )}
40
+ {description && (
41
+ <p
42
+ style={{
43
+ margin: "4px 0 0",
44
+ fontSize: 14,
45
+ color: "hsl(var(--muted-foreground))",
46
+ }}
47
+ >
48
+ {description}
49
+ </p>
50
+ )}
51
+ </div>
52
+ )}
53
+ <div style={{ padding: paddings[padding || ""] || "16px" }}>
54
+ {children}
55
+ </div>
56
+ </div>
57
+ );
58
+ }