@jike-js/ui 0.1.0 → 0.2.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 CHANGED
@@ -2,8 +2,24 @@
2
2
 
3
3
  这是 jike 统一前端组件的参考实现,组件契约见 `frontend/UI_COMPONENT_CATALOG.md`。
4
4
 
5
+ ## 安装
6
+
7
+ ```powershell
8
+ pnpm add @jike-js/ui@0.1.1
9
+ ```
10
+
11
+ 当前公开版本:`0.1.1`,已包含登录行高隔离修订;品牌配置 API 的本地实现版本为 `0.2.0`,尚未发布。业务项目安装后锁定明确版本,不使用 Git、本地 `file:` 或未发布版本。
12
+
5
13
  业务项目只需引入 `@jike-js/ui/styles.css` 一个样式入口;该入口在构建时包含 Token、基础层和组件层,避免漏引或形成第二份 reset。Token 的唯一源文件仍是 `frontend/tokens/design-tokens.json`,CSS 是其运行时输出。
6
14
 
7
15
  当前提供:`LoginShell`、`PageLayout`、`PageHeader`、`Notice`、`EmptyState`、`LoadingState`。包构建会同时检查 TypeScript,并输出单一 `styles.css`。
8
16
 
9
17
  本包不包含业务请求、路由、权限和项目 Logo。业务项目只能通过组件参数接入这些能力。登录文案使用 `brandTitle`、`brandSubtitle`、`formTitle`、`formSubtitle` 四个明确层级。
18
+
19
+ 品牌差异通过 `LoginBrandConfig` 传入:
20
+
21
+ ```tsx
22
+ <LoginShell brand={{ primary: "#3566ec", primaryHover: "#2853d4" }} {...props} />
23
+ ```
24
+
25
+ 不得通过全局 CSS 或任意 Token 覆盖登录结构。
@@ -4,14 +4,21 @@ export interface LoginCredentials {
4
4
  username: string;
5
5
  password: string;
6
6
  }
7
+ export interface LoginBrandConfig {
8
+ primary?: string;
9
+ primaryHover?: string;
10
+ focusRing?: string;
11
+ fontFamily?: string;
12
+ }
7
13
  export interface LoginShellProps {
8
14
  brandTitle: string;
9
15
  logo: string | ReactNode;
10
16
  brandSubtitle?: string;
11
17
  formTitle?: string;
12
18
  formSubtitle?: string;
19
+ brand?: LoginBrandConfig;
13
20
  onSubmit: (credentials: LoginCredentials) => void | Promise<void>;
14
21
  loading?: boolean;
15
22
  error?: string;
16
23
  }
17
- export declare function LoginShell({ brandTitle, logo, brandSubtitle, formTitle, formSubtitle, onSubmit, loading, error, }: LoginShellProps): import("react").JSX.Element;
24
+ export declare function LoginShell({ brandTitle, logo, brandSubtitle, formTitle, formSubtitle, brand, onSubmit, loading, error, }: LoginShellProps): import("react").JSX.Element;
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { LockKeyhole, LoaderCircle, LogIn, User } from "lucide-react";
3
3
  import { useState } from "react";
4
4
  import "./styles.css";
