@marianmeres/stuic 3.167.0 → 3.169.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.
Files changed (115) hide show
  1. package/AGENTS.md +6 -6
  2. package/dist/README.md +1 -1
  3. package/dist/attachments/index.d.ts +1 -0
  4. package/dist/attachments/index.js +1 -0
  5. package/dist/attachments/long-press.d.ts +43 -0
  6. package/dist/attachments/long-press.js +107 -0
  7. package/dist/components/Breadcrumbs/Breadcrumbs.svelte +198 -0
  8. package/dist/components/Breadcrumbs/Breadcrumbs.svelte.d.ts +60 -0
  9. package/dist/components/Breadcrumbs/README.md +174 -0
  10. package/dist/components/Breadcrumbs/i18n-sk.d.ts +17 -0
  11. package/dist/components/Breadcrumbs/i18n-sk.js +19 -0
  12. package/dist/components/Breadcrumbs/i18n.d.ts +31 -0
  13. package/dist/components/Breadcrumbs/i18n.js +40 -0
  14. package/dist/components/Breadcrumbs/index.css +84 -0
  15. package/dist/components/Breadcrumbs/index.d.ts +4 -0
  16. package/dist/components/Breadcrumbs/index.js +4 -0
  17. package/dist/components/Breadcrumbs/json-ld.d.ts +58 -0
  18. package/dist/components/Breadcrumbs/json-ld.js +56 -0
  19. package/dist/components/Collapsible/Collapsible.svelte +84 -15
  20. package/dist/components/Collapsible/Collapsible.svelte.d.ts +29 -2
  21. package/dist/components/Collapsible/README.md +82 -23
  22. package/dist/components/Collapsible/index.css +38 -0
  23. package/dist/components/Collapsible/index.d.ts +1 -1
  24. package/dist/components/ContextMenu/ContextMenu.svelte +302 -0
  25. package/dist/components/ContextMenu/ContextMenu.svelte.d.ts +94 -0
  26. package/dist/components/ContextMenu/README.md +159 -0
  27. package/dist/components/ContextMenu/i18n-sk.d.ts +17 -0
  28. package/dist/components/ContextMenu/i18n-sk.js +18 -0
  29. package/dist/components/ContextMenu/i18n.d.ts +30 -0
  30. package/dist/components/ContextMenu/i18n.js +38 -0
  31. package/dist/components/ContextMenu/index.css +26 -0
  32. package/dist/components/ContextMenu/index.d.ts +3 -0
  33. package/dist/components/ContextMenu/index.js +3 -0
  34. package/dist/components/CopyButton/CopyButton.svelte +278 -0
  35. package/dist/components/CopyButton/CopyButton.svelte.d.ts +76 -0
  36. package/dist/components/CopyButton/README.md +241 -0
  37. package/dist/components/CopyButton/i18n-sk.d.ts +17 -0
  38. package/dist/components/CopyButton/i18n-sk.js +20 -0
  39. package/dist/components/CopyButton/i18n.d.ts +35 -0
  40. package/dist/components/CopyButton/i18n.js +43 -0
  41. package/dist/components/CopyButton/index.css +67 -0
  42. package/dist/components/CopyButton/index.d.ts +3 -0
  43. package/dist/components/CopyButton/index.js +3 -0
  44. package/dist/components/DataTable/DataTable.svelte +41 -34
  45. package/dist/components/DataTable/README.md +45 -0
  46. package/dist/components/DataTable/i18n-sk.js +1 -0
  47. package/dist/components/DataTable/i18n.d.ts +1 -0
  48. package/dist/components/DataTable/i18n.js +3 -0
  49. package/dist/components/DataTable/index.css +41 -3
  50. package/dist/components/EmptyState/EmptyState.svelte +121 -0
  51. package/dist/components/EmptyState/EmptyState.svelte.d.ts +40 -0
  52. package/dist/components/EmptyState/README.md +188 -0
  53. package/dist/components/EmptyState/index.css +154 -0
  54. package/dist/components/EmptyState/index.d.ts +1 -0
  55. package/dist/components/EmptyState/index.js +1 -0
  56. package/dist/components/Input/FieldOptions.svelte +92 -2
  57. package/dist/components/Input/FieldOptions.svelte.d.ts +13 -0
  58. package/dist/components/Input/README.md +66 -31
  59. package/dist/components/Input/_internal/FieldLikeChips.svelte +286 -0
  60. package/dist/components/Input/_internal/FieldLikeChips.svelte.d.ts +65 -0
  61. package/dist/components/Input/index.css +58 -0
  62. package/dist/components/Pagination/Pagination.svelte +250 -0
  63. package/dist/components/Pagination/Pagination.svelte.d.ts +69 -0
  64. package/dist/components/Pagination/README.md +152 -0
  65. package/dist/components/Pagination/i18n-sk.d.ts +17 -0
  66. package/dist/components/Pagination/i18n-sk.js +24 -0
  67. package/dist/components/Pagination/i18n.d.ts +41 -0
  68. package/dist/components/Pagination/i18n.js +49 -0
  69. package/dist/components/Pagination/index.css +50 -0
  70. package/dist/components/Pagination/index.d.ts +4 -0
  71. package/dist/components/Pagination/index.js +4 -0
  72. package/dist/components/Pagination/pagination-range.d.ts +21 -0
  73. package/dist/components/Pagination/pagination-range.js +41 -0
  74. package/dist/components/Pill/Pill.svelte +8 -2
  75. package/dist/components/Pill/Pill.svelte.d.ts +2 -0
  76. package/dist/components/Pill/README.md +35 -22
  77. package/dist/components/Pill/index.css +57 -18
  78. package/dist/components/Stat/README.md +157 -0
  79. package/dist/components/Stat/Stat.svelte +226 -0
  80. package/dist/components/Stat/Stat.svelte.d.ts +54 -0
  81. package/dist/components/Stat/index.css +217 -0
  82. package/dist/components/Stat/index.d.ts +1 -0
  83. package/dist/components/Stat/index.js +1 -0
  84. package/dist/components/Stepper/README.md +181 -0
  85. package/dist/components/Stepper/Stepper.svelte +240 -0
  86. package/dist/components/Stepper/Stepper.svelte.d.ts +86 -0
  87. package/dist/components/Stepper/i18n-sk.d.ts +17 -0
  88. package/dist/components/Stepper/i18n-sk.js +21 -0
  89. package/dist/components/Stepper/i18n.d.ts +35 -0
  90. package/dist/components/Stepper/i18n.js +43 -0
  91. package/dist/components/Stepper/index.css +302 -0
  92. package/dist/components/Stepper/index.d.ts +3 -0
  93. package/dist/components/Stepper/index.js +3 -0
  94. package/dist/components/TrendChart/README.md +123 -0
  95. package/dist/components/TrendChart/TrendChart.svelte +114 -0
  96. package/dist/components/TrendChart/TrendChart.svelte.d.ts +36 -0
  97. package/dist/components/TrendChart/index.css +51 -0
  98. package/dist/components/TrendChart/index.d.ts +1 -0
  99. package/dist/components/TrendChart/index.js +1 -0
  100. package/dist/icons/index.d.ts +4 -0
  101. package/dist/icons/index.js +4 -0
  102. package/dist/index.css +7 -0
  103. package/dist/index.d.ts +7 -0
  104. package/dist/index.js +12 -0
  105. package/dist/utils/copy-to-clipboard.d.ts +43 -0
  106. package/dist/utils/copy-to-clipboard.js +102 -0
  107. package/dist/utils/index.d.ts +1 -0
  108. package/dist/utils/index.js +1 -0
  109. package/docs/architecture.md +1 -1
  110. package/docs/domains/attachments.md +41 -4
  111. package/docs/domains/components.md +280 -13
  112. package/docs/domains/utils.md +8 -6
  113. package/docs/maybe-todo.md +132 -0
  114. package/docs/upgrading.md +6 -0
  115. package/package.json +11 -1
