@yca-software/yca-react-core 0.0.1

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 (87) hide show
  1. package/README.md +129 -0
  2. package/dist/admin/index.d.ts +10 -0
  3. package/dist/admin/index.js +3 -0
  4. package/dist/admin/index.js.map +1 -0
  5. package/dist/admin-BVYdWtEy.d.ts +3 -0
  6. package/dist/api/index.d.ts +68 -0
  7. package/dist/api/index.js +3 -0
  8. package/dist/api/index.js.map +1 -0
  9. package/dist/auth/index.d.ts +49 -0
  10. package/dist/auth/index.js +3 -0
  11. package/dist/auth/index.js.map +1 -0
  12. package/dist/chunk-2HJO2ICF.js +339 -0
  13. package/dist/chunk-2HJO2ICF.js.map +1 -0
  14. package/dist/chunk-3AYG73DK.js +174 -0
  15. package/dist/chunk-3AYG73DK.js.map +1 -0
  16. package/dist/chunk-3SCZ4Z6P.js +26 -0
  17. package/dist/chunk-3SCZ4Z6P.js.map +1 -0
  18. package/dist/chunk-3ZLODBRJ.js +21 -0
  19. package/dist/chunk-3ZLODBRJ.js.map +1 -0
  20. package/dist/chunk-6LO5O4G6.js +224 -0
  21. package/dist/chunk-6LO5O4G6.js.map +1 -0
  22. package/dist/chunk-APZVW5SW.js +75 -0
  23. package/dist/chunk-APZVW5SW.js.map +1 -0
  24. package/dist/chunk-EVZBADFR.js +583 -0
  25. package/dist/chunk-EVZBADFR.js.map +1 -0
  26. package/dist/chunk-GXWVFQCK.js +52 -0
  27. package/dist/chunk-GXWVFQCK.js.map +1 -0
  28. package/dist/chunk-KG4JVAKS.js +13 -0
  29. package/dist/chunk-KG4JVAKS.js.map +1 -0
  30. package/dist/chunk-KZH56VPP.js +183 -0
  31. package/dist/chunk-KZH56VPP.js.map +1 -0
  32. package/dist/chunk-LP7Q75C6.js +96 -0
  33. package/dist/chunk-LP7Q75C6.js.map +1 -0
  34. package/dist/chunk-NL4NAHXG.js +3 -0
  35. package/dist/chunk-NL4NAHXG.js.map +1 -0
  36. package/dist/chunk-PLSNCPEC.js +2507 -0
  37. package/dist/chunk-PLSNCPEC.js.map +1 -0
  38. package/dist/chunk-QFKVPVFC.js +1391 -0
  39. package/dist/chunk-QFKVPVFC.js.map +1 -0
  40. package/dist/chunk-TSXUUD64.js +64 -0
  41. package/dist/chunk-TSXUUD64.js.map +1 -0
  42. package/dist/chunk-UCJ76BEC.js +32 -0
  43. package/dist/chunk-UCJ76BEC.js.map +1 -0
  44. package/dist/chunk-WFTIYYII.js +3 -0
  45. package/dist/chunk-WFTIYYII.js.map +1 -0
  46. package/dist/components/forms/index.d.ts +101 -0
  47. package/dist/components/forms/index.js +7 -0
  48. package/dist/components/forms/index.js.map +1 -0
  49. package/dist/components/marketing/index.d.ts +473 -0
  50. package/dist/components/marketing/index.js +5 -0
  51. package/dist/components/marketing/index.js.map +1 -0
  52. package/dist/components/spa/index.d.ts +275 -0
  53. package/dist/components/spa/index.js +8 -0
  54. package/dist/components/spa/index.js.map +1 -0
  55. package/dist/components/ui/index.d.ts +420 -0
  56. package/dist/components/ui/index.js +6 -0
  57. package/dist/components/ui/index.js.map +1 -0
  58. package/dist/constants/index.d.ts +2 -0
  59. package/dist/constants/index.js +4 -0
  60. package/dist/constants/index.js.map +1 -0
  61. package/dist/cookies-ZS_QYOR7.d.ts +8 -0
  62. package/dist/dateRangePickerTranslations-B99F2WQA.d.ts +9 -0
  63. package/dist/errors/index.d.ts +48 -0
  64. package/dist/errors/index.js +3 -0
  65. package/dist/errors/index.js.map +1 -0
  66. package/dist/forms-VpLvEFob.d.ts +18 -0
  67. package/dist/hooks/index.d.ts +18 -0
  68. package/dist/hooks/index.js +4 -0
  69. package/dist/hooks/index.js.map +1 -0
  70. package/dist/index.d.ts +42 -0
  71. package/dist/index.js +19 -0
  72. package/dist/index.js.map +1 -0
  73. package/dist/lib/index.d.ts +49 -0
  74. package/dist/lib/index.js +7 -0
  75. package/dist/lib/index.js.map +1 -0
  76. package/dist/pagination-CFqoqiAG.d.ts +10 -0
  77. package/dist/radix-overlays-BlCr-uem.d.ts +10 -0
  78. package/dist/styles.css +9 -0
  79. package/dist/theme/marketing.css +750 -0
  80. package/dist/theme/tokens.css +115 -0
  81. package/dist/types/index.d.ts +2 -0
  82. package/dist/types/index.js +3 -0
  83. package/dist/types/index.js.map +1 -0
  84. package/dist/types-BuNHOJ1h.d.ts +58 -0
  85. package/dist/types-CN73TWSY.d.ts +51 -0
  86. package/dist/useAdminListPage-C4ZTTllP.d.ts +26 -0
  87. package/package.json +152 -0
