feat(design-system): add reusable EmptyState primitive
Standalone, OnPush. title required input (caller supplies translated text), optional description, icon/actions content-projection slots. Colors via existing tenant CSS vars with fallbacks.
This commit is contained in:
12
src/app/shared/ui/empty-state/empty-state.component.html
Normal file
12
src/app/shared/ui/empty-state/empty-state.component.html
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<div class="app-empty-state">
|
||||||
|
<div class="app-empty-state__icon">
|
||||||
|
<ng-content select="[slot=icon]" />
|
||||||
|
</div>
|
||||||
|
<h3 class="app-empty-state__title">{{ title() }}</h3>
|
||||||
|
@if (description()) {
|
||||||
|
<p class="app-empty-state__description">{{ description() }}</p>
|
||||||
|
}
|
||||||
|
<div class="app-empty-state__actions">
|
||||||
|
<ng-content select="[slot=actions]" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
44
src/app/shared/ui/empty-state/empty-state.component.scss
Normal file
44
src/app/shared/ui/empty-state/empty-state.component.scss
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
.app-empty-state {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
gap: var(--space-sm, 0.5rem);
|
||||||
|
padding: var(--space-xl, 2rem) var(--space-lg, 1.5rem);
|
||||||
|
color: var(--text-secondary, #6b7280);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-empty-state__icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: var(--text-secondary, #6b7280);
|
||||||
|
|
||||||
|
&:empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-empty-state__title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.0625rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary, #1f322d);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-empty-state__description {
|
||||||
|
margin: 0;
|
||||||
|
max-width: 32rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-empty-state__actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-sm, 0.5rem);
|
||||||
|
margin-top: var(--space-xs, 0.25rem);
|
||||||
|
|
||||||
|
&:empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/app/shared/ui/empty-state/empty-state.component.ts
Normal file
13
src/app/shared/ui/empty-state/empty-state.component.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component, input } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-empty-state',
|
||||||
|
standalone: true,
|
||||||
|
templateUrl: './empty-state.component.html',
|
||||||
|
styleUrl: './empty-state.component.scss',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
})
|
||||||
|
export class EmptyStateComponent {
|
||||||
|
readonly title = input.required<string>();
|
||||||
|
readonly description = input<string | null>(null);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user