@better-auth-ui/heroui 1.6.5 → 1.6.6

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.
@@ -9,7 +9,7 @@ function a({ className: a, variant: o, ...s }) {
9
9
  className: n("flex w-full flex-col", a),
10
10
  ...s,
11
11
  children: [/* @__PURE__ */ r("h2", {
12
- className: n("text-sm font-semibold mb-3"),
12
+ className: n("text-sm font-semibold mb-3 text-danger"),
13
13
  children: c.settings.dangerZone
14
14
  }), /* @__PURE__ */ r(e, { variant: o })]
15
15
  });
@@ -2,88 +2,88 @@ import { deleteUserPlugin as e } from "../../../lib/auth/delete-user-plugin.js";
2
2
  import { authQueryKeys as t } from "@better-auth-ui/core";
3
3
  import { useAuth as n, useAuthPlugin as r, useDeleteUser as i, useListAccounts as a } from "@better-auth-ui/react";
4
4
  import { TriangleExclamation as o } from "@gravity-ui/icons";
5
- import { AlertDialog as s, Button as c, Card as l, FieldError as u, Form as d, Input as f, Label as p, Spinner as m, TextField as h, cn as g, toast as _ } from "@heroui/react";
6
- import { useState as v } from "react";
7
- import { jsx as y, jsxs as b } from "react/jsx-runtime";
8
- import { useQueryClient as x } from "@tanstack/react-query";
5
+ import { AlertDialog as s, Button as c, Card as l, FieldError as u, Form as d, Input as f, Label as p, Spinner as m, TextField as h, toast as g } from "@heroui/react";
6
+ import { useState as _ } from "react";
7
+ import { jsx as v, jsxs as y } from "react/jsx-runtime";
8
+ import { useQueryClient as b } from "@tanstack/react-query";
9
9
  //#region src/components/auth/delete-user/delete-user.tsx
