create-lego-box 0.1.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.
Files changed (42) hide show
  1. package/README.md +70 -0
  2. package/index.js +169 -0
  3. package/package.json +31 -0
  4. package/templates/packages/create-pilet/index.js +200 -0
  5. package/templates/packages/create-pilet/package.json +14 -0
  6. package/templates/packages/create-pilet/templates/.krasrc +1 -0
  7. package/templates/packages/create-pilet/templates/package.json +1 -0
  8. package/templates/packages/create-pilet/templates/src/index.tsx +85 -0
  9. package/templates/packages/create-pilet/templates/src/pages/Test1Page.tsx +23 -0
  10. package/templates/packages/create-pilet/templates/src/pages/Test2Page.tsx +22 -0
  11. package/templates/packages/create-pilet/templates/src/pages/demo.tsx +220 -0
  12. package/templates/packages/create-pilet/templates/src/pages/home.tsx +20 -0
  13. package/templates/packages/create-pilet/templates/tailwind.config.js +54 -0
  14. package/templates/packages/create-pilet/templates/tsconfig.json +1 -0
  15. package/templates/packages/create-pilet/templates/webpack.config.js +34 -0
  16. package/templates/packages/create-pilet/templates.js +117 -0
  17. package/templates/packages/ui-kit/package.json +42 -0
  18. package/templates/packages/ui-kit/postcss.config.js +6 -0
  19. package/templates/packages/ui-kit/src/components/example-custom-component.tsx +21 -0
  20. package/templates/packages/ui-kit/src/components/index.ts +6 -0
  21. package/templates/packages/ui-kit/src/index.css +75 -0
  22. package/templates/packages/ui-kit/src/index.ts +10 -0
  23. package/templates/packages/ui-kit/tailwind.config.js +26 -0
  24. package/templates/packages/ui-kit/tsconfig.json +8 -0
  25. package/templates/packages/ui-kit/tsup.config.ts +15 -0
  26. package/templates/pilets/my-pilet/.krasrc +1 -0
  27. package/templates/pilets/my-pilet/package.json +32 -0
  28. package/templates/pilets/my-pilet/src/index.tsx +85 -0
  29. package/templates/pilets/my-pilet/src/pages/Test1Page.tsx +23 -0
  30. package/templates/pilets/my-pilet/src/pages/Test2Page.tsx +25 -0
  31. package/templates/pilets/my-pilet/src/pages/demo.tsx +220 -0
  32. package/templates/pilets/my-pilet/src/pages/home.tsx +20 -0
  33. package/templates/pilets/my-pilet/tailwind.config.js +26 -0
  34. package/templates/pilets/my-pilet/tsconfig.json +1 -0
  35. package/templates/pilets/my-pilet/webpack.config.js +27 -0
  36. package/templates/root/env.example +5 -0
  37. package/templates/root/package.json +19 -0
  38. package/templates/root/pnpm-workspace.yaml +3 -0
  39. package/templates/root/tsconfig.base.json +17 -0
  40. package/templates/scripts/dev-multi-pilet.js +97 -0
  41. package/templates/scripts/discover-pilets.js +76 -0
  42. package/templates/scripts/run-shell.js +22 -0
