@ploykit/ui 0.1.0 → 0.1.1
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 +41 -41
- package/dist/components/NotificationBell.js +2 -2
- package/dist/components/UserMenu.js +1 -1
- package/package.json +63 -63
package/README.md
CHANGED
|
@@ -1,41 +1,41 @@
|
|
|
1
|
-
# @ploykit/ui
|
|
2
|
-
|
|
3
|
-
React 前端壳:登录/工作区/计费的 Headless Hook + 默认页面。
|
|
4
|
-
|
|
5
|
-
## 快速使用
|
|
6
|
-
|
|
7
|
-
```tsx
|
|
8
|
-
import { PloykitProvider, useAuth, useLogin, LoginPage } from '@ploykit/ui'
|
|
9
|
-
|
|
10
|
-
// App 根部
|
|
11
|
-
<PloykitProvider>
|
|
12
|
-
<App />
|
|
13
|
-
</PloykitProvider>
|
|
14
|
-
|
|
15
|
-
// 任意组件中
|
|
16
|
-
const { user, workspaces, loading } = useAuth()
|
|
17
|
-
const { sendCode, submit } = useLogin()
|
|
18
|
-
|
|
19
|
-
// 或直接用默认登录页
|
|
20
|
-
<LoginPage />
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
## 核心 API
|
|
24
|
-
|
|
25
|
-
| 导出 | 说明 |
|
|
26
|
-
|---|---|
|
|
27
|
-
| `PloykitProvider` | 根 Provider(App 根部包裹一次) |
|
|
28
|
-
| `useAuth()` | `{ user, workspaces, loading, refresh, logout }` |
|
|
29
|
-
| `useLogin()` | `{ sendCode, submit, loading, error }` |
|
|
30
|
-
| `useWorkspace()` | `{ current, switchTo, clear }` |
|
|
31
|
-
| `useWorkspaceSwitch()` | `{ current, list, switchTo, create, creating }` |
|
|
32
|
-
| `useBilling()` | `{ subscription, plans, checkout, loading }` |
|
|
33
|
-
| `PlanGate` | 付费门 `<PlanGate plan="pro" denied={<Upgrade/>}>` |
|
|
34
|
-
| `useApi()` | `{ get, post, put, delete }` 带认证 + 工作区上下文 |
|
|
35
|
-
| `LoginPage` | 默认登录页(可拷贝自定义,Hook 不变) |
|
|
36
|
-
|
|
37
|
-
## 自定义三个层级
|
|
38
|
-
|
|
39
|
-
1. **换皮**:CSS/Tailwind 覆盖样式
|
|
40
|
-
2. **换壳**:拷贝 `LoginPage.tsx` 到产品 `src/pages/`,改 JSX,Hook 调用不变
|
|
41
|
-
3. **纯逻辑**:只 `import { useLogin } from '@ploykit/ui'`,完全自己画 UI
|
|
1
|
+
# @ploykit/ui
|
|
2
|
+
|
|
3
|
+
React 前端壳:登录/工作区/计费的 Headless Hook + 默认页面。
|
|
4
|
+
|
|
5
|
+
## 快速使用
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { PloykitProvider, useAuth, useLogin, LoginPage } from '@ploykit/ui'
|
|
9
|
+
|
|
10
|
+
// App 根部
|
|
11
|
+
<PloykitProvider>
|
|
12
|
+
<App />
|
|
13
|
+
</PloykitProvider>
|
|
14
|
+
|
|
15
|
+
// 任意组件中
|
|
16
|
+
const { user, workspaces, loading } = useAuth()
|
|
17
|
+
const { sendCode, submit } = useLogin()
|
|
18
|
+
|
|
19
|
+
// 或直接用默认登录页
|
|
20
|
+
<LoginPage />
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 核心 API
|
|
24
|
+
|
|
25
|
+
| 导出 | 说明 |
|
|
26
|
+
|---|---|
|
|
27
|
+
| `PloykitProvider` | 根 Provider(App 根部包裹一次) |
|
|
28
|
+
| `useAuth()` | `{ user, workspaces, loading, refresh, logout }` |
|
|
29
|
+
| `useLogin()` | `{ sendCode, submit, loading, error }` |
|
|
30
|
+
| `useWorkspace()` | `{ current, switchTo, clear }` |
|
|
31
|
+
| `useWorkspaceSwitch()` | `{ current, list, switchTo, create, creating }` |
|
|
32
|
+
| `useBilling()` | `{ subscription, plans, checkout, loading }` |
|
|
33
|
+
| `PlanGate` | 付费门 `<PlanGate plan="pro" denied={<Upgrade/>}>` |
|
|
34
|
+
| `useApi()` | `{ get, post, put, delete }` 带认证 + 工作区上下文 |
|
|
35
|
+
| `LoginPage` | 默认登录页(可拷贝自定义,Hook 不变) |
|
|
36
|
+
|
|
37
|
+
## 自定义三个层级
|
|
38
|
+
|
|
39
|
+
1. **换皮**:CSS/Tailwind 覆盖样式
|
|
40
|
+
2. **换壳**:拷贝 `LoginPage.tsx` 到产品 `src/pages/`,改 JSX,Hook 调用不变
|
|
41
|
+
3. **纯逻辑**:只 `import { useLogin } from '@ploykit/ui'`,完全自己画 UI
|
|
@@ -40,8 +40,8 @@ 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\
|
|
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-blue-600 hover:underline disabled:opacity-50\
|
|
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-red-500 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')
|
|
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-blue-600 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-gray-400", "aria-live": "polite", children: "\u52A0\u8F7D\u4E2D\u2026" })) : notifications.length === 0 ? (_jsx("li", { className: "py-8 text-center text-sm text-gray-400", 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-gray-50 ' +
|
|
46
46
|
'focus-visible:outline-none focus-visible:bg-gray-50 focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring', !n.read_at && 'bg-blue-50/50'), children: [_jsxs("span", { className: "flex items-start justify-between gap-2", children: [_jsx("span", { className: "text-sm font-medium text-gray-900", children: n.title }), n.count > 1 && (_jsxs("span", { className: "text-xs bg-gray-100 text-gray-600 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-gray-500 line-clamp-2", children: n.body }), _jsx("span", { className: "block mt-1 text-[10px] text-gray-400", children: formatDateTime(n.created_at) })] }) }, n.id))) })] }))] }));
|
|
47
47
|
}
|
|
@@ -26,5 +26,5 @@ export function UserMenu({ items = [], logoutRedirect = '/login' }) {
|
|
|
26
26
|
}
|
|
27
27
|
navigate(logoutRedirect);
|
|
28
28
|
};
|
|
29
|
-
return (_jsxs(DropdownMenu, { children: [_jsxs(DropdownMenuTrigger, { className: "flex items-center gap-2 rounded-md px-2 py-1.5 text-sm text-muted-foreground\
|
|
29
|
+
return (_jsxs(DropdownMenu, { children: [_jsxs(DropdownMenuTrigger, { className: "flex items-center gap-2 rounded-md px-2 py-1.5 text-sm text-muted-foreground\n transition-colors hover:bg-accent hover:text-accent-foreground\n aria-expanded:bg-accent", "aria-label": "\u7528\u6237\u83DC\u5355", children: [_jsxs(Avatar, { size: "sm", children: [user?.avatar_url ? _jsx(AvatarImage, { src: user.avatar_url, alt: name }) : null, _jsx(AvatarFallback, { children: initialsOf(user?.display_name ?? '', email) })] }), _jsx("span", { className: "hidden max-w-40 truncate md:inline", children: name || '未登录' })] }), _jsxs(DropdownMenuContent, { align: "end", className: "w-56", children: [_jsx(DropdownMenuGroup, { children: _jsx(DropdownMenuLabel, { className: "p-0 font-normal", children: _jsxs("div", { className: "flex flex-col px-2 py-1.5", children: [_jsx("span", { className: "truncate text-sm font-medium text-foreground", children: name || '未登录' }), _jsx("span", { className: "truncate text-xs text-muted-foreground", children: email })] }) }) }), _jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { onClick: () => navigate('/account/profile'), children: [_jsx(Settings, {}), " \u8D26\u6237\u8BBE\u7F6E"] }), user?.is_platform_admin && (_jsxs(DropdownMenuItem, { onClick: () => navigate('/admin'), children: [_jsx(ShieldCheck, {}), " \u7BA1\u7406\u63A7\u5236\u53F0"] })), items.length > 0 && _jsx(DropdownMenuSeparator, {}), items.map((item) => (_jsxs(DropdownMenuItem, { variant: item.destructive ? 'destructive' : 'default', onClick: () => (item.to ? navigate(item.to) : item.onClick?.()), children: [item.icon, item.label] }, item.label))), _jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { variant: "destructive", onClick: handleLogout, children: [_jsx(LogOut, {}), " \u9000\u51FA\u767B\u5F55"] })] })] }));
|
|
30
30
|
}
|
package/package.json
CHANGED
|
@@ -1,63 +1,63 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@ploykit/ui",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"type": "module",
|
|
5
|
-
"main": "dist/index.js",
|
|
6
|
-
"types": "dist/index.d.ts",
|
|
7
|
-
"exports": {
|
|
8
|
-
".": {
|
|
9
|
-
"development": "./src/index.ts",
|
|
10
|
-
"types": "./dist/index.d.ts",
|
|
11
|
-
"default": "./dist/index.js"
|
|
12
|
-
},
|
|
13
|
-
"./package.json": "./package.json"
|
|
14
|
-
},
|
|
15
|
-
"files": [
|
|
16
|
-
"dist"
|
|
17
|
-
],
|
|
18
|
-
"peerDependencies": {
|
|
19
|
-
"@tanstack/react-query": "^5.0.0",
|
|
20
|
-
"react": "^19.0.0",
|
|
21
|
-
"react-dom": "^19.0.0",
|
|
22
|
-
"react-router": "^7.0.0",
|
|
23
|
-
"tailwindcss": "^4.0.0"
|
|
24
|
-
},
|
|
25
|
-
"dependencies": {
|
|
26
|
-
"@base-ui/react": "^1.8.0",
|
|
27
|
-
"@hookform/resolvers": "^5.9.1",
|
|
28
|
-
"@ploykit/client": "^0.2.0",
|
|
29
|
-
"@ploykit/runtime": "^0.1.0",
|
|
30
|
-
"class-variance-authority": "^0.7.1",
|
|
31
|
-
"clsx": "^2.1.1",
|
|
32
|
-
"lucide-react": "^1.52.0",
|
|
33
|
-
"react-hook-form": "^7.0.0",
|
|
34
|
-
"sonner": "^2.0.8",
|
|
35
|
-
"tailwind-merge": "^3.7.0",
|
|
36
|
-
"zod": "^4.0.0"
|
|
37
|
-
},
|
|
38
|
-
"description": "React 前端壳:登录/工作区/计费的 Headless Hook + 默认页面。产物为 ESM(相对导入无扩展名,消费方需经 vite/esbuild 等打包器解析);exports 的 development 条件指向源码,供 monorepo 开发期直出消费。",
|
|
39
|
-
"scripts": {
|
|
40
|
-
"build": "tsc -p tsconfig.json",
|
|
41
|
-
"test": "vitest run",
|
|
42
|
-
"prepublishOnly": "npm run build"
|
|
43
|
-
},
|
|
44
|
-
"keywords": [],
|
|
45
|
-
"author": "",
|
|
46
|
-
"license": "ISC",
|
|
47
|
-
"devDependencies": {
|
|
48
|
-
"@tailwindcss/vite": "^4.3.3",
|
|
49
|
-
"@testing-library/dom": "^10.4.2",
|
|
50
|
-
"@testing-library/jest-dom": "^7.0.1",
|
|
51
|
-
"@testing-library/react": "^16.3.3",
|
|
52
|
-
"jsdom": "^29.1.1",
|
|
53
|
-
"react-router": "7.18.4",
|
|
54
|
-
"tailwindcss": "^4.3.3",
|
|
55
|
-
"typescript": "^5.9.3",
|
|
56
|
-
"vitest": "^5.0.3"
|
|
57
|
-
},
|
|
58
|
-
"repository": {
|
|
59
|
-
"type": "git",
|
|
60
|
-
"url": "git+https://github.com/haozing/ploykit.git",
|
|
61
|
-
"directory": "packages/ui"
|
|
62
|
-
}
|
|
63
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@ploykit/ui",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"development": "./src/index.ts",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./package.json": "./package.json"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"peerDependencies": {
|
|
19
|
+
"@tanstack/react-query": "^5.0.0",
|
|
20
|
+
"react": "^19.0.0",
|
|
21
|
+
"react-dom": "^19.0.0",
|
|
22
|
+
"react-router": "^7.0.0",
|
|
23
|
+
"tailwindcss": "^4.0.0"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@base-ui/react": "^1.8.0",
|
|
27
|
+
"@hookform/resolvers": "^5.9.1",
|
|
28
|
+
"@ploykit/client": "^0.2.0",
|
|
29
|
+
"@ploykit/runtime": "^0.1.0",
|
|
30
|
+
"class-variance-authority": "^0.7.1",
|
|
31
|
+
"clsx": "^2.1.1",
|
|
32
|
+
"lucide-react": "^1.52.0",
|
|
33
|
+
"react-hook-form": "^7.0.0",
|
|
34
|
+
"sonner": "^2.0.8",
|
|
35
|
+
"tailwind-merge": "^3.7.0",
|
|
36
|
+
"zod": "^4.0.0"
|
|
37
|
+
},
|
|
38
|
+
"description": "React 前端壳:登录/工作区/计费的 Headless Hook + 默认页面。产物为 ESM(相对导入无扩展名,消费方需经 vite/esbuild 等打包器解析);exports 的 development 条件指向源码,供 monorepo 开发期直出消费。",
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsc -p tsconfig.json",
|
|
41
|
+
"test": "vitest run",
|
|
42
|
+
"prepublishOnly": "npm run build"
|
|
43
|
+
},
|
|
44
|
+
"keywords": [],
|
|
45
|
+
"author": "",
|
|
46
|
+
"license": "ISC",
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@tailwindcss/vite": "^4.3.3",
|
|
49
|
+
"@testing-library/dom": "^10.4.2",
|
|
50
|
+
"@testing-library/jest-dom": "^7.0.1",
|
|
51
|
+
"@testing-library/react": "^16.3.3",
|
|
52
|
+
"jsdom": "^29.1.1",
|
|
53
|
+
"react-router": "7.18.4",
|
|
54
|
+
"tailwindcss": "^4.3.3",
|
|
55
|
+
"typescript": "^5.9.3",
|
|
56
|
+
"vitest": "^5.0.3"
|
|
57
|
+
},
|
|
58
|
+
"repository": {
|
|
59
|
+
"type": "git",
|
|
60
|
+
"url": "git+https://github.com/haozing/ploykit.git",
|
|
61
|
+
"directory": "packages/ui"
|
|
62
|
+
}
|
|
63
|
+
}
|