@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 +44 -8
- package/eslint-config/index.js +22 -12
- package/package.json +3 -1
- package/schematics/collection.json +6 -0
- package/schematics/layouts/files/dashboard/dashboard.html +7 -0
- package/schematics/layouts/files/dashboard/dashboard.scss +34 -0
- package/schematics/layouts/files/dashboard/dashboard.spec.ts +39 -0
- package/schematics/layouts/files/dashboard/dashboard.stories.ts +50 -0
- package/schematics/layouts/files/dashboard/dashboard.ts +65 -0
- package/schematics/layouts/files/dashboard/index.ts +1 -0
- package/schematics/layouts/index.js +22 -0
- package/schematics/layouts/index.js.map +1 -0
- package/schematics/layouts/index.ts +26 -0
- package/schematics/layouts/schema.js +3 -0
- package/schematics/layouts/schema.js.map +1 -0
- package/schematics/layouts/schema.json +52 -0
- package/schematics/layouts/schema.ts +7 -0
- package/schematics/layouts/templates/README.md.template +40 -0
- package/services/selected-elements.js.map +1 -1
- package/services/selected-elements.ts +1 -3
- package/utils/apply-file-template.util.js.map +1 -1
- package/utils/apply-file-template.util.ts +2 -2
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.
|
|
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. [
|
|
84
|
-
6. [
|
|
85
|
-
7. [
|
|
86
|
-
8. [
|
|
87
|
-
9. [
|
|
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';
|
package/eslint-config/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
const { defineConfig } = require('eslint/config');
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Config block for UI components inside
|
|
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: [
|
|
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
|
|
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
|
|
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
|
|
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
|
|
89
|
-
*
|
|
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.
|
|
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,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 @@
|
|
|
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,40 @@
|
|
|
1
|
+
# <%= classify(name) %> layout
|
|
2
|
+
|
|
3
|
+

|
|
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":";;;
|
|
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 +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
|
|
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
|
|
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:
|
|
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);
|