@poly-x/react 0.1.0-alpha.1 → 0.1.0-alpha.3

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/dist/index.cjs CHANGED
@@ -352,14 +352,182 @@ function Protected({ children, fallback = null, loading = null }) {
352
352
  }
353
353
  //#endregion
354
354
  //#region src/components/sign-in.tsx
355
- /** Minimal sign-in trigger — popup by default. Style via `className`/children. */
356
- function SignIn({ children, className, ...options }) {
357
- const { signIn } = useAuth();
358
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
359
- type: "button",
360
- className,
361
- onClick: () => void signIn(options),
362
- children: children ?? "Sign in"
355
+ const DEFAULT_LABELS = {
356
+ heading: "Sign in",
357
+ subheading: "Enter your credentials to continue.",
358
+ emailLabel: "Email",
359
+ emailPlaceholder: "you@company.com",
360
+ passwordLabel: "Password",
361
+ passwordPlaceholder: "••••••••",
362
+ submit: "Sign in",
363
+ submitting: "Signing in…",
364
+ chooseWorkspace: "Choose a workspace",
365
+ chooseWorkspaceSubheading: "You have access to more than one workspace.",
366
+ invalidCredentials: "Invalid email or password.",
367
+ noAccess: "You don't have access to this application.",
368
+ passwordChangeRequired: "You must reset your password before you can sign in.",
369
+ genericError: "Something went wrong. Please try again."
370
+ };
371
+ /**
372
+ * The embedded, Clerk-style sign-in form (D2b). Renders email/password inside the
373
+ * consuming app and POSTs to the SDK's BFF (`@poly-x/next` `authenticate`), which
374
+ * runs the credential authorize server-side and seals an httpOnly session — no
375
+ * redirect to a hosted PolyX login page, no tokens or credentials in the browser
376
+ * after submit. Handles the workspace picker (select_tenant) inline.
377
+ *
378
+ * Ships styled: `import "@poly-x/react/styles.css"` once, then theme it by
379
+ * overriding the `--polyx-*` custom properties on any ancestor.
380
+ */
381
+ function SignIn(props) {
382
+ const action = props.action ?? "/api/polyx/authenticate";
383
+ const variant = props.variant ?? "card";
384
+ const labels = {
385
+ ...DEFAULT_LABELS,
386
+ ...props.labels
387
+ };
388
+ const [email, setEmail] = (0, react.useState)("");
389
+ const [password, setPassword] = (0, react.useState)("");
390
+ const [tenants, setTenants] = (0, react.useState)(null);
391
+ const [submitting, setSubmitting] = (0, react.useState)(false);
392
+ const [error, setError] = (0, react.useState)(null);
393
+ async function authenticate(tenantId) {
394
+ setSubmitting(true);
395
+ setError(null);
396
+ try {
397
+ const body = await (await fetch(action, {
398
+ method: "POST",
399
+ headers: { "content-type": "application/json" },
400
+ body: JSON.stringify({
401
+ email,
402
+ password,
403
+ organizationCode: props.organizationCode,
404
+ tenantId
405
+ })
406
+ })).json().catch(() => ({}));
407
+ switch (body.status) {
408
+ case "success":
409
+ window.location.assign(props.afterSignInPath ?? body.redirectTo ?? "/");
410
+ return;
411
+ case "select_tenant":
412
+ setTenants(body.tenants ?? []);
413
+ return;
414
+ case "invalid_credentials":
415
+ setError(labels.invalidCredentials);
416
+ return;
417
+ case "no_access":
418
+ setError(labels.noAccess);
419
+ return;
420
+ case "password_change_required":
421
+ setError(labels.passwordChangeRequired);
422
+ if (body.userId) props.onPasswordChangeRequired?.(body.userId);
423
+ return;
424
+ default:
425
+ setError(labels.genericError);
426
+ return;
427
+ }
428
+ } catch {
429
+ setError(labels.genericError);
430
+ } finally {
431
+ setSubmitting(false);
432
+ }
433
+ }
434
+ function onSubmit(event) {
435
+ event.preventDefault();
436
+ authenticate();
437
+ }
438
+ const rootClass = [
439
+ "polyx-signin",
440
+ `polyx-signin--${variant}`,
441
+ props.className
442
+ ].filter(Boolean).join(" ");
443
+ const picking = tenants !== null;
444
+ const header = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
445
+ className: "polyx-signin__header",
446
+ children: [
447
+ props.logo ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
448
+ className: "polyx-signin__logo",
449
+ children: props.logo
450
+ }) : null,
451
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", {
452
+ className: "polyx-signin__heading",
453
+ children: picking ? labels.chooseWorkspace : labels.heading
454
+ }),
455
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
456
+ className: "polyx-signin__subheading",
457
+ children: picking ? labels.chooseWorkspaceSubheading : labels.subheading
458
+ })
459
+ ]
460
+ });
461
+ const errorNode = error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
462
+ role: "alert",
463
+ className: "polyx-signin__error",
464
+ children: error
465
+ }) : null;
466
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
467
+ className: rootClass,
468
+ "data-polyx-signin": picking ? "select-tenant" : "credentials",
469
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
470
+ className: "polyx-signin__card",
471
+ children: [header, picking ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
472
+ className: "polyx-signin__form",
473
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
474
+ className: "polyx-signin__tenants",
475
+ children: tenants.map((tenant) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
476
+ type: "button",
477
+ className: "polyx-signin__tenant",
478
+ disabled: submitting,
479
+ onClick: () => void authenticate(tenant.tenantId),
480
+ children: tenant.name ?? tenant.slug ?? tenant.organizationCode ?? tenant.tenantId
481
+ }) }, tenant.tenantId))
482
+ }), errorNode]
483
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
484
+ className: "polyx-signin__form",
485
+ onSubmit,
486
+ children: [
487
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
488
+ className: "polyx-signin__field",
489
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
490
+ className: "polyx-signin__label",
491
+ children: labels.emailLabel
492
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
493
+ className: "polyx-signin__input",
494
+ type: "email",
495
+ name: "email",
496
+ autoComplete: "username",
497
+ placeholder: labels.emailPlaceholder,
498
+ required: true,
499
+ value: email,
500
+ disabled: submitting,
501
+ onChange: (event) => setEmail(event.target.value)
502
+ })]
503
+ }),
504
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
505
+ className: "polyx-signin__field",
506
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
507
+ className: "polyx-signin__label",
508
+ children: labels.passwordLabel
509
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
510
+ className: "polyx-signin__input",
511
+ type: "password",
512
+ name: "password",
513
+ autoComplete: "current-password",
514
+ placeholder: labels.passwordPlaceholder,
515
+ required: true,
516
+ value: password,
517
+ disabled: submitting,
518
+ onChange: (event) => setPassword(event.target.value)
519
+ })]
520
+ }),
521
+ errorNode,
522
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
523
+ type: "submit",
524
+ className: "polyx-signin__submit",
525
+ disabled: submitting,
526
+ children: submitting ? labels.submitting : labels.submit
527
+ })
528
+ ]
529
+ })]
530
+ })
363
531
  });
