@ploykit/ui 0.7.0 → 0.7.4
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
|
@@ -194,6 +194,21 @@ import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '@
|
|
|
194
194
|
- **工具**:`cn`、`formatDate` / `formatDateTime` / `shortID`、`statusTone` / `statusLabel`、`apiErrorMessage`、`FormField`、`CronInput`。
|
|
195
195
|
- 基础 UI 件(Button / Input / Card / Tabs / AlertDialog / DropdownMenu / Popover / Switch / Checkbox / Progress / Avatar / Breadcrumb / Empty / Field / Label / Tooltip / Skeleton / Separator…)全部从 barrel 导出;也可走子路径 `import { Button } from '@ploykit/ui/components/ui/button'`(推荐统一走 barrel)。
|
|
196
196
|
|
|
197
|
+
## Tailwind v4 集成
|
|
198
|
+
|
|
199
|
+
本包组件的 class 全部走语义令牌(`bg-card`、`text-muted-foreground`…),需要 Tailwind 扫描到本包源码才会生成对应规则。Tailwind v4 的自动内容探测会跳过 node_modules,因此按消费方式二选一:
|
|
200
|
+
|
|
201
|
+
- **monorepo 消费方**(workspace 软链):可保留自己写的 `@source "<相对路径>/packages/ui/src";`,也可改用下面的 import(推荐——相对路径在产品拆出独立仓库后会静默失效,曾有产品因此上线了无样式的登录页数周未察觉)。
|
|
202
|
+
- **独立(npm)消费方**:在 CSS 入口(如 `src/index.css`)加一行:
|
|
203
|
+
|
|
204
|
+
```css
|
|
205
|
+
@import "@ploykit/ui/tailwind.css";
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
`@ploykit/ui/tailwind.css` 内部只有一条 `@source "./src";`。`@source` 相对样式表文件自身解析,因此无论包位于 monorepo workspace 还是 node_modules,都指向包内的 `src/`,无需消费方关心安装路径。
|
|
209
|
+
|
|
210
|
+
令牌定义(`--card` / `--popover` 等 CSS 变量与 `@theme` 映射)仍由消费方按 shadcn 惯例在自己的 CSS 里提供(参见 `example/web/src/index.css`)。
|
|
211
|
+
|
|
197
212
|
## 自定义三个层级
|
|
198
213
|
|
|
199
214
|
1. **换皮**:CSS/Tailwind 覆盖样式(组件全部走 design token:`bg-popover`、`text-muted-foreground` 等)。
|
|
@@ -40,7 +40,7 @@ export function NotificationBell({ onNavigate }) {
|
|
|
40
40
|
document.addEventListener('mousedown', onClickOutside);
|
|
41
41
|
return () => document.removeEventListener('mousedown', onClickOutside);
|
|
42
42
|
}, []);
|
|
43
|
-
return (_jsxs("div", { ref: ref, className: "relative", children: [_jsxs("button", { onClick: () => setOpen(!open), className: "relative p-2 text-muted-foreground hover:text-foreground hover:bg-accent rounded-md\n focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1", "aria-label": "\u901A\u77E5", "aria-haspopup": "true", "aria-expanded": open, children: [_jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" }) }), unread > 0 && (_jsx("span", { className: "absolute -top-0.5 -right-0.5 flex items-center justify-center min-w-[18px] h-[18px]\n px-1 text-xs font-bold text-white bg-destructive rounded-full", children: unread > 99 ? '99+' : unread }))] }), open && (_jsxs("div", { className: "absolute right-0 mt-2 w-80 bg-
|
|
43
|
+
return (_jsxs("div", { ref: ref, className: "relative", children: [_jsxs("button", { onClick: () => setOpen(!open), className: "relative p-2 text-muted-foreground hover:text-foreground hover:bg-accent rounded-md\n focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1", "aria-label": "\u901A\u77E5", "aria-haspopup": "true", "aria-expanded": open, children: [_jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" }) }), unread > 0 && (_jsx("span", { className: "absolute -top-0.5 -right-0.5 flex items-center justify-center min-w-[18px] h-[18px]\n px-1 text-xs font-bold text-white bg-destructive rounded-full", children: unread > 99 ? '99+' : unread }))] }), open && (_jsxs("div", { className: "absolute right-0 mt-2 w-80 bg-popover rounded-lg shadow-lg border z-50", role: "dialog", "aria-label": "\u901A\u77E5\u5217\u8868", onKeyDown: (e) => { if (e.key === 'Escape')
|
|
44
44
|
setOpen(false); }, children: [_jsxs("div", { className: "flex items-center justify-between px-4 py-3 border-b", children: [_jsx("span", { className: "text-sm font-semibold", children: "\u901A\u77E5" }), unread > 0 && (_jsx("button", { onClick: () => markAllReadM.mutate(), disabled: markAllReadM.isPending, className: "text-xs text-primary hover:underline disabled:opacity-50\n focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded", children: "\u5168\u90E8\u5DF2\u8BFB" }))] }), _jsx("ul", { className: "max-h-80 overflow-y-auto list-none p-0 m-0", "data-slot": "notification-list", children: listQ.isPending ? (_jsx("li", { className: "py-8 text-center text-sm text-muted-foreground", "aria-live": "polite", children: "\u52A0\u8F7D\u4E2D\u2026" })) : notifications.length === 0 ? (_jsx("li", { className: "py-8 text-center text-sm text-muted-foreground", children: "\u6682\u65E0\u901A\u77E5" })) : notifications.map((n) => (_jsx("li", { className: "border-b last:border-0", children: _jsxs("button", { type: "button", onClick: () => { if (n.link)
|
|
45
45
|
onNavigate?.(n.link); markReadM.mutate(n.id); setOpen(false); }, className: cn('w-full text-left px-4 py-3 cursor-pointer hover:bg-muted ' +
|
|
46
46
|
'focus-visible:outline-none focus-visible:bg-muted focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring', !n.read_at && 'bg-accent/50'), children: [_jsxs("span", { className: "flex items-start justify-between gap-2", children: [_jsx("span", { className: "text-sm font-medium text-foreground", children: n.title }), n.count > 1 && (_jsxs("span", { className: "text-xs bg-muted text-muted-foreground px-1.5 py-0.5 rounded-full shrink-0", children: ["\u00D7", n.count] }))] }), n.body && _jsx("span", { className: "block mt-0.5 text-xs text-muted-foreground line-clamp-2", children: n.body }), _jsx("span", { className: "block mt-1 text-[10px] text-muted-foreground", children: formatDateTime(n.created_at) })] }) }, n.id))) })] }))] }));
|
package/dist/pages/LoginPage.js
CHANGED
|
@@ -77,7 +77,7 @@ export function LoginPage({ resetPath = '/forgot-password', registerPath = '/reg
|
|
|
77
77
|
setPwLoading(false);
|
|
78
78
|
}
|
|
79
79
|
};
|
|
80
|
-
return (_jsx("div", { className: "min-h-screen flex items-center justify-center bg-background", children: _jsxs("div", { className: "w-full max-w-sm space-y-4 p-8 bg-
|
|
80
|
+
return (_jsx("div", { className: "min-h-screen flex items-center justify-center bg-background", children: _jsxs("div", { className: "w-full max-w-sm space-y-4 p-8 bg-card rounded-lg shadow", children: [_jsxs("div", { className: "text-center", children: [_jsx("h1", { className: "text-2xl font-bold", children: title }), _jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: description })] }), passwordOnly ? (_jsxs("form", { onSubmit: handlePasswordSubmit, className: "space-y-4 pt-2", children: [_jsx(FormField, { label: "\u90AE\u7BB1", htmlFor: "login-pw-email", children: _jsx(Input, { id: "login-pw-email", type: "email", value: email, required: true, onChange: (e) => setEmail(e.target.value), placeholder: "you@example.com", autoComplete: "email" }) }), _jsx(FormField, { label: "\u5BC6\u7801", htmlFor: "login-pw-password", children: _jsx(Input, { id: "login-pw-password", type: "password", value: password, required: true, onChange: (e) => setPassword(e.target.value), placeholder: "\u8F93\u5165\u5BC6\u7801", autoComplete: "current-password" }) }), pwError && _jsx("p", { role: "alert", className: "text-destructive text-sm", children: pwError }), _jsx(Button, { type: "submit", className: "w-full", disabled: pwLoading || !email || !password, children: pwLoading ? '登录中…' : '登录' })] })) : (_jsxs(Tabs, { defaultValue: "code", children: [_jsxs(TabsList, { className: "w-full", children: [_jsx(TabsTrigger, { value: "code", children: "\u9A8C\u8BC1\u7801\u767B\u5F55" }), _jsx(TabsTrigger, { value: "password", children: "\u5BC6\u7801\u767B\u5F55" })] }), _jsx(TabsContent, { value: "code", children: _jsxs("form", { onSubmit: handleCodeSubmit, className: "space-y-4 pt-2", children: [_jsxs("div", { className: "space-y-1", children: [_jsx("label", { htmlFor: "login-email", className: "text-sm text-foreground", children: "\u90AE\u7BB1" }), _jsx("input", { id: "login-email", type: "email", value: email, onChange: (e) => setEmail(e.target.value), placeholder: "you@example.com", required: true, autoComplete: "email", className: "w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-ring" })] }), _jsxs("div", { className: "space-y-1", children: [_jsx("label", { htmlFor: "login-code", className: "text-sm text-foreground", children: "\u9A8C\u8BC1\u7801" }), _jsxs("div", { className: "flex gap-2", children: [_jsx("input", { id: "login-code", type: "text", value: code, onChange: (e) => setCode(e.target.value), placeholder: "6\u4F4D\u6570\u5B57", maxLength: 6, required: true, autoComplete: "one-time-code", className: "flex-1 px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-ring" }), _jsx("button", { type: "button", onClick: handleSendCode, disabled: sending || cooldown > 0 || !email, className: "px-4 py-2 text-sm text-primary border border-primary/30 rounded-md hover:bg-primary/10 disabled:opacity-50", children: cooldown > 0
|
|
81
81
|
? `重发(${cooldown}s)`
|
|
82
82
|
: codeSent
|
|
83
83
|
? '重新发送'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ploykit/ui",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -15,11 +15,13 @@
|
|
|
15
15
|
"types": "./dist/components/ui/*.d.ts",
|
|
16
16
|
"default": "./dist/components/ui/*.js"
|
|
17
17
|
},
|
|
18
|
+
"./tailwind.css": "./tailwind.css",
|
|
18
19
|
"./package.json": "./package.json"
|
|
19
20
|
},
|
|
20
21
|
"files": [
|
|
21
22
|
"dist",
|
|
22
|
-
"src"
|
|
23
|
+
"src",
|
|
24
|
+
"tailwind.css"
|
|
23
25
|
],
|
|
24
26
|
"peerDependencies": {
|
|
25
27
|
"@tanstack/react-query": "^5.0.0",
|
|
@@ -30,8 +32,8 @@
|
|
|
30
32
|
},
|
|
31
33
|
"dependencies": {
|
|
32
34
|
"@base-ui/react": "^1.8.0",
|
|
33
|
-
"@ploykit/client": "^0.7.
|
|
34
|
-
"@ploykit/hooks": "^0.7.
|
|
35
|
+
"@ploykit/client": "^0.7.4",
|
|
36
|
+
"@ploykit/hooks": "^0.7.4",
|
|
35
37
|
"class-variance-authority": "^0.7.1",
|
|
36
38
|
"clsx": "^2.1.1",
|
|
37
39
|
"cn": "^0.4.0",
|
|
@@ -81,7 +81,7 @@ export function NotificationBell({ onNavigate }: { onNavigate?: (link: string) =
|
|
|
81
81
|
|
|
82
82
|
{open && (
|
|
83
83
|
<div
|
|
84
|
-
className="absolute right-0 mt-2 w-80 bg-
|
|
84
|
+
className="absolute right-0 mt-2 w-80 bg-popover rounded-lg shadow-lg border z-50"
|
|
85
85
|
role="dialog"
|
|
86
86
|
aria-label="通知列表"
|
|
87
87
|
onKeyDown={(e) => { if (e.key === 'Escape') setOpen(false) }}
|
package/src/pages/LoginPage.tsx
CHANGED
|
@@ -127,7 +127,7 @@ export function LoginPage({
|
|
|
127
127
|
|
|
128
128
|
return (
|
|
129
129
|
<div className="min-h-screen flex items-center justify-center bg-background">
|
|
130
|
-
<div className="w-full max-w-sm space-y-4 p-8 bg-
|
|
130
|
+
<div className="w-full max-w-sm space-y-4 p-8 bg-card rounded-lg shadow">
|
|
131
131
|
<div className="text-center">
|
|
132
132
|
<h1 className="text-2xl font-bold">{title}</h1>
|
|
133
133
|
<p className="mt-1 text-sm text-muted-foreground">{description}</p>
|
package/tailwind.css
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/* Tailwind v4 source registration for @ploykit/ui.
|
|
2
|
+
Tailwind v4 skips node_modules in automatic content detection, so standalone
|
|
3
|
+
(npm) consumers must register this package's source explicitly. @source
|
|
4
|
+
resolves relative to THIS stylesheet, so it points at the package's own src/
|
|
5
|
+
whether the package is consumed from a monorepo workspace or from node_modules.
|
|
6
|
+
Standalone consumers: add `@import "@ploykit/ui/tailwind.css";` to their CSS entry. */
|
|
7
|
+
@source "./src";
|