@schandlergarcia/sf-web-components 1.7.0 → 1.8.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/dist/components/library/cards/ActionList.d.ts +10 -10
- package/dist/components/library/cards/ActionList.js +2 -3
- package/dist/components/library/cards/ActionList.js.map +1 -1
- package/dist/components/library/cards/ActivityCard.d.ts +18 -5
- package/dist/components/library/cards/ActivityCard.js +3 -4
- package/dist/components/library/cards/ActivityCard.js.map +1 -1
- package/dist/components/library/cards/BaseCard.d.ts +30 -24
- package/dist/components/library/cards/BaseCard.js +2 -3
- package/dist/components/library/cards/BaseCard.js.map +1 -1
- package/dist/components/library/cards/CalloutCard.d.ts +11 -9
- package/dist/components/library/cards/CalloutCard.js +2 -3
- package/dist/components/library/cards/CalloutCard.js.map +1 -1
- package/dist/components/library/cards/ChartCard.d.ts +29 -17
- package/dist/components/library/cards/ChartCard.js +13 -14
- package/dist/components/library/cards/ChartCard.js.map +1 -1
- package/dist/components/library/cards/FeedPanel.d.ts +12 -11
- package/dist/components/library/cards/FeedPanel.js +3 -4
- package/dist/components/library/cards/FeedPanel.js.map +1 -1
- package/dist/components/library/cards/ListCard.d.ts +33 -20
- package/dist/components/library/cards/ListCard.js +35 -35
- package/dist/components/library/cards/ListCard.js.map +1 -1
- package/dist/components/library/cards/MetricCard.d.ts +23 -17
- package/dist/components/library/cards/MetricCard.js +10 -11
- package/dist/components/library/cards/MetricCard.js.map +1 -1
- package/dist/components/library/cards/MetricsStrip.d.ts +11 -11
- package/dist/components/library/cards/MetricsStrip.js +1 -1
- package/dist/components/library/cards/MetricsStrip.js.map +1 -1
- package/dist/components/library/cards/SectionCard.d.ts +17 -12
- package/dist/components/library/cards/SectionCard.js +18 -19
- package/dist/components/library/cards/SectionCard.js.map +1 -1
- package/dist/components/library/cards/SemanticMetricCard.d.ts +15 -20
- package/dist/components/library/cards/SemanticMetricCardWithLoading.d.ts +8 -7
- package/dist/components/library/cards/SemanticTableCard.d.ts +13 -18
- package/dist/components/library/cards/SemanticTableCardWithLoading.d.ts +8 -7
- package/dist/components/library/cards/StatusCard.d.ts +29 -15
- package/dist/components/library/cards/StatusCard.js +16 -17
- package/dist/components/library/cards/StatusCard.js.map +1 -1
- package/dist/components/library/cards/TableCard.d.ts +40 -23
- package/dist/components/library/cards/TableCard.js +59 -59
- package/dist/components/library/cards/TableCard.js.map +1 -1
- package/dist/components/library/cards/WidgetCard.d.ts +19 -11
- package/dist/components/library/cards/WidgetCard.js.map +1 -1
- package/dist/components/library/charts/D3Chart.d.ts +23 -16
- package/dist/components/library/charts/D3Chart.js.map +1 -1
- package/dist/components/library/charts/D3ChartTemplates.d.ts +33 -3
- package/dist/components/library/charts/D3ChartTemplates.js +7 -7
- package/dist/components/library/charts/D3ChartTemplates.js.map +1 -1
- package/dist/components/library/charts/GeoMap.d.ts +81 -18
- package/dist/components/library/charts/GeoMap.js +28 -26
- package/dist/components/library/charts/GeoMap.js.map +1 -1
- package/dist/components/library/filters/FilterBar.d.ts +18 -8
- package/dist/components/library/filters/FilterBar.js +2 -3
- package/dist/components/library/filters/FilterBar.js.map +1 -1
- package/dist/components/library/filters/SearchFilter.d.ts +7 -6
- package/dist/components/library/filters/SearchFilter.js +2 -3
- package/dist/components/library/filters/SearchFilter.js.map +1 -1
- package/dist/components/library/filters/SelectFilter.d.ts +13 -7
- package/dist/components/library/filters/SelectFilter.js +2 -3
- package/dist/components/library/filters/SelectFilter.js.map +1 -1
- package/dist/components/library/filters/ToggleFilter.d.ts +7 -5
- package/dist/components/library/filters/ToggleFilter.js +2 -3
- package/dist/components/library/filters/ToggleFilter.js.map +1 -1
- package/dist/components/library/forms/FormField.d.ts +10 -8
- package/dist/components/library/forms/FormField.js +3 -4
- package/dist/components/library/forms/FormField.js.map +1 -1
- package/dist/components/library/forms/FormModal.d.ts +23 -14
- package/dist/components/library/forms/FormModal.js.map +1 -1
- package/dist/components/library/forms/FormRenderer.d.ts +29 -9
- package/dist/components/library/forms/FormRenderer.js +6 -7
- package/dist/components/library/forms/FormRenderer.js.map +1 -1
- package/dist/components/library/forms/FormSection.d.ts +10 -8
- package/dist/components/library/forms/FormSection.js +2 -3
- package/dist/components/library/forms/FormSection.js.map +1 -1
- package/dist/components/library/forms/index.d.ts +5 -0
- package/dist/components/library/forms/useFormState.d.ts +23 -15
- package/dist/components/library/forms/useFormState.js +53 -47
- package/dist/components/library/forms/useFormState.js.map +1 -1
- package/dist/components/library/layout/PageContainer.d.ts +6 -4
- package/dist/components/library/layout/PageContainer.js +4 -5
- package/dist/components/library/layout/PageContainer.js.map +1 -1
- package/package.json +4 -1
- package/src/components/library/cards/{ActionList.jsx → ActionList.tsx} +13 -9
- package/src/components/library/cards/{ActivityCard.jsx → ActivityCard.tsx} +33 -4
- package/src/components/library/cards/{BaseCard.jsx → BaseCard.tsx} +33 -6
- package/src/components/library/cards/{CalloutCard.jsx → CalloutCard.tsx} +12 -10
- package/src/components/library/cards/{ChartCard.jsx → ChartCard.tsx} +32 -6
- package/src/components/library/cards/{FeedPanel.jsx → FeedPanel.tsx} +13 -2
- package/src/components/library/cards/{ListCard.jsx → ListCard.tsx} +43 -7
- package/src/components/library/cards/{MetricCard.jsx → MetricCard.tsx} +25 -6
- package/src/components/library/cards/{MetricsStrip.jsx → MetricsStrip.tsx} +22 -12
- package/src/components/library/cards/{SectionCard.jsx → SectionCard.tsx} +27 -8
- package/src/components/library/cards/{SemanticMetricCard.jsx → SemanticMetricCard.tsx} +17 -5
- package/src/components/library/cards/{SemanticMetricCardWithLoading.jsx → SemanticMetricCardWithLoading.tsx} +9 -3
- package/src/components/library/cards/{SemanticTableCard.jsx → SemanticTableCard.tsx} +14 -3
- package/src/components/library/cards/{SemanticTableCardWithLoading.jsx → SemanticTableCardWithLoading.tsx} +9 -5
- package/src/components/library/cards/{StatusCard.jsx → StatusCard.tsx} +61 -12
- package/src/components/library/cards/{TableCard.jsx → TableCard.tsx} +51 -12
- package/src/components/library/cards/{WidgetCard.jsx → WidgetCard.tsx} +28 -5
- package/src/components/library/charts/{D3Chart.jsx → D3Chart.tsx} +27 -7
- package/src/components/library/charts/{D3ChartTemplates.jsx → D3ChartTemplates.tsx} +60 -28
- package/src/components/library/charts/{GeoMap.jsx → GeoMap.tsx} +106 -17
- package/src/components/library/filters/{FilterBar.jsx → FilterBar.tsx} +21 -11
- package/src/components/library/filters/{SearchFilter.jsx → SearchFilter.tsx} +8 -2
- package/src/components/library/filters/{SelectFilter.jsx → SelectFilter.tsx} +15 -8
- package/src/components/library/filters/{ToggleFilter.jsx → ToggleFilter.tsx} +7 -6
- package/src/components/library/forms/{FormField.jsx → FormField.tsx} +91 -45
- package/src/components/library/forms/{FormModal.jsx → FormModal.tsx} +21 -20
- package/src/components/library/forms/{FormRenderer.jsx → FormRenderer.tsx} +32 -10
- package/src/components/library/forms/{FormSection.jsx → FormSection.tsx} +13 -7
- package/src/components/library/forms/index.tsx +11 -0
- package/src/components/library/forms/{useFormState.jsx → useFormState.tsx} +43 -23
- package/src/components/library/layout/{PageContainer.jsx → PageContainer.tsx} +6 -3
- package/src/components/library/forms/index.jsx +0 -5
- /package/src/components/library/filters/{index.jsx → index.ts} +0 -0
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { jsx as
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
return /* @__PURE__ */ t("div", { className: ["mx-auto w-full max-w-6xl px-4 sm:px-6", o].filter(Boolean).join(" "), children: a });
|
|
1
|
+
import { jsx as e } from "react/jsx-runtime";
|
|
2
|
+
function l({ className: a = "", children: o }) {
|
|
3
|
+
return /* @__PURE__ */ e("div", { className: ["mx-auto w-full max-w-6xl px-4 sm:px-6", a].filter(Boolean).join(" "), children: o });
|
|
5
4
|
}
|
|
6
5
|
export {
|
|
7
|
-
|
|
6
|
+
l as default
|
|
8
7
|
};
|
|
9
8
|
//# sourceMappingURL=PageContainer.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PageContainer.js","sources":["../../../../src/components/library/layout/PageContainer.
|
|
1
|
+
{"version":3,"file":"PageContainer.js","sources":["../../../../src/components/library/layout/PageContainer.tsx"],"sourcesContent":["import React from \"react\";\n\nexport interface PageContainerProps {\n className?: string;\n children: React.ReactNode;\n}\n\nexport default function PageContainer({ className = \"\", children }: PageContainerProps) {\n return (\n <div className={[\"mx-auto w-full max-w-6xl px-4 sm:px-6\", className].filter(Boolean).join(\" \")}>\n {children}\n </div>\n );\n}\n"],"names":["PageContainer","className","children","jsx"],"mappings":";AAOA,SAAwBA,EAAc,EAAE,WAAAC,IAAY,IAAI,UAAAC,KAAgC;AACtF,SACE,gBAAAC,EAAC,OAAA,EAAI,WAAW,CAAC,yCAAyCF,CAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG,GAC1F,UAAAC,EAAA,CACH;AAEJ;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@schandlergarcia/sf-web-components",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "Reusable Salesforce web components library with Tailwind CSS v4 and shadcn/ui",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -75,8 +75,11 @@
|
|
|
75
75
|
"@radix-ui/react-dialog": "^1.1.15",
|
|
76
76
|
"@radix-ui/react-popover": "^1.1.15",
|
|
77
77
|
"@radix-ui/react-select": "^2.2.6",
|
|
78
|
+
"@types/d3": "^7.4.3",
|
|
78
79
|
"@types/react": "^19.2.5",
|
|
79
80
|
"@types/react-dom": "^19.2.3",
|
|
81
|
+
"@types/topojson-client": "^3.1.5",
|
|
82
|
+
"@types/topojson-specification": "^1.0.5",
|
|
80
83
|
"@vitejs/plugin-react": "^5.1.1",
|
|
81
84
|
"date-fns": "^4.0.0",
|
|
82
85
|
"react-day-picker": "^9.14.0",
|
|
@@ -1,19 +1,23 @@
|
|
|
1
|
-
import React from "react";
|
|
2
1
|
import UIButton from "../ui/UIButton";
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
export interface Action {
|
|
4
|
+
label: string;
|
|
5
|
+
[key: string]: unknown;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface ActionListProps {
|
|
9
|
+
actions?: (Action | string)[];
|
|
10
|
+
title?: string;
|
|
11
|
+
onAction?: (action: Action | string) => void;
|
|
12
|
+
className?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
11
15
|
export default function ActionList({
|
|
12
16
|
actions = [],
|
|
13
17
|
title,
|
|
14
18
|
onAction,
|
|
15
19
|
className = "",
|
|
16
|
-
}) {
|
|
20
|
+
}: ActionListProps) {
|
|
17
21
|
return (
|
|
18
22
|
<div className={`rounded-2xl border border-slate-200 bg-white p-4 dark:border-slate-800 dark:bg-slate-900 ${className}`}>
|
|
19
23
|
{title && (
|
|
@@ -3,14 +3,37 @@ import { motion, AnimatePresence } from "framer-motion";
|
|
|
3
3
|
import { ArrowPathIcon, CheckCircleIcon, ExclamationCircleIcon, ClockIcon } from "@heroicons/react/24/outline";
|
|
4
4
|
import UIText from "../ui/Text";
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
type ActionStatus = "working" | "pending" | "complete" | "error";
|
|
7
|
+
|
|
8
|
+
interface StatusIconConfig {
|
|
9
|
+
Icon: React.ComponentType<{ className?: string }>;
|
|
10
|
+
color: string;
|
|
11
|
+
spin: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const STATUS_ICON: Record<ActionStatus, StatusIconConfig> = {
|
|
7
15
|
working: { Icon: ArrowPathIcon, color: "text-indigo-500", spin: true },
|
|
8
16
|
pending: { Icon: ClockIcon, color: "text-slate-400", spin: false },
|
|
9
17
|
complete: { Icon: CheckCircleIcon, color: "text-emerald-500", spin: false },
|
|
10
18
|
error: { Icon: ExclamationCircleIcon, color: "text-red-500", spin: false },
|
|
11
19
|
};
|
|
12
20
|
|
|
13
|
-
|
|
21
|
+
export interface ActivityAction {
|
|
22
|
+
id: string;
|
|
23
|
+
status: ActionStatus;
|
|
24
|
+
title?: string;
|
|
25
|
+
action?: string;
|
|
26
|
+
subtitle?: string;
|
|
27
|
+
traveler?: string;
|
|
28
|
+
timestamp?: string;
|
|
29
|
+
startedAt?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface ActionItemProps {
|
|
33
|
+
action: ActivityAction;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function ActionItem({ action }: ActionItemProps) {
|
|
14
37
|
const s = STATUS_ICON[action.status] ?? STATUS_ICON.pending;
|
|
15
38
|
return (
|
|
16
39
|
<motion.div
|
|
@@ -34,13 +57,19 @@ function ActionItem({ action }) {
|
|
|
34
57
|
);
|
|
35
58
|
}
|
|
36
59
|
|
|
37
|
-
export
|
|
60
|
+
export interface ActivityCardProps {
|
|
61
|
+
title?: string;
|
|
62
|
+
actions?: ActivityAction[];
|
|
63
|
+
className?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export default function ActivityCard({ title = "Activity", actions = [], className = "" }: ActivityCardProps) {
|
|
38
67
|
if (actions.length === 0) return null;
|
|
39
68
|
|
|
40
69
|
return (
|
|
41
70
|
<div className={className}>
|
|
42
71
|
{title && (
|
|
43
|
-
<UIText as="div" size="xs" weight="
|
|
72
|
+
<UIText as="div" size="xs" weight="medium" muted className="mb-2 uppercase tracking-wider">
|
|
44
73
|
{title}
|
|
45
74
|
</UIText>
|
|
46
75
|
)}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
type Variant = "default" | "metric" | "chart" | "table" | "widget" | "status";
|
|
4
|
+
type Size = "xs" | "sm" | "md" | "lg" | "xl" | "full";
|
|
5
|
+
type Padding = "none" | "xs" | "sm" | "default" | "lg" | "xl";
|
|
6
|
+
type Radius = "none" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl";
|
|
7
|
+
|
|
8
|
+
const VARIANT_CLASSES: Record<Variant, string> = {
|
|
4
9
|
default: "",
|
|
5
10
|
metric: "",
|
|
6
11
|
chart: "",
|
|
@@ -9,7 +14,7 @@ const VARIANT_CLASSES = {
|
|
|
9
14
|
status: ""
|
|
10
15
|
};
|
|
11
16
|
|
|
12
|
-
const SIZE_CLASSES = {
|
|
17
|
+
const SIZE_CLASSES: Record<Size, string> = {
|
|
13
18
|
xs: "",
|
|
14
19
|
sm: "min-h-[80px]",
|
|
15
20
|
md: "min-h-[120px]",
|
|
@@ -18,7 +23,7 @@ const SIZE_CLASSES = {
|
|
|
18
23
|
full: ""
|
|
19
24
|
};
|
|
20
25
|
|
|
21
|
-
const PADDING_CLASSES = {
|
|
26
|
+
const PADDING_CLASSES: Record<Padding, string> = {
|
|
22
27
|
none: "p-0",
|
|
23
28
|
xs: "p-2",
|
|
24
29
|
sm: "p-3",
|
|
@@ -27,6 +32,30 @@ const PADDING_CLASSES = {
|
|
|
27
32
|
xl: "p-8"
|
|
28
33
|
};
|
|
29
34
|
|
|
35
|
+
export interface BaseCardProps extends Omit<React.HTMLAttributes<HTMLDivElement | HTMLButtonElement>, "onPress"> {
|
|
36
|
+
header?: React.ReactNode;
|
|
37
|
+
body?: React.ReactNode;
|
|
38
|
+
footer?: React.ReactNode;
|
|
39
|
+
children?: React.ReactNode;
|
|
40
|
+
variant?: Variant;
|
|
41
|
+
size?: Size;
|
|
42
|
+
padding?: Padding;
|
|
43
|
+
shadow?: boolean;
|
|
44
|
+
radius?: Radius;
|
|
45
|
+
border?: boolean;
|
|
46
|
+
isHoverable?: boolean;
|
|
47
|
+
isPressable?: boolean;
|
|
48
|
+
isLoading?: boolean;
|
|
49
|
+
isDisabled?: boolean;
|
|
50
|
+
isSelected?: boolean;
|
|
51
|
+
className?: string;
|
|
52
|
+
headerClassName?: string;
|
|
53
|
+
bodyClassName?: string;
|
|
54
|
+
footerClassName?: string;
|
|
55
|
+
onPress?: () => void;
|
|
56
|
+
onHover?: () => void;
|
|
57
|
+
}
|
|
58
|
+
|
|
30
59
|
export default function BaseCard({
|
|
31
60
|
header,
|
|
32
61
|
body,
|
|
@@ -50,7 +79,7 @@ export default function BaseCard({
|
|
|
50
79
|
onPress,
|
|
51
80
|
onHover,
|
|
52
81
|
...rest
|
|
53
|
-
}) {
|
|
82
|
+
}: BaseCardProps) {
|
|
54
83
|
const Comp = isPressable ? "button" : "div";
|
|
55
84
|
|
|
56
85
|
const radiusClass = radius ? `rounded-${radius}` : "rounded-2xl";
|
|
@@ -105,5 +134,3 @@ export default function BaseCard({
|
|
|
105
134
|
</Comp>
|
|
106
135
|
);
|
|
107
136
|
}
|
|
108
|
-
|
|
109
|
-
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
type Tone = "neutral" | "success" | "warning" | "danger" | "info";
|
|
4
|
+
|
|
5
|
+
const TONE_CLASSES: Record<Tone, string> = {
|
|
4
6
|
neutral: "border-slate-200 bg-slate-50 text-slate-700 dark:border-slate-800 dark:bg-slate-950/30 dark:text-slate-200",
|
|
5
7
|
success: "border-emerald-200 bg-emerald-50 text-emerald-800 dark:border-emerald-900/40 dark:bg-emerald-950/20 dark:text-emerald-200",
|
|
6
8
|
warning: "border-amber-200 bg-amber-50 text-amber-800 dark:border-amber-900/40 dark:bg-amber-950/20 dark:text-amber-200",
|
|
@@ -8,21 +10,21 @@ const TONE_CLASSES = {
|
|
|
8
10
|
info: "border-blue-200 bg-blue-50 text-blue-800 dark:border-blue-900/40 dark:bg-blue-950/20 dark:text-blue-200",
|
|
9
11
|
};
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
export interface CalloutCardProps {
|
|
14
|
+
title?: string;
|
|
15
|
+
message: string | React.ReactNode;
|
|
16
|
+
tone?: Tone;
|
|
17
|
+
icon?: React.ReactNode;
|
|
18
|
+
className?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
19
21
|
export default function CalloutCard({
|
|
20
22
|
title,
|
|
21
23
|
message,
|
|
22
24
|
tone = "neutral",
|
|
23
25
|
icon,
|
|
24
26
|
className = "",
|
|
25
|
-
}) {
|
|
27
|
+
}: CalloutCardProps) {
|
|
26
28
|
return (
|
|
27
29
|
<div className={`rounded-xl border p-4 ${TONE_CLASSES[tone] ?? TONE_CLASSES.neutral} ${className}`}>
|
|
28
30
|
<div className="flex gap-3">
|
|
@@ -1,8 +1,36 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import BaseCard from "./BaseCard";
|
|
2
|
+
import BaseCard, { BaseCardProps } from "./BaseCard";
|
|
3
3
|
import UIText from "../ui/Text";
|
|
4
4
|
import UIChip from "../ui/Chip";
|
|
5
5
|
|
|
6
|
+
interface TimeRangeOption {
|
|
7
|
+
value: string;
|
|
8
|
+
label: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface TimeRange {
|
|
12
|
+
current: string;
|
|
13
|
+
options?: (TimeRangeOption | string)[];
|
|
14
|
+
onChange?: (value: string) => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ChartCardProps extends Omit<BaseCardProps, "variant" | "header" | "body"> {
|
|
18
|
+
chart: React.ReactNode;
|
|
19
|
+
chartType?: string;
|
|
20
|
+
title?: string;
|
|
21
|
+
subtitle?: string;
|
|
22
|
+
filters?: React.ReactNode;
|
|
23
|
+
timeRange?: TimeRange;
|
|
24
|
+
actions?: React.ReactNode;
|
|
25
|
+
legend?: React.ReactNode;
|
|
26
|
+
height?: number;
|
|
27
|
+
showGrid?: boolean;
|
|
28
|
+
showAxes?: boolean;
|
|
29
|
+
data?: unknown[];
|
|
30
|
+
loading?: boolean;
|
|
31
|
+
error?: string | Error;
|
|
32
|
+
}
|
|
33
|
+
|
|
6
34
|
export default function ChartCard({
|
|
7
35
|
chart,
|
|
8
36
|
chartType,
|
|
@@ -19,7 +47,7 @@ export default function ChartCard({
|
|
|
19
47
|
loading = false,
|
|
20
48
|
error,
|
|
21
49
|
...cardProps
|
|
22
|
-
}) {
|
|
50
|
+
}: ChartCardProps) {
|
|
23
51
|
const header = (
|
|
24
52
|
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
|
25
53
|
<div className="min-w-0">
|
|
@@ -44,8 +72,8 @@ export default function ChartCard({
|
|
|
44
72
|
aria-label="Time range"
|
|
45
73
|
>
|
|
46
74
|
{timeRange.options?.map((opt) => (
|
|
47
|
-
<option key={opt
|
|
48
|
-
{opt
|
|
75
|
+
<option key={typeof opt === "string" ? opt : opt.value} value={typeof opt === "string" ? opt : opt.value}>
|
|
76
|
+
{typeof opt === "string" ? opt : opt.label}
|
|
49
77
|
</option>
|
|
50
78
|
))}
|
|
51
79
|
</select>
|
|
@@ -101,5 +129,3 @@ export default function ChartCard({
|
|
|
101
129
|
/>
|
|
102
130
|
);
|
|
103
131
|
}
|
|
104
|
-
|
|
105
|
-
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import UIText from "../ui/Text";
|
|
3
3
|
|
|
4
|
+
export interface FeedPanelProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "title"> {
|
|
5
|
+
title?: string;
|
|
6
|
+
subtitle?: string;
|
|
7
|
+
actions?: React.ReactNode;
|
|
8
|
+
children?: React.ReactNode;
|
|
9
|
+
width?: number | string;
|
|
10
|
+
className?: string;
|
|
11
|
+
headerClassName?: string;
|
|
12
|
+
bodyClassName?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
4
15
|
export default function FeedPanel({
|
|
5
16
|
title,
|
|
6
17
|
subtitle,
|
|
@@ -11,7 +22,7 @@ export default function FeedPanel({
|
|
|
11
22
|
headerClassName = "",
|
|
12
23
|
bodyClassName = "",
|
|
13
24
|
...rest
|
|
14
|
-
}) {
|
|
25
|
+
}: FeedPanelProps) {
|
|
15
26
|
const widthStyle = width ? (typeof width === "number" ? { width: `${width}px` } : { width }) : undefined;
|
|
16
27
|
|
|
17
28
|
return (
|
|
@@ -24,7 +35,7 @@ export default function FeedPanel({
|
|
|
24
35
|
<div className={`shrink-0 border-b border-slate-100 px-4 py-3 dark:border-slate-800 ${headerClassName}`}>
|
|
25
36
|
<div className="flex items-start justify-between gap-3">
|
|
26
37
|
<div className="min-w-0">
|
|
27
|
-
{title && <UIText as="div" size="sm" weight="
|
|
38
|
+
{title && <UIText as="div" size="sm" weight="medium">{title}</UIText>}
|
|
28
39
|
{subtitle && <UIText as="div" size="xs" muted className="mt-0.5">{subtitle}</UIText>}
|
|
29
40
|
</div>
|
|
30
41
|
{actions && <div className="shrink-0">{actions}</div>}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import BaseCard from "./BaseCard";
|
|
2
|
+
import BaseCard, { BaseCardProps } from "./BaseCard";
|
|
3
3
|
import UIText from "../ui/Text";
|
|
4
4
|
import UIChip from "../ui/Chip";
|
|
5
5
|
|
|
6
|
-
function formatTimestamp(ts) {
|
|
6
|
+
function formatTimestamp(ts: string | Date | undefined): string {
|
|
7
7
|
if (!ts) return "";
|
|
8
8
|
try {
|
|
9
9
|
const d = ts instanceof Date ? ts : new Date(ts);
|
|
@@ -14,7 +14,11 @@ function formatTimestamp(ts) {
|
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
interface AvatarProps {
|
|
18
|
+
item: ListItem;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function Avatar({ item }: AvatarProps) {
|
|
18
22
|
const avatar = item?.avatar;
|
|
19
23
|
const name = item?.title ?? item?.name ?? "Item";
|
|
20
24
|
if (!avatar) return null;
|
|
@@ -41,6 +45,38 @@ function Avatar({ item }) {
|
|
|
41
45
|
);
|
|
42
46
|
}
|
|
43
47
|
|
|
48
|
+
export interface ListItem {
|
|
49
|
+
id?: string | number;
|
|
50
|
+
title?: string;
|
|
51
|
+
name?: string;
|
|
52
|
+
description?: string;
|
|
53
|
+
status?: string;
|
|
54
|
+
timestamp?: string | Date;
|
|
55
|
+
value?: string | number;
|
|
56
|
+
unit?: string;
|
|
57
|
+
avatar?: string | React.ReactNode;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface ListCardProps extends Omit<BaseCardProps, "variant" | "header" | "body"> {
|
|
61
|
+
items?: ListItem[];
|
|
62
|
+
title?: string;
|
|
63
|
+
subtitle?: string;
|
|
64
|
+
showAvatars?: boolean;
|
|
65
|
+
showStatus?: boolean;
|
|
66
|
+
showActions?: boolean;
|
|
67
|
+
showTimestamp?: boolean;
|
|
68
|
+
dense?: boolean;
|
|
69
|
+
divided?: boolean;
|
|
70
|
+
actions?: React.ReactNode;
|
|
71
|
+
itemActions?: (item: ListItem, index: number) => React.ReactNode;
|
|
72
|
+
onItemClick?: (item: ListItem, index: number) => void;
|
|
73
|
+
loading?: boolean;
|
|
74
|
+
error?: string | Error;
|
|
75
|
+
emptyMessage?: string;
|
|
76
|
+
emptyIcon?: React.ReactNode;
|
|
77
|
+
maxBodyHeight?: string | number;
|
|
78
|
+
}
|
|
79
|
+
|
|
44
80
|
export default function ListCard({
|
|
45
81
|
items = [],
|
|
46
82
|
title,
|
|
@@ -60,7 +96,7 @@ export default function ListCard({
|
|
|
60
96
|
emptyIcon,
|
|
61
97
|
maxBodyHeight,
|
|
62
98
|
...cardProps
|
|
63
|
-
}) {
|
|
99
|
+
}: ListCardProps) {
|
|
64
100
|
const header = (
|
|
65
101
|
<div className="flex items-start justify-between gap-3">
|
|
66
102
|
<div className="min-w-0">
|
|
@@ -96,7 +132,9 @@ export default function ListCard({
|
|
|
96
132
|
|
|
97
133
|
const padY = dense ? "py-2" : "py-3";
|
|
98
134
|
|
|
99
|
-
const scrollStyle = maxBodyHeight
|
|
135
|
+
const scrollStyle: React.CSSProperties = maxBodyHeight
|
|
136
|
+
? { maxHeight: typeof maxBodyHeight === "number" ? `${maxBodyHeight}px` : maxBodyHeight, overflowY: "auto" }
|
|
137
|
+
: {};
|
|
100
138
|
|
|
101
139
|
const body =
|
|
102
140
|
loading ? (
|
|
@@ -189,5 +227,3 @@ export default function ListCard({
|
|
|
189
227
|
|
|
190
228
|
return <BaseCard variant="widget" header={header} body={body} isLoading={false} {...cardProps} />;
|
|
191
229
|
}
|
|
192
|
-
|
|
193
|
-
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import BaseCard from "./BaseCard";
|
|
2
|
+
import BaseCard, { BaseCardProps } from "./BaseCard";
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
type ChangeType = "positive" | "negative" | "neutral";
|
|
5
|
+
type Color = "default" | "primary" | "success" | "warning" | "danger";
|
|
6
|
+
type Layout = "default" | "compact";
|
|
7
|
+
|
|
8
|
+
const CHANGE_STYLES: Record<ChangeType, string> = {
|
|
5
9
|
positive: "text-emerald-700 dark:text-emerald-400",
|
|
6
10
|
negative: "text-rose-700 dark:text-rose-400",
|
|
7
11
|
neutral: "text-slate-600 dark:text-slate-300"
|
|
8
12
|
};
|
|
9
13
|
|
|
10
|
-
const COLOR_STYLES = {
|
|
14
|
+
const COLOR_STYLES: Record<Color, string> = {
|
|
11
15
|
default: "bg-slate-100 text-slate-800 dark:bg-slate-800 dark:text-slate-100",
|
|
12
16
|
primary: "bg-brand-100 text-brand-800 dark:bg-brand-950/40 dark:text-brand-200",
|
|
13
17
|
success: "bg-emerald-100 text-emerald-800 dark:bg-emerald-950/40 dark:text-emerald-200",
|
|
@@ -15,6 +19,23 @@ const COLOR_STYLES = {
|
|
|
15
19
|
danger: "bg-rose-100 text-rose-800 dark:bg-rose-950/40 dark:text-rose-200"
|
|
16
20
|
};
|
|
17
21
|
|
|
22
|
+
export interface MetricCardProps extends Omit<BaseCardProps, "variant" | "padding" | "size" | "header" | "body" | "footer"> {
|
|
23
|
+
title: string;
|
|
24
|
+
value: string | number;
|
|
25
|
+
subtitle?: string;
|
|
26
|
+
change?: string | number;
|
|
27
|
+
changeType?: ChangeType;
|
|
28
|
+
icon?: React.ReactNode;
|
|
29
|
+
color?: Color;
|
|
30
|
+
trend?: string;
|
|
31
|
+
trendIcon?: React.ReactNode;
|
|
32
|
+
layout?: Layout;
|
|
33
|
+
footer?: React.ReactNode;
|
|
34
|
+
actions?: React.ReactNode;
|
|
35
|
+
loading?: boolean;
|
|
36
|
+
error?: string | Error;
|
|
37
|
+
}
|
|
38
|
+
|
|
18
39
|
export default function MetricCard({
|
|
19
40
|
title,
|
|
20
41
|
value,
|
|
@@ -31,7 +52,7 @@ export default function MetricCard({
|
|
|
31
52
|
loading = false,
|
|
32
53
|
error,
|
|
33
54
|
...cardProps
|
|
34
|
-
}) {
|
|
55
|
+
}: MetricCardProps) {
|
|
35
56
|
const changeClass = CHANGE_STYLES[changeType] ?? CHANGE_STYLES.neutral;
|
|
36
57
|
const pillClass = COLOR_STYLES[color] ?? COLOR_STYLES.default;
|
|
37
58
|
|
|
@@ -105,5 +126,3 @@ export default function MetricCard({
|
|
|
105
126
|
/>
|
|
106
127
|
);
|
|
107
128
|
}
|
|
108
|
-
|
|
109
|
-
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useState } from "react";
|
|
2
2
|
|
|
3
3
|
const PLACEHOLDER_METRICS = [
|
|
4
4
|
{ label: "Metric A", value: "—", trend: null },
|
|
@@ -6,21 +6,27 @@ const PLACEHOLDER_METRICS = [
|
|
|
6
6
|
{ label: "Metric C", value: "—", trend: null },
|
|
7
7
|
];
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
9
|
+
export interface Metric {
|
|
10
|
+
label: string;
|
|
11
|
+
value: string | number;
|
|
12
|
+
trend?: string | number | null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface MetricsStripProps {
|
|
16
|
+
metrics?: Metric[];
|
|
17
|
+
title?: string;
|
|
18
|
+
collapsible?: boolean;
|
|
19
|
+
collapsed?: boolean;
|
|
20
|
+
className?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
17
23
|
export default function MetricsStrip({
|
|
18
24
|
metrics = [],
|
|
19
25
|
title,
|
|
20
26
|
collapsible = false,
|
|
21
27
|
collapsed: initialCollapsed = false,
|
|
22
28
|
className = "",
|
|
23
|
-
}) {
|
|
29
|
+
}: MetricsStripProps) {
|
|
24
30
|
const [collapsed, setCollapsed] = useState(initialCollapsed);
|
|
25
31
|
const items = metrics.length ? metrics : PLACEHOLDER_METRICS;
|
|
26
32
|
|
|
@@ -70,8 +76,12 @@ export default function MetricsStrip({
|
|
|
70
76
|
);
|
|
71
77
|
}
|
|
72
78
|
|
|
73
|
-
|
|
74
|
-
|
|
79
|
+
interface TrendBadgeProps {
|
|
80
|
+
trend: string | number;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function TrendBadge({ trend }: TrendBadgeProps) {
|
|
84
|
+
const isPositive = String(trend).startsWith?.("+") || (typeof trend === "number" && trend > 0);
|
|
75
85
|
const color = isPositive ? "text-red-500" : "text-emerald-500";
|
|
76
86
|
const label = typeof trend === "number" ? (trend > 0 ? `+${trend}` : trend) : trend;
|
|
77
87
|
return <span className={`text-xs ${color}`}>{label}</span>;
|
|
@@ -1,27 +1,48 @@
|
|
|
1
|
-
import
|
|
2
|
-
import BaseCard from "./BaseCard";
|
|
1
|
+
import BaseCard, { BaseCardProps } from "./BaseCard";
|
|
3
2
|
import UIText from "../ui/Text";
|
|
4
3
|
|
|
5
|
-
|
|
4
|
+
type Variant = "default" | "primary" | "secondary" | "accent";
|
|
5
|
+
type Size = "sm" | "md" | "lg" | "xl";
|
|
6
|
+
type Alignment = "left" | "center" | "right";
|
|
7
|
+
|
|
8
|
+
const VARIANT_STYLES: Record<Variant, string> = {
|
|
6
9
|
default: "bg-white dark:bg-slate-900",
|
|
7
10
|
primary: "bg-brand-50 dark:bg-brand-950/30",
|
|
8
11
|
secondary: "bg-slate-50 dark:bg-slate-950/30",
|
|
9
12
|
accent: "bg-emerald-50 dark:bg-emerald-950/25"
|
|
10
13
|
};
|
|
11
14
|
|
|
12
|
-
|
|
15
|
+
interface SizeConfig {
|
|
16
|
+
title: string;
|
|
17
|
+
desc: string;
|
|
18
|
+
pad: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const SIZE_STYLES: Record<Size, SizeConfig> = {
|
|
13
22
|
sm: { title: "text-lg", desc: "text-sm", pad: "p-4" },
|
|
14
23
|
md: { title: "text-xl", desc: "text-sm", pad: "p-5" },
|
|
15
24
|
lg: { title: "text-2xl", desc: "text-base", pad: "p-6" },
|
|
16
25
|
xl: { title: "text-3xl", desc: "text-base", pad: "p-7" }
|
|
17
26
|
};
|
|
18
27
|
|
|
19
|
-
const ALIGN_STYLES = {
|
|
28
|
+
const ALIGN_STYLES: Record<Alignment, string> = {
|
|
20
29
|
left: "text-left items-start",
|
|
21
30
|
center: "text-center items-center",
|
|
22
31
|
right: "text-right items-end"
|
|
23
32
|
};
|
|
24
33
|
|
|
34
|
+
export interface SectionCardProps extends Omit<BaseCardProps, "variant" | "padding" | "body"> {
|
|
35
|
+
title?: string;
|
|
36
|
+
description?: string;
|
|
37
|
+
label?: string;
|
|
38
|
+
variant?: Variant;
|
|
39
|
+
showDivider?: boolean;
|
|
40
|
+
alignment?: Alignment;
|
|
41
|
+
size?: Size;
|
|
42
|
+
isDark?: boolean;
|
|
43
|
+
className?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
25
46
|
export default function SectionCard({
|
|
26
47
|
title,
|
|
27
48
|
description,
|
|
@@ -33,7 +54,7 @@ export default function SectionCard({
|
|
|
33
54
|
isDark = false,
|
|
34
55
|
className = "",
|
|
35
56
|
...cardProps
|
|
36
|
-
}) {
|
|
57
|
+
}: SectionCardProps) {
|
|
37
58
|
const s = SIZE_STYLES[size] ?? SIZE_STYLES.md;
|
|
38
59
|
const align = ALIGN_STYLES[alignment] ?? ALIGN_STYLES.left;
|
|
39
60
|
const variantClass = VARIANT_STYLES[variant] ?? VARIANT_STYLES.default;
|
|
@@ -79,5 +100,3 @@ export default function SectionCard({
|
|
|
79
100
|
/>
|
|
80
101
|
);
|
|
81
102
|
}
|
|
82
|
-
|
|
83
|
-
|
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import MetricCard from "./MetricCard";
|
|
2
|
+
import MetricCard, { MetricCardProps } from "./MetricCard";
|
|
3
3
|
import { getSemanticMetric } from "../data/chartDataProvider";
|
|
4
4
|
|
|
5
|
+
export interface SemanticMetricCardProps extends Omit<MetricCardProps, "value"> {
|
|
6
|
+
semanticId: string;
|
|
7
|
+
metricId?: string;
|
|
8
|
+
value?: string | number;
|
|
9
|
+
unit?: string;
|
|
10
|
+
format?: string;
|
|
11
|
+
changeLabel?: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
seriesName?: string;
|
|
14
|
+
availableFilters?: string[];
|
|
15
|
+
showFilters?: boolean;
|
|
16
|
+
compact?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
5
19
|
export default function SemanticMetricCard({
|
|
6
20
|
semanticId,
|
|
7
21
|
metricId,
|
|
@@ -21,7 +35,7 @@ export default function SemanticMetricCard({
|
|
|
21
35
|
className,
|
|
22
36
|
loading = false,
|
|
23
37
|
...rest
|
|
24
|
-
}) {
|
|
38
|
+
}: SemanticMetricCardProps) {
|
|
25
39
|
const metric = React.useMemo(() => getSemanticMetric(semanticId, metricId), [semanticId, metricId]);
|
|
26
40
|
|
|
27
41
|
const resolvedTitle = title ?? metric?.title ?? metricId ?? "Metric";
|
|
@@ -32,13 +46,12 @@ export default function SemanticMetricCard({
|
|
|
32
46
|
const resolvedChangeType = metric?.changeType ?? "neutral";
|
|
33
47
|
const resolvedColor = metric?.color ?? "default";
|
|
34
48
|
|
|
35
|
-
// Note: unit/format/etc. are accepted for forward compatibility; basic rendering is handled by MetricCard for now.
|
|
36
49
|
return (
|
|
37
50
|
<MetricCard
|
|
38
51
|
title={resolvedTitle}
|
|
39
52
|
subtitle={resolvedSubtitle}
|
|
40
53
|
value={resolvedValue}
|
|
41
|
-
change={
|
|
54
|
+
change={changeLabel ? `${changeLabel} ${resolvedChange ?? ""}`.trim() : resolvedChange}
|
|
42
55
|
changeType={resolvedChangeType}
|
|
43
56
|
color={resolvedColor}
|
|
44
57
|
trend={resolvedTrend}
|
|
@@ -49,4 +62,3 @@ export default function SemanticMetricCard({
|
|
|
49
62
|
/>
|
|
50
63
|
);
|
|
51
64
|
}
|
|
52
|
-
|