@agentaily/design-system 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.
package/DESIGN.md CHANGED
@@ -98,7 +98,7 @@ Examples — ✓ "Rate limited. Retry in 18s." ✗ "Oops! Something went wrong
98
98
  | `components/code/` | **Terminal, FileTree, Snippet, StackTrace, TestResults, Artifact, WebPreview, Agent, Commit, EnvironmentVariables, PackageInfo, Sandbox, SchemaDisplay, JSXPreview** |
99
99
  | `components/voice/` | **AudioPlayer, MicSelector, VoiceSelector, SpeechInput, Transcription, Persona** |
100
100
  | `components/workflow/` | **Flow (Canvas), Canvas, Node, Edge, Connection, Controls, Panel, Toolbar** |
101
- | `components/utilities/` | **Image, OpenInChat, Icon (unified Lucide set), BrandMark** |
101
+ | `components/utilities/` | **Image, OpenInChat, Icon (unified Lucide set), BrandMark, RotatingTagline** |
102
102
  | `components/settings/` | **TestRow, HelpSteps, IntegrationSettings** — connection-card primitives + the DeepSeek/Feishu integration modal |
103
103
  | `components/auth/` | **AuthDialog (+ AuthDialog.useAuth), AccountControl, SignInPage** — sign-in/register modal + persisted session + top-bar account menu + full-page sign-in |
104
104
  | `components/review/` | **MarkupLayer** — point-at-an-element review overlay (`data-mk-label`) |
@@ -108,7 +108,7 @@ Examples — ✓ "Rate limited. Retry in 18s." ✗ "Oops! Something went wrong
108
108
  | `ui_kits/docs/` | Documentation site (interactive) |
109
109
  | `SKILL.md` | Agent-skill entry point |
110
110
 
111
- Every component ships `<Name>.jsx` + `<Name>.d.ts` (props) + `<Name>.prompt.md` (usage) — **145 component exports** across the primitive categories (buttons, inputs, display, feedback, overlay, layout, chat, ai, code, voice, workflow, utilities) plus product-domain layers (**settings, auth, review**). Full-page frames — `AppShell` / `DesignerShell` / `DocsLayout` / `SettingsPage` (layout), `ConversationThread` (chat), `SignInPage` (auth) — are **live components, not copy-templates**: change one, every consuming project benefits on re-sync. Consume via the compiled bundle: `window.AxiomDesignSystem_7fc962`. Read each component's `.prompt.md` for copy-paste usage.
111
+ Every component ships `<Name>.jsx` + `<Name>.d.ts` (props) + `<Name>.prompt.md` (usage) — **146 component exports** across the primitive categories (buttons, inputs, display, feedback, overlay, layout, chat, ai, code, voice, workflow, utilities) plus product-domain layers (**settings, auth, review**). Full-page frames — `AppShell` / `DesignerShell` / `DocsLayout` / `SettingsPage` (layout), `ConversationThread` (chat), `SignInPage` (auth) — are **live components, not copy-templates**: change one, every consuming project benefits on re-sync. Consume via the compiled bundle: `window.AxiomDesignSystem_7fc962`. Read each component's `.prompt.md` for copy-paste usage.
112
112
 
113
113
  **Forms are layered, not monolithic.** Presentational controls (Input/Select/Field…) own layout and never depend on a form engine. `Form` + `FormActions` add pure structure. `Form.useForm` is an **optional**, zero-dependency orchestration hook (values/errors/touched/validate/submit) exposed off the capitalized `Form` export — drop it for react-hook-form or TanStack and the controls still work. Errors surface only after blur or submit; spread `form.field(name)` onto any value control, or `form.field(name, {type:"checkbox"})` for boolean ones.
114
114
 
@@ -121,7 +121,7 @@ The system sits on **two orthogonal axes**. Don't conflate them: a *domain* is n
121
121
  | Layer | What | Depends on | Examples |
122
122
  |---|---|---|---|
123
123
  | **L0 Tokens** | colour / type / space / radius / shadow / motion variables | — | `tokens/*.css` |
124
- | **L1 Primitives** (atoms) | no business meaning, usable anywhere | tokens | `Button`, `Input`, `Badge`, `Icon`, `BrandMark` |
124
+ | **L1 Primitives** (atoms) | no business meaning, usable anywhere | tokens | `Button`, `Input`, `Badge`, `Icon`, `BrandMark`, `RotatingTagline` |
125
125
  | **L2 Composites** (molecules) | a few primitives combined; still cross-product | L1 | `Composer`, `SecretField`, `StatusPill` |
126
126
  | **L3 Patterns** (organisms) | self-contained interaction + state, configurable | L1–L2 | `AuthDialog`, `IntegrationSettings`, `MarkupLayer`, `ConversationThread` |
127
127
  | **L4 Page shells / frames** | full-page layout with slots | L1–L3 | `AppShell`, `DesignerShell`, `DocsLayout`, `SettingsPage`, `SignInPage` |
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # agentaily design system
2
2
 
3
- Agentaily(AI chatbot)设计系统:112 个 React 组件 + Storybook,单色亮色优先(暗色可切)。品牌一句话:**极客风格,简约,大气,科技感**。
3
+ Agentaily(AI chatbot)设计系统:113 个 React 组件 + Storybook,单色亮色优先(暗色可切)。品牌一句话:**极客风格,简约,大气,科技感**。
4
4
 
5
5
  📖 **在线 Storybook:** https://agentaily.github.io/design-system/
6
6
 
@@ -28,7 +28,7 @@ npm run build:lib # 产出 dist/:每组件一个 .js + index.d.ts + styles.css
28
28
 
29
29
  | 路径 | 内容 |
30
30
  | ----------------------------------------------- | ----------------------------------------- |
31
- | `dist/index.js` | ESM 入口,re-export 全部 112 个组件符号 |
31
+ | `dist/index.js` | ESM 入口,re-export 全部 113 个组件符号 |
32
32
  | `dist/components/**/*.js` | 每个组件独立模块(含运行时 CSS 注入) |
33
33
  | `dist/index.d.ts` + `dist/components/**/*.d.ts` | TypeScript 类型契约 |
34
34
  | `dist/styles.css` | 内联好的 tokens + 字体,消费方 import 一次 |
@@ -15,6 +15,21 @@ export interface SignInPageCopy {
15
15
  /** Footer link label that flips mode, e.g. "Create one" */
16
16
  switchCta?: string;
17
17
  }
