@styloviz/empty-state 0.1.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.
- package/LICENSE +21 -0
- package/README.md +58 -0
- package/fesm2022/styloviz-empty-state.mjs +194 -0
- package/fesm2022/styloviz-empty-state.mjs.map +1 -0
- package/package.json +42 -0
- package/types/styloviz-empty-state.d.ts +109 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 StyloViz
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# @styloviz/empty-state
|
|
2
|
+
|
|
3
|
+
> Full-page or inline empty states with icons, titles and action buttons.
|
|
4
|
+
|
|
5
|
+
Part of the **StyloViz UI Kit** — a premium Angular 21 + Tailwind CSS 4 dashboard component library. Standalone, `OnPush`, strongly typed, dark-mode ready.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @styloviz/core @styloviz/empty-state
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Requires `@angular/core` and `@angular/common` >= 21. `@styloviz/core` is a peer dependency shared by every component. Prefer everything at once? `@styloviz/all` installs the whole free tier in one command.
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { SvEmptyStateComponent } from '@styloviz/empty-state';
|
|
19
|
+
|
|
20
|
+
@Component({
|
|
21
|
+
standalone: true,
|
|
22
|
+
imports: [SvEmptyStateComponent],
|
|
23
|
+
template: `
|
|
24
|
+
<sv-empty-state icon="inbox" title="No messages" />
|
|
25
|
+
`,
|
|
26
|
+
})
|
|
27
|
+
export class DemoComponent {}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Inputs
|
|
31
|
+
|
|
32
|
+
| Input | Type | Default | Description |
|
|
33
|
+
| --- | --- | --- | --- |
|
|
34
|
+
| `title` | `string` | `'Nothing here yet'` | Main heading text. |
|
|
35
|
+
| `message` | `string` | `''` | Supporting message under the title. |
|
|
36
|
+
| `icon` | `EmptyStateIcon` | `'inbox'` | Built-in icon preset. Set to `'custom'` and provide `iconPath` to use your own SVG. |
|
|
37
|
+
| `iconPath` | `string` | `''` | Custom SVG `<path d="...">` — required when `icon === 'custom'`, ignored otherwise. |
|
|
38
|
+
| `iconViewBox` | `string` | `'0 0 24 24'` | SVG viewBox for a custom icon. |
|
|
39
|
+
| `showRings` | `boolean` | `true` | Show the decorative concentric-ring background behind the icon. |
|
|
40
|
+
| `size` | `EmptyStateSize` | `'md'` | Overall size. |
|
|
41
|
+
| `layout` | `EmptyStateLayout` | `'vertical'` | Content stacking axis. |
|
|
42
|
+
| `bordered` | `boolean` | `true` | Whether to render a card surface (border + background + rounded corners) or let the component blend into its parent. |
|
|
43
|
+
| `customClass` | `string` | `''` | Additional CSS classes on the root element. |
|
|
44
|
+
| `actions` | `EmptyStateAction[]` | `[]` | CTA buttons rendered below the message. Maximum 3 — first one is typically primary, rest secondary/ghost. |
|
|
45
|
+
|
|
46
|
+
## Outputs
|
|
47
|
+
|
|
48
|
+
| Output | Type | Description |
|
|
49
|
+
| --- | --- | --- |
|
|
50
|
+
| `actionClick` | `string` | Emitted when any action button is clicked, carrying the action's `id`. |
|
|
51
|
+
|
|
52
|
+
## Documentation
|
|
53
|
+
|
|
54
|
+
Full API reference and live demos: https://styloviz.dev/docs/empty-state
|
|
55
|
+
|
|
56
|
+
## License
|
|
57
|
+
|
|
58
|
+
MIT
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { input, output, computed, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
+
import { NgClass } from '@angular/common';
|
|
4
|
+
|
|
5
|
+
// ─── Built-in icon paths ──────────────────────────────────────────────────────
|
|
6
|
+
const ICON_PATHS = {
|
|
7
|
+
inbox: 'M3 8l7.89 5.26a2 2 0 0 0 2.22 0L21 8M5 19h14a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2z',
|
|
8
|
+
search: 'M21 21l-6-6m2-5a7 7 0 1 1-14 0 7 7 0 0 1 14 0z',
|
|
9
|
+
folder: 'M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7z',
|
|
10
|
+
users: 'M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2M23 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75M9 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8z',
|
|
11
|
+
chart: 'M18 20V10M12 20V4M6 20v-6',
|
|
12
|
+
document: 'M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6zM14 2v6h6M16 13H8M16 17H8M10 9H8',
|
|
13
|
+
notification: 'M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9M13.73 21a2 2 0 0 1-3.46 0',
|
|
14
|
+
cart: 'M6 2 3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4zM3 6h18M16 10a4 4 0 0 1-8 0',
|
|
15
|
+
filter: 'M22 3H2l8 9.46V19l4 2v-8.54L22 3z',
|
|
16
|
+
star: 'M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z',
|
|
17
|
+
lock: 'M19 11H5a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7a2 2 0 0 0-2-2zM7 11V7a5 5 0 0 1 10 0v4',
|
|
18
|
+
error: 'M12 8v4m0 4h.01M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z',
|
|
19
|
+
};
|
|
20
|
+
// ─── Component ───────────────────────────────────────────────────────────────
|
|
21
|
+
/**
|
|
22
|
+
* EmptyState — A polished zero-data placeholder for tables, lists, dashboards,
|
|
23
|
+
* search results, and any panel that may have no content to display.
|
|
24
|
+
*
|
|
25
|
+
* Features:
|
|
26
|
+
* - 12 built-in contextual icon presets (no external assets required)
|
|
27
|
+
* - Custom SVG path override
|
|
28
|
+
* - Optional decorative illustration background ring
|
|
29
|
+
* - Vertical and horizontal layout modes
|
|
30
|
+
* - 3 sizes: sm (inline), md (card), lg (full-page)
|
|
31
|
+
* - Up to 3 CTA action buttons (primary / secondary / ghost)
|
|
32
|
+
* - Optional body slot for projected rich content below the message
|
|
33
|
+
* - Full dark-mode support
|
|
34
|
+
*/
|
|
35
|
+
class SvEmptyStateComponent {
|
|
36
|
+
// ── Content ───────────────────────────────────────────────────────────────
|
|
37
|
+
/** Main heading text. */
|
|
38
|
+
title = input('Nothing here yet', ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
39
|
+
/** Supporting message under the title. */
|
|
40
|
+
message = input('', ...(ngDevMode ? [{ debugName: "message" }] : /* istanbul ignore next */ []));
|
|
41
|
+
// ── Icon ──────────────────────────────────────────────────────────────────
|
|
42
|
+
/**
|
|
43
|
+
* Built-in icon preset. Set to `'custom'` and provide `iconPath` to use
|
|
44
|
+
* your own SVG. @default 'inbox'
|
|
45
|
+
*/
|
|
46
|
+
icon = input('inbox', ...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
|
|
47
|
+
/**
|
|
48
|
+
* Custom SVG `<path d="...">` — required when `icon === 'custom'`,
|
|
49
|
+
* ignored otherwise.
|
|
50
|
+
*/
|
|
51
|
+
iconPath = input('', ...(ngDevMode ? [{ debugName: "iconPath" }] : /* istanbul ignore next */ []));
|
|
52
|
+
/** SVG viewBox for a custom icon. @default '0 0 24 24' */
|
|
53
|
+
iconViewBox = input('0 0 24 24', ...(ngDevMode ? [{ debugName: "iconViewBox" }] : /* istanbul ignore next */ []));
|
|
54
|
+
/**
|
|
55
|
+
* Show the decorative concentric-ring background behind the icon.
|
|
56
|
+
* @default true
|
|
57
|
+
*/
|
|
58
|
+
showRings = input(true, ...(ngDevMode ? [{ debugName: "showRings" }] : /* istanbul ignore next */ []));
|
|
59
|
+
// ── Appearance ────────────────────────────────────────────────────────────
|
|
60
|
+
/** Overall size. @default 'md' */
|
|
61
|
+
size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
62
|
+
/** Content stacking axis. @default 'vertical' */
|
|
63
|
+
layout = input('vertical', ...(ngDevMode ? [{ debugName: "layout" }] : /* istanbul ignore next */ []));
|
|
64
|
+
/**
|
|
65
|
+
* Whether to render a card surface (border + background + rounded corners)
|
|
66
|
+
* or let the component blend into its parent.
|
|
67
|
+
* @default true
|
|
68
|
+
*/
|
|
69
|
+
bordered = input(true, ...(ngDevMode ? [{ debugName: "bordered" }] : /* istanbul ignore next */ []));
|
|
70
|
+
/** Additional CSS classes on the root element. */
|
|
71
|
+
customClass = input('', ...(ngDevMode ? [{ debugName: "customClass" }] : /* istanbul ignore next */ []));
|
|
72
|
+
// ── Actions ───────────────────────────────────────────────────────────────
|
|
73
|
+
/**
|
|
74
|
+
* CTA buttons rendered below the message.
|
|
75
|
+
* Maximum 3 — first one is typically primary, rest secondary/ghost.
|
|
76
|
+
*/
|
|
77
|
+
actions = input([], ...(ngDevMode ? [{ debugName: "actions" }] : /* istanbul ignore next */ []));
|
|
78
|
+
// ── Outputs ───────────────────────────────────────────────────────────────
|
|
79
|
+
/** Emitted when any action button is clicked, carrying the action's `id`. */
|
|
80
|
+
actionClick = output();
|
|
81
|
+
// ── Computed ──────────────────────────────────────────────────────────────
|
|
82
|
+
resolvedIconPath = computed(() => {
|
|
83
|
+
const preset = this.icon();
|
|
84
|
+
if (preset === 'custom')
|
|
85
|
+
return this.iconPath();
|
|
86
|
+
return ICON_PATHS[preset];
|
|
87
|
+
}, ...(ngDevMode ? [{ debugName: "resolvedIconPath" }] : /* istanbul ignore next */ []));
|
|
88
|
+
// ── Root wrapper ──────────────────────────────────────────────────────────
|
|
89
|
+
rootClasses = computed(() => {
|
|
90
|
+
const bordered = this.bordered()
|
|
91
|
+
? 'border border-gray-200 bg-white dark:border-gray-800 dark:bg-gray-900'
|
|
92
|
+
: '';
|
|
93
|
+
const sizeMap = {
|
|
94
|
+
sm: 'rounded-xl px-6 py-8',
|
|
95
|
+
md: 'rounded-xl px-8 py-12',
|
|
96
|
+
lg: 'rounded-2xl px-10 py-16',
|
|
97
|
+
};
|
|
98
|
+
return [
|
|
99
|
+
'w-full',
|
|
100
|
+
bordered,
|
|
101
|
+
sizeMap[this.size()],
|
|
102
|
+
this.layout() === 'horizontal'
|
|
103
|
+
? 'flex items-center gap-8'
|
|
104
|
+
: 'flex flex-col items-center text-center',
|
|
105
|
+
this.customClass(),
|
|
106
|
+
].filter(Boolean).join(' ');
|
|
107
|
+
}, ...(ngDevMode ? [{ debugName: "rootClasses" }] : /* istanbul ignore next */ []));
|
|
108
|
+
// ── Icon ring ─────────────────────────────────────────────────────────────
|
|
109
|
+
/** Outer ring container size keyed to component size. */
|
|
110
|
+
ringSizeClass = computed(() => {
|
|
111
|
+
switch (this.size()) {
|
|
112
|
+
case 'sm': return 'h-20 w-20';
|
|
113
|
+
case 'lg': return 'h-36 w-36';
|
|
114
|
+
default: return 'h-28 w-28';
|
|
115
|
+
}
|
|
116
|
+
}, ...(ngDevMode ? [{ debugName: "ringSizeClass" }] : /* istanbul ignore next */ []));
|
|
117
|
+
/** Inner icon container size. */
|
|
118
|
+
iconContainerClass = computed(() => {
|
|
119
|
+
switch (this.size()) {
|
|
120
|
+
case 'sm': return 'h-10 w-10';
|
|
121
|
+
case 'lg': return 'h-16 w-16';
|
|
122
|
+
default: return 'h-14 w-14';
|
|
123
|
+
}
|
|
124
|
+
}, ...(ngDevMode ? [{ debugName: "iconContainerClass" }] : /* istanbul ignore next */ []));
|
|
125
|
+
/** SVG icon size. */
|
|
126
|
+
iconSvgClass = computed(() => {
|
|
127
|
+
switch (this.size()) {
|
|
128
|
+
case 'sm': return 'h-5 w-5';
|
|
129
|
+
case 'lg': return 'h-8 w-8';
|
|
130
|
+
default: return 'h-7 w-7';
|
|
131
|
+
}
|
|
132
|
+
}, ...(ngDevMode ? [{ debugName: "iconSvgClass" }] : /* istanbul ignore next */ []));
|
|
133
|
+
// ── Typography ────────────────────────────────────────────────────────────
|
|
134
|
+
titleClass = computed(() => {
|
|
135
|
+
const base = 'font-semibold text-gray-900 dark:text-white';
|
|
136
|
+
switch (this.size()) {
|
|
137
|
+
case 'sm': return `${base} text-sm`;
|
|
138
|
+
case 'lg': return `${base} text-2xl`;
|
|
139
|
+
default: return `${base} text-lg`;
|
|
140
|
+
}
|
|
141
|
+
}, ...(ngDevMode ? [{ debugName: "titleClass" }] : /* istanbul ignore next */ []));
|
|
142
|
+
messageClass = computed(() => {
|
|
143
|
+
const base = 'text-gray-500 dark:text-gray-400 leading-relaxed';
|
|
144
|
+
const maxW = this.layout() === 'vertical'
|
|
145
|
+
? (this.size() === 'lg' ? 'max-w-md' : 'max-w-xs')
|
|
146
|
+
: '';
|
|
147
|
+
switch (this.size()) {
|
|
148
|
+
case 'sm': return `${base} text-xs ${maxW}`;
|
|
149
|
+
case 'lg': return `${base} text-base ${maxW}`;
|
|
150
|
+
default: return `${base} text-sm ${maxW}`;
|
|
151
|
+
}
|
|
152
|
+
}, ...(ngDevMode ? [{ debugName: "messageClass" }] : /* istanbul ignore next */ []));
|
|
153
|
+
// ── Text block layout ─────────────────────────────────────────────────────
|
|
154
|
+
textBlockClass = computed(() => this.layout() === 'horizontal'
|
|
155
|
+
? 'flex flex-1 flex-col items-start text-left gap-1 min-w-0'
|
|
156
|
+
: 'flex flex-col items-center gap-1.5', ...(ngDevMode ? [{ debugName: "textBlockClass" }] : /* istanbul ignore next */ []));
|
|
157
|
+
actionsClass = computed(() => this.layout() === 'horizontal'
|
|
158
|
+
? 'flex flex-wrap gap-2 mt-4'
|
|
159
|
+
: 'flex flex-wrap justify-center gap-3 mt-4', ...(ngDevMode ? [{ debugName: "actionsClass" }] : /* istanbul ignore next */ []));
|
|
160
|
+
// ── Action button classes ─────────────────────────────────────────────────
|
|
161
|
+
/**
|
|
162
|
+
* Pre-built action button class strings keyed by style variant.
|
|
163
|
+
* Computed once per `size()` change so the template does a cheap key lookup
|
|
164
|
+
* instead of calling a method inside the `@for` loop on every CD cycle.
|
|
165
|
+
*/
|
|
166
|
+
actionClasses = computed(() => {
|
|
167
|
+
const base = 'inline-flex items-center gap-1.5 rounded-lg font-medium transition-colors duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2';
|
|
168
|
+
const sizeMap = {
|
|
169
|
+
sm: 'px-3 py-1.5 text-xs',
|
|
170
|
+
md: 'px-4 py-2 text-sm',
|
|
171
|
+
lg: 'px-5 py-2.5 text-sm',
|
|
172
|
+
};
|
|
173
|
+
const sizeClass = sizeMap[this.size()];
|
|
174
|
+
const make = (styleClass) => [base, sizeClass, styleClass].join(' ');
|
|
175
|
+
return {
|
|
176
|
+
primary: make('bg-primary-600 text-white hover:bg-primary-700 focus-visible:ring-primary-500 shadow-sm'),
|
|
177
|
+
secondary: make('bg-white text-gray-700 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus-visible:ring-gray-400 dark:bg-gray-800 dark:text-gray-200 dark:ring-gray-700 dark:hover:bg-gray-700 shadow-sm'),
|
|
178
|
+
ghost: make('text-gray-500 hover:text-gray-900 hover:bg-gray-100 dark:text-gray-400 dark:hover:text-gray-100 dark:hover:bg-gray-800'),
|
|
179
|
+
};
|
|
180
|
+
}, ...(ngDevMode ? [{ debugName: "actionClasses" }] : /* istanbul ignore next */ []));
|
|
181
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: SvEmptyStateComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
182
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.13", type: SvEmptyStateComponent, isStandalone: true, selector: "sv-empty-state", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, iconPath: { classPropertyName: "iconPath", publicName: "iconPath", isSignal: true, isRequired: false, transformFunction: null }, iconViewBox: { classPropertyName: "iconViewBox", publicName: "iconViewBox", isSignal: true, isRequired: false, transformFunction: null }, showRings: { classPropertyName: "showRings", publicName: "showRings", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, layout: { classPropertyName: "layout", publicName: "layout", isSignal: true, isRequired: false, transformFunction: null }, bordered: { classPropertyName: "bordered", publicName: "bordered", isSignal: true, isRequired: false, transformFunction: null }, customClass: { classPropertyName: "customClass", publicName: "customClass", isSignal: true, isRequired: false, transformFunction: null }, actions: { classPropertyName: "actions", publicName: "actions", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { actionClick: "actionClick" }, ngImport: i0, template: "<div\n [ngClass]=\"rootClasses()\"\n role=\"region\"\n [attr.aria-label]=\"title()\"\n>\n\n <!-- \u2500\u2500 Icon / illustration \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div\n class=\"relative flex shrink-0 items-center justify-center\"\n [ngClass]=\"ringSizeClass()\"\n >\n @if (showRings()) {\n <!-- Outer decorative ring -->\n <span\n class=\"absolute inset-0 rounded-full border border-gray-200/70\n dark:border-gray-700/50\"\n aria-hidden=\"true\"\n ></span>\n <!-- Middle ring -->\n <span\n class=\"absolute rounded-full border border-gray-200/50\n dark:border-gray-700/40\"\n [ngClass]=\"size() === 'sm' ? 'inset-3' : size() === 'lg' ? 'inset-5' : 'inset-4'\"\n aria-hidden=\"true\"\n ></span>\n <!-- Inner tinted ring / icon bg -->\n <span\n class=\"absolute rounded-full bg-gray-50 dark:bg-gray-800/80\"\n [ngClass]=\"size() === 'sm' ? 'inset-5' : size() === 'lg' ? 'inset-8' : 'inset-7'\"\n aria-hidden=\"true\"\n ></span>\n }\n\n <!-- Icon container -->\n <span\n class=\"relative z-10 flex items-center justify-center rounded-full\n bg-white shadow-sm ring-1 ring-gray-200/80\n dark:bg-gray-800 dark:ring-gray-700/60\"\n [ngClass]=\"iconContainerClass()\"\n aria-hidden=\"true\"\n >\n <svg\n [ngClass]=\"iconSvgClass()\"\n class=\"text-gray-500 dark:text-gray-400\"\n [attr.viewBox]=\"icon() === 'custom' ? iconViewBox() : '0 0 24 24'\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path [attr.d]=\"resolvedIconPath()\" />\n </svg>\n </span>\n </div>\n\n <!-- \u2500\u2500 Text + actions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div [ngClass]=\"textBlockClass()\">\n\n <!-- Title -->\n <h3 [ngClass]=\"titleClass()\">{{ title() }}</h3>\n\n <!-- Message -->\n @if (message()) {\n <p [ngClass]=\"messageClass()\" class=\"mt-1\">{{ message() }}</p>\n }\n\n <!-- Projected rich content -->\n <ng-content />\n\n <!-- CTA actions -->\n @if (actions().length > 0) {\n <div [ngClass]=\"actionsClass()\">\n @for (action of actions(); track action.id) {\n <button\n type=\"button\"\n [ngClass]=\"actionClasses()[action.style ?? 'primary']\"\n (click)=\"actionClick.emit(action.id)\"\n >\n @if (action.iconPath) {\n <svg\n class=\"shrink-0\"\n [ngClass]=\"size() === 'sm' ? 'h-3.5 w-3.5' : 'h-4 w-4'\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path [attr.d]=\"action.iconPath\" />\n </svg>\n }\n {{ action.label }}\n </button>\n }\n </div>\n }\n\n </div>\n\n</div>\n", dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
183
|
+
}
|
|
184
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: SvEmptyStateComponent, decorators: [{
|
|
185
|
+
type: Component,
|
|
186
|
+
args: [{ selector: 'sv-empty-state', standalone: true, imports: [NgClass], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n [ngClass]=\"rootClasses()\"\n role=\"region\"\n [attr.aria-label]=\"title()\"\n>\n\n <!-- \u2500\u2500 Icon / illustration \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div\n class=\"relative flex shrink-0 items-center justify-center\"\n [ngClass]=\"ringSizeClass()\"\n >\n @if (showRings()) {\n <!-- Outer decorative ring -->\n <span\n class=\"absolute inset-0 rounded-full border border-gray-200/70\n dark:border-gray-700/50\"\n aria-hidden=\"true\"\n ></span>\n <!-- Middle ring -->\n <span\n class=\"absolute rounded-full border border-gray-200/50\n dark:border-gray-700/40\"\n [ngClass]=\"size() === 'sm' ? 'inset-3' : size() === 'lg' ? 'inset-5' : 'inset-4'\"\n aria-hidden=\"true\"\n ></span>\n <!-- Inner tinted ring / icon bg -->\n <span\n class=\"absolute rounded-full bg-gray-50 dark:bg-gray-800/80\"\n [ngClass]=\"size() === 'sm' ? 'inset-5' : size() === 'lg' ? 'inset-8' : 'inset-7'\"\n aria-hidden=\"true\"\n ></span>\n }\n\n <!-- Icon container -->\n <span\n class=\"relative z-10 flex items-center justify-center rounded-full\n bg-white shadow-sm ring-1 ring-gray-200/80\n dark:bg-gray-800 dark:ring-gray-700/60\"\n [ngClass]=\"iconContainerClass()\"\n aria-hidden=\"true\"\n >\n <svg\n [ngClass]=\"iconSvgClass()\"\n class=\"text-gray-500 dark:text-gray-400\"\n [attr.viewBox]=\"icon() === 'custom' ? iconViewBox() : '0 0 24 24'\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path [attr.d]=\"resolvedIconPath()\" />\n </svg>\n </span>\n </div>\n\n <!-- \u2500\u2500 Text + actions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div [ngClass]=\"textBlockClass()\">\n\n <!-- Title -->\n <h3 [ngClass]=\"titleClass()\">{{ title() }}</h3>\n\n <!-- Message -->\n @if (message()) {\n <p [ngClass]=\"messageClass()\" class=\"mt-1\">{{ message() }}</p>\n }\n\n <!-- Projected rich content -->\n <ng-content />\n\n <!-- CTA actions -->\n @if (actions().length > 0) {\n <div [ngClass]=\"actionsClass()\">\n @for (action of actions(); track action.id) {\n <button\n type=\"button\"\n [ngClass]=\"actionClasses()[action.style ?? 'primary']\"\n (click)=\"actionClick.emit(action.id)\"\n >\n @if (action.iconPath) {\n <svg\n class=\"shrink-0\"\n [ngClass]=\"size() === 'sm' ? 'h-3.5 w-3.5' : 'h-4 w-4'\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path [attr.d]=\"action.iconPath\" />\n </svg>\n }\n {{ action.label }}\n </button>\n }\n </div>\n }\n\n </div>\n\n</div>\n" }]
|
|
187
|
+
}], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], iconPath: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconPath", required: false }] }], iconViewBox: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconViewBox", required: false }] }], showRings: [{ type: i0.Input, args: [{ isSignal: true, alias: "showRings", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], layout: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout", required: false }] }], bordered: [{ type: i0.Input, args: [{ isSignal: true, alias: "bordered", required: false }] }], customClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "customClass", required: false }] }], actions: [{ type: i0.Input, args: [{ isSignal: true, alias: "actions", required: false }] }], actionClick: [{ type: i0.Output, args: ["actionClick"] }] } });
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Generated bundle index. Do not edit.
|
|
191
|
+
*/
|
|
192
|
+
|
|
193
|
+
export { SvEmptyStateComponent };
|
|
194
|
+
//# sourceMappingURL=styloviz-empty-state.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"styloviz-empty-state.mjs","sources":["../../../../projects/empty-state/src/lib/empty-state.component.ts","../../../../projects/empty-state/src/lib/empty-state.component.html","../../../../projects/empty-state/src/styloviz-empty-state.ts"],"sourcesContent":["import {\n Component,\n input,\n output,\n computed,\n ChangeDetectionStrategy,\n} from '@angular/core';\nimport { NgClass } from '@angular/common';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\n/**\n * Visual size of the empty state block.\n * - `sm` — compact, inline use (table cells, sidebars)\n * - `md` — default card/panel use\n * - `lg` — full-page / hero-style\n */\nexport type EmptyStateSize = 'sm' | 'md' | 'lg';\n\n/**\n * Layout axis for the content stack.\n * - `vertical` — icon → title → message → actions (default)\n * - `horizontal` — icon on the left, text/actions on the right\n */\nexport type EmptyStateLayout = 'vertical' | 'horizontal';\n\n/**\n * Built-in icon preset.\n * The component ships a curated set of SVG paths for the most common\n * empty-state scenarios. Pass `iconPath` to override with any custom SVG.\n */\nexport type EmptyStateIcon =\n | 'inbox'\n | 'search'\n | 'folder'\n | 'users'\n | 'chart'\n | 'document'\n | 'notification'\n | 'cart'\n | 'filter'\n | 'star'\n | 'lock'\n | 'error'\n | 'custom'; // falls through to `iconPath`\n\n/** A single CTA button rendered below the message. */\nexport interface EmptyStateAction {\n id: string;\n label: string;\n /** @default 'primary' */\n style?: 'primary' | 'secondary' | 'ghost';\n /** Optional SVG path prefix shown before the label. */\n iconPath?: string;\n}\n\n// ─── Built-in icon paths ──────────────────────────────────────────────────────\n\nconst ICON_PATHS: Record<Exclude<EmptyStateIcon, 'custom'>, string> = {\n inbox: 'M3 8l7.89 5.26a2 2 0 0 0 2.22 0L21 8M5 19h14a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2z',\n search: 'M21 21l-6-6m2-5a7 7 0 1 1-14 0 7 7 0 0 1 14 0z',\n folder: 'M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7z',\n users: 'M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2M23 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75M9 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8z',\n chart: 'M18 20V10M12 20V4M6 20v-6',\n document: 'M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6zM14 2v6h6M16 13H8M16 17H8M10 9H8',\n notification: 'M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9M13.73 21a2 2 0 0 1-3.46 0',\n cart: 'M6 2 3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4zM3 6h18M16 10a4 4 0 0 1-8 0',\n filter: 'M22 3H2l8 9.46V19l4 2v-8.54L22 3z',\n star: 'M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z',\n lock: 'M19 11H5a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7a2 2 0 0 0-2-2zM7 11V7a5 5 0 0 1 10 0v4',\n error: 'M12 8v4m0 4h.01M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z',\n};\n\n// ─── Component ───────────────────────────────────────────────────────────────\n\n/**\n * EmptyState — A polished zero-data placeholder for tables, lists, dashboards,\n * search results, and any panel that may have no content to display.\n *\n * Features:\n * - 12 built-in contextual icon presets (no external assets required)\n * - Custom SVG path override\n * - Optional decorative illustration background ring\n * - Vertical and horizontal layout modes\n * - 3 sizes: sm (inline), md (card), lg (full-page)\n * - Up to 3 CTA action buttons (primary / secondary / ghost)\n * - Optional body slot for projected rich content below the message\n * - Full dark-mode support\n */\n@Component({\n selector: 'sv-empty-state',\n standalone: true,\n imports: [NgClass],\n templateUrl: './empty-state.component.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class SvEmptyStateComponent {\n // ── Content ───────────────────────────────────────────────────────────────\n\n /** Main heading text. */\n title = input<string>('Nothing here yet');\n\n /** Supporting message under the title. */\n message = input<string>('');\n\n // ── Icon ──────────────────────────────────────────────────────────────────\n\n /**\n * Built-in icon preset. Set to `'custom'` and provide `iconPath` to use\n * your own SVG. @default 'inbox'\n */\n icon = input<EmptyStateIcon>('inbox');\n\n /**\n * Custom SVG `<path d=\"...\">` — required when `icon === 'custom'`,\n * ignored otherwise.\n */\n iconPath = input<string>('');\n\n /** SVG viewBox for a custom icon. @default '0 0 24 24' */\n iconViewBox = input<string>('0 0 24 24');\n\n /**\n * Show the decorative concentric-ring background behind the icon.\n * @default true\n */\n showRings = input<boolean>(true);\n\n // ── Appearance ────────────────────────────────────────────────────────────\n\n /** Overall size. @default 'md' */\n size = input<EmptyStateSize>('md');\n\n /** Content stacking axis. @default 'vertical' */\n layout = input<EmptyStateLayout>('vertical');\n\n /**\n * Whether to render a card surface (border + background + rounded corners)\n * or let the component blend into its parent.\n * @default true\n */\n bordered = input<boolean>(true);\n\n /** Additional CSS classes on the root element. */\n customClass = input<string>('');\n\n // ── Actions ───────────────────────────────────────────────────────────────\n\n /**\n * CTA buttons rendered below the message.\n * Maximum 3 — first one is typically primary, rest secondary/ghost.\n */\n actions = input<EmptyStateAction[]>([]);\n\n // ── Outputs ───────────────────────────────────────────────────────────────\n\n /** Emitted when any action button is clicked, carrying the action's `id`. */\n actionClick = output<string>();\n\n // ── Computed ──────────────────────────────────────────────────────────────\n\n resolvedIconPath = computed<string>(() => {\n const preset = this.icon();\n if (preset === 'custom') return this.iconPath();\n return ICON_PATHS[preset];\n });\n\n // ── Root wrapper ──────────────────────────────────────────────────────────\n\n rootClasses = computed<string>(() => {\n const bordered = this.bordered()\n ? 'border border-gray-200 bg-white dark:border-gray-800 dark:bg-gray-900'\n : '';\n\n const sizeMap: Record<EmptyStateSize, string> = {\n sm: 'rounded-xl px-6 py-8',\n md: 'rounded-xl px-8 py-12',\n lg: 'rounded-2xl px-10 py-16',\n };\n\n return [\n 'w-full',\n bordered,\n sizeMap[this.size()],\n this.layout() === 'horizontal'\n ? 'flex items-center gap-8'\n : 'flex flex-col items-center text-center',\n this.customClass(),\n ].filter(Boolean).join(' ');\n });\n\n // ── Icon ring ─────────────────────────────────────────────────────────────\n\n /** Outer ring container size keyed to component size. */\n ringSizeClass = computed<string>(() => {\n switch (this.size()) {\n case 'sm': return 'h-20 w-20';\n case 'lg': return 'h-36 w-36';\n default: return 'h-28 w-28';\n }\n });\n\n /** Inner icon container size. */\n iconContainerClass = computed<string>(() => {\n switch (this.size()) {\n case 'sm': return 'h-10 w-10';\n case 'lg': return 'h-16 w-16';\n default: return 'h-14 w-14';\n }\n });\n\n /** SVG icon size. */\n iconSvgClass = computed<string>(() => {\n switch (this.size()) {\n case 'sm': return 'h-5 w-5';\n case 'lg': return 'h-8 w-8';\n default: return 'h-7 w-7';\n }\n });\n\n // ── Typography ────────────────────────────────────────────────────────────\n\n titleClass = computed<string>(() => {\n const base = 'font-semibold text-gray-900 dark:text-white';\n switch (this.size()) {\n case 'sm': return `${base} text-sm`;\n case 'lg': return `${base} text-2xl`;\n default: return `${base} text-lg`;\n }\n });\n\n messageClass = computed<string>(() => {\n const base = 'text-gray-500 dark:text-gray-400 leading-relaxed';\n const maxW = this.layout() === 'vertical'\n ? (this.size() === 'lg' ? 'max-w-md' : 'max-w-xs')\n : '';\n switch (this.size()) {\n case 'sm': return `${base} text-xs ${maxW}`;\n case 'lg': return `${base} text-base ${maxW}`;\n default: return `${base} text-sm ${maxW}`;\n }\n });\n\n // ── Text block layout ─────────────────────────────────────────────────────\n\n textBlockClass = computed<string>(() =>\n this.layout() === 'horizontal'\n ? 'flex flex-1 flex-col items-start text-left gap-1 min-w-0'\n : 'flex flex-col items-center gap-1.5'\n );\n\n actionsClass = computed<string>(() =>\n this.layout() === 'horizontal'\n ? 'flex flex-wrap gap-2 mt-4'\n : 'flex flex-wrap justify-center gap-3 mt-4'\n );\n\n // ── Action button classes ─────────────────────────────────────────────────\n\n /**\n * Pre-built action button class strings keyed by style variant.\n * Computed once per `size()` change so the template does a cheap key lookup\n * instead of calling a method inside the `@for` loop on every CD cycle.\n */\n actionClasses = computed<Record<NonNullable<EmptyStateAction['style']>, string>>(() => {\n const base = 'inline-flex items-center gap-1.5 rounded-lg font-medium transition-colors duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2';\n const sizeMap: Record<EmptyStateSize, string> = {\n sm: 'px-3 py-1.5 text-xs',\n md: 'px-4 py-2 text-sm',\n lg: 'px-5 py-2.5 text-sm',\n };\n const sizeClass = sizeMap[this.size()];\n\n const make = (styleClass: string) => [base, sizeClass, styleClass].join(' ');\n\n return {\n primary: make('bg-primary-600 text-white hover:bg-primary-700 focus-visible:ring-primary-500 shadow-sm'),\n secondary: make('bg-white text-gray-700 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus-visible:ring-gray-400 dark:bg-gray-800 dark:text-gray-200 dark:ring-gray-700 dark:hover:bg-gray-700 shadow-sm'),\n ghost: make('text-gray-500 hover:text-gray-900 hover:bg-gray-100 dark:text-gray-400 dark:hover:text-gray-100 dark:hover:bg-gray-800'),\n };\n });\n}\n","<div\n [ngClass]=\"rootClasses()\"\n role=\"region\"\n [attr.aria-label]=\"title()\"\n>\n\n <!-- ── Icon / illustration ───────────────────────────────────────────── -->\n <div\n class=\"relative flex shrink-0 items-center justify-center\"\n [ngClass]=\"ringSizeClass()\"\n >\n @if (showRings()) {\n <!-- Outer decorative ring -->\n <span\n class=\"absolute inset-0 rounded-full border border-gray-200/70\n dark:border-gray-700/50\"\n aria-hidden=\"true\"\n ></span>\n <!-- Middle ring -->\n <span\n class=\"absolute rounded-full border border-gray-200/50\n dark:border-gray-700/40\"\n [ngClass]=\"size() === 'sm' ? 'inset-3' : size() === 'lg' ? 'inset-5' : 'inset-4'\"\n aria-hidden=\"true\"\n ></span>\n <!-- Inner tinted ring / icon bg -->\n <span\n class=\"absolute rounded-full bg-gray-50 dark:bg-gray-800/80\"\n [ngClass]=\"size() === 'sm' ? 'inset-5' : size() === 'lg' ? 'inset-8' : 'inset-7'\"\n aria-hidden=\"true\"\n ></span>\n }\n\n <!-- Icon container -->\n <span\n class=\"relative z-10 flex items-center justify-center rounded-full\n bg-white shadow-sm ring-1 ring-gray-200/80\n dark:bg-gray-800 dark:ring-gray-700/60\"\n [ngClass]=\"iconContainerClass()\"\n aria-hidden=\"true\"\n >\n <svg\n [ngClass]=\"iconSvgClass()\"\n class=\"text-gray-500 dark:text-gray-400\"\n [attr.viewBox]=\"icon() === 'custom' ? iconViewBox() : '0 0 24 24'\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path [attr.d]=\"resolvedIconPath()\" />\n </svg>\n </span>\n </div>\n\n <!-- ── Text + actions ─────────────────────────────────────────────────── -->\n <div [ngClass]=\"textBlockClass()\">\n\n <!-- Title -->\n <h3 [ngClass]=\"titleClass()\">{{ title() }}</h3>\n\n <!-- Message -->\n @if (message()) {\n <p [ngClass]=\"messageClass()\" class=\"mt-1\">{{ message() }}</p>\n }\n\n <!-- Projected rich content -->\n <ng-content />\n\n <!-- CTA actions -->\n @if (actions().length > 0) {\n <div [ngClass]=\"actionsClass()\">\n @for (action of actions(); track action.id) {\n <button\n type=\"button\"\n [ngClass]=\"actionClasses()[action.style ?? 'primary']\"\n (click)=\"actionClick.emit(action.id)\"\n >\n @if (action.iconPath) {\n <svg\n class=\"shrink-0\"\n [ngClass]=\"size() === 'sm' ? 'h-3.5 w-3.5' : 'h-4 w-4'\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path [attr.d]=\"action.iconPath\" />\n </svg>\n }\n {{ action.label }}\n </button>\n }\n </div>\n }\n\n </div>\n\n</div>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@styloviz/empty-state",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Full-page or inline empty states with icons, titles and action buttons.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "sazzad-bs23",
|
|
7
|
+
"homepage": "https://styloviz.dev",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/rhossain/angular-tailwind-dashboard-components.git",
|
|
11
|
+
"directory": "projects/empty-state"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/rhossain/angular-tailwind-dashboard-components/issues"
|
|
15
|
+
},
|
|
16
|
+
"peerDependencies": {
|
|
17
|
+
"@angular/common": ">=21.0.0",
|
|
18
|
+
"@angular/core": ">=21.0.0"
|
|
19
|
+
},
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
|
25
|
+
},
|
|
26
|
+
"sideEffects": false,
|
|
27
|
+
"module": "fesm2022/styloviz-empty-state.mjs",
|
|
28
|
+
"typings": "types/styloviz-empty-state.d.ts",
|
|
29
|
+
"exports": {
|
|
30
|
+
"./package.json": {
|
|
31
|
+
"default": "./package.json"
|
|
32
|
+
},
|
|
33
|
+
".": {
|
|
34
|
+
"types": "./types/styloviz-empty-state.d.ts",
|
|
35
|
+
"default": "./fesm2022/styloviz-empty-state.mjs"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"type": "module",
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"tslib": "^2.3.0"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import * as _angular_core from '@angular/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Visual size of the empty state block.
|
|
5
|
+
* - `sm` — compact, inline use (table cells, sidebars)
|
|
6
|
+
* - `md` — default card/panel use
|
|
7
|
+
* - `lg` — full-page / hero-style
|
|
8
|
+
*/
|
|
9
|
+
type EmptyStateSize = 'sm' | 'md' | 'lg';
|
|
10
|
+
/**
|
|
11
|
+
* Layout axis for the content stack.
|
|
12
|
+
* - `vertical` — icon → title → message → actions (default)
|
|
13
|
+
* - `horizontal` — icon on the left, text/actions on the right
|
|
14
|
+
*/
|
|
15
|
+
type EmptyStateLayout = 'vertical' | 'horizontal';
|
|
16
|
+
/**
|
|
17
|
+
* Built-in icon preset.
|
|
18
|
+
* The component ships a curated set of SVG paths for the most common
|
|
19
|
+
* empty-state scenarios. Pass `iconPath` to override with any custom SVG.
|
|
20
|
+
*/
|
|
21
|
+
type EmptyStateIcon = 'inbox' | 'search' | 'folder' | 'users' | 'chart' | 'document' | 'notification' | 'cart' | 'filter' | 'star' | 'lock' | 'error' | 'custom';
|
|
22
|
+
/** A single CTA button rendered below the message. */
|
|
23
|
+
interface EmptyStateAction {
|
|
24
|
+
id: string;
|
|
25
|
+
label: string;
|
|
26
|
+
/** @default 'primary' */
|
|
27
|
+
style?: 'primary' | 'secondary' | 'ghost';
|
|
28
|
+
/** Optional SVG path prefix shown before the label. */
|
|
29
|
+
iconPath?: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* EmptyState — A polished zero-data placeholder for tables, lists, dashboards,
|
|
33
|
+
* search results, and any panel that may have no content to display.
|
|
34
|
+
*
|
|
35
|
+
* Features:
|
|
36
|
+
* - 12 built-in contextual icon presets (no external assets required)
|
|
37
|
+
* - Custom SVG path override
|
|
38
|
+
* - Optional decorative illustration background ring
|
|
39
|
+
* - Vertical and horizontal layout modes
|
|
40
|
+
* - 3 sizes: sm (inline), md (card), lg (full-page)
|
|
41
|
+
* - Up to 3 CTA action buttons (primary / secondary / ghost)
|
|
42
|
+
* - Optional body slot for projected rich content below the message
|
|
43
|
+
* - Full dark-mode support
|
|
44
|
+
*/
|
|
45
|
+
declare class SvEmptyStateComponent {
|
|
46
|
+
/** Main heading text. */
|
|
47
|
+
title: _angular_core.InputSignal<string>;
|
|
48
|
+
/** Supporting message under the title. */
|
|
49
|
+
message: _angular_core.InputSignal<string>;
|
|
50
|
+
/**
|
|
51
|
+
* Built-in icon preset. Set to `'custom'` and provide `iconPath` to use
|
|
52
|
+
* your own SVG. @default 'inbox'
|
|
53
|
+
*/
|
|
54
|
+
icon: _angular_core.InputSignal<EmptyStateIcon>;
|
|
55
|
+
/**
|
|
56
|
+
* Custom SVG `<path d="...">` — required when `icon === 'custom'`,
|
|
57
|
+
* ignored otherwise.
|
|
58
|
+
*/
|
|
59
|
+
iconPath: _angular_core.InputSignal<string>;
|
|
60
|
+
/** SVG viewBox for a custom icon. @default '0 0 24 24' */
|
|
61
|
+
iconViewBox: _angular_core.InputSignal<string>;
|
|
62
|
+
/**
|
|
63
|
+
* Show the decorative concentric-ring background behind the icon.
|
|
64
|
+
* @default true
|
|
65
|
+
*/
|
|
66
|
+
showRings: _angular_core.InputSignal<boolean>;
|
|
67
|
+
/** Overall size. @default 'md' */
|
|
68
|
+
size: _angular_core.InputSignal<EmptyStateSize>;
|
|
69
|
+
/** Content stacking axis. @default 'vertical' */
|
|
70
|
+
layout: _angular_core.InputSignal<EmptyStateLayout>;
|
|
71
|
+
/**
|
|
72
|
+
* Whether to render a card surface (border + background + rounded corners)
|
|
73
|
+
* or let the component blend into its parent.
|
|
74
|
+
* @default true
|
|
75
|
+
*/
|
|
76
|
+
bordered: _angular_core.InputSignal<boolean>;
|
|
77
|
+
/** Additional CSS classes on the root element. */
|
|
78
|
+
customClass: _angular_core.InputSignal<string>;
|
|
79
|
+
/**
|
|
80
|
+
* CTA buttons rendered below the message.
|
|
81
|
+
* Maximum 3 — first one is typically primary, rest secondary/ghost.
|
|
82
|
+
*/
|
|
83
|
+
actions: _angular_core.InputSignal<EmptyStateAction[]>;
|
|
84
|
+
/** Emitted when any action button is clicked, carrying the action's `id`. */
|
|
85
|
+
actionClick: _angular_core.OutputEmitterRef<string>;
|
|
86
|
+
resolvedIconPath: _angular_core.Signal<string>;
|
|
87
|
+
rootClasses: _angular_core.Signal<string>;
|
|
88
|
+
/** Outer ring container size keyed to component size. */
|
|
89
|
+
ringSizeClass: _angular_core.Signal<string>;
|
|
90
|
+
/** Inner icon container size. */
|
|
91
|
+
iconContainerClass: _angular_core.Signal<string>;
|
|
92
|
+
/** SVG icon size. */
|
|
93
|
+
iconSvgClass: _angular_core.Signal<string>;
|
|
94
|
+
titleClass: _angular_core.Signal<string>;
|
|
95
|
+
messageClass: _angular_core.Signal<string>;
|
|
96
|
+
textBlockClass: _angular_core.Signal<string>;
|
|
97
|
+
actionsClass: _angular_core.Signal<string>;
|
|
98
|
+
/**
|
|
99
|
+
* Pre-built action button class strings keyed by style variant.
|
|
100
|
+
* Computed once per `size()` change so the template does a cheap key lookup
|
|
101
|
+
* instead of calling a method inside the `@for` loop on every CD cycle.
|
|
102
|
+
*/
|
|
103
|
+
actionClasses: _angular_core.Signal<Record<NonNullable<"primary" | "secondary" | "ghost" | undefined>, string>>;
|
|
104
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<SvEmptyStateComponent, never>;
|
|
105
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<SvEmptyStateComponent, "sv-empty-state", never, { "title": { "alias": "title"; "required": false; "isSignal": true; }; "message": { "alias": "message"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "iconPath": { "alias": "iconPath"; "required": false; "isSignal": true; }; "iconViewBox": { "alias": "iconViewBox"; "required": false; "isSignal": true; }; "showRings": { "alias": "showRings"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "layout": { "alias": "layout"; "required": false; "isSignal": true; }; "bordered": { "alias": "bordered"; "required": false; "isSignal": true; }; "customClass": { "alias": "customClass"; "required": false; "isSignal": true; }; "actions": { "alias": "actions"; "required": false; "isSignal": true; }; }, { "actionClick": "actionClick"; }, never, ["*"], true, never>;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export { SvEmptyStateComponent };
|
|
109
|
+
export type { EmptyStateAction, EmptyStateIcon, EmptyStateLayout, EmptyStateSize };
|