@schandlergarcia/sf-web-components 1.9.37 → 1.9.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/package.json +4 -1
  2. package/scripts/postinstall.mjs +116 -65
  3. package/src/components/library/cards/ActionList.jsx +38 -0
  4. package/src/components/library/cards/ActivityCard.jsx +56 -0
  5. package/src/components/library/cards/BaseCard.jsx +109 -0
  6. package/src/components/library/cards/CalloutCard.jsx +37 -0
  7. package/src/components/library/cards/ChartCard.jsx +105 -0
  8. package/src/components/library/cards/FeedPanel.jsx +39 -0
  9. package/src/components/library/cards/ListCard.jsx +193 -0
  10. package/src/components/library/cards/MetricCard.jsx +109 -0
  11. package/src/components/library/cards/MetricsStrip.jsx +78 -0
  12. package/src/components/library/cards/SectionCard.jsx +83 -0
  13. package/src/components/library/cards/SemanticMetricCard.jsx +52 -0
  14. package/src/components/library/cards/SemanticMetricCardWithLoading.jsx +23 -0
  15. package/src/components/library/cards/SemanticTableCard.jsx +48 -0
  16. package/src/components/library/cards/SemanticTableCardWithLoading.jsx +22 -0
  17. package/src/components/library/cards/StatusCard.jsx +220 -0
  18. package/src/components/library/cards/TableCard.jsx +337 -0
  19. package/src/components/library/cards/WidgetCard.jsx +90 -0
  20. package/src/components/library/charts/D3Chart.jsx +109 -0
  21. package/src/components/library/charts/D3ChartTemplates.jsx +126 -0
  22. package/src/components/library/charts/GeoMap.jsx +293 -0
  23. package/src/components/library/chat/ChatBar.jsx +256 -0
  24. package/src/components/library/chat/ChatInput.jsx +89 -0
  25. package/src/components/library/chat/ChatMessage.jsx +178 -0
  26. package/src/components/library/chat/ChatMessageList.jsx +73 -0
  27. package/src/components/library/chat/ChatPanel.jsx +97 -0
  28. package/src/components/library/chat/ChatSuggestions.jsx +28 -0
  29. package/src/components/library/chat/ChatToolCall.jsx +100 -0
  30. package/src/components/library/chat/ChatTypingIndicator.jsx +23 -0
  31. package/src/components/library/chat/ChatWelcome.jsx +43 -0
  32. package/src/components/library/chat/index.jsx +10 -0
  33. package/src/components/library/chat/useChatState.jsx +130 -0
  34. package/src/components/library/data/DataModeProvider.jsx +67 -0
  35. package/src/components/library/data/DataModeToggle.jsx +36 -0
  36. package/src/components/library/data/chartDataProvider.jsx +61 -0
  37. package/src/components/library/data/filterUtils.jsx +141 -0
  38. package/src/components/library/data/useDataSource.jsx +33 -0
  39. package/src/components/library/data/usePageFilters.jsx +99 -0
  40. package/src/components/library/filters/FilterBar.jsx +95 -0
  41. package/src/components/library/filters/SearchFilter.jsx +36 -0
  42. package/src/components/library/filters/SelectFilter.jsx +55 -0
  43. package/src/components/library/filters/ToggleFilter.jsx +52 -0
  44. package/src/components/library/filters/index.jsx +4 -0
  45. package/src/components/library/forms/FormField.jsx +291 -0
  46. package/src/components/library/forms/FormModal.jsx +201 -0
  47. package/src/components/library/forms/FormRenderer.jsx +46 -0
  48. package/src/components/library/forms/FormSection.jsx +69 -0
  49. package/src/components/library/forms/index.jsx +5 -0
  50. package/src/components/library/forms/useFormState.jsx +165 -0
  51. package/src/components/library/heroui/Accordion.jsx +26 -0
  52. package/src/components/library/heroui/Alert.jsx +8 -0
  53. package/src/components/library/heroui/Badge.jsx +8 -0
  54. package/src/components/library/heroui/Breadcrumbs.jsx +22 -0
  55. package/src/components/library/heroui/Button.jsx +58 -0
  56. package/src/components/library/heroui/Card.jsx +8 -0
  57. package/src/components/library/heroui/Collapsible.jsx +42 -0
  58. package/src/components/library/heroui/DatePicker.jsx +34 -0
  59. package/src/components/library/heroui/Dialog.jsx +37 -0
  60. package/src/components/library/heroui/Drawer.jsx +32 -0
  61. package/src/components/library/heroui/Dropdown.jsx +28 -0
  62. package/src/components/library/heroui/Field.jsx +51 -0
  63. package/src/components/library/heroui/Input.jsx +6 -0
  64. package/src/components/library/heroui/Kbd.jsx +8 -0
  65. package/src/components/library/heroui/Meter.jsx +8 -0
  66. package/src/components/library/heroui/Modal.jsx +32 -0
  67. package/src/components/library/heroui/Pagination.jsx +8 -0
  68. package/src/components/library/heroui/Popover.jsx +64 -0
  69. package/src/components/library/heroui/ProgressBar.jsx +8 -0
  70. package/src/components/library/heroui/ProgressCircle.jsx +8 -0
  71. package/src/components/library/heroui/ScrollShadow.jsx +8 -0
  72. package/src/components/library/heroui/Select.jsx +37 -0
  73. package/src/components/library/heroui/Separator.jsx +8 -0
  74. package/src/components/library/heroui/Skeleton.jsx +8 -0
  75. package/src/components/library/heroui/Tabs.jsx +26 -0
  76. package/src/components/library/heroui/Toast.jsx +25 -0
  77. package/src/components/library/heroui/Toggle.jsx +14 -0
  78. package/src/components/library/heroui/Tooltip.jsx +21 -0
  79. package/src/components/library/index.jsx +146 -0
  80. package/src/components/library/layout/PageContainer.jsx +11 -0
  81. package/src/components/library/skeletons/CardSkeleton.jsx +30 -0
  82. package/src/components/library/theme/AppThemeProvider.jsx +67 -0
  83. package/src/components/library/theme/tokens.jsx +72 -0
  84. package/src/components/library/ui/Alert.jsx +80 -0
  85. package/src/components/library/ui/Avatar.jsx +44 -0
  86. package/src/components/library/ui/BreadcrumbExtras.tsx +120 -0
  87. package/src/components/library/ui/Button.jsx +61 -0
  88. package/src/components/library/ui/Card.jsx +117 -0
  89. package/src/components/library/ui/Checkbox.jsx +17 -0
  90. package/src/components/library/ui/Chip.jsx +38 -0
  91. package/src/components/library/ui/Collapsible.tsx +31 -0
  92. package/src/components/library/ui/Container.jsx +56 -0
  93. package/src/components/library/ui/DatePicker.tsx +34 -0
  94. package/src/components/library/ui/Dialog.tsx +141 -0
  95. package/src/components/library/ui/EmptyState.jsx +46 -0
  96. package/src/components/library/ui/Field.tsx +82 -0
  97. package/src/components/library/ui/FieldGroup.jsx +17 -0
  98. package/src/components/library/ui/Input.jsx +21 -0
  99. package/src/components/library/ui/Label.jsx +22 -0
  100. package/src/components/library/ui/PaginationExtras.tsx +142 -0
  101. package/src/components/library/ui/Popover.tsx +39 -0
  102. package/src/components/library/ui/Select.tsx +113 -0
  103. package/src/components/library/ui/Spinner.d.ts +10 -0
  104. package/src/components/library/ui/Spinner.jsx +64 -0
  105. package/src/components/library/ui/Text.jsx +46 -0
  106. package/src/components/library/ui/Toggle.jsx +42 -0
  107. package/src/components/workspace/ComponentRegistry.jsx +297 -0
  108. package/src/lib/index.ts +1 -0
  109. package/src/lib/utils.ts +6 -0
