teamshare-bridge 0.22.7 → 0.22.8
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 +383 -383
- package/dist/bridge/daemon.js +43 -20
- package/dist/bridge/daemon.js.map +1 -1
- package/dist/bridge/install-verify.js +8 -8
- package/dist/bridge/spawn.d.ts +14 -0
- package/dist/bridge/spawn.js +7 -1
- package/dist/bridge/spawn.js.map +1 -1
- package/dist/bridge/wake-handler.d.ts +8 -15
- package/dist/bridge/wake-handler.js +109 -14
- package/dist/bridge/wake-handler.js.map +1 -1
- package/dist/bridge/wake-outbox.js +7 -0
- package/dist/bridge/wake-outbox.js.map +1 -1
- package/dist/cli/commands/plan.js +4 -0
- package/dist/cli/commands/plan.js.map +1 -1
- package/dist/lib/browser/human-input-guard.js +98 -98
- package/dist/lib/browser/screen-change.js +60 -60
- package/dist/lib/llm/usage-reader.js +4 -4
- package/dist/lib/workspace/classify.d.ts +29 -0
- package/dist/lib/workspace/classify.js +62 -0
- package/dist/lib/workspace/classify.js.map +1 -1
- package/dist/lib/workspace/presence.d.ts +31 -0
- package/dist/lib/workspace/presence.js +35 -1
- package/dist/lib/workspace/presence.js.map +1 -1
- package/dist/lib/workspace/shim-runner.js +23 -0
- package/dist/lib/workspace/shim-runner.js.map +1 -1
- package/package.json +70 -70
- package/skills/THIRD_PARTY_NOTICES.md +26 -26
- package/skills/architect/LICENSE +21 -21
- package/skills/codebase-design/LICENSE +21 -21
- package/skills/diagnosing-bugs/LICENSE +21 -21
- package/skills/domain-modeling/LICENSE +21 -21
- package/skills/improve-codebase-architecture/LICENSE +21 -21
- package/skills/recover/LICENSE +21 -21
- package/skills/review/LICENSE +21 -21
- package/skills/tdd/LICENSE +21 -21
- package/skills/teamshare-auth-rbac/SKILL.md +78 -78
- package/skills/teamshare-cdd/SKILL.md +205 -205
- package/skills/teamshare-modularity/SKILL.md +146 -146
- package/skills/teamshare-orchestrator-ask/SKILL.md +77 -77
- package/skills/teamshare-scaffold/SKILL.md +116 -116
- package/templates/backend/.env.example.tpl +13 -13
- package/templates/backend/nest-cli.json.tpl +8 -8
- package/templates/backend/package.json.tpl +41 -41
- package/templates/backend/prisma/schema.prisma.tpl +39 -39
- package/templates/backend/src/app.module.ts.tpl +24 -24
- package/templates/backend/src/common/filters/all-exceptions.filter.ts.tpl +59 -59
- package/templates/backend/src/common/guards/jwt-auth.guard.ts.tpl +27 -27
- package/templates/backend/src/common/guards/rbac.guard.ts.tpl +48 -48
- package/templates/backend/src/common/interceptors/transform.interceptor.ts.tpl +44 -44
- package/templates/backend/src/common/pipes/zod-validation.pipe.ts.tpl +29 -29
- package/templates/backend/src/common/swagger/envelope.schemas.ts.tpl +53 -53
- package/templates/backend/src/common/swagger/error.schemas.ts.tpl +62 -62
- package/templates/backend/src/main.ts.tpl +27 -27
- package/templates/backend/src/modules/example/example.controller.ts.tpl +102 -102
- package/templates/backend/src/modules/example/example.dto.ts.tpl +14 -14
- package/templates/backend/src/modules/example/example.module.ts.tpl +10 -10
- package/templates/backend/src/modules/example/example.service.ts.tpl +45 -45
- package/templates/backend/tsconfig.json.tpl +26 -26
- package/templates/common/.gitignore.tpl +42 -42
- package/templates/common/AGENTS.md.tpl +58 -58
- package/templates/common/docs/adr/README.md +25 -25
- package/templates/common/docs/api-contract.md.tpl +72 -72
- package/templates/web/next.config.ts.tpl +7 -7
- package/templates/web/package.json.tpl +37 -37
- package/templates/web/src/app/globals.css.tpl +48 -48
- package/templates/web/src/app/layout.tsx.tpl +25 -25
- package/templates/web/src/components/shared/app-button.tsx.tpl +59 -59
- package/templates/web/src/components/shared/app-empty-state.tsx.tpl +39 -39
- package/templates/web/src/components/shared/app-list-toolbar.tsx.tpl +63 -63
- package/templates/web/src/components/shared/app-skeleton.tsx.tpl +14 -14
- package/templates/web/src/components/shared/app-table.tsx.tpl +128 -128
- package/templates/web/src/components/shared/index.ts.tpl +5 -5
- package/templates/web/src/hooks/use-list-state.ts.tpl +72 -72
- package/templates/web/src/hooks/use-url-tab.ts.tpl +25 -25
- package/templates/web/src/lib/api/client.ts.tpl +123 -123
- package/templates/web/src/lib/providers.tsx.tpl +35 -35
- package/templates/web/src/lib/query-keys.ts.tpl +21 -21
- package/templates/web/src/lib/utils.ts.tpl +7 -7
- package/templates/web/src/lib/validation/schemas.ts.tpl +15 -15
- package/templates/web/src/modules/example/views/example-list-view.tsx.tpl +97 -97
- package/templates/web/tsconfig.json.tpl +23 -23
- package/tools/workspace-arbitration-check.mjs +389 -389
|
@@ -1,72 +1,72 @@
|
|
|
1
|
-
# API Contract - {{projectName}}
|
|
2
|
-
|
|
3
|
-
## Response envelope
|
|
4
|
-
|
|
5
|
-
Every successful response returns:
|
|
6
|
-
|
|
7
|
-
```json
|
|
8
|
-
{
|
|
9
|
-
"success": true,
|
|
10
|
-
"data": { ... }
|
|
11
|
-
}
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
Paginated responses include a `pagination` field:
|
|
15
|
-
|
|
16
|
-
```json
|
|
17
|
-
{
|
|
18
|
-
"success": true,
|
|
19
|
-
"data": [ ... ],
|
|
20
|
-
"pagination": {
|
|
21
|
-
"page": 1,
|
|
22
|
-
"limit": 20,
|
|
23
|
-
"total": 100,
|
|
24
|
-
"totalPages": 5
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
## Error envelope
|
|
30
|
-
|
|
31
|
-
```json
|
|
32
|
-
{
|
|
33
|
-
"success": false,
|
|
34
|
-
"error": {
|
|
35
|
-
"code": "NOT_FOUND",
|
|
36
|
-
"message": "Resource not found",
|
|
37
|
-
"details": null
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
Common error codes: `BAD_REQUEST`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `CONFLICT`, `UNPROCESSABLE_ENTITY`, `TOO_MANY_REQUESTS`, `INTERNAL_ERROR`.
|
|
43
|
-
|
|
44
|
-
## Authentication
|
|
45
|
-
|
|
46
|
-
- Bearer token in `Authorization` header
|
|
47
|
-
- JWT issued by `/api/auth/login`
|
|
48
|
-
- Public routes marked with `@Public()` decorator
|
|
49
|
-
|
|
50
|
-
## RBAC
|
|
51
|
-
|
|
52
|
-
Route permissions declared via `@Permissions("resource:action")` decorator.
|
|
53
|
-
Roles resolve against the addressed company scope.
|
|
54
|
-
|
|
55
|
-
| Role | Description |
|
|
56
|
-
|---|---|
|
|
57
|
-
| owner | Full access |
|
|
58
|
-
| admin | Manage members, settings |
|
|
59
|
-
| member | Standard access |
|
|
60
|
-
| viewer | Read-only |
|
|
61
|
-
|
|
62
|
-
## Pagination
|
|
63
|
-
|
|
64
|
-
Query params: `page` (default 1), `limit` (default 20, max 100).
|
|
65
|
-
|
|
66
|
-
## Search
|
|
67
|
-
|
|
68
|
-
`search` query param performs case-insensitive text matching.
|
|
69
|
-
|
|
70
|
-
## Sorting
|
|
71
|
-
|
|
72
|
-
`sort` and `order` query params (e.g. `?sort=createdAt&order=desc`).
|
|
1
|
+
# API Contract - {{projectName}}
|
|
2
|
+
|
|
3
|
+
## Response envelope
|
|
4
|
+
|
|
5
|
+
Every successful response returns:
|
|
6
|
+
|
|
7
|
+
```json
|
|
8
|
+
{
|
|
9
|
+
"success": true,
|
|
10
|
+
"data": { ... }
|
|
11
|
+
}
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Paginated responses include a `pagination` field:
|
|
15
|
+
|
|
16
|
+
```json
|
|
17
|
+
{
|
|
18
|
+
"success": true,
|
|
19
|
+
"data": [ ... ],
|
|
20
|
+
"pagination": {
|
|
21
|
+
"page": 1,
|
|
22
|
+
"limit": 20,
|
|
23
|
+
"total": 100,
|
|
24
|
+
"totalPages": 5
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Error envelope
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"success": false,
|
|
34
|
+
"error": {
|
|
35
|
+
"code": "NOT_FOUND",
|
|
36
|
+
"message": "Resource not found",
|
|
37
|
+
"details": null
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Common error codes: `BAD_REQUEST`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `CONFLICT`, `UNPROCESSABLE_ENTITY`, `TOO_MANY_REQUESTS`, `INTERNAL_ERROR`.
|
|
43
|
+
|
|
44
|
+
## Authentication
|
|
45
|
+
|
|
46
|
+
- Bearer token in `Authorization` header
|
|
47
|
+
- JWT issued by `/api/auth/login`
|
|
48
|
+
- Public routes marked with `@Public()` decorator
|
|
49
|
+
|
|
50
|
+
## RBAC
|
|
51
|
+
|
|
52
|
+
Route permissions declared via `@Permissions("resource:action")` decorator.
|
|
53
|
+
Roles resolve against the addressed company scope.
|
|
54
|
+
|
|
55
|
+
| Role | Description |
|
|
56
|
+
|---|---|
|
|
57
|
+
| owner | Full access |
|
|
58
|
+
| admin | Manage members, settings |
|
|
59
|
+
| member | Standard access |
|
|
60
|
+
| viewer | Read-only |
|
|
61
|
+
|
|
62
|
+
## Pagination
|
|
63
|
+
|
|
64
|
+
Query params: `page` (default 1), `limit` (default 20, max 100).
|
|
65
|
+
|
|
66
|
+
## Search
|
|
67
|
+
|
|
68
|
+
`search` query param performs case-insensitive text matching.
|
|
69
|
+
|
|
70
|
+
## Sorting
|
|
71
|
+
|
|
72
|
+
`sort` and `order` query params (e.g. `?sort=createdAt&order=desc`).
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { NextConfig } from "next";
|
|
2
|
-
|
|
3
|
-
const nextConfig: NextConfig = {
|
|
4
|
-
reactStrictMode: true,
|
|
5
|
-
};
|
|
6
|
-
|
|
7
|
-
export default nextConfig;
|
|
1
|
+
import type { NextConfig } from "next";
|
|
2
|
+
|
|
3
|
+
const nextConfig: NextConfig = {
|
|
4
|
+
reactStrictMode: true,
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export default nextConfig;
|
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "{{packageName}}",
|
|
3
|
-
"version": "0.1.0",
|
|
4
|
-
"private": true,
|
|
5
|
-
"description": "{{description}}",
|
|
6
|
-
"scripts": {
|
|
7
|
-
"dev": "next dev --turbopack",
|
|
8
|
-
"build": "next build",
|
|
9
|
-
"start": "next start",
|
|
10
|
-
"lint": "next lint"
|
|
11
|
-
},
|
|
12
|
-
"dependencies": {
|
|
13
|
-
"next": "^16.0.0",
|
|
14
|
-
"react": "^19.0.0",
|
|
15
|
-
"react-dom": "^19.0.0",
|
|
16
|
-
"next-auth": "^4.24.0",
|
|
17
|
-
"next-themes": "^0.4.0",
|
|
18
|
-
"@tanstack/react-query": "^5.60.0",
|
|
19
|
-
"@tanstack/react-table": "^8.20.0",
|
|
20
|
-
"react-hook-form": "^7.53.0",
|
|
21
|
-
"@hookform/resolvers": "^3.9.0",
|
|
22
|
-
"zod": "^3.23.0",
|
|
23
|
-
"socket.io-client": "^4.8.0",
|
|
24
|
-
"iconsax-react": "^0.0.8",
|
|
25
|
-
"clsx": "^2.1.0",
|
|
26
|
-
"tailwind-merge": "^2.5.0"
|
|
27
|
-
},
|
|
28
|
-
"devDependencies": {
|
|
29
|
-
"@types/node": "^22.0.0",
|
|
30
|
-
"@types/react": "^19.0.0",
|
|
31
|
-
"@types/react-dom": "^19.0.0",
|
|
32
|
-
"typescript": "^5.6.0",
|
|
33
|
-
"tailwindcss": "^4.0.0",
|
|
34
|
-
"@tailwindcss/postcss": "^4.0.0",
|
|
35
|
-
"postcss": "^8.4.0"
|
|
36
|
-
}
|
|
37
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "{{packageName}}",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "{{description}}",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "next dev --turbopack",
|
|
8
|
+
"build": "next build",
|
|
9
|
+
"start": "next start",
|
|
10
|
+
"lint": "next lint"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"next": "^16.0.0",
|
|
14
|
+
"react": "^19.0.0",
|
|
15
|
+
"react-dom": "^19.0.0",
|
|
16
|
+
"next-auth": "^4.24.0",
|
|
17
|
+
"next-themes": "^0.4.0",
|
|
18
|
+
"@tanstack/react-query": "^5.60.0",
|
|
19
|
+
"@tanstack/react-table": "^8.20.0",
|
|
20
|
+
"react-hook-form": "^7.53.0",
|
|
21
|
+
"@hookform/resolvers": "^3.9.0",
|
|
22
|
+
"zod": "^3.23.0",
|
|
23
|
+
"socket.io-client": "^4.8.0",
|
|
24
|
+
"iconsax-react": "^0.0.8",
|
|
25
|
+
"clsx": "^2.1.0",
|
|
26
|
+
"tailwind-merge": "^2.5.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^22.0.0",
|
|
30
|
+
"@types/react": "^19.0.0",
|
|
31
|
+
"@types/react-dom": "^19.0.0",
|
|
32
|
+
"typescript": "^5.6.0",
|
|
33
|
+
"tailwindcss": "^4.0.0",
|
|
34
|
+
"@tailwindcss/postcss": "^4.0.0",
|
|
35
|
+
"postcss": "^8.4.0"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -1,48 +1,48 @@
|
|
|
1
|
-
@import "tailwindcss";
|
|
2
|
-
|
|
3
|
-
@theme inline {
|
|
4
|
-
--app-primary-50: #eff6ff;
|
|
5
|
-
--app-primary-100: #dbeafe;
|
|
6
|
-
--app-primary-200: #bfdbfe;
|
|
7
|
-
--app-primary-300: #93c5fd;
|
|
8
|
-
--app-primary-400: #60a5fa;
|
|
9
|
-
--app-primary-500: #3b82f6;
|
|
10
|
-
--app-primary-600: #2563eb;
|
|
11
|
-
--app-primary-700: #1d4ed8;
|
|
12
|
-
--app-primary-800: #1e40af;
|
|
13
|
-
--app-primary-900: #1e3a8a;
|
|
14
|
-
|
|
15
|
-
--app-bg-primary: #ffffff;
|
|
16
|
-
--app-bg-secondary: #f8fafc;
|
|
17
|
-
--app-bg-tertiary: #f1f5f9;
|
|
18
|
-
--app-bg-inverse: #0f172a;
|
|
19
|
-
|
|
20
|
-
--app-text-primary: #0f172a;
|
|
21
|
-
--app-text-secondary: #475569;
|
|
22
|
-
--app-text-tertiary: #94a3b8;
|
|
23
|
-
--app-text-inverse: #f8fafc;
|
|
24
|
-
|
|
25
|
-
--app-border-default: #e2e8f0;
|
|
26
|
-
--app-border-strong: #cbd5e1;
|
|
27
|
-
|
|
28
|
-
--app-radius: 0.5rem;
|
|
29
|
-
|
|
30
|
-
--color-background: var(--app-bg-primary);
|
|
31
|
-
--color-foreground: var(--app-text-primary);
|
|
32
|
-
--color-muted: var(--app-bg-secondary);
|
|
33
|
-
--color-muted-foreground: var(--app-text-secondary);
|
|
34
|
-
--color-border: var(--app-border-default);
|
|
35
|
-
--color-primary: var(--app-primary-600);
|
|
36
|
-
--color-primary-foreground: var(--app-text-inverse);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
@custom-variant dark (&:is(.dark *));
|
|
40
|
-
|
|
41
|
-
@layer base {
|
|
42
|
-
* {
|
|
43
|
-
@apply border-border;
|
|
44
|
-
}
|
|
45
|
-
body {
|
|
46
|
-
@apply bg-background text-foreground;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
|
|
3
|
+
@theme inline {
|
|
4
|
+
--app-primary-50: #eff6ff;
|
|
5
|
+
--app-primary-100: #dbeafe;
|
|
6
|
+
--app-primary-200: #bfdbfe;
|
|
7
|
+
--app-primary-300: #93c5fd;
|
|
8
|
+
--app-primary-400: #60a5fa;
|
|
9
|
+
--app-primary-500: #3b82f6;
|
|
10
|
+
--app-primary-600: #2563eb;
|
|
11
|
+
--app-primary-700: #1d4ed8;
|
|
12
|
+
--app-primary-800: #1e40af;
|
|
13
|
+
--app-primary-900: #1e3a8a;
|
|
14
|
+
|
|
15
|
+
--app-bg-primary: #ffffff;
|
|
16
|
+
--app-bg-secondary: #f8fafc;
|
|
17
|
+
--app-bg-tertiary: #f1f5f9;
|
|
18
|
+
--app-bg-inverse: #0f172a;
|
|
19
|
+
|
|
20
|
+
--app-text-primary: #0f172a;
|
|
21
|
+
--app-text-secondary: #475569;
|
|
22
|
+
--app-text-tertiary: #94a3b8;
|
|
23
|
+
--app-text-inverse: #f8fafc;
|
|
24
|
+
|
|
25
|
+
--app-border-default: #e2e8f0;
|
|
26
|
+
--app-border-strong: #cbd5e1;
|
|
27
|
+
|
|
28
|
+
--app-radius: 0.5rem;
|
|
29
|
+
|
|
30
|
+
--color-background: var(--app-bg-primary);
|
|
31
|
+
--color-foreground: var(--app-text-primary);
|
|
32
|
+
--color-muted: var(--app-bg-secondary);
|
|
33
|
+
--color-muted-foreground: var(--app-text-secondary);
|
|
34
|
+
--color-border: var(--app-border-default);
|
|
35
|
+
--color-primary: var(--app-primary-600);
|
|
36
|
+
--color-primary-foreground: var(--app-text-inverse);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@custom-variant dark (&:is(.dark *));
|
|
40
|
+
|
|
41
|
+
@layer base {
|
|
42
|
+
* {
|
|
43
|
+
@apply border-border;
|
|
44
|
+
}
|
|
45
|
+
body {
|
|
46
|
+
@apply bg-background text-foreground;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
import type { Metadata } from "next";
|
|
2
|
-
import { Inter } from "next/font/google";
|
|
3
|
-
import { Providers } from "@/lib/providers";
|
|
4
|
-
import "./globals.css";
|
|
5
|
-
|
|
6
|
-
const inter = Inter({ subsets: ["latin"] });
|
|
7
|
-
|
|
8
|
-
export const metadata: Metadata = {
|
|
9
|
-
title: "{{projectName}}",
|
|
10
|
-
description: "{{description}}",
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
export default function RootLayout({
|
|
14
|
-
children,
|
|
15
|
-
}: {
|
|
16
|
-
children: React.ReactNode;
|
|
17
|
-
}) {
|
|
18
|
-
return (
|
|
19
|
-
<html lang="en" suppressHydrationWarning>
|
|
20
|
-
<body className={inter.className}>
|
|
21
|
-
<Providers>{children}</Providers>
|
|
22
|
-
</body>
|
|
23
|
-
</html>
|
|
24
|
-
);
|
|
25
|
-
}
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import { Inter } from "next/font/google";
|
|
3
|
+
import { Providers } from "@/lib/providers";
|
|
4
|
+
import "./globals.css";
|
|
5
|
+
|
|
6
|
+
const inter = Inter({ subsets: ["latin"] });
|
|
7
|
+
|
|
8
|
+
export const metadata: Metadata = {
|
|
9
|
+
title: "{{projectName}}",
|
|
10
|
+
description: "{{description}}",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default function RootLayout({
|
|
14
|
+
children,
|
|
15
|
+
}: {
|
|
16
|
+
children: React.ReactNode;
|
|
17
|
+
}) {
|
|
18
|
+
return (
|
|
19
|
+
<html lang="en" suppressHydrationWarning>
|
|
20
|
+
<body className={inter.className}>
|
|
21
|
+
<Providers>{children}</Providers>
|
|
22
|
+
</body>
|
|
23
|
+
</html>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -1,59 +1,59 @@
|
|
|
1
|
-
import { Loader2 } from "lucide-react";
|
|
2
|
-
import { type ButtonHTMLAttributes, forwardRef } from "react";
|
|
3
|
-
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
|
-
import { cn } from "@/lib/utils";
|
|
5
|
-
|
|
6
|
-
const buttonVariants = cva(
|
|
7
|
-
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
|
|
8
|
-
{
|
|
9
|
-
variants: {
|
|
10
|
-
variant: {
|
|
11
|
-
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
12
|
-
destructive:
|
|
13
|
-
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
|
14
|
-
outline:
|
|
15
|
-
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
|
16
|
-
secondary:
|
|
17
|
-
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
18
|
-
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
19
|
-
link: "text-primary underline-offset-4 hover:underline",
|
|
20
|
-
},
|
|
21
|
-
size: {
|
|
22
|
-
default: "h-10 px-4 py-2",
|
|
23
|
-
sm: "h-9 rounded-md px-3",
|
|
24
|
-
lg: "h-11 rounded-md px-8",
|
|
25
|
-
icon: "h-10 w-10",
|
|
26
|
-
},
|
|
27
|
-
},
|
|
28
|
-
defaultVariants: {
|
|
29
|
-
variant: "default",
|
|
30
|
-
size: "default",
|
|
31
|
-
},
|
|
32
|
-
}
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
export interface AppButtonProps
|
|
36
|
-
extends ButtonHTMLAttributes<HTMLButtonElement>,
|
|
37
|
-
VariantProps<typeof buttonVariants> {
|
|
38
|
-
loading?: boolean;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const AppButton = forwardRef<HTMLButtonElement, AppButtonProps>(
|
|
42
|
-
({ className, variant, size, loading, disabled, children, ...props }, ref) => {
|
|
43
|
-
return (
|
|
44
|
-
<button
|
|
45
|
-
ref={ref}
|
|
46
|
-
data-slot="app-button"
|
|
47
|
-
className={cn(buttonVariants({ variant, size, className }))}
|
|
48
|
-
disabled={disabled || loading}
|
|
49
|
-
{...props}
|
|
50
|
-
>
|
|
51
|
-
{loading && <Loader2 className="h-4 w-4 animate-spin" variant="Outline" color="currentColor" />}
|
|
52
|
-
{children}
|
|
53
|
-
</button>
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
);
|
|
57
|
-
AppButton.displayName = "AppButton";
|
|
58
|
-
|
|
59
|
-
export { AppButton, buttonVariants };
|
|
1
|
+
import { Loader2 } from "lucide-react";
|
|
2
|
+
import { type ButtonHTMLAttributes, forwardRef } from "react";
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
|
+
import { cn } from "@/lib/utils";
|
|
5
|
+
|
|
6
|
+
const buttonVariants = cva(
|
|
7
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
12
|
+
destructive:
|
|
13
|
+
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
|
14
|
+
outline:
|
|
15
|
+
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
|
16
|
+
secondary:
|
|
17
|
+
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
18
|
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
19
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
20
|
+
},
|
|
21
|
+
size: {
|
|
22
|
+
default: "h-10 px-4 py-2",
|
|
23
|
+
sm: "h-9 rounded-md px-3",
|
|
24
|
+
lg: "h-11 rounded-md px-8",
|
|
25
|
+
icon: "h-10 w-10",
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
defaultVariants: {
|
|
29
|
+
variant: "default",
|
|
30
|
+
size: "default",
|
|
31
|
+
},
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
export interface AppButtonProps
|
|
36
|
+
extends ButtonHTMLAttributes<HTMLButtonElement>,
|
|
37
|
+
VariantProps<typeof buttonVariants> {
|
|
38
|
+
loading?: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const AppButton = forwardRef<HTMLButtonElement, AppButtonProps>(
|
|
42
|
+
({ className, variant, size, loading, disabled, children, ...props }, ref) => {
|
|
43
|
+
return (
|
|
44
|
+
<button
|
|
45
|
+
ref={ref}
|
|
46
|
+
data-slot="app-button"
|
|
47
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
48
|
+
disabled={disabled || loading}
|
|
49
|
+
{...props}
|
|
50
|
+
>
|
|
51
|
+
{loading && <Loader2 className="h-4 w-4 animate-spin" variant="Outline" color="currentColor" />}
|
|
52
|
+
{children}
|
|
53
|
+
</button>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
AppButton.displayName = "AppButton";
|
|
58
|
+
|
|
59
|
+
export { AppButton, buttonVariants };
|
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
import { type ReactNode } from "react";
|
|
2
|
-
import { cn } from "@/lib/utils";
|
|
3
|
-
|
|
4
|
-
export interface AppEmptyStateProps {
|
|
5
|
-
icon?: ReactNode;
|
|
6
|
-
title: string;
|
|
7
|
-
description?: string;
|
|
8
|
-
action?: ReactNode;
|
|
9
|
-
className?: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function AppEmptyState({
|
|
13
|
-
icon,
|
|
14
|
-
title,
|
|
15
|
-
description,
|
|
16
|
-
action,
|
|
17
|
-
className,
|
|
18
|
-
}: AppEmptyStateProps) {
|
|
19
|
-
return (
|
|
20
|
-
<div
|
|
21
|
-
data-slot="app-empty-state"
|
|
22
|
-
className={cn(
|
|
23
|
-
"flex flex-col items-center justify-center py-12 text-center",
|
|
24
|
-
className
|
|
25
|
-
)}
|
|
26
|
-
>
|
|
27
|
-
{icon && (
|
|
28
|
-
<div className="mb-4 text-tertiary [&_svg]:h-12 [&_svg]:w-12">
|
|
29
|
-
{icon}
|
|
30
|
-
</div>
|
|
31
|
-
)}
|
|
32
|
-
<h3 className="text-lg font-semibold text-primary">{title}</h3>
|
|
33
|
-
{description && (
|
|
34
|
-
<p className="mt-1 max-w-sm text-sm text-secondary">{description}</p>
|
|
35
|
-
)}
|
|
36
|
-
{action && <div className="mt-4">{action}</div>}
|
|
37
|
-
</div>
|
|
38
|
-
);
|
|
39
|
-
}
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { cn } from "@/lib/utils";
|
|
3
|
+
|
|
4
|
+
export interface AppEmptyStateProps {
|
|
5
|
+
icon?: ReactNode;
|
|
6
|
+
title: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
action?: ReactNode;
|
|
9
|
+
className?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function AppEmptyState({
|
|
13
|
+
icon,
|
|
14
|
+
title,
|
|
15
|
+
description,
|
|
16
|
+
action,
|
|
17
|
+
className,
|
|
18
|
+
}: AppEmptyStateProps) {
|
|
19
|
+
return (
|
|
20
|
+
<div
|
|
21
|
+
data-slot="app-empty-state"
|
|
22
|
+
className={cn(
|
|
23
|
+
"flex flex-col items-center justify-center py-12 text-center",
|
|
24
|
+
className
|
|
25
|
+
)}
|
|
26
|
+
>
|
|
27
|
+
{icon && (
|
|
28
|
+
<div className="mb-4 text-tertiary [&_svg]:h-12 [&_svg]:w-12">
|
|
29
|
+
{icon}
|
|
30
|
+
</div>
|
|
31
|
+
)}
|
|
32
|
+
<h3 className="text-lg font-semibold text-primary">{title}</h3>
|
|
33
|
+
{description && (
|
|
34
|
+
<p className="mt-1 max-w-sm text-sm text-secondary">{description}</p>
|
|
35
|
+
)}
|
|
36
|
+
{action && <div className="mt-4">{action}</div>}
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
39
|
+
}
|