@xenosystem/components 0.3.0 → 0.4.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.
@@ -1,5 +1,5 @@
1
1
  import * as react from 'react';
2
- import { ReactNode } from 'react';
2
+ import { ReactNode, ReactElement } from 'react';
3
3
  import { ButtonVariant } from '@xenosystem/elements-react';
4
4
 
5
5
  declare function ensureAuthStyles(): void;
@@ -122,12 +122,6 @@ declare function OrDivider({ children }: {
122
122
  children?: string;
123
123
  }): react.JSX.Element;
124
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
125
  interface EmailDisclosureProps {
132
126
  open: boolean;
133
127
  onToggle: () => void;
@@ -149,4 +143,24 @@ declare const CREDENTIAL_STATES: {
149
143
  props: CredentialStateProps;
150
144
  }[];
151
145
 
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 };
146
+ /**
147
+ * `gallery()` — every unit of the family in every state, as renderable entries. This is what the
148
+ * workshop (rungs 1–2, from source) and xeno-apps' Library mode (from the published package) both
149
+ * consume, so neither hand-lists a state: `XENO FRONT-END HIERARCHY.md` — "states are derived, never
150
+ * hand-listed, IN the rung packages, so the workshop and the Library render the same list."
151
+ *
152
+ * `render` is pure: fixture props in, element out, callbacks inert. A gallery entry never reaches a
153
+ * network or a host.
154
+ */
155
+
156
+ interface GalleryEntry {
157
+ family: 'auth';
158
+ unit: string;
159
+ state: string;
160
+ /** How the entry wants to be staged: `form` (a 400 px column) or `door` (fills a box, `inset: 0`). */
161
+ stage: 'form' | 'door';
162
+ render: () => ReactElement;
163
+ }
164
+ declare function gallery(): GalleryEntry[];
165
+
166
+ export { AuthDoor, type AuthDoorProps, AuthHero, type AuthHeroProps, type AuthHeroStat, CREDENTIAL_STATES, type CredentialErrors, CredentialForm, type CredentialFormProps, type CredentialMode, type CredentialValues, EmailDisclosure, type EmailDisclosureProps, type GalleryEntry, LicencePlate, OrDivider, STATES, type SignInAction, SignInForm, type SignInFormProps, type SocialProvider, SocialSignIn, type SocialSignInProps, XenoMark, ensureAuthStyles, gallery };
@@ -1,4 +1,5 @@
1
1
  // src/auth/styles.ts
2
+ import { useInsertionEffect } from "react";
2
3
  var STYLE_ID = "xeno-components-auth";
3
4
  var CSS = `
4
5
  .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}
@@ -45,7 +46,7 @@ var CSS = `
45
46
  .xc-cred__mode{width:100%}
46
47
  .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
48
  .xc-cred__collapse[data-open=false]{grid-template-rows:0fr;opacity:0}
48
- .xc-cred__collapse > *{overflow:hidden;min-height:0}
49
+ .xc-cred__collapseIn{overflow:hidden;min-height:0;display:flex;flex-direction:column;gap:24px}
49
50
  .xc-cred__fields{display:flex;flex-direction:column;gap:16px}
50
51
  .xc-cred__labelRow{display:flex;justify-content:space-between;align-items:baseline}
51
52
  .xc-cred__labelRow a{font-size:12px;color:var(--xeno-muted);text-decoration:none}
@@ -84,6 +85,11 @@ function ensureAuthStyles() {
84
85
  style.textContent = CSS;
85
86
  document.head.appendChild(style);
86
87
  }
88
+ function useAuthStyles() {
89
+ useInsertionEffect(() => {
90
+ ensureAuthStyles();
91
+ }, []);
92
+ }
87
93
 
88
94
  // src/auth/XenoMark.tsx
