@schandlergarcia/sf-web-components 1.2.5 → 1.2.7

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 (165) hide show
  1. package/package.json +2 -1
  2. package/scripts/postinstall.mjs +69 -93
  3. package/src/components/library/.sfdx/tools/sobjects/standardObjects/Account.cls +196 -0
  4. package/src/components/library/.sfdx/tools/sobjects/standardObjects/AccountHistory.cls +25 -0
  5. package/src/components/library/.sfdx/tools/sobjects/standardObjects/Asset.cls +138 -0
  6. package/src/components/library/.sfdx/tools/sobjects/standardObjects/Attachment.cls +35 -0
  7. package/src/components/library/.sfdx/tools/sobjects/standardObjects/Case.cls +111 -0
  8. package/src/components/library/.sfdx/tools/sobjects/standardObjects/Contact.cls +167 -0
  9. package/src/components/library/.sfdx/tools/sobjects/standardObjects/Contract.cls +96 -0
  10. package/src/components/library/.sfdx/tools/sobjects/standardObjects/Domain.cls +29 -0
  11. package/src/components/library/.sfdx/tools/sobjects/standardObjects/Lead.cls +128 -0
  12. package/src/components/library/.sfdx/tools/sobjects/standardObjects/Note.cls +32 -0
  13. package/src/components/library/.sfdx/tools/sobjects/standardObjects/Opportunity.cls +113 -0
  14. package/src/components/library/.sfdx/tools/sobjects/standardObjects/Order.cls +127 -0
  15. package/src/components/library/.sfdx/tools/sobjects/standardObjects/Pricebook2.cls +47 -0
  16. package/src/components/library/.sfdx/tools/sobjects/standardObjects/PricebookEntry.cls +47 -0
  17. package/src/components/library/.sfdx/tools/sobjects/standardObjects/Product2.cls +91 -0
  18. package/src/components/library/.sfdx/tools/sobjects/standardObjects/RecordType.cls +35 -0
  19. package/src/components/library/.sfdx/tools/sobjects/standardObjects/Report.cls +47 -0
  20. package/src/components/library/.sfdx/tools/sobjects/standardObjects/Task.cls +79 -0
  21. package/src/components/library/.sfdx/tools/sobjects/standardObjects/User.cls +2318 -0
  22. package/src/components/library/.sfdx/tools/soqlMetadata/standardObjects/Account.json +2952 -0
  23. package/src/components/library/.sfdx/tools/soqlMetadata/standardObjects/AccountHistory.json +875 -0
  24. package/src/components/library/.sfdx/tools/soqlMetadata/standardObjects/Asset.json +1699 -0
  25. package/src/components/library/.sfdx/tools/soqlMetadata/standardObjects/Attachment.json +362 -0
  26. package/src/components/library/.sfdx/tools/soqlMetadata/standardObjects/Case.json +1371 -0
  27. package/src/components/library/.sfdx/tools/soqlMetadata/standardObjects/Contact.json +2309 -0
  28. package/src/components/library/.sfdx/tools/soqlMetadata/standardObjects/Contract.json +1304 -0
  29. package/src/components/library/.sfdx/tools/soqlMetadata/standardObjects/Domain.json +293 -0
  30. package/src/components/library/.sfdx/tools/soqlMetadata/standardObjects/Lead.json +1977 -0
  31. package/src/components/library/.sfdx/tools/soqlMetadata/standardObjects/Note.json +303 -0
  32. package/src/components/library/.sfdx/tools/soqlMetadata/standardObjects/Opportunity.json +1470 -0
  33. package/src/components/library/.sfdx/tools/soqlMetadata/standardObjects/Order.json +1646 -0
  34. package/src/components/library/.sfdx/tools/soqlMetadata/standardObjects/Pricebook2.json +482 -0
  35. package/src/components/library/.sfdx/tools/soqlMetadata/standardObjects/PricebookEntry.json +433 -0
  36. package/src/components/library/.sfdx/tools/soqlMetadata/standardObjects/Product2.json +1039 -0
  37. package/src/components/library/.sfdx/tools/soqlMetadata/standardObjects/RecordType.json +2576 -0
  38. package/src/components/library/.sfdx/tools/soqlMetadata/standardObjects/Report.json +486 -0
  39. package/src/components/library/.sfdx/tools/soqlMetadata/standardObjects/Task.json +4296 -0
  40. package/src/components/library/.sfdx/tools/soqlMetadata/standardObjects/User.json +30415 -0
  41. package/src/components/library/.sfdx/tools/soqlMetadata/typeNames.json +78 -0
  42. package/src/components/library/.sfdx/typings/lwc/sobjects/Account.d.ts +264 -0
  43. package/src/components/library/.sfdx/typings/lwc/sobjects/AccountHistory.d.ts +44 -0
  44. package/src/components/library/.sfdx/typings/lwc/sobjects/Asset.d.ts +240 -0
  45. package/src/components/library/.sfdx/typings/lwc/sobjects/Attachment.d.ts +76 -0
  46. package/src/components/library/.sfdx/typings/lwc/sobjects/Case.d.ts +172 -0
  47. package/src/components/library/.sfdx/typings/lwc/sobjects/Contact.d.ts +264 -0
  48. package/src/components/library/.sfdx/typings/lwc/sobjects/Contract.d.ts +188 -0
  49. package/src/components/library/.sfdx/typings/lwc/sobjects/Domain.d.ts +52 -0
  50. package/src/components/library/.sfdx/typings/lwc/sobjects/Lead.d.ts +252 -0
  51. package/src/components/library/.sfdx/typings/lwc/sobjects/Note.d.ts +64 -0
  52. package/src/components/library/.sfdx/typings/lwc/sobjects/Opportunity.d.ts +200 -0
  53. package/src/components/library/.sfdx/typings/lwc/sobjects/Order.d.ts +260 -0
  54. package/src/components/library/.sfdx/typings/lwc/sobjects/Pricebook2.d.ts +64 -0
  55. package/src/components/library/.sfdx/typings/lwc/sobjects/PricebookEntry.d.ts +76 -0
  56. package/src/components/library/.sfdx/typings/lwc/sobjects/Product2.d.ts +96 -0
  57. package/src/components/library/.sfdx/typings/lwc/sobjects/RecordType.d.ts +64 -0
  58. package/src/components/library/.sfdx/typings/lwc/sobjects/Report.d.ts +80 -0
  59. package/src/components/library/.sfdx/typings/lwc/sobjects/Task.d.ts +184 -0
  60. package/src/components/library/.sfdx/typings/lwc/sobjects/User.d.ts +752 -0
  61. package/src/components/library/cards/ActionList.jsx +38 -0
  62. package/src/components/library/cards/ActivityCard.jsx +56 -0
  63. package/src/components/library/cards/BaseCard.jsx +109 -0
  64. package/src/components/library/cards/CalloutCard.jsx +37 -0
  65. package/src/components/library/cards/ChartCard.jsx +105 -0
  66. package/src/components/library/cards/FeedPanel.jsx +39 -0
  67. package/src/components/library/cards/ListCard.jsx +193 -0
  68. package/src/components/library/cards/MetricCard.jsx +109 -0
  69. package/src/components/library/cards/MetricsStrip.jsx +78 -0
  70. package/src/components/library/cards/SectionCard.jsx +83 -0
  71. package/src/components/library/cards/SemanticMetricCard.jsx +52 -0
  72. package/src/components/library/cards/SemanticMetricCardWithLoading.jsx +23 -0
  73. package/src/components/library/cards/SemanticTableCard.jsx +48 -0
  74. package/src/components/library/cards/SemanticTableCardWithLoading.jsx +22 -0
  75. package/src/components/library/cards/StatusCard.jsx +220 -0
  76. package/src/components/library/cards/TableCard.jsx +337 -0
  77. package/src/components/library/cards/WidgetCard.jsx +90 -0
  78. package/src/components/library/charts/D3Chart.jsx +109 -0
  79. package/src/components/library/charts/D3ChartTemplates.jsx +126 -0
  80. package/src/components/library/charts/GeoMap.jsx +293 -0
  81. package/src/components/library/chat/ChatBar.jsx +256 -0
  82. package/src/components/library/chat/ChatInput.jsx +89 -0
  83. package/src/components/library/chat/ChatMessage.jsx +178 -0
  84. package/src/components/library/chat/ChatMessageList.jsx +73 -0
  85. package/src/components/library/chat/ChatPanel.jsx +97 -0
  86. package/src/components/library/chat/ChatSuggestions.jsx +28 -0
  87. package/src/components/library/chat/ChatToolCall.jsx +100 -0
  88. package/src/components/library/chat/ChatTypingIndicator.jsx +23 -0
  89. package/src/components/library/chat/ChatWelcome.jsx +43 -0
  90. package/src/components/library/chat/index.jsx +10 -0
  91. package/src/components/library/chat/useChatState.jsx +130 -0
  92. package/src/components/library/data/DataModeProvider.jsx +67 -0
  93. package/src/components/library/data/DataModeToggle.jsx +36 -0
  94. package/src/components/library/data/chartDataProvider.jsx +61 -0
  95. package/src/components/library/data/filterUtils.jsx +141 -0
  96. package/src/components/library/data/useDataSource.jsx +33 -0
  97. package/src/components/library/data/usePageFilters.jsx +99 -0
  98. package/src/components/library/filters/FilterBar.jsx +95 -0
  99. package/src/components/library/filters/SearchFilter.jsx +36 -0
  100. package/src/components/library/filters/SelectFilter.jsx +55 -0
  101. package/src/components/library/filters/ToggleFilter.jsx +52 -0
  102. package/src/components/library/filters/index.jsx +4 -0
  103. package/src/components/library/forms/FormField.jsx +291 -0
  104. package/src/components/library/forms/FormModal.jsx +201 -0
  105. package/src/components/library/forms/FormRenderer.jsx +46 -0
  106. package/src/components/library/forms/FormSection.jsx +69 -0
  107. package/src/components/library/forms/index.jsx +5 -0
  108. package/src/components/library/forms/useFormState.jsx +165 -0
  109. package/src/components/library/heroui/Accordion.jsx +26 -0
  110. package/src/components/library/heroui/Alert.jsx +8 -0
  111. package/src/components/library/heroui/Badge.jsx +8 -0
  112. package/src/components/library/heroui/Breadcrumbs.jsx +22 -0
  113. package/src/components/library/heroui/Button.jsx +58 -0
  114. package/src/components/library/heroui/Card.jsx +8 -0
  115. package/src/components/library/heroui/Collapsible.jsx +42 -0
  116. package/src/components/library/heroui/DatePicker.jsx +34 -0
  117. package/src/components/library/heroui/Dialog.jsx +37 -0
  118. package/src/components/library/heroui/Drawer.jsx +32 -0
  119. package/src/components/library/heroui/Dropdown.jsx +28 -0
  120. package/src/components/library/heroui/Field.jsx +51 -0
  121. package/src/components/library/heroui/Input.jsx +6 -0
  122. package/src/components/library/heroui/Kbd.jsx +8 -0
  123. package/src/components/library/heroui/Meter.jsx +8 -0
  124. package/src/components/library/heroui/Modal.jsx +32 -0
  125. package/src/components/library/heroui/Pagination.jsx +8 -0
  126. package/src/components/library/heroui/Popover.jsx +64 -0
  127. package/src/components/library/heroui/ProgressBar.jsx +8 -0
  128. package/src/components/library/heroui/ProgressCircle.jsx +8 -0
  129. package/src/components/library/heroui/ScrollShadow.jsx +8 -0
  130. package/src/components/library/heroui/Select.jsx +37 -0
  131. package/src/components/library/heroui/Separator.jsx +8 -0
  132. package/src/components/library/heroui/Skeleton.jsx +8 -0
  133. package/src/components/library/heroui/Tabs.jsx +26 -0
  134. package/src/components/library/heroui/Toast.jsx +25 -0
  135. package/src/components/library/heroui/Toggle.jsx +14 -0
  136. package/src/components/library/heroui/Tooltip.jsx +21 -0
  137. package/src/components/library/index.jsx +149 -0
  138. package/src/components/library/layout/PageContainer.jsx +11 -0
  139. package/src/components/library/skeletons/CardSkeleton.jsx +30 -0
  140. package/src/components/library/theme/AppThemeProvider.jsx +67 -0
  141. package/src/components/library/theme/tokens.jsx +72 -0
  142. package/src/components/library/ui/Alert.jsx +80 -0
  143. package/src/components/library/ui/Avatar.jsx +44 -0
  144. package/src/components/library/ui/BreadcrumbExtras.tsx +119 -0
  145. package/src/components/library/ui/Card.jsx +117 -0
  146. package/src/components/library/ui/Checkbox.jsx +17 -0
  147. package/src/components/library/ui/Chip.jsx +38 -0
  148. package/src/components/library/ui/Collapsible.tsx +31 -0
  149. package/src/components/library/ui/Container.jsx +56 -0
  150. package/src/components/library/ui/DatePicker.tsx +34 -0
  151. package/src/components/library/ui/Dialog.tsx +141 -0
  152. package/src/components/library/ui/EmptyState.jsx +46 -0
  153. package/src/components/library/ui/Field.tsx +82 -0
  154. package/src/components/library/ui/FieldGroup.jsx +17 -0
  155. package/src/components/library/ui/Label.jsx +22 -0
  156. package/src/components/library/ui/PaginationExtras.tsx +143 -0
  157. package/src/components/library/ui/Popover.tsx +39 -0
  158. package/src/components/library/ui/Select.tsx +113 -0
  159. package/src/components/library/ui/Spinner.jsx +64 -0
  160. package/src/components/library/ui/Text.jsx +46 -0
  161. package/src/components/library/ui/UIButton.jsx +61 -0
  162. package/src/components/library/ui/UIInput.jsx +21 -0
  163. package/src/components/workspace/ComponentRegistry.jsx +297 -0
  164. package/src/templates/pages/Home.tsx.template +5 -5
  165. package/src/templates/pages/NotFound.tsx.template +2 -2