@@ -0,0 +1,220 @@
1
+ import * as React from 'react';
2
+ import type { PiletApi } from '@lego-box/shell/pilet';
3
+ import { Button, Card, CardHeader, CardTitle, CardDescription, CardContent, Badge, Loading } from '@lego-box/ui-kit';
4
+
5
+ /**
6
+ * Demo page: verifies shell exposes auth, casl, pocketbase, and test API to the pilet.
7
+ * Shows permissions list (like Profile page), Fetch Audit Logs, and all integrations.
8
+ */
9
+ export function DemoPage({ api }: { api: PiletApi }) {
10
+ const [testMsg, setTestMsg] = React.useState('');
11
+ const [auditLogs, setAuditLogs] = React.useState<Record<string, unknown>[]>([]);
12
+ const [auditLoading, setAuditLoading] = React.useState(false);
13
+ const [auditError, setAuditError] = React.useState<string | null>(null);
14
+ const [allPermissions, setAllPermissions] = React.useState<Array<{ id: string; action: string; collection: string }>>([]);
15
+ const [permsLoading, setPermsLoading] = React.useState(true);
16
+
17
+ const auth = api?.auth;
18
+ const casl = api?.casl;
19
+ const pb = api?.pocketbase;
20
+
21
+ const can = React.useCallback(
22
+ (action: string, subject: string) => casl?.can?.(action, subject) ?? auth?.can?.(action, subject) ?? false,
23
+ [auth, casl]
24
+ );
25
+
26
+ const userId = auth?.getId?.() ?? null;
27
+ const email = auth?.getEmail?.() ?? null;
28
+ const role = auth?.getRole?.() ?? null;
29
+ const permissions = auth?.getPermissions?.() ?? [];
30
+ const user = auth?.getUser?.();
31
+ const isAuth = auth?.isAuthenticated?.() ?? false;
32
+
33
+ // Fetch all permissions from shared PocketBase (for full permissions list like Profile page)
34
+ React.useEffect(() => {
35
+ if (!pb || !isAuth) {
36
+ setPermsLoading(false);
37
+ return;
38
+ }
39
+ pb.collection('permissions')
40
+ .getFullList({ sort: 'collection,action', $autoCancel: false })
41
+ .then((records: unknown[]) => {
42
+ const items = (records as Array<{ id: string; action: string; collection: string }>).map((r) => ({
43
+ id: r.id,
44
+ action: r.action,
45
+ collection: r.collection,
46
+ }));
47
+ setAllPermissions(items);
48
+ })
49
+ .catch((err: Error) => {
50
+ console.error('[DemoPage] Error fetching permissions:', err);
51
+ })
52
+ .finally(() => setPermsLoading(false));
53
+ }, [pb, isAuth]);
54
+
55
+ const handleFetchAuditLogs = React.useCallback(() => {
56
+ if (!pb) {
57
+ setAuditError('PocketBase instance not available. Use the shared instance from the shell.');
58
+ return;
59
+ }
60
+ setAuditLoading(true);
61
+ setAuditError(null);
62
+ pb.collection('audit_logs')
63
+ .getFullList({ sort: '-created', $autoCancel: false })
64
+ .then((records: unknown[]) => {
65
+ setAuditLogs(records as Record<string, unknown>[]);
66
+ })
67
+ .catch((err: Error) => {
68
+ setAuditError(err.message || 'Failed to fetch audit logs');
69
+ })
70
+ .finally(() => setAuditLoading(false));
71
+ }, [pb]);
72
+
73
+ const permissionsByCollection = React.useMemo(() => {
74
+ const grouped: Record<string, Array<{ id: string; action: string; collection: string }>> = {};
75
+ allPermissions.forEach((p) => {
76
+ if (!grouped[p.collection]) grouped[p.collection] = [];
77
+ grouped[p.collection].push(p);
78
+ });
79
+ Object.keys(grouped).forEach((k) => grouped[k].sort((a, b) => a.action.localeCompare(b.action)));
80
+ return grouped;
81
+ }, [allPermissions]);
82
+
83
+ const collections = Object.keys(permissionsByCollection).sort();
84
+
85
+ return (
86
+ <div className="space-y-6 p-4 max-w-4xl mx-auto">
87
+ <h2 className="text-3xl font-bold tracking-tight">Pilet Demo</h2>
88
+ <p className="text-muted-foreground">
89
+ This page verifies the pilet can use the shell&apos;s auth, casl, pocketbase, and test API.
90
+ </p>
91
+
92
+ {/* Auth API */}
93
+ <Card className="p-4">
94
+ <CardHeader>
95
+ <CardTitle>Auth API (from shell)</CardTitle>
96
+ <CardDescription>
97
+ User info and auth utilities exposed via app.auth
98
+ </CardDescription>
99
+ </CardHeader>
100
+ <CardContent className="space-y-2">
101
+ <p><strong>auth.isAuthenticated():</strong> {String(isAuth)}</p>
102
+ <p><strong>auth.getId():</strong> {userId ?? '—'}</p>
103
+ <p><strong>auth.getEmail():</strong> {email ?? '—'}</p>
104
+ <p><strong>auth.getRole():</strong> {role ?? '—'}</p>
105
+ <p><strong>auth.getUser() – name:</strong> {user?.name ?? '—'}</p>
106
+ <p><strong>auth.getPermissions() count:</strong> {permissions.length}</p>
107
+ </CardContent>
108
+ </Card>
109
+
110
+ {/* CASL / Permissions List (like Profile page) */}
111
+ <Card className="p-4">
112
+ <CardHeader>
113
+ <CardTitle>CASL Permissions (app.auth.can / app.casl.can)</CardTitle>
114
+ <CardDescription>
115
+ Your assigned permissions across all collections. Check marks indicate granted access.
116
+ </CardDescription>
117
+ </CardHeader>
118
+ <CardContent>
119
+ {permsLoading ? (
120
+ <Loading message="Loading permissions..." fullPage={false} />
121
+ ) : collections.length === 0 ? (
122
+ <p className="text-sm text-muted-foreground">No permissions defined.</p>
123
+ ) : (
124
+ <div className="space-y-6">
125
+ {collections.map((collection) => {
126
+ const perms = permissionsByCollection[collection] ?? [];
127
+ return (
128
+ <div key={collection}>
129
+ <h4 className="text-sm font-medium text-muted-foreground capitalize mb-2">
130
+ {collection}
131
+ </h4>
132
+ <div className="space-y-2">
133
+ {perms.map((perm) => {
134
+ const allowed = can(perm.action, perm.collection);
135
+ return (
136
+ <div
137
+ key={perm.id}
138
+ className="flex items-center justify-between py-2 px-3 rounded-lg bg-muted/50"
139
+ >
140
+ <div className="flex items-center gap-3">
141
+ {allowed ? (
142
+ <span className="text-green-600 dark:text-green-400">✓</span>
143
+ ) : (
144
+ <span className="text-red-500 dark:text-red-400">✗</span>
145
+ )}
146
+ <span className="text-sm font-medium capitalize">
147
+ {perm.action} {perm.collection}
148
+ </span>
149
+ </div>
150
+ <Badge variant={allowed ? 'success' : 'destructive'}>
151
+ {allowed ? 'Granted' : 'Denied'}
152
+ </Badge>
153
+ </div>
154
+ );
155
+ })}
156
+ </div>
157
+ </div>
158
+ );
159
+ })}
160
+ </div>
161
+ )}
162
+ </CardContent>
163
+ </Card>
164
+
165
+ {/* PocketBase – shared instance */}
166
+ <Card className="p-4">
167
+ <CardHeader>
168
+ <CardTitle>PocketBase (shared instance from shell)</CardTitle>
169
+ <CardDescription>
170
+ Fetches audit_logs using the shared PocketBase instance – no new instance created.
171
+ </CardDescription>
172
+ </CardHeader>
173
+ <CardContent>
174
+ <Button
175
+ onClick={handleFetchAuditLogs}
176
+ disabled={!pb || auditLoading}
177
+ >
178
+ {auditLoading ? 'Fetching...' : 'Fetch Audit Logs'}
179
+ </Button>
180
+ {auditError && (
181
+ <p className="mt-2 text-sm text-destructive">{auditError}</p>
182
+ )}
183
+ {auditLogs.length > 0 && (
184
+ <div className="mt-4 space-y-2 max-h-60 overflow-y-auto">
185
+ <p className="text-sm font-medium">{auditLogs.length} audit log(s)</p>
186
+ {auditLogs.slice(0, 5).map((log, i) => (
187
+ <div key={i} className="text-xs p-2 rounded bg-muted/50 font-mono truncate">
188
+ {String(log.action ?? '')} / {String(log.resource ?? '')} – {String(log.timestamp ?? '')}
189
+ </div>
190
+ ))}
191
+ {auditLogs.length > 5 && (
192
+ <p className="text-xs text-muted-foreground">… and {auditLogs.length - 5} more</p>
193
+ )}
194
+ </div>
195
+ )}
196
+ </CardContent>
197
+ </Card>
198
+
199
+ {/* Test API */}
200
+ <Card className="p-4">
201
+ <CardHeader>
202
+ <CardTitle>Test API</CardTitle>
203
+ <CardDescription>
204
+ app.test.getTesting() validates the plugin wrapper is working.
205
+ </CardDescription>
206
+ </CardHeader>
207
+ <CardContent>
208
+ <Button
209
+ onClick={() => setTestMsg(api?.test?.getTesting?.() ?? '')}
210
+ >
211
+ Call getTesting()
212
+ </Button>
213
+ {testMsg && (
214
+ <p className="mt-2 text-sm text-muted-foreground">{testMsg}</p>
215
+ )}
216
+ </CardContent>
217
+ </Card>
218
+ </div>
219
+ );
220
+ }
@@ -0,0 +1,20 @@
1
+ import { Button } from '@lego-box/ui-kit';
2
+
3
+ export function HomePage() {
4
+ return (
5
+ <div className="space-y-4">
6
+ <h2 className="text-3xl font-bold tracking-tight">
7
+ Welcome to my World
8
+ </h2>
9
+ <p className="text-muted-foreground">
10
+ This page demonstrates the shared UI Kit integration.
11
+ </p>
12
+ <div className="flex gap-2">
13
+ <Button variant="default">Default Button</Button>
14
+ <Button variant="outline">Outline Button</Button>
15
+ <Button variant="secondary">Secondary Button</Button>
16
+ <Button variant="ghost">Ghost Button</Button>
17
+ </div>
18
+ </div>
19
+ );
20
+ }
@@ -0,0 +1,54 @@
1
+ /** @type {import('tailwindcss').Config} */
2
+ module.exports = {
3
+ darkMode: ['class'],
4
+ content: ['./src/**/*.{ts,tsx}'],
5
+ theme: {
6
+ extend: {
7
+ colors: {
8
+ border: 'hsl(var(--border))',
9
+ input: 'hsl(var(--input))',
10
+ ring: 'hsl(var(--ring))',
11
+ background: 'hsl(var(--background))',
12
+ foreground: 'hsl(var(--foreground))',
13
+ primary: {
14
+ DEFAULT: 'hsl(var(--primary))',
15
+ foreground: 'hsl(var(--primary-foreground))',
16
+ },
17
+ secondary: {
18
+ DEFAULT: 'hsl(var(--secondary))',
19
+ foreground: 'hsl(var(--secondary-foreground))',
20
+ },
21
+ destructive: {
22
+ DEFAULT: 'hsl(var(--destructive))',
23
+ foreground: 'hsl(var(--destructive-foreground))',
24
+ },
25
+ muted: {
26
+ DEFAULT: 'hsl(var(--muted))',
27
+ foreground: 'hsl(var(--muted-foreground))',
28
+ },
29
+ accent: {
30
+ DEFAULT: 'hsl(var(--accent))',
31
+ foreground: 'hsl(var(--accent-foreground))',
32
+ },
33
+ card: {
34
+ DEFAULT: 'hsl(var(--card))',
35
+ foreground: 'hsl(var(--card-foreground))',
36
+ },
37
+ success: {
38
+ DEFAULT: 'hsl(var(--success))',
39
+ foreground: 'hsl(var(--success-foreground))',
40
+ },
41
+ warning: {
42
+ DEFAULT: 'hsl(var(--warning))',
43
+ foreground: 'hsl(var(--warning-foreground))',
44
+ },
45
+ },
46
+ borderRadius: {
47
+ lg: 'var(--radius)',
48
+ md: 'calc(var(--radius) - 2px)',
49
+ sm: 'calc(var(--radius) - 4px)',
50
+ },
51
+ },
52
+ },
53
+ plugins: [],
54
+ };
@@ -0,0 +1 @@
1
+ {"extends":"../../tsconfig.base.json","compilerOptions":{"jsx":"react-jsx","types":["piral-cli"]},"include":["src"]}
@@ -0,0 +1,34 @@
1
+ const path = require('path');
2
+
3
+ /**
4
+ * Force react and react-dom to resolve from the shell's global (window.React / window.ReactDOM)
5
+ * so the pilet uses the same React instance as the shell and avoids "dispatcher is null".
6
+ * Override any existing react/react-dom externals so we always use root.
7
+ * Also resolve 'strip-ansi' for piral-cli-webpack5's webpack-hot-middleware (pnpm isolation).
8
+ */
9
+ module.exports = function (config) {
10
+ config.resolve = config.resolve || {};
11
+ config.resolve.alias = {
12
+ ...config.resolve.alias,
13
+ 'strip-ansi': path.dirname(require.resolve('strip-ansi/package.json')),
14
+ };
15
+
16
+ const prev = config.externals;
17
+ const globalReact = {
18
+ react: 'root React',
19
+ 'react-dom': 'root ReactDOM',
20
+ 'react-dom/client': 'root ReactDOM',
21
+ };
22
+ if (Array.isArray(prev)) {
23
+ // Ensure our root React override is applied; webpack uses first match, so add object last to override string entries
24
+ const withoutReact = prev.filter(
25
+ (e) => typeof e !== 'object' || (e && typeof e === 'object' && !('react' in e) && !('react-dom' in e))
26
+ );
27
+ config.externals = [...withoutReact, globalReact];
28
+ } else if (prev && typeof prev === 'object' && !Array.isArray(prev)) {
29
+ config.externals = { ...prev, ...globalReact };
30
+ } else {
31
+ config.externals = globalReact;
32
+ }
33
+ return config;
34
+ };
@@ -0,0 +1,117 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+
5
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
+ const BUNDLED_TEMPLATES_DIR = path.resolve(__dirname, 'templates');
7
+
8
+ function getTemplateRoot() {
9
+ return BUNDLED_TEMPLATES_DIR;
10
+ }
11
+
12
+ function readTemplateFile(relativePath) {
13
+ const fullPath = path.join(getTemplateRoot(), relativePath);
14
+ return fs.readFileSync(fullPath, 'utf-8');
15
+ }
16
+
17
+ function toTitleCase(str) {
18
+ if (!str) return str;
19
+ return str
20
+ .replace(/[-_]/g, ' ')
21
+ .replace(/([a-z])([A-Z])/g, '$1 $2')
22
+ .replace(/\b\w/g, (c) => c.toUpperCase());
23
+ }
24
+
25
+ export function loadTemplate(sourcePath, variables) {
26
+ let content = readTemplateFile(sourcePath);
27
+ for (const [key, value] of Object.entries(variables)) {
28
+ if (value !== undefined && value !== null) {
29
+ content = content.replace(new RegExp(`{{${key}}}`, 'g'), String(value));
30
+ }
31
+ }
32
+ return content;
33
+ }
34
+
35
+ export function getTemplateFiles() {
36
+ return [
37
+ 'package.json',
38
+ 'tsconfig.json',
39
+ 'webpack.config.js',
40
+ '.krasrc',
41
+ 'tailwind.config.js',
42
+ 'src/index.tsx',
43
+ 'src/pages/home.tsx',
44
+ 'src/pages/demo.tsx',
45
+ 'src/pages/Test1Page.tsx',
46
+ 'src/pages/Test2Page.tsx',
47
+ ];
48
+ }
49
+
50
+ export function generateFileContent(sourcePath, variables, mode) {
51
+ const titleCaseName = toTitleCase(variables.PILET_NAME);
52
+
53
+ if (sourcePath === 'package.json') {
54
+ const pkg = JSON.parse(readTemplateFile(sourcePath));
55
+ pkg.name = variables.PILET_NAME;
56
+ pkg.description = variables.DESCRIPTION || '';
57
+ if (variables.AUTHOR) pkg.author = variables.AUTHOR;
58
+ pkg.devDependencies['@lego-box/shell'] = variables.SHELL_DEPENDENCY;
59
+ pkg.devDependencies['@lego-box/ui-kit'] = variables.UI_KIT_DEPENDENCY;
60
+ return JSON.stringify(pkg, null, 2);
61
+ }
62
+
63
+ if (sourcePath === '.krasrc') {
64
+ const krasrc = JSON.parse(readTemplateFile(sourcePath));
65
+ krasrc.injectors.pilet.assetUrl = `http://localhost:${variables.PORT}`;
66
+ return JSON.stringify(krasrc, null, 2);
67
+ }
68
+
69
+ if (sourcePath === 'tsconfig.json') {
70
+ const tsconfig = JSON.parse(readTemplateFile(sourcePath));
71
+ if (mode === 'published') {
72
+ delete tsconfig.extends;
73
+ tsconfig.compilerOptions = {
74
+ target: 'ES2020',
75
+ lib: ['ES2020', 'DOM', 'DOM.Iterable'],
76
+ module: 'ESNext',
77
+ moduleResolution: 'bundler',
78
+ jsx: 'react-jsx',
79
+ strict: true,
80
+ esModuleInterop: true,
81
+ skipLibCheck: true,
82
+ forceConsistentCasingInFileNames: true,
83
+ resolveJsonModule: true,
84
+ isolatedModules: true,
85
+ declaration: true,
86
+ declarationMap: true,
87
+ types: ['piral-cli'],
88
+ };
89
+ tsconfig.include = ['src'];
90
+ }
91
+ return JSON.stringify(tsconfig, null, 2);
92
+ }
93
+
94
+ if (sourcePath === 'src/index.tsx') {
95
+ let content = readTemplateFile(sourcePath);
96
+ content = content.replace(/'pilet-demo'/g, `'${variables.PILET_NAME}'`);
97
+ content = content.replace(/"pilet-demo"/g, `"${variables.PILET_NAME}"`);
98
+ content = content.replace(/label: 'Pilet Demo'/g, `label: '${titleCaseName}'`);
99
+ content = content.replace(/label: "Pilet Demo"/g, `label: "${titleCaseName}"`);
100
+ content = content.replace(/\[pilet-demo\]/g, `[${variables.PILET_NAME}]`);
101
+ return content;
102
+ }
103
+
104
+ if (sourcePath === 'src/pages/home.tsx') {
105
+ let content = readTemplateFile(sourcePath);
106
+ content = content.replace(/Welcome to my World/g, `Welcome to ${titleCaseName}`);
107
+ return content;
108
+ }
109
+
110
+ if (sourcePath === 'src/pages/Test1Page.tsx' || sourcePath === 'src/pages/Test2Page.tsx') {
111
+ let content = readTemplateFile(sourcePath);
112
+ content = content.replace(/pilet-demo/g, variables.PILET_NAME);
113
+ return content;
114
+ }
115
+
116
+ return readTemplateFile(sourcePath);
117
+ }
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@lego-box/ui-kit",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "description": "Extensible UI kit - extends @lego-box/ui-kit-base, add your custom components here",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.mjs",
8
+ "types": "./dist/index.d.mts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.mts",
12
+ "import": "./dist/index.mjs",
13
+ "require": "./dist/index.js"
14
+ },
15
+ "./package.json": "./package.json"
16
+ },
17
+ "scripts": {
18
+ "dev": "tsup --watch",
19
+ "build": "tsup",
20
+ "typecheck": "tsc --noEmit"
21
+ },
22
+ "dependencies": {
23
+ "@lego-box/ui-kit-base": "npm:@lego-box/ui-kit@^0.1.0",
24
+ "class-variance-authority": "^0.7.0",
25
+ "clsx": "^2.0.0",
26
+ "tailwind-merge": "^2.2.0",
27
+ "lucide-react": "^0.400.0"
28
+ },
29
+ "peerDependencies": {
30
+ "react": "^18.2.0",
31
+ "react-dom": "^18.2.0"
32
+ },
33
+ "devDependencies": {
34
+ "@types/react": "^18.2.0",
35
+ "tsup": "^8.0.0",
36
+ "tailwindcss": "^3.4.0",
37
+ "autoprefixer": "^10.4.16",
38
+ "postcss": "^8.4.32",
39
+ "tailwindcss-animate": "^1.0.7",
40
+ "typescript": "^5.3.3"
41
+ }
42
+ }
@@ -0,0 +1,6 @@
1
+ module.exports = {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ };
@@ -0,0 +1,21 @@
1
+ import * as React from 'react';
2
+ import { cn } from '@lego-box/ui-kit-base';
3
+
4
+ /**
5
+ * Example custom component - add your own components here.
6
+ * Delete this file and create your own when building your app.
7
+ */
8
+ export function ExampleCustomComponent({
9
+ className,
10
+ children,
11
+ }: {
12
+ className?: string;
13
+ children?: React.ReactNode;
14
+ }) {
15
+ return (
16
+ <div className={cn('rounded-lg border p-4', className)}>
17
+ <p className="text-sm text-muted-foreground">Custom component placeholder</p>
18
+ {children}
19
+ </div>
20
+ );
21
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Custom components - add your app-specific components here.
3
+ * Example: export { MyCustomCard } from './my-custom-card';
4
+ */
5
+
6
+ export { ExampleCustomComponent } from './example-custom-component';
@@ -0,0 +1,75 @@
1
+ @import url('https://fonts.googleapis.com/css2?family=Fredoka:wght@400;500;600;700&display=swap');
2
+
3
+ @tailwind base;
4
+ @tailwind components;
5
+ @tailwind utilities;
6
+
7
+ @layer base {
8
+ :root {
9
+ --background: 0 0% 100%;
10
+ --foreground: 222.2 84% 4.9%;
11
+ --card: 0 0% 100%;
12
+ --card-foreground: 222.2 84% 4.9%;
13
+ --primary: 217 91% 60%;
14
+ --primary-foreground: 0 0% 100%;
15
+ --primary-50: 217 91% 97%;
16
+ --primary-100: 217 91% 93%;
17
+ --primary-200: 217 91% 85%;
18
+ --primary-300: 217 91% 75%;
19
+ --primary-400: 217 91% 68%;
20
+ --primary-500: 217 91% 60%;
21
+ --primary-600: 217 75% 50%;
22
+ --secondary: 258 90% 66%;
23
+ --secondary-foreground: 0 0% 100%;
24
+ --secondary-50: 258 90% 97%;
25
+ --secondary-100: 258 90% 93%;
26
+ --secondary-200: 258 90% 85%;
27
+ --destructive: 0 84% 60%;
28
+ --destructive-foreground: 0 0% 100%;
29
+ --success: 158 64% 52%;
30
+ --success-foreground: 0 0% 100%;
31
+ --warning: 38 92% 50%;
32
+ --warning-foreground: 0 0% 100%;
33
+ --muted: 210 40% 96.1%;
34
+ --muted-foreground: 215.4 16.3% 46.9%;
35
+ --accent: 210 40% 96.1%;
36
+ --accent-foreground: 222.2 47.4% 11.2%;
37
+ --border: 210 40% 88%;
38
+ --input: 210 40% 88%;
39
+ --ring: 217 91% 60%;
40
+ --radius: 0.5rem;
41
+ }
42
+ .dark {
43
+ --background: 222.2 84% 4.9%;
44
+ --foreground: 210 40% 98%;
45
+ --card: 222.2 84% 6.9%;
46
+ --card-foreground: 210 40% 98%;
47
+ --primary: 217 91% 60%;
48
+ --primary-foreground: 0 0% 100%;
49
+ --primary-50: 217 70% 15%;
50
+ --primary-100: 217 70% 20%;
51
+ --primary-200: 217 70% 30%;
52
+ --secondary: 258 90% 66%;
53
+ --secondary-foreground: 0 0% 100%;
54
+ --secondary-50: 258 70% 15%;
55
+ --secondary-100: 258 70% 20%;
56
+ --destructive: 0 84% 60%;
57
+ --destructive-foreground: 0 0% 100%;
58
+ --muted: 217.2 32.6% 17.5%;
59
+ --muted-foreground: 215 20.2% 65.1%;
60
+ --accent: 217.2 32.6% 17.5%;
61
+ --accent-foreground: 210 40% 98%;
62
+ --border: 217.2 32.6% 17.5%;
63
+ --input: 217.2 32.6% 17.5%;
64
+ --ring: 217 91% 60%;
65
+ --radius: 0.5rem;
66
+ }
67
+ }
68
+
69
+ @layer base {
70
+ * { @apply border-border; }
71
+ body {
72
+ @apply bg-background text-foreground;
73
+ font-family: 'Inter', system-ui, -apple-system, sans-serif;
74
+ }
75
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Extensible UI Kit - extends @lego-box/ui-kit-base with your custom components.
3
+ * Add new components in src/components/ and export them here.
4
+ * Contribute upstream: https://github.com/lego-box/lego-box
5
+ */
6
+
7
+ export * from '@lego-box/ui-kit-base';
8
+ export * from './components';
9
+
10
+ import './index.css';
@@ -0,0 +1,26 @@
1
+ /** @type {import('tailwindcss').Config} */
2
+ module.exports = {
3
+ darkMode: ['class'],
4
+ content: ['./src/**/*.{ts,tsx}'],
5
+ theme: {
6
+ extend: {
7
+ colors: {
8
+ border: 'hsl(var(--border))',
9
+ input: 'hsl(var(--input))',
10
+ ring: 'hsl(var(--ring))',
11
+ background: 'hsl(var(--background))',
12
+ foreground: 'hsl(var(--foreground))',
13
+ primary: { DEFAULT: 'hsl(var(--primary))', foreground: 'hsl(var(--primary-foreground))' },
14
+ secondary: { DEFAULT: 'hsl(var(--secondary))', foreground: 'hsl(var(--secondary-foreground))' },
15
+ destructive: { DEFAULT: 'hsl(var(--destructive))', foreground: 'hsl(var(--destructive-foreground))' },
16
+ muted: { DEFAULT: 'hsl(var(--muted))', foreground: 'hsl(var(--muted-foreground))' },
17
+ accent: { DEFAULT: 'hsl(var(--accent))', foreground: 'hsl(var(--accent-foreground))' },
18
+ card: { DEFAULT: 'hsl(var(--card))', foreground: 'hsl(var(--card-foreground))' },
19
+ success: { DEFAULT: 'hsl(var(--success))', foreground: 'hsl(var(--success-foreground))' },
20
+ warning: { DEFAULT: 'hsl(var(--warning))', foreground: 'hsl(var(--warning-foreground))' },
21
+ },
22
+ borderRadius: { lg: 'var(--radius)', md: 'calc(var(--radius) - 2px)', sm: 'calc(var(--radius) - 4px)' },
23
+ },
24
+ },
25
+ plugins: [],
26
+ };
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src"
6
+ },
7
+ "include": ["src"]
8
+ }
@@ -0,0 +1,15 @@
1
+ import { defineConfig } from 'tsup';
2
+
3
+ export default defineConfig({
4
+ entry: ['src/index.ts'],
5
+ format: ['cjs', 'esm'],
6
+ dts: true,
7
+ splitting: false,
8
+ sourcemap: true,
9
+ clean: true,
10
+ external: ['react', 'react-dom', 'lucide-react', '@lego-box/ui-kit-base'],
11
+ esbuildOptions(options) {
12
+ options.banner = { js: '"use client";' };
13
+ },
14
+ injectStyle: true,
15
+ });
@@ -0,0 +1 @@
1
+ {"injectors":{"pilet":{"assetUrl":"http://localhost:9000"}}}