@schandlergarcia/sf-web-components 1.9.37 → 1.9.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -1
- package/scripts/postinstall.mjs +116 -65
- package/src/components/library/cards/ActionList.jsx +38 -0
- package/src/components/library/cards/ActivityCard.jsx +56 -0
- package/src/components/library/cards/BaseCard.jsx +109 -0
- package/src/components/library/cards/CalloutCard.jsx +37 -0
- package/src/components/library/cards/ChartCard.jsx +105 -0
- package/src/components/library/cards/FeedPanel.jsx +39 -0
- package/src/components/library/cards/ListCard.jsx +193 -0
- package/src/components/library/cards/MetricCard.jsx +109 -0
- package/src/components/library/cards/MetricsStrip.jsx +78 -0
- package/src/components/library/cards/SectionCard.jsx +83 -0
- package/src/components/library/cards/SemanticMetricCard.jsx +52 -0
- package/src/components/library/cards/SemanticMetricCardWithLoading.jsx +23 -0
- package/src/components/library/cards/SemanticTableCard.jsx +48 -0
- package/src/components/library/cards/SemanticTableCardWithLoading.jsx +22 -0
- package/src/components/library/cards/StatusCard.jsx +220 -0
- package/src/components/library/cards/TableCard.jsx +337 -0
- package/src/components/library/cards/WidgetCard.jsx +90 -0
- package/src/components/library/charts/D3Chart.jsx +109 -0
- package/src/components/library/charts/D3ChartTemplates.jsx +126 -0
- package/src/components/library/charts/GeoMap.jsx +293 -0
- package/src/components/library/chat/ChatBar.jsx +256 -0
- package/src/components/library/chat/ChatInput.jsx +89 -0
- package/src/components/library/chat/ChatMessage.jsx +178 -0
- package/src/components/library/chat/ChatMessageList.jsx +73 -0
- package/src/components/library/chat/ChatPanel.jsx +97 -0
- package/src/components/library/chat/ChatSuggestions.jsx +28 -0
- package/src/components/library/chat/ChatToolCall.jsx +100 -0
- package/src/components/library/chat/ChatTypingIndicator.jsx +23 -0
- package/src/components/library/chat/ChatWelcome.jsx +43 -0
- package/src/components/library/chat/index.jsx +10 -0
- package/src/components/library/chat/useChatState.jsx +130 -0
- package/src/components/library/data/DataModeProvider.jsx +67 -0
- package/src/components/library/data/DataModeToggle.jsx +36 -0
- package/src/components/library/data/chartDataProvider.jsx +61 -0
- package/src/components/library/data/filterUtils.jsx +141 -0
- package/src/components/library/data/useDataSource.jsx +33 -0
- package/src/components/library/data/usePageFilters.jsx +99 -0
- package/src/components/library/filters/FilterBar.jsx +95 -0
- package/src/components/library/filters/SearchFilter.jsx +36 -0
- package/src/components/library/filters/SelectFilter.jsx +55 -0
- package/src/components/library/filters/ToggleFilter.jsx +52 -0
- package/src/components/library/filters/index.jsx +4 -0
- package/src/components/library/forms/FormField.jsx +291 -0
- package/src/components/library/forms/FormModal.jsx +201 -0
- package/src/components/library/forms/FormRenderer.jsx +46 -0
- package/src/components/library/forms/FormSection.jsx +69 -0
- package/src/components/library/forms/index.jsx +5 -0
- package/src/components/library/forms/useFormState.jsx +165 -0
- package/src/components/library/heroui/Accordion.jsx +26 -0
- package/src/components/library/heroui/Alert.jsx +8 -0
- package/src/components/library/heroui/Badge.jsx +8 -0
- package/src/components/library/heroui/Breadcrumbs.jsx +22 -0
- package/src/components/library/heroui/Button.jsx +58 -0
- package/src/components/library/heroui/Card.jsx +8 -0
- package/src/components/library/heroui/Collapsible.jsx +42 -0
- package/src/components/library/heroui/DatePicker.jsx +34 -0
- package/src/components/library/heroui/Dialog.jsx +37 -0
- package/src/components/library/heroui/Drawer.jsx +32 -0
- package/src/components/library/heroui/Dropdown.jsx +28 -0
- package/src/components/library/heroui/Field.jsx +51 -0
- package/src/components/library/heroui/Input.jsx +6 -0
- package/src/components/library/heroui/Kbd.jsx +8 -0
- package/src/components/library/heroui/Meter.jsx +8 -0
- package/src/components/library/heroui/Modal.jsx +32 -0
- package/src/components/library/heroui/Pagination.jsx +8 -0
- package/src/components/library/heroui/Popover.jsx +64 -0
- package/src/components/library/heroui/ProgressBar.jsx +8 -0
- package/src/components/library/heroui/ProgressCircle.jsx +8 -0
- package/src/components/library/heroui/ScrollShadow.jsx +8 -0
- package/src/components/library/heroui/Select.jsx +37 -0
- package/src/components/library/heroui/Separator.jsx +8 -0
- package/src/components/library/heroui/Skeleton.jsx +8 -0
- package/src/components/library/heroui/Tabs.jsx +26 -0
- package/src/components/library/heroui/Toast.jsx +25 -0
- package/src/components/library/heroui/Toggle.jsx +14 -0
- package/src/components/library/heroui/Tooltip.jsx +21 -0
- package/src/components/library/index.jsx +146 -0
- package/src/components/library/layout/PageContainer.jsx +11 -0
- package/src/components/library/skeletons/CardSkeleton.jsx +30 -0
- package/src/components/library/theme/AppThemeProvider.jsx +67 -0
- package/src/components/library/theme/tokens.jsx +72 -0
- package/src/components/library/ui/Alert.jsx +80 -0
- package/src/components/library/ui/Avatar.jsx +44 -0
- package/src/components/library/ui/BreadcrumbExtras.tsx +120 -0
- package/src/components/library/ui/Button.jsx +61 -0
- package/src/components/library/ui/Card.jsx +117 -0
- package/src/components/library/ui/Checkbox.jsx +17 -0
- package/src/components/library/ui/Chip.jsx +38 -0
- package/src/components/library/ui/Collapsible.tsx +31 -0
- package/src/components/library/ui/Container.jsx +56 -0
- package/src/components/library/ui/DatePicker.tsx +34 -0
- package/src/components/library/ui/Dialog.tsx +141 -0
- package/src/components/library/ui/EmptyState.jsx +46 -0
- package/src/components/library/ui/Field.tsx +82 -0
- package/src/components/library/ui/FieldGroup.jsx +17 -0
- package/src/components/library/ui/Input.jsx +21 -0
- package/src/components/library/ui/Label.jsx +22 -0
- package/src/components/library/ui/PaginationExtras.tsx +142 -0
- package/src/components/library/ui/Popover.tsx +39 -0
- package/src/components/library/ui/Select.tsx +113 -0
- package/src/components/library/ui/Spinner.d.ts +10 -0
- package/src/components/library/ui/Spinner.jsx +64 -0
- package/src/components/library/ui/Text.jsx +46 -0
- package/src/components/library/ui/Toggle.jsx +42 -0
- package/src/components/workspace/ComponentRegistry.jsx +297 -0
- package/src/lib/index.ts +1 -0
- package/src/lib/utils.ts +6 -0
|
@@ -0,0 +1,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
|
+
};
|