@marianmeres/stuic 3.167.0 → 3.168.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/AGENTS.md +6 -6
- package/dist/attachments/index.d.ts +1 -0
- package/dist/attachments/index.js +1 -0
- package/dist/attachments/long-press.d.ts +43 -0
- package/dist/attachments/long-press.js +107 -0
- package/dist/components/Breadcrumbs/Breadcrumbs.svelte +198 -0
- package/dist/components/Breadcrumbs/Breadcrumbs.svelte.d.ts +60 -0
- package/dist/components/Breadcrumbs/README.md +174 -0
- package/dist/components/Breadcrumbs/i18n-sk.d.ts +17 -0
- package/dist/components/Breadcrumbs/i18n-sk.js +19 -0
- package/dist/components/Breadcrumbs/i18n.d.ts +31 -0
- package/dist/components/Breadcrumbs/i18n.js +40 -0
- package/dist/components/Breadcrumbs/index.css +84 -0
- package/dist/components/Breadcrumbs/index.d.ts +4 -0
- package/dist/components/Breadcrumbs/index.js +4 -0
- package/dist/components/Breadcrumbs/json-ld.d.ts +58 -0
- package/dist/components/Breadcrumbs/json-ld.js +56 -0
- package/dist/components/Collapsible/Collapsible.svelte +84 -15
- package/dist/components/Collapsible/Collapsible.svelte.d.ts +29 -2
- package/dist/components/Collapsible/README.md +82 -23
- package/dist/components/Collapsible/index.css +38 -0
- package/dist/components/Collapsible/index.d.ts +1 -1
- package/dist/components/ContextMenu/ContextMenu.svelte +302 -0
- package/dist/components/ContextMenu/ContextMenu.svelte.d.ts +94 -0
- package/dist/components/ContextMenu/README.md +159 -0
- package/dist/components/ContextMenu/i18n-sk.d.ts +17 -0
- package/dist/components/ContextMenu/i18n-sk.js +18 -0
- package/dist/components/ContextMenu/i18n.d.ts +30 -0
- package/dist/components/ContextMenu/i18n.js +38 -0
- package/dist/components/ContextMenu/index.css +26 -0
- package/dist/components/ContextMenu/index.d.ts +3 -0
- package/dist/components/ContextMenu/index.js +3 -0
- package/dist/components/CopyButton/CopyButton.svelte +278 -0
- package/dist/components/CopyButton/CopyButton.svelte.d.ts +76 -0
- package/dist/components/CopyButton/README.md +241 -0
- package/dist/components/CopyButton/i18n-sk.d.ts +17 -0
- package/dist/components/CopyButton/i18n-sk.js +20 -0
- package/dist/components/CopyButton/i18n.d.ts +35 -0
- package/dist/components/CopyButton/i18n.js +43 -0
- package/dist/components/CopyButton/index.css +67 -0
- package/dist/components/CopyButton/index.d.ts +3 -0
- package/dist/components/CopyButton/index.js +3 -0
- package/dist/components/DataTable/DataTable.svelte +41 -34
- package/dist/components/DataTable/README.md +45 -0
- package/dist/components/DataTable/i18n-sk.js +1 -0
- package/dist/components/DataTable/i18n.d.ts +1 -0
- package/dist/components/DataTable/i18n.js +3 -0
- package/dist/components/DataTable/index.css +41 -3
- package/dist/components/EmptyState/EmptyState.svelte +121 -0
- package/dist/components/EmptyState/EmptyState.svelte.d.ts +40 -0
- package/dist/components/EmptyState/README.md +188 -0
- package/dist/components/EmptyState/index.css +154 -0
- package/dist/components/EmptyState/index.d.ts +1 -0
- package/dist/components/EmptyState/index.js +1 -0
- package/dist/components/Pagination/Pagination.svelte +250 -0
- package/dist/components/Pagination/Pagination.svelte.d.ts +69 -0
- package/dist/components/Pagination/README.md +152 -0
- package/dist/components/Pagination/i18n-sk.d.ts +17 -0
- package/dist/components/Pagination/i18n-sk.js +24 -0
- package/dist/components/Pagination/i18n.d.ts +41 -0
- package/dist/components/Pagination/i18n.js +49 -0
- package/dist/components/Pagination/index.css +50 -0
- package/dist/components/Pagination/index.d.ts +4 -0
- package/dist/components/Pagination/index.js +4 -0
- package/dist/components/Pagination/pagination-range.d.ts +21 -0
- package/dist/components/Pagination/pagination-range.js +41 -0
- package/dist/components/Stat/README.md +157 -0
- package/dist/components/Stat/Stat.svelte +226 -0
- package/dist/components/Stat/Stat.svelte.d.ts +54 -0
- package/dist/components/Stat/index.css +217 -0
- package/dist/components/Stat/index.d.ts +1 -0
- package/dist/components/Stat/index.js +1 -0
- package/dist/components/Stepper/README.md +181 -0
- package/dist/components/Stepper/Stepper.svelte +240 -0
- package/dist/components/Stepper/Stepper.svelte.d.ts +86 -0
- package/dist/components/Stepper/i18n-sk.d.ts +17 -0
- package/dist/components/Stepper/i18n-sk.js +21 -0
- package/dist/components/Stepper/i18n.d.ts +35 -0
- package/dist/components/Stepper/i18n.js +43 -0
- package/dist/components/Stepper/index.css +302 -0
- package/dist/components/Stepper/index.d.ts +3 -0
- package/dist/components/Stepper/index.js +3 -0
- package/dist/components/TrendChart/README.md +123 -0
- package/dist/components/TrendChart/TrendChart.svelte +114 -0
- package/dist/components/TrendChart/TrendChart.svelte.d.ts +36 -0
- package/dist/components/TrendChart/index.css +51 -0
- package/dist/components/TrendChart/index.d.ts +1 -0
- package/dist/components/TrendChart/index.js +1 -0
- package/dist/icons/index.d.ts +4 -0
- package/dist/icons/index.js +4 -0
- package/dist/index.css +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +12 -0
- package/dist/utils/copy-to-clipboard.d.ts +43 -0
- package/dist/utils/copy-to-clipboard.js +102 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/docs/architecture.md +1 -1
- package/docs/domains/attachments.md +41 -4
- package/docs/domains/components.md +280 -13
- package/docs/domains/utils.md +8 -6
- package/docs/maybe-todo.md +111 -0
- package/package.json +11 -1
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# EmptyState
|
|
2
|
+
|
|
3
|
+
Icon + title + description + CTA placeholder for empty lists, tables, and search
|
|
4
|
+
results. Centered, theme-aware, and non-interactive by itself — the CTA area is a
|
|
5
|
+
snippet you fill with your own `Button`s or links. Pairs naturally with `DataTable`
|
|
6
|
+
("no rows"), search UIs ("no results"), and first-run dashboard tiles.
|
|
7
|
+
|
|
8
|
+
## Props
|
|
9
|
+
|
|
10
|
+
| Prop | Type | Default | Description |
|
|
11
|
+
| ------------------ | ---------------------------------- | --------- | ------------------------------------------------- |
|
|
12
|
+
| `icon` | `THC` | - | Icon area content (e.g. `{ html: iconSearch() }`) |
|
|
13
|
+
| `title` | `THC` | - | Title headline |
|
|
14
|
+
| `description` | `THC` | - | Short supporting text below the title |
|
|
15
|
+
| `variant` | `"plain" \| "outline" \| "dashed"` | `"plain"` | Container treatment (bordered box or none) |
|
|
16
|
+
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | Padding, icon and typography scale |
|
|
17
|
+
| `unstyled` | `boolean` | `false` | Skip all default styling |
|
|
18
|
+
| `class` | `string` | - | Additional CSS classes (merged via twMerge) |
|
|
19
|
+
| `classIcon` | `string` | - | Class for the icon area |
|
|
20
|
+
| `classTitle` | `string` | - | Class for the title |
|
|
21
|
+
| `classDescription` | `string` | - | Class for the description |
|
|
22
|
+
| `classActions` | `string` | - | Class for the actions area |
|
|
23
|
+
| `el` | `HTMLElement` | - | Element reference (bindable) |
|
|
24
|
+
|
|
25
|
+
## Snippet Props
|
|
26
|
+
|
|
27
|
+
| Snippet | Description |
|
|
28
|
+
| ------------ | -------------------------------------------------- |
|
|
29
|
+
| `actions` | CTA area below the description (buttons, links) |
|
|
30
|
+
| `renderIcon` | Override the icon area (instead of the `icon` THC) |
|
|
31
|
+
| `children` | Override the entire default layout |
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
### Basic
|
|
36
|
+
|
|
37
|
+
```svelte
|
|
38
|
+
<script lang="ts">
|
|
39
|
+
import { EmptyState, Button, iconSearch } from "@marianmeres/stuic";
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<EmptyState
|
|
43
|
+
icon={{ html: iconSearch() }}
|
|
44
|
+
title="No results found"
|
|
45
|
+
description="Try adjusting your search or filters."
|
|
46
|
+
/>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### With CTA
|
|
50
|
+
|
|
51
|
+
```svelte
|
|
52
|
+
<EmptyState
|
|
53
|
+
icon={{ html: iconPlus() }}
|
|
54
|
+
title="No projects yet"
|
|
55
|
+
description="Create your first project to get started."
|
|
56
|
+
>
|
|
57
|
+
{#snippet actions()}
|
|
58
|
+
<Button intent="primary">New project</Button>
|
|
59
|
+
<Button variant="ghost">Learn more</Button>
|
|
60
|
+
{/snippet}
|
|
61
|
+
</EmptyState>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Variants
|
|
65
|
+
|
|
66
|
+
```svelte
|
|
67
|
+
<!-- No box (default) — for use inside an already-framed area (table body, panel) -->
|
|
68
|
+
<EmptyState variant="plain" title="Nothing here" />
|
|
69
|
+
|
|
70
|
+
<!-- Bordered container -->
|
|
71
|
+
<EmptyState variant="outline" title="Nothing here" />
|
|
72
|
+
|
|
73
|
+
<!-- Dashed container — the "drop zone / placeholder" look -->
|
|
74
|
+
<EmptyState variant="dashed" title="Nothing here" />
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Sizes
|
|
78
|
+
|
|
79
|
+
```svelte
|
|
80
|
+
<EmptyState size="sm" title="No comments" />
|
|
81
|
+
<EmptyState size="md" title="No comments" />
|
|
82
|
+
<EmptyState size="lg" title="No comments" />
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### With DataTable
|
|
86
|
+
|
|
87
|
+
`DataTable` already renders an `EmptyState` (titled with its `no_data` message) when it
|
|
88
|
+
has no rows. Its `empty` snippet replaces that one, which is where an icon, a
|
|
89
|
+
description and a CTA go:
|
|
90
|
+
|
|
91
|
+
```svelte
|
|
92
|
+
<DataTable {columns} {data}>
|
|
93
|
+
{#snippet empty()}
|
|
94
|
+
<EmptyState
|
|
95
|
+
icon={{ html: iconFile() }}
|
|
96
|
+
title="No documents"
|
|
97
|
+
description="Documents you upload will show up here."
|
|
98
|
+
>
|
|
99
|
+
{#snippet actions()}
|
|
100
|
+
<Button intent="primary">Upload</Button>
|
|
101
|
+
{/snippet}
|
|
102
|
+
</EmptyState>
|
|
103
|
+
{/snippet}
|
|
104
|
+
</DataTable>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Swapping the whole table out is the other option — when the empty case should not look
|
|
108
|
+
like a table at all (no header row, no pager):
|
|
109
|
+
|
|
110
|
+
```svelte
|
|
111
|
+
{#if !rows.length}
|
|
112
|
+
<EmptyState
|
|
113
|
+
icon={{ html: iconFile() }}
|
|
114
|
+
title="No documents"
|
|
115
|
+
description="Documents you upload will show up here."
|
|
116
|
+
>
|
|
117
|
+
{#snippet actions()}
|
|
118
|
+
<Button intent="primary">Upload</Button>
|
|
119
|
+
{/snippet}
|
|
120
|
+
</EmptyState>
|
|
121
|
+
{:else}
|
|
122
|
+
<DataTable {rows} ... />
|
|
123
|
+
{/if}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Custom Styling
|
|
127
|
+
|
|
128
|
+
```svelte
|
|
129
|
+
<!-- Override the entire layout -->
|
|
130
|
+
<EmptyState variant="dashed">
|
|
131
|
+
<img src="/illustrations/empty.svg" alt="" class="w-40" />
|
|
132
|
+
<p>Absolutely nothing.</p>
|
|
133
|
+
</EmptyState>
|
|
134
|
+
|
|
135
|
+
<!-- Override component tokens inline -->
|
|
136
|
+
<EmptyState
|
|
137
|
+
title="Big icon"
|
|
138
|
+
icon={{ html: iconSearch() }}
|
|
139
|
+
style="--stuic-empty-state-icon-size-md: 5rem;"
|
|
140
|
+
/>
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Accessibility
|
|
144
|
+
|
|
145
|
+
The component renders a plain `<div>` with no implicit role. When an empty state
|
|
146
|
+
replaces content dynamically (e.g. search results updating), pass `role="status"`
|
|
147
|
+
so screen readers announce it:
|
|
148
|
+
|
|
149
|
+
```svelte
|
|
150
|
+
<EmptyState role="status" title="No results found" />
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## CSS Variables
|
|
154
|
+
|
|
155
|
+
### Component Tokens
|
|
156
|
+
|
|
157
|
+
| Variable | Default | Description |
|
|
158
|
+
| ------------------------------------------- | -------------------------------- | -------------------------------------- |
|
|
159
|
+
| `--stuic-empty-state-gap` | `0.5rem` | Gap between icon/title/description |
|
|
160
|
+
| `--stuic-empty-state-icon-text` | `--stuic-color-muted-foreground` | Icon color |
|
|
161
|
+
| `--stuic-empty-state-icon-opacity` | `0.8` | Icon opacity |
|
|
162
|
+
| `--stuic-empty-state-icon-margin-bottom` | `0.25rem` | Extra space below the icon |
|
|
163
|
+
| `--stuic-empty-state-title-font-weight` | `--font-weight-semibold` | Title font weight |
|
|
164
|
+
| `--stuic-empty-state-title-text` | `--stuic-color-foreground` | Title color |
|
|
165
|
+
| `--stuic-empty-state-description-text` | `--stuic-color-muted-foreground` | Description color |
|
|
166
|
+
| `--stuic-empty-state-description-max-width` | `45ch` | Description line-length cap |
|
|
167
|
+
| `--stuic-empty-state-actions-gap` | `0.5rem` | Gap between CTA buttons |
|
|
168
|
+
| `--stuic-empty-state-actions-margin-top` | `1rem` | Space above the CTA row |
|
|
169
|
+
| `--stuic-empty-state-bg` | `transparent` | Background |
|
|
170
|
+
| `--stuic-empty-state-border` | `--stuic-color-border` | Border color (outline/dashed variants) |
|
|
171
|
+
| `--stuic-empty-state-border-width` | `--stuic-border-width` | Border width (outline/dashed variants) |
|
|
172
|
+
| `--stuic-empty-state-radius` | `--stuic-radius-container` | Border radius (outline/dashed) |
|
|
173
|
+
|
|
174
|
+
### Size Tokens
|
|
175
|
+
|
|
176
|
+
Each size (sm, md, lg) has corresponding tokens:
|
|
177
|
+
|
|
178
|
+
- `--stuic-empty-state-padding-{size}`
|
|
179
|
+
- `--stuic-empty-state-icon-size-{size}` (applied to any `svg` inside the icon area)
|
|
180
|
+
- `--stuic-empty-state-title-font-size-{size}`
|
|
181
|
+
- `--stuic-empty-state-description-font-size-{size}`
|
|
182
|
+
|
|
183
|
+
## Data Attributes
|
|
184
|
+
|
|
185
|
+
The component uses data attributes for styling:
|
|
186
|
+
|
|
187
|
+
- `data-variant` - `"plain" | "outline" | "dashed"`
|
|
188
|
+
- `data-size` - `"sm" | "md" | "lg"`
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
EMPTY STATE COMPONENT TOKENS
|
|
3
|
+
Override globally: :root { --stuic-empty-state-icon-size-md: 4rem; }
|
|
4
|
+
Override locally: <EmptyState style="--stuic-empty-state-gap: 1rem;">
|
|
5
|
+
============================================================================ */
|
|
6
|
+
|
|
7
|
+
/* prettier-ignore */
|
|
8
|
+
:root {
|
|
9
|
+
--stuic-empty-state-gap: 0.5rem;
|
|
10
|
+
|
|
11
|
+
/* Size: sm */
|
|
12
|
+
--stuic-empty-state-padding-sm: 1.5rem;
|
|
13
|
+
--stuic-empty-state-icon-size-sm: 2rem;
|
|
14
|
+
--stuic-empty-state-title-font-size-sm: var(--text-base);
|
|
15
|
+
--stuic-empty-state-description-font-size-sm: var(--text-xs);
|
|
16
|
+
|
|
17
|
+
/* Size: md */
|
|
18
|
+
--stuic-empty-state-padding-md: 3rem;
|
|
19
|
+
--stuic-empty-state-icon-size-md: 2.5rem;
|
|
20
|
+
--stuic-empty-state-title-font-size-md: var(--text-lg);
|
|
21
|
+
--stuic-empty-state-description-font-size-md: var(--text-sm);
|
|
22
|
+
|
|
23
|
+
/* Size: lg */
|
|
24
|
+
--stuic-empty-state-padding-lg: 4rem;
|
|
25
|
+
--stuic-empty-state-icon-size-lg: 3rem;
|
|
26
|
+
--stuic-empty-state-title-font-size-lg: var(--text-xl);
|
|
27
|
+
--stuic-empty-state-description-font-size-lg: var(--text-base);
|
|
28
|
+
|
|
29
|
+
/* Icon */
|
|
30
|
+
--stuic-empty-state-icon-text: var(--stuic-color-muted-foreground);
|
|
31
|
+
--stuic-empty-state-icon-opacity: 0.8;
|
|
32
|
+
--stuic-empty-state-icon-margin-bottom: 0.25rem;
|
|
33
|
+
|
|
34
|
+
/* Typography */
|
|
35
|
+
--stuic-empty-state-title-font-weight: var(--font-weight-semibold);
|
|
36
|
+
--stuic-empty-state-title-text: var(--stuic-color-foreground);
|
|
37
|
+
--stuic-empty-state-description-text: var(--stuic-color-muted-foreground);
|
|
38
|
+
--stuic-empty-state-description-max-width: 45ch;
|
|
39
|
+
|
|
40
|
+
/* Actions */
|
|
41
|
+
--stuic-empty-state-actions-gap: 0.5rem;
|
|
42
|
+
--stuic-empty-state-actions-margin-top: 1rem;
|
|
43
|
+
|
|
44
|
+
/* Outline/dashed variants */
|
|
45
|
+
--stuic-empty-state-bg: transparent;
|
|
46
|
+
--stuic-empty-state-border: var(--stuic-color-border);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@layer components {
|
|
50
|
+
/* ============================================================================
|
|
51
|
+
BASE STYLES
|
|
52
|
+
============================================================================ */
|
|
53
|
+
|
|
54
|
+
.stuic-empty-state {
|
|
55
|
+
display: flex;
|
|
56
|
+
flex-direction: column;
|
|
57
|
+
align-items: center;
|
|
58
|
+
justify-content: center;
|
|
59
|
+
text-align: center;
|
|
60
|
+
width: 100%;
|
|
61
|
+
gap: var(--stuic-empty-state-gap);
|
|
62
|
+
background: var(--stuic-empty-state-bg);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/* ============================================================================
|
|
66
|
+
SIZE VARIANTS
|
|
67
|
+
Each size sets the scale via internal CSS vars consumed by the child areas.
|
|
68
|
+
============================================================================ */
|
|
69
|
+
|
|
70
|
+
.stuic-empty-state[data-size="sm"] {
|
|
71
|
+
padding: var(--stuic-empty-state-padding-sm);
|
|
72
|
+
--_icon-size: var(--stuic-empty-state-icon-size-sm);
|
|
73
|
+
--_title-font-size: var(--stuic-empty-state-title-font-size-sm);
|
|
74
|
+
--_description-font-size: var(--stuic-empty-state-description-font-size-sm);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.stuic-empty-state[data-size="md"] {
|
|
78
|
+
padding: var(--stuic-empty-state-padding-md);
|
|
79
|
+
--_icon-size: var(--stuic-empty-state-icon-size-md);
|
|
80
|
+
--_title-font-size: var(--stuic-empty-state-title-font-size-md);
|
|
81
|
+
--_description-font-size: var(--stuic-empty-state-description-font-size-md);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.stuic-empty-state[data-size="lg"] {
|
|
85
|
+
padding: var(--stuic-empty-state-padding-lg);
|
|
86
|
+
--_icon-size: var(--stuic-empty-state-icon-size-lg);
|
|
87
|
+
--_title-font-size: var(--stuic-empty-state-title-font-size-lg);
|
|
88
|
+
--_description-font-size: var(--stuic-empty-state-description-font-size-lg);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* ============================================================================
|
|
92
|
+
CONTAINER VARIANTS
|
|
93
|
+
plain (default): no box. outline/dashed: bordered container.
|
|
94
|
+
============================================================================ */
|
|
95
|
+
|
|
96
|
+
.stuic-empty-state[data-variant="outline"],
|
|
97
|
+
.stuic-empty-state[data-variant="dashed"] {
|
|
98
|
+
border: var(--stuic-empty-state-border-width, var(--stuic-border-width)) solid
|
|
99
|
+
var(--stuic-empty-state-border);
|
|
100
|
+
border-radius: var(--stuic-empty-state-radius, var(--stuic-radius-container));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.stuic-empty-state[data-variant="dashed"] {
|
|
104
|
+
border-style: dashed;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/* ============================================================================
|
|
108
|
+
ICON AREA
|
|
109
|
+
============================================================================ */
|
|
110
|
+
|
|
111
|
+
.stuic-empty-state-icon {
|
|
112
|
+
display: flex;
|
|
113
|
+
align-items: center;
|
|
114
|
+
justify-content: center;
|
|
115
|
+
color: var(--stuic-empty-state-icon-text);
|
|
116
|
+
opacity: var(--stuic-empty-state-icon-opacity);
|
|
117
|
+
margin-bottom: var(--stuic-empty-state-icon-margin-bottom);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* Normalize any passed svg icon to the size preset (like DismissibleMessage) */
|
|
121
|
+
.stuic-empty-state-icon svg {
|
|
122
|
+
width: var(--_icon-size);
|
|
123
|
+
height: var(--_icon-size);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/* ============================================================================
|
|
127
|
+
TITLE + DESCRIPTION
|
|
128
|
+
============================================================================ */
|
|
129
|
+
|
|
130
|
+
.stuic-empty-state-title {
|
|
131
|
+
font-size: var(--_title-font-size);
|
|
132
|
+
font-weight: var(--stuic-empty-state-title-font-weight);
|
|
133
|
+
color: var(--stuic-empty-state-title-text);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.stuic-empty-state-description {
|
|
137
|
+
font-size: var(--_description-font-size);
|
|
138
|
+
color: var(--stuic-empty-state-description-text);
|
|
139
|
+
max-width: var(--stuic-empty-state-description-max-width);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/* ============================================================================
|
|
143
|
+
ACTIONS (CTA)
|
|
144
|
+
============================================================================ */
|
|
145
|
+
|
|
146
|
+
.stuic-empty-state-actions {
|
|
147
|
+
display: flex;
|
|
148
|
+
flex-wrap: wrap;
|
|
149
|
+
align-items: center;
|
|
150
|
+
justify-content: center;
|
|
151
|
+
gap: var(--stuic-empty-state-actions-gap);
|
|
152
|
+
margin-top: var(--stuic-empty-state-actions-margin-top);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as EmptyState, type Props as EmptyStateProps, type EmptyStateVariant, type EmptyStateSize, } from "./EmptyState.svelte";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as EmptyState, } from "./EmptyState.svelte";
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
3
|
+
import type { Snippet } from "svelte";
|
|
4
|
+
import type { PagingCalcResult } from "@marianmeres/paging-store";
|
|
5
|
+
import type { TranslateFn } from "../../types.js";
|
|
6
|
+
import type { ButtonSize } from "../Button/Button.svelte";
|
|
7
|
+
|
|
8
|
+
export type PaginationVariant = "compact" | "numbers";
|
|
9
|
+
|
|
10
|
+
export interface Props extends Omit<HTMLAttributes<HTMLElement>, "children"> {
|
|
11
|
+
/**
|
|
12
|
+
* Paging calculation result from `@marianmeres/paging-store` — the exact shape
|
|
13
|
+
* `DataTable` consumes, so both can be fed from the same store or `calculatePaging`
|
|
14
|
+
* call. Takes precedence over the `total`/`limit`/`offset` convenience props.
|
|
15
|
+
*/
|
|
16
|
+
paging?: PagingCalcResult;
|
|
17
|
+
/** Convenience alternative to `paging`: total number of items in the dataset. */
|
|
18
|
+
total?: number;
|
|
19
|
+
/** Convenience alternative to `paging`: items per page (default 10). */
|
|
20
|
+
limit?: number;
|
|
21
|
+
/** Convenience alternative to `paging`: current offset (default 0). */
|
|
22
|
+
offset?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Callback when the user navigates to a different page. First argument is the new
|
|
25
|
+
* offset (`DataTable`'s `onPageChange` signature), second is the 1-based target page.
|
|
26
|
+
*/
|
|
27
|
+
onPageChange?: (offset: number, page: number) => void;
|
|
28
|
+
/**
|
|
29
|
+
* `"compact"` (default): prev / "Page X of Y" / next — the `DataTable` pager.
|
|
30
|
+
* `"numbers"`: windowed page-number buttons with ellipsis gaps.
|
|
31
|
+
*/
|
|
32
|
+
variant?: PaginationVariant;
|
|
33
|
+
/** Numbers variant: pages always shown around the current page (default 1). */
|
|
34
|
+
siblingCount?: number;
|
|
35
|
+
/** Numbers variant: pages always shown at the start and at the end (default 1). */
|
|
36
|
+
boundaryCount?: number;
|
|
37
|
+
/** Render first/last jump buttons (default false). */
|
|
38
|
+
showFirstLast?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Render the "Page X of Y" info. Defaults per variant: `true` for compact (between
|
|
41
|
+
* prev/next), `false` for numbers (after the buttons when enabled).
|
|
42
|
+
*/
|
|
43
|
+
showInfo?: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Render nothing when there is at most one page (default true — the `DataTable`
|
|
46
|
+
* rule: one page of results needs no pager). Set `false` to keep it visible.
|
|
47
|
+
*/
|
|
48
|
+
hideSinglePage?: boolean;
|
|
49
|
+
/** Disable all controls (e.g. while a page of data is loading). */
|
|
50
|
+
disabled?: boolean;
|
|
51
|
+
/** Size preset passed to the inner Buttons (default "sm", as in `DataTable`). */
|
|
52
|
+
size?: ButtonSize;
|
|
53
|
+
/** i18n translate function (see `createPaginationT`). */
|
|
54
|
+
t?: TranslateFn;
|
|
55
|
+
/** Override the "Page X of Y" info content. */
|
|
56
|
+
renderInfo?: Snippet<[PagingCalcResult]>;
|
|
57
|
+
/** Skip all default styling (inner Buttons keep their own Button styling) */
|
|
58
|
+
unstyled?: boolean;
|
|
59
|
+
/** Additional CSS classes */
|
|
60
|
+
class?: string;
|
|
61
|
+
/** Class for every inner button */
|
|
62
|
+
classButton?: string;
|
|
63
|
+
/** Extra class for the current-page button (numbers variant) */
|
|
64
|
+
classButtonCurrent?: string;
|
|
65
|
+
/** Class for the "Page X of Y" info */
|
|
66
|
+
classInfo?: string;
|
|
67
|
+
/** Bindable element reference */
|
|
68
|
+
el?: HTMLElement;
|
|
69
|
+
}
|
|
70
|
+
</script>
|
|
71
|
+
|
|
72
|
+
<script lang="ts">
|
|
73
|
+
import { calculatePaging } from "@marianmeres/paging-store";
|
|
74
|
+
import { twMerge } from "../../utils/tw-merge.js";
|
|
75
|
+
import Button from "../Button/Button.svelte";
|
|
76
|
+
import { t_default } from "./i18n.js";
|
|
77
|
+
import { paginationRange } from "./pagination-range.js";
|
|
78
|
+
|
|
79
|
+
let {
|
|
80
|
+
paging,
|
|
81
|
+
total,
|
|
82
|
+
limit,
|
|
83
|
+
offset,
|
|
84
|
+
onPageChange,
|
|
85
|
+
variant = "compact",
|
|
86
|
+
siblingCount = 1,
|
|
87
|
+
boundaryCount = 1,
|
|
88
|
+
showFirstLast = false,
|
|
89
|
+
showInfo,
|
|
90
|
+
hideSinglePage = true,
|
|
91
|
+
disabled = false,
|
|
92
|
+
size = "sm",
|
|
93
|
+
t = t_default,
|
|
94
|
+
renderInfo,
|
|
95
|
+
unstyled = false,
|
|
96
|
+
class: classProp,
|
|
97
|
+
classButton: classButtonProp,
|
|
98
|
+
classButtonCurrent: classButtonCurrentProp,
|
|
99
|
+
classInfo: classInfoProp,
|
|
100
|
+
el = $bindable(),
|
|
101
|
+
...rest
|
|
102
|
+
}: Props = $props();
|
|
103
|
+
|
|
104
|
+
let _paging: PagingCalcResult | undefined = $derived(
|
|
105
|
+
paging ??
|
|
106
|
+
(total != null || limit != null || offset != null
|
|
107
|
+
? calculatePaging({ total, limit, offset })
|
|
108
|
+
: undefined)
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
let _render = $derived(!!_paging && (_paging.pageCount > 1 || !hideSinglePage));
|
|
112
|
+
let _showInfo = $derived(showInfo ?? variant === "compact");
|
|
113
|
+
|
|
114
|
+
let _pages = $derived(
|
|
115
|
+
variant === "numbers" && _paging
|
|
116
|
+
? paginationRange(_paging.currentPage, _paging.pageCount, {
|
|
117
|
+
siblingCount,
|
|
118
|
+
boundaryCount,
|
|
119
|
+
})
|
|
120
|
+
: []
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
let _class = $derived(unstyled ? classProp : twMerge("stuic-pagination", classProp));
|
|
124
|
+
let _classButton = $derived(
|
|
125
|
+
unstyled ? classButtonProp : twMerge("stuic-pagination-button", classButtonProp)
|
|
126
|
+
);
|
|
127
|
+
let _classInfo = $derived(
|
|
128
|
+
unstyled ? classInfoProp : twMerge("stuic-pagination-info", classInfoProp)
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
function goToPage(page: number) {
|
|
132
|
+
if (!_paging || disabled) return;
|
|
133
|
+
if (page < 1 || page > _paging.pageCount || page === _paging.currentPage) return;
|
|
134
|
+
onPageChange?.((page - 1) * _paging.limit, page);
|
|
135
|
+
}
|
|
136
|
+
</script>
|
|
137
|
+
|
|
138
|
+
{#if _paging && _render}
|
|
139
|
+
<nav
|
|
140
|
+
bind:this={el}
|
|
141
|
+
class={_class}
|
|
142
|
+
data-variant={!unstyled ? variant : undefined}
|
|
143
|
+
aria-label={t("pagination", null, "Pagination")}
|
|
144
|
+
{...rest}
|
|
145
|
+
>
|
|
146
|
+
{#if showFirstLast}
|
|
147
|
+
<Button
|
|
148
|
+
variant="ghost"
|
|
149
|
+
{size}
|
|
150
|
+
class={_classButton}
|
|
151
|
+
disabled={disabled || !_paging.hasPrevious}
|
|
152
|
+
onclick={() => goToPage(1)}
|
|
153
|
+
aria-label={t("first_page")}
|
|
154
|
+
>
|
|
155
|
+
«
|
|
156
|
+
</Button>
|
|
157
|
+
{/if}
|
|
158
|
+
|
|
159
|
+
<Button
|
|
160
|
+
variant="ghost"
|
|
161
|
+
{size}
|
|
162
|
+
class={_classButton}
|
|
163
|
+
disabled={disabled || !_paging.hasPrevious}
|
|
164
|
+
onclick={() => goToPage(_paging!.currentPage - 1)}
|
|
165
|
+
aria-label={t("previous_page")}
|
|
166
|
+
>
|
|
167
|
+
{#if variant === "compact"}
|
|
168
|
+
‹ {t("previous_page")}
|
|
169
|
+
{:else}
|
|
170
|
+
‹
|
|
171
|
+
{/if}
|
|
172
|
+
</Button>
|
|
173
|
+
|
|
174
|
+
{#if variant === "numbers"}
|
|
175
|
+
{#each _pages as item, i (item === "ellipsis" ? `ellipsis-${i}` : item)}
|
|
176
|
+
{#if item === "ellipsis"}
|
|
177
|
+
<span
|
|
178
|
+
class={!unstyled ? "stuic-pagination-ellipsis" : undefined}
|
|
179
|
+
aria-hidden="true"
|
|
180
|
+
>
|
|
181
|
+
…
|
|
182
|
+
</span>
|
|
183
|
+
{:else}
|
|
184
|
+
{@const isCurrent = item === _paging.currentPage}
|
|
185
|
+
<Button
|
|
186
|
+
variant={isCurrent ? "solid" : "ghost"}
|
|
187
|
+
intent={isCurrent ? "primary" : undefined}
|
|
188
|
+
{size}
|
|
189
|
+
class={isCurrent
|
|
190
|
+
? twMerge(_classButton, classButtonCurrentProp)
|
|
191
|
+
: _classButton}
|
|
192
|
+
{disabled}
|
|
193
|
+
aria-label={t("go_to_page_x", { page: item })}
|
|
194
|
+
aria-current={isCurrent ? "page" : undefined}
|
|
195
|
+
data-current={isCurrent ? "true" : undefined}
|
|
196
|
+
onclick={() => goToPage(item)}
|
|
197
|
+
>
|
|
198
|
+
{item}
|
|
199
|
+
</Button>
|
|
200
|
+
{/if}
|
|
201
|
+
{/each}
|
|
202
|
+
{:else if _showInfo}
|
|
203
|
+
<span class={_classInfo}>
|
|
204
|
+
{#if renderInfo}
|
|
205
|
+
{@render renderInfo(_paging)}
|
|
206
|
+
{:else}
|
|
207
|
+
{t("page_x_of_y", { page: _paging.currentPage, pageCount: _paging.pageCount })}
|
|
208
|
+
{/if}
|
|
209
|
+
</span>
|
|
210
|
+
{/if}
|
|
211
|
+
|
|
212
|
+
<Button
|
|
213
|
+
variant="ghost"
|
|
214
|
+
{size}
|
|
215
|
+
class={_classButton}
|
|
216
|
+
disabled={disabled || !_paging.hasNext}
|
|
217
|
+
onclick={() => goToPage(_paging!.currentPage + 1)}
|
|
218
|
+
aria-label={t("next_page")}
|
|
219
|
+
>
|
|
220
|
+
{#if variant === "compact"}
|
|
221
|
+
{t("next_page")} ›
|
|
222
|
+
{:else}
|
|
223
|
+
›
|
|
224
|
+
{/if}
|
|
225
|
+
</Button>
|
|
226
|
+
|
|
227
|
+
{#if showFirstLast}
|
|
228
|
+
<Button
|
|
229
|
+
variant="ghost"
|
|
230
|
+
{size}
|
|
231
|
+
class={_classButton}
|
|
232
|
+
disabled={disabled || !_paging.hasNext}
|
|
233
|
+
onclick={() => goToPage(_paging!.pageCount)}
|
|
234
|
+
aria-label={t("last_page")}
|
|
235
|
+
>
|
|
236
|
+
»
|
|
237
|
+
</Button>
|
|
238
|
+
{/if}
|
|
239
|
+
|
|
240
|
+
{#if variant === "numbers" && _showInfo}
|
|
241
|
+
<span class={_classInfo}>
|
|
242
|
+
{#if renderInfo}
|
|
243
|
+
{@render renderInfo(_paging)}
|
|
244
|
+
{:else}
|
|
245
|
+
{t("page_x_of_y", { page: _paging.currentPage, pageCount: _paging.pageCount })}
|
|
246
|
+
{/if}
|
|
247
|
+
</span>
|
|
248
|
+
{/if}
|
|
249
|
+
</nav>
|
|
250
|
+
{/if}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
import type { PagingCalcResult } from "@marianmeres/paging-store";
|
|
4
|
+
import type { TranslateFn } from "../../types.js";
|
|
5
|
+
import type { ButtonSize } from "../Button/Button.svelte";
|
|
6
|
+
export type PaginationVariant = "compact" | "numbers";
|
|
7
|
+
export interface Props extends Omit<HTMLAttributes<HTMLElement>, "children"> {
|
|
8
|
+
/**
|
|
9
|
+
* Paging calculation result from `@marianmeres/paging-store` — the exact shape
|
|
10
|
+
* `DataTable` consumes, so both can be fed from the same store or `calculatePaging`
|
|
11
|
+
* call. Takes precedence over the `total`/`limit`/`offset` convenience props.
|
|
12
|
+
*/
|
|
13
|
+
paging?: PagingCalcResult;
|
|
14
|
+
/** Convenience alternative to `paging`: total number of items in the dataset. */
|
|
15
|
+
total?: number;
|
|
16
|
+
/** Convenience alternative to `paging`: items per page (default 10). */
|
|
17
|
+
limit?: number;
|
|
18
|
+
/** Convenience alternative to `paging`: current offset (default 0). */
|
|
19
|
+
offset?: number;
|
|
20
|
+
/**
|
|
21
|
+
* Callback when the user navigates to a different page. First argument is the new
|
|
22
|
+
* offset (`DataTable`'s `onPageChange` signature), second is the 1-based target page.
|
|
23
|
+
*/
|
|
24
|
+
onPageChange?: (offset: number, page: number) => void;
|
|
25
|
+
/**
|
|
26
|
+
* `"compact"` (default): prev / "Page X of Y" / next — the `DataTable` pager.
|
|
27
|
+
* `"numbers"`: windowed page-number buttons with ellipsis gaps.
|
|
28
|
+
*/
|
|
29
|
+
variant?: PaginationVariant;
|
|
30
|
+
/** Numbers variant: pages always shown around the current page (default 1). */
|
|
31
|
+
siblingCount?: number;
|
|
32
|
+
/** Numbers variant: pages always shown at the start and at the end (default 1). */
|
|
33
|
+
boundaryCount?: number;
|
|
34
|
+
/** Render first/last jump buttons (default false). */
|
|
35
|
+
showFirstLast?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Render the "Page X of Y" info. Defaults per variant: `true` for compact (between
|
|
38
|
+
* prev/next), `false` for numbers (after the buttons when enabled).
|
|
39
|
+
*/
|
|
40
|
+
showInfo?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Render nothing when there is at most one page (default true — the `DataTable`
|
|
43
|
+
* rule: one page of results needs no pager). Set `false` to keep it visible.
|
|
44
|
+
*/
|
|
45
|
+
hideSinglePage?: boolean;
|
|
46
|
+
/** Disable all controls (e.g. while a page of data is loading). */
|
|
47
|
+
disabled?: boolean;
|
|
48
|
+
/** Size preset passed to the inner Buttons (default "sm", as in `DataTable`). */
|
|
49
|
+
size?: ButtonSize;
|
|
50
|
+
/** i18n translate function (see `createPaginationT`). */
|
|
51
|
+
t?: TranslateFn;
|
|
52
|
+
/** Override the "Page X of Y" info content. */
|
|
53
|
+
renderInfo?: Snippet<[PagingCalcResult]>;
|
|
54
|
+
/** Skip all default styling (inner Buttons keep their own Button styling) */
|
|
55
|
+
unstyled?: boolean;
|
|
56
|
+
/** Additional CSS classes */
|
|
57
|
+
class?: string;
|
|
58
|
+
/** Class for every inner button */
|
|
59
|
+
classButton?: string;
|
|
60
|
+
/** Extra class for the current-page button (numbers variant) */
|
|
61
|
+
classButtonCurrent?: string;
|
|
62
|
+
/** Class for the "Page X of Y" info */
|
|
63
|
+
classInfo?: string;
|
|
64
|
+
/** Bindable element reference */
|
|
65
|
+
el?: HTMLElement;
|
|
66
|
+
}
|
|
67
|
+
declare const Pagination: import("svelte").Component<Props, {}, "el">;
|
|
68
|
+
type Pagination = ReturnType<typeof Pagination>;
|
|
69
|
+
export default Pagination;
|