package/README.md ADDED
@@ -0,0 +1,129 @@
1
+ # @yca-software/yca-react-core
2
+
3
+ Shared React components and helpers for YCA apps.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pnpm add @yca-software/yca-react-core
9
+ ```
10
+
11
+ Import styles once in your app entry:
12
+
13
+ ```tsx
14
+ import '@yca-software/yca-react-core/styles.css';
15
+ ```
16
+
17
+ **Peers:** `react`, `react-dom`, `react-hook-form`, `react-i18next`, `react-router`, `@tanstack/react-query`
18
+
19
+ ## Usage
20
+
21
+ Prefer subpath imports:
22
+
23
+ ```tsx
24
+ import { Button, Form } from '@yca-software/yca-react-core/ui';
25
+ import { Hero, Navigation, Footer } from '@yca-software/yca-react-core/marketing';
26
+ import { AdminListPage, PageLoader } from '@yca-software/yca-react-core/spa';
27
+ import { EmailField, PermissionsField } from '@yca-software/yca-react-core/forms';
28
+ import { createApiProvider, useAPI } from '@yca-software/yca-react-core/api';
29
+ import { resolveApiError, formatApiError } from '@yca-software/yca-react-core/errors';
30
+ import { createCookieNames } from '@yca-software/yca-react-core/constants';
31
+ import { useAdminListPage, useTranslationNamespace } from '@yca-software/yca-react-core/hooks';
32
+ import { getAdminDetailErrorKind } from '@yca-software/yca-react-core/admin';
33
+ ```
34
+
35
+ | Subpath | Contents |
36
+ |---------|----------|
37
+ | `/ui` | Radix + Tailwind UI primitives (~34 components) |
38
+ | `/marketing` | Landing page blocks (Hero, Navigation, PricingSection, …) |
39
+ | `/spa` | Admin lists, loaders, query shells, entity rows, theme |
40
+ | `/forms` | Generic RHF fields (EmailField, PermissionsField, …) |
41
+ | `/api` | HTTP client, `createApiProvider`, `useAPI`, 401 refresh |
42
+ | `/auth` | Cookie helpers, JWT decode |
43
+ | `/errors` | `resolveApiError`, `formatApiError`, mutation/query toast helpers |
44
+ | `/constants` | `createCookieNames`, pagination flatten helpers |
45
+ | `/admin` | `getAdminDetailErrorKind` |
46
+ | `/types` | Shared form/admin types (`RoleOption`, `PermissionGroup`, …) |
47
+ | `/hooks` | `useAdminListPage`, `useTranslationNamespace` |
48
+ | `/lib` | `cn`, dates, pagination, surfaces, radix overlays |
49
+ | `/styles.css` | Theme tokens + `ds-mkt-*` marketing utilities |
50
+
51
+ The root export re-exports all subpaths for convenience.
52
+
53
+ See component READMEs under `src/components/{ui,marketing,spa,forms}/`.
54
+
55
+ ## Consumer wiring
56
+
57
+ The library provides **generic mechanisms**. Your SPA binds env, stores, and locales in a thin `src/helpers/` layer (same pattern as `templates/react-spa`):
58
+
59
+ ```tsx
60
+ // helpers/error.ts — app owns i18n + toast
61
+ export function formatApiError(error: unknown) {
62
+ return coreFormatApiError(error, i18n.t.bind(i18n));
63
+ }
64
+
65
+ // helpers/cookie.ts — app owns env
66
+ export const cookieNames = createCookieNames(APP_NAME, ENV);
67
+
68
+ // helpers/hooks/useTranslationNamespace.ts
69
+ export function useTranslationNamespace(ns: string | string[]) {
70
+ return useCoreTranslationNamespace(ns, loadNamespace);
71
+ }
72
+ ```
73
+
74
+ **Keep in the app, not the library:** routes, TanStack Query hooks per resource, zustand stores, `locales/**`, billing/Paddle, permission constants, PostHog.
75
+
76
+ ## Theming
77
+
78
+ Import styles once, then override CSS variables in your global stylesheet:
79
+
80
+ ```tsx
81
+ import '@yca-software/yca-react-core/styles.css';
82
+ ```
83
+
84
+ ```css
85
+ :root {
86
+ --radius: 0.875rem;
87
+ --primary: oklch(0.35 0.03 85);
88
+ --border: oklch(0.88 0.006 85);
89
+ }
90
+
91
+ .dark {
92
+ --background: oklch(0.14 0.01 85);
93
+ }
94
+ ```
95
+
96
+ Key variables: `--background`, `--foreground`, `--card`, `--primary`, `--muted`, `--border`, `--border-subtle`, `--radius`, `--shadow-surface`, `--shadow-elevated`, `--font-sans`.
97
+
98
+ Browse **Foundation → Theme** in Storybook for a full table and live preview.
99
+
100
+ See [`CHANGELOG.md`](CHANGELOG.md) for visual default changes in the latest release.
101
+
102
+ ## Development
103
+
104
+ ```bash
105
+ pnpm install
106
+ pnpm run build
107
+ pnpm run ci
108
+ pnpm run storybook
109
+ ```
110
+
111
+ ## Adding modules
112
+
113
+ 1. Add source under `src/components/`, `src/hooks/`, `src/lib/`, or kit folders (`api/`, `auth/`, …)
114
+ 2. UI components: implement in `ui/<name>/index.tsx` (see `src/components/ui/README.md`)
115
+ 3. Marketing blocks: implement in `marketing/<name>/index.tsx`
116
+ 4. SPA patterns: implement in `spa/<name>/index.tsx`
117
+ 5. Form fields: implement in `forms/<name>/index.tsx`
118
+ 6. Add a `tsup` entry in `tsup.config.ts` if you add a new subpath
119
+ 7. Add a matching `exports` entry in `package.json`
120
+
121
+ ## Release
122
+
123
+ Publishing is automated when you [create a GitHub Release](https://github.com/yca-software/yca-react-core/releases/new):
124
+
125
+ 1. Bump `version` in `package.json`
126
+ 2. Commit and push to `main`
127
+ 3. Tag `vX.Y.Z` and publish the release
128
+
129
+ CI runs build, lint, tests, then publishes to npm with `NPM_TOKEN`.
@@ -0,0 +1,10 @@
1
+ import { A as AdminDetailErrorKind } from '../admin-BVYdWtEy.js';
2
+
3
+ declare function getAdminDetailErrorKind(options: {
4
+ isLoading: boolean;
5
+ isError: boolean;
6
+ hasData: boolean;
7
+ status?: number;
8
+ }): AdminDetailErrorKind | undefined;
9
+
10
+ export { AdminDetailErrorKind, getAdminDetailErrorKind };
@@ -0,0 +1,3 @@
1
+ export { getAdminDetailErrorKind } from '../chunk-3ZLODBRJ.js';
2
+ //# sourceMappingURL=index.js.map
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
@@ -0,0 +1,3 @@
1
+ type AdminDetailErrorKind = 'notFound' | 'forbidden' | 'load';
2
+
3
+ export type { AdminDetailErrorKind as A };
@@ -0,0 +1,68 @@
1
+ import * as React from 'react';
2
+ import { ReactNode } from 'react';
3
+ import { a as ApiClientProviderConfig, F as FetchFn, A as ApiClientConfig, d as RefreshRequestConfig, e as RequestConfig } from '../types-CN73TWSY.js';
4
+ export { b as ApiErrorBody, R as RefreshBodyContext, c as RefreshConfig, f as RequestError } from '../types-CN73TWSY.js';
5
+
6
+ declare function ApiClientProvider({ baseURL, config, children, }: {
7
+ baseURL: string;
8
+ config: ApiClientProviderConfig;
9
+ children: ReactNode;
10
+ }): React.JSX.Element;
11
+ declare function useApiClientConfig(): ApiClientConfig;
12
+ declare function useAPI(): FetchFn;
13
+
14
+ interface CreateApiProviderOptions {
15
+ baseURL: string;
16
+ getAccessToken: () => string | null;
17
+ getRefreshToken: () => string | null;
18
+ /** When true, refresh uses HttpOnly cookie credentials instead of body token. */
19
+ cookieCredentialsEnabled: () => boolean;
20
+ setAccessToken: (token: string) => void;
21
+ onRefreshFailure: () => void;
22
+ getAcceptLanguage?: () => string;
23
+ onRefreshStart?: () => void;
24
+ onRefreshEnd?: () => void;
25
+ /** Refresh endpoint path relative to baseURL. Defaults to `auth/refresh`. */
26
+ refreshEndpoint?: string;
27
+ /** Paths that must not trigger automatic refresh retry on 401. Defaults to `['auth/']`. */
28
+ refreshExcludedPrefixes?: string[];
29
+ }
30
+ /**
31
+ * Factory for app `ApiProvider` components. Pass a hook that returns wiring options
32
+ * (cookies, i18n, zustand). Memoize that return value with `useMemo` so the API
33
+ * client context is not rebuilt on every parent render.
34
+ */
35
+ declare function createApiProvider(useOptions: () => CreateApiProviderOptions): ({ children }: {
36
+ children: ReactNode;
37
+ }) => React.JSX.Element;
38
+
39
+ declare const DEFAULT_REFRESH_ENDPOINT = "auth/refresh";
40
+ declare const DEFAULT_REFRESH_EXCLUDED_PREFIXES: string[];
41
+ interface DefaultRefreshRequestOptions {
42
+ endpoint?: string;
43
+ excludedRetryPrefixes?: string[];
44
+ }
45
+ declare function createDefaultRefreshRequest({ endpoint, excludedRetryPrefixes, }?: DefaultRefreshRequestOptions): RefreshRequestConfig;
46
+ declare function buildRequestUrl(baseURL: string, endpoint: string): string;
47
+ declare function getRequestHeaders(accessToken: string | null, options?: {
48
+ multipart?: boolean;
49
+ acceptLanguage?: string;
50
+ }): HeadersInit;
51
+ declare function getRequestOptions(config: RequestConfig, token: string | null, acceptLanguage?: string): RequestInit;
52
+ declare function performAccessTokenRefresh(params: {
53
+ baseURL: string;
54
+ getRefreshToken: () => string | null;
55
+ request: RefreshRequestConfig;
56
+ useCookieCredentials: boolean;
57
+ setAccessToken: (token: string) => void;
58
+ onFailure: () => void;
59
+ acceptLanguage?: string;
60
+ }): Promise<string>;
61
+ declare function executeConfiguredRefresh(config: ApiClientConfig): Promise<string>;
62
+
63
+ declare function createResponseHandler(config: ApiClientConfig): (response: Response, options?: {
64
+ config?: RequestConfig;
65
+ isRetry?: boolean;
66
+ }) => Promise<unknown>;
67
+
68
+ export { ApiClientConfig, ApiClientProvider, ApiClientProviderConfig, type CreateApiProviderOptions, DEFAULT_REFRESH_ENDPOINT, DEFAULT_REFRESH_EXCLUDED_PREFIXES, type DefaultRefreshRequestOptions, FetchFn, RefreshRequestConfig, RequestConfig, buildRequestUrl, createApiProvider, createDefaultRefreshRequest, createResponseHandler, executeConfiguredRefresh, getRequestHeaders, getRequestOptions, performAccessTokenRefresh, useAPI, useApiClientConfig };
@@ -0,0 +1,3 @@
1
+ export { ApiClientProvider, DEFAULT_REFRESH_ENDPOINT, DEFAULT_REFRESH_EXCLUDED_PREFIXES, buildRequestUrl, createApiProvider, createDefaultRefreshRequest, createResponseHandler, executeConfiguredRefresh, getRequestHeaders, getRequestOptions, performAccessTokenRefresh, useAPI, useApiClientConfig } from '../chunk-6LO5O4G6.js';
2
+ //# sourceMappingURL=index.js.map
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
@@ -0,0 +1,49 @@
1
+ import { C as CookieNames } from '../cookies-ZS_QYOR7.js';
2
+
3
+ interface CookieConsent {
4
+ analytics: boolean;
5
+ timestamp: string;
6
+ }
7
+ interface TokenCookieOptions {
8
+ names: CookieNames;
9
+ domain?: string;
10
+ secure?: boolean;
11
+ sameSite?: 'strict' | 'lax' | 'none';
12
+ }
13
+ declare function getAccessTokenFromCookies(names: CookieNames): string | null;
14
+ declare function getRefreshTokenFromCookies(names: CookieNames): string | null;
15
+ declare function getCookieConsentFromCookies(names: CookieNames): CookieConsent | null;
16
+ declare function removeAccessTokenCookie(options: TokenCookieOptions): void;
17
+ declare function removeRefreshTokenCookie(options: TokenCookieOptions): void;
18
+ declare function removeCookieConsentCookie(names: CookieNames): void;
19
+ declare function setCookieConsentCookie(names: CookieNames, timestamp: string, analytics: boolean, domain?: string): void;
20
+ declare function setAccessTokenCookie(token: string, options: TokenCookieOptions): void;
21
+ declare function setRefreshTokenCookie(token: string, options: TokenCookieOptions): void;
22
+ declare function setTokens(accessToken: string, refreshToken: string, options: TokenCookieOptions): void;
23
+
24
+ interface JWTAccessTokenPermissionData {
25
+ organizationId: string;
26
+ roleId?: string;
27
+ permissions: string[];
28
+ }
29
+ interface DecodedAccessToken {
30
+ sub: string;
31
+ email?: string;
32
+ permissions?: JWTAccessTokenPermissionData[];
33
+ isAdmin?: boolean;
34
+ impersonatedBy?: string | null;
35
+ impersonatedByEmail?: string | null;
36
+ }
37
+ interface AccessInfo {
38
+ userId: string;
39
+ email: string;
40
+ permissions: JWTAccessTokenPermissionData[];
41
+ isAdmin: boolean;
42
+ impersonatedBy: string | null;
43
+ impersonatedByEmail: string | null;
44
+ }
45
+
46
+ declare function getAccessInfoFromToken(accessToken: string): AccessInfo | null;
47
+ declare function getInitials(firstName?: string, lastName?: string): string;
48
+
49
+ export { type AccessInfo, type CookieConsent, type DecodedAccessToken, type JWTAccessTokenPermissionData, type TokenCookieOptions, getAccessInfoFromToken, getAccessTokenFromCookies, getCookieConsentFromCookies, getInitials, getRefreshTokenFromCookies, removeAccessTokenCookie, removeCookieConsentCookie, removeRefreshTokenCookie, setAccessTokenCookie, setCookieConsentCookie, setRefreshTokenCookie, setTokens };
@@ -0,0 +1,3 @@
1
+ export { getAccessInfoFromToken, getAccessTokenFromCookies, getCookieConsentFromCookies, getInitials, getRefreshTokenFromCookies, removeAccessTokenCookie, removeCookieConsentCookie, removeRefreshTokenCookie, setAccessTokenCookie, setCookieConsentCookie, setRefreshTokenCookie, setTokens } from '../chunk-APZVW5SW.js';
2
+ //# sourceMappingURL=index.js.map
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
@@ -0,0 +1,339 @@
1
+ import { FormField, FormItem, FormLabel, FormControl, Textarea, FormMessage, Select, Tooltip, Checkbox, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem } from './chunk-PLSNCPEC.js';
2
+ import { Input } from './chunk-3AYG73DK.js';
3
+ import { cn } from './chunk-GXWVFQCK.js';
4
+ import { jsx, jsxs } from 'react/jsx-runtime';
5
+ import { Info, ChevronDown } from 'lucide-react';
6
+
7
+ function DescriptionField({
8
+ control,
9
+ name,
10
+ label,
11
+ placeholder,
12
+ className,
13
+ disabled
14
+ }) {
15
+ return /* @__PURE__ */ jsx(
16
+ FormField,
17
+ {
18
+ control,
19
+ name,
20
+ render: ({ field }) => /* @__PURE__ */ jsxs(FormItem, { className, children: [
21
+ /* @__PURE__ */ jsx(FormLabel, { children: label }),
22
+ /* @__PURE__ */ jsx(FormControl, { children: /* @__PURE__ */ jsx(Textarea, { placeholder, disabled, ...field }) }),
23
+ /* @__PURE__ */ jsx(FormMessage, {})
24
+ ] })
25
+ }
26
+ );
27
+ }
28
+ function EmailField({
29
+ control,
30
+ name,
31
+ label,
32
+ placeholder,
33
+ description,
34
+ autoComplete = "email",
35
+ className
36
+ }) {
37
+ return /* @__PURE__ */ jsx(
38
+ FormField,
39
+ {
40
+ control,
41
+ name,
42
+ render: ({ field }) => /* @__PURE__ */ jsxs(FormItem, { className, children: [
43
+ /* @__PURE__ */ jsx(FormLabel, { children: label }),
44
+ description && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mt-0.5", children: description }),
45
+ /* @__PURE__ */ jsx(FormControl, { children: /* @__PURE__ */ jsx(Input, { type: "email", placeholder, autoComplete, ...field }) }),
46
+ /* @__PURE__ */ jsx(FormMessage, {})
47
+ ] })
48
+ }
49
+ );
50
+ }
51
+ function ExpiresAtField({
52
+ control,
53
+ name,
54
+ label,
55
+ placeholder,
56
+ ariaLabel,
57
+ options
58
+ }) {
59
+ return /* @__PURE__ */ jsx(
60
+ FormField,
61
+ {
62
+ control,
63
+ name,
64
+ render: ({ field }) => /* @__PURE__ */ jsxs(FormItem, { children: [
65
+ /* @__PURE__ */ jsx(FormLabel, { children: label }),
66
+ /* @__PURE__ */ jsx(
67
+ Select,
68
+ {
69
+ value: field.value ?? "",
70
+ onValueChange: (next) => {
71
+ field.onChange(next);
72
+ },
73
+ options,
74
+ placeholder,
75
+ "aria-label": ariaLabel ?? label
76
+ }
77
+ ),
78
+ /* @__PURE__ */ jsx(FormMessage, {})
79
+ ] })
80
+ }
81
+ );
82
+ }
83
+ function FirstNameField({
84
+ control,
85
+ name,
86
+ label,
87
+ placeholder,
88
+ className
89
+ }) {
90
+ return /* @__PURE__ */ jsx(
91
+ FormField,
92
+ {
93
+ control,
94
+ name,
95
+ render: ({ field }) => /* @__PURE__ */ jsxs(FormItem, { className, children: [
96
+ /* @__PURE__ */ jsx(FormLabel, { children: label }),
97
+ /* @__PURE__ */ jsx(FormControl, { children: /* @__PURE__ */ jsx(Input, { placeholder, autoComplete: "given-name", ...field }) }),
98
+ /* @__PURE__ */ jsx(FormMessage, {})
99
+ ] })
100
+ }
101
+ );
102
+ }
103
+ function LastNameField({
104
+ control,
105
+ name,
106
+ label,
107
+ placeholder,
108
+ className
109
+ }) {
110
+ return /* @__PURE__ */ jsx(
111
+ FormField,
112
+ {
113
+ control,
114
+ name,
115
+ render: ({ field }) => /* @__PURE__ */ jsxs(FormItem, { className, children: [
116
+ /* @__PURE__ */ jsx(FormLabel, { children: label }),
117
+ /* @__PURE__ */ jsx(FormControl, { children: /* @__PURE__ */ jsx(Input, { placeholder, autoComplete: "family-name", ...field }) }),
118
+ /* @__PURE__ */ jsx(FormMessage, {})
119
+ ] })
120
+ }
121
+ );
122
+ }
123
+ function NameField({
124
+ control,
125
+ name,
126
+ label,
127
+ placeholder,
128
+ className,
129
+ disabled
130
+ }) {
131
+ return /* @__PURE__ */ jsx(
132
+ FormField,
133
+ {
134
+ control,
135
+ name,
136
+ render: ({ field }) => /* @__PURE__ */ jsxs(FormItem, { className, children: [
137
+ /* @__PURE__ */ jsx(FormLabel, { children: label }),
138
+ /* @__PURE__ */ jsx(FormControl, { children: /* @__PURE__ */ jsx(Input, { placeholder, disabled, autoComplete: "off", ...field }) }),
139
+ /* @__PURE__ */ jsx(FormMessage, {})
140
+ ] })
141
+ }
142
+ );
143
+ }
144
+ function PasswordField({
145
+ control,
146
+ name,
147
+ label,
148
+ placeholder,
149
+ autoComplete = "current-password",
150
+ rightLabel,
151
+ className
152
+ }) {
153
+ return /* @__PURE__ */ jsx(
154
+ FormField,
155
+ {
156
+ control,
157
+ name,
158
+ render: ({ field }) => /* @__PURE__ */ jsxs(FormItem, { className, children: [
159
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
160
+ /* @__PURE__ */ jsx(FormLabel, { children: label }),
161
+ rightLabel
162
+ ] }),
163
+ /* @__PURE__ */ jsx(FormControl, { children: /* @__PURE__ */ jsx(
164
+ Input,
165
+ {
166
+ type: "password",
167
+ placeholder,
168
+ autoComplete,
169
+ ...field
170
+ }
171
+ ) }),
172
+ /* @__PURE__ */ jsx(FormMessage, {})
173
+ ] })
174
+ }
175
+ );
176
+ }
177
+ function PermissionsField({
178
+ control,
179
+ name,
180
+ label,
181
+ groups,
182
+ t,
183
+ disabled = false
184
+ }) {
185
+ return /* @__PURE__ */ jsx(
186
+ FormField,
187
+ {
188
+ control,
189
+ name,
190
+ render: ({ field }) => {
191
+ const selected = field.value ?? [];
192
+ const togglePermission = (key, group) => {
193
+ const readKey = getReadKeyForGroup(group);
194
+ const opt = group.find((p) => p.key === key);
195
+ if (!opt) return;
196
+ const next = selected.includes(key) ? selected.filter((k) => k !== key) : [...selected, key];
197
+ if (opt.action === "write" || opt.action === "delete") {
198
+ if (next.includes(key) && readKey && !next.includes(readKey)) {
199
+ next.push(readKey);
200
+ }
201
+ }
202
+ if (opt.action === "delete") {
203
+ const writeKey = getWriteKeyForGroup(group);
204
+ if (next.includes(key) && writeKey && !next.includes(writeKey)) {
205
+ next.push(writeKey);
206
+ }
207
+ }
208
+ field.onChange(next);
209
+ };
210
+ return /* @__PURE__ */ jsxs(FormItem, { children: [
211
+ /* @__PURE__ */ jsx(FormLabel, { children: label }),
212
+ /* @__PURE__ */ jsx("div", { className: "space-y-4", children: groups.map((group) => {
213
+ const readKey = getReadKeyForGroup(group.permissions);
214
+ const writeKey = getWriteKeyForGroup(group.permissions);
215
+ const readDisabled = readKey ? hasWriteOrDelete(group.permissions, selected) : false;
216
+ const writeDisabled = writeKey ? hasDelete(group.permissions, selected) : false;
217
+ const readChecked = readKey !== null && (selected.includes(readKey) || readDisabled);
218
+ const writeChecked = writeKey !== null && (selected.includes(writeKey) || writeDisabled);
219
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
220
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
221
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: t(group.labelKey) }),
222
+ /* @__PURE__ */ jsx(Tooltip, { content: t(group.descriptionKey), side: "top", align: "start", children: /* @__PURE__ */ jsx(
223
+ "button",
224
+ {
225
+ type: "button",
226
+ className: "text-muted-foreground hover:text-foreground cursor-help inline-flex rounded p-0.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
227
+ "aria-label": t(group.descriptionKey),
228
+ children: /* @__PURE__ */ jsx(Info, { className: "size-3.5", "aria-hidden": true })
229
+ }
230
+ ) })
231
+ ] }),
232
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-4 pl-2", children: group.permissions.map((perm) => {
233
+ const isRead = perm.action === "read";
234
+ const isWrite = perm.action === "write";
235
+ const checked = isRead ? readChecked : isWrite ? writeChecked : selected.includes(perm.key);
236
+ const permissionDisabled = isRead && readDisabled || isWrite && writeDisabled;
237
+ const globallyDisabled = disabled || permissionDisabled;
238
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center space-x-2", children: [
239
+ /* @__PURE__ */ jsx(
240
+ Checkbox,
241
+ {
242
+ id: perm.key,
243
+ checked: isRead ? readChecked : checked,
244
+ disabled: globallyDisabled,
245
+ onCheckedChange: () => !globallyDisabled && togglePermission(perm.key, group.permissions)
246
+ }
247
+ ),
248
+ /* @__PURE__ */ jsx(
249
+ "label",
250
+ {
251
+ htmlFor: perm.key,
252
+ className: "text-sm font-normal leading-none cursor-pointer peer-disabled:opacity-70",
253
+ children: t(perm.labelKey)
254
+ }
255
+ )
256
+ ] }, perm.key);
257
+ }) })
258
+ ] }, group.contextKey);
259
+ }) }),
260
+ /* @__PURE__ */ jsx(FormMessage, {})
261
+ ] });
262
+ }
263
+ }
264
+ );
265
+ }
266
+ function getReadKeyForGroup(permissions) {
267
+ const readOpt = permissions.find((p) => p.action === "read");
268
+ return readOpt?.key ?? null;
269
+ }
270
+ function getWriteKeyForGroup(permissions) {
271
+ const writeOpt = permissions.find((p) => p.action === "write");
272
+ return writeOpt?.key ?? null;
273
+ }
274
+ function hasWriteOrDelete(permissions, selected) {
275
+ return permissions.some(
276
+ (p) => (p.action === "write" || p.action === "delete") && selected.includes(p.key)
277
+ );
278
+ }
279
+ function hasDelete(permissions, selected) {
280
+ return permissions.some((p) => p.action === "delete" && selected.includes(p.key));
281
+ }
282
+ function RoleSelectField({
283
+ control,
284
+ name,
285
+ label,
286
+ options,
287
+ className
288
+ }) {
289
+ return /* @__PURE__ */ jsx(
290
+ FormField,
291
+ {
292
+ control,
293
+ name,
294
+ render: ({ field }) => {
295
+ const selectedOption = options.find((opt) => opt.value === field.value);
296
+ const displayValue = selectedOption?.label ?? label;
297
+ return /* @__PURE__ */ jsxs(FormItem, { className, children: [
298
+ /* @__PURE__ */ jsx(FormLabel, { children: label }),
299
+ /* @__PURE__ */ jsx(FormControl, { children: /* @__PURE__ */ jsxs(DropdownMenu, { children: [
300
+ /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
301
+ "button",
302
+ {
303
+ type: "button",
304
+ role: "combobox",
305
+ "aria-label": label,
306
+ onBlur: field.onBlur,
307
+ className: cn(
308
+ "flex h-9 w-full cursor-pointer items-center justify-between rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-xs outline-none transition-colors",
309
+ "hover:bg-accent/50 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
310
+ "disabled:pointer-events-none disabled:opacity-50",
311
+ "aria-invalid:ring-destructive/20 aria-invalid:border-destructive",
312
+ !field.value && "text-muted-foreground"
313
+ ),
314
+ children: [
315
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate text-left", children: displayValue }),
316
+ /* @__PURE__ */ jsx(ChevronDown, { className: "ml-2 h-4 w-4 shrink-0 text-muted-foreground" })
317
+ ]
318
+ }
319
+ ) }),
320
+ /* @__PURE__ */ jsx(
321
+ DropdownMenuContent,
322
+ {
323
+ align: "start",
324
+ sideOffset: 0,
325
+ className: cn("min-w-(--radix-dropdown-menu-trigger-width) z-101"),
326
+ children: options.map((opt) => /* @__PURE__ */ jsx(DropdownMenuItem, { onClick: () => field.onChange(opt.value), children: opt.label }, opt.value))
327
+ }
328
+ )
329
+ ] }) }),
330
+ /* @__PURE__ */ jsx(FormMessage, {})
331
+ ] });
332
+ }
333
+ }
334
+ );
335
+ }
336
+
337
+ export { DescriptionField, EmailField, ExpiresAtField, FirstNameField, LastNameField, NameField, PasswordField, PermissionsField, RoleSelectField };
338
+ //# sourceMappingURL=chunk-2HJO2ICF.js.map
339
+ //# sourceMappingURL=chunk-2HJO2ICF.js.map