@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-white rounded-lg shadow-lg border z-50", role: "dialog", "aria-label": "\u901A\u77E5\u5217\u8868", onKeyDown: (e) => { if (e.key === 'Escape')
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))) })] }))] }));
@@ -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-white 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
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.0",
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.0",
34
- "@ploykit/hooks": "^0.7.0",
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-white rounded-lg shadow-lg border z-50"
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) }}
@@ -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-white rounded-lg shadow">
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";