@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kopai/ui",
|
|
3
|
-
"version": "0.0
|
|
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
|
-
"@
|
|
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
39
|
"react": "^19.2.4",
|
|
34
40
|
"react-dom": "^19.2.4"
|
|
35
41
|
},
|
|
36
42
|
"devDependencies": {
|
|
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",
|
|
37
47
|
"@types/react": "^19.2.10",
|
|
38
48
|
"@types/react-dom": "^19.1.0",
|
|
49
|
+
"@vitejs/plugin-react": "^4.5.2",
|
|
50
|
+
"autoprefixer": "^10.4.20",
|
|
51
|
+
"jsdom": "^26.1.0",
|
|
52
|
+
"postcss": "^8.5.3",
|
|
39
53
|
"react": "^19.2.4",
|
|
40
54
|
"react-dom": "^19.2.4",
|
|
41
|
-
"
|
|
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
|
+
×
|
|
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,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
|
+
}
|