@validationcloud/fractal-ui 1.82.0 → 1.84.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.
@@ -1,42 +1,65 @@
1
- import { default as React } from 'react';
2
1
  import { DropdownMenu } from '../dropdown-menu/dropdown-menu';
3
- type UserDropdownUser = {
2
+ import { IconID } from '../icon/icon';
3
+ export type UserDropdownUser = {
4
4
  email: string;
5
5
  };
6
- type UserDropdownProps = {
7
- user: UserDropdownUser | undefined;
8
- loginUrl: string;
9
- signupUrl: string;
10
- logoutUrl: string;
11
- accountUrl?: string;
12
- /** Extra menu items rendered between account and logout. */
13
- menuItems?: React.ReactNode;
14
- /** Display name shown on the trigger card. Falls back to email prefix when omitted. */
6
+ type UserDropdownRootProps = {
7
+ user: UserDropdownUser;
8
+ /** Display name shown on the trigger. Falls back to the email prefix when omitted. */
15
9
  displayName?: string;
16
- /** Plan tier label (e.g. "Free", "PRO · Monthly") shown below the name on the trigger card. */
10
+ trigger: React.ReactNode;
11
+ /** When true, the dropdown content width matches the trigger width. */
12
+ matchTriggerWidth?: boolean;
13
+ className?: string;
14
+ children: React.ReactNode;
15
+ } & Omit<React.ComponentProps<typeof DropdownMenu>, 'trigger' | 'contentClassName' | 'contentStyle'>;
16
+ declare function UserDropdownRoot({ user, displayName, trigger, matchTriggerWidth, className, children, ...props }: UserDropdownRootProps): import("react/jsx-runtime").JSX.Element;
17
+ type UserDropdownTriggerProps = {
18
+ /** Plan tier label (e.g. "Free", "PRO \u00b7 Monthly") shown below the name. */
17
19
  planLabel?: string;
18
- /** When provided, shows an "Upgrade" badge on the trigger and an upgrade menu item in the dropdown. */
19
- upgradeUrl?: string;
20
- /** When true, renders a compact avatar-only trigger (e.g. when the sidebar is collapsed). */
21
- collapsed?: boolean;
22
- /** When true, renders the trigger with a card-like background (use in sidebar context). */
20
+ /** Render a card-like background (use in sidebar context). */
23
21
  card?: boolean;
24
- } & Omit<React.ComponentProps<typeof DropdownMenu>, 'trigger' | 'contentClassName'>;
25
- /**
26
- * User dropdown menu with a trigger card showing avatar, display name, plan
27
- * tier, and an optional upgrade pill. Opens a dropdown with email, account,
28
- * contact, and logout items.
29
- *
30
- * @example
31
- * <UserDropdown
32
- * user={{ email: 'john.doe@example.com' }}
33
- * displayName="John"
34
- * planLabel="PRO · Monthly"
35
- * loginUrl="/auth/login"
36
- * signupUrl="/auth/login?screen_hint=signup"
37
- * logoutUrl="/auth/logout"
38
- * accountUrl="/account"
39
- * />
40
- */
41
- export declare function UserDropdown({ user, loginUrl, signupUrl, logoutUrl, accountUrl, menuItems, displayName, planLabel, upgradeUrl, collapsed, card, className, ...props }: UserDropdownProps): import("react/jsx-runtime").JSX.Element;
22
+ /** Custom trailing content. Defaults to a chevron icon. */
23
+ trailing?: React.ReactNode;
24
+ } & React.ComponentPropsWithoutRef<'button'>;
25
+ declare function UserDropdownTrigger({ planLabel, card, trailing, className, ...props }: UserDropdownTriggerProps): import("react/jsx-runtime").JSX.Element;
26
+ type UserDropdownAvatarTriggerProps = React.ComponentPropsWithoutRef<'button'>;
27
+ declare function UserDropdownAvatarTrigger({ className, ...props }: UserDropdownAvatarTriggerProps): import("react/jsx-runtime").JSX.Element;
28
+ declare function UserDropdownUpgradeBadge(): import("react/jsx-runtime").JSX.Element;
29
+ declare function UserDropdownEmail(): import("react/jsx-runtime").JSX.Element;
30
+ type UserDropdownItemProps = {
31
+ icon: IconID;
32
+ children: React.ReactNode;
33
+ /** When true, the single child element is rendered as the anchor. Use to integrate with client-side routers (e.g. next/link). */
34
+ asChild?: boolean;
35
+ } & React.ComponentPropsWithoutRef<'a'>;
36
+ declare function UserDropdownItem({ icon, children, asChild, className, ...props }: UserDropdownItemProps): import("react/jsx-runtime").JSX.Element;
37
+ declare function UserDropdownUpgrade({ href }: {
38
+ href: string;
39
+ }): import("react/jsx-runtime").JSX.Element;
40
+ declare function UserDropdownAccount({ href }: {
41
+ href: string;
42
+ }): import("react/jsx-runtime").JSX.Element;
43
+ declare function UserDropdownLogout({ href }: {
44
+ href: string;
45
+ }): import("react/jsx-runtime").JSX.Element;
46
+ type UserDropdownLoggedOutProps = {
47
+ loginUrl: string;
48
+ signupUrl: string;
49
+ className?: string;
50
+ /** Extra menu items rendered inside the mobile dropdown. */
51
+ children?: React.ReactNode;
52
+ };
53
+ declare function UserDropdownLoggedOut({ loginUrl, signupUrl, className, children }: UserDropdownLoggedOutProps): import("react/jsx-runtime").JSX.Element;
54
+ export declare const UserDropdown: typeof UserDropdownRoot & {
55
+ Trigger: typeof UserDropdownTrigger;
56
+ AvatarTrigger: typeof UserDropdownAvatarTrigger;
57
+ UpgradeBadge: typeof UserDropdownUpgradeBadge;
58
+ Email: typeof UserDropdownEmail;
59
+ Item: typeof UserDropdownItem;
60
+ Upgrade: typeof UserDropdownUpgrade;
61
+ Account: typeof UserDropdownAccount;
62
+ Logout: typeof UserDropdownLogout;
63
+ LoggedOut: typeof UserDropdownLoggedOut;
64
+ };
42
65
  export {};
@@ -1,118 +1,154 @@
1
1
  "use client";
2
- import { jsxs as t, Fragment as y, jsx as e } from "react/jsx-runtime";
3
- import z from "../../assets/default-avatar.svg.js";
4
- import { Button as x } from "../button/button.js";
5
- import { DropdownMenu as b, DropdownMenuItem as n } from "../dropdown-menu/dropdown-menu.js";
6
- import { Icon as a } from "../icon/icon.js";
7
- import { InitialsAvatar as N } from "./initials-avatar.js";
8
- import { twMerge as o } from "../../lib/tailwind-merge.js";
9
- function S({
2
+ import { jsx as e, jsxs as a, Fragment as v } from "react/jsx-runtime";
3
+ import { Slot as g } from "radix-ui";
4
+ import { createContext as N, use as U } from "react";
5
+ import D from "../../assets/default-avatar.svg.js";
6
+ import { Button as h } from "../button/button.js";
7
+ import { DropdownMenu as f, DropdownMenuItem as u } from "../dropdown-menu/dropdown-menu.js";
8
+ import { Icon as p } from "../icon/icon.js";
9
+ import { InitialsAvatar as x } from "./initials-avatar.js";
10
+ import { twMerge as l } from "../../lib/tailwind-merge.js";
11
+ const b = N(null);
12
+ function m() {
13
+ const r = U(b);
14
+ if (!r)
15
+ throw new Error("UserDropdown compound components must be rendered inside <UserDropdown>.");
16
+ return r;
17
+ }
18
+ function y({
10
19
  user: r,
11
- loginUrl: c,
12
- signupUrl: d,
13
- logoutUrl: v,
14
- accountUrl: u,
15
- menuItems: m,
16
- displayName: i,
17
- planLabel: h,
18
- upgradeUrl: l,
19
- collapsed: g,
20
- card: p,
21
- className: s,
22
- ...f
20
+ displayName: t,
21
+ trigger: n,
22
+ matchTriggerWidth: o,
23
+ className: i,
24
+ children: s,
25
+ ...c
23
26
  }) {
24
- if (!r)
25
- return /* @__PURE__ */ t(y, { children: [
26
- /* @__PURE__ */ t("div", { className: o("hidden gap-3 sm:flex", s), children: [
27
- /* @__PURE__ */ e(x, { variant: "secondary", size: "medium", asChild: !0, children: /* @__PURE__ */ e("a", { href: c, children: "Log in" }) }),
28
- /* @__PURE__ */ e(x, { variant: "primary", size: "medium", asChild: !0, children: /* @__PURE__ */ e("a", { href: d, children: "Sign up" }) })
29
- ] }),
30
- /* @__PURE__ */ t(
31
- b,
32
- {
33
- trigger: /* @__PURE__ */ e("img", { className: "size-7 cursor-pointer sm:hidden", src: z, alt: "User avatar" }),
34
- className: o(
35
- "hover:bg-neutral-60 focus-visible:ring-neutral-60 data-[state=open]:bg-neutral-60 rounded-full outline-none focus-visible:ring-2 sm:hidden",
36
- s
37
- ),
38
- ...f,
39
- children: [
40
- /* @__PURE__ */ e(n, { asChild: !0, children: /* @__PURE__ */ e("a", { href: c, className: "tg-body", children: "Log in" }) }),
41
- /* @__PURE__ */ e(n, { asChild: !0, children: /* @__PURE__ */ e("a", { href: d, className: "tg-body", children: "Sign up" }) }),
42
- m
43
- ]
44
- }
45
- )
46
- ] });
47
- const w = i ?? r.email.split("@")[0] ?? r.email;
48
- return /* @__PURE__ */ t(
49
- b,
27
+ const w = t ?? r.email.split("@")[0] ?? r.email;
28
+ return /* @__PURE__ */ e(b, { value: { user: r, displayName: w }, children: /* @__PURE__ */ e(
29
+ f,
50
30
  {
51
- trigger: g ? /* @__PURE__ */ e(
52
- "button",
53
- {
54
- type: "button",
55
- "aria-label": "User menu",
56
- className: "group focus-visible:ring-neutral-40 flex h-14 w-16 cursor-pointer items-center justify-center rounded-xl focus:outline-none focus-visible:ring-2",
57
- children: /* @__PURE__ */ e(
58
- N,
59
- {
60
- email: r.email,
61
- displayName: i,
62
- className: "group-hover:brightness-125 group-data-[state=open]:brightness-125"
63
- }
64
- )
65
- }
66
- ) : /* @__PURE__ */ t(
67
- "button",
68
- {
69
- type: "button",
70
- className: o(
71
- "group focus-visible:ring-neutral-40 flex h-14 w-full cursor-pointer items-center gap-4 rounded-xl pr-3.5 pl-4 focus:outline-none focus-visible:ring-2",
72
- p && "bg-neutral-65 hover:bg-neutral-60 shadow-[0_-6px_12px_-4px_rgba(0,0,0,0.25)]"
73
- ),
74
- children: [
75
- /* @__PURE__ */ e(N, { email: r.email, displayName: i, className: "shrink-0" }),
76
- /* @__PURE__ */ t("div", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [
77
- /* @__PURE__ */ e("span", { className: "tg-body-bold text-neutral-10 truncate text-left", children: w }),
78
- h && /* @__PURE__ */ e("span", { className: "tg-caption text-neutral-40 group-data-[state=open]:text-neutral-10 truncate text-left", children: h })
79
- ] }),
80
- l ? /* @__PURE__ */ e("span", { className: "tg-caption-bold text-neutral-20 shrink-0 rounded-full border border-neutral-50 px-2.5 py-0.5", children: "Upgrade" }) : /* @__PURE__ */ e(
81
- a,
82
- {
83
- icon: "chevron-vertical",
84
- className: "text-neutral-40 group-data-[state=open]:text-neutral-10 size-3.5 shrink-0"
85
- }
86
- )
87
- ]
88
- }
89
- ),
90
- className: s,
31
+ trigger: n,
32
+ className: i,
91
33
  contentClassName: "bg-neutral-60",
92
- contentStyle: p && !g ? { width: "var(--radix-dropdown-menu-trigger-width)" } : void 0,
93
- ...f,
34
+ contentStyle: o ? { width: "var(--radix-dropdown-menu-trigger-width)" } : void 0,
35
+ ...c,
36
+ children: s
37
+ }
38
+ ) });
39
+ }
40
+ function C({ planLabel: r, card: t, trailing: n, className: o, ...i }) {
41
+ const { user: s, displayName: c } = m();
42
+ return /* @__PURE__ */ a(
43
+ "button",
44
+ {
45
+ type: "button",
46
+ className: l(
47
+ "group focus-visible:ring-neutral-40 flex h-14 w-full cursor-pointer items-center gap-4 rounded-xl pr-3.5 pl-4 focus:outline-none focus-visible:ring-2",
48
+ t && "bg-neutral-65 hover:bg-neutral-60 shadow-[0_-6px_12px_-4px_rgba(0,0,0,0.25)]",
49
+ o
50
+ ),
51
+ ...i,
94
52
  children: [
95
- l && /* @__PURE__ */ e(n, { asChild: !0, children: /* @__PURE__ */ t("a", { href: l, className: "flex items-center gap-3", children: [
96
- /* @__PURE__ */ e(a, { icon: "sparkles", className: "size-4 flex-none text-white" }),
97
- /* @__PURE__ */ e("p", { className: "tg-body", children: "Upgrade plan" })
98
- ] }) }),
99
- /* @__PURE__ */ t("div", { className: "tg-button-small text-neutral-40 flex items-center gap-3 overflow-hidden px-4 py-2.5 select-text", children: [
100
- /* @__PURE__ */ e(a, { icon: "user", className: "size-4 flex-none" }),
101
- /* @__PURE__ */ e("p", { className: "tg-body min-w-0 truncate", children: r.email })
53
+ /* @__PURE__ */ e(x, { email: s.email, displayName: c, className: "shrink-0" }),
54
+ /* @__PURE__ */ a("div", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [
55
+ /* @__PURE__ */ e("span", { className: "tg-body-bold text-neutral-10 truncate text-left", children: c }),
56
+ r && /* @__PURE__ */ e("span", { className: "tg-caption text-neutral-40 group-data-[state=open]:text-neutral-10 truncate text-left", children: r })
102
57
  ] }),
103
- u && /* @__PURE__ */ e(n, { asChild: !0, children: /* @__PURE__ */ t("a", { href: u, className: "flex items-center gap-3", children: [
104
- /* @__PURE__ */ e(a, { icon: "cog", className: "size-4 flex-none text-white" }),
105
- /* @__PURE__ */ e("p", { className: "tg-body", children: "Account" })
106
- ] }) }),
107
- m,
108
- /* @__PURE__ */ e(n, { asChild: !0, children: /* @__PURE__ */ t("a", { href: v, className: "flex items-center gap-3", children: [
109
- /* @__PURE__ */ e(a, { icon: "logout", className: "size-4 flex-none text-white" }),
110
- /* @__PURE__ */ e("p", { className: "tg-body", children: "Log out" })
111
- ] }) })
58
+ n ?? /* @__PURE__ */ e(
59
+ p,
60
+ {
61
+ icon: "chevron-vertical",
62
+ className: "text-neutral-40 group-data-[state=open]:text-neutral-10 size-3.5 shrink-0"
63
+ }
64
+ )
112
65
  ]
113
66
  }
114
67
  );
115
68
  }
69
+ function A({ className: r, ...t }) {
70
+ const { user: n, displayName: o } = m();
71
+ return /* @__PURE__ */ e(
72
+ "button",
73
+ {
74
+ type: "button",
75
+ "aria-label": "User menu",
76
+ className: l(
77
+ "group focus-visible:ring-neutral-40 flex h-14 w-16 cursor-pointer items-center justify-center rounded-xl focus:outline-none focus-visible:ring-2",
78
+ r
79
+ ),
80
+ ...t,
81
+ children: /* @__PURE__ */ e(
82
+ x,
83
+ {
84
+ email: n.email,
85
+ displayName: o,
86
+ className: "group-hover:brightness-125 group-data-[state=open]:brightness-125"
87
+ }
88
+ )
89
+ }
90
+ );
91
+ }
92
+ function z() {
93
+ return /* @__PURE__ */ e("span", { className: "tg-caption-bold text-neutral-20 shrink-0 rounded-full border border-neutral-50 px-2.5 py-0.5", children: "Upgrade" });
94
+ }
95
+ function L() {
96
+ const { user: r } = m();
97
+ return /* @__PURE__ */ a("div", { className: "tg-button-small text-neutral-40 flex items-center gap-3 overflow-hidden px-4 py-2.5 select-text", children: [
98
+ /* @__PURE__ */ e(p, { icon: "user", className: "size-4 flex-none" }),
99
+ /* @__PURE__ */ e("p", { className: "tg-body min-w-0 truncate", children: r.email })
100
+ ] });
101
+ }
102
+ function d({ icon: r, children: t, asChild: n, className: o, ...i }) {
103
+ const s = n ? g.Root : "a";
104
+ return /* @__PURE__ */ e(u, { asChild: !0, children: /* @__PURE__ */ a(s, { ...i, className: l("tg-body flex items-center gap-3", o), children: [
105
+ /* @__PURE__ */ e(p, { icon: r, className: "size-4 flex-none text-white" }),
106
+ /* @__PURE__ */ e(g.Slottable, { children: t })
107
+ ] }) });
108
+ }
109
+ function I({ href: r }) {
110
+ return /* @__PURE__ */ e(d, { href: r, icon: "sparkles", children: "Upgrade plan" });
111
+ }
112
+ function S({ href: r }) {
113
+ return /* @__PURE__ */ e(d, { href: r, icon: "cog", children: "Account" });
114
+ }
115
+ function j({ href: r }) {
116
+ return /* @__PURE__ */ e(d, { href: r, icon: "logout", children: "Log out" });
117
+ }
118
+ function k({ loginUrl: r, signupUrl: t, className: n, children: o }) {
119
+ return /* @__PURE__ */ a(v, { children: [
120
+ /* @__PURE__ */ a("div", { className: l("hidden gap-3 sm:flex", n), children: [
121
+ /* @__PURE__ */ e(h, { variant: "secondary", size: "medium", asChild: !0, children: /* @__PURE__ */ e("a", { href: r, children: "Log in" }) }),
122
+ /* @__PURE__ */ e(h, { variant: "primary", size: "medium", asChild: !0, children: /* @__PURE__ */ e("a", { href: t, children: "Sign up" }) })
123
+ ] }),
124
+ /* @__PURE__ */ a(
125
+ f,
126
+ {
127
+ trigger: /* @__PURE__ */ e("img", { className: "size-7 cursor-pointer sm:hidden", src: D, alt: "User avatar" }),
128
+ className: l(
129
+ "hover:bg-neutral-60 focus-visible:ring-neutral-60 data-[state=open]:bg-neutral-60 rounded-full outline-none focus-visible:ring-2 sm:hidden",
130
+ n
131
+ ),
132
+ children: [
133
+ /* @__PURE__ */ e(u, { asChild: !0, children: /* @__PURE__ */ e("a", { href: r, className: "tg-body", children: "Log in" }) }),
134
+ /* @__PURE__ */ e(u, { asChild: !0, children: /* @__PURE__ */ e("a", { href: t, className: "tg-body", children: "Sign up" }) }),
135
+ o
136
+ ]
137
+ }
138
+ )
139
+ ] });
140
+ }
141
+ const G = Object.assign(y, {
142
+ Trigger: C,
143
+ AvatarTrigger: A,
144
+ UpgradeBadge: z,
145
+ Email: L,
146
+ Item: d,
147
+ Upgrade: I,
148
+ Account: S,
149
+ Logout: j,
150
+ LoggedOut: k
151
+ });
116
152
  export {
117
- S as UserDropdown
153
+ G as UserDropdown
118
154
  };
package/dist/index.d.ts CHANGED
@@ -25,5 +25,5 @@ export { Tooltip } from './components/tooltip/tooltip';
25
25
  export { TooltipProvider } from './components/tooltip-provider/tooltip-provider';
26
26
  export { TouchTarget } from './components/touch-target/touch-target';
27
27
  export { twMerge } from './lib/tailwind-merge';
28
- export { UserDropdown } from './components/user-dropdown/user-dropdown';
28
+ export { UserDropdown, type UserDropdownUser } from './components/user-dropdown/user-dropdown';
29
29
  export { useScrollToBottom } from './hooks/use-scroll-to-bottom';
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@validationcloud/fractal-ui",
3
3
  "description": "Validation Cloud's shared React component library with design tokens, Tailwind CSS utilities, and CLI tools",
4
4
  "private": false,
5
- "version": "1.82.0",
5
+ "version": "1.84.0",
6
6
  "module": "./dist/index.js",
7
7
  "type": "module",
8
8
  "bin": {