@feature23/ngx-mat-split-button 0.1.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 +49 -0
- package/ng-package.json +7 -0
- package/package.json +15 -0
- package/src/lib/ngx-mat-split-button.component.spec.ts +23 -0
- package/src/lib/ngx-mat-split-button.component.ts +109 -0
- package/src/lib/ngx-mat-split-button.service.ts +23 -0
- package/src/lib/ngx-mat-split-primary-action.directive.ts +10 -0
- package/src/public-api.ts +6 -0
- package/tsconfig.lib.json +15 -0
- package/tsconfig.lib.prod.json +11 -0
- package/tsconfig.spec.json +15 -0
package/README.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# ngx-mat-split-button
|
|
2
|
+
|
|
3
|
+
A split button component for Angular Material.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm i --save @feature23/ngx-mat-split-button
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
Import `NgxMatSplitButton` and `NgxMatSplitButtonPrimaryAction` into either your standalone component's `imports` array, or the module in which it will be used.
|
|
14
|
+
|
|
15
|
+
You likely will also need to import `MatMenuItem` from `@angular/material/menu`, if you want to use the attached menu as intended.
|
|
16
|
+
|
|
17
|
+
Example:
|
|
18
|
+
```html
|
|
19
|
+
<ngx-mat-split-button color="primary" buttonStyle="raised" (primaryClick)="primaryClick()">
|
|
20
|
+
<ng-template ngx-mat-split-primary-action>
|
|
21
|
+
Primary action
|
|
22
|
+
</ng-template>
|
|
23
|
+
<button mat-menu-item (click)="secondaryClick()">Item 1</button>
|
|
24
|
+
<button mat-menu-item (click)="secondaryClick()">Item 2</button>
|
|
25
|
+
<button mat-menu-item (click)="secondaryClick()">Item 3</button>
|
|
26
|
+
</ngx-mat-split-button>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The `ng-template` with the `ngx-mat-split-primary-action` directive is required to provide the inner content for the primary action button (the button to the left of the dropdown arrow).
|
|
30
|
+
Anything else inside the `ngx-mat-split-button` component will be rendered in the dropdown menu, which internally is just a `mat-menu` with `xPosition="before"`.
|
|
31
|
+
|
|
32
|
+
## Inputs
|
|
33
|
+
|
|
34
|
+
The `ngx-mat-split-button` component has the following inputs to customize its appearance and behavior:
|
|
35
|
+
|
|
36
|
+
| Input | Type | Description |
|
|
37
|
+
| --- | --- | --- |
|
|
38
|
+
| `color` | `'primary' \| 'accent' \| 'warn' \| undefined` | (Optional; default `undefined`) The color of the primary action button. (Material 2 themes only.) |
|
|
39
|
+
| `buttonStyle` | `'raised' \| 'stroked' \| 'flat' \| 'basic'` | (Optional; default `'basic'`) The style of the primary action button. These map to the equivalent [Angular Material Button directives](https://material.angular.io/components/button/overview), with `raised` being slightly different in the DOM than the others due to needing a unified shadow. (`raised` style uses `mat-flat-button` internally with a `box-shadow`.) |
|
|
40
|
+
|
|
41
|
+
## Events
|
|
42
|
+
|
|
43
|
+
The following output events are available to support interactivity:
|
|
44
|
+
|
|
45
|
+
| Output | Event/Argument Type | Description |
|
|
46
|
+
| --- | --- | --- |
|
|
47
|
+
| `primaryClick` | `MouseEvent` | Emitted when the primary action button is clicked. |
|
|
48
|
+
|
|
49
|
+
To handle clicks on the secondary action buttons, you can simply add `(click)="handler()"` to the buttons you provide inside the `ngx-mat-split-button` component.
|
package/ng-package.json
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@feature23/ngx-mat-split-button",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"private": false,
|
|
6
|
+
"peerDependencies": {
|
|
7
|
+
"@angular/common": "^18.2.0",
|
|
8
|
+
"@angular/core": "^18.2.0",
|
|
9
|
+
"@angular/material": "^18.2.0"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"tslib": "^2.3.0"
|
|
13
|
+
},
|
|
14
|
+
"sideEffects": false
|
|
15
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
2
|
+
|
|
3
|
+
import { NgxMatSplitButton } from './ngx-mat-split-button.component';
|
|
4
|
+
|
|
5
|
+
describe('NgxMatSplitButton', () => {
|
|
6
|
+
let component: NgxMatSplitButton;
|
|
7
|
+
let fixture: ComponentFixture<NgxMatSplitButton>;
|
|
8
|
+
|
|
9
|
+
beforeEach(async () => {
|
|
10
|
+
await TestBed.configureTestingModule({
|
|
11
|
+
imports: [NgxMatSplitButton]
|
|
12
|
+
})
|
|
13
|
+
.compileComponents();
|
|
14
|
+
|
|
15
|
+
fixture = TestBed.createComponent(NgxMatSplitButton);
|
|
16
|
+
component = fixture.componentInstance;
|
|
17
|
+
fixture.detectChanges();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should create', () => {
|
|
21
|
+
expect(component).toBeTruthy();
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { CommonModule } from '@angular/common';
|
|
2
|
+
import { Component, contentChild, inject, input, output } from '@angular/core';
|
|
3
|
+
import { MatButton } from '@angular/material/button';
|
|
4
|
+
import { MatIcon } from '@angular/material/icon';
|
|
5
|
+
import { MatMenuModule } from '@angular/material/menu';
|
|
6
|
+
import { NgxMatSplitButtonService } from './ngx-mat-split-button.service';
|
|
7
|
+
import { NgxMatSplitPrimaryAction } from './ngx-mat-split-primary-action.directive';
|
|
8
|
+
|
|
9
|
+
@Component({
|
|
10
|
+
selector: 'ngx-mat-split-button',
|
|
11
|
+
standalone: true,
|
|
12
|
+
imports: [
|
|
13
|
+
CommonModule,
|
|
14
|
+
MatButton,
|
|
15
|
+
MatIcon,
|
|
16
|
+
MatMenuModule,
|
|
17
|
+
],
|
|
18
|
+
// NOTE: the repetitive code here is required because it doesn't appear that you can conditionally add directives like `mat-button` vs `mat-flat-button`.
|
|
19
|
+
template: `
|
|
20
|
+
@if (buttonStyle() === 'basic') {
|
|
21
|
+
<button mat-button [color]="color()" (click)="primaryClick.emit($event)"
|
|
22
|
+
class="ngx-mat-split-button-primary ngx-mat-split-button-basic">
|
|
23
|
+
<ng-container [ngTemplateOutlet]="primaryAction().templateRef" />
|
|
24
|
+
</button>
|
|
25
|
+
<button mat-button [color]="color()" [matMenuTriggerFor]="menu"
|
|
26
|
+
class="ngx-mat-split-button-trigger"
|
|
27
|
+
aria-label="Toggle dropdown">
|
|
28
|
+
<mat-icon svgIcon="ngx-mat-split-arrow-down-icon" aria-label="Down arrow icon" />
|
|
29
|
+
</button>
|
|
30
|
+
} @else if (buttonStyle() === 'raised') {
|
|
31
|
+
<div class="ngx-mat-split-button-raised-wrapper">
|
|
32
|
+
<button mat-flat-button [color]="color()" (click)="primaryClick.emit($event)"
|
|
33
|
+
class="ngx-mat-split-button-primary ngx-mat-split-button-raised">
|
|
34
|
+
<ng-container [ngTemplateOutlet]="primaryAction().templateRef" />
|
|
35
|
+
</button>
|
|
36
|
+
<button mat-flat-button [color]="color()" [matMenuTriggerFor]="menu"
|
|
37
|
+
class="ngx-mat-split-button-trigger"
|
|
38
|
+
aria-label="Toggle dropdown">
|
|
39
|
+
<mat-icon svgIcon="ngx-mat-split-arrow-down-icon" aria-label="Down arrow icon" />
|
|
40
|
+
</button>
|
|
41
|
+
</div>
|
|
42
|
+
} @else if (buttonStyle() === 'stroked') {
|
|
43
|
+
<button mat-stroked-button [color]="color()" (click)="primaryClick.emit($event)"
|
|
44
|
+
class="ngx-mat-split-button-primary ngx-mat-split-button-stroked">
|
|
45
|
+
<ng-container [ngTemplateOutlet]="primaryAction().templateRef" />
|
|
46
|
+
</button>
|
|
47
|
+
<button mat-stroked-button [color]="color()" [matMenuTriggerFor]="menu"
|
|
48
|
+
class="ngx-mat-split-button-trigger"
|
|
49
|
+
aria-label="Toggle dropdown">
|
|
50
|
+
<mat-icon svgIcon="ngx-mat-split-arrow-down-icon" aria-label="Down arrow icon" />
|
|
51
|
+
</button>
|
|
52
|
+
} @else if (buttonStyle() === 'flat') {
|
|
53
|
+
<button mat-flat-button [color]="color()" (click)="primaryClick.emit($event)"
|
|
54
|
+
class="ngx-mat-split-button-primary ngx-mat-split-button-flat">
|
|
55
|
+
<ng-container [ngTemplateOutlet]="primaryAction().templateRef" />
|
|
56
|
+
</button>
|
|
57
|
+
<button mat-flat-button [color]="color()" [matMenuTriggerFor]="menu"
|
|
58
|
+
class="ngx-mat-split-button-trigger"
|
|
59
|
+
aria-label="Toggle dropdown">
|
|
60
|
+
<mat-icon svgIcon="ngx-mat-split-arrow-down-icon" aria-label="Down arrow icon" />
|
|
61
|
+
</button>
|
|
62
|
+
}
|
|
63
|
+
<mat-menu #menu="matMenu" xPosition="before">
|
|
64
|
+
<ng-content />
|
|
65
|
+
</mat-menu>
|
|
66
|
+
`,
|
|
67
|
+
styles: `
|
|
68
|
+
.ngx-mat-split-button-primary {
|
|
69
|
+
border-top-right-radius: 0;
|
|
70
|
+
border-bottom-right-radius: 0;
|
|
71
|
+
}
|
|
72
|
+
.ngx-mat-split-button-trigger {
|
|
73
|
+
border-top-left-radius: 0;
|
|
74
|
+
border-bottom-left-radius: 0;
|
|
75
|
+
border-left-width: 0;
|
|
76
|
+
min-width: 3rem;
|
|
77
|
+
|
|
78
|
+
mat-icon {
|
|
79
|
+
--mat-text-button-icon-spacing: 0;
|
|
80
|
+
--mat-filled-button-icon-spacing: 0;
|
|
81
|
+
--mat-protected-button-icon-spacing: 0;
|
|
82
|
+
--mat-outlined-button-icon-spacing: 0;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
.ngx-mat-split-button-raised-wrapper {
|
|
86
|
+
display: inline-block;
|
|
87
|
+
border-radius: var(--mdc-filled-button-container-shape, var(--mat-app-corner-full));
|
|
88
|
+
box-shadow: var(--mdc-protected-button-container-elevation-shadow, var(--mat-app-level1));
|
|
89
|
+
}
|
|
90
|
+
.ngx-mat-split-button-flat {
|
|
91
|
+
mat-icon svg {
|
|
92
|
+
fill: var(--mdc-filled-button-label-text-color, var(--mat-app-on-primary));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
`
|
|
96
|
+
})
|
|
97
|
+
export class NgxMatSplitButton {
|
|
98
|
+
readonly color = input<string>();
|
|
99
|
+
readonly buttonStyle = input<'basic' | 'raised' | 'stroked' | 'flat'>('basic');
|
|
100
|
+
|
|
101
|
+
readonly primaryClick = output<MouseEvent>();
|
|
102
|
+
|
|
103
|
+
readonly primaryAction = contentChild.required(NgxMatSplitPrimaryAction);
|
|
104
|
+
|
|
105
|
+
constructor() {
|
|
106
|
+
const service = inject(NgxMatSplitButtonService);
|
|
107
|
+
service.initialize();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { inject, Injectable } from '@angular/core';
|
|
2
|
+
import { MatIconRegistry } from '@angular/material/icon';
|
|
3
|
+
import { DomSanitizer } from '@angular/platform-browser';
|
|
4
|
+
|
|
5
|
+
const ARROW_DOWN_ICON = `
|
|
6
|
+
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px"><path d="M0 0h24v24H0z" fill="none"/><path d="M7 10l5 5 5-5z"/></svg>
|
|
7
|
+
`;
|
|
8
|
+
|
|
9
|
+
@Injectable({
|
|
10
|
+
providedIn: 'root'
|
|
11
|
+
})
|
|
12
|
+
export class NgxMatSplitButtonService {
|
|
13
|
+
|
|
14
|
+
constructor() {
|
|
15
|
+
const iconRegistry = inject(MatIconRegistry);
|
|
16
|
+
const sanitizer = inject(DomSanitizer);
|
|
17
|
+
iconRegistry.addSvgIconLiteral('ngx-mat-split-arrow-down-icon', sanitizer.bypassSecurityTrustHtml(ARROW_DOWN_ICON));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
initialize() {
|
|
21
|
+
// noop, ensure service is injected and constructor run
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
|
2
|
+
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
|
3
|
+
{
|
|
4
|
+
"extends": "../../tsconfig.json",
|
|
5
|
+
"compilerOptions": {
|
|
6
|
+
"outDir": "../../out-tsc/lib",
|
|
7
|
+
"declaration": true,
|
|
8
|
+
"declarationMap": true,
|
|
9
|
+
"inlineSources": true,
|
|
10
|
+
"types": []
|
|
11
|
+
},
|
|
12
|
+
"exclude": [
|
|
13
|
+
"**/*.spec.ts"
|
|
14
|
+
]
|
|
15
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
|
2
|
+
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
|
3
|
+
{
|
|
4
|
+
"extends": "./tsconfig.lib.json",
|
|
5
|
+
"compilerOptions": {
|
|
6
|
+
"declarationMap": false
|
|
7
|
+
},
|
|
8
|
+
"angularCompilerOptions": {
|
|
9
|
+
"compilationMode": "partial"
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
|
2
|
+
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
|
3
|
+
{
|
|
4
|
+
"extends": "../../tsconfig.json",
|
|
5
|
+
"compilerOptions": {
|
|
6
|
+
"outDir": "../../out-tsc/spec",
|
|
7
|
+
"types": [
|
|
8
|
+
"jasmine"
|
|
9
|
+
]
|
|
10
|
+
},
|
|
11
|
+
"include": [
|
|
12
|
+
"**/*.spec.ts",
|
|
13
|
+
"**/*.d.ts"
|
|
14
|
+
]
|
|
15
|
+
}
|