10
- function S({ className: S, variant: C, ...w }) {
11
- let { authClient: T, basePaths: E, localization: D, navigate: O, viewPaths: k } = n(), { localization: A, sendDeleteAccountVerification: j } = r(e), { data: M } = a(T), N = x(), [P, F] = v(!1), [I, L] = v(""), R = M?.some((e) => e.providerId === "credential"), z = !j && R, { mutate: B, isPending: V } = i(T), H = (e) => {
12
- F(e), L("");
13
- }, U = async (e) => {
14
- e.preventDefault(), B({ ...z ? { password: I } : {} }, { onSuccess: () => {
15
- F(!1), L(""), j ? _.success(A.deleteUserVerificationSent) : (_.success(A.deleteUserSuccess), N.removeQueries({ queryKey: t.all }), O({
16
- to: `${E.auth}/${k.auth.signIn}`,
10
+ function x({ className: x, variant: S, ...C }) {
11
+ let { authClient: w, basePaths: T, localization: E, navigate: D, viewPaths: O } = n(), { localization: k, sendDeleteAccountVerification: A } = r(e), { data: j } = a(w), M = b(), [N, P] = _(!1), [F, I] = _(""), L = j?.some((e) => e.providerId === "credential"), R = !A && L, { mutate: z, isPending: B } = i(w), V = (e) => {
12
+ P(e), I("");
13
+ }, H = async (e) => {
14
+ e.preventDefault(), z({ ...R ? { password: F } : {} }, { onSuccess: () => {
15
+ P(!1), I(""), A ? g.success(k.deleteUserVerificationSent) : (g.success(k.deleteUserSuccess), M.removeQueries({ queryKey: t.all }), D({
16
+ to: `${T.auth}/${O.auth.signIn}`,
17
17
  replace: !0
18
18
  }));
19
19
  } });
20
20
  };
21
- return /* @__PURE__ */ y(l, {
22
- className: g("border border-danger", S),
23
- variant: C,
24
- ...w,
25
- children: /* @__PURE__ */ b(l.Content, {
21
+ return /* @__PURE__ */ v(l, {
22
+ className: x,
23
+ variant: S,
24
+ ...C,
25
+ children: /* @__PURE__ */ y(l.Content, {
26
26
  className: "flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between",
27
- children: [/* @__PURE__ */ b("div", { children: [/* @__PURE__ */ y("p", {
27
+ children: [/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("p", {
28
28
  className: "text-sm font-medium leading-tight",
29
- children: A.deleteUser
30
- }), /* @__PURE__ */ y("p", {
29
+ children: k.deleteUser
30
+ }), /* @__PURE__ */ v("p", {
31
31
  className: "text-muted text-xs mt-0.5",
32
- children: A.deleteUserDescription
33
- })] }), /* @__PURE__ */ b(s, { children: [/* @__PURE__ */ y(c, {
34
- isDisabled: !M,
32
+ children: k.deleteUserDescription
33
+ })] }), /* @__PURE__ */ y(s, { children: [/* @__PURE__ */ v(c, {
34
+ isDisabled: !j,
35
35
  size: "sm",
36
- variant: "danger",
37
- onPress: () => F(!0),
38
- children: A.deleteUser
39
- }), /* @__PURE__ */ y(s.Backdrop, {
40
- isOpen: P,
41
- onOpenChange: H,
42
- children: /* @__PURE__ */ y(s.Container, { children: /* @__PURE__ */ y(s.Dialog, { children: /* @__PURE__ */ b(d, {
43
- onSubmit: U,
36
+ variant: "danger-soft",
37
+ onPress: () => P(!0),
38
+ children: k.deleteUser
39
+ }), /* @__PURE__ */ v(s.Backdrop, {
40
+ isOpen: N,
41
+ onOpenChange: V,
42
+ children: /* @__PURE__ */ v(s.Container, { children: /* @__PURE__ */ v(s.Dialog, { children: /* @__PURE__ */ y(d, {
43
+ onSubmit: H,
44
44
  children: [
45
- /* @__PURE__ */ y(s.CloseTrigger, {}),
46
- /* @__PURE__ */ b(s.Header, { children: [/* @__PURE__ */ y(s.Icon, {
45
+ /* @__PURE__ */ v(s.CloseTrigger, {}),
46
+ /* @__PURE__ */ y(s.Header, { children: [/* @__PURE__ */ v(s.Icon, {
47
47
  status: "danger",
48
- children: /* @__PURE__ */ y(o, {})
49
- }), /* @__PURE__ */ y(s.Heading, { children: A.deleteUser })] }),
50
- /* @__PURE__ */ b(s.Body, {
48
+ children: /* @__PURE__ */ v(o, {})
49
+ }), /* @__PURE__ */ v(s.Heading, { children: k.deleteUser })] }),
50
+ /* @__PURE__ */ y(s.Body, {
51
51
  className: "overflow-visible",
52
- children: [/* @__PURE__ */ y("p", {
52
+ children: [/* @__PURE__ */ v("p", {
53
53
  className: "text-muted text-sm",
54
- children: A.deleteUserDescription
55
- }), z && /* @__PURE__ */ b(h, {
54
+ children: k.deleteUserDescription
55
+ }), R && /* @__PURE__ */ y(h, {
56
56
  className: "mt-4",
57
57
  name: "password",
58
58
  type: "password",
59
- isDisabled: V,
60
- value: I,
61
- onChange: L,
59
+ isDisabled: B,
60
+ value: F,
61
+ onChange: I,
62
62
  children: [
63
- /* @__PURE__ */ y(p, { children: D.auth.password }),
64
- /* @__PURE__ */ y(f, {
63
+ /* @__PURE__ */ v(p, { children: E.auth.password }),
64
+ /* @__PURE__ */ v(f, {
65
65
  autoComplete: "current-password",
66
- placeholder: D.auth.passwordPlaceholder,
66
+ placeholder: E.auth.passwordPlaceholder,
67
67
  required: !0,
68
68
  variant: "secondary"
69
69
  }),
70
- /* @__PURE__ */ y(u, {})
70
+ /* @__PURE__ */ v(u, {})
71
71
  ]
72
72
  })]
73
73
  }),
74
- /* @__PURE__ */ b(s.Footer, { children: [/* @__PURE__ */ y(c, {
74
+ /* @__PURE__ */ y(s.Footer, { children: [/* @__PURE__ */ v(c, {
75
75
  slot: "close",
76
76
  variant: "tertiary",
77
- isDisabled: V,
78
- children: D.settings.cancel
79
- }), /* @__PURE__ */ b(c, {
77
+ isDisabled: B,
78
+ children: E.settings.cancel
79
+ }), /* @__PURE__ */ y(c, {
80
80
  type: "submit",
81
81
  variant: "danger",
82
- isPending: V,
83
- children: [V && /* @__PURE__ */ y(m, {
82
+ isPending: B,
83
+ children: [B && /* @__PURE__ */ v(m, {
84
84
  color: "current",
85
85
  size: "sm"
86
- }), A.deleteUser]
86
+ }), k.deleteUser]
87
87
  })] })
88
88
  ]
89
89
  }) }) })
@@ -92,4 +92,4 @@ function S({ className: S, variant: C, ...w }) {
92
92
  });
93
93
  }
94
94
  //#endregion
95
- export { S as DeleteUser };
95
+ export { x as DeleteUser };
@@ -1,58 +1,62 @@
1
1
  import { themePlugin as e } from "../../../lib/auth/theme-plugin.js";
2
2
  import { useAuthPlugin as t } from "@better-auth-ui/react";
3
- import { Display as n, Moon as r, Sun as i } from "@gravity-ui/icons";
4
- import { Dropdown as a, Label as o, Tabs as s } from "@heroui/react";
5
- import { jsx as c, jsxs as l } from "react/jsx-runtime";
3
+ import { Display as n, Moon as r, Palette as i, Sun as a } from "@gravity-ui/icons";
4
+ import { Dropdown as o, Label as s, Tabs as c } from "@heroui/react";
5
+ import { jsx as l, jsxs as u } from "react/jsx-runtime";
6
6
  //#region src/components/auth/theme/theme-toggle-item.tsx
7
- var u = "[role=\"tab\"][aria-selected=\"true\"]";
8
- function d() {
9
- let { useTheme: d, localization: f } = t(e), { theme: p, setTheme: m, themes: h = [] } = d();
10
- return /* @__PURE__ */ l(a.Item, {
7
+ var d = "[role=\"tab\"][aria-selected=\"true\"]";
8
+ function f() {
9
+ let { useTheme: f, localization: p } = t(e), { theme: m, setTheme: h, themes: g = [] } = f();
10
+ return /* @__PURE__ */ u(o.Item, {
11
11
  className: "py-1 pe-2",
12
12
  onFocus: (e) => {
13
- e.target === e.currentTarget && e.currentTarget.querySelector(u)?.focus({ preventScroll: !0 });
13
+ e.target === e.currentTarget && e.currentTarget.querySelector(d)?.focus({ preventScroll: !0 });
14
14
  },
15
15
  shouldCloseOnSelect: !1,
16
- children: [/* @__PURE__ */ c(o, { children: f.theme }), /* @__PURE__ */ c(s, {
17
- className: "ml-auto",
18
- selectedKey: p,
19
- onSelectionChange: (e) => m(e),
20
- children: /* @__PURE__ */ c(s.ListContainer, {
21
- onKeyDown: (e) => {
22
- if (e.key !== "ArrowUp" && e.key !== "ArrowDown") return;
23
- let t = e.target;
24
- if (t.getAttribute("role") !== "tab") return;
25
- let n = t.closest("[role=\"menuitem\"]"), r = n?.closest("[role=\"menu\"]");
26
- if (!n || !r) return;
27
- let i = Array.from(r.querySelectorAll("[role=\"menuitem\"]:not([data-disabled])")), a = i.indexOf(n);
28
- if (a === -1) return;
29
- let o = i[e.key === "ArrowDown" ? a + 1 : a - 1];
30
- o && (e.preventDefault(), o.focus());
31
- },
32
- children: /* @__PURE__ */ l(s.List, {
33
- "aria-label": f.theme,
34
- className: "*:h-5 *:w-5 *:p-0",
35
- children: [
36
- h.includes("system") && /* @__PURE__ */ l(s.Tab, {
37
- id: "system",
38
- "aria-label": f.system,
39
- children: [/* @__PURE__ */ c(n, { className: "size-3" }), /* @__PURE__ */ c(s.Indicator, {})]
40
- }),
41
- h.includes("light") && /* @__PURE__ */ l(s.Tab, {
42
- id: "light",
43
- "aria-label": f.light,
44
- children: [/* @__PURE__ */ c(i, { className: "size-3" }), /* @__PURE__ */ c(s.Indicator, {})]
45
- }),
46
- h.includes("dark") && /* @__PURE__ */ l(s.Tab, {
47
- id: "dark",
48
- "aria-label": f.dark,
49
- children: [/* @__PURE__ */ c(r, { className: "size-3" }), /* @__PURE__ */ c(s.Indicator, {})]
50
- })
51
- ]
16
+ children: [
17
+ /* @__PURE__ */ l(i, { className: "text-muted" }),
18
+ /* @__PURE__ */ l(s, { children: p.theme }),
19
+ /* @__PURE__ */ l(c, {
20
+ className: "ml-auto",
21
+ selectedKey: m,
22
+ onSelectionChange: (e) => h(e),
23
+ children: /* @__PURE__ */ l(c.ListContainer, {
24
+ onKeyDown: (e) => {
25
+ if (e.key !== "ArrowUp" && e.key !== "ArrowDown") return;
26
+ let t = e.target;
27
+ if (t.getAttribute("role") !== "tab") return;
28
+ let n = t.closest("[role=\"menuitem\"]"), r = n?.closest("[role=\"menu\"]");
29
+ if (!n || !r) return;
30
+ let i = Array.from(r.querySelectorAll("[role=\"menuitem\"]:not([data-disabled])")), a = i.indexOf(n);
31
+ if (a === -1) return;
32
+ let o = i[e.key === "ArrowDown" ? a + 1 : a - 1];
33
+ o && (e.preventDefault(), o.focus());
34
+ },
35
+ children: /* @__PURE__ */ u(c.List, {
36
+ "aria-label": p.theme,
37
+ className: "*:h-5 *:w-5 *:p-0",
38
+ children: [
39
+ g.includes("system") && /* @__PURE__ */ u(c.Tab, {
40
+ id: "system",
41
+ "aria-label": p.system,
42
+ children: [/* @__PURE__ */ l(n, { className: "size-3" }), /* @__PURE__ */ l(c.Indicator, {})]
43
+ }),
44
+ g.includes("light") && /* @__PURE__ */ u(c.Tab, {
45
+ id: "light",
46
+ "aria-label": p.light,
47
+ children: [/* @__PURE__ */ l(a, { className: "size-3" }), /* @__PURE__ */ l(c.Indicator, {})]
48
+ }),
49
+ g.includes("dark") && /* @__PURE__ */ u(c.Tab, {
50
+ id: "dark",
51
+ "aria-label": p.dark,
52
+ children: [/* @__PURE__ */ l(r, { className: "size-3" }), /* @__PURE__ */ l(c.Indicator, {})]
53
+ })
54
+ ]
55
+ })
52
56
  })
53
57
  })
54
- })]
58
+ ]
55
59
  });
56
60
  }
57
61
  //#endregion
58
- export { d as ThemeToggleItem };
62
+ export { f as ThemeToggleItem };
@@ -1,4 +1,23 @@
1
- import { ButtonProps, DropdownPopoverProps } from '@heroui/react';
1
+ import { ButtonProps, DropdownItemProps, DropdownPopoverProps } from '@heroui/react';
2
+ import { ReactElement, ReactNode } from 'react';
3
+ /** Auth states a `UserButton` link can be visible in. */
4
+ export type UserButtonLinkVisibility = "authenticated" | "unauthenticated" | "always";
5
+ /** A simple link entry rendered as a `Dropdown.Item` in the `UserButton` menu. */
6
+ export type UserButtonLink = {
7
+ /** Visible label. */
8
+ label: ReactNode;
9
+ /** Destination URL. */
10
+ href: string;
11
+ /** Optional leading icon. Sized/coloured to match built-in items. */
12
+ icon?: ReactNode;
13
+ /** Forwarded to the underlying `Dropdown.Item`. */
14
+ variant?: DropdownItemProps["variant"];
15
+ /**
16
+ * When this link is visible based on auth state.
17
+ * @default "always"
18
+ */
19
+ visibility?: UserButtonLinkVisibility;
20
+ };
2
21
  export type UserButtonProps = {
3
22
  className?: string;
4
23
  size?: "default" | "icon";
@@ -8,6 +27,10 @@ export type UserButtonProps = {
8
27
  */
9
28
  placement?: DropdownPopoverProps["placement"];
10
29
  variant?: ButtonProps["variant"];
30
+ /** Additional menu entries rendered above the built-in items. */
31
+ links?: (UserButtonLink | ReactElement)[];
32
+ /** Hide the built-in "Settings" link. Useful when replacing it via `links`. */
33
+ hideSettings?: boolean;
11
34
  };
12
35
  /**
13
36
  * Render a user account dropdown button that shows account actions.
@@ -16,6 +39,8 @@ export type UserButtonProps = {
16
39
  * @param placement - Dropdown popover placement (e.g., "bottom", "top-start", "bottom-end")
17
40
  * @param size - "icon" renders an avatar-only trigger; "default" renders a button with label and chevron
18
41
  * @param variant - Button visual variant passed to the underlying Button component
42
+ * @param links - Additional menu entries rendered above the built-in items
43
+ * @param hideSettings - Hide the built-in "Settings" link
19
44
  * @returns The user button and its dropdown menu as a JSX element
20
45
  */
21
- export declare function UserButton({ className, placement, size, variant }: UserButtonProps): import("react/jsx-runtime").JSX.Element;
46
+ export declare function UserButton({ className, placement, size, variant, links, hideSettings }: UserButtonProps): import("react/jsx-runtime").JSX.Element;
@@ -2,54 +2,73 @@ import { UserAvatar as e } from "./user-avatar.js";
2
2
  import { UserView as t } from "./user-view.js";
3
3
  import { useAuth as n, useSession as r } from "@better-auth-ui/react";
4
4
  import { ArrowRightFromSquare as i, ArrowRightToSquare as a, ChevronsExpandVertical as o, Gear as s, PersonPlus as c } from "@gravity-ui/icons";
5
- import { Button as l, Dropdown as u, Label as d, Separator as f, cn as p } from "@heroui/react";
5
+ import { Button as l, Dropdown as u, Label as d, cn as f } from "@heroui/react";
6
+ import { isValidElement as p } from "react";
6
7
  import { Fragment as m, jsx as h, jsxs as g } from "react/jsx-runtime";
7
8
  //#region src/components/auth/user/user-button.tsx
8
- function _({ className: _, placement: v = "bottom", size: y = "default", variant: b = "ghost" }) {
9
- let { authClient: x, basePaths: S, viewPaths: C, localization: w, plugins: T } = n(), { data: E, isPending: D } = r(x), O = T.flatMap((e) => e.userMenuItems?.map((t, n) => /* @__PURE__ */ h(t, {}, `${e.id}-${n.toString()}`)) ?? []);
10
- return /* @__PURE__ */ g(u, { children: [y === "icon" ? /* @__PURE__ */ h(u.Trigger, {
11
- className: p("rounded-full", _),
9
+ function _(e, t) {
10
+ if (p(e)) return e;
11
+ let { label: n, href: r, icon: i, variant: a } = e;
12
+ return /* @__PURE__ */ g(u.Item, {
13
+ href: r,
14
+ variant: a,
15
+ textValue: typeof n == "string" ? n : void 0,
16
+ children: [i, /* @__PURE__ */ h(d, { children: n })]
17
+ }, t);
18
+ }
19
+ function v({ className: v, placement: y = "bottom", size: b = "default", variant: x = "ghost", links: S, hideSettings: C = !1 }) {
20
+ let { authClient: w, basePaths: T, viewPaths: E, localization: D, plugins: O } = n(), { data: k, isPending: A } = r(w), j = O.flatMap((e) => e.userMenuItems?.map((t, n) => /* @__PURE__ */ h(t, {}, `${e.id}-${n.toString()}`)) ?? []), M = S?.flatMap((e, t) => {
21
+ if (!p(e)) {
22
+ let t = e.visibility ?? "always";
23
+ if (t === "authenticated" && !k || t === "unauthenticated" && k) return [];
24
+ }
25
+ return [_(e, `user-button-link-${t.toString()}`)];
26
+ });
27
+ return /* @__PURE__ */ g(u, { children: [b === "icon" ? /* @__PURE__ */ h(u.Trigger, {
28
+ className: f("rounded-full", v),
12
29
  children: /* @__PURE__ */ h(e, {})
13
30
  }) : /* @__PURE__ */ g(l, {
14
- variant: b,
15
- className: p("h-auto font-normal justify-start px-3 py-2 text-left", _),
16
- children: [E || D ? /* @__PURE__ */ h(t, { isPending: D }) : /* @__PURE__ */ g(m, { children: [/* @__PURE__ */ h(e, {}), /* @__PURE__ */ h("p", {
31
+ variant: x,
32
+ className: f("h-auto font-normal justify-start px-3 py-2 text-left", v),
33
+ children: [k || A ? /* @__PURE__ */ h(t, { isPending: A }) : /* @__PURE__ */ g(m, { children: [/* @__PURE__ */ h(e, {}), /* @__PURE__ */ h("p", {
17
34
  className: "text-sm font-medium",
18
- children: w.auth.account
35
+ children: D.auth.account
19
36
  })] }), /* @__PURE__ */ h(o, { className: "ml-auto size-3.5" })]
20
37
  }), /* @__PURE__ */ g(u.Popover, {
21
- placement: v,
38
+ placement: y,
22
39
  className: "min-w-40 md:min-w-56 max-w-[48svw]",
23
- children: [E && /* @__PURE__ */ h("div", {
40
+ children: [k && /* @__PURE__ */ h("div", {
24
41
  className: "px-3 pt-3 pb-1",
25
42
  children: /* @__PURE__ */ h(t, {})
26
- }), /* @__PURE__ */ h(u.Menu, { children: E ? /* @__PURE__ */ g(m, { children: [
27
- /* @__PURE__ */ g(u.Item, {
28
- textValue: w.settings.settings,
29
- href: `${S.settings}/${C.settings.account}`,
30
- children: [/* @__PURE__ */ h(s, { className: "text-muted" }), /* @__PURE__ */ h(d, { children: w.settings.settings })]
43
+ }), /* @__PURE__ */ h(u.Menu, { children: k ? /* @__PURE__ */ g(m, { children: [
44
+ M,
45
+ !C && /* @__PURE__ */ g(u.Item, {
46
+ textValue: D.settings.settings,
47
+ href: `${T.settings}/${E.settings.account}`,
48
+ children: [/* @__PURE__ */ h(s, { className: "text-muted" }), /* @__PURE__ */ h(d, { children: D.settings.settings })]
31
49
  }),
32
- O,
33
- /* @__PURE__ */ h(f, {}),
50
+ j,
34
51
  /* @__PURE__ */ g(u.Item, {
35
- textValue: w.auth.signOut,
36
- href: `${S.auth}/${C.auth.signOut}`,
37
- children: [/* @__PURE__ */ h(i, { className: "text-muted" }), /* @__PURE__ */ h(d, { children: w.auth.signOut })]
52
+ textValue: D.auth.signOut,
53
+ href: `${T.auth}/${E.auth.signOut}`,
54
+ variant: "danger",
55
+ children: [/* @__PURE__ */ h(i, { className: "text-danger" }), /* @__PURE__ */ h(d, { children: D.auth.signOut })]
38
56
  })
39
57
  ] }) : /* @__PURE__ */ g(m, { children: [
58
+ M,
40
59
  /* @__PURE__ */ g(u.Item, {
41
- textValue: w.auth.signIn,
42
- href: `${S.auth}/${C.auth.signIn}`,
43
- children: [/* @__PURE__ */ h(a, { className: "text-muted" }), /* @__PURE__ */ h(d, { children: w.auth.signIn })]
60
+ textValue: D.auth.signIn,
61
+ href: `${T.auth}/${E.auth.signIn}`,
62
+ children: [/* @__PURE__ */ h(a, { className: "text-muted" }), /* @__PURE__ */ h(d, { children: D.auth.signIn })]
44
63
  }),
45
64
  /* @__PURE__ */ g(u.Item, {
46
- textValue: w.auth.signUp,
47
- href: `${S.auth}/${C.auth.signUp}`,
48
- children: [/* @__PURE__ */ h(c, { className: "text-muted" }), /* @__PURE__ */ h(d, { children: w.auth.signUp })]
65
+ textValue: D.auth.signUp,
66
+ href: `${T.auth}/${E.auth.signUp}`,
67
+ children: [/* @__PURE__ */ h(c, { className: "text-muted" }), /* @__PURE__ */ h(d, { children: D.auth.signUp })]
49
68
  }),
50
- O
69
+ j
51
70
  ] }) })]
52
71
  })] });
53
72
  }
54
73
  //#endregion
55
- export { _ as UserButton };
74
+ export { v as UserButton };
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@better-auth-ui/heroui",
3
- "version": "1.6.5",
3
+ "version": "1.6.6",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "build": "vite build",
7
- "dev": "vite build --watch",
7
+ "dev": "tsc --watch",
8
8
  "test": "vitest"
9
9
  },
10
10
  "files": [
@@ -28,11 +28,11 @@
28
28
  },
29
29
  "devDependencies": {
30
30
  "@gravity-ui/icons": "^2.18.0",
31
- "@heroui/react": "^3.0.2",
32
- "@heroui/styles": "^3.0.2",
31
+ "@heroui/react": "^3.0.4",
32
+ "@heroui/styles": "^3.0.4",
33
33
  "@internationalized/date": "^3.12.1",
34
34
  "@tanstack/react-pacer": "^0.22.0",
35
- "@tanstack/react-query": "^5.100.8",
35
+ "@tanstack/react-query": "^5.100.9",
36
36
  "@testing-library/react": "^16.3.2",
37
37
  "@testing-library/user-event": "^14.5.2",
38
38
  "@types/react": "^19.2.14",
@@ -41,22 +41,22 @@
41
41
  "better-auth": "^1.6.9",
42
42
  "bowser": "^2.11.0",
43
43
  "playwright": "^1.59.1",
44
- "react": "^19.2.5",
45
- "react-dom": "^19.2.5",
44
+ "react": "^19.2.6",
45
+ "react-dom": "^19.2.6",
46
46
  "vitest": "^4.1.5"
47
47
  },
48
48
  "peerDependencies": {
49
49
  "@better-auth-ui/core": "*",
50
50
  "@better-auth-ui/react": "*",
51
51
  "@gravity-ui/icons": ">=2.18.0",
52
- "@heroui/react": ">=3.0.2",
53
- "@heroui/styles": ">=3.0.2",
52
+ "@heroui/react": ">=3.0.4",
53
+ "@heroui/styles": ">=3.0.4",
54
54
  "@internationalized/date": ">=3.12.1",
55
55
  "@tanstack/react-pacer": ">=0.22.0",
56
- "@tanstack/react-query": ">=5.100.8",
56
+ "@tanstack/react-query": ">=5.100.9",
57
57
  "better-auth": ">=1.6.9",
58
- "react": ">=19.2.5",
59
- "react-dom": ">=19.2.5",
58
+ "react": ">=19.2.6",
59
+ "react-dom": ">=19.2.6",
60
60
  "bowser": ">=2.11.0"
61
61
  },
62
62
  "repository": {
@@ -22,7 +22,7 @@ export function DangerZone({
22
22
 
23
23
  return (
24
24
  <div className={cn("flex w-full flex-col", className)} {...props}>
25
- <h2 className={cn("text-sm font-semibold mb-3")}>
25
+ <h2 className={cn("text-sm font-semibold mb-3 text-danger")}>
26
26
  {localization.settings.dangerZone}
27
27
  </h2>
28
28
 
@@ -11,7 +11,6 @@ import {
11
11
  Button,
12
12
  Card,
13
13
  type CardProps,
14
- cn,
15
14
  FieldError,
16
15
  Form,
17
16
  Input,
@@ -91,11 +90,7 @@ export function DeleteUser({
91
90
  }
92
91
 
93
92
  return (
94
- <Card
95
- className={cn("border border-danger", className)}
96
- variant={variant}
97
- {...props}
98
- >
93
+ <Card className={className} variant={variant} {...props}>
99
94
  <Card.Content className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
100
95
  <div>
101
96
  <p className="text-sm font-medium leading-tight">
@@ -111,7 +106,7 @@ export function DeleteUser({
111
106
  <Button
112
107
  isDisabled={!accounts}
113
108
  size="sm"
114
- variant="danger"
109
+ variant="danger-soft"
115
110
  onPress={() => setConfirmOpen(true)}
116
111
  >
117
112
  {deleteUserLocalization.deleteUser}
@@ -1,5 +1,5 @@
1
1
  import { useAuthPlugin } from "@better-auth-ui/react"
2
- import { Display, Moon, Sun } from "@gravity-ui/icons"
2
+ import { Display, Moon, Palette, Sun } from "@gravity-ui/icons"
3
3
  import { Dropdown, Label, Tabs } from "@heroui/react"
4
4
 
5
5
  import { themePlugin } from "../../../lib/auth/theme-plugin"
@@ -67,6 +67,7 @@ export function ThemeToggleItem() {
67
67
  // menu-item activation.
68
68
  shouldCloseOnSelect={false}
69
69
  >
70
+ <Palette className="text-muted" />
70
71
  <Label>{localization.theme}</Label>
71
72
 
72
73
  <Tabs
@@ -11,14 +11,38 @@ import {
11
11
  type ButtonProps,
12
12
  cn,
13
13
  Dropdown,
14
+ type DropdownItemProps,
14
15
  type DropdownPopoverProps,
15
- Label,
16
- Separator
16
+ Label
17
17
  } from "@heroui/react"
18
+ import { isValidElement, type ReactElement, type ReactNode } from "react"
18
19
 
19
20
  import { UserAvatar } from "./user-avatar"
20
21
  import { UserView } from "./user-view"
21
22
 
23
+ /** Auth states a `UserButton` link can be visible in. */
24
+ export type UserButtonLinkVisibility =
25
+ | "authenticated"
26
+ | "unauthenticated"
27
+ | "always"
28
+
29
+ /** A simple link entry rendered as a `Dropdown.Item` in the `UserButton` menu. */
30
+ export type UserButtonLink = {
31
+ /** Visible label. */
32
+ label: ReactNode
33
+ /** Destination URL. */
34
+ href: string
35
+ /** Optional leading icon. Sized/coloured to match built-in items. */
36
+ icon?: ReactNode
37
+ /** Forwarded to the underlying `Dropdown.Item`. */
38
+ variant?: DropdownItemProps["variant"]
39
+ /**
40
+ * When this link is visible based on auth state.
41
+ * @default "always"
42
+ */
43
+ visibility?: UserButtonLinkVisibility
44
+ }
45
+
22
46
  export type UserButtonProps = {
23
47
  className?: string
24
48
  size?: "default" | "icon"
@@ -28,6 +52,30 @@ export type UserButtonProps = {
28
52
  */
29
53
  placement?: DropdownPopoverProps["placement"]
30
54
  variant?: ButtonProps["variant"]
55
+ /** Additional menu entries rendered above the built-in items. */
56
+ links?: (UserButtonLink | ReactElement)[]
57
+ /** Hide the built-in "Settings" link. Useful when replacing it via `links`. */
58
+ hideSettings?: boolean
59
+ }
60
+
61
+ function renderUserLink(
62
+ link: UserButtonLink | ReactElement,
63
+ fallbackKey: string
64
+ ): ReactNode {
65
+ if (isValidElement(link)) return link
66
+
67
+ const { label, href, icon, variant } = link
68
+ return (
69
+ <Dropdown.Item
70
+ key={fallbackKey}
71
+ href={href}
72
+ variant={variant}
73
+ textValue={typeof label === "string" ? label : undefined}
74
+ >
75
+ {icon}
76
+ <Label>{label}</Label>
77
+ </Dropdown.Item>
78
+ )
31
79
  }
32
80
 
33
81
  /**
@@ -37,13 +85,17 @@ export type UserButtonProps = {
37
85
  * @param placement - Dropdown popover placement (e.g., "bottom", "top-start", "bottom-end")
38
86
  * @param size - "icon" renders an avatar-only trigger; "default" renders a button with label and chevron
39
87
  * @param variant - Button visual variant passed to the underlying Button component
88
+ * @param links - Additional menu entries rendered above the built-in items
89
+ * @param hideSettings - Hide the built-in "Settings" link
40
90
  * @returns The user button and its dropdown menu as a JSX element
41
91
  */
42
92
  export function UserButton({
43
93
  className,
44
94
  placement = "bottom",
45
95
  size = "default",
46
- variant = "ghost"
96
+ variant = "ghost",
97
+ links,
98
+ hideSettings = false
47
99
  }: UserButtonProps) {
48
100
  const { authClient, basePaths, viewPaths, localization, plugins } = useAuth()
49
101
 
@@ -55,6 +107,16 @@ export function UserButton({
55
107
  <Item key={`${plugin.id}-${index.toString()}`} />
56
108
  )) ?? []
57
109
  )
110
+
111
+ const userLinks = links?.flatMap((link, index) => {
112
+ if (!isValidElement(link)) {
113
+ const visibility = link.visibility ?? "always"
114
+ if (visibility === "authenticated" && !session) return []
115
+ if (visibility === "unauthenticated" && session) return []
116
+ }
117
+ return [renderUserLink(link, `user-button-link-${index.toString()}`)]
118
+ })
119
+
58
120
  return (
59
121
  <Dropdown>
60
122
  {size === "icon" ? (
@@ -96,30 +158,35 @@ export function UserButton({
96
158
  <Dropdown.Menu>
97
159
  {session ? (
98
160
  <>
99
- <Dropdown.Item
100
- textValue={localization.settings.settings}
101
- href={`${basePaths.settings}/${viewPaths.settings.account}`}
102
- >
103
- <Gear className="text-muted" />
161
+ {userLinks}
104
162
 
105
- <Label>{localization.settings.settings}</Label>
106
- </Dropdown.Item>
163
+ {!hideSettings && (
164
+ <Dropdown.Item
165
+ textValue={localization.settings.settings}
166
+ href={`${basePaths.settings}/${viewPaths.settings.account}`}
167
+ >
168
+ <Gear className="text-muted" />
107
169
 
108
- {userMenuItems}
170
+ <Label>{localization.settings.settings}</Label>
171
+ </Dropdown.Item>
172
+ )}
109
173
 
110
- <Separator />
174
+ {userMenuItems}
111
175
 
112
176
  <Dropdown.Item
113
177
  textValue={localization.auth.signOut}
114
178
  href={`${basePaths.auth}/${viewPaths.auth.signOut}`}
179
+ variant="danger"
115
180
  >
116
- <ArrowRightFromSquare className="text-muted" />
181
+ <ArrowRightFromSquare className="text-danger" />
117
182
 
118
183
  <Label>{localization.auth.signOut}</Label>
119
184
  </Dropdown.Item>
120
185
  </>
121
186
  ) : (
122
187
  <>
188
+ {userLinks}
189
+
123
190
  <Dropdown.Item
124
191
  textValue={localization.auth.signIn}
125
192
  href={`${basePaths.auth}/${viewPaths.auth.signIn}`}