@ng-zen/cli 22.7.0 β†’ 22.8.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/README.md CHANGED
@@ -60,7 +60,13 @@ ng add @ng-zen/cli
60
60
  ng generate @ng-zen/cli:ui
61
61
  ```
62
62
 
63
- **3. Use the generated standalone component:**
63
+ **3. Generate layouts (interactive mode):**
64
+
65
+ ```bash
66
+ ng generate @ng-zen/cli:layouts
67
+ ```
68
+
69
+ **4. Use the generated standalone component:**
64
70
 
65
71
  ```typescript
66
72
  import { ZenButton } from './ui/button';
@@ -80,11 +86,12 @@ export class MyComponent {}
80
86
  2. [Features](#-features)
81
87
  3. [CLI Usage & Options](#-cli-usage--options)
82
88
  4. [Available UIs](#-available-uis)
83
- 5. [Customization](#-customization)
84
- 6. [ESLint Configuration](#-eslint-configuration)
85
- 7. [Philosophy & Architecture](#-philosophy--architecture)
86
- 8. [Community & Contributions](#-community--contributions)
87
- 9. [Documentation & Links](#-documentation--links)
89
+ 5. [Available Layouts](#-available-layouts)
90
+ 6. [Customization](#-customization)
91
+ 7. [ESLint Configuration](#-eslint-configuration)
92
+ 8. [Philosophy & Architecture](#-philosophy--architecture)
93
+ 9. [Community & Contributions](#-community--contributions)
94
+ 10. [Documentation & Links](#-documentation--links)
88
95
 
89
96
  ## ✨ Features
90
97
 
@@ -131,6 +138,29 @@ ng generate @ng-zen/cli:ui ./src/app/shared/components --ui avatar badge card
131
138
  ng generate @ng-zen/cli:ui --ui button input --stories
132
139
  ```
133
140
 
141
+ ### Layouts
142
+
143
+ Generate app-shell layouts (page-level components composed with UI components) into `src/app/layouts` by default.
144
+
145
+ ```bash
146
+ ng generate @ng-zen/cli:layouts [path] --layouts <elements...> --stories
147
+ ```
148
+
149
+ | Property | Status | Default | Description |
150
+ | :-------------- | :--------- | :---------------- | :-------------------------------------------------------------------------------------------------------- |
151
+ | **`[path]`** | _Optional_ | `src/app/layouts` | The directory where layouts will be generated (e.g., `./src/app/shared/layouts`). |
152
+ | **`--layouts`** | _Required_ | - | A space-separated list of layouts to generate (e.g., `dashboard`). Interactive prompt appears if omitted. |
153
+ | **`--stories`** | _Optional_ | `false` | Generates `.stories.ts` files alongside the layout, instantly ready for your Storybook integration. |
154
+ | **`--project`** | _Optional_ | auto-detected | Specify the target project name in a multi-project workspace. |
155
+
156
+ **Generate the dashboard layout with Storybook files:**
157
+
158
+ ```bash
159
+ ng generate @ng-zen/cli:layouts --layouts dashboard --stories
160
+ ```
161
+
162
+ > Layout stories compose generated UI components (dashboard stories use `sidenav`, `button` and `icon`). Generate those UI components at their default paths first so the story imports resolve.
163
+
134
164
  ## 🧩 Available UIs
135
165
 
136
166
  ### Forms & Inputs
@@ -177,6 +207,12 @@ ng generate @ng-zen/cli:ui --ui button input --stories
177
207
  | ----------- | --------------------------- | ---------------------------------------------------------------- |
178
208
  | **Sidenav** | Responsive navigation shell | Collapsible rail, mobile bottom bar, header/content/footer slots |
179
209
 
210
+ ## 🧭 Available Layouts
211
+
212
+ | Layout | Description | Features |
213
+ | ------------- | ----------------------------------- | --------------------------------------------------------------------------- |
214
+ | **Dashboard** | Responsive app shell: nav + content | Nav slot + scrollable main, in-flow mobile bottom bar, pairs with `sidenav` |
215
+
180
216
  ## 🎨 Customization
181
217
 
182
218
  Every generated component utilizes CSS variables for immediate theming without touching the core logic. Overwrite them globally or locally:
@@ -208,7 +244,7 @@ ng add @ng-zen/cli
208
244
  # β†’ Prompt: "ESLint config detected. Update it for ng-zen UI components?"
209
245
  ```
210
246
 
211
- It adds `@ng-zen/cli/eslint-config` to your `eslint.config.*` and spreads it into the `defineConfig` array. This enforces the `zen` prefix on component selectors and relaxes rules for `.stories.ts` / `.spec.ts` files generated inside `ui/` directories.
247
+ It adds `@ng-zen/cli/eslint-config` to your `eslint.config.*` and spreads it into the `defineConfig` array. This enforces the `zen` prefix on component selectors and relaxes rules for `.stories.ts` / `.spec.ts` files generated inside `ui/` and `layouts/` directories.
212
248
 
213
249
  **Manual setup** (if you declined the prompt):
214
250
 
@@ -218,7 +254,7 @@ import ngZen from '@ng-zen/cli/eslint-config';
218
254
  export default defineConfig([...otherConfigs, ...ngZen]);
219
255
  ```
220
256
 
221
- **Custom path or rules** β€” use the named exports for fine-grained control:
257
+ **Custom path or rules** β€” use the named exports for fine-grained control (use `layoutsFiles` the same way for generated layouts):
222
258
 
223
259
  ```ts
224
260
  import ngZen from '@ng-zen/cli/eslint-config';
@@ -2,7 +2,7 @@
2
2
  const { defineConfig } = require('eslint/config');
3
3
 
4
4
  /**
5
- * Config block for UI components inside a `ui/` directory.
5
+ * Config block for UI components and layouts inside `ui/` / `layouts/` directories.
6
6
  *
7
7
  * Enforces:
8
8
  * - `zen` prefix for component selectors (`element`, `kebab-case`)
@@ -12,7 +12,7 @@ const { defineConfig } = require('eslint/config');
12
12
  * @type {import('eslint').Linter.Config}
13
13
  */
14
14
  const uiComponent = {
15
- files: ['**/ui/**/*.ts'],
15
+ files: ['**/ui/**/*.ts', '**/layouts/**/*.ts'],
16
16
  rules: {
17
17
  '@angular-eslint/directive-selector': ['error', { type: 'attribute', prefix: 'zen', style: 'camelCase' }],
18
18
  '@angular-eslint/component-selector': ['error', { type: 'element', prefix: 'zen', style: 'kebab-case' }],
@@ -30,7 +30,14 @@ const uiComponent = {
30
30
  * @type {import('eslint').Linter.Config}
31
31
  */
32
32
  const generatedFiles = {
33
- files: ['**/ui/**/*.spec.ts', '**/ui/**/*.test.ts', '**/ui/**/*.stories.ts'],
33
+ files: [
34
+ '**/ui/**/*.spec.ts',
35
+ '**/ui/**/*.test.ts',
36
+ '**/ui/**/*.stories.ts',
37
+ '**/layouts/**/*.spec.ts',
38
+ '**/layouts/**/*.test.ts',
39
+ '**/layouts/**/*.stories.ts',
40
+ ],
34
41
  rules: {
35
42
  '@typescript-eslint/unbound-method': 'off',
36
43
  '@typescript-eslint/no-unsafe-return': 'off',
@@ -46,13 +53,15 @@ const generatedFiles = {
46
53
  };
47
54
 
48
55
  /**
49
- * Creates a custom UI component config block with a custom file glob.
56
+ * Creates a custom ng-zen config block with a custom file glob.
50
57
  *
51
58
  * @param {Object} [options]
52
59
  * @param {string | string[]} [options.uiFiles] - Glob pattern(s) for UI component files.
53
60
  * Uses the same format as the `files` property in flat config.
61
+ * @param {string | string[]} [options.layoutsFiles] - Glob pattern(s) for layout files.
62
+ * Uses the same format as the `files` property in flat config.
54
63
  * @returns {import('eslint').Linter.Config} A config block with the same rules as
55
- * uiComponent but scoped to the provided file pattern.
64
+ * uiComponent but scoped to the provided file patterns.
56
65
  *
57
66
  * @example
58
67
  * ```ts
@@ -60,8 +69,8 @@ const generatedFiles = {
60
69
  * ```
61
70
  */
62
71
  function createNgZenConfig(options = {}) {
63
- const { uiFiles = ['**/ui/**/*.ts'] } = options;
64
- return { ...uiComponent, files: [uiFiles].flat() };
72
+ const { uiFiles = ['**/ui/**/*.ts'], layoutsFiles = ['**/layouts/**/*.ts'] } = options;
73
+ return { ...uiComponent, files: [uiFiles, layoutsFiles].flat() };
65
74
  }
66
75
 
67
76
  /**
@@ -81,19 +90,20 @@ function createNgZenConfig(options = {}) {
81
90
  * |---|---|---|
82
91
  * | `.uiComponent` | `Config` | Rules for UI component files. Override `files` / `rules` via spread. |
83
92
  * | `.generatedFiles` | `Config` | Rules for generated files (stories, spec, test). |
84
- * | `.createNgZenConfig(opts)` | `Config` | Helper β€” same rules as uiComponent with a custom file glob. |
93
+ * | `.createNgZenConfig(opts)` | `Config` | Helper β€” same rules as uiComponent with custom file globs (`uiFiles`, `layoutsFiles`). |
85
94
  *
86
95
  * ## Custom path
87
96
  *
88
- * By default, UI component files are detected via a glob pattern targeting any `ui` directory
89
- * (glob: `**` + `/ui/` + `**` + `/*`). If your components live in a different location, choose one of the
97
+ * By default, UI component and layout files are detected via glob patterns targeting any `ui`
98
+ * or `layouts` directory
99
+ * (globs: `**` + `/ui/` + `**` + `/*` and `**` + `/layouts/` + `**` + `/*`). If your files live in different locations, choose one of the
90
100
  * approaches below instead of the default `...ngZen` spread.
91
101
  *
92
102
  * @example <caption>Custom UI path</caption>
93
103
  * ```ts
94
104
  * import ngZen from '@ng-zen/cli/eslint-config';
95
105
  * export default defineConfig([
96
- * ngZen.createNgZenConfig({ uiFiles: 'src/app/shared/ui' }),
106
+ * ngZen.createNgZenConfig({ uiFiles: 'src/app/shared/ui', layoutsFiles: 'src/app/shared/layouts' }),
97
107
  * ...ngZen.generatedFiles,
98
108
  * ]);
99
109
  * ```
@@ -117,7 +127,7 @@ function createNgZenConfig(options = {}) {
117
127
  * {
118
128
  * uiComponent: import('eslint').Linter.Config,
119
129
  * generatedFiles: import('eslint').Linter.Config,
120
- * createNgZenConfig: (options?: { uiFiles?: string | string[] }) => import('eslint').Linter.Config
130
+ * createNgZenConfig: (options?: { uiFiles?: string | string[]; layoutsFiles?: string | string[] }) => import('eslint').Linter.Config
121
131
  * }
122
132
  * }
123
133
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ng-zen/cli",
3
- "version": "22.7.0",
3
+ "version": "22.8.0",
4
4
  "description": "Angular CLI generator for creating fully customizable UI components.",
5
5
  "license": "BSD-2-Clause",
6
6
  "private": false,
@@ -48,6 +48,8 @@
48
48
  "theming",
49
49
  "angular schematics generator",
50
50
  "ui components cli",
51
+ "layouts",
52
+ "dashboard",
51
53
  "alert",
52
54
  "avatar",
53
55
  "badge",
@@ -11,6 +11,12 @@
11
11
  "factory": "./ui/index#uiGenerator",
12
12
  "schema": "./ui/schema.json",
13
13
  "aliases": ["c", "component", "components"]
14
+ },
15
+ "layouts": {
16
+ "description": "This schematic generates layouts",
17
+ "factory": "./layouts/index#layoutsGenerator",
18
+ "schema": "./layouts/schema.json",
19
+ "aliases": ["layout", "l"]
14
20
  }
15
21
  }
16
22
  }
@@ -0,0 +1,7 @@
1
+ <div class="dashboard-nav">
2
+ <ng-content select="[zenDashboardNav]" />
3
+ </div>
4
+
5
+ <main class="dashboard-main">
6
+ <ng-content />
7
+ </main>
@@ -0,0 +1,34 @@
1
+ :host {
2
+ --zen-dashboard-content-padding: 1.5rem;
3
+ }
4
+
5
+ /* stylelint-disable-next-line no-duplicate-selectors -- separate variables and styles */
6
+ :host {
7
+ display: flex;
8
+ height: 100dvh;
9
+ }
10
+
11
+ .dashboard-nav {
12
+ display: block;
13
+ flex: 0 0 auto;
14
+ min-inline-size: 0;
15
+ }
16
+
17
+ .dashboard-main {
18
+ flex: 1;
19
+ min-inline-size: 0;
20
+ overflow-y: auto;
21
+ padding: var(--zen-dashboard-content-padding);
22
+ }
23
+
24
+ /* Mobile: content scrolls above an in-flow bottom nav (no overlay) */
25
+ @media (width <= 48rem) {
26
+ :host {
27
+ flex-direction: column;
28
+ }
29
+
30
+ .dashboard-main {
31
+ min-block-size: 0;
32
+ order: -1;
33
+ }
34
+ }
@@ -0,0 +1,39 @@
1
+ import { Component, provideZonelessChangeDetection } from '@angular/core';
2
+ import { TestBed } from '@angular/core/testing';
3
+ import { beforeEach, describe, expect, it } from 'vitest';
4
+
5
+ import { ZenDashboard } from './dashboard';
6
+
7
+ @Component({
8
+ template: `
9
+ <zen-dashboard>
10
+ <nav zenDashboardNav>Nav</nav>
11
+ <p>Content</p>
12
+ </zen-dashboard>
13
+ `,
14
+ imports: [ZenDashboard],
15
+ })
16
+ class DashboardTestComponent {}
17
+
18
+ describe('ZenDashboard', () => {
19
+ beforeEach(async () => {
20
+ await TestBed.configureTestingModule({
21
+ imports: [DashboardTestComponent],
22
+ providers: [provideZonelessChangeDetection()],
23
+ }).compileComponents();
24
+ });
25
+
26
+ it('should create', () => {
27
+ const fixture = TestBed.createComponent(DashboardTestComponent);
28
+ fixture.detectChanges();
29
+ expect(fixture.nativeElement.querySelector('zen-dashboard')).toBeTruthy();
30
+ });
31
+
32
+ it('should project nav and content slots', () => {
33
+ const fixture = TestBed.createComponent(DashboardTestComponent);
34
+ fixture.detectChanges();
35
+
36
+ expect(fixture.nativeElement.querySelector('.dashboard-nav [zenDashboardNav]')?.textContent).toBe('Nav');
37
+ expect(fixture.nativeElement.querySelector('.dashboard-main p')?.textContent).toBe('Content');
38
+ });
39
+ });
@@ -0,0 +1,50 @@
1
+ import { applicationConfig, Meta, moduleMetadata, StoryObj } from '@storybook/angular';
2
+
3
+ import { ZenDashboard } from './dashboard';
4
+
5
+ type Story = StoryObj<ZenDashboard>;
6
+
7
+ const meta = {
8
+ title: 'Layouts/Dashboard',
9
+ component: ZenDashboard,
10
+ tags: ['autodocs'],
11
+ parameters: {
12
+ layout: 'fullscreen',
13
+ },
14
+ decorators: [applicationConfig({ providers: [] }), moduleMetadata({ imports: [ZenDashboard] })],
15
+ } satisfies Meta<ZenDashboard>;
16
+
17
+ export default meta;
18
+
19
+ const dashboardTemplate = `
20
+ <zen-dashboard>
21
+ <nav zenDashboardNav aria-label="Main navigation" style="display: flex; flex-direction: column; gap: 0.5rem; width: 14rem; height: 100%; box-sizing: border-box; padding: 1rem; background-color: #f5f5f5;">
22
+ <span style="font-weight: 700;">Zen</span>
23
+ <span>Home</span>
24
+ <span>Settings</span>
25
+ </nav>
26
+ <h1 style="font-size: 1.5rem; margin: 0 0 1rem;">Dashboard</h1>
27
+ <p style="margin: 0 0 1rem;">Nav slot hosts the navigation; this content scrolls internally while the nav stays visible.</p>
28
+ <div style="display: grid; gap: 1rem; grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));">
29
+ <div style="border: 1px solid #e0e0e0; border-radius: 0.75rem; padding: 1rem;">Card one</div>
30
+ <div style="border: 1px solid #e0e0e0; border-radius: 0.75rem; padding: 1rem;">Card two</div>
31
+ <div style="border: 1px solid #e0e0e0; border-radius: 0.75rem; padding: 1rem;">Card three</div>
32
+ </div>
33
+ </zen-dashboard>
34
+ `;
35
+
36
+ export const Default: Story = {
37
+ render: () => ({
38
+ template: dashboardTemplate,
39
+ }),
40
+ };
41
+
42
+ export const Mobile: Story = {
43
+ tags: ['!autodocs'],
44
+ globals: {
45
+ viewport: { value: 'mobile1', isRotated: false },
46
+ },
47
+ render: () => ({
48
+ template: dashboardTemplate,
49
+ }),
50
+ };
@@ -0,0 +1,65 @@
1
+ import { ChangeDetectionStrategy, Component } from '@angular/core';
2
+
3
+ /**
4
+ * ZenDashboard is a presentational app shell: navigation + content. No business logic β€”
5
+ * nav items, auth and i18n live in the consumer (e.g. `zen-sidenav`).
6
+ *
7
+ * Slots:
8
+ * - `[zenDashboardNav]` β€” navigation (e.g. `<zen-sidenav zenDashboardNav />`)
9
+ * - default β€” main content (e.g. `<router-outlet />`, rendered in place)
10
+ *
11
+ * Layout:
12
+ * ```
13
+ * Desktop (row):
14
+ * β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
15
+ * β”‚ .dashboard- β”‚ <main> (flex: 1, β”‚
16
+ * β”‚ nav β”‚ internal scroll) β”‚
17
+ * β”‚ (sized by β”‚ <router-outlet> β”‚
18
+ * β”‚ nav itself)β”‚ β”‚
19
+ * β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
20
+ * Mobile (<= 48rem, column, in-flow β€” never fixed):
21
+ * β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
22
+ * β”‚ <main> (order: -1, scrolls internally) β”‚
23
+ * β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
24
+ * β”‚ .dashboard-nav bottom bar β”‚
25
+ * β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
26
+ * ```
27
+ * Full height is self-contained: the host is a `100dvh` flex, `main` takes
28
+ * `flex: 1` with `min-block-size: 0` + `overflow-y: auto`, so only content
29
+ * scrolls and the nav is always visible without overlaying anything.
30
+ * Slots sit inside plain wrapper elements (`.dashboard-nav`, `main`), so
31
+ * `order` and sizing stay local β€” no `display: contents` or cross-component
32
+ * selectors needed.
33
+ *
34
+ * @example
35
+ * ```html
36
+ * <zen-dashboard>
37
+ * <zen-sidenav zenDashboardNav [collapsed]="collapsed()">
38
+ * <div zenSidenavHeader>…</div>
39
+ * <nav zenSidenavContent>…</nav>
40
+ * <div zenSidenavFooter>…</div>
41
+ * </zen-sidenav>
42
+ * <router-outlet />
43
+ * </zen-dashboard>
44
+ * ```
45
+ *
46
+ * ### CSS Custom Properties
47
+ *
48
+ * You can customize the component using CSS custom properties:
49
+ * ```css
50
+ * :root {
51
+ * --zen-dashboard-content-padding: 1.5rem;
52
+ * }
53
+ * ```
54
+ *
55
+ * @author Konrad StΔ™pieΕ„
56
+ * @license {@link https://github.com/kstepien3/ng-zen/blob/master/LICENSE|BSD-2-Clause}
57
+ * @see [GitHub](https://github.com/kstepien3/ng-zen)
58
+ */
59
+ @Component({
60
+ selector: 'zen-dashboard',
61
+ templateUrl: './dashboard.html',
62
+ styleUrl: './dashboard.scss',
63
+ changeDetection: ChangeDetectionStrategy.OnPush,
64
+ })
65
+ export class ZenDashboard {}
@@ -0,0 +1 @@
1
+ export * from './dashboard';
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.layoutsGenerator = layoutsGenerator;
4
+ const core_1 = require("@angular-devkit/core");
5
+ const schematics_1 = require("@angular-devkit/schematics");
6
+ const workspace_1 = require("@schematics/angular/utility/workspace");
7
+ const utils_1 = require("../../utils");
8
+ const DEFAULT_GENERATION_PATH = 'layouts'; // src/app/layouts
9
+ function layoutsGenerator({ layouts: selected, project, ...options }) {
10
+ return async (tree) => {
11
+ const workspace = await (0, workspace_1.getWorkspace)(tree);
12
+ const projectName = project ?? workspace.extensions['defaultProject'];
13
+ const projectObj = workspace.projects.get(projectName);
14
+ if (!projectObj) {
15
+ throw new Error(`Project "${projectName}" not found in workspace.`);
16
+ }
17
+ options.path ??= ((0, workspace_1.buildDefaultPath)(projectObj) + '/' + DEFAULT_GENERATION_PATH);
18
+ const workingDirectory = (0, core_1.normalize)((0, core_1.join)(options.currentDirectory, options.path));
19
+ return (0, schematics_1.chain)([...(0, utils_1.applyFileTemplateUtil)([...selected], { ...options, path: workingDirectory })]);
20
+ };
21
+ }
22
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/schematics/layouts/index.ts"],"names":[],"mappings":";;AASA,4CAgBC;AAzBD,+CAA6D;AAC7D,2DAAyD;AACzD,qEAAuF;AAEvF,uCAAoD;AAGpD,MAAM,uBAAuB,GAAG,SAAS,CAAC,CAAC,kBAAkB;AAE7D,SAAgB,gBAAgB,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,EAAkB;IACzF,OAAO,KAAK,EAAC,IAAI,EAAC,EAAE;QAClB,MAAM,SAAS,GAAG,MAAM,IAAA,wBAAY,EAAC,IAAI,CAAC,CAAC;QAC3C,MAAM,WAAW,GAAG,OAAO,IAAK,SAAS,CAAC,UAAU,CAAC,gBAAgB,CAAY,CAAC;QAClF,MAAM,UAAU,GAAG,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QAEvD,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,MAAM,IAAI,KAAK,CAAC,YAAY,WAAW,2BAA2B,CAAC,CAAC;QACtE,CAAC;QAED,OAAO,CAAC,IAAI,KAAK,CAAC,IAAA,4BAAgB,EAAC,UAAU,CAAC,GAAG,GAAG,GAAG,uBAAuB,CAAS,CAAC;QAExF,MAAM,gBAAgB,GAAG,IAAA,gBAAS,EAAC,IAAA,WAAI,EAAC,OAAO,CAAC,gBAAgB,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QAEjF,OAAO,IAAA,kBAAK,EAAC,CAAC,GAAG,IAAA,6BAAqB,EAAC,CAAC,GAAG,QAAQ,CAAC,EAAE,EAAE,GAAG,OAAO,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC,CAAC,CAAC,CAAC;IAClG,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import { join, normalize, Path } from '@angular-devkit/core';\nimport { chain, Rule } from '@angular-devkit/schematics';\nimport { buildDefaultPath, getWorkspace } from '@schematics/angular/utility/workspace';\n\nimport { applyFileTemplateUtil } from '../../utils';\nimport { Schema as LayoutsOptions } from './schema';\n\nconst DEFAULT_GENERATION_PATH = 'layouts'; // src/app/layouts\n\nexport function layoutsGenerator({ layouts: selected, project, ...options }: LayoutsOptions): Rule {\n return async tree => {\n const workspace = await getWorkspace(tree);\n const projectName = project ?? (workspace.extensions['defaultProject'] as string);\n const projectObj = workspace.projects.get(projectName);\n\n if (!projectObj) {\n throw new Error(`Project \"${projectName}\" not found in workspace.`);\n }\n\n options.path ??= (buildDefaultPath(projectObj) + '/' + DEFAULT_GENERATION_PATH) as Path;\n\n const workingDirectory = normalize(join(options.currentDirectory, options.path));\n\n return chain([...applyFileTemplateUtil([...selected], { ...options, path: workingDirectory })]);\n };\n}\n"]}
@@ -0,0 +1,26 @@
1
+ import { join, normalize, Path } from '@angular-devkit/core';
2
+ import { chain, Rule } from '@angular-devkit/schematics';
3
+ import { buildDefaultPath, getWorkspace } from '@schematics/angular/utility/workspace';
4
+
5
+ import { applyFileTemplateUtil } from '../../utils';
6
+ import { Schema as LayoutsOptions } from './schema';
7
+
8
+ const DEFAULT_GENERATION_PATH = 'layouts'; // src/app/layouts
9
+
10
+ export function layoutsGenerator({ layouts: selected, project, ...options }: LayoutsOptions): Rule {
11
+ return async tree => {
12
+ const workspace = await getWorkspace(tree);
13
+ const projectName = project ?? (workspace.extensions['defaultProject'] as string);
14
+ const projectObj = workspace.projects.get(projectName);
15
+
16
+ if (!projectObj) {
17
+ throw new Error(`Project "${projectName}" not found in workspace.`);
18
+ }
19
+
20
+ options.path ??= (buildDefaultPath(projectObj) + '/' + DEFAULT_GENERATION_PATH) as Path;
21
+
22
+ const workingDirectory = normalize(join(options.currentDirectory, options.path));
23
+
24
+ return chain([...applyFileTemplateUtil([...selected], { ...options, path: workingDirectory })]);
25
+ };
26
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../../../src/schematics/layouts/schema.ts"],"names":[],"mappings":"","sourcesContent":["import { GeneratorSchemaBase } from '../ui/schema';\n\nexport type LayoutType = 'dashboard';\n\nexport interface Schema extends GeneratorSchemaBase {\n layouts: LayoutType[];\n}\n"]}
@@ -0,0 +1,52 @@
1
+ {
2
+ "$schema": "http://json-schema.org/schema",
3
+ "$id": "NgZenCliLayouts",
4
+ "title": "Schema to create Zen layouts",
5
+ "type": "object",
6
+ "description": "Adds Zen layout πŸ“",
7
+ "properties": {
8
+ "currentDirectory": {
9
+ "description": "Base path from working directory",
10
+ "type": "string",
11
+ "format": "path",
12
+ "hidden": true,
13
+ "$default": {
14
+ "$source": "workingDirectory"
15
+ },
16
+ "readOnly": true
17
+ },
18
+ "path": {
19
+ "description": "The path at which to create the layout file, relative to the current workspace. If not provided, layouts will be generated in the 'layouts' folder within the project's source directory (e.g., src/app/layouts).",
20
+ "type": "string",
21
+ "format": "path",
22
+ "$default": {
23
+ "$source": "argv",
24
+ "index": 0
25
+ }
26
+ },
27
+ "project": {
28
+ "description": "The name of the project",
29
+ "type": "string",
30
+ "$default": {
31
+ "$source": "projectName"
32
+ }
33
+ },
34
+ "layouts": {
35
+ "description": "Select elements to generate",
36
+ "type": "array",
37
+ "items": {
38
+ "type": "string",
39
+ "enum": ["dashboard"]
40
+ },
41
+ "multiselect": true,
42
+ "minItems": 1,
43
+ "x-prompt": "Which layouts should be generated?"
44
+ },
45
+ "stories": {
46
+ "description": "Include stories for better documentation and testing",
47
+ "type": "boolean",
48
+ "default": false
49
+ }
50
+ },
51
+ "required": ["layouts"]
52
+ }
@@ -0,0 +1,7 @@
1
+ import { GeneratorSchemaBase } from '../ui/schema';
2
+
3
+ export type LayoutType = 'dashboard';
4
+
5
+ export interface Schema extends GeneratorSchemaBase {
6
+ layouts: LayoutType[];
7
+ }
@@ -0,0 +1,40 @@
1
+ # <%= classify(name) %> layout
2
+
3
+ ![GitHub License](https://img.shields.io/github/license/kstepien3/ng-zen)
4
+
5
+ Generated by `@ng-zen/cli` on <%= localeDate %>
6
+
7
+ ## Overview
8
+
9
+ <%= classify(name) %> layout was generated by
10
+ ```bash
11
+ ng generate @ng-zen/cli:layouts --layouts <%= name %>
12
+ ```
13
+
14
+ This layout is part of a larger set of layouts aimed at enhancing and streamlining the development process in Angular applications.
15
+
16
+ More information you can find here
17
+ ```bash
18
+ ng generate @ng-zen/cli:layouts --help
19
+ ```
20
+
21
+ ## Installation
22
+
23
+ To generate other layouts, ensure that you have the `@ng-zen/cli` library installed. You can add it to your project with the following command:
24
+ ```bash
25
+ ng add @ng-zen/cli
26
+ ```
27
+
28
+ then you can generate layouts via
29
+ ```bash
30
+ ng generate @ng-zen/cli:layouts
31
+ ```
32
+
33
+
34
+ ## Author
35
+
36
+ This layout was generated by the `@ng-zen/cli` tool developed by Konrad StΔ™pieΕ„.
37
+
38
+ This code is licensed under the BSD-2-Clause license. See the [LICENSE](https://github.com/kstepien3/ng-zen/blob/master/projects/cli/LICENSE) file for more details.
39
+
40
+ Source code can be found [here](https://github.com/kstepien3/ng-zen).
@@ -1 +1 @@
1
- {"version":3,"file":"selected-elements.js","sourceRoot":"","sources":["../../../../src/services/selected-elements.ts"],"names":[],"mappings":";;;AAEA,qFAAqF;AACxE,QAAA,gBAAgB,GAAa,EAAE,CAAC","sourcesContent":["import { UiType } from '../schematics/ui/schema';\n\n// add selected elements to this array, then values are available in other schematics\nexport const selectedElements: UiType[] = [];\n"]}
1
+ {"version":3,"file":"selected-elements.js","sourceRoot":"","sources":["../../../../src/services/selected-elements.ts"],"names":[],"mappings":";;;AAAA,qFAAqF;AACxE,QAAA,gBAAgB,GAAa,EAAE,CAAC","sourcesContent":["// add selected elements to this array, then values are available in other schematics\nexport const selectedElements: string[] = [];\n"]}
@@ -1,4 +1,2 @@
1
- import { UiType } from '../schematics/ui/schema';
2
-
3
1
  // add selected elements to this array, then values are available in other schematics
4
- export const selectedElements: UiType[] = [];
2
+ export const selectedElements: string[] = [];
@@ -1 +1 @@
1
- {"version":3,"file":"apply-file-template.util.js","sourceRoot":"","sources":["../../../../src/utils/apply-file-template.util.ts"],"names":[],"mappings":";;AAkBA,sDASC;AA3BD,+CAA0D;AAC1D,2DAAsH;AAGtH,qEAAiE;AAEjE,MAAM,mBAAmB,GAAG,CAAC,MAAc,EAAE,IAAY,EAAU,EAAE,CAAC;IACpE,IAAA,2BAAc,EAAC;QACb,IAAI,EAAE,MAAM;QACZ,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,cAAc,EAAE;QACvC,GAAG,cAAO;KACX,CAAC;IACF,IAAA,iBAAI,EAAC,IAAA,gBAAS,EAAC,GAAG,IAAI,IAAI,MAAM,EAAE,CAAC,CAAC;CACrC,CAAC;AAEF,MAAM,YAAY,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,IAAA,kBAAK,EAAC,IAAA,gBAAG,EAAC,aAAa,CAAC,EAAE,KAAK,CAAC,CAAC;AACjF,MAAM,cAAc,GAAG,CAAC,OAAgB,EAAQ,EAAE,CAAC,IAAA,mBAAM,EAAC,QAAQ,CAAC,EAAE,CAAC,OAAO,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC;AAEpH,SAAgB,qBAAqB,CAAC,OAAiB,EAAE,MAA2B;IAClF,oCAAgB,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC;IAClC,OAAO,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE;QAC1B,MAAM,KAAK,GAAG,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,IAAc,CAAC,CAAC;QAEjE,MAAM,YAAY,GAAG,IAAA,kBAAK,EAAC,IAAA,gBAAG,EAAC,WAAW,MAAM,EAAE,CAAC,EAAE,CAAC,cAAc,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC;QAEjG,OAAO,IAAA,kBAAK,EAAC,CAAC,YAAY,EAAE,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,sBAAS,CAAC,CAAC,CAAC;IACnE,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { normalize, strings } from '@angular-devkit/core';\nimport { apply, applyTemplates, chain, filter, mergeWith, move, Rule, Source, url } from '@angular-devkit/schematics';\n\nimport { GeneratorSchemaBase, UiType } from '../schematics/ui/schema';\nimport { selectedElements } from '../services/selected-elements';\n\nconst createTemplateRules = (folder: string, path: string): Rule[] => [\n applyTemplates({\n name: folder,\n localeDate: new Date().toLocaleString(),\n ...strings,\n }),\n move(normalize(`${path}/${folder}`)),\n];\n\nconst getTemplates = (rules: Rule[]): Source => apply(url(`./templates`), rules);\nconst includeStories = (include: boolean): Rule => filter(filePath => include || !filePath.endsWith('.stories.ts'));\n\nexport function applyFileTemplateUtil(folders: UiType[], config: GeneratorSchemaBase): Rule[] {\n selectedElements.push(...folders);\n return folders.map(folder => {\n const RULES = createTemplateRules(folder, config.path as string);\n\n const folderSource = apply(url(`./files/${folder}`), [includeStories(config.stories), ...RULES]);\n\n return chain([folderSource, getTemplates(RULES)].map(mergeWith));\n });\n}\n"]}
1
+ {"version":3,"file":"apply-file-template.util.js","sourceRoot":"","sources":["../../../../src/utils/apply-file-template.util.ts"],"names":[],"mappings":";;AAkBA,sDASC;AA3BD,+CAA0D;AAC1D,2DAAsH;AAGtH,qEAAiE;AAEjE,MAAM,mBAAmB,GAAG,CAAC,MAAc,EAAE,IAAY,EAAU,EAAE,CAAC;IACpE,IAAA,2BAAc,EAAC;QACb,IAAI,EAAE,MAAM;QACZ,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,cAAc,EAAE;QACvC,GAAG,cAAO;KACX,CAAC;IACF,IAAA,iBAAI,EAAC,IAAA,gBAAS,EAAC,GAAG,IAAI,IAAI,MAAM,EAAE,CAAC,CAAC;CACrC,CAAC;AAEF,MAAM,YAAY,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,IAAA,kBAAK,EAAC,IAAA,gBAAG,EAAC,aAAa,CAAC,EAAE,KAAK,CAAC,CAAC;AACjF,MAAM,cAAc,GAAG,CAAC,OAAgB,EAAQ,EAAE,CAAC,IAAA,mBAAM,EAAC,QAAQ,CAAC,EAAE,CAAC,OAAO,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC;AAEpH,SAAgB,qBAAqB,CAAC,OAAiB,EAAE,MAA2B;IAClF,oCAAgB,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC;IAClC,OAAO,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE;QAC1B,MAAM,KAAK,GAAG,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,IAAc,CAAC,CAAC;QAEjE,MAAM,YAAY,GAAG,IAAA,kBAAK,EAAC,IAAA,gBAAG,EAAC,WAAW,MAAM,EAAE,CAAC,EAAE,CAAC,cAAc,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC;QAEjG,OAAO,IAAA,kBAAK,EAAC,CAAC,YAAY,EAAE,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,sBAAS,CAAC,CAAC,CAAC;IACnE,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { normalize, strings } from '@angular-devkit/core';\nimport { apply, applyTemplates, chain, filter, mergeWith, move, Rule, Source, url } from '@angular-devkit/schematics';\n\nimport { GeneratorSchemaBase } from '../schematics/ui/schema';\nimport { selectedElements } from '../services/selected-elements';\n\nconst createTemplateRules = (folder: string, path: string): Rule[] => [\n applyTemplates({\n name: folder,\n localeDate: new Date().toLocaleString(),\n ...strings,\n }),\n move(normalize(`${path}/${folder}`)),\n];\n\nconst getTemplates = (rules: Rule[]): Source => apply(url(`./templates`), rules);\nconst includeStories = (include: boolean): Rule => filter(filePath => include || !filePath.endsWith('.stories.ts'));\n\nexport function applyFileTemplateUtil(folders: string[], config: GeneratorSchemaBase): Rule[] {\n selectedElements.push(...folders);\n return folders.map(folder => {\n const RULES = createTemplateRules(folder, config.path as string);\n\n const folderSource = apply(url(`./files/${folder}`), [includeStories(config.stories), ...RULES]);\n\n return chain([folderSource, getTemplates(RULES)].map(mergeWith));\n });\n}\n"]}
@@ -1,7 +1,7 @@
1
1
  import { normalize, strings } from '@angular-devkit/core';
2
2
  import { apply, applyTemplates, chain, filter, mergeWith, move, Rule, Source, url } from '@angular-devkit/schematics';
3
3
 
4
- import { GeneratorSchemaBase, UiType } from '../schematics/ui/schema';
4
+ import { GeneratorSchemaBase } from '../schematics/ui/schema';
5
5
  import { selectedElements } from '../services/selected-elements';
6
6
 
7
7
  const createTemplateRules = (folder: string, path: string): Rule[] => [
@@ -16,7 +16,7 @@ const createTemplateRules = (folder: string, path: string): Rule[] => [
16
16
  const getTemplates = (rules: Rule[]): Source => apply(url(`./templates`), rules);
17
17
  const includeStories = (include: boolean): Rule => filter(filePath => include || !filePath.endsWith('.stories.ts'));
18
18
 
19
- export function applyFileTemplateUtil(folders: UiType[], config: GeneratorSchemaBase): Rule[] {
19
+ export function applyFileTemplateUtil(folders: string[], config: GeneratorSchemaBase): Rule[] {
20
20
  selectedElements.push(...folders);
21
21
  return folders.map(folder => {
22
22
  const RULES = createTemplateRules(folder, config.path as string);