@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.
Files changed (123) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/LICENSE +21 -0
  3. package/README.md +36 -0
  4. package/dist/components/app-shell/app-shell.d.ts +54 -0
  5. package/dist/components/app-shell/app-shell.js +91 -0
  6. package/dist/components/app-shell/index.d.ts +1 -0
  7. package/dist/components/app-shell/index.js +2 -0
  8. package/dist/components/charts/area-chart.d.ts +6 -0
  9. package/dist/components/charts/area-chart.js +172 -0
  10. package/dist/components/charts/bar-chart.d.ts +6 -0
  11. package/dist/components/charts/bar-chart.js +153 -0
  12. package/dist/components/charts/chart-skeleton.d.ts +36 -0
  13. package/dist/components/charts/chart-skeleton.js +13 -0
  14. package/dist/components/charts/chart.d.ts +25 -0
  15. package/dist/components/charts/chart.js +48 -0
  16. package/dist/components/charts/chart.types.d.ts +196 -0
  17. package/dist/components/charts/chart.types.js +6 -0
  18. package/dist/components/charts/donut-chart.d.ts +7 -0
  19. package/dist/components/charts/donut-chart.js +146 -0
  20. package/dist/components/charts/index.d.ts +14 -0
  21. package/dist/components/charts/index.js +15 -0
  22. package/dist/components/charts/line-chart.d.ts +6 -0
  23. package/dist/components/charts/line-chart.js +158 -0
  24. package/dist/components/charts/radial-bar-chart.d.ts +6 -0
  25. package/dist/components/charts/radial-bar-chart.js +115 -0
  26. package/dist/components/charts/theme-utils.d.ts +77 -0
  27. package/dist/components/charts/theme-utils.js +243 -0
  28. package/dist/components/charts/use-theme-aware-chart.d.ts +12 -0
  29. package/dist/components/charts/use-theme-aware-chart.js +68 -0
  30. package/dist/components/charts/utils.d.ts +56 -0
  31. package/dist/components/charts/utils.js +135 -0
  32. package/dist/components/data-table/column-filters.d.ts +14 -0
  33. package/dist/components/data-table/column-filters.js +51 -0
  34. package/dist/components/data-table/column-header-filter.d.ts +14 -0
  35. package/dist/components/data-table/column-header-filter.js +75 -0
  36. package/dist/components/data-table/data-table-body-rows.d.ts +23 -0
  37. package/dist/components/data-table/data-table-body-rows.js +43 -0
  38. package/dist/components/data-table/data-table-filter-chips.d.ts +14 -0
  39. package/dist/components/data-table/data-table-filter-chips.js +42 -0
  40. package/dist/components/data-table/data-table-header-row.d.ts +21 -0
  41. package/dist/components/data-table/data-table-header-row.js +35 -0
  42. package/dist/components/data-table/data-table-skeleton.d.ts +49 -0
  43. package/dist/components/data-table/data-table-skeleton.js +32 -0
  44. package/dist/components/data-table/data-table-toolbar.d.ts +18 -0
  45. package/dist/components/data-table/data-table-toolbar.js +25 -0
  46. package/dist/components/data-table/data-table.d.ts +23 -0
  47. package/dist/components/data-table/data-table.js +250 -0
  48. package/dist/components/data-table/data-table.types.d.ts +305 -0
  49. package/dist/components/data-table/data-table.types.js +6 -0
  50. package/dist/components/data-table/index.d.ts +6 -0
  51. package/dist/components/data-table/index.js +6 -0
  52. package/dist/components/data-table/pagination-nav.d.ts +15 -0
  53. package/dist/components/data-table/pagination-nav.js +23 -0
  54. package/dist/components/data-table/useTableState.d.ts +29 -0
  55. package/dist/components/data-table/useTableState.js +312 -0
  56. package/dist/components/dnd/dnd.d.ts +118 -0
  57. package/dist/components/dnd/dnd.js +128 -0
  58. package/dist/components/dnd/index.d.ts +1 -0
  59. package/dist/components/dnd/index.js +2 -0
  60. package/dist/components/footer/footer.d.ts +37 -0
  61. package/dist/components/footer/footer.js +8 -0
  62. package/dist/components/footer/index.d.ts +2 -0
  63. package/dist/components/footer/index.js +2 -0
  64. package/dist/components/header/header-skeleton.d.ts +54 -0
  65. package/dist/components/header/header-skeleton.js +23 -0
  66. package/dist/components/header/header.d.ts +40 -0
  67. package/dist/components/header/header.js +77 -0
  68. package/dist/components/header/header.types.d.ts +39 -0
  69. package/dist/components/header/header.types.js +2 -0
  70. package/dist/components/header/index.d.ts +4 -0
  71. package/dist/components/header/index.js +3 -0
  72. package/dist/components/metric-card/index.d.ts +2 -0
  73. package/dist/components/metric-card/index.js +3 -0
  74. package/dist/components/metric-card/metric-card-skeleton.d.ts +32 -0
  75. package/dist/components/metric-card/metric-card-skeleton.js +29 -0
  76. package/dist/components/metric-card/metric-card.d.ts +16 -0
  77. package/dist/components/metric-card/metric-card.js +55 -0
  78. package/dist/components/metric-card/metric-card.types.d.ts +14 -0
  79. package/dist/components/metric-card/metric-card.types.js +2 -0
  80. package/dist/components/nav-link/index.d.ts +1 -0
  81. package/dist/components/nav-link/index.js +2 -0
  82. package/dist/components/nav-link/nav-link.d.ts +26 -0
  83. package/dist/components/nav-link/nav-link.js +23 -0
  84. package/dist/components/navigation/index.d.ts +4 -0
  85. package/dist/components/navigation/index.js +3 -0
  86. package/dist/components/navigation/navigation-skeleton.d.ts +54 -0
  87. package/dist/components/navigation/navigation-skeleton.js +23 -0
  88. package/dist/components/navigation/navigation.d.ts +26 -0
  89. package/dist/components/navigation/navigation.js +44 -0
  90. package/dist/components/session-card/index.d.ts +2 -0
  91. package/dist/components/session-card/index.js +3 -0
  92. package/dist/components/session-card/session-card-skeleton.d.ts +31 -0
  93. package/dist/components/session-card/session-card-skeleton.js +28 -0
  94. package/dist/components/session-card/session-card.d.ts +27 -0
  95. package/dist/components/session-card/session-card.js +67 -0
  96. package/dist/components/sidebar/index.d.ts +3 -0
  97. package/dist/components/sidebar/index.js +3 -0
  98. package/dist/components/sidebar/sidebar-skeleton.d.ts +54 -0
  99. package/dist/components/sidebar/sidebar-skeleton.js +23 -0
  100. package/dist/components/sidebar/sidebar.d.ts +46 -0
  101. package/dist/components/sidebar/sidebar.js +69 -0
  102. package/dist/components/social-login-button/index.d.ts +1 -0
  103. package/dist/components/social-login-button/index.js +2 -0
  104. package/dist/components/social-login-button/social-login-button.d.ts +20 -0
  105. package/dist/components/social-login-button/social-login-button.js +57 -0
  106. package/dist/components/social-login-buttons/index.d.ts +1 -0
  107. package/dist/components/social-login-buttons/index.js +2 -0
  108. package/dist/components/social-login-buttons/social-login-buttons.d.ts +34 -0
  109. package/dist/components/social-login-buttons/social-login-buttons.js +18 -0
  110. package/dist/components/sparkline-chart/index.d.ts +2 -0
  111. package/dist/components/sparkline-chart/index.js +2 -0
  112. package/dist/components/sparkline-chart/sparkline-chart.d.ts +51 -0
  113. package/dist/components/sparkline-chart/sparkline-chart.js +134 -0
  114. package/dist/components/user-menu-dropdown/index.d.ts +1 -0
  115. package/dist/components/user-menu-dropdown/index.js +2 -0
  116. package/dist/components/user-menu-dropdown/user-menu-dropdown.d.ts +31 -0
  117. package/dist/components/user-menu-dropdown/user-menu-dropdown.js +27 -0
  118. package/dist/index.d.ts +16 -0
  119. package/dist/index.js +19 -0
  120. package/dist/lib/active-path.d.ts +5 -0
  121. package/dist/lib/active-path.js +12 -0
  122. package/dist/styles/app.css +2125 -0
  123. 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,2 @@