5
- export function LoginShell({ brandTitle, logo, brandSubtitle = "提升工作效率,简化操作流程", formTitle = "欢迎回来", formSubtitle = "登录以继续使用", onSubmit, loading = false, error, }) {
5
+ export function LoginShell({ brandTitle, logo, brandSubtitle = "提升工作效率,简化操作流程", formTitle = "欢迎回来", formSubtitle = "登录以继续使用", brand, onSubmit, loading = false, error, }) {
6
6
  const [username, setUsername] = useState("");
7
7
  const [password, setPassword] = useState("");
8
8
  async function handleSubmit(event) {
@@ -11,5 +11,11 @@ export function LoginShell({ brandTitle, logo, brandSubtitle = "提升工作效
11
11
  return;
12
12
  await onSubmit({ username: username.trim(), password: password.trim() });
13
13
  }
14
- return (_jsx("main", { className: "jike-login-shell", children: _jsxs("section", { className: "jike-login-shell__card", "aria-label": `${brandTitle}登录`, children: [_jsxs("div", { className: "jike-login-shell__brand", children: [_jsx("div", { className: "jike-login-shell__logo", children: typeof logo === "string" ? _jsx("img", { src: logo, alt: brandTitle }) : logo }), _jsx("h1", { children: brandTitle }), _jsx("p", { children: brandSubtitle })] }), _jsxs("div", { className: "jike-login-shell__form-panel", children: [_jsx("h2", { children: formTitle }), _jsx("p", { className: "jike-login-shell__hint", children: formSubtitle }), _jsxs("form", { onSubmit: handleSubmit, autoComplete: "on", noValidate: true, "aria-busy": loading, children: [_jsxs("label", { className: "jike-login-shell__field", htmlFor: "jike-login-username", children: [_jsx("span", { children: "\u7528\u6237\u540D" }), _jsxs("span", { className: "jike-login-shell__control", children: [_jsx(User, { "aria-hidden": "true" }), _jsx("input", { id: "jike-login-username", name: "username", value: username, onChange: (event) => setUsername(event.target.value), placeholder: "\u8BF7\u8F93\u5165\u7528\u6237\u540D", autoComplete: "username", disabled: loading, required: true, "aria-invalid": Boolean(error) })] })] }), _jsxs("label", { className: "jike-login-shell__field", htmlFor: "jike-login-password", children: [_jsx("span", { children: "\u5BC6\u7801" }), _jsxs("span", { className: "jike-login-shell__control", children: [_jsx(LockKeyhole, { "aria-hidden": "true" }), _jsx("input", { id: "jike-login-password", name: "password", type: "password", value: password, onChange: (event) => setPassword(event.target.value), placeholder: "\u8BF7\u8F93\u5165\u5BC6\u7801", autoComplete: "current-password", disabled: loading, required: true, "aria-invalid": Boolean(error) })] })] }), error ? _jsx("p", { className: "jike-login-shell__error", role: "alert", "aria-live": "polite", children: error }) : null, _jsxs("button", { className: "jike-login-shell__submit", type: "submit", disabled: loading, children: [loading ? "登录中..." : "登录", loading ? _jsx(LoaderCircle, { "aria-hidden": "true", className: "jike-login-shell__spinner" }) : _jsx(LogIn, { "aria-hidden": "true" })] })] })] })] }) }));
14
+ const brandStyle = {
15
+ ...(brand?.primary ? { "--jike-login-brand-primary": brand.primary } : {}),
16
+ ...(brand?.primaryHover ? { "--jike-login-brand-primary-hover": brand.primaryHover } : {}),
17
+ ...(brand?.focusRing ? { "--jike-login-brand-focus-ring": brand.focusRing } : {}),
18
+ ...(brand?.fontFamily ? { "--jike-login-font-family": brand.fontFamily } : {}),
19
+ };
20
+ return (_jsx("main", { className: "jike-login-shell", style: brandStyle, children: _jsxs("section", { className: "jike-login-shell__card", "aria-label": `${brandTitle}登录`, children: [_jsxs("div", { className: "jike-login-shell__brand", children: [_jsx("div", { className: "jike-login-shell__logo", children: typeof logo === "string" ? _jsx("img", { src: logo, alt: brandTitle }) : logo }), _jsx("h1", { children: brandTitle }), _jsx("p", { children: brandSubtitle })] }), _jsxs("div", { className: "jike-login-shell__form-panel", children: [_jsx("h2", { children: formTitle }), _jsx("p", { className: "jike-login-shell__hint", children: formSubtitle }), _jsxs("form", { onSubmit: handleSubmit, autoComplete: "on", noValidate: true, "aria-busy": loading, children: [_jsxs("label", { className: "jike-login-shell__field", htmlFor: "jike-login-username", children: [_jsx("span", { children: "\u7528\u6237\u540D" }), _jsxs("span", { className: "jike-login-shell__control", children: [_jsx(User, { "aria-hidden": "true" }), _jsx("input", { id: "jike-login-username", name: "username", value: username, onChange: (event) => setUsername(event.target.value), placeholder: "\u8BF7\u8F93\u5165\u7528\u6237\u540D", autoComplete: "username", disabled: loading, required: true, "aria-invalid": Boolean(error) })] })] }), _jsxs("label", { className: "jike-login-shell__field", htmlFor: "jike-login-password", children: [_jsx("span", { children: "\u5BC6\u7801" }), _jsxs("span", { className: "jike-login-shell__control", children: [_jsx(LockKeyhole, { "aria-hidden": "true" }), _jsx("input", { id: "jike-login-password", name: "password", type: "password", value: password, onChange: (event) => setPassword(event.target.value), placeholder: "\u8BF7\u8F93\u5165\u5BC6\u7801", autoComplete: "current-password", disabled: loading, required: true, "aria-invalid": Boolean(error) })] })] }), error ? _jsx("p", { className: "jike-login-shell__error", role: "alert", "aria-live": "polite", children: error }) : null, _jsxs("button", { className: "jike-login-shell__submit", type: "submit", disabled: loading, children: [loading ? "登录中..." : "登录", loading ? _jsx(LoaderCircle, { "aria-hidden": "true", className: "jike-login-shell__spinner" }) : _jsx(LogIn, { "aria-hidden": "true" })] })] })] })] }) }));
15
21
  }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { LoginShell } from "./LoginShell";
2
- export type { LoginCredentials, LoginShellProps } from "./LoginShell";
2
+ export type { LoginBrandConfig, LoginCredentials, LoginShellProps } from "./LoginShell";
3
3
  export { PageLayout } from "./PageLayout";
4
4
  export type { PageLayoutProps } from "./PageLayout";
5
5
  export { PageHeader } from "./PageHeader";
package/dist/styles.css CHANGED
@@ -77,6 +77,7 @@
77
77
  --login-subtitle-form-gap: 24px;
78
78
  --login-field-gap: 16px;
79
79
  --login-button-gap: 8px;
80
+ --login-form-line-height: normal;
80
81
  --login-form-offset-y: -24px;
81
82
  --login-mobile-breakpoint: 768px;
82
83
  --z-sidebar: 20;
@@ -130,29 +131,29 @@ svg {
130
131
  display: block;
131
132
  }
132
133
 
133
- .jike-login-shell { box-sizing: border-box; min-height: 100vh; display: grid; place-items: center; padding: var(--space-6); background: var(--color-bg-page); color: var(--color-text-primary); font-family: var(--font-family-sans); }
134
+ .jike-login-shell { box-sizing: border-box; min-height: 100vh; display: grid; place-items: center; padding: var(--space-6); background: var(--color-bg-page); color: var(--color-text-primary); font-family: var(--jike-login-font-family, var(--font-family-sans)); line-height: var(--login-form-line-height); }
134
135
  .jike-login-shell__card { box-sizing: border-box; display: grid; grid-template-columns: var(--login-column-ratio); width: var(--login-container-width); max-width: var(--login-container-max-width); min-height: var(--login-container-min-height); overflow: hidden; background: var(--color-bg-surface); border: var(--border-width-default) solid var(--color-border); border-radius: var(--radius-container); box-shadow: var(--shadow-login); }
135
- .jike-login-shell__brand { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: var(--login-brand-padding-y) var(--login-brand-padding-x); color: var(--color-bg-surface); text-align: center; background: linear-gradient(135deg, var(--color-brand), var(--color-brand-hover)); }
136
+ .jike-login-shell__brand { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: var(--login-brand-padding-y) var(--login-brand-padding-x); color: var(--color-bg-surface); text-align: center; background: linear-gradient(135deg, var(--jike-login-brand-primary, var(--color-brand)), var(--jike-login-brand-primary-hover, var(--color-brand-hover))); }
136
137
  .jike-login-shell__logo { display: grid; place-items: center; width: var(--login-logo-size); height: var(--login-logo-size); margin-bottom: var(--space-6); }
137
138
  .jike-login-shell__logo img { display: block; max-width: 100%; max-height: 100%; object-fit: contain; }
138
139
  .jike-login-shell__brand h1, .jike-login-shell__form-panel h2 { margin: 0; font-size: var(--font-size-2xl); font-weight: 700; line-height: var(--line-height-heading); }
139
140
  .jike-login-shell__brand p { margin: var(--space-4) 0 0; font-size: var(--font-size-lg); line-height: var(--line-height-body); }
140
141
  .jike-login-shell__form-panel { display: flex; flex-direction: column; justify-content: center; padding: var(--login-panel-padding-y) var(--login-panel-padding-x); transform: translateY(var(--login-form-offset-y)); }
141
142
  .jike-login-shell__form-panel h2 { font-size: var(--font-size-3xl); }
142
- .jike-login-shell__hint { margin: var(--space-2) 0 var(--login-subtitle-form-gap); color: var(--color-text-muted); font-size: var(--font-size-md); }
143
- .jike-login-shell__field { display: block; margin-bottom: var(--login-field-gap); color: var(--color-text-secondary); font-size: var(--font-size-md); font-weight: 600; }
143
+ .jike-login-shell__hint { margin: var(--space-2) 0 var(--login-subtitle-form-gap); color: var(--color-text-muted); font-size: var(--font-size-md); line-height: var(--login-form-line-height); }
144
+ .jike-login-shell__field { display: block; margin-bottom: var(--login-field-gap); color: var(--color-text-secondary); font-size: var(--font-size-md); font-weight: 600; line-height: var(--login-form-line-height); }
144
145
  .jike-login-shell__control { position: relative; display: block; margin-top: var(--space-2); }
145
146
  .jike-login-shell__control svg { position: absolute; top: 50%; left: var(--space-4); width: var(--icon-size-sm); height: var(--icon-size-sm); color: var(--color-text-muted); transform: translateY(-50%); pointer-events: none; }
146
- .jike-login-shell__control input { box-sizing: border-box; width: 100%; height: var(--login-control-height); padding: 0 var(--control-input-padding-x) 0 var(--control-input-icon-padding-left); color: var(--color-text-primary); font: inherit; font-size: var(--font-size-md); background: var(--color-bg-muted); border: var(--border-width-default) solid var(--color-border); border-radius: var(--radius-control); transition: border-color 160ms ease, box-shadow 160ms ease, background-color 160ms ease; }
147
+ .jike-login-shell__control input { box-sizing: border-box; width: 100%; height: var(--login-control-height); padding: 0 var(--control-input-padding-x) 0 var(--control-input-icon-padding-left); color: var(--color-text-primary); font: inherit; font-size: var(--font-size-md); line-height: var(--login-form-line-height); background: var(--color-bg-muted); border: var(--border-width-default) solid var(--color-border); border-radius: var(--radius-control); transition: border-color 160ms ease, box-shadow 160ms ease, background-color 160ms ease; }
147
148
  .jike-login-shell__control input:hover:not(:disabled) { border-color: var(--color-text-muted); background: var(--color-bg-surface); }
148
- .jike-login-shell__control input:focus { outline: none; border-color: var(--color-primary); background: var(--color-bg-surface); box-shadow: var(--focus-ring); }
149
+ .jike-login-shell__control input:focus { outline: none; border-color: var(--jike-login-brand-primary, var(--color-primary)); background: var(--color-bg-surface); box-shadow: var(--jike-login-brand-focus-ring, var(--focus-ring)); }
149
150
  .jike-login-shell__control input[aria-invalid="true"] { border-color: var(--color-danger); }
150
151
  .jike-login-shell__control input[aria-invalid="true"]:focus { box-shadow: 0 0 0 3px rgba(180, 35, 24, .18); }
151
152
  .jike-login-shell__control input:disabled { cursor: not-allowed; background: var(--color-bg-muted); }
152
- .jike-login-shell__error { margin: calc(var(--space-2) * -1) 0 var(--space-4); color: var(--color-danger); font-size: var(--font-size-sm); }
153
- .jike-login-shell__submit { display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2); width: 100%; height: var(--login-control-height); margin-top: var(--login-button-gap); color: var(--color-bg-surface); font: inherit; font-size: var(--font-size-lg); font-weight: 600; background: var(--color-brand); border: var(--border-width-default) solid var(--color-brand); border-radius: var(--radius-control); transition: background-color 160ms ease, border-color 160ms ease, box-shadow 160ms ease, transform 160ms ease; }
154
- .jike-login-shell__submit:hover:not(:disabled) { background: var(--color-brand-hover); border-color: var(--color-brand-hover); }
155
- .jike-login-shell__submit:focus-visible { outline: none; box-shadow: var(--focus-ring); }
153
+ .jike-login-shell__error { margin: calc(var(--space-2) * -1) 0 var(--space-4); color: var(--color-danger); font-size: var(--font-size-sm); line-height: var(--login-form-line-height); }
154
+ .jike-login-shell__submit { display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2); width: 100%; height: var(--login-control-height); margin-top: var(--login-button-gap); color: var(--color-bg-surface); font: inherit; font-size: var(--font-size-lg); font-weight: 600; line-height: var(--login-form-line-height); background: var(--jike-login-brand-primary, var(--color-brand)); border: var(--border-width-default) solid var(--jike-login-brand-primary, var(--color-brand)); border-radius: var(--radius-control); transition: background-color 160ms ease, border-color 160ms ease, box-shadow 160ms ease, transform 160ms ease; }
155
+ .jike-login-shell__submit:hover:not(:disabled) { background: var(--jike-login-brand-primary-hover, var(--color-brand-hover)); border-color: var(--jike-login-brand-primary-hover, var(--color-brand-hover)); }
156
+ .jike-login-shell__submit:focus-visible { outline: none; box-shadow: var(--jike-login-brand-focus-ring, var(--focus-ring)); }
156
157
  .jike-login-shell__submit:active:not(:disabled) { transform: translateY(1px); }
157
158
  .jike-login-shell__submit:disabled { cursor: wait; opacity: var(--state-disabled-opacity); }
158
159
  .jike-login-shell__submit svg { width: var(--icon-size-md); height: var(--icon-size-md); }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jike-js/ui",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "jike 统一前端组件实现",
5
5
  "license": "MIT",
6
6
  "repository": {