18
+ /** Shared, non-mode-specific strings. All optional — merged over the English defaults. */
19
+ export interface SignInPageSharedCopy {
20
+ /** Field labels + chrome: { email, password, confirm, forgot, or, sso }. */
21
+ labels?: { email?: string; password?: string; confirm?: string; forgot?: string; or?: string; sso?: string };
22
+ /** Input placeholders: { email, password, passwordNew, confirm }. */
23
+ placeholders?: { email?: string; password?: string; passwordNew?: string; confirm?: string };
24
+ /** Validation messages: { emailRequired, emailInvalid, passwordRequired, passwordShort, confirmRequired, confirmMismatch }. */
25
+ errors?: {
26
+ emailRequired?: string; emailInvalid?: string;
27
+ passwordRequired?: string; passwordShort?: string;
28
+ confirmRequired?: string; confirmMismatch?: string;
29
+ };
30
+ /** Default signup terms line (string). Overridden by the `terms` prop if passed. */
31
+ terms?: React.ReactNode;
32
+ }
18
33
  export interface SignInPageProps {
19
34
  /** Controlled mode. Omit for uncontrolled (uses defaultMode). */
20
35
  mode?: "signin" | "signup";
@@ -24,8 +39,14 @@ export interface SignInPageProps {
24
39
  onModeChange?: (mode: "signin" | "signup") => void;
25
40
  /** Brand-panel lockup. @default <BrandMark wordmark /> (blinking cursor) */
26
41
  brand?: React.ReactNode;
27
- /** Per-mode copy overrides, merged over defaults: { signin?, signup? }. */
28
- copy?: { signin?: SignInPageCopy; signup?: SignInPageCopy };
42
+ /**
43
+ * All user-facing strings, deep-merged over the English defaults. Per-mode
44
+ * copy under `signin`/`signup`; shared labels, placeholders, errors, and the
45
+ * terms line at the top level. Pass a full zh-CN object to localize.
46
+ */
47
+ copy?: { signin?: SignInPageCopy; signup?: SignInPageCopy } & SignInPageSharedCopy;
48
+ /** Props forwarded to the brand-panel RotatingTagline (prefix, phrases, flowDuration…). @default { breakAfterPrefix: true } */
49
+ tagline?: Record<string, unknown>;
29
50
  email?: string;
30
51
  password?: string;
31
52
  onEmailChange?: (value: string) => void;
@@ -34,7 +55,7 @@ export interface SignInPageProps {
34
55
  onSubmit?: (values: { mode: "signin" | "signup"; email: string; password: string }) => void;
35
56
  /** Show + handle the "Forgot?" link (signin mode only). */
36
57
  onForgot?: () => void;
37
- /** @default "Continue with SSO" */
58
+ /** SSO button label. Overrides copy.labels.sso. @default "Continue with SSO" */
38
59
  ssoLabel?: string;
39
60
  /** Show + handle the SSO button (hidden when omitted). */
40
61
  onSSO?: () => void;
@@ -1,20 +1,23 @@
1
- import { jsxs, jsx, Fragment } from "react/jsx-runtime";
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
2
  import React, { useState } from "react";
3
- import { BrandMark } from "../utilities/BrandMark.js";
3
+ import { Input } from "../inputs/Input.js";
4
4
  import { Button } from "../buttons/Button.js";
5
5
  import { Icon } from "../utilities/Icon.js";
6
- import { Input } from "../inputs/Input.js";
6
+ import { BrandMark } from "../utilities/BrandMark.js";
7
+ import { RotatingTagline } from "../utilities/RotatingTagline.js";
7
8
  const AX_SIGNIN_CSS = `
8
9
  .ax-signin { display: grid; grid-template-columns: 1fr 1fr; height: 100%; background: var(--surface-page); }
9
10
  .ax-signin__brand { position: relative; border-right: 1px solid var(--border-default); background: var(--surface-panel);
10
11
  background-image: var(--dot-grid); background-size: 24px 24px; padding: 40px; display: flex; flex-direction: column; }
11
12
  .ax-signin__brandtop { display: flex; align-items: center; gap: 10px; }
12
- .ax-signin__mid { margin-top: auto; margin-bottom: auto; max-width: 22ch; }
13
- .ax-signin__quote { font-family: var(--font-display); font-size: var(--text-2xl); font-weight: var(--weight-medium); line-height: var(--leading-tight); letter-spacing: var(--tracking-tight); color: var(--text-body); margin: 0; }
13
+ .ax-signin__mid { margin-top: auto; margin-bottom: auto; }
14
+ .ax-signin__quote { font-family: var(--font-display); white-space: nowrap; font-size: clamp(40px, 4.4vw, var(--text-hero)); font-weight: var(--weight-medium); line-height: var(--leading-tight); letter-spacing: var(--tracking-tight); color: var(--text-body); margin: 0; }
14
15
  .ax-signin__by { margin: 16px 0 0; font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.04em; color: var(--text-faint); }
15
16
  .ax-signin__form { display: flex; align-items: center; justify-content: center; padding: 40px; }
16
17
  .ax-signin__card { width: 100%; max-width: 360px; }
17
18
  .ax-signin__mbrand { display: none; }
19
+ .ax-signin__mtag { display: none; }
20
+ .ax-signin__mtag .ax-signin__quote { font-size: var(--text-3xl); }
18
21
  .ax-signin__h { margin: 0; font-family: var(--font-display); font-size: var(--text-2xl); font-weight: var(--weight-bold); letter-spacing: var(--tracking-tight); color: var(--text-body); }
19
22
  .ax-signin__sub { margin: 8px 0 28px; font-size: var(--text-sm); color: var(--text-muted); }
20
23
  .ax-signin__fields { display: flex; flex-direction: column; gap: 16px; }
@@ -34,7 +37,7 @@ const AX_SIGNIN_CSS = `
34
37
  .ax-signin__footlink { color: var(--text-body); background: none; border: none; cursor: pointer; font-family: inherit; font-size: var(--text-sm);
35
38
  border-bottom: 1px solid var(--border-strong); padding: 0 0 1px; transition: border-color var(--dur-1) var(--ease-out); }
36
39
  .ax-signin__footlink:hover { border-color: var(--text-faint); }
37
- @media (max-width: 860px) { .ax-signin { grid-template-columns: 1fr; } .ax-signin__brand { display: none; } .ax-signin__mbrand { display: flex; margin-bottom: 26px; } }
40
+ @media (max-width: 860px) { .ax-signin { grid-template-columns: 1fr; } .ax-signin__brand { display: none; } .ax-signin__form { padding: 56px 40px; } .ax-signin__mbrand { display: flex; margin-bottom: 28px; } .ax-signin__mtag { display: block; margin-bottom: 36px; } .ax-signin__h { font-family: var(--font-mono); font-size: var(--text-xs); font-weight: var(--weight-medium); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--text-body); margin: 0 0 8px; } .ax-signin__sub { margin: 0 0 32px; color: var(--text-muted); } .ax-signin__fields { gap: 22px; } .ax-signin__submit { margin-top: 32px; } .ax-signin__or { margin: 32px 0; } .ax-signin__foot { margin-top: 28px; } }
38
41
  `;
39
42
  if (typeof document !== "undefined" && !document.getElementById("ax-signin-css")) {
40
43
  const s = document.createElement("style");
@@ -56,7 +59,30 @@ const DEFAULT_COPY = {
56
59
  submit: "Create account",
57
60
  switchText: "Already have an account?",
58
61
  switchCta: "Sign in"
59
- }
62
+ },
63
+ labels: {
64
+ email: "EMAIL",
65
+ password: "PASSWORD",
66
+ confirm: "CONFIRM PASSWORD",
67
+ forgot: "Forgot?",
68
+ or: "OR",
69
+ sso: "Continue with SSO"
70
+ },
71
+ placeholders: {
72
+ email: "you@example.com",
73
+ password: "••••••••",
74
+ passwordNew: "At least 8 characters",
75
+ confirm: "Re-enter your password"
76
+ },
77
+ errors: {
78
+ emailRequired: "Enter your email",
79
+ emailInvalid: "That email doesn’t look right",
80
+ passwordRequired: "Enter your password",
81
+ passwordShort: "Password must be at least 8 characters",
82
+ confirmRequired: "Re-enter your password",
83
+ confirmMismatch: "Passwords don’t match"
84
+ },
85
+ terms: "By creating an account you agree to our Terms and Privacy Policy."
60
86
  };
61
87
  function SignInPage({
62
88
  mode,
@@ -64,13 +90,14 @@ function SignInPage({
64
90
  onModeChange,
65
91
  brand,
66
92
  copy,
93
+ tagline,
67
94
  email,
68
95
  password,
69
96
  onEmailChange,
70
97
  onPasswordChange,
71
98
  onSubmit,
72
99
  onForgot,
73
- ssoLabel = "Continue with SSO",
100
+ ssoLabel,
74
101
  onSSO,
75
102
  terms,
76
103
  footer,
@@ -80,6 +107,11 @@ function SignInPage({
80
107
  const m = mode !== void 0 ? mode : modeI;
81
108
  const isSignup = m === "signup";
82
109
  const c = { ...DEFAULT_COPY[m], ...copy && copy[m] };
110
+ const labels = { ...DEFAULT_COPY.labels, ...copy && copy.labels };
111
+ const ph = { ...DEFAULT_COPY.placeholders, ...copy && copy.placeholders };
112
+ const err = { ...DEFAULT_COPY.errors, ...copy && copy.errors };
113
+ const ssoText = ssoLabel !== void 0 ? ssoLabel : labels.sso;
114
+ const taglineProps = { breakAfterPrefix: true, ...tagline };
83
115
  const [emailI, setEmailI] = useState("");
84
116
  const [pwI, setPwI] = useState("");
85
117
  const [confirm, setConfirm] = useState("");
@@ -110,13 +142,13 @@ function SignInPage({
110
142
  const validate = () => {
111
143
  const e = {};
112
144
  const em = (emailV || "").trim();
113
- if (!em) e.email = "请输入邮箱";
114
- else if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(em)) e.email = "邮箱格式不正确";
115
- if (!pwV) e.password = "请输入密码";
116
- else if (isSignup && pwV.length < 8) e.password = "密码至少 8 位";
145
+ if (!em) e.email = err.emailRequired;
146
+ else if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(em)) e.email = err.emailInvalid;
147
+ if (!pwV) e.password = err.passwordRequired;
148
+ else if (isSignup && pwV.length < 8) e.password = err.passwordShort;
117
149
  if (isSignup) {
118
- if (!confirm) e.confirm = "请再次输入密码";
119
- else if (confirm !== pwV) e.confirm = "两次输入的密码不一致";
150
+ if (!confirm) e.confirm = err.confirmRequired;
151
+ else if (confirm !== pwV) e.confirm = err.confirmMismatch;
120
152
  }
121
153
  return e;
122
154
  };
@@ -127,38 +159,28 @@ function SignInPage({
127
159
  if (Object.keys(e).some((k) => e[k])) return;
128
160
  onSubmit && onSubmit({ mode: m, email: (emailV || "").trim(), password: pwV });
129
161
  };
130
- const termsNode = terms !== void 0 ? terms : /* @__PURE__ */ jsxs(Fragment, { children: [
131
- "注册即代表你同意 ",
132
- /* @__PURE__ */ jsx("span", { className: "lk", children: "服务条款" }),
133
- " 与",
134
- " ",
135
- /* @__PURE__ */ jsx("span", { className: "lk", children: "隐私政策" }),
136
- "。"
137
- ] });
162
+ const termsNode = terms !== void 0 ? terms : c.terms !== void 0 ? c.terms : DEFAULT_COPY.terms;
138
163
  return /* @__PURE__ */ jsxs("div", { className: "ax-signin", children: [
139
164
  showBrandPanel ? /* @__PURE__ */ jsxs("aside", { className: "ax-signin__brand", children: [
140
- /* @__PURE__ */ jsx("div", { className: "ax-signin__brandtop", children: brand || /* @__PURE__ */ jsx(BrandMark, { size: 24, wordmark: true }) }),
165
+ /* @__PURE__ */ jsx("div", { className: "ax-signin__brandtop", children: brand || /* @__PURE__ */ jsx(BrandMark, { size: 24, wordmark: true, blink: false }) }),
141
166
  /* @__PURE__ */ jsxs("div", { className: "ax-signin__mid", children: [
142
- /* @__PURE__ */ jsxs("p", { className: "ax-signin__quote", children: [
143
- "聊天,",
144
- /* @__PURE__ */ jsx("br", {}),
145
- "构建万物"
146
- ] }),
167
+ /* @__PURE__ */ jsx("p", { className: "ax-signin__quote", children: /* @__PURE__ */ jsx(RotatingTagline, { ...taglineProps }) }),
147
168
  /* @__PURE__ */ jsx("p", { className: "ax-signin__by", children: "— AGENTAILY" })
148
169
  ] })
149
170
  ] }) : null,
150
171
  /* @__PURE__ */ jsx("main", { className: "ax-signin__form", children: /* @__PURE__ */ jsxs("form", { className: "ax-signin__card", onSubmit: submit, noValidate: true, children: [
151
- /* @__PURE__ */ jsx("div", { className: "ax-signin__mbrand", children: brand || /* @__PURE__ */ jsx(BrandMark, { size: 22, wordmark: true }) }),
172
+ /* @__PURE__ */ jsx("div", { className: "ax-signin__mbrand", children: brand || /* @__PURE__ */ jsx(BrandMark, { size: 22, wordmark: true, blink: false }) }),
173
+ /* @__PURE__ */ jsx("div", { className: "ax-signin__mtag", children: /* @__PURE__ */ jsx("p", { className: "ax-signin__quote", children: /* @__PURE__ */ jsx(RotatingTagline, { ...taglineProps }) }) }),
152
174
  /* @__PURE__ */ jsx("h1", { className: "ax-signin__h", children: c.title }),
153
175
  /* @__PURE__ */ jsx("p", { className: "ax-signin__sub", children: c.subtitle }),
154
176
  /* @__PURE__ */ jsxs("div", { className: "ax-signin__fields", children: [
155
177
  /* @__PURE__ */ jsx(
156
178
  Input,
157
179
  {
158
- label: "EMAIL",
180
+ label: labels.email,
159
181
  type: "email",
160
182
  autoComplete: "email",
161
- placeholder: "you@example.com",
183
+ placeholder: ph.email,
162
184
  value: emailV,
163
185
  error: errs.email,
164
186
  onChange: (e) => setEmail(e.target.value)
@@ -166,15 +188,15 @@ function SignInPage({
166
188
  ),
167
189
  /* @__PURE__ */ jsxs("div", { children: [
168
190
  /* @__PURE__ */ jsxs("div", { className: "ax-signin__row", children: [
169
- /* @__PURE__ */ jsx("span", { className: "ax-label", children: "PASSWORD" }),
170
- !isSignup && onForgot ? /* @__PURE__ */ jsx("button", { type: "button", className: "ax-signin__link", onClick: onForgot, children: "Forgot?" }) : null
191
+ /* @__PURE__ */ jsx("span", { className: "ax-label", children: labels.password }),
192
+ !isSignup && onForgot ? /* @__PURE__ */ jsx("button", { type: "button", className: "ax-signin__link", onClick: onForgot, children: labels.forgot }) : null
171
193
  ] }),
172
194
  /* @__PURE__ */ jsx(
173
195
  Input,
174
196
  {
175
197
  type: "password",
176
198
  autoComplete: isSignup ? "new-password" : "current-password",
177
- placeholder: isSignup ? "至少 8 位" : "••••••••",
199
+ placeholder: isSignup ? ph.passwordNew : ph.password,
178
200
  value: pwV,
179
201
  error: errs.password,
180
202
  onChange: (e) => setPw(e.target.value)
@@ -184,10 +206,10 @@ function SignInPage({
184
206
  isSignup ? /* @__PURE__ */ jsx(
185
207
  Input,
186
208
  {
187
- label: "CONFIRM PASSWORD",
209
+ label: labels.confirm,
188
210
  type: "password",
189
211
  autoComplete: "new-password",
190
- placeholder: "再次输入密码",
212
+ placeholder: ph.confirm,
191
213
  value: confirm,
192
214
  error: errs.confirm,
193
215
  onChange: (e) => setConf(e.target.value)
@@ -197,10 +219,10 @@ function SignInPage({
197
219
  /* @__PURE__ */ jsx("div", { className: "ax-signin__submit", children: /* @__PURE__ */ jsx(Button, { variant: "primary", full: true, type: "submit", icon: /* @__PURE__ */ jsx(Icon, { name: "arrow", size: 15 }), children: c.submit }) }),
198
220
  isSignup ? /* @__PURE__ */ jsx("p", { className: "ax-signin__terms", children: termsNode }) : null,
199
221
  onSSO ? /* @__PURE__ */ jsxs(React.Fragment, { children: [
200
- /* @__PURE__ */ jsx("div", { className: "ax-signin__or", children: /* @__PURE__ */ jsx("span", { children: "OR" }) }),
222
+ /* @__PURE__ */ jsx("div", { className: "ax-signin__or", children: /* @__PURE__ */ jsx("span", { children: labels.or }) }),
201
223
  /* @__PURE__ */ jsxs("button", { type: "button", className: "ax-signin__sso", onClick: onSSO, children: [
202
224
  /* @__PURE__ */ jsx(Icon, { name: "shield", size: 15 }),
203
- ssoLabel
225
+ ssoText
204
226
  ] })
205
227
  ] }) : null,
206
228
  footer !== void 0 ? footer ? /* @__PURE__ */ jsx("p", { className: "ax-signin__foot", children: footer }) : null : /* @__PURE__ */ jsxs("p", { className: "ax-signin__foot", children: [
@@ -1 +1 @@
1
- {"version":3,"file":"SignInPage.js","sources":["../../../src/components/auth/SignInPage.jsx"],"sourcesContent":["import React, { useState } from \"react\";\nimport { BrandMark } from \"../utilities/BrandMark.jsx\";\nimport { Button } from \"../buttons/Button.jsx\";\nimport { Icon } from \"../utilities/Icon.jsx\";\nimport { Input } from \"../inputs/Input.jsx\";\n\n// SignInPage — full-page auth: a split brand panel (dot-grid + quote) beside a\n// centered card. One component, two modes (signin / signup) — the brand panel,\n// SSO, and card chrome are shared; only the fields, copy, and footer differ.\n// The footer link flips mode (and fires onModeChange so a router can swap the\n// URL). The full-page sibling of AuthDialog (the modal); same login/register idea.\nconst AX_SIGNIN_CSS = `\n.ax-signin { display: grid; grid-template-columns: 1fr 1fr; height: 100%; background: var(--surface-page); }\n.ax-signin__brand { position: relative; border-right: 1px solid var(--border-default); background: var(--surface-panel);\n background-image: var(--dot-grid); background-size: 24px 24px; padding: 40px; display: flex; flex-direction: column; }\n.ax-signin__brandtop { display: flex; align-items: center; gap: 10px; }\n.ax-signin__mid { margin-top: auto; margin-bottom: auto; max-width: 22ch; }\n.ax-signin__quote { font-family: var(--font-display); font-size: var(--text-2xl); font-weight: var(--weight-medium); line-height: var(--leading-tight); letter-spacing: var(--tracking-tight); color: var(--text-body); margin: 0; }\n.ax-signin__by { margin: 16px 0 0; font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.04em; color: var(--text-faint); }\n.ax-signin__form { display: flex; align-items: center; justify-content: center; padding: 40px; }\n.ax-signin__card { width: 100%; max-width: 360px; }\n.ax-signin__mbrand { display: none; }\n.ax-signin__h { margin: 0; font-family: var(--font-display); font-size: var(--text-2xl); font-weight: var(--weight-bold); letter-spacing: var(--tracking-tight); color: var(--text-body); }\n.ax-signin__sub { margin: 8px 0 28px; font-size: var(--text-sm); color: var(--text-muted); }\n.ax-signin__fields { display: flex; flex-direction: column; gap: 16px; }\n.ax-signin__row { display: flex; align-items: center; justify-content: space-between; margin-bottom: 7px; }\n.ax-signin__link { font-size: 12px; color: var(--text-muted); text-decoration: none; background: none; border: none; cursor: pointer; font-family: inherit; }\n.ax-signin__link:hover { color: var(--text-body); }\n.ax-signin__submit { margin-top: 22px; }\n.ax-signin__or { display: flex; align-items: center; gap: 12px; margin: 22px 0; }\n.ax-signin__or::before, .ax-signin__or::after { content: \"\"; flex: 1; height: 1px; background: var(--border-default); }\n.ax-signin__or span { font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.1em; color: var(--text-faint); }\n.ax-signin__sso { display: flex; align-items: center; justify-content: center; gap: 9px; width: 100%; height: 40px; border: 1px solid var(--border-default);\n border-radius: var(--radius-2); background: var(--surface-card); color: var(--text-body); font-family: inherit; font-size: var(--text-sm); cursor: pointer; transition: border-color var(--dur-1) var(--ease-out); }\n.ax-signin__sso:hover { border-color: var(--border-strong); }\n.ax-signin__terms { margin: 16px 0 0; font-size: var(--text-xs); color: var(--text-faint); line-height: var(--leading-snug); text-align: center; }\n.ax-signin__terms a, .ax-signin__terms span.lk { color: var(--text-muted); text-decoration: underline; text-underline-offset: 2px; cursor: pointer; }\n.ax-signin__foot { margin-top: 26px; text-align: center; font-size: var(--text-sm); color: var(--text-muted); }\n.ax-signin__footlink { color: var(--text-body); background: none; border: none; cursor: pointer; font-family: inherit; font-size: var(--text-sm);\n border-bottom: 1px solid var(--border-strong); padding: 0 0 1px; transition: border-color var(--dur-1) var(--ease-out); }\n.ax-signin__footlink:hover { border-color: var(--text-faint); }\n@media (max-width: 860px) { .ax-signin { grid-template-columns: 1fr; } .ax-signin__brand { display: none; } .ax-signin__mbrand { display: flex; margin-bottom: 26px; } }\n`;\n\nif (typeof document !== \"undefined\" && !document.getElementById(\"ax-signin-css\")) {\n const s = document.createElement(\"style\");\n s.id = \"ax-signin-css\";\n s.textContent = AX_SIGNIN_CSS;\n document.head.appendChild(s);\n}\n\nconst DEFAULT_COPY = {\n signin: {\n title: \"Sign in\",\n subtitle: \"Welcome back. Use your work email to continue.\",\n submit: \"Continue\",\n switchText: \"No account?\",\n switchCta: \"Create one\",\n },\n signup: {\n title: \"Create account\",\n subtitle: \"Start with your work email — it takes a minute.\",\n submit: \"Create account\",\n switchText: \"Already have an account?\",\n switchCta: \"Sign in\",\n },\n};\n\nexport function SignInPage({\n mode,\n defaultMode = \"signin\",\n onModeChange,\n brand,\n copy,\n email,\n password,\n onEmailChange,\n onPasswordChange,\n onSubmit,\n onForgot,\n ssoLabel = \"Continue with SSO\",\n onSSO,\n terms,\n footer,\n showBrandPanel = true,\n}) {\n const [modeI, setModeI] = useState(defaultMode);\n const m = mode !== undefined ? mode : modeI;\n const isSignup = m === \"signup\";\n const c = { ...DEFAULT_COPY[m], ...(copy && copy[m]) };\n\n const [emailI, setEmailI] = useState(\"\");\n const [pwI, setPwI] = useState(\"\");\n const [confirm, setConfirm] = useState(\"\");\n const [errs, setErrs] = useState({});\n const emailV = email !== undefined ? email : emailI;\n const pwV = password !== undefined ? password : pwI;\n const setEmail = (v) => {\n if (onEmailChange) onEmailChange(v);\n else setEmailI(v);\n if (errs.email) setErrs((e) => ({ ...e, email: undefined }));\n };\n const setPw = (v) => {\n if (onPasswordChange) onPasswordChange(v);\n else setPwI(v);\n if (errs.password) setErrs((e) => ({ ...e, password: undefined }));\n };\n const setConf = (v) => {\n setConfirm(v);\n if (errs.confirm) setErrs((e) => ({ ...e, confirm: undefined }));\n };\n\n const switchMode = () => {\n const next = isSignup ? \"signin\" : \"signup\";\n setErrs({});\n setConfirm(\"\");\n if (onModeChange) onModeChange(next);\n if (mode === undefined) setModeI(next);\n };\n\n const validate = () => {\n const e = {};\n const em = (emailV || \"\").trim();\n if (!em) e.email = \"请输入邮箱\";\n else if (!/^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$/.test(em)) e.email = \"邮箱格式不正确\";\n if (!pwV) e.password = \"请输入密码\";\n else if (isSignup && pwV.length < 8) e.password = \"密码至少 8 位\";\n if (isSignup) {\n if (!confirm) e.confirm = \"请再次输入密码\";\n else if (confirm !== pwV) e.confirm = \"两次输入的密码不一致\";\n }\n return e;\n };\n\n const submit = (ev) => {\n ev.preventDefault();\n const e = validate();\n setErrs(e);\n if (Object.keys(e).some((k) => e[k])) return;\n onSubmit && onSubmit({ mode: m, email: (emailV || \"\").trim(), password: pwV });\n };\n\n const termsNode =\n terms !== undefined ? (\n terms\n ) : (\n <>\n 注册即代表你同意 <span className=\"lk\">服务条款</span> 与{\" \"}\n <span className=\"lk\">隐私政策</span>。\n </>\n );\n\n return (\n <div className=\"ax-signin\">\n {showBrandPanel ? (\n <aside className=\"ax-signin__brand\">\n <div className=\"ax-signin__brandtop\">{brand || <BrandMark size={24} wordmark />}</div>\n <div className=\"ax-signin__mid\">\n <p className=\"ax-signin__quote\">\n 聊天,\n <br />\n 构建万物\n </p>\n <p className=\"ax-signin__by\">— AGENTAILY</p>\n </div>\n </aside>\n ) : null}\n\n <main className=\"ax-signin__form\">\n <form className=\"ax-signin__card\" onSubmit={submit} noValidate>\n <div className=\"ax-signin__mbrand\">{brand || <BrandMark size={22} wordmark />}</div>\n <h1 className=\"ax-signin__h\">{c.title}</h1>\n <p className=\"ax-signin__sub\">{c.subtitle}</p>\n\n <div className=\"ax-signin__fields\">\n <Input\n label=\"EMAIL\"\n type=\"email\"\n autoComplete=\"email\"\n placeholder=\"you@example.com\"\n value={emailV}\n error={errs.email}\n onChange={(e) => setEmail(e.target.value)}\n />\n <div>\n <div className=\"ax-signin__row\">\n <span className=\"ax-label\">PASSWORD</span>\n {!isSignup && onForgot ? (\n <button type=\"button\" className=\"ax-signin__link\" onClick={onForgot}>\n Forgot?\n </button>\n ) : null}\n </div>\n <Input\n type=\"password\"\n autoComplete={isSignup ? \"new-password\" : \"current-password\"}\n placeholder={isSignup ? \"至少 8 位\" : \"••••••••\"}\n value={pwV}\n error={errs.password}\n onChange={(e) => setPw(e.target.value)}\n />\n </div>\n {isSignup ? (\n <Input\n label=\"CONFIRM PASSWORD\"\n type=\"password\"\n autoComplete=\"new-password\"\n placeholder=\"再次输入密码\"\n value={confirm}\n error={errs.confirm}\n onChange={(e) => setConf(e.target.value)}\n />\n ) : null}\n </div>\n\n <div className=\"ax-signin__submit\">\n <Button variant=\"primary\" full type=\"submit\" icon={<Icon name=\"arrow\" size={15} />}>\n {c.submit}\n </Button>\n </div>\n\n {isSignup ? <p className=\"ax-signin__terms\">{termsNode}</p> : null}\n\n {onSSO ? (\n <React.Fragment>\n <div className=\"ax-signin__or\">\n <span>OR</span>\n </div>\n <button type=\"button\" className=\"ax-signin__sso\" onClick={onSSO}>\n <Icon name=\"shield\" size={15} />\n {ssoLabel}\n </button>\n </React.Fragment>\n ) : null}\n\n {footer !== undefined ? (\n footer ? (\n <p className=\"ax-signin__foot\">{footer}</p>\n ) : null\n ) : (\n <p className=\"ax-signin__foot\">\n {c.switchText}{\" \"}\n <button type=\"button\" className=\"ax-signin__footlink\" onClick={switchMode}>\n {c.switchCta}\n </button>\n </p>\n )}\n </form>\n </main>\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"SignInPage.js","sources":["../../../src/components/auth/SignInPage.jsx"],"sourcesContent":["import React, { useState } from \"react\";\nimport { Input } from \"../inputs/Input.jsx\";\nimport { Button } from \"../buttons/Button.jsx\";\nimport { Icon } from \"../utilities/Icon.jsx\";\nimport { BrandMark } from \"../utilities/BrandMark.jsx\";\nimport { RotatingTagline } from \"../utilities/RotatingTagline.jsx\";\n\n// SignInPage — full-page auth: a split brand panel (dot-grid + quote) beside a\n// centered card. One component, two modes (signin / signup) — the brand panel,\n// SSO, and card chrome are shared; only the fields, copy, and footer differ.\n// The footer link flips mode (and fires onModeChange so a router can swap the\n// URL). The full-page sibling of AuthDialog (the modal); same login/register idea.\nconst AX_SIGNIN_CSS = `\n.ax-signin { display: grid; grid-template-columns: 1fr 1fr; height: 100%; background: var(--surface-page); }\n.ax-signin__brand { position: relative; border-right: 1px solid var(--border-default); background: var(--surface-panel);\n background-image: var(--dot-grid); background-size: 24px 24px; padding: 40px; display: flex; flex-direction: column; }\n.ax-signin__brandtop { display: flex; align-items: center; gap: 10px; }\n.ax-signin__mid { margin-top: auto; margin-bottom: auto; }\n.ax-signin__quote { font-family: var(--font-display); white-space: nowrap; font-size: clamp(40px, 4.4vw, var(--text-hero)); font-weight: var(--weight-medium); line-height: var(--leading-tight); letter-spacing: var(--tracking-tight); color: var(--text-body); margin: 0; }\n.ax-signin__by { margin: 16px 0 0; font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.04em; color: var(--text-faint); }\n.ax-signin__form { display: flex; align-items: center; justify-content: center; padding: 40px; }\n.ax-signin__card { width: 100%; max-width: 360px; }\n.ax-signin__mbrand { display: none; }\n.ax-signin__mtag { display: none; }\n.ax-signin__mtag .ax-signin__quote { font-size: var(--text-3xl); }\n.ax-signin__h { margin: 0; font-family: var(--font-display); font-size: var(--text-2xl); font-weight: var(--weight-bold); letter-spacing: var(--tracking-tight); color: var(--text-body); }\n.ax-signin__sub { margin: 8px 0 28px; font-size: var(--text-sm); color: var(--text-muted); }\n.ax-signin__fields { display: flex; flex-direction: column; gap: 16px; }\n.ax-signin__row { display: flex; align-items: center; justify-content: space-between; margin-bottom: 7px; }\n.ax-signin__link { font-size: 12px; color: var(--text-muted); text-decoration: none; background: none; border: none; cursor: pointer; font-family: inherit; }\n.ax-signin__link:hover { color: var(--text-body); }\n.ax-signin__submit { margin-top: 22px; }\n.ax-signin__or { display: flex; align-items: center; gap: 12px; margin: 22px 0; }\n.ax-signin__or::before, .ax-signin__or::after { content: \"\"; flex: 1; height: 1px; background: var(--border-default); }\n.ax-signin__or span { font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.1em; color: var(--text-faint); }\n.ax-signin__sso { display: flex; align-items: center; justify-content: center; gap: 9px; width: 100%; height: 40px; border: 1px solid var(--border-default);\n border-radius: var(--radius-2); background: var(--surface-card); color: var(--text-body); font-family: inherit; font-size: var(--text-sm); cursor: pointer; transition: border-color var(--dur-1) var(--ease-out); }\n.ax-signin__sso:hover { border-color: var(--border-strong); }\n.ax-signin__terms { margin: 16px 0 0; font-size: var(--text-xs); color: var(--text-faint); line-height: var(--leading-snug); text-align: center; }\n.ax-signin__terms a, .ax-signin__terms span.lk { color: var(--text-muted); text-decoration: underline; text-underline-offset: 2px; cursor: pointer; }\n.ax-signin__foot { margin-top: 26px; text-align: center; font-size: var(--text-sm); color: var(--text-muted); }\n.ax-signin__footlink { color: var(--text-body); background: none; border: none; cursor: pointer; font-family: inherit; font-size: var(--text-sm);\n border-bottom: 1px solid var(--border-strong); padding: 0 0 1px; transition: border-color var(--dur-1) var(--ease-out); }\n.ax-signin__footlink:hover { border-color: var(--text-faint); }\n@media (max-width: 860px) { .ax-signin { grid-template-columns: 1fr; } .ax-signin__brand { display: none; } .ax-signin__form { padding: 56px 40px; } .ax-signin__mbrand { display: flex; margin-bottom: 28px; } .ax-signin__mtag { display: block; margin-bottom: 36px; } .ax-signin__h { font-family: var(--font-mono); font-size: var(--text-xs); font-weight: var(--weight-medium); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--text-body); margin: 0 0 8px; } .ax-signin__sub { margin: 0 0 32px; color: var(--text-muted); } .ax-signin__fields { gap: 22px; } .ax-signin__submit { margin-top: 32px; } .ax-signin__or { margin: 32px 0; } .ax-signin__foot { margin-top: 28px; } }\n`;\n\nif (typeof document !== \"undefined\" && !document.getElementById(\"ax-signin-css\")) {\n const s = document.createElement(\"style\");\n s.id = \"ax-signin-css\";\n s.textContent = AX_SIGNIN_CSS;\n document.head.appendChild(s);\n}\n\n// Every user-facing string lives here so nothing is hardcoded in the markup.\n// Defaults are a self-consistent English baseline; pass a `copy` prop (any\n// subset, deep-merged per group) to localize — e.g. a full zh-CN object.\nconst DEFAULT_COPY = {\n signin: {\n title: \"Sign in\",\n subtitle: \"Welcome back. Use your work email to continue.\",\n submit: \"Continue\",\n switchText: \"No account?\",\n switchCta: \"Create one\",\n },\n signup: {\n title: \"Create account\",\n subtitle: \"Start with your work email — it takes a minute.\",\n submit: \"Create account\",\n switchText: \"Already have an account?\",\n switchCta: \"Sign in\",\n },\n labels: {\n email: \"EMAIL\",\n password: \"PASSWORD\",\n confirm: \"CONFIRM PASSWORD\",\n forgot: \"Forgot?\",\n or: \"OR\",\n sso: \"Continue with SSO\",\n },\n placeholders: {\n email: \"you@example.com\",\n password: \"••••••••\",\n passwordNew: \"At least 8 characters\",\n confirm: \"Re-enter your password\",\n },\n errors: {\n emailRequired: \"Enter your email\",\n emailInvalid: \"That email doesn’t look right\",\n passwordRequired: \"Enter your password\",\n passwordShort: \"Password must be at least 8 characters\",\n confirmRequired: \"Re-enter your password\",\n confirmMismatch: \"Passwords don’t match\",\n },\n terms: \"By creating an account you agree to our Terms and Privacy Policy.\",\n};\n\nexport function SignInPage({\n mode,\n defaultMode = \"signin\",\n onModeChange,\n brand,\n copy,\n tagline,\n email,\n password,\n onEmailChange,\n onPasswordChange,\n onSubmit,\n onForgot,\n ssoLabel,\n onSSO,\n terms,\n footer,\n showBrandPanel = true,\n}) {\n const [modeI, setModeI] = useState(defaultMode);\n const m = mode !== undefined ? mode : modeI;\n const isSignup = m === \"signup\";\n const c = { ...DEFAULT_COPY[m], ...(copy && copy[m]) };\n const labels = { ...DEFAULT_COPY.labels, ...(copy && copy.labels) };\n const ph = { ...DEFAULT_COPY.placeholders, ...(copy && copy.placeholders) };\n const err = { ...DEFAULT_COPY.errors, ...(copy && copy.errors) };\n const ssoText = ssoLabel !== undefined ? ssoLabel : labels.sso;\n const taglineProps = { breakAfterPrefix: true, ...tagline };\n\n const [emailI, setEmailI] = useState(\"\");\n const [pwI, setPwI] = useState(\"\");\n const [confirm, setConfirm] = useState(\"\");\n const [errs, setErrs] = useState({});\n const emailV = email !== undefined ? email : emailI;\n const pwV = password !== undefined ? password : pwI;\n const setEmail = (v) => {\n if (onEmailChange) onEmailChange(v);\n else setEmailI(v);\n if (errs.email) setErrs((e) => ({ ...e, email: undefined }));\n };\n const setPw = (v) => {\n if (onPasswordChange) onPasswordChange(v);\n else setPwI(v);\n if (errs.password) setErrs((e) => ({ ...e, password: undefined }));\n };\n const setConf = (v) => {\n setConfirm(v);\n if (errs.confirm) setErrs((e) => ({ ...e, confirm: undefined }));\n };\n\n const switchMode = () => {\n const next = isSignup ? \"signin\" : \"signup\";\n setErrs({});\n setConfirm(\"\");\n if (onModeChange) onModeChange(next);\n if (mode === undefined) setModeI(next);\n };\n\n const validate = () => {\n const e = {};\n const em = (emailV || \"\").trim();\n if (!em) e.email = err.emailRequired;\n else if (!/^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$/.test(em)) e.email = err.emailInvalid;\n if (!pwV) e.password = err.passwordRequired;\n else if (isSignup && pwV.length < 8) e.password = err.passwordShort;\n if (isSignup) {\n if (!confirm) e.confirm = err.confirmRequired;\n else if (confirm !== pwV) e.confirm = err.confirmMismatch;\n }\n return e;\n };\n\n const submit = (ev) => {\n ev.preventDefault();\n const e = validate();\n setErrs(e);\n if (Object.keys(e).some((k) => e[k])) return;\n onSubmit && onSubmit({ mode: m, email: (emailV || \"\").trim(), password: pwV });\n };\n\n const termsNode =\n terms !== undefined ? terms : c.terms !== undefined ? c.terms : DEFAULT_COPY.terms;\n\n return (\n <div className=\"ax-signin\">\n {showBrandPanel ? (\n <aside className=\"ax-signin__brand\">\n <div className=\"ax-signin__brandtop\">\n {brand || <BrandMark size={24} wordmark blink={false} />}\n </div>\n <div className=\"ax-signin__mid\">\n <p className=\"ax-signin__quote\">\n <RotatingTagline {...taglineProps} />\n </p>\n <p className=\"ax-signin__by\">— AGENTAILY</p>\n </div>\n </aside>\n ) : null}\n\n <main className=\"ax-signin__form\">\n <form className=\"ax-signin__card\" onSubmit={submit} noValidate>\n <div className=\"ax-signin__mbrand\">\n {brand || <BrandMark size={22} wordmark blink={false} />}\n </div>\n <div className=\"ax-signin__mtag\">\n <p className=\"ax-signin__quote\">\n <RotatingTagline {...taglineProps} />\n </p>\n </div>\n <h1 className=\"ax-signin__h\">{c.title}</h1>\n <p className=\"ax-signin__sub\">{c.subtitle}</p>\n\n <div className=\"ax-signin__fields\">\n <Input\n label={labels.email}\n type=\"email\"\n autoComplete=\"email\"\n placeholder={ph.email}\n value={emailV}\n error={errs.email}\n onChange={(e) => setEmail(e.target.value)}\n />\n <div>\n <div className=\"ax-signin__row\">\n <span className=\"ax-label\">{labels.password}</span>\n {!isSignup && onForgot ? (\n <button type=\"button\" className=\"ax-signin__link\" onClick={onForgot}>\n {labels.forgot}\n </button>\n ) : null}\n </div>\n <Input\n type=\"password\"\n autoComplete={isSignup ? \"new-password\" : \"current-password\"}\n placeholder={isSignup ? ph.passwordNew : ph.password}\n value={pwV}\n error={errs.password}\n onChange={(e) => setPw(e.target.value)}\n />\n </div>\n {isSignup ? (\n <Input\n label={labels.confirm}\n type=\"password\"\n autoComplete=\"new-password\"\n placeholder={ph.confirm}\n value={confirm}\n error={errs.confirm}\n onChange={(e) => setConf(e.target.value)}\n />\n ) : null}\n </div>\n\n <div className=\"ax-signin__submit\">\n <Button variant=\"primary\" full type=\"submit\" icon={<Icon name=\"arrow\" size={15} />}>\n {c.submit}\n </Button>\n </div>\n\n {isSignup ? <p className=\"ax-signin__terms\">{termsNode}</p> : null}\n\n {onSSO ? (\n <React.Fragment>\n <div className=\"ax-signin__or\">\n <span>{labels.or}</span>\n </div>\n <button type=\"button\" className=\"ax-signin__sso\" onClick={onSSO}>\n <Icon name=\"shield\" size={15} />\n {ssoText}\n </button>\n </React.Fragment>\n ) : null}\n\n {footer !== undefined ? (\n footer ? (\n <p className=\"ax-signin__foot\">{footer}</p>\n ) : null\n ) : (\n <p className=\"ax-signin__foot\">\n {c.switchText}{\" \"}\n <button type=\"button\" className=\"ax-signin__footlink\" onClick={switchMode}>\n {c.switchCta}\n </button>\n </p>\n )}\n </form>\n </main>\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * RotatingTagline — the brand's signature animated headline. A fixed prefix
3
+ * (e.g. "聊天,") followed by a phrase that types in, holds, deletes, and
4
+ * advances. The rotating phrase wears the flowing geek-rainbow gradient; a
5
+ * block cursor trails it and blinks only at rest. Font size/weight inherit
6
+ * from the parent — size it by styling the container. Respects
7
+ * prefers-reduced-motion (whole-phrase swap, no gradient flow).
8
+ */
9
+ export interface RotatingTaglineProps {
10
+ /** Static text shown before the rotating phrase. @default "聊天," */
11
+ prefix?: string;
12
+ /** Phrases cycled through, in order. @default ["构建万物","生成万物","设计万物","学习万物"] */
13
+ phrases?: string[];
14
+ /** Paint the rotating phrase with the flowing rainbow gradient. @default true */
15
+ gradient?: boolean;
16
+ /** Show the trailing block cursor. @default true */
17
+ cursor?: boolean;
18
+ /** Put a <br/> between prefix and phrase (two-line tagline). @default false */
19
+ breakAfterPrefix?: boolean;
20
+ /** Per-character type-in delay, ms. @default 140 */
21
+ typeSpeed?: number;
22
+ /** Per-character delete delay, ms. @default 70 */
23
+ deleteSpeed?: number;
24
+ /** Hold time on the full phrase before deleting, ms. @default 1700 */
25
+ hold?: number;
26
+ /** Gradient flow loop duration (CSS time). @default "1.2s" */
27
+ flowDuration?: string;
28
+ className?: string;
29
+ }
30
+ export declare function RotatingTagline(props: RotatingTaglineProps): JSX.Element;
@@ -0,0 +1,90 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { useState, useEffect } from "react";
3
+ const AX_RTAG_CSS = `
4
+ .ax-rtag { display: inline; color: var(--text-body); }
5
+ .ax-rtag__prefix { color: var(--text-body); }
6
+ .ax-rtag__verb { color: var(--text-body); }
7
+ .ax-rtag__grad {
8
+ background: linear-gradient(95deg, #F2806B 0%, #E9B24F 18%, #6FB66A 36%, #4DB6AE 54%, #5B8DEF 72%, #9B7BEA 86%, #F2806B 100%);
9
+ background-size: 220% 100%;
10
+ -webkit-background-clip: text; background-clip: text;
11
+ -webkit-text-fill-color: transparent; color: transparent;
12
+ animation: ax-rtag-flow var(--ax-rtag-flow, 1.2s) linear infinite;
13
+ }
14
+ .ax-rtag__cursor {
15
+ display: inline-block; width: 0.5em; height: 0.84em; background: var(--text-body);
16
+ vertical-align: -0.02em; margin-left: 0.04em;
17
+ }
18
+ .ax-rtag__cursor.is-blink { animation: ax-rtag-blink 1.1s steps(1) infinite; }
19
+ @keyframes ax-rtag-flow { to { background-position: 220% 0; } }
20
+ @keyframes ax-rtag-blink { 50% { opacity: 0; } }
21
+ @media (prefers-reduced-motion: reduce) { .ax-rtag__grad { animation: none; } }
22
+ `;
23
+ if (typeof document !== "undefined" && !document.getElementById("ax-rtag-css")) {
24
+ const s = document.createElement("style");
25
+ s.id = "ax-rtag-css";
26
+ s.textContent = AX_RTAG_CSS;
27
+ document.head.appendChild(s);
28
+ }
29
+ function RotatingTagline({
30
+ prefix = "聊天,",
31
+ phrases = ["构建万物", "生成万物", "设计万物", "学习万物"],
32
+ gradient = true,
33
+ cursor = true,
34
+ breakAfterPrefix = false,
35
+ typeSpeed = 140,
36
+ deleteSpeed = 70,
37
+ hold = 1700,
38
+ flowDuration = "1.2s",
39
+ className = "",
40
+ ...rest
41
+ }) {
42
+ const list = phrases && phrases.length ? phrases : [""];
43
+ const reduced = typeof window !== "undefined" && window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
44
+ const [wi, setWi] = useState(0);
45
+ const [txt, setTxt] = useState(list[0]);
46
+ const [deleting, setDeleting] = useState(false);
47
+ useEffect(() => {
48
+ if (reduced) {
49
+ const t2 = setTimeout(() => setWi((w) => (w + 1) % list.length), hold + 700);
50
+ return () => clearTimeout(t2);
51
+ }
52
+ const word = list[wi % list.length];
53
+ let t;
54
+ if (!deleting && txt === word) {
55
+ t = setTimeout(() => setDeleting(true), hold);
56
+ } else if (deleting && txt === "") {
57
+ setDeleting(false);
58
+ setWi((w) => (w + 1) % list.length);
59
+ } else {
60
+ const next = deleting ? word.slice(0, txt.length - 1) : word.slice(0, txt.length + 1);
61
+ t = setTimeout(() => setTxt(next), deleting ? deleteSpeed : typeSpeed);
62
+ }
63
+ return () => clearTimeout(t);
64
+ }, [txt, deleting, wi, reduced, hold, typeSpeed, deleteSpeed]);
65
+ useEffect(() => {
66
+ if (reduced) setTxt(list[wi % list.length]);
67
+ }, [wi, reduced]);
68
+ const atRest = reduced || !deleting && txt === list[wi % list.length];
69
+ const verbCls = ["ax-rtag__verb", gradient ? "ax-rtag__grad" : ""].filter(Boolean).join(" ");
70
+ return /* @__PURE__ */ jsxs(
71
+ "span",
72
+ {
73
+ className: ["ax-rtag", className].filter(Boolean).join(" "),
74
+ style: { "--ax-rtag-flow": flowDuration },
75
+ ...rest,
76
+ children: [
77
+ prefix ? /* @__PURE__ */ jsxs("span", { className: "ax-rtag__prefix", children: [
78
+ prefix,
79
+ breakAfterPrefix ? /* @__PURE__ */ jsx("br", {}) : null
80
+ ] }) : null,
81
+ /* @__PURE__ */ jsx("span", { className: verbCls, children: txt }),
82
+ cursor ? /* @__PURE__ */ jsx("i", { className: "ax-rtag__cursor" + (atRest ? " is-blink" : ""), "aria-hidden": "true" }) : null
83
+ ]
84
+ }
85
+ );
86
+ }
87
+ export {
88
+ RotatingTagline
89
+ };
90
+ //# sourceMappingURL=RotatingTagline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RotatingTagline.js","sources":["../../../src/components/utilities/RotatingTagline.jsx"],"sourcesContent":["import React, { useState, useEffect } from \"react\";\n\n// RotatingTagline — the brand's signature animated headline: a fixed prefix\n// followed by a phrase that types in, holds, deletes, and advances to the next.\n// The rotating phrase wears the flowing geek-rainbow gradient; a block cursor\n// trails the text and blinks only while at rest. Used in the auth brand panel\n// and the marketing hero. Respects prefers-reduced-motion (whole-phrase swap,\n// no gradient flow). Font size / weight are inherited from the parent — size it\n// by styling the container, not this component.\nconst AX_RTAG_CSS = `\n.ax-rtag { display: inline; color: var(--text-body); }\n.ax-rtag__prefix { color: var(--text-body); }\n.ax-rtag__verb { color: var(--text-body); }\n.ax-rtag__grad {\n background: linear-gradient(95deg, #F2806B 0%, #E9B24F 18%, #6FB66A 36%, #4DB6AE 54%, #5B8DEF 72%, #9B7BEA 86%, #F2806B 100%);\n background-size: 220% 100%;\n -webkit-background-clip: text; background-clip: text;\n -webkit-text-fill-color: transparent; color: transparent;\n animation: ax-rtag-flow var(--ax-rtag-flow, 1.2s) linear infinite;\n}\n.ax-rtag__cursor {\n display: inline-block; width: 0.5em; height: 0.84em; background: var(--text-body);\n vertical-align: -0.02em; margin-left: 0.04em;\n}\n.ax-rtag__cursor.is-blink { animation: ax-rtag-blink 1.1s steps(1) infinite; }\n@keyframes ax-rtag-flow { to { background-position: 220% 0; } }\n@keyframes ax-rtag-blink { 50% { opacity: 0; } }\n@media (prefers-reduced-motion: reduce) { .ax-rtag__grad { animation: none; } }\n`;\n\nif (typeof document !== \"undefined\" && !document.getElementById(\"ax-rtag-css\")) {\n const s = document.createElement(\"style\");\n s.id = \"ax-rtag-css\";\n s.textContent = AX_RTAG_CSS;\n document.head.appendChild(s);\n}\n\nexport function RotatingTagline({\n prefix = \"聊天,\",\n phrases = [\"构建万物\", \"生成万物\", \"设计万物\", \"学习万物\"],\n gradient = true,\n cursor = true,\n breakAfterPrefix = false,\n typeSpeed = 140,\n deleteSpeed = 70,\n hold = 1700,\n flowDuration = \"1.2s\",\n className = \"\",\n ...rest\n}) {\n const list = phrases && phrases.length ? phrases : [\"\"];\n const reduced =\n typeof window !== \"undefined\" &&\n window.matchMedia &&\n window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n const [wi, setWi] = useState(0);\n const [txt, setTxt] = useState(list[0]);\n const [deleting, setDeleting] = useState(false);\n\n useEffect(() => {\n if (reduced) {\n const t = setTimeout(() => setWi((w) => (w + 1) % list.length), hold + 700);\n return () => clearTimeout(t);\n }\n const word = list[wi % list.length];\n let t;\n if (!deleting && txt === word) {\n t = setTimeout(() => setDeleting(true), hold);\n } else if (deleting && txt === \"\") {\n setDeleting(false);\n setWi((w) => (w + 1) % list.length);\n } else {\n const next = deleting ? word.slice(0, txt.length - 1) : word.slice(0, txt.length + 1);\n t = setTimeout(() => setTxt(next), deleting ? deleteSpeed : typeSpeed);\n }\n return () => clearTimeout(t);\n }, [txt, deleting, wi, reduced, hold, typeSpeed, deleteSpeed]);\n\n useEffect(() => {\n if (reduced) setTxt(list[wi % list.length]);\n }, [wi, reduced]);\n\n const atRest = reduced || (!deleting && txt === list[wi % list.length]);\n const verbCls = [\"ax-rtag__verb\", gradient ? \"ax-rtag__grad\" : \"\"].filter(Boolean).join(\" \");\n\n return (\n <span\n className={[\"ax-rtag\", className].filter(Boolean).join(\" \")}\n style={{ \"--ax-rtag-flow\": flowDuration }}\n {...rest}\n >\n {prefix ? (\n <span className=\"ax-rtag__prefix\">\n {prefix}\n {breakAfterPrefix ? <br /> : null}\n </span>\n ) : null}\n <span className={verbCls}>{txt}</span>\n {cursor ? (\n <i className={\"ax-rtag__cursor\" + (atRest ? \" is-blink\" : \"\")} aria-hidden=\"true\"></i>\n ) : null}\n </span>\n );\n}\n"],"names":["t"],"mappings":";;AASA,MAAM,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAqBpB,IAAI,OAAO,aAAa,eAAe,CAAC,SAAS,eAAe,aAAa,GAAG;AAC9E,QAAM,IAAI,SAAS,cAAc,OAAO;AACxC,IAAE,KAAK;AACP,IAAE,cAAc;AAChB,WAAS,KAAK,YAAY,CAAC;AAC7B;AAEO,SAAS,gBAAgB;AAAA,EAC9B,SAAS;AAAA,EACT,UAAU,CAAC,QAAQ,QAAQ,QAAQ,MAAM;AAAA,EACzC,WAAW;AAAA,EACX,SAAS;AAAA,EACT,mBAAmB;AAAA,EACnB,YAAY;AAAA,EACZ,cAAc;AAAA,EACd,OAAO;AAAA,EACP,eAAe;AAAA,EACf,YAAY;AAAA,EACZ,GAAG;AACL,GAAG;AACD,QAAM,OAAO,WAAW,QAAQ,SAAS,UAAU,CAAC,EAAE;AACtD,QAAM,UACJ,OAAO,WAAW,eAClB,OAAO,cACP,OAAO,WAAW,kCAAkC,EAAE;AACxD,QAAM,CAAC,IAAI,KAAK,IAAI,SAAS,CAAC;AAC9B,QAAM,CAAC,KAAK,MAAM,IAAI,SAAS,KAAK,CAAC,CAAC;AACtC,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,KAAK;AAE9C,YAAU,MAAM;AACd,QAAI,SAAS;AACX,YAAMA,KAAI,WAAW,MAAM,MAAM,CAAC,OAAO,IAAI,KAAK,KAAK,MAAM,GAAG,OAAO,GAAG;AAC1E,aAAO,MAAM,aAAaA,EAAC;AAAA,IAC7B;AACA,UAAM,OAAO,KAAK,KAAK,KAAK,MAAM;AAClC,QAAI;AACJ,QAAI,CAAC,YAAY,QAAQ,MAAM;AAC7B,UAAI,WAAW,MAAM,YAAY,IAAI,GAAG,IAAI;AAAA,IAC9C,WAAW,YAAY,QAAQ,IAAI;AACjC,kBAAY,KAAK;AACjB,YAAM,CAAC,OAAO,IAAI,KAAK,KAAK,MAAM;AAAA,IACpC,OAAO;AACL,YAAM,OAAO,WAAW,KAAK,MAAM,GAAG,IAAI,SAAS,CAAC,IAAI,KAAK,MAAM,GAAG,IAAI,SAAS,CAAC;AACpF,UAAI,WAAW,MAAM,OAAO,IAAI,GAAG,WAAW,cAAc,SAAS;AAAA,IACvE;AACA,WAAO,MAAM,aAAa,CAAC;AAAA,EAC7B,GAAG,CAAC,KAAK,UAAU,IAAI,SAAS,MAAM,WAAW,WAAW,CAAC;AAE7D,YAAU,MAAM;AACd,QAAI,QAAS,QAAO,KAAK,KAAK,KAAK,MAAM,CAAC;AAAA,EAC5C,GAAG,CAAC,IAAI,OAAO,CAAC;AAEhB,QAAM,SAAS,WAAY,CAAC,YAAY,QAAQ,KAAK,KAAK,KAAK,MAAM;AACrE,QAAM,UAAU,CAAC,iBAAiB,WAAW,kBAAkB,EAAE,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AAE3F,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAW,CAAC,WAAW,SAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AAAA,MAC1D,OAAO,EAAE,kBAAkB,aAAA;AAAA,MAC1B,GAAG;AAAA,MAEH,UAAA;AAAA,QAAA,SACC,qBAAC,QAAA,EAAK,WAAU,mBACb,UAAA;AAAA,UAAA;AAAA,UACA,mBAAmB,oBAAC,MAAA,CAAA,CAAG,IAAK;AAAA,QAAA,EAAA,CAC/B,IACE;AAAA,QACJ,oBAAC,QAAA,EAAK,WAAW,SAAU,UAAA,KAAI;AAAA,QAC9B,SACC,oBAAC,KAAA,EAAE,WAAW,qBAAqB,SAAS,cAAc,KAAK,eAAY,OAAA,CAAO,IAChF;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGV;"}
package/dist/index.d.ts CHANGED
@@ -130,6 +130,7 @@ export * from "./components/utilities/BrandMark";
130
130
  export * from "./components/utilities/Icon";
131
131
  export * from "./components/utilities/Image";
132
132
  export * from "./components/utilities/OpenInChat";
133
+ export * from "./components/utilities/RotatingTagline";
133
134
 
134
135
  // voice
135
136
  export * from "./components/voice/AudioPlayer";
package/dist/index.js CHANGED
@@ -103,6 +103,7 @@ import { BrandMark } from "./components/utilities/BrandMark.js";
103
103
  import { Icon } from "./components/utilities/Icon.js";
104
104
  import { Image } from "./components/utilities/Image.js";
105
105
  import { OpenInChat } from "./components/utilities/OpenInChat.js";
106
+ import { RotatingTagline } from "./components/utilities/RotatingTagline.js";
106
107
  import { AudioPlayer } from "./components/voice/AudioPlayer.js";
107
108
  import { MicSelector } from "./components/voice/MicSelector.js";
108
109
  import { Persona } from "./components/voice/Persona.js";
@@ -199,6 +200,7 @@ export {
199
200
  RadioGroup,
200
201
  Reasoning,
201
202
  Resizable,
203
+ RotatingTagline,
202
204
  Sandbox,
203
205
  SchemaDisplay,
204
206
  ScrollArea,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@agentaily/design-system",
3
- "version": "0.3.0",
4
- "description": "Agentaily design system — light-first monochrome React component library (112 components) + Storybook.",
3
+ "version": "0.4.0",
4
+ "description": "Agentaily design system — light-first monochrome React component library (113 components) + Storybook.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
7
7
  "author": "Agentaily",