Skip to content

Commit

Permalink
feat(directives): add generator for directives (#65)
Browse files Browse the repository at this point in the history
additionally add disabled host directive
  • Loading branch information
Kordrad authored Jul 10, 2024
1 parent b6b4678 commit eada555
Show file tree
Hide file tree
Showing 33 changed files with 822 additions and 317 deletions.
3 changes: 3 additions & 0 deletions angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
"style": "scss",
"standalone": true,
"changeDetection": "OnPush"
},
"@schematics/angular:directive": {
"standalone": true
}
},
"projects": {
Expand Down
4 changes: 4 additions & 0 deletions jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ export default {
coverageProvider: 'v8',
moduleNameMapper: {
'^@ng-zen/cli/(.*)': '<rootDir>/projects/cli/src/$1',
'^ng-zen/components/(.*)':
'<rootDir>/projects/cli/schematics/components/files/$1',
'^ng-zen/directives/(.*)':
'<rootDir>/projects/cli/schematics/directives/files/$1',
},
preset: 'jest-preset-angular',
testEnvironment: 'jsdom',
Expand Down
2 changes: 1 addition & 1 deletion projects/cli/.storybook/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
"resolveJsonModule": true
},
"exclude": ["../**/*.spec.ts"],
"include": ["./preview.ts", "../**/*.stories.*", "../**/*.component.ts"],
"include": ["./preview.ts", "../**/*.stories.*", "../**/files/**/*.ts"],
"files": ["./typings.d.ts"]
}
11 changes: 9 additions & 2 deletions projects/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,21 @@ ng add @ng-zen/cli

This command will add the necessary dependencies and configurations to your Angular project.

## Generating Components
## Generating

You can generate components using the ng-zen CLI. Components are generated using the following command format:
You can generate code using the ng-zen CLI.


#### Components
```bash
ng generate @ng-zen/cli:component
```

#### Directives
```bash
ng generate @ng-zen/cli:directive
```

## License

This project is licensed under the BSD 2-Clause License. For more details, refer to the LICENSE file in the repository.
Expand Down
736 changes: 493 additions & 243 deletions projects/cli/documentation.json

Large diffs are not rendered by default.

32 changes: 32 additions & 0 deletions projects/cli/schematics/_utils/apply-file-template.util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { normalize, strings } from '@angular-devkit/core';
import {
apply,
applyTemplates,
chain,
mergeWith,
move,
Rule,
url,
} from '@angular-devkit/schematics';

export function applyFileTemplateUtil(
sources: string[],
path: string,
templatesPath = `./templates`
): Rule[] {
return sources.map(source => {
const rules: Rule[] = [
applyTemplates({
name: source,
localeDate: new Date().toLocaleString(),
...strings,
}),
move(normalize(`${path}/${source}`)),
];

const componentTemplateSource = apply(url(`./files/${source}`), rules);
const genericTemplates = apply(url(templatesPath), rules);

return chain([componentTemplateSource, genericTemplates].map(mergeWith));
});
}
6 changes: 6 additions & 0 deletions projects/cli/schematics/collection.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@
"factory": "./components/index#componentGenerator",
"schema": "./components/schema.json",
"aliases": ["c"]
},
"directive": {
"description": "This schematic generates ui directives",
"factory": "./directives/index#directiveGenerator",
"schema": "./directives/schema.json",
"aliases": ["d"]
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ $transition-duration: 0.4s;
}