364
532
  }
365
533
  //#endregion
package/dist/index.d.cts CHANGED
@@ -191,16 +191,56 @@ declare function Protected({
191
191
  }: ProtectedProps): ReactNode;
192
192
  //#endregion
193
193
  //#region src/components/sign-in.d.ts
194
- interface SignInProps extends SignInOptions {
195
- children?: ReactNode;
194
+ /** Every user-facing string, overridable for i18n / white-labelling. */
195
+ interface SignInLabels {
196
+ heading: string;
197
+ subheading: string;
198
+ emailLabel: string;
199
+ emailPlaceholder: string;
200
+ passwordLabel: string;
201
+ passwordPlaceholder: string;
202
+ submit: string;
203
+ submitting: string;
204
+ chooseWorkspace: string;
205
+ chooseWorkspaceSubheading: string;
206
+ invalidCredentials: string;
207
+ noAccess: string;
208
+ passwordChangeRequired: string;
209
+ genericError: string;
210
+ }
211
+ /**
212
+ * `card` renders just the card — drop it into your own layout.
213
+ * `page` owns the viewport — a centered card on a full-bleed background.
214
+ */
215
+ type SignInVariant = "card" | "page";
216
+ interface SignInProps {
217
+ /** Layout preset. Default `card`. */
218
+ variant?: SignInVariant;
219
+ /** BFF endpoint backed by `createAuthHandlers().authenticate`. Default `/api/polyx/authenticate`. */
220
+ action?: string;
221
+ /** Where to send the browser after success. Overrides the server's default redirect. */
222
+ afterSignInPath?: string;
223
+ /** Pre-scope the login to a single organization (skips org discovery). */
224
+ organizationCode?: string;
225
+ /** Rendered above the heading — your wordmark or logo. */
226
+ logo?: ReactNode;
227
+ /** Appended to the root element's class list, for escape-hatch styling. */
196
228
  className?: string;
229
+ labels?: Partial<SignInLabels>;
230
+ /** Fired when the backend requires the user to set a new password before continuing. */
231
+ onPasswordChangeRequired?: (userId: string) => void;
197
232
  }
198
- /** Minimal sign-in trigger — popup by default. Style via `className`/children. */
199
- declare function SignIn({
200
- children,
201
- className,
202
- ...options
203
- }: SignInProps): ReactNode;
233
+ /**
234
+ * The embedded, Clerk-style sign-in form (D2b). Renders email/password inside the
235
+ * consuming app and POSTs to the SDK's BFF (`@poly-x/next` `authenticate`), which
236
+ * runs the credential authorize server-side and seals an httpOnly session — no
237
+ * redirect to a hosted PolyX login page, no tokens or credentials in the browser
238
+ * after submit. Handles the workspace picker (select_tenant) inline.
239
+ *
240
+ * Ships styled: `import "@poly-x/react/styles.css"` once, then theme it by
241
+ * overriding the `--polyx-*` custom properties on any ancestor.
242
+ */
243
+ declare function SignIn(props: SignInProps): ReactNode;
204
244
  //#endregion
205
245
  //#region src/components/auth-callback.d.ts
206
246
  interface AuthCallbackProps {
@@ -220,4 +260,4 @@ declare function AuthCallback({
220
260
  declare const PACKAGE_NAME = "@poly-x/react";
221
261
  declare const version = "0.0.0";
222
262
  //#endregion
223
- export { AuthCallback, type AuthCallbackMessage, type AuthCallbackProps, type AuthChannel, BroadcastAuthChannel, BroadcastChannelSessionChannel, type BrowserBridge, type CallbackParams, type LoginController, type LoginControllerDeps, PACKAGE_NAME, PolyXContext, type PolyXContextValue, PolyXProvider, type PolyXProviderProps, PopupCancelledError, type PopupHandle, Protected, type ProtectedProps, SessionStoragePkceStore, SignIn, type SignInOptions, type SignInProps, type UseAuth, WebLocksLock, WindowBrowserBridge, createLoginController, useAuth, useAuthCallback, useSession, useUser, version };
263
+ export { AuthCallback, type AuthCallbackMessage, type AuthCallbackProps, type AuthChannel, BroadcastAuthChannel, BroadcastChannelSessionChannel, type BrowserBridge, type CallbackParams, type LoginController, type LoginControllerDeps, PACKAGE_NAME, PolyXContext, type PolyXContextValue, PolyXProvider, type PolyXProviderProps, PopupCancelledError, type PopupHandle, Protected, type ProtectedProps, SessionStoragePkceStore, SignIn, type SignInLabels, type SignInOptions, type SignInProps, type SignInVariant, type UseAuth, WebLocksLock, WindowBrowserBridge, createLoginController, useAuth, useAuthCallback, useSession, useUser, version };
package/dist/index.d.mts CHANGED
@@ -191,16 +191,56 @@ declare function Protected({
191
191
  }: ProtectedProps): ReactNode;
192
192
  //#endregion
193
193
  //#region src/components/sign-in.d.ts
194
- interface SignInProps extends SignInOptions {
195
- children?: ReactNode;
194
+ /** Every user-facing string, overridable for i18n / white-labelling. */
195
+ interface SignInLabels {
196
+ heading: string;
197
+ subheading: string;
198
+ emailLabel: string;
199
+ emailPlaceholder: string;
200
+ passwordLabel: string;
201
+ passwordPlaceholder: string;
202
+ submit: string;
203
+ submitting: string;
204
+ chooseWorkspace: string;
205
+ chooseWorkspaceSubheading: string;
206
+ invalidCredentials: string;
207
+ noAccess: string;
208
+ passwordChangeRequired: string;
209
+ genericError: string;
210
+ }
211
+ /**
212
+ * `card` renders just the card — drop it into your own layout.
213
+ * `page` owns the viewport — a centered card on a full-bleed background.
214
+ */
215
+ type SignInVariant = "card" | "page";
216
+ interface SignInProps {
217
+ /** Layout preset. Default `card`. */
218
+ variant?: SignInVariant;
219
+ /** BFF endpoint backed by `createAuthHandlers().authenticate`. Default `/api/polyx/authenticate`. */
220
+ action?: string;
221
+ /** Where to send the browser after success. Overrides the server's default redirect. */
222
+ afterSignInPath?: string;
223
+ /** Pre-scope the login to a single organization (skips org discovery). */
224
+ organizationCode?: string;
225
+ /** Rendered above the heading — your wordmark or logo. */
226
+ logo?: ReactNode;
227
+ /** Appended to the root element's class list, for escape-hatch styling. */
196
228
  className?: string;
229
+ labels?: Partial<SignInLabels>;
230
+ /** Fired when the backend requires the user to set a new password before continuing. */
231
+ onPasswordChangeRequired?: (userId: string) => void;
197
232
  }
198
- /** Minimal sign-in trigger — popup by default. Style via `className`/children. */
199
- declare function SignIn({
200
- children,
201
- className,
202
- ...options
203
- }: SignInProps): ReactNode;
233
+ /**
234
+ * The embedded, Clerk-style sign-in form (D2b). Renders email/password inside the
235
+ * consuming app and POSTs to the SDK's BFF (`@poly-x/next` `authenticate`), which
236
+ * runs the credential authorize server-side and seals an httpOnly session — no
237
+ * redirect to a hosted PolyX login page, no tokens or credentials in the browser
238
+ * after submit. Handles the workspace picker (select_tenant) inline.
239
+ *
240
+ * Ships styled: `import "@poly-x/react/styles.css"` once, then theme it by
241
+ * overriding the `--polyx-*` custom properties on any ancestor.
242
+ */
243
+ declare function SignIn(props: SignInProps): ReactNode;
204
244
  //#endregion
205
245
  //#region src/components/auth-callback.d.ts
206
246
  interface AuthCallbackProps {
@@ -220,4 +260,4 @@ declare function AuthCallback({
220
260
  declare const PACKAGE_NAME = "@poly-x/react";
221
261
  declare const version = "0.0.0";
222
262
  //#endregion
223
- export { AuthCallback, type AuthCallbackMessage, type AuthCallbackProps, type AuthChannel, BroadcastAuthChannel, BroadcastChannelSessionChannel, type BrowserBridge, type CallbackParams, type LoginController, type LoginControllerDeps, PACKAGE_NAME, PolyXContext, type PolyXContextValue, PolyXProvider, type PolyXProviderProps, PopupCancelledError, type PopupHandle, Protected, type ProtectedProps, SessionStoragePkceStore, SignIn, type SignInOptions, type SignInProps, type UseAuth, WebLocksLock, WindowBrowserBridge, createLoginController, useAuth, useAuthCallback, useSession, useUser, version };
263
+ export { AuthCallback, type AuthCallbackMessage, type AuthCallbackProps, type AuthChannel, BroadcastAuthChannel, BroadcastChannelSessionChannel, type BrowserBridge, type CallbackParams, type LoginController, type LoginControllerDeps, PACKAGE_NAME, PolyXContext, type PolyXContextValue, PolyXProvider, type PolyXProviderProps, PopupCancelledError, type PopupHandle, Protected, type ProtectedProps, SessionStoragePkceStore, SignIn, type SignInLabels, type SignInOptions, type SignInProps, type SignInVariant, type UseAuth, WebLocksLock, WindowBrowserBridge, createLoginController, useAuth, useAuthCallback, useSession, useUser, version };
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { AuthClient, FetchTransport, InMemoryLock, InMemoryPkceStore, InMemorySessionChannel, InMemorySessionStore, createSessionEngine, generatePkce, randomState, resolveConfig } from "@poly-x/core";
3
- import { createContext, useCallback, useContext, useEffect, useMemo, useSyncExternalStore } from "react";
4
- import { jsx } from "react/jsx-runtime";
3
+ import { createContext, useCallback, useContext, useEffect, useMemo, useState, useSyncExternalStore } from "react";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
5
  //#region src/auth-channel.ts
6
6
  var BroadcastAuthChannel = class {
7
7
  supported;
@@ -351,14 +351,182 @@ function Protected({ children, fallback = null, loading = null }) {
351
351
  }
352
352
  //#endregion
353
353
  //#region src/components/sign-in.tsx
354
- /** Minimal sign-in trigger — popup by default. Style via `className`/children. */
355
- function SignIn({ children, className, ...options }) {
356
- const { signIn } = useAuth();
357
- return /* @__PURE__ */ jsx("button", {
358
- type: "button",
359
- className,
360
- onClick: () => void signIn(options),
361
- children: children ?? "Sign in"
354
+ const DEFAULT_LABELS = {
355
+ heading: "Sign in",
356
+ subheading: "Enter your credentials to continue.",
357
+ emailLabel: "Email",
358
+ emailPlaceholder: "you@company.com",
359
+ passwordLabel: "Password",
360
+ passwordPlaceholder: "••••••••",
361
+ submit: "Sign in",
362
+ submitting: "Signing in…",
363
+ chooseWorkspace: "Choose a workspace",
364
+ chooseWorkspaceSubheading: "You have access to more than one workspace.",
365
+ invalidCredentials: "Invalid email or password.",
366
+ noAccess: "You don't have access to this application.",
367
+ passwordChangeRequired: "You must reset your password before you can sign in.",
368
+ genericError: "Something went wrong. Please try again."
369
+ };
370
+ /**
371
+ * The embedded, Clerk-style sign-in form (D2b). Renders email/password inside the
372
+ * consuming app and POSTs to the SDK's BFF (`@poly-x/next` `authenticate`), which
373
+ * runs the credential authorize server-side and seals an httpOnly session — no
374
+ * redirect to a hosted PolyX login page, no tokens or credentials in the browser
375
+ * after submit. Handles the workspace picker (select_tenant) inline.
376
+ *
377
+ * Ships styled: `import "@poly-x/react/styles.css"` once, then theme it by
378
+ * overriding the `--polyx-*` custom properties on any ancestor.
379
+ */
380
+ function SignIn(props) {
381
+ const action = props.action ?? "/api/polyx/authenticate";
382
+ const variant = props.variant ?? "card";
383
+ const labels = {
384
+ ...DEFAULT_LABELS,
385
+ ...props.labels
386
+ };
387
+ const [email, setEmail] = useState("");
388
+ const [password, setPassword] = useState("");
389
+ const [tenants, setTenants] = useState(null);
390
+ const [submitting, setSubmitting] = useState(false);
391
+ const [error, setError] = useState(null);
392
+ async function authenticate(tenantId) {
393
+ setSubmitting(true);
394
+ setError(null);
395
+ try {
396
+ const body = await (await fetch(action, {
397
+ method: "POST",
398
+ headers: { "content-type": "application/json" },
399
+ body: JSON.stringify({
400
+ email,
401
+ password,
402
+ organizationCode: props.organizationCode,
403
+ tenantId
404
+ })
405
+ })).json().catch(() => ({}));
406
+ switch (body.status) {
407
+ case "success":
408
+ window.location.assign(props.afterSignInPath ?? body.redirectTo ?? "/");
409
+ return;
410
+ case "select_tenant":
411
+ setTenants(body.tenants ?? []);
412
+ return;
413
+ case "invalid_credentials":
414
+ setError(labels.invalidCredentials);
415
+ return;
416
+ case "no_access":
417
+ setError(labels.noAccess);
418
+ return;
419
+ case "password_change_required":
420
+ setError(labels.passwordChangeRequired);
421
+ if (body.userId) props.onPasswordChangeRequired?.(body.userId);
422
+ return;
423
+ default:
424
+ setError(labels.genericError);
425
+ return;
426
+ }
427
+ } catch {
428
+ setError(labels.genericError);
429
+ } finally {
430
+ setSubmitting(false);
431
+ }
432
+ }
433
+ function onSubmit(event) {
434
+ event.preventDefault();
435
+ authenticate();
436
+ }
437
+ const rootClass = [
438
+ "polyx-signin",
439
+ `polyx-signin--${variant}`,
440
+ props.className
441
+ ].filter(Boolean).join(" ");
442
+ const picking = tenants !== null;
443
+ const header = /* @__PURE__ */ jsxs("div", {
444
+ className: "polyx-signin__header",
445
+ children: [
446
+ props.logo ? /* @__PURE__ */ jsx("div", {
447
+ className: "polyx-signin__logo",
448
+ children: props.logo
449
+ }) : null,
450
+ /* @__PURE__ */ jsx("h1", {
451
+ className: "polyx-signin__heading",
452
+ children: picking ? labels.chooseWorkspace : labels.heading
453
+ }),
454
+ /* @__PURE__ */ jsx("p", {
455
+ className: "polyx-signin__subheading",
456
+ children: picking ? labels.chooseWorkspaceSubheading : labels.subheading
457
+ })
458
+ ]
459
+ });
460
+ const errorNode = error ? /* @__PURE__ */ jsx("p", {
461
+ role: "alert",
462
+ className: "polyx-signin__error",
463
+ children: error
464
+ }) : null;
465
+ return /* @__PURE__ */ jsx("div", {
466
+ className: rootClass,
467
+ "data-polyx-signin": picking ? "select-tenant" : "credentials",
468
+ children: /* @__PURE__ */ jsxs("div", {
469
+ className: "polyx-signin__card",
470
+ children: [header, picking ? /* @__PURE__ */ jsxs("div", {
471
+ className: "polyx-signin__form",
472
+ children: [/* @__PURE__ */ jsx("ul", {
473
+ className: "polyx-signin__tenants",
474
+ children: tenants.map((tenant) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("button", {
475
+ type: "button",
476
+ className: "polyx-signin__tenant",
477
+ disabled: submitting,
478
+ onClick: () => void authenticate(tenant.tenantId),
479
+ children: tenant.name ?? tenant.slug ?? tenant.organizationCode ?? tenant.tenantId
480
+ }) }, tenant.tenantId))
481
+ }), errorNode]
482
+ }) : /* @__PURE__ */ jsxs("form", {
483
+ className: "polyx-signin__form",
484
+ onSubmit,
485
+ children: [
486
+ /* @__PURE__ */ jsxs("label", {
487
+ className: "polyx-signin__field",
488
+ children: [/* @__PURE__ */ jsx("span", {
489
+ className: "polyx-signin__label",
490
+ children: labels.emailLabel
491
+ }), /* @__PURE__ */ jsx("input", {
492
+ className: "polyx-signin__input",
493
+ type: "email",
494
+ name: "email",
495
+ autoComplete: "username",
496
+ placeholder: labels.emailPlaceholder,
497
+ required: true,
498
+ value: email,
499
+ disabled: submitting,
500
+ onChange: (event) => setEmail(event.target.value)
501
+ })]
502
+ }),
503
+ /* @__PURE__ */ jsxs("label", {
504
+ className: "polyx-signin__field",
505
+ children: [/* @__PURE__ */ jsx("span", {
506
+ className: "polyx-signin__label",
507
+ children: labels.passwordLabel
508
+ }), /* @__PURE__ */ jsx("input", {
509
+ className: "polyx-signin__input",
510
+ type: "password",
511
+ name: "password",
512
+ autoComplete: "current-password",
513
+ placeholder: labels.passwordPlaceholder,
514
+ required: true,
515
+ value: password,
516
+ disabled: submitting,
517
+ onChange: (event) => setPassword(event.target.value)
518
+ })]
519
+ }),
520
+ errorNode,
521
+ /* @__PURE__ */ jsx("button", {
522
+ type: "submit",
523
+ className: "polyx-signin__submit",
524
+ disabled: submitting,
525
+ children: submitting ? labels.submitting : labels.submit
526
+ })
527
+ ]
528
+ })]
529
+ })
362
530
  });
363
531
  }
364
532
  //#endregion
@@ -0,0 +1,274 @@
1
+ /**
2
+ * @poly-x/react — default component styles.
3
+ *
4
+ * Import once, near the root of your app:
5
+ * import "@poly-x/react/styles.css"; // or "@poly-x/next/styles.css"
6
+ *
7
+ * The SDK is a guest in your app: every rule is scoped under a `.polyx-*` class,
8
+ * there are no global element selectors, and nothing here leaks into your own
9
+ * markup. To theme it, override the custom properties below on any ancestor
10
+ * (`:root`, a wrapper div, …) — that is the supported theming surface. Dark mode
11
+ * follows the OS by default and defers to an explicit `.dark` / `[data-theme]`
12
+ * on a host ancestor when one is present.
13
+ */
14
+
15
+ .polyx-signin {
16
+ /* Colors */
17
+ --polyx-brand: #4f46e5;
18
+ --polyx-brand-hover: #4338ca;
19
+ --polyx-brand-fg: #ffffff;
20
+ --polyx-bg: #ffffff;
21
+ --polyx-fg: #18181b;
22
+ --polyx-muted-fg: #71717a;
23
+ --polyx-border: #e4e4e7;
24
+ --polyx-input-bg: #ffffff;
25
+ --polyx-ring: #a5b4fc;
26
+ --polyx-danger: #dc2626;
27
+ --polyx-danger-bg: #fef2f2;
28
+ --polyx-page-bg: #fafafa;
29
+
30
+ /* Shape + type */
31
+ --polyx-radius: 0.75rem;
32
+ --polyx-radius-sm: 0.5rem;
33
+ --polyx-font: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
34
+ --polyx-shadow: 0 1px 3px rgb(0 0 0 / 0.06), 0 10px 30px rgb(0 0 0 / 0.07);
35
+
36
+ font-family: var(--polyx-font);
37
+ color: var(--polyx-fg);
38
+ -webkit-font-smoothing: antialiased;
39
+ }
40
+
41
+ /* Scoped box model — applies only inside our own subtree. */
42
+ .polyx-signin,
43
+ .polyx-signin *,
44
+ .polyx-signin *::before,
45
+ .polyx-signin *::after {
46
+ box-sizing: border-box;
47
+ }
48
+
49
+ /* Dark: follow the OS… */
50
+ @media (prefers-color-scheme: dark) {
51
+ .polyx-signin {
52
+ --polyx-brand: #6366f1;
53
+ --polyx-brand-hover: #818cf8;
54
+ --polyx-bg: #18181b;
55
+ --polyx-fg: #fafafa;
56
+ --polyx-muted-fg: #a1a1aa;
57
+ --polyx-border: #3f3f46;
58
+ --polyx-input-bg: #27272a;
59
+ --polyx-ring: #4f46e5;
60
+ --polyx-danger: #f87171;
61
+ --polyx-danger-bg: #2a1616;
62
+ --polyx-page-bg: #09090b;
63
+ --polyx-shadow: 0 1px 3px rgb(0 0 0 / 0.4), 0 10px 30px rgb(0 0 0 / 0.35);
64
+ }
65
+ }
66
+
67
+ /* …but an explicit host theme wins over the OS preference (`:where` keeps
68
+ specificity at 0 so these stay as easy to override as the base rule). */
69
+ :where(.dark, [data-theme="dark"]) .polyx-signin {
70
+ --polyx-brand: #6366f1;
71
+ --polyx-brand-hover: #818cf8;
72
+ --polyx-bg: #18181b;
73
+ --polyx-fg: #fafafa;
74
+ --polyx-muted-fg: #a1a1aa;
75
+ --polyx-border: #3f3f46;
76
+ --polyx-input-bg: #27272a;
77
+ --polyx-ring: #4f46e5;
78
+ --polyx-danger: #f87171;
79
+ --polyx-danger-bg: #2a1616;
80
+ --polyx-page-bg: #09090b;
81
+ --polyx-shadow: 0 1px 3px rgb(0 0 0 / 0.4), 0 10px 30px rgb(0 0 0 / 0.35);
82
+ }
83
+
84
+ :where(.light, [data-theme="light"]) .polyx-signin {
85
+ --polyx-brand: #4f46e5;
86
+ --polyx-brand-hover: #4338ca;
87
+ --polyx-bg: #ffffff;
88
+ --polyx-fg: #18181b;
89
+ --polyx-muted-fg: #71717a;
90
+ --polyx-border: #e4e4e7;
91
+ --polyx-input-bg: #ffffff;
92
+ --polyx-ring: #a5b4fc;
93
+ --polyx-danger: #dc2626;
94
+ --polyx-danger-bg: #fef2f2;
95
+ --polyx-page-bg: #fafafa;
96
+ --polyx-shadow: 0 1px 3px rgb(0 0 0 / 0.06), 0 10px 30px rgb(0 0 0 / 0.07);
97
+ }
98
+
99
+ /* --- Variants ------------------------------------------------------------ */
100
+
101
+ /* `page`: owns the viewport — centered card on a full-bleed background. */
102
+ .polyx-signin--page {
103
+ display: grid;
104
+ place-items: center;
105
+ min-height: 100dvh;
106
+ padding: 1.5rem;
107
+ background: var(--polyx-page-bg);
108
+ }
109
+
110
+ /* `card`: just the card — drop it anywhere in your own layout. */
111
+ .polyx-signin--card {
112
+ display: block;
113
+ }
114
+
115
+ .polyx-signin__card {
116
+ width: 100%;
117
+ max-width: 24rem;
118
+ margin-inline: auto;
119
+ padding: 2rem;
120
+ background: var(--polyx-bg);
121
+ border: 1px solid var(--polyx-border);
122
+ border-radius: var(--polyx-radius);
123
+ box-shadow: var(--polyx-shadow);
124
+ }
125
+
126
+ /* --- Header -------------------------------------------------------------- */
127
+
128
+ .polyx-signin__header {
129
+ display: flex;
130
+ flex-direction: column;
131
+ gap: 0.375rem;
132
+ margin-bottom: 1.75rem;
133
+ }
134
+
135
+ .polyx-signin__logo {
136
+ display: flex;
137
+ margin-bottom: 0.75rem;
138
+ }
139
+
140
+ .polyx-signin__heading {
141
+ margin: 0;
142
+ font-size: 1.375rem;
143
+ font-weight: 600;
144
+ letter-spacing: -0.01em;
145
+ color: var(--polyx-fg);
146
+ }
147
+
148
+ .polyx-signin__subheading {
149
+ margin: 0;
150
+ font-size: 0.875rem;
151
+ color: var(--polyx-muted-fg);
152
+ }
153
+
154
+ /* --- Form ---------------------------------------------------------------- */
155
+
156
+ .polyx-signin__form {
157
+ display: flex;
158
+ flex-direction: column;
159
+ gap: 1.125rem;
160
+ }
161
+
162
+ .polyx-signin__field {
163
+ display: flex;
164
+ flex-direction: column;
165
+ gap: 0.4375rem;
166
+ }
167
+
168
+ .polyx-signin__label {
169
+ font-size: 0.875rem;
170
+ font-weight: 500;
171
+ color: var(--polyx-fg);
172
+ }
173
+
174
+ .polyx-signin__input {
175
+ width: 100%;
176
+ height: 2.5rem;
177
+ padding: 0 0.75rem;
178
+ font: inherit;
179
+ font-size: 0.875rem;
180
+ color: var(--polyx-fg);
181
+ background: var(--polyx-input-bg);
182
+ border: 1px solid var(--polyx-border);
183
+ border-radius: var(--polyx-radius-sm);
184
+ outline: none;
185
+ transition: border-color 0.15s ease, box-shadow 0.15s ease;
186
+ }
187
+
188
+ .polyx-signin__input::placeholder {
189
+ color: var(--polyx-muted-fg);
190
+ }
191
+
192
+ .polyx-signin__input:focus-visible {
193
+ border-color: var(--polyx-brand);
194
+ box-shadow: 0 0 0 3px var(--polyx-ring);
195
+ }
196
+
197
+ .polyx-signin__input:disabled {
198
+ opacity: 0.6;
199
+ cursor: not-allowed;
200
+ }
201
+
202
+ /* --- Buttons ------------------------------------------------------------- */
203
+
204
+ .polyx-signin__submit,
205
+ .polyx-signin__tenant {
206
+ display: inline-flex;
207
+ align-items: center;
208
+ justify-content: center;
209
+ width: 100%;
210
+ min-height: 2.5rem;
211
+ padding: 0 1rem;
212
+ font: inherit;
213
+ font-size: 0.875rem;
214
+ font-weight: 500;
215
+ color: var(--polyx-brand-fg);
216
+ background: var(--polyx-brand);
217
+ border: 1px solid transparent;
218
+ border-radius: var(--polyx-radius-sm);
219
+ cursor: pointer;
220
+ transition: background-color 0.15s ease;
221
+ }
222
+
223
+ .polyx-signin__submit:hover:not(:disabled),
224
+ .polyx-signin__tenant:hover:not(:disabled) {
225
+ background: var(--polyx-brand-hover);
226
+ }
227
+
228
+ .polyx-signin__submit:focus-visible,
229
+ .polyx-signin__tenant:focus-visible {
230
+ outline: none;
231
+ box-shadow: 0 0 0 3px var(--polyx-ring);
232
+ }
233
+
234
+ .polyx-signin__submit:disabled,
235
+ .polyx-signin__tenant:disabled {
236
+ opacity: 0.6;
237
+ cursor: not-allowed;
238
+ }
239
+
240
+ /* --- Error --------------------------------------------------------------- */
241
+
242
+ .polyx-signin__error {
243
+ margin: 0;
244
+ padding: 0.625rem 0.75rem;
245
+ font-size: 0.8125rem;
246
+ color: var(--polyx-danger);
247
+ background: var(--polyx-danger-bg);
248
+ border: 1px solid color-mix(in srgb, var(--polyx-danger) 30%, transparent);
249
+ border-radius: var(--polyx-radius-sm);
250
+ }
251
+
252
+ /* --- Workspace picker ---------------------------------------------------- */
253
+
254
+ .polyx-signin__tenants {
255
+ display: flex;
256
+ flex-direction: column;
257
+ gap: 0.625rem;
258
+ margin: 0;
259
+ padding: 0;
260
+ list-style: none;
261
+ }
262
+
263
+ /* The picker's buttons read as choices, not as the primary CTA. */
264
+ .polyx-signin__tenant {
265
+ justify-content: flex-start;
266
+ color: var(--polyx-fg);
267
+ background: var(--polyx-input-bg);
268
+ border-color: var(--polyx-border);
269
+ }
270
+
271
+ .polyx-signin__tenant:hover:not(:disabled) {
272
+ background: var(--polyx-input-bg);
273
+ border-color: var(--polyx-brand);
274
+ }
package/package.json CHANGED
@@ -1,10 +1,12 @@
1
1
  {
2
2
  "name": "@poly-x/react",
3
- "version": "0.1.0-alpha.1",
3
+ "version": "0.1.0-alpha.3",
4
4
  "description": "PolyX SDK for React SPAs - provider, hooks, drop-in auth UI",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
- "sideEffects": false,
7
+ "sideEffects": [
8
+ "**/*.css"
9
+ ],
8
10
  "files": [
9
11
  "dist"
10
12
  ],
@@ -18,13 +20,14 @@
18
20
  "types": "./dist/index.d.cts",
19
21
  "default": "./dist/index.cjs"
20
22
  }
21
- }
23
+ },
24
+ "./styles.css": "./dist/styles.css"
22
25
  },
23
26
  "main": "./dist/index.cjs",
24
27
  "module": "./dist/index.mjs",
25
28
  "types": "./dist/index.d.cts",
26
29
  "dependencies": {
27
- "@poly-x/core": "0.1.0-alpha.1"
30
+ "@poly-x/core": "0.1.0-alpha.3"
28
31
  },
29
32
  "peerDependencies": {
30
33
  "react": ">=18",
@@ -51,7 +54,7 @@
51
54
  "react-dom": "^19.2.7"
52
55
  },
53
56
  "scripts": {
54
- "build": "tsdown",
57
+ "build": "tsdown && node scripts/copy-styles.mjs",
55
58
  "test": "vitest run",
56
59
  "lint": "eslint src"
57
60
  }