@vvedantb/ui 0.9.0 → 0.10.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.
- package/README.md +1 -1
- package/app/README.md +67 -1
- package/app/src/convex/ConvexClerkProvider.tsx +37 -0
- package/app/src/convex/env.ts +76 -0
- package/app/src/convex/useEnsureUser.ts +24 -0
- package/app/src/index.ts +17 -0
- package/package.json +9 -3
- package/src/index.ts +1 -1
package/README.md
CHANGED
|
@@ -943,6 +943,6 @@ The compat file includes a fallback `glass-panel-strong` (`rgb(var(--card) / 0.9
|
|
|
943
943
|
|
|
944
944
|
A fixture that renders the main components against sample light and dark RGB variables is in `tokens/examples/compat-shadcn-rgb/`.
|
|
945
945
|
|
|
946
|
-
`@clerk/clerk-react`, `@tanstack/react-hotkeys`, `motion` and `zod` are optional peers (`peerDependenciesMeta`).
|
|
946
|
+
`@clerk/clerk-react`, `convex`, `@tanstack/react-hotkeys`, `motion` and `zod` are optional peers (`peerDependenciesMeta`), so the package manager does not install or warn about them. The root still imports each of them statically from some module (`useStableAuth` and `ConvexClerkProvider` import Clerk; `ConvexClerkProvider` and `useEnsureUser` import `convex/react`; the motion presets, `ConfirmSkipHint` and others import the rest). So an app that imports `@vvedantb/ui` must have all five resolvable, even if it uses none of those exports: a bundler resolves every import before it tree-shakes. `app/tests-node/convex-esm.test.mjs` pins which root modules import `convex` and Clerk.
|
|
947
947
|
|
|
948
948
|
Full documentation: the repository `README.md`. Upgrading from `@vvedantb/tokens`, `@vvedantb/shell` or `@vvedantb/app-kit`: `MIGRATION.md`.
|
package/app/README.md
CHANGED
|
@@ -10,7 +10,7 @@ npm install @vvedantb/ui
|
|
|
10
10
|
|
|
11
11
|
## Peers
|
|
12
12
|
|
|
13
|
-
Required: `react` and `react-dom` (>=19). Optional peers of `@vvedantb/ui
|
|
13
|
+
Required: `react` and `react-dom` (>=19). Optional peers of `@vvedantb/ui` used by the app helpers: `@clerk/clerk-react` (`useStableAuth`, `ConvexClerkProvider`), `convex` (`ConvexClerkProvider`, `useEnsureUser`), `@tanstack/react-hotkeys` and `zod` (your `z` for `parseClientEnv`). The root imports Clerk and `convex/react` statically, so they must be installed even if you do not use these helpers; see the package README.
|
|
14
14
|
|
|
15
15
|
`sonner` is not a dependency. The update watcher takes the toast API as an option. The app source uses a local class join, which the root does not export (the root `cn` is the clsx and tailwind-merge one). Tailwind must scan `node_modules/@vvedantb/ui/app/src/**/*.{ts,tsx}` so the class names are generated, and the app needs the `@vvedantb/ui/tokens` names (`bg-surface-card`, `divide-separator`, `text-muted-foreground` and so on).
|
|
16
16
|
|
|
@@ -191,3 +191,69 @@ Pure TypeScript, no dependencies. Options:
|
|
|
191
191
|
- `calendar`: `false` (default) counts elapsed time: a day is 24 hours, a month 30 days, a year 12 such months. `true` counts as `dayjs().diff(date, unit)` does: days by the local calendar across DST changes, and calendar months and years.
|
|
192
192
|
|
|
193
193
|
Strings are parsed with `new Date(string)`. dayjs parses a date-only string such as `"2026-03-01"` as local midnight, `new Date` as UTC midnight; numbers, `Date` objects and ISO strings with a time give the same result either way. An invalid date gives `"NaNy"` (`"0mo"` with `calendar`).
|
|
194
|
+
|
|
195
|
+
## Convex provider, VITE env and ensure-user (0.10.0)
|
|
196
|
+
|
|
197
|
+
React and VITE env helpers for Convex apps. The non-React Convex helpers (`mapLoadedQuery`, `forEachLoadedQuery`, `updateAllCachedQueries`, `patchLoadedQuery`, `OptimisticStore`, `tempId`, `createMemoryOptimisticStore`) live in `@vvedantb/server`, so backend packages, tests and React Native can use them without loading this package.
|
|
198
|
+
|
|
199
|
+
### `ConvexClerkProvider`
|
|
200
|
+
|
|
201
|
+
```tsx
|
|
202
|
+
import { ConvexClerkProvider, useStableAuth } from "@vvedantb/ui";
|
|
203
|
+
import { ConvexReactClient } from "convex/react";
|
|
204
|
+
|
|
205
|
+
const convex = new ConvexReactClient(env.VITE_CONVEX_URL);
|
|
206
|
+
|
|
207
|
+
<ClerkProvider publishableKey={env.VITE_CLERK_PUBLISHABLE_KEY}>
|
|
208
|
+
<ConvexClerkProvider client={convex} useAuth={useStableAuth}>
|
|
209
|
+
<App />
|
|
210
|
+
</ConvexClerkProvider>
|
|
211
|
+
</ClerkProvider>;
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
| Props | Renders |
|
|
215
|
+
| --- | --- |
|
|
216
|
+
| `client` is `null` or `undefined` | `children` alone (no provider). |
|
|
217
|
+
| `clerk={false}` | `<ConvexProvider client={client}>`. |
|
|
218
|
+
| otherwise | `<ConvexProviderWithClerk client={client} useAuth={useAuth}>`; `useAuth` defaults to `@clerk/clerk-react`'s. |
|
|
219
|
+
|
|
220
|
+
`clerk` defaults to `true`. Render it inside `ClerkProvider` unless `clerk` is `false`. Types: `ConvexClerkProviderProps`, `ConvexClerkUseAuth`.
|
|
221
|
+
|
|
222
|
+
### `parseClientEnv`
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
import { z } from "zod";
|
|
226
|
+
import { parseClientEnv } from "@vvedantb/ui";
|
|
227
|
+
|
|
228
|
+
export const clientEnv = parseClientEnv(import.meta.env, { z, viteEnv: "fallback" });
|
|
229
|
+
// { VITE_CONVEX_URL: string; VITE_CLERK_PUBLISHABLE_KEY: string; VITE_ENV: "development" | "staging" | "production" }
|
|
230
|
+
|
|
231
|
+
export const extensionEnv = parseClientEnv(import.meta.env, {
|
|
232
|
+
z,
|
|
233
|
+
viteEnv: false,
|
|
234
|
+
extend: { VITE_CLERK_SYNC_HOST: z.string().url() },
|
|
235
|
+
});
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
The schema is `z.object({ VITE_CONVEX_URL: z.string().min(1), VITE_CLERK_PUBLISHABLE_KEY: z.string().min(1), VITE_ENV: z.enum(["development", "staging", "production"]), ...extend })`, built with your `z` (zod 3.25+ or 4; the package never imports zod at runtime). Only the schema's keys are read from `rawEnv`. Failure throws the `ZodError` from `.parse`.
|
|
239
|
+
|
|
240
|
+
| `viteEnv` | `VITE_ENV` |
|
|
241
|
+
| --- | --- |
|
|
242
|
+
| `"strict"` (default) | Must be one of `VITE_ENV_NAMES`. |
|
|
243
|
+
| `"fallback"` | Any other value, or none, becomes `"development"` before parsing. |
|
|
244
|
+
| `false` | Not in the schema or the result. |
|
|
245
|
+
|
|
246
|
+
Types: `ClientEnv`, `ClientEnvWithoutViteEnv`, `ClientEnvOptions`, `ClientEnvSource`, `ViteEnvMode`, `ViteEnvName`.
|
|
247
|
+
|
|
248
|
+
### `useEnsureUser`
|
|
249
|
+
|
|
250
|
+
```tsx
|
|
251
|
+
function EnsureUser() {
|
|
252
|
+
useEnsureUser(api.users.ensureUserExists);
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Calls `mutation({})` every time `useConvexAuth().isAuthenticated` becomes `true` (once on mount if already signed in), and logs a rejection with `console.error`. Needs a Convex auth provider above it (`ConvexClerkProvider` with Clerk on). Pair it with `ensureUser` from `@vvedantb/server`. Type: `EnsureUserMutation`.
|
|
258
|
+
|
|
259
|
+
Tests: `app/tests/convex/` ports each app's copy and runs it beside the helper (env with zod 4 and zod 3.25; provider variants; ensure-user calls over auth sequences). `app/tests-node/convex-esm.test.mjs` loads the helpers in plain Node.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { useAuth as useClerkAuth } from "@clerk/clerk-react";
|
|
2
|
+
import { ConvexProvider, type ConvexReactClient } from "convex/react";
|
|
3
|
+
import { ConvexProviderWithClerk } from "convex/react-clerk";
|
|
4
|
+
import type { ComponentProps, ReactNode } from "react";
|
|
5
|
+
|
|
6
|
+
/** The `useAuth` hook `ConvexProviderWithClerk` takes (Clerk's, or a wrapper such as `useStableAuth`). */
|
|
7
|
+
export type ConvexClerkUseAuth = ComponentProps<typeof ConvexProviderWithClerk>["useAuth"];
|
|
8
|
+
|
|
9
|
+
export type ConvexClerkProviderProps = {
|
|
10
|
+
/** No client (for example no Convex URL configured): children render without a provider. */
|
|
11
|
+
client: ConvexReactClient | null | undefined;
|
|
12
|
+
/** Default: `useAuth` from `@clerk/clerk-react`. */
|
|
13
|
+
useAuth?: ConvexClerkUseAuth;
|
|
14
|
+
/** `false` renders a plain `ConvexProvider` (no Clerk auth). Default `true`. */
|
|
15
|
+
clerk?: boolean;
|
|
16
|
+
children: ReactNode;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The Convex provider for a Clerk app: `ConvexProviderWithClerk`, a plain
|
|
21
|
+
* `ConvexProvider` when `clerk` is `false`, or just the children when there is
|
|
22
|
+
* no client. Render it inside `ClerkProvider` unless `clerk` is `false`.
|
|
23
|
+
*/
|
|
24
|
+
export function ConvexClerkProvider({
|
|
25
|
+
client,
|
|
26
|
+
useAuth = useClerkAuth,
|
|
27
|
+
clerk = true,
|
|
28
|
+
children,
|
|
29
|
+
}: ConvexClerkProviderProps) {
|
|
30
|
+
if (!client) return children;
|
|
31
|
+
if (!clerk) return <ConvexProvider client={client}>{children}</ConvexProvider>;
|
|
32
|
+
return (
|
|
33
|
+
<ConvexProviderWithClerk client={client} useAuth={useAuth}>
|
|
34
|
+
{children}
|
|
35
|
+
</ConvexProviderWithClerk>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Type-only: the app passes its own `z`, so zod stays an optional peer and the
|
|
2
|
+
// thrown ZodError comes from the app's zod version (3.25+ or 4), as today.
|
|
3
|
+
import type { z } from "zod";
|
|
4
|
+
|
|
5
|
+
export type ViteEnvName = "development" | "staging" | "production";
|
|
6
|
+
|
|
7
|
+
export const VITE_ENV_NAMES: readonly ["development", "staging", "production"] = ["development", "staging", "production"];
|
|
8
|
+
|
|
9
|
+
/** `import.meta.env`, or any record of raw env values. */
|
|
10
|
+
export type ClientEnvSource = Readonly<Record<string, string | boolean | undefined>>;
|
|
11
|
+
|
|
12
|
+
type BaseShape = {
|
|
13
|
+
VITE_CONVEX_URL: z.ZodType<string>;
|
|
14
|
+
VITE_CLERK_PUBLISHABLE_KEY: z.ZodType<string>;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type ViteEnvShape = BaseShape & { VITE_ENV: z.ZodType<ViteEnvName> };
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* - `"strict"` (default): `VITE_ENV` must be one of `VITE_ENV_NAMES`.
|
|
21
|
+
* - `"fallback"`: any other value (or none) is read as `"development"`.
|
|
22
|
+
* - `false`: no `VITE_ENV` key.
|
|
23
|
+
*/
|
|
24
|
+
export type ViteEnvMode = "strict" | "fallback" | false;
|
|
25
|
+
|
|
26
|
+
export type ClientEnvOptions<Extra extends z.ZodRawShape> = {
|
|
27
|
+
/** The app's `z` from `"zod"`. */
|
|
28
|
+
z: typeof z;
|
|
29
|
+
viteEnv?: ViteEnvMode;
|
|
30
|
+
/** Extra keys, parsed after the base keys in the same `z.object`. A key here replaces a base key. */
|
|
31
|
+
extend?: Extra;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/** Parsed env with `VITE_ENV`. */
|
|
35
|
+
export type ClientEnv<Extra extends z.ZodRawShape = Record<never, never>> = z.output<z.ZodObject<ViteEnvShape & Extra>>;
|
|
36
|
+
|
|
37
|
+
/** Parsed env without `VITE_ENV` (`viteEnv: false`). */
|
|
38
|
+
export type ClientEnvWithoutViteEnv<Extra extends z.ZodRawShape = Record<never, never>> = z.output<
|
|
39
|
+
z.ZodObject<BaseShape & Extra>
|
|
40
|
+
>;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Parse VITE env with zod: `VITE_CONVEX_URL` and `VITE_CLERK_PUBLISHABLE_KEY`
|
|
44
|
+
* are `z.string().min(1)`, `VITE_ENV` is `z.enum(VITE_ENV_NAMES)`, then any
|
|
45
|
+
* `extend` keys. Only those keys are read from `rawEnv`. Throws the ZodError
|
|
46
|
+
* from `z.object(shape).parse`.
|
|
47
|
+
*/
|
|
48
|
+
export function parseClientEnv<Extra extends z.ZodRawShape = Record<never, never>>(
|
|
49
|
+
rawEnv: ClientEnvSource,
|
|
50
|
+
options: ClientEnvOptions<Extra> & { viteEnv: false },
|
|
51
|
+
): ClientEnvWithoutViteEnv<Extra>;
|
|
52
|
+
export function parseClientEnv<Extra extends z.ZodRawShape = Record<never, never>>(
|
|
53
|
+
rawEnv: ClientEnvSource,
|
|
54
|
+
options: ClientEnvOptions<Extra> & { viteEnv?: "strict" | "fallback" },
|
|
55
|
+
): ClientEnv<Extra>;
|
|
56
|
+
export function parseClientEnv(
|
|
57
|
+
rawEnv: ClientEnvSource,
|
|
58
|
+
options: ClientEnvOptions<z.ZodRawShape>,
|
|
59
|
+
): z.output<z.ZodObject<z.ZodRawShape>> {
|
|
60
|
+
const zod = options.z;
|
|
61
|
+
const viteEnv = options.viteEnv ?? "strict";
|
|
62
|
+
const shape: z.ZodRawShape = {
|
|
63
|
+
VITE_CONVEX_URL: zod.string().min(1),
|
|
64
|
+
VITE_CLERK_PUBLISHABLE_KEY: zod.string().min(1),
|
|
65
|
+
...(viteEnv === false ? {} : { VITE_ENV: zod.enum(VITE_ENV_NAMES) }),
|
|
66
|
+
...options.extend,
|
|
67
|
+
};
|
|
68
|
+
const input: Record<string, string | boolean | undefined> = {};
|
|
69
|
+
for (const key of Object.keys(shape)) input[key] = rawEnv[key];
|
|
70
|
+
if (viteEnv === "fallback") input.VITE_ENV = viteEnvOrDevelopment(rawEnv.VITE_ENV);
|
|
71
|
+
return zod.object(shape).parse(input);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function viteEnvOrDevelopment(value: string | boolean | undefined): ViteEnvName {
|
|
75
|
+
return value === "production" || value === "staging" || value === "development" ? value : "development";
|
|
76
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useConvexAuth, useMutation } from "convex/react";
|
|
2
|
+
import type { FunctionReference } from "convex/server";
|
|
3
|
+
import { useEffect } from "react";
|
|
4
|
+
|
|
5
|
+
/** A public mutation that takes no args, such as `api.users.ensureUserExists`. */
|
|
6
|
+
export type EnsureUserMutation = FunctionReference<"mutation", "public", Record<string, never>>;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Call `mutation({})` each time Convex auth becomes authenticated, logging a
|
|
10
|
+
* failure with `console.error`. Needs a Convex auth provider above it
|
|
11
|
+
* (`ConvexClerkProvider` with Clerk on). Kept as an effect: it syncs the
|
|
12
|
+
* server's users row with the sign-in state, and there is no render-time
|
|
13
|
+
* alternative.
|
|
14
|
+
*/
|
|
15
|
+
export function useEnsureUser(mutation: EnsureUserMutation): void {
|
|
16
|
+
const { isAuthenticated } = useConvexAuth();
|
|
17
|
+
const ensureUser = useMutation(mutation);
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
if (isAuthenticated) {
|
|
21
|
+
ensureUser({}).catch(console.error);
|
|
22
|
+
}
|
|
23
|
+
}, [isAuthenticated, ensureUser]);
|
|
24
|
+
}
|
package/app/src/index.ts
CHANGED
|
@@ -72,3 +72,20 @@ export {
|
|
|
72
72
|
formatCompactRelativeTime,
|
|
73
73
|
type CompactRelativeTimeOptions,
|
|
74
74
|
} from "./time/compactRelativeTime.js";
|
|
75
|
+
|
|
76
|
+
export {
|
|
77
|
+
ConvexClerkProvider,
|
|
78
|
+
type ConvexClerkProviderProps,
|
|
79
|
+
type ConvexClerkUseAuth,
|
|
80
|
+
} from "./convex/ConvexClerkProvider.js";
|
|
81
|
+
export {
|
|
82
|
+
parseClientEnv,
|
|
83
|
+
VITE_ENV_NAMES,
|
|
84
|
+
type ClientEnv,
|
|
85
|
+
type ClientEnvOptions,
|
|
86
|
+
type ClientEnvSource,
|
|
87
|
+
type ClientEnvWithoutViteEnv,
|
|
88
|
+
type ViteEnvMode,
|
|
89
|
+
type ViteEnvName,
|
|
90
|
+
} from "./convex/env.js";
|
|
91
|
+
export { useEnsureUser, type EnsureUserMutation } from "./convex/useEnsureUser.js";
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vvedantb/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"description": "Shared React 19 UI for web apps: one JavaScript entry (primitives, shell pieces, app helpers, native tokens), design-token CSS, shared Tailwind v4 CSS and a Tailwind preset.",
|
|
5
|
+
"description": "Shared React 19 UI for web apps: one JavaScript entry (primitives, shell pieces, app helpers including the Convex and Clerk provider, VITE env parsing and ensure-user, native tokens), design-token CSS, shared Tailwind v4 CSS and a Tailwind preset.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"publishConfig": {
|
|
8
8
|
"access": "public"
|
|
@@ -94,6 +94,7 @@
|
|
|
94
94
|
"class-variance-authority": "^0.7.1",
|
|
95
95
|
"clsx": "^2.1.1",
|
|
96
96
|
"cmdk": "^1.1.1",
|
|
97
|
+
"convex": "^1.24.0",
|
|
97
98
|
"embla-carousel-react": "^8.6.0",
|
|
98
99
|
"motion": ">=12.0.0",
|
|
99
100
|
"nanoid": "^5.1.0",
|
|
@@ -142,6 +143,7 @@
|
|
|
142
143
|
"class-variance-authority": "^0.7.1",
|
|
143
144
|
"clsx": "^2.1.1",
|
|
144
145
|
"cmdk": "^1.1.1",
|
|
146
|
+
"convex": "^1.47.0",
|
|
145
147
|
"dayjs": "1.11.19",
|
|
146
148
|
"embla-carousel-react": "^8.6.0",
|
|
147
149
|
"happy-dom": "^18.0.0",
|
|
@@ -160,12 +162,16 @@
|
|
|
160
162
|
"use-stick-to-bottom": "^1.1.0",
|
|
161
163
|
"vaul": "^1.1.0",
|
|
162
164
|
"vitest": "^3.2.0",
|
|
163
|
-
"zod": "^4.0.0"
|
|
165
|
+
"zod": "^4.0.0",
|
|
166
|
+
"zod3": "npm:zod@^3.25.76"
|
|
164
167
|
},
|
|
165
168
|
"peerDependenciesMeta": {
|
|
166
169
|
"@clerk/clerk-react": {
|
|
167
170
|
"optional": true
|
|
168
171
|
},
|
|
172
|
+
"convex": {
|
|
173
|
+
"optional": true
|
|
174
|
+
},
|
|
169
175
|
"@tanstack/react-hotkeys": {
|
|
170
176
|
"optional": true
|
|
171
177
|
},
|
package/src/index.ts
CHANGED
|
@@ -222,7 +222,7 @@ export type { SiteFooterLink, SiteFooterProps, SiteFooterSocial } from "./market
|
|
|
222
222
|
export { PricingModal } from "./marketing/PricingModal";
|
|
223
223
|
export type { PricingModalProps } from "./marketing/PricingModal";
|
|
224
224
|
|
|
225
|
-
// App helpers: confirm skip, update watcher, settings chrome, stable auth.
|
|
225
|
+
// App helpers: confirm skip, update watcher, settings chrome, stable auth, Convex provider, VITE env and ensure-user.
|
|
226
226
|
export * from "../app/src/index";
|
|
227
227
|
|
|
228
228
|
// Native tokens (JS values that match tokens.css).
|