/* Disabled state */
:host[disabled] {
:host[aria-disabled='true'] {
cursor: not-allowed;
opacity: 0.6;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { ZenHostDirective } from 'ng-zen/directives/disabled';

/**
* ZenButtonComponent is a reusable button component designed to provide
Expand All @@ -23,5 +24,6 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
template: ` <ng-content /> `,
styleUrl: './button.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
hostDirectives: [ZenHostDirective],
})
export class ZenButtonComponent {}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
#inputElement
[attr.aria-checked]="checked()"
[checked]="checked()"
[attr.aria-disabled]="disabled()"
[disabled]="disabled()"
[attr.aria-disabled]="zenDisabledDirective.disabledBoolean()"
[disabled]="zenDisabledDirective.disabledBoolean()"
(change)="onToggle()"
(keydown.enter)="onToggle()"
type="checkbox"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ input[type='checkbox'] {
}

/* DISABLED STATE */
:host[disabled],
:host:has([disabled]) {
:host[aria-disabled='true'],
:host:has([aria-disabled='true']) {
background-color: $checkbox-disabled-bg-color;
border-color: $checkbox-disabled-bg-color;
opacity: 0.6;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ import {
FormsModule,
NG_VALUE_ACCESSOR,
} from '@angular/forms';
import {
ZenDisabledDirective,
ZenHostDirective,
} from 'ng-zen/directives/disabled';
import { map } from 'rxjs';

type CheckedState = boolean | 'mixed';
Expand Down Expand Up @@ -52,15 +56,16 @@ type OnTouchedFn = () => void;
},
],
imports: [FormsModule],
hostDirectives: [ZenHostDirective],
})
export class ZenCheckboxComponent
implements ControlValueAccessor, AfterViewInit
{
/** Model for the checked state of the checkbox. */
readonly checked = model<CheckedState>(false);

/** Model for the disabled state of the checkbox. */
readonly disabled = model<boolean>(false);
/** @ignore */
readonly zenDisabledDirective = inject(ZenDisabledDirective, { self: true });

/** @ignore */
private readonly checked$ = toObservable(this.checked);
Expand Down Expand Up @@ -112,15 +117,15 @@ export class ZenCheckboxComponent
* Sets the disabled state of the component.
*/
setDisabledState(isDisabled: boolean): void {
this.disabled.set(isDisabled);
this.zenDisabledDirective.disabled.set(isDisabled);
}

/**
* Toggles the checkbox value and notifies the change.
* If the component is disabled, no action is performed.
*/
onToggle(): void {
if (this.disabled()) return;
if (this.zenDisabledDirective.disabledBoolean()) return;

this.checked.update(value => !value);
this.onChange(this.checked());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<button
class="switch"
[attr.aria-checked]="checked()"
[attr.aria-disabled]="disabled()"
[disabled]="disabled()"
[attr.aria-disabled]="zenDisabledDirective.disabledBoolean()"
[disabled]="zenDisabledDirective.disabledBoolean()"
(click)="onToggle()"
(keydown)="onKeyDown($event)"
role="switch"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,27 @@ import {
ChangeDetectionStrategy,
Component,
forwardRef,
inject,
model,
} from '@angular/core';
import {
ControlValueAccessor,
FormsModule,
NG_VALUE_ACCESSOR,
} from '@angular/forms';
import {
ZenDisabledDirective,
ZenHostDirective,
} from 'ng-zen/directives/disabled';

type OnChangeFn = (value: boolean) => void;
type OnTouchedFn = () => void;

/**
* ZenSwitchComponent is a custom switch component that implements ControlValueAccessor to work seamlessly with Angular forms.
*
* @example <zen-switch />
* @example
* <zen-switch />
*
* @export
* @class ZenSwitchComponent
Expand All @@ -40,13 +46,14 @@ type OnTouchedFn = () => void;
},
],
imports: [FormsModule],
hostDirectives: [ZenHostDirective],
})
export class ZenSwitchComponent implements ControlValueAccessor {
/** Model for the checked state of the switch. */
checked = model<boolean>(false);

/** Model for the disabled state of the switch. */
disabled = model<boolean>(false);
/** @ignore */
readonly zenDisabledDirective = inject(ZenDisabledDirective, { self: true });

/** @ignore */
private onChange: OnChangeFn = () => {};
Expand Down Expand Up @@ -82,14 +89,14 @@ export class ZenSwitchComponent implements ControlValueAccessor {
* @ignore
*/
setDisabledState(isDisabled: boolean): void {
this.disabled.set(isDisabled);
this.zenDisabledDirective.disabled.set(isDisabled);
}

/**
* Toggles the switch value and notifies the change.
*/
onToggle(check?: boolean): void {
if (this.disabled()) return;
if (this.zenDisabledDirective.disabledBoolean()) return;

const value = check ?? !this.checked();

Expand Down
30 changes: 3 additions & 27 deletions projects/cli/schematics/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,10 @@
import { normalize, strings } from '@angular-devkit/core';
import {
apply,
applyTemplates,
chain,
mergeWith,
move,
Rule,
url,
} from '@angular-devkit/schematics';
import { chain, Rule } from '@angular-devkit/schematics';

import { applyFileTemplateUtil } from '../_utils/apply-file-template.util';
import { ComponentGeneratorSchema } from './components-generator';

export function componentGenerator(options: ComponentGeneratorSchema): Rule {
return () => {
const componentsSource = options.components.map(component => {
const rules: Rule[] = [
applyTemplates({
name: component,
localeDate: new Date().toLocaleString(),
...strings,
}),
move(normalize(`${options.path}/${component}`)),
];

const componentTemplateSource = apply(url(`./files/${component}`), rules);
const genericTemplates = apply(url(`./templates`), rules);

return chain([componentTemplateSource, genericTemplates].map(mergeWith));
});

return chain(componentsSource);
return chain(applyFileTemplateUtil(options.components, options.path));
};
}
4 changes: 4 additions & 0 deletions projects/cli/schematics/directives/directives-generator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface DirectiveGeneratorSchema {
directives: 'disabled'[];
path: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Component } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { ZenDisabledDirective } from './disabled.directive';
import { ZenHostDirective } from './index';

@Component({
template: ``,
hostDirectives: [ZenHostDirective],
standalone: true,
})
class ZenDisabledComponent {}

describe('ZenDisabledDirective', () => {
let component: ZenDisabledComponent;
let directive: ZenDisabledDirective;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ZenDisabledComponent],
}).compileComponents();

const fixture: ComponentFixture<ZenDisabledComponent> =
TestBed.createComponent(ZenDisabledComponent);
component = fixture.componentInstance;
directive = fixture.debugElement.injector.get(ZenDisabledDirective);
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
expect(directive).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { computed, Directive, HostBinding, model } from '@angular/core';

/**
* @example
* <button zenDisabled> ... </button>
*
* @export
* @class ZenDisabledDirective
*
* @license BSD-2-Clause
* @author Konrad Stępień
* @see {https://github.com/Kordrad/ng-zen GitHub Repository}
* */
@Directive({
selector: '[zenDisabled]',
standalone: true,
})
export class ZenDisabledDirective {
/** Model for the disabled state of the checkbox. */
readonly disabled = model<boolean | 'true' | 'false' | ''>(false, {
alias: 'zenDisabled',
});

/** @ignore */
readonly disabledBoolean = computed(() =>
[true, 'true', ''].includes(this.disabled())
);

@HostBinding('disabled')
get disabledAttr(): string | null | boolean {
return this.disabledBoolean() || null;
}

@HostBinding('attr.aria-disabled')
get ariaDisabledAttr(): boolean {
return this.disabledBoolean();
}

@HostBinding('class.zen-disabled')
get disabledClass(): boolean {
return this.disabledBoolean();
}
}
Loading

0 comments on commit eada555

Please sign in to comment.