@@ -0,0 +1,35 @@
1
+ import type { TranslateFn } from "../../types.js";
2
+ /**
3
+ * The built-in (English) message catalog of `CopyButton`. Also the fallback of every
4
+ * other bundled locale, so a locale missing a key still renders text.
5
+ */
6
+ export declare const COPY_BUTTON_MESSAGES_EN: {
7
+ /** Idle: accessible name of the icon-only button, and the default `label` */
8
+ copy: string;
9
+ /** After a successful copy: label, accessible name, and the live announcement */
10
+ copied: string;
11
+ /** After a failed copy: label, accessible name, and the live announcement */
12
+ copy_failed: string;
13
+ };
14
+ /** Every message key `CopyButton` may look up. */
15
+ export type CopyButtonMessageKey = keyof typeof COPY_BUTTON_MESSAGES_EN;
16
+ /** A (possibly partial) catalog for one locale. */
17
+ export type CopyButtonMessages = Record<CopyButtonMessageKey, string>;
18
+ /**
19
+ * Builds the `t` prop of `CopyButton` from a message catalog. Unknown or untranslated
20
+ * keys fall back to `fallbackMessages` (English by default), so a catalog may safely be
21
+ * partial and never renders a raw key.
22
+ *
23
+ * @example
24
+ * ```svelte
25
+ * <script>
26
+ * import { CopyButton, createCopyButtonT, COPY_BUTTON_MESSAGES_SK } from "@marianmeres/stuic";
27
+ * const t = createCopyButtonT(COPY_BUTTON_MESSAGES_SK);
28
+ * </script>
29
+ *
30
+ * <CopyButton text={url} {t} />
31
+ * ```
32
+ */
33
+ export declare function createCopyButtonT(messages: Partial<CopyButtonMessages> | Record<string, string>, fallbackMessages?: Partial<CopyButtonMessages> | Record<string, string>): TranslateFn;
34
+ /** The component's built-in English `t`. */
35
+ export declare const t_default: TranslateFn;
@@ -0,0 +1,43 @@
1
+ import { isPlainObject } from "../../utils/is-plain-object.js";
2
+ import { replaceMap } from "../../utils/replace-map.js";
3
+ /**
4
+ * The built-in (English) message catalog of `CopyButton`. Also the fallback of every
5
+ * other bundled locale, so a locale missing a key still renders text.
6
+ */
7
+ export const COPY_BUTTON_MESSAGES_EN = {
8
+ /** Idle: accessible name of the icon-only button, and the default `label` */
9
+ copy: "Copy",
10
+ /** After a successful copy: label, accessible name, and the live announcement */
11
+ copied: "Copied",
12
+ /** After a failed copy: label, accessible name, and the live announcement */
13
+ copy_failed: "Copy failed",
14
+ };
15
+ /**
16
+ * Builds the `t` prop of `CopyButton` from a message catalog. Unknown or untranslated
17
+ * keys fall back to `fallbackMessages` (English by default), so a catalog may safely be
18
+ * partial and never renders a raw key.
19
+ *
20
+ * @example
21
+ * ```svelte
22
+ * <script>
23
+ * import { CopyButton, createCopyButtonT, COPY_BUTTON_MESSAGES_SK } from "@marianmeres/stuic";
24
+ * const t = createCopyButtonT(COPY_BUTTON_MESSAGES_SK);
25
+ * </script>
26
+ *
27
+ * <CopyButton text={url} {t} />
28
+ * ```
29
+ */
30
+ export function createCopyButtonT(messages, fallbackMessages = COPY_BUTTON_MESSAGES_EN) {
31
+ return (k, values = null, fallback = "") => {
32
+ const out = messages[k] ??
33
+ fallbackMessages[k] ??
34
+ (typeof fallback === "string" ? fallback : k);
35
+ return isPlainObject(values)
36
+ ? replaceMap(out, values, {
37
+ preSearchKeyTransform: (k) => `{${k}}`,
38
+ })
39
+ : out;
40
+ };
41
+ }
42
+ /** The component's built-in English `t`. */
43
+ export const t_default = createCopyButtonT(COPY_BUTTON_MESSAGES_EN);
@@ -0,0 +1,67 @@
1
+ /* ============================================================================
2
+ COPY BUTTON COMPONENT TOKENS
3
+ The button itself IS a Button — colors, sizes, radius and focus ring theme via
4
+ the --stuic-button-* tokens. These tokens only cover the icon and the feedback
5
+ "pop" when the state flips to copied/error.
6
+ Override globally: :root { --stuic-copy-button-icon-size: 1em; }
7
+ Override locally: <CopyButton style="--stuic-copy-button-icon-size: 1em;">
8
+ ============================================================================ */
9
+
10
+ /* prettier-ignore */
11
+ :root {
12
+ /* Icon (any svg inside the icon wrapper is normalized to this) */
13
+ --stuic-copy-button-icon-size: 1.25em;
14
+
15
+ /* Feedback pop: the icon scales up from this factor when the state changes */
16
+ --stuic-copy-button-icon-pop-duration: 200ms;
17
+ --stuic-copy-button-icon-pop-scale: 0.6;
18
+ }
19
+
20
+ @layer components {
21
+ /* ============================================================================
22
+ ICON
23
+ ============================================================================ */
24
+
25
+ .stuic-copy-button-icon {
26
+ display: inline-flex;
27
+ align-items: center;
28
+ justify-content: center;
29
+ flex: none;
30
+ }
31
+
32
+ .stuic-copy-button-icon svg {
33
+ width: var(--stuic-copy-button-icon-size);
34
+ height: var(--stuic-copy-button-icon-size);
35
+ }
36
+
37
+ /* ============================================================================
38
+ FEEDBACK POP
39
+ Runs once when [data-state] flips to copied/error (the selector starts
40
+ matching). Re-clicking within the feedback window keeps the state and so
41
+ does not replay it.
42
+ ============================================================================ */
43
+
44
+ .stuic-copy-button[data-state="copied"] .stuic-copy-button-icon,
45
+ .stuic-copy-button[data-state="error"] .stuic-copy-button-icon {
46
+ animation: stuic-copy-button-icon-pop var(--stuic-copy-button-icon-pop-duration)
47
+ ease-out;
48
+ }
49
+
50
+ @media (prefers-reduced-motion: reduce) {
51
+ .stuic-copy-button[data-state="copied"] .stuic-copy-button-icon,
52
+ .stuic-copy-button[data-state="error"] .stuic-copy-button-icon {
53
+ animation: none;
54
+ }
55
+ }
56
+ }
57
+
58
+ @keyframes stuic-copy-button-icon-pop {
59
+ from {
60
+ transform: scale(var(--stuic-copy-button-icon-pop-scale));
61
+ opacity: 0.4;
62
+ }
63
+ to {
64
+ transform: scale(1);
65
+ opacity: 1;
66
+ }
67
+ }
@@ -0,0 +1,3 @@
1
+ export { default as CopyButton, type Props as CopyButtonProps, type CopyButtonState, type CopyButtonText, } from "./CopyButton.svelte";
2
+ export { createCopyButtonT, COPY_BUTTON_MESSAGES_EN, type CopyButtonMessageKey, type CopyButtonMessages, } from "./i18n.js";
3
+ export { COPY_BUTTON_MESSAGES_SK } from "./i18n-sk.js";
@@ -0,0 +1,3 @@
1
+ export { default as CopyButton, } from "./CopyButton.svelte";
2
+ export { createCopyButtonT, COPY_BUTTON_MESSAGES_EN, } from "./i18n.js";
3
+ export { COPY_BUTTON_MESSAGES_SK } from "./i18n-sk.js";
@@ -235,6 +235,8 @@
235
235
  import { Breakpoint } from "../../utils/breakpoint.svelte.js";
