@kala-ui/react-app 0.1.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/LICENSE +21 -0
- package/README.md +36 -0
- package/dist/components/app-shell/app-shell.d.ts +54 -0
- package/dist/components/app-shell/app-shell.js +91 -0
- package/dist/components/app-shell/index.d.ts +1 -0
- package/dist/components/app-shell/index.js +2 -0
- package/dist/components/charts/area-chart.d.ts +6 -0
- package/dist/components/charts/area-chart.js +172 -0
- package/dist/components/charts/bar-chart.d.ts +6 -0
- package/dist/components/charts/bar-chart.js +153 -0
- package/dist/components/charts/chart-skeleton.d.ts +36 -0
- package/dist/components/charts/chart-skeleton.js +13 -0
- package/dist/components/charts/chart.d.ts +25 -0
- package/dist/components/charts/chart.js +48 -0
- package/dist/components/charts/chart.types.d.ts +196 -0
- package/dist/components/charts/chart.types.js +6 -0
- package/dist/components/charts/donut-chart.d.ts +7 -0
- package/dist/components/charts/donut-chart.js +146 -0
- package/dist/components/charts/index.d.ts +14 -0
- package/dist/components/charts/index.js +15 -0
- package/dist/components/charts/line-chart.d.ts +6 -0
- package/dist/components/charts/line-chart.js +158 -0
- package/dist/components/charts/radial-bar-chart.d.ts +6 -0
- package/dist/components/charts/radial-bar-chart.js +115 -0
- package/dist/components/charts/theme-utils.d.ts +77 -0
- package/dist/components/charts/theme-utils.js +243 -0
- package/dist/components/charts/use-theme-aware-chart.d.ts +12 -0
- package/dist/components/charts/use-theme-aware-chart.js +68 -0
- package/dist/components/charts/utils.d.ts +56 -0
- package/dist/components/charts/utils.js +135 -0
- package/dist/components/data-table/column-filters.d.ts +14 -0
- package/dist/components/data-table/column-filters.js +51 -0
- package/dist/components/data-table/column-header-filter.d.ts +14 -0
- package/dist/components/data-table/column-header-filter.js +75 -0
- package/dist/components/data-table/data-table-body-rows.d.ts +23 -0
- package/dist/components/data-table/data-table-body-rows.js +43 -0
- package/dist/components/data-table/data-table-filter-chips.d.ts +14 -0
- package/dist/components/data-table/data-table-filter-chips.js +42 -0
- package/dist/components/data-table/data-table-header-row.d.ts +21 -0
- package/dist/components/data-table/data-table-header-row.js +35 -0
- package/dist/components/data-table/data-table-skeleton.d.ts +49 -0
- package/dist/components/data-table/data-table-skeleton.js +32 -0
- package/dist/components/data-table/data-table-toolbar.d.ts +18 -0
- package/dist/components/data-table/data-table-toolbar.js +25 -0
- package/dist/components/data-table/data-table.d.ts +23 -0
- package/dist/components/data-table/data-table.js +250 -0
- package/dist/components/data-table/data-table.types.d.ts +305 -0
- package/dist/components/data-table/data-table.types.js +6 -0
- package/dist/components/data-table/index.d.ts +6 -0
- package/dist/components/data-table/index.js +6 -0
- package/dist/components/data-table/pagination-nav.d.ts +15 -0
- package/dist/components/data-table/pagination-nav.js +23 -0
- package/dist/components/data-table/useTableState.d.ts +29 -0
- package/dist/components/data-table/useTableState.js +312 -0
- package/dist/components/dnd/dnd.d.ts +118 -0
- package/dist/components/dnd/dnd.js +128 -0
- package/dist/components/dnd/index.d.ts +1 -0
- package/dist/components/dnd/index.js +2 -0
- package/dist/components/footer/footer.d.ts +37 -0
- package/dist/components/footer/footer.js +8 -0
- package/dist/components/footer/index.d.ts +2 -0
- package/dist/components/footer/index.js +2 -0
- package/dist/components/header/header-skeleton.d.ts +54 -0
- package/dist/components/header/header-skeleton.js +23 -0
- package/dist/components/header/header.d.ts +40 -0
- package/dist/components/header/header.js +77 -0
- package/dist/components/header/header.types.d.ts +39 -0
- package/dist/components/header/header.types.js +2 -0
- package/dist/components/header/index.d.ts +4 -0
- package/dist/components/header/index.js +3 -0
- package/dist/components/metric-card/index.d.ts +2 -0
- package/dist/components/metric-card/index.js +3 -0
- package/dist/components/metric-card/metric-card-skeleton.d.ts +32 -0
- package/dist/components/metric-card/metric-card-skeleton.js +29 -0
- package/dist/components/metric-card/metric-card.d.ts +16 -0
- package/dist/components/metric-card/metric-card.js +55 -0
- package/dist/components/metric-card/metric-card.types.d.ts +14 -0
- package/dist/components/metric-card/metric-card.types.js +2 -0
- package/dist/components/nav-link/index.d.ts +1 -0
- package/dist/components/nav-link/index.js +2 -0
- package/dist/components/nav-link/nav-link.d.ts +26 -0
- package/dist/components/nav-link/nav-link.js +23 -0
- package/dist/components/navigation/index.d.ts +4 -0
- package/dist/components/navigation/index.js +3 -0
- package/dist/components/navigation/navigation-skeleton.d.ts +54 -0
- package/dist/components/navigation/navigation-skeleton.js +23 -0
- package/dist/components/navigation/navigation.d.ts +26 -0
- package/dist/components/navigation/navigation.js +44 -0
- package/dist/components/session-card/index.d.ts +2 -0
- package/dist/components/session-card/index.js +3 -0
- package/dist/components/session-card/session-card-skeleton.d.ts +31 -0
- package/dist/components/session-card/session-card-skeleton.js +28 -0
- package/dist/components/session-card/session-card.d.ts +27 -0
- package/dist/components/session-card/session-card.js +67 -0
- package/dist/components/sidebar/index.d.ts +3 -0
- package/dist/components/sidebar/index.js +3 -0
- package/dist/components/sidebar/sidebar-skeleton.d.ts +54 -0
- package/dist/components/sidebar/sidebar-skeleton.js +23 -0
- package/dist/components/sidebar/sidebar.d.ts +46 -0
- package/dist/components/sidebar/sidebar.js +69 -0
- package/dist/components/social-login-button/index.d.ts +1 -0
- package/dist/components/social-login-button/index.js +2 -0
- package/dist/components/social-login-button/social-login-button.d.ts +20 -0
- package/dist/components/social-login-button/social-login-button.js +57 -0
- package/dist/components/social-login-buttons/index.d.ts +1 -0
- package/dist/components/social-login-buttons/index.js +2 -0
- package/dist/components/social-login-buttons/social-login-buttons.d.ts +34 -0
- package/dist/components/social-login-buttons/social-login-buttons.js +18 -0
- package/dist/components/sparkline-chart/index.d.ts +2 -0
- package/dist/components/sparkline-chart/index.js +2 -0
- package/dist/components/sparkline-chart/sparkline-chart.d.ts +51 -0
- package/dist/components/sparkline-chart/sparkline-chart.js +134 -0
- package/dist/components/user-menu-dropdown/index.d.ts +1 -0
- package/dist/components/user-menu-dropdown/index.js +2 -0
- package/dist/components/user-menu-dropdown/user-menu-dropdown.d.ts +31 -0
- package/dist/components/user-menu-dropdown/user-menu-dropdown.js +27 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +19 -0
- package/dist/lib/active-path.d.ts +5 -0
- package/dist/lib/active-path.js +12 -0
- package/dist/styles/app.css +2125 -0
- package/package.json +164 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { cn } from "@kala-ui/react/lib/utils";
|
|
4
|
+
import { ChevronDown } from "lucide-react";
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
import { isActivePath } from "../../lib/active-path.js";
|
|
7
|
+
export function Navigation({ ref, className, links, orientation = "horizontal", mobileLayout = "dropdown", pathname = "", ...props }) {
|
|
8
|
+
const [isMobileOpen, setIsMobileOpen] = React.useState(false);
|
|
9
|
+
const isActive = (href) => isActivePath(pathname, href);
|
|
10
|
+
// Close mobile menu when clicking outside
|
|
11
|
+
React.useEffect(() => {
|
|
12
|
+
if (!isMobileOpen)
|
|
13
|
+
return;
|
|
14
|
+
const handleClickOutside = (event) => {
|
|
15
|
+
const target = event.target;
|
|
16
|
+
if (!target.closest("[data-mobile-nav]")) {
|
|
17
|
+
setIsMobileOpen(false);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
document.addEventListener("click", handleClickOutside);
|
|
21
|
+
return () => document.removeEventListener("click", handleClickOutside);
|
|
22
|
+
}, [isMobileOpen]);
|
|
23
|
+
// Vertical desktop navigation (explicit opt-in via orientation)
|
|
24
|
+
if (orientation === "vertical") {
|
|
25
|
+
return (_jsx("nav", { "data-comp": "navigation", className: cn(orientation === "vertical"
|
|
26
|
+
? "hidden md:flex md:flex-col gap-2"
|
|
27
|
+
: "flex md:hidden flex-col gap-2", className), "aria-label": "Navigation", ...props, ref: ref, children: links.map((link) => (_jsx("a", { href: link.href, className: cn("text-sm font-medium px-3 py-2 rounded-md transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring", isActive(link.href)
|
|
28
|
+
? "bg-accent text-accent-foreground"
|
|
29
|
+
: "text-foreground hover:bg-accent hover:text-accent-foreground"), "aria-current": isActive(link.href) ? "page" : undefined, children: link.label }, link.href))) }));
|
|
30
|
+
}
|
|
31
|
+
// Horizontal on desktop + stacked vertical list on mobile
|
|
32
|
+
if (mobileLayout === "vertical") {
|
|
33
|
+
return (_jsxs(_Fragment, { children: [_jsx("nav", { "data-comp": "navigation", className: cn("hidden md:flex flex-row items-center gap-6", className), "aria-label": "Main navigation", ...props, ref: ref, children: links.map((link) => (_jsx("a", { href: link.href, className: cn("text-sm font-medium transition-colors hover:text-primary outline-none focus-visible:underline", isActive(link.href) ? "text-primary" : "text-foreground"), "aria-current": isActive(link.href) ? "page" : undefined, children: link.label }, link.href))) }), _jsx("nav", { "data-comp": "navigation", className: cn("flex md:hidden flex-col gap-2", className), "aria-label": "Mobile navigation", children: links.map((link) => (_jsx("a", { href: link.href, className: cn("text-sm font-medium px-3 py-2 rounded-md transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring", isActive(link.href)
|
|
34
|
+
? "bg-accent text-accent-foreground"
|
|
35
|
+
: "text-foreground hover:bg-accent hover:text-accent-foreground"), "aria-current": isActive(link.href) ? "page" : undefined, children: link.label }, link.href))) })] }));
|
|
36
|
+
}
|
|
37
|
+
// Horizontal desktop + dropdown mobile
|
|
38
|
+
return (_jsxs(_Fragment, { children: [_jsx("nav", { "data-comp": "navigation", className: cn("hidden md:flex flex-row items-center gap-6", className), "aria-label": "Main navigation", ...props, ref: ref, children: links.map((link) => (_jsx("a", { href: link.href, className: cn("text-sm font-medium transition-colors hover:text-primary outline-none focus-visible:underline", isActive(link.href) ? "text-primary" : "text-foreground"), "aria-current": isActive(link.href) ? "page" : undefined, children: link.label }, link.href))) }), _jsxs("div", { className: "md:hidden", "data-mobile-nav": true, children: [_jsxs("button", { type: "button", onClick: (e) => {
|
|
39
|
+
e.stopPropagation();
|
|
40
|
+
setIsMobileOpen(!isMobileOpen);
|
|
41
|
+
}, className: "flex items-center gap-2 text-sm font-medium text-foreground hover:text-primary transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring rounded-md px-2 py-1", "aria-label": "Toggle mobile navigation", "aria-expanded": isMobileOpen, children: ["Menu", _jsx(ChevronDown, { className: cn("size-4 transition-transform", isMobileOpen && "rotate-180") })] }), isMobileOpen && (_jsx("nav", { "data-comp": "navigation", className: "mt-2 flex flex-col gap-2 bg-popover border rounded-md p-3 text-popover-foreground kala-surface-popover", "aria-label": "Mobile navigation", onClick: (e) => e.stopPropagation(), children: links.map((link) => (_jsx("a", { href: link.href, onClick: () => setIsMobileOpen(false), className: cn("text-sm font-medium px-3 py-2 rounded-md transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring", isActive(link.href)
|
|
42
|
+
? "bg-accent text-accent-foreground"
|
|
43
|
+
: "text-foreground hover:bg-accent hover:text-accent-foreground"), "aria-current": isActive(link.href) ? "page" : undefined, children: link.label }, link.href))) }))] })] }));
|
|
44
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Card Skeleton Component
|
|
3
|
+
*
|
|
4
|
+
* Loading placeholder for the Session Card component.
|
|
5
|
+
* Provides zero-CLS skeleton state that matches actual session card layout.
|
|
6
|
+
*/
|
|
7
|
+
import type { SessionCardSkeletonConfig } from "@kala-ui/react/skeleton";
|
|
8
|
+
export interface SessionCardSkeletonProps extends SessionCardSkeletonConfig {
|
|
9
|
+
/**
|
|
10
|
+
* Additional className for the skeleton container
|
|
11
|
+
*/
|
|
12
|
+
className?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Test ID for querying the element
|
|
15
|
+
*/
|
|
16
|
+
"data-testid"?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Session Card skeleton component
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```tsx
|
|
23
|
+
* <SessionCardSkeleton />
|
|
24
|
+
*
|
|
25
|
+
* <SessionCardSkeleton showBadge={true} />
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export declare function SessionCardSkeleton({ showBadge, showRevokeButton, className, "data-testid": dataTestId, }: SessionCardSkeletonProps): import("react").JSX.Element;
|
|
29
|
+
export declare namespace SessionCardSkeleton {
|
|
30
|
+
export { SessionCardSkeleton as Skeleton };
|
|
31
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* Session Card Skeleton Component
|
|
5
|
+
*
|
|
6
|
+
* Loading placeholder for the Session Card component.
|
|
7
|
+
* Provides zero-CLS skeleton state that matches actual session card layout.
|
|
8
|
+
*/
|
|
9
|
+
import { Card, CardContent, CardHeader } from "@kala-ui/react/card";
|
|
10
|
+
import { cn } from "@kala-ui/react/lib/utils";
|
|
11
|
+
import { Skeleton, SkeletonCircle } from "@kala-ui/react/skeleton";
|
|
12
|
+
/**
|
|
13
|
+
* Session Card skeleton component
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```tsx
|
|
17
|
+
* <SessionCardSkeleton />
|
|
18
|
+
*
|
|
19
|
+
* <SessionCardSkeleton showBadge={true} />
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export function SessionCardSkeleton({ showBadge = false, showRevokeButton = true, className, "data-testid": dataTestId, }) {
|
|
23
|
+
return (_jsxs(Card, { "data-testid": dataTestId || "session-card-skeleton", className: cn("relative", className), children: [_jsx(CardHeader, { className: "pb-3", children: _jsxs("div", { className: "flex items-start justify-between gap-4", children: [_jsxs("div", { className: "flex items-start gap-3 flex-1", children: [_jsx(SkeletonCircle, { size: "1.5rem", className: "rounded-md" }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx(Skeleton, { className: "h-4 w-32 mb-1" }), _jsx(Skeleton, { className: "h-3 w-48" })] })] }), showBadge ? (_jsx(Skeleton, { className: "h-5 w-24 rounded-md" })) : (showRevokeButton && _jsx(Skeleton, { className: "h-8 w-16 rounded-md" }))] }) }), _jsx(CardContent, { className: "pt-0", children: _jsxs("div", { className: "space-y-1.5", children: [_jsx(Skeleton, { className: "h-4 w-48" }), _jsx(Skeleton, { className: "h-4 w-40" }), _jsx(Skeleton, { className: "h-4 w-56" })] }) })] }));
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Compound component: SessionCard.Skeleton
|
|
27
|
+
*/
|
|
28
|
+
SessionCardSkeleton.Skeleton = SessionCardSkeleton;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Card Component
|
|
3
|
+
* Displays information about an active session with device, browser, location, and last activity
|
|
4
|
+
*/
|
|
5
|
+
import type { SessionCardSkeletonConfig } from "@kala-ui/react/skeleton";
|
|
6
|
+
import type * as React from "react";
|
|
7
|
+
export interface SessionData {
|
|
8
|
+
id: string;
|
|
9
|
+
device: string;
|
|
10
|
+
browser: string;
|
|
11
|
+
os: string;
|
|
12
|
+
location?: string;
|
|
13
|
+
ip: string;
|
|
14
|
+
lastActiveAt: string;
|
|
15
|
+
createdAt: string;
|
|
16
|
+
isCurrent: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface SessionCardProps extends React.ComponentProps<"div"> {
|
|
19
|
+
session: SessionData;
|
|
20
|
+
onRevoke?: (sessionId: string) => void | Promise<void>;
|
|
21
|
+
isRevoking?: boolean;
|
|
22
|
+
className?: string;
|
|
23
|
+
isLoading?: boolean;
|
|
24
|
+
skeletonConfig?: SessionCardSkeletonConfig;
|
|
25
|
+
skeleton?: React.ReactNode;
|
|
26
|
+
}
|
|
27
|
+
export declare function SessionCard({ session, onRevoke, isRevoking, className, isLoading, skeletonConfig, skeleton, ref, ...props }: SessionCardProps): React.JSX.Element;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* Session Card Component
|
|
5
|
+
* Displays information about an active session with device, browser, location, and last activity
|
|
6
|
+
*/
|
|
7
|
+
import { Button } from "@kala-ui/react/button";
|
|
8
|
+
import { Card, CardContent, CardHeader } from "@kala-ui/react/card";
|
|
9
|
+
import { cn } from "@kala-ui/react/lib/utils";
|
|
10
|
+
import { Clock, HelpCircle, MapPin, Monitor, Smartphone, Tablet, Wifi, } from "lucide-react";
|
|
11
|
+
import { SessionCardSkeleton } from "./session-card-skeleton.js";
|
|
12
|
+
function getDeviceIcon(device) {
|
|
13
|
+
const deviceLower = device.toLowerCase();
|
|
14
|
+
if (deviceLower.includes("mobile") || deviceLower.includes("phone")) {
|
|
15
|
+
return Smartphone;
|
|
16
|
+
}
|
|
17
|
+
if (deviceLower.includes("tablet") || deviceLower.includes("ipad")) {
|
|
18
|
+
return Tablet;
|
|
19
|
+
}
|
|
20
|
+
if (deviceLower.includes("desktop") || deviceLower.includes("computer")) {
|
|
21
|
+
return Monitor;
|
|
22
|
+
}
|
|
23
|
+
return HelpCircle;
|
|
24
|
+
}
|
|
25
|
+
function formatTimestamp(timestamp) {
|
|
26
|
+
const date = new Date(timestamp);
|
|
27
|
+
if (Number.isNaN(date.getTime())) {
|
|
28
|
+
return "Unknown";
|
|
29
|
+
}
|
|
30
|
+
const now = new Date();
|
|
31
|
+
const diffMs = now.getTime() - date.getTime();
|
|
32
|
+
const diffMins = Math.floor(diffMs / 60000);
|
|
33
|
+
const diffHours = Math.floor(diffMs / 3600000);
|
|
34
|
+
const diffDays = Math.floor(diffMs / 86400000);
|
|
35
|
+
if (diffMins < 1)
|
|
36
|
+
return "Just now";
|
|
37
|
+
if (diffMins < 60)
|
|
38
|
+
return `${diffMins} minute${diffMins > 1 ? "s" : ""} ago`;
|
|
39
|
+
if (diffHours < 24)
|
|
40
|
+
return `${diffHours} hour${diffHours > 1 ? "s" : ""} ago`;
|
|
41
|
+
if (diffDays === 1) {
|
|
42
|
+
return `Yesterday at ${date.toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit", hour12: true })}`;
|
|
43
|
+
}
|
|
44
|
+
return date.toLocaleDateString("en-US", {
|
|
45
|
+
month: "short",
|
|
46
|
+
day: "numeric",
|
|
47
|
+
hour: "numeric",
|
|
48
|
+
minute: "2-digit",
|
|
49
|
+
hour12: true,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
export function SessionCard({ session, onRevoke, isRevoking, className, isLoading = false, skeletonConfig, skeleton, ref, ...props }) {
|
|
53
|
+
if (isLoading) {
|
|
54
|
+
if (skeleton) {
|
|
55
|
+
return (_jsx(Card, { "data-comp": "session-card", ref: ref, className: cn("relative", className), ...props, children: skeleton }));
|
|
56
|
+
}
|
|
57
|
+
return _jsx(SessionCardSkeleton, { className: className, ...skeletonConfig });
|
|
58
|
+
}
|
|
59
|
+
const DeviceIcon = getDeviceIcon(session.device);
|
|
60
|
+
const lastActive = formatTimestamp(session.lastActiveAt);
|
|
61
|
+
const handleRevoke = () => {
|
|
62
|
+
if (onRevoke && !session.isCurrent) {
|
|
63
|
+
void onRevoke(session.id);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
return (_jsxs(Card, { "data-comp": "session-card", ref: ref, className: cn("relative", className), ...props, children: [_jsx(CardHeader, { className: "pb-3", children: _jsxs("div", { className: "flex items-start justify-between gap-4", children: [_jsxs("div", { className: "flex items-start gap-3 flex-1", children: [_jsx(DeviceIcon, { className: "size-6 text-muted-foreground mt-0.5", "aria-hidden": "true" }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "flex items-center gap-2 flex-wrap", children: _jsx("h4", { className: "font-semibold text-sm", children: session.browser }) }), _jsxs("p", { className: "text-xs text-muted-foreground mt-0.5", children: [session.os, " \u2022 ", session.device] })] })] }), session.isCurrent ? (_jsx("div", { className: "inline-flex items-center rounded-md bg-success/10 px-2 py-1 text-xs font-medium text-success border border-success/20", children: "Current Session" })) : (onRevoke && (_jsx(Button, { variant: "ghost", size: "sm", onClick: handleRevoke, disabled: isRevoking, className: "text-destructive hover:text-destructive hover:bg-destructive/10", "aria-label": "Revoke session", children: isRevoking ? "Revoking..." : "Revoke" })))] }) }), _jsx(CardContent, { className: "pt-0", children: _jsxs("div", { className: "space-y-1.5 text-xs text-muted-foreground", children: [session.location && (_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx(MapPin, { className: "size-3.5", "aria-hidden": "true" }), _jsx("span", { children: session.location })] })), _jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx(Wifi, { className: "size-3.5", "aria-hidden": "true" }), _jsx("span", { children: session.ip })] }), _jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx(Clock, { className: "size-3.5", "aria-hidden": "true" }), _jsxs("span", { children: ["Last active ", lastActive] })] })] }) })] }));
|
|
67
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sidebar Skeleton Component
|
|
3
|
+
*
|
|
4
|
+
* Loading placeholder for the Sidebar component.
|
|
5
|
+
* Supports sections, header, footer, and collapsed state.
|
|
6
|
+
*/
|
|
7
|
+
export interface SidebarSkeletonConfig {
|
|
8
|
+
/**
|
|
9
|
+
* Whether sidebar is collapsed
|
|
10
|
+
* @default false
|
|
11
|
+
*/
|
|
12
|
+
collapsed?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Show header/logo area
|
|
15
|
+
* @default true
|
|
16
|
+
*/
|
|
17
|
+
showHeader?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Show footer/user profile
|
|
20
|
+
* @default true
|
|
21
|
+
*/
|
|
22
|
+
showFooter?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Number of navigation sections
|
|
25
|
+
* @default 3
|
|
26
|
+
*/
|
|
27
|
+
sectionCount?: number;
|
|
28
|
+
/**
|
|
29
|
+
* Items per section
|
|
30
|
+
* @default 4
|
|
31
|
+
*/
|
|
32
|
+
itemsPerSection?: number;
|
|
33
|
+
}
|
|
34
|
+
export interface SidebarSkeletonProps extends SidebarSkeletonConfig {
|
|
35
|
+
/**
|
|
36
|
+
* Additional className for the skeleton container
|
|
37
|
+
*/
|
|
38
|
+
className?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Test ID for querying the element
|
|
41
|
+
*/
|
|
42
|
+
"data-testid"?: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Sidebar skeleton component
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```tsx
|
|
49
|
+
* <SidebarSkeleton sectionCount={3} itemsPerSection={4} />
|
|
50
|
+
*
|
|
51
|
+
* <SidebarSkeleton collapsed />
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export declare function SidebarSkeleton({ collapsed, showHeader, showFooter, sectionCount, itemsPerSection, className, "data-testid": dataTestId, }: SidebarSkeletonProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* Sidebar Skeleton Component
|
|
5
|
+
*
|
|
6
|
+
* Loading placeholder for the Sidebar component.
|
|
7
|
+
* Supports sections, header, footer, and collapsed state.
|
|
8
|
+
*/
|
|
9
|
+
import { cn } from "@kala-ui/react/lib/utils";
|
|
10
|
+
import { Skeleton } from "@kala-ui/react/skeleton";
|
|
11
|
+
/**
|
|
12
|
+
* Sidebar skeleton component
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```tsx
|
|
16
|
+
* <SidebarSkeleton sectionCount={3} itemsPerSection={4} />
|
|
17
|
+
*
|
|
18
|
+
* <SidebarSkeleton collapsed />
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export function SidebarSkeleton({ collapsed = false, showHeader = true, showFooter = true, sectionCount = 3, itemsPerSection = 4, className, "data-testid": dataTestId, }) {
|
|
22
|
+
return (_jsxs("aside", { "data-testid": dataTestId || "sidebar-skeleton", className: cn("flex flex-col h-full bg-popover border-r", collapsed ? "w-16" : "w-64", className), "aria-label": "Loading sidebar", "aria-busy": "true", children: [showHeader && (_jsx("div", { className: "flex items-center h-16 px-6 border-b", children: collapsed ? (_jsx(Skeleton, { className: "h-8 w-8 mx-auto" })) : (_jsx(Skeleton, { className: "h-8 w-32" })) })), _jsx("div", { className: "flex-1 overflow-y-auto py-4 px-3", children: Array.from({ length: sectionCount }).map((_, sectionIndex) => (_jsxs("div", { className: "mb-6 last:mb-0", children: [!collapsed && _jsx(Skeleton, { className: "h-3 w-20 mb-2 mx-3" }), _jsx("div", { className: "space-y-2", children: Array.from({ length: itemsPerSection }).map((_, itemIndex) => (_jsxs("div", { className: cn("flex items-center px-3 py-2", collapsed ? "justify-center" : "gap-3"), children: [_jsx(Skeleton, { className: "h-5 w-5 flex-shrink-0" }), !collapsed && _jsx(Skeleton, { className: "h-4 flex-1" }), !collapsed && itemIndex === 0 && (_jsx(Skeleton, { className: "h-4 w-4 rounded-full flex-shrink-0" }))] }, itemIndex))) })] }, sectionIndex))) }), showFooter && (_jsx("div", { className: "p-4 border-t", children: _jsxs("div", { className: cn("flex items-center", collapsed ? "justify-center" : "gap-3"), children: [_jsx(Skeleton, { className: "h-10 w-10 rounded-full flex-shrink-0" }), !collapsed && (_jsxs("div", { className: "flex-1 min-w-0", children: [_jsx(Skeleton, { className: "h-4 w-24 mb-1" }), _jsx(Skeleton, { className: "h-3 w-32" })] }))] }) }))] }));
|
|
23
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type * as React from "react";
|
|
2
|
+
import { type ReactNode } from "react";
|
|
3
|
+
export interface SidebarLink {
|
|
4
|
+
label: string;
|
|
5
|
+
href: string;
|
|
6
|
+
icon?: ReactNode;
|
|
7
|
+
badge?: string | number;
|
|
8
|
+
}
|
|
9
|
+
export interface SidebarSection {
|
|
10
|
+
title?: string;
|
|
11
|
+
links: SidebarLink[];
|
|
12
|
+
collapsible?: boolean;
|
|
13
|
+
defaultOpen?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface SidebarProps extends Omit<React.ComponentProps<"aside">, "color"> {
|
|
16
|
+
/**
|
|
17
|
+
* Logo or brand element to display at the top
|
|
18
|
+
*/
|
|
19
|
+
logo?: ReactNode;
|
|
20
|
+
/**
|
|
21
|
+
* Navigation sections with links
|
|
22
|
+
*/
|
|
23
|
+
navSections?: SidebarSection[];
|
|
24
|
+
/**
|
|
25
|
+
* Current pathname to highlight active links
|
|
26
|
+
*/
|
|
27
|
+
pathname?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Whether the sidebar is open
|
|
30
|
+
* @default true
|
|
31
|
+
*/
|
|
32
|
+
isOpen?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Callback when sidebar should close (mobile)
|
|
35
|
+
*/
|
|
36
|
+
onClose?: () => void;
|
|
37
|
+
/**
|
|
38
|
+
* Additional CSS classes
|
|
39
|
+
*/
|
|
40
|
+
className?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Footer content
|
|
43
|
+
*/
|
|
44
|
+
footer?: ReactNode;
|
|
45
|
+
}
|
|
46
|
+
export declare function Sidebar({ logo, navSections, pathname, isOpen, onClose, className, footer, ref, ...props }: SidebarProps): React.JSX.Element;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Button } from "@kala-ui/react/button";
|
|
4
|
+
import { cn } from "@kala-ui/react/lib/utils";
|
|
5
|
+
import { useFocusTrap, useMediaQuery, useMergedRef, useScrollLock, } from "@kala-ui/react-hooks";
|
|
6
|
+
import { ChevronDown } from "lucide-react";
|
|
7
|
+
import { useEffect, useId, useState } from "react";
|
|
8
|
+
import { isActivePath } from "../../lib/active-path.js";
|
|
9
|
+
export function Sidebar({ logo, navSections = [], pathname = "", isOpen = true, onClose, className, footer, ref, ...props }) {
|
|
10
|
+
// Mobile overlay behavior: on small screens the sidebar acts as a modal
|
|
11
|
+
// drawer (Escape to close, focus trap, background scroll lock).
|
|
12
|
+
const isDesktop = useMediaQuery("(min-width: 768px)");
|
|
13
|
+
const overlayActive = isOpen && !isDesktop;
|
|
14
|
+
const [, setScrollLocked] = useScrollLock();
|
|
15
|
+
const trapRef = useFocusTrap(overlayActive);
|
|
16
|
+
const headingBaseId = useId();
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
setScrollLocked(overlayActive);
|
|
19
|
+
}, [overlayActive, setScrollLocked]);
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (!overlayActive)
|
|
22
|
+
return;
|
|
23
|
+
const handleKeyDown = (event) => {
|
|
24
|
+
if (event.key === "Escape")
|
|
25
|
+
onClose?.();
|
|
26
|
+
};
|
|
27
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
28
|
+
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
29
|
+
}, [overlayActive, onClose]);
|
|
30
|
+
// Track which sections are expanded
|
|
31
|
+
const [expandedSections, setExpandedSections] = useState(new Set(navSections
|
|
32
|
+
.map((section, index) => (section.defaultOpen !== false ? index : -1))
|
|
33
|
+
.filter((index) => index !== -1)));
|
|
34
|
+
const toggleSection = (index) => {
|
|
35
|
+
setExpandedSections((prev) => {
|
|
36
|
+
const next = new Set(prev);
|
|
37
|
+
if (next.has(index)) {
|
|
38
|
+
next.delete(index);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
next.add(index);
|
|
42
|
+
}
|
|
43
|
+
return next;
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
const isLinkActive = (href) => isActivePath(pathname, href);
|
|
47
|
+
const renderLink = (link) => {
|
|
48
|
+
const active = isLinkActive(link.href);
|
|
49
|
+
return (_jsxs("a", { href: link.href, onClick: onClose, className: cn("flex items-center gap-3 px-3 py-2 rounded-lg text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset", active
|
|
50
|
+
? "bg-primary text-primary-foreground"
|
|
51
|
+
: "text-muted-foreground hover:bg-accent hover:text-accent-foreground"), children: [link.icon && _jsx("span", { className: "shrink-0", children: link.icon }), _jsx("span", { className: "flex-1 truncate", children: link.label }), link.badge !== undefined && (_jsx("span", { className: cn("shrink-0 px-2 py-0.5 text-xs font-semibold rounded-full", active
|
|
52
|
+
? "bg-primary-foreground/20 text-primary-foreground"
|
|
53
|
+
: "bg-muted text-muted-foreground"), children: link.badge }))] }, link.href));
|
|
54
|
+
};
|
|
55
|
+
const sidebarContent = (_jsxs(_Fragment, { children: [logo && (_jsx("div", { className: "flex items-center h-16 px-6 border-b", children: logo })), _jsx("nav", { className: "flex-1 overflow-y-auto py-4 px-3", children: navSections.map((section, sectionIndex) => {
|
|
56
|
+
const isExpanded = expandedSections.has(sectionIndex);
|
|
57
|
+
const hasTitle = section.title;
|
|
58
|
+
const isCollapsible = section.collapsible && hasTitle;
|
|
59
|
+
const panelId = `${headingBaseId}-section-${sectionIndex}`;
|
|
60
|
+
return (_jsxs("div", { className: "mb-6 last:mb-0", children: [hasTitle &&
|
|
61
|
+
(isCollapsible ? (_jsxs(Button, { variant: "ghost", className: "w-full flex items-center justify-between px-3 mb-2 text-xs font-semibold text-muted-foreground uppercase tracking-wider h-auto py-0 hover:bg-transparent cursor-pointer hover:text-foreground", onClick: () => toggleSection(sectionIndex), "aria-expanded": isExpanded, "aria-controls": panelId, children: [_jsx("span", { children: section.title }), _jsx(ChevronDown, { className: cn("size-4 transition-transform", isExpanded && "rotate-180") })] })) : (_jsx("h3", { className: "flex items-center justify-between px-3 mb-2 text-xs font-semibold text-muted-foreground uppercase tracking-wider", children: section.title }))), !isCollapsible ? (_jsx("div", { className: "space-y-1", children: section.links.map((link) => renderLink(link)) })) : (_jsx("div", { id: panelId, className: "space-y-1", hidden: !isExpanded, children: section.links.map((link) => renderLink(link)) }))] }, section.title ?? `section-${sectionIndex}`));
|
|
62
|
+
}) }), footer && _jsx("div", { className: "border-t p-4", children: footer })] }));
|
|
63
|
+
// The sidebar is a plain <aside> on desktop; on mobile it becomes a modal
|
|
64
|
+
// dialog — role=dialog is not allowed on <aside>, so the element itself
|
|
65
|
+
// switches with the viewport.
|
|
66
|
+
const shellRef = useMergedRef(ref, trapRef);
|
|
67
|
+
const shellClassName = cn("fixed left-0 top-0 z-30 h-full w-64 bg-popover border-r text-foreground transition-transform duration-300 ease-out flex flex-col kala-surface-card", "md:translate-x-0 md:z-10", isOpen ? "animate-slide-in-from-left" : "-translate-x-full", className);
|
|
68
|
+
return (_jsxs(_Fragment, { children: [isOpen && (_jsx("div", { className: "fixed inset-0 bg-overlay z-30 md:hidden animate-fade-in transition-opacity duration-200 ease-out", onClick: onClose, "aria-hidden": "true" })), overlayActive ? (_jsx("div", { ref: shellRef, "data-comp": "sidebar", role: "dialog", "aria-modal": "true", "aria-label": "Sidebar", className: shellClassName, ...props, children: sidebarContent })) : (_jsx("aside", { ref: shellRef, "data-comp": "sidebar", className: shellClassName, ...props, children: sidebarContent }))] }));
|
|
69
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SocialLoginButton, type SocialLoginButtonProps, } from "./social-login-button";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SocialLoginButton Component
|
|
3
|
+
* Individual OAuth provider button with loading state and brand colors from design tokens
|
|
4
|
+
*/
|
|
5
|
+
import { type ButtonProps } from "@kala-ui/react/button";
|
|
6
|
+
export interface SocialLoginButtonProps extends Omit<ButtonProps, "variant"> {
|
|
7
|
+
/**
|
|
8
|
+
* OAuth provider name (e.g., 'google', 'github')
|
|
9
|
+
*/
|
|
10
|
+
provider: "google" | "github" | "facebook" | "twitter" | "x" | "linkedin";
|
|
11
|
+
/**
|
|
12
|
+
* Loading state during OAuth redirect
|
|
13
|
+
*/
|
|
14
|
+
isLoading?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Optional custom label (defaults to provider name, e.g., "Google")
|
|
17
|
+
*/
|
|
18
|
+
label?: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function SocialLoginButton({ ref, provider, isLoading, label, className, disabled, ...props }: SocialLoginButtonProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* SocialLoginButton Component
|
|
5
|
+
* Individual OAuth provider button with loading state and brand colors from design tokens
|
|
6
|
+
*/
|
|
7
|
+
import { Button } from "@kala-ui/react/button";
|
|
8
|
+
import { cn } from "@kala-ui/react/lib/utils";
|
|
9
|
+
import { Loader2 } from "lucide-react";
|
|
10
|
+
import { siFacebook, siGithub, siGoogle, siX } from "simple-icons";
|
|
11
|
+
/**
|
|
12
|
+
* Provider configuration with proper brand colors
|
|
13
|
+
* Using solid hover colors instead of opacity to fix white background issue
|
|
14
|
+
*/
|
|
15
|
+
// Custom LinkedIn icon (removed from simple-icons due to Microsoft legal requirements)
|
|
16
|
+
// See: https://github.com/simple-icons/simple-icons/issues/11372
|
|
17
|
+
const LinkedInIcon = ({ className }) => (_jsxs("svg", { role: "img", viewBox: "0 0 24 24", className: className, fill: "currentColor", xmlns: "http://www.w3.org/2000/svg", children: [_jsx("title", { children: "LinkedIn" }), _jsx("path", { d: "M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z" })] }));
|
|
18
|
+
// Wrapper component to convert SimpleIcon to React component
|
|
19
|
+
const SimpleIconWrapper = ({ icon, className, label, }) => (_jsxs("svg", { role: "img", viewBox: "0 0 24 24", className: className, fill: "currentColor", xmlns: "http://www.w3.org/2000/svg", children: [_jsx("title", { children: label }), _jsx("path", { d: icon.path })] }));
|
|
20
|
+
const providerConfig = {
|
|
21
|
+
google: {
|
|
22
|
+
icon: siGoogle,
|
|
23
|
+
label: "Google",
|
|
24
|
+
color: "hover:bg-brand-google hover:text-white hover:border-brand-google",
|
|
25
|
+
},
|
|
26
|
+
github: {
|
|
27
|
+
icon: siGithub,
|
|
28
|
+
label: "GitHub",
|
|
29
|
+
color: "hover:bg-brand-github hover:text-white hover:border-brand-github",
|
|
30
|
+
},
|
|
31
|
+
facebook: {
|
|
32
|
+
icon: siFacebook,
|
|
33
|
+
label: "Facebook",
|
|
34
|
+
color: "hover:bg-brand-facebook hover:text-white hover:border-brand-facebook",
|
|
35
|
+
},
|
|
36
|
+
twitter: {
|
|
37
|
+
icon: siX,
|
|
38
|
+
label: "X",
|
|
39
|
+
color: "hover:bg-brand-twitter hover:text-white hover:border-brand-twitter",
|
|
40
|
+
},
|
|
41
|
+
x: {
|
|
42
|
+
icon: siX,
|
|
43
|
+
label: "X",
|
|
44
|
+
color: "hover:bg-brand-twitter hover:text-white hover:border-brand-twitter",
|
|
45
|
+
},
|
|
46
|
+
linkedin: {
|
|
47
|
+
icon: null, // Custom component used instead
|
|
48
|
+
label: "LinkedIn",
|
|
49
|
+
color: "hover:bg-brand-linkedin hover:text-white hover:border-brand-linkedin",
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
export function SocialLoginButton({ ref, provider, isLoading = false, label, className, disabled, ...props }) {
|
|
53
|
+
const config = providerConfig[provider];
|
|
54
|
+
const displayLabel = label ?? config.label;
|
|
55
|
+
const icon = config.icon;
|
|
56
|
+
return (_jsxs(Button, { ref: ref, "data-comp": "social-login-button", type: "button", variant: "outline", className: cn("relative transition-colors duration-200 bg-transparent border-input text-foreground", config.color, className), disabled: disabled || isLoading, ...props, children: [isLoading ? (_jsx(Loader2, { className: "w-5 h-5 animate-spin mr-2" })) : provider === "linkedin" ? (_jsx(LinkedInIcon, { className: "w-5 h-5 mr-2" })) : icon ? (_jsx(SimpleIconWrapper, { icon: icon, className: "w-5 h-5 mr-2", label: displayLabel })) : null, _jsx("span", { children: isLoading ? "Redirecting..." : displayLabel })] }));
|
|
57
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SocialLoginButtons, type SocialLoginButtonsProps, } from "./social-login-buttons";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SocialLoginButtons Component
|
|
3
|
+
* Grid of all OAuth provider buttons with divider
|
|
4
|
+
*/
|
|
5
|
+
import type * as React from "react";
|
|
6
|
+
import { type SocialLoginButtonProps } from "../social-login-button";
|
|
7
|
+
export interface SocialLoginButtonsProps {
|
|
8
|
+
/**
|
|
9
|
+
* Callback when any provider button is clicked
|
|
10
|
+
*/
|
|
11
|
+
onProviderClick: (provider: SocialLoginButtonProps["provider"]) => void;
|
|
12
|
+
/**
|
|
13
|
+
* Currently loading provider (if any)
|
|
14
|
+
*/
|
|
15
|
+
loadingProvider?: SocialLoginButtonProps["provider"];
|
|
16
|
+
/**
|
|
17
|
+
* Optional list of providers to display (defaults to all 5)
|
|
18
|
+
*/
|
|
19
|
+
providers?: SocialLoginButtonProps["provider"][];
|
|
20
|
+
/**
|
|
21
|
+
* Optional custom class name for the container
|
|
22
|
+
*/
|
|
23
|
+
className?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Show divider with "Or continue with" text
|
|
26
|
+
*/
|
|
27
|
+
showDivider?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Custom text for the divider
|
|
30
|
+
* @default "Or sign in with"
|
|
31
|
+
*/
|
|
32
|
+
dividerText?: string;
|
|
33
|
+
}
|
|
34
|
+
export declare function SocialLoginButtons({ onProviderClick, loadingProvider, providers, className, showDivider, dividerText, }: SocialLoginButtonsProps): React.JSX.Element;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* SocialLoginButtons Component
|
|
5
|
+
* Grid of all OAuth provider buttons with divider
|
|
6
|
+
*/
|
|
7
|
+
import { cn } from "@kala-ui/react/lib/utils";
|
|
8
|
+
import { SocialLoginButton, } from "../social-login-button/index.js";
|
|
9
|
+
const defaultProviders = [
|
|
10
|
+
"google",
|
|
11
|
+
"github",
|
|
12
|
+
"facebook",
|
|
13
|
+
"x",
|
|
14
|
+
"linkedin",
|
|
15
|
+
];
|
|
16
|
+
export function SocialLoginButtons({ onProviderClick, loadingProvider, providers = defaultProviders, className, showDivider = true, dividerText = "Or sign in with", }) {
|
|
17
|
+
return (_jsxs("div", { "data-comp": "social-login-buttons", className: cn("w-full space-y-3", className), children: [showDivider && (_jsxs("div", { className: "relative", children: [_jsx("div", { className: "absolute inset-0 flex items-center", children: _jsx("span", { className: "w-full border-t" }) }), _jsx("div", { className: "relative flex justify-center text-xs uppercase", children: _jsx("span", { className: "bg-background px-2 text-muted-foreground", children: dividerText }) })] })), _jsx("div", { className: "flex flex-wrap justify-center gap-2", children: providers.map((provider) => (_jsx(SocialLoginButton, { provider: provider, isLoading: loadingProvider === provider, onClick: () => onProviderClick(provider), disabled: loadingProvider !== undefined && loadingProvider !== provider }, provider))) })] }));
|
|
18
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SparklineChart Component
|
|
3
|
+
* Small inline chart for use in cards and dashboards
|
|
4
|
+
* Optimized for compact display with minimal visual noise
|
|
5
|
+
*/
|
|
6
|
+
export interface SparklineChartProps {
|
|
7
|
+
/**
|
|
8
|
+
* Data points for the sparkline
|
|
9
|
+
*/
|
|
10
|
+
data: number[];
|
|
11
|
+
/**
|
|
12
|
+
* Chart type
|
|
13
|
+
*/
|
|
14
|
+
type?: "line" | "area" | "bar";
|
|
15
|
+
/**
|
|
16
|
+
* Primary color for the chart
|
|
17
|
+
*/
|
|
18
|
+
color?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Width of the chart in pixels
|
|
21
|
+
*/
|
|
22
|
+
width?: number | string;
|
|
23
|
+
/**
|
|
24
|
+
* Height of the chart in pixels
|
|
25
|
+
*/
|
|
26
|
+
height?: number | string;
|
|
27
|
+
/**
|
|
28
|
+
* Show values on hover
|
|
29
|
+
* @default false
|
|
30
|
+
*/
|
|
31
|
+
tooltip?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Optional className for styling
|
|
34
|
+
*/
|
|
35
|
+
className?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Gradient colors (for area charts)
|
|
38
|
+
*/
|
|
39
|
+
gradientFrom?: string;
|
|
40
|
+
gradientTo?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Animation settings
|
|
43
|
+
*/
|
|
44
|
+
animated?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Enable interactions (hover states, markers, etc.)
|
|
47
|
+
* @default false
|
|
48
|
+
*/
|
|
49
|
+
interactive?: boolean;
|
|
50
|
+
}
|
|
51
|
+
export declare function SparklineChart({ data, type, color, width, height, tooltip, className, gradientFrom, gradientTo, animated, interactive, }: SparklineChartProps): import("react").JSX.Element;
|