@kala-ui/react-app 0.1.0-beta.1 → 0.1.0-beta.10
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 +66 -0
- package/dist/components/app-shell/app-shell.d.ts +8 -46
- package/dist/components/app-shell/app-shell.js +41 -42
- package/dist/components/app-shell/app-shell.types.d.ts +45 -0
- package/dist/components/app-shell/app-shell.types.js +2 -0
- package/dist/components/app-shell/index.d.ts +1 -0
- package/dist/components/charts/area-chart.d.ts +1 -1
- package/dist/components/charts/area-chart.js +4 -3
- package/dist/components/charts/bar-chart.d.ts +1 -1
- package/dist/components/charts/bar-chart.js +4 -3
- package/dist/components/charts/chart-skeleton.d.ts +0 -21
- package/dist/components/charts/chart-skeleton.js +2 -1
- package/dist/components/charts/chart-utils.d.ts +56 -0
- package/dist/components/charts/chart-utils.js +135 -0
- package/dist/components/charts/chart.d.ts +3 -1
- package/dist/components/charts/chart.js +13 -3
- package/dist/components/charts/chart.types.d.ts +3 -0
- package/dist/components/charts/donut-chart.d.ts +1 -1
- package/dist/components/charts/donut-chart.js +4 -3
- package/dist/components/charts/index.d.ts +1 -1
- package/dist/components/charts/index.js +1 -1
- package/dist/components/charts/line-chart.d.ts +1 -1
- package/dist/components/charts/line-chart.js +4 -3
- package/dist/components/charts/radial-bar-chart.d.ts +1 -1
- package/dist/components/charts/radial-bar-chart.js +4 -3
- package/dist/components/data-table/data-table.js +4 -2
- package/dist/components/data-table/data-table.types.d.ts +2 -0
- package/dist/components/data-table/index.d.ts +1 -1
- package/dist/components/data-table/index.js +1 -1
- package/dist/components/data-table/use-table-state.d.ts +29 -0
- package/dist/components/data-table/use-table-state.js +312 -0
- package/dist/components/dnd/dnd.d.ts +16 -98
- package/dist/components/dnd/dnd.js +30 -2
- package/dist/components/dnd/dnd.types.d.ts +92 -0
- package/dist/components/dnd/dnd.types.js +7 -0
- package/dist/components/dnd/index.d.ts +2 -1
- package/dist/components/dnd/index.js +1 -1
- package/dist/components/footer/footer.d.ts +2 -37
- package/dist/components/footer/footer.js +18 -2
- package/dist/components/footer/footer.types.d.ts +25 -0
- package/dist/components/footer/footer.types.js +2 -0
- package/dist/components/footer/index.d.ts +1 -1
- package/dist/components/header/header.js +5 -11
- package/dist/components/metric-card/index.d.ts +2 -1
- package/dist/components/metric-card/metric-card-skeleton.js +2 -1
- package/dist/components/metric-card/metric-card.d.ts +2 -14
- package/dist/components/metric-card/metric-card.js +32 -28
- package/dist/components/metric-card/metric-card.types.d.ts +3 -0
- package/dist/components/nav-link/index.d.ts +2 -1
- package/dist/components/nav-link/index.js +1 -1
- package/dist/components/nav-link/nav-link.d.ts +2 -25
- package/dist/components/nav-link/nav-link.js +43 -5
- package/dist/components/nav-link/nav-link.types.d.ts +23 -0
- package/dist/components/nav-link/nav-link.types.js +2 -0
- package/dist/components/navigation/index.d.ts +1 -1
- package/dist/components/navigation/navigation.d.ts +1 -24
- package/dist/components/navigation/navigation.js +7 -17
- package/dist/components/navigation/navigation.types.d.ts +15 -0
- package/dist/components/navigation/navigation.types.js +2 -0
- package/dist/components/session-card/index.d.ts +2 -1
- package/dist/components/session-card/index.js +1 -1
- package/dist/components/session-card/session-card-skeleton.d.ts +3 -1
- package/dist/components/session-card/session-card-skeleton.js +3 -2
- package/dist/components/session-card/session-card.d.ts +3 -26
- package/dist/components/session-card/session-card.js +23 -8
- package/dist/components/session-card/session-card.types.d.ts +26 -0
- package/dist/components/session-card/session-card.types.js +2 -0
- package/dist/components/sidebar/sidebar.js +5 -11
- package/dist/components/social-login-button/index.d.ts +3 -1
- package/dist/components/social-login-button/index.js +2 -1
- package/dist/components/social-login-button/social-login-button.d.ts +2 -20
- package/dist/components/social-login-button/social-login-button.js +9 -11
- package/dist/components/social-login-button/social-login-button.types.d.ts +28 -0
- package/dist/components/social-login-button/social-login-button.types.js +2 -0
- package/dist/components/social-login-button/social-login-buttons.d.ts +4 -0
- package/dist/components/social-login-button/social-login-buttons.js +18 -0
- package/dist/components/user-menu-dropdown/index.d.ts +2 -1
- package/dist/components/user-menu-dropdown/index.js +1 -1
- package/dist/components/user-menu-dropdown/user-menu-dropdown.d.ts +2 -31
- package/dist/components/user-menu-dropdown/user-menu-dropdown.js +14 -2
- package/dist/components/user-menu-dropdown/user-menu-dropdown.types.d.ts +16 -0
- package/dist/components/user-menu-dropdown/user-menu-dropdown.types.js +2 -0
- package/dist/config/app-shell.d.ts +13 -0
- package/dist/config/app-shell.js +20 -0
- package/dist/config/charts.d.ts +23 -0
- package/dist/config/charts.js +23 -0
- package/dist/config/footer.d.ts +16 -0
- package/dist/config/footer.js +16 -0
- package/dist/config/index.d.ts +7 -0
- package/dist/config/index.js +7 -0
- package/dist/config/metric-card.d.ts +36 -0
- package/dist/config/metric-card.js +44 -0
- package/dist/config/session-card.d.ts +32 -0
- package/dist/config/session-card.js +32 -0
- package/dist/config/social-login-button.d.ts +15 -0
- package/dist/config/social-login-button.js +15 -0
- package/dist/config/user-menu-dropdown.d.ts +10 -0
- package/dist/config/user-menu-dropdown.js +10 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/styles/app.css +18 -53
- package/package.json +24 -28
|
@@ -1,26 +1,3 @@
|
|
|
1
1
|
import type * as React from "react";
|
|
2
|
-
|
|
3
|
-
|
|
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;
|
|
2
|
+
import type { NavLinkProps } from "./nav-link.types";
|
|
3
|
+
export declare function NavLink({ className, label, description, icon, rightSection, active, href, defaultOpen, open: openProp, onOpenChange, children, disableRightSectionRotation, indent, onClick, type, disabled, ...props }: NavLinkProps): React.JSX.Element;
|
|
@@ -3,12 +3,53 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
3
3
|
import { cn } from "@kala-ui/react/lib/utils";
|
|
4
4
|
import { useUncontrolled } from "@kala-ui/react-hooks";
|
|
5
5
|
import { ChevronRight } from "lucide-react";
|
|
6
|
-
|
|
6
|
+
/** True for plain left-clicks only — modifier and non-left clicks stay native
|
|
7
|
+
* so open-in-new-tab and download-as-link keep working. */
|
|
8
|
+
function isPlainLeftClick(e) {
|
|
9
|
+
return e.button === 0 && !e.metaKey && !e.ctrlKey && !e.shiftKey && !e.altKey;
|
|
10
|
+
}
|
|
11
|
+
/** Props shared by both arms with element-specific handlers widened to a
|
|
12
|
+
* common HTMLElement base, so the same rest-object spreads onto <button>
|
|
13
|
+
* and <a> without fighting their event-target variance. */
|
|
14
|
+
const widenHandlers = (props) => {
|
|
15
|
+
const out = {};
|
|
16
|
+
for (const [key, value] of Object.entries(props)) {
|
|
17
|
+
out[key] =
|
|
18
|
+
typeof value === "function" && (key.startsWith("on") || key === "ref")
|
|
19
|
+
? value
|
|
20
|
+
: value;
|
|
21
|
+
}
|
|
22
|
+
return out;
|
|
23
|
+
};
|
|
24
|
+
/** Label/description/icon/right-section row shared by the anchor and
|
|
25
|
+
* button arms. */
|
|
26
|
+
function NavLinkContent({ label, description, icon, rightSection, children, disableRightSectionRotation, isOpened, }) {
|
|
27
|
+
return (_jsxs(_Fragment, { 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 && !disableRightSectionRotation && isOpened && "rotate-90"), children: rightSection || (children && _jsx(ChevronRight, { className: "h-4 w-4" })) }))] }));
|
|
28
|
+
}
|
|
29
|
+
/** Base classes for both arms; the active variant swaps fg/bg. */
|
|
30
|
+
function navLinkBaseClassName(active, className) {
|
|
31
|
+
return 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);
|
|
32
|
+
}
|
|
33
|
+
export function NavLink({ className, label, description, icon, rightSection, active, href, defaultOpen = false, open: openProp, onOpenChange, children, disableRightSectionRotation = false, indent = false, onClick, type, disabled, ...props }) {
|
|
7
34
|
const [isOpened, handleOpenChange] = useUncontrolled({
|
|
8
35
|
value: openProp,
|
|
9
36
|
defaultValue: defaultOpen,
|
|
10
37
|
onChange: onOpenChange,
|
|
11
38
|
});
|
|
39
|
+
const content = (_jsx(NavLinkContent, { label: label, description: description, icon: icon, rightSection: rightSection, children: children, disableRightSectionRotation: disableRightSectionRotation, isOpened: isOpened }));
|
|
40
|
+
const baseClassName = navLinkBaseClassName(active, className);
|
|
41
|
+
if (href) {
|
|
42
|
+
// Anchor mode: button-only props (type/disabled) are invalid on <a> —
|
|
43
|
+
// disabled degrades to aria-disabled + pointer-events styling. Plain
|
|
44
|
+
// left-clicks are the SPA router's to handle; every other click
|
|
45
|
+
// (modifiers, middle) stays native so open-in-new-tab keeps working.
|
|
46
|
+
const handleAnchorClick = (e) => {
|
|
47
|
+
if (isPlainLeftClick(e))
|
|
48
|
+
e.preventDefault();
|
|
49
|
+
onClick?.(e);
|
|
50
|
+
};
|
|
51
|
+
return (_jsx("a", { "data-kala-component": "nav-link", href: href, "aria-current": active ? "page" : undefined, "aria-disabled": disabled || undefined, className: cn(baseClassName, disabled && "pointer-events-none opacity-50"), onClick: handleAnchorClick, ...widenHandlers(props), children: content }));
|
|
52
|
+
}
|
|
12
53
|
const handleToggle = (e) => {
|
|
13
54
|
if (children) {
|
|
14
55
|
e.preventDefault();
|
|
@@ -16,8 +57,5 @@ export function NavLink({ ref, className, label, description, icon, rightSection
|
|
|
16
57
|
}
|
|
17
58
|
onClick?.(e);
|
|
18
59
|
};
|
|
19
|
-
return (_jsxs(_Fragment, { 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 }))] }));
|
|
60
|
+
return (_jsxs(_Fragment, { children: [_jsx("button", { "data-kala-component": "nav-link", type: type ?? "button", disabled: disabled, className: baseClassName, onClick: handleToggle, ...widenHandlers(props), children: content }), children && isOpened && (_jsx("div", { className: cn("flex flex-col gap-1", indent && "pl-4"), children: children }))] }));
|
|
23
61
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type * as React from "react";
|
|
2
|
+
export interface NavLinkProps extends Omit<React.ComponentProps<"button">, "onChange" | "ref"> {
|
|
3
|
+
label: React.ReactNode;
|
|
4
|
+
description?: React.ReactNode;
|
|
5
|
+
icon?: React.ReactNode;
|
|
6
|
+
rightSection?: React.ReactNode;
|
|
7
|
+
active?: boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Renders the NavLink as a real anchor (<a href>) instead of a button.
|
|
10
|
+
* Use for top-level navigation targets: the href keeps middle-click,
|
|
11
|
+
* clone-tab and keyboard semantics native. A plain left-click is
|
|
12
|
+
* preventDefaulted so SPA routers can route client-side; modifier clicks
|
|
13
|
+
* are left to the browser. Nested-collapse behavior (children chevron
|
|
14
|
+
* toggle) only applies in button mode.
|
|
15
|
+
*/
|
|
16
|
+
href?: string;
|
|
17
|
+
defaultOpen?: boolean;
|
|
18
|
+
open?: boolean;
|
|
19
|
+
onOpenChange?: (open: boolean) => void;
|
|
20
|
+
children?: React.ReactNode;
|
|
21
|
+
disableRightSectionRotation?: boolean;
|
|
22
|
+
indent?: boolean;
|
|
23
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { NavigationLink, NavigationProps } from "./navigation";
|
|
1
|
+
export type { NavigationLink, NavigationProps } from "./navigation.types";
|
|
2
2
|
export { Navigation } from "./navigation";
|
|
3
3
|
export type { NavigationSkeletonConfig, NavigationSkeletonProps, } from "./navigation-skeleton";
|
|
4
4
|
export { NavigationSkeleton } from "./navigation-skeleton";
|
|
@@ -1,26 +1,3 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
|
|
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
|
-
}
|
|
2
|
+
import type { NavigationProps } from "./navigation.types";
|
|
26
3
|
export declare function Navigation({ ref, className, links, orientation, mobileLayout, pathname, ...props }: NavigationProps): React.JSX.Element;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useClickOutside } from "@kala-ui/react-hooks";
|
|
3
4
|
import { cn } from "@kala-ui/react/lib/utils";
|
|
4
5
|
import { ChevronDown } from "lucide-react";
|
|
5
6
|
import * as React from "react";
|
|
@@ -7,19 +8,11 @@ import { isActivePath } from "../../lib/active-path.js";
|
|
|
7
8
|
export function Navigation({ ref, className, links, orientation = "horizontal", mobileLayout = "dropdown", pathname = "", ...props }) {
|
|
8
9
|
const [isMobileOpen, setIsMobileOpen] = React.useState(false);
|
|
9
10
|
const isActive = (href) => isActivePath(pathname, href);
|
|
10
|
-
// Close mobile menu when clicking outside
|
|
11
|
-
React
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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]);
|
|
11
|
+
// Close mobile menu when clicking outside the wrapper (toggle + dropdown);
|
|
12
|
+
// React bails out on the redundant setState while already closed.
|
|
13
|
+
const mobileNavRef = useClickOutside(() => {
|
|
14
|
+
setIsMobileOpen(false);
|
|
15
|
+
});
|
|
23
16
|
// Vertical desktop navigation (explicit opt-in via orientation)
|
|
24
17
|
if (orientation === "vertical") {
|
|
25
18
|
return (_jsx("nav", { "data-kala-component": "navigation", className: cn(orientation === "vertical"
|
|
@@ -35,10 +28,7 @@ export function Navigation({ ref, className, links, orientation = "horizontal",
|
|
|
35
28
|
: "text-foreground hover:bg-accent hover:text-accent-foreground"), "aria-current": isActive(link.href) ? "page" : undefined, children: link.label }, link.href))) })] }));
|
|
36
29
|
}
|
|
37
30
|
// Horizontal desktop + dropdown mobile
|
|
38
|
-
return (_jsxs(_Fragment, { children: [_jsx("nav", { "data-kala-component": "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", { "data-kala-component": "navigation", className: "md:hidden", "
|
|
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", { 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)
|
|
31
|
+
return (_jsxs(_Fragment, { children: [_jsx("nav", { "data-kala-component": "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", { "data-kala-component": "navigation", className: "md:hidden", ref: mobileNavRef, children: [_jsxs("button", { type: "button", onClick: () => setIsMobileOpen(!isMobileOpen), 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", { 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", 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
32
|
? "bg-accent text-accent-foreground"
|
|
43
33
|
: "text-foreground hover:bg-accent hover:text-accent-foreground"), "aria-current": isActive(link.href) ? "page" : undefined, children: link.label }, link.href))) }))] })] }));
|
|
44
34
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type * 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
|
+
/** Array of navigation links */
|
|
8
|
+
links: NavigationLink[];
|
|
9
|
+
/** Orientation of the navigation. @default 'horizontal' */
|
|
10
|
+
orientation?: "horizontal" | "vertical";
|
|
11
|
+
/** Mobile layout style. @default 'dropdown' */
|
|
12
|
+
mobileLayout?: "dropdown" | "vertical";
|
|
13
|
+
/** Current pathname for active link detection */
|
|
14
|
+
pathname?: string;
|
|
15
|
+
}
|
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
export { SessionCard
|
|
1
|
+
export { SessionCard } from "./session-card";
|
|
2
|
+
export type { SessionCardProps, SessionData, } from "./session-card.types";
|
|
2
3
|
export { SessionCardSkeleton } from "./session-card-skeleton";
|
|
@@ -14,6 +14,8 @@ export interface SessionCardSkeletonProps extends SessionCardSkeletonConfig {
|
|
|
14
14
|
* Test ID for querying the element
|
|
15
15
|
*/
|
|
16
16
|
"data-testid"?: string;
|
|
17
|
+
[dataKala: `data-${string}`]: string | undefined;
|
|
18
|
+
style?: React.CSSProperties;
|
|
17
19
|
}
|
|
18
20
|
/**
|
|
19
21
|
* Session Card skeleton component
|
|
@@ -25,7 +27,7 @@ export interface SessionCardSkeletonProps extends SessionCardSkeletonConfig {
|
|
|
25
27
|
* <SessionCardSkeleton showBadge={true} />
|
|
26
28
|
* ```
|
|
27
29
|
*/
|
|
28
|
-
export declare function SessionCardSkeleton({ showBadge, showRevokeButton, className, "data-testid": dataTestId, }: SessionCardSkeletonProps): import("react").JSX.Element;
|
|
30
|
+
export declare function SessionCardSkeleton({ showBadge, showRevokeButton, className, "data-testid": dataTestId, style, ...markerProps }: SessionCardSkeletonProps): import("react").JSX.Element;
|
|
29
31
|
export declare namespace SessionCardSkeleton {
|
|
30
32
|
export { SessionCardSkeleton as Skeleton };
|
|
31
33
|
}
|
|
@@ -8,6 +8,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
8
8
|
*/
|
|
9
9
|
import { Card, CardContent, CardHeader } from "@kala-ui/react/card";
|
|
10
10
|
import { cn } from "@kala-ui/react/lib/utils";
|
|
11
|
+
import { sessionCardSkeletonStyles, sessionCardStyles, } from "../../config/session-card.js";
|
|
11
12
|
import { Skeleton, SkeletonCircle } from "@kala-ui/react/skeleton";
|
|
12
13
|
/**
|
|
13
14
|
* Session Card skeleton component
|
|
@@ -19,8 +20,8 @@ import { Skeleton, SkeletonCircle } from "@kala-ui/react/skeleton";
|
|
|
19
20
|
* <SessionCardSkeleton showBadge={true} />
|
|
20
21
|
* ```
|
|
21
22
|
*/
|
|
22
|
-
export function SessionCardSkeleton({ showBadge = false, showRevokeButton = true, className, "data-testid": dataTestId, }) {
|
|
23
|
-
return (_jsxs(Card, { "data-kala-component": "session-card-skeleton", "data-testid": dataTestId || "session-card-skeleton", className: cn(
|
|
23
|
+
export function SessionCardSkeleton({ showBadge = false, showRevokeButton = true, className, "data-testid": dataTestId, style, ...markerProps }) {
|
|
24
|
+
return (_jsxs(Card, { "data-kala-component": "session-card-skeleton", "data-testid": dataTestId || "session-card-skeleton", className: cn(sessionCardSkeletonStyles.root, className), style: style, ...markerProps, children: [_jsx(CardHeader, { className: sessionCardStyles.header, children: _jsxs("div", { className: sessionCardSkeletonStyles.headerRow, children: [_jsxs("div", { className: sessionCardSkeletonStyles.headerMain, children: [_jsx(SkeletonCircle, { size: "1.5rem", className: sessionCardSkeletonStyles.deviceIcon }), _jsxs("div", { className: sessionCardSkeletonStyles.titleBlock, children: [_jsx(Skeleton, { className: sessionCardSkeletonStyles.titleSkeleton }), _jsx(Skeleton, { className: sessionCardSkeletonStyles.subtitleSkeleton })] })] }), showBadge ? (_jsx(Skeleton, { className: sessionCardSkeletonStyles.badgeSkeleton })) : (showRevokeButton && (_jsx(Skeleton, { className: sessionCardSkeletonStyles.revokeSkeleton })))] }) }), _jsx(CardContent, { className: sessionCardStyles.content, children: _jsxs("div", { className: sessionCardSkeletonStyles.detailList, children: [_jsx(Skeleton, { className: sessionCardSkeletonStyles.detailRowMedium }), _jsx(Skeleton, { className: sessionCardSkeletonStyles.detailRowNarrow }), _jsx(Skeleton, { className: sessionCardSkeletonStyles.detailRowWide })] }) })] }));
|
|
24
25
|
}
|
|
25
26
|
/**
|
|
26
27
|
* Compound component: SessionCard.Skeleton
|
|
@@ -1,27 +1,4 @@
|
|
|
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
1
|
import type * as React from "react";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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;
|
|
2
|
+
import type { SessionCardProps } from "./session-card.types";
|
|
3
|
+
export declare function SessionCard({ session, onRevoke, isRevoking, className, style, slotStyles: slotStylesRaw, isLoading, skeletonConfig, skeleton, ref, ...props }: SessionCardProps): React.JSX.Element;
|
|
4
|
+
export type { SessionData } from "./session-card.types";
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
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
3
|
import { Button } from "@kala-ui/react/button";
|
|
8
4
|
import { Card, CardContent, CardHeader } from "@kala-ui/react/card";
|
|
5
|
+
import { useSlotStyles } from "@kala-ui/react/kala-provider";
|
|
6
|
+
import { applySlot } from "@kala-ui/react/lib/slot-styles";
|
|
9
7
|
import { cn } from "@kala-ui/react/lib/utils";
|
|
10
8
|
import { Clock, HelpCircle, MapPin, Monitor, Smartphone, Tablet, Wifi, } from "lucide-react";
|
|
9
|
+
import { sessionCardStyles } from "../../config/session-card.js";
|
|
11
10
|
import { SessionCardSkeleton } from "./session-card-skeleton.js";
|
|
12
11
|
function getDeviceIcon(device) {
|
|
13
12
|
const deviceLower = device.toLowerCase();
|
|
@@ -49,12 +48,14 @@ function formatTimestamp(timestamp) {
|
|
|
49
48
|
hour12: true,
|
|
50
49
|
});
|
|
51
50
|
}
|
|
52
|
-
export function SessionCard({ session, onRevoke, isRevoking, className, isLoading = false, skeletonConfig, skeleton, ref, ...props }) {
|
|
51
|
+
export function SessionCard({ session, onRevoke, isRevoking, className, style, slotStyles: slotStylesRaw, isLoading = false, skeletonConfig, skeleton, ref, ...props }) {
|
|
52
|
+
const slotStyles = useSlotStyles("session-card", slotStylesRaw);
|
|
53
|
+
const root = applySlot(cn(sessionCardStyles.root, className), slotStyles?.root);
|
|
53
54
|
if (isLoading) {
|
|
54
55
|
if (skeleton) {
|
|
55
|
-
return (_jsx(Card, { "data-kala-component": "session-card", ref: ref, className:
|
|
56
|
+
return (_jsx(Card, { "data-kala-component": "session-card", ref: ref, className: root.className, style: root.style, ...props, children: skeleton }));
|
|
56
57
|
}
|
|
57
|
-
return (_jsx(SessionCardSkeleton, { "data-kala-component": "session-card", className: className, ...skeletonConfig }));
|
|
58
|
+
return (_jsx(SessionCardSkeleton, { "data-kala-component": "session-card", className: root.className, ...skeletonConfig }));
|
|
58
59
|
}
|
|
59
60
|
const DeviceIcon = getDeviceIcon(session.device);
|
|
60
61
|
const lastActive = formatTimestamp(session.lastActiveAt);
|
|
@@ -63,5 +64,19 @@ export function SessionCard({ session, onRevoke, isRevoking, className, isLoadin
|
|
|
63
64
|
void onRevoke(session.id);
|
|
64
65
|
}
|
|
65
66
|
};
|
|
66
|
-
|
|
67
|
+
const header = applySlot(sessionCardStyles.header, slotStyles?.header);
|
|
68
|
+
const headerRow = applySlot(sessionCardStyles.headerRow, slotStyles?.headerRow);
|
|
69
|
+
const headerMain = applySlot(sessionCardStyles.headerMain, slotStyles?.headerMain);
|
|
70
|
+
const deviceIcon = applySlot(sessionCardStyles.deviceIcon, slotStyles?.deviceIcon);
|
|
71
|
+
const titleBlock = applySlot(sessionCardStyles.titleBlock, slotStyles?.titleBlock);
|
|
72
|
+
const titleRow = applySlot(sessionCardStyles.titleRow, slotStyles?.titleRow);
|
|
73
|
+
const title = applySlot(sessionCardStyles.title, slotStyles?.title);
|
|
74
|
+
const subtitle = applySlot(sessionCardStyles.subtitle, slotStyles?.subtitle);
|
|
75
|
+
const currentBadge = applySlot(sessionCardStyles.currentBadge, slotStyles?.currentBadge);
|
|
76
|
+
const revokeButton = applySlot(sessionCardStyles.revokeButton, slotStyles?.revokeButton);
|
|
77
|
+
const content = applySlot(sessionCardStyles.content, slotStyles?.content);
|
|
78
|
+
const detailList = applySlot(sessionCardStyles.detailList, slotStyles?.detailList);
|
|
79
|
+
const detailRow = applySlot(sessionCardStyles.detailRow, slotStyles?.detailRow);
|
|
80
|
+
const detailIcon = applySlot(sessionCardStyles.detailIcon, slotStyles?.detailIcon);
|
|
81
|
+
return (_jsxs(Card, { "data-kala-component": "session-card", ref: ref, className: root.className, style: root.style, ...props, children: [_jsx(CardHeader, { className: header.className, style: header.style, children: _jsxs("div", { className: headerRow.className, style: headerRow.style, children: [_jsxs("div", { className: headerMain.className, style: headerMain.style, children: [_jsx(DeviceIcon, { className: deviceIcon.className, style: deviceIcon.style, "aria-hidden": "true" }), _jsxs("div", { className: titleBlock.className, style: titleBlock.style, children: [_jsx("div", { className: titleRow.className, style: titleRow.style, children: _jsx("h4", { className: title.className, style: title.style, children: session.browser }) }), _jsxs("p", { className: subtitle.className, style: subtitle.style, children: [session.os, " \u2022 ", session.device] })] })] }), session.isCurrent ? (_jsx("div", { className: currentBadge.className, style: currentBadge.style, children: "Current Session" })) : (onRevoke && (_jsx(Button, { variant: "ghost", size: "sm", onClick: handleRevoke, disabled: isRevoking, className: revokeButton.className, style: revokeButton.style, "aria-label": "Revoke session", children: isRevoking ? "Revoking..." : "Revoke" })))] }) }), _jsx(CardContent, { className: content.className, style: content.style, children: _jsxs("div", { className: detailList.className, style: detailList.style, children: [session.location && (_jsxs("div", { className: detailRow.className, style: detailRow.style, children: [_jsx(MapPin, { className: detailIcon.className, "aria-hidden": "true" }), _jsx("span", { children: session.location })] })), _jsxs("div", { className: detailRow.className, style: detailRow.style, children: [_jsx(Wifi, { className: detailIcon.className, "aria-hidden": "true" }), _jsx("span", { children: session.ip })] }), _jsxs("div", { className: detailRow.className, style: detailRow.style, children: [_jsx(Clock, { className: detailIcon.className, "aria-hidden": "true" }), _jsxs("span", { children: ["Last active ", lastActive] })] })] }) })] }));
|
|
67
82
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { SlotStyles } from "@kala-ui/react/lib/slot-styles";
|
|
2
|
+
import type { SessionCardSkeletonConfig } from "@kala-ui/react/skeleton";
|
|
3
|
+
import type * as React from "react";
|
|
4
|
+
export interface SessionData {
|
|
5
|
+
id: string;
|
|
6
|
+
device: string;
|
|
7
|
+
browser: string;
|
|
8
|
+
os: string;
|
|
9
|
+
location?: string;
|
|
10
|
+
ip: string;
|
|
11
|
+
lastActiveAt: string;
|
|
12
|
+
createdAt: string;
|
|
13
|
+
isCurrent: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface SessionCardProps extends React.ComponentProps<"div"> {
|
|
16
|
+
session: SessionData;
|
|
17
|
+
onRevoke?: (sessionId: string) => void | Promise<void>;
|
|
18
|
+
isRevoking?: boolean;
|
|
19
|
+
className?: string;
|
|
20
|
+
isLoading?: boolean;
|
|
21
|
+
skeletonConfig?: SessionCardSkeletonConfig;
|
|
22
|
+
skeleton?: React.ReactNode;
|
|
23
|
+
/** Per-part overrides: root, header, headerRow, deviceIcon, title, subtitle,
|
|
24
|
+
* currentBadge, revokeButton, content, detailList, detailRow, detailIcon. */
|
|
25
|
+
slotStyles?: SlotStyles;
|
|
26
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { Button } from "@kala-ui/react/button";
|
|
4
4
|
import { cn } from "@kala-ui/react/lib/utils";
|
|
5
|
-
import { useFocusTrap, useMediaQuery, useMergedRef, useScrollLock, } from "@kala-ui/react-hooks";
|
|
5
|
+
import { useFocusTrap, useMediaQuery, useMergedRef, useScrollLock, useWindowEvent, } from "@kala-ui/react-hooks";
|
|
6
6
|
import { ChevronDown } from "lucide-react";
|
|
7
7
|
import { useEffect, useId, useState } from "react";
|
|
8
8
|
import { isActivePath } from "../../lib/active-path.js";
|
|
@@ -17,16 +17,10 @@ export function Sidebar({ logo, navSections = [], pathname = "", isOpen = true,
|
|
|
17
17
|
useEffect(() => {
|
|
18
18
|
setScrollLocked(overlayActive);
|
|
19
19
|
}, [overlayActive, setScrollLocked]);
|
|
20
|
-
|
|
21
|
-
if (
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if (event.key === "Escape")
|
|
25
|
-
onClose?.();
|
|
26
|
-
};
|
|
27
|
-
window.addEventListener("keydown", handleKeyDown);
|
|
28
|
-
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
29
|
-
}, [overlayActive, onClose]);
|
|
20
|
+
useWindowEvent("keydown", (event) => {
|
|
21
|
+
if (event.key === "Escape" && overlayActive)
|
|
22
|
+
onClose?.();
|
|
23
|
+
});
|
|
30
24
|
// Track which sections are expanded
|
|
31
25
|
const [expandedSections, setExpandedSections] = useState(new Set(navSections
|
|
32
26
|
.map((section, index) => (section.defaultOpen !== false ? index : -1))
|
|
@@ -1 +1,3 @@
|
|
|
1
|
-
export { SocialLoginButton
|
|
1
|
+
export { SocialLoginButton } from "./social-login-button";
|
|
2
|
+
export type { SocialLoginButtonProps, SocialLoginButtonsProps, } from "./social-login-button.types";
|
|
3
|
+
export { SocialLoginButtons } from "./social-login-buttons";
|
|
@@ -1,20 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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;
|
|
1
|
+
import type { SocialLoginButtonProps } from "./social-login-button.types";
|
|
2
|
+
export declare function SocialLoginButton({ ref, provider, isLoading, label, className, style, slotStyles: slotStylesRaw, disabled, ...props }: SocialLoginButtonProps): import("react").JSX.Element;
|
|
@@ -1,20 +1,15 @@
|
|
|
1
1
|
"use client";
|
|
2
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
3
|
import { Button } from "@kala-ui/react/button";
|
|
4
|
+
import { useSlotStyles } from "@kala-ui/react/kala-provider";
|
|
5
|
+
import { applySlot } from "@kala-ui/react/lib/slot-styles";
|
|
8
6
|
import { cn } from "@kala-ui/react/lib/utils";
|
|
9
7
|
import { Loader2 } from "lucide-react";
|
|
10
8
|
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
|
-
*/
|
|
9
|
+
import { socialLoginButtonStyles } from "../../config/social-login-button.js";
|
|
15
10
|
// Custom LinkedIn icon (removed from simple-icons due to Microsoft legal requirements)
|
|
16
11
|
// 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.
|
|
12
|
+
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.224 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z" })] }));
|
|
18
13
|
// Wrapper component to convert SimpleIcon to React component
|
|
19
14
|
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
15
|
const providerConfig = {
|
|
@@ -49,9 +44,12 @@ const providerConfig = {
|
|
|
49
44
|
color: "hover:bg-brand-linkedin hover:text-white hover:border-brand-linkedin",
|
|
50
45
|
},
|
|
51
46
|
};
|
|
52
|
-
export function SocialLoginButton({ ref, provider, isLoading = false, label, className, disabled, ...props }) {
|
|
47
|
+
export function SocialLoginButton({ ref, provider, isLoading = false, label, className, style, slotStyles: slotStylesRaw, disabled, ...props }) {
|
|
48
|
+
const slotStyles = useSlotStyles("social-login-button", slotStylesRaw);
|
|
53
49
|
const config = providerConfig[provider];
|
|
54
50
|
const displayLabel = label ?? config.label;
|
|
55
51
|
const icon = config.icon;
|
|
56
|
-
|
|
52
|
+
const root = applySlot(cn(socialLoginButtonStyles.root, config.color, className), slotStyles?.root);
|
|
53
|
+
const iconSlot = applySlot(socialLoginButtonStyles.icon, slotStyles?.icon);
|
|
54
|
+
return (_jsxs(Button, { "data-kala-component": "social-login-button", ref: ref, type: "button", variant: "outline", className: root.className, style: root.style, disabled: disabled || isLoading, ...props, children: [isLoading ? (_jsx(Loader2, { className: applySlot(socialLoginButtonStyles.spinner, slotStyles?.icon).className })) : provider === "linkedin" ? (_jsx(LinkedInIcon, { className: iconSlot.className })) : icon ? (_jsx(SimpleIconWrapper, { icon: icon, className: iconSlot.className, label: displayLabel })) : null, _jsx("span", { children: isLoading ? "Redirecting..." : displayLabel })] }));
|
|
57
55
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ButtonProps } from "@kala-ui/react/button";
|
|
2
|
+
import type { SlotStyles } from "@kala-ui/react/lib/slot-styles";
|
|
3
|
+
export interface SocialLoginButtonProps extends Omit<ButtonProps, "variant"> {
|
|
4
|
+
/** OAuth provider name (e.g., 'google', 'github') */
|
|
5
|
+
provider: "google" | "github" | "facebook" | "twitter" | "x" | "linkedin";
|
|
6
|
+
/** Loading state during OAuth redirect */
|
|
7
|
+
isLoading?: boolean;
|
|
8
|
+
/** Optional custom label (defaults to provider name, e.g., "Google") */
|
|
9
|
+
label?: string;
|
|
10
|
+
/** Per-part overrides: `root` (button), `icon` (provider glyph). */
|
|
11
|
+
slotStyles?: SlotStyles;
|
|
12
|
+
}
|
|
13
|
+
export interface SocialLoginButtonsProps {
|
|
14
|
+
/** Callback when any provider button is clicked */
|
|
15
|
+
onProviderClick: (provider: SocialLoginButtonProps["provider"]) => void;
|
|
16
|
+
/** Currently loading provider (if any) */
|
|
17
|
+
loadingProvider?: SocialLoginButtonProps["provider"];
|
|
18
|
+
/** Optional list of providers to display (defaults to all 5) */
|
|
19
|
+
providers?: SocialLoginButtonProps["provider"][];
|
|
20
|
+
/** Optional custom class name for the container */
|
|
21
|
+
className?: string;
|
|
22
|
+
/** Show divider with "Or continue with" text */
|
|
23
|
+
showDivider?: boolean;
|
|
24
|
+
/** Custom text for the divider; defaults to "Or sign in with" */
|
|
25
|
+
dividerText?: string;
|
|
26
|
+
/** Per-part overrides: root, divider, dividerLine, dividerMask, dividerLabel, dividerRule, list. */
|
|
27
|
+
slotStyles?: SlotStyles;
|
|
28
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type * as React from "react";
|
|
2
|
+
import type { SocialLoginButtonsProps } from "./social-login-button.types";
|
|
3
|
+
export declare function SocialLoginButtons({ onProviderClick, loadingProvider, providers, className, showDivider, dividerText, slotStyles: slotStylesRaw, }: SocialLoginButtonsProps): React.JSX.Element;
|
|
4
|
+
export type { SocialLoginButtonProps } from "./social-login-button.types";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useSlotStyles } from "@kala-ui/react/kala-provider";
|
|
4
|
+
import { applySlot } from "@kala-ui/react/lib/slot-styles";
|
|
5
|
+
import { cn } from "@kala-ui/react/lib/utils";
|
|
6
|
+
import { socialLoginButtonsStyles } from "../../config/social-login-button.js";
|
|
7
|
+
import { SocialLoginButton } from "./social-login-button.js";
|
|
8
|
+
export function SocialLoginButtons({ onProviderClick, loadingProvider, providers = ["google", "github", "facebook", "x", "linkedin"], className, showDivider = true, dividerText = "Or sign in with", slotStyles: slotStylesRaw, }) {
|
|
9
|
+
const slotStyles = useSlotStyles("social-login-buttons", slotStylesRaw);
|
|
10
|
+
const root = applySlot(cn(socialLoginButtonsStyles.root, className), slotStyles?.root);
|
|
11
|
+
const divider = applySlot(socialLoginButtonsStyles.divider, slotStyles?.divider);
|
|
12
|
+
const dividerLine = applySlot(socialLoginButtonsStyles.dividerLine, slotStyles?.dividerLine);
|
|
13
|
+
const dividerMask = applySlot(socialLoginButtonsStyles.dividerMask, slotStyles?.dividerMask);
|
|
14
|
+
const dividerLabel = applySlot(socialLoginButtonsStyles.dividerLabel, slotStyles?.dividerLabel);
|
|
15
|
+
const dividerRule = applySlot(socialLoginButtonsStyles.dividerRule, slotStyles?.dividerRule);
|
|
16
|
+
const list = applySlot(socialLoginButtonsStyles.list, slotStyles?.list);
|
|
17
|
+
return (_jsxs("div", { "data-kala-component": "social-login-buttons", className: root.className, children: [showDivider && (_jsxs("div", { className: divider.className, children: [_jsx("div", { className: dividerLine.className, children: _jsx("span", { className: dividerRule.className }) }), _jsx("div", { className: dividerMask.className, children: _jsx("span", { className: dividerLabel.className, children: dividerText }) })] })), _jsx("div", { className: list.className, children: providers.map((provider) => (_jsx(SocialLoginButton, { provider: provider, isLoading: loadingProvider === provider, onClick: () => onProviderClick(provider), disabled: loadingProvider !== undefined && loadingProvider !== provider }, provider))) })] }));
|
|
18
|
+
}
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export { UserMenuDropdown
|
|
1
|
+
export { UserMenuDropdown } from "./user-menu-dropdown";
|
|
2
|
+
export type { UserMenuDropdownProps } from "./user-menu-dropdown.types";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
export { UserMenuDropdown
|
|
2
|
+
export { UserMenuDropdown } from "./user-menu-dropdown.js";
|