@unopsitg/ux 21.1.0 → 21.2.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/AGENTS.md +86 -0
- package/README.md +39 -17
- package/package.json +16 -10
- package/schematics/collection.json +10 -0
- package/schematics/ng-add/index.d.ts +7 -0
- package/schematics/ng-add/index.js +241 -0
- package/schematics/ng-add/schema.json +21 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# @unopsitg/ux — AI Agent Integration Guide
|
|
2
|
+
|
|
3
|
+
This file is for AI coding assistants (Cursor, Claude Code, Copilot, etc.) working in projects that depend on `@unopsitg/ux`.
|
|
4
|
+
|
|
5
|
+
## What this library provides
|
|
6
|
+
|
|
7
|
+
- **Layout shell** — full application chrome (sidebar, topbar, breadcrumb, configurator)
|
|
8
|
+
- **Brand theme** — PrimeNG / PrimeUIX preset (`BrandSoft`) with UNOPS brand colors
|
|
9
|
+
- **Tailwind CSS** — design tokens, custom utilities, and component animations (Tailwind v4 source file)
|
|
10
|
+
- **Shared types** — demo data interfaces
|
|
11
|
+
|
|
12
|
+
## Quick setup (or run `ng add @unopsitg/ux`)
|
|
13
|
+
|
|
14
|
+
1. Create `.postcssrc.json` (not `.mjs` — Angular 21 esbuild ignores `.mjs`):
|
|
15
|
+
```json
|
|
16
|
+
{ "plugins": { "@tailwindcss/postcss": {} } }
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
2. Create `src/tailwind.css`:
|
|
20
|
+
```css
|
|
21
|
+
@import "tailwindcss";
|
|
22
|
+
@import "@unopsitg/ux/tailwind";
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
3. In `angular.json` styles:
|
|
26
|
+
```json
|
|
27
|
+
["node_modules/@unopsitg/ux/assets/styles.scss", "src/tailwind.css", "node_modules/primeicons/primeicons.css", "src/styles.scss"]
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
4. In `angular.json` assets:
|
|
31
|
+
```json
|
|
32
|
+
{ "glob": "**/*", "input": "node_modules/@unopsitg/ux/assets/opp", "output": "assets/opp" }
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
5. Install dev dependencies:
|
|
36
|
+
```bash
|
|
37
|
+
npm install -D @tailwindcss/postcss tailwindcss postcss
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
6. In `app.config.ts` providers:
|
|
41
|
+
```typescript
|
|
42
|
+
import { providePrimeNG } from 'primeng/config';
|
|
43
|
+
import { BrandSoft, TOPBAR_PROFILE_MENU_CONFIG, LayoutService } from '@unopsitg/ux';
|
|
44
|
+
|
|
45
|
+
providePrimeNG({ theme: { preset: BrandSoft, options: { darkModeSelector: '.app-dark' } } })
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Critical rules
|
|
49
|
+
|
|
50
|
+
- **NEVER** add `node_modules/@unopsitg/ux/assets/tailwind.css` directly to `angular.json` styles — Angular's esbuild does NOT run PostCSS on node_modules CSS, so all directives pass through as raw text and zero utilities are generated.
|
|
51
|
+
- **ALWAYS** use `src/tailwind.css` with `@import "@unopsitg/ux/tailwind"` — this lives in the source tree where PostCSS processes it.
|
|
52
|
+
- **NEVER** put `@source` directives in `.scss` files — Sass passes them through as inert text.
|
|
53
|
+
- **NEVER** use `postcss.config.mjs` — Angular 21 esbuild silently ignores it.
|
|
54
|
+
- Shell-critical utilities (`.hidden`, `.animate-scalein`, `.animate-fadeout`) ship as real CSS. Do not redefine them.
|
|
55
|
+
|
|
56
|
+
## Package exports
|
|
57
|
+
|
|
58
|
+
| Import path | Resolves to |
|
|
59
|
+
|-------------|-------------|
|
|
60
|
+
| `@unopsitg/ux` | `fesm2022/unopsitg-ux.mjs` (Angular library) |
|
|
61
|
+
| `@unopsitg/ux/tailwind` | `assets/tailwind.css` (Tailwind v4 source) |
|
|
62
|
+
| `@unopsitg/ux/styles` | `assets/styles.scss` (layout SCSS) |
|
|
63
|
+
|
|
64
|
+
## Injection tokens
|
|
65
|
+
|
|
66
|
+
| Token | Purpose | Shape |
|
|
67
|
+
|-------|---------|-------|
|
|
68
|
+
| `MENU_MODEL` | Sidebar menu tree | `MenuItem[]` |
|
|
69
|
+
| `SIDEBAR_LOGO` | Expanded/compact logo URLs | `{ expanded, compact, alt }` |
|
|
70
|
+
| `TOPBAR_MOBILE_LOGO` | Mobile header logos | `{ light, dark }` |
|
|
71
|
+
| `TOPBAR_PROFILE_MENU_CONFIG` | Profile dropdown items | `{ items: { id, label, icon, command?, separator? }[] }` |
|
|
72
|
+
|
|
73
|
+
## Theme initialization
|
|
74
|
+
|
|
75
|
+
`LayoutService` defaults to `darkTheme: true`. To avoid a flash of light mode, add an `APP_INITIALIZER`:
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
import { APP_INITIALIZER } from '@angular/core';
|
|
79
|
+
import { LayoutService } from '@unopsitg/ux';
|
|
80
|
+
|
|
81
|
+
{ provide: APP_INITIALIZER, useFactory: (ls: LayoutService) => () => ls.toggleDarkMode(), deps: [LayoutService], multi: true }
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Full documentation
|
|
85
|
+
|
|
86
|
+
See `README.md` in this package for complete configuration reference.
|
package/README.md
CHANGED
|
@@ -44,11 +44,25 @@ export const appConfig: ApplicationConfig = {
|
|
|
44
44
|
|
|
45
45
|
## Styles and assets
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
### Automated setup
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
```bash
|
|
50
|
+
ng add @unopsitg/ux
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
This creates all required files and patches `angular.json` + `app.config.ts` automatically. The rest of this section documents the manual equivalent.
|
|
54
|
+
|
|
55
|
+
### Understanding `assets/tailwind.css`
|
|
56
|
+
|
|
57
|
+
The library ships `assets/tailwind.css` as a **Tailwind v4 source file** — it contains `@plugin`, `@source`, `@theme`, and `@utility` directives that must be processed by `@tailwindcss/postcss` to generate utility classes.
|
|
50
58
|
|
|
51
|
-
|
|
59
|
+
> **Do NOT add `assets/tailwind.css` directly to angular.json styles.** Angular's esbuild/Vite builder does not run PostCSS on CSS files referenced from `node_modules` — it inlines them as raw text. All directives will appear literally in the browser output and zero utilities will be generated.
|
|
60
|
+
|
|
61
|
+
### Manual setup
|
|
62
|
+
|
|
63
|
+
#### 1. PostCSS configuration
|
|
64
|
+
|
|
65
|
+
Angular 21's `application` builder only loads JSON-format PostCSS configs. Create `.postcssrc.json` in the project root:
|
|
52
66
|
|
|
53
67
|
```json
|
|
54
68
|
{
|
|
@@ -58,9 +72,20 @@ Angular 21's `application` builder (esbuild) only loads JSON-format PostCSS conf
|
|
|
58
72
|
}
|
|
59
73
|
```
|
|
60
74
|
|
|
61
|
-
> **Warning:** `.mjs` configs (`postcss.config.mjs`) are silently ignored by esbuild.
|
|
75
|
+
> **Warning:** `.mjs` configs (`postcss.config.mjs`) are silently ignored by esbuild.
|
|
76
|
+
|
|
77
|
+
#### 2. Tailwind entry point
|
|
78
|
+
|
|
79
|
+
Create `src/tailwind.css` — this lives in your source tree so Angular **will** run PostCSS on it:
|
|
80
|
+
|
|
81
|
+
```css
|
|
82
|
+
@import "tailwindcss";
|
|
83
|
+
@import "@unopsitg/ux/tailwind";
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The `@unopsitg/ux/tailwind` export resolves to the library's `assets/tailwind.css` via the package `exports` field. The file includes brand tokens, custom utilities, and a `@source` directive that scans the library's compiled JS for class references.
|
|
62
87
|
|
|
63
|
-
|
|
88
|
+
#### 3. angular.json styles and assets
|
|
64
89
|
|
|
65
90
|
```json
|
|
66
91
|
"styles": [
|
|
@@ -75,23 +100,20 @@ Angular 21's `application` builder (esbuild) only loads JSON-format PostCSS conf
|
|
|
75
100
|
]
|
|
76
101
|
```
|
|
77
102
|
|
|
78
|
-
|
|
103
|
+
#### 4. Dev dependencies
|
|
79
104
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
```css
|
|
83
|
-
/* src/tailwind.css */
|
|
84
|
-
@import "../node_modules/@unopsitg/ux/assets/tailwind.css";
|
|
85
|
-
@source "../node_modules/@unopsitg/ux/fesm2022";
|
|
105
|
+
```bash
|
|
106
|
+
npm install -D @tailwindcss/postcss tailwindcss postcss
|
|
86
107
|
```
|
|
87
108
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
> **Do not** put `@source` directives in `.scss` files — Sass copies them as inert text and PostCSS/Tailwind never processes them.
|
|
109
|
+
### Troubleshooting
|
|
91
110
|
|
|
92
|
-
**
|
|
111
|
+
- **Tailwind directives appear as raw text in browser CSS** — you added the library's CSS directly to `angular.json` instead of using `src/tailwind.css`.
|
|
112
|
+
- **"The path './assets/tailwind.css' is not exported"** — upgrade to `@unopsitg/ux@21.2.0+` which adds the `./tailwind` export.
|
|
113
|
+
- **Zero utilities generated** — verify `.postcssrc.json` exists (not `.mjs`) and `src/tailwind.css` is in the styles array.
|
|
114
|
+
- **Do not** put `@source` directives in `.scss` files — Sass copies them as inert text.
|
|
93
115
|
|
|
94
|
-
|
|
116
|
+
**Verify:** after `ng serve`, inspect the compiled CSS for `.flex { display: flex }`. If missing, PostCSS is not running on your tailwind entry point.
|
|
95
117
|
|
|
96
118
|
## Tokens
|
|
97
119
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unopsitg/ux",
|
|
3
|
-
"version": "21.
|
|
3
|
+
"version": "21.2.0",
|
|
4
4
|
"description": "UNOPS Angular 21 layout shell, brand theme (PrimeNG / PrimeUIX), and shared types",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"angular",
|
|
@@ -9,6 +9,21 @@
|
|
|
9
9
|
"layout"
|
|
10
10
|
],
|
|
11
11
|
"license": "UNLICENSED",
|
|
12
|
+
"schematics": "./schematics/collection.json",
|
|
13
|
+
"ng-add": {
|
|
14
|
+
"save": "dependencies"
|
|
15
|
+
},
|
|
16
|
+
"exports": {
|
|
17
|
+
"./tailwind": "./assets/tailwind.css",
|
|
18
|
+
"./styles": "./assets/styles.scss",
|
|
19
|
+
"./package.json": {
|
|
20
|
+
"default": "./package.json"
|
|
21
|
+
},
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./types/unopsitg-ux.d.ts",
|
|
24
|
+
"default": "./fesm2022/unopsitg-ux.mjs"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
12
27
|
"repository": {
|
|
13
28
|
"type": "git",
|
|
14
29
|
"url": "https://github.com/opp_plus/unops-ng21_ux.git"
|
|
@@ -29,15 +44,6 @@
|
|
|
29
44
|
},
|
|
30
45
|
"module": "fesm2022/unopsitg-ux.mjs",
|
|
31
46
|
"typings": "types/unopsitg-ux.d.ts",
|
|
32
|
-
"exports": {
|
|
33
|
-
"./package.json": {
|
|
34
|
-
"default": "./package.json"
|
|
35
|
-
},
|
|
36
|
-
".": {
|
|
37
|
-
"types": "./types/unopsitg-ux.d.ts",
|
|
38
|
-
"default": "./fesm2022/unopsitg-ux.mjs"
|
|
39
|
-
}
|
|
40
|
-
},
|
|
41
47
|
"sideEffects": false,
|
|
42
48
|
"type": "module",
|
|
43
49
|
"dependencies": {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../../../node_modules/@angular-devkit/schematics/collection-schema.json",
|
|
3
|
+
"schematics": {
|
|
4
|
+
"ng-add": {
|
|
5
|
+
"description": "Set up @unopsitg/ux in an Angular project",
|
|
6
|
+
"factory": "./ng-add/index#ngAdd",
|
|
7
|
+
"schema": "./ng-add/schema.json"
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ngAdd = ngAdd;
|
|
4
|
+
const schematics_1 = require("@angular-devkit/schematics");
|
|
5
|
+
const tasks_1 = require("@angular-devkit/schematics/tasks");
|
|
6
|
+
const RUNTIME_DEPS = {
|
|
7
|
+
primeng: '^21.0.4',
|
|
8
|
+
'@primeuix/themes': '^2.0.0',
|
|
9
|
+
primeicons: '^7.0.0',
|
|
10
|
+
};
|
|
11
|
+
const DEV_DEPS = {
|
|
12
|
+
'@tailwindcss/postcss': '^4.0.0',
|
|
13
|
+
tailwindcss: '^4.0.0',
|
|
14
|
+
postcss: '^8.4.0',
|
|
15
|
+
};
|
|
16
|
+
const STYLES_ENTRIES = [
|
|
17
|
+
'node_modules/@unopsitg/ux/assets/styles.scss',
|
|
18
|
+
'src/tailwind.css',
|
|
19
|
+
'node_modules/primeicons/primeicons.css',
|
|
20
|
+
'src/styles.scss',
|
|
21
|
+
];
|
|
22
|
+
const ASSETS_ENTRY = {
|
|
23
|
+
glob: '**/*',
|
|
24
|
+
input: 'node_modules/@unopsitg/ux/assets/opp',
|
|
25
|
+
output: 'assets/opp',
|
|
26
|
+
};
|
|
27
|
+
function ngAdd(options) {
|
|
28
|
+
return (0, schematics_1.chain)([
|
|
29
|
+
addPeerDependencies(),
|
|
30
|
+
createPostcssConfig(),
|
|
31
|
+
createTailwindWrapper(),
|
|
32
|
+
patchAngularJson(options),
|
|
33
|
+
patchAppConfig(options),
|
|
34
|
+
createCursorRule(),
|
|
35
|
+
installDependencies(),
|
|
36
|
+
]);
|
|
37
|
+
}
|
|
38
|
+
function addPeerDependencies() {
|
|
39
|
+
return (tree) => {
|
|
40
|
+
const pkgPath = '/package.json';
|
|
41
|
+
const buffer = tree.read(pkgPath);
|
|
42
|
+
if (!buffer) {
|
|
43
|
+
throw new schematics_1.SchematicsException('Could not find package.json');
|
|
44
|
+
}
|
|
45
|
+
const pkg = JSON.parse(buffer.toString('utf-8'));
|
|
46
|
+
if (!pkg.dependencies) {
|
|
47
|
+
pkg.dependencies = {};
|
|
48
|
+
}
|
|
49
|
+
if (!pkg.devDependencies) {
|
|
50
|
+
pkg.devDependencies = {};
|
|
51
|
+
}
|
|
52
|
+
for (const [name, version] of Object.entries(RUNTIME_DEPS)) {
|
|
53
|
+
if (!pkg.dependencies[name] && !pkg.devDependencies[name]) {
|
|
54
|
+
pkg.dependencies[name] = version;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
for (const [name, version] of Object.entries(DEV_DEPS)) {
|
|
58
|
+
if (!pkg.dependencies[name] && !pkg.devDependencies[name]) {
|
|
59
|
+
pkg.devDependencies[name] = version;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
tree.overwrite(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
63
|
+
return tree;
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function createPostcssConfig() {
|
|
67
|
+
return (tree) => {
|
|
68
|
+
const path = '/.postcssrc.json';
|
|
69
|
+
if (tree.exists(path)) {
|
|
70
|
+
return tree;
|
|
71
|
+
}
|
|
72
|
+
tree.create(path, JSON.stringify({ plugins: { '@tailwindcss/postcss': {} } }, null, 2) + '\n');
|
|
73
|
+
return tree;
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function createTailwindWrapper() {
|
|
77
|
+
return (tree) => {
|
|
78
|
+
const path = '/src/tailwind.css';
|
|
79
|
+
if (tree.exists(path)) {
|
|
80
|
+
return tree;
|
|
81
|
+
}
|
|
82
|
+
tree.create(path, [
|
|
83
|
+
'@import "tailwindcss";',
|
|
84
|
+
'@import "@unopsitg/ux/tailwind";',
|
|
85
|
+
'',
|
|
86
|
+
].join('\n'));
|
|
87
|
+
return tree;
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function patchAngularJson(options) {
|
|
91
|
+
return (tree) => {
|
|
92
|
+
const angularJsonPath = '/angular.json';
|
|
93
|
+
const buffer = tree.read(angularJsonPath);
|
|
94
|
+
if (!buffer) {
|
|
95
|
+
throw new schematics_1.SchematicsException('Could not find angular.json');
|
|
96
|
+
}
|
|
97
|
+
const workspace = JSON.parse(buffer.toString('utf-8'));
|
|
98
|
+
const projectName = options.project || workspace.defaultProject || Object.keys(workspace.projects)[0];
|
|
99
|
+
const project = workspace.projects[projectName];
|
|
100
|
+
if (!project) {
|
|
101
|
+
throw new schematics_1.SchematicsException(`Project "${projectName}" not found in angular.json`);
|
|
102
|
+
}
|
|
103
|
+
const buildTarget = project.architect?.build || project.targets?.build;
|
|
104
|
+
if (!buildTarget) {
|
|
105
|
+
throw new schematics_1.SchematicsException(`No build target found for project "${projectName}"`);
|
|
106
|
+
}
|
|
107
|
+
const buildOptions = buildTarget.options || (buildTarget.options = {});
|
|
108
|
+
// Patch styles
|
|
109
|
+
const styles = buildOptions.styles || [];
|
|
110
|
+
const desiredStyles = STYLES_ENTRIES.filter((s) => !styles.includes(s));
|
|
111
|
+
if (desiredStyles.length > 0) {
|
|
112
|
+
const srcStylesIdx = styles.indexOf('src/styles.scss');
|
|
113
|
+
if (srcStylesIdx >= 0) {
|
|
114
|
+
// Insert library styles before src/styles.scss
|
|
115
|
+
const before = desiredStyles.filter((s) => s !== 'src/styles.scss');
|
|
116
|
+
styles.splice(srcStylesIdx, 0, ...before);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
styles.push(...desiredStyles);
|
|
120
|
+
}
|
|
121
|
+
buildOptions.styles = styles;
|
|
122
|
+
}
|
|
123
|
+
// Patch assets
|
|
124
|
+
const assets = buildOptions.assets || [];
|
|
125
|
+
const hasOppAsset = assets.some((a) => typeof a === 'object' && a.input === ASSETS_ENTRY.input);
|
|
126
|
+
if (!hasOppAsset) {
|
|
127
|
+
assets.push(ASSETS_ENTRY);
|
|
128
|
+
buildOptions.assets = assets;
|
|
129
|
+
}
|
|
130
|
+
tree.overwrite(angularJsonPath, JSON.stringify(workspace, null, 2) + '\n');
|
|
131
|
+
return tree;
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function patchAppConfig(options) {
|
|
135
|
+
return (tree) => {
|
|
136
|
+
const configPath = '/src/app/app.config.ts';
|
|
137
|
+
if (!tree.exists(configPath)) {
|
|
138
|
+
// Try alternate path
|
|
139
|
+
const altPath = '/src/app.config.ts';
|
|
140
|
+
if (!tree.exists(altPath)) {
|
|
141
|
+
return tree;
|
|
142
|
+
}
|
|
143
|
+
return patchConfigFile(tree, altPath, options);
|
|
144
|
+
}
|
|
145
|
+
return patchConfigFile(tree, configPath, options);
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function patchConfigFile(tree, path, options) {
|
|
149
|
+
const content = tree.read(path).toString('utf-8');
|
|
150
|
+
if (content.includes('@unopsitg/ux')) {
|
|
151
|
+
return tree;
|
|
152
|
+
}
|
|
153
|
+
const darkMode = options.darkMode !== false;
|
|
154
|
+
const importBlock = [
|
|
155
|
+
"import { providePrimeNG } from 'primeng/config';",
|
|
156
|
+
"import { BrandSoft, TOPBAR_PROFILE_MENU_CONFIG, LayoutService } from '@unopsitg/ux';",
|
|
157
|
+
].join('\n');
|
|
158
|
+
const providerBlock = ` providePrimeNG({ theme: { preset: BrandSoft, options: { darkModeSelector: '.app-dark' } } }),`;
|
|
159
|
+
// Insert imports at the top (after existing imports)
|
|
160
|
+
const lastImportIdx = content.lastIndexOf('\nimport ');
|
|
161
|
+
let result;
|
|
162
|
+
if (lastImportIdx >= 0) {
|
|
163
|
+
const endOfImportLine = content.indexOf('\n', lastImportIdx + 1);
|
|
164
|
+
result =
|
|
165
|
+
content.slice(0, endOfImportLine + 1) +
|
|
166
|
+
importBlock +
|
|
167
|
+
'\n' +
|
|
168
|
+
content.slice(endOfImportLine + 1);
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
result = importBlock + '\n\n' + content;
|
|
172
|
+
}
|
|
173
|
+
// Insert provider into providers array
|
|
174
|
+
const providersMatch = result.match(/providers\s*:\s*\[/);
|
|
175
|
+
if (providersMatch && providersMatch.index != null) {
|
|
176
|
+
const insertPos = providersMatch.index + providersMatch[0].length;
|
|
177
|
+
result =
|
|
178
|
+
result.slice(0, insertPos) + '\n' + providerBlock + '\n' + result.slice(insertPos);
|
|
179
|
+
}
|
|
180
|
+
tree.overwrite(path, result);
|
|
181
|
+
return tree;
|
|
182
|
+
}
|
|
183
|
+
function createCursorRule() {
|
|
184
|
+
return (tree) => {
|
|
185
|
+
const rulePath = '/.cursor/rules/unopsitg-ux.mdc';
|
|
186
|
+
if (tree.exists(rulePath)) {
|
|
187
|
+
return tree;
|
|
188
|
+
}
|
|
189
|
+
const content = `---
|
|
190
|
+
description: Integration rules for @unopsitg/ux library
|
|
191
|
+
globs: ["**/*.{ts,html,scss,css,json}"]
|
|
192
|
+
alwaysApply: false
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
# @unopsitg/ux Integration
|
|
196
|
+
|
|
197
|
+
This project uses the @unopsitg/ux Angular library for its layout shell, brand theme, and shared components.
|
|
198
|
+
|
|
199
|
+
## Critical Integration Invariants
|
|
200
|
+
|
|
201
|
+
1. **PostCSS config must be JSON format** — use \`.postcssrc.json\`, never \`.mjs\`. Angular 21 esbuild silently ignores \`.mjs\` configs.
|
|
202
|
+
2. **\`assets/tailwind.css\` is a Tailwind v4 source file** (not pre-compiled CSS). It contains \`@plugin\`, \`@source\`, \`@theme\`, and \`@utility\` directives that must be processed by \`@tailwindcss/postcss\`. Never add it directly to \`angular.json\` styles — it will be inlined as raw text with no utility generation.
|
|
203
|
+
3. **Use \`src/tailwind.css\`** as the entry point. It imports \`tailwindcss\` and \`@unopsitg/ux/tailwind\` via the package exports. This file IS processed by PostCSS because it lives in \`src/\`.
|
|
204
|
+
4. **Never put \`@source\` directives in \`.scss\` files** — Sass copies them as inert text.
|
|
205
|
+
5. **Shell utilities** (\`.hidden\`, \`.animate-scalein\`, \`.animate-fadeout\`) are shipped as real CSS in the library. Do not redefine them.
|
|
206
|
+
|
|
207
|
+
## Correct src/tailwind.css
|
|
208
|
+
|
|
209
|
+
\`\`\`css
|
|
210
|
+
@import "tailwindcss";
|
|
211
|
+
@import "@unopsitg/ux/tailwind";
|
|
212
|
+
\`\`\`
|
|
213
|
+
|
|
214
|
+
## Available Injection Tokens
|
|
215
|
+
|
|
216
|
+
| Token | Purpose | Shape |
|
|
217
|
+
|-------|---------|-------|
|
|
218
|
+
| \`MENU_MODEL\` | Sidebar menu tree | \`MenuItem[]\` |
|
|
219
|
+
| \`SIDEBAR_LOGO\` | Expanded/compact logo URLs | \`{ expanded, compact, alt }\` |
|
|
220
|
+
| \`TOPBAR_MOBILE_LOGO\` | Mobile header logos | \`{ light, dark }\` |
|
|
221
|
+
| \`TOPBAR_PROFILE_MENU_CONFIG\` | Profile dropdown items | \`{ items: { id, label, icon, command?, separator? }[] }\` |
|
|
222
|
+
|
|
223
|
+
## Theme
|
|
224
|
+
|
|
225
|
+
- \`LayoutService.layoutConfig()\` holds the active theme state.
|
|
226
|
+
- \`LayoutService.toggleDarkMode()\` synchronizes \`.app-dark\` on \`<html>\`.
|
|
227
|
+
- Use an \`APP_INITIALIZER\` to call \`toggleDarkMode()\` at startup to avoid a flash of wrong theme.
|
|
228
|
+
|
|
229
|
+
## Full Documentation
|
|
230
|
+
|
|
231
|
+
See \`node_modules/@unopsitg/ux/README.md\` for complete setup and configuration.
|
|
232
|
+
`;
|
|
233
|
+
tree.create(rulePath, content);
|
|
234
|
+
return tree;
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
function installDependencies() {
|
|
238
|
+
return (_tree, context) => {
|
|
239
|
+
context.addTask(new tasks_1.NodePackageInstallTask());
|
|
240
|
+
};
|
|
241
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema",
|
|
3
|
+
"$id": "SchematicsUnopsUxNgAdd",
|
|
4
|
+
"title": "UNOPS UX ng-add schematic",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"project": {
|
|
8
|
+
"type": "string",
|
|
9
|
+
"description": "The project to add @unopsitg/ux to.",
|
|
10
|
+
"$default": {
|
|
11
|
+
"$source": "projectName"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"darkMode": {
|
|
15
|
+
"type": "boolean",
|
|
16
|
+
"default": true,
|
|
17
|
+
"description": "Whether the app starts in dark mode."
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"required": []
|
|
21
|
+
}
|