@@ -0,0 +1,30 @@
1
+ import React from "react";
2
+
3
+ export default function CardSkeleton({ lines = 3, className = "" }) {
4
+ return (
5
+ <div
6
+ className={[
7
+ "rounded-2xl border border-slate-200 bg-white p-5 shadow-sm dark:border-slate-800 dark:bg-slate-900",
8
+ className
9
+ ]
10
+ .filter(Boolean)
11
+ .join(" ")}
12
+ aria-busy="true"
13
+ aria-label="Loading"
14
+ >
15
+ <div className="space-y-3">
16
+ {Array.from({ length: lines }).map((_, i) => (
17
+ <div
18
+ key={i}
19
+ className={[
20
+ "h-4 animate-pulse rounded bg-slate-200 dark:bg-slate-800",
21
+ i === 0 ? "w-1/3" : i === 1 ? "w-2/3" : "w-1/2"
22
+ ].join(" ")}
23
+ />
24
+ ))}
25
+ </div>
26
+ </div>
27
+ );
28
+ }
29
+
30
+
@@ -0,0 +1,67 @@
1
+ import React from "react";
2
+ import { tokens, getTokenCSSProperties } from "./tokens";
3
+
4
+ const ThemeModeContext = React.createContext({
5
+ mode: "light",
6
+ theme: tokens,
7
+ toggle: () => {}
8
+ });
9
+
10
+ const STORAGE_KEY = "app-color-mode";
11
+
12
+ function applyHtmlDarkClass(mode) {
13
+ if (typeof document === "undefined") return;
14
+ const root = document.documentElement;
15
+ if (mode === "dark") root.classList.add("dark");
16
+ else root.classList.remove("dark");
17
+ }
18
+
19
+ function applyTokenCSSProperties() {
20
+ if (typeof document === "undefined") return;
21
+ const root = document.documentElement;
22
+ const props = getTokenCSSProperties();
23
+ for (const [key, value] of Object.entries(props)) {
24
+ root.style.setProperty(key, value);
25
+ }
26
+ }
27
+
28
+ export function useThemeMode() {
29
+ return React.useContext(ThemeModeContext);
30
+ }
31
+
32
+ export default function AppThemeProvider({ initialMode = "light", children }) {
33
+ const [mode, setMode] = React.useState(initialMode);
34
+
35
+ React.useEffect(() => {
36
+ applyTokenCSSProperties();
37
+ }, []);
38
+
39
+ React.useEffect(() => {
40
+ try {
41
+ const stored = window.localStorage.getItem(STORAGE_KEY);
42
+ if (stored === "light" || stored === "dark") setMode(stored);
43
+ } catch {
44
+ // ignore
45
+ }
46
+ }, []);
47
+
48
+ React.useEffect(() => {
49
+ applyHtmlDarkClass(mode);
50
+ try {
51
+ window.localStorage.setItem(STORAGE_KEY, mode);
52
+ } catch {
53
+ // ignore
54
+ }
55
+ }, [mode]);
56
+
57
+ const value = React.useMemo(
58
+ () => ({
59
+ mode,
60
+ theme: tokens,
61
+ toggle: () => setMode((m) => (m === "dark" ? "light" : "dark"))
62
+ }),
63
+ [mode]
64
+ );
65
+
66
+ return <ThemeModeContext.Provider value={value}>{children}</ThemeModeContext.Provider>;
67
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Design tokens — single source of truth for branding.
3
+ *
4
+ * To rebrand a command center:
5
+ * 1. Swap the `brand` palette (use any Tailwind-style color scale)
6
+ * 2. Swap the `accent` palette
7
+ * 3. Change `fonts.sans` in _app.js (next/font import)
8
+ *
9
+ * AppThemeProvider injects these as CSS custom properties on :root,
10
+ * and tailwind.config.js references them via var(--color-brand-*).
11
+ */
12
+
13
+ export const tokens = {
14
+ colors: {
15
+ brand: {
16
+ 50: "#EEF2FF",
17
+ 100: "#E0E7FF",
18
+ 200: "#C7D2FE",
19
+ 300: "#A5B4FC",
20
+ 400: "#818CF8",
21
+ 500: "#6366F1",
22
+ 600: "#4F46E5",
23
+ 700: "#4338CA",
24
+ 800: "#3730A3",
25
+ 900: "#312E81",
26
+ 950: "#1E1B4E",
27
+ },
28
+ accent: {
29
+ 50: "#ECFEFF",
30
+ 100: "#CFFAFE",
31
+ 200: "#A5F3FC",
32
+ 300: "#67E8F9",
33
+ 400: "#22D3EE",
34
+ 500: "#06B6D4",
35
+ 600: "#0891B2",
36
+ 700: "#0E7490",
37
+ 800: "#155E75",
38
+ 900: "#164E63",
39
+ 950: "#083344",
40
+ },
41
+ },
42
+
43
+ fonts: {
44
+ sans: "Inter",
45
+ mono: "JetBrains Mono",
46
+ },
47
+
48
+ radius: {
49
+ sm: "0.5rem",
50
+ md: "0.75rem",
51
+ lg: "1rem",
52
+ },
53
+
54
+ spacing: {
55
+ pageX: "1.25rem",
56
+ pageY: "1.25rem",
57
+ },
58
+ };
59
+
60
+ /**
61
+ * Generates CSS custom property assignments from the token palettes.
62
+ * Used by AppThemeProvider to inject on :root at runtime.
63
+ */
64
+ export function getTokenCSSProperties() {
65
+ const props = {};
66
+ for (const [palette, shades] of Object.entries(tokens.colors)) {
67
+ for (const [shade, value] of Object.entries(shades)) {
68
+ props[`--color-${palette}-${shade}`] = value;
69
+ }
70
+ }
71
+ return props;
72
+ }
@@ -0,0 +1,80 @@
1
+ import React from "react";
2
+
3
+ const VARIANT_CLASSES = {
4
+ default: "bg-slate-50 border-slate-200 text-slate-900 dark:bg-slate-900 dark:border-slate-800 dark:text-slate-50",
5
+ info: "bg-blue-50 border-blue-200 text-blue-900 dark:bg-blue-950 dark:border-blue-800 dark:text-blue-50",
6
+ success: "bg-green-50 border-green-200 text-green-900 dark:bg-green-950 dark:border-green-800 dark:text-green-50",
7
+ warning: "bg-yellow-50 border-yellow-200 text-yellow-900 dark:bg-yellow-950 dark:border-yellow-800 dark:text-yellow-50",
8
+ error: "bg-red-50 border-red-200 text-red-900 dark:bg-red-950 dark:border-red-800 dark:text-red-50",
9
+ destructive: "bg-red-50 border-red-200 text-red-900 dark:bg-red-950 dark:border-red-800 dark:text-red-50"
10
+ };
11
+
12
+ export default function Alert({ variant = "default", className = "", children, ...rest }) {
13
+ const variantClasses = VARIANT_CLASSES[variant] || VARIANT_CLASSES.default;
14
+
15
+ return (
16
+ <div
17
+ role="alert"
18
+ className={[
19
+ "rounded-lg border p-4",
20
+ variantClasses,
21
+ className
22
+ ]
23
+ .filter(Boolean)
24
+ .join(" ")}
25
+ {...rest}
26
+ >
27
+ {children}
28
+ </div>
29
+ );
30
+ }
31
+
32
+ export function AlertTitle({ className = "", children, ...rest }) {
33
+ return (
34
+ <h5
35
+ className={[
36
+ "mb-1 font-medium leading-none tracking-tight",
37
+ className
38
+ ]
39
+ .filter(Boolean)
40
+ .join(" ")}
41
+ {...rest}
42
+ >
43
+ {children}
44
+ </h5>
45
+ );
46
+ }
47
+
48
+ export function AlertDescription({ className = "", children, ...rest }) {
49
+ return (
50
+ <div
51
+ className={[
52
+ "text-sm opacity-90",
53
+ className
54
+ ]
55
+ .filter(Boolean)
56
+ .join(" ")}
57
+ {...rest}
58
+ >
59
+ {children}
60
+ </div>
61
+ );
62
+ }
63
+
64
+ export function AlertAction({ className = "", children, ...rest }) {
65
+ return (
66
+ <div
67
+ className={[
68
+ "mt-3",
69
+ className
70
+ ]
71
+ .filter(Boolean)
72
+ .join(" ")}
73
+ {...rest}
74
+ >
75
+ {children}
76
+ </div>
77
+ );
78
+ }
79
+
80
+ export { Alert };
@@ -0,0 +1,44 @@
1
+ import React from "react";
2
+
3
+ const SIZE_MAP = {
4
+ xs: "h-6 w-6 text-[9px]",
5
+ sm: "h-8 w-8 text-[10px]",
6
+ md: "h-9 w-9 text-xs",
7
+ lg: "h-11 w-11 text-sm",
8
+ };
9
+
10
+ const TONE_MAP = {
11
+ slate: "bg-slate-800 text-white",
12
+ brand: "bg-brand-500 text-white",
13
+ neutral: "bg-slate-100 text-slate-700 dark:bg-slate-800 dark:text-slate-200",
14
+ };
15
+
16
+ export default function Avatar({ src, name, initials, icon, size = "sm", tone = "slate", className = "", ...rest }) {
17
+ const sizeClass = SIZE_MAP[size] ?? SIZE_MAP.sm;
18
+
19
+ if (src) {
20
+ return (
21
+ <img
22
+ src={src}
23
+ alt={name ?? ""}
24
+ className={`${sizeClass} shrink-0 rounded-full border border-slate-200 object-cover dark:border-slate-800 ${className}`}
25
+ {...rest}
26
+ />
27
+ );
28
+ }
29
+
30
+ if (React.isValidElement(icon)) {
31
+ return (
32
+ <div className={`${sizeClass} ${TONE_MAP[tone] ?? TONE_MAP.slate} flex shrink-0 items-center justify-center rounded-full ${className}`} {...rest}>
33
+ {icon}
34
+ </div>
35
+ );
36
+ }
37
+
38
+ const label = initials ?? (name ? name.split(" ").map(w => w[0]).join("").slice(0, 2).toUpperCase() : "?");
39
+ return (
40
+ <div className={`${sizeClass} ${TONE_MAP[tone] ?? TONE_MAP.slate} flex shrink-0 items-center justify-center rounded-full font-bold ${className}`} {...rest}>
41
+ {label}
42
+ </div>
43
+ );
44
+ }
@@ -0,0 +1,120 @@
1
+ import * as React from "react";
2
+ import { Link } from "react-router-dom";
3
+
4
+ // Shadcn-style Breadcrumb subcomponents to work with HeroUI Breadcrumbs
5
+
6
+ function Breadcrumb({ className, children, ...props }: React.ComponentProps<"nav">) {
7
+ return (
8
+ <nav aria-label="breadcrumb" className={className} {...props}>
9
+ {children}
10
+ </nav>
11
+ );
12
+ }
13
+
14
+ function BreadcrumbList({ className, children, ...props }: React.ComponentProps<"ol">) {
15
+ return (
16
+ <ol
17
+ className={[
18
+ "flex flex-wrap items-center gap-1.5 break-words text-sm text-slate-500 dark:text-slate-400",
19
+ className
20
+ ].filter(Boolean).join(" ")}
21
+ {...props}
22
+ >
23
+ {children}
24
+ </ol>
25
+ );
26
+ }
27
+
28
+ function BreadcrumbItem({ className, children, ...props }: React.ComponentProps<"li">) {
29
+ return (
30
+ <li
31
+ className={[
32
+ "inline-flex items-center gap-1.5",
33
+ className
34
+ ].filter(Boolean).join(" ")}
35
+ {...props}
36
+ >
37
+ {children}
38
+ </li>
39
+ );
40
+ }
41
+
42
+ function BreadcrumbLink({ className, href, children, ...props }: React.ComponentProps<typeof Link>) {
43
+ return (
44
+ <Link
45
+ to={href || "#"}
46
+ className={[
47
+ "transition-colors hover:text-slate-900 dark:hover:text-slate-50",
48
+ className
49
+ ].filter(Boolean).join(" ")}
50
+ {...props}
51
+ >
52
+ {children}
53
+ </Link>
54
+ );
55
+ }
56
+
57
+ function BreadcrumbPage({ className, children, ...props }: React.ComponentProps<"span">) {
58
+ return (
59
+ <span
60
+ role="link"
61
+ aria-disabled="true"
62
+ aria-current="page"
63
+ className={[
64
+ "font-normal text-slate-900 dark:text-slate-50",
65
+ className
66
+ ].filter(Boolean).join(" ")}
67
+ {...props}
68
+ >
69
+ {children}
70
+ </span>
71
+ );
72
+ }
73
+
74
+ function BreadcrumbSeparator({ children, className, ...props }: React.ComponentProps<"li">) {
75
+ return (
76
+ <li
77
+ role="presentation"
78
+ aria-hidden="true"
79
+ className={[
80
+ "select-none",
81
+ className
82
+ ].filter(Boolean).join(" ")}
83
+ {...props}
84
+ >
85
+ {children ?? "/"}
86
+ </li>
87
+ );
88
+ }
89
+
90
+ function BreadcrumbEllipsis({ className, ...props }: React.ComponentProps<"span">) {
91
+ return (
92
+ <span
93
+ role="presentation"
94
+ aria-hidden="true"
95
+ className={[
96
+ "flex h-9 w-9 items-center justify-center",
97
+ className
98
+ ].filter(Boolean).join(" ")}
99
+ {...props}
100
+ >
101
+ <svg width="15" height="15" viewBox="0 0 15 15" fill="none">
102
+ <path
103
+ d="M3.625 7.5C3.625 8.12132 3.12132 8.625 2.5 8.625C1.87868 8.625 1.375 8.12132 1.375 7.5C1.375 6.87868 1.87868 6.375 2.5 6.375C3.12132 6.375 3.625 6.87868 3.625 7.5ZM8.625 7.5C8.625 8.12132 8.12132 8.625 7.5 8.625C6.87868 8.625 6.375 8.12132 6.375 7.5C6.375 6.87868 6.87868 6.375 7.5 6.375C8.12132 6.375 8.625 6.87868 8.625 7.5ZM12.5 8.625C13.1213 8.625 13.625 8.12132 13.625 7.5C13.625 6.87868 13.1213 6.375 12.5 6.375C11.8787 6.375 11.375 6.87868 11.375 7.5C11.375 8.12132 11.8787 8.625 12.5 8.625Z"
104
+ fill="currentColor"
105
+ />
106
+ </svg>
107
+ <span className="sr-only">More</span>
108
+ </span>
109
+ );
110
+ }
111
+
112
+ export {
113
+ Breadcrumb,
114
+ BreadcrumbList,
115
+ BreadcrumbItem,
116
+ BreadcrumbLink,
117
+ BreadcrumbPage,
118
+ BreadcrumbSeparator,
119
+ BreadcrumbEllipsis,
120
+ };
@@ -0,0 +1,61 @@
1
+ import React from "react";
2
+
3
+ const VARIANT_CLASSES = {
4
+ primary:
5
+ "bg-brand-600 text-white hover:bg-brand-500 dark:bg-brand-500 dark:hover:bg-brand-400 border-transparent",
6
+ secondary:
7
+ "bg-slate-900 text-white hover:bg-slate-800 dark:bg-slate-100 dark:text-slate-900 dark:hover:bg-slate-200 border-transparent",
8
+ destructive:
9
+ "bg-red-600 text-white hover:bg-red-500 dark:bg-red-500 dark:hover:bg-red-400 border-transparent",
10
+ outline:
11
+ "bg-transparent text-slate-900 hover:bg-slate-50 dark:text-slate-50 dark:hover:bg-slate-900 border-slate-200 dark:border-slate-800",
12
+ ghost:
13
+ "bg-transparent text-slate-900 hover:bg-slate-100 dark:text-slate-50 dark:hover:bg-slate-900 border-transparent"
14
+ };
15
+
16
+ const SIZE_CLASSES = {
17
+ sm: "h-8 px-3 text-sm",
18
+ md: "h-10 px-4 text-sm",
19
+ lg: "h-12 px-5 text-base",
20
+ icon: "h-10 w-10 p-0"
21
+ };
22
+
23
+ export default function UIButton({
24
+ variant = "primary",
25
+ size = "md",
26
+ fullWidth = false,
27
+ disabled = false,
28
+ onClick = () => {},
29
+ children,
30
+ style = undefined,
31
+ className = "",
32
+ ...rest
33
+ }) {
34
+ const variantClass = VARIANT_CLASSES[variant] ?? VARIANT_CLASSES.primary;
35
+ const sizeClass = SIZE_CLASSES[size] ?? SIZE_CLASSES.md;
36
+
37
+ return (
38
+ <button
39
+ type="button"
40
+ onClick={onClick}
41
+ disabled={disabled}
42
+ style={style}
43
+ className={[
44
+ "inline-flex items-center justify-center gap-2 rounded-lg border font-medium shadow-sm transition",
45
+ "focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-500 focus-visible:ring-offset-2 dark:focus-visible:ring-offset-slate-950",
46
+ "disabled:cursor-not-allowed disabled:opacity-60",
47
+ variantClass,
48
+ sizeClass,
49
+ fullWidth ? "w-full" : "",
50
+ className
51
+ ]
52
+ .filter(Boolean)
53
+ .join(" ")}
54
+ {...rest}
55
+ >
56
+ {children}
57
+ </button>
58
+ );
59
+ }
60
+
61
+
@@ -0,0 +1,117 @@
1
+ import React from "react";
2
+
3
+ export default function UICard({ children, padding = "p-5", style, className = "", ...rest }) {
4
+ return (
5
+ <div
6
+ style={style}
7
+ className={[
8
+ "rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900",
9
+ padding,
10
+ className
11
+ ]
12
+ .filter(Boolean)
13
+ .join(" ")}
14
+ {...rest}
15
+ >
16
+ {children}
17
+ </div>
18
+ );
19
+ }
20
+
21
+ // shadcn-compatible Card components
22
+ export function Card({ className = "", children, ...rest }) {
23
+ return (
24
+ <div
25
+ className={[
26
+ "rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900",
27
+ className
28
+ ]
29
+ .filter(Boolean)
30
+ .join(" ")}
31
+ {...rest}
32
+ >
33
+ {children}
34
+ </div>
35
+ );
36
+ }
37
+
38
+ export function CardHeader({ className = "", children, ...rest }) {
39
+ return (
40
+ <div
41
+ className={[
42
+ "flex flex-col space-y-1.5 p-6",
43
+ className
44
+ ]
45
+ .filter(Boolean)
46
+ .join(" ")}
47
+ {...rest}
48
+ >
49
+ {children}
50
+ </div>
51
+ );
52
+ }
53
+
54
+ export function CardTitle({ className = "", children, ...rest }) {
55
+ return (
56
+ <h3
57
+ className={[
58
+ "text-2xl font-semibold leading-none tracking-tight",
59
+ className
60
+ ]
61
+ .filter(Boolean)
62
+ .join(" ")}
63
+ {...rest}
64
+ >
65
+ {children}
66
+ </h3>
67
+ );
68
+ }
69
+
70
+ export function CardDescription({ className = "", children, ...rest }) {
71
+ return (
72
+ <p
73
+ className={[
74
+ "text-sm text-slate-500 dark:text-slate-400",
75
+ className
76
+ ]
77
+ .filter(Boolean)
78
+ .join(" ")}
79
+ {...rest}
80
+ >
81
+ {children}
82
+ </p>
83
+ );
84
+ }
85
+
86
+ export function CardContent({ className = "", children, ...rest }) {
87
+ return (
88
+ <div
89
+ className={[
90
+ "p-6 pt-0",
91
+ className
92
+ ]
93
+ .filter(Boolean)
94
+ .join(" ")}
95
+ {...rest}
96
+ >
97
+ {children}
98
+ </div>
99
+ );
100
+ }
101
+
102
+ export function CardFooter({ className = "", children, ...rest }) {
103
+ return (
104
+ <div
105
+ className={[
106
+ "flex items-center p-6 pt-0",
107
+ className
108
+ ]
109
+ .filter(Boolean)
110
+ .join(" ")}
111
+ {...rest}
112
+ >
113
+ {children}
114
+ </div>
115
+ );
116
+ }
117
+
@@ -0,0 +1,17 @@
1
+ import React from "react";
2
+
3
+ export default function Checkbox({ className = "", ...rest }) {
4
+ return (
5
+ <input
6
+ type="checkbox"
7
+ className={[
8
+ "h-4 w-4 rounded border-slate-300 text-brand-600 focus:ring-brand-500",
9
+ "dark:border-slate-600 dark:bg-slate-800 dark:focus:ring-brand-400",
10
+ className
11
+ ]
12
+ .filter(Boolean)
13
+ .join(" ")}
14
+ {...rest}
15
+ />
16
+ );
17
+ }
@@ -0,0 +1,38 @@
1
+ import React from "react";
2
+
3
+ const TONE_STYLES = {
4
+ neutral:
5
+ "border-slate-200/80 bg-white/60 text-slate-700 ring-black/5 hover:bg-white/80 dark:border-slate-800/80 dark:bg-slate-950/30 dark:text-slate-200 dark:ring-white/10 dark:hover:bg-slate-900/50",
6
+ primary:
7
+ "border-brand-200/80 bg-brand-50/70 text-brand-800 ring-brand-900/5 hover:bg-brand-50 dark:border-brand-900/40 dark:bg-brand-950/25 dark:text-brand-200 dark:ring-brand-300/10 dark:hover:bg-brand-950/35",
8
+ success:
9
+ "border-emerald-200/80 bg-emerald-50/70 text-emerald-800 ring-emerald-900/5 hover:bg-emerald-50 dark:border-emerald-900/40 dark:bg-emerald-950/20 dark:text-emerald-200 dark:ring-emerald-300/10 dark:hover:bg-emerald-950/30",
10
+ warning:
11
+ "border-amber-200/80 bg-amber-50/70 text-amber-900 ring-amber-900/5 hover:bg-amber-50 dark:border-amber-900/40 dark:bg-amber-950/20 dark:text-amber-200 dark:ring-amber-300/10 dark:hover:bg-amber-950/30",
12
+ danger:
13
+ "border-rose-200/80 bg-rose-50/70 text-rose-900 ring-rose-900/5 hover:bg-rose-50 dark:border-rose-900/40 dark:bg-rose-950/20 dark:text-rose-200 dark:ring-rose-300/10 dark:hover:bg-rose-950/30"
14
+ };
15
+
16
+ const SIZE_STYLES = {
17
+ xs: "px-2 py-0.5 text-[11px]",
18
+ sm: "px-2.5 py-1 text-xs"
19
+ };
20
+
21
+ export default function UIChip({ tone = "neutral", size = "xs", className = "", children, ...rest }) {
22
+ return (
23
+ <span
24
+ className={[
25
+ "inline-flex items-center gap-1 rounded-full border font-semibold shadow-sm ring-1 transition",
26
+ SIZE_STYLES[size] ?? SIZE_STYLES.xs,
27
+ TONE_STYLES[tone] ?? TONE_STYLES.neutral,
28
+ className
29
+ ]
30
+ .filter(Boolean)
31
+ .join(" ")}
32
+ {...rest}
33
+ >
34
+ {children}
35
+ </span>
36
+ );
37
+ }
38
+
@@ -0,0 +1,31 @@
1
+ import * as React from "react";
2
+ import { Collapsible as CollapsiblePrimitive } from "radix-ui";
3
+
4
+ function Collapsible({ ...props }: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {
5
+ return <CollapsiblePrimitive.Root {...props} />;
6
+ }
7
+
8
+ function CollapsibleTrigger({ ...props }: React.ComponentProps<typeof CollapsiblePrimitive.Trigger>) {
9
+ return <CollapsiblePrimitive.Trigger {...props} />;
10
+ }
11
+
12
+ function CollapsibleContent({
13
+ className,
14
+ ...props
15
+ }: React.ComponentProps<typeof CollapsiblePrimitive.Content>) {
16
+ return (
17
+ <CollapsiblePrimitive.Content
18
+ className={[
19
+ "overflow-hidden data-[state=closed]:animate-out data-[state=open]:animate-in",
20
+ className
21
+ ].filter(Boolean).join(" ")}
22
+ {...props}
23
+ />
24
+ );
25
+ }
26
+
27
+ export {
28
+ Collapsible,
29
+ CollapsibleTrigger,
30
+ CollapsibleContent,
31
+ };