236
236
  import Spinner from "../Spinner/Spinner.svelte";
237
237
  import Button from "../Button/Button.svelte";
238
+ import EmptyState from "../EmptyState/EmptyState.svelte";
239
+ import Pagination from "../Pagination/Pagination.svelte";
238
240
  import Thc, { isTHCNotEmpty, getTHCStringContent } from "../Thc/Thc.svelte";
239
241
 
240
242
  let {
@@ -517,6 +519,25 @@
517
519
  {/if}
518
520
  {/snippet}
519
521
 
522
+ <!--
523
+ The empty state, shared by both layouts. The built-in one is an `EmptyState`; its
524
+ typography is pulled back to table chrome (muted, cell-sized, no headline weight) by
525
+ the `--stuic-empty-state-*` overrides in index.css, so it reads exactly as the bare
526
+ `t("no_data")` string it replaces. An `empty` snippet still replaces it whole.
527
+ -->
528
+ {#snippet emptyBody()}
529
+ {#if empty}
530
+ {@render empty()}
531
+ {:else}
532
+ <EmptyState
533
+ title={t("no_data")}
534
+ size="sm"
535
+ {unstyled}
536
+ class={!unstyled ? "stuic-data-table-empty-state" : undefined}
537
+ />
538
+ {/if}
539
+ {/snippet}
540
+
520
541
  <!--
521
542
  Selection bar -- one strip carrying the selection status, the consumer's
522
543
  `batchActions`, and the select-all-across-pages offer. Deliberately a single element
@@ -668,11 +689,7 @@
668
689
  colspan={columns.length + (selectable ? 1 : 0)}
669
690
  class={!unstyled ? "stuic-data-table-empty" : undefined}
670
691
  >
671
- {#if empty}
672
- {@render empty()}
673
- {:else}
674
- {t("no_data")}
675
- {/if}
692
+ {@render emptyBody()}
676
693
  </td>
677
694
  </tr>
678
695
  {/each}
@@ -749,11 +766,7 @@
749
766
  {/if}
750
767
  {:else}
751
768
  <div class={!unstyled ? "stuic-data-table-empty" : undefined}>
752
- {#if empty}
753
- {@render empty()}
754
- {:else}
755
- {t("no_data")}
756
- {/if}
769
+ {@render emptyBody()}
757
770
  </div>
758
771
  {/each}
759
772
  </div>
@@ -766,30 +779,24 @@
766
779
  </div>
767
780
  {/if}
768
781
 
769
- <!-- Paging -->
782
+ <!--
783
+ Paging -- the built-in pager IS a `Pagination` in its default "compact" variant,
784
+ which is exactly the prev / "Page X of Y" / next control this used to inline.
785
+ `.stuic-data-table-paging` moves onto its `<nav>` rather than wrapping it, so the
786
+ region keeps its class (and its `--stuic-data-table-paging-*` tokens, re-pointed
787
+ at Pagination's in index.css) without an extra node.
788
+
789
+ Note `Pagination`'s own single-page rule is left at its default: `renderPager`
790
+ already encodes it, plus `showPager`.
791
+ -->
770
792
  {#if renderPager && paging}
771
- <div class={!unstyled ? "stuic-data-table-paging" : undefined}>
772
- <Button
773
- variant="ghost"
774
- size="sm"
775
- disabled={!paging.hasPrevious}
776
- onclick={() => onPageChange?.(paging!.previousOffset)}
777
- aria-label={t("previous_page")}
778
- >
779
- &lsaquo; {t("previous_page")}
780
- </Button>
781
- <span class={!unstyled ? "stuic-data-table-paging-info" : undefined}>
782
- {t("page_x_of_y", { page: paging.currentPage, pageCount: paging.pageCount })}
783
- </span>
784
- <Button
785
- variant="ghost"
786
- size="sm"
787
- disabled={!paging.hasNext}
788
- onclick={() => onPageChange?.(paging!.nextOffset)}
789
- aria-label={t("next_page")}
790
- >
791
- {t("next_page")} &rsaquo;
792
- </Button>
793
- </div>
793
+ <Pagination
794
+ {paging}
795
+ {onPageChange}
796
+ {t}
797
+ {unstyled}
798
+ class={!unstyled ? "stuic-data-table-paging" : undefined}
799
+ classInfo={!unstyled ? "stuic-data-table-paging-info" : undefined}
800
+ />
794
801
  {/if}
795
802
  </div>
@@ -75,6 +75,50 @@ so your own control is the single thing driving the offset; don't wire both.
75
75
  (The node's class stays `.stuic-data-table-paging` and its `--stuic-data-table-paging-*`
76
76
  tokens are unchanged -- `showPager` names the control, the CSS names the region.)
77
77
 
78
+ ### What the built-in pager and empty state are made of
79
+
80
+ Neither is bespoke markup any more:
81
+
82
+ - the pager is a [`Pagination`](../Pagination/README.md) in its default `"compact"`
83
+ variant, rendered with `.stuic-data-table-paging` on its own `<nav>`;
84
+ - the default empty state (no `empty` snippet) is an
85
+ [`EmptyState`](../EmptyState/README.md) at `size="sm"`, carrying the marker class
86
+ `.stuic-data-table-empty-state` inside the usual `.stuic-data-table-empty` cell.
87
+
88
+ The table's `--stuic-data-table-*` tokens are re-pointed at the inner components' own
89
+ tokens, so an existing override still drives them, and the `t` you pass `DataTable` is
90
+ the one the pager uses -- the shared `previous_page`, `next_page` and `page_x_of_y`
91
+ keys are identical in both catalogs, so nothing to wire.
92
+
93
+ Two things did change:
94
+
95
+ - the pager is a `<nav>` landmark named by the new `pagination` key, where it used to
96
+ be a plain `<div>`. Same buttons, same labels, same `onPageChange` offsets;
97
+ - the built-in empty state now gets the `2rem` of padding `.stuic-data-table-empty` has
98
+ always declared. On desktop it never actually got it (the more specific
99
+ `.stuic-data-table td` padding rule won), so that cell is taller than it was, and now
100
+ matches the mobile card layout, which already had it. Typography is unchanged.
101
+
102
+ An `empty` snippet is untouched by any of this -- it replaces the built-in `EmptyState`
103
+ outright and keeps the container padding it has always had. Rendering your own
104
+ `EmptyState` in it is a perfectly good way to get an icon, a description and a CTA:
105
+
106
+ ```svelte
107
+ <DataTable {columns} {data}>
108
+ {#snippet empty()}
109
+ <EmptyState
110
+ icon={{ html: iconSearch({ size: 48 }) }}
111
+ title="No matches"
112
+ description="Try widening the filter."
113
+ >
114
+ {#snippet actions()}
115
+ <Button onclick={resetFilters}>Reset filters</Button>
116
+ {/snippet}
117
+ </EmptyState>
118
+ {/snippet}
119
+ </DataTable>
120
+ ```
121
+
78
122
  ### With Selection
79
123
 
80
124
  ```svelte
@@ -437,6 +481,7 @@ const t = createDataTableT({ ...DATA_TABLE_MESSAGES_SK, no_data: "Nič tu nie je
437
481
  | `previous_page` | Prev | Paging |
438
482
  | `next_page` | Next | Paging |
439
483
  | `page_x_of_y` | Page {page} of {pageCount} | Paging |
484
+ | `pagination` | Pagination | Pager `<nav>` `aria-label` |
440
485
  | `no_data` | No data | Empty state (no `empty` snippet) |
441
486
  | `select_all_rows` | Select all rows on this page | Header checkbox `aria-label` |
442
487
  | `select_row` | Select row | Row checkbox `aria-label` |
@@ -17,6 +17,7 @@ export const DATA_TABLE_MESSAGES_SK = {
17
17
  previous_page: "Späť",
18
18
  next_page: "Ďalej",
19
19
  page_x_of_y: "Strana {page} z {pageCount}",
20
+ pagination: "Stránkovanie",
20
21
  no_data: "Žiadne údaje",
21
22
  select_all_rows: "Vybrať všetky riadky na tejto strane",
22
23
  select_row: "Vybrať riadok",
@@ -9,6 +9,7 @@ export declare const DATA_TABLE_MESSAGES_EN: {
9
9
  previous_page: string;
10
10
  next_page: string;
11
11
  page_x_of_y: string;
12
+ pagination: string;
12
13
  no_data: string;
13
14
  select_all_rows: string;
14
15
  select_row: string;
@@ -10,6 +10,9 @@ export const DATA_TABLE_MESSAGES_EN = {
10
10
  previous_page: "Prev",
11
11
  next_page: "Next",
12
12
  page_x_of_y: "Page {page} of {pageCount}",
13
+ // The pager is a `Pagination`; this is its `<nav>` accessible name. Identical to
14
+ // `PAGINATION_MESSAGES_EN` so one merged catalog can still serve both components.
15
+ pagination: "Pagination",
13
16
  no_data: "No data",
14
17
  select_all_rows: "Select all rows on this page",
15
18
  select_row: "Select row",
@@ -224,18 +224,31 @@
224
224
  PAGING
225
225
  ============================================================================ */
226
226
 
227
+ /*
228
+ The built-in pager IS a `Pagination` (compact variant), so these two classes now
229
+ sit on Pagination's own nodes alongside `.stuic-pagination`. They add the region's
230
+ top padding (which Pagination has no notion of) and re-point Pagination's tokens
231
+ at the table's -- as custom properties rather than plain declarations on purpose:
232
+ an existing `--stuic-data-table-paging-*` / `--stuic-data-table-cell-font-size`
233
+ override keeps driving the pager, with no dependence on which of the two
234
+ same-specificity rules happens to win the cascade. The layout declarations below
235
+ are a deliberate duplicate of Pagination's own, so the region class stays
236
+ self-sufficient.
237
+ */
227
238
  .stuic-data-table-paging {
228
239
  display: flex;
229
240
  align-items: center;
230
241
  justify-content: center;
231
- gap: var(--stuic-data-table-paging-gap);
232
242
  padding-top: var(--stuic-data-table-paging-padding-y);
243
+
244
+ --stuic-pagination-gap: var(--stuic-data-table-paging-gap);
233
245
  }
234
246
 
235
247
  .stuic-data-table-paging-info {
236
- font-size: var(--stuic-data-table-cell-font-size);
237
- color: var(--stuic-data-table-header-color);
238
248
  white-space: nowrap;
249
+
250
+ --stuic-pagination-info-font-size: var(--stuic-data-table-cell-font-size);
251
+ --stuic-pagination-info-text: var(--stuic-data-table-header-color);
239
252
  }
240
253
 
241
254
  /* ============================================================================
@@ -322,6 +335,31 @@
322
335
  justify-content: center;
323
336
  }
324
337
 
338
+ /*
339
+ The built-in empty state is an `EmptyState`, which brings its own padding -- which
340
+ would otherwise stack on the container's. Keyed on the marker class rather than on
341
+ `.stuic-empty-state` so that an `empty` snippet rendering an EmptyState of its own
342
+ is left alone: it keeps the container padding it has always had, and the
343
+ component's own look.
344
+ */
345
+ .stuic-data-table-empty:has(> .stuic-data-table-empty-state) {
346
+ padding: 0;
347
+ }
348
+
349
+ /*
350
+ Pull the built-in EmptyState back to table chrome: the "No data" line is a cell
351
+ note, not a headline. Fed from the tokens the rest of the table uses, so it stays
352
+ in sync with a consumer's overrides.
353
+ */
354
+ .stuic-data-table-empty-state {
355
+ --stuic-empty-state-padding-sm: 2rem;
356
+ --stuic-empty-state-title-font-size-sm: var(--stuic-data-table-cell-font-size);
357
+ /* NOT `inherit`: a CSS-wide keyword in a custom property applies to the property
358
+ itself, so it would inherit the :root value (semibold) rather than the cell's. */
359
+ --stuic-empty-state-title-font-weight: normal;
360
+ --stuic-empty-state-title-text: var(--stuic-data-table-header-color);
361
+ }
362
+
325
363
  /* ============================================================================
326
364
  MOBILE CARDS
327
365
  ============================================================================ */
@@ -0,0 +1,121 @@
1
+ <script lang="ts" module>
2
+ import type { HTMLAttributes } from "svelte/elements";
3
+ import type { Snippet } from "svelte";
4
+ import type { THC } from "../Thc/Thc.svelte";
5
+
6
+ export type EmptyStateVariant = "plain" | "outline" | "dashed";
7
+ export type EmptyStateSize = "sm" | "md" | "lg";
8
+
9
+ export interface Props extends Omit<
10
+ HTMLAttributes<HTMLDivElement>,
11
+ "children" | "title"
12
+ > {
13
+ /** Icon area content (typically `{ html: iconSearch({ size: 48 }) }`) */
14
+ icon?: THC;
15
+ /** Title (the "Nothing here yet" headline) */
16
+ title?: THC;
17
+ /** Short supporting description below the title */
18
+ description?: THC;
19
+ /** Container treatment */
20
+ variant?: EmptyStateVariant;
21
+ /** Size preset (padding, icon and typography scale) */
22
+ size?: EmptyStateSize;
23
+ /** CTA area below the description (buttons, links) */
24
+ actions?: Snippet;
25
+ /** Override the entire default layout */
26
+ children?: Snippet;
27
+ /** Override the icon area */
28
+ renderIcon?: Snippet;
29
+ /** Skip all default styling */
30
+ unstyled?: boolean;
31
+ /** Additional CSS classes */
32
+ class?: string;
33
+ /** Class for the icon area */
34
+ classIcon?: string;
35
+ /** Class for the title */
36
+ classTitle?: string;
37
+ /** Class for the description */
38
+ classDescription?: string;
39
+ /** Class for the actions area */
40
+ classActions?: string;
41
+ /** Bindable element reference */
42
+ el?: HTMLElement;
43
+ }
44
+ </script>
45
+
46
+ <script lang="ts">
47
+ import { twMerge } from "../../utils/tw-merge.js";
48
+ import Thc from "../Thc/Thc.svelte";
49
+
50
+ let {
51
+ icon,
52
+ title,
53
+ description,
54
+ variant = "plain",
55
+ size = "md",
56
+ actions,
57
+ children,
58
+ renderIcon,
59
+ unstyled = false,
60
+ class: classProp,
61
+ classIcon: classIconProp,
62
+ classTitle: classTitleProp,
63
+ classDescription: classDescriptionProp,
64
+ classActions: classActionsProp,
65
+ el = $bindable(),
66
+ ...rest
67
+ }: Props = $props();
68
+
69
+ let _class = $derived(unstyled ? classProp : twMerge("stuic-empty-state", classProp));
70
+ let _classIcon = $derived(
71
+ unstyled ? classIconProp : twMerge("stuic-empty-state-icon", classIconProp)
72
+ );
73
+ let _classTitle = $derived(
74
+ unstyled ? classTitleProp : twMerge("stuic-empty-state-title", classTitleProp)
75
+ );
76
+ let _classDescription = $derived(
77
+ unstyled
78
+ ? classDescriptionProp
79
+ : twMerge("stuic-empty-state-description", classDescriptionProp)
80
+ );
81
+ let _classActions = $derived(
82
+ unstyled ? classActionsProp : twMerge("stuic-empty-state-actions", classActionsProp)
83
+ );
84
+ </script>
85
+
86
+ <div
87
+ bind:this={el}
88
+ class={_class}
89
+ data-variant={!unstyled ? variant : undefined}
90
+ data-size={!unstyled ? size : undefined}
91
+ {...rest}
92
+ >
93
+ {#if children}
94
+ {@render children()}
95
+ {:else}
96
+ {#if renderIcon || icon}
97
+ <div class={_classIcon}>
98
+ {#if renderIcon}
99
+ {@render renderIcon()}
100
+ {:else if icon}
101
+ <Thc thc={icon} />
102
+ {/if}
103
+ </div>
104
+ {/if}
105
+ {#if title}
106
+ <div class={_classTitle}>
107
+ <Thc thc={title} />
108
+ </div>
109
+ {/if}
110
+ {#if description}
111
+ <div class={_classDescription}>
112
+ <Thc thc={description} />
113
+ </div>
114
+ {/if}
115
+ {#if actions}
116
+ <div class={_classActions}>
117
+ {@render actions()}
118
+ </div>
119
+ {/if}
120
+ {/if}
121
+ </div>
@@ -0,0 +1,40 @@
1
+ import type { HTMLAttributes } from "svelte/elements";
2
+ import type { Snippet } from "svelte";
3
+ import type { THC } from "../Thc/Thc.svelte";
4
+ export type EmptyStateVariant = "plain" | "outline" | "dashed";
5
+ export type EmptyStateSize = "sm" | "md" | "lg";
6
+ export interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "children" | "title"> {
7
+ /** Icon area content (typically `{ html: iconSearch({ size: 48 }) }`) */
8
+ icon?: THC;
9
+ /** Title (the "Nothing here yet" headline) */
10
+ title?: THC;
11
+ /** Short supporting description below the title */
12
+ description?: THC;
13
+ /** Container treatment */
14
+ variant?: EmptyStateVariant;
15
+ /** Size preset (padding, icon and typography scale) */
16
+ size?: EmptyStateSize;
17
+ /** CTA area below the description (buttons, links) */
18
+ actions?: Snippet;
19
+ /** Override the entire default layout */
20
+ children?: Snippet;
21
+ /** Override the icon area */
22
+ renderIcon?: Snippet;
23
+ /** Skip all default styling */
24
+ unstyled?: boolean;
25
+ /** Additional CSS classes */
26
+ class?: string;
27
+ /** Class for the icon area */
28
+ classIcon?: string;
29
+ /** Class for the title */
30
+ classTitle?: string;
31
+ /** Class for the description */
32
+ classDescription?: string;
33
+ /** Class for the actions area */
34
+ classActions?: string;
35
+ /** Bindable element reference */
36
+ el?: HTMLElement;
37
+ }
38
+ declare const EmptyState: import("svelte").Component<Props, {}, "el">;
39
+ type EmptyState = ReturnType<typeof EmptyState>;
40
+ export default EmptyState;