appos 0.4.3-0 → 0.5.1-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. package/dist/exports/api/index.d.mts +3 -3
  2. package/dist/exports/api/index.mjs +34 -9
  3. package/dist/exports/api/workflows/index.d.mts +2 -2
  4. package/dist/exports/api/workflows/index.mjs +2 -2
  5. package/dist/exports/{auth-By0xx0MI.d.mts → auth-BCOIpGDO.d.mts} +325 -214
  6. package/dist/exports/cli/index.d.mts +105 -106
  7. package/dist/exports/cli/index.mjs +87 -13
  8. package/dist/exports/{constants-BePPc_yF.mjs → constants-BicCnEiJ.mjs} +7 -1
  9. package/dist/exports/{index-Bpo5QE7k.d.mts → index-CiuTp085.d.mts} +580 -614
  10. package/dist/exports/{openapi-crG3j4xx.mjs → openapi-uisUTLq7.mjs} +45 -102
  11. package/dist/exports/{send-email-Bgcdjy-e.mjs → send-email-CXxlkfFL.mjs} +1 -1
  12. package/dist/exports/{server-BlNxgHUc.mjs → server-CA4aI0U6.mjs} +6 -2
  13. package/dist/exports/storybook/index.d.ts +40 -0
  14. package/dist/exports/storybook/index.js +43 -0
  15. package/dist/exports/tests/api.d.mts +2 -2
  16. package/dist/exports/tests/api.mjs +3 -3
  17. package/dist/exports/vite/index.d.mts +19 -3
  18. package/dist/exports/vite/index.mjs +82 -6
  19. package/dist/exports/web/index.d.ts +2247 -700
  20. package/dist/exports/web/index.js +2445 -754
  21. package/dist/exports/web/routes.d.mts +41 -43
  22. package/dist/exports/web/ssr.d.mts +2 -2
  23. package/package.json +48 -31
  24. package/src/storybook/.storybook/main.ts +119 -0
  25. package/src/storybook/.storybook/manager.ts +75 -0
  26. package/src/storybook/.storybook/preview.tsx +76 -0
  27. package/src/storybook/components/ColorGrid.tsx +324 -0
  28. package/src/storybook/components/IconGallery.tsx +268 -0
  29. package/src/storybook/components/SpacingScale.tsx +236 -0
  30. package/src/storybook/components/TypographyScale.tsx +277 -0
  31. package/src/storybook/components/index.ts +3 -0
  32. package/src/storybook/docs/Introduction.mdx +29 -0
  33. package/src/storybook/docs/foundations/Colors.mdx +27 -0
  34. package/src/storybook/docs/foundations/Icons.mdx +26 -0
  35. package/src/storybook/docs/foundations/Spacing.mdx +23 -0
  36. package/src/storybook/docs/foundations/Typography.mdx +39 -0
  37. package/src/storybook/index.ts +147 -0
  38. package/src/storybook/shims/void-elements.js +18 -0
  39. package/src/web/client/auth-paths.ts +148 -0
  40. package/src/web/client/auth.ts +218 -0
  41. package/src/web/client/i18n.d.ts +9 -0
  42. package/src/web/client/i18n.ts +127 -0
  43. package/src/web/client/react-router.ts +84 -0
  44. package/src/web/hooks/index.ts +1 -0
  45. package/src/web/hooks/use-mobile.ts +19 -0
  46. package/src/web/index.ts +42 -0
  47. package/src/web/lib/colors.ts +220 -0
  48. package/src/web/lib/utils.ts +52 -0
  49. package/src/web/routes/auth/_layout.tsx +5 -11
  50. package/src/web/routes.ts +101 -0
  51. package/src/web/server/context.ts +14 -0
  52. package/src/web/server/render.ts +11 -0
  53. package/src/web/ssr.ts +17 -0
  54. package/src/web/ui/accordion.stories.tsx +183 -0
  55. package/src/web/ui/accordion.tsx +134 -0
  56. package/src/web/ui/alert-dialog.stories.tsx +215 -0
  57. package/src/web/ui/alert-dialog.tsx +328 -0
  58. package/src/web/ui/alert.stories.tsx +224 -0
  59. package/src/web/ui/alert.tsx +136 -0
  60. package/src/web/ui/animated-check.stories.tsx +200 -0
  61. package/src/web/ui/animated-check.tsx +98 -0
  62. package/src/web/ui/aspect-ratio.stories.tsx +204 -0
  63. package/src/web/ui/aspect-ratio.tsx +36 -0
  64. package/src/web/ui/auth/__mocks__/auth-context.tsx +273 -0
  65. package/src/web/ui/auth/auth-card.stories.tsx +295 -0
  66. package/src/web/ui/auth/auth-card.tsx +124 -0
  67. package/src/web/ui/auth/auth-layout.stories.tsx +330 -0
  68. package/src/web/ui/auth/auth-layout.tsx +269 -0
  69. package/src/web/ui/auth/captcha-widget.stories.tsx +200 -0
  70. package/src/web/ui/auth/captcha-widget.tsx +197 -0
  71. package/src/web/ui/auth/context.tsx +87 -0
  72. package/src/web/ui/auth/email-input.stories.tsx +167 -0
  73. package/src/web/ui/auth/email-input.tsx +84 -0
  74. package/src/web/ui/auth/forms/email-otp-form.tsx +377 -0
  75. package/src/web/ui/auth/forms/forgot-password-form.tsx +240 -0
  76. package/src/web/ui/auth/forms/index.ts +40 -0
  77. package/src/web/ui/auth/forms/login-form.tsx +486 -0
  78. package/src/web/ui/auth/forms/phone-otp-form.tsx +388 -0
  79. package/src/web/ui/auth/forms/reset-password-form.tsx +331 -0
  80. package/src/web/ui/auth/forms/signup-form.tsx +499 -0
  81. package/src/web/ui/auth/forms/two-factor-form.tsx +410 -0
  82. package/src/web/ui/auth/forms/verify-email-form.tsx +373 -0
  83. package/src/web/ui/auth/hooks.ts +266 -0
  84. package/src/web/ui/auth/index.ts +31 -0
  85. package/src/web/ui/auth/magic-link-form.tsx +191 -0
  86. package/src/web/ui/auth/otp-input.stories.tsx +199 -0
  87. package/src/web/ui/auth/otp-input.tsx +157 -0
  88. package/src/web/ui/auth/passkey-button.stories.tsx +115 -0
  89. package/src/web/ui/auth/passkey-button.tsx +113 -0
  90. package/src/web/ui/auth/passkey-register-button.stories.tsx +109 -0
  91. package/src/web/ui/auth/passkey-register-button.tsx +106 -0
  92. package/src/web/ui/auth/password-input.stories.tsx +287 -0
  93. package/src/web/ui/auth/password-input.tsx +306 -0
  94. package/src/web/ui/auth/phone-input.stories.tsx +160 -0
  95. package/src/web/ui/auth/phone-input.tsx +89 -0
  96. package/src/web/ui/auth/social-buttons.stories.tsx +217 -0
  97. package/src/web/ui/auth/social-buttons.tsx +196 -0
  98. package/src/web/ui/auth/sso-button.stories.tsx +160 -0
  99. package/src/web/ui/auth/sso-button.tsx +108 -0
  100. package/src/web/ui/auth/trust-signals.stories.tsx +208 -0
  101. package/src/web/ui/auth/trust-signals.tsx +260 -0
  102. package/src/web/ui/avatar.stories.tsx +302 -0
  103. package/src/web/ui/avatar.tsx +189 -0
  104. package/src/web/ui/badge.stories.tsx +225 -0
  105. package/src/web/ui/badge.tsx +66 -0
  106. package/src/web/ui/brand.stories.tsx +266 -0
  107. package/src/web/ui/brand.tsx +162 -0
  108. package/src/web/ui/breadcrumb.stories.tsx +271 -0
  109. package/src/web/ui/breadcrumb.tsx +214 -0
  110. package/src/web/ui/button-group.stories.tsx +251 -0
  111. package/src/web/ui/button-group.tsx +87 -0
  112. package/src/web/ui/button.stories.tsx +264 -0
  113. package/src/web/ui/button.tsx +120 -0
  114. package/src/web/ui/calendar.stories.tsx +235 -0
  115. package/src/web/ui/calendar.tsx +221 -0
  116. package/src/web/ui/card.stories.tsx +262 -0
  117. package/src/web/ui/card.tsx +199 -0
  118. package/src/web/ui/carousel.stories.tsx +244 -0
  119. package/src/web/ui/carousel.tsx +241 -0
  120. package/src/web/ui/chart.stories.tsx +833 -0
  121. package/src/web/ui/chart.tsx +390 -0
  122. package/src/web/ui/checkbox.stories.tsx +208 -0
  123. package/src/web/ui/checkbox.tsx +39 -0
  124. package/src/web/ui/collapsible.stories.tsx +239 -0
  125. package/src/web/ui/collapsible.tsx +21 -0
  126. package/src/web/ui/command.stories.tsx +291 -0
  127. package/src/web/ui/command.tsx +189 -0
  128. package/src/web/ui/context-menu.stories.tsx +255 -0
  129. package/src/web/ui/context-menu.tsx +263 -0
  130. package/src/web/ui/dialog.stories.tsx +263 -0
  131. package/src/web/ui/dialog.tsx +273 -0
  132. package/src/web/ui/drawer.stories.tsx +299 -0
  133. package/src/web/ui/drawer.tsx +130 -0
  134. package/src/web/ui/dropdown-menu.stories.tsx +320 -0
  135. package/src/web/ui/dropdown-menu.tsx +263 -0
  136. package/src/web/ui/empty.stories.tsx +204 -0
  137. package/src/web/ui/empty.tsx +101 -0
  138. package/src/web/ui/error-page.stories.tsx +181 -0
  139. package/src/web/ui/error-page.tsx +136 -0
  140. package/src/web/ui/field.stories.tsx +364 -0
  141. package/src/web/ui/field.tsx +252 -0
  142. package/src/web/ui/globals.css +3 -0
  143. package/src/web/ui/hover-card.stories.tsx +261 -0
  144. package/src/web/ui/hover-card.tsx +50 -0
  145. package/src/web/ui/index.ts +55 -0
  146. package/src/web/ui/input-group.stories.tsx +290 -0
  147. package/src/web/ui/input-group.tsx +157 -0
  148. package/src/web/ui/input-otp.stories.tsx +235 -0
  149. package/src/web/ui/input-otp.tsx +86 -0
  150. package/src/web/ui/input.stories.tsx +205 -0
  151. package/src/web/ui/input.tsx +30 -0
  152. package/src/web/ui/item.stories.tsx +348 -0
  153. package/src/web/ui/item.tsx +200 -0
  154. package/src/web/ui/kbd.stories.tsx +236 -0
  155. package/src/web/ui/kbd.tsx +27 -0
  156. package/src/web/ui/label.stories.tsx +180 -0
  157. package/src/web/ui/label.tsx +32 -0
  158. package/src/web/ui/locales/en/ui.json +13 -30
  159. package/src/web/ui/locales/zh-CN/ui.json +17 -34
  160. package/src/web/ui/locales/zh-TW/ui.json +17 -34
  161. package/src/web/ui/menubar.stories.tsx +385 -0
  162. package/src/web/ui/menubar.tsx +273 -0
  163. package/src/web/ui/navigation-menu.stories.tsx +361 -0
  164. package/src/web/ui/navigation-menu.tsx +168 -0
  165. package/src/web/ui/pagination.stories.tsx +312 -0
  166. package/src/web/ui/pagination.tsx +221 -0
  167. package/src/web/ui/popover.stories.tsx +315 -0
  168. package/src/web/ui/popover.tsx +89 -0
  169. package/src/web/ui/progress.stories.tsx +239 -0
  170. package/src/web/ui/progress.tsx +128 -0
  171. package/src/web/ui/radio-group.stories.tsx +315 -0
  172. package/src/web/ui/radio-group.tsx +38 -0
  173. package/src/web/ui/resizable.stories.tsx +304 -0
  174. package/src/web/ui/resizable.tsx +53 -0
  175. package/src/web/ui/scroll-area.stories.tsx +276 -0
  176. package/src/web/ui/scroll-area.tsx +53 -0
  177. package/src/web/ui/select.stories.tsx +314 -0
  178. package/src/web/ui/select.tsx +312 -0
  179. package/src/web/ui/separator.stories.tsx +246 -0
  180. package/src/web/ui/separator.tsx +36 -0
  181. package/src/web/ui/sheet.stories.tsx +417 -0
  182. package/src/web/ui/sheet.tsx +133 -0
  183. package/src/web/ui/sidebar.stories.tsx +402 -0
  184. package/src/web/ui/sidebar.tsx +723 -0
  185. package/src/web/ui/skeleton.stories.tsx +235 -0
  186. package/src/web/ui/skeleton.tsx +26 -0
  187. package/src/web/ui/slider.stories.tsx +294 -0
  188. package/src/web/ui/slider.tsx +75 -0
  189. package/src/web/ui/sonner.stories.tsx +279 -0
  190. package/src/web/ui/sonner.tsx +46 -0
  191. package/src/web/ui/spinner.stories.tsx +204 -0
  192. package/src/web/ui/spinner.tsx +28 -0
  193. package/src/web/ui/switch.stories.tsx +285 -0
  194. package/src/web/ui/switch.tsx +43 -0
  195. package/src/web/ui/table.stories.tsx +362 -0
  196. package/src/web/ui/table.tsx +115 -0
  197. package/src/web/ui/tabs.stories.tsx +401 -0
  198. package/src/web/ui/tabs.tsx +136 -0
  199. package/src/web/ui/textarea.stories.tsx +272 -0
  200. package/src/web/ui/textarea.tsx +28 -0
  201. package/src/web/ui/toggle-group.stories.tsx +334 -0
  202. package/src/web/ui/toggle-group.tsx +88 -0
  203. package/src/web/ui/toggle.stories.tsx +264 -0
  204. package/src/web/ui/toggle.tsx +57 -0
  205. package/src/web/ui/tooltip.stories.tsx +342 -0
  206. package/src/web/ui/tooltip.tsx +116 -0