@@ -0,0 +1,38 @@
1
+ import React from "react";
2
+ import UIButton from "../ui/UIButton";
3
+
4
+ /**
5
+ * Row of action buttons — typically used at the bottom of a dashboard section.
6
+ *
7
+ * @param {{ label: string, [key]: any }[] | string[]} actions
8
+ * @param {string} title
9
+ * @param {Function} onAction Called with the action object/string when clicked
10
+ */
11
+ export default function ActionList({
12
+ actions = [],
13
+ title,
14
+ onAction,
15
+ className = "",
16
+ }) {
17
+ return (
18
+ <div className={`rounded-2xl border border-slate-200 bg-white p-4 dark:border-slate-800 dark:bg-slate-900 ${className}`}>
19
+ {title && (
20
+ <div className="mb-3 text-sm font-medium text-slate-900 dark:text-slate-50">
21
+ {title}
22
+ </div>
23
+ )}
24
+ <div className="flex flex-wrap gap-2">
25
+ {actions.map((action, i) => (
26
+ <UIButton
27
+ key={i}
28
+ size="sm"
29
+ variant={i === 0 ? "primary" : "outline"}
30
+ onClick={() => onAction?.(action)}
31
+ >
32
+ {typeof action === "string" ? action : action.label}
33
+ </UIButton>
34
+ ))}
35
+ </div>
36
+ </div>
37
+ );
38
+ }
@@ -0,0 +1,56 @@
1
+ import React from "react";
2
+ import { motion, AnimatePresence } from "framer-motion";
3
+ import { ArrowPathIcon, CheckCircleIcon, ExclamationCircleIcon, ClockIcon } from "@heroicons/react/24/outline";
4
+ import UIText from "../ui/Text";
5
+
6
+ const STATUS_ICON = {
7
+ working: { Icon: ArrowPathIcon, color: "text-indigo-500", spin: true },
8
+ pending: { Icon: ClockIcon, color: "text-slate-400", spin: false },
9
+ complete: { Icon: CheckCircleIcon, color: "text-emerald-500", spin: false },
10
+ error: { Icon: ExclamationCircleIcon, color: "text-red-500", spin: false },
11
+ };
12
+
13
+ function ActionItem({ action }) {
14
+ const s = STATUS_ICON[action.status] ?? STATUS_ICON.pending;
15
+ return (
16
+ <motion.div
17
+ initial={{ y: 12, opacity: 0 }}
18
+ animate={{ y: 0, opacity: 1 }}
19
+ exit={{ y: -12, opacity: 0 }}
20
+ className="rounded-lg border border-slate-100 bg-slate-50 p-3 dark:border-slate-800 dark:bg-slate-950/40"
21
+ >
22
+ <div className="flex items-start gap-2">
23
+ <s.Icon className={`mt-0.5 h-4 w-4 shrink-0 ${s.color} ${s.spin ? "animate-spin" : ""}`} />
24
+ <div className="min-w-0">
25
+ <div className="text-xs font-medium text-slate-700 dark:text-slate-200">{action.title ?? action.action}</div>
26
+ {(action.subtitle ?? action.traveler ?? action.timestamp ?? action.startedAt) && (
27
+ <div className="mt-0.5 text-[10px] text-slate-400">
28
+ {[action.subtitle, action.traveler, action.timestamp ?? action.startedAt].filter(Boolean).join(" · ")}
29
+ </div>
30
+ )}
31
+ </div>
32
+ </div>
33
+ </motion.div>
34
+ );
35
+ }
36
+
37
+ export default function ActivityCard({ title = "Activity", actions = [], className = "" }) {
38
+ if (actions.length === 0) return null;
39
+
40
+ return (
41
+ <div className={className}>
42
+ {title && (
43
+ <UIText as="div" size="xs" weight="semibold" muted className="mb-2 uppercase tracking-wider">
44
+ {title}
45
+ </UIText>
46
+ )}
47
+ <div className="space-y-2">
48
+ <AnimatePresence>
49
+ {actions.map(a => (
50
+ <ActionItem key={a.id} action={a} />
51
+ ))}
52
+ </AnimatePresence>
53
+ </div>
54
+ </div>
55
+ );
56
+ }
@@ -0,0 +1,109 @@
1
+ import React from "react";
2
+
3
+ const VARIANT_CLASSES = {
4
+ default: "",
5
+ metric: "",
6
+ chart: "",
7
+ table: "",
8
+ widget: "",
9
+ status: ""
10
+ };
11
+
12
+ const SIZE_CLASSES = {
13
+ xs: "",
14
+ sm: "min-h-[80px]",
15
+ md: "min-h-[120px]",
16
+ lg: "min-h-[160px]",
17
+ xl: "min-h-[220px]",
18
+ full: ""
19
+ };
20
+
21
+ const PADDING_CLASSES = {
22
+ none: "p-0",
23
+ xs: "p-2",
24
+ sm: "p-3",
25
+ default: "p-4",
26
+ lg: "p-6",
27
+ xl: "p-8"
28
+ };
29
+
30
+ export default function BaseCard({
31
+ header,
32
+ body,
33
+ footer,
34
+ children,
35
+ variant = "default",
36
+ size = "md",
37
+ padding = "default",
38
+ shadow = true,
39
+ radius = "2xl",
40
+ border = true,
41
+ isHoverable = false,
42
+ isPressable = false,
43
+ isLoading = false,
44
+ isDisabled = false,
45
+ isSelected = false,
46
+ className = "",
47
+ headerClassName = "",
48
+ bodyClassName = "",
49
+ footerClassName = "",
50
+ onPress,
51
+ onHover,
52
+ ...rest
53
+ }) {
54
+ const Comp = isPressable ? "button" : "div";
55
+
56
+ const radiusClass = radius ? `rounded-${radius}` : "rounded-2xl";
57
+ const paddingClass = PADDING_CLASSES[padding] ?? PADDING_CLASSES.default;
58
+ const sizeClass = SIZE_CLASSES[size] ?? SIZE_CLASSES.md;
59
+ const variantClass = VARIANT_CLASSES[variant] ?? VARIANT_CLASSES.default;
60
+
61
+ const interactive = isHoverable || isPressable;
62
+ const disabled = isDisabled || (isPressable && !onPress);
63
+
64
+ return (
65
+ <Comp
66
+ type={isPressable ? "button" : undefined}
67
+ onClick={isPressable ? onPress : undefined}
68
+ onMouseEnter={onHover}
69
+ disabled={isPressable ? disabled : undefined}
70
+ className={[
71
+ "w-full text-left",
72
+ radiusClass,
73
+ border ? "border border-slate-200 dark:border-slate-800" : "border border-transparent",
74
+ "bg-white dark:bg-slate-900",
75
+ shadow ? "shadow-sm" : "",
76
+ sizeClass,
77
+ variantClass,
78
+ interactive ? "transition hover:-translate-y-[1px] hover:shadow-md" : "",
79
+ isSelected ? "ring-2 ring-brand-500" : "",
80
+ disabled ? "opacity-60" : "",
81
+ className
82
+ ]
83
+ .filter(Boolean)
84
+ .join(" ")}
85
+ {...rest}
86
+ >
87
+ <div className={paddingClass}>
88
+ {header != null ? <div className={headerClassName}>{header}</div> : null}
89
+
90
+ {isLoading ? (
91
+ <div className={["mt-3 space-y-3", bodyClassName].filter(Boolean).join(" ")}>
92
+ <div className="h-4 w-1/3 animate-pulse rounded bg-slate-200 dark:bg-slate-800" />
93
+ <div className="h-4 w-2/3 animate-pulse rounded bg-slate-200 dark:bg-slate-800" />
94
+ <div className="h-4 w-1/2 animate-pulse rounded bg-slate-200 dark:bg-slate-800" />
95
+ </div>
96
+ ) : (
97
+ <>
98
+ {body != null ? <div className={bodyClassName}>{body}</div> : null}
99
+ {children}
100
+ </>
101
+ )}
102
+
103
+ {footer != null ? <div className={footerClassName}>{footer}</div> : null}
104
+ </div>
105
+ </Comp>
106
+ );
107
+ }
108
+
109
+
@@ -0,0 +1,37 @@
1
+ import React from "react";
2
+
3
+ const TONE_CLASSES = {
4
+ neutral: "border-slate-200 bg-slate-50 text-slate-700 dark:border-slate-800 dark:bg-slate-950/30 dark:text-slate-200",
5
+ 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
+ warning: "border-amber-200 bg-amber-50 text-amber-800 dark:border-amber-900/40 dark:bg-amber-950/20 dark:text-amber-200",
7
+ danger: "border-rose-200 bg-rose-50 text-rose-800 dark:border-rose-900/40 dark:bg-rose-950/20 dark:text-rose-200",
8
+ 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
+ };
10
+
11
+ /**
12
+ * Highlighted callout box for important inline messages.
13
+ *
14
+ * @param {string} title
15
+ * @param {string|ReactNode} message
16
+ * @param {"neutral"|"success"|"warning"|"danger"|"info"} tone
17
+ * @param {ReactNode} icon Optional leading icon
18
+ */
19
+ export default function CalloutCard({
20
+ title,
21
+ message,
22
+ tone = "neutral",
23
+ icon,
24
+ className = "",
25
+ }) {
26
+ return (
27
+ <div className={`rounded-xl border p-4 ${TONE_CLASSES[tone] ?? TONE_CLASSES.neutral} ${className}`}>
28
+ <div className="flex gap-3">
29
+ {icon && <div className="mt-0.5 shrink-0">{icon}</div>}
30
+ <div>
31
+ {title && <div className="mb-1 text-sm font-semibold">{title}</div>}
32
+ <div className="text-sm">{message}</div>
33
+ </div>
34
+ </div>
35
+ </div>
36
+ );
37
+ }
@@ -0,0 +1,105 @@
1
+ import React from "react";
2
+ import BaseCard from "./BaseCard";
3
+ import UIText from "../ui/Text";
4
+ import UIChip from "../ui/Chip";
5
+
6
+ export default function ChartCard({
7
+ chart,
8
+ chartType,
9
+ title,
10
+ subtitle,
11
+ filters,
12
+ timeRange,
13
+ actions,
14
+ legend,
15
+ height = 280,
16
+ showGrid,
17
+ showAxes,
18
+ data,
19
+ loading = false,
20
+ error,
21
+ ...cardProps
22
+ }) {
23
+ const header = (
24
+ <div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
25
+ <div className="min-w-0">
26
+ {title ? (
27
+ <UIText as="div" size="sm" weight="medium">
28
+ {title}
29
+ </UIText>
30
+ ) : null}
31
+ {subtitle ? (
32
+ <UIText as="div" size="xs" muted className="mt-1">
33
+ {subtitle}
34
+ </UIText>
35
+ ) : null}
36
+ </div>
37
+ <div className="flex flex-wrap items-center justify-end gap-2">
38
+ {filters ? <div className="flex items-center gap-2">{filters}</div> : null}
39
+ {timeRange ? (
40
+ <select
41
+ className="h-9 rounded-lg border border-slate-200 bg-white px-3 text-sm text-slate-900 shadow-sm dark:border-slate-800 dark:bg-slate-900 dark:text-slate-50"
42
+ value={timeRange.current}
43
+ onChange={(e) => timeRange.onChange?.(e.target.value)}
44
+ aria-label="Time range"
45
+ >
46
+ {timeRange.options?.map((opt) => (
47
+ <option key={opt.value ?? opt} value={opt.value ?? opt}>
48
+ {opt.label ?? opt}
49
+ </option>
50
+ ))}
51
+ </select>
52
+ ) : null}
53
+ {actions ? <div className="flex items-center gap-2">{actions}</div> : null}
54
+ </div>
55
+ </div>
56
+ );
57
+
58
+ if (error) {
59
+ return (
60
+ <BaseCard
61
+ variant="chart"
62
+ header={header}
63
+ body={
64
+ <div className="mt-4 rounded-xl border border-rose-200 bg-rose-50 p-4 text-sm text-rose-900 dark:border-rose-900/40 dark:bg-rose-950/30 dark:text-rose-100">
65
+ {String(error)}
66
+ </div>
67
+ }
68
+ {...cardProps}
69
+ />
70
+ );
71
+ }
72
+
73
+ const hint = chartType ? <UIChip>{chartType}</UIChip> : null;
74
+
75
+ return (
76
+ <BaseCard
77
+ variant="chart"
78
+ header={
79
+ <div className="flex items-center justify-between gap-3">
80
+ <div className="min-w-0">{header}</div>
81
+ {hint}
82
+ </div>
83
+ }
84
+ body={
85
+ <div className="mt-4">
86
+ <div className="w-full" style={{ height }}>
87
+ {chart}
88
+ </div>
89
+ {legend ? <div className="mt-3">{legend}</div> : null}
90
+ {(showGrid != null || showAxes != null) && data ? (
91
+ <div className="mt-2 text-xs text-slate-500 dark:text-slate-400">
92
+ grid: {String(!!showGrid)} · axes: {String(!!showAxes)} · points: {data.length}
93
+ </div>
94
+ ) : null}
95
+ {loading ? (
96
+ <div className="mt-3 text-xs text-slate-500 dark:text-slate-400">Loading…</div>
97
+ ) : null}
98
+ </div>
99
+ }
100
+ {...cardProps}
101
+ />
102
+ );
103
+ }
104
+
105
+
@@ -0,0 +1,39 @@
1
+ import React from "react";
2
+ import UIText from "../ui/Text";
3
+
4
+ export default function FeedPanel({
5
+ title,
6
+ subtitle,
7
+ actions,
8
+ children,
9
+ width,
10
+ className = "",
11
+ headerClassName = "",
12
+ bodyClassName = "",
13
+ ...rest
14
+ }) {
15
+ const widthStyle = width ? (typeof width === "number" ? { width: `${width}px` } : { width }) : undefined;
16
+
17
+ return (
18
+ <div
19
+ className={`flex shrink-0 flex-col overflow-hidden rounded-xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900 ${className}`}
20
+ style={widthStyle}
21
+ {...rest}
22
+ >
23
+ {(title || subtitle || actions) && (
24
+ <div className={`shrink-0 border-b border-slate-100 px-4 py-3 dark:border-slate-800 ${headerClassName}`}>
25
+ <div className="flex items-start justify-between gap-3">
26
+ <div className="min-w-0">
27
+ {title && <UIText as="div" size="sm" weight="semibold">{title}</UIText>}
28
+ {subtitle && <UIText as="div" size="xs" muted className="mt-0.5">{subtitle}</UIText>}
29
+ </div>
30
+ {actions && <div className="shrink-0">{actions}</div>}
31
+ </div>
32
+ </div>
33
+ )}
34
+ <div className={`flex-1 overflow-y-auto ${bodyClassName}`}>
35
+ {children}
36
+ </div>
37
+ </div>
38
+ );
39
+ }
@@ -0,0 +1,193 @@
1
+ import React from "react";
2
+ import BaseCard from "./BaseCard";
3
+ import UIText from "../ui/Text";
4
+ import UIChip from "../ui/Chip";
5
+
6
+ function formatTimestamp(ts) {
7
+ if (!ts) return "";
8
+ try {
9
+ const d = ts instanceof Date ? ts : new Date(ts);
10
+ if (Number.isNaN(d.getTime())) return String(ts);
11
+ return d.toLocaleString(undefined, { dateStyle: "medium", timeStyle: "short" });
12
+ } catch {
13
+ return String(ts);
14
+ }
15
+ }
16
+
17
+ function Avatar({ item }) {
18
+ const avatar = item?.avatar;
19
+ const name = item?.title ?? item?.name ?? "Item";
20
+ if (!avatar) return null;
21
+ if (typeof avatar === "string") {
22
+ return (
23
+ <img
24
+ src={avatar}
25
+ alt=""
26
+ className="h-9 w-9 rounded-full border border-slate-200 object-cover dark:border-slate-800"
27
+ />
28
+ );
29
+ }
30
+ if (React.isValidElement(avatar)) {
31
+ return (
32
+ <div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-full border border-slate-200 bg-slate-50 dark:border-slate-800 dark:bg-slate-800">
33
+ {avatar}
34
+ </div>
35
+ );
36
+ }
37
+ return (
38
+ <div className="h-9 w-9 rounded-full border border-slate-200 bg-slate-100 text-xs font-semibold text-slate-700 dark:border-slate-800 dark:bg-slate-800 dark:text-slate-200 grid place-items-center">
39
+ {String(name).slice(0, 2).toUpperCase()}
40
+ </div>
41
+ );
42
+ }
43
+
44
+ export default function ListCard({
45
+ items = [],
46
+ title,
47
+ subtitle,
48
+ showAvatars = true,
49
+ showStatus = true,
50
+ showActions = true,
51
+ showTimestamp = true,
52
+ dense = false,
53
+ divided = true,
54
+ actions,
55
+ itemActions,
56
+ onItemClick,
57
+ loading = false,
58
+ error,
59
+ emptyMessage = "No items.",
60
+ emptyIcon,
61
+ maxBodyHeight,
62
+ ...cardProps
63
+ }) {
64
+ const header = (
65
+ <div className="flex items-start justify-between gap-3">
66
+ <div className="min-w-0">
67
+ {title ? (
68
+ <UIText as="div" size="sm" weight="medium">
69
+ {title}
70
+ </UIText>
71
+ ) : null}
72
+ {subtitle ? (
73
+ <UIText as="div" size="xs" muted className="mt-1">
74
+ {subtitle}
75
+ </UIText>
76
+ ) : null}
77
+ </div>
78
+ {actions ? <div className="shrink-0">{actions}</div> : null}
79
+ </div>
80
+ );
81
+
82
+ if (error) {
83
+ return (
84
+ <BaseCard
85
+ variant="widget"
86
+ header={header}
87
+ body={
88
+ <div className="mt-4 rounded-xl border border-rose-200 bg-rose-50 p-4 text-sm text-rose-900 dark:border-rose-900/40 dark:bg-rose-950/30 dark:text-rose-100">
89
+ {String(error)}
90
+ </div>
91
+ }
92
+ {...cardProps}
93
+ />
94
+ );
95
+ }
96
+
97
+ const padY = dense ? "py-2" : "py-3";
98
+
99
+ const scrollStyle = maxBodyHeight ? { maxHeight: maxBodyHeight, overflowY: "auto" } : {};
100
+
101
+ const body =
102
+ loading ? (
103
+ <div className="mt-4 space-y-3">
104
+ {Array.from({ length: 4 }).map((_, i) => (
105
+ <div key={i} className="flex items-center gap-3 rounded-xl border border-slate-200 bg-white p-3 dark:border-slate-800 dark:bg-slate-900">
106
+ {showAvatars ? <div className="h-9 w-9 animate-pulse rounded-full bg-slate-200 dark:bg-slate-800" /> : null}
107
+ <div className="flex-1 space-y-2">
108
+ <div className="h-4 w-1/3 animate-pulse rounded bg-slate-200 dark:bg-slate-800" />
109
+ <div className="h-3 w-2/3 animate-pulse rounded bg-slate-200 dark:bg-slate-800" />
110
+ </div>
111
+ </div>
112
+ ))}
113
+ </div>
114
+ ) : items.length === 0 ? (
115
+ <div className="mt-4 rounded-xl border border-dashed border-slate-300 bg-slate-50 p-6 text-center dark:border-slate-700 dark:bg-slate-950/30">
116
+ {emptyIcon ? <div className="mb-2">{emptyIcon}</div> : null}
117
+ <UIText size="sm" muted>
118
+ {emptyMessage}
119
+ </UIText>
120
+ </div>
121
+ ) : (
122
+ <div className="mt-4 overflow-hidden rounded-xl border border-slate-200 bg-white dark:border-slate-800 dark:bg-slate-900">
123
+ <ul className={divided ? "divide-y divide-slate-200 dark:divide-slate-800" : ""} style={scrollStyle}>
124
+ {items.map((item, idx) => {
125
+ const key = item?.id ?? idx;
126
+ const name = item?.title ?? item?.name ?? "Item";
127
+ const desc = item?.description;
128
+ const status = item?.status;
129
+ const ts = item?.timestamp;
130
+ const itemValue = item?.value;
131
+ const itemUnit = item?.unit;
132
+ const right = showActions ? itemActions?.(item, idx) : null;
133
+
134
+ return (
135
+ <li
136
+ key={key}
137
+ className={[
138
+ "group flex items-start justify-between gap-3 px-4",
139
+ padY,
140
+ onItemClick ? "cursor-pointer hover:bg-slate-50 dark:hover:bg-slate-950/30" : ""
141
+ ]
142
+ .filter(Boolean)
143
+ .join(" ")}
144
+ onClick={() => onItemClick?.(item, idx)}
145
+ >
146
+ <div className="flex min-w-0 items-start gap-3">
147
+ {showAvatars ? <Avatar item={item} /> : null}
148
+ <div className="min-w-0">
149
+ <div className="flex flex-wrap items-center gap-2">
150
+ <div className="text-sm font-semibold text-slate-900 dark:text-slate-50">
151
+ {name}
152
+ </div>
153
+ {showStatus && status ? (
154
+ <UIChip>{String(status)}</UIChip>
155
+ ) : null}
156
+ </div>
157
+ {desc ? (
158
+ <div className="mt-1 text-sm text-slate-600 dark:text-slate-300">{desc}</div>
159
+ ) : null}
160
+ {showTimestamp && ts ? (
161
+ <div className="mt-1 text-xs text-slate-500 dark:text-slate-400">
162
+ {formatTimestamp(ts)}
163
+ </div>
164
+ ) : null}
165
+ </div>
166
+ </div>
167
+
168
+ <div className="flex shrink-0 items-center gap-2">
169
+ {itemValue != null ? (
170
+ <div className="text-right">
171
+ <div className="text-sm font-semibold text-slate-900 dark:text-slate-50">
172
+ {String(itemValue)}
173
+ {itemUnit ? <span className="ml-1 text-xs font-medium text-slate-500 dark:text-slate-400">{itemUnit}</span> : null}
174
+ </div>
175
+ </div>
176
+ ) : null}
177
+ {right ? (
178
+ <div className="opacity-100 sm:opacity-0 sm:group-hover:opacity-100">
179
+ {right}
180
+ </div>
181
+ ) : null}
182
+ </div>
183
+ </li>
184
+ );
185
+ })}
186
+ </ul>
187
+ </div>
188
+ );
189
+
190
+ return <BaseCard variant="widget" header={header} body={body} isLoading={false} {...cardProps} />;
191
+ }
192
+
193
+
@@ -0,0 +1,109 @@
1
+ import React from "react";
2
+ import BaseCard from "./BaseCard";
3
+
4
+ const CHANGE_STYLES = {
5
+ positive: "text-emerald-700 dark:text-emerald-400",
6
+ negative: "text-rose-700 dark:text-rose-400",
7
+ neutral: "text-slate-600 dark:text-slate-300"
8
+ };
9
+
10
+ const COLOR_STYLES = {
11
+ default: "bg-slate-100 text-slate-800 dark:bg-slate-800 dark:text-slate-100",
12
+ primary: "bg-brand-100 text-brand-800 dark:bg-brand-950/40 dark:text-brand-200",
13
+ success: "bg-emerald-100 text-emerald-800 dark:bg-emerald-950/40 dark:text-emerald-200",
14
+ warning: "bg-amber-100 text-amber-900 dark:bg-amber-950/40 dark:text-amber-200",
15
+ danger: "bg-rose-100 text-rose-800 dark:bg-rose-950/40 dark:text-rose-200"
16
+ };
17
+
18
+ export default function MetricCard({
19
+ title,
20
+ value,
21
+ subtitle,
22
+ change,
23
+ changeType = "neutral",
24
+ icon,
25
+ color = "default",
26
+ trend,
27
+ trendIcon,
28
+ layout = "default",
29
+ footer,
30
+ actions,
31
+ loading = false,
32
+ error,
33
+ ...cardProps
34
+ }) {
35
+ const changeClass = CHANGE_STYLES[changeType] ?? CHANGE_STYLES.neutral;
36
+ const pillClass = COLOR_STYLES[color] ?? COLOR_STYLES.default;
37
+
38
+ if (error) {
39
+ return (
40
+ <BaseCard
41
+ variant="metric"
42
+ body={
43
+ <div className="rounded-xl border border-rose-200 bg-rose-50 p-4 text-sm text-rose-900 dark:border-rose-900/40 dark:bg-rose-950/30 dark:text-rose-100">
44
+ {String(error)}
45
+ </div>
46
+ }
47
+ {...cardProps}
48
+ />
49
+ );
50
+ }
51
+
52
+ const header = (
53
+ <div className="flex items-start justify-between gap-3">
54
+ <div className="min-w-0">
55
+ <div className="text-sm font-medium text-slate-600 dark:text-slate-300">{title}</div>
56
+ {subtitle ? (
57
+ <div className="mt-1 text-xs text-slate-500 dark:text-slate-400">{subtitle}</div>
58
+ ) : null}
59
+ </div>
60
+ {actions ? <div className="shrink-0">{actions}</div> : null}
61
+ </div>
62
+ );
63
+
64
+ const body = (
65
+ <div className={["mt-2", layout === "compact" ? "space-y-1" : "space-y-2"].join(" ")}>
66
+ <div className="flex flex-wrap items-center gap-x-3 gap-y-1">
67
+ <div className="flex items-center gap-2">
68
+ {icon ? (
69
+ <span
70
+ className={[
71
+ "inline-flex h-8 w-8 items-center justify-center rounded-lg",
72
+ pillClass
73
+ ].join(" ")}
74
+ aria-hidden="true"
75
+ >
76
+ {icon}
77
+ </span>
78
+ ) : null}
79
+ <div className="text-2xl font-semibold tracking-tight text-slate-900 dark:text-slate-50">
80
+ {value}
81
+ </div>
82
+ </div>
83
+
84
+ {(change || trend) && (
85
+ <div className={["flex items-center gap-1.5 text-xs font-medium", changeClass].join(" ")}>
86
+ {trendIcon ? <span aria-hidden="true">{trendIcon}</span> : null}
87
+ {change ? <span>{change}</span> : null}
88
+ {trend ? <span className="text-[11px] font-normal text-slate-500 dark:text-slate-400">{trend}</span> : null}
89
+ </div>
90
+ )}
91
+ </div>
92
+ </div>
93
+ );
94
+
95
+ return (
96
+ <BaseCard
97
+ variant="metric"
98
+ padding="sm"
99
+ size="sm"
100
+ header={header}
101
+ body={body}
102
+ footer={footer ? <div className="mt-3">{footer}</div> : null}
103
+ isLoading={loading}
104
+ {...cardProps}
105
+ />
106
+ );
107
+ }
108
+
109
+