89
95
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -99,6 +105,7 @@ function XenoMark() {
99
105
  // src/auth/AuthHero.tsx
100
106
  import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
101
107
  function AuthHero({ headline, lead, stats, imageSrc, compact }) {
108
+ useAuthStyles();
102
109
  return /* @__PURE__ */ jsxs2("div", { className: "xc-hero", "data-xc": "auth-hero", children: [
103
110
  imageSrc && /* @__PURE__ */ jsx2("img", { className: "xc-hero__img", src: imageSrc, alt: "", draggable: false }),
104
111
  /* @__PURE__ */ jsxs2("div", { className: "xc-hero__in", children: [
@@ -128,6 +135,7 @@ import { useEffect, useState } from "react";
128
135
  import { Button } from "@xenosystem/elements-react";
129
136
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
130
137
  function SignInForm({ tag, title, body, actions, note, warning, identity, animate = true, phase }) {
138
+ useAuthStyles();
131
139
  const [visible, setVisible] = useState(!animate);
132
140
  useEffect(() => {
133
141
  if (!animate) return;
@@ -191,6 +199,7 @@ var L = {
191
199
  signup: "Sign Up"
192
200
  };
193
201
  function CredentialForm({ mode, onModeChange, values, onChange, errors = {}, submitting = false, onSubmit, title, body, before, after, labels, passwordRules, passwordAside, open = true, id = "xc-credential-fields" }) {
202
+ useAuthStyles();
194
203
  const [showPassword, setShowPassword] = useState3(false);
195
204
  const [passwordFocused, setPasswordFocused] = useState3(false);
196
205
  const showRules = mode === "signup" && !!passwordRules?.length && (passwordFocused || values.password.length > 0);
@@ -217,35 +226,37 @@ function CredentialForm({ mode, onModeChange, values, onChange, errors = {}, sub
217
226
  }
218
227
  ),
219
228
  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
229
+ /* @__PURE__ */ jsx5("div", { className: "xc-cred__collapse", "data-open": open, id, "aria-hidden": !open, children: /* @__PURE__ */ jsxs5("div", { className: "xc-cred__collapseIn", children: [
230
+ /* @__PURE__ */ jsxs5("div", { className: "xc-cred__fields", children: [
231
+ /* @__PURE__ */ jsx5("div", { className: "xc-cred__reveal", "data-open": mode === "signup", "aria-hidden": mode !== "signup", children: /* @__PURE__ */ jsxs5("label", { className: "xc-cred__field", children: [
232
+ /* @__PURE__ */ jsx5("span", { className: "xc-cred__label", children: t.name }),
233
+ /* @__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" }),
234
+ errors.name && /* @__PURE__ */ jsx5("span", { className: "xc-cred__error", role: "alert", children: errors.name })
235
+ ] }) }),
236
+ /* @__PURE__ */ jsxs5("label", { className: "xc-cred__field", children: [
237
+ /* @__PURE__ */ jsx5("span", { className: "xc-cred__label", children: t.email }),
238
+ /* @__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" }),
239
+ errors.email && /* @__PURE__ */ jsx5("span", { className: "xc-cred__error", role: "alert", children: errors.email })
235
240
  ] }),
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
- ] })
241
+ /* @__PURE__ */ jsxs5("label", { className: "xc-cred__field", children: [
242
+ /* @__PURE__ */ jsxs5("span", { className: "xc-cred__labelRow", children: [
243
+ /* @__PURE__ */ jsx5("span", { className: "xc-cred__label", children: t.password }),
244
+ passwordAside
245
+ ] }),
246
+ /* @__PURE__ */ jsxs5("span", { className: "xc-cred__pw", children: [
247
+ /* @__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", tabIndex: open ? 0 : -1, autoComplete: mode === "signup" ? "new-password" : "current-password", required: true, "aria-invalid": !!errors.password, "data-field": "password" }),
248
+ /* @__PURE__ */ jsx5(Button2, { type: "button", variant: "ghost", size: "sm", className: "xc-cred__eye", tabIndex: open ? 0 : -1, onClick: () => setShowPassword((v) => !v), "aria-label": showPassword ? "Hide password" : "Show password", "aria-pressed": showPassword, children: showPassword ? "Hide" : "Show" })
249
+ ] }),
250
+ errors.password && /* @__PURE__ */ jsx5("span", { className: "xc-cred__error", role: "alert", children: errors.password }),
251
+ 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: [
252
+ /* @__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" }) }),
253
+ /* @__PURE__ */ jsx5("span", { children: r.label })
254
+ ] }, r.label)) })
255
+ ] })
256
+ ] }),
257
+ errors.form && /* @__PURE__ */ jsx5("p", { className: "xc-form__warn", role: "alert", children: errors.form }),
258
+ /* @__PURE__ */ jsx5("div", { className: "xc-form__actions", children: /* @__PURE__ */ jsx5(Button2, { type: "submit", variant: "primary", size: "lg", busy: submitting, tabIndex: open ? 0 : -1, "data-intent": "submit", children: mode === "signin" ? t.submitSignIn : t.submitSignUp }) })
246
259
  ] }) }),
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
260
  after && /* @__PURE__ */ jsx5("div", { className: "xc-cred__after", children: after })
250
261
  ] });
251
262
  }
@@ -265,18 +276,21 @@ function Mark({ p }) {
265
276
  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" }) });
266
277
  }
267
278
  function SocialSignIn({ providers = ["google", "github", "x"], onSelect, disabled }) {
279
+ useAuthStyles();
268
280
  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
281
  }
270
282
 
271
283
  // src/auth/OrDivider.tsx
272
284
  import { jsx as jsx7 } from "react/jsx-runtime";
273
285
  function OrDivider({ children = "or continue with email" }) {
286
+ useAuthStyles();
274
287
  return /* @__PURE__ */ jsx7("div", { className: "xc-or", role: "separator", "aria-label": children, children: /* @__PURE__ */ jsx7("span", { className: "xc-or__label", children }) });
275
288
  }
276
289
 
277
290
  // src/auth/EmailDisclosure.tsx
278
291
  import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
