@xenosystem/components 0.1.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.
package/README.md ADDED
@@ -0,0 +1,13 @@
1
+ # @xenosystem/components
2
+
3
+ Rung 2 of the XENO front ladder (`XENO FRONT-END HIERARCHY.md`): molecules composed from
4
+ `@xenosystem/elements-react`, props in, callbacks out, **no host contract** and **no visual value of
5
+ its own** — every colour, radius and spacing is a `--xeno-*` token (`test/no-chrome.test.ts` fails
6
+ on a hex literal).
7
+
8
+ | family | import | components | extracted from |
9
+ |---|---|---|---|
10
+ | auth | `@xenosystem/components/auth` | `SignInForm`, `LicencePlate`, `AuthHero`, `AuthDoor` | `@xenosystem/panel-auth-gate` (Hub's login design) |
11
+
12
+ Each family exports a `STATES` fixture list the harness renders, so every state is seen before a
13
+ panel uses it. Import `@xenosystem/elements-react/xeno-elements.css` once in the host.
@@ -0,0 +1,67 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+ import { ButtonVariant } from '@xenosystem/elements-react';
4
+
5
+ declare function ensureAuthStyles(): void;
6
+ declare function XenoMark(): ReactNode;
7
+ interface AuthHeroStat {
8
+ value: string;
9
+ label: string;
10
+ }
11
+ interface AuthHeroProps {
12
+ headline: ReactNode;
13
+ lead?: ReactNode;
14
+ stats?: AuthHeroStat[];
15
+ imageSrc?: string;
16
+ /** `banner` is the short form (the square aspect): brand + headline only. */
17
+ compact?: boolean;
18
+ }
19
+ declare function AuthHero({ headline, lead, stats, imageSrc, compact }: AuthHeroProps): react.JSX.Element;
20
+ interface SignInAction {
21
+ id: string;
22
+ label: string;
23
+ /** `primary` is the one white button; `quiet` a text-weight one. */
24
+ variant?: ButtonVariant;
25
+ disabled?: boolean;
26
+ onClick?: () => void;
27
+ }
28
+ interface SignInFormProps {
29
+ /** A small uppercase tag above the heading — `No active plan`, `Update required`. */
30
+ tag?: string;
31
+ title: ReactNode;
32
+ body: ReactNode;
33
+ actions: SignInAction[];
34
+ /** Explanatory note under the actions (where the credential is entered, etc). */
35
+ note?: ReactNode;
36
+ /** A warning line — the host's own sentence about what went wrong. */
37
+ warning?: ReactNode;
38
+ /** Who is signed in, when known — display only. */
39
+ identity?: string;
40
+ /** The entrance fade; `false` renders settled (tests, screenshots). */
41
+ animate?: boolean;
42
+ /** Diagnostic hook the panel sets to its phase; carried to the DOM as `data-phase`. */
43
+ phase?: string;
44
+ }
45
+ declare function SignInForm({ tag, title, body, actions, note, warning, identity, animate, phase }: SignInFormProps): react.JSX.Element;
46
+ /** The licence plates are the same form with a tag; kept as a name so a panel reads honestly. */
47
+ declare const LicencePlate: typeof SignInForm;
48
+ interface AuthDoorProps {
49
+ hero: Omit<AuthHeroProps, 'compact'>;
50
+ /** Aria label of the dialog. */
51
+ label: string;
52
+ children: ReactNode;
53
+ /** Diagnostic: carried to the root as `data-xeno-auth-gate` by the panel. */
54
+ rootAttributes?: Record<string, string | undefined>;
55
+ }
56
+ /**
57
+ * Hub's three aspect modes, measured on the door's OWN box — in a product the door is `inset: 0`
58
+ * and the two agree; in a harness it is a tile, and a component lays itself out for the box it
59
+ * was given.
60
+ */
61
+ declare function AuthDoor({ hero, label, children, rootAttributes }: AuthDoorProps): react.JSX.Element;
62
+ declare const STATES: {
63
+ id: string;
64
+ props: SignInFormProps;
65
+ }[];
66
+
67
+ export { AuthDoor, type AuthDoorProps, AuthHero, type AuthHeroProps, type AuthHeroStat, LicencePlate, STATES, type SignInAction, SignInForm, type SignInFormProps, XenoMark, ensureAuthStyles };
@@ -0,0 +1,146 @@
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";
5
+ var STYLE_ID = "xeno-components-auth";
6
+ var CSS = `
7
+ .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}
8
+ .xc-auth *{box-sizing:border-box;margin:0}
9
+ .xc-auth[data-aspect=landscape]{flex-direction:row}
10
+ .xc-auth[data-aspect=square],.xc-auth[data-aspect=portrait]{flex-direction:column}
11
+ .xc-auth__heroWrap{position:relative;overflow:hidden;flex:none}
12
+ .xc-auth[data-aspect=landscape] .xc-auth__heroWrap{width:55%;height:100%}
13
+ .xc-auth[data-aspect=square] .xc-auth__heroWrap{height:200px;width:100%}
14
+ .xc-hero{position:absolute;inset:24px;border-radius:var(--xeno-radius-card);overflow:hidden;background:var(--xeno-surface)}
15
+ .xc-auth[data-aspect=square] .xc-hero{inset:16px}
16
+ .xc-hero__img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;user-select:none}
17
+ .xc-hero__in{position:relative;z-index:1;display:flex;flex-direction:column;justify-content:space-between;height:100%;padding:48px}
18
+ .xc-auth[data-aspect=square] .xc-hero__in{padding:32px}
19
+ .xc-brand{display:flex;align-items:center;gap:12px}
20
+ .xc-brand svg{width:32px;height:32px}
21
+ .xc-auth[data-aspect=square] .xc-brand svg,.xc-auth__head .xc-brand svg{width:20px;height:20px}
22
+ .xc-brand__word{font-size:13px;font-weight:700;letter-spacing:.2em;text-transform:uppercase}
23
+ .xc-auth[data-aspect=square] .xc-brand__word,.xc-auth__head .xc-brand__word{font-size:11px}
24
+ .xc-hero__h1{font-size:3.5rem;font-weight:700;line-height:1.1;letter-spacing:-.02em;max-width:32rem}
25
+ .xc-auth[data-aspect=square] .xc-hero__h1{font-size:2rem}
26
+ .xc-hero__lead{margin-top:16px;max-width:24rem;font-size:16px;line-height:1.6}
27
+ .xc-hero__stats{display:flex;align-items:center;gap:32px}
28
+ .xc-hero__stat b{display:block;font-size:24px;font-weight:700}
29
+ .xc-hero__stat span{display:block;margin-top:2px;font-size:13px;font-weight:500}
30
+ .xc-hero__sep{width:1px;height:32px;background:var(--xeno-border)}
31
+ .xc-auth__head{display:flex;align-items:center;height:56px;padding:0 24px;flex:none}
32
+ .xc-auth__side{flex:1;display:flex;flex-direction:column;min-width:0;min-height:0;overflow-y:auto}
33
+ .xc-auth__center{flex:1;display:flex;align-items:center;justify-content:center;padding:0 32px}
34
+ .xc-form{width:100%;max-width:340px;opacity:0;transform:translateY(16px);transition:opacity var(--xeno-dur-entrance,.7s) var(--xeno-ease),transform var(--xeno-dur-entrance,.7s) var(--xeno-ease)}
35
+ .xc-form[data-visible=true]{opacity:1;transform:none}
36
+ .xc-form__tag{display:inline-block;margin-bottom:14px;padding:3px 8px;border:1px solid var(--xeno-border);border-radius:var(--xeno-radius-xs);font-size:10px;font-weight:600;letter-spacing:.12em;text-transform:uppercase;color:var(--xeno-muted)}
37
+ .xc-form__h2{font-size:28px;font-weight:700;letter-spacing:-.02em;line-height:1.2}
38
+ .xc-form__sub{margin-top:8px;font-size:15px;font-weight:500;line-height:1.6;color:var(--xeno-muted)}
39
+ .xc-form__actions{margin-top:32px;display:flex;flex-direction:column;gap:12px}
40
+ .xc-form__actions > *{width:100%}
41
+ .xc-form__note{margin-top:16px;font-size:12px;line-height:1.6;color:var(--xeno-muted)}
42
+ .xc-form__warn{margin-top:16px;font-size:12px;line-height:1.6;color:var(--xeno-danger)}
43
+ .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
+ .xc-form__who b{color:var(--xeno-text);font-weight:500}
45
+ `;
46
+ function ensureAuthStyles() {
47
+ if (typeof document === "undefined" || document.getElementById(STYLE_ID)) return;
48
+ const style = document.createElement("style");
49
+ style.id = STYLE_ID;
50
+ style.textContent = CSS;
51
+ document.head.appendChild(style);
52
+ }
53
+ function XenoMark() {
54
+ return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 1082 1082", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", children: [
55
+ /* @__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" }),
56
+ /* @__PURE__ */ jsx("path", { d: "M861.765 489.52L1041.69 323.704L1050.94 101.684C1053.03 51.6793 1013.87 39.5273 994.024 39.7019L770.9 30.3995L426.135 348.133L543.8 476.263L798.083 241.917C835.108 207.796 841.408 222.184 839.931 233.644L830.674 455.664L861.765 489.52Z", fill: "currentColor" }),
57
+ /* @__PURE__ */ jsx("path", { d: "M592.871 862.143L758.514 1042.2L980.322 1051.45C1030.28 1053.53 1042.42 1014.33 1042.25 994.477L1051.56 771.171L734.155 426.15L606.138 543.919L840.241 798.392C874.327 835.444 859.952 841.751 848.504 840.272L626.696 831.025L592.871 862.143Z", fill: "currentColor" }),
58
+ /* @__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
+ ] });
60
+ }
61
+ 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" })
68
+ ] }),
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 })
73
+ ] }),
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 })
79
+ ] })
80
+ ] }, s.label)) })
81
+ ] })
82
+ ] })
83
+ ] });
84
+ }
85
+ function SignInForm({ tag, title, body, actions, note, warning, identity, animate = true, phase }) {
86
+ const [visible, setVisible] = useState(!animate);
87
+ useEffect(() => {
88
+ if (!animate) return;
89
+ const t = window.setTimeout(() => setVisible(true), 100);
90
+ return () => window.clearTimeout(t);
91
+ }, [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: [
100
+ "Signed in as ",
101
+ /* @__PURE__ */ jsx("b", { children: identity })
102
+ ] })
103
+ ] });
104
+ }
105
+ var LicencePlate = SignInForm;
106
+ var aspectOf = (w, h) => h <= 0 ? "landscape" : w / h > 1.2 ? "landscape" : w / h < 0.85 ? "portrait" : "square";
107
+ function AuthDoor({ hero, label, children, rootAttributes }) {
108
+ const ref = useRef(null);
109
+ const [aspect, setAspect] = useState("landscape");
110
+ useEffect(() => {
111
+ ensureAuthStyles();
112
+ const el = ref.current;
113
+ if (!el || typeof ResizeObserver === "undefined") return;
114
+ const ro = new ResizeObserver(([entry]) => {
115
+ const { width, height } = entry.contentRect;
116
+ setAspect(aspectOf(width, height));
117
+ });
118
+ ro.observe(el);
119
+ return () => ro.disconnect();
120
+ }, []);
121
+ const brand = /* @__PURE__ */ jsxs("div", { className: "xc-brand", children: [
122
+ /* @__PURE__ */ jsx(XenoMark, {}),
123
+ /* @__PURE__ */ jsx("span", { className: "xc-brand__word", children: "XENO" })
124
+ ] });
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 }) })
128
+ ] });
129
+ }
130
+ var STATES = [
131
+ { 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
+ { 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 } },
133
+ { id: "unavailable", props: { tag: "Sign-in unavailable", title: "Can\u2019t reach your account", body: "The OS keystore is not available on this machine, so a sign-in could not be kept.", actions: [{ id: "refresh", label: "Try again", variant: "primary" }], animate: false } },
134
+ { id: "unlicensed", props: { tag: "No active plan", title: "XENO Canvas needs a plan", body: "Your account is on the Free plan. XENO Canvas is included from Creator.", actions: [{ id: "manageBilling", label: "Choose a plan", variant: "primary" }, { id: "refresh", label: "Check again", variant: "quiet" }, { id: "signOut", label: "Sign out", variant: "quiet" }], identity: "emilian@bnkrsys.com", animate: false } },
135
+ { 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
+ { 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
+ ];
138
+ export {
139
+ AuthDoor,
140
+ AuthHero,
141
+ LicencePlate,
142
+ STATES,
143
+ SignInForm,
144
+ XenoMark,
145
+ ensureAuthStyles
146
+ };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * `@xenosystem/components` — rung 2 of the FRONT ladder (`XENO FRONT-END HIERARCHY.md`).
3
+ * Families are subpaths: `@xenosystem/components/auth`, `/navigation`, `/feedback`, `/data`.
4
+ * This root re-exports nothing on purpose: import the family you use, so a bundle carries only it.
5
+ */
6
+ declare const COMPONENTS_VERSION = "0.1.0";
7
+
8
+ export { COMPONENTS_VERSION };
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ // src/index.ts
2
+ var COMPONENTS_VERSION = "0.1.0";
3
+ export {
4
+ COMPONENTS_VERSION
5
+ };
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@xenosystem/components",
3
+ "version": "0.1.0",
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
+ "license": "UNLICENSED",
6
+ "type": "module",
7
+ "sideEffects": false,
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" }
11
+ },
12
+ "files": ["dist", "README.md"],
13
+ "scripts": {
14
+ "build": "tsup",
15
+ "typecheck": "tsc --noEmit",
16
+ "test": "vitest run"
17
+ },
18
+ "peerDependencies": {
19
+ "@xenosystem/elements-react": ">=0.0.1",
20
+ "react": ">=18.0.0",
21
+ "react-dom": ">=18.0.0"
22
+ },
23
+ "devDependencies": {
24
+ "@types/node": "^22.0.0",
25
+ "@types/react": "^19.0.0",
26
+ "@types/react-dom": "^19.0.0",
27
+ "@xenosystem/elements-react": "0.0.1",
28
+ "react": "^19.0.0",
29
+ "react-dom": "^19.0.0",
30
+ "tsup": "^8.0.0",
31
+ "typescript": "^5.4.0",
32
+ "vitest": "^4.1.0"
33
+ },
34
+ "publishConfig": { "access": "public" }
35
+ }