@schandlergarcia/sf-web-components 1.9.37 → 1.9.39
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/package.json +4 -1
- package/scripts/postinstall.mjs +116 -65
- package/src/components/library/cards/ActionList.jsx +38 -0
- package/src/components/library/cards/ActivityCard.jsx +56 -0
- package/src/components/library/cards/BaseCard.jsx +109 -0
- package/src/components/library/cards/CalloutCard.jsx +37 -0
- package/src/components/library/cards/ChartCard.jsx +105 -0
- package/src/components/library/cards/FeedPanel.jsx +39 -0
- package/src/components/library/cards/ListCard.jsx +193 -0
- package/src/components/library/cards/MetricCard.jsx +109 -0
- package/src/components/library/cards/MetricsStrip.jsx +78 -0
- package/src/components/library/cards/SectionCard.jsx +83 -0
- package/src/components/library/cards/SemanticMetricCard.jsx +52 -0
- package/src/components/library/cards/SemanticMetricCardWithLoading.jsx +23 -0
- package/src/components/library/cards/SemanticTableCard.jsx +48 -0
- package/src/components/library/cards/SemanticTableCardWithLoading.jsx +22 -0
- package/src/components/library/cards/StatusCard.jsx +220 -0
- package/src/components/library/cards/TableCard.jsx +337 -0
- package/src/components/library/cards/WidgetCard.jsx +90 -0
- package/src/components/library/charts/D3Chart.jsx +109 -0
- package/src/components/library/charts/D3ChartTemplates.jsx +126 -0
- package/src/components/library/charts/GeoMap.jsx +293 -0
- package/src/components/library/chat/ChatBar.jsx +256 -0
- package/src/components/library/chat/ChatInput.jsx +89 -0
- package/src/components/library/chat/ChatMessage.jsx +178 -0
- package/src/components/library/chat/ChatMessageList.jsx +73 -0
- package/src/components/library/chat/ChatPanel.jsx +97 -0
- package/src/components/library/chat/ChatSuggestions.jsx +28 -0
- package/src/components/library/chat/ChatToolCall.jsx +100 -0
- package/src/components/library/chat/ChatTypingIndicator.jsx +23 -0
- package/src/components/library/chat/ChatWelcome.jsx +43 -0
- package/src/components/library/chat/index.jsx +10 -0
- package/src/components/library/chat/useChatState.jsx +130 -0
- package/src/components/library/data/DataModeProvider.jsx +67 -0
- package/src/components/library/data/DataModeToggle.jsx +36 -0
- package/src/components/library/data/chartDataProvider.jsx +61 -0
- package/src/components/library/data/filterUtils.jsx +141 -0
- package/src/components/library/data/useDataSource.jsx +33 -0
- package/src/components/library/data/usePageFilters.jsx +99 -0
- package/src/components/library/filters/FilterBar.jsx +95 -0
- package/src/components/library/filters/SearchFilter.jsx +36 -0
- package/src/components/library/filters/SelectFilter.jsx +55 -0
- package/src/components/library/filters/ToggleFilter.jsx +52 -0
- package/src/components/library/filters/index.jsx +4 -0
- package/src/components/library/forms/FormField.jsx +291 -0
- package/src/components/library/forms/FormModal.jsx +201 -0
- package/src/components/library/forms/FormRenderer.jsx +46 -0
- package/src/components/library/forms/FormSection.jsx +69 -0
- package/src/components/library/forms/index.jsx +5 -0
- package/src/components/library/forms/useFormState.jsx +165 -0
- package/src/components/library/heroui/Accordion.jsx +26 -0
- package/src/components/library/heroui/Alert.jsx +8 -0
- package/src/components/library/heroui/Badge.jsx +8 -0
- package/src/components/library/heroui/Breadcrumbs.jsx +22 -0
- package/src/components/library/heroui/Button.jsx +58 -0
- package/src/components/library/heroui/Card.jsx +8 -0
- package/src/components/library/heroui/Collapsible.jsx +42 -0
- package/src/components/library/heroui/DatePicker.jsx +34 -0
- package/src/components/library/heroui/Dialog.jsx +37 -0
- package/src/components/library/heroui/Drawer.jsx +32 -0
- package/src/components/library/heroui/Dropdown.jsx +28 -0
- package/src/components/library/heroui/Field.jsx +51 -0
- package/src/components/library/heroui/Input.jsx +6 -0
- package/src/components/library/heroui/Kbd.jsx +8 -0
- package/src/components/library/heroui/Meter.jsx +8 -0
- package/src/components/library/heroui/Modal.jsx +32 -0
- package/src/components/library/heroui/Pagination.jsx +8 -0
- package/src/components/library/heroui/Popover.jsx +64 -0
- package/src/components/library/heroui/ProgressBar.jsx +8 -0
- package/src/components/library/heroui/ProgressCircle.jsx +8 -0
- package/src/components/library/heroui/ScrollShadow.jsx +8 -0
- package/src/components/library/heroui/Select.jsx +37 -0
- package/src/components/library/heroui/Separator.jsx +8 -0
- package/src/components/library/heroui/Skeleton.jsx +8 -0
- package/src/components/library/heroui/Tabs.jsx +26 -0
- package/src/components/library/heroui/Toast.jsx +25 -0
- package/src/components/library/heroui/Toggle.jsx +14 -0
- package/src/components/library/heroui/Tooltip.jsx +21 -0
- package/src/components/library/index.jsx +146 -0
- package/src/components/library/layout/PageContainer.jsx +11 -0
- package/src/components/library/skeletons/CardSkeleton.jsx +30 -0
- package/src/components/library/theme/AppThemeProvider.jsx +67 -0
- package/src/components/library/theme/tokens.jsx +72 -0
- package/src/components/library/ui/Alert.jsx +80 -0
- package/src/components/library/ui/Avatar.jsx +44 -0
- package/src/components/library/ui/BreadcrumbExtras.tsx +120 -0
- package/src/components/library/ui/Button.jsx +61 -0
- package/src/components/library/ui/Card.jsx +117 -0
- package/src/components/library/ui/Checkbox.jsx +17 -0
- package/src/components/library/ui/Chip.jsx +38 -0
- package/src/components/library/ui/Collapsible.tsx +31 -0
- package/src/components/library/ui/Container.jsx +56 -0
- package/src/components/library/ui/DatePicker.tsx +34 -0
- package/src/components/library/ui/Dialog.tsx +141 -0
- package/src/components/library/ui/EmptyState.jsx +46 -0
- package/src/components/library/ui/Field.tsx +82 -0
- package/src/components/library/ui/FieldGroup.jsx +17 -0
- package/src/components/library/ui/Input.jsx +21 -0
- package/src/components/library/ui/Label.jsx +22 -0
- package/src/components/library/ui/PaginationExtras.tsx +142 -0
- package/src/components/library/ui/Popover.tsx +39 -0
- package/src/components/library/ui/Select.tsx +113 -0
- package/src/components/library/ui/Spinner.d.ts +10 -0
- package/src/components/library/ui/Spinner.jsx +64 -0
- package/src/components/library/ui/Text.jsx +46 -0
- package/src/components/library/ui/Toggle.jsx +42 -0
- package/src/components/workspace/ComponentRegistry.jsx +297 -0
- package/src/lib/index.ts +1 -0
- package/src/lib/utils.ts +6 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Select as SelectPrimitive } from "radix-ui";
|
|
3
|
+
|
|
4
|
+
function Select({ ...props }: React.ComponentProps<typeof SelectPrimitive.Root>) {
|
|
5
|
+
return <SelectPrimitive.Root {...props} />;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function SelectTrigger({
|
|
9
|
+
className,
|
|
10
|
+
children,
|
|
11
|
+
...props
|
|
12
|
+
}: React.ComponentProps<typeof SelectPrimitive.Trigger>) {
|
|
13
|
+
return (
|
|
14
|
+
<SelectPrimitive.Trigger
|
|
15
|
+
className={[
|
|
16
|
+
"flex h-10 w-full items-center justify-between rounded-lg border border-slate-200 bg-white px-3 py-2",
|
|
17
|
+
"text-sm text-slate-900 placeholder:text-slate-400",
|
|
18
|
+
"focus:outline-none focus:ring-2 focus:ring-brand-500 focus:ring-offset-2",
|
|
19
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
20
|
+
"dark:border-slate-800 dark:bg-slate-900 dark:text-slate-50 dark:focus:ring-offset-slate-950",
|
|
21
|
+
className
|
|
22
|
+
].filter(Boolean).join(" ")}
|
|
23
|
+
{...props}
|
|
24
|
+
>
|
|
25
|
+
{children}
|
|
26
|
+
<SelectPrimitive.Icon asChild>
|
|
27
|
+
<svg width="15" height="15" viewBox="0 0 15 15" fill="none">
|
|
28
|
+
<path
|
|
29
|
+
d="M4.93179 5.43179C4.75605 5.60753 4.75605 5.89245 4.93179 6.06819C5.10753 6.24392 5.39245 6.24392 5.56819 6.06819L7.49999 4.13638L9.43179 6.06819C9.60753 6.24392 9.89245 6.24392 10.0682 6.06819C10.2439 5.89245 10.2439 5.60753 10.0682 5.43179L7.81819 3.18179C7.73379 3.0974 7.61933 3.04999 7.49999 3.04999C7.38064 3.04999 7.26618 3.0974 7.18179 3.18179L4.93179 5.43179ZM10.0682 9.56819C10.2439 9.39245 10.2439 9.10753 10.0682 8.93179C9.89245 8.75606 9.60753 8.75606 9.43179 8.93179L7.49999 10.8636L5.56819 8.93179C5.39245 8.75606 5.10753 8.75606 4.93179 8.93179C4.75605 9.10753 4.75605 9.39245 4.93179 9.56819L7.18179 11.8182C7.26618 11.9026 7.38064 11.95 7.49999 11.95C7.61933 11.95 7.73379 11.9026 7.81819 11.8182L10.0682 9.56819Z"
|
|
30
|
+
fill="currentColor"
|
|
31
|
+
/>
|
|
32
|
+
</svg>
|
|
33
|
+
</SelectPrimitive.Icon>
|
|
34
|
+
</SelectPrimitive.Trigger>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function SelectValue({ ...props }: React.ComponentProps<typeof SelectPrimitive.Value>) {
|
|
39
|
+
return <SelectPrimitive.Value {...props} />;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function SelectContent({
|
|
43
|
+
className,
|
|
44
|
+
children,
|
|
45
|
+
position = "popper",
|
|
46
|
+
...props
|
|
47
|
+
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
|
|
48
|
+
return (
|
|
49
|
+
<SelectPrimitive.Portal>
|
|
50
|
+
<SelectPrimitive.Content
|
|
51
|
+
className={[
|
|
52
|
+
"relative z-50 min-w-[8rem] overflow-hidden rounded-md border border-slate-200 bg-white text-slate-900 shadow-md",
|
|
53
|
+
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
|
|
54
|
+
"dark:border-slate-800 dark:bg-slate-900 dark:text-slate-50",
|
|
55
|
+
position === "popper" &&
|
|
56
|
+
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
|
57
|
+
className
|
|
58
|
+
].filter(Boolean).join(" ")}
|
|
59
|
+
position={position}
|
|
60
|
+
{...props}
|
|
61
|
+
>
|
|
62
|
+
<SelectPrimitive.Viewport
|
|
63
|
+
className={[
|
|
64
|
+
"p-1",
|
|
65
|
+
position === "popper" &&
|
|
66
|
+
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
|
|
67
|
+
].filter(Boolean).join(" ")}
|
|
68
|
+
>
|
|
69
|
+
{children}
|
|
70
|
+
</SelectPrimitive.Viewport>
|
|
71
|
+
</SelectPrimitive.Content>
|
|
72
|
+
</SelectPrimitive.Portal>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function SelectItem({
|
|
77
|
+
className,
|
|
78
|
+
children,
|
|
79
|
+
...props
|
|
80
|
+
}: React.ComponentProps<typeof SelectPrimitive.Item>) {
|
|
81
|
+
return (
|
|
82
|
+
<SelectPrimitive.Item
|
|
83
|
+
className={[
|
|
84
|
+
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none",
|
|
85
|
+
"focus:bg-slate-100 focus:text-slate-900",
|
|
86
|
+
"data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
87
|
+
"dark:focus:bg-slate-800 dark:focus:text-slate-50",
|
|
88
|
+
className
|
|
89
|
+
].filter(Boolean).join(" ")}
|
|
90
|
+
{...props}
|
|
91
|
+
>
|
|
92
|
+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
93
|
+
<SelectPrimitive.ItemIndicator>
|
|
94
|
+
<svg width="15" height="15" viewBox="0 0 15 15" fill="none">
|
|
95
|
+
<path
|
|
96
|
+
d="M11.4669 3.72684C11.7558 3.91574 11.8369 4.30308 11.648 4.59198L7.39799 11.092C7.29783 11.2452 7.13556 11.3467 6.95402 11.3699C6.77247 11.3931 6.58989 11.3355 6.45446 11.2124L3.70446 8.71241C3.44905 8.48022 3.43023 8.08494 3.66242 7.82953C3.89461 7.57412 4.28989 7.55529 4.5453 7.78749L6.75292 9.79441L10.6018 3.90792C10.7907 3.61902 11.178 3.53795 11.4669 3.72684Z"
|
|
97
|
+
fill="currentColor"
|
|
98
|
+
/>
|
|
99
|
+
</svg>
|
|
100
|
+
</SelectPrimitive.ItemIndicator>
|
|
101
|
+
</span>
|
|
102
|
+
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
|
103
|
+
</SelectPrimitive.Item>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export {
|
|
108
|
+
Select,
|
|
109
|
+
SelectTrigger,
|
|
110
|
+
SelectValue,
|
|
111
|
+
SelectContent,
|
|
112
|
+
SelectItem,
|
|
113
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
interface SpinnerProps extends React.SVGAttributes<SVGSVGElement> {
|
|
4
|
+
size?: "xs" | "sm" | "md" | "lg" | "xl";
|
|
5
|
+
tone?: "brand" | "white" | "muted" | "current";
|
|
6
|
+
label?: string;
|
|
7
|
+
className?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default function Spinner(props: SpinnerProps): React.JSX.Element;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
const SIZE_CLASSES = {
|
|
4
|
+
xs: "h-3 w-3",
|
|
5
|
+
sm: "h-4 w-4",
|
|
6
|
+
md: "h-5 w-5",
|
|
7
|
+
lg: "h-6 w-6",
|
|
8
|
+
xl: "h-8 w-8",
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const TONE_CLASSES = {
|
|
12
|
+
brand: "text-brand-500",
|
|
13
|
+
white: "text-white",
|
|
14
|
+
muted: "text-slate-400 dark:text-slate-500",
|
|
15
|
+
current: "text-current",
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Animated spinner.
|
|
20
|
+
*
|
|
21
|
+
* @param {"xs"|"sm"|"md"|"lg"|"xl"} size
|
|
22
|
+
* @param {"brand"|"white"|"muted"|"current"} tone
|
|
23
|
+
* @param {string} label — screen-reader label
|
|
24
|
+
*/
|
|
25
|
+
export default function Spinner({
|
|
26
|
+
size = "md",
|
|
27
|
+
tone = "brand",
|
|
28
|
+
label = "Loading",
|
|
29
|
+
className = "",
|
|
30
|
+
...rest
|
|
31
|
+
}) {
|
|
32
|
+
return (
|
|
33
|
+
<svg
|
|
34
|
+
{...rest}
|
|
35
|
+
className={[
|
|
36
|
+
"animate-spin",
|
|
37
|
+
SIZE_CLASSES[size] ?? SIZE_CLASSES.md,
|
|
38
|
+
TONE_CLASSES[tone] ?? TONE_CLASSES.brand,
|
|
39
|
+
className,
|
|
40
|
+
]
|
|
41
|
+
.filter(Boolean)
|
|
42
|
+
.join(" ")}
|
|
43
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
44
|
+
fill="none"
|
|
45
|
+
viewBox="0 0 24 24"
|
|
46
|
+
role="status"
|
|
47
|
+
aria-label={label}
|
|
48
|
+
>
|
|
49
|
+
<circle
|
|
50
|
+
className="opacity-25"
|
|
51
|
+
cx="12"
|
|
52
|
+
cy="12"
|
|
53
|
+
r="10"
|
|
54
|
+
stroke="currentColor"
|
|
55
|
+
strokeWidth="4"
|
|
56
|
+
/>
|
|
57
|
+
<path
|
|
58
|
+
className="opacity-75"
|
|
59
|
+
fill="currentColor"
|
|
60
|
+
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
61
|
+
/>
|
|
62
|
+
</svg>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
const SIZE_CLASSES = {
|
|
4
|
+
xs: "text-xs",
|
|
5
|
+
sm: "text-sm",
|
|
6
|
+
md: "text-base",
|
|
7
|
+
lg: "text-lg",
|
|
8
|
+
xl: "text-xl",
|
|
9
|
+
xxl: "text-2xl"
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const WEIGHT_CLASSES = {
|
|
13
|
+
regular: "font-normal",
|
|
14
|
+
medium: "font-medium",
|
|
15
|
+
bold: "font-semibold"
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export default function UIText({
|
|
19
|
+
as: Comp = "span",
|
|
20
|
+
size = "md",
|
|
21
|
+
weight = "regular",
|
|
22
|
+
muted = false,
|
|
23
|
+
style,
|
|
24
|
+
className = "",
|
|
25
|
+
children,
|
|
26
|
+
...rest
|
|
27
|
+
}) {
|
|
28
|
+
return (
|
|
29
|
+
<Comp
|
|
30
|
+
style={style}
|
|
31
|
+
className={[
|
|
32
|
+
SIZE_CLASSES[size] ?? SIZE_CLASSES.md,
|
|
33
|
+
WEIGHT_CLASSES[weight] ?? WEIGHT_CLASSES.regular,
|
|
34
|
+
muted ? "text-slate-600 dark:text-slate-300" : "text-slate-900 dark:text-slate-50",
|
|
35
|
+
className
|
|
36
|
+
]
|
|
37
|
+
.filter(Boolean)
|
|
38
|
+
.join(" ")}
|
|
39
|
+
{...rest}
|
|
40
|
+
>
|
|
41
|
+
{children}
|
|
42
|
+
</Comp>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { useThemeMode } from "../theme/AppThemeProvider";
|
|
3
|
+
|
|
4
|
+
export default function UIToggle({ label = "Dark mode", style, className = "" }) {
|
|
5
|
+
const { mode, toggle } = useThemeMode();
|
|
6
|
+
const isDark = mode === "dark";
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<button
|
|
10
|
+
type="button"
|
|
11
|
+
onClick={toggle}
|
|
12
|
+
style={style}
|
|
13
|
+
className={[
|
|
14
|
+
"inline-flex items-center gap-2 rounded-lg border border-slate-200 bg-white px-3 py-2 text-sm font-medium text-slate-900 shadow-sm",
|
|
15
|
+
"hover:bg-slate-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-500 focus-visible:ring-offset-2 dark:focus-visible:ring-offset-slate-950",
|
|
16
|
+
"dark:border-slate-800 dark:bg-slate-900 dark:text-slate-50 dark:hover:bg-slate-800",
|
|
17
|
+
className
|
|
18
|
+
]
|
|
19
|
+
.filter(Boolean)
|
|
20
|
+
.join(" ")}
|
|
21
|
+
aria-pressed={isDark}
|
|
22
|
+
aria-label={label}
|
|
23
|
+
>
|
|
24
|
+
<span
|
|
25
|
+
className={[
|
|
26
|
+
"inline-flex h-5 w-9 items-center rounded-full border transition",
|
|
27
|
+
isDark ? "border-brand-400 bg-brand-500" : "border-slate-300 bg-slate-200"
|
|
28
|
+
].join(" ")}
|
|
29
|
+
>
|
|
30
|
+
<span
|
|
31
|
+
className={[
|
|
32
|
+
"h-4 w-4 rounded-full bg-white shadow-sm transition",
|
|
33
|
+
isDark ? "translate-x-4" : "translate-x-1"
|
|
34
|
+
].join(" ")}
|
|
35
|
+
/>
|
|
36
|
+
</span>
|
|
37
|
+
<span className="hidden sm:inline">{label}</span>
|
|
38
|
+
</button>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import {
|
|
3
|
+
BaseCard,
|
|
4
|
+
MetricCard,
|
|
5
|
+
TableCard,
|
|
6
|
+
ChartCard,
|
|
7
|
+
D3Chart,
|
|
8
|
+
D3ChartTemplates,
|
|
9
|
+
StatusCard,
|
|
10
|
+
ListCard,
|
|
11
|
+
UIButton,
|
|
12
|
+
UIChip,
|
|
13
|
+
UIText
|
|
14
|
+
} from "@/components/library";
|
|
15
|
+
|
|
16
|
+
const BUILTIN_COMPONENTS = {
|
|
17
|
+
NarrativeSummary({ summary, title }) {
|
|
18
|
+
return (
|
|
19
|
+
<div className="text-sm text-slate-600 dark:text-slate-300">
|
|
20
|
+
{title && <div className="mb-1 font-medium text-slate-900 dark:text-slate-50">{title}</div>}
|
|
21
|
+
{summary}
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
MetricsStrip({ metrics = [], title, collapsible = false, collapsed: initialCollapsed = false }) {
|
|
27
|
+
const [collapsed, setCollapsed] = React.useState(initialCollapsed);
|
|
28
|
+
|
|
29
|
+
const items = metrics.length ? metrics : [
|
|
30
|
+
{ label: "Metric A", value: "—", trend: null },
|
|
31
|
+
{ label: "Metric B", value: "—", trend: null },
|
|
32
|
+
{ label: "Metric C", value: "—", trend: null }
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
if (collapsible && collapsed) {
|
|
36
|
+
return (
|
|
37
|
+
<button
|
|
38
|
+
type="button"
|
|
39
|
+
onClick={() => setCollapsed(false)}
|
|
40
|
+
className="flex w-full items-center justify-between rounded-lg border border-slate-200 bg-white px-3 py-2 text-left text-xs text-slate-500 hover:bg-slate-50 dark:border-slate-800 dark:bg-slate-900 dark:text-slate-400 dark:hover:bg-slate-800"
|
|
41
|
+
>
|
|
42
|
+
<span>{title ?? "Metrics"}: {items.map(m => `${m.label} ${m.value}`).join(" · ")}</span>
|
|
43
|
+
<span>▸</span>
|
|
44
|
+
</button>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<div className="rounded-xl border border-slate-200 bg-white p-3 dark:border-slate-800 dark:bg-slate-900">
|
|
50
|
+
{(title || collapsible) && (
|
|
51
|
+
<div className="mb-2 flex items-center justify-between">
|
|
52
|
+
<span className="text-xs font-medium text-slate-500 dark:text-slate-400">{title ?? "Metrics"}</span>
|
|
53
|
+
{collapsible && (
|
|
54
|
+
<button
|
|
55
|
+
type="button"
|
|
56
|
+
onClick={() => setCollapsed(true)}
|
|
57
|
+
className="text-xs text-slate-400 hover:text-slate-600 dark:text-slate-500 dark:hover:text-slate-300"
|
|
58
|
+
>
|
|
59
|
+
Collapse
|
|
60
|
+
</button>
|
|
61
|
+
)}
|
|
62
|
+
</div>
|
|
63
|
+
)}
|
|
64
|
+
<div className="flex flex-wrap gap-4">
|
|
65
|
+
{items.map((m) => (
|
|
66
|
+
<div key={m.label} className="min-w-[80px]">
|
|
67
|
+
<div className="text-xs text-slate-400 dark:text-slate-500">{m.label}</div>
|
|
68
|
+
<div className="flex items-baseline gap-1.5">
|
|
69
|
+
<span className="text-sm font-semibold text-slate-700 dark:text-slate-200">{m.value}</span>
|
|
70
|
+
{m.trend && (
|
|
71
|
+
<span className={`text-xs ${String(m.trend).startsWith?.("+") || m.trend > 0 ? "text-red-500" : "text-emerald-500"}`}>
|
|
72
|
+
{typeof m.trend === "number" ? (m.trend > 0 ? `+${m.trend}` : m.trend) : m.trend}
|
|
73
|
+
</span>
|
|
74
|
+
)}
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
))}
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
);
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
ItemList({ items = [], title, onItemClick }) {
|
|
84
|
+
const [expanded, setExpanded] = React.useState(null);
|
|
85
|
+
if (!items.length) {
|
|
86
|
+
return <div className="text-sm text-slate-500 dark:text-slate-400">No items.</div>;
|
|
87
|
+
}
|
|
88
|
+
return (
|
|
89
|
+
<div className="space-y-2">
|
|
90
|
+
{title && (
|
|
91
|
+
<div className="flex items-center justify-between">
|
|
92
|
+
<span className="text-sm font-medium text-slate-900 dark:text-slate-50">{title}</span>
|
|
93
|
+
<span className="text-xs text-slate-400 dark:text-slate-500">{items.length} items</span>
|
|
94
|
+
</div>
|
|
95
|
+
)}
|
|
96
|
+
<div className="space-y-1.5">
|
|
97
|
+
{items.map((item, idx) => (
|
|
98
|
+
<div key={item.id ?? idx} className="rounded-xl border border-slate-200 bg-white dark:border-slate-800 dark:bg-slate-900">
|
|
99
|
+
<button
|
|
100
|
+
type="button"
|
|
101
|
+
onClick={() => {
|
|
102
|
+
setExpanded(expanded === (item.id ?? idx) ? null : (item.id ?? idx));
|
|
103
|
+
onItemClick?.(item);
|
|
104
|
+
}}
|
|
105
|
+
className="flex w-full items-start justify-between gap-3 p-3 text-left"
|
|
106
|
+
>
|
|
107
|
+
<div className="min-w-0 flex-1">
|
|
108
|
+
<div className="flex items-start justify-between gap-2">
|
|
109
|
+
<div className="text-sm font-medium text-slate-900 dark:text-slate-50">
|
|
110
|
+
{item.title ?? item.name ?? `Item ${idx + 1}`}
|
|
111
|
+
</div>
|
|
112
|
+
{item.status && (
|
|
113
|
+
<UIChip tone={item.status === "critical" ? "danger" : item.status === "warning" ? "warning" : "neutral"} className="shrink-0">
|
|
114
|
+
{item.status}
|
|
115
|
+
</UIChip>
|
|
116
|
+
)}
|
|
117
|
+
</div>
|
|
118
|
+
{item.description && (
|
|
119
|
+
<div className="mt-1 text-xs text-slate-500 dark:text-slate-400">{item.description}</div>
|
|
120
|
+
)}
|
|
121
|
+
</div>
|
|
122
|
+
</button>
|
|
123
|
+
{expanded === (item.id ?? idx) && item.actions && (
|
|
124
|
+
<div className="border-t border-slate-100 px-3 py-2 dark:border-slate-800">
|
|
125
|
+
<div className="flex flex-wrap gap-1.5">
|
|
126
|
+
{item.actions.map((action, i) => (
|
|
127
|
+
<button
|
|
128
|
+
key={typeof action === "string" ? action : action.label}
|
|
129
|
+
type="button"
|
|
130
|
+
className={`rounded-md px-2.5 py-1 text-xs font-medium transition-colors ${
|
|
131
|
+
i === 0
|
|
132
|
+
? "bg-slate-900 text-white hover:bg-slate-800 dark:bg-slate-50 dark:text-slate-900 dark:hover:bg-slate-200"
|
|
133
|
+
: "bg-slate-100 text-slate-700 hover:bg-slate-200 dark:bg-slate-800 dark:text-slate-300 dark:hover:bg-slate-700"
|
|
134
|
+
}`}
|
|
135
|
+
>
|
|
136
|
+
{typeof action === "string" ? action : action.label}
|
|
137
|
+
</button>
|
|
138
|
+
))}
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
)}
|
|
142
|
+
</div>
|
|
143
|
+
))}
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
);
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
DataTable({ title, subtitle, columns = [], data = [], rows, searchable = true, sortable = true, paginated = true, pageSize = 5 }) {
|
|
150
|
+
const tableData = data.length ? data : (rows ?? []);
|
|
151
|
+
const cols = columns.length ? columns : [
|
|
152
|
+
{ key: "name", label: "Name" },
|
|
153
|
+
{ key: "status", label: "Status" },
|
|
154
|
+
{ key: "value", label: "Value" }
|
|
155
|
+
];
|
|
156
|
+
return (
|
|
157
|
+
<TableCard
|
|
158
|
+
title={title ?? "Data"}
|
|
159
|
+
subtitle={subtitle}
|
|
160
|
+
columns={cols}
|
|
161
|
+
data={tableData}
|
|
162
|
+
searchable={searchable}
|
|
163
|
+
sortable={sortable}
|
|
164
|
+
paginated={paginated}
|
|
165
|
+
pageSize={pageSize}
|
|
166
|
+
/>
|
|
167
|
+
);
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
DataChart({ title, subtitle, chartType = "line", data = [], height = 200 }) {
|
|
171
|
+
const series = data.length ? data : Array.from({ length: 12 }, (_, i) => ({ x: i, y: Math.random() * 100 }));
|
|
172
|
+
return (
|
|
173
|
+
<ChartCard
|
|
174
|
+
title={title ?? "Trend"}
|
|
175
|
+
subtitle={subtitle ?? "Data visualization"}
|
|
176
|
+
chartType={chartType}
|
|
177
|
+
height={height}
|
|
178
|
+
chart={
|
|
179
|
+
<D3Chart
|
|
180
|
+
data={series}
|
|
181
|
+
responsive
|
|
182
|
+
height={height}
|
|
183
|
+
ariaLabel={title}
|
|
184
|
+
renderChart={(svg, d, dims, opts) => D3ChartTemplates.lineChart(svg, d, dims, opts)}
|
|
185
|
+
options={{ xKey: "x", yKey: "y", showGrid: true, showAxes: true }}
|
|
186
|
+
/>
|
|
187
|
+
}
|
|
188
|
+
/>
|
|
189
|
+
);
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
MetricCard({ title, label, value, trend, change, changeType, color, icon, description }) {
|
|
193
|
+
return (
|
|
194
|
+
<MetricCard
|
|
195
|
+
title={title ?? label}
|
|
196
|
+
value={value ?? "—"}
|
|
197
|
+
trend={trend}
|
|
198
|
+
change={change}
|
|
199
|
+
changeType={changeType}
|
|
200
|
+
color={color}
|
|
201
|
+
icon={icon}
|
|
202
|
+
description={description}
|
|
203
|
+
/>
|
|
204
|
+
);
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
StatusCard({ title, subtitle, status, items, layout, showProgress, showTimestamp }) {
|
|
208
|
+
return (
|
|
209
|
+
<StatusCard
|
|
210
|
+
title={title}
|
|
211
|
+
subtitle={subtitle}
|
|
212
|
+
status={status}
|
|
213
|
+
items={items}
|
|
214
|
+
layout={layout}
|
|
215
|
+
showProgress={showProgress}
|
|
216
|
+
showTimestamp={showTimestamp}
|
|
217
|
+
/>
|
|
218
|
+
);
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
ActionList({ actions = [], title, onAction }) {
|
|
222
|
+
return (
|
|
223
|
+
<div className="rounded-2xl border border-slate-200 bg-white p-4 dark:border-slate-800 dark:bg-slate-900">
|
|
224
|
+
{title && <div className="mb-3 text-sm font-medium text-slate-900 dark:text-slate-50">{title}</div>}
|
|
225
|
+
<div className="flex flex-wrap gap-2">
|
|
226
|
+
{actions.map((action, i) => (
|
|
227
|
+
<UIButton
|
|
228
|
+
key={i}
|
|
229
|
+
size="sm"
|
|
230
|
+
variant={i === 0 ? "primary" : "outline"}
|
|
231
|
+
onClick={() => onAction?.(action)}
|
|
232
|
+
>
|
|
233
|
+
{typeof action === "string" ? action : action.label}
|
|
234
|
+
</UIButton>
|
|
235
|
+
))}
|
|
236
|
+
</div>
|
|
237
|
+
</div>
|
|
238
|
+
);
|
|
239
|
+
},
|
|
240
|
+
|
|
241
|
+
CalloutCard({ title, message, tone = "neutral" }) {
|
|
242
|
+
const toneClasses = {
|
|
243
|
+
neutral: "border-slate-200 bg-slate-50 text-slate-700 dark:border-slate-800 dark:bg-slate-950/30 dark:text-slate-200",
|
|
244
|
+
success: "border-emerald-200 bg-emerald-50 text-emerald-800 dark:border-emerald-900/40 dark:bg-emerald-950/20 dark:text-emerald-200",
|
|
245
|
+
warning: "border-amber-200 bg-amber-50 text-amber-800 dark:border-amber-900/40 dark:bg-amber-950/20 dark:text-amber-200",
|
|
246
|
+
danger: "border-rose-200 bg-rose-50 text-rose-800 dark:border-rose-900/40 dark:bg-rose-950/20 dark:text-rose-200"
|
|
247
|
+
};
|
|
248
|
+
return (
|
|
249
|
+
<div className={`rounded-xl border p-4 ${toneClasses[tone] ?? toneClasses.neutral}`}>
|
|
250
|
+
{title && <div className="mb-1 text-sm font-semibold">{title}</div>}
|
|
251
|
+
<div className="text-sm">{message}</div>
|
|
252
|
+
</div>
|
|
253
|
+
);
|
|
254
|
+
},
|
|
255
|
+
|
|
256
|
+
Divider() {
|
|
257
|
+
return <div className="h-px bg-slate-200 dark:bg-slate-800" />;
|
|
258
|
+
},
|
|
259
|
+
|
|
260
|
+
Spacer({ size = "md" }) {
|
|
261
|
+
const heights = { sm: "h-2", md: "h-4", lg: "h-6" };
|
|
262
|
+
return <div className={heights[size] ?? heights.md} />;
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
let _registry = { ...BUILTIN_COMPONENTS };
|
|
267
|
+
|
|
268
|
+
export function getComponentRegistry() {
|
|
269
|
+
return _registry;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export function registerComponent(type, Component) {
|
|
273
|
+
_registry = { ..._registry, [type]: Component };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export function registerComponents(map) {
|
|
277
|
+
_registry = { ..._registry, ...map };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export function renderSchemaComponent(component, index) {
|
|
281
|
+
const registry = getComponentRegistry();
|
|
282
|
+
const Component = registry[component.type];
|
|
283
|
+
if (!Component) {
|
|
284
|
+
return (
|
|
285
|
+
<div key={index} className="rounded-lg border border-dashed border-slate-300 p-3 text-xs text-slate-500 dark:border-slate-700">
|
|
286
|
+
Unknown component: {component.type}
|
|
287
|
+
</div>
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
return <Component key={component.id ?? index} {...(component.props ?? {})} />;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export function renderSchema(components = []) {
|
|
294
|
+
return components.map((c, i) => renderSchemaComponent(c, i));
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export default BUILTIN_COMPONENTS;
|
package/src/lib/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './utils';
|