1
+ export { SessionCard, type SessionCardProps, type SessionData, } from "./session-card";
2
+ export { SessionCardSkeleton } from "./session-card-skeleton";
@@ -0,0 +1,3 @@
1
+ "use client";
2
+ export { SessionCard, } from "./session-card.js";
3
+ export { SessionCardSkeleton } from "./session-card-skeleton.js";
@@ -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,3 @@
1
+ export { Sidebar, type SidebarLink, type SidebarProps, type SidebarSection, } from "./sidebar";
2
+ export type { SidebarSkeletonConfig, SidebarSkeletonProps, } from "./sidebar-skeleton";
3
+ export { SidebarSkeleton } from "./sidebar-skeleton";
@@ -0,0 +1,3 @@
1
+ "use client";
2
+ export { Sidebar, } from "./sidebar.js";
3
+ export { SidebarSkeleton } from "./sidebar-skeleton.js";
@@ -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,2 @@
1
+ "use client";
2
+ export { SocialLoginButton, } from "./social-login-button.js";
@@ -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,2 @@
1
+ "use client";
2
+ export { SocialLoginButtons, } from "./social-login-buttons.js";
@@ -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,2 @@
1
+ export type { SparklineChartProps } from "./sparkline-chart";
2
+ export { SparklineChart } from "./sparkline-chart";
@@ -0,0 +1,2 @@
1
+ "use client";
2
+ export { SparklineChart } from "./sparkline-chart.js";
@@ -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;