@ng-cn/core 1.0.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.
- package/.cursor/rules/cursor.mdc +53 -0
- package/.editorconfig +17 -0
- package/.postcssrc.json +5 -0
- package/.prettierrc +27 -0
- package/README.md +199 -0
- package/angular.json +122 -0
- package/dist/schematics/component/index.d.ts +8 -0
- package/dist/schematics/component/index.js +109 -0
- package/dist/schematics/ng-add/index.d.ts +7 -0
- package/dist/schematics/ng-add/index.js +51 -0
- package/mcp.json +336 -0
- package/package.json +99 -0
- package/postcss.config.mjs +6 -0
- package/public/.!14865!favicon.ico +0 -0
- package/public/angular-shad-cn.png +0 -0
- package/public/favicon.ico +0 -0
- package/schematics/README.md +97 -0
- package/schematics/collection.json +16 -0
- package/schematics/component/index.d.ts +9 -0
- package/schematics/component/index.js +214 -0
- package/schematics/component/index.ts +238 -0
- package/schematics/component/schema.json +36 -0
- package/schematics/ng-add/index.d.ts +8 -0
- package/schematics/ng-add/index.js +310 -0
- package/schematics/ng-add/index.ts +334 -0
- package/schematics/ng-add/schema.json +25 -0
- package/schematics/tsconfig.json +20 -0
- package/tsconfig.app.json +17 -0
- package/tsconfig.json +45 -0
- package/tsconfig.spec.json +15 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ngAdd = ngAdd;
|
|
4
|
+
const tasks_1 = require("@angular-devkit/schematics/tasks");
|
|
5
|
+
// CSS Variables template for shadcn theming
|
|
6
|
+
const CSS_VARIABLES_TEMPLATE = `/* ng-cn/core - shadcn-angular styles */
|
|
7
|
+
@use "tailwindcss";
|
|
8
|
+
|
|
9
|
+
@custom-variant dark (&:is(.dark *));
|
|
10
|
+
|
|
11
|
+
:root {
|
|
12
|
+
--radius: 0.625rem;
|
|
13
|
+
--background: oklch(1 0 0);
|
|
14
|
+
--foreground: oklch(0.145 0 0);
|
|
15
|
+
--card: oklch(1 0 0);
|
|
16
|
+
--card-foreground: oklch(0.145 0 0);
|
|
17
|
+
--popover: oklch(1 0 0);
|
|
18
|
+
--popover-foreground: oklch(0.145 0 0);
|
|
19
|
+
--primary: oklch(0.205 0 0);
|
|
20
|
+
--primary-foreground: oklch(0.985 0 0);
|
|
21
|
+
--secondary: oklch(0.97 0 0);
|
|
22
|
+
--secondary-foreground: oklch(0.205 0 0);
|
|
23
|
+
--muted: oklch(0.97 0 0);
|
|
24
|
+
--muted-foreground: oklch(0.556 0 0);
|
|
25
|
+
--accent: oklch(0.97 0 0);
|
|
26
|
+
--accent-foreground: oklch(0.205 0 0);
|
|
27
|
+
--destructive: oklch(0.577 0.245 27.325);
|
|
28
|
+
--destructive-foreground: oklch(0.577 0.245 27.325);
|
|
29
|
+
--border: oklch(0.922 0 0);
|
|
30
|
+
--input: oklch(0.922 0 0);
|
|
31
|
+
--ring: oklch(0.708 0 0);
|
|
32
|
+
--chart-1: oklch(0.646 0.222 41.116);
|
|
33
|
+
--chart-2: oklch(0.6 0.118 184.704);
|
|
34
|
+
--chart-3: oklch(0.398 0.07 227.392);
|
|
35
|
+
--chart-4: oklch(0.828 0.189 84.429);
|
|
36
|
+
--chart-5: oklch(0.769 0.188 70.08);
|
|
37
|
+
--sidebar: oklch(0.985 0 0);
|
|
38
|
+
--sidebar-foreground: oklch(0.145 0 0);
|
|
39
|
+
--sidebar-primary: oklch(0.205 0 0);
|
|
40
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
41
|
+
--sidebar-accent: oklch(0.97 0 0);
|
|
42
|
+
--sidebar-accent-foreground: oklch(0.205 0 0);
|
|
43
|
+
--sidebar-border: oklch(0.922 0 0);
|
|
44
|
+
--sidebar-ring: oklch(0.708 0 0);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.dark {
|
|
48
|
+
--background: oklch(0.145 0 0);
|
|
49
|
+
--foreground: oklch(0.985 0 0);
|
|
50
|
+
--card: oklch(0.205 0 0);
|
|
51
|
+
--card-foreground: oklch(0.985 0 0);
|
|
52
|
+
--popover: oklch(0.205 0 0);
|
|
53
|
+
--popover-foreground: oklch(0.985 0 0);
|
|
54
|
+
--primary: oklch(0.985 0 0);
|
|
55
|
+
--primary-foreground: oklch(0.205 0 0);
|
|
56
|
+
--secondary: oklch(0.269 0 0);
|
|
57
|
+
--secondary-foreground: oklch(0.985 0 0);
|
|
58
|
+
--muted: oklch(0.269 0 0);
|
|
59
|
+
--muted-foreground: oklch(0.708 0 0);
|
|
60
|
+
--accent: oklch(0.269 0 0);
|
|
61
|
+
--accent-foreground: oklch(0.985 0 0);
|
|
62
|
+
--destructive: oklch(0.396 0.141 25.723);
|
|
63
|
+
--destructive-foreground: oklch(0.637 0.237 25.331);
|
|
64
|
+
--border: oklch(0.269 0 0);
|
|
65
|
+
--input: oklch(0.269 0 0);
|
|
66
|
+
--ring: oklch(0.439 0 0);
|
|
67
|
+
--chart-1: oklch(0.488 0.243 264.376);
|
|
68
|
+
--chart-2: oklch(0.696 0.17 162.48);
|
|
69
|
+
--chart-3: oklch(0.769 0.188 70.08);
|
|
70
|
+
--chart-4: oklch(0.627 0.265 303.9);
|
|
71
|
+
--chart-5: oklch(0.645 0.246 16.439);
|
|
72
|
+
--sidebar: oklch(0.205 0 0);
|
|
73
|
+
--sidebar-foreground: oklch(0.985 0 0);
|
|
74
|
+
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
75
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
76
|
+
--sidebar-accent: oklch(0.269 0 0);
|
|
77
|
+
--sidebar-accent-foreground: oklch(0.985 0 0);
|
|
78
|
+
--sidebar-border: oklch(0.269 0 0);
|
|
79
|
+
--sidebar-ring: oklch(0.439 0 0);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
@theme inline {
|
|
83
|
+
--color-background: var(--background);
|
|
84
|
+
--color-foreground: var(--foreground);
|
|
85
|
+
--color-card: var(--card);
|
|
86
|
+
--color-card-foreground: var(--card-foreground);
|
|
87
|
+
--color-popover: var(--popover);
|
|
88
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
89
|
+
--color-primary: var(--primary);
|
|
90
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
91
|
+
--color-secondary: var(--secondary);
|
|
92
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
93
|
+
--color-muted: var(--muted);
|
|
94
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
95
|
+
--color-accent: var(--accent);
|
|
96
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
97
|
+
--color-destructive: var(--destructive);
|
|
98
|
+
--color-destructive-foreground: var(--destructive-foreground);
|
|
99
|
+
--color-border: var(--border);
|
|
100
|
+
--color-input: var(--input);
|
|
101
|
+
--color-ring: var(--ring);
|
|
102
|
+
--color-chart-1: var(--chart-1);
|
|
103
|
+
--color-chart-2: var(--chart-2);
|
|
104
|
+
--color-chart-3: var(--chart-3);
|
|
105
|
+
--color-chart-4: var(--chart-4);
|
|
106
|
+
--color-chart-5: var(--chart-5);
|
|
107
|
+
--color-sidebar: var(--sidebar);
|
|
108
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
109
|
+
--color-sidebar-primary: var(--sidebar-primary);
|
|
110
|
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
111
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
112
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
113
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
114
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
115
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
116
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
117
|
+
--radius-lg: var(--radius);
|
|
118
|
+
--radius-xl: calc(var(--radius) + 4px);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
@layer base {
|
|
122
|
+
* { @apply border-border outline-ring/50; }
|
|
123
|
+
body { @apply bg-background text-foreground; font-feature-settings: "rlig" 1, "calt" 1; }
|
|
124
|
+
html { scroll-behavior: smooth; }
|
|
125
|
+
:focus-visible { @apply outline-2 outline-ring outline-offset-2; }
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
@layer utilities {
|
|
129
|
+
.animate-accordion-down { animation: accordion-down 0.2s ease-out; }
|
|
130
|
+
.animate-accordion-up { animation: accordion-up 0.2s ease-out; }
|
|
131
|
+
@keyframes accordion-down { from { height: 0; } to { height: var(--accordion-content-height); } }
|
|
132
|
+
@keyframes accordion-up { from { height: var(--accordion-content-height); } to { height: 0; } }
|
|
133
|
+
.animate-collapsible-down { animation: collapsible-down 0.2s ease-out; }
|
|
134
|
+
.animate-collapsible-up { animation: collapsible-up 0.2s ease-out; }
|
|
135
|
+
@keyframes collapsible-down { from { height: 0; } to { height: var(--collapsible-content-height); } }
|
|
136
|
+
@keyframes collapsible-up { from { height: var(--collapsible-content-height); } to { height: 0; } }
|
|
137
|
+
}
|
|
138
|
+
`;
|
|
139
|
+
// cn utility template
|
|
140
|
+
const CN_UTILITY_TEMPLATE = `import { clsx, type ClassValue } from 'clsx';
|
|
141
|
+
import { twMerge } from 'tailwind-merge';
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Utility function to merge Tailwind CSS classes with proper conflict resolution.
|
|
145
|
+
* Combines clsx for conditional classes with tailwind-merge for deduplication.
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* cn('px-4 py-2', 'px-6') // => 'py-2 px-6'
|
|
149
|
+
* cn('bg-red-500', condition && 'bg-blue-500') // conditional classes
|
|
150
|
+
*/
|
|
151
|
+
export function cn(...inputs: ClassValue[]): string {
|
|
152
|
+
return twMerge(clsx(inputs));
|
|
153
|
+
}
|
|
154
|
+
`;
|
|
155
|
+
const UTILS_INDEX_TEMPLATE = `export { cn } from './cn';
|
|
156
|
+
`;
|
|
157
|
+
function ngAdd(options) {
|
|
158
|
+
return (tree, context) => {
|
|
159
|
+
context.logger.info('');
|
|
160
|
+
context.logger.info('╭──────────────────────────────────────────────────╮');
|
|
161
|
+
context.logger.info('│ │');
|
|
162
|
+
context.logger.info('│ 🎨 @ng-cn/core - shadcn for Angular │');
|
|
163
|
+
context.logger.info('│ │');
|
|
164
|
+
context.logger.info('╰──────────────────────────────────────────────────╯');
|
|
165
|
+
context.logger.info('');
|
|
166
|
+
// Add dependencies to package.json
|
|
167
|
+
const packageJsonPath = '/package.json';
|
|
168
|
+
if (tree.exists(packageJsonPath)) {
|
|
169
|
+
const packageJson = JSON.parse(tree.read(packageJsonPath).toString('utf-8'));
|
|
170
|
+
const requiredDependencies = {
|
|
171
|
+
'lucide-angular': '^0.562.0',
|
|
172
|
+
'class-variance-authority': '^0.7.1',
|
|
173
|
+
'clsx': '^2.1.1',
|
|
174
|
+
'tailwind-merge': '^3.4.0',
|
|
175
|
+
'@angular/cdk': '^21.0.5',
|
|
176
|
+
'tailwindcss': '^4.1.18',
|
|
177
|
+
'@tailwindcss/postcss': '^4.1.18'
|
|
178
|
+
};
|
|
179
|
+
// Check and add missing dependencies
|
|
180
|
+
let needsInstall = false;
|
|
181
|
+
context.logger.info('📦 Dependencies');
|
|
182
|
+
for (const [pkg, version] of Object.entries(requiredDependencies)) {
|
|
183
|
+
if (!packageJson.dependencies?.[pkg]) {
|
|
184
|
+
packageJson.dependencies = packageJson.dependencies || {};
|
|
185
|
+
packageJson.dependencies[pkg] = version;
|
|
186
|
+
needsInstall = true;
|
|
187
|
+
context.logger.info(` + ${pkg}@${version}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (needsInstall) {
|
|
191
|
+
tree.overwrite(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
|
192
|
+
if (!options.skipInstall) {
|
|
193
|
+
context.addTask(new tasks_1.NodePackageInstallTask());
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
context.logger.info(' ✓ All dependencies already installed');
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// Create lib/utils folder structure
|
|
201
|
+
context.logger.info('');
|
|
202
|
+
context.logger.info('📁 Project Structure');
|
|
203
|
+
const utilsPath = '/src/app/lib/utils';
|
|
204
|
+
const uiPath = '/src/app/lib/components/ui';
|
|
205
|
+
// Create cn utility
|
|
206
|
+
if (!tree.exists(`${utilsPath}/cn.ts`)) {
|
|
207
|
+
tree.create(`${utilsPath}/cn.ts`, CN_UTILITY_TEMPLATE);
|
|
208
|
+
context.logger.info(` + src/app/lib/utils/cn.ts`);
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
context.logger.info(` ✓ utils/cn.ts exists`);
|
|
212
|
+
}
|
|
213
|
+
// Create utils index
|
|
214
|
+
if (!tree.exists(`${utilsPath}/index.ts`)) {
|
|
215
|
+
tree.create(`${utilsPath}/index.ts`, UTILS_INDEX_TEMPLATE);
|
|
216
|
+
context.logger.info(` + src/app/lib/utils/index.ts`);
|
|
217
|
+
}
|
|
218
|
+
// Create ui components directory
|
|
219
|
+
if (!tree.exists(`${uiPath}/.gitkeep`)) {
|
|
220
|
+
tree.create(`${uiPath}/.gitkeep`, '');
|
|
221
|
+
context.logger.info(` + src/app/lib/components/ui/`);
|
|
222
|
+
}
|
|
223
|
+
// Setup styles
|
|
224
|
+
if (!options.skipStyles) {
|
|
225
|
+
context.logger.info('');
|
|
226
|
+
context.logger.info('🎨 Styles');
|
|
227
|
+
const ngCnStylesPath = '/src/ng-cn.scss';
|
|
228
|
+
const stylesPath = '/src/styles.scss';
|
|
229
|
+
const stylesCssPath = '/src/styles.css';
|
|
230
|
+
// Create ng-cn.scss with all CSS variables
|
|
231
|
+
tree.create(ngCnStylesPath, CSS_VARIABLES_TEMPLATE);
|
|
232
|
+
context.logger.info(` + src/ng-cn.scss (theme variables)`);
|
|
233
|
+
// Import in styles.scss or styles.css
|
|
234
|
+
if (tree.exists(stylesPath)) {
|
|
235
|
+
const stylesContent = tree.read(stylesPath).toString('utf-8');
|
|
236
|
+
if (!stylesContent.includes('ng-cn.scss')) {
|
|
237
|
+
const newContent = `@import './ng-cn.scss';\n\n${stylesContent}`;
|
|
238
|
+
tree.overwrite(stylesPath, newContent);
|
|
239
|
+
context.logger.info(` ✓ Imported in styles.scss`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
else if (tree.exists(stylesCssPath)) {
|
|
243
|
+
const stylesContent = tree.read(stylesCssPath).toString('utf-8');
|
|
244
|
+
if (!stylesContent.includes('ng-cn.scss')) {
|
|
245
|
+
const newContent = `@import './ng-cn.scss';\n\n${stylesContent}`;
|
|
246
|
+
tree.overwrite(stylesCssPath, newContent);
|
|
247
|
+
context.logger.info(` ✓ Imported in styles.css`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
// Update tsconfig paths
|
|
252
|
+
context.logger.info('');
|
|
253
|
+
context.logger.info('⚙️ TypeScript Config');
|
|
254
|
+
const tsconfigPath = '/tsconfig.json';
|
|
255
|
+
if (tree.exists(tsconfigPath)) {
|
|
256
|
+
const tsconfig = JSON.parse(tree.read(tsconfigPath).toString('utf-8'));
|
|
257
|
+
tsconfig.compilerOptions = tsconfig.compilerOptions || {};
|
|
258
|
+
tsconfig.compilerOptions.paths = tsconfig.compilerOptions.paths || {};
|
|
259
|
+
const pathAliases = {
|
|
260
|
+
'@/*': ['src/*'],
|
|
261
|
+
'@/lib/*': ['src/app/lib/*'],
|
|
262
|
+
'@/ui/*': ['src/app/lib/components/ui/*'],
|
|
263
|
+
'@/utils/*': ['src/app/lib/utils/*']
|
|
264
|
+
};
|
|
265
|
+
let pathsUpdated = false;
|
|
266
|
+
for (const [alias, paths] of Object.entries(pathAliases)) {
|
|
267
|
+
if (!tsconfig.compilerOptions.paths[alias]) {
|
|
268
|
+
tsconfig.compilerOptions.paths[alias] = paths;
|
|
269
|
+
pathsUpdated = true;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
if (pathsUpdated) {
|
|
273
|
+
tree.overwrite(tsconfigPath, JSON.stringify(tsconfig, null, 2));
|
|
274
|
+
context.logger.info(' ✓ Path aliases configured');
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
context.logger.info(' ✓ Path aliases already set');
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
// Success message with ASCII art banner
|
|
281
|
+
context.logger.info('');
|
|
282
|
+
context.logger.info('');
|
|
283
|
+
context.logger.info(' ███╗ ██╗ ██████╗ ██████╗███╗ ██╗');
|
|
284
|
+
context.logger.info(' ████╗ ██║██╔════╝ ██╔════╝████╗ ██║');
|
|
285
|
+
context.logger.info(' ██╔██╗ ██║██║ ███╗█████╗██║ ██╔██╗ ██║');
|
|
286
|
+
context.logger.info(' ██║╚██╗██║██║ ██║╚════╝██║ ██║╚██╗██║');
|
|
287
|
+
context.logger.info(' ██║ ╚████║╚██████╔╝ ╚██████╗██║ ╚████║');
|
|
288
|
+
context.logger.info(' ╚═╝ ╚═══╝ ╚═════╝ ╚═════╝╚═╝ ╚═══╝');
|
|
289
|
+
context.logger.info('');
|
|
290
|
+
context.logger.info(' ✅ Setup complete! shadcn for Angular');
|
|
291
|
+
context.logger.info('');
|
|
292
|
+
context.logger.info('╭──────────────────────────────────────────────────╮');
|
|
293
|
+
context.logger.info('│ 🚀 Next steps: │');
|
|
294
|
+
context.logger.info('│ │');
|
|
295
|
+
context.logger.info('│ 1. Add components: │');
|
|
296
|
+
context.logger.info('│ ng g @ng-cn/core:c button │');
|
|
297
|
+
context.logger.info('│ ng g @ng-cn/core:c card │');
|
|
298
|
+
context.logger.info('│ │');
|
|
299
|
+
context.logger.info('│ 2. Or install individual packages: │');
|
|
300
|
+
context.logger.info('│ npm i @ng-cn/button @ng-cn/card │');
|
|
301
|
+
context.logger.info('│ │');
|
|
302
|
+
context.logger.info('│ 3. Import and use: │');
|
|
303
|
+
context.logger.info("│ import { Button } from '@/ui/button'; │");
|
|
304
|
+
context.logger.info('│ │');
|
|
305
|
+
context.logger.info('│ 📚 Docs: https://shadcn-angular.tigayon.com │');
|
|
306
|
+
context.logger.info('╰──────────────────────────────────────────────────╯');
|
|
307
|
+
context.logger.info('');
|
|
308
|
+
return tree;
|
|
309
|
+
};
|
|
310
|
+
}
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';
|
|
2
|
+
import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks';
|
|
3
|
+
|
|
4
|
+
interface NgAddOptions {
|
|
5
|
+
project?: string;
|
|
6
|
+
skipInstall?: boolean;
|
|
7
|
+
skipStyles?: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// CSS Variables template for shadcn theming
|
|
11
|
+
const CSS_VARIABLES_TEMPLATE = `/* ng-cn/core - shadcn-angular styles */
|
|
12
|
+
@use "tailwindcss";
|
|
13
|
+
|
|
14
|
+
@custom-variant dark (&:is(.dark *));
|
|
15
|
+
|
|
16
|
+
:root {
|
|
17
|
+
--radius: 0.625rem;
|
|
18
|
+
--background: oklch(1 0 0);
|
|
19
|
+
--foreground: oklch(0.145 0 0);
|
|
20
|
+
--card: oklch(1 0 0);
|
|
21
|
+
--card-foreground: oklch(0.145 0 0);
|
|
22
|
+
--popover: oklch(1 0 0);
|
|
23
|
+
--popover-foreground: oklch(0.145 0 0);
|
|
24
|
+
--primary: oklch(0.205 0 0);
|
|
25
|
+
--primary-foreground: oklch(0.985 0 0);
|
|
26
|
+
--secondary: oklch(0.97 0 0);
|
|
27
|
+
--secondary-foreground: oklch(0.205 0 0);
|
|
28
|
+
--muted: oklch(0.97 0 0);
|
|
29
|
+
--muted-foreground: oklch(0.556 0 0);
|
|
30
|
+
--accent: oklch(0.97 0 0);
|
|
31
|
+
--accent-foreground: oklch(0.205 0 0);
|
|
32
|
+
--destructive: oklch(0.577 0.245 27.325);
|
|
33
|
+
--destructive-foreground: oklch(0.577 0.245 27.325);
|
|
34
|
+
--border: oklch(0.922 0 0);
|
|
35
|
+
--input: oklch(0.922 0 0);
|
|
36
|
+
--ring: oklch(0.708 0 0);
|
|
37
|
+
--chart-1: oklch(0.646 0.222 41.116);
|
|
38
|
+
--chart-2: oklch(0.6 0.118 184.704);
|
|
39
|
+
--chart-3: oklch(0.398 0.07 227.392);
|
|
40
|
+
--chart-4: oklch(0.828 0.189 84.429);
|
|
41
|
+
--chart-5: oklch(0.769 0.188 70.08);
|
|
42
|
+
--sidebar: oklch(0.985 0 0);
|
|
43
|
+
--sidebar-foreground: oklch(0.145 0 0);
|
|
44
|
+
--sidebar-primary: oklch(0.205 0 0);
|
|
45
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
46
|
+
--sidebar-accent: oklch(0.97 0 0);
|
|
47
|
+
--sidebar-accent-foreground: oklch(0.205 0 0);
|
|
48
|
+
--sidebar-border: oklch(0.922 0 0);
|
|
49
|
+
--sidebar-ring: oklch(0.708 0 0);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.dark {
|
|
53
|
+
--background: oklch(0.145 0 0);
|
|
54
|
+
--foreground: oklch(0.985 0 0);
|
|
55
|
+
--card: oklch(0.205 0 0);
|
|
56
|
+
--card-foreground: oklch(0.985 0 0);
|
|
57
|
+
--popover: oklch(0.205 0 0);
|
|
58
|
+
--popover-foreground: oklch(0.985 0 0);
|
|
59
|
+
--primary: oklch(0.985 0 0);
|
|
60
|
+
--primary-foreground: oklch(0.205 0 0);
|
|
61
|
+
--secondary: oklch(0.269 0 0);
|
|
62
|
+
--secondary-foreground: oklch(0.985 0 0);
|
|
63
|
+
--muted: oklch(0.269 0 0);
|
|
64
|
+
--muted-foreground: oklch(0.708 0 0);
|
|
65
|
+
--accent: oklch(0.269 0 0);
|
|
66
|
+
--accent-foreground: oklch(0.985 0 0);
|
|
67
|
+
--destructive: oklch(0.396 0.141 25.723);
|
|
68
|
+
--destructive-foreground: oklch(0.637 0.237 25.331);
|
|
69
|
+
--border: oklch(0.269 0 0);
|
|
70
|
+
--input: oklch(0.269 0 0);
|
|
71
|
+
--ring: oklch(0.439 0 0);
|
|
72
|
+
--chart-1: oklch(0.488 0.243 264.376);
|
|
73
|
+
--chart-2: oklch(0.696 0.17 162.48);
|
|
74
|
+
--chart-3: oklch(0.769 0.188 70.08);
|
|
75
|
+
--chart-4: oklch(0.627 0.265 303.9);
|
|
76
|
+
--chart-5: oklch(0.645 0.246 16.439);
|
|
77
|
+
--sidebar: oklch(0.205 0 0);
|
|
78
|
+
--sidebar-foreground: oklch(0.985 0 0);
|
|
79
|
+
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
80
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
81
|
+
--sidebar-accent: oklch(0.269 0 0);
|
|
82
|
+
--sidebar-accent-foreground: oklch(0.985 0 0);
|
|
83
|
+
--sidebar-border: oklch(0.269 0 0);
|
|
84
|
+
--sidebar-ring: oklch(0.439 0 0);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
@theme inline {
|
|
88
|
+
--color-background: var(--background);
|
|
89
|
+
--color-foreground: var(--foreground);
|
|
90
|
+
--color-card: var(--card);
|
|
91
|
+
--color-card-foreground: var(--card-foreground);
|
|
92
|
+
--color-popover: var(--popover);
|
|
93
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
94
|
+
--color-primary: var(--primary);
|
|
95
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
96
|
+
--color-secondary: var(--secondary);
|
|
97
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
98
|
+
--color-muted: var(--muted);
|
|
99
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
100
|
+
--color-accent: var(--accent);
|
|
101
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
102
|
+
--color-destructive: var(--destructive);
|
|
103
|
+
--color-destructive-foreground: var(--destructive-foreground);
|
|
104
|
+
--color-border: var(--border);
|
|
105
|
+
--color-input: var(--input);
|
|
106
|
+
--color-ring: var(--ring);
|
|
107
|
+
--color-chart-1: var(--chart-1);
|
|
108
|
+
--color-chart-2: var(--chart-2);
|
|
109
|
+
--color-chart-3: var(--chart-3);
|
|
110
|
+
--color-chart-4: var(--chart-4);
|
|
111
|
+
--color-chart-5: var(--chart-5);
|
|
112
|
+
--color-sidebar: var(--sidebar);
|
|
113
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
114
|
+
--color-sidebar-primary: var(--sidebar-primary);
|
|
115
|
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
116
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
117
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
118
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
119
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
120
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
121
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
122
|
+
--radius-lg: var(--radius);
|
|
123
|
+
--radius-xl: calc(var(--radius) + 4px);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
@layer base {
|
|
127
|
+
* { @apply border-border outline-ring/50; }
|
|
128
|
+
body { @apply bg-background text-foreground; font-feature-settings: "rlig" 1, "calt" 1; }
|
|
129
|
+
html { scroll-behavior: smooth; }
|
|
130
|
+
:focus-visible { @apply outline-2 outline-ring outline-offset-2; }
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
@layer utilities {
|
|
134
|
+
.animate-accordion-down { animation: accordion-down 0.2s ease-out; }
|
|
135
|
+
.animate-accordion-up { animation: accordion-up 0.2s ease-out; }
|
|
136
|
+
@keyframes accordion-down { from { height: 0; } to { height: var(--accordion-content-height); } }
|
|
137
|
+
@keyframes accordion-up { from { height: var(--accordion-content-height); } to { height: 0; } }
|
|
138
|
+
.animate-collapsible-down { animation: collapsible-down 0.2s ease-out; }
|
|
139
|
+
.animate-collapsible-up { animation: collapsible-up 0.2s ease-out; }
|
|
140
|
+
@keyframes collapsible-down { from { height: 0; } to { height: var(--collapsible-content-height); } }
|
|
141
|
+
@keyframes collapsible-up { from { height: var(--collapsible-content-height); } to { height: 0; } }
|
|
142
|
+
}
|
|
143
|
+
`;
|
|
144
|
+
|
|
145
|
+
// cn utility template
|
|
146
|
+
const CN_UTILITY_TEMPLATE = `import { clsx, type ClassValue } from 'clsx';
|
|
147
|
+
import { twMerge } from 'tailwind-merge';
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Utility function to merge Tailwind CSS classes with proper conflict resolution.
|
|
151
|
+
* Combines clsx for conditional classes with tailwind-merge for deduplication.
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* cn('px-4 py-2', 'px-6') // => 'py-2 px-6'
|
|
155
|
+
* cn('bg-red-500', condition && 'bg-blue-500') // conditional classes
|
|
156
|
+
*/
|
|
157
|
+
export function cn(...inputs: ClassValue[]): string {
|
|
158
|
+
return twMerge(clsx(inputs));
|
|
159
|
+
}
|
|
160
|
+
`;
|
|
161
|
+
|
|
162
|
+
const UTILS_INDEX_TEMPLATE = `export { cn } from './cn';
|
|
163
|
+
`;
|
|
164
|
+
|
|
165
|
+
export function ngAdd(options: NgAddOptions): Rule {
|
|
166
|
+
return (tree: Tree, context: SchematicContext) => {
|
|
167
|
+
context.logger.info('');
|
|
168
|
+
context.logger.info('╭──────────────────────────────────────────────────╮');
|
|
169
|
+
context.logger.info('│ │');
|
|
170
|
+
context.logger.info('│ 🎨 @ng-cn/core - shadcn for Angular │');
|
|
171
|
+
context.logger.info('│ │');
|
|
172
|
+
context.logger.info('╰──────────────────────────────────────────────────╯');
|
|
173
|
+
context.logger.info('');
|
|
174
|
+
|
|
175
|
+
// Add dependencies to package.json
|
|
176
|
+
const packageJsonPath = '/package.json';
|
|
177
|
+
if (tree.exists(packageJsonPath)) {
|
|
178
|
+
const packageJson = JSON.parse(tree.read(packageJsonPath)!.toString('utf-8'));
|
|
179
|
+
|
|
180
|
+
const requiredDependencies = {
|
|
181
|
+
'lucide-angular': '^0.562.0',
|
|
182
|
+
'class-variance-authority': '^0.7.1',
|
|
183
|
+
'clsx': '^2.1.1',
|
|
184
|
+
'tailwind-merge': '^3.4.0',
|
|
185
|
+
'@angular/cdk': '^21.0.5',
|
|
186
|
+
'tailwindcss': '^4.1.18',
|
|
187
|
+
'@tailwindcss/postcss': '^4.1.18'
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// Check and add missing dependencies
|
|
191
|
+
let needsInstall = false;
|
|
192
|
+
context.logger.info('📦 Dependencies');
|
|
193
|
+
for (const [pkg, version] of Object.entries(requiredDependencies)) {
|
|
194
|
+
if (!packageJson.dependencies?.[pkg]) {
|
|
195
|
+
packageJson.dependencies = packageJson.dependencies || {};
|
|
196
|
+
packageJson.dependencies[pkg] = version;
|
|
197
|
+
needsInstall = true;
|
|
198
|
+
context.logger.info(` + ${pkg}@${version}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (needsInstall) {
|
|
203
|
+
tree.overwrite(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
|
204
|
+
if (!options.skipInstall) {
|
|
205
|
+
context.addTask(new NodePackageInstallTask());
|
|
206
|
+
}
|
|
207
|
+
} else {
|
|
208
|
+
context.logger.info(' ✓ All dependencies already installed');
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Create lib/utils folder structure
|
|
213
|
+
context.logger.info('');
|
|
214
|
+
context.logger.info('📁 Project Structure');
|
|
215
|
+
|
|
216
|
+
const utilsPath = '/src/app/lib/utils';
|
|
217
|
+
const uiPath = '/src/app/lib/components/ui';
|
|
218
|
+
|
|
219
|
+
// Create cn utility
|
|
220
|
+
if (!tree.exists(`${utilsPath}/cn.ts`)) {
|
|
221
|
+
tree.create(`${utilsPath}/cn.ts`, CN_UTILITY_TEMPLATE);
|
|
222
|
+
context.logger.info(` + src/app/lib/utils/cn.ts`);
|
|
223
|
+
} else {
|
|
224
|
+
context.logger.info(` ✓ utils/cn.ts exists`);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Create utils index
|
|
228
|
+
if (!tree.exists(`${utilsPath}/index.ts`)) {
|
|
229
|
+
tree.create(`${utilsPath}/index.ts`, UTILS_INDEX_TEMPLATE);
|
|
230
|
+
context.logger.info(` + src/app/lib/utils/index.ts`);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Create ui components directory
|
|
234
|
+
if (!tree.exists(`${uiPath}/.gitkeep`)) {
|
|
235
|
+
tree.create(`${uiPath}/.gitkeep`, '');
|
|
236
|
+
context.logger.info(` + src/app/lib/components/ui/`);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Setup styles
|
|
240
|
+
if (!options.skipStyles) {
|
|
241
|
+
context.logger.info('');
|
|
242
|
+
context.logger.info('🎨 Styles');
|
|
243
|
+
|
|
244
|
+
const ngCnStylesPath = '/src/ng-cn.scss';
|
|
245
|
+
const stylesPath = '/src/styles.scss';
|
|
246
|
+
const stylesCssPath = '/src/styles.css';
|
|
247
|
+
|
|
248
|
+
// Create ng-cn.scss with all CSS variables
|
|
249
|
+
tree.create(ngCnStylesPath, CSS_VARIABLES_TEMPLATE);
|
|
250
|
+
context.logger.info(` + src/ng-cn.scss (theme variables)`);
|
|
251
|
+
|
|
252
|
+
// Import in styles.scss or styles.css
|
|
253
|
+
if (tree.exists(stylesPath)) {
|
|
254
|
+
const stylesContent = tree.read(stylesPath)!.toString('utf-8');
|
|
255
|
+
if (!stylesContent.includes('ng-cn.scss')) {
|
|
256
|
+
const newContent = `@import './ng-cn.scss';\n\n${stylesContent}`;
|
|
257
|
+
tree.overwrite(stylesPath, newContent);
|
|
258
|
+
context.logger.info(` ✓ Imported in styles.scss`);
|
|
259
|
+
}
|
|
260
|
+
} else if (tree.exists(stylesCssPath)) {
|
|
261
|
+
const stylesContent = tree.read(stylesCssPath)!.toString('utf-8');
|
|
262
|
+
if (!stylesContent.includes('ng-cn.scss')) {
|
|
263
|
+
const newContent = `@import './ng-cn.scss';\n\n${stylesContent}`;
|
|
264
|
+
tree.overwrite(stylesCssPath, newContent);
|
|
265
|
+
context.logger.info(` ✓ Imported in styles.css`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Update tsconfig paths
|
|
271
|
+
context.logger.info('');
|
|
272
|
+
context.logger.info('⚙️ TypeScript Config');
|
|
273
|
+
|
|
274
|
+
const tsconfigPath = '/tsconfig.json';
|
|
275
|
+
if (tree.exists(tsconfigPath)) {
|
|
276
|
+
const tsconfig = JSON.parse(tree.read(tsconfigPath)!.toString('utf-8'));
|
|
277
|
+
tsconfig.compilerOptions = tsconfig.compilerOptions || {};
|
|
278
|
+
tsconfig.compilerOptions.paths = tsconfig.compilerOptions.paths || {};
|
|
279
|
+
|
|
280
|
+
const pathAliases: Record<string, string[]> = {
|
|
281
|
+
'@/*': ['src/*'],
|
|
282
|
+
'@/lib/*': ['src/app/lib/*'],
|
|
283
|
+
'@/ui/*': ['src/app/lib/components/ui/*'],
|
|
284
|
+
'@/utils/*': ['src/app/lib/utils/*']
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
let pathsUpdated = false;
|
|
288
|
+
for (const [alias, paths] of Object.entries(pathAliases)) {
|
|
289
|
+
if (!tsconfig.compilerOptions.paths[alias]) {
|
|
290
|
+
tsconfig.compilerOptions.paths[alias] = paths;
|
|
291
|
+
pathsUpdated = true;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (pathsUpdated) {
|
|
296
|
+
tree.overwrite(tsconfigPath, JSON.stringify(tsconfig, null, 2));
|
|
297
|
+
context.logger.info(' ✓ Path aliases configured');
|
|
298
|
+
} else {
|
|
299
|
+
context.logger.info(' ✓ Path aliases already set');
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Success message with ASCII art banner
|
|
304
|
+
context.logger.info('');
|
|
305
|
+
context.logger.info('');
|
|
306
|
+
context.logger.info(' ███╗ ██╗ ██████╗ ██████╗███╗ ██╗');
|
|
307
|
+
context.logger.info(' ████╗ ██║██╔════╝ ██╔════╝████╗ ██║');
|
|
308
|
+
context.logger.info(' ██╔██╗ ██║██║ ███╗█████╗██║ ██╔██╗ ██║');
|
|
309
|
+
context.logger.info(' ██║╚██╗██║██║ ██║╚════╝██║ ██║╚██╗██║');
|
|
310
|
+
context.logger.info(' ██║ ╚████║╚██████╔╝ ╚██████╗██║ ╚████║');
|
|
311
|
+
context.logger.info(' ╚═╝ ╚═══╝ ╚═════╝ ╚═════╝╚═╝ ╚═══╝');
|
|
312
|
+
context.logger.info('');
|
|
313
|
+
context.logger.info(' ✅ Setup complete! shadcn for Angular');
|
|
314
|
+
context.logger.info('');
|
|
315
|
+
context.logger.info('╭──────────────────────────────────────────────────╮');
|
|
316
|
+
context.logger.info('│ 🚀 Next steps: │');
|
|
317
|
+
context.logger.info('│ │');
|
|
318
|
+
context.logger.info('│ 1. Add components: │');
|
|
319
|
+
context.logger.info('│ ng g @ng-cn/core:c button │');
|
|
320
|
+
context.logger.info('│ ng g @ng-cn/core:c card │');
|
|
321
|
+
context.logger.info('│ │');
|
|
322
|
+
context.logger.info('│ 2. Or install individual packages: │');
|
|
323
|
+
context.logger.info('│ npm i @ng-cn/button @ng-cn/card │');
|
|
324
|
+
context.logger.info('│ │');
|
|
325
|
+
context.logger.info('│ 3. Import and use: │');
|
|
326
|
+
context.logger.info("│ import { Button } from '@/ui/button'; │");
|
|
327
|
+
context.logger.info('│ │');
|
|
328
|
+
context.logger.info('│ 📚 Docs: https://shadcn-angular.tigayon.com │');
|
|
329
|
+
context.logger.info('╰──────────────────────────────────────────────────╯');
|
|
330
|
+
context.logger.info('');
|
|
331
|
+
|
|
332
|
+
return tree;
|
|
333
|
+
};
|
|
334
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/schema",
|
|
3
|
+
"$id": "NgCnCoreNgAddSchema",
|
|
4
|
+
"title": "@ng-cn/core initialization schematic",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"project": {
|
|
8
|
+
"type": "string",
|
|
9
|
+
"description": "The name of the project to initialize.",
|
|
10
|
+
"$default": {
|
|
11
|
+
"$source": "projectName"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"skipInstall": {
|
|
15
|
+
"type": "boolean",
|
|
16
|
+
"default": false,
|
|
17
|
+
"description": "Skip installing dependency packages."
|
|
18
|
+
},
|
|
19
|
+
"skipStyles": {
|
|
20
|
+
"type": "boolean",
|
|
21
|
+
"default": false,
|
|
22
|
+
"description": "Skip creating ng-cn.scss styles file."
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|