@schandlergarcia/sf-web-components 1.2.6 → 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,78 @@
1
+ import React, { useState } from "react";
2
+
3
+ const PLACEHOLDER_METRICS = [
4
+ { label: "Metric A", value: "—", trend: null },
5
+ { label: "Metric B", value: "—", trend: null },
6
+ { label: "Metric C", value: "—", trend: null },
7
+ ];
8
+
9
+ /**
10
+ * Horizontal strip of KPI metrics — compact alternative to a row of MetricCards.
11
+ *
12
+ * @param {{ label: string, value: string|number, trend?: string|number }[]} metrics
13
+ * @param {string} title
14
+ * @param {boolean} collapsible Allow collapsing into a single-line summary
15
+ * @param {boolean} collapsed Initial collapsed state
16
+ */
17
+ export default function MetricsStrip({
18
+ metrics = [],
19
+ title,
20
+ collapsible = false,
21
+ collapsed: initialCollapsed = false,
22
+ className = "",
23
+ }) {
24
+ const [collapsed, setCollapsed] = useState(initialCollapsed);
25
+ const items = metrics.length ? metrics : PLACEHOLDER_METRICS;
26
+
27
+ if (collapsible && collapsed) {
28
+ return (
29
+ <button
30
+ type="button"
31
+ onClick={() => setCollapsed(false)}
32
+ 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 ${className}`}
33
+ >
34
+ <span>{title ?? "Metrics"}: {items.map((m) => `${m.label} ${m.value}`).join(" · ")}</span>
35
+ <span>▸</span>
36
+ </button>
37
+ );
38
+ }
39
+
40
+ return (
41
+ <div className={`rounded-xl border border-slate-200 bg-white p-3 dark:border-slate-800 dark:bg-slate-900 ${className}`}>
42
+ {(title || collapsible) && (
43
+ <div className="mb-2 flex items-center justify-between">
44
+ <span className="text-xs font-medium text-slate-500 dark:text-slate-400">{title ?? "Metrics"}</span>
45
+ {collapsible && (
46
+ <button
47
+ type="button"
48
+ onClick={() => setCollapsed(true)}
49
+ className="text-xs text-slate-400 hover:text-slate-600 dark:text-slate-500 dark:hover:text-slate-300"
50
+ >
51
+ Collapse
52
+ </button>
53
+ )}
54
+ </div>
55
+ )}
56
+ <div className="flex flex-wrap gap-4">
57
+ {items.map((m) => (
58
+ <div key={m.label} className="min-w-[80px]">
59
+ <div className="text-xs text-slate-400 dark:text-slate-500">{m.label}</div>
60
+ <div className="flex items-baseline gap-1.5">
61
+ <span className="text-sm font-semibold text-slate-700 dark:text-slate-200">{m.value}</span>
62
+ {m.trend != null && (
63
+ <TrendBadge trend={m.trend} />
64
+ )}
65
+ </div>
66
+ </div>
67
+ ))}
68
+ </div>
69
+ </div>
70
+ );
71
+ }
72
+
73
+ function TrendBadge({ trend }) {
74
+ const isPositive = String(trend).startsWith?.("+") || trend > 0;
75
+ const color = isPositive ? "text-red-500" : "text-emerald-500";
76
+ const label = typeof trend === "number" ? (trend > 0 ? `+${trend}` : trend) : trend;
77
+ return <span className={`text-xs ${color}`}>{label}</span>;
78
+ }
@@ -0,0 +1,83 @@
1
+ import React from "react";
2
+ import BaseCard from "./BaseCard";
3
+ import UIText from "../ui/Text";
4
+
5
+ const VARIANT_STYLES = {
6
+ default: "bg-white dark:bg-slate-900",
7
+ primary: "bg-brand-50 dark:bg-brand-950/30",
8
+ secondary: "bg-slate-50 dark:bg-slate-950/30",
9
+ accent: "bg-emerald-50 dark:bg-emerald-950/25"
10
+ };
11
+
12
+ const SIZE_STYLES = {
13
+ sm: { title: "text-lg", desc: "text-sm", pad: "p-4" },
14
+ md: { title: "text-xl", desc: "text-sm", pad: "p-5" },
15
+ lg: { title: "text-2xl", desc: "text-base", pad: "p-6" },
16
+ xl: { title: "text-3xl", desc: "text-base", pad: "p-7" }
17
+ };
18
+
19
+ const ALIGN_STYLES = {
20
+ left: "text-left items-start",
21
+ center: "text-center items-center",
22
+ right: "text-right items-end"
23
+ };
24
+
25
+ export default function SectionCard({
26
+ title,
27
+ description,
28
+ label,
29
+ variant = "default",
30
+ showDivider = true,
31
+ alignment = "left",
32
+ size = "md",
33
+ isDark = false,
34
+ className = "",
35
+ ...cardProps
36
+ }) {
37
+ const s = SIZE_STYLES[size] ?? SIZE_STYLES.md;
38
+ const align = ALIGN_STYLES[alignment] ?? ALIGN_STYLES.left;
39
+ const variantClass = VARIANT_STYLES[variant] ?? VARIANT_STYLES.default;
40
+
41
+ return (
42
+ <BaseCard
43
+ variant="widget"
44
+ padding="none"
45
+ className={[
46
+ variantClass,
47
+ isDark ? "dark" : "",
48
+ "overflow-hidden",
49
+ className
50
+ ]
51
+ .filter(Boolean)
52
+ .join(" ")}
53
+ {...cardProps}
54
+ body={
55
+ <div className={s.pad}>
56
+ <div className={["flex flex-col gap-2", align].join(" ")}>
57
+ {label ? (
58
+ <span className="inline-flex rounded-full bg-slate-900 px-2 py-1 text-[11px] font-semibold text-white dark:bg-slate-100 dark:text-slate-900">
59
+ {label}
60
+ </span>
61
+ ) : null}
62
+ {title ? (
63
+ <UIText as="h2" weight="bold" className={["tracking-tight", s.title].join(" ")}>
64
+ {title}
65
+ </UIText>
66
+ ) : null}
67
+ {description ? (
68
+ <UIText as="p" muted className={[s.desc, "max-w-3xl"].join(" ")}>
69
+ {description}
70
+ </UIText>
71
+ ) : null}
72
+ </div>
73
+
74
+ {showDivider ? (
75
+ <div className="mt-4 h-px w-full bg-slate-200 dark:bg-slate-800" />
76
+ ) : null}
77
+ </div>
78
+ }
79
+ />
80
+ );
81
+ }
82
+
83
+
@@ -0,0 +1,52 @@
1
+ import React from "react";
2
+ import MetricCard from "./MetricCard";
3
+ import { getSemanticMetric } from "../data/chartDataProvider";
4
+
5
+ export default function SemanticMetricCard({
6
+ semanticId,
7
+ metricId,
8
+ title,
9
+ subtitle,
10
+ value,
11
+ unit,
12
+ format,
13
+ trend,
14
+ change,
15
+ changeLabel,
16
+ description,
17
+ seriesName,
18
+ availableFilters,
19
+ showFilters,
20
+ compact,
21
+ className,
22
+ loading = false,
23
+ ...rest
24
+ }) {
25
+ const metric = React.useMemo(() => getSemanticMetric(semanticId, metricId), [semanticId, metricId]);
26
+
27
+ const resolvedTitle = title ?? metric?.title ?? metricId ?? "Metric";
28
+ const resolvedSubtitle = subtitle ?? metric?.subtitle;
29
+ const resolvedValue = value ?? metric?.value ?? "";
30
+ const resolvedTrend = trend ?? metric?.trend;
31
+ const resolvedChange = change ?? metric?.change;
32
+ const resolvedChangeType = metric?.changeType ?? "neutral";
33
+ const resolvedColor = metric?.color ?? "default";
34
+
35
+ // Note: unit/format/etc. are accepted for forward compatibility; basic rendering is handled by MetricCard for now.
36
+ return (
37
+ <MetricCard
38
+ title={resolvedTitle}
39
+ subtitle={resolvedSubtitle}
40
+ value={resolvedValue}
41
+ change={resolvedChangeLabel ? `${resolvedChangeLabel} ${resolvedChange ?? ""}`.trim() : resolvedChange}
42
+ changeType={resolvedChangeType}
43
+ color={resolvedColor}
44
+ trend={resolvedTrend}
45
+ layout={compact ? "compact" : "default"}
46
+ loading={loading}
47
+ className={className}
48
+ {...rest}
49
+ />
50
+ );
51
+ }
52
+
@@ -0,0 +1,23 @@
1
+ import React from "react";
2
+ import SemanticMetricCard from "./SemanticMetricCard";
3
+
4
+ export default function SemanticMetricCardWithLoading({
5
+ simulateInitialLoad = true,
6
+ minInitialDelayMs = 350,
7
+ maxInitialDelayMs = 900,
8
+ loading: loadingProp,
9
+ ...props
10
+ }) {
11
+ const [loading, setLoading] = React.useState(Boolean(simulateInitialLoad));
12
+
13
+ React.useEffect(() => {
14
+ if (!simulateInitialLoad) return;
15
+ const delay =
16
+ Math.floor(Math.random() * (maxInitialDelayMs - minInitialDelayMs + 1)) + minInitialDelayMs;
17
+ const t = setTimeout(() => setLoading(false), delay);
18
+ return () => clearTimeout(t);
19
+ }, [simulateInitialLoad, minInitialDelayMs, maxInitialDelayMs]);
20
+
21
+ return <SemanticMetricCard loading={loadingProp ?? loading} {...props} />;
22
+ }
23
+
@@ -0,0 +1,48 @@
1
+ import React from "react";
2
+ import TableCard from "./TableCard";
3
+ import { getSemanticDataset } from "../data/chartDataProvider";
4
+
5
+ export default function SemanticTableCard({
6
+ semanticId,
7
+ dataOverride,
8
+ columnsOverride,
9
+ title,
10
+ subtitle,
11
+ searchable = true,
12
+ sortable = true,
13
+ paginated = true,
14
+ pageSize = 10,
15
+ compact,
16
+ striped,
17
+ isDark,
18
+ simulateInitialLoad,
19
+ minInitialDelayMs,
20
+ maxInitialDelayMs,
21
+ ...rest
22
+ }) {
23
+ const ds = React.useMemo(() => getSemanticDataset(semanticId), [semanticId]);
24
+ const table = ds?.table;
25
+
26
+ const resolvedColumns = columnsOverride ?? table?.columns ?? [];
27
+ const resolvedData = dataOverride ?? table?.rows ?? [];
28
+ const resolvedTitle = title ?? table?.title ?? ds?.title ?? "Table";
29
+ const resolvedSubtitle = subtitle ?? table?.subtitle;
30
+
31
+ return (
32
+ <TableCard
33
+ title={resolvedTitle}
34
+ subtitle={resolvedSubtitle}
35
+ columns={resolvedColumns}
36
+ data={resolvedData}
37
+ searchable={searchable}
38
+ sortable={sortable}
39
+ paginated={paginated}
40
+ pageSize={pageSize}
41
+ simulateInitialLoad={simulateInitialLoad}
42
+ minInitialDelayMs={minInitialDelayMs}
43
+ maxInitialDelayMs={maxInitialDelayMs}
44
+ {...rest}
45
+ />
46
+ );
47
+ }
48
+
@@ -0,0 +1,22 @@
1
+ import React from "react";
2
+ import SemanticTableCard from "./SemanticTableCard";
3
+
4
+ export default function SemanticTableCardWithLoading({
5
+ simulateInitialLoad = true,
6
+ minInitialDelayMs = 350,
7
+ maxInitialDelayMs = 900,
8
+ loading: loadingProp,
9
+ ...props
10
+ }) {
11
+ // Leverage TableCard's simulateInitialLoad by default; also allow explicit loading override.
12
+ return (
13
+ <SemanticTableCard
14
+ {...props}
15
+ loading={loadingProp}
16
+ simulateInitialLoad={simulateInitialLoad}
17
+ minInitialDelayMs={minInitialDelayMs}
18
+ maxInitialDelayMs={maxInitialDelayMs}
19
+ />
20
+ );
21
+ }
22
+
@@ -0,0 +1,220 @@
1
+ import React from "react";
2
+ import BaseCard from "./BaseCard";
3
+ import UIText from "../ui/Text";
4
+
5
+ const STATUS_META = {
6
+ operational: {
7
+ label: "Operational",
8
+ dot: "bg-emerald-500",
9
+ chip: "bg-emerald-100 text-emerald-900 dark:bg-emerald-950/30 dark:text-emerald-200"
10
+ },
11
+ degraded: {
12
+ label: "Degraded",
13
+ dot: "bg-amber-500",
14
+ chip: "bg-amber-100 text-amber-900 dark:bg-amber-950/30 dark:text-amber-200"
15
+ },
16
+ outage: {
17
+ label: "Outage",
18
+ dot: "bg-rose-500",
19
+ chip: "bg-rose-100 text-rose-900 dark:bg-rose-950/30 dark:text-rose-200"
20
+ },
21
+ maintenance: {
22
+ label: "Maintenance",
23
+ dot: "bg-slate-500",
24
+ chip: "bg-slate-100 text-slate-900 dark:bg-slate-800 dark:text-slate-100"
25
+ }
26
+ };
27
+
28
+ function normalizeStatus(status) {
29
+ const s = String(status ?? "operational").toLowerCase();
30
+ if (s === "ok" || s === "healthy" || s === "up") return "operational";
31
+ if (s === "warn" || s === "warning" || s === "partial") return "degraded";
32
+ if (s === "down" || s === "critical") return "outage";
33
+ return STATUS_META[s] ? s : "operational";
34
+ }
35
+
36
+ function formatTimestamp(ts) {
37
+ if (!ts) return "";
38
+ try {
39
+ const d = ts instanceof Date ? ts : new Date(ts);
40
+ if (Number.isNaN(d.getTime())) return String(ts);
41
+ return d.toLocaleString(undefined, { dateStyle: "medium", timeStyle: "short" });
42
+ } catch {
43
+ return String(ts);
44
+ }
45
+ }
46
+
47
+ function StatusChip({ status }) {
48
+ const key = normalizeStatus(status);
49
+ const meta = STATUS_META[key];
50
+ return (
51
+ <span className={["inline-flex items-center gap-2 rounded-full px-2.5 py-1 text-xs font-semibold", meta.chip].join(" ")}>
52
+ <span className={["h-2 w-2 rounded-full", meta.dot].join(" ")} aria-hidden="true" />
53
+ {meta.label}
54
+ </span>
55
+ );
56
+ }
57
+
58
+ function ItemRow({ item, showTimestamp }) {
59
+ const name = item?.title ?? item?.name ?? "Item";
60
+ const desc = item?.description;
61
+ const value = item?.value;
62
+ const unit = item?.unit;
63
+ const ts = item?.timestamp;
64
+ const status = item?.status;
65
+
66
+ return (
67
+ <div className="flex items-start justify-between gap-3 rounded-xl border border-slate-200 bg-white p-3 dark:border-slate-800 dark:bg-slate-900">
68
+ <div className="min-w-0">
69
+ <div className="flex items-center gap-2">
70
+ <div className="truncate text-sm font-semibold text-slate-900 dark:text-slate-50">{name}</div>
71
+ {status ? <StatusChip status={status} /> : null}
72
+ </div>
73
+ {desc ? (
74
+ <div className="mt-1 text-sm text-slate-600 dark:text-slate-300">{desc}</div>
75
+ ) : null}
76
+ {showTimestamp && ts ? (
77
+ <div className="mt-1 text-xs text-slate-500 dark:text-slate-400">{formatTimestamp(ts)}</div>
78
+ ) : null}
79
+ </div>
80
+ {value != null ? (
81
+ <div className="shrink-0 text-right">
82
+ <div className="text-sm font-semibold text-slate-900 dark:text-slate-50">
83
+ {String(value)}
84
+ {unit ? <span className="ml-1 text-xs font-medium text-slate-500 dark:text-slate-400">{unit}</span> : null}
85
+ </div>
86
+ </div>
87
+ ) : null}
88
+ </div>
89
+ );
90
+ }
91
+
92
+ function Timeline({ items, showTimestamp }) {
93
+ return (
94
+ <div className="relative mt-4">
95
+ <div className="absolute left-3 top-0 h-full w-px bg-slate-200 dark:bg-slate-800" aria-hidden="true" />
96
+ <div className="space-y-3">
97
+ {items.map((item, idx) => {
98
+ const key = item?.id ?? idx;
99
+ const status = item?.status;
100
+ const meta = STATUS_META[normalizeStatus(status)];
101
+ return (
102
+ <div key={key} className="relative pl-8">
103
+ <div className={["absolute left-[9px] top-3 h-2.5 w-2.5 rounded-full", meta.dot].join(" ")} aria-hidden="true" />
104
+ <ItemRow item={item} showTimestamp={showTimestamp} />
105
+ </div>
106
+ );
107
+ })}
108
+ </div>
109
+ </div>
110
+ );
111
+ }
112
+
113
+ export default function StatusCard({
114
+ title,
115
+ subtitle,
116
+ status = "operational",
117
+ items = [],
118
+ layout = "list",
119
+ showProgress = false,
120
+ showTimestamp = true,
121
+ actions,
122
+ loading = false,
123
+ error,
124
+ emptyMessage = "No status items.",
125
+ maxBodyHeight,
126
+ ...cardProps
127
+ }) {
128
+ const overall = normalizeStatus(status);
129
+ const header = (
130
+ <div className="flex items-start justify-between gap-3">
131
+ <div className="min-w-0">
132
+ {title ? (
133
+ <UIText as="div" size="sm" weight="medium">
134
+ {title}
135
+ </UIText>
136
+ ) : null}
137
+ {subtitle ? (
138
+ <UIText as="div" size="xs" muted className="mt-1">
139
+ {subtitle}
140
+ </UIText>
141
+ ) : null}
142
+ </div>
143
+ <div className="flex items-center gap-2">
144
+ <StatusChip status={overall} />
145
+ {actions ? <div className="ml-1">{actions}</div> : null}
146
+ </div>
147
+ </div>
148
+ );
149
+
150
+ if (error) {
151
+ return (
152
+ <BaseCard
153
+ variant="status"
154
+ header={header}
155
+ body={
156
+ <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">
157
+ {String(error)}
158
+ </div>
159
+ }
160
+ {...cardProps}
161
+ />
162
+ );
163
+ }
164
+
165
+ const total = items.length;
166
+ const okCount = items.filter((it) => normalizeStatus(it?.status) === "operational").length;
167
+ const percentOk = total > 0 ? Math.round((okCount / total) * 100) : 100;
168
+
169
+ const scrollStyle = maxBodyHeight ? { maxHeight: maxBodyHeight, overflowY: "auto" } : {};
170
+
171
+ const itemsContent = loading ? (
172
+ <div className="space-y-3">
173
+ {Array.from({ length: 3 }).map((_, i) => (
174
+ <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">
175
+ <div className="h-4 w-1/4 animate-pulse rounded bg-slate-200 dark:bg-slate-800" />
176
+ <div className="h-4 w-1/3 animate-pulse rounded bg-slate-200 dark:bg-slate-800" />
177
+ </div>
178
+ ))}
179
+ </div>
180
+ ) : items.length === 0 ? (
181
+ <div className="rounded-xl border border-dashed border-slate-300 bg-slate-50 p-6 text-center text-sm text-slate-600 dark:border-slate-700 dark:bg-slate-950/30 dark:text-slate-300">
182
+ {emptyMessage}
183
+ </div>
184
+ ) : layout === "grid" ? (
185
+ <div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
186
+ {items.map((it, idx) => (
187
+ <ItemRow key={it?.id ?? idx} item={it} showTimestamp={showTimestamp} />
188
+ ))}
189
+ </div>
190
+ ) : layout === "timeline" ? (
191
+ <Timeline items={items} showTimestamp={showTimestamp} />
192
+ ) : (
193
+ <div className="space-y-3">
194
+ {items.map((it, idx) => (
195
+ <ItemRow key={it?.id ?? idx} item={it} showTimestamp={showTimestamp} />
196
+ ))}
197
+ </div>
198
+ );
199
+
200
+ const body = (
201
+ <div className="mt-4">
202
+ {showProgress && total > 0 ? (
203
+ <div className="mb-4">
204
+ <div className="flex items-center justify-between text-xs text-slate-600 dark:text-slate-300">
205
+ <span>{okCount} / {total} operational</span>
206
+ <span>{percentOk}%</span>
207
+ </div>
208
+ <div className="mt-2 h-2 w-full overflow-hidden rounded-full bg-slate-200 dark:bg-slate-800">
209
+ <div className="h-full bg-emerald-500" style={{ width: `${percentOk}%` }} />
210
+ </div>
211
+ </div>
212
+ ) : null}
213
+ <div style={scrollStyle}>{itemsContent}</div>
214
+ </div>
215
+ );
216
+
217
+ return <BaseCard variant="status" header={header} body={body} {...cardProps} />;
218
+ }
219
+
220
+