279
292
  function EmailDisclosure({ open, onToggle, controls, openLabel = "hide email sign-in", closedLabel = "or continue with email" }) {
293
+ useAuthStyles();
280
294
  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
295
  open ? openLabel : closedLabel,
282
296
  /* @__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" }) })
@@ -303,6 +317,46 @@ var CREDENTIAL_STATES = [
303
317
  { id: "collapsed", props: { mode: "signin", values: empty, open: false, title: "Welcome back", body: "Enter your credentials to access your account" } },
304
318
  { 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
319
  ];
320
+
321
+ // src/auth/gallery.tsx
322
+ import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
323
+ var noop = () => {
324
+ };
325
+ function gallery() {
326
+ const out = [];
327
+ const hero = {
328
+ headline: /* @__PURE__ */ jsxs8(Fragment2, { children: [
329
+ "One account.",
330
+ /* @__PURE__ */ jsx9("br", {}),
331
+ "Every XENO product."
332
+ ] }),
333
+ lead: "Sign in once and XENO Canvas \u2014 and every other XENO app \u2014 runs under your plan, your credits and your files.",
334
+ stats: [{ value: "1", label: "Account" }, { value: "Yours", label: "Files stay put" }, { value: "Browser", label: "Sign-in, never in-app" }]
335
+ };
336
+ for (const s of STATES) {
337
+ out.push({ family: "auth", unit: "SignInForm", state: s.id, stage: "form", render: () => /* @__PURE__ */ jsx9(SignInForm, { ...s.props, phase: s.id }) });
338
+ out.push({ family: "auth", unit: "AuthDoor", state: s.id, stage: "door", render: () => /* @__PURE__ */ jsx9(AuthDoor, { label: `XENO Canvas sign-in \u2014 ${s.id}`, hero, rootAttributes: { "data-xeno-auth-gate": s.id }, children: /* @__PURE__ */ jsx9(SignInForm, { ...s.props, phase: s.id }) }) });
339
+ }
340
+ for (const s of CREDENTIAL_STATES) {
341
+ out.push({ family: "auth", unit: "CredentialForm", state: s.id, stage: "form", render: () => /* @__PURE__ */ jsx9(CredentialForm, { ...s.props, id: `gallery-${s.id}`, onModeChange: noop, onChange: noop, onSubmit: noop, before: /* @__PURE__ */ jsxs8(Fragment2, { children: [
342
+ /* @__PURE__ */ jsx9(SocialSignIn, { onSelect: noop }),
343
+ /* @__PURE__ */ jsx9(EmailDisclosure, { open: s.props.open !== false, onToggle: noop, controls: `gallery-${s.id}` })
344
+ ] }) }) });
345
+ }
346
+ out.push({ family: "auth", unit: "SocialSignIn", state: "default", stage: "form", render: () => /* @__PURE__ */ jsx9(SocialSignIn, { onSelect: noop }) });
347
+ out.push({ family: "auth", unit: "SocialSignIn", state: "disabled", stage: "form", render: () => /* @__PURE__ */ jsx9(SocialSignIn, { onSelect: noop, disabled: true }) });
348
+ const region = (id, open) => /* @__PURE__ */ jsx9("div", { id, hidden: !open, className: "xc-form__note", children: "the email form" });
349
+ out.push({ family: "auth", unit: "EmailDisclosure", state: "closed", stage: "form", render: () => /* @__PURE__ */ jsxs8(Fragment2, { children: [
350
+ /* @__PURE__ */ jsx9(EmailDisclosure, { open: false, onToggle: noop, controls: "gallery-disclosure-closed" }),
351
+ region("gallery-disclosure-closed", false)
352
+ ] }) });
353
+ out.push({ family: "auth", unit: "EmailDisclosure", state: "open", stage: "form", render: () => /* @__PURE__ */ jsxs8(Fragment2, { children: [
354
+ /* @__PURE__ */ jsx9(EmailDisclosure, { open: true, onToggle: noop, controls: "gallery-disclosure-open" }),
355
+ region("gallery-disclosure-open", true)
356
+ ] }) });
357
+ out.push({ family: "auth", unit: "OrDivider", state: "default", stage: "form", render: () => /* @__PURE__ */ jsx9(OrDivider, {}) });
358
+ return out;
359
+ }
306
360
  export {
307
361
  AuthDoor,
308
362
  AuthHero,
@@ -315,5 +369,6 @@ export {
315
369
  SignInForm,
316
370
  SocialSignIn,
317
371
  XenoMark,
318
- ensureAuthStyles
372
+ ensureAuthStyles,
373
+ gallery
319
374
  };
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.3.0";
6
+ declare const COMPONENTS_VERSION = "0.4.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.3.0";
2
+ var COMPONENTS_VERSION = "0.4.0";
3
3
  export {
4
4
  COMPONENTS_VERSION
5
5
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xenosystem/components",
3
- "version": "0.3.0",
3
+ "version": "0.4.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",