This is an automated email from the ASF dual-hosted git repository. riemer pushed a commit to branch add-split-button in repository https://gitbox.apache.org/repos/asf/streampipes.git
commit e412749143f70b81891310bb2ef29e0cb4aa13cc Author: Dominik Riemer <[email protected]> AuthorDate: Tue Mar 24 17:56:00 2026 +0100 feat: Add split button --- .../asset-link-configuration.component.html | 9 +- .../asset-link-configuration.component.ts | 4 + .../split-button/split-button.component.html | 83 ++++++++++++++ .../split-button/split-button.component.scss | 125 +++++++++++++++++++++ .../split-button/split-button.component.ts | 118 +++++++++++++++++++ .../streampipes/shared-ui/src/public-api.ts | 1 + 6 files changed, 339 insertions(+), 1 deletion(-) diff --git a/ui/projects/streampipes/shared-ui/src/lib/components/asset-link-configuration/asset-link-configuration.component.html b/ui/projects/streampipes/shared-ui/src/lib/components/asset-link-configuration/asset-link-configuration.component.html index 25bfcf49e0..368ac42e67 100644 --- a/ui/projects/streampipes/shared-ui/src/lib/components/asset-link-configuration/asset-link-configuration.component.html +++ b/ui/projects/streampipes/shared-ui/src/lib/components/asset-link-configuration/asset-link-configuration.component.html @@ -82,7 +82,14 @@ <!-- If no assets available --> @if (!assetsData?.length) { <div> - <p>No assets available</p> + <sp-alert-banner + type="info" + [title]="'No assets available' | translate" + [description]=" + 'Create a new asset in the asset view before adding it to a resource.' + | translate + " + ></sp-alert-banner> </div> } } diff --git a/ui/projects/streampipes/shared-ui/src/lib/components/asset-link-configuration/asset-link-configuration.component.ts b/ui/projects/streampipes/shared-ui/src/lib/components/asset-link-configuration/asset-link-configuration.component.ts index cde0c9e4d5..fc66c27748 100644 --- a/ui/projects/streampipes/shared-ui/src/lib/components/asset-link-configuration/asset-link-configuration.component.ts +++ b/ui/projects/streampipes/shared-ui/src/lib/components/asset-link-configuration/asset-link-configuration.component.ts @@ -45,6 +45,8 @@ import { MatStepper } from '@angular/material/stepper'; import { MatIconButton } from '@angular/material/button'; import { MatIcon } from '@angular/material/icon'; import { LayoutAlignDirective } from '@ngbracket/ngx-layout/flex'; +import { SpAlertBannerComponent } from '../alert-banner/alert-banner.component'; +import { TranslatePipe } from '@ngx-translate/core'; @Component({ selector: 'sp-asset-link-configuration', @@ -60,6 +62,8 @@ import { LayoutAlignDirective } from '@ngbracket/ngx-layout/flex'; LayoutAlignDirective, MatTreeNodeOutlet, MatTreeNode, + SpAlertBannerComponent, + TranslatePipe, ], }) export class AssetLinkConfigurationComponent implements OnInit { diff --git a/ui/projects/streampipes/shared-ui/src/lib/components/split-button/split-button.component.html b/ui/projects/streampipes/shared-ui/src/lib/components/split-button/split-button.component.html new file mode 100644 index 0000000000..bce26a4533 --- /dev/null +++ b/ui/projects/streampipes/shared-ui/src/lib/components/split-button/split-button.component.html @@ -0,0 +1,83 @@ +<!-- + ~ Licensed to the Apache Software Foundation (ASF) under one or more + ~ contributor license agreements. See the NOTICE file distributed with + ~ this work for additional information regarding copyright ownership. + ~ The ASF licenses this file to You under the Apache License, Version 2.0 + ~ (the "License"); you may not use this file except in compliance with + ~ the License. You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + ~ + --> + +<div + class="split-button" + cdkOverlayOrigin + #overlayOrigin="cdkOverlayOrigin" + [class.split-button--basic]="appearance === 'mat-basic'" + [class.split-button--open]="menuOpen" +> + <button + mat-flat-button + [type]="buttonType" + class="split-button__main" + [class.mat-basic]="appearance === 'mat-basic'" + [disabled]="disabled" + (click)="onPrimaryActionClicked()" + > + @if (icon) { + <mat-icon>{{ icon }}</mat-icon> + } + <span>{{ label }}</span> + </button> + + <button + mat-flat-button + type="button" + class="split-button__toggle" + [class.mat-basic]="appearance === 'mat-basic'" + [attr.aria-label]="menuAriaLabel | translate" + [disabled]="isDropdownDisabled()" + (click)="toggleMenu($event)" + > + <mat-icon>arrow_drop_down</mat-icon> + </button> +</div> + +<ng-template + cdkConnectedOverlay + [cdkConnectedOverlayOrigin]="overlayOrigin" + [cdkConnectedOverlayOpen]="menuOpen" + [cdkConnectedOverlayPositions]="overlayPositions" + [cdkConnectedOverlayHasBackdrop]="true" + cdkConnectedOverlayBackdropClass="cdk-overlay-transparent-backdrop" + (backdropClick)="closeMenu()" + (detach)="closeMenu()" +> + <div + class="split-button__menu-panel" + [style.width.px]="menuWidth" + role="menu" + > + @for (action of actions; track action.action) { + <button + type="button" + class="split-button__menu-item" + [disabled]="action.disabled" + (click)="onSplitActionClicked(action)" + role="menuitem" + > + @if (action.icon) { + <mat-icon>{{ action.icon }}</mat-icon> + } + <span>{{ action.label }}</span> + </button> + } + </div> +</ng-template> diff --git a/ui/projects/streampipes/shared-ui/src/lib/components/split-button/split-button.component.scss b/ui/projects/streampipes/shared-ui/src/lib/components/split-button/split-button.component.scss new file mode 100644 index 0000000000..accf1236b0 --- /dev/null +++ b/ui/projects/streampipes/shared-ui/src/lib/components/split-button/split-button.component.scss @@ -0,0 +1,125 @@ +/*! + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +:host { + display: inline-flex; +} + +.split-button { + display: inline-flex; + align-items: stretch; + gap: 2px; +} + +.split-button__main, +.split-button__toggle { + margin: 0; + transition: border-radius 150ms ease; +} + +.split-button__main { + border-radius: 999px 4px 4px 999px; +} + +.split-button__main mat-icon { + margin-right: 8px; +} + +.split-button__toggle { + min-width: 2.5rem; + width: 2.5rem; + padding: 0; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 4px 999px 999px 4px; +} + +.split-button__toggle mat-icon { + margin: 0; +} + +.split-button--basic .split-button__toggle { + border-color: var(--mat-sys-surface-container-high); +} + +.split-button__menu-panel { + position: relative; + margin-top: 2px; + border-radius: 4px; + overflow: hidden; + display: flex; + flex-direction: column; + padding: 8px 0; + background: var(--mat-sys-surface); + color: var(--mat-sys-on-surface); + box-shadow: + 0 2px 6px rgba(0, 0, 0, 0.18), + 0 1px 2px rgba(0, 0, 0, 0.1); +} + +.split-button__menu-item { + border: 0; + background: transparent; + color: inherit; + min-height: 48px; + padding: 0 12px; + text-align: left; + display: inline-flex; + align-items: center; + gap: 0.5rem; + font-size: var(--font-size-sm); + font-weight: 400; + line-height: 1.25; + cursor: pointer; + border-radius: 0; + transition: + background-color 120ms ease, + color 120ms ease; +} + +.split-button__menu-item mat-icon { + color: inherit; + margin: 0; + width: 1rem; + height: 1rem; + font-size: 1rem; +} + +.split-button__menu-item:hover { + background: color-mix(in srgb, var(--mat-sys-on-surface) 8%, transparent); +} + +.split-button__menu-item:focus-visible { + outline: none; + background: color-mix(in srgb, var(--mat-sys-on-surface) 12%, transparent); +} + +.split-button__menu-item:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.split-button--open .split-button__toggle mat-icon { + transform: rotate(180deg); + transition: transform 150ms ease; +} + +.split-button--open .split-button__toggle { + border-radius: 999px; +} diff --git a/ui/projects/streampipes/shared-ui/src/lib/components/split-button/split-button.component.ts b/ui/projects/streampipes/shared-ui/src/lib/components/split-button/split-button.component.ts new file mode 100644 index 0000000000..bb3b6b5493 --- /dev/null +++ b/ui/projects/streampipes/shared-ui/src/lib/components/split-button/split-button.component.ts @@ -0,0 +1,118 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { + ConnectedPosition, + CdkConnectedOverlay, + CdkOverlayOrigin, +} from '@angular/cdk/overlay'; +import { + Component, + EventEmitter, + Input, + Output, + ViewChild, +} from '@angular/core'; +import { MatButton } from '@angular/material/button'; +import { MatIcon } from '@angular/material/icon'; +import { TranslatePipe } from '@ngx-translate/core'; + +export interface SpSplitButtonAction { + label: string; + action: string; + icon?: string; + disabled?: boolean; +} + +@Component({ + selector: 'sp-split-button', + templateUrl: './split-button.component.html', + styleUrls: ['./split-button.component.scss'], + imports: [ + CdkOverlayOrigin, + CdkConnectedOverlay, + MatButton, + MatIcon, + TranslatePipe, + ], +}) +export class SpSplitButtonComponent { + @Input() label = ''; + @Input() icon?: string; + @Input() actions: SpSplitButtonAction[] = []; + @Input() appearance: 'primary' | 'mat-basic' = 'primary'; + @Input() disabled = false; + @Input() menuDisabled = false; + @Input() buttonType: 'button' | 'submit' | 'reset' = 'button'; + @Input() menuAriaLabel = 'Additional actions'; + + @Output() primaryAction = new EventEmitter<void>(); + @Output() actionSelected = new EventEmitter<SpSplitButtonAction>(); + + @ViewChild(CdkOverlayOrigin) overlayOrigin?: CdkOverlayOrigin; + + menuOpen = false; + menuWidth = 0; + + readonly overlayPositions: ConnectedPosition[] = [ + { + originX: 'start', + originY: 'bottom', + overlayX: 'start', + overlayY: 'top', + offsetY: 0, + }, + { + originX: 'end', + originY: 'bottom', + overlayX: 'end', + overlayY: 'top', + offsetY: 0, + }, + ]; + + onPrimaryActionClicked(): void { + this.closeMenu(); + this.primaryAction.emit(); + } + + onSplitActionClicked(action: SpSplitButtonAction): void { + this.closeMenu(); + this.actionSelected.emit(action); + } + + toggleMenu(event: MouseEvent): void { + event.stopPropagation(); + + if (this.isDropdownDisabled()) { + return; + } + + this.menuWidth = + this.overlayOrigin?.elementRef.nativeElement.offsetWidth ?? 0; + this.menuOpen = !this.menuOpen; + } + + closeMenu(): void { + this.menuOpen = false; + } + + isDropdownDisabled(): boolean { + return this.disabled || this.menuDisabled || this.actions.length === 0; + } +} diff --git a/ui/projects/streampipes/shared-ui/src/public-api.ts b/ui/projects/streampipes/shared-ui/src/public-api.ts index dc2cdd57b5..28efd371b9 100644 --- a/ui/projects/streampipes/shared-ui/src/public-api.ts +++ b/ui/projects/streampipes/shared-ui/src/public-api.ts @@ -39,6 +39,7 @@ export * from './lib/components/date-input/date-input.component'; export * from './lib/components/form-field/form-field.component'; export * from './lib/components/form-label/form-label.component'; export * from './lib/components/split-section/split-section.component'; +export * from './lib/components/split-button/split-button.component'; export * from './lib/components/sp-exception-message/sp-exception-message.component'; export * from './lib/components/sp-exception-message/exception-details-dialog/exception-details-dialog.component'; export * from './lib/components/sp-exception-message/exception-details/exception-details.component';