@@ -3,7 +3,7 @@ import i18n, { changeLanguage, createInstance, getFixedT, init, loadLanguages, l
3
3
  import { I18nextProvider, Trans, initReactI18next, initReactI18next as initReactI18next$1, useTranslation, useTranslation as useTranslation$1 } from "react-i18next";
4
4
  import { passkeyClient } from "@better-auth/passkey/client";
5
5
  import { ssoClient } from "@better-auth/sso/client";
6
- import { adminClient, anonymousClient, apiKeyClient, emailOTPClient, magicLinkClient, multiSessionClient, phoneNumberClient, twoFactorClient, usernameClient } from "better-auth/client/plugins";
6
+ import { adminClient, anonymousClient, apiKeyClient, emailOTPClient, lastLoginMethodClient, magicLinkClient, multiSessionClient, phoneNumberClient, twoFactorClient, usernameClient } from "better-auth/client/plugins";
7
7
  import { createAuthClient } from "better-auth/react";
8
8
  import { Form, IDLE_FETCHER, Link, Link as Link$1, Links, Meta, NavLink, Outlet, Route, Scripts, ScrollRestoration, ServerRouter, href, isRouteErrorResponse, matchPath, matchRoutes, parsePath, redirect, redirect as redirect$1, redirectDocument, renderMatches, replace, resolvePath, useActionData, useAsyncError, useAsyncValue, useBeforeUnload, useBlocker, useFetcher, useFetchers, useFormAction, useHref, useInRouterContext, useLinkClickHandler, useLoaderData, useLocation, useMatch, useMatches, useNavigate, useNavigation, useNavigationType, useOutlet, useOutletContext, useParams, useResolvedPath, useRevalidator, useRouteError, useRouteError as useRouteError$1, useRouteLoaderData, useRoutes, useSearchParams, useSubmit, useViewTransitionState } from "react-router";
9
9
  import LanguageDetector from "i18next-browser-languagedetector";
@@ -13,22 +13,26 @@ import * as React from "react";
13
13
  import { createContext, useCallback, useContext, useEffect, useId, useMemo, useRef, useState } from "react";
14
14
  import { clsx } from "clsx";
15
15
  import { twMerge } from "tailwind-merge";
16
+ import { z } from "zod";
16
17
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
17
18
  import { cva } from "class-variance-authority";
18
- import { AlertCircleIcon, AlertTriangleIcon, ArrowLeftIcon, Building2Icon, CheckCircleIcon, CheckIcon, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronUpIcon, CircleCheckIcon, CircleIcon, EyeIcon, EyeOffIcon, FingerprintIcon, HomeIcon, InfoIcon, KeyIcon, Loader2Icon, LockIcon, MailIcon, MinusIcon, MoreHorizontalIcon, OctagonXIcon, PanelLeftIcon, PhoneIcon, PlusIcon, RefreshCwIcon, SearchIcon, SettingsIcon, ShieldCheckIcon, TriangleAlertIcon, UserIcon, XIcon } from "lucide-react";
19
+ import { CaptchaFox } from "@captchafox/react";
20
+ import HCaptcha from "@hcaptcha/react-hcaptcha";
21
+ import { Turnstile } from "@marsidev/react-turnstile";
22
+ import { AlertCircleIcon, AlertTriangleIcon, ArrowLeftIcon, AtSignIcon, Building2Icon, CheckCircleIcon, CheckIcon, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronUpIcon, CircleCheckIcon, CircleIcon, EyeIcon, EyeOffIcon, FingerprintIcon, HomeIcon, InfoIcon, KeyIcon, Loader2Icon, LoaderCircleIcon, LockIcon, MailIcon, MinusIcon, MoreHorizontalIcon, OctagonXIcon, PanelLeftIcon, PhoneIcon, PlusIcon, RefreshCwIcon, SearchIcon, SettingsIcon, ShieldCheckIcon, TriangleAlertIcon, UserIcon, XIcon } from "lucide-react";
23
+ import ReCAPTCHA from "react-google-recaptcha";
19
24
  import { Controller, FormProvider, useForm, useFormContext } from "react-hook-form";
20
25
  import { Separator as Separator$1 } from "@base-ui/react/separator";
21
26
  import { Input as Input$1 } from "@base-ui/react/input";
22
27
  import { zodResolver } from "@hookform/resolvers/zod";
23
- import { z } from "zod";
24
28
  import { Button as Button$1 } from "@base-ui/react/button";
25
29
  import { OTPInput, OTPInputContext } from "input-otp";
26
30
  import { Checkbox as Checkbox$1 } from "@base-ui/react/checkbox";
31
+ import { mergeProps } from "@base-ui/react/merge-props";
32
+ import { useRender } from "@base-ui/react/use-render";
27
33
  import { Accordion as Accordion$1 } from "@base-ui/react/accordion";
28
34
  import { AlertDialog as AlertDialog$1 } from "@base-ui/react/alert-dialog";
29
35
  import { Avatar as Avatar$1 } from "@base-ui/react/avatar";
30
- import { mergeProps } from "@base-ui/react/merge-props";
31
- import { useRender } from "@base-ui/react/use-render";
32
36
  import { DayPicker, getDefaultClassNames } from "react-day-picker";
33
37
  import useEmblaCarousel from "embla-carousel-react";
34
38
  import * as RechartsPrimitive from "recharts";
@@ -77,10 +81,10 @@ function defineAuthFeatures(config) {
77
81
  phoneOtp: config.methods?.phoneOtp !== void 0,
78
82
  emailOtp: config.methods?.emailOtp !== void 0,
79
83
  oauth: {
80
- google: config.oauth?.google === true,
81
- github: config.oauth?.github === true,
82
- apple: config.oauth?.apple === true,
83
- facebook: config.oauth?.facebook === true
84
+ google: config.methods?.oauth?.google === true,
85
+ github: config.methods?.oauth?.github === true,
86
+ apple: config.methods?.oauth?.apple === true,
87
+ facebook: config.methods?.oauth?.facebook === true
84
88
  },
85
89
  twoFactor: {
86
90
  enabled: config.plugins?.twoFactor !== void 0,
@@ -91,7 +95,13 @@ function defineAuthFeatures(config) {
91
95
  multiSession: config.plugins?.multiSession !== void 0,
92
96
  username: config.plugins?.username !== void 0,
93
97
  anonymous: config.plugins?.anonymous !== void 0,
94
- sso: config.plugins?.sso !== void 0
98
+ sso: config.plugins?.sso !== void 0,
99
+ lastUsedMethod: config.plugins?.lastUsedMethod !== void 0,
100
+ captcha: {
101
+ enabled: config.plugins?.captcha !== void 0,
102
+ provider: config.plugins?.captcha?.provider ?? null,
103
+ siteKey: config.plugins?.captcha?.siteKey ?? null
104
+ }
95
105
  };
96
106
  }
97
107
  /**
@@ -115,6 +125,7 @@ function defineAuthClient(opts) {
115
125
  if (config.plugins?.anonymous) plugins.push(anonymousClient());
116
126
  if (config.plugins?.multiSession) plugins.push(multiSessionClient());
117
127
  if (config.plugins?.sso) plugins.push(ssoClient({ domainVerification: config.plugins.sso.domainVerification ? { enabled: true } : void 0 }));
128
+ if (config.plugins?.lastUsedMethod) plugins.push(lastLoginMethodClient());
118
129
  return {
119
130
  client: createAuthClient({
120
131
  baseURL: (baseURL ?? config.baseURL ?? AUTH_BASE_URL) || void 0,
@@ -277,9 +288,70 @@ function useIsMobile(mobileBreakpoint = 768) {
277
288
  function cn(...inputs) {
278
289
  return twMerge(clsx(inputs));
279
290
  }
291
+ /**
292
+ * Standard size variants used across UI components.
293
+ *
294
+ * @example
295
+ * ```tsx
296
+ * import { type Size, sizes } from "#src/web/lib/utils.ts";
297
+ *
298
+ * interface Props {
299
+ * size?: Size;
300
+ * }
301
+ *
302
+ * // In stories
303
+ * argTypes: {
304
+ * size: { control: "select", options: sizes },
305
+ * }
306
+ * ```
307
+ */
308
+ const sizeSchema = z.enum([
309
+ "xs",
310
+ "sm",
311
+ "default",
312
+ "lg",
313
+ "xl"
314
+ ]);
315
+ /** Available sizes for storybook controls. */
316
+ const sizes = sizeSchema.options;
317
+ /**
318
+ * Standard orientation variants for layout components.
319
+ *
320
+ * @example
321
+ * ```tsx
322
+ * import { type Orientation, orientations } from "#src/web/lib/utils.ts";
323
+ *
324
+ * interface Props {
325
+ * orientation?: Orientation;
326
+ * }
327
+ * ```
328
+ */
329
+ const orientationSchema = z.enum(["horizontal", "vertical"]);
330
+ /** Available orientation keys for storybook controls. */
331
+ const orientations = orientationSchema.options;
280
332
 
281
333
  //#endregion
282
334
  //#region src/web/ui/card.tsx
335
+ /**
336
+ * A container component for grouping related content and actions.
337
+ *
338
+ * Provides a visual boundary with optional header, content, and footer
339
+ * sections. Supports multiple sizes for different layout needs.
340
+ *
341
+ * @example
342
+ * ```tsx
343
+ * <Card>
344
+ * <CardHeader>
345
+ * <CardTitle>Card Title</CardTitle>
346
+ * <CardDescription>Card description text.</CardDescription>
347
+ * </CardHeader>
348
+ * <CardContent>Main content goes here.</CardContent>
349
+ * <CardFooter>
350
+ * <Button>Action</Button>
351
+ * </CardFooter>
352
+ * </Card>
353
+ * ```
354
+ */
283
355
  function Card({ className, size = "default", ...props }) {
284
356
  return /* @__PURE__ */ jsx("div", {
285
357
  "data-slot": "card",
@@ -288,6 +360,20 @@ function Card({ className, size = "default", ...props }) {
288
360
  ...props
289
361
  });
290
362
  }
363
+ /**
364
+ * The header section of a Card containing title and description.
365
+ *
366
+ * Supports an optional CardAction slot for buttons or menus positioned
367
+ * in the top-right corner.
368
+ *
369
+ * @example
370
+ * ```tsx
371
+ * <CardHeader>
372
+ * <CardTitle>Settings</CardTitle>
373
+ * <CardDescription>Manage your preferences.</CardDescription>
374
+ * </CardHeader>
375
+ * ```
376
+ */
291
377
  function CardHeader({ className, ...props }) {
292
378
  return /* @__PURE__ */ jsx("div", {
293
379
  "data-slot": "card-header",
@@ -295,6 +381,17 @@ function CardHeader({ className, ...props }) {
295
381
  ...props
296
382
  });
297
383
  }
384
+ /**
385
+ * The title text displayed in a CardHeader.
386
+ *
387
+ * Renders with medium font weight and appropriate sizing based on the
388
+ * parent Card's size prop.
389
+ *
390
+ * @example
391
+ * ```tsx
392
+ * <CardTitle>Account Settings</CardTitle>
393
+ * ```
394
+ */
298
395
  function CardTitle({ className, ...props }) {
299
396
  return /* @__PURE__ */ jsx("div", {
300
397
  "data-slot": "card-title",
@@ -302,6 +399,16 @@ function CardTitle({ className, ...props }) {
302
399
  ...props
303
400
  });
304
401
  }
402
+ /**
403
+ * A secondary text element providing context for a Card.
404
+ *
405
+ * Displays in muted foreground color below the CardTitle.
406
+ *
407
+ * @example
408
+ * ```tsx
409
+ * <CardDescription>Manage your account settings and preferences.</CardDescription>
410
+ * ```
411
+ */
305
412
  function CardDescription({ className, ...props }) {
306
413
  return /* @__PURE__ */ jsx("div", {
307
414
  "data-slot": "card-description",
@@ -309,6 +416,21 @@ function CardDescription({ className, ...props }) {
309
416
  ...props
310
417
  });
311
418
  }
419
+ /**
420
+ * A slot for action buttons positioned in the CardHeader.
421
+ *
422
+ * Automatically positioned in the top-right corner of the header.
423
+ * Use for dropdown menus, edit buttons, or other quick actions.
424
+ *
425
+ * @example
426
+ * ```tsx
427
+ * <CardAction>
428
+ * <Button variant="ghost" size="icon-sm">
429
+ * <MoreHorizontalIcon />
430
+ * </Button>
431
+ * </CardAction>
432
+ * ```
433
+ */
312
434
  function CardAction({ className, ...props }) {
313
435
  return /* @__PURE__ */ jsx("div", {
314
436
  "data-slot": "card-action",
@@ -316,6 +438,19 @@ function CardAction({ className, ...props }) {
316
438
  ...props
317
439
  });
318
440
  }
441
+ /**
442
+ * The main content area of a Card.
443
+ *
444
+ * Provides consistent horizontal padding. Use for the primary content
445
+ * of the card such as forms, lists, or text.
446
+ *
447
+ * @example
448
+ * ```tsx
449
+ * <CardContent>
450
+ * <p>Your content goes here.</p>
451
+ * </CardContent>
452
+ * ```
453
+ */
319
454
  function CardContent({ className, ...props }) {
320
455
  return /* @__PURE__ */ jsx("div", {
321
456
  "data-slot": "card-content",
@@ -323,6 +458,20 @@ function CardContent({ className, ...props }) {
323
458
  ...props
324
459
  });
325
460
  }
461
+ /**
462
+ * The footer section of a Card for actions or supplementary content.
463
+ *
464
+ * Typically contains action buttons aligned horizontally. Provides
465
+ * consistent padding that adapts to the Card's size.
466
+ *
467
+ * @example
468
+ * ```tsx
469
+ * <CardFooter>
470
+ * <Button variant="outline">Cancel</Button>
471
+ * <Button>Save</Button>
472
+ * </CardFooter>
473
+ * ```
474
+ */
326
475
  function CardFooter({ className, ...props }) {
327
476
  return /* @__PURE__ */ jsx("div", {
328
477
  "data-slot": "card-footer",
@@ -403,7 +552,7 @@ const brandVariants = cva("inline-flex items-center select-none shrink-0", {
403
552
  size: {
404
553
  xs: "gap-1",
405
554
  sm: "gap-1",
406
- md: "gap-1.5",
555
+ default: "gap-1.5",
407
556
  lg: "gap-2",
408
557
  xl: "gap-2"
409
558
  },
@@ -413,7 +562,7 @@ const brandVariants = cva("inline-flex items-center select-none shrink-0", {
413
562
  }
414
563
  },
415
564
  defaultVariants: {
416
- size: "md",
565
+ size: "default",
417
566
  orientation: "horizontal"
418
567
  }
419
568
  });
@@ -421,21 +570,21 @@ const logoVariants = cva("shrink-0 flex items-center justify-center", {
421
570
  variants: { size: {
422
571
  xs: "size-6 [&>svg]:size-6",
423
572
  sm: "size-7 [&>svg]:size-7",
424
- md: "size-8 [&>svg]:size-8",
573
+ default: "size-8 [&>svg]:size-8",
425
574
  lg: "size-10 [&>svg]:size-10",
426
575
  xl: "size-12 [&>svg]:size-12"
427
576
  } },
428
- defaultVariants: { size: "md" }
577
+ defaultVariants: { size: "default" }
429
578
  });
430
579
  const nameVariants = cva("font-semibold tracking-tight text-foreground", {
431
580
  variants: { size: {
432
581
  xs: "text-sm",
433
582
  sm: "text-base",
434
- md: "text-lg",
583
+ default: "text-lg",
435
584
  lg: "text-xl",
436
585
  xl: "text-2xl"
437
586
  } },
438
- defaultVariants: { size: "md" }
587
+ defaultVariants: { size: "default" }
439
588
  });
440
589
  /**
441
590
  * Brand component for displaying app/company logo and name with proportionate sizing.
@@ -473,7 +622,7 @@ const nameVariants = cva("font-semibold tracking-tight text-foreground", {
473
622
  * </Link>
474
623
  * ```
475
624
  */
476
- function Brand({ className, logo, logoAlt, name, orientation = "horizontal", size = "md", to = "/" }) {
625
+ function Brand({ className, logo, logoAlt, name, orientation = "horizontal", size = "default", to = "/" }) {
477
626
  const renderLogo = () => {
478
627
  if (!logo) return null;
479
628
  if (typeof logo === "string") return /* @__PURE__ */ jsx("div", {
@@ -520,7 +669,7 @@ function Brand({ className, logo, logoAlt, name, orientation = "horizontal", siz
520
669
  * />
521
670
  * ```
522
671
  */
523
- function TrustSignals({ showSSL, showPrivacyLink, privacyUrl = "/privacy", showTermsLink, termsUrl = "/terms", complianceBadges, customBadges, securityMessage, className }) {
672
+ function TrustSignals({ showSSL, showPrivacyLink, privacyUrl = "/legal/privacy-policy", showTermsLink, termsUrl = "/legal/terms", complianceBadges, customBadges, securityMessage, className }) {
524
673
  const { t: t$1 } = useTranslation$1(["ui"]);
525
674
  const hasLinks = showPrivacyLink || showTermsLink;
526
675
  const hasBadges = complianceBadges && complianceBadges.length > 0 || customBadges && customBadges.length > 0;
@@ -695,7 +844,7 @@ function AuthLayout({ variant = "centered", children, brand, brandContent, trust
695
844
  className: "flex justify-center",
696
845
  children: /* @__PURE__ */ jsx(Brand, {
697
846
  ...brand,
698
- size: brand.size ?? "md"
847
+ size: brand.size ?? "default"
699
848
  })
700
849
  }), children]
701
850
  }), trustSignals && /* @__PURE__ */ jsx("div", {
@@ -869,8 +1018,330 @@ function useAuthContext() {
869
1018
  return context;
870
1019
  }
871
1020
 
1021
+ //#endregion
1022
+ //#region src/web/ui/auth/hooks.ts
1023
+ /**
1024
+ * Hook to access the Better Auth client, features, and config.
1025
+ *
1026
+ * Provides unified access to all auth-related context values:
1027
+ * - `client`: The Better Auth client for making auth API calls.
1028
+ * - `features`: Derived feature flags for conditional rendering based on enabled auth methods.
1029
+ * - `config`: The auth configuration values for forms and validation.
1030
+ *
1031
+ * @returns An object containing the auth client, features, and config.
1032
+ *
1033
+ * @example
1034
+ * ```tsx
1035
+ * // Access client for auth operations
1036
+ * const { client } = useAuth();
1037
+ * await client.signIn.email({ email, password });
1038
+ *
1039
+ * // Access features for conditional rendering
1040
+ * const { features } = useAuth();
1041
+ * if (features.oauth.google) {
1042
+ * // Show Google login button
1043
+ * }
1044
+ *
1045
+ * // Access config for form validation
1046
+ * const { config } = useAuth();
1047
+ * const minPassword = config.methods?.emailPassword?.minPasswordLength ?? 8;
1048
+ * ```
1049
+ */
1050
+ function useAuth() {
1051
+ const { client, features, config } = useAuthContext();
1052
+ return {
1053
+ client,
1054
+ features,
1055
+ config
1056
+ };
1057
+ }
1058
+ /**
1059
+ * Hook for managing auth form state.
1060
+ * Provides loading, error, and success state management.
1061
+ *
1062
+ * @example
1063
+ * ```tsx
1064
+ * const { state, execute, reset } = useAuthForm();
1065
+ *
1066
+ * const onSubmit = async (data) => {
1067
+ * await execute(async () => {
1068
+ * await client.signIn.email(data);
1069
+ * });
1070
+ * };
1071
+ *
1072
+ * if (state.error) {
1073
+ * // Show error message
1074
+ * }
1075
+ * ```
1076
+ */
1077
+ function useAuthForm() {
1078
+ const [state, setState] = useState({
1079
+ isLoading: false,
1080
+ error: null,
1081
+ isSuccess: false
1082
+ });
1083
+ return {
1084
+ state,
1085
+ execute: useCallback(async (fn) => {
1086
+ setState({
1087
+ isLoading: true,
1088
+ error: null,
1089
+ isSuccess: false
1090
+ });
1091
+ try {
1092
+ const result = await fn();
1093
+ setState({
1094
+ isLoading: false,
1095
+ error: null,
1096
+ isSuccess: true
1097
+ });
1098
+ return result;
1099
+ } catch (err) {
1100
+ setState({
1101
+ isLoading: false,
1102
+ error: err instanceof Error ? err.message : "An error occurred",
1103
+ isSuccess: false
1104
+ });
1105
+ return;
1106
+ }
1107
+ }, []),
1108
+ reset: useCallback(() => {
1109
+ setState({
1110
+ isLoading: false,
1111
+ error: null,
1112
+ isSuccess: false
1113
+ });
1114
+ }, []),
1115
+ setError: useCallback((error) => {
1116
+ setState((prev) => ({
1117
+ ...prev,
1118
+ error,
1119
+ isSuccess: false
1120
+ }));
1121
+ }, []),
1122
+ isLoading: state.isLoading,
1123
+ error: state.error,
1124
+ isSuccess: state.isSuccess
1125
+ };
1126
+ }
1127
+ /**
1128
+ * Hook to get and manage the last used login method.
1129
+ * Returns null values if the lastUsedMethod feature is not enabled.
1130
+ *
1131
+ * @returns An object with lastMethod, isLastMethod helper, and clearLastMethod function.
1132
+ *
1133
+ * @example
1134
+ * ```tsx
1135
+ * const { lastMethod, isLastMethod } = useLastUsedMethod();
1136
+ *
1137
+ * // Check if a specific method was last used
1138
+ * if (isLastMethod("google")) {
1139
+ * // Highlight Google button
1140
+ * }
1141
+ *
1142
+ * // Display the last method
1143
+ * {lastMethod && <Badge>Last used: {lastMethod}</Badge>}
1144
+ * ```
1145
+ */
1146
+ function useLastUsedMethod() {
1147
+ const { client, features } = useAuthContext();
1148
+ return useMemo(() => {
1149
+ if (!features.lastUsedMethod) return {
1150
+ lastMethod: null,
1151
+ isLastMethod: () => false,
1152
+ clearLastMethod: () => {}
1153
+ };
1154
+ const lastLoginMethodPlugin = client.lastLoginMethod;
1155
+ if (!lastLoginMethodPlugin) return {
1156
+ lastMethod: null,
1157
+ isLastMethod: () => false,
1158
+ clearLastMethod: () => {}
1159
+ };
1160
+ return {
1161
+ lastMethod: lastLoginMethodPlugin.getLastUsedLoginMethod() ?? null,
1162
+ isLastMethod: (method) => lastLoginMethodPlugin.isLastUsedLoginMethod(method),
1163
+ clearLastMethod: () => lastLoginMethodPlugin.clearLastUsedLoginMethod()
1164
+ };
1165
+ }, [client, features.lastUsedMethod]);
1166
+ }
1167
+ /**
1168
+ * Hook to get captcha configuration for rendering widgets.
1169
+ *
1170
+ * Apps need to install and render the appropriate captcha widget based on the provider:
1171
+ * - `cloudflare-turnstile`: Use `@marsidev/react-turnstile`
1172
+ * - `google-recaptcha`: Use `react-google-recaptcha` (v2 or v3 based on server minScore)
1173
+ * - `hcaptcha`: Use `@hcaptcha/react-hcaptcha`
1174
+ * - `captchafox`: Use `@captchafox/react`
1175
+ *
1176
+ * Pass the captcha token to auth forms via the `captchaToken` prop.
1177
+ *
1178
+ * @returns Captcha configuration including provider and siteKey.
1179
+ *
1180
+ * @example
1181
+ * ```tsx
1182
+ * import { Turnstile } from "@marsidev/react-turnstile";
1183
+ *
1184
+ * function LoginPage() {
1185
+ * const { enabled, provider, siteKey } = useCaptcha();
1186
+ * const [captchaToken, setCaptchaToken] = useState<string | null>(null);
1187
+ *
1188
+ * return (
1189
+ * <>
1190
+ * {enabled && provider === "cloudflare-turnstile" && siteKey && (
1191
+ * <Turnstile
1192
+ * siteKey={siteKey}
1193
+ * onSuccess={setCaptchaToken}
1194
+ * />
1195
+ * )}
1196
+ * <LoginForm captchaToken={captchaToken} />
1197
+ * </>
1198
+ * );
1199
+ * }
1200
+ * ```
1201
+ */
1202
+ function useCaptcha() {
1203
+ const { features } = useAuthContext();
1204
+ return features.captcha;
1205
+ }
1206
+
1207
+ //#endregion
1208
+ //#region src/web/ui/auth/captcha-widget.tsx
1209
+ /**
1210
+ * Unified captcha widget that renders the appropriate provider based on config.
1211
+ * Supports Cloudflare Turnstile, Google reCAPTCHA, hCaptcha, and CaptchaFox.
1212
+ *
1213
+ * @example
1214
+ * ```tsx
1215
+ * const [captchaToken, setCaptchaToken] = useState<string | null>(null);
1216
+ * const captchaResetRef = useRef<(() => void) | null>(null);
1217
+ *
1218
+ * <CaptchaWidget
1219
+ * onSuccess={setCaptchaToken}
1220
+ * onExpire={() => setCaptchaToken(null)}
1221
+ * resetRef={captchaResetRef}
1222
+ * />
1223
+ *
1224
+ * // Reset on form error:
1225
+ * captchaResetRef.current?.();
1226
+ * ```
1227
+ */
1228
+ function CaptchaWidget({ onSuccess, onError, onExpire, resetRef, className }) {
1229
+ const { t: t$1 } = useTranslation$1(["ui"]);
1230
+ const captcha = useCaptcha();
1231
+ const turnstileRef = useRef(null);
1232
+ const recaptchaRef = useRef(null);
1233
+ const hcaptchaRef = useRef(null);
1234
+ const captchafoxRef = useRef(null);
1235
+ const [isLoading, setIsLoading] = useState(true);
1236
+ const handleReset = useCallback(() => {
1237
+ turnstileRef.current?.reset();
1238
+ recaptchaRef.current?.reset();
1239
+ hcaptchaRef.current?.resetCaptcha();
1240
+ captchafoxRef.current?.reset();
1241
+ }, []);
1242
+ const handleWidgetLoad = useCallback(() => {
1243
+ setIsLoading(false);
1244
+ }, []);
1245
+ if (resetRef) resetRef.current = handleReset;
1246
+ const handleSuccess = useCallback((token) => {
1247
+ onSuccess(token);
1248
+ }, [onSuccess]);
1249
+ const handleError = useCallback(() => {
1250
+ onError?.();
1251
+ }, [onError]);
1252
+ const handleExpire = useCallback(() => {
1253
+ onExpire?.();
1254
+ }, [onExpire]);
1255
+ if (!captcha.enabled || !captcha.siteKey) return null;
1256
+ const loadingIndicator = isLoading && /* @__PURE__ */ jsxs("div", {
1257
+ className: "flex h-16.25 w-full items-center justify-center rounded-md border border-border bg-muted/30",
1258
+ children: [/* @__PURE__ */ jsx(LoaderCircleIcon, { className: "size-5 animate-spin text-muted-foreground" }), /* @__PURE__ */ jsx("span", {
1259
+ className: "ml-2 text-sm text-muted-foreground",
1260
+ children: t$1("ui:captcha.loading")
1261
+ })]
1262
+ });
1263
+ switch (captcha.provider) {
1264
+ case "cloudflare-turnstile": return /* @__PURE__ */ jsxs("div", {
1265
+ className: cn("flex flex-col gap-2", className),
1266
+ children: [loadingIndicator, /* @__PURE__ */ jsx("div", {
1267
+ className: cn(isLoading && "sr-only"),
1268
+ children: /* @__PURE__ */ jsx(Turnstile, {
1269
+ ref: turnstileRef,
1270
+ siteKey: captcha.siteKey,
1271
+ onSuccess: handleSuccess,
1272
+ onError: handleError,
1273
+ onExpire: handleExpire,
1274
+ onWidgetLoad: handleWidgetLoad,
1275
+ options: {
1276
+ theme: "auto",
1277
+ size: "flexible"
1278
+ }
1279
+ })
1280
+ })]
1281
+ });
1282
+ case "google-recaptcha": return /* @__PURE__ */ jsxs("div", {
1283
+ className: cn("flex flex-col gap-2", className),
1284
+ children: [loadingIndicator, /* @__PURE__ */ jsx("div", {
1285
+ className: cn(isLoading && "sr-only"),
1286
+ children: /* @__PURE__ */ jsx(ReCAPTCHA, {
1287
+ ref: recaptchaRef,
1288
+ sitekey: captcha.siteKey,
1289
+ onChange: (token) => {
1290
+ if (token) handleSuccess(token);
1291
+ else handleExpire();
1292
+ },
1293
+ onErrored: handleError,
1294
+ onExpired: handleExpire,
1295
+ asyncScriptOnLoad: handleWidgetLoad
1296
+ })
1297
+ })]
1298
+ });
1299
+ case "hcaptcha": return /* @__PURE__ */ jsxs("div", {
1300
+ className: cn("flex flex-col gap-2", className),
1301
+ children: [loadingIndicator, /* @__PURE__ */ jsx("div", {
1302
+ className: cn(isLoading && "sr-only"),
1303
+ children: /* @__PURE__ */ jsx(HCaptcha, {
1304
+ ref: hcaptchaRef,
1305
+ sitekey: captcha.siteKey,
1306
+ onVerify: handleSuccess,
1307
+ onError: handleError,
1308
+ onExpire: handleExpire,
1309
+ onLoad: handleWidgetLoad
1310
+ })
1311
+ })]
1312
+ });
1313
+ case "captchafox": return /* @__PURE__ */ jsxs("div", {
1314
+ className: cn("flex flex-col gap-2", className),
1315
+ children: [loadingIndicator, /* @__PURE__ */ jsx("div", {
1316
+ className: cn(isLoading && "sr-only"),
1317
+ children: /* @__PURE__ */ jsx(CaptchaFox, {
1318
+ ref: captchafoxRef,
1319
+ sitekey: captcha.siteKey,
1320
+ onVerify: handleSuccess,
1321
+ onError: handleError,
1322
+ onExpire: handleExpire,
1323
+ onLoad: handleWidgetLoad
1324
+ })
1325
+ })]
1326
+ });
1327
+ default: return null;
1328
+ }
1329
+ }
1330
+
872
1331
  //#endregion
873
1332
  //#region src/web/ui/label.tsx
1333
+ /**
1334
+ * A text label associated with a form control.
1335
+ *
1336
+ * Provides accessible labeling for inputs. Automatically handles disabled
1337
+ * states when used with peer or group patterns.
1338
+ *
1339
+ * @example
1340
+ * ```tsx
1341
+ * <Label htmlFor="email">Email Address</Label>
1342
+ * <Input id="email" type="email" />
1343
+ * ```
1344
+ */
874
1345
  function Label({ className, ...props }) {
875
1346
  return /* @__PURE__ */ jsx("label", {
876
1347
  "data-slot": "label",
@@ -881,6 +1352,18 @@ function Label({ className, ...props }) {
881
1352
 
882
1353
  //#endregion
883
1354
  //#region src/web/ui/separator.tsx
1355
+ /**
1356
+ * A visual divider to separate content sections.
1357
+ *
1358
+ * Renders as a horizontal or vertical line depending on orientation.
1359
+ * Use to create visual boundaries between related content groups.
1360
+ *
1361
+ * @example
1362
+ * ```tsx
1363
+ * <Separator />
1364
+ * <Separator orientation="vertical" className="h-4" />
1365
+ * ```
1366
+ */
884
1367
  function Separator({ className, orientation = "horizontal", ...props }) {
885
1368
  return /* @__PURE__ */ jsx(Separator$1, {
886
1369
  "data-slot": "separator",
@@ -910,7 +1393,7 @@ function FieldLegend({ className, variant = "legend", ...props }) {
910
1393
  function FieldGroup({ className, ...props }) {
911
1394
  return /* @__PURE__ */ jsx("div", {
912
1395
  "data-slot": "field-group",
913
- className: cn("gap-7 data-[slot=checkbox-group]:gap-3 [&>[data-slot=field-group]]:gap-4 group/field-group @container/field-group flex w-full flex-col", className),
1396
+ className: cn("gap-7 data-[slot=checkbox-group]:gap-3 *:data-[slot=field-group]:gap-4 group/field-group @container/field-group flex w-full flex-col", className),
914
1397
  ...props
915
1398
  });
916
1399
  }
@@ -941,7 +1424,7 @@ function FieldContent({ className, ...props }) {
941
1424
  function FieldLabel({ className, ...props }) {
942
1425
  return /* @__PURE__ */ jsx(Label, {
943
1426
  "data-slot": "field-label",
944
- className: cn("has-data-checked:bg-primary/5 has-data-checked:border-primary dark:has-data-checked:bg-primary/10 gap-2 group-data-[disabled=true]/field:opacity-50 has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border [&>*]:data-[slot=field]:p-3 group/field-label peer/field-label flex w-fit leading-snug", "has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col", className),
1427
+ className: cn("has-data-checked:bg-primary/5 has-data-checked:border-primary dark:has-data-checked:bg-primary/10 gap-2 group-data-[disabled=true]/field:opacity-50 has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border *:data-[slot=field]:p-3 group/field-label peer/field-label flex w-fit leading-snug", "has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col", className),
945
1428
  ...props
946
1429
  });
947
1430
  }
@@ -955,7 +1438,7 @@ function FieldTitle({ className, ...props }) {
955
1438
  function FieldDescription({ className, ...props }) {
956
1439
  return /* @__PURE__ */ jsx("p", {
957
1440
  "data-slot": "field-description",
958
- className: cn("text-muted-foreground text-left text-sm [[data-variant=legend]+&]:-mt-1.5 leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance", "last:mt-0 nth-last-2:-mt-1", "[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4", className),
1441
+ className: cn("text-muted-foreground text-left text-sm [[data-variant=legend]+&]:-mt-1.5 leading-normal font-normal group-has-data-[orientation=horizontal]/field:text-balance", "last:mt-0 nth-last-2:-mt-1", "[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4", className),
959
1442
  ...props
960
1443
  });
961
1444
  }
@@ -998,8 +1481,19 @@ function FieldError({ className, children, errors, showIcon = true, ...props })
998
1481
 
999
1482
  //#endregion
1000
1483
  //#region src/web/ui/input.tsx
1001
- function Input({ className, type, ...props }) {
1002
- return /* @__PURE__ */ jsx(Input$1, {
1484
+ /**
1485
+ * A text input field for user data entry.
1486
+ *
1487
+ * Supports all native input types including text, email, password, and file.
1488
+ * Includes focus and validation states with appropriate visual feedback.
1489
+ *
1490
+ * @example
1491
+ * ```tsx
1492
+ * <Input type="email" placeholder="Enter your email" />
1493
+ * ```
1494
+ */
1495
+ function Input({ className, type, ...props }) {
1496
+ return /* @__PURE__ */ jsx(Input$1, {
1003
1497
  type,
1004
1498
  "data-slot": "input",
1005
1499
  className: cn("dark:bg-input/30 border-input hover:border-ring/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 h-9 rounded-md border bg-transparent px-2.5 py-1 text-base shadow-xs transition-[color,box-shadow] file:h-7 file:text-sm file:font-medium focus-visible:ring-[3px] focus-visible:shadow-[0_0_8px_rgba(0,0,0,0.04)] aria-invalid:ring-[3px] md:text-sm file:text-foreground placeholder:text-muted-foreground w-full min-w-0 outline-none file:inline-flex file:border-0 file:bg-transparent disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50", className),
@@ -1061,6 +1555,7 @@ function EmailInput({ name = "email", label, hideLabel = false, showIcon = false
1061
1555
 
1062
1556
  //#endregion
1063
1557
  //#region src/web/ui/alert.tsx
1558
+ /** Alert variant styles for different semantic meanings. */
1064
1559
  const alertVariants = cva("grid gap-0.5 rounded-lg border px-4 py-3 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2.5 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4 w-full relative group/alert", {
1065
1560
  variants: { variant: {
1066
1561
  default: "bg-card text-card-foreground border-border",
@@ -1071,6 +1566,22 @@ const alertVariants = cva("grid gap-0.5 rounded-lg border px-4 py-3 text-left te
1071
1566
  } },
1072
1567
  defaultVariants: { variant: "default" }
1073
1568
  });
1569
+ /**
1570
+ * A contextual feedback message for user actions or system status.
1571
+ *
1572
+ * Supports semantic variants (info, success, warning, destructive) with
1573
+ * appropriate color coding. Use with AlertTitle and AlertDescription for
1574
+ * structured content, and AlertAction for dismissal or action buttons.
1575
+ *
1576
+ * @example
1577
+ * ```tsx
1578
+ * <Alert variant="info">
1579
+ * <InfoIcon />
1580
+ * <AlertTitle>Heads up!</AlertTitle>
1581
+ * <AlertDescription>This is an informational alert.</AlertDescription>
1582
+ * </Alert>
1583
+ * ```
1584
+ */
1074
1585
  function Alert({ className, variant, ...props }) {
1075
1586
  return /* @__PURE__ */ jsx("div", {
1076
1587
  "data-slot": "alert",
@@ -1079,6 +1590,17 @@ function Alert({ className, variant, ...props }) {
1079
1590
  ...props
1080
1591
  });
1081
1592
  }
1593
+ /**
1594
+ * The title heading for an Alert component.
1595
+ *
1596
+ * Displays prominently with medium font weight. Automatically positioned
1597
+ * after any icon in the alert.
1598
+ *
1599
+ * @example
1600
+ * ```tsx
1601
+ * <AlertTitle>Success!</AlertTitle>
1602
+ * ```
1603
+ */
1082
1604
  function AlertTitle({ className, ...props }) {
1083
1605
  return /* @__PURE__ */ jsx("div", {
1084
1606
  "data-slot": "alert-title",
@@ -1086,6 +1608,19 @@ function AlertTitle({ className, ...props }) {
1086
1608
  ...props
1087
1609
  });
1088
1610
  }
1611
+ /**
1612
+ * The descriptive text content for an Alert component.
1613
+ *
1614
+ * Displays in muted foreground color with proper text wrapping. Supports
1615
+ * multiple paragraphs with automatic spacing.
1616
+ *
1617
+ * @example
1618
+ * ```tsx
1619
+ * <AlertDescription>
1620
+ * Your changes have been saved successfully.
1621
+ * </AlertDescription>
1622
+ * ```
1623
+ */
1089
1624
  function AlertDescription({ className, ...props }) {
1090
1625
  return /* @__PURE__ */ jsx("div", {
1091
1626
  "data-slot": "alert-description",
@@ -1093,6 +1628,21 @@ function AlertDescription({ className, ...props }) {
1093
1628
  ...props
1094
1629
  });
1095
1630
  }
1631
+ /**
1632
+ * An action slot positioned in the top-right corner of an Alert.
1633
+ *
1634
+ * Use for dismiss buttons or quick actions. Absolutely positioned to not
1635
+ * interfere with the alert content flow.
1636
+ *
1637
+ * @example
1638
+ * ```tsx
1639
+ * <AlertAction>
1640
+ * <Button variant="ghost" size="icon-sm" aria-label="Dismiss">
1641
+ * <XIcon />
1642
+ * </Button>
1643
+ * </AlertAction>
1644
+ * ```
1645
+ */
1096
1646
  function AlertAction({ className, ...props }) {
1097
1647
  return /* @__PURE__ */ jsx("div", {
1098
1648
  "data-slot": "alert-action",
@@ -1103,32 +1653,67 @@ function AlertAction({ className, ...props }) {
1103
1653
 
1104
1654
  //#endregion
1105
1655
  //#region src/web/ui/button.tsx
1656
+ /** Button variant styles. */
1657
+ const variants = {
1658
+ default: "bg-primary text-primary-foreground hover:bg-primary/80",
1659
+ outline: "border-border bg-background hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 aria-expanded:bg-muted aria-expanded:text-foreground shadow-xs",
1660
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
1661
+ ghost: "hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground",
1662
+ destructive: "bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30",
1663
+ link: "text-primary underline-offset-4 hover:underline"
1664
+ };
1665
+ /** Button size styles. */
1666
+ const sizes$1 = {
1667
+ default: "h-9 gap-1.5 px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
1668
+ xs: "h-6 gap-1 rounded-[min(var(--radius-md),8px)] px-2 text-xs in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
1669
+ sm: "h-8 gap-1 rounded-[min(var(--radius-md),10px)] px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5",
1670
+ lg: "h-10 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3",
1671
+ icon: "size-9",
1672
+ "icon-xs": "size-6 rounded-[min(var(--radius-md),8px)] in-data-[slot=button-group]:rounded-md [&_svg:not([class*='size-'])]:size-3",
1673
+ "icon-sm": "size-8 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-md",
1674
+ "icon-lg": "size-10"
1675
+ };
1106
1676
  const buttonVariants = cva("focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-md border border-transparent bg-clip-padding text-sm font-medium focus-visible:ring-[3px] aria-invalid:ring-[3px] [&_svg:not([class*='size-'])]:size-4 inline-flex items-center justify-center whitespace-nowrap transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none shrink-0 [&_svg]:shrink-0 outline-none group/button select-none", {
1107
1677
  variants: {
1108
- variant: {
1109
- default: "bg-primary text-primary-foreground hover:bg-primary/80",
1110
- outline: "border-border bg-background hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 aria-expanded:bg-muted aria-expanded:text-foreground shadow-xs",
1111
- secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
1112
- ghost: "hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground",
1113
- destructive: "bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30",
1114
- link: "text-primary underline-offset-4 hover:underline"
1115
- },
1116
- size: {
1117
- default: "h-9 gap-1.5 px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
1118
- xs: "h-6 gap-1 rounded-[min(var(--radius-md),8px)] px-2 text-xs in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
1119
- sm: "h-8 gap-1 rounded-[min(var(--radius-md),10px)] px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5",
1120
- lg: "h-10 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3",
1121
- icon: "size-9",
1122
- "icon-xs": "size-6 rounded-[min(var(--radius-md),8px)] in-data-[slot=button-group]:rounded-md [&_svg:not([class*='size-'])]:size-3",
1123
- "icon-sm": "size-8 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-md",
1124
- "icon-lg": "size-10"
1125
- }
1678
+ variant: variants,
1679
+ size: sizes$1
1126
1680
  },
1127
1681
  defaultVariants: {
1128
1682
  variant: "default",
1129
1683
  size: "default"
1130
1684
  }
1131
1685
  });
1686
+ /** Available button variant keys. */
1687
+ const buttonVariantKeys = Object.keys(variants);
1688
+ /** Available button size keys. */
1689
+ const buttonSizeKeys = Object.keys(sizes$1);
1690
+ /**
1691
+ * A versatile button component with multiple visual variants and sizes.
1692
+ *
1693
+ * Supports different visual styles through the `variant` prop (default, secondary,
1694
+ * outline, ghost, destructive, link) and various sizes through the `size` prop.
1695
+ * Icon-only buttons should use `size="icon"` variants with an `aria-label` for accessibility.
1696
+ *
1697
+ * @example
1698
+ * ```tsx
1699
+ * // Default button
1700
+ * <Button>Click me</Button>
1701
+ *
1702
+ * // With variant and size
1703
+ * <Button variant="destructive" size="sm">Delete</Button>
1704
+ *
1705
+ * // With inline icon
1706
+ * <Button>
1707
+ * <IconPlus data-icon="inline-start" />
1708
+ * Create
1709
+ * </Button>
1710
+ *
1711
+ * // Icon-only button (requires aria-label)
1712
+ * <Button size="icon" variant="ghost" aria-label="Settings">
1713
+ * <IconSettings />
1714
+ * </Button>
1715
+ * ```
1716
+ */
1132
1717
  function Button({ className, variant = "default", size = "default", ...props }) {
1133
1718
  return /* @__PURE__ */ jsx(Button$1, {
1134
1719
  "data-slot": "button",
@@ -1143,120 +1728,25 @@ function Button({ className, variant = "default", size = "default", ...props })
1143
1728
 
1144
1729
  //#endregion
1145
1730
  //#region src/web/ui/spinner.tsx
1146
- function Spinner({ className, ...props }) {
1147
- return /* @__PURE__ */ jsx(Loader2Icon, {
1148
- role: "status",
1149
- "aria-label": "Loading",
1150
- className: cn("size-4 animate-spin", className),
1151
- ...props
1152
- });
1153
- }
1154
-
1155
- //#endregion
1156
- //#region src/web/ui/auth/hooks.ts
1157
1731
  /**
1158
- * Hook to access the Better Auth client, features, and config.
1159
- *
1160
- * Provides unified access to all auth-related context values:
1161
- * - `client`: The Better Auth client for making auth API calls.
1162
- * - `features`: Derived feature flags for conditional rendering based on enabled auth methods.
1163
- * - `config`: The auth configuration values for forms and validation.
1164
- *
1165
- * @returns An object containing the auth client, features, and config.
1166
- *
1167
- * @example
1168
- * ```tsx
1169
- * // Access client for auth operations
1170
- * const { client } = useAuth();
1171
- * await client.signIn.email({ email, password });
1172
- *
1173
- * // Access features for conditional rendering
1174
- * const { features } = useAuth();
1175
- * if (features.oauth.google) {
1176
- * // Show Google login button
1177
- * }
1732
+ * A spinning loading indicator.
1178
1733
  *
1179
- * // Access config for form validation
1180
- * const { config } = useAuth();
1181
- * const minPassword = config.methods?.emailPassword?.minPasswordLength ?? 8;
1182
- * ```
1183
- */
1184
- function useAuth() {
1185
- const { client, features, config } = useAuthContext();
1186
- return {
1187
- client,
1188
- features,
1189
- config
1190
- };
1191
- }
1192
- /**
1193
- * Hook for managing auth form state.
1194
- * Provides loading, error, and success state management.
1734
+ * Use to indicate async operations in progress. Includes accessible
1735
+ * status role and label for screen readers.
1195
1736
  *
1196
1737
  * @example
1197
1738
  * ```tsx
1198
- * const { state, execute, reset } = useAuthForm();
1199
- *
1200
- * const onSubmit = async (data) => {
1201
- * await execute(async () => {
1202
- * await client.signIn.email(data);
1203
- * });
1204
- * };
1205
- *
1206
- * if (state.error) {
1207
- * // Show error message
1208
- * }
1739
+ * <Spinner />
1740
+ * <Button disabled><Spinner className="mr-2" />Loading...</Button>
1209
1741
  * ```
1210
1742
  */
1211
- function useAuthForm() {
1212
- const [state, setState] = useState({
1213
- isLoading: false,
1214
- error: null,
1215
- isSuccess: false
1743
+ function Spinner({ className, ...props }) {
1744
+ return /* @__PURE__ */ jsx(Loader2Icon, {
1745
+ role: "status",
1746
+ "aria-label": "Loading",
1747
+ className: cn("size-4 animate-spin", className),
1748
+ ...props
1216
1749
  });
1217
- return {
1218
- state,
1219
- execute: useCallback(async (fn) => {
1220
- setState({
1221
- isLoading: true,
1222
- error: null,
1223
- isSuccess: false
1224
- });
1225
- try {
1226
- const result = await fn();
1227
- setState({
1228
- isLoading: false,
1229
- error: null,
1230
- isSuccess: true
1231
- });
1232
- return result;
1233
- } catch (err) {
1234
- setState({
1235
- isLoading: false,
1236
- error: err instanceof Error ? err.message : "An error occurred",
1237
- isSuccess: false
1238
- });
1239
- return;
1240
- }
1241
- }, []),
1242
- reset: useCallback(() => {
1243
- setState({
1244
- isLoading: false,
1245
- error: null,
1246
- isSuccess: false
1247
- });
1248
- }, []),
1249
- setError: useCallback((error) => {
1250
- setState((prev) => ({
1251
- ...prev,
1252
- error,
1253
- isSuccess: false
1254
- }));
1255
- }, []),
1256
- isLoading: state.isLoading,
1257
- error: state.error,
1258
- isSuccess: state.isSuccess
1259
- };
1260
1750
  }
1261
1751
 
1262
1752
  //#endregion
@@ -1418,6 +1908,9 @@ function defineEmailOtpSchema() {
1418
1908
  function EmailOTPForm({ callbackURL, className, title, description, logo, onSuccess }) {
1419
1909
  const { t: t$1 } = useTranslation$1(["ui"]);
1420
1910
  const { client, config, features } = useAuth();
1911
+ const captcha = useCaptcha();
1912
+ const [captchaToken, setCaptchaToken] = useState(null);
1913
+ const captchaResetRef = useRef(null);
1421
1914
  const { isLoading, error, execute, setError } = useAuthForm();
1422
1915
  const [stage, setStage] = useState("email");
1423
1916
  const [pendingEmail, setPendingEmail] = useState(null);
@@ -1458,16 +1951,21 @@ function EmailOTPForm({ callbackURL, className, title, description, logo, onSucc
1458
1951
  }, 1e3);
1459
1952
  };
1460
1953
  const handleEmailSubmit = async (data) => {
1461
- await execute(async () => {
1462
- const result = await client.emailOtp.sendVerificationOtp({
1954
+ if (!await execute(async () => {
1955
+ const response = await client.emailOtp.sendVerificationOtp({
1463
1956
  email: data.email,
1464
- type: "sign-in"
1957
+ type: "sign-in",
1958
+ fetchOptions: captchaToken ? { headers: { "x-captcha-response": captchaToken } } : void 0
1465
1959
  });
1466
- if (result.error) throw new Error(result.error.message || t$1("ui:auth.errors.generic"));
1960
+ if (response.error) throw new Error(response.error.message || t$1("ui:auth.errors.generic"));
1467
1961
  setPendingEmail(data.email);
1468
1962
  setStage("verify");
1469
1963
  startCountdown();
1470
- });
1964
+ return response;
1965
+ })) {
1966
+ setCaptchaToken(null);
1967
+ captchaResetRef.current?.();
1968
+ }
1471
1969
  };
1472
1970
  const handleOtpSubmit = async (data) => {
1473
1971
  if (!pendingEmail) return;
@@ -1482,14 +1980,20 @@ function EmailOTPForm({ callbackURL, className, title, description, logo, onSucc
1482
1980
  };
1483
1981
  const handleResend = async () => {
1484
1982
  if (!pendingEmail) return;
1485
- if (await execute(async () => {
1983
+ const result = await execute(async () => {
1486
1984
  const response = await client.emailOtp.sendVerificationOtp({
1487
1985
  email: pendingEmail,
1488
- type: "sign-in"
1986
+ type: "sign-in",
1987
+ fetchOptions: captchaToken ? { headers: { "x-captcha-response": captchaToken } } : void 0
1489
1988
  });
1490
1989
  if (response.error) throw new Error(response.error.message || t$1("ui:auth.errors.generic"));
1491
1990
  return response;
1492
- })) startCountdown();
1991
+ });
1992
+ if (!result) {
1993
+ setCaptchaToken(null);
1994
+ captchaResetRef.current?.();
1995
+ }
1996
+ if (result) startCountdown();
1493
1997
  };
1494
1998
  const handleBack = () => {
1495
1999
  setError(null);
@@ -1566,10 +2070,16 @@ function EmailOTPForm({ callbackURL, className, title, description, logo, onSucc
1566
2070
  label: t$1("ui:auth.login.email"),
1567
2071
  showIcon: true
1568
2072
  }),
2073
+ captcha.enabled && /* @__PURE__ */ jsx(CaptchaWidget, {
2074
+ onSuccess: setCaptchaToken,
2075
+ onExpire: () => setCaptchaToken(null),
2076
+ onError: () => setCaptchaToken(null),
2077
+ resetRef: captchaResetRef
2078
+ }),
1569
2079
  /* @__PURE__ */ jsx(Button, {
1570
2080
  type: "submit",
1571
2081
  className: "h-10 w-full font-medium",
1572
- disabled: isLoading,
2082
+ disabled: isLoading || captcha.enabled && !captchaToken,
1573
2083
  children: isLoading ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Spinner, { className: "mr-2 size-4" }), t$1("ui:auth.emailOtp.sending")] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(MailIcon, { className: "mr-2 size-4" }), t$1("ui:auth.emailOtp.send")] })
1574
2084
  })
1575
2085
  ]
@@ -1590,10 +2100,10 @@ function EmailOTPForm({ callbackURL, className, title, description, logo, onSucc
1590
2100
  * <AnimatedCheck size="lg" color="green" />
1591
2101
  *
1592
2102
  * // Primary color variant
1593
- * <AnimatedCheck size="md" color="primary" />
2103
+ * <AnimatedCheck size="default" color="primary" />
1594
2104
  * ```
1595
2105
  */
1596
- function AnimatedCheck({ className, size = "md", color = "green" }) {
2106
+ function AnimatedCheck({ className, size = "default", color = "green" }) {
1597
2107
  return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("style", { children: `
1598
2108
  @keyframes ac-scale-in {
1599
2109
  from { transform: scale(0); opacity: 0; }
@@ -1604,9 +2114,11 @@ function AnimatedCheck({ className, size = "md", color = "green" }) {
1604
2114
  }
1605
2115
  ` }), /* @__PURE__ */ jsxs("svg", {
1606
2116
  className: cn({
2117
+ xs: "size-6",
1607
2118
  sm: "size-8",
1608
- md: "size-12",
1609
- lg: "size-16"
2119
+ default: "size-12",
2120
+ lg: "size-16",
2121
+ xl: "size-20"
1610
2122
  }[size], {
1611
2123
  green: "text-green-600 dark:text-green-400",
1612
2124
  primary: "text-primary"
@@ -1667,6 +2179,9 @@ function defineForgotPasswordSchema() {
1667
2179
  function ForgotPasswordForm({ callbackURL, className, loginLink = "/login", title, description, logo, onSuccess }) {
1668
2180
  const { t: t$1 } = useTranslation$1(["ui"]);
1669
2181
  const { client } = useAuth();
2182
+ const captcha = useCaptcha();
2183
+ const [captchaToken, setCaptchaToken] = useState(null);
2184
+ const captchaResetRef = useRef(null);
1670
2185
  const { isLoading, error, isSuccess, execute, reset: resetForm } = useAuthForm();
1671
2186
  const form = useForm({
1672
2187
  resolver: zodResolver(defineForgotPasswordSchema()),
@@ -1675,14 +2190,20 @@ function ForgotPasswordForm({ callbackURL, className, loginLink = "/login", titl
1675
2190
  });
1676
2191
  const submittedEmail = form.getValues("email");
1677
2192
  const onSubmit = async (data) => {
1678
- if (await execute(async () => {
2193
+ const result = await execute(async () => {
1679
2194
  const response = await client.requestPasswordReset({
1680
2195
  email: data.email,
1681
- redirectTo: callbackURL
2196
+ redirectTo: callbackURL,
2197
+ fetchOptions: captchaToken ? { headers: { "x-captcha-response": captchaToken } } : void 0
1682
2198
  });
1683
2199
  if (response.error) throw new Error(response.error.message || t$1("ui:auth.errors.generic"));
1684
2200
  return response;
1685
- })) onSuccess?.();
2201
+ });
2202
+ if (!result) {
2203
+ setCaptchaToken(null);
2204
+ captchaResetRef.current?.();
2205
+ }
2206
+ if (result) onSuccess?.();
1686
2207
  };
1687
2208
  const handleTryAgain = () => {
1688
2209
  resetForm();
@@ -1750,10 +2271,16 @@ function ForgotPasswordForm({ callbackURL, className, loginLink = "/login", titl
1750
2271
  label: t$1("ui:auth.forgotPassword.email"),
1751
2272
  showIcon: true
1752
2273
  }),
2274
+ captcha.enabled && /* @__PURE__ */ jsx(CaptchaWidget, {
2275
+ onSuccess: setCaptchaToken,
2276
+ onExpire: () => setCaptchaToken(null),
2277
+ onError: () => setCaptchaToken(null),
2278
+ resetRef: captchaResetRef
2279
+ }),
1753
2280
  /* @__PURE__ */ jsx(Button, {
1754
2281
  type: "submit",
1755
2282
  className: "h-10 w-full font-medium",
1756
- disabled: isLoading,
2283
+ disabled: isLoading || captcha.enabled && !captchaToken,
1757
2284
  children: isLoading ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Spinner, { className: "mr-2 size-4" }), t$1("ui:auth.forgotPassword.submitting")] }) : t$1("ui:auth.forgotPassword.submit")
1758
2285
  })
1759
2286
  ]
@@ -1764,6 +2291,18 @@ function ForgotPasswordForm({ callbackURL, className, loginLink = "/login", titl
1764
2291
 
1765
2292
  //#endregion
1766
2293
  //#region src/web/ui/checkbox.tsx
2294
+ /**
2295
+ * A control for toggling a boolean value on or off.
2296
+ *
2297
+ * Use for binary choices in forms like agreeing to terms, enabling features,
2298
+ * or selecting items. Supports validation states and disabled mode.
2299
+ *
2300
+ * @example
2301
+ * ```tsx
2302
+ * <Checkbox id="terms" />
2303
+ * <Label htmlFor="terms">Accept terms and conditions</Label>
2304
+ * ```
2305
+ */
1767
2306
  function Checkbox({ className, ...props }) {
1768
2307
  return /* @__PURE__ */ jsx(Checkbox$1.Root, {
1769
2308
  "data-slot": "checkbox",
@@ -1857,7 +2396,10 @@ function defineMagicLinkSchema() {
1857
2396
  function MagicLinkForm({ callbackURL, className, successMessage, onSuccess }) {
1858
2397
  const { t: t$1 } = useTranslation$1(["ui"]);
1859
2398
  const { client, features } = useAuth();
1860
- const { isLoading, error, isSuccess, execute, setError } = useAuthForm();
2399
+ const captcha = useCaptcha();
2400
+ const [captchaToken, setCaptchaToken] = useState(null);
2401
+ const captchaResetRef = useRef(null);
2402
+ const { isLoading, error, isSuccess, execute, reset } = useAuthForm();
1861
2403
  const [sentEmail, setSentEmail] = useState(null);
1862
2404
  const resolvedSuccessMessage = successMessage ?? t$1("ui:auth.magicLink.successDescription", { email: sentEmail });
1863
2405
  const form = useForm({
@@ -1866,21 +2408,40 @@ function MagicLinkForm({ callbackURL, className, successMessage, onSuccess }) {
1866
2408
  mode: "onBlur"
1867
2409
  });
1868
2410
  if (!features.magicLink) return null;
2411
+ const handleTryDifferentEmail = () => {
2412
+ reset();
2413
+ form.reset();
2414
+ setSentEmail(null);
2415
+ };
1869
2416
  const onSubmit = async (data) => {
1870
- if (await execute(async () => {
2417
+ const result = await execute(async () => {
1871
2418
  const response = await client.signIn.magicLink({
1872
2419
  email: data.email,
1873
- callbackURL
2420
+ callbackURL,
2421
+ fetchOptions: captchaToken ? { headers: { "x-captcha-response": captchaToken } } : void 0
1874
2422
  });
1875
2423
  if (response.error) throw new Error(response.error.message || t$1("ui:auth.errors.generic"));
1876
2424
  setSentEmail(data.email);
1877
2425
  return response;
1878
- })) onSuccess?.();
2426
+ });
2427
+ if (!result) {
2428
+ setCaptchaToken(null);
2429
+ captchaResetRef.current?.();
2430
+ }
2431
+ if (result) onSuccess?.();
1879
2432
  };
1880
- if (isSuccess && sentEmail) return /* @__PURE__ */ jsxs(Alert, {
1881
- variant: "success",
1882
- className,
1883
- children: [/* @__PURE__ */ jsx(CheckCircleIcon, { className: "size-4" }), /* @__PURE__ */ jsx(AlertDescription, { children: resolvedSuccessMessage })]
2433
+ if (isSuccess && sentEmail) return /* @__PURE__ */ jsxs("div", {
2434
+ className: cn("space-y-4", className),
2435
+ children: [/* @__PURE__ */ jsxs(Alert, {
2436
+ variant: "success",
2437
+ children: [/* @__PURE__ */ jsx(CheckCircleIcon, { className: "size-4" }), /* @__PURE__ */ jsx(AlertDescription, { children: resolvedSuccessMessage })]
2438
+ }), /* @__PURE__ */ jsx(Button, {
2439
+ type: "button",
2440
+ variant: "outline",
2441
+ className: "h-10 w-full font-medium",
2442
+ onClick: handleTryDifferentEmail,
2443
+ children: t$1("ui:auth.magicLink.tryDifferentEmail")
2444
+ })]
1884
2445
  });
1885
2446
  return /* @__PURE__ */ jsx(FormProvider, {
1886
2447
  ...form,
@@ -1896,10 +2457,16 @@ function MagicLinkForm({ callbackURL, className, successMessage, onSuccess }) {
1896
2457
  name: "email",
1897
2458
  label: t$1("ui:auth.login.email")
1898
2459
  }),
2460
+ captcha.enabled && /* @__PURE__ */ jsx(CaptchaWidget, {
2461
+ onSuccess: setCaptchaToken,
2462
+ onExpire: () => setCaptchaToken(null),
2463
+ onError: () => setCaptchaToken(null),
2464
+ resetRef: captchaResetRef
2465
+ }),
1899
2466
  /* @__PURE__ */ jsx(Button, {
1900
2467
  type: "submit",
1901
2468
  className: "h-10 w-full font-medium",
1902
- disabled: isLoading,
2469
+ disabled: isLoading || captcha.enabled && !captchaToken,
1903
2470
  children: isLoading ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Spinner, { className: "mr-2 size-4" }), t$1("ui:auth.magicLink.submitting")] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(MailIcon, { className: "mr-2 size-4" }), t$1("ui:auth.magicLink.submit")] })
1904
2471
  })
1905
2472
  ]
@@ -1907,6 +2474,49 @@ function MagicLinkForm({ callbackURL, className, successMessage, onSuccess }) {
1907
2474
  });
1908
2475
  }
1909
2476
 
2477
+ //#endregion
2478
+ //#region src/web/ui/badge.tsx
2479
+ /** Badge variant styles for different visual appearances. */
2480
+ const badgeVariants = cva("h-5 gap-1 rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium transition-all has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&>svg]:size-3! inline-flex items-center justify-center w-fit whitespace-nowrap shrink-0 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-colors overflow-hidden group/badge", {
2481
+ variants: { variant: {
2482
+ default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
2483
+ secondary: "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
2484
+ destructive: "bg-destructive/10 [a]:hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 text-destructive dark:bg-destructive/20",
2485
+ outline: "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
2486
+ ghost: "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
2487
+ link: "text-primary underline-offset-4 hover:underline"
2488
+ } },
2489
+ defaultVariants: { variant: "default" }
2490
+ });
2491
+ /**
2492
+ * A small label component for highlighting status, categories, or counts.
2493
+ *
2494
+ * Supports multiple visual variants and can render as different elements
2495
+ * using the `render` prop. Commonly used for tags, status indicators,
2496
+ * and notification counts.
2497
+ *
2498
+ * @example
2499
+ * ```tsx
2500
+ * <Badge>New</Badge>
2501
+ * <Badge variant="secondary">Draft</Badge>
2502
+ * <Badge variant="destructive">Error</Badge>
2503
+ * ```
2504
+ */
2505
+ function Badge({ className, variant = "default", render, ...props }) {
2506
+ return useRender({
2507
+ defaultTagName: "span",
2508
+ props: mergeProps({ className: cn(badgeVariants({
2509
+ className,
2510
+ variant
2511
+ })) }, props),
2512
+ render,
2513
+ state: {
2514
+ slot: "badge",
2515
+ variant
2516
+ }
2517
+ });
2518
+ }
2519
+
1910
2520
  //#endregion
1911
2521
  //#region src/web/ui/auth/passkey-button.tsx
1912
2522
  /**
@@ -1923,8 +2533,10 @@ function MagicLinkForm({ callbackURL, className, successMessage, onSuccess }) {
1923
2533
  function PasskeyButton({ autoFill = true, className, disabled, label, onSuccess, onError }) {
1924
2534
  const { t: t$1 } = useTranslation$1(["ui"]);
1925
2535
  const { client, features } = useAuth();
2536
+ const { isLastMethod } = useLastUsedMethod();
1926
2537
  const [isLoading, setIsLoading] = useState(false);
1927
2538
  if (!features.passkey) return null;
2539
+ const isLast = isLastMethod("passkey");
1928
2540
  const handleSignIn = async () => {
1929
2541
  setIsLoading(true);
1930
2542
  try {
@@ -1937,30 +2549,58 @@ function PasskeyButton({ autoFill = true, className, disabled, label, onSuccess,
1937
2549
  }
1938
2550
  };
1939
2551
  return /* @__PURE__ */ jsxs(Button, {
1940
- variant: "outline",
2552
+ variant: isLast ? "default" : "outline",
1941
2553
  type: "button",
1942
2554
  disabled: disabled || isLoading,
1943
2555
  onClick: handleSignIn,
1944
2556
  className: cn("h-10 w-full gap-3 font-medium transition-all hover:shadow-sm", className),
1945
- children: [isLoading ? /* @__PURE__ */ jsx(Spinner, { className: "size-5" }) : /* @__PURE__ */ jsx(FingerprintIcon, { className: "size-5 text-primary" }), /* @__PURE__ */ jsx("span", { children: label || t$1("ui:auth.passkey.signIn") })]
2557
+ children: [
2558
+ isLoading ? /* @__PURE__ */ jsx(Spinner, { className: "size-5" }) : /* @__PURE__ */ jsx(FingerprintIcon, { className: cn("size-5", !isLast && "text-primary") }),
2559
+ /* @__PURE__ */ jsx("span", {
2560
+ className: "flex-1 text-left",
2561
+ children: label || t$1("ui:auth.passkey.signIn")
2562
+ }),
2563
+ isLast && /* @__PURE__ */ jsx(Badge, {
2564
+ variant: "secondary",
2565
+ className: "ml-1 text-xs",
2566
+ children: t$1("ui:auth.common.lastUsed")
2567
+ })
2568
+ ]
1946
2569
  });
1947
2570
  }
1948
2571
 
1949
2572
  //#endregion
1950
2573
  //#region src/web/ui/auth/password-input.tsx
1951
2574
  /**
1952
- * Calculates password strength based on common criteria.
1953
- * Returns a strength level and score for the visual indicator.
2575
+ * Calculates password strength based on length thresholds relative to minLength
2576
+ * and character variety criteria. Uses proportional thresholds so strength
2577
+ * calculation scales with the configured minimum password length.
2578
+ *
2579
+ * @param password - The password to evaluate.
2580
+ * @param minLength - The minimum required password length from config.
2581
+ * @returns Object with strength level (weak/fair/good/strong) and numeric score (0-100).
2582
+ *
2583
+ * @example
2584
+ * ```typescript
2585
+ * // With default minLength of 8
2586
+ * calculatePasswordStrength("short", 8); // { strength: "weak", score: 25 }
2587
+ * calculatePasswordStrength("LongP@ss123", 8); // { strength: "strong", score: 100 }
2588
+ *
2589
+ * // With custom minLength of 12
2590
+ * calculatePasswordStrength("Password123!", 12); // { strength: "fair", score: 50 }
2591
+ * ```
1954
2592
  */
1955
- function calculatePasswordStrength(password) {
2593
+ function calculatePasswordStrength(password, minLength) {
1956
2594
  if (!password) return {
1957
2595
  strength: "weak",
1958
2596
  score: 0
1959
2597
  };
1960
2598
  let score = 0;
1961
- if (password.length >= 8) score += 1;
1962
- if (password.length >= 12) score += 1;
1963
- if (password.length >= 16) score += 1;
2599
+ const mediumLength = Math.floor(minLength * 1.5);
2600
+ const longLength = minLength * 2;
2601
+ if (password.length >= minLength) score += 1;
2602
+ if (password.length >= mediumLength) score += 1;
2603
+ if (password.length >= longLength) score += 1;
1964
2604
  if (/[a-z]/.test(password)) score += 1;
1965
2605
  if (/[A-Z]/.test(password)) score += 1;
1966
2606
  if (/[0-9]/.test(password)) score += 1;
@@ -2011,7 +2651,7 @@ function PasswordInput({ name = "password", label, hideLabel = false, showToggle
2011
2651
  const error = form?.formState?.errors?.[name];
2012
2652
  const hasError = !!error;
2013
2653
  const passwordValue = form?.watch(name) || "";
2014
- const { strength, score } = calculatePasswordStrength(passwordValue);
2654
+ const { strength, score } = calculatePasswordStrength(passwordValue, minLength);
2015
2655
  const strengthConfig = {
2016
2656
  weak: {
2017
2657
  label: t$1("ui:auth.password.weak"),
@@ -2194,9 +2834,16 @@ const PROVIDER_CONFIG = {
2194
2834
  function SocialButtons({ callbackURL, className, disabled, layout = "stack", onSuccess }) {
2195
2835
  const { t: t$1 } = useTranslation$1(["ui"]);
2196
2836
  const { client, features } = useAuth();
2837
+ const { isLastMethod } = useLastUsedMethod();
2197
2838
  const [loadingProvider, setLoadingProvider] = useState(null);
2198
- const enabledProviders = Object.keys(PROVIDER_CONFIG).filter((provider) => features.oauth[provider]);
2199
- if (enabledProviders.length === 0) return null;
2839
+ const sortedProviders = useMemo(() => {
2840
+ return [...Object.keys(PROVIDER_CONFIG).filter((provider) => features.oauth[provider])].sort((a, b) => {
2841
+ if (isLastMethod(a)) return -1;
2842
+ if (isLastMethod(b)) return 1;
2843
+ return 0;
2844
+ });
2845
+ }, [features.oauth, isLastMethod]);
2846
+ if (sortedProviders.length === 0) return null;
2200
2847
  const handleSignIn = async (provider) => {
2201
2848
  setLoadingProvider(provider);
2202
2849
  try {
@@ -2210,17 +2857,29 @@ function SocialButtons({ callbackURL, className, disabled, layout = "stack", onS
2210
2857
  }
2211
2858
  };
2212
2859
  return /* @__PURE__ */ jsx("div", {
2213
- className: cn("grid gap-3", layout === "grid" && enabledProviders.length > 1 ? "grid-cols-2" : "grid-cols-1", className),
2214
- children: enabledProviders.map((provider) => {
2860
+ className: cn("grid gap-3", layout === "grid" && sortedProviders.length > 1 ? "grid-cols-2" : "grid-cols-1", className),
2861
+ children: sortedProviders.map((provider) => {
2215
2862
  const config = PROVIDER_CONFIG[provider];
2216
2863
  const isLoading = loadingProvider === provider;
2864
+ const isLast = isLastMethod(provider);
2217
2865
  return /* @__PURE__ */ jsxs(Button, {
2218
- variant: "outline",
2866
+ variant: isLast ? "default" : "outline",
2219
2867
  type: "button",
2220
2868
  disabled: disabled || loadingProvider !== null,
2221
2869
  onClick: () => handleSignIn(provider),
2222
2870
  className: cn("h-10 w-full gap-3 font-medium transition-all hover:shadow-sm", config.hoverClass),
2223
- children: [isLoading ? /* @__PURE__ */ jsx(Spinner, { className: "size-5" }) : config.icon, /* @__PURE__ */ jsx("span", { children: layout === "grid" && enabledProviders.length > 1 ? config.label : t$1("ui:auth.common.continueWith", { provider: config.label }) })]
2871
+ children: [
2872
+ isLoading ? /* @__PURE__ */ jsx(Spinner, { className: "size-5" }) : config.icon,
2873
+ /* @__PURE__ */ jsx("span", {
2874
+ className: "flex-1 text-left",
2875
+ children: layout === "grid" && sortedProviders.length > 1 ? config.label : t$1("ui:auth.common.continueWith", { provider: config.label })
2876
+ }),
2877
+ isLast && /* @__PURE__ */ jsx(Badge, {
2878
+ variant: "secondary",
2879
+ className: "ml-1 text-xs",
2880
+ children: t$1("ui:auth.common.lastUsed")
2881
+ })
2882
+ ]
2224
2883
  }, provider);
2225
2884
  })
2226
2885
  });
@@ -2242,7 +2901,9 @@ function SocialButtons({ callbackURL, className, disabled, layout = "stack", onS
2242
2901
  function SSOButton({ email, domain, callbackURL, className, disabled, label }) {
2243
2902
  const { t: t$1 } = useTranslation$1(["ui"]);
2244
2903
  const { client, features } = useAuth();
2904
+ const { isLastMethod } = useLastUsedMethod();
2245
2905
  const [isLoading, setIsLoading] = useState(false);
2906
+ const isLast = isLastMethod("sso");
2246
2907
  if (!features.sso) return null;
2247
2908
  const handleSignIn = async () => {
2248
2909
  setIsLoading(true);
@@ -2257,84 +2918,72 @@ function SSOButton({ email, domain, callbackURL, className, disabled, label }) {
2257
2918
  }
2258
2919
  };
2259
2920
  return /* @__PURE__ */ jsxs(Button, {
2260
- variant: "outline",
2921
+ variant: isLast ? "default" : "outline",
2261
2922
  type: "button",
2262
2923
  disabled: disabled || isLoading,
2263
2924
  onClick: handleSignIn,
2264
2925
  className: cn("h-10 w-full gap-3 font-medium transition-all hover:shadow-sm", className),
2265
- children: [isLoading ? /* @__PURE__ */ jsx(Spinner, { className: "size-5" }) : /* @__PURE__ */ jsx(Building2Icon, { className: "size-5 text-primary" }), /* @__PURE__ */ jsx("span", { children: label || t$1("ui:auth.sso.submit") })]
2926
+ children: [
2927
+ isLoading ? /* @__PURE__ */ jsx(Spinner, { className: "size-5" }) : /* @__PURE__ */ jsx(Building2Icon, { className: cn("size-5", !isLast && "text-primary") }),
2928
+ /* @__PURE__ */ jsx("span", {
2929
+ className: "flex-1 text-left",
2930
+ children: label || t$1("ui:auth.sso.submit")
2931
+ }),
2932
+ isLast && /* @__PURE__ */ jsx(Badge, {
2933
+ variant: "secondary",
2934
+ className: "ml-1 text-xs",
2935
+ children: t$1("ui:auth.common.lastUsed")
2936
+ })
2937
+ ]
2266
2938
  });
2267
2939
  }
2268
2940
 
2269
2941
  //#endregion
2270
- //#region src/web/ui/auth/forms/two-factor-form.tsx
2271
- /**
2272
- * Creates the two-factor TOTP verification schema with i18n-aware error messages.
2273
- * Accepts optional TOTP digits config for customizing validation rules.
2274
- *
2275
- * @param digits - The expected TOTP code length (default: 6).
2276
- * @returns A Zod schema for validating TOTP verification form data.
2277
- *
2278
- * @example
2279
- * ```typescript
2280
- * const config = useAuthConfig();
2281
- * const digits = config.plugins?.twoFactor?.totp?.digits ?? 6;
2282
- * const schema = defineTwoFactorTotpSchema(digits);
2283
- * ```
2284
- */
2285
- function defineTwoFactorTotpSchema(digits = 6) {
2286
- return z.object({
2287
- code: z.string().min(digits, i18n.t("ui:validation.codeLength", { length: digits })).max(digits, i18n.t("ui:validation.codeLength", { length: digits })).regex(/^\d+$/, i18n.t("ui:validation.codeDigitsOnly")),
2288
- trustDevice: z.boolean().optional()
2289
- });
2290
- }
2942
+ //#region src/web/ui/auth/phone-input.tsx
2291
2943
  /**
2292
- * Creates the two-factor backup code verification schema with i18n-aware error messages.
2293
- * Accepts optional backup code length config for customizing validation rules.
2294
- *
2295
- * @param codeLength - The expected backup code length (default: 10).
2296
- * @returns A Zod schema for validating backup code verification form data.
2944
+ * Phone number input field with react-hook-form integration.
2945
+ * Automatically handles validation errors from form context.
2946
+ * Accepts E.164 format phone numbers.
2297
2947
  *
2298
2948
  * @example
2299
- * ```typescript
2300
- * const config = useAuthConfig();
2301
- * const codeLength = config.plugins?.twoFactor?.backupCodes?.length ?? 10;
2302
- * const schema = defineTwoFactorBackupSchema(codeLength);
2949
+ * ```tsx
2950
+ * <FormProvider {...form}>
2951
+ * <PhoneInput name="phoneNumber" label="Phone number" showIcon />
2952
+ * </FormProvider>
2303
2953
  * ```
2304
2954
  */
2305
- function defineTwoFactorBackupSchema(codeLength = 10) {
2306
- return z.object({
2307
- code: z.string().min(1, i18n.t("ui:validation.backupCodeRequired")).max(codeLength * 2, i18n.t("ui:validation.backupCodeInvalid")),
2308
- trustDevice: z.boolean().optional()
2309
- });
2310
- }
2311
- function BackupCodeInput() {
2955
+ function PhoneInput({ name = "phoneNumber", label, hideLabel = false, showIcon = false, className, placeholder, ...props }) {
2312
2956
  const id = useId();
2313
2957
  const { t: t$1 } = useTranslation$1(["ui"]);
2314
2958
  const form = useFormContext();
2315
- const error = form?.formState?.errors?.code;
2959
+ const error = form?.formState?.errors?.[name];
2316
2960
  const hasError = !!error;
2961
+ const resolvedLabel = label ?? t$1("ui:auth.phoneOtp.number");
2962
+ const resolvedPlaceholder = placeholder ?? t$1("ui:auth.phoneOtp.placeholder");
2317
2963
  return /* @__PURE__ */ jsxs(Field, {
2318
2964
  "data-invalid": hasError || void 0,
2319
2965
  children: [
2320
2966
  /* @__PURE__ */ jsx(FieldLabel, {
2321
2967
  htmlFor: id,
2322
- children: t$1("ui:auth.twoFactor.backupCode")
2968
+ className: cn(hideLabel && "sr-only"),
2969
+ children: resolvedLabel
2323
2970
  }),
2324
2971
  /* @__PURE__ */ jsxs("div", {
2325
2972
  className: "relative",
2326
- children: [/* @__PURE__ */ jsx("div", {
2973
+ children: [showIcon && /* @__PURE__ */ jsx("div", {
2327
2974
  className: "pointer-events-none absolute left-3 top-1/2 -translate-y-1/2",
2328
- children: /* @__PURE__ */ jsx(KeyIcon, { className: "size-4 text-muted-foreground" })
2975
+ children: /* @__PURE__ */ jsx(PhoneIcon, { className: "size-4 text-muted-foreground" })
2329
2976
  }), /* @__PURE__ */ jsx(Input, {
2330
2977
  id,
2331
- type: "text",
2332
- autoComplete: "one-time-code",
2333
- placeholder: t$1("ui:auth.twoFactor.backupPlaceholder"),
2978
+ type: "tel",
2979
+ inputMode: "tel",
2980
+ autoComplete: "tel",
2981
+ placeholder: resolvedPlaceholder,
2334
2982
  "aria-invalid": hasError,
2335
2983
  "aria-describedby": hasError ? `${id}-error` : void 0,
2336
- className: "h-10 pl-10 font-mono",
2337
- ...form?.register("code")
2984
+ className: cn(showIcon && "pl-10", "h-10", className),
2985
+ ...form?.register(name),
2986
+ ...props
2338
2987
  })]
2339
2988
  }),
2340
2989
  error?.message && /* @__PURE__ */ jsx(FieldError, {
@@ -2344,123 +2993,425 @@ function BackupCodeInput() {
2344
2993
  ]
2345
2994
  });
2346
2995
  }
2996
+
2997
+ //#endregion
2998
+ //#region src/web/ui/auth/forms/phone-otp-form.tsx
2347
2999
  /**
2348
- * Two-factor authentication form with improved UX.
2349
- * Supports TOTP authenticator apps and backup codes.
3000
+ * Creates the OTP verification schema with i18n-aware error messages.
3001
+ * Accepts optional OTP length config for customizing validation rules.
3002
+ *
3003
+ * @param otpLength - The expected OTP code length (default: 6).
3004
+ * @returns A Zod schema for validating OTP verification form data.
3005
+ *
3006
+ * @example
3007
+ * ```typescript
3008
+ * const config = useAuthConfig();
3009
+ * const otpLength = config.methods?.phoneOtp?.otpLength ?? 6;
3010
+ * const schema = defineOtpSchema(otpLength);
3011
+ * ```
3012
+ */
3013
+ function defineOtpSchema(otpLength = 6) {
3014
+ return z.object({ code: z.string().min(otpLength, i18n.t("ui:validation.codeLength", { length: otpLength })).max(otpLength, i18n.t("ui:validation.codeLength", { length: otpLength })).regex(/^\d+$/, i18n.t("ui:validation.codeDigitsOnly")) });
3015
+ }
3016
+ /**
3017
+ * Creates the phone number schema with i18n-aware error messages.
3018
+ *
3019
+ * @returns A Zod schema for validating phone number form data.
3020
+ */
3021
+ function definePhoneNumberSchema() {
3022
+ return z.object({ phoneNumber: z.string().min(10, i18n.t("ui:validation.phoneTooShort")).regex(/^\+?[1-9]\d{1,14}$/, i18n.t("ui:validation.phoneInvalid")) });
3023
+ }
3024
+ /**
3025
+ * Phone OTP authentication form with two-stage flow.
3026
+ * Stage 1: Enter phone number to request OTP.
3027
+ * Stage 2: Enter the OTP code to verify.
2350
3028
  *
2351
3029
  * @example
2352
3030
  * ```tsx
2353
3031
  * <AuthProvider auth={auth}>
2354
- * <TwoFactorForm
2355
- * showTrustDevice
3032
+ * <PhoneOTPForm
3033
+ * callbackURL="/dashboard"
2356
3034
  * onSuccess={() => navigate("/dashboard")}
2357
3035
  * />
2358
3036
  * </AuthProvider>
2359
3037
  * ```
2360
3038
  */
2361
- function TwoFactorForm({ className, title, description, logo, showTrustDevice = true, onSuccess, onBack }) {
3039
+ function PhoneOTPForm({ callbackURL, className, title, description, logo, onSuccess }) {
2362
3040
  const { t: t$1 } = useTranslation$1(["ui"]);
2363
- const { client, config } = useAuth();
3041
+ const { client, config, features } = useAuth();
3042
+ const captcha = useCaptcha();
3043
+ const [captchaToken, setCaptchaToken] = useState(null);
3044
+ const captchaResetRef = useRef(null);
2364
3045
  const { isLoading, error, execute, setError } = useAuthForm();
2365
- const [mode, setMode] = useState("totp");
2366
- const totpDigits = config.plugins?.twoFactor?.totp?.digits ?? 6;
2367
- const backupCodeLength = config.plugins?.twoFactor?.backupCodes?.length ?? 10;
2368
- const totpForm = useForm({
2369
- resolver: zodResolver(defineTwoFactorTotpSchema(totpDigits)),
2370
- defaultValues: {
2371
- code: "",
2372
- trustDevice: false
2373
- },
3046
+ const [stage, setStage] = useState("phone");
3047
+ const [pendingPhone, setPendingPhone] = useState(null);
3048
+ const [countdown, setCountdown] = useState(0);
3049
+ const countdownRef = useRef(null);
3050
+ const otpLength = config.methods?.phoneOtp?.otpLength ?? 6;
3051
+ useEffect(() => {
3052
+ return () => {
3053
+ if (countdownRef.current) clearInterval(countdownRef.current);
3054
+ };
3055
+ }, []);
3056
+ const phoneForm = useForm({
3057
+ resolver: zodResolver(definePhoneNumberSchema()),
3058
+ defaultValues: { phoneNumber: "" },
2374
3059
  mode: "onBlur"
2375
3060
  });
2376
- const backupForm = useForm({
2377
- resolver: zodResolver(defineTwoFactorBackupSchema(backupCodeLength)),
2378
- defaultValues: {
2379
- code: "",
2380
- trustDevice: false
2381
- },
3061
+ const otpForm = useForm({
3062
+ resolver: zodResolver(defineOtpSchema(otpLength)),
3063
+ defaultValues: { code: "" },
2382
3064
  mode: "onBlur"
2383
3065
  });
2384
- const handleTotpSubmit = async (data) => {
2385
- if (await execute(async () => {
2386
- const response = await client.twoFactor.verifyTotp({
2387
- code: data.code,
2388
- trustDevice: data.trustDevice
3066
+ if (!features.phoneOtp) return null;
3067
+ /**
3068
+ * Starts a 30-second countdown timer for the resend button.
3069
+ * Clears any existing timer before starting a new one.
3070
+ */
3071
+ const startCountdown = () => {
3072
+ if (countdownRef.current) clearInterval(countdownRef.current);
3073
+ setCountdown(30);
3074
+ countdownRef.current = setInterval(() => {
3075
+ setCountdown((c) => {
3076
+ if (c <= 1) {
3077
+ if (countdownRef.current) clearInterval(countdownRef.current);
3078
+ return 0;
3079
+ }
3080
+ return c - 1;
2389
3081
  });
2390
- if (response.error) throw new Error(response.error.message || t$1("ui:auth.errors.codeInvalid"));
3082
+ }, 1e3);
3083
+ };
3084
+ const handlePhoneSubmit = async (data) => {
3085
+ if (!await execute(async () => {
3086
+ const response = await client.phoneNumber.sendOtp({
3087
+ phoneNumber: data.phoneNumber,
3088
+ fetchOptions: captchaToken ? { headers: { "x-captcha-response": captchaToken } } : void 0
3089
+ });
3090
+ if (response.error) throw new Error(response.error.message || t$1("ui:auth.errors.generic"));
3091
+ setPendingPhone(data.phoneNumber);
3092
+ setStage("verify");
3093
+ startCountdown();
2391
3094
  return response;
2392
- })) onSuccess?.();
3095
+ })) {
3096
+ setCaptchaToken(null);
3097
+ captchaResetRef.current?.();
3098
+ }
2393
3099
  };
2394
- const handleBackupSubmit = async (data) => {
3100
+ const handleOtpSubmit = async (data) => {
3101
+ if (!pendingPhone) return;
2395
3102
  if (await execute(async () => {
2396
- const response = await client.twoFactor.verifyBackupCode({
2397
- code: data.code,
2398
- trustDevice: data.trustDevice
3103
+ const response = await client.phoneNumber.verify({
3104
+ phoneNumber: pendingPhone,
3105
+ code: data.code
2399
3106
  });
2400
- if (response.error) throw new Error(response.error.message || t$1("ui:auth.errors.backupCodeInvalid"));
3107
+ if (response.error) throw new Error(response.error.message || t$1("ui:auth.otp.invalid"));
2401
3108
  return response;
2402
3109
  })) onSuccess?.();
2403
3110
  };
2404
- const switchMode = () => {
3111
+ const handleResend = async () => {
3112
+ if (!pendingPhone) return;
3113
+ const result = await execute(async () => {
3114
+ const response = await client.phoneNumber.sendOtp({
3115
+ phoneNumber: pendingPhone,
3116
+ fetchOptions: captchaToken ? { headers: { "x-captcha-response": captchaToken } } : void 0
3117
+ });
3118
+ if (response.error) throw new Error(response.error.message || t$1("ui:auth.errors.generic"));
3119
+ return response;
3120
+ });
3121
+ if (!result) {
3122
+ setCaptchaToken(null);
3123
+ captchaResetRef.current?.();
3124
+ }
3125
+ if (result) startCountdown();
3126
+ };
3127
+ const handleBack = () => {
2405
3128
  setError(null);
2406
- setMode(mode === "totp" ? "backup" : "totp");
3129
+ setStage("phone");
3130
+ otpForm.reset();
2407
3131
  };
2408
- if (mode === "backup") return /* @__PURE__ */ jsx(AuthCard, {
2409
- title: t$1("ui:auth.twoFactor.backupTitle"),
2410
- description: t$1("ui:auth.twoFactor.backupDescription"),
3132
+ if (stage === "verify" && pendingPhone) return /* @__PURE__ */ jsx(AuthCard, {
3133
+ title: t$1("ui:auth.otp.code"),
3134
+ description: t$1("ui:auth.phoneOtp.codeSent", { phone: pendingPhone }),
2411
3135
  logo,
2412
3136
  className,
2413
3137
  children: /* @__PURE__ */ jsx(FormProvider, {
2414
- ...backupForm,
3138
+ ...otpForm,
2415
3139
  children: /* @__PURE__ */ jsxs("form", {
2416
- onSubmit: backupForm.handleSubmit(handleBackupSubmit),
3140
+ onSubmit: otpForm.handleSubmit(handleOtpSubmit),
2417
3141
  className: "space-y-4",
2418
3142
  children: [
2419
3143
  error && /* @__PURE__ */ jsxs(Alert, {
2420
3144
  variant: "destructive",
2421
3145
  children: [/* @__PURE__ */ jsx(AlertCircleIcon, { className: "size-4" }), /* @__PURE__ */ jsx(AlertDescription, { children: error })]
2422
3146
  }),
2423
- /* @__PURE__ */ jsx(BackupCodeInput, {}),
2424
- showTrustDevice && /* @__PURE__ */ jsxs(Field, {
2425
- orientation: "horizontal",
2426
- className: "w-auto",
2427
- children: [/* @__PURE__ */ jsx(Checkbox, {
2428
- id: "trustDevice-backup",
2429
- ...backupForm.register("trustDevice")
2430
- }), /* @__PURE__ */ jsx(FieldLabel, {
2431
- htmlFor: "trustDevice-backup",
2432
- className: "text-sm font-normal",
2433
- children: t$1("ui:auth.twoFactor.trustDevice")
2434
- })]
3147
+ /* @__PURE__ */ jsx(OtpInput, {
3148
+ name: "code",
3149
+ label: t$1("ui:auth.otp.code"),
3150
+ length: otpLength,
3151
+ hideLabel: true,
3152
+ showSeparator: true,
3153
+ onComplete: () => otpForm.handleSubmit(handleOtpSubmit)()
2435
3154
  }),
2436
3155
  /* @__PURE__ */ jsx(Button, {
2437
3156
  type: "submit",
2438
3157
  className: "h-10 w-full font-medium",
2439
3158
  disabled: isLoading,
2440
- children: isLoading ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Spinner, { className: "mr-2 size-4" }), t$1("ui:auth.twoFactor.submitting")] }) : t$1("ui:auth.twoFactor.submit")
2441
- }),
2442
- /* @__PURE__ */ jsx(Button, {
2443
- type: "button",
2444
- variant: "ghost",
2445
- className: "w-full text-muted-foreground",
2446
- onClick: switchMode,
2447
- children: t$1("ui:auth.twoFactor.useAuthenticator")
3159
+ children: isLoading ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Spinner, { className: "mr-2 size-4" }), t$1("ui:auth.otp.verifying")] }) : t$1("ui:auth.otp.verify")
2448
3160
  }),
2449
- onBack && /* @__PURE__ */ jsxs(Button, {
2450
- type: "button",
2451
- variant: "ghost",
2452
- className: "w-full text-muted-foreground",
2453
- onClick: onBack,
2454
- children: [/* @__PURE__ */ jsx(ArrowLeftIcon, { className: "mr-2 size-4" }), t$1("ui:auth.common.backButton")]
2455
- })
2456
- ]
2457
- })
2458
- })
2459
- });
2460
- return /* @__PURE__ */ jsx(AuthCard, {
2461
- title: title || t$1("ui:auth.twoFactor.title"),
2462
- description: description || t$1("ui:auth.twoFactor.description"),
2463
- logo,
3161
+ /* @__PURE__ */ jsxs("div", {
3162
+ className: "flex gap-2",
3163
+ children: [/* @__PURE__ */ jsxs(Button, {
3164
+ type: "button",
3165
+ variant: "ghost",
3166
+ className: "flex-1 text-muted-foreground",
3167
+ onClick: handleBack,
3168
+ children: [/* @__PURE__ */ jsx(ArrowLeftIcon, { className: "mr-2 size-4" }), t$1("ui:auth.common.backButton")]
3169
+ }), /* @__PURE__ */ jsx(Button, {
3170
+ type: "button",
3171
+ variant: "ghost",
3172
+ className: "flex-1 text-muted-foreground",
3173
+ onClick: handleResend,
3174
+ disabled: isLoading || countdown > 0,
3175
+ children: countdown > 0 ? t$1("ui:auth.otp.resendIn", { seconds: countdown }) : t$1("ui:auth.phoneOtp.resend")
3176
+ })]
3177
+ })
3178
+ ]
3179
+ })
3180
+ })
3181
+ });
3182
+ return /* @__PURE__ */ jsx(AuthCard, {
3183
+ title: title || t$1("ui:auth.phoneOtp.title"),
3184
+ description,
3185
+ logo,
3186
+ className,
3187
+ children: /* @__PURE__ */ jsx(FormProvider, {
3188
+ ...phoneForm,
3189
+ children: /* @__PURE__ */ jsxs("form", {
3190
+ onSubmit: phoneForm.handleSubmit(handlePhoneSubmit),
3191
+ className: "space-y-4",
3192
+ children: [
3193
+ error && /* @__PURE__ */ jsxs(Alert, {
3194
+ variant: "destructive",
3195
+ children: [/* @__PURE__ */ jsx(AlertCircleIcon, { className: "size-4" }), /* @__PURE__ */ jsx(AlertDescription, { children: error })]
3196
+ }),
3197
+ /* @__PURE__ */ jsx(PhoneInput, {
3198
+ name: "phoneNumber",
3199
+ label: t$1("ui:auth.phoneOtp.number"),
3200
+ showIcon: true
3201
+ }),
3202
+ captcha.enabled && /* @__PURE__ */ jsx(CaptchaWidget, {
3203
+ onSuccess: setCaptchaToken,
3204
+ onExpire: () => setCaptchaToken(null),
3205
+ onError: () => setCaptchaToken(null),
3206
+ resetRef: captchaResetRef
3207
+ }),
3208
+ /* @__PURE__ */ jsx(Button, {
3209
+ type: "submit",
3210
+ className: "h-10 w-full font-medium",
3211
+ disabled: isLoading || captcha.enabled && !captchaToken,
3212
+ children: isLoading ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Spinner, { className: "mr-2 size-4" }), t$1("ui:auth.phoneOtp.sending")] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(PhoneIcon, { className: "mr-2 size-4" }), t$1("ui:auth.phoneOtp.send")] })
3213
+ })
3214
+ ]
3215
+ })
3216
+ })
3217
+ });
3218
+ }
3219
+
3220
+ //#endregion
3221
+ //#region src/web/ui/auth/forms/two-factor-form.tsx
3222
+ /**
3223
+ * Creates the two-factor TOTP verification schema with i18n-aware error messages.
3224
+ * Accepts optional TOTP digits config for customizing validation rules.
3225
+ *
3226
+ * @param digits - The expected TOTP code length (default: 6).
3227
+ * @returns A Zod schema for validating TOTP verification form data.
3228
+ *
3229
+ * @example
3230
+ * ```typescript
3231
+ * const config = useAuthConfig();
3232
+ * const digits = config.plugins?.twoFactor?.totp?.digits ?? 6;
3233
+ * const schema = defineTwoFactorTotpSchema(digits);
3234
+ * ```
3235
+ */
3236
+ function defineTwoFactorTotpSchema(digits = 6) {
3237
+ return z.object({
3238
+ code: z.string().min(digits, i18n.t("ui:validation.codeLength", { length: digits })).max(digits, i18n.t("ui:validation.codeLength", { length: digits })).regex(/^\d+$/, i18n.t("ui:validation.codeDigitsOnly")),
3239
+ trustDevice: z.boolean().optional()
3240
+ });
3241
+ }
3242
+ /**
3243
+ * Creates the two-factor backup code verification schema with i18n-aware error messages.
3244
+ * Accepts optional backup code length config for customizing validation rules.
3245
+ *
3246
+ * @param codeLength - The expected backup code length (default: 10).
3247
+ * @returns A Zod schema for validating backup code verification form data.
3248
+ *
3249
+ * @example
3250
+ * ```typescript
3251
+ * const config = useAuthConfig();
3252
+ * const codeLength = config.plugins?.twoFactor?.backupCodes?.length ?? 10;
3253
+ * const schema = defineTwoFactorBackupSchema(codeLength);
3254
+ * ```
3255
+ */
3256
+ function defineTwoFactorBackupSchema(codeLength = 10) {
3257
+ return z.object({
3258
+ code: z.string().min(1, i18n.t("ui:validation.backupCodeRequired")).max(codeLength * 2, i18n.t("ui:validation.backupCodeInvalid")),
3259
+ trustDevice: z.boolean().optional()
3260
+ });
3261
+ }
3262
+ function BackupCodeInput() {
3263
+ const id = useId();
3264
+ const { t: t$1 } = useTranslation$1(["ui"]);
3265
+ const form = useFormContext();
3266
+ const error = form?.formState?.errors?.code;
3267
+ const hasError = !!error;
3268
+ return /* @__PURE__ */ jsxs(Field, {
3269
+ "data-invalid": hasError || void 0,
3270
+ children: [
3271
+ /* @__PURE__ */ jsx(FieldLabel, {
3272
+ htmlFor: id,
3273
+ children: t$1("ui:auth.twoFactor.backupCode")
3274
+ }),
3275
+ /* @__PURE__ */ jsxs("div", {
3276
+ className: "relative",
3277
+ children: [/* @__PURE__ */ jsx("div", {
3278
+ className: "pointer-events-none absolute left-3 top-1/2 -translate-y-1/2",
3279
+ children: /* @__PURE__ */ jsx(KeyIcon, { className: "size-4 text-muted-foreground" })
3280
+ }), /* @__PURE__ */ jsx(Input, {
3281
+ id,
3282
+ type: "text",
3283
+ autoComplete: "one-time-code",
3284
+ placeholder: t$1("ui:auth.twoFactor.backupPlaceholder"),
3285
+ "aria-invalid": hasError,
3286
+ "aria-describedby": hasError ? `${id}-error` : void 0,
3287
+ className: "h-10 pl-10 font-mono",
3288
+ ...form?.register("code")
3289
+ })]
3290
+ }),
3291
+ error?.message && /* @__PURE__ */ jsx(FieldError, {
3292
+ id: `${id}-error`,
3293
+ children: String(error.message)
3294
+ })
3295
+ ]
3296
+ });
3297
+ }
3298
+ /**
3299
+ * Two-factor authentication form with improved UX.
3300
+ * Supports TOTP authenticator apps and backup codes.
3301
+ *
3302
+ * @example
3303
+ * ```tsx
3304
+ * <AuthProvider auth={auth}>
3305
+ * <TwoFactorForm
3306
+ * showTrustDevice
3307
+ * onSuccess={() => navigate("/dashboard")}
3308
+ * />
3309
+ * </AuthProvider>
3310
+ * ```
3311
+ */
3312
+ function TwoFactorForm({ className, title, description, logo, showTrustDevice = true, onSuccess, onBack }) {
3313
+ const { t: t$1 } = useTranslation$1(["ui"]);
3314
+ const { client, config } = useAuth();
3315
+ const { isLoading, error, execute, setError } = useAuthForm();
3316
+ const [mode, setMode] = useState("totp");
3317
+ const totpDigits = config.plugins?.twoFactor?.totp?.digits ?? 6;
3318
+ const backupCodeLength = config.plugins?.twoFactor?.backupCodes?.length ?? 10;
3319
+ const totpForm = useForm({
3320
+ resolver: zodResolver(defineTwoFactorTotpSchema(totpDigits)),
3321
+ defaultValues: {
3322
+ code: "",
3323
+ trustDevice: false
3324
+ },
3325
+ mode: "onBlur"
3326
+ });
3327
+ const backupForm = useForm({
3328
+ resolver: zodResolver(defineTwoFactorBackupSchema(backupCodeLength)),
3329
+ defaultValues: {
3330
+ code: "",
3331
+ trustDevice: false
3332
+ },
3333
+ mode: "onBlur"
3334
+ });
3335
+ const handleTotpSubmit = async (data) => {
3336
+ if (await execute(async () => {
3337
+ const response = await client.twoFactor.verifyTotp({
3338
+ code: data.code,
3339
+ trustDevice: data.trustDevice
3340
+ });
3341
+ if (response.error) throw new Error(response.error.message || t$1("ui:auth.errors.codeInvalid"));
3342
+ return response;
3343
+ })) onSuccess?.();
3344
+ };
3345
+ const handleBackupSubmit = async (data) => {
3346
+ if (await execute(async () => {
3347
+ const response = await client.twoFactor.verifyBackupCode({
3348
+ code: data.code,
3349
+ trustDevice: data.trustDevice
3350
+ });
3351
+ if (response.error) throw new Error(response.error.message || t$1("ui:auth.errors.backupCodeInvalid"));
3352
+ return response;
3353
+ })) onSuccess?.();
3354
+ };
3355
+ const switchMode = () => {
3356
+ setError(null);
3357
+ setMode(mode === "totp" ? "backup" : "totp");
3358
+ };
3359
+ if (mode === "backup") return /* @__PURE__ */ jsx(AuthCard, {
3360
+ title: t$1("ui:auth.twoFactor.backupTitle"),
3361
+ description: t$1("ui:auth.twoFactor.backupDescription"),
3362
+ logo,
3363
+ className,
3364
+ children: /* @__PURE__ */ jsx(FormProvider, {
3365
+ ...backupForm,
3366
+ children: /* @__PURE__ */ jsxs("form", {
3367
+ onSubmit: backupForm.handleSubmit(handleBackupSubmit),
3368
+ className: "space-y-4",
3369
+ children: [
3370
+ error && /* @__PURE__ */ jsxs(Alert, {
3371
+ variant: "destructive",
3372
+ children: [/* @__PURE__ */ jsx(AlertCircleIcon, { className: "size-4" }), /* @__PURE__ */ jsx(AlertDescription, { children: error })]
3373
+ }),
3374
+ /* @__PURE__ */ jsx(BackupCodeInput, {}),
3375
+ showTrustDevice && /* @__PURE__ */ jsxs(Field, {
3376
+ orientation: "horizontal",
3377
+ className: "w-auto",
3378
+ children: [/* @__PURE__ */ jsx(Checkbox, {
3379
+ id: "trustDevice-backup",
3380
+ ...backupForm.register("trustDevice")
3381
+ }), /* @__PURE__ */ jsx(FieldLabel, {
3382
+ htmlFor: "trustDevice-backup",
3383
+ className: "text-sm font-normal",
3384
+ children: t$1("ui:auth.twoFactor.trustDevice")
3385
+ })]
3386
+ }),
3387
+ /* @__PURE__ */ jsx(Button, {
3388
+ type: "submit",
3389
+ className: "h-10 w-full font-medium",
3390
+ disabled: isLoading,
3391
+ children: isLoading ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Spinner, { className: "mr-2 size-4" }), t$1("ui:auth.twoFactor.submitting")] }) : t$1("ui:auth.twoFactor.submit")
3392
+ }),
3393
+ /* @__PURE__ */ jsx(Button, {
3394
+ type: "button",
3395
+ variant: "ghost",
3396
+ className: "w-full text-muted-foreground",
3397
+ onClick: switchMode,
3398
+ children: t$1("ui:auth.twoFactor.useAuthenticator")
3399
+ }),
3400
+ onBack && /* @__PURE__ */ jsxs(Button, {
3401
+ type: "button",
3402
+ variant: "ghost",
3403
+ className: "w-full text-muted-foreground",
3404
+ onClick: onBack,
3405
+ children: [/* @__PURE__ */ jsx(ArrowLeftIcon, { className: "mr-2 size-4" }), t$1("ui:auth.common.backButton")]
3406
+ })
3407
+ ]
3408
+ })
3409
+ })
3410
+ });
3411
+ return /* @__PURE__ */ jsx(AuthCard, {
3412
+ title: title || t$1("ui:auth.twoFactor.title"),
3413
+ description: description || t$1("ui:auth.twoFactor.description"),
3414
+ logo,
2464
3415
  className,
2465
3416
  children: /* @__PURE__ */ jsx(FormProvider, {
2466
3417
  ...totpForm,
@@ -2566,6 +3517,9 @@ function LoginForm({ callbackURL, className, forgotPasswordLink = "/forgot-passw
2566
3517
  const { t: t$1 } = useTranslation$1(["ui"]);
2567
3518
  const { client, features } = useAuth();
2568
3519
  const { isLoading, error, execute } = useAuthForm();
3520
+ const captcha = useCaptcha();
3521
+ const [captchaToken, setCaptchaToken] = useState(null);
3522
+ const captchaResetRef = useRef(null);
2569
3523
  const [twoFactorPending, setTwoFactorPending] = useState(false);
2570
3524
  const [emailVerificationRequired, setEmailVerificationRequired] = useState(false);
2571
3525
  const [resendSuccess, setResendSuccess] = useState(false);
@@ -2579,7 +3533,7 @@ function LoginForm({ callbackURL, className, forgotPasswordLink = "/forgot-passw
2579
3533
  mode: "onBlur"
2580
3534
  });
2581
3535
  const email = form.watch("email");
2582
- const hasAlternativeAuth = features.magicLink || features.passkey || features.sso || Object.values(features.oauth).some(Boolean);
3536
+ const hasAlternativeAuth = features.magicLink || features.passkey || features.sso || features.phoneOtp || features.emailOtp || Object.values(features.oauth).some(Boolean);
2583
3537
  const hasPasswordlessOnly = !features.emailPassword && hasAlternativeAuth;
2584
3538
  const hasAnyAuthMethod = features.emailPassword || hasAlternativeAuth;
2585
3539
  const handleResendVerification = async () => {
@@ -2603,7 +3557,8 @@ function LoginForm({ callbackURL, className, forgotPasswordLink = "/forgot-passw
2603
3557
  email: data.email,
2604
3558
  password: data.password,
2605
3559
  rememberMe: data.rememberMe,
2606
- callbackURL
3560
+ callbackURL,
3561
+ fetchOptions: captchaToken ? { headers: { "x-captcha-response": captchaToken } } : void 0
2607
3562
  });
2608
3563
  if (response.error?.status === 403) {
2609
3564
  setEmailVerificationRequired(true);
@@ -2614,410 +3569,173 @@ function LoginForm({ callbackURL, className, forgotPasswordLink = "/forgot-passw
2614
3569
  return response;
2615
3570
  }
2616
3571
  if (response.error) throw new Error(response.error.message || t$1("ui:auth.errors.invalidCredentials"));
2617
- return response;
2618
- });
2619
- if (result) {
2620
- if (!result.data?.twoFactorRedirect) onSuccess?.();
2621
- }
2622
- };
2623
- const footer = signupLink && /* @__PURE__ */ jsxs("span", { children: [
2624
- t$1("ui:auth.login.noAccount"),
2625
- " ",
2626
- /* @__PURE__ */ jsx(Link$1, {
2627
- to: signupLink,
2628
- className: "font-medium text-primary transition-colors hover:text-primary/80 hover:underline",
2629
- children: t$1("ui:auth.login.signUp")
2630
- })
2631
- ] });
2632
- if (twoFactorPending) return /* @__PURE__ */ jsx(TwoFactorForm, {
2633
- className,
2634
- logo,
2635
- onSuccess,
2636
- onBack: () => setTwoFactorPending(false)
2637
- });
2638
- if (!hasAnyAuthMethod) return /* @__PURE__ */ jsx(AuthCard, {
2639
- title: title || t$1("ui:auth.login.title"),
2640
- description,
2641
- footer,
2642
- logo,
2643
- showSecurityIcon,
2644
- className,
2645
- children: /* @__PURE__ */ jsx(Empty, {
2646
- className: "border-none p-6",
2647
- children: /* @__PURE__ */ jsxs(EmptyHeader, { children: [
2648
- /* @__PURE__ */ jsx(EmptyMedia, {
2649
- variant: "icon",
2650
- children: /* @__PURE__ */ jsx(SettingsIcon, {})
2651
- }),
2652
- /* @__PURE__ */ jsx(EmptyTitle, { children: t$1("ui:auth.common.noMethodsConfigured") }),
2653
- /* @__PURE__ */ jsx(EmptyDescription, { children: t$1("ui:auth.common.noMethodsDescription") })
2654
- ] })
2655
- })
2656
- });
2657
- return /* @__PURE__ */ jsx(AuthCard, {
2658
- title: title || t$1("ui:auth.login.title"),
2659
- description: description || t$1("ui:auth.login.description"),
2660
- footer,
2661
- logo,
2662
- showSecurityIcon,
2663
- className,
2664
- children: /* @__PURE__ */ jsx(FormProvider, {
2665
- ...form,
2666
- children: /* @__PURE__ */ jsxs("form", {
2667
- onSubmit: form.handleSubmit(onSubmit),
2668
- className: "space-y-4",
2669
- children: [
2670
- error && !emailVerificationRequired && /* @__PURE__ */ jsxs(Alert, {
2671
- variant: "destructive",
2672
- children: [/* @__PURE__ */ jsx(AlertCircleIcon, { className: "size-4" }), /* @__PURE__ */ jsx(AlertDescription, { children: error })]
2673
- }),
2674
- emailVerificationRequired && (resendSuccess ? /* @__PURE__ */ jsxs(Alert, {
2675
- variant: "success",
2676
- children: [/* @__PURE__ */ jsx(CheckCircleIcon, { className: "size-4" }), /* @__PURE__ */ jsx(AlertDescription, { children: t$1("ui:auth.verifyEmail.resendSuccess") })]
2677
- }) : /* @__PURE__ */ jsxs(Alert, {
2678
- variant: "warning",
2679
- children: [
2680
- /* @__PURE__ */ jsx(MailIcon, { className: "size-4" }),
2681
- /* @__PURE__ */ jsx(AlertDescription, { children: t$1("ui:auth.login.emailNotVerifiedDescription") }),
2682
- /* @__PURE__ */ jsx("div", {
2683
- className: "col-start-2 flex justify-end pt-2",
2684
- children: /* @__PURE__ */ jsxs(Button, {
2685
- type: "button",
2686
- size: "sm",
2687
- className: "h-7 bg-amber-900 text-white hover:bg-amber-800 dark:bg-amber-100 dark:text-amber-900 dark:hover:bg-amber-200",
2688
- onClick: handleResendVerification,
2689
- disabled: isLoading,
2690
- children: [isLoading && /* @__PURE__ */ jsx(Spinner, { className: "size-3" }), t$1("ui:auth.verifyEmail.resend")]
2691
- })
2692
- })
2693
- ]
2694
- })),
2695
- hasPasswordlessOnly && /* @__PURE__ */ jsxs("div", {
2696
- className: "space-y-3",
2697
- children: [
2698
- /* @__PURE__ */ jsx(PasskeyButton, {
2699
- disabled: isLoading,
2700
- onSuccess
2701
- }),
2702
- /* @__PURE__ */ jsx(SocialButtons, {
2703
- callbackURL,
2704
- disabled: isLoading
2705
- }),
2706
- /* @__PURE__ */ jsx(SSOButton, {
2707
- email,
2708
- callbackURL,
2709
- disabled: isLoading
2710
- })
2711
- ]
2712
- }),
2713
- features.emailPassword && /* @__PURE__ */ jsxs(Fragment, { children: [
2714
- (features.passkey || Object.values(features.oauth).some(Boolean)) && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
2715
- className: "space-y-3",
2716
- children: [/* @__PURE__ */ jsx(PasskeyButton, {
2717
- disabled: isLoading,
2718
- onSuccess
2719
- }), /* @__PURE__ */ jsx(SocialButtons, {
2720
- callbackURL,
2721
- disabled: isLoading
2722
- })]
2723
- }), /* @__PURE__ */ jsx(FieldSeparator, { children: t$1("ui:auth.login.orContinueWith") })] }),
2724
- /* @__PURE__ */ jsx(EmailInput, {
2725
- name: "email",
2726
- label: t$1("ui:auth.login.email"),
2727
- showIcon: true
2728
- }),
2729
- /* @__PURE__ */ jsx(PasswordInput, {
2730
- name: "password",
2731
- label: t$1("ui:auth.login.password"),
2732
- showIcon: true
2733
- }),
2734
- /* @__PURE__ */ jsxs("div", {
2735
- className: "flex items-center justify-between",
2736
- children: [showRememberMe && /* @__PURE__ */ jsxs(Field, {
2737
- orientation: "horizontal",
2738
- className: "w-auto",
2739
- children: [/* @__PURE__ */ jsx(Checkbox, {
2740
- id: "rememberMe",
2741
- ...form.register("rememberMe")
2742
- }), /* @__PURE__ */ jsx(FieldLabel, {
2743
- htmlFor: "rememberMe",
2744
- className: "text-sm font-normal",
2745
- children: t$1("ui:auth.login.rememberMe")
2746
- })]
2747
- }), forgotPasswordLink && /* @__PURE__ */ jsx(Link$1, {
2748
- to: forgotPasswordLink,
2749
- className: "text-sm text-muted-foreground transition-colors hover:text-primary",
2750
- children: t$1("ui:auth.login.forgotPassword")
2751
- })]
2752
- }),
2753
- /* @__PURE__ */ jsx(Button, {
2754
- type: "submit",
2755
- className: "h-10 w-full font-medium",
2756
- disabled: isLoading,
2757
- children: isLoading ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Spinner, { className: "mr-2 size-4" }), t$1("ui:auth.login.submitting")] }) : t$1("ui:auth.login.submit")
2758
- }),
2759
- features.sso && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(FieldSeparator, { children: t$1("ui:auth.common.or") }), /* @__PURE__ */ jsx(SSOButton, {
2760
- email,
2761
- callbackURL,
2762
- disabled: isLoading
2763
- })] })
2764
- ] }),
2765
- features.magicLink && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(FieldSeparator, { children: t$1("ui:auth.login.orSignInWith") }), /* @__PURE__ */ jsx(MagicLinkForm, { callbackURL })] })
2766
- ]
2767
- })
2768
- })
2769
- });
2770
- }
2771
-
2772
- //#endregion
2773
- //#region src/web/ui/auth/phone-input.tsx
2774
- /**
2775
- * Phone number input field with react-hook-form integration.
2776
- * Automatically handles validation errors from form context.
2777
- * Accepts E.164 format phone numbers.
2778
- *
2779
- * @example
2780
- * ```tsx
2781
- * <FormProvider {...form}>
2782
- * <PhoneInput name="phoneNumber" label="Phone number" showIcon />
2783
- * </FormProvider>
2784
- * ```
2785
- */
2786
- function PhoneInput({ name = "phoneNumber", label, hideLabel = false, showIcon = false, className, placeholder, ...props }) {
2787
- const id = useId();
2788
- const { t: t$1 } = useTranslation$1(["ui"]);
2789
- const form = useFormContext();
2790
- const error = form?.formState?.errors?.[name];
2791
- const hasError = !!error;
2792
- const resolvedLabel = label ?? t$1("ui:auth.phoneOtp.number");
2793
- const resolvedPlaceholder = placeholder ?? t$1("ui:auth.phoneOtp.placeholder");
2794
- return /* @__PURE__ */ jsxs(Field, {
2795
- "data-invalid": hasError || void 0,
2796
- children: [
2797
- /* @__PURE__ */ jsx(FieldLabel, {
2798
- htmlFor: id,
2799
- className: cn(hideLabel && "sr-only"),
2800
- children: resolvedLabel
2801
- }),
2802
- /* @__PURE__ */ jsxs("div", {
2803
- className: "relative",
2804
- children: [showIcon && /* @__PURE__ */ jsx("div", {
2805
- className: "pointer-events-none absolute left-3 top-1/2 -translate-y-1/2",
2806
- children: /* @__PURE__ */ jsx(PhoneIcon, { className: "size-4 text-muted-foreground" })
2807
- }), /* @__PURE__ */ jsx(Input, {
2808
- id,
2809
- type: "tel",
2810
- inputMode: "tel",
2811
- autoComplete: "tel",
2812
- placeholder: resolvedPlaceholder,
2813
- "aria-invalid": hasError,
2814
- "aria-describedby": hasError ? `${id}-error` : void 0,
2815
- className: cn(showIcon && "pl-10", "h-10", className),
2816
- ...form?.register(name),
2817
- ...props
2818
- })]
2819
- }),
2820
- error?.message && /* @__PURE__ */ jsx(FieldError, {
2821
- id: `${id}-error`,
2822
- children: String(error.message)
2823
- })
2824
- ]
2825
- });
2826
- }
2827
-
2828
- //#endregion
2829
- //#region src/web/ui/auth/forms/phone-otp-form.tsx
2830
- /**
2831
- * Creates the OTP verification schema with i18n-aware error messages.
2832
- * Accepts optional OTP length config for customizing validation rules.
2833
- *
2834
- * @param otpLength - The expected OTP code length (default: 6).
2835
- * @returns A Zod schema for validating OTP verification form data.
2836
- *
2837
- * @example
2838
- * ```typescript
2839
- * const config = useAuthConfig();
2840
- * const otpLength = config.methods?.phoneOtp?.otpLength ?? 6;
2841
- * const schema = defineOtpSchema(otpLength);
2842
- * ```
2843
- */
2844
- function defineOtpSchema(otpLength = 6) {
2845
- return z.object({ code: z.string().min(otpLength, i18n.t("ui:validation.codeLength", { length: otpLength })).max(otpLength, i18n.t("ui:validation.codeLength", { length: otpLength })).regex(/^\d+$/, i18n.t("ui:validation.codeDigitsOnly")) });
2846
- }
2847
- /**
2848
- * Creates the phone number schema with i18n-aware error messages.
2849
- *
2850
- * @returns A Zod schema for validating phone number form data.
2851
- */
2852
- function definePhoneNumberSchema() {
2853
- return z.object({ phoneNumber: z.string().min(10, i18n.t("ui:validation.phoneTooShort")).regex(/^\+?[1-9]\d{1,14}$/, i18n.t("ui:validation.phoneInvalid")) });
2854
- }
2855
- /**
2856
- * Phone OTP authentication form with two-stage flow.
2857
- * Stage 1: Enter phone number to request OTP.
2858
- * Stage 2: Enter the OTP code to verify.
2859
- *
2860
- * @example
2861
- * ```tsx
2862
- * <AuthProvider auth={auth}>
2863
- * <PhoneOTPForm
2864
- * callbackURL="/dashboard"
2865
- * onSuccess={() => navigate("/dashboard")}
2866
- * />
2867
- * </AuthProvider>
2868
- * ```
2869
- */
2870
- function PhoneOTPForm({ callbackURL, className, title, description, logo, onSuccess }) {
2871
- const { t: t$1 } = useTranslation$1(["ui"]);
2872
- const { client, config, features } = useAuth();
2873
- const { isLoading, error, execute, setError } = useAuthForm();
2874
- const [stage, setStage] = useState("phone");
2875
- const [pendingPhone, setPendingPhone] = useState(null);
2876
- const [countdown, setCountdown] = useState(0);
2877
- const countdownRef = useRef(null);
2878
- const otpLength = config.methods?.phoneOtp?.otpLength ?? 6;
2879
- useEffect(() => {
2880
- return () => {
2881
- if (countdownRef.current) clearInterval(countdownRef.current);
2882
- };
2883
- }, []);
2884
- const phoneForm = useForm({
2885
- resolver: zodResolver(definePhoneNumberSchema()),
2886
- defaultValues: { phoneNumber: "" },
2887
- mode: "onBlur"
2888
- });
2889
- const otpForm = useForm({
2890
- resolver: zodResolver(defineOtpSchema(otpLength)),
2891
- defaultValues: { code: "" },
2892
- mode: "onBlur"
2893
- });
2894
- if (!features.phoneOtp) return null;
2895
- /**
2896
- * Starts a 30-second countdown timer for the resend button.
2897
- * Clears any existing timer before starting a new one.
2898
- */
2899
- const startCountdown = () => {
2900
- if (countdownRef.current) clearInterval(countdownRef.current);
2901
- setCountdown(30);
2902
- countdownRef.current = setInterval(() => {
2903
- setCountdown((c) => {
2904
- if (c <= 1) {
2905
- if (countdownRef.current) clearInterval(countdownRef.current);
2906
- return 0;
2907
- }
2908
- return c - 1;
2909
- });
2910
- }, 1e3);
2911
- };
2912
- const handlePhoneSubmit = async (data) => {
2913
- await execute(async () => {
2914
- const result = await client.phoneNumber.sendOtp({ phoneNumber: data.phoneNumber });
2915
- if (result.error) throw new Error(result.error.message || t$1("ui:auth.errors.generic"));
2916
- setPendingPhone(data.phoneNumber);
2917
- setStage("verify");
2918
- startCountdown();
2919
- });
2920
- };
2921
- const handleOtpSubmit = async (data) => {
2922
- if (!pendingPhone) return;
2923
- if (await execute(async () => {
2924
- const response = await client.phoneNumber.verify({
2925
- phoneNumber: pendingPhone,
2926
- code: data.code
2927
- });
2928
- if (response.error) throw new Error(response.error.message || t$1("ui:auth.otp.invalid"));
2929
- return response;
2930
- })) onSuccess?.();
2931
- };
2932
- const handleResend = async () => {
2933
- if (!pendingPhone) return;
2934
- if (await execute(async () => {
2935
- const response = await client.phoneNumber.sendOtp({ phoneNumber: pendingPhone });
2936
- if (response.error) throw new Error(response.error.message || t$1("ui:auth.errors.generic"));
2937
- return response;
2938
- })) startCountdown();
2939
- };
2940
- const handleBack = () => {
2941
- setError(null);
2942
- setStage("phone");
2943
- otpForm.reset();
3572
+ return response;
3573
+ });
3574
+ if (!result) {
3575
+ setCaptchaToken(null);
3576
+ captchaResetRef.current?.();
3577
+ }
3578
+ if (result) {
3579
+ if (!result.data?.twoFactorRedirect) onSuccess?.();
3580
+ }
2944
3581
  };
2945
- if (stage === "verify" && pendingPhone) return /* @__PURE__ */ jsx(AuthCard, {
2946
- title: t$1("ui:auth.otp.code"),
2947
- description: t$1("ui:auth.phoneOtp.codeSent", { phone: pendingPhone }),
3582
+ const footer = signupLink && /* @__PURE__ */ jsxs("span", { children: [
3583
+ t$1("ui:auth.login.noAccount"),
3584
+ " ",
3585
+ /* @__PURE__ */ jsx(Link$1, {
3586
+ to: signupLink,
3587
+ className: "font-medium text-primary transition-colors hover:text-primary/80 hover:underline",
3588
+ children: t$1("ui:auth.login.signUp")
3589
+ })
3590
+ ] });
3591
+ if (twoFactorPending) return /* @__PURE__ */ jsx(TwoFactorForm, {
3592
+ className,
3593
+ logo,
3594
+ onSuccess,
3595
+ onBack: () => setTwoFactorPending(false)
3596
+ });
3597
+ if (!hasAnyAuthMethod) return /* @__PURE__ */ jsx(AuthCard, {
3598
+ title: title || t$1("ui:auth.login.title"),
3599
+ description,
3600
+ footer,
2948
3601
  logo,
3602
+ showSecurityIcon,
2949
3603
  className,
2950
- children: /* @__PURE__ */ jsx(FormProvider, {
2951
- ...otpForm,
2952
- children: /* @__PURE__ */ jsxs("form", {
2953
- onSubmit: otpForm.handleSubmit(handleOtpSubmit),
2954
- className: "space-y-4",
2955
- children: [
2956
- error && /* @__PURE__ */ jsxs(Alert, {
2957
- variant: "destructive",
2958
- children: [/* @__PURE__ */ jsx(AlertCircleIcon, { className: "size-4" }), /* @__PURE__ */ jsx(AlertDescription, { children: error })]
2959
- }),
2960
- /* @__PURE__ */ jsx(OtpInput, {
2961
- name: "code",
2962
- label: t$1("ui:auth.otp.code"),
2963
- length: otpLength,
2964
- hideLabel: true,
2965
- showSeparator: true,
2966
- onComplete: () => otpForm.handleSubmit(handleOtpSubmit)()
2967
- }),
2968
- /* @__PURE__ */ jsx(Button, {
2969
- type: "submit",
2970
- className: "h-10 w-full font-medium",
2971
- disabled: isLoading,
2972
- children: isLoading ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Spinner, { className: "mr-2 size-4" }), t$1("ui:auth.otp.verifying")] }) : t$1("ui:auth.otp.verify")
2973
- }),
2974
- /* @__PURE__ */ jsxs("div", {
2975
- className: "flex gap-2",
2976
- children: [/* @__PURE__ */ jsxs(Button, {
2977
- type: "button",
2978
- variant: "ghost",
2979
- className: "flex-1 text-muted-foreground",
2980
- onClick: handleBack,
2981
- children: [/* @__PURE__ */ jsx(ArrowLeftIcon, { className: "mr-2 size-4" }), t$1("ui:auth.common.backButton")]
2982
- }), /* @__PURE__ */ jsx(Button, {
2983
- type: "button",
2984
- variant: "ghost",
2985
- className: "flex-1 text-muted-foreground",
2986
- onClick: handleResend,
2987
- disabled: isLoading || countdown > 0,
2988
- children: countdown > 0 ? t$1("ui:auth.otp.resendIn", { seconds: countdown }) : t$1("ui:auth.phoneOtp.resend")
2989
- })]
2990
- })
2991
- ]
2992
- })
3604
+ children: /* @__PURE__ */ jsx(Empty, {
3605
+ className: "border-none p-6",
3606
+ children: /* @__PURE__ */ jsxs(EmptyHeader, { children: [
3607
+ /* @__PURE__ */ jsx(EmptyMedia, {
3608
+ variant: "icon",
3609
+ children: /* @__PURE__ */ jsx(SettingsIcon, {})
3610
+ }),
3611
+ /* @__PURE__ */ jsx(EmptyTitle, { children: t$1("ui:auth.common.noMethodsConfigured") }),
3612
+ /* @__PURE__ */ jsx(EmptyDescription, { children: t$1("ui:auth.common.noMethodsDescription") })
3613
+ ] })
2993
3614
  })
2994
3615
  });
2995
3616
  return /* @__PURE__ */ jsx(AuthCard, {
2996
- title: title || t$1("ui:auth.phoneOtp.title"),
2997
- description,
3617
+ title: title || t$1("ui:auth.login.title"),
3618
+ description: description || t$1("ui:auth.login.description"),
3619
+ footer,
2998
3620
  logo,
3621
+ showSecurityIcon,
2999
3622
  className,
3000
3623
  children: /* @__PURE__ */ jsx(FormProvider, {
3001
- ...phoneForm,
3624
+ ...form,
3002
3625
  children: /* @__PURE__ */ jsxs("form", {
3003
- onSubmit: phoneForm.handleSubmit(handlePhoneSubmit),
3626
+ onSubmit: form.handleSubmit(onSubmit),
3004
3627
  className: "space-y-4",
3005
3628
  children: [
3006
- error && /* @__PURE__ */ jsxs(Alert, {
3629
+ error && !emailVerificationRequired && /* @__PURE__ */ jsxs(Alert, {
3007
3630
  variant: "destructive",
3008
3631
  children: [/* @__PURE__ */ jsx(AlertCircleIcon, { className: "size-4" }), /* @__PURE__ */ jsx(AlertDescription, { children: error })]
3009
3632
  }),
3010
- /* @__PURE__ */ jsx(PhoneInput, {
3011
- name: "phoneNumber",
3012
- label: t$1("ui:auth.phoneOtp.number"),
3013
- showIcon: true
3633
+ emailVerificationRequired && (resendSuccess ? /* @__PURE__ */ jsxs(Alert, {
3634
+ variant: "success",
3635
+ children: [/* @__PURE__ */ jsx(CheckCircleIcon, { className: "size-4" }), /* @__PURE__ */ jsx(AlertDescription, { children: t$1("ui:auth.verifyEmail.resendSuccess") })]
3636
+ }) : /* @__PURE__ */ jsxs(Alert, {
3637
+ variant: "warning",
3638
+ children: [
3639
+ /* @__PURE__ */ jsx(MailIcon, { className: "size-4" }),
3640
+ /* @__PURE__ */ jsx(AlertDescription, { children: t$1("ui:auth.login.emailNotVerifiedDescription") }),
3641
+ /* @__PURE__ */ jsx("div", {
3642
+ className: "col-start-2 flex justify-end pt-2",
3643
+ children: /* @__PURE__ */ jsxs(Button, {
3644
+ type: "button",
3645
+ size: "sm",
3646
+ className: "h-7 bg-amber-900 text-white hover:bg-amber-800 dark:bg-amber-100 dark:text-amber-900 dark:hover:bg-amber-200",
3647
+ onClick: handleResendVerification,
3648
+ disabled: isLoading,
3649
+ children: [isLoading && /* @__PURE__ */ jsx(Spinner, { className: "size-3" }), t$1("ui:auth.verifyEmail.resend")]
3650
+ })
3651
+ })
3652
+ ]
3653
+ })),
3654
+ hasPasswordlessOnly && /* @__PURE__ */ jsxs("div", {
3655
+ className: "space-y-3",
3656
+ children: [
3657
+ /* @__PURE__ */ jsx(PasskeyButton, {
3658
+ disabled: isLoading,
3659
+ onSuccess
3660
+ }),
3661
+ /* @__PURE__ */ jsx(SocialButtons, {
3662
+ callbackURL,
3663
+ disabled: isLoading
3664
+ }),
3665
+ /* @__PURE__ */ jsx(SSOButton, {
3666
+ email,
3667
+ callbackURL,
3668
+ disabled: isLoading
3669
+ }),
3670
+ features.phoneOtp && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(FieldSeparator, { children: t$1("ui:auth.login.orSignInWith") }), /* @__PURE__ */ jsx(PhoneOTPForm, {
3671
+ callbackURL,
3672
+ onSuccess
3673
+ })] }),
3674
+ features.emailOtp && /* @__PURE__ */ jsxs(Fragment, { children: [!features.phoneOtp && /* @__PURE__ */ jsx(FieldSeparator, { children: t$1("ui:auth.login.orSignInWith") }), /* @__PURE__ */ jsx(EmailOTPForm, {
3675
+ callbackURL,
3676
+ onSuccess
3677
+ })] })
3678
+ ]
3014
3679
  }),
3015
- /* @__PURE__ */ jsx(Button, {
3016
- type: "submit",
3017
- className: "h-10 w-full font-medium",
3018
- disabled: isLoading,
3019
- children: isLoading ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Spinner, { className: "mr-2 size-4" }), t$1("ui:auth.phoneOtp.sending")] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(PhoneIcon, { className: "mr-2 size-4" }), t$1("ui:auth.phoneOtp.send")] })
3020
- })
3680
+ features.emailPassword && /* @__PURE__ */ jsxs(Fragment, { children: [
3681
+ (features.passkey || Object.values(features.oauth).some(Boolean)) && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
3682
+ className: "space-y-3",
3683
+ children: [/* @__PURE__ */ jsx(PasskeyButton, {
3684
+ disabled: isLoading,
3685
+ onSuccess
3686
+ }), /* @__PURE__ */ jsx(SocialButtons, {
3687
+ callbackURL,
3688
+ disabled: isLoading
3689
+ })]
3690
+ }), /* @__PURE__ */ jsx(FieldSeparator, { children: t$1("ui:auth.login.orContinueWith") })] }),
3691
+ /* @__PURE__ */ jsx(EmailInput, {
3692
+ name: "email",
3693
+ label: t$1("ui:auth.login.email"),
3694
+ showIcon: true
3695
+ }),
3696
+ /* @__PURE__ */ jsx(PasswordInput, {
3697
+ name: "password",
3698
+ label: t$1("ui:auth.login.password"),
3699
+ showIcon: true
3700
+ }),
3701
+ /* @__PURE__ */ jsxs("div", {
3702
+ className: "flex items-center justify-between",
3703
+ children: [showRememberMe && /* @__PURE__ */ jsxs(Field, {
3704
+ orientation: "horizontal",
3705
+ className: "w-auto",
3706
+ children: [/* @__PURE__ */ jsx(Checkbox, {
3707
+ id: "rememberMe",
3708
+ ...form.register("rememberMe")
3709
+ }), /* @__PURE__ */ jsx(FieldLabel, {
3710
+ htmlFor: "rememberMe",
3711
+ className: "text-sm font-normal",
3712
+ children: t$1("ui:auth.login.rememberMe")
3713
+ })]
3714
+ }), forgotPasswordLink && /* @__PURE__ */ jsx(Link$1, {
3715
+ to: forgotPasswordLink,
3716
+ className: "text-sm text-muted-foreground transition-colors hover:text-primary",
3717
+ children: t$1("ui:auth.login.forgotPassword")
3718
+ })]
3719
+ }),
3720
+ captcha.enabled && /* @__PURE__ */ jsx(CaptchaWidget, {
3721
+ onSuccess: setCaptchaToken,
3722
+ onExpire: () => setCaptchaToken(null),
3723
+ onError: () => setCaptchaToken(null),
3724
+ resetRef: captchaResetRef
3725
+ }),
3726
+ /* @__PURE__ */ jsx(Button, {
3727
+ type: "submit",
3728
+ className: "h-10 w-full font-medium",
3729
+ disabled: isLoading || captcha.enabled && !captchaToken,
3730
+ children: isLoading ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Spinner, { className: "mr-2 size-4" }), t$1("ui:auth.login.submitting")] }) : t$1("ui:auth.login.submit")
3731
+ }),
3732
+ features.sso && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(FieldSeparator, { children: t$1("ui:auth.common.or") }), /* @__PURE__ */ jsx(SSOButton, {
3733
+ email,
3734
+ callbackURL,
3735
+ disabled: isLoading
3736
+ })] })
3737
+ ] }),
3738
+ features.magicLink && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(FieldSeparator, { children: t$1("ui:auth.login.orSignInWith") }), /* @__PURE__ */ jsx(MagicLinkForm, { callbackURL })] })
3021
3739
  ]
3022
3740
  })
3023
3741
  })
@@ -3232,25 +3950,33 @@ function ResetPasswordForm({ token, className, loginLink = "/login", forgotPassw
3232
3950
  //#region src/web/ui/auth/forms/signup-form.tsx
3233
3951
  /**
3234
3952
  * Creates the signup form schema with i18n-aware error messages.
3235
- * Accepts optional password config for customizing validation rules.
3953
+ * Accepts optional password and username config for customizing validation rules.
3236
3954
  *
3237
- * @param passwordConfig - Optional password validation config from authConfig.methods.emailPassword.
3955
+ * @param options - Schema configuration options.
3238
3956
  * @returns A Zod schema for validating signup form data.
3239
3957
  *
3240
3958
  * @example
3241
3959
  * ```typescript
3242
3960
  * const config = useAuthConfig();
3243
- * const schema = defineSignupSchema(config.methods?.emailPassword);
3961
+ * const schema = defineSignupSchema({
3962
+ * passwordConfig: config.methods?.emailPassword,
3963
+ * usernameConfig: config.plugins?.username,
3964
+ * usernameEnabled: features.username,
3965
+ * });
3244
3966
  * ```
3245
3967
  */
3246
- function defineSignupSchema(passwordConfig) {
3247
- const minPassword = passwordConfig?.minPasswordLength ?? 8;
3248
- const maxPassword = passwordConfig?.maxPasswordLength ?? 128;
3249
- return z.object({
3968
+ function defineSignupSchema(options) {
3969
+ const minPassword = options?.passwordConfig?.minPasswordLength ?? 8;
3970
+ const maxPassword = options?.passwordConfig?.maxPasswordLength ?? 128;
3971
+ const minUsername = options?.usernameConfig?.minUsernameLength ?? 3;
3972
+ const maxUsername = options?.usernameConfig?.maxUsernameLength ?? 32;
3973
+ const baseSchema = z.object({
3250
3974
  name: z.string().min(1, i18n.t("ui:validation.nameRequired")).max(100, i18n.t("ui:validation.nameMax", { max: 100 })),
3251
3975
  email: z.email(i18n.t("ui:validation.emailInvalid")).min(1, i18n.t("ui:validation.emailRequired")),
3252
3976
  password: z.string().min(minPassword, i18n.t("ui:validation.passwordMin", { min: minPassword })).max(maxPassword, i18n.t("ui:validation.passwordMax", { max: maxPassword }))
3253
3977
  });
3978
+ if (options?.usernameEnabled) return baseSchema.extend({ username: z.string().min(minUsername, i18n.t("ui:validation.usernameMin", { min: minUsername })).max(maxUsername, i18n.t("ui:validation.usernameMax", { max: maxUsername })).regex(/^[a-zA-Z0-9_-]+$/, i18n.t("ui:validation.usernameFormat")) });
3979
+ return baseSchema;
3254
3980
  }
3255
3981
  function NameInput({ showIcon = false }) {
3256
3982
  const id = useId();
@@ -3287,6 +4013,41 @@ function NameInput({ showIcon = false }) {
3287
4013
  ]
3288
4014
  });
3289
4015
  }
4016
+ function UsernameInput({ showIcon = false }) {
4017
+ const id = useId();
4018
+ const { t: t$1 } = useTranslation$1(["ui"]);
4019
+ const form = useFormContext();
4020
+ const error = form?.formState?.errors?.username;
4021
+ const hasError = !!error;
4022
+ return /* @__PURE__ */ jsxs(Field, {
4023
+ "data-invalid": hasError || void 0,
4024
+ children: [
4025
+ /* @__PURE__ */ jsx(FieldLabel, {
4026
+ htmlFor: id,
4027
+ children: t$1("ui:auth.signup.username")
4028
+ }),
4029
+ /* @__PURE__ */ jsxs("div", {
4030
+ className: "relative",
4031
+ children: [showIcon && /* @__PURE__ */ jsx("div", {
4032
+ className: "pointer-events-none absolute left-3 top-1/2 -translate-y-1/2",
4033
+ children: /* @__PURE__ */ jsx(AtSignIcon, { className: "size-4 text-muted-foreground" })
4034
+ }), /* @__PURE__ */ jsx(Input, {
4035
+ id,
4036
+ type: "text",
4037
+ autoComplete: "username",
4038
+ "aria-invalid": hasError,
4039
+ "aria-describedby": hasError ? `${id}-error` : void 0,
4040
+ className: cn(showIcon && "pl-10", "h-10"),
4041
+ ...form?.register("username")
4042
+ })]
4043
+ }),
4044
+ error?.message && /* @__PURE__ */ jsx(FieldError, {
4045
+ id: `${id}-error`,
4046
+ children: String(error.message)
4047
+ })
4048
+ ]
4049
+ });
4050
+ }
3290
4051
  /**
3291
4052
  * Complete signup form with password strength indicator.
3292
4053
  * Renders based on enabled features in AuthFeatures.
@@ -3302,32 +4063,48 @@ function NameInput({ showIcon = false }) {
3302
4063
  * </AuthProvider>
3303
4064
  * ```
3304
4065
  */
3305
- function SignupForm({ callbackURL, className, loginLink = "/login", title, description, logo, showSecurityIcon, showTermsCheckbox = false, termsUrl = "/terms", privacyUrl = "/privacy", onSuccess }) {
4066
+ function SignupForm({ callbackURL, className, loginLink = "/login", title, description, logo, showSecurityIcon, showTermsCheckbox = false, termsUrl = "/legal/terms", privacyUrl = "/legal/privacy-policy", onSuccess }) {
3306
4067
  const { t: t$1 } = useTranslation$1(["ui"]);
3307
4068
  const { client, config, features } = useAuth();
3308
4069
  const { isLoading, error, execute } = useAuthForm();
4070
+ const captcha = useCaptcha();
4071
+ const [captchaToken, setCaptchaToken] = useState(null);
4072
+ const captchaResetRef = useRef(null);
3309
4073
  const form = useForm({
3310
- resolver: zodResolver(defineSignupSchema(config.methods?.emailPassword)),
4074
+ resolver: zodResolver(defineSignupSchema({
4075
+ passwordConfig: config.methods?.emailPassword,
4076
+ usernameConfig: config.plugins?.username,
4077
+ usernameEnabled: features.username
4078
+ })),
3311
4079
  defaultValues: {
3312
4080
  name: "",
3313
4081
  email: "",
3314
- password: ""
4082
+ password: "",
4083
+ ...features.username && { username: "" }
3315
4084
  },
3316
4085
  mode: "onBlur"
3317
4086
  });
3318
4087
  const hasOAuth = Object.values(features.oauth).some(Boolean);
3319
4088
  const hasAnyAuthMethod = features.emailPassword || hasOAuth;
3320
4089
  const onSubmit = async (data) => {
3321
- if (await execute(async () => {
3322
- const response = await client.signUp.email({
4090
+ const result = await execute(async () => {
4091
+ const signUpData = {
3323
4092
  name: data.name,
3324
4093
  email: data.email,
3325
4094
  password: data.password,
3326
- callbackURL
3327
- });
4095
+ callbackURL,
4096
+ fetchOptions: captchaToken ? { headers: { "x-captcha-response": captchaToken } } : void 0
4097
+ };
4098
+ if (features.username && "username" in data) signUpData.username = data.username;
4099
+ const response = await client.signUp.email(signUpData);
3328
4100
  if (response.error) throw new Error(response.error.message || t$1("ui:auth.common.signupFailed"));
3329
4101
  return response;
3330
- })) onSuccess?.({ email: data.email });
4102
+ });
4103
+ if (!result) {
4104
+ setCaptchaToken(null);
4105
+ captchaResetRef.current?.();
4106
+ }
4107
+ if (result) onSuccess?.({ email: data.email });
3331
4108
  };
3332
4109
  const footer = loginLink && /* @__PURE__ */ jsxs("span", { children: [
3333
4110
  t$1("ui:auth.signup.hasAccount"),
@@ -3380,6 +4157,7 @@ function SignupForm({ callbackURL, className, loginLink = "/login", title, descr
3380
4157
  }), features.emailPassword && /* @__PURE__ */ jsx(FieldSeparator, { children: t$1("ui:auth.signup.orContinueWith") })] }),
3381
4158
  features.emailPassword && /* @__PURE__ */ jsxs(Fragment, { children: [
3382
4159
  /* @__PURE__ */ jsx(NameInput, { showIcon: true }),
4160
+ features.username && /* @__PURE__ */ jsx(UsernameInput, { showIcon: true }),
3383
4161
  /* @__PURE__ */ jsx(EmailInput, {
3384
4162
  name: "email",
3385
4163
  label: t$1("ui:auth.signup.email"),
@@ -3423,10 +4201,16 @@ function SignupForm({ callbackURL, className, loginLink = "/login", title, descr
3423
4201
  ]
3424
4202
  })]
3425
4203
  }),
4204
+ captcha.enabled && /* @__PURE__ */ jsx(CaptchaWidget, {
4205
+ onSuccess: setCaptchaToken,
4206
+ onExpire: () => setCaptchaToken(null),
4207
+ onError: () => setCaptchaToken(null),
4208
+ resetRef: captchaResetRef
4209
+ }),
3426
4210
  /* @__PURE__ */ jsx(Button, {
3427
4211
  type: "submit",
3428
4212
  className: "h-10 w-full font-medium",
3429
- disabled: isLoading,
4213
+ disabled: isLoading || captcha.enabled && !captchaToken,
3430
4214
  children: isLoading ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Spinner, { className: "mr-2 size-4" }), t$1("ui:auth.signup.submitting")] }) : t$1("ui:auth.signup.submit")
3431
4215
  })
3432
4216
  ] })
@@ -3457,6 +4241,9 @@ function SignupForm({ callbackURL, className, loginLink = "/login", title, descr
3457
4241
  function VerifyEmailForm({ token, email, callbackURL, className, loginLink = "/login", title, description, logo, onSuccess }) {
3458
4242
  const { t: t$1 } = useTranslation$1(["ui"]);
3459
4243
  const { client } = useAuth();
4244
+ const captcha = useCaptcha();
4245
+ const [captchaToken, setCaptchaToken] = useState(null);
4246
+ const captchaResetRef = useRef(null);
3460
4247
  const { isLoading, error, isSuccess, execute, setError } = useAuthForm();
3461
4248
  const [resendSuccess, setResendSuccess] = useState(false);
3462
4249
  const [countdown, setCountdown] = useState(0);
@@ -3507,14 +4294,20 @@ function VerifyEmailForm({ token, email, callbackURL, className, loginLink = "/l
3507
4294
  return;
3508
4295
  }
3509
4296
  setResendSuccess(false);
3510
- if (await execute(async () => {
4297
+ const result = await execute(async () => {
3511
4298
  const response = await client.sendVerificationEmail({
3512
4299
  email,
3513
- callbackURL
4300
+ callbackURL,
4301
+ fetchOptions: captchaToken ? { headers: { "x-captcha-response": captchaToken } } : void 0
3514
4302
  });
3515
4303
  if (response.error) throw new Error(response.error.message || t$1("ui:auth.errors.generic"));
3516
4304
  return response;
3517
- })) {
4305
+ });
4306
+ if (!result) {
4307
+ setCaptchaToken(null);
4308
+ captchaResetRef.current?.();
4309
+ }
4310
+ if (result) {
3518
4311
  setResendSuccess(true);
3519
4312
  startCountdown();
3520
4313
  }
@@ -3588,10 +4381,16 @@ function VerifyEmailForm({ token, email, callbackURL, className, loginLink = "/l
3588
4381
  variant: "destructive",
3589
4382
  children: [/* @__PURE__ */ jsx(AlertCircleIcon, { className: "size-4" }), /* @__PURE__ */ jsx(AlertDescription, { children: error })]
3590
4383
  }),
4384
+ email && captcha.enabled && /* @__PURE__ */ jsx(CaptchaWidget, {
4385
+ onSuccess: setCaptchaToken,
4386
+ onExpire: () => setCaptchaToken(null),
4387
+ onError: () => setCaptchaToken(null),
4388
+ resetRef: captchaResetRef
4389
+ }),
3591
4390
  email && /* @__PURE__ */ jsxs(Button, {
3592
4391
  className: "h-10 w-full font-medium",
3593
4392
  onClick: handleResend,
3594
- disabled: isLoading || countdown > 0,
4393
+ disabled: isLoading || countdown > 0 || captcha.enabled && !captchaToken,
3595
4394
  children: [isLoading ? /* @__PURE__ */ jsx(Spinner, { className: "mr-2 size-4" }) : /* @__PURE__ */ jsx(RefreshCwIcon, { className: "mr-2 size-4" }), countdown > 0 ? t$1("ui:auth.otp.resendIn", { seconds: countdown }) : t$1("ui:auth.verifyEmail.resend")]
3596
4395
  })
3597
4396
  ]
@@ -3629,10 +4428,16 @@ function VerifyEmailForm({ token, email, callbackURL, className, loginLink = "/l
3629
4428
  variant: "success",
3630
4429
  children: [/* @__PURE__ */ jsx(CheckCircleIcon, { className: "size-4" }), /* @__PURE__ */ jsx(AlertDescription, { children: t$1("ui:auth.verifyEmail.resendSuccess") })]
3631
4430
  }),
4431
+ email && captcha.enabled && /* @__PURE__ */ jsx(CaptchaWidget, {
4432
+ onSuccess: setCaptchaToken,
4433
+ onExpire: () => setCaptchaToken(null),
4434
+ onError: () => setCaptchaToken(null),
4435
+ resetRef: captchaResetRef
4436
+ }),
3632
4437
  email && /* @__PURE__ */ jsxs(Button, {
3633
4438
  className: "h-10 w-full font-medium",
3634
4439
  onClick: handleResend,
3635
- disabled: isLoading || countdown > 0,
4440
+ disabled: isLoading || countdown > 0 || captcha.enabled && !captchaToken,
3636
4441
  children: [isLoading ? /* @__PURE__ */ jsx(Spinner, { className: "mr-2 size-4" }) : /* @__PURE__ */ jsx(RefreshCwIcon, { className: "mr-2 size-4" }), countdown > 0 ? t$1("ui:auth.otp.resendIn", { seconds: countdown }) : t$1("ui:auth.verifyEmail.resend")]
3637
4442
  })
3638
4443
  ]
@@ -3688,6 +4493,23 @@ function PasskeyRegisterButton({ className, disabled, label, onSuccess, onError
3688
4493
 
3689
4494
  //#endregion
3690
4495
  //#region src/web/ui/accordion.tsx
4496
+ /**
4497
+ * A vertically stacked set of interactive headings that reveal content sections.
4498
+ *
4499
+ * Use accordions to organize content into collapsible sections, reducing visual
4500
+ * clutter while keeping information accessible. Supports single or multiple
4501
+ * expanded items.
4502
+ *
4503
+ * @example
4504
+ * ```tsx
4505
+ * <Accordion>
4506
+ * <AccordionItem value="item-1">
4507
+ * <AccordionTrigger>Section Title</AccordionTrigger>
4508
+ * <AccordionContent>Section content goes here.</AccordionContent>
4509
+ * </AccordionItem>
4510
+ * </Accordion>
4511
+ * ```
4512
+ */
3691
4513
  function Accordion({ className, ...props }) {
3692
4514
  return /* @__PURE__ */ jsx(Accordion$1.Root, {
3693
4515
  "data-slot": "accordion",
@@ -3695,6 +4517,20 @@ function Accordion({ className, ...props }) {
3695
4517
  ...props
3696
4518
  });
3697
4519
  }
4520
+ /**
4521
+ * A single collapsible section within an Accordion.
4522
+ *
4523
+ * Each item contains a trigger and content panel. The `value` prop uniquely
4524
+ * identifies this item for controlled state management.
4525
+ *
4526
+ * @example
4527
+ * ```tsx
4528
+ * <AccordionItem value="faq-1">
4529
+ * <AccordionTrigger>What is this?</AccordionTrigger>
4530
+ * <AccordionContent>This is an accordion item.</AccordionContent>
4531
+ * </AccordionItem>
4532
+ * ```
4533
+ */
3698
4534
  function AccordionItem({ className, ...props }) {
3699
4535
  return /* @__PURE__ */ jsx(Accordion$1.Item, {
3700
4536
  "data-slot": "accordion-item",
@@ -3702,6 +4538,17 @@ function AccordionItem({ className, ...props }) {
3702
4538
  ...props
3703
4539
  });
3704
4540
  }
4541
+ /**
4542
+ * The clickable header that toggles the accordion content visibility.
4543
+ *
4544
+ * Includes an animated chevron icon that rotates based on the expanded state.
4545
+ * Supports keyboard navigation and focus management.
4546
+ *
4547
+ * @example
4548
+ * ```tsx
4549
+ * <AccordionTrigger>Click to expand</AccordionTrigger>
4550
+ * ```
4551
+ */
3705
4552
  function AccordionTrigger({ className, children, ...props }) {
3706
4553
  return /* @__PURE__ */ jsx(Accordion$1.Header, {
3707
4554
  className: "flex",
@@ -3723,6 +4570,19 @@ function AccordionTrigger({ className, children, ...props }) {
3723
4570
  })
3724
4571
  });
3725
4572
  }
4573
+ /**
4574
+ * The collapsible content panel of an accordion item.
4575
+ *
4576
+ * Animates smoothly when expanding and collapsing. Content is hidden from
4577
+ * screen readers when collapsed.
4578
+ *
4579
+ * @example
4580
+ * ```tsx
4581
+ * <AccordionContent>
4582
+ * <p>This content is revealed when the accordion is expanded.</p>
4583
+ * </AccordionContent>
4584
+ * ```
4585
+ */
3726
4586
  function AccordionContent({ className, children, ...props }) {
3727
4587
  return /* @__PURE__ */ jsx(Accordion$1.Panel, {
3728
4588
  "data-slot": "accordion-content",
@@ -3737,24 +4597,72 @@ function AccordionContent({ className, children, ...props }) {
3737
4597
 
3738
4598
  //#endregion
3739
4599
  //#region src/web/ui/alert-dialog.tsx
4600
+ /**
4601
+ * A modal dialog that interrupts the user with important content and expects a response.
4602
+ *
4603
+ * Use for destructive actions or critical decisions that require explicit user
4604
+ * confirmation. Prevents interaction with the rest of the page until dismissed.
4605
+ *
4606
+ * @example
4607
+ * ```tsx
4608
+ * <AlertDialog>
4609
+ * <AlertDialogTrigger render={<Button variant="destructive" />}>
4610
+ * Delete Account
4611
+ * </AlertDialogTrigger>
4612
+ * <AlertDialogContent>
4613
+ * <AlertDialogHeader>
4614
+ * <AlertDialogTitle>Are you sure?</AlertDialogTitle>
4615
+ * <AlertDialogDescription>This action cannot be undone.</AlertDialogDescription>
4616
+ * </AlertDialogHeader>
4617
+ * <AlertDialogFooter>
4618
+ * <AlertDialogCancel>Cancel</AlertDialogCancel>
4619
+ * <AlertDialogAction>Continue</AlertDialogAction>
4620
+ * </AlertDialogFooter>
4621
+ * </AlertDialogContent>
4622
+ * </AlertDialog>
4623
+ * ```
4624
+ */
3740
4625
  function AlertDialog({ ...props }) {
3741
4626
  return /* @__PURE__ */ jsx(AlertDialog$1.Root, {
3742
4627
  "data-slot": "alert-dialog",
3743
4628
  ...props
3744
4629
  });
3745
4630
  }
4631
+ /**
4632
+ * The button that opens the alert dialog.
4633
+ *
4634
+ * Use the `render` prop to customize the trigger element while maintaining
4635
+ * proper accessibility attributes.
4636
+ *
4637
+ * @example
4638
+ * ```tsx
4639
+ * <AlertDialogTrigger render={<Button />}>Open Dialog</AlertDialogTrigger>
4640
+ * ```
4641
+ */
3746
4642
  function AlertDialogTrigger({ ...props }) {
3747
4643
  return /* @__PURE__ */ jsx(AlertDialog$1.Trigger, {
3748
4644
  "data-slot": "alert-dialog-trigger",
3749
4645
  ...props
3750
4646
  });
3751
4647
  }
4648
+ /**
4649
+ * Portals the dialog content to the end of the document body.
4650
+ *
4651
+ * Used internally by AlertDialogContent. Ensures the dialog renders above
4652
+ * other content regardless of DOM position.
4653
+ */
3752
4654
  function AlertDialogPortal({ ...props }) {
3753
4655
  return /* @__PURE__ */ jsx(AlertDialog$1.Portal, {
3754
4656
  "data-slot": "alert-dialog-portal",
3755
4657
  ...props
3756
4658
  });
3757
4659
  }
4660
+ /**
4661
+ * The backdrop overlay behind the alert dialog.
4662
+ *
4663
+ * Dims the background and prevents interaction with the page. Includes
4664
+ * blur effect on supported browsers. Used internally by AlertDialogContent.
4665
+ */
3758
4666
  function AlertDialogOverlay({ className, ...props }) {
3759
4667
  return /* @__PURE__ */ jsx(AlertDialog$1.Backdrop, {
3760
4668
  "data-slot": "alert-dialog-overlay",
@@ -3762,6 +4670,20 @@ function AlertDialogOverlay({ className, ...props }) {
3762
4670
  ...props
3763
4671
  });
3764
4672
  }
4673
+ /**
4674
+ * The container for the alert dialog content.
4675
+ *
4676
+ * Centers the dialog on screen with smooth open/close animations. Includes
4677
+ * the portal and overlay automatically.
4678
+ *
4679
+ * @example
4680
+ * ```tsx
4681
+ * <AlertDialogContent size="sm">
4682
+ * <AlertDialogHeader>...</AlertDialogHeader>
4683
+ * <AlertDialogFooter>...</AlertDialogFooter>
4684
+ * </AlertDialogContent>
4685
+ * ```
4686
+ */
3765
4687
  function AlertDialogContent({ className, size = "default", ...props }) {
3766
4688
  return /* @__PURE__ */ jsxs(AlertDialogPortal, { children: [/* @__PURE__ */ jsx(AlertDialogOverlay, {}), /* @__PURE__ */ jsx(AlertDialog$1.Popup, {
3767
4689
  "data-slot": "alert-dialog-content",
@@ -3770,6 +4692,19 @@ function AlertDialogContent({ className, size = "default", ...props }) {
3770
4692
  ...props
3771
4693
  })] });
3772
4694
  }
4695
+ /**
4696
+ * The header section containing the dialog title and description.
4697
+ *
4698
+ * Handles layout for optional media icon and adapts to different dialog sizes.
4699
+ *
4700
+ * @example
4701
+ * ```tsx
4702
+ * <AlertDialogHeader>
4703
+ * <AlertDialogTitle>Confirm Action</AlertDialogTitle>
4704
+ * <AlertDialogDescription>This will affect your data.</AlertDialogDescription>
4705
+ * </AlertDialogHeader>
4706
+ * ```
4707
+ */
3773
4708
  function AlertDialogHeader({ className, ...props }) {
3774
4709
  return /* @__PURE__ */ jsx("div", {
3775
4710
  "data-slot": "alert-dialog-header",
@@ -3777,6 +4712,19 @@ function AlertDialogHeader({ className, ...props }) {
3777
4712
  ...props
3778
4713
  });
3779
4714
  }
4715
+ /**
4716
+ * The footer section containing dialog action buttons.
4717
+ *
4718
+ * Arranges buttons in a responsive layout with proper spacing.
4719
+ *
4720
+ * @example
4721
+ * ```tsx
4722
+ * <AlertDialogFooter>
4723
+ * <AlertDialogCancel>Cancel</AlertDialogCancel>
4724
+ * <AlertDialogAction>Confirm</AlertDialogAction>
4725
+ * </AlertDialogFooter>
4726
+ * ```
4727
+ */
3780
4728
  function AlertDialogFooter({ className, ...props }) {
3781
4729
  return /* @__PURE__ */ jsx("div", {
3782
4730
  "data-slot": "alert-dialog-footer",
@@ -3784,6 +4732,18 @@ function AlertDialogFooter({ className, ...props }) {
3784
4732
  ...props
3785
4733
  });
3786
4734
  }
4735
+ /**
4736
+ * An optional media container for icons or images in the dialog header.
4737
+ *
4738
+ * Displays prominently above or beside the title depending on dialog size.
4739
+ *
4740
+ * @example
4741
+ * ```tsx
4742
+ * <AlertDialogMedia>
4743
+ * <AlertTriangleIcon />
4744
+ * </AlertDialogMedia>
4745
+ * ```
4746
+ */
3787
4747
  function AlertDialogMedia({ className, ...props }) {
3788
4748
  return /* @__PURE__ */ jsx("div", {
3789
4749
  "data-slot": "alert-dialog-media",
@@ -3791,6 +4751,16 @@ function AlertDialogMedia({ className, ...props }) {
3791
4751
  ...props
3792
4752
  });
3793
4753
  }
4754
+ /**
4755
+ * The title text for the alert dialog.
4756
+ *
4757
+ * Displayed prominently and announced by screen readers when the dialog opens.
4758
+ *
4759
+ * @example
4760
+ * ```tsx
4761
+ * <AlertDialogTitle>Delete Item</AlertDialogTitle>
4762
+ * ```
4763
+ */
3794
4764
  function AlertDialogTitle({ className, ...props }) {
3795
4765
  return /* @__PURE__ */ jsx(AlertDialog$1.Title, {
3796
4766
  "data-slot": "alert-dialog-title",
@@ -3798,13 +4768,35 @@ function AlertDialogTitle({ className, ...props }) {
3798
4768
  ...props
3799
4769
  });
3800
4770
  }
4771
+ /**
4772
+ * The descriptive text explaining the alert dialog purpose.
4773
+ *
4774
+ * Provides context about the action and its consequences.
4775
+ *
4776
+ * @example
4777
+ * ```tsx
4778
+ * <AlertDialogDescription>
4779
+ * This action cannot be undone. Your data will be permanently removed.
4780
+ * </AlertDialogDescription>
4781
+ * ```
4782
+ */
3801
4783
  function AlertDialogDescription({ className, ...props }) {
3802
4784
  return /* @__PURE__ */ jsx(AlertDialog$1.Description, {
3803
4785
  "data-slot": "alert-dialog-description",
3804
- className: cn("text-muted-foreground *:[a]:hover:text-foreground text-sm text-balance md:text-pretty *:[a]:underline *:[a]:underline-offset-3", className),
4786
+ className: cn("text-foreground/70 *:[a]:hover:text-foreground text-sm text-balance md:text-pretty *:[a]:underline *:[a]:underline-offset-3", className),
3805
4787
  ...props
3806
4788
  });
3807
4789
  }
4790
+ /**
4791
+ * The primary action button that confirms the dialog action.
4792
+ *
4793
+ * Styled as the default button variant. Does not automatically close the dialog.
4794
+ *
4795
+ * @example
4796
+ * ```tsx
4797
+ * <AlertDialogAction onClick={handleDelete}>Delete</AlertDialogAction>
4798
+ * ```
4799
+ */
3808
4800
  function AlertDialogAction({ className, ...props }) {
3809
4801
  return /* @__PURE__ */ jsx(Button, {
3810
4802
  "data-slot": "alert-dialog-action",
@@ -3812,6 +4804,16 @@ function AlertDialogAction({ className, ...props }) {
3812
4804
  ...props
3813
4805
  });
3814
4806
  }
4807
+ /**
4808
+ * The cancel button that dismisses the dialog without taking action.
4809
+ *
4810
+ * Automatically closes the dialog when clicked. Styled as outline variant by default.
4811
+ *
4812
+ * @example
4813
+ * ```tsx
4814
+ * <AlertDialogCancel>Cancel</AlertDialogCancel>
4815
+ * ```
4816
+ */
3815
4817
  function AlertDialogCancel({ className, variant = "outline", size = "default", ...props }) {
3816
4818
  return /* @__PURE__ */ jsx(AlertDialog$1.Close, {
3817
4819
  "data-slot": "alert-dialog-cancel",
@@ -3826,6 +4828,19 @@ function AlertDialogCancel({ className, variant = "outline", size = "default", .
3826
4828
 
3827
4829
  //#endregion
3828
4830
  //#region src/web/ui/aspect-ratio.tsx
4831
+ /**
4832
+ * A container that maintains a specific aspect ratio for its content.
4833
+ *
4834
+ * Useful for images, videos, and embedded content that need to maintain
4835
+ * consistent proportions across different viewport sizes.
4836
+ *
4837
+ * @example
4838
+ * ```tsx
4839
+ * <AspectRatio ratio={16 / 9}>
4840
+ * <img src="/image.jpg" alt="Landscape" className="object-cover" />
4841
+ * </AspectRatio>
4842
+ * ```
4843
+ */
3829
4844
  function AspectRatio({ ratio, className, ...props }) {
3830
4845
  return /* @__PURE__ */ jsx("div", {
3831
4846
  "data-slot": "aspect-ratio",
@@ -3837,6 +4852,21 @@ function AspectRatio({ ratio, className, ...props }) {
3837
4852
 
3838
4853
  //#endregion
3839
4854
  //#region src/web/ui/avatar.tsx
4855
+ /**
4856
+ * A circular image element for representing users or entities.
4857
+ *
4858
+ * Supports multiple sizes and integrates with AvatarImage for the photo,
4859
+ * AvatarFallback for initials when no image is available, and AvatarBadge
4860
+ * for status indicators.
4861
+ *
4862
+ * @example
4863
+ * ```tsx
4864
+ * <Avatar>
4865
+ * <AvatarImage src="/user.jpg" alt="John Doe" />
4866
+ * <AvatarFallback>JD</AvatarFallback>
4867
+ * </Avatar>
4868
+ * ```
4869
+ */
3840
4870
  function Avatar({ className, size = "default", ...props }) {
3841
4871
  return /* @__PURE__ */ jsx(Avatar$1.Root, {
3842
4872
  "data-slot": "avatar",
@@ -3845,6 +4875,17 @@ function Avatar({ className, size = "default", ...props }) {
3845
4875
  ...props
3846
4876
  });
3847
4877
  }
4878
+ /**
4879
+ * The image element displayed within an Avatar.
4880
+ *
4881
+ * Automatically scales to fill the avatar container and handles image loading.
4882
+ * Falls back to AvatarFallback when the image fails to load.
4883
+ *
4884
+ * @example
4885
+ * ```tsx
4886
+ * <AvatarImage src="/profile.jpg" alt="User profile" />
4887
+ * ```
4888
+ */
3848
4889
  function AvatarImage({ className, ...props }) {
3849
4890
  return /* @__PURE__ */ jsx(Avatar$1.Image, {
3850
4891
  "data-slot": "avatar-image",
@@ -3852,6 +4893,17 @@ function AvatarImage({ className, ...props }) {
3852
4893
  ...props
3853
4894
  });
3854
4895
  }
4896
+ /**
4897
+ * A fallback element displayed when the Avatar image is unavailable.
4898
+ *
4899
+ * Typically contains user initials or a placeholder icon. Automatically shown
4900
+ * when AvatarImage fails to load or is not provided.
4901
+ *
4902
+ * @example
4903
+ * ```tsx
4904
+ * <AvatarFallback>JD</AvatarFallback>
4905
+ * ```
4906
+ */
3855
4907
  function AvatarFallback({ className, ...props }) {
3856
4908
  return /* @__PURE__ */ jsx(Avatar$1.Fallback, {
3857
4909
  "data-slot": "avatar-fallback",
@@ -3859,6 +4911,20 @@ function AvatarFallback({ className, ...props }) {
3859
4911
  ...props
3860
4912
  });
3861
4913
  }
4914
+ /**
4915
+ * A status indicator badge positioned at the corner of an Avatar.
4916
+ *
4917
+ * Use for online/offline status, notification counts, or verification badges.
4918
+ * Automatically sizes based on the parent Avatar size.
4919
+ *
4920
+ * @example
4921
+ * ```tsx
4922
+ * <Avatar>
4923
+ * <AvatarImage src="/user.jpg" alt="John Doe" />
4924
+ * <AvatarBadge />
4925
+ * </Avatar>
4926
+ * ```
4927
+ */
3862
4928
  function AvatarBadge({ className, ...props }) {
3863
4929
  return /* @__PURE__ */ jsx("span", {
3864
4930
  "data-slot": "avatar-badge",
@@ -3866,6 +4932,25 @@ function AvatarBadge({ className, ...props }) {
3866
4932
  ...props
3867
4933
  });
3868
4934
  }
4935
+ /**
4936
+ * A container for displaying multiple avatars in an overlapping stack.
4937
+ *
4938
+ * Automatically handles negative spacing and ring styles for visual separation.
4939
+ * Commonly used for displaying team members or participants.
4940
+ *
4941
+ * @example
4942
+ * ```tsx
4943
+ * <AvatarGroup>
4944
+ * <Avatar>
4945
+ * <AvatarImage src="/user1.jpg" alt="User 1" />
4946
+ * </Avatar>
4947
+ * <Avatar>
4948
+ * <AvatarImage src="/user2.jpg" alt="User 2" />
4949
+ * </Avatar>
4950
+ * <AvatarGroupCount>+5</AvatarGroupCount>
4951
+ * </AvatarGroup>
4952
+ * ```
4953
+ */
3869
4954
  function AvatarGroup({ className, ...props }) {
3870
4955
  return /* @__PURE__ */ jsx("div", {
3871
4956
  "data-slot": "avatar-group",
@@ -3873,6 +4958,17 @@ function AvatarGroup({ className, ...props }) {
3873
4958
  ...props
3874
4959
  });
3875
4960
  }
4961
+ /**
4962
+ * A count indicator for an AvatarGroup showing additional members.
4963
+ *
4964
+ * Displays remaining count when not all avatars are shown. Styled to match
4965
+ * the avatar sizes within the group.
4966
+ *
4967
+ * @example
4968
+ * ```tsx
4969
+ * <AvatarGroupCount>+12</AvatarGroupCount>
4970
+ * ```
4971
+ */
3876
4972
  function AvatarGroupCount({ className, ...props }) {
3877
4973
  return /* @__PURE__ */ jsx("div", {
3878
4974
  "data-slot": "avatar-group-count",
@@ -3881,36 +4977,29 @@ function AvatarGroupCount({ className, ...props }) {
3881
4977
  });
3882
4978
  }
3883
4979
 
3884
- //#endregion
3885
- //#region src/web/ui/badge.tsx
3886
- const badgeVariants = cva("h-5 gap-1 rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium transition-all has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&>svg]:size-3! inline-flex items-center justify-center w-fit whitespace-nowrap shrink-0 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-colors overflow-hidden group/badge", {
3887
- variants: { variant: {
3888
- default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
3889
- secondary: "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
3890
- destructive: "bg-destructive/10 [a]:hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 text-destructive dark:bg-destructive/20",
3891
- outline: "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
3892
- ghost: "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
3893
- link: "text-primary underline-offset-4 hover:underline"
3894
- } },
3895
- defaultVariants: { variant: "default" }
3896
- });
3897
- function Badge({ className, variant = "default", render, ...props }) {
3898
- return useRender({
3899
- defaultTagName: "span",
3900
- props: mergeProps({ className: cn(badgeVariants({
3901
- className,
3902
- variant
3903
- })) }, props),
3904
- render,
3905
- state: {
3906
- slot: "badge",
3907
- variant
3908
- }
3909
- });
3910
- }
3911
-
3912
4980
  //#endregion
3913
4981
  //#region src/web/ui/breadcrumb.tsx
4982
+ /**
4983
+ * A navigation component that shows the user's current location in a hierarchy.
4984
+ *
4985
+ * Helps users understand their position within a site structure and navigate
4986
+ * back to parent pages. Includes proper accessibility attributes.
4987
+ *
4988
+ * @example
4989
+ * ```tsx
4990
+ * <Breadcrumb>
4991
+ * <BreadcrumbList>
4992
+ * <BreadcrumbItem>
4993
+ * <BreadcrumbLink href="/">Home</BreadcrumbLink>
4994
+ * </BreadcrumbItem>
4995
+ * <BreadcrumbSeparator />
4996
+ * <BreadcrumbItem>
4997
+ * <BreadcrumbPage>Current Page</BreadcrumbPage>
4998
+ * </BreadcrumbItem>
4999
+ * </BreadcrumbList>
5000
+ * </Breadcrumb>
5001
+ * ```
5002
+ */
3914
5003
  function Breadcrumb({ className, ...props }) {
3915
5004
  return /* @__PURE__ */ jsx("nav", {
3916
5005
  "aria-label": "breadcrumb",
@@ -3919,6 +5008,18 @@ function Breadcrumb({ className, ...props }) {
3919
5008
  ...props
3920
5009
  });
3921
5010
  }
5011
+ /**
5012
+ * The ordered list container for breadcrumb items.
5013
+ *
5014
+ * Wraps BreadcrumbItem components and handles flex layout with proper spacing.
5015
+ *
5016
+ * @example
5017
+ * ```tsx
5018
+ * <BreadcrumbList>
5019
+ * <BreadcrumbItem>...</BreadcrumbItem>
5020
+ * </BreadcrumbList>
5021
+ * ```
5022
+ */
3922
5023
  function BreadcrumbList({ className, ...props }) {
3923
5024
  return /* @__PURE__ */ jsx("ol", {
3924
5025
  "data-slot": "breadcrumb-list",
@@ -3926,6 +5027,19 @@ function BreadcrumbList({ className, ...props }) {
3926
5027
  ...props
3927
5028
  });
3928
5029
  }
5030
+ /**
5031
+ * A single item in the breadcrumb navigation.
5032
+ *
5033
+ * Contains either a BreadcrumbLink for navigable items or BreadcrumbPage
5034
+ * for the current page indicator.
5035
+ *
5036
+ * @example
5037
+ * ```tsx
5038
+ * <BreadcrumbItem>
5039
+ * <BreadcrumbLink href="/products">Products</BreadcrumbLink>
5040
+ * </BreadcrumbItem>
5041
+ * ```
5042
+ */
3929
5043
  function BreadcrumbItem({ className, ...props }) {
3930
5044
  return /* @__PURE__ */ jsx("li", {
3931
5045
  "data-slot": "breadcrumb-item",
@@ -3933,6 +5047,17 @@ function BreadcrumbItem({ className, ...props }) {
3933
5047
  ...props
3934
5048
  });
3935
5049
  }
5050
+ /**
5051
+ * A navigable link within a breadcrumb item.
5052
+ *
5053
+ * Use for all breadcrumb levels except the current page. Supports custom
5054
+ * rendering via the `render` prop for integration with routing libraries.
5055
+ *
5056
+ * @example
5057
+ * ```tsx
5058
+ * <BreadcrumbLink href="/dashboard">Dashboard</BreadcrumbLink>
5059
+ * ```
5060
+ */
3936
5061
  function BreadcrumbLink({ className, render, ...props }) {
3937
5062
  return useRender({
3938
5063
  defaultTagName: "a",
@@ -3941,6 +5066,17 @@ function BreadcrumbLink({ className, render, ...props }) {
3941
5066
  state: { slot: "breadcrumb-link" }
3942
5067
  });
3943
5068
  }
5069
+ /**
5070
+ * The current page indicator in a breadcrumb trail.
5071
+ *
5072
+ * Non-interactive element that represents the user's current location.
5073
+ * Includes proper ARIA attributes for accessibility.
5074
+ *
5075
+ * @example
5076
+ * ```tsx
5077
+ * <BreadcrumbPage>Settings</BreadcrumbPage>
5078
+ * ```
5079
+ */
3944
5080
  function BreadcrumbPage({ className, ...props }) {
3945
5081
  return /* @__PURE__ */ jsx("span", {
3946
5082
  "data-slot": "breadcrumb-page",
@@ -3951,6 +5087,18 @@ function BreadcrumbPage({ className, ...props }) {
3951
5087
  ...props
3952
5088
  });
3953
5089
  }
5090
+ /**
5091
+ * A visual separator between breadcrumb items.
5092
+ *
5093
+ * Displays a chevron icon by default but accepts custom children.
5094
+ * Hidden from screen readers as it's purely decorative.
5095
+ *
5096
+ * @example
5097
+ * ```tsx
5098
+ * <BreadcrumbSeparator />
5099
+ * <BreadcrumbSeparator>/</BreadcrumbSeparator>
5100
+ * ```
5101
+ */
3954
5102
  function BreadcrumbSeparator({ children, className, ...props }) {
3955
5103
  return /* @__PURE__ */ jsx("li", {
3956
5104
  "data-slot": "breadcrumb-separator",
@@ -3961,6 +5109,19 @@ function BreadcrumbSeparator({ children, className, ...props }) {
3961
5109
  children: children ?? /* @__PURE__ */ jsx(ChevronRightIcon, {})
3962
5110
  });
3963
5111
  }
5112
+ /**
5113
+ * An ellipsis indicator for truncated breadcrumb trails.
5114
+ *
5115
+ * Use when there are too many levels to display. Often combined with a
5116
+ * dropdown menu to show hidden items.
5117
+ *
5118
+ * @example
5119
+ * ```tsx
5120
+ * <BreadcrumbItem>
5121
+ * <BreadcrumbEllipsis />
5122
+ * </BreadcrumbItem>
5123
+ * ```
5124
+ */
3964
5125
  function BreadcrumbEllipsis({ className, ...props }) {
3965
5126
  return /* @__PURE__ */ jsxs("span", {
3966
5127
  "data-slot": "breadcrumb-ellipsis",
@@ -4016,7 +5177,7 @@ function Calendar({ className, classNames, showOutsideDays = true, captionLayout
4016
5177
  const defaultClassNames = getDefaultClassNames();
4017
5178
  return /* @__PURE__ */ jsx(DayPicker, {
4018
5179
  showOutsideDays,
4019
- className: cn("p-3 [--cell-radius:var(--radius-md)] [--cell-size:--spacing(8)] bg-background group/calendar [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent", String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`, String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`, className),
5180
+ className: cn("p-3 [--cell-radius:var(--radius-md)] [--cell-size:--spacing(8)] bg-background group/calendar in-data-[slot=card-content]:bg-transparent in-data-[slot=popover-content]:bg-transparent", String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`, String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`, className),
4020
5181
  captionLayout,
4021
5182
  formatters: {
4022
5183
  formatMonthDropdown: (date) => date.toLocaleString("default", { month: "short" }),
@@ -4389,30 +5550,85 @@ function CollapsibleContent({ ...props }) {
4389
5550
 
4390
5551
  //#endregion
4391
5552
  //#region src/web/ui/dialog.tsx
5553
+ /**
5554
+ * A modal window that appears above the page content.
5555
+ *
5556
+ * Use for focused tasks like forms, confirmations, or displaying detailed
5557
+ * information. Traps focus and prevents interaction with the background.
5558
+ *
5559
+ * @example
5560
+ * ```tsx
5561
+ * <Dialog>
5562
+ * <DialogTrigger render={<Button />}>Open Dialog</DialogTrigger>
5563
+ * <DialogContent>
5564
+ * <DialogHeader>
5565
+ * <DialogTitle>Edit Profile</DialogTitle>
5566
+ * <DialogDescription>Make changes to your profile.</DialogDescription>
5567
+ * </DialogHeader>
5568
+ * <DialogFooter>
5569
+ * <Button>Save changes</Button>
5570
+ * </DialogFooter>
5571
+ * </DialogContent>
5572
+ * </Dialog>
5573
+ * ```
5574
+ */
4392
5575
  function Dialog({ ...props }) {
4393
5576
  return /* @__PURE__ */ jsx(Dialog$1.Root, {
4394
5577
  "data-slot": "dialog",
4395
5578
  ...props
4396
5579
  });
4397
5580
  }
5581
+ /**
5582
+ * The button that opens the dialog.
5583
+ *
5584
+ * Use the `render` prop to customize the trigger element while maintaining
5585
+ * proper accessibility attributes.
5586
+ *
5587
+ * @example
5588
+ * ```tsx
5589
+ * <DialogTrigger render={<Button />}>Open</DialogTrigger>
5590
+ * ```
5591
+ */
4398
5592
  function DialogTrigger({ ...props }) {
4399
5593
  return /* @__PURE__ */ jsx(Dialog$1.Trigger, {
4400
5594
  "data-slot": "dialog-trigger",
4401
5595
  ...props
4402
5596
  });
4403
5597
  }
5598
+ /**
5599
+ * Portals the dialog content to the end of the document body.
5600
+ *
5601
+ * Used internally by DialogContent. Ensures the dialog renders above
5602
+ * other content regardless of DOM position.
5603
+ */
4404
5604
  function DialogPortal({ ...props }) {
4405
5605
  return /* @__PURE__ */ jsx(Dialog$1.Portal, {
4406
5606
  "data-slot": "dialog-portal",
4407
5607
  ...props
4408
5608
  });
4409
5609
  }
5610
+ /**
5611
+ * A button that closes the dialog when clicked.
5612
+ *
5613
+ * Can be placed anywhere within the dialog content.
5614
+ *
5615
+ * @example
5616
+ * ```tsx
5617
+ * <DialogClose render={<Button variant="outline" />}>Cancel</DialogClose>
5618
+ * ```
5619
+ */
4410
5620
  function DialogClose({ ...props }) {
4411
5621
  return /* @__PURE__ */ jsx(Dialog$1.Close, {
4412
5622
  "data-slot": "dialog-close",
4413
5623
  ...props
4414
5624
  });
4415
5625
  }
5626
+ /**
5627
+ * The backdrop overlay behind the dialog.
5628
+ *
5629
+ * Dims the background and prevents interaction with the page. Includes
5630
+ * blur effect on supported browsers. Used internally by DialogContent.
5631
+ */
4416
5632
  function DialogOverlay({ className, ...props }) {
4417
5633
  return /* @__PURE__ */ jsx(Dialog$1.Backdrop, {
4418
5634
  "data-slot": "dialog-overlay",
@@ -4420,6 +5636,20 @@ function DialogOverlay({ className, ...props }) {
4420
5636
  ...props
4421
5637
  });
4422
5638
  }
5639
+ /**
5640
+ * The container for the dialog content.
5641
+ *
5642
+ * Centers the dialog on screen with smooth open/close animations. Includes
5643
+ * the portal and overlay automatically. Optionally shows a close button.
5644
+ *
5645
+ * @example
5646
+ * ```tsx
5647
+ * <DialogContent>
5648
+ * <DialogHeader>...</DialogHeader>
5649
+ * <DialogFooter>...</DialogFooter>
5650
+ * </DialogContent>
5651
+ * ```
5652
+ */
4423
5653
  function DialogContent({ className, children, showCloseButton = true, ...props }) {
4424
5654
  return /* @__PURE__ */ jsxs(DialogPortal, { children: [/* @__PURE__ */ jsx(DialogOverlay, {}), /* @__PURE__ */ jsxs(Dialog$1.Popup, {
4425
5655
  "data-slot": "dialog-content",
@@ -4439,6 +5669,19 @@ function DialogContent({ className, children, showCloseButton = true, ...props }
4439
5669
  })]
4440
5670
  })] });
4441
5671
  }
5672
+ /**
5673
+ * The header section containing the dialog title and description.
5674
+ *
5675
+ * Provides consistent spacing and layout for dialog headers.
5676
+ *
5677
+ * @example
5678
+ * ```tsx
5679
+ * <DialogHeader>
5680
+ * <DialogTitle>Edit Settings</DialogTitle>
5681
+ * <DialogDescription>Configure your preferences.</DialogDescription>
5682
+ * </DialogHeader>
5683
+ * ```
5684
+ */
4442
5685
  function DialogHeader({ className, ...props }) {
4443
5686
  return /* @__PURE__ */ jsx("div", {
4444
5687
  "data-slot": "dialog-header",
@@ -4446,6 +5689,20 @@ function DialogHeader({ className, ...props }) {
4446
5689
  ...props
4447
5690
  });
4448
5691
  }
5692
+ /**
5693
+ * The footer section containing dialog action buttons.
5694
+ *
5695
+ * Arranges buttons in a responsive layout with proper spacing.
5696
+ * Optionally includes a close button.
5697
+ *
5698
+ * @example
5699
+ * ```tsx
5700
+ * <DialogFooter>
5701
+ * <Button variant="outline">Cancel</Button>
5702
+ * <Button>Save</Button>
5703
+ * </DialogFooter>
5704
+ * ```
5705
+ */
4449
5706
  function DialogFooter({ className, showCloseButton = false, children, ...props }) {
4450
5707
  return /* @__PURE__ */ jsxs("div", {
4451
5708
  "data-slot": "dialog-footer",
@@ -4457,6 +5714,16 @@ function DialogFooter({ className, showCloseButton = false, children, ...props }
4457
5714
  })]
4458
5715
  });
4459
5716
  }
5717
+ /**
5718
+ * The title text for the dialog.
5719
+ *
5720
+ * Displayed prominently and announced by screen readers when the dialog opens.
5721
+ *
5722
+ * @example
5723
+ * ```tsx
5724
+ * <DialogTitle>Confirm Action</DialogTitle>
5725
+ * ```
5726
+ */
4460
5727
  function DialogTitle({ className, ...props }) {
4461
5728
  return /* @__PURE__ */ jsx(Dialog$1.Title, {
4462
5729
  "data-slot": "dialog-title",
@@ -4464,6 +5731,18 @@ function DialogTitle({ className, ...props }) {
4464
5731
  ...props
4465
5732
  });
4466
5733
  }
5734
+ /**
5735
+ * The descriptive text explaining the dialog purpose.
5736
+ *
5737
+ * Provides context about the action or content within the dialog.
5738
+ *
5739
+ * @example
5740
+ * ```tsx
5741
+ * <DialogDescription>
5742
+ * This action cannot be undone. Please confirm your choice.
5743
+ * </DialogDescription>
5744
+ * ```
5745
+ */
4467
5746
  function DialogDescription({ className, ...props }) {
4468
5747
  return /* @__PURE__ */ jsx(Dialog$1.Description, {
4469
5748
  "data-slot": "dialog-description",
@@ -4474,6 +5753,17 @@ function DialogDescription({ className, ...props }) {
4474
5753
 
4475
5754
  //#endregion
4476
5755
  //#region src/web/ui/textarea.tsx
5756
+ /**
5757
+ * A multi-line text input field.
5758
+ *
5759
+ * Use for longer text content like comments, descriptions, or messages.
5760
+ * Automatically expands based on content with a minimum height.
5761
+ *
5762
+ * @example
5763
+ * ```tsx
5764
+ * <Textarea placeholder="Enter your message..." />
5765
+ * ```
5766
+ */
4477
5767
  function Textarea({ className, ...props }) {
4478
5768
  return /* @__PURE__ */ jsx("textarea", {
4479
5769
  "data-slot": "textarea",
@@ -5405,6 +6695,29 @@ function NavigationMenuIndicator({ className, ...props }) {
5405
6695
 
5406
6696
  //#endregion
5407
6697
  //#region src/web/ui/pagination.tsx
6698
+ /**
6699
+ * A navigation component for moving between pages of content.
6700
+ *
6701
+ * Provides a consistent interface for paginated data. Includes proper
6702
+ * ARIA attributes for accessibility.
6703
+ *
6704
+ * @example
6705
+ * ```tsx
6706
+ * <Pagination>
6707
+ * <PaginationContent>
6708
+ * <PaginationItem>
6709
+ * <PaginationPrevious href="/page/1" />
6710
+ * </PaginationItem>
6711
+ * <PaginationItem>
6712
+ * <PaginationLink href="/page/2">2</PaginationLink>
6713
+ * </PaginationItem>
6714
+ * <PaginationItem>
6715
+ * <PaginationNext href="/page/3" />
6716
+ * </PaginationItem>
6717
+ * </PaginationContent>
6718
+ * </Pagination>
6719
+ * ```
6720
+ */
5408
6721
  function Pagination({ className, ...props }) {
5409
6722
  return /* @__PURE__ */ jsx("nav", {
5410
6723
  "aria-label": "pagination",
@@ -5413,6 +6726,18 @@ function Pagination({ className, ...props }) {
5413
6726
  ...props
5414
6727
  });
5415
6728
  }
6729
+ /**
6730
+ * The container for pagination items.
6731
+ *
6732
+ * Wraps PaginationItem components in a horizontal flex layout.
6733
+ *
6734
+ * @example
6735
+ * ```tsx
6736
+ * <PaginationContent>
6737
+ * <PaginationItem>...</PaginationItem>
6738
+ * </PaginationContent>
6739
+ * ```
6740
+ */
5416
6741
  function PaginationContent({ className, ...props }) {
5417
6742
  return /* @__PURE__ */ jsx("ul", {
5418
6743
  "data-slot": "pagination-content",
@@ -5420,12 +6745,35 @@ function PaginationContent({ className, ...props }) {
5420
6745
  ...props
5421
6746
  });
5422
6747
  }
6748
+ /**
6749
+ * A single item wrapper in the pagination list.
6750
+ *
6751
+ * Contains a PaginationLink, PaginationPrevious, PaginationNext, or PaginationEllipsis.
6752
+ *
6753
+ * @example
6754
+ * ```tsx
6755
+ * <PaginationItem>
6756
+ * <PaginationLink href="/page/1">1</PaginationLink>
6757
+ * </PaginationItem>
6758
+ * ```
6759
+ */
5423
6760
  function PaginationItem({ ...props }) {
5424
6761
  return /* @__PURE__ */ jsx("li", {
5425
6762
  "data-slot": "pagination-item",
5426
6763
  ...props
5427
6764
  });
5428
6765
  }
6766
+ /**
6767
+ * A clickable link to a specific page number.
6768
+ *
6769
+ * Displays as active when representing the current page. Renders as a
6770
+ * button styled anchor element.
6771
+ *
6772
+ * @example
6773
+ * ```tsx
6774
+ * <PaginationLink href="/page/1" isActive>1</PaginationLink>
6775
+ * ```
6776
+ */
5429
6777
  function PaginationLink({ className, isActive, size = "icon", ...props }) {
5430
6778
  return /* @__PURE__ */ jsx(Button, {
5431
6779
  variant: isActive ? "outline" : "ghost",
@@ -5440,6 +6788,16 @@ function PaginationLink({ className, isActive, size = "icon", ...props }) {
5440
6788
  })
5441
6789
  });
5442
6790
  }
6791
+ /**
6792
+ * A link to the previous page with chevron icon.
6793
+ *
6794
+ * Shows "Previous" text on larger screens. Includes accessible label.
6795
+ *
6796
+ * @example
6797
+ * ```tsx
6798
+ * <PaginationPrevious href="/page/1" />
6799
+ * ```
6800
+ */
5443
6801
  function PaginationPrevious({ className, ...props }) {
5444
6802
  return /* @__PURE__ */ jsxs(PaginationLink, {
5445
6803
  "aria-label": "Go to previous page",
@@ -5452,6 +6810,16 @@ function PaginationPrevious({ className, ...props }) {
5452
6810
  })]
5453
6811
  });
5454
6812
  }
6813
+ /**
6814
+ * A link to the next page with chevron icon.
6815
+ *
6816
+ * Shows "Next" text on larger screens. Includes accessible label.
6817
+ *
6818
+ * @example
6819
+ * ```tsx
6820
+ * <PaginationNext href="/page/3" />
6821
+ * ```
6822
+ */
5455
6823
  function PaginationNext({ className, ...props }) {
5456
6824
  return /* @__PURE__ */ jsxs(PaginationLink, {
5457
6825
  "aria-label": "Go to next page",
@@ -5464,6 +6832,19 @@ function PaginationNext({ className, ...props }) {
5464
6832
  }), /* @__PURE__ */ jsx(ChevronRightIcon, { "data-icon": "inline-end" })]
5465
6833
  });
5466
6834
  }
6835
+ /**
6836
+ * An ellipsis indicator for skipped page numbers.
6837
+ *
6838
+ * Use when there are too many pages to display all links. Hidden from
6839
+ * screen readers with accessible fallback text.
6840
+ *
6841
+ * @example
6842
+ * ```tsx
6843
+ * <PaginationItem>
6844
+ * <PaginationEllipsis />
6845
+ * </PaginationItem>
6846
+ * ```
6847
+ */
5467
6848
  function PaginationEllipsis({ className, ...props }) {
5468
6849
  return /* @__PURE__ */ jsxs("span", {
5469
6850
  "aria-hidden": true,
@@ -5529,6 +6910,20 @@ function PopoverDescription({ className, ...props }) {
5529
6910
 
5530
6911
  //#endregion
5531
6912
  //#region src/web/ui/progress.tsx
6913
+ /**
6914
+ * A visual indicator showing the completion progress of a task.
6915
+ *
6916
+ * Displays a horizontal bar that fills based on the value prop.
6917
+ * Use for upload progress, multi-step forms, or loading states.
6918
+ *
6919
+ * @example
6920
+ * ```tsx
6921
+ * <Progress value={60}>
6922
+ * <ProgressLabel>Uploading...</ProgressLabel>
6923
+ * <ProgressValue />
6924
+ * </Progress>
6925
+ * ```
6926
+ */
5532
6927
  function Progress({ className, children, value, ...props }) {
5533
6928
  return /* @__PURE__ */ jsxs(Progress$1.Root, {
5534
6929
  value,
@@ -5538,6 +6933,12 @@ function Progress({ className, children, value, ...props }) {
5538
6933
  children: [children, /* @__PURE__ */ jsx(ProgressTrack, { children: /* @__PURE__ */ jsx(ProgressIndicator, {}) })]
5539
6934
  });
5540
6935
  }
6936
+ /**
6937
+ * The background track of the progress bar.
6938
+ *
6939
+ * Contains the ProgressIndicator that shows the filled portion.
6940
+ * Used internally by Progress.
6941
+ */
5541
6942
  function ProgressTrack({ className, ...props }) {
5542
6943
  return /* @__PURE__ */ jsx(Progress$1.Track, {
5543
6944
  className: cn("bg-muted h-1.5 rounded-full relative flex w-full items-center overflow-x-hidden", className),
@@ -5545,6 +6946,12 @@ function ProgressTrack({ className, ...props }) {
5545
6946
  ...props
5546
6947
  });
5547
6948
  }
6949
+ /**
6950
+ * The filled portion of the progress bar.
6951
+ *
6952
+ * Animates width based on the parent Progress value.
6953
+ * Used internally by Progress.
6954
+ */
5548
6955
  function ProgressIndicator({ className, ...props }) {
5549
6956
  return /* @__PURE__ */ jsx(Progress$1.Indicator, {
5550
6957
  "data-slot": "progress-indicator",
@@ -5552,6 +6959,16 @@ function ProgressIndicator({ className, ...props }) {
5552
6959
  ...props
5553
6960
  });
5554
6961
  }
6962
+ /**
6963
+ * A descriptive label for the progress bar.
6964
+ *
6965
+ * Displays above the progress track to describe what is being measured.
6966
+ *
6967
+ * @example
6968
+ * ```tsx
6969
+ * <ProgressLabel>Uploading files...</ProgressLabel>
6970
+ * ```
6971
+ */
5555
6972
  function ProgressLabel({ className, ...props }) {
5556
6973
  return /* @__PURE__ */ jsx(Progress$1.Label, {
5557
6974
  className: cn("text-sm font-medium", className),
@@ -5559,6 +6976,16 @@ function ProgressLabel({ className, ...props }) {
5559
6976
  ...props
5560
6977
  });
5561
6978
  }
6979
+ /**
6980
+ * A numeric display of the progress percentage.
6981
+ *
6982
+ * Automatically shows the current progress value as a percentage.
6983
+ *
6984
+ * @example
6985
+ * ```tsx
6986
+ * <ProgressValue />
6987
+ * ```
6988
+ */
5562
6989
  function ProgressValue({ className, ...props }) {
5563
6990
  return /* @__PURE__ */ jsx(Progress$1.Value, {
5564
6991
  className: cn("text-muted-foreground ml-auto text-sm tabular-nums", className),
@@ -5647,7 +7074,39 @@ function ScrollBar({ className, orientation = "vertical", ...props }) {
5647
7074
 
5648
7075
  //#endregion
5649
7076
  //#region src/web/ui/select.tsx
7077
+ /**
7078
+ * A dropdown for selecting a single value from a list of options.
7079
+ *
7080
+ * Provides a native-like select experience with custom styling. Supports
7081
+ * keyboard navigation, search, and grouping of options.
7082
+ *
7083
+ * @example
7084
+ * ```tsx
7085
+ * <Select>
7086
+ * <SelectTrigger>
7087
+ * <SelectValue placeholder="Select an option" />
7088
+ * </SelectTrigger>
7089
+ * <SelectContent>
7090
+ * <SelectItem value="apple">Apple</SelectItem>
7091
+ * <SelectItem value="banana">Banana</SelectItem>
7092
+ * </SelectContent>
7093
+ * </Select>
7094
+ * ```
7095
+ */
5650
7096
  const Select = Select$1.Root;
7097
+ /**
7098
+ * A container for grouping related SelectItem components.
7099
+ *
7100
+ * Use with SelectLabel to create labeled groups of options.
7101
+ *
7102
+ * @example
7103
+ * ```tsx
7104
+ * <SelectGroup>
7105
+ * <SelectLabel>Fruits</SelectLabel>
7106
+ * <SelectItem value="apple">Apple</SelectItem>
7107
+ * </SelectGroup>
7108
+ * ```
7109
+ */
5651
7110
  function SelectGroup({ className, ...props }) {
5652
7111
  return /* @__PURE__ */ jsx(Select$1.Group, {
5653
7112
  "data-slot": "select-group",
@@ -5655,6 +7114,16 @@ function SelectGroup({ className, ...props }) {
5655
7114
  ...props
5656
7115
  });
5657
7116
  }
7117
+ /**
7118
+ * Displays the currently selected value in the trigger.
7119
+ *
7120
+ * Shows a placeholder when no value is selected.
7121
+ *
7122
+ * @example
7123
+ * ```tsx
7124
+ * <SelectValue placeholder="Select a fruit" />
7125
+ * ```
7126
+ */
5658
7127
  function SelectValue({ className, ...props }) {
5659
7128
  return /* @__PURE__ */ jsx(Select$1.Value, {
5660
7129
  "data-slot": "select-value",
@@ -5662,6 +7131,19 @@ function SelectValue({ className, ...props }) {
5662
7131
  ...props
5663
7132
  });
5664
7133
  }
7134
+ /**
7135
+ * The button that opens the select dropdown.
7136
+ *
7137
+ * Displays the SelectValue and a dropdown indicator icon.
7138
+ * Supports multiple sizes.
7139
+ *
7140
+ * @example
7141
+ * ```tsx
7142
+ * <SelectTrigger>
7143
+ * <SelectValue placeholder="Select..." />
7144
+ * </SelectTrigger>
7145
+ * ```
7146
+ */
5665
7147
  function SelectTrigger({ className, size = "default", children, ...props }) {
5666
7148
  return /* @__PURE__ */ jsxs(Select$1.Trigger, {
5667
7149
  "data-slot": "select-trigger",
@@ -5671,6 +7153,20 @@ function SelectTrigger({ className, size = "default", children, ...props }) {
5671
7153
  children: [children, /* @__PURE__ */ jsx(Select$1.Icon, { render: /* @__PURE__ */ jsx(ChevronDownIcon, { className: "text-muted-foreground size-4 pointer-events-none" }) })]
5672
7154
  });
5673
7155
  }
7156
+ /**
7157
+ * The dropdown popup containing the select options.
7158
+ *
7159
+ * Positions relative to the trigger with scroll buttons for long lists.
7160
+ * Supports alignment and side positioning options.
7161
+ *
7162
+ * @example
7163
+ * ```tsx
7164
+ * <SelectContent>
7165
+ * <SelectItem value="1">Option 1</SelectItem>
7166
+ * <SelectItem value="2">Option 2</SelectItem>
7167
+ * </SelectContent>
7168
+ * ```
7169
+ */
5674
7170
  function SelectContent({ className, children, side = "bottom", sideOffset = 4, align = "center", alignOffset = 0, alignItemWithTrigger = true, ...props }) {
5675
7171
  return /* @__PURE__ */ jsx(Select$1.Portal, { children: /* @__PURE__ */ jsx(Select$1.Positioner, {
5676
7172
  side,
@@ -5691,6 +7187,16 @@ function SelectContent({ className, children, side = "bottom", sideOffset = 4, a
5691
7187
  })
5692
7188
  }) });
5693
7189
  }
7190
+ /**
7191
+ * A label for a SelectGroup.
7192
+ *
7193
+ * Displays in muted style above the group's items.
7194
+ *
7195
+ * @example
7196
+ * ```tsx
7197
+ * <SelectLabel>Category</SelectLabel>
7198
+ * ```
7199
+ */
5694
7200
  function SelectLabel({ className, ...props }) {
5695
7201
  return /* @__PURE__ */ jsx(Select$1.GroupLabel, {
5696
7202
  "data-slot": "select-label",
@@ -5698,6 +7204,17 @@ function SelectLabel({ className, ...props }) {
5698
7204
  ...props
5699
7205
  });
5700
7206
  }
7207
+ /**
7208
+ * A single selectable option in the dropdown.
7209
+ *
7210
+ * Displays a checkmark when selected. Supports keyboard navigation
7211
+ * and focus states.
7212
+ *
7213
+ * @example
7214
+ * ```tsx
7215
+ * <SelectItem value="apple">Apple</SelectItem>
7216
+ * ```
7217
+ */
5701
7218
  function SelectItem({ className, children, ...props }) {
5702
7219
  return /* @__PURE__ */ jsxs(Select$1.Item, {
5703
7220
  "data-slot": "select-item",
@@ -5712,6 +7229,16 @@ function SelectItem({ className, children, ...props }) {
5712
7229
  })]
5713
7230
  });
5714
7231
  }
7232
+ /**
7233
+ * A horizontal line separating groups of select items.
7234
+ *
7235
+ * Use to visually divide different sections of options.
7236
+ *
7237
+ * @example
7238
+ * ```tsx
7239
+ * <SelectSeparator />
7240
+ * ```
7241
+ */
5715
7242
  function SelectSeparator({ className, ...props }) {
5716
7243
  return /* @__PURE__ */ jsx(Select$1.Separator, {
5717
7244
  "data-slot": "select-separator",
@@ -5719,6 +7246,12 @@ function SelectSeparator({ className, ...props }) {
5719
7246
  ...props
5720
7247
  });
5721
7248
  }
7249
+ /**
7250
+ * A scroll indicator button at the top of the select dropdown.
7251
+ *
7252
+ * Appears when content is scrollable and there are items above the viewport.
7253
+ * Used internally by SelectContent.
7254
+ */
5722
7255
  function SelectScrollUpButton({ className, ...props }) {
5723
7256
  return /* @__PURE__ */ jsx(Select$1.ScrollUpArrow, {
5724
7257
  "data-slot": "select-scroll-up-button",
@@ -5727,6 +7260,12 @@ function SelectScrollUpButton({ className, ...props }) {
5727
7260
  children: /* @__PURE__ */ jsx(ChevronUpIcon, {})
5728
7261
  });
5729
7262
  }
7263
+ /**
7264
+ * A scroll indicator button at the bottom of the select dropdown.
7265
+ *
7266
+ * Appears when content is scrollable and there are items below the viewport.
7267
+ * Used internally by SelectContent.
7268
+ */
5730
7269
  function SelectScrollDownButton({ className, ...props }) {
5731
7270
  return /* @__PURE__ */ jsx(Select$1.ScrollDownArrow, {
5732
7271
  "data-slot": "select-scroll-down-button",
@@ -5820,6 +7359,18 @@ function SheetDescription({ className, ...props }) {
5820
7359
 
5821
7360
  //#endregion
5822
7361
  //#region src/web/ui/skeleton.tsx
7362
+ /**
7363
+ * A placeholder component showing a pulsing animation while content loads.
7364
+ *
7365
+ * Use to indicate loading states and reduce layout shift. Apply appropriate
7366
+ * width and height classes to match the expected content dimensions.
7367
+ *
7368
+ * @example
7369
+ * ```tsx
7370
+ * <Skeleton className="h-4 w-32" />
7371
+ * <Skeleton className="h-12 w-12 rounded-full" />
7372
+ * ```
7373
+ */
5823
7374
  function Skeleton({ className, ...props }) {
5824
7375
  return /* @__PURE__ */ jsx("div", {
5825
7376
  "data-slot": "skeleton",
@@ -5830,6 +7381,12 @@ function Skeleton({ className, ...props }) {
5830
7381
 
5831
7382
  //#endregion
5832
7383
  //#region src/web/ui/tooltip.tsx
7384
+ /**
7385
+ * Provides shared delay configuration for child tooltips.
7386
+ *
7387
+ * Used internally by Tooltip. Can be used explicitly to share delay
7388
+ * settings across multiple tooltips.
7389
+ */
5833
7390
  function TooltipProvider({ delay = 0, ...props }) {
5834
7391
  return /* @__PURE__ */ jsx(Tooltip$1.Provider, {
5835
7392
  "data-slot": "tooltip-provider",
@@ -5837,18 +7394,59 @@ function TooltipProvider({ delay = 0, ...props }) {
5837
7394
  ...props
5838
7395
  });
5839
7396
  }
7397
+ /**
7398
+ * A popup that displays information on hover or focus.
7399
+ *
7400
+ * Use for additional context about UI elements. Shows after a brief
7401
+ * delay when hovering or focusing the trigger.
7402
+ *
7403
+ * @example
7404
+ * ```tsx
7405
+ * <Tooltip>
7406
+ * <TooltipTrigger>
7407
+ * <Button variant="ghost" size="icon">?</Button>
7408
+ * </TooltipTrigger>
7409
+ * <TooltipContent>Help information</TooltipContent>
7410
+ * </Tooltip>
7411
+ * ```
7412
+ */
5840
7413
  function Tooltip({ ...props }) {
5841
7414
  return /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsx(Tooltip$1.Root, {
5842
7415
  "data-slot": "tooltip",
5843
7416
  ...props
5844
7417
  }) });
5845
7418
  }
7419
+ /**
7420
+ * The element that triggers the tooltip on hover or focus.
7421
+ *
7422
+ * Wraps any interactive element to enable tooltip behavior.
7423
+ *
7424
+ * @example
7425
+ * ```tsx
7426
+ * <TooltipTrigger>
7427
+ * <Button>Hover me</Button>
7428
+ * </TooltipTrigger>
7429
+ * ```
7430
+ */
5846
7431
  function TooltipTrigger({ ...props }) {
5847
7432
  return /* @__PURE__ */ jsx(Tooltip$1.Trigger, {
5848
7433
  "data-slot": "tooltip-trigger",
5849
7434
  ...props
5850
7435
  });
5851
7436
  }
7437
+ /**
7438
+ * The popup content displayed when the tooltip is visible.
7439
+ *
7440
+ * Supports positioning relative to the trigger with smooth animations.
7441
+ * Includes an arrow pointing to the trigger.
7442
+ *
7443
+ * @example
7444
+ * ```tsx
7445
+ * <TooltipContent side="bottom">
7446
+ * This is tooltip content
7447
+ * </TooltipContent>
7448
+ * ```
7449
+ */
5852
7450
  function TooltipContent({ className, side = "top", sideOffset = 4, align = "center", alignOffset = 0, children, ...props }) {
5853
7451
  return /* @__PURE__ */ jsx(Tooltip$1.Portal, { children: /* @__PURE__ */ jsx(Tooltip$1.Positioner, {
5854
7452
  align,
@@ -6231,6 +7829,18 @@ function SidebarMenuSubButton({ render, size = "md", isActive = false, className
6231
7829
 
6232
7830
  //#endregion
6233
7831
  //#region src/web/ui/slider.tsx
7832
+ /**
7833
+ * A control for selecting a numeric value within a range.
7834
+ *
7835
+ * Supports single value or range selection with draggable thumbs.
7836
+ * Works with both horizontal and vertical orientations.
7837
+ *
7838
+ * @example
7839
+ * ```tsx
7840
+ * <Slider defaultValue={[50]} max={100} step={1} />
7841
+ * <Slider defaultValue={[25, 75]} max={100} />
7842
+ * ```
7843
+ */
6234
7844
  function Slider({ className, defaultValue, value, min = 0, max = 100, ...props }) {
6235
7845
  const _values = React.useMemo(() => Array.isArray(value) ? value : Array.isArray(defaultValue) ? defaultValue : [min, max], [
6236
7846
  value,
@@ -6291,6 +7901,18 @@ const Toaster = ({ ...props }) => {
6291
7901
 
6292
7902
  //#endregion
6293
7903
  //#region src/web/ui/switch.tsx
7904
+ /**
7905
+ * A toggle control for switching between on and off states.
7906
+ *
7907
+ * Use for binary settings that take effect immediately. Supports
7908
+ * multiple sizes and disabled states.
7909
+ *
7910
+ * @example
7911
+ * ```tsx
7912
+ * <Switch id="airplane" />
7913
+ * <Label htmlFor="airplane">Airplane Mode</Label>
7914
+ * ```
7915
+ */
6294
7916
  function Switch({ className, size = "default", ...props }) {
6295
7917
  return /* @__PURE__ */ jsx(Switch$1.Root, {
6296
7918
  "data-slot": "switch",
@@ -6369,6 +7991,24 @@ function TableCaption({ className, ...props }) {
6369
7991
 
6370
7992
  //#endregion
6371
7993
  //#region src/web/ui/tabs.tsx
7994
+ /**
7995
+ * A set of layered content panels displayed one at a time.
7996
+ *
7997
+ * Organizes content into distinct views that users can switch between.
7998
+ * Supports horizontal and vertical orientations.
7999
+ *
8000
+ * @example
8001
+ * ```tsx
8002
+ * <Tabs defaultValue="account">
8003
+ * <TabsList>
8004
+ * <TabsTrigger value="account">Account</TabsTrigger>
8005
+ * <TabsTrigger value="settings">Settings</TabsTrigger>
8006
+ * </TabsList>
8007
+ * <TabsContent value="account">Account content</TabsContent>
8008
+ * <TabsContent value="settings">Settings content</TabsContent>
8009
+ * </Tabs>
8010
+ * ```
8011
+ */
6372
8012
  function Tabs({ className, orientation = "horizontal", ...props }) {
6373
8013
  return /* @__PURE__ */ jsx(Tabs$1.Root, {
6374
8014
  "data-slot": "tabs",
@@ -6377,6 +8017,7 @@ function Tabs({ className, orientation = "horizontal", ...props }) {
6377
8017
  ...props
6378
8018
  });
6379
8019
  }
8020
+ /** TabsList variant styles for different visual appearances. */
6380
8021
  const tabsListVariants = cva("rounded-lg p-[3px] group-data-horizontal/tabs:h-9 data-[variant=line]:rounded-none group/tabs-list text-muted-foreground inline-flex w-fit items-center justify-center group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col", {
6381
8022
  variants: { variant: {
6382
8023
  default: "bg-muted",
@@ -6384,6 +8025,19 @@ const tabsListVariants = cva("rounded-lg p-[3px] group-data-horizontal/tabs:h-9
6384
8025
  } },
6385
8026
  defaultVariants: { variant: "default" }
6386
8027
  });
8028
+ /**
8029
+ * The container for tab triggers.
8030
+ *
8031
+ * Supports default (pill-style) and line variants. Arranges triggers
8032
+ * horizontally or vertically based on parent orientation.
8033
+ *
8034
+ * @example
8035
+ * ```tsx
8036
+ * <TabsList variant="line">
8037
+ * <TabsTrigger value="tab1">Tab 1</TabsTrigger>
8038
+ * </TabsList>
8039
+ * ```
8040
+ */
6387
8041
  function TabsList({ className, variant = "default", ...props }) {
6388
8042
  return /* @__PURE__ */ jsx(Tabs$1.List, {
6389
8043
  "data-slot": "tabs-list",
@@ -6392,6 +8046,16 @@ function TabsList({ className, variant = "default", ...props }) {
6392
8046
  ...props
6393
8047
  });
6394
8048
  }
8049
+ /**
8050
+ * A button that activates its associated tab content panel.
8051
+ *
8052
+ * Displays active state styling and supports keyboard navigation.
8053
+ *
8054
+ * @example
8055
+ * ```tsx
8056
+ * <TabsTrigger value="settings">Settings</TabsTrigger>
8057
+ * ```
8058
+ */
6395
8059
  function TabsTrigger({ className, ...props }) {
6396
8060
  return /* @__PURE__ */ jsx(Tabs$1.Tab, {
6397
8061
  "data-slot": "tabs-trigger",
@@ -6399,6 +8063,19 @@ function TabsTrigger({ className, ...props }) {
6399
8063
  ...props
6400
8064
  });
6401
8065
  }
8066
+ /**
8067
+ * The content panel associated with a tab trigger.
8068
+ *
8069
+ * Only visible when its corresponding trigger is active. Hidden panels
8070
+ * are removed from the accessibility tree.
8071
+ *
8072
+ * @example
8073
+ * ```tsx
8074
+ * <TabsContent value="settings">
8075
+ * <p>Settings panel content.</p>
8076
+ * </TabsContent>
8077
+ * ```
8078
+ */
6402
8079
  function TabsContent({ className, ...props }) {
6403
8080
  return /* @__PURE__ */ jsx(Tabs$1.Panel, {
6404
8081
  "data-slot": "tabs-content",
@@ -6409,6 +8086,7 @@ function TabsContent({ className, ...props }) {
6409
8086
 
6410
8087
  //#endregion
6411
8088
  //#region src/web/ui/toggle.tsx
8089
+ /** Toggle variant and size styles. */
6412
8090
  const toggleVariants = cva("hover:text-foreground aria-pressed:bg-muted focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive gap-1 rounded-md text-sm font-medium transition-[color,box-shadow] [&_svg:not([class*='size-'])]:size-4 group/toggle hover:bg-muted inline-flex items-center justify-center whitespace-nowrap outline-none focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0", {
6413
8091
  variants: {
6414
8092
  variant: {
@@ -6426,6 +8104,19 @@ const toggleVariants = cva("hover:text-foreground aria-pressed:bg-muted focus-vi
6426
8104
  size: "default"
6427
8105
  }
6428
8106
  });
8107
+ /**
8108
+ * A button that can be toggled on or off.
8109
+ *
8110
+ * Use for toolbar buttons, formatting options, or any binary state toggle.
8111
+ * Shows a pressed state when active.
8112
+ *
8113
+ * @example
8114
+ * ```tsx
8115
+ * <Toggle aria-label="Toggle bold">
8116
+ * <BoldIcon />
8117
+ * </Toggle>
8118
+ * ```
8119
+ */
6429
8120
  function Toggle({ className, variant = "default", size = "default", ...props }) {
6430
8121
  return /* @__PURE__ */ jsx(Toggle$1, {
6431
8122
  "data-slot": "toggle",
@@ -6484,4 +8175,4 @@ function ToggleGroupItem({ className, children, variant = "default", size = "def
6484
8175
  }
6485
8176
 
6486
8177
  //#endregion
6487
- export { AUTH_BASE_PATH, AUTH_BASE_URL, Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AlertAction, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogMedia, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AlertTitle, AnimatedCheck, AspectRatio, AuthCard, AuthLayout, AuthProvider, Avatar, AvatarBadge, AvatarFallback, AvatarGroup, AvatarGroupCount, AvatarImage, Badge, Brand, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, ButtonGroup, ButtonGroupSeparator, ButtonGroupText, Calendar, CalendarDayButton, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious, ChartContainer, ChartLegend, ChartLegendContent, ChartStyle, ChartTooltip, ChartTooltipContent, Checkbox, Collapsible, CollapsibleContent, CollapsibleTrigger, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, DEFAULT_AUTH_PATHS, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmailInput, EmailOTPForm, Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle, ErrorPage, Field, FieldContent, FieldDescription, FieldError, FieldGroup, FieldLabel, FieldLegend, FieldSeparator, FieldSet, FieldTitle, ForgotPasswordForm, Form, HoverCard, HoverCardContent, HoverCardTrigger, HydratedRouter, I18nextProvider, IDLE_FETCHER, Input, InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText, InputGroupTextarea, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, Item, ItemActions, ItemContent, ItemDescription, ItemFooter, ItemGroup, ItemHeader, ItemMedia, ItemSeparator, ItemTitle, Kbd, KbdGroup, Label, Link, Links, LoginForm, MagicLinkForm, Menubar, MenubarCheckboxItem, MenubarContent, MenubarGroup, MenubarItem, MenubarLabel, MenubarMenu, MenubarPortal, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarShortcut, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger, Meta, NavLink, NavigationMenu, NavigationMenuContent, NavigationMenuIndicator, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuPositioner, NavigationMenuTrigger, OtpInput, Outlet, Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, PasskeyButton, PasskeyRegisterButton, PasswordInput, PhoneInput, PhoneOTPForm, Popover, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger, Progress, ProgressIndicator, ProgressLabel, ProgressTrack, ProgressValue, QueryClient, QueryClientProvider, RadioGroup, RadioGroupItem, ResetPasswordForm, ResizableHandle, ResizablePanel, ResizablePanelGroup, Route, SSOButton, Scripts, ScrollArea, ScrollBar, ScrollRestoration, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, ServerRouter, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, SignupForm, Skeleton, Slider, SocialButtons, Spinner, Switch, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, Toaster, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, Trans, TrustSignals, TwoFactorForm, VerifyEmailForm, badgeVariants, buttonGroupVariants, buttonVariants, changeLanguage, cn, createInstance, defaultI18nConfig, defineAuthClient, defineAuthFeatures, defineEmailOtpSchema, defineForgotPasswordSchema, defineI18nClient, defineLoginRedirect, defineLoginSchema, defineMagicLinkSchema, defineOtpSchema, definePhoneNumberSchema, definePostAuthRedirect, defineResetPasswordSchema, defineSignupSchema, defineTwoFactorBackupSchema, defineTwoFactorTotpSchema, getFixedT, href, init, initReactI18next, loadLanguages, loadNamespaces, loadResources, matchPath, matchRoutes, navigationMenuTriggerStyle, parsePath, redirect, redirectDocument, reloadResources, renderMatches, replace, resolvePath, setDefaultNamespace, t, tabsListVariants, toggleVariants, use, useActionData, useAsyncError, useAsyncValue, useAuth, useAuthContext, useAuthForm, useBeforeUnload, useBlocker, useCarousel, useFetcher, useFetchers, useFormAction, useHref, useInRouterContext, useIsMobile, useLinkClickHandler, useLoaderData, useLocation, useMatch, useMatches, useMutation, useNavigate, useNavigation, useNavigationType, useOutlet, useOutletContext, useParams, useQuery, useQueryClient, useResolvedPath, useRevalidator, useRouteError, useRouteLoaderData, useRoutes, useSearchParams, useSidebar, useSubmit, useTranslation, useViewTransitionState };
8178
+ export { AUTH_BASE_PATH, AUTH_BASE_URL, Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AlertAction, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogMedia, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AlertTitle, AnimatedCheck, AspectRatio, AuthCard, AuthLayout, AuthProvider, Avatar, AvatarBadge, AvatarFallback, AvatarGroup, AvatarGroupCount, AvatarImage, Badge, Brand, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, ButtonGroup, ButtonGroupSeparator, ButtonGroupText, Calendar, CalendarDayButton, CaptchaWidget, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious, ChartContainer, ChartLegend, ChartLegendContent, ChartStyle, ChartTooltip, ChartTooltipContent, Checkbox, Collapsible, CollapsibleContent, CollapsibleTrigger, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, DEFAULT_AUTH_PATHS, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmailInput, EmailOTPForm, Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle, ErrorPage, Field, FieldContent, FieldDescription, FieldError, FieldGroup, FieldLabel, FieldLegend, FieldSeparator, FieldSet, FieldTitle, ForgotPasswordForm, Form, HoverCard, HoverCardContent, HoverCardTrigger, HydratedRouter, I18nextProvider, IDLE_FETCHER, Input, InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText, InputGroupTextarea, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, Item, ItemActions, ItemContent, ItemDescription, ItemFooter, ItemGroup, ItemHeader, ItemMedia, ItemSeparator, ItemTitle, Kbd, KbdGroup, Label, Link, Links, LoginForm, MagicLinkForm, Menubar, MenubarCheckboxItem, MenubarContent, MenubarGroup, MenubarItem, MenubarLabel, MenubarMenu, MenubarPortal, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarShortcut, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger, Meta, NavLink, NavigationMenu, NavigationMenuContent, NavigationMenuIndicator, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuPositioner, NavigationMenuTrigger, OtpInput, Outlet, Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, PasskeyButton, PasskeyRegisterButton, PasswordInput, PhoneInput, PhoneOTPForm, Popover, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger, Progress, ProgressIndicator, ProgressLabel, ProgressTrack, ProgressValue, QueryClient, QueryClientProvider, RadioGroup, RadioGroupItem, ResetPasswordForm, ResizableHandle, ResizablePanel, ResizablePanelGroup, Route, SSOButton, Scripts, ScrollArea, ScrollBar, ScrollRestoration, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, ServerRouter, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, SignupForm, Skeleton, Slider, SocialButtons, Spinner, Switch, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, Toaster, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, Trans, TrustSignals, TwoFactorForm, VerifyEmailForm, badgeVariants, buttonGroupVariants, buttonSizeKeys, buttonVariantKeys, buttonVariants, changeLanguage, cn, createInstance, defaultI18nConfig, defineAuthClient, defineAuthFeatures, defineEmailOtpSchema, defineForgotPasswordSchema, defineI18nClient, defineLoginRedirect, defineLoginSchema, defineMagicLinkSchema, defineOtpSchema, definePhoneNumberSchema, definePostAuthRedirect, defineResetPasswordSchema, defineSignupSchema, defineTwoFactorBackupSchema, defineTwoFactorTotpSchema, getFixedT, href, init, initReactI18next, loadLanguages, loadNamespaces, loadResources, matchPath, matchRoutes, navigationMenuTriggerStyle, orientationSchema, orientations, parsePath, redirect, redirectDocument, reloadResources, renderMatches, replace, resolvePath, setDefaultNamespace, sizeSchema, sizes, t, tabsListVariants, toggleVariants, use, useActionData, useAsyncError, useAsyncValue, useAuth, useAuthContext, useAuthForm, useBeforeUnload, useBlocker, useCaptcha, useCarousel, useFetcher, useFetchers, useFormAction, useHref, useInRouterContext, useIsMobile, useLastUsedMethod, useLinkClickHandler, useLoaderData, useLocation, useMatch, useMatches, useMutation, useNavigate, useNavigation, useNavigationType, useOutlet, useOutletContext, useParams, useQuery, useQueryClient, useResolvedPath, useRevalidator, useRouteError, useRouteLoaderData, useRoutes, useSearchParams, useSidebar, useSubmit, useTranslation, useViewTransitionState };