@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,54 @@
1
+ /**
2
+ * Header Skeleton Component
3
+ *
4
+ * Loading placeholder for the Header component.
5
+ * Matches logo + navigation + actions layout.
6
+ */
7
+ export interface HeaderSkeletonConfig {
8
+ /**
9
+ * Show navigation items
10
+ * @default true
11
+ */
12
+ showNavigation?: boolean;
13
+ /**
14
+ * Number of navigation items
15
+ * @default 4
16
+ */
17
+ navItemCount?: number;
18
+ /**
19
+ * Show search input
20
+ * @default false
21
+ */
22
+ showSearch?: boolean;
23
+ /**
24
+ * Show notifications button
25
+ * @default false
26
+ */
27
+ showNotifications?: boolean;
28
+ /**
29
+ * Show user menu button
30
+ * @default true
31
+ */
32
+ showUserMenu?: boolean;
33
+ }
34
+ export interface HeaderSkeletonProps extends HeaderSkeletonConfig {
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
+ * Header skeleton component
46
+ *
47
+ * @example
48
+ * ```tsx
49
+ * <HeaderSkeleton showNavigation showSearch showUserMenu />
50
+ *
51
+ * <HeaderSkeleton navItemCount={5} showNotifications />
52
+ * ```
53
+ */
54
+ export declare function HeaderSkeleton({ showNavigation, navItemCount, showSearch, showNotifications, showUserMenu, className, "data-testid": dataTestId, }: HeaderSkeletonProps): 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
+ * Header Skeleton Component
5
+ *
6
+ * Loading placeholder for the Header component.
7
+ * Matches logo + navigation + actions layout.
8
+ */
9
+ import { cn } from "@kala-ui/react/lib/utils";
10
+ import { Skeleton } from "@kala-ui/react/skeleton";
11
+ /**
12
+ * Header skeleton component
13
+ *
14
+ * @example
15
+ * ```tsx
16
+ * <HeaderSkeleton showNavigation showSearch showUserMenu />
17
+ *
18
+ * <HeaderSkeleton navItemCount={5} showNotifications />
19
+ * ```
20
+ */
21
+ export function HeaderSkeleton({ showNavigation = true, navItemCount = 4, showSearch = false, showNotifications = false, showUserMenu = true, className, "data-testid": dataTestId, }) {
22
+ return (_jsxs("header", { "data-testid": dataTestId || "header-skeleton", className: cn("flex items-center justify-between p-4 border-b bg-background", className), children: [_jsx(Skeleton, { className: "h-8 w-32 flex-shrink-0" }), showNavigation && (_jsx("div", { className: "hidden md:flex gap-6 flex-1 mx-8", children: Array.from({ length: navItemCount }).map((_, i) => (_jsx(Skeleton, { className: "h-4 w-20" }, `nav-${i}`))) })), _jsxs("div", { className: "flex items-center gap-3 flex-shrink-0", children: [showSearch && (_jsx(Skeleton, { className: "hidden sm:block h-9 w-64 rounded-full" })), showNotifications && _jsx(Skeleton, { className: "h-9 w-9 rounded-full" }), showUserMenu && _jsx(Skeleton, { className: "h-9 w-9 rounded-full" }), _jsx(Skeleton, { className: "h-9 w-9 md:hidden" })] })] }));
23
+ }
@@ -0,0 +1,40 @@
1
+ import * as React from "react";
2
+ export interface HeaderNavLink {
3
+ label: string;
4
+ href: string;
5
+ active?: boolean;
6
+ children?: HeaderNavLink[];
7
+ }
8
+ export interface UserProfileLink {
9
+ label: string;
10
+ href?: string;
11
+ icon?: React.ReactNode;
12
+ onClick?: () => void;
13
+ divider?: boolean;
14
+ variant?: "default" | "danger";
15
+ }
16
+ export interface UserProfile {
17
+ name: string;
18
+ email: string;
19
+ username?: string | null;
20
+ avatar?: string | null;
21
+ role?: string;
22
+ links: UserProfileLink[];
23
+ }
24
+ export interface HeaderProps extends React.HTMLAttributes<HTMLElement>, React.RefAttributes<HTMLElement> {
25
+ logo?: React.ReactNode;
26
+ navLinks?: HeaderNavLink[];
27
+ userMenu?: React.ReactNode;
28
+ userProfile?: UserProfile;
29
+ searchBar?: React.ReactNode;
30
+ notifications?: React.ReactNode;
31
+ mobileNotifications?: React.ReactNode;
32
+ themeSwitcher?: React.ReactNode;
33
+ themeSwitcherMobile?: React.ReactNode;
34
+ languageSwitcher?: React.ReactNode;
35
+ languageSwitcherMobile?: React.ReactNode;
36
+ onMobileMenuToggle?: () => void;
37
+ isMobileMenuOpen?: boolean;
38
+ variant?: "default" | "dashboard";
39
+ }
40
+ export declare function Header({ ref, className, logo, navLinks, userMenu, userProfile, searchBar, notifications, mobileNotifications, themeSwitcher, themeSwitcherMobile, languageSwitcher, languageSwitcherMobile, onMobileMenuToggle, isMobileMenuOpen, variant, ...props }: HeaderProps): React.JSX.Element;
@@ -0,0 +1,77 @@
1
+ "use client";
2
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Box } from "@kala-ui/react/box";
4
+ import { Button } from "@kala-ui/react/button";
5
+ import { Flex } from "@kala-ui/react/flex";
6
+ import { cn } from "@kala-ui/react/lib/utils";
7
+ import { List } from "@kala-ui/react/list";
8
+ import { NavigationMenu, NavigationMenuContent, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, NavigationMenuViewport, } from "@kala-ui/react/navigation-menu";
9
+ import { Stack } from "@kala-ui/react/stack";
10
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@kala-ui/react/tabs";
11
+ import { Text } from "@kala-ui/react/text";
12
+ import { useFocusTrap, useMounted, useScrollLock } from "@kala-ui/react-hooks";
13
+ import { Bell, ChevronDown, Menu, Settings, X } from "lucide-react";
14
+ import * as React from "react";
15
+ import { createPortal } from "react-dom";
16
+ export function Header({ ref, className, logo, navLinks = [], userMenu, userProfile, searchBar, notifications, mobileNotifications, themeSwitcher, themeSwitcherMobile, languageSwitcher, languageSwitcherMobile, onMobileMenuToggle, isMobileMenuOpen, variant = "default", ...props }) {
17
+ // useScrollLock's argument is the INITIAL state only — drive it through
18
+ // the setter so opening/closing the menu actually engages the lock.
19
+ const [, setScrollLocked] = useScrollLock();
20
+ React.useEffect(() => {
21
+ setScrollLocked(!!isMobileMenuOpen);
22
+ }, [isMobileMenuOpen, setScrollLocked]);
23
+ // Fullscreen mobile menu: trap focus, close on Escape.
24
+ const mobileNavRef = useFocusTrap(!!isMobileMenuOpen);
25
+ React.useEffect(() => {
26
+ if (!isMobileMenuOpen)
27
+ return;
28
+ const handleKeyDown = (event) => {
29
+ if (event.key === "Escape")
30
+ onMobileMenuToggle?.();
31
+ };
32
+ window.addEventListener("keydown", handleKeyDown);
33
+ return () => window.removeEventListener("keydown", handleKeyDown);
34
+ }, [isMobileMenuOpen, onMobileMenuToggle]);
35
+ const mounted = useMounted();
36
+ // Track expanded mobile menu items
37
+ const [expandedItems, setExpandedItems] = React.useState(new Set());
38
+ const toggleExpanded = (label) => {
39
+ setExpandedItems((prev) => {
40
+ const next = new Set(prev);
41
+ if (next.has(label)) {
42
+ next.delete(label);
43
+ }
44
+ else {
45
+ next.add(label);
46
+ }
47
+ return next;
48
+ });
49
+ };
50
+ return (_jsxs(Box, { as: "header", ref: ref, "data-comp": "header", className: cn("w-full bg-popover border-b sticky top-0 z-20 kala-surface-popover", className), ...props, children: [_jsx(Box, { className: "container mx-auto px-3 sm:px-4 lg:px-6 xl:px-8", children: _jsxs(Flex, { align: "center", justify: "between", className: "h-14 sm:h-16 gap-2 sm:gap-4", children: [_jsx(Box, { className: "shrink-0 min-w-0 max-w-[120px] sm:max-w-none", children: logo }), _jsx(Flex, { align: "center", gap: variant === "default" ? 3 : 6, className: cn("hidden lg:flex", variant === "default" ? "flex-1" : ""), children: _jsx(Box, { className: cn("flex", variant === "default" ? "mx-auto" : ""), children: _jsxs(NavigationMenu, { children: [_jsx(NavigationMenuList, { children: navLinks.map((link) => (_jsx(NavigationMenuItem, { children: link.children ? (_jsxs(_Fragment, { children: [_jsx(NavigationMenuTrigger, { className: cn("text-sm lg:text-base", link.active && "text-primary"), children: link.label }), _jsx(NavigationMenuContent, { children: _jsx(Box, { className: "py-2", children: _jsx(List, { divided: false, className: cn("bg-transparent border-none", link.children.length <= 3
51
+ ? "w-[220px]"
52
+ : "w-[440px] grid grid-cols-2"), children: link.children.map((child) => (_jsx(Box, { as: "li", children: _jsx(NavigationMenuLink, { asChild: true, children: _jsx(Box, { as: "a", href: child.href, className: cn("block px-4 py-2.5 text-sm font-normal no-underline outline-none transition-colors", "text-foreground", "hover:bg-accent hover:text-accent-foreground", "focus-visible:bg-accent focus-visible:text-accent-foreground", child.active &&
53
+ "text-primary bg-primary/10"), children: child.label }) }) }, child.href))) }) }) })] })) : (_jsx(NavigationMenuLink, { href: link.href, className: cn("px-4 py-2 text-sm lg:text-base", link.active && "text-primary"), "aria-current": link.active ? "page" : undefined, children: link.label })) }, link.href))) }), _jsx(NavigationMenuViewport, {})] }) }) }), _jsxs(Flex, { align: "center", className: "gap-1.5 sm:gap-2 lg:gap-3 shrink-0", children: [searchBar && (_jsx(Box, { className: "hidden lg:block w-48 xl:w-64", children: searchBar })), languageSwitcher && (_jsx(Box, { className: "hidden lg:block", children: languageSwitcher })), themeSwitcher && (_jsx(Box, { className: "hidden lg:block", children: themeSwitcher })), notifications && (_jsx(Box, { className: "hidden lg:block", children: notifications })), userMenu && _jsx(Box, { className: "hidden lg:block", children: userMenu }), onMobileMenuToggle && (_jsx(Button, { variant: "ghost", size: "icon", className: "lg:hidden h-9 w-9 sm:h-10 sm:w-10", onClick: onMobileMenuToggle, "aria-label": "Toggle mobile menu", "aria-expanded": isMobileMenuOpen, children: isMobileMenuOpen ? (_jsx(X, { className: "w-5 h-5 sm:w-6 sm:h-6" })) : (_jsx(Menu, { className: "w-5 h-5 sm:w-6 sm:h-6" })) }))] })] }) }), isMobileMenuOpen &&
54
+ mounted &&
55
+ createPortal(_jsxs(_Fragment, { children: [_jsx(Box, { className: "fixed inset-0 bg-overlay/40 backdrop-blur-sm z-40 lg:hidden", onClick: onMobileMenuToggle, "aria-hidden": "true" }), _jsxs(Box, { as: "nav", ref: mobileNavRef, role: "dialog", "aria-modal": "true", className: "fixed top-0 left-0 right-0 bottom-0 bg-popover z-40 lg:hidden overflow-hidden flex flex-col kala-surface-popover", "aria-label": "Mobile navigation", children: [_jsxs(Flex, { align: "center", justify: "between", className: "px-4 h-14 sm:h-16 border-b shrink-0", children: [_jsx(Box, { className: "shrink-0 min-w-0 max-w-[120px] sm:max-w-none", children: logo }), _jsx(Button, { variant: "ghost", size: "icon", className: "h-9 w-9 sm:h-10 sm:w-10", onClick: onMobileMenuToggle, "aria-label": "Close mobile menu", children: _jsx(X, { className: "w-5 h-5 sm:w-6 sm:h-6" }) })] }), _jsxs(Tabs, { defaultValue: "menu", className: "flex flex-col flex-1 overflow-hidden", children: [_jsx(Box, { className: "px-4 py-2 border-b shrink-0", children: _jsxs(TabsList, { className: "w-full grid grid-cols-3", children: [_jsx(TabsTrigger, { value: "menu", children: _jsxs(Flex, { align: "center", gap: 2, children: [_jsx(Menu, { className: "w-4 h-4" }), _jsx(Text, { children: "Menu" })] }) }), _jsx(TabsTrigger, { value: "notifications", children: _jsxs(Flex, { align: "center", gap: 2, children: [_jsx(Bell, { className: "w-4 h-4" }), _jsx(Text, { children: "Alerts" })] }) }), _jsx(TabsTrigger, { value: "preferences", children: _jsxs(Flex, { align: "center", gap: 2, children: [_jsx(Settings, { className: "w-4 h-4" }), _jsx(Text, { children: "Preferences" })] }) })] }) }), _jsxs(Box, { className: "flex-1 overflow-y-auto", children: [_jsxs(TabsContent, { value: "menu", className: "mt-0 h-full", children: [searchBar && (_jsx(Box, { className: "px-4 py-3 border-b", children: searchBar })), userProfile ? (_jsxs(Box, { className: "border-b", children: [_jsxs(Flex, { align: "center", gap: 3, className: "px-4 py-4 bg-muted", children: [userProfile.avatar ? (_jsx(Box, { as: "img", src: userProfile.avatar, alt: userProfile.name, className: "w-10 h-10 rounded-full object-cover" })) : (_jsx(Flex, { align: "center", justify: "center", className: "w-10 h-10 rounded-full bg-primary/10 text-primary font-semibold", children: userProfile.name.charAt(0).toUpperCase() })), _jsxs(Stack, { gap: 0, className: "min-w-0", children: [_jsx(Text, { size: "sm", weight: "medium", className: "truncate", children: userProfile.name }), _jsx(Text, { size: "xs", className: "text-muted-foreground truncate", children: userProfile.email })] })] }), _jsx(Box, { className: "py-2", children: userProfile.links.map((link, index) => (_jsxs(React.Fragment, { children: [link.divider && index > 0 && (_jsx(Box, { className: "my-2 border-t" })), link.href ? (_jsxs(Box, { as: "a", href: link.href, className: cn("flex items-center gap-3 px-4 py-2.5 text-sm transition-colors hover:bg-accent outline-none focus-visible:bg-accent", link.variant === "danger"
56
+ ? "text-destructive hover:bg-destructive/10"
57
+ : "text-foreground hover:text-foreground"), onClick: onMobileMenuToggle, children: [link.icon && (_jsx(Box, { as: "span", className: "w-4 h-4", children: link.icon })), link.label] })) : (_jsxs(Button, { variant: "ghost", className: cn("w-full flex items-center gap-3 px-4 py-2.5 text-sm transition-colors hover:bg-accent text-left justify-start font-normal h-auto rounded-none", link.variant === "danger"
58
+ ? "text-destructive hover:bg-destructive/10"
59
+ : "text-foreground hover:text-foreground"), onClick: () => {
60
+ link.onClick?.();
61
+ onMobileMenuToggle?.();
62
+ }, children: [link.icon && (_jsx(Box, { as: "span", className: "w-4 h-4", children: link.icon })), link.label] }))] }, link.label))) })] })) : userMenu ? (_jsxs(Box, { className: "px-4 py-3 border-b", children: [_jsx(Text, { size: "xs", weight: "semibold", className: "text-muted-foreground uppercase mb-2", children: "Account" }), userMenu] })) : null, _jsx(Box, { className: "py-2", children: navLinks.map((link) => (_jsx(React.Fragment, { children: _jsxs(Flex, { direction: "column", children: [_jsxs(Box, { as: "a", href: link.href, className: cn("text-sm font-medium px-4 py-3 flex items-center justify-between transition-colors hover:bg-accent border-l-4 outline-none focus-visible:bg-accent", link.active
63
+ ? "bg-primary/10 text-primary border-primary"
64
+ : "text-foreground border-transparent hover:border-border"), "aria-current": link.active ? "page" : undefined, onClick: (e) => {
65
+ if (link.children || link.href === "#") {
66
+ e.preventDefault();
67
+ toggleExpanded(link.label);
68
+ }
69
+ else {
70
+ onMobileMenuToggle?.();
71
+ }
72
+ }, children: [link.label, link.children && (_jsx(ChevronDown, { className: cn("w-4 h-4 transition-transform duration-200", expandedItems.has(link.label)
73
+ ? "rotate-180"
74
+ : "") }))] }), link.children && expandedItems.has(link.label) && (_jsx(Box, { className: "bg-muted animate-in slide-in-from-top-2 duration-200", children: link.children.map((child) => (_jsx(Box, { as: "a", href: child.href, className: cn("block text-sm px-8 py-2.5 transition-colors hover:bg-accent border-l-4 border-transparent outline-none focus-visible:bg-accent", child.active
75
+ ? "text-primary font-medium"
76
+ : "text-muted-foreground hover:text-foreground"), onClick: onMobileMenuToggle, children: child.label }, child.label))) }))] }) }, link.label))) })] }), _jsx(TabsContent, { value: "notifications", className: "mt-0 h-full", children: mobileNotifications || notifications ? (_jsx(Box, { className: "p-0", children: mobileNotifications || notifications })) : (_jsxs(Flex, { direction: "column", align: "center", justify: "center", className: "h-64 text-muted-foreground", children: [_jsx(Bell, { className: "w-12 h-12 mb-4 opacity-20" }), _jsx(Text, { children: "No notifications" })] })) }), _jsx(TabsContent, { value: "preferences", className: "mt-0 h-full", children: _jsx(Flex, { direction: "column", children: (themeSwitcherMobile || languageSwitcherMobile) && (_jsxs(Stack, { gap: 6, className: "p-4", children: [_jsx(Text, { size: "xs", weight: "semibold", className: "text-muted-foreground uppercase", children: "Preferences" }), _jsxs(Stack, { gap: 6, children: [themeSwitcherMobile && (_jsx(Box, { className: "w-full", children: themeSwitcherMobile })), languageSwitcherMobile && (_jsx(Box, { className: "w-full", children: languageSwitcherMobile }))] })] })) }) })] })] })] })] }), document.body)] }));
77
+ }
@@ -0,0 +1,39 @@
1
+ import type * as React from "react";
2
+ export interface NavLink {
3
+ label: string;
4
+ href: string;
5
+ active?: boolean;
6
+ children?: NavLink[];
7
+ }
8
+ export interface UserProfileLink {
9
+ label: string;
10
+ href?: string;
11
+ icon?: React.ReactNode;
12
+ onClick?: () => void;
13
+ divider?: boolean;
14
+ variant?: "default" | "danger";
15
+ }
16
+ export interface UserProfile {
17
+ name: string;
18
+ email: string;
19
+ username?: string | null;
20
+ avatar?: string | null;
21
+ role?: string;
22
+ links: UserProfileLink[];
23
+ }
24
+ export interface HeaderProps extends React.HTMLAttributes<HTMLElement>, React.RefAttributes<HTMLElement> {
25
+ logo?: React.ReactNode;
26
+ navLinks?: NavLink[];
27
+ userMenu?: React.ReactNode;
28
+ userProfile?: UserProfile;
29
+ searchBar?: React.ReactNode;
30
+ notifications?: React.ReactNode;
31
+ mobileNotifications?: React.ReactNode;
32
+ themeSwitcher?: React.ReactNode;
33
+ themeSwitcherMobile?: React.ReactNode;
34
+ languageSwitcher?: React.ReactNode;
35
+ languageSwitcherMobile?: React.ReactNode;
36
+ onMobileMenuToggle?: () => void;
37
+ isMobileMenuOpen?: boolean;
38
+ variant?: "default" | "dashboard";
39
+ }
@@ -0,0 +1,2 @@
1
+ "use client";
2
+ export {};
@@ -0,0 +1,4 @@
1
+ export type { HeaderNavLink, HeaderProps } from "./header";
2
+ export { Header } from "./header";
3
+ export type { HeaderSkeletonConfig, HeaderSkeletonProps, } from "./header-skeleton";
4
+ export { HeaderSkeleton } from "./header-skeleton";
@@ -0,0 +1,3 @@
1
+ "use client";
2
+ export { Header } from "./header.js";
3
+ export { HeaderSkeleton } from "./header-skeleton.js";
@@ -0,0 +1,2 @@
1
+ export { MetricCard, type MetricCardProps } from "./metric-card";
2
+ export { MetricCardSkeleton } from "./metric-card-skeleton";
@@ -0,0 +1,3 @@
1
+ "use client";
2
+ export { MetricCard } from "./metric-card.js";
3
+ export { MetricCardSkeleton } from "./metric-card-skeleton.js";
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Metric Card Skeleton Component
3
+ *
4
+ * Loading placeholder variants for the Metric Card component.
5
+ * Provides zero-CLS skeleton states that match actual metric card layouts.
6
+ */
7
+ import type { MetricCardSkeletonConfig } from "@kala-ui/react/skeleton";
8
+ import * as React from "react";
9
+ export interface MetricCardSkeletonProps extends MetricCardSkeletonConfig {
10
+ /**
11
+ * Additional className for the skeleton container
12
+ */
13
+ className?: string;
14
+ /**
15
+ * Test ID for querying the element
16
+ */
17
+ "data-testid"?: string;
18
+ }
19
+ /**
20
+ * Metric Card skeleton component with multiple variants
21
+ *
22
+ * @example
23
+ * ```tsx
24
+ * <MetricCardSkeleton variant="default" />
25
+ *
26
+ * <MetricCardSkeleton variant="withIcon" />
27
+ * ```
28
+ */
29
+ export declare function MetricCardSkeleton({ variant, showIcon, showChange, className, "data-testid": dataTestId, }: MetricCardSkeletonProps): React.JSX.Element;
30
+ export declare namespace MetricCardSkeleton {
31
+ export { MetricCardSkeleton as Skeleton };
32
+ }
@@ -0,0 +1,29 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ /**
4
+ * Metric Card Skeleton Component
5
+ *
6
+ * Loading placeholder variants for the Metric Card component.
7
+ * Provides zero-CLS skeleton states that match actual metric card layouts.
8
+ */
9
+ import { Card } from "@kala-ui/react/card";
10
+ import { cn } from "@kala-ui/react/lib/utils";
11
+ import { Skeleton, SkeletonCircle } from "@kala-ui/react/skeleton";
12
+ import * as React from "react";
13
+ /**
14
+ * Metric Card skeleton component with multiple variants
15
+ *
16
+ * @example
17
+ * ```tsx
18
+ * <MetricCardSkeleton variant="default" />
19
+ *
20
+ * <MetricCardSkeleton variant="withIcon" />
21
+ * ```
22
+ */
23
+ export function MetricCardSkeleton({ variant = "default", showIcon = false, showChange = true, className, "data-testid": dataTestId, }) {
24
+ return (_jsx(Card, { ref: React.createRef(), "data-testid": dataTestId || "metric-card-skeleton", className: cn(className), children: _jsxs("div", { className: "p-6", children: [_jsxs("div", { className: "flex items-center justify-between mb-4", children: [_jsx(Skeleton, { className: "h-4 w-24" }), variant === "withIcon" || showIcon ? (_jsx(SkeletonCircle, { size: "1.5rem", className: "rounded-md" })) : null] }), _jsx("div", { className: "mb-3", children: _jsx(Skeleton, { className: "h-10 w-32" }) }), (variant === "default" || showChange) && (_jsx("div", { className: "text-sm", children: _jsx(Skeleton, { className: "h-4 w-32" }) }))] }) }));
25
+ }
26
+ /**
27
+ * Compound component: MetricCard.Skeleton
28
+ */
29
+ MetricCardSkeleton.Skeleton = MetricCardSkeleton;
@@ -0,0 +1,16 @@
1
+ import type { MetricCardSkeletonConfig } from "@kala-ui/react/skeleton";
2
+ import type * as React from "react";
3
+ export interface MetricCardProps extends Omit<React.ComponentProps<"div">, "title"> {
4
+ title: string;
5
+ value: number | string;
6
+ icon?: React.ReactNode;
7
+ change?: number;
8
+ changeLabel?: string;
9
+ subtitle?: string;
10
+ color?: "primary" | "secondary" | "destructive" | "success" | "warning" | "info" | "muted";
11
+ isLoading?: boolean;
12
+ skeletonConfig?: MetricCardSkeletonConfig;
13
+ skeleton?: React.ReactNode;
14
+ }
15
+ declare function MetricCard({ ref, title, value, icon, change, changeLabel, subtitle, className, color, isLoading, skeletonConfig, skeleton, ...props }: MetricCardProps): React.JSX.Element;
16
+ export { MetricCard };
@@ -0,0 +1,55 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Card } from "@kala-ui/react/card";
4
+ import { cn } from "@kala-ui/react/lib/utils";
5
+ import { MetricCardSkeleton } from "./metric-card-skeleton.js";
6
+ function MetricCard({ ref, title, value, icon, change, changeLabel, subtitle, className, color = "muted", isLoading = false, skeletonConfig, skeleton, ...props }) {
7
+ if (isLoading) {
8
+ if (skeleton) {
9
+ return (_jsx(Card, { ref: ref, "data-comp": "metric-card", className: cn(className), ...props, children: skeleton }));
10
+ }
11
+ return _jsx(MetricCardSkeleton, { className: className, ...skeletonConfig });
12
+ }
13
+ const getChangeColor = (changeValue) => {
14
+ if (changeValue > 0)
15
+ return "text-success";
16
+ if (changeValue < 0)
17
+ return "text-destructive";
18
+ return "text-muted-foreground";
19
+ };
20
+ const getChangeIcon = (changeValue) => {
21
+ if (changeValue > 0)
22
+ return "↑";
23
+ if (changeValue < 0)
24
+ return "↓";
25
+ return "−";
26
+ };
27
+ const getChangeText = (changeValue) => {
28
+ const absValue = Math.abs(changeValue);
29
+ const defaultLabel = changeValue > 0 || changeValue < 0
30
+ ? `${absValue}% than last week`
31
+ : "No change";
32
+ return changeLabel || defaultLabel;
33
+ };
34
+ const getColorStyles = () => {
35
+ switch (color) {
36
+ case "primary":
37
+ return "bg-gradient-to-br from-primary to-primary/90 text-primary-foreground";
38
+ case "secondary":
39
+ return "bg-gradient-to-br from-secondary to-secondary/90 text-secondary-foreground";
40
+ case "destructive":
41
+ return "bg-gradient-to-br from-destructive to-destructive/90 text-destructive-foreground";
42
+ case "success":
43
+ return "bg-gradient-to-br from-success to-success/90 text-success-foreground";
44
+ case "warning":
45
+ return "bg-gradient-to-br from-warning to-warning/90 text-warning-foreground";
46
+ case "info":
47
+ return "bg-gradient-to-br from-info to-info/90 text-info-foreground";
48
+ default:
49
+ return "bg-card border text-card-foreground";
50
+ }
51
+ };
52
+ const isColorful = color !== "muted" || className?.includes("bg-");
53
+ return (_jsx(Card, { ref: ref, "data-comp": "metric-card", className: cn(getColorStyles(), className), ...props, children: _jsxs("div", { className: "p-6", children: [_jsxs("div", { className: "flex items-center justify-between mb-4", children: [_jsx("h6", { className: cn("text-xs font-semibold uppercase tracking-wide", isColorful ? "opacity-90" : "text-muted-foreground"), children: title }), icon && (_jsx("div", { className: isColorful ? "opacity-70" : "text-muted-foreground", "aria-hidden": "true", children: icon }))] }), _jsx("div", { className: "mb-3", children: _jsxs("div", { className: cn("text-4xl font-bold leading-none", isColorful ? "" : "text-card-foreground"), children: [_jsxs("span", { className: "sr-only", children: [title, ":", " ", typeof value === "number" ? value.toLocaleString() : value] }), typeof value === "number" ? value.toLocaleString() : value] }) }), (change !== undefined || subtitle) && (_jsx("div", { className: "text-sm", children: change !== undefined ? (_jsxs("div", { className: cn("flex items-center gap-1", isColorful ? "opacity-90" : getChangeColor(change)), children: [_jsxs("span", { className: "sr-only", children: ["Change: ", getChangeText(change)] }), _jsx("span", { className: "inline-block", "aria-hidden": "true", children: getChangeIcon(change) }), _jsx("span", { children: getChangeText(change) })] })) : subtitle ? (_jsx("div", { className: cn(isColorful ? "opacity-80" : "text-muted-foreground"), children: subtitle })) : null }))] }) }));
54
+ }
55
+ export { MetricCard };
@@ -0,0 +1,14 @@
1
+ import type { MetricCardSkeletonConfig } from "@kala-ui/react/skeleton";
2
+ import type * as React from "react";
3
+ export interface MetricCardProps extends Omit<React.ComponentProps<"div">, "title"> {
4
+ title: string;
5
+ value: number | string;
6
+ icon?: React.ReactNode;
7
+ change?: number;
8
+ changeLabel?: string;
9
+ subtitle?: string;
10
+ color?: "primary" | "secondary" | "destructive" | "success" | "warning" | "info" | "muted";
11
+ isLoading?: boolean;
12
+ skeletonConfig?: MetricCardSkeletonConfig;
13
+ skeleton?: React.ReactNode;
14
+ }
@@ -0,0 +1,2 @@
1
+ "use client";
2
+ export {};
@@ -0,0 +1 @@
1
+ export * from "./nav-link";
@@ -0,0 +1,2 @@
1
+ "use client";
2
+ export * from "./nav-link.js";
@@ -0,0 +1,26 @@
1
+ import type * as React from "react";
2
+ export interface NavLinkProps extends Omit<React.ComponentProps<"button">, "onChange"> {
3
+ /** Link label */
4
+ label: React.ReactNode;
5
+ /** Link description */
6
+ description?: React.ReactNode;
7
+ /** Left icon */
8
+ icon?: React.ReactNode;
9
+ /** Right section (e.g. badge, icon) */
10
+ rightSection?: React.ReactNode;
11
+ /** Active state */
12
+ active?: boolean;
13
+ /** Collapsed/Expanded state for nested items */
14
+ defaultOpen?: boolean;
15
+ /** Controlled opened state */
16
+ open?: boolean;
17
+ /** Callback for opened state change */
18
+ onOpenChange?: (open: boolean) => void;
19
+ /** Nested links */
20
+ children?: React.ReactNode;
21
+ /** Disable right section rotation when opened */
22
+ disableRightSectionRotation?: boolean;
23
+ /** Indentation for nested items */
24
+ indent?: boolean;
25
+ }
26
+ export declare function NavLink({ ref, className, label, description, icon, rightSection, active, defaultOpen, open: openProp, onOpenChange, children, disableRightSectionRotation, indent, onClick, ...props }: NavLinkProps): React.JSX.Element;
@@ -0,0 +1,23 @@
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 { useUncontrolled } from "@kala-ui/react-hooks";
5
+ import { ChevronRight } from "lucide-react";
6
+ export function NavLink({ ref, className, label, description, icon, rightSection, active, defaultOpen = false, open: openProp, onOpenChange, children, disableRightSectionRotation = false, indent = false, onClick, ...props }) {
7
+ const [isOpened, handleOpenChange] = useUncontrolled({
8
+ value: openProp,
9
+ defaultValue: defaultOpen,
10
+ onChange: onOpenChange,
11
+ });
12
+ const handleToggle = (e) => {
13
+ if (children) {
14
+ e.preventDefault();
15
+ handleOpenChange(!isOpened);
16
+ }
17
+ onClick?.(e);
18
+ };
19
+ return (_jsxs(_Fragment, { children: [_jsxs("button", { ref: ref, className: cn("flex w-full items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50", active ? "bg-accent text-accent-foreground" : "text-muted-foreground", className), onClick: handleToggle, ...props, children: [icon && (_jsx("span", { className: "flex items-center justify-center", children: icon })), _jsxs("div", { className: "flex flex-1 flex-col items-start overflow-hidden", children: [_jsx("span", { className: "truncate", children: label }), description && (_jsx("span", { className: "truncate text-xs text-muted-foreground font-normal", children: description }))] }), (rightSection || children) && (_jsx("span", { className: cn("flex items-center justify-center text-muted-foreground transition-transform duration-200", children &&
20
+ !disableRightSectionRotation &&
21
+ isOpened &&
22
+ "rotate-90"), children: rightSection || (children && _jsx(ChevronRight, { className: "h-4 w-4" })) }))] }), children && isOpened && (_jsx("div", { className: cn("flex flex-col gap-1", indent && "pl-4"), children: children }))] }));
23
+ }
@@ -0,0 +1,4 @@
1
+ export type { NavigationLink, NavigationProps } from "./navigation";
2
+ export { Navigation } from "./navigation";
3
+ export type { NavigationSkeletonConfig, NavigationSkeletonProps, } from "./navigation-skeleton";
4
+ export { NavigationSkeleton } from "./navigation-skeleton";
@@ -0,0 +1,3 @@
1
+ "use client";
2
+ export { Navigation } from "./navigation.js";
3
+ export { NavigationSkeleton } from "./navigation-skeleton.js";
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Navigation Skeleton Component
3
+ *
4
+ * Loading placeholder for the Navigation component.
5
+ * Supports horizontal and vertical layouts with configurable items.
6
+ */
7
+ export interface NavigationSkeletonConfig {
8
+ /**
9
+ * Orientation of the navigation
10
+ * @default 'horizontal'
11
+ */
12
+ orientation?: "horizontal" | "vertical";
13
+ /**
14
+ * Number of navigation items
15
+ * @default 5
16
+ */
17
+ itemCount?: number;
18
+ /**
19
+ * Show icon skeletons
20
+ * @default false
21
+ */
22
+ showIcons?: boolean;
23
+ /**
24
+ * Show badge skeletons
25
+ * @default false
26
+ */
27
+ showBadges?: boolean;
28
+ /**
29
+ * Show nested navigation items
30
+ * @default false
31
+ */
32
+ nested?: boolean;
33
+ }
34
+ export interface NavigationSkeletonProps extends NavigationSkeletonConfig {
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
+ * Navigation skeleton component
46
+ *
47
+ * @example
48
+ * ```tsx
49
+ * <NavigationSkeleton orientation="horizontal" itemCount={5} />
50
+ *
51
+ * <NavigationSkeleton orientation="vertical" showIcons showBadges />
52
+ * ```
53
+ */
54
+ export declare function NavigationSkeleton({ orientation, itemCount, showIcons, showBadges, nested, className, "data-testid": dataTestId, }: NavigationSkeletonProps): 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
+ * Navigation Skeleton Component
5
+ *
6
+ * Loading placeholder for the Navigation component.
7
+ * Supports horizontal and vertical layouts with configurable items.
8
+ */
9
+ import { cn } from "@kala-ui/react/lib/utils";
10
+ import { Skeleton } from "@kala-ui/react/skeleton";
11
+ /**
12
+ * Navigation skeleton component
13
+ *
14
+ * @example
15
+ * ```tsx
16
+ * <NavigationSkeleton orientation="horizontal" itemCount={5} />
17
+ *
18
+ * <NavigationSkeleton orientation="vertical" showIcons showBadges />
19
+ * ```
20
+ */
21
+ export function NavigationSkeleton({ orientation = "horizontal", itemCount = 5, showIcons = false, showBadges = false, nested = false, className, "data-testid": dataTestId, }) {
22
+ return (_jsxs("nav", { "data-testid": dataTestId || "navigation-skeleton", className: cn("flex", orientation === "vertical" ? "flex-col gap-2" : "flex-row gap-6", className), "aria-label": "Loading navigation", "aria-busy": "true", children: [Array.from({ length: itemCount }).map((_, i) => (_jsxs("div", { className: cn("flex items-center", orientation === "vertical" ? "gap-3 px-3 py-2" : "gap-2"), children: [showIcons && (_jsx(Skeleton, { className: cn("flex-shrink-0", orientation === "vertical" ? "h-5 w-5" : "h-4 w-4") })), _jsx(Skeleton, { className: cn("h-4", orientation === "vertical" ? "w-24 flex-1" : "w-20") }), showBadges && (_jsx(Skeleton, { className: "h-4 w-4 rounded-full ml-auto flex-shrink-0" }))] }, `nav-${i}`))), nested && orientation === "vertical" && (_jsx("div", { className: "ml-8 space-y-2", children: Array.from({ length: 2 }).map((_, i) => (_jsxs("div", { className: "flex items-center gap-3 px-3 py-2", children: [showIcons && _jsx(Skeleton, { className: "h-4 w-4 flex-shrink-0" }), _jsx(Skeleton, { className: "h-4 w-20" })] }, `nested-${i}`))) }))] }));
23
+ }
@@ -0,0 +1,26 @@
1
+ import * as React from "react";
2
+ export interface NavigationLink {
3
+ label: string;
4
+ href: string;
5
+ }
6
+ export interface NavigationProps extends React.HTMLAttributes<HTMLElement>, React.RefAttributes<HTMLElement> {
7
+ /**
8
+ * Array of navigation links
9
+ */
10
+ links: NavigationLink[];
11
+ /**
12
+ * Orientation of the navigation
13
+ * @default 'horizontal'
14
+ */
15
+ orientation?: "horizontal" | "vertical";
16
+ /**
17
+ * Mobile layout style
18
+ * @default 'dropdown'
19
+ */
20
+ mobileLayout?: "dropdown" | "vertical";
21
+ /**
22
+ * Current pathname for active link detection
23
+ */
24
+ pathname?: string;
25
+ }
26
+ export declare function Navigation({ ref, className, links, orientation, mobileLayout, pathname, ...props }: NavigationProps): React.JSX.Element;