@xenosystem/components 0.2.0 → 0.3.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.
@@ -3,7 +3,9 @@ import { ReactNode } from 'react';
3
3
  import { ButtonVariant } from '@xenosystem/elements-react';
4
4
 
5
5
  declare function ensureAuthStyles(): void;
6
+
6
7
  declare function XenoMark(): ReactNode;
8
+
7
9
  interface AuthHeroStat {
8
10
  value: string;
9
11
  label: string;
@@ -17,6 +19,7 @@ interface AuthHeroProps {
17
19
  compact?: boolean;
18
20
  }
19
21
  declare function AuthHero({ headline, lead, stats, imageSrc, compact }: AuthHeroProps): react.JSX.Element;
22
+
20
23
  interface SignInAction {
21
24
  id: string;
22
25
  label: string;
@@ -45,6 +48,7 @@ interface SignInFormProps {
45
48
  declare function SignInForm({ tag, title, body, actions, note, warning, identity, animate, phase }: SignInFormProps): react.JSX.Element;
46
49
  /** The licence plates are the same form with a tag; kept as a name so a panel reads honestly. */
47
50
  declare const LicencePlate: typeof SignInForm;
51
+
48
52
  interface AuthDoorProps {
49
53
  hero: Omit<AuthHeroProps, 'compact'>;
50
54
  /** Aria label of the dialog. */
@@ -59,9 +63,90 @@ interface AuthDoorProps {
59
63
  * was given.
60
64
  */
61
65
  declare function AuthDoor({ hero, label, children, rootAttributes }: AuthDoorProps): react.JSX.Element;
66
+
67
+ type CredentialMode = 'signin' | 'signup';
68
+ interface CredentialValues {
69
+ name: string;
70
+ email: string;
71
+ password: string;
72
+ }
73
+ interface CredentialErrors {
74
+ name?: string;
75
+ email?: string;
76
+ password?: string;
77
+ form?: string;
78
+ }
79
+ interface CredentialFormProps {
80
+ mode: CredentialMode;
81
+ onModeChange: (mode: CredentialMode) => void;
82
+ values: CredentialValues;
83
+ onChange: (values: CredentialValues) => void;
84
+ errors?: CredentialErrors;
85
+ submitting?: boolean;
86
+ onSubmit: () => void;
87
+ /** Rendered above the switch — the page's "Welcome back" / "Get started" copy. */
88
+ title?: ReactNode;
89
+ body?: ReactNode;
90
+ /** Rendered between the switch and the fields — `SocialSignIn` + `OrDivider`, when the page has them. */
91
+ before?: ReactNode;
92
+ /** Rendered under the submit — "Forgot password?", terms, the other-mode link. */
93
+ after?: ReactNode;
94
+ /** Live password rules (sign-up): shown while the field is focused or partly filled — never before,
95
+ * a list of things you have failed before you typed is a telling-off for a crime not yet committed. */
96
+ passwordRules?: {
97
+ label: string;
98
+ met: boolean;
99
+ }[];
100
+ /** Rendered beside the password label — the page's "Forgot?" link on sign-in. */
101
+ passwordAside?: ReactNode;
102
+ /** When false, the fields collapse (0fr grid) and leave the accessibility tree; the page's disclosure
103
+ * controls it. Default open. */
104
+ open?: boolean;
105
+ /** id for the collapsible region (matched by `EmailDisclosure.controls`). */
106
+ id?: string;
107
+ /** Copy overrides; defaults are the platform page's. */
108
+ labels?: Partial<Record<'name' | 'email' | 'password' | 'submitSignIn' | 'submitSignUp' | 'signin' | 'signup', string>>;
109
+ }
110
+ declare function CredentialForm({ mode, onModeChange, values, onChange, errors, submitting, onSubmit, title, body, before, after, labels, passwordRules, passwordAside, open, id }: CredentialFormProps): react.JSX.Element;
111
+
112
+ type SocialProvider = 'google' | 'github' | 'x';
113
+ interface SocialSignInProps {
114
+ providers?: readonly SocialProvider[];
115
+ onSelect: (provider: SocialProvider) => void;
116
+ disabled?: boolean;
117
+ }
118
+ declare function SocialSignIn({ providers, onSelect, disabled }: SocialSignInProps): react.JSX.Element;
119
+
120
+ /** The "or continue with email" rule between the social row and the fields. */
121
+ declare function OrDivider({ children }: {
122
+ children?: string;
123
+ }): react.JSX.Element;
124
+
125
+ /**
126
+ * `EmailDisclosure` — the divider that is a BUTTON. On the origin page the email/password form starts
127
+ * collapsed because it is the minority path (most people press a provider), so the "or continue with
128
+ * email" rule opens it. A real button with `aria-expanded`/`aria-controls`, never a styled span, so it is
129
+ * keyboard-reachable and announced as what it is.
130
+ */
131
+ interface EmailDisclosureProps {
132
+ open: boolean;
133
+ onToggle: () => void;
134
+ /** id of the form region it controls. */
135
+ controls: string;
136
+ openLabel?: string;
137
+ closedLabel?: string;
138
+ }
139
+ declare function EmailDisclosure({ open, onToggle, controls, openLabel, closedLabel }: EmailDisclosureProps): react.JSX.Element;
140
+
62
141
  declare const STATES: {
63
142
  id: string;
64
143
  props: SignInFormProps;
65
144
  }[];
145
+ /** The origin page's states — everything but the callbacks, which the page supplies. */
146
+ type CredentialStateProps = Omit<CredentialFormProps, 'onModeChange' | 'onChange' | 'onSubmit'>;
147
+ declare const CREDENTIAL_STATES: {
148
+ id: string;
149
+ props: CredentialStateProps;
150
+ }[];
66
151
 
67
- export { AuthDoor, type AuthDoorProps, AuthHero, type AuthHeroProps, type AuthHeroStat, LicencePlate, STATES, type SignInAction, SignInForm, type SignInFormProps, XenoMark, ensureAuthStyles };
152
+ export { AuthDoor, type AuthDoorProps, AuthHero, type AuthHeroProps, type AuthHeroStat, CREDENTIAL_STATES, type CredentialErrors, CredentialForm, type CredentialFormProps, type CredentialMode, type CredentialValues, EmailDisclosure, type EmailDisclosureProps, LicencePlate, OrDivider, STATES, type SignInAction, SignInForm, type SignInFormProps, type SocialProvider, SocialSignIn, type SocialSignInProps, XenoMark, ensureAuthStyles };
@@ -1,7 +1,4 @@
1
- // src/auth/index.tsx
2
- import { useEffect, useRef, useState } from "react";
3
- import { Button } from "@xenosystem/elements-react";
4
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
1
+ // src/auth/styles.ts
5
2
  var STYLE_ID = "xeno-components-auth";
6
3
  var CSS = `
7
4
  .xc-auth{position:absolute;inset:0;display:flex;background:var(--xeno-canvas);color:var(--xeno-text);font-family:var(--xeno-font-sans);-webkit-font-smoothing:antialiased;overflow:hidden}
@@ -42,6 +39,43 @@ var CSS = `
42
39
  .xc-form__warn{margin-top:16px;font-size:12px;line-height:1.6;color:var(--xeno-danger)}
43
40
  .xc-form__who{margin-top:24px;padding-top:16px;border-top:1px solid var(--xeno-border-subtle);font-size:12px;color:var(--xeno-muted)}
44
41
  .xc-form__who b{color:var(--xeno-text);font-weight:500}
42
+
43
+ .xc-cred{width:100%;max-width:400px;display:flex;flex-direction:column;gap:24px}
44
+ .xc-cred__head .xc-form__h2{font-size:30px}
45
+ .xc-cred__mode{width:100%}
46
+ .xc-cred__collapse{display:grid;grid-template-rows:1fr;opacity:1;transition:grid-template-rows var(--xeno-dur-entrance) var(--xeno-ease),opacity var(--xeno-dur-entrance) var(--xeno-ease)}
47
+ .xc-cred__collapse[data-open=false]{grid-template-rows:0fr;opacity:0}
48
+ .xc-cred__collapse > *{overflow:hidden;min-height:0}
49
+ .xc-cred__fields{display:flex;flex-direction:column;gap:16px}
50
+ .xc-cred__labelRow{display:flex;justify-content:space-between;align-items:baseline}
51
+ .xc-cred__labelRow a{font-size:12px;color:var(--xeno-muted);text-decoration:none}
52
+ .xc-cred__labelRow a:hover{color:var(--xeno-text)}
53
+ .xc-cred__rules{list-style:none;margin:0;padding:0;display:grid;grid-template-rows:0fr;opacity:0;transition:grid-template-rows var(--xeno-dur-entrance) var(--xeno-ease),opacity var(--xeno-dur-entrance) var(--xeno-ease)}
54
+ .xc-cred__rules[data-open=true]{grid-template-rows:1fr;opacity:1;margin-top:10px}
55
+ .xc-cred__rules > li{overflow:hidden;min-height:0}
56
+ .xc-cred__rule{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--xeno-muted);transition:color var(--xeno-dur-fast) var(--xeno-ease)}
57
+ .xc-cred__rule[data-met=true]{color:var(--xeno-text)}
58
+ .xc-cred__ruleMark{width:14px;height:14px;flex:none}
59
+ .xc-or__btn{border:0;cursor:pointer;display:inline-flex;align-items:center;gap:6px;font:inherit;font-size:11px;letter-spacing:.12em;text-transform:uppercase}
60
+ .xc-or__btn:hover{color:var(--xeno-text)}
61
+ .xc-or__caret{width:12px;height:12px;transition:transform var(--xeno-dur-fast) var(--xeno-ease)}
62
+ .xc-or__caret[data-open=true]{transform:rotate(180deg)}
63
+ .xc-cred__reveal{display:grid;grid-template-rows:0fr;opacity:0;transition:grid-template-rows var(--xeno-dur-entrance) var(--xeno-ease),opacity var(--xeno-dur-entrance) var(--xeno-ease)}
64
+ .xc-cred__reveal[data-open=true]{grid-template-rows:1fr;opacity:1}
65
+ .xc-cred__reveal > *{overflow:hidden;min-height:0}
66
+ .xc-cred__field{display:flex;flex-direction:column;gap:8px}
67
+ .xc-cred__label{font-size:13px;font-weight:500;color:var(--xeno-muted)}
68
+ .xc-cred__pw{display:grid;grid-template-columns:1fr auto;gap:8px;align-items:center}
69
+ .xc-cred__error{font-size:12px;color:var(--xeno-danger)}
70
+ .xc-cred__after{font-size:13px;color:var(--xeno-muted);text-align:center}
71
+ .xc-cred .xc-form__actions{margin-top:0}
72
+ .xc-social{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}
73
+ .xc-social > *{width:100%}
74
+ .xc-social__mark{width:20px;height:20px;opacity:.75;transition:opacity var(--xeno-dur-fast) var(--xeno-ease)}
75
+ .xc-social button:hover .xc-social__mark{opacity:1}
76
+ .xc-or{position:relative;display:flex;justify-content:center;margin:4px 0}
77
+ .xc-or::before{content:'';position:absolute;inset:50% 0 auto 0;border-top:1px solid var(--xeno-border-subtle)}
78
+ .xc-or__label{position:relative;padding:0 16px;background:var(--xeno-canvas);font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--xeno-muted)}
45
79
  `;
46
80
  function ensureAuthStyles() {
47
81
  if (typeof document === "undefined" || document.getElementById(STYLE_ID)) return;
@@ -50,6 +84,9 @@ function ensureAuthStyles() {
50
84
  style.textContent = CSS;
51
85
  document.head.appendChild(style);
52
86
  }
87
+
88
+ // src/auth/XenoMark.tsx
89
+ import { jsx, jsxs } from "react/jsx-runtime";
53
90
  function XenoMark() {
54
91
  return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 1082 1082", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", children: [
55
92
  /* @__PURE__ */ jsx("path", { d: "M489.1 219.763L323.457 39.7072L101.649 30.4597C51.6926 28.3769 39.5494 67.5718 39.7224 87.4296L30.4124 310.735L347.816 655.757L475.833 537.987L241.73 283.514C207.644 246.462 222.019 240.156 233.467 241.634L455.275 250.881L489.1 219.763Z", fill: "currentColor" }),
@@ -58,30 +95,38 @@ function XenoMark() {
58
95
  /* @__PURE__ */ jsx("path", { d: "M220.763 592.907L40.7063 758.55L31.4588 980.358C29.3761 1030.31 68.5709 1042.46 88.4287 1042.28L311.735 1051.59L656.756 734.191L538.986 606.174L284.514 840.277C247.462 874.363 241.155 859.988 242.633 848.54L251.881 626.733L220.763 592.907Z", fill: "currentColor" })
59
96
  ] });
60
97
  }
98
+
99
+ // src/auth/AuthHero.tsx
100
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
61
101
  function AuthHero({ headline, lead, stats, imageSrc, compact }) {
62
- return /* @__PURE__ */ jsxs("div", { className: "xc-hero", "data-xc": "auth-hero", children: [
63
- imageSrc && /* @__PURE__ */ jsx("img", { className: "xc-hero__img", src: imageSrc, alt: "", draggable: false }),
64
- /* @__PURE__ */ jsxs("div", { className: "xc-hero__in", children: [
65
- /* @__PURE__ */ jsxs("div", { className: "xc-brand", children: [
66
- /* @__PURE__ */ jsx(XenoMark, {}),
67
- /* @__PURE__ */ jsx("span", { className: "xc-brand__word", children: "XENO" })
102
+ return /* @__PURE__ */ jsxs2("div", { className: "xc-hero", "data-xc": "auth-hero", children: [
103
+ imageSrc && /* @__PURE__ */ jsx2("img", { className: "xc-hero__img", src: imageSrc, alt: "", draggable: false }),
104
+ /* @__PURE__ */ jsxs2("div", { className: "xc-hero__in", children: [
105
+ /* @__PURE__ */ jsxs2("div", { className: "xc-brand", children: [
106
+ /* @__PURE__ */ jsx2(XenoMark, {}),
107
+ /* @__PURE__ */ jsx2("span", { className: "xc-brand__word", children: "XENO" })
68
108
  ] }),
69
- compact ? /* @__PURE__ */ jsx("h1", { className: "xc-hero__h1", children: headline }) : /* @__PURE__ */ jsxs(Fragment, { children: [
70
- /* @__PURE__ */ jsxs("div", { children: [
71
- /* @__PURE__ */ jsx("h1", { className: "xc-hero__h1", children: headline }),
72
- lead && /* @__PURE__ */ jsx("p", { className: "xc-hero__lead", children: lead })
109
+ compact ? /* @__PURE__ */ jsx2("h1", { className: "xc-hero__h1", children: headline }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
110
+ /* @__PURE__ */ jsxs2("div", { children: [
111
+ /* @__PURE__ */ jsx2("h1", { className: "xc-hero__h1", children: headline }),
112
+ lead && /* @__PURE__ */ jsx2("p", { className: "xc-hero__lead", children: lead })
73
113
  ] }),
74
- stats && stats.length > 0 && /* @__PURE__ */ jsx("div", { className: "xc-hero__stats", children: stats.map((s, i) => /* @__PURE__ */ jsxs("div", { style: { display: "contents" }, children: [
75
- i > 0 && /* @__PURE__ */ jsx("div", { className: "xc-hero__sep" }),
76
- /* @__PURE__ */ jsxs("div", { className: "xc-hero__stat", children: [
77
- /* @__PURE__ */ jsx("b", { children: s.value }),
78
- /* @__PURE__ */ jsx("span", { children: s.label })
114
+ stats && stats.length > 0 && /* @__PURE__ */ jsx2("div", { className: "xc-hero__stats", children: stats.map((s, i) => /* @__PURE__ */ jsxs2("div", { style: { display: "contents" }, children: [
115
+ i > 0 && /* @__PURE__ */ jsx2("div", { className: "xc-hero__sep" }),
116
+ /* @__PURE__ */ jsxs2("div", { className: "xc-hero__stat", children: [
117
+ /* @__PURE__ */ jsx2("b", { children: s.value }),
118
+ /* @__PURE__ */ jsx2("span", { children: s.label })
79
119
  ] })
80
120
  ] }, s.label)) })
81
121
  ] })
82
122
  ] })
83
123
  ] });
84
124
  }
125
+
126
+ // src/auth/SignInForm.tsx
127
+ import { useEffect, useState } from "react";
128
+ import { Button } from "@xenosystem/elements-react";
129
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
85
130
  function SignInForm({ tag, title, body, actions, note, warning, identity, animate = true, phase }) {
86
131
  const [visible, setVisible] = useState(!animate);
87
132
  useEffect(() => {
@@ -89,25 +134,29 @@ function SignInForm({ tag, title, body, actions, note, warning, identity, animat
89
134
  const t = window.setTimeout(() => setVisible(true), 100);
90
135
  return () => window.clearTimeout(t);
91
136
  }, [animate]);
92
- return /* @__PURE__ */ jsxs("div", { className: "xc-form", "data-xc": "sign-in-form", "data-visible": visible, "data-phase": phase, children: [
93
- tag && /* @__PURE__ */ jsx("span", { className: "xc-form__tag", children: tag }),
94
- /* @__PURE__ */ jsx("h2", { className: "xc-form__h2", children: title }),
95
- /* @__PURE__ */ jsx("p", { className: "xc-form__sub", children: body }),
96
- actions.length > 0 && /* @__PURE__ */ jsx("div", { className: "xc-form__actions", children: actions.map((a) => /* @__PURE__ */ jsx(Button, { variant: a.variant ?? "secondary", size: "lg", disabled: a.disabled, onClick: a.onClick, "data-intent": a.id, children: a.label }, a.id)) }),
97
- note && /* @__PURE__ */ jsx("p", { className: "xc-form__note", children: note }),
98
- warning && /* @__PURE__ */ jsx("p", { className: "xc-form__warn", children: warning }),
99
- identity && /* @__PURE__ */ jsxs("p", { className: "xc-form__who", children: [
137
+ return /* @__PURE__ */ jsxs3("div", { className: "xc-form", "data-xc": "sign-in-form", "data-visible": visible, "data-phase": phase, children: [
138
+ tag && /* @__PURE__ */ jsx3("span", { className: "xc-form__tag", children: tag }),
139
+ /* @__PURE__ */ jsx3("h2", { className: "xc-form__h2", children: title }),
140
+ /* @__PURE__ */ jsx3("p", { className: "xc-form__sub", children: body }),
141
+ actions.length > 0 && /* @__PURE__ */ jsx3("div", { className: "xc-form__actions", children: actions.map((a) => /* @__PURE__ */ jsx3(Button, { variant: a.variant ?? "secondary", size: "lg", disabled: a.disabled, onClick: a.onClick, "data-intent": a.id, children: a.label }, a.id)) }),
142
+ note && /* @__PURE__ */ jsx3("p", { className: "xc-form__note", children: note }),
143
+ warning && /* @__PURE__ */ jsx3("p", { className: "xc-form__warn", children: warning }),
144
+ identity && /* @__PURE__ */ jsxs3("p", { className: "xc-form__who", children: [
100
145
  "Signed in as ",
101
- /* @__PURE__ */ jsx("b", { children: identity })
146
+ /* @__PURE__ */ jsx3("b", { children: identity })
102
147
  ] })
103
148
  ] });
104
149
  }
105
150
  var LicencePlate = SignInForm;
151
+
152
+ // src/auth/AuthDoor.tsx
153
+ import { useEffect as useEffect2, useRef, useState as useState2 } from "react";
154
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
106
155
  var aspectOf = (w, h) => h <= 0 ? "landscape" : w / h > 1.2 ? "landscape" : w / h < 0.85 ? "portrait" : "square";
107
156
  function AuthDoor({ hero, label, children, rootAttributes }) {
108
157
  const ref = useRef(null);
109
- const [aspect, setAspect] = useState("landscape");
110
- useEffect(() => {
158
+ const [aspect, setAspect] = useState2("landscape");
159
+ useEffect2(() => {
111
160
  ensureAuthStyles();
112
161
  const el = ref.current;
113
162
  if (!el || typeof ResizeObserver === "undefined") return;
@@ -118,15 +167,123 @@ function AuthDoor({ hero, label, children, rootAttributes }) {
118
167
  ro.observe(el);
119
168
  return () => ro.disconnect();
120
169
  }, []);
121
- const brand = /* @__PURE__ */ jsxs("div", { className: "xc-brand", children: [
122
- /* @__PURE__ */ jsx(XenoMark, {}),
123
- /* @__PURE__ */ jsx("span", { className: "xc-brand__word", children: "XENO" })
170
+ const brand = /* @__PURE__ */ jsxs4("div", { className: "xc-brand", children: [
171
+ /* @__PURE__ */ jsx4(XenoMark, {}),
172
+ /* @__PURE__ */ jsx4("span", { className: "xc-brand__word", children: "XENO" })
173
+ ] });
174
+ return /* @__PURE__ */ jsxs4("div", { ref, className: "xc-auth", "data-xc": "auth-door", "data-aspect": aspect, role: "dialog", "aria-label": label, ...rootAttributes, children: [
175
+ aspect === "portrait" ? /* @__PURE__ */ jsx4("header", { className: "xc-auth__head", children: brand }) : /* @__PURE__ */ jsx4("div", { className: "xc-auth__heroWrap", children: /* @__PURE__ */ jsx4(AuthHero, { ...hero, compact: aspect === "square" }) }),
176
+ /* @__PURE__ */ jsx4("div", { className: "xc-auth__side", children: /* @__PURE__ */ jsx4("div", { className: "xc-auth__center", children }) })
177
+ ] });
178
+ }
179
+
180
+ // src/auth/CredentialForm.tsx
181
+ import { useState as useState3 } from "react";
182
+ import { Button as Button2, SegmentedControl, TextInput } from "@xenosystem/elements-react";
183
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
184
+ var L = {
185
+ name: "Full name",
186
+ email: "Email",
187
+ password: "Password",
188
+ submitSignIn: "Sign in",
189
+ submitSignUp: "Create account",
190
+ signin: "Sign In",
191
+ signup: "Sign Up"
192
+ };
193
+ function CredentialForm({ mode, onModeChange, values, onChange, errors = {}, submitting = false, onSubmit, title, body, before, after, labels, passwordRules, passwordAside, open = true, id = "xc-credential-fields" }) {
194
+ const [showPassword, setShowPassword] = useState3(false);
195
+ const [passwordFocused, setPasswordFocused] = useState3(false);
196
+ const showRules = mode === "signup" && !!passwordRules?.length && (passwordFocused || values.password.length > 0);
197
+ const t = { ...L, ...labels };
198
+ const set = (k) => (e) => onChange({ ...values, [k]: e.target.value });
199
+ const submit = (e) => {
200
+ e.preventDefault();
201
+ if (!submitting) onSubmit();
202
+ };
203
+ return /* @__PURE__ */ jsxs5("form", { className: "xc-cred", "data-xc": "credential-form", "data-mode": mode, onSubmit: submit, noValidate: true, children: [
204
+ (title || body) && /* @__PURE__ */ jsxs5("div", { className: "xc-cred__head", children: [
205
+ title && /* @__PURE__ */ jsx5("h2", { className: "xc-form__h2", children: title }),
206
+ body && /* @__PURE__ */ jsx5("p", { className: "xc-form__sub", children: body })
207
+ ] }),
208
+ /* @__PURE__ */ jsx5(
209
+ SegmentedControl,
210
+ {
211
+ className: "xc-cred__mode",
212
+ value: mode,
213
+ onValueChange: (v) => onModeChange(v),
214
+ options: [{ value: "signin", label: t.signin }, { value: "signup", label: t.signup }],
215
+ size: "lg",
216
+ "aria-label": "Sign in or sign up"
217
+ }
218
+ ),
219
+ before,
220
+ /* @__PURE__ */ jsx5("div", { className: "xc-cred__collapse", "data-open": open, id, "aria-hidden": !open, children: /* @__PURE__ */ jsxs5("div", { className: "xc-cred__fields", children: [
221
+ /* @__PURE__ */ jsx5("div", { className: "xc-cred__reveal", "data-open": mode === "signup", "aria-hidden": mode !== "signup", children: /* @__PURE__ */ jsxs5("label", { className: "xc-cred__field", children: [
222
+ /* @__PURE__ */ jsx5("span", { className: "xc-cred__label", children: t.name }),
223
+ /* @__PURE__ */ jsx5(TextInput, { size: "lg", value: values.name, onChange: set("name"), placeholder: "Your name", autoComplete: "name", tabIndex: mode === "signup" && open ? 0 : -1, "aria-invalid": !!errors.name, "data-field": "name" }),
224
+ errors.name && /* @__PURE__ */ jsx5("span", { className: "xc-cred__error", role: "alert", children: errors.name })
225
+ ] }) }),
226
+ /* @__PURE__ */ jsxs5("label", { className: "xc-cred__field", children: [
227
+ /* @__PURE__ */ jsx5("span", { className: "xc-cred__label", children: t.email }),
228
+ /* @__PURE__ */ jsx5(TextInput, { size: "lg", type: "email", value: values.email, onChange: set("email"), placeholder: "you@example.com", autoComplete: "email", required: true, tabIndex: open ? 0 : -1, "aria-invalid": !!errors.email, "data-field": "email" }),
229
+ errors.email && /* @__PURE__ */ jsx5("span", { className: "xc-cred__error", role: "alert", children: errors.email })
230
+ ] }),
231
+ /* @__PURE__ */ jsxs5("label", { className: "xc-cred__field", children: [
232
+ /* @__PURE__ */ jsxs5("span", { className: "xc-cred__labelRow", children: [
233
+ /* @__PURE__ */ jsx5("span", { className: "xc-cred__label", children: t.password }),
234
+ passwordAside
235
+ ] }),
236
+ /* @__PURE__ */ jsxs5("span", { className: "xc-cred__pw", children: [
237
+ /* @__PURE__ */ jsx5(TextInput, { size: "lg", type: showPassword ? "text" : "password", value: values.password, onChange: set("password"), onFocus: () => setPasswordFocused(true), onBlur: () => setPasswordFocused(false), placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022", autoComplete: mode === "signup" ? "new-password" : "current-password", required: true, "aria-invalid": !!errors.password, "data-field": "password" }),
238
+ /* @__PURE__ */ jsx5(Button2, { type: "button", variant: "ghost", size: "sm", className: "xc-cred__eye", onClick: () => setShowPassword((v) => !v), "aria-label": showPassword ? "Hide password" : "Show password", "aria-pressed": showPassword, children: showPassword ? "Hide" : "Show" })
239
+ ] }),
240
+ errors.password && /* @__PURE__ */ jsx5("span", { className: "xc-cred__error", role: "alert", children: errors.password }),
241
+ passwordRules && /* @__PURE__ */ jsx5("ul", { className: "xc-cred__rules", "data-open": showRules, "aria-live": "polite", children: passwordRules.map((r, i) => /* @__PURE__ */ jsxs5("li", { className: "xc-cred__rule", "data-met": r.met, style: { transitionDelay: `${i * 60}ms` }, children: [
242
+ /* @__PURE__ */ jsx5("svg", { className: "xc-cred__ruleMark", viewBox: "0 0 16 16", "aria-hidden": "true", children: r.met ? /* @__PURE__ */ jsx5("path", { d: "M3.5 8.5l3 3 6-6", fill: "none", stroke: "currentColor", strokeWidth: "1.6" }) : /* @__PURE__ */ jsx5("rect", { x: "4", y: "7.25", width: "8", height: "1.5", fill: "currentColor" }) }),
243
+ /* @__PURE__ */ jsx5("span", { children: r.label })
244
+ ] }, r.label)) })
245
+ ] })
246
+ ] }) }),
247
+ errors.form && /* @__PURE__ */ jsx5("p", { className: "xc-form__warn", role: "alert", children: errors.form }),
248
+ /* @__PURE__ */ jsx5("div", { className: "xc-form__actions", children: /* @__PURE__ */ jsx5(Button2, { type: "submit", variant: "primary", size: "lg", busy: submitting, "data-intent": "submit", children: mode === "signin" ? t.submitSignIn : t.submitSignUp }) }),
249
+ after && /* @__PURE__ */ jsx5("div", { className: "xc-cred__after", children: after })
124
250
  ] });
125
- return /* @__PURE__ */ jsxs("div", { ref, className: "xc-auth", "data-xc": "auth-door", "data-aspect": aspect, role: "dialog", "aria-label": label, ...rootAttributes, children: [
126
- aspect === "portrait" ? /* @__PURE__ */ jsx("header", { className: "xc-auth__head", children: brand }) : /* @__PURE__ */ jsx("div", { className: "xc-auth__heroWrap", children: /* @__PURE__ */ jsx(AuthHero, { ...hero, compact: aspect === "square" }) }),
127
- /* @__PURE__ */ jsx("div", { className: "xc-auth__side", children: /* @__PURE__ */ jsx("div", { className: "xc-auth__center", children }) })
251
+ }
252
+
253
+ // src/auth/SocialSignIn.tsx
254
+ import { Button as Button3 } from "@xenosystem/elements-react";
255
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
256
+ var NAMES = { google: "Google", github: "GitHub", x: "X" };
257
+ function Mark({ p }) {
258
+ if (p === "google") return /* @__PURE__ */ jsxs6("svg", { className: "xc-social__mark", viewBox: "0 0 48 48", "aria-hidden": "true", children: [
259
+ /* @__PURE__ */ jsx6("path", { fill: "#FFC107", d: "M43.611,20.083H42V20H24v8h11.303c-1.649,4.657-6.08,8-11.303,8c-6.627,0-12-5.373-12-12c0-6.627,5.373-12,12-12c3.059,0,5.842,1.154,7.961,3.039l5.657-5.657C34.046,6.053,29.268,4,24,4C12.955,4,4,12.955,4,24c0,11.045,8.955,20,20,20c11.045,0,20-8.955,20-20C44,22.659,43.862,21.35,43.611,20.083z" }),
260
+ /* @__PURE__ */ jsx6("path", { fill: "#FF3D00", d: "M6.306,14.691l6.571,4.819C14.655,15.108,18.961,12,24,12c3.059,0,5.842,1.154,7.961,3.039l5.657-5.657C34.046,6.053,29.268,4,24,4C16.318,4,9.656,8.337,6.306,14.691z" }),
261
+ /* @__PURE__ */ jsx6("path", { fill: "#4CAF50", d: "M24,44c5.166,0,9.86-1.977,13.409-5.192l-6.19-5.238C29.211,35.091,26.715,36,24,36c-5.202,0-9.619-3.317-11.283-7.946l-6.522,5.025C9.505,39.556,16.227,44,24,44z" }),
262
+ /* @__PURE__ */ jsx6("path", { fill: "#1976D2", d: "M43.611,20.083H42V20H24v8h11.303c-0.792,2.237-2.231,4.166-4.087,5.571c0.001-0.001,0.002-0.001,0.003-0.002l6.19,5.238C36.971,39.205,44,34,44,24C44,22.659,43.862,21.35,43.611,20.083z" })
128
263
  ] });
264
+ if (p === "github") return /* @__PURE__ */ jsx6("svg", { className: "xc-social__mark", viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ jsx6("path", { d: "M12 .5C5.65.5.5 5.65.5 12c0 5.08 3.29 9.39 7.86 10.91.58.1.79-.25.79-.56v-2c-3.2.7-3.87-1.37-3.87-1.37-.52-1.33-1.28-1.69-1.28-1.69-1.05-.71.08-.7.08-.7 1.16.08 1.77 1.19 1.77 1.19 1.03 1.77 2.7 1.26 3.36.96.1-.75.4-1.26.73-1.55-2.55-.29-5.24-1.28-5.24-5.69 0-1.26.45-2.29 1.19-3.09-.12-.29-.52-1.46.11-3.05 0 0 .97-.31 3.17 1.18a11 11 0 0 1 5.77 0c2.2-1.49 3.17-1.18 3.17-1.18.63 1.59.23 2.76.11 3.05.74.8 1.19 1.83 1.19 3.09 0 4.42-2.69 5.39-5.25 5.68.41.36.78 1.06.78 2.13v3.16c0 .31.21.67.8.56A11.5 11.5 0 0 0 23.5 12C23.5 5.65 18.35.5 12 .5z" }) });
265
+ return /* @__PURE__ */ jsx6("svg", { className: "xc-social__mark", viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ jsx6("path", { d: "M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" }) });
129
266
  }
267
+ function SocialSignIn({ providers = ["google", "github", "x"], onSelect, disabled }) {
268
+ return /* @__PURE__ */ jsx6("div", { className: "xc-social", "data-xc": "social-sign-in", role: "group", "aria-label": "Continue with", children: providers.map((p) => /* @__PURE__ */ jsx6(Button3, { variant: "quiet", size: "lg", disabled, onClick: () => onSelect(p), "aria-label": `Continue with ${NAMES[p]}`, "data-provider": p, children: /* @__PURE__ */ jsx6(Mark, { p }) }, p)) });
269
+ }
270
+
271
+ // src/auth/OrDivider.tsx
272
+ import { jsx as jsx7 } from "react/jsx-runtime";
273
+ function OrDivider({ children = "or continue with email" }) {
274
+ return /* @__PURE__ */ jsx7("div", { className: "xc-or", role: "separator", "aria-label": children, children: /* @__PURE__ */ jsx7("span", { className: "xc-or__label", children }) });
275
+ }
276
+
277
+ // src/auth/EmailDisclosure.tsx
278
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
279
+ function EmailDisclosure({ open, onToggle, controls, openLabel = "hide email sign-in", closedLabel = "or continue with email" }) {
280
+ return /* @__PURE__ */ jsx8("div", { className: "xc-or", "data-xc": "email-disclosure", children: /* @__PURE__ */ jsxs7("button", { type: "button", className: "xc-or__label xc-or__btn", onClick: onToggle, "aria-expanded": open, "aria-controls": controls, children: [
281
+ open ? openLabel : closedLabel,
282
+ /* @__PURE__ */ jsx8("svg", { className: "xc-or__caret", "data-open": open, viewBox: "0 0 16 16", "aria-hidden": "true", children: /* @__PURE__ */ jsx8("path", { d: "M4 6l4 4 4-4", fill: "none", stroke: "currentColor", strokeWidth: "1.5" }) })
283
+ ] }) });
284
+ }
285
+
286
+ // src/auth/states.ts
130
287
  var STATES = [
131
288
  { id: "sign-in", props: { title: "Welcome back", body: "Sign in to your XENO account to use XENO Canvas.", actions: [{ id: "signIn", label: "Sign in to XENO", variant: "primary" }], note: "Your browser opens at xenostudio.ai, where you can continue with Google, GitHub or email. XENO Canvas never sees your password.", animate: false } },
132
289
  { id: "pending", props: { title: "Welcome back", body: "Sign in to your XENO account to use XENO Canvas.", actions: [{ id: "pending", label: "Finish in your browser\u2026", variant: "primary", disabled: true }, { id: "cancelSignIn", label: "Cancel", variant: "quiet" }], animate: false } },
@@ -135,12 +292,28 @@ var STATES = [
135
292
  { id: "expired-offline", props: { tag: "Offline too long", title: "Your plan couldn\u2019t be confirmed", body: "XENO Canvas hasn\u2019t been able to reach XENO for 14 days \u2014 last confirmed 16 days ago. Your plan may be fine; connect once and it will pick up where it left off.", actions: [{ id: "refresh", label: "Check again", variant: "primary" }, { id: "signOut", label: "Sign out", variant: "quiet" }], identity: "emilian@bnkrsys.com", animate: false } },
136
293
  { id: "update-required", props: { tag: "Update required", title: "Update XENO Canvas to continue", body: "Versions before 0.39.0 are no longer supported. The latest version is a short download away.", actions: [{ id: "openDownload", label: "Download the latest XENO Canvas", variant: "primary" }, { id: "signOut", label: "Sign out", variant: "quiet" }], identity: "emilian@bnkrsys.com", animate: false } }
137
294
  ];
295
+ var empty = { name: "", email: "", password: "" };
296
+ var CREDENTIAL_STATES = [
297
+ { id: "signin", props: { mode: "signin", values: empty, title: "Welcome back", body: "Enter your credentials to access your account" } },
298
+ { id: "signup", props: { mode: "signup", values: empty, title: "Get started", body: "Create your account and start creating" } },
299
+ { id: "filled", props: { mode: "signin", values: { name: "", email: "you@example.com", password: "hunter22" }, title: "Welcome back", body: "Enter your credentials to access your account" } },
300
+ { id: "errors", props: { mode: "signup", values: { name: "", email: "not-an-email", password: "123" }, errors: { name: "Please enter your full name", email: "Please enter a valid email address", password: "Password must be at least 6 characters" }, title: "Get started", body: "Create your account and start creating" } },
301
+ { id: "submitting", props: { mode: "signin", values: { name: "", email: "you@example.com", password: "hunter22" }, submitting: true, title: "Welcome back", body: "Enter your credentials to access your account" } },
302
+ { id: "rules", props: { mode: "signup", values: { name: "Ada", email: "ada@example.com", password: "abcdefgh1" }, passwordRules: [{ label: "Minimum 8 letters", met: true }, { label: "At least one number", met: true }, { label: "At least one special character", met: false }], title: "Get started", body: "Create your account and start creating" } },
303
+ { id: "collapsed", props: { mode: "signin", values: empty, open: false, title: "Welcome back", body: "Enter your credentials to access your account" } },
304
+ { id: "rejected", props: { mode: "signin", values: { name: "", email: "you@example.com", password: "hunter22" }, errors: { form: "Invalid email or password." }, title: "Welcome back", body: "Enter your credentials to access your account" } }
305
+ ];
138
306
  export {
139
307
  AuthDoor,
140
308
  AuthHero,
309
+ CREDENTIAL_STATES,
310
+ CredentialForm,
311
+ EmailDisclosure,
141
312
  LicencePlate,
313
+ OrDivider,
142
314
  STATES,
143
315
  SignInForm,
316
+ SocialSignIn,
144
317
  XenoMark,
145
318
  ensureAuthStyles
146
319
  };
package/dist/index.d.ts CHANGED
@@ -3,6 +3,6 @@
3
3
  * Families are subpaths: `@xenosystem/components/auth`, `/navigation`, `/feedback`, `/data`.
4
4
  * This root re-exports nothing on purpose: import the family you use, so a bundle carries only it.
5
5
  */
6
- declare const COMPONENTS_VERSION = "0.1.0";
6
+ declare const COMPONENTS_VERSION = "0.3.0";
7
7
 
8
8
  export { COMPONENTS_VERSION };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/index.ts
2
- var COMPONENTS_VERSION = "0.1.0";
2
+ var COMPONENTS_VERSION = "0.3.0";
3
3
  export {
4
4
  COMPONENTS_VERSION
5
5
  };
package/package.json CHANGED
@@ -1,19 +1,29 @@
1
1
  {
2
2
  "name": "@xenosystem/components",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "XENO Components — rung 2 of the front ladder: molecules composed from @xenosystem/elements, with no host contract. Families as subpaths: /auth, /navigation, /feedback, /data.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
7
7
  "sideEffects": false,
8
8
  "exports": {
9
- ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" },
10
- "./auth": { "types": "./dist/auth/index.d.ts", "import": "./dist/auth/index.js" }
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ },
13
+ "./auth": {
14
+ "types": "./dist/auth/index.d.ts",
15
+ "import": "./dist/auth/index.js"
16
+ }
11
17
  },
12
- "files": ["dist", "README.md"],
18
+ "files": [
19
+ "dist",
20
+ "README.md"
21
+ ],
13
22
  "scripts": {
14
23
  "build": "tsup",
15
24
  "typecheck": "tsc --noEmit",
16
- "test": "vitest run"
25
+ "test": "vitest run",
26
+ "prepublishOnly": "node scripts/verify-dist.mjs"
17
27
  },
18
28
  "peerDependencies": {
19
29
  "@xenosystem/elements-react": ">=0.0.1",
@@ -31,5 +41,7 @@
31
41
  "typescript": "^5.4.0",
32
42
  "vitest": "^4.1.0"
33
43
  },
34
- "publishConfig": { "access": "public" }
44
+ "publishConfig": {
45
+ "access": "public"
46
+ }
35
47
  }