@cronus-ui/ui 0.6.0 → 0.6.2
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/dist/components/invite-dialog.d.ts +62 -0
- package/dist/components/invite-dialog.js +141 -0
- package/dist/components/workspace-switcher.d.ts +36 -0
- package/dist/components/workspace-switcher.js +56 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +2 -0
- package/dist/lib/auth-adapter.d.ts +26 -0
- package/dist/lib/auth-adapter.js +46 -0
- package/package.json +15 -3
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type ComponentPropsWithoutRef, type ReactNode, type Ref } from "react";
|
|
2
|
+
import { DialogContent } from "./dialog.js";
|
|
3
|
+
export interface InviteDialogLabels {
|
|
4
|
+
title?: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
email?: string;
|
|
7
|
+
emailPlaceholder?: string;
|
|
8
|
+
role?: string;
|
|
9
|
+
send?: string;
|
|
10
|
+
cancel?: string;
|
|
11
|
+
sending?: string;
|
|
12
|
+
errorFallback?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface InviteRole {
|
|
15
|
+
value: string;
|
|
16
|
+
label: string;
|
|
17
|
+
}
|
|
18
|
+
export interface InviteDialogProps extends Omit<ComponentPropsWithoutRef<typeof DialogContent>, "title" | "children"> {
|
|
19
|
+
/** Element that opens the dialog. Rendered inside a `DialogTrigger asChild`. Omit when driving the dialog purely via `open`. */
|
|
20
|
+
trigger?: ReactNode;
|
|
21
|
+
/** Controlled open state. Provide alongside `onOpenChange` to control the dialog externally. */
|
|
22
|
+
open?: boolean;
|
|
23
|
+
/** Uncontrolled initial open state. @default false */
|
|
24
|
+
defaultOpen?: boolean;
|
|
25
|
+
/** Called whenever the open state changes. Dismissal is suppressed while an invite promise is pending. */
|
|
26
|
+
onOpenChange?: (open: boolean) => void;
|
|
27
|
+
/** Roles offered in the select. Defaults to Member and Admin. */
|
|
28
|
+
roles?: readonly InviteRole[];
|
|
29
|
+
/** Initially selected role `value`. Falls back to the first role. */
|
|
30
|
+
defaultRole?: string;
|
|
31
|
+
/**
|
|
32
|
+
* Invoked with the submitted email and role. If it returns a promise, the send
|
|
33
|
+
* button shows a spinner and actions are disabled until it settles; on resolve
|
|
34
|
+
* the dialog closes, on reject it stays open and surfaces the error.
|
|
35
|
+
*/
|
|
36
|
+
onInvite?: (payload: {
|
|
37
|
+
email: string;
|
|
38
|
+
role: string;
|
|
39
|
+
}) => void | Promise<void>;
|
|
40
|
+
/** Override any subset of the built-in English strings. */
|
|
41
|
+
labels?: InviteDialogLabels;
|
|
42
|
+
/** Forwarded to the dialog content surface. */
|
|
43
|
+
ref?: Ref<HTMLDivElement>;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* An ergonomic invite dialog for adding a member by email and role, composed
|
|
47
|
+
* on top of the accessible `Dialog` primitive (focus trap, overlay, Escape).
|
|
48
|
+
*
|
|
49
|
+
* Behavior: `onInvite` may be async — while its promise is pending the send
|
|
50
|
+
* button shows a `Spinner`, both actions are disabled, and the dialog cannot be
|
|
51
|
+
* dismissed so the operation can't be interrupted mid-flight. On resolve the
|
|
52
|
+
* dialog closes; on reject it stays open and renders the error in a
|
|
53
|
+
* `role="alert"` region. Works controlled (`open`/`onOpenChange`) or
|
|
54
|
+
* uncontrolled (`defaultOpen`).
|
|
55
|
+
*
|
|
56
|
+
* Performance/a11y: late promise settlements are ignored after unmount via a
|
|
57
|
+
* mounted ref. The spinner is decorative (`aria-hidden`) with loading state
|
|
58
|
+
* conveyed through `aria-busy`. All entrance/exit motion is owned by the
|
|
59
|
+
* underlying primitive and degrades under `prefers-reduced-motion`.
|
|
60
|
+
*/
|
|
61
|
+
export declare function InviteDialog({ trigger, open, defaultOpen, onOpenChange, roles, defaultRole, onInvite, labels, className, ref, ...contentProps }: InviteDialogProps): import("react").JSX.Element;
|
|
62
|
+
//# sourceMappingURL=invite-dialog.d.ts.map
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { CircleAlert } from "lucide-react";
|
|
4
|
+
import { useCallback, useEffect, useId, useRef, useState, } from "react";
|
|
5
|
+
import { cn } from "../lib/cn.js";
|
|
6
|
+
import { Button } from "./button.js";
|
|
7
|
+
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "./dialog.js";
|
|
8
|
+
import { Field, FieldLabel } from "./field.js";
|
|
9
|
+
import { Input } from "./input.js";
|
|
10
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./select.js";
|
|
11
|
+
import { Spinner } from "./spinner.js";
|
|
12
|
+
const DEFAULT_LABELS = {
|
|
13
|
+
title: "Invite member",
|
|
14
|
+
description: "Send an invitation to join this workspace.",
|
|
15
|
+
email: "Email",
|
|
16
|
+
emailPlaceholder: "name@example.com",
|
|
17
|
+
role: "Role",
|
|
18
|
+
send: "Send invite",
|
|
19
|
+
cancel: "Cancel",
|
|
20
|
+
sending: "Sending",
|
|
21
|
+
errorFallback: "Something went wrong. Please try again.",
|
|
22
|
+
};
|
|
23
|
+
const DEFAULT_ROLES = [
|
|
24
|
+
{ value: "member", label: "Member" },
|
|
25
|
+
{ value: "admin", label: "Admin" },
|
|
26
|
+
];
|
|
27
|
+
function resolveErrorMessage(error, fallback) {
|
|
28
|
+
if (error instanceof Error && error.message) {
|
|
29
|
+
return error.message;
|
|
30
|
+
}
|
|
31
|
+
if (typeof error === "string" && error) {
|
|
32
|
+
return error;
|
|
33
|
+
}
|
|
34
|
+
return fallback;
|
|
35
|
+
}
|
|
36
|
+
function resolveRoles(roles) {
|
|
37
|
+
return roles && roles.length > 0 ? roles : DEFAULT_ROLES;
|
|
38
|
+
}
|
|
39
|
+
function resolveDefaultRole(roles, defaultRole) {
|
|
40
|
+
if (defaultRole && roles.some((role) => role.value === defaultRole)) {
|
|
41
|
+
return defaultRole;
|
|
42
|
+
}
|
|
43
|
+
return roles[0]?.value ?? "";
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* An ergonomic invite dialog for adding a member by email and role, composed
|
|
47
|
+
* on top of the accessible `Dialog` primitive (focus trap, overlay, Escape).
|
|
48
|
+
*
|
|
49
|
+
* Behavior: `onInvite` may be async — while its promise is pending the send
|
|
50
|
+
* button shows a `Spinner`, both actions are disabled, and the dialog cannot be
|
|
51
|
+
* dismissed so the operation can't be interrupted mid-flight. On resolve the
|
|
52
|
+
* dialog closes; on reject it stays open and renders the error in a
|
|
53
|
+
* `role="alert"` region. Works controlled (`open`/`onOpenChange`) or
|
|
54
|
+
* uncontrolled (`defaultOpen`).
|
|
55
|
+
*
|
|
56
|
+
* Performance/a11y: late promise settlements are ignored after unmount via a
|
|
57
|
+
* mounted ref. The spinner is decorative (`aria-hidden`) with loading state
|
|
58
|
+
* conveyed through `aria-busy`. All entrance/exit motion is owned by the
|
|
59
|
+
* underlying primitive and degrades under `prefers-reduced-motion`.
|
|
60
|
+
*/
|
|
61
|
+
export function InviteDialog({ trigger, open, defaultOpen, onOpenChange, roles, defaultRole, onInvite, labels, className, ref, ...contentProps }) {
|
|
62
|
+
const resolvedLabels = { ...DEFAULT_LABELS, ...labels };
|
|
63
|
+
const resolvedRoles = resolveRoles(roles);
|
|
64
|
+
const initialRole = resolveDefaultRole(resolvedRoles, defaultRole);
|
|
65
|
+
const isControlled = open !== undefined;
|
|
66
|
+
const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen ?? false);
|
|
67
|
+
const isOpen = isControlled ? open : uncontrolledOpen;
|
|
68
|
+
const [email, setEmail] = useState("");
|
|
69
|
+
const [role, setRole] = useState(initialRole);
|
|
70
|
+
const [loading, setLoading] = useState(false);
|
|
71
|
+
const [error, setError] = useState(null);
|
|
72
|
+
const loadingRef = useRef(false);
|
|
73
|
+
const mountedRef = useRef(true);
|
|
74
|
+
const emailId = useId();
|
|
75
|
+
const roleId = useId();
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
mountedRef.current = true;
|
|
78
|
+
return () => {
|
|
79
|
+
mountedRef.current = false;
|
|
80
|
+
};
|
|
81
|
+
}, []);
|
|
82
|
+
const setOpen = useCallback((next) => {
|
|
83
|
+
if (!isControlled) {
|
|
84
|
+
setUncontrolledOpen(next);
|
|
85
|
+
}
|
|
86
|
+
onOpenChange?.(next);
|
|
87
|
+
}, [isControlled, onOpenChange]);
|
|
88
|
+
const handleOpenChange = useCallback((next) => {
|
|
89
|
+
if (loadingRef.current && !next) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (next) {
|
|
93
|
+
setError(null);
|
|
94
|
+
setEmail("");
|
|
95
|
+
setRole(initialRole);
|
|
96
|
+
}
|
|
97
|
+
setOpen(next);
|
|
98
|
+
}, [initialRole, setOpen]);
|
|
99
|
+
const beginLoading = useCallback((value) => {
|
|
100
|
+
loadingRef.current = value;
|
|
101
|
+
setLoading(value);
|
|
102
|
+
}, []);
|
|
103
|
+
const handleSubmit = useCallback(async (event) => {
|
|
104
|
+
event.preventDefault();
|
|
105
|
+
if (loadingRef.current) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const trimmed = email.trim();
|
|
109
|
+
if (!trimmed) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
setError(null);
|
|
113
|
+
if (!onInvite) {
|
|
114
|
+
setOpen(false);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const result = onInvite({ email: trimmed, role });
|
|
118
|
+
if (!(result instanceof Promise)) {
|
|
119
|
+
setOpen(false);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
beginLoading(true);
|
|
123
|
+
try {
|
|
124
|
+
await result;
|
|
125
|
+
if (!mountedRef.current) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
beginLoading(false);
|
|
129
|
+
setOpen(false);
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
if (!mountedRef.current) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
beginLoading(false);
|
|
136
|
+
setError(resolveErrorMessage(err, resolvedLabels.errorFallback));
|
|
137
|
+
}
|
|
138
|
+
}, [beginLoading, email, onInvite, resolvedLabels.errorFallback, role, setOpen]);
|
|
139
|
+
return (_jsxs(Dialog, { open: isOpen, onOpenChange: handleOpenChange, children: [trigger ? _jsx(DialogTrigger, { asChild: true, children: trigger }) : null, _jsxs(DialogContent, { ref: ref, "data-slot": "invite-dialog", className: cn("max-w-md", className), ...contentProps, children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: resolvedLabels.title }), resolvedLabels.description ? (_jsx(DialogDescription, { children: resolvedLabels.description })) : null] }), _jsxs("form", { className: "flex flex-col gap-4", onSubmit: handleSubmit, children: [_jsxs(Field, { children: [_jsx(FieldLabel, { htmlFor: emailId, children: resolvedLabels.email }), _jsx(Input, { id: emailId, type: "email", name: "email", autoComplete: "email", required: true, value: email, onChange: (event) => setEmail(event.target.value), placeholder: resolvedLabels.emailPlaceholder, disabled: loading })] }), _jsxs(Field, { children: [_jsx(FieldLabel, { htmlFor: roleId, children: resolvedLabels.role }), _jsxs(Select, { value: role, onValueChange: setRole, disabled: loading, children: [_jsx(SelectTrigger, { id: roleId, children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: resolvedRoles.map((item) => (_jsx(SelectItem, { value: item.value, children: item.label }, item.value))) })] })] }), error ? (_jsxs("div", { role: "alert", "data-slot": "invite-dialog-error", className: "flex items-start gap-2 rounded-lg border border-error/30 bg-error/10 px-3 py-2 text-start text-sm text-error-strong", children: [_jsx(CircleAlert, { "aria-hidden": true, className: "mt-0.5 size-4 shrink-0" }), _jsx("span", { children: error })] })) : null, _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "outline", disabled: loading, onClick: () => handleOpenChange(false), children: resolvedLabels.cancel }), _jsxs(Button, { type: "submit", "data-slot": "invite-dialog-send", disabled: loading, "aria-busy": loading, className: "min-w-24", children: [loading ? _jsx(Spinner, { size: "sm", "aria-hidden": true }) : null, loading ? resolvedLabels.sending : resolvedLabels.send] })] })] })] })] }));
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=invite-dialog.js.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type Ref } from "react";
|
|
2
|
+
export interface WorkspaceItem {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
initials?: string;
|
|
6
|
+
image?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface WorkspaceSwitcherLabels {
|
|
9
|
+
/** Accessible name for the trigger. @default "Switch workspace" */
|
|
10
|
+
label?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface WorkspaceSwitcherProps {
|
|
13
|
+
workspaces: readonly WorkspaceItem[];
|
|
14
|
+
/** Controlled selected workspace id. Pair with `onValueChange`. */
|
|
15
|
+
value?: string;
|
|
16
|
+
/** Uncontrolled initial workspace id. Defaults to the first workspace. */
|
|
17
|
+
defaultValue?: string;
|
|
18
|
+
/** Called with the selected workspace id. */
|
|
19
|
+
onValueChange?: (id: string) => void;
|
|
20
|
+
/** Override any subset of the built-in English strings. */
|
|
21
|
+
labels?: WorkspaceSwitcherLabels;
|
|
22
|
+
className?: string;
|
|
23
|
+
/** Forwarded to the trigger button. */
|
|
24
|
+
ref?: Ref<HTMLButtonElement>;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Compact workspace/org switcher for app chrome. The trigger shows the current
|
|
28
|
+
* workspace avatar, name, and a chevron; the menu lists every workspace with
|
|
29
|
+
* the selected row marked as a radio item (`menuitemradio`).
|
|
30
|
+
*
|
|
31
|
+
* Controlled via `value`/`onValueChange` or uncontrolled via `defaultValue`.
|
|
32
|
+
* Renders nothing when `workspaces` is empty. Keyboard access is owned by the
|
|
33
|
+
* underlying (non-modal) dropdown menu.
|
|
34
|
+
*/
|
|
35
|
+
export declare function WorkspaceSwitcher({ workspaces, value, defaultValue, onValueChange, labels, className, ref, }: WorkspaceSwitcherProps): import("react").JSX.Element | null;
|
|
36
|
+
//# sourceMappingURL=workspace-switcher.d.ts.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { ChevronsUpDown } from "lucide-react";
|
|
4
|
+
import { useCallback, useState } from "react";
|
|
5
|
+
import { cn } from "../lib/cn.js";
|
|
6
|
+
import { Avatar, AvatarFallback, AvatarImage } from "./avatar.js";
|
|
7
|
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuTrigger, } from "./dropdown-menu.js";
|
|
8
|
+
const DEFAULT_LABELS = {
|
|
9
|
+
label: "Switch workspace",
|
|
10
|
+
};
|
|
11
|
+
function workspaceInitials(workspace) {
|
|
12
|
+
if (workspace.initials) {
|
|
13
|
+
return workspace.initials;
|
|
14
|
+
}
|
|
15
|
+
const parts = workspace.name.trim().split(/\s+/).filter(Boolean);
|
|
16
|
+
if (parts.length >= 2) {
|
|
17
|
+
const first = parts[0]?.[0] ?? "";
|
|
18
|
+
const second = parts[1]?.[0] ?? "";
|
|
19
|
+
return `${first}${second}`.toUpperCase();
|
|
20
|
+
}
|
|
21
|
+
return workspace.name.slice(0, 2).toUpperCase();
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Compact workspace/org switcher for app chrome. The trigger shows the current
|
|
25
|
+
* workspace avatar, name, and a chevron; the menu lists every workspace with
|
|
26
|
+
* the selected row marked as a radio item (`menuitemradio`).
|
|
27
|
+
*
|
|
28
|
+
* Controlled via `value`/`onValueChange` or uncontrolled via `defaultValue`.
|
|
29
|
+
* Renders nothing when `workspaces` is empty. Keyboard access is owned by the
|
|
30
|
+
* underlying (non-modal) dropdown menu.
|
|
31
|
+
*/
|
|
32
|
+
export function WorkspaceSwitcher({ workspaces, value, defaultValue, onValueChange, labels, className, ref, }) {
|
|
33
|
+
const resolvedLabels = { ...DEFAULT_LABELS, ...labels };
|
|
34
|
+
const isControlled = value !== undefined;
|
|
35
|
+
const [uncontrolled, setUncontrolled] = useState(() => defaultValue ?? workspaces[0]?.id ?? "");
|
|
36
|
+
const selectedId = isControlled ? value : uncontrolled;
|
|
37
|
+
const handleChange = useCallback((id) => {
|
|
38
|
+
if (!isControlled) {
|
|
39
|
+
setUncontrolled(id);
|
|
40
|
+
}
|
|
41
|
+
onValueChange?.(id);
|
|
42
|
+
}, [isControlled, onValueChange]);
|
|
43
|
+
if (workspaces.length === 0) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
const current = workspaces.find((workspace) => workspace.id === selectedId) ?? workspaces[0];
|
|
47
|
+
if (!current) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
const triggerLabel = `${resolvedLabels.label}, ${current.name}`;
|
|
51
|
+
return (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs("button", { ref: ref, type: "button", "data-slot": "workspace-switcher", "aria-label": triggerLabel, className: cn("flex w-full min-w-0 items-center gap-2 rounded-lg px-2 py-1.5 text-sm text-fg outline-none hover:bg-surface-overlay focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface-base disabled:pointer-events-none disabled:opacity-50", className), children: [_jsx(WorkspaceAvatar, { workspace: current }), _jsx("span", { className: "min-w-0 flex-1 truncate text-start font-medium", children: current.name }), _jsx(ChevronsUpDown, { className: "size-4 shrink-0 text-fg-tertiary", "aria-hidden": true })] }) }), _jsx(DropdownMenuContent, { "data-slot": "workspace-switcher-content", align: "start", className: "min-w-56", children: _jsx(DropdownMenuRadioGroup, { value: current.id, onValueChange: handleChange, children: workspaces.map((workspace) => (_jsxs(DropdownMenuRadioItem, { value: workspace.id, className: "gap-2", children: [_jsx(WorkspaceAvatar, { workspace: workspace }), _jsx("span", { className: "min-w-0 flex-1 truncate text-start", children: workspace.name })] }, workspace.id))) }) })] }));
|
|
52
|
+
}
|
|
53
|
+
function WorkspaceAvatar({ workspace }) {
|
|
54
|
+
return (_jsxs(Avatar, { className: "size-6", children: [workspace.image ? _jsx(AvatarImage, { src: workspace.image, alt: "" }) : null, _jsx(AvatarFallback, { className: "text-xs", children: workspaceInitials(workspace) })] }));
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=workspace-switcher.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -142,6 +142,8 @@ export { Input } from "./components/input.js";
|
|
|
142
142
|
export type { InputGroupAddonProps, InputGroupProps } from "./components/input-group.js";
|
|
143
143
|
export { InputGroup, InputGroupAddon } from "./components/input-group.js";
|
|
144
144
|
export { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, } from "./components/input-otp.js";
|
|
145
|
+
export type { InviteDialogLabels, InviteDialogProps, InviteRole, } from "./components/invite-dialog.js";
|
|
146
|
+
export { InviteDialog } from "./components/invite-dialog.js";
|
|
145
147
|
export type { JsonViewerProps } from "./components/json-viewer.js";
|
|
146
148
|
export { JsonViewer } from "./components/json-viewer.js";
|
|
147
149
|
export type { KanbanColumn, KanbanItem, KanbanProps } from "./components/kanban.js";
|
|
@@ -302,5 +304,7 @@ export type { VideoPlayerLabels, VideoPlayerProps } from "./components/video-pla
|
|
|
302
304
|
export { VideoPlayer, videoPlayerVariants } from "./components/video-player.js";
|
|
303
305
|
export type { WordRotateProps } from "./components/word-rotate.js";
|
|
304
306
|
export { WordRotate } from "./components/word-rotate.js";
|
|
307
|
+
export type { WorkspaceItem, WorkspaceSwitcherLabels, WorkspaceSwitcherProps, } from "./components/workspace-switcher.js";
|
|
308
|
+
export { WorkspaceSwitcher } from "./components/workspace-switcher.js";
|
|
305
309
|
export { cn } from "./lib/cn.js";
|
|
306
310
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -85,6 +85,7 @@ export { ImageZoom, imageZoomVariants } from "./components/image-zoom.js";
|
|
|
85
85
|
export { Input } from "./components/input.js";
|
|
86
86
|
export { InputGroup, InputGroupAddon } from "./components/input-group.js";
|
|
87
87
|
export { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, } from "./components/input-otp.js";
|
|
88
|
+
export { InviteDialog } from "./components/invite-dialog.js";
|
|
88
89
|
export { JsonViewer } from "./components/json-viewer.js";
|
|
89
90
|
export { Kanban } from "./components/kanban.js";
|
|
90
91
|
export { Kbd } from "./components/kbd.js";
|
|
@@ -175,5 +176,6 @@ export { TypingText } from "./components/typing-text.js";
|
|
|
175
176
|
export { UsageMeter, UsageMeterCircular, UsageMeterLinear } from "./components/usage-meter.js";
|
|
176
177
|
export { VideoPlayer, videoPlayerVariants } from "./components/video-player.js";
|
|
177
178
|
export { WordRotate } from "./components/word-rotate.js";
|
|
179
|
+
export { WorkspaceSwitcher } from "./components/workspace-switcher.js";
|
|
178
180
|
export { cn } from "./lib/cn.js";
|
|
179
181
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DEMO auth adapter. Gold-path apps replace `lib/auth-adapter.ts` with a
|
|
3
|
+
* Better-Auth implementation that keeps the same named exports
|
|
4
|
+
* (`signInEmail`, `signUpEmail`, `requestPasswordReset`).
|
|
5
|
+
*
|
|
6
|
+
* PURE TS (zero React / hooks / DOM). Gallery and store demos fake success
|
|
7
|
+
* after client-side validation — no network.
|
|
8
|
+
*/
|
|
9
|
+
/** Email + password used by sign-in (and as the base of sign-up). */
|
|
10
|
+
export interface AuthCredentials {
|
|
11
|
+
email: string;
|
|
12
|
+
password: string;
|
|
13
|
+
}
|
|
14
|
+
/** Sign-up payload — same credentials, optional display name. */
|
|
15
|
+
export interface SignUpInput extends AuthCredentials {
|
|
16
|
+
name?: string;
|
|
17
|
+
}
|
|
18
|
+
/** Demo sign-in: validate, wait briefly, resolve. No network. */
|
|
19
|
+
export declare function signInEmail(input: AuthCredentials): Promise<void>;
|
|
20
|
+
/** Demo sign-up: validate, wait briefly, resolve. No network. */
|
|
21
|
+
export declare function signUpEmail(input: SignUpInput): Promise<void>;
|
|
22
|
+
/** Demo password-reset request: validate email, wait briefly, resolve. No network. */
|
|
23
|
+
export declare function requestPasswordReset(input: {
|
|
24
|
+
email: string;
|
|
25
|
+
}): Promise<void>;
|
|
26
|
+
//# sourceMappingURL=auth-adapter.d.ts.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DEMO auth adapter. Gold-path apps replace `lib/auth-adapter.ts` with a
|
|
3
|
+
* Better-Auth implementation that keeps the same named exports
|
|
4
|
+
* (`signInEmail`, `signUpEmail`, `requestPasswordReset`).
|
|
5
|
+
*
|
|
6
|
+
* PURE TS (zero React / hooks / DOM). Gallery and store demos fake success
|
|
7
|
+
* after client-side validation — no network.
|
|
8
|
+
*/
|
|
9
|
+
const DEMO_LATENCY_MS = 200;
|
|
10
|
+
function wait(ms) {
|
|
11
|
+
return new Promise((resolve) => {
|
|
12
|
+
setTimeout(resolve, ms);
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
function requireEmail(email) {
|
|
16
|
+
const trimmed = email.trim();
|
|
17
|
+
if (!trimmed) {
|
|
18
|
+
throw new Error("Email is required.");
|
|
19
|
+
}
|
|
20
|
+
if (!trimmed.includes("@")) {
|
|
21
|
+
throw new Error("Enter a valid email address.");
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function requirePassword(password) {
|
|
25
|
+
if (password.length < 8) {
|
|
26
|
+
throw new Error("Password must be at least 8 characters.");
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** Demo sign-in: validate, wait briefly, resolve. No network. */
|
|
30
|
+
export async function signInEmail(input) {
|
|
31
|
+
requireEmail(input.email);
|
|
32
|
+
requirePassword(input.password);
|
|
33
|
+
await wait(DEMO_LATENCY_MS);
|
|
34
|
+
}
|
|
35
|
+
/** Demo sign-up: validate, wait briefly, resolve. No network. */
|
|
36
|
+
export async function signUpEmail(input) {
|
|
37
|
+
requireEmail(input.email);
|
|
38
|
+
requirePassword(input.password);
|
|
39
|
+
await wait(DEMO_LATENCY_MS);
|
|
40
|
+
}
|
|
41
|
+
/** Demo password-reset request: validate email, wait briefly, resolve. No network. */
|
|
42
|
+
export async function requestPasswordReset(input) {
|
|
43
|
+
requireEmail(input.email);
|
|
44
|
+
await wait(DEMO_LATENCY_MS);
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=auth-adapter.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cronus-ui/ui",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "Cronus UI — themeable, accessible React components (Radix + CVA + Tailwind v4).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -20,6 +20,10 @@
|
|
|
20
20
|
"types": "./dist/lib/demo-saas.d.ts",
|
|
21
21
|
"import": "./dist/lib/demo-saas.js"
|
|
22
22
|
},
|
|
23
|
+
"./auth-adapter": {
|
|
24
|
+
"types": "./dist/lib/auth-adapter.d.ts",
|
|
25
|
+
"import": "./dist/lib/auth-adapter.js"
|
|
26
|
+
},
|
|
23
27
|
"./testing": {
|
|
24
28
|
"types": "./dist/testing/index.d.ts",
|
|
25
29
|
"import": "./dist/testing/index.js"
|
|
@@ -316,6 +320,10 @@
|
|
|
316
320
|
"types": "./dist/components/input-otp.d.ts",
|
|
317
321
|
"import": "./dist/components/input-otp.js"
|
|
318
322
|
},
|
|
323
|
+
"./invite-dialog": {
|
|
324
|
+
"types": "./dist/components/invite-dialog.d.ts",
|
|
325
|
+
"import": "./dist/components/invite-dialog.js"
|
|
326
|
+
},
|
|
319
327
|
"./json-viewer": {
|
|
320
328
|
"types": "./dist/components/json-viewer.d.ts",
|
|
321
329
|
"import": "./dist/components/json-viewer.js"
|
|
@@ -668,6 +676,10 @@
|
|
|
668
676
|
"types": "./dist/components/word-rotate.d.ts",
|
|
669
677
|
"import": "./dist/components/word-rotate.js"
|
|
670
678
|
},
|
|
679
|
+
"./workspace-switcher": {
|
|
680
|
+
"types": "./dist/components/workspace-switcher.d.ts",
|
|
681
|
+
"import": "./dist/components/workspace-switcher.js"
|
|
682
|
+
},
|
|
671
683
|
"./package.json": "./package.json"
|
|
672
684
|
},
|
|
673
685
|
"main": "./dist/index.js",
|
|
@@ -747,7 +759,7 @@
|
|
|
747
759
|
"vaul": "^1.1.2"
|
|
748
760
|
},
|
|
749
761
|
"peerDependencies": {
|
|
750
|
-
"@cronus-ui/theme": "0.6.
|
|
762
|
+
"@cronus-ui/theme": "0.6.2",
|
|
751
763
|
"@dnd-kit/core": "^6.3.1",
|
|
752
764
|
"@dnd-kit/sortable": "^10.0.0",
|
|
753
765
|
"@dnd-kit/utilities": "^3.2.2",
|
|
@@ -891,7 +903,7 @@
|
|
|
891
903
|
}
|
|
892
904
|
},
|
|
893
905
|
"devDependencies": {
|
|
894
|
-
"@cronus-ui/theme": "0.6.
|
|
906
|
+
"@cronus-ui/theme": "0.6.2",
|
|
895
907
|
"@dnd-kit/core": "^6.3.1",
|
|
896
908
|
"@dnd-kit/sortable": "^10.0.0",
|
|
897
909
|
"@dnd-kit/utilities": "^3.2.2",
|