@ng-zen/cli 20.2.0 → 20.2.1

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.
@@ -2,19 +2,46 @@ import type { Meta, StoryObj } from '@storybook/angular';
2
2
 
3
3
  import { ZenButton } from './button';
4
4
 
5
+ interface StoryParams {
6
+ content: string;
7
+ disabled: boolean;
8
+ }
9
+
10
+ type Options = ZenButton & StoryParams;
11
+
5
12
  export default {
6
13
  title: 'Components/Button',
7
14
  component: ZenButton,
8
- tags: ['autodocs'],
9
- render: args => ({ props: { ...args } }),
10
- } satisfies Meta<ZenButton>;
15
+ args: {
16
+ content: 'Test',
17
+ disabled: false,
18
+ },
19
+ argTypes: {
20
+ content: {
21
+ control: 'text',
22
+ table: {
23
+ category: 'story parameters',
24
+ type: {
25
+ summary: 'ng-content',
26
+ },
27
+ },
28
+ },
29
+ disabled: {
30
+ control: 'boolean',
31
+ table: {
32
+ category: 'attributes',
33
+ type: {
34
+ summary: 'boolean',
35
+ },
36
+ },
37
+ },
38
+ },
39
+ render: ({ content, ...args }) => ({
40
+ props: args,
41
+ template: `<button zen-btn ${args.disabled ? 'disabled' : ''}>${content}</button>`,
42
+ }),
43
+ } satisfies Meta<Options>;
11
44
 
12
- type Story = StoryObj<ZenButton>;
45
+ type Story = StoryObj<Options>;
13
46
 
14
- export const Default: Story = {
15
- render: () => ({
16
- template: `
17
- <button zen-btn>Test</button>
18
- `,
19
- }),
20
- };
47
+ export const Default: Story = {};
@@ -2,38 +2,65 @@ import { Meta, StoryObj } from '@storybook/angular';
2
2
 
3
3
  import { ZenCheckbox } from './checkbox';
4
4
 
5
+ type Options = ZenCheckbox;
6
+
5
7
  export default {
6
8
  title: 'Components/Checkbox',
7
9
  component: ZenCheckbox,
8
- tags: ['autodocs'],
9
10
  argTypes: {
10
11
  value: {
12
+ table: {
13
+ category: 'models',
14
+ type: {
15
+ summary: 'boolean | null',
16
+ },
17
+ defaultValue: {
18
+ summary: 'false',
19
+ },
20
+ },
11
21
  control: 'radio',
12
22
  options: [true, false, null],
13
23
  },
14
- disabled: { control: 'boolean' },
15
- required: { control: 'boolean' },
24
+ disabled: {
25
+ control: 'boolean',
26
+ table: {
27
+ category: 'models',
28
+ type: {
29
+ summary: 'boolean',
30
+ },
31
+ },
32
+ },
33
+ required: {
34
+ control: 'boolean',
35
+ table: {
36
+ category: 'inputs',
37
+ type: {
38
+ summary: 'boolean',
39
+ },
40
+ defaultValue: {
41
+ summary: 'false',
42
+ },
43
+ },
44
+ },
45
+ onInput: {
46
+ table: {
47
+ readonly: true,
48
+ type: {
49
+ summary: '(value: boolean | null) => void',
50
+ },
51
+ },
52
+ },
16
53
  },
17
54
  args: {
18
55
  value: false,
19
56
  disabled: false,
20
57
  required: false,
21
58
  },
22
- } satisfies Meta<ZenCheckbox>;
59
+ } satisfies Meta<Options>;
23
60
 
24
- type Story = StoryObj<ZenCheckbox>;
61
+ type Story = StoryObj<Options>;
25
62
 
26
- export const Default: Story = {
27
- render: args => ({
28
- props: { ...args },
29
- template: `
30
- <zen-checkbox
31
- [disabled]="${args.disabled}"
32
- [value]="${args.value}"
33
- ${args.required ? 'required' : ''}
34
- />`,
35
- }),
36
- };
63
+ export const Default: Story = {};
37
64
 
38
65
  export const WithLabel: Story = {
39
66
  render: () => ({
@@ -37,7 +37,6 @@ import { ZenFormControl, ZenFormControlProvider } from '../form-control';
37
37
  */
38
38
  @Component({
39
39
  selector: 'zen-checkbox',
40
- standalone: true,
41
40
  template: `
42
41
  <input
43
42
  [attr.aria-disabled]="disabled()"
@@ -68,7 +67,6 @@ export class ZenCheckbox extends ZenFormControl<boolean | null> {
68
67
  * Set value to `null` to mark the checkbox as indeterminate
69
68
  */
70
69
  readonly value = model<boolean | null>(false);
71
- /** @ignore */
72
70
  private readonly inputElement = viewChild.required<ElementRef<HTMLInputElement>>('inputElement');
73
71
 
74
72
  constructor() {
@@ -7,22 +7,42 @@ interface StoryParams {
7
7
  vertical: boolean;
8
8
  }
9
9
 
10
+ type Options = ZenDivider & StoryParams;
11
+
10
12
  export default {
11
13
  title: 'Components/Divider',
12
14
  component: ZenDivider,
13
- tags: ['autodocs'],
14
15
  parameters: {
15
16
  layout: 'padded',
16
17
  },
17
18
  argTypes: {
18
- content: { control: 'text' },
19
- vertical: { control: 'boolean' },
20
- align: { control: 'select', options: ['start', 'center', 'end'] },
19
+ content: {
20
+ control: 'text',
21
+ table: {
22
+ category: 'Story parameters',
23
+ type: {
24
+ summary: 'ng-content',
25
+ },
26
+ },
27
+ },
28
+ vertical: {
29
+ control: 'boolean',
30
+ table: { category: 'attributes', type: { summary: 'boolean' }, defaultValue: { summary: 'false' } },
31
+ },
32
+ align: {
33
+ control: 'select',
34
+ options: ['start', 'center', 'end'],
35
+ table: {
36
+ category: 'Inputs',
37
+ defaultValue: { summary: 'center' },
38
+ type: { summary: '"start" | "center" | "end"' },
39
+ },
40
+ },
21
41
  },
22
42
  args: { content: '', vertical: false, align: 'center' },
23
- } satisfies Meta<ZenDivider & StoryParams>;
43
+ } satisfies Meta<Options>;
24
44
 
25
- type Story = StoryObj<ZenDivider & StoryParams>;
45
+ type Story = StoryObj<Options>;
26
46
 
27
47
  export const Default: Story = {
28
48
  render: args => ({
@@ -9,6 +9,7 @@ import { ChangeDetectionStrategy, Component, computed, ElementRef, inject, input
9
9
  * Content alignment can be controlled via the `align` input property.
10
10
  *
11
11
  * @example
12
+ *
12
13
  * ```html
13
14
  * <zen-divider/>
14
15
  * <zen-divider>With content</zen-divider>
@@ -42,17 +43,14 @@ import { ChangeDetectionStrategy, Component, computed, ElementRef, inject, input
42
43
  },
43
44
  })
44
45
  export class ZenDivider {
45
- /**
46
- * Controls the alignment of content within the divider.
47
- * @default 'center'
48
- */
46
+ /** Controls the alignment of content within the divider. */
49
47
  readonly align = input<'start' | 'end' | 'center'>('center');
50
48
 
51
49
  /**
52
50
  * Computed property that determines if the divider contains any content.
53
51
  * Used to apply appropriate styling when content is present.
54
52
  */
55
- readonly hasContent = computed(() => {
53
+ protected readonly hasContent = computed(() => {
56
54
  return this.elementRef.nativeElement.childNodes.length > 0;
57
55
  });
58
56
 
@@ -1,25 +1,68 @@
1
- import { Meta, moduleMetadata } from '@storybook/angular';
1
+ import { componentWrapperDecorator, Meta, moduleMetadata, StoryObj } from '@storybook/angular';
2
2
 
3
3
  import { ZenCheckbox } from '../checkbox';
4
4
  import { ZenInput } from '../input';
5
5
  import { ZenSwitch } from '../switch';
6
6
  import { ZenFormControl } from './form-control';
7
7
 
8
+ type Options = ZenFormControl<unknown>;
9
+
8
10
  export default {
9
11
  title: 'Components/FormControl',
10
12
  component: ZenFormControl,
11
- tags: ['autodocs'],
12
- decorators: [moduleMetadata({ imports: [ZenCheckbox, ZenInput, ZenSwitch] })],
13
+ decorators: [
14
+ moduleMetadata({ imports: [ZenCheckbox, ZenInput, ZenSwitch] }),
15
+ componentWrapperDecorator(
16
+ story => `<div style="display: flex; flex-direction: column; gap: 2rem; align-items: center">${story}</div>`
17
+ ),
18
+ ],
13
19
  args: {
14
20
  value: '',
15
21
  disabled: false,
16
22
  required: false,
17
23
  },
18
24
  argTypes: {
19
- value: { control: false },
20
- placeholder: { control: false },
21
- disabled: { control: 'boolean' },
22
- required: { control: 'boolean' },
25
+ value: {
26
+ control: false,
27
+ table: {
28
+ category: 'models',
29
+ readonly: true,
30
+ type: {
31
+ summary: 'T',
32
+ },
33
+ },
34
+ },
35
+ disabled: {
36
+ control: 'boolean',
37
+ table: {
38
+ category: 'models',
39
+ type: {
40
+ summary: 'boolean',
41
+ },
42
+ readonly: true,
43
+ },
44
+ },
45
+ required: {
46
+ control: 'boolean',
47
+ table: {
48
+ category: 'inputs',
49
+ type: {
50
+ summary: 'boolean',
51
+ },
52
+ defaultValue: {
53
+ summary: 'false',
54
+ },
55
+ readonly: true,
56
+ },
57
+ },
58
+ onInput: {
59
+ table: {
60
+ readonly: true,
61
+ type: {
62
+ summary: '(value: T) => void',
63
+ },
64
+ },
65
+ },
23
66
  },
24
67
  parameters: {
25
68
  docs: {
@@ -30,8 +73,16 @@ export default {
30
73
  },
31
74
  },
32
75
  },
33
- } satisfies Meta<ZenFormControl<unknown>>;
76
+ render: ({ ...args }) => ({
77
+ props: args,
78
+ template: `
79
+ <zen-checkbox />
80
+ <zen-input />
81
+ <zen-switch />
82
+ `,
83
+ }),
84
+ } satisfies Meta<Options>;
85
+
86
+ type Story = StoryObj<Options>;
34
87
 
35
- export { Default as Checkbox } from '../checkbox/checkbox.stories';
36
- export { Default as Input } from '../input/input.stories';
37
- export { Default as Switch } from '../switch/switch.stories';
88
+ export const Default: Story = {};
@@ -37,34 +37,18 @@ export const ZenFormControlProvider = <T extends ZenFormControl<any>>(component:
37
37
  export abstract class ZenFormControl<Value> implements ControlValueAccessor {
38
38
  /**
39
39
  * The underlying value of the control.
40
- * Subclasses must provide their own implementation, typically using `model()`.
40
+ * Subclasses must provide their own implementation using `model()`.
41
41
  */
42
42
  abstract readonly value: ModelSignal<Value>;
43
43
 
44
- /** The placeholder text for the form control. */
45
- readonly placeholder = input<string>();
46
-
47
- /**
48
- * Whether the form control is disabled.
49
- */
50
44
  readonly disabled = model(false);
51
-
52
- /** Whether the form control is required. */
53
45
  readonly required = input(false, { transform: booleanAttribute });
54
46
 
55
- /**
56
- * @internal For internal use by Angular forms.
57
- * @ignore
58
- */
59
47
  // eslint-disable-next-line @typescript-eslint/no-empty-function
60
- onChange: (value: Value) => void = () => {};
48
+ protected onChange: (value: Value) => void = () => {};
61
49
 
62
- /**
63
- * @internal For internal use by Angular forms.
64
- * @ignore
65
- */
66
50
  // eslint-disable-next-line @typescript-eslint/no-empty-function
67
- onTouched: () => void = () => {};
51
+ protected onTouched: () => void = () => {};
68
52
 
69
53
  /**
70
54
  * Should be called by the subclass when the control's value changes
@@ -1,3 +1,4 @@
1
+ import * as icons from '@hugeicons/core-free-icons';
1
2
  import { Meta, StoryObj } from '@storybook/angular';
2
3
 
3
4
  import { ZenIcon } from './icon';
@@ -7,25 +8,42 @@ type Options = ZenIcon;
7
8
  export default {
8
9
  title: 'Components/Icon',
9
10
  component: ZenIcon,
10
- tags: ['autodocs'],
11
11
  argTypes: {
12
- size: { control: { type: 'range', min: 12, max: 100, step: 1 }, description: 'Size' },
13
- strokeWidth: { control: { type: 'range', min: 1, max: 5, step: 0.25 }, description: 'strokeWidth' },
14
- absoluteStrokeWidth: { control: 'boolean' },
12
+ icon: {
13
+ control: 'select',
14
+ options: Object.keys(icons),
15
+ table: {
16
+ type: { summary: 'string' },
17
+ category: 'inputs',
18
+ subcategory: 'required',
19
+ defaultValue: { summary: '' },
20
+ },
21
+ },
22
+ size: {
23
+ control: { type: 'range', min: 12, max: 100, step: 1 },
24
+ table: { category: 'inputs', type: { summary: 'number' } },
25
+ },
26
+ strokeWidth: {
27
+ control: { type: 'range', min: 1, max: 5, step: 0.25 },
28
+ table: { category: 'inputs', defaultValue: { summary: '1.5' }, type: { summary: 'number' } },
29
+ },
30
+ absoluteStrokeWidth: {
31
+ control: 'boolean',
32
+ table: { category: 'inputs', defaultValue: { summary: 'false' }, type: { summary: 'boolean' } },
33
+ },
34
+ color: {
35
+ control: 'color',
36
+ table: { category: 'inputs', type: { summary: 'string' }, defaultValue: { summary: 'currentColor' } },
37
+ },
15
38
  },
16
39
  args: {
17
- size: 64,
40
+ size: 24,
18
41
  strokeWidth: 1.5,
19
42
  absoluteStrokeWidth: false,
43
+ icon: 'Tree02Icon',
20
44
  },
21
45
  } satisfies Meta<Options>;
22
46
 
23
47
  type Story = StoryObj<Options>;
24
48
 
25
- export const Default: Story = {
26
- render: args => ({
27
- template: `
28
- <zen-icon icon="Tree02Icon" [size]="${args.size}" [strokeWidth]="${args.strokeWidth}" ${args.absoluteStrokeWidth ? 'absoluteStrokeWidth' : ''}>
29
- `,
30
- }),
31
- };
49
+ export const Default: Story = {};
@@ -20,7 +20,6 @@ interface Path {
20
20
  * @example
21
21
  * <zen-icon icon="Tree02Icon" />
22
22
  *
23
- * @license {@link https://github.com/hugeicons/angular/blob/main/README.md#license|MIT}
24
23
  * @see [Hugeicons](https://hugeicons.com)
25
24
  */
26
25
  @Component({
@@ -29,9 +28,9 @@ interface Path {
29
28
  <svg
30
29
  [attr.color]="color()"
31
30
  [attr.height]="size()"
31
+ [attr.viewBox]="'0 0 ' + BASE_SIZE + ' ' + BASE_SIZE"
32
32
  [attr.width]="size()"
33
33
  fill="none"
34
- viewBox="0 0 24 24"
35
34
  xmlns="http://www.w3.org/2000/svg"
36
35
  >
37
36
  @for (path of paths(); track path) {
@@ -41,7 +40,7 @@ interface Path {
41
40
  [attr.fill]="path.fill"
42
41
  [attr.opacity]="path.opacity"
43
42
  [attr.stroke-width]="path.strokeWidth"
44
- [attr.stroke]="'currentColor'"
43
+ stroke="currentColor"
45
44
  />
46
45
  }
47
46
  </svg>
@@ -51,22 +50,19 @@ interface Path {
51
50
  export class ZenIcon {
52
51
  /** Icon file names from HugeIcons */
53
52
  readonly icon = input.required({ transform: (icon: Icon) => icons[icon] });
53
+ /** Size of the icon in pixels. */
54
54
  readonly size = input<number>(24);
55
+ /** Determines if stroke width scales with icon size. */
55
56
  readonly absoluteStrokeWidth = input<boolean, unknown>(false, { transform: booleanAttribute });
57
+ /** Width of the stroke for icon paths. */
56
58
  readonly strokeWidth = input<number>(1.5);
59
+ /** Color of the icon. */
57
60
  readonly color = input<string>('currentColor');
58
61
 
59
- readonly paths = computed<Path[]>(() => this.updatePaths());
60
-
62
+ protected readonly BASE_SIZE = 24;
63
+ protected readonly paths = computed<Path[]>(() => this.updatePaths());
61
64
  private readonly calculatedStrokeWidth = computed(() => this.calculateStrokeWidth());
62
65
 
63
- private calculateStrokeWidth(): number {
64
- if (!this.absoluteStrokeWidth()) return this.strokeWidth();
65
-
66
- const BASE_SIZE = 24;
67
- return (this.strokeWidth() * BASE_SIZE) / this.size();
68
- }
69
-
70
66
  private updatePaths(): Path[] {
71
67
  return this.icon().map(([, attrs]) => ({
72
68
  d: attrs['d'],
@@ -76,4 +72,10 @@ export class ZenIcon {
76
72
  strokeWidth: this.calculatedStrokeWidth(),
77
73
  }));
78
74
  }
75
+
76
+ private calculateStrokeWidth(): number {
77
+ if (!this.absoluteStrokeWidth()) return this.strokeWidth();
78
+
79
+ return (this.strokeWidth() * this.BASE_SIZE) / this.size();
80
+ }
79
81
  }
@@ -2,15 +2,54 @@ import { Meta, StoryObj } from '@storybook/angular';
2
2
 
3
3
  import { ZenInput } from './input';
4
4
 
5
+ type Options = ZenInput;
6
+
5
7
  export default {
6
8
  title: 'Components/Input',
7
9
  component: ZenInput,
8
- tags: ['autodocs'],
9
10
  argTypes: {
10
- value: { control: 'text' },
11
- placeholder: { control: 'text' },
12
- disabled: { control: 'boolean' },
13
- required: { control: 'boolean' },
11
+ value: {
12
+ control: 'text',
13
+ table: {
14
+ category: 'models',
15
+ type: {
16
+ summary: 'string',
17
+ },
18
+ defaultValue: {
19
+ summary: '',
20
+ },
21
+ },
22
+ },
23
+ placeholder: { control: 'text', table: { type: { summary: 'string' } } },
24
+ disabled: {
25
+ control: 'boolean',
26
+ table: {
27
+ category: 'models',
28
+ type: {
29
+ summary: 'boolean',
30
+ },
31
+ },
32
+ },
33
+ required: {
34
+ control: 'boolean',
35
+ table: {
36
+ category: 'inputs',
37
+ type: {
38
+ summary: 'boolean',
39
+ },
40
+ defaultValue: {
41
+ summary: 'false',
42
+ },
43
+ },
44
+ },
45
+ onInput: {
46
+ table: {
47
+ readonly: true,
48
+ type: {
49
+ summary: '(value: string) => void',
50
+ },
51
+ },
52
+ },
14
53
  },
15
54
  args: {
16
55
  value: '',
@@ -18,22 +57,11 @@ export default {
18
57
  disabled: false,
19
58
  required: false,
20
59
  },
21
- } satisfies Meta<ZenInput>;
60
+ } satisfies Meta<Options>;
22
61
 
23
- type Story = StoryObj<ZenInput>;
62
+ type Story = StoryObj<Options>;
24
63
 
25
- export const Default: Story = {
26
- render: args => ({
27
- props: { ...args },
28
- template: `
29
- <zen-input
30
- [disabled]="${args.disabled}"
31
- [value]="'${args.value}'"
32
- ${args.placeholder ? 'placeholder="' + args.placeholder + '"' : ''}
33
- ${args.required ? 'required' : ''}
34
- />`,
35
- }),
36
- };
64
+ export const Default: Story = {};
37
65
 
38
66
  export const WithLabel: Story = {
39
67
  render: () => ({
@@ -1,7 +1,7 @@
1
- import { ChangeDetectionStrategy, Component, model } from '@angular/core';
1
+ import { ChangeDetectionStrategy, Component, input, model } from '@angular/core';
2
2
  import { FormsModule } from '@angular/forms';
3
3
 
4
- import { ZenFormControl, ZenFormControlProvider } from '../form-control/form-control';
4
+ import { ZenFormControl, ZenFormControlProvider } from '../form-control';
5
5
 
6
6
  /**
7
7
  * ZenInput is a reusable text input component designed to provide
@@ -32,7 +32,6 @@ import { ZenFormControl, ZenFormControlProvider } from '../form-control/form-con
32
32
  */
33
33
  @Component({
34
34
  selector: 'zen-input',
35
- standalone: true,
36
35
  template: `
37
36
  <input
38
37
  [attr.placeholder]="placeholder()"
@@ -50,5 +49,9 @@ import { ZenFormControl, ZenFormControlProvider } from '../form-control/form-con
50
49
  imports: [FormsModule],
51
50
  })
52
51
  export class ZenInput extends ZenFormControl<string> {
52
+ /** The current input value with two-way binding support. */
53
53
  readonly value = model('');
54
+
55
+ /** The placeholder text for the form control. */
56
+ readonly placeholder = input<string>();
54
57
  }
@@ -8,30 +8,51 @@ interface StoryParams {
8
8
  width: number;
9
9
  }
10
10
 
11
- type StoryType = ZenSkeleton & StoryParams;
11
+ type Options = ZenSkeleton & StoryParams;
12
12
 
13
13
  export default {
14
14
  title: 'Components/Skeleton',
15
15
  component: ZenSkeleton,
16
- tags: ['autodocs'],
17
16
  argTypes: {
18
- rounded: { control: { type: 'boolean' } },
19
- height: { control: { type: 'range', min: 1, max: 20, step: 0.25 }, description: 'Height managed by css' },
20
- width: { control: { type: 'range', min: 1, max: 20, step: 0.25 }, description: 'Width managed by css' },
17
+ rounded: {
18
+ control: { type: 'boolean' },
19
+ table: {
20
+ category: 'attributes',
21
+ },
22
+ },
23
+ height: {
24
+ control: { type: 'range', min: 1, max: 20, step: 0.25 },
25
+ description: 'Height managed by css',
26
+ table: {
27
+ category: 'story parameters',
28
+ type: {
29
+ summary: undefined,
30
+ },
31
+ },
32
+ },
33
+ width: {
34
+ control: { type: 'range', min: 1, max: 20, step: 0.25 },
35
+ description: 'Width managed by css',
36
+ table: {
37
+ category: 'story parameters',
38
+ type: {
39
+ summary: undefined,
40
+ },
41
+ },
42
+ },
21
43
  },
22
44
  args: {
23
45
  rounded: false,
24
46
  height: 3,
25
47
  width: 3,
26
48
  },
27
- } satisfies Meta<StoryType>;
28
-
29
- type Story = StoryObj<StoryType>;
30
-
31
- export const Default: Story = {
32
49
  render: args => ({
33
50
  props: { ...args },
34
51
  template: `
35
52
  <zen-skeleton ${args.rounded ? 'rounded' : ''} style="width: ${args.width}rem; height: ${args.height}rem"/>`,
36
53
  }),
37
- };
54
+ } satisfies Meta<Options>;
55
+
56
+ type Story = StoryObj<Options>;
57
+
58
+ export const Default: Story = {};