@marianmeres/stuic 2.66.0 → 3.0.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 (171) hide show
  1. package/README.md +292 -4
  2. package/dist/README.md +41 -18
  3. package/dist/actions/popover/README.md +19 -0
  4. package/dist/actions/popover/index.css +6 -9
  5. package/dist/actions/popover/popover.svelte.js +2 -2
  6. package/dist/actions/tooltip/README.md +18 -0
  7. package/dist/actions/tooltip/index.css +5 -8
  8. package/dist/actions/tooltip/tooltip.svelte.js +1 -1
  9. package/dist/components/AlertConfirmPrompt/AlertConfirmPrompt.svelte +9 -10
  10. package/dist/components/AlertConfirmPrompt/AlertConfirmPrompt.svelte.d.ts +3 -3
  11. package/dist/components/AlertConfirmPrompt/Current.svelte +15 -17
  12. package/dist/components/AlertConfirmPrompt/Current.svelte.d.ts +5 -3
  13. package/dist/components/AlertConfirmPrompt/acp-icons.js +5 -4
  14. package/dist/components/AlertConfirmPrompt/index.css +62 -0
  15. package/dist/components/AssetsPreview/AssetsPreview.svelte +92 -73
  16. package/dist/components/AssetsPreview/AssetsPreview.svelte.d.ts +1 -0
  17. package/dist/components/AssetsPreview/index.css +59 -0
  18. package/dist/components/Avatar/Avatar.svelte +32 -18
  19. package/dist/components/Avatar/Avatar.svelte.d.ts +1 -0
  20. package/dist/components/Avatar/README.md +166 -0
  21. package/dist/components/Avatar/index.css +128 -0
  22. package/dist/components/Backdrop/Backdrop.svelte +8 -2
  23. package/dist/components/Backdrop/Backdrop.svelte.d.ts +1 -0
  24. package/dist/components/Backdrop/README.md +71 -6
  25. package/dist/components/Backdrop/index.css +29 -0
  26. package/dist/components/Button/Button.svelte +117 -124
  27. package/dist/components/Button/Button.svelte.d.ts +35 -23
  28. package/dist/components/Button/README.md +87 -21
  29. package/dist/components/Button/index.css +473 -9
  30. package/dist/components/Button/index.d.ts +1 -1
  31. package/dist/components/Button/index.js +1 -1
  32. package/dist/components/ButtonGroupRadio/ButtonGroupRadio.svelte +7 -38
  33. package/dist/components/ButtonGroupRadio/README.md +82 -4
  34. package/dist/components/ButtonGroupRadio/index.css +152 -14
  35. package/dist/components/Collapsible/Collapsible.svelte +7 -7
  36. package/dist/components/Collapsible/Collapsible.svelte.d.ts +2 -2
  37. package/dist/components/Collapsible/README.md +34 -2
  38. package/dist/components/Collapsible/index.css +38 -0
  39. package/dist/components/CommandMenu/CommandMenu.svelte +13 -24
  40. package/dist/components/CommandMenu/README.md +39 -0
  41. package/dist/components/CommandMenu/index.css +45 -2
  42. package/dist/components/DismissibleMessage/DismissibleMessage.svelte +53 -50
  43. package/dist/components/DismissibleMessage/DismissibleMessage.svelte.d.ts +6 -5
  44. package/dist/components/DismissibleMessage/README.md +93 -11
  45. package/dist/components/DismissibleMessage/index.css +122 -8
  46. package/dist/components/DismissibleMessage/index.d.ts +1 -1
  47. package/dist/components/DropdownMenu/DropdownMenu.svelte +14 -50
  48. package/dist/components/DropdownMenu/DropdownMenu.svelte.d.ts +6 -6
  49. package/dist/components/DropdownMenu/README.md +132 -0
  50. package/dist/components/DropdownMenu/index.css +231 -27
  51. package/dist/components/Input/FieldAssets.svelte +8 -5
  52. package/dist/components/Input/FieldCheckbox.svelte +7 -44
  53. package/dist/components/Input/FieldFile.svelte +1 -6
  54. package/dist/components/Input/FieldInput.svelte +1 -1
  55. package/dist/components/Input/FieldOptions.svelte +41 -38
  56. package/dist/components/Input/FieldRadios.svelte +7 -16
  57. package/dist/components/Input/FieldSelect.svelte +1 -1
  58. package/dist/components/Input/FieldSwitch.svelte +1 -5
  59. package/dist/components/Input/FieldTextarea.svelte +1 -1
  60. package/dist/components/Input/README.md +194 -0
  61. package/dist/components/Input/_internal/FieldRadioInternal.svelte +2 -40
  62. package/dist/components/Input/_internal/InputWrap.svelte +8 -48
  63. package/dist/components/Input/index.css +522 -127
  64. package/dist/components/ListItemButton/ListItemButton.svelte +37 -73
  65. package/dist/components/ListItemButton/ListItemButton.svelte.d.ts +1 -9
  66. package/dist/components/ListItemButton/README.md +100 -45
  67. package/dist/components/ListItemButton/index.css +175 -56
  68. package/dist/components/ListItemButton/index.d.ts +1 -1
  69. package/dist/components/ListItemButton/index.js +1 -1
  70. package/dist/components/Modal/Modal.svelte +2 -8
  71. package/dist/components/Modal/Modal.svelte.d.ts +1 -0
  72. package/dist/components/Modal/README.md +29 -0
  73. package/dist/components/Modal/index.css +36 -0
  74. package/dist/components/ModalDialog/ModalDialog.svelte +2 -21
  75. package/dist/components/ModalDialog/README.md +35 -0
  76. package/dist/components/ModalDialog/index.css +57 -0
  77. package/dist/components/Notifications/Notifications.svelte +44 -128
  78. package/dist/components/Notifications/Notifications.svelte.d.ts +9 -17
  79. package/dist/components/Notifications/README.md +186 -70
  80. package/dist/components/Notifications/index.css +212 -15
  81. package/dist/components/Progress/README.md +15 -0
  82. package/dist/components/Progress/_internal/Bar.svelte +2 -2
  83. package/dist/components/Progress/index.css +4 -4
  84. package/dist/components/Skeleton/Skeleton.svelte +3 -2
  85. package/dist/components/Skeleton/index.css +11 -14
  86. package/dist/components/Spinner/Spinner.svelte +2 -2
  87. package/dist/components/Spinner/SpinnerCircle.svelte +1 -1
  88. package/dist/components/Switch/README.md +15 -0
  89. package/dist/components/Switch/Switch.svelte +4 -7
  90. package/dist/components/Switch/Switch.svelte.d.ts +1 -1
  91. package/dist/components/Switch/SwitchButton.svelte +4 -5
  92. package/dist/components/Switch/index.css +3 -4
  93. package/dist/components/TabbedMenu/README.md +26 -21
  94. package/dist/components/TabbedMenu/TabbedMenu.svelte +5 -5
  95. package/dist/components/TabbedMenu/index.css +7 -22
  96. package/dist/components/ThemePreview/README.md +289 -0
  97. package/dist/components/ThemePreview/ThemePreview.svelte +341 -0
  98. package/dist/components/ThemePreview/ThemePreview.svelte.d.ts +33 -0
  99. package/dist/components/ThemePreview/index.css +493 -0
  100. package/dist/components/ThemePreview/index.d.ts +1 -0
  101. package/dist/components/ThemePreview/index.js +1 -0
  102. package/dist/components/TwCheck/TwCheck.svelte +4 -4
  103. package/dist/components/TwCheck/index.css +3 -2
  104. package/dist/components/TypeaheadInput/TypeaheadInput.svelte +1 -1
  105. package/dist/components/X/X.svelte +12 -5
  106. package/dist/components/X/X.svelte.d.ts +1 -0
  107. package/dist/icons/index.d.ts +1 -0
  108. package/dist/icons/index.js +1 -0
  109. package/dist/index.css +31 -16
  110. package/dist/index.d.ts +1 -0
  111. package/dist/index.js +1 -0
  112. package/dist/themes/blue-orange.css +163 -0
  113. package/dist/themes/blue-orange.d.ts +6 -0
  114. package/dist/themes/blue-orange.js +151 -0
  115. package/dist/themes/cyan-red.css +163 -0
  116. package/dist/themes/cyan-red.d.ts +6 -0
  117. package/dist/themes/cyan-red.js +151 -0
  118. package/dist/themes/cyan-slate.css +163 -0
  119. package/dist/themes/cyan-slate.d.ts +6 -0
  120. package/dist/themes/cyan-slate.js +151 -0
  121. package/dist/themes/emerald-pink.css +163 -0
  122. package/dist/themes/emerald-pink.d.ts +6 -0
  123. package/dist/themes/emerald-pink.js +151 -0
  124. package/dist/themes/fuchsia-emerald.css +163 -0
  125. package/dist/themes/fuchsia-emerald.d.ts +6 -0
  126. package/dist/themes/fuchsia-emerald.js +151 -0
  127. package/dist/themes/gray.css +163 -0
  128. package/dist/themes/gray.d.ts +6 -0
  129. package/dist/themes/gray.js +151 -0
  130. package/dist/themes/indigo-amber.css +163 -0
  131. package/dist/themes/indigo-amber.d.ts +6 -0
  132. package/dist/themes/indigo-amber.js +151 -0
  133. package/dist/themes/neutral.css +163 -0
  134. package/dist/themes/neutral.d.ts +6 -0
  135. package/dist/themes/neutral.js +151 -0
  136. package/dist/themes/pink-emerald.css +163 -0
  137. package/dist/themes/pink-emerald.d.ts +6 -0
  138. package/dist/themes/pink-emerald.js +151 -0
  139. package/dist/themes/purple-yellow.css +163 -0
  140. package/dist/themes/purple-yellow.d.ts +6 -0
  141. package/dist/themes/purple-yellow.js +151 -0
  142. package/dist/themes/rainbow.css +163 -0
  143. package/dist/themes/rainbow.d.ts +6 -0
  144. package/dist/themes/rainbow.js +156 -0
  145. package/dist/themes/red-blue.css +163 -0
  146. package/dist/themes/red-blue.d.ts +6 -0
  147. package/dist/themes/red-blue.js +151 -0
  148. package/dist/themes/red-cyan.css +163 -0
  149. package/dist/themes/red-cyan.d.ts +6 -0
  150. package/dist/themes/red-cyan.js +151 -0
  151. package/dist/themes/rose-teal.css +163 -0
  152. package/dist/themes/rose-teal.d.ts +6 -0
  153. package/dist/themes/rose-teal.js +151 -0
  154. package/dist/themes/sky-amber.css +163 -0
  155. package/dist/themes/sky-amber.d.ts +6 -0
  156. package/dist/themes/sky-amber.js +151 -0
  157. package/dist/themes/slate-cyan.css +163 -0
  158. package/dist/themes/slate-cyan.d.ts +6 -0
  159. package/dist/themes/slate-cyan.js +151 -0
  160. package/dist/themes/tailwind-color-pairs.md +31 -0
  161. package/dist/themes/teal-rose.css +163 -0
  162. package/dist/themes/teal-rose.d.ts +6 -0
  163. package/dist/themes/teal-rose.js +151 -0
  164. package/dist/themes/violet-lime.css +163 -0
  165. package/dist/themes/violet-lime.d.ts +6 -0
  166. package/dist/themes/violet-lime.js +151 -0
  167. package/dist/utils/design-tokens.d.ts +43 -0
  168. package/dist/utils/design-tokens.js +100 -0
  169. package/dist/utils/index.d.ts +1 -0
  170. package/dist/utils/index.js +1 -0
  171. package/package.json +22 -2
@@ -1,4 +1,6 @@
1
+ import type { IntentColorKey } from "../../utils/design-tokens.js";
1
2
  import { type AlertConfirmPromptStack } from "./alert-confirm-prompt-stack.svelte.js";
3
+ import "./index.css";
2
4
  interface Props {
3
5
  acp?: AlertConfirmPromptStack;
4
6
  isPending?: boolean;
@@ -19,9 +21,9 @@ interface Props {
19
21
  classButtonCancel?: string;
20
22
  classButtonCustom?: string;
21
23
  classButtonPrimary?: string;
22
- variantButtonCancel?: string;
23
- variantButtonCustom?: string;
24
- variantButtonPrimary?: string;
24
+ intentButtonCancel?: IntentColorKey;
25
+ intentButtonCustom?: IntentColorKey;
26
+ intentButtonPrimary?: IntentColorKey;
25
27
  classSpinnerBox?: string;
26
28
  defaultIcons?: Partial<Record<"info" | "success" | "warn" | "error" | "spinner", () => string | undefined>>;
27
29
  }
@@ -1,8 +1,9 @@
1
1
  import { iconAlertInfo, iconAlertSuccess, iconAlertWarning, iconAlertError, iconRefresh, } from "../../icons/index.js";
2
+ const size = 23;
2
3
  export const acpDefaultIcons = {
3
- info: () => iconAlertInfo({ size: 24 }),
4
- success: () => iconAlertSuccess({}),
5
- warn: () => iconAlertWarning({ class: "-mt-[3px]" }), // move up a little because it looks better with the triangle
6
- error: () => iconAlertError({}),
4
+ info: () => iconAlertInfo({ size }),
5
+ success: () => iconAlertSuccess({ size }),
6
+ warn: () => iconAlertWarning({ size, class: "-mt-[2px]" }), // move up a little because it looks better with the triangle
7
+ error: () => iconAlertError({ size }),
7
8
  spinner: () => iconRefresh({ size: 32, class: "opacity-50" }),
8
9
  };
@@ -0,0 +1,62 @@
1
+ /* ============================================================================
2
+ ALERT CONFIRM PROMPT COMPONENT TOKENS
3
+ Override globally: :root { --stuic-acp-icon-box-radius: 0; }
4
+ Override locally: <AlertConfirmPrompt style="--stuic-acp-icon-box-radius: 4px;">
5
+ ============================================================================ */
6
+
7
+ :root {
8
+ --stuic-acp-icon-box-radius: 9999px;
9
+ --stuic-acp-spinner-overlay-opacity: 0.75;
10
+ }
11
+
12
+ /* ============================================================================
13
+ ICON BOX - colors set by variant
14
+ ============================================================================ */
15
+
16
+ .stuic-acp .icon-box {
17
+ background: var(--_icon-bg);
18
+ color: var(--_icon-text);
19
+ border-radius: var(--stuic-acp-icon-box-radius);
20
+ }
21
+
22
+ /* ============================================================================
23
+ SPINNER OVERLAY - uses surface color
24
+ ============================================================================ */
25
+
26
+ .stuic-acp .spinner-box {
27
+ background: color-mix(
28
+ in srgb,
29
+ var(--stuic-color-surface) calc(var(--stuic-acp-spinner-overlay-opacity) * 100%),
30
+ transparent
31
+ );
32
+ }
33
+
34
+ /* ============================================================================
35
+ VARIANT COLOR MAPPING
36
+ Each variant sets internal CSS vars for the icon box.
37
+ ============================================================================ */
38
+
39
+ /* Variant: info (default) */
40
+ .stuic-acp[data-variant='info'],
41
+ .stuic-acp:not([data-variant]) {
42
+ --_icon-bg: color-mix(in srgb, var(--stuic-color-info) 15%, transparent);
43
+ --_icon-text: var(--stuic-color-info);
44
+ }
45
+
46
+ /* Variant: success */
47
+ .stuic-acp[data-variant='success'] {
48
+ --_icon-bg: color-mix(in srgb, var(--stuic-color-success) 15%, transparent);
49
+ --_icon-text: var(--stuic-color-success);
50
+ }
51
+
52
+ /* Variant: warn */
53
+ .stuic-acp[data-variant='warn'] {
54
+ --_icon-bg: color-mix(in srgb, var(--stuic-color-warning) 15%, transparent);
55
+ --_icon-text: var(--stuic-color-warning);
56
+ }
57
+
58
+ /* Variant: error */
59
+ .stuic-acp[data-variant='error'] {
60
+ --_icon-bg: color-mix(in srgb, var(--stuic-color-destructive) 15%, transparent);
61
+ --_icon-text: var(--stuic-color-destructive);
62
+ }
@@ -171,6 +171,8 @@
171
171
  </script>
172
172
 
173
173
  <script lang="ts">
174
+ import "./index.css";
175
+ import Button from "../Button/Button.svelte";
174
176
  const clog = createClog("AssetsPreview", { color: "auto" });
175
177
 
176
178
  let {
@@ -219,7 +221,13 @@
219
221
  let imgEl: HTMLImageElement | null = null;
220
222
  let containerEl: HTMLDivElement | null = $state(null);
221
223
 
222
- const TOP_BUTTON_CLS = "rounded bg-white hover:bg-neutral-200 p-1";
224
+ const BUTTON_CLS = "stuic-assets-preview-control pointer-events-auto p-0!";
225
+
226
+ const BUTTON_PROPS = {
227
+ aspect1: true,
228
+ variant: "soft",
229
+ roundedFull: true,
230
+ };
223
231
 
224
232
  $effect(() => {
225
233
  const visible = modal?.visibility().visible;
@@ -457,6 +465,7 @@
457
465
  previewIdx = idx % assets.length;
458
466
  }
459
467
 
468
+ const ICON_SIZE = 24;
460
469
  // $inspect(assets).with(clog);
461
470
  </script>
462
471
 
@@ -513,7 +522,9 @@
513
522
  class: "mx-auto",
514
523
  })}
515
524
  </div>
516
- <div class="opacity-50 mt-4">{t("unable_to_preview")}</div>
525
+ <div class="text-(--stuic-color-muted-foreground) mt-4">
526
+ {t("unable_to_preview")}
527
+ </div>
517
528
  </div>
518
529
  {/if}
519
530
 
@@ -521,95 +532,103 @@
521
532
  <div
522
533
  class="absolute inset-0 flex items-center justify-between pointer-events-none"
523
534
  >
524
- <button
525
- class={twMerge("p-4 pointer-events-auto", classControls)}
535
+ <!-- class={twMerge("p-4 aspect-square pointer-events-auto", classControls)} -->
536
+ <Button
537
+ class={twMerge(BUTTON_CLS, "ml-4", classControls)}
526
538
  onclick={preview_previous}
527
539
  type="button"
540
+ {...BUTTON_PROPS}
528
541
  >
529
- <span class="bg-white rounded-full p-3 block">
530
- {@html iconPrevious()}
531
- </span>
532
- </button>
533
-
534
- <button
535
- class={twMerge("p-4 pointer-events-auto", classControls)}
542
+ <!-- <span class="stuic-assets-preview-control-nav p-3 block"> -->
543
+ {@html iconPrevious({ size: ICON_SIZE })}
544
+ <!-- </span> -->
545
+ </Button>
546
+
547
+ <!-- class={twMerge("p-4 aspect-square pointer-events-auto", classControls)} -->
548
+ <Button
549
+ class={twMerge(BUTTON_CLS, "mr-4", classControls)}
536
550
  onclick={preview_next}
537
551
  type="button"
552
+ {...BUTTON_PROPS}
538
553
  >
539
- <span class="bg-white rounded-full p-3 block">
540
- {@html iconNext()}
541
- </span>
542
- </button>
554
+ <!-- <span class="stuic-assets-preview-control-nav p-3 block"> -->
555
+ {@html iconNext({ size: ICON_SIZE })}
556
+ <!-- </span> -->
557
+ </Button>
543
558
  </div>
544
559
  {/if}
545
560
 
546
561
  <div class="absolute top-4 left-4 right-4 flex items-center justify-between gap-3">
547
562
  {#if !noName && previewAsset?.name}
548
- <span class="truncate bg-white px-1 rounded">
563
+ <span class="stuic-assets-preview-label truncate px-1">
549
564
  {previewAsset?.name}
550
565
  </span>
551
566
  {:else}
552
567
  <span></span>
553
568
  {/if}
554
569
  <div class="flex items-center space-x-3 shrink-0">
555
- {#if previewAsset.isImage}
556
- <button
557
- class={twMerge(TOP_BUTTON_CLS, classControls)}
558
- type="button"
559
- onclick={zoomOut}
560
- disabled={zoomLevelIdx === 0}
561
- aria-label={t("zoom_out")}
562
- use:tooltip={() => ({ content: t("zoom_out") })}
563
- >
564
- {@html iconZoomOut({ class: "size-6" })}
565
- </button>
566
-
567
- <button
568
- class={twMerge(TOP_BUTTON_CLS, classControls)}
570
+ {#if previewAsset.isImage}
571
+ <Button
572
+ class={twMerge(BUTTON_CLS, classControls)}
573
+ type="button"
574
+ onclick={zoomOut}
575
+ disabled={zoomLevelIdx === 0}
576
+ aria-label={t("zoom_out")}
577
+ tooltip={t("zoom_out")}
578
+ {...BUTTON_PROPS}
579
+ >
580
+ {@html iconZoomOut({ size: ICON_SIZE })}
581
+ </Button>
582
+
583
+ <Button
584
+ class={twMerge(BUTTON_CLS, classControls)}
585
+ type="button"
586
+ onclick={zoomIn}
587
+ disabled={zoomLevelIdx === ZOOM_LEVELS.length - 1}
588
+ aria-label={t("zoom_in")}
589
+ tooltip={t("zoom_in")}
590
+ {...BUTTON_PROPS}
591
+ >
592
+ {@html iconZoomIn({ size: ICON_SIZE })}
593
+ </Button>
594
+ {/if}
595
+
596
+ {#if typeof onDelete === "function"}
597
+ <Button
598
+ class={twMerge(BUTTON_CLS, classControls)}
599
+ type="button"
600
+ onclick={() => onDelete(previewAsset, previewIdx, { close })}
601
+ aria-label={t("delete")}
602
+ tooltip={t("delete")}
603
+ {...BUTTON_PROPS}
604
+ >
605
+ {@html iconDelete({ size: ICON_SIZE })}
606
+ </Button>
607
+ {/if}
608
+
609
+ <Button
610
+ class={twMerge(BUTTON_CLS, classControls)}
569
611
  type="button"
570
- onclick={zoomIn}
571
- disabled={zoomLevelIdx === ZOOM_LEVELS.length - 1}
572
- aria-label={t("zoom_in")}
573
- use:tooltip={() => ({ content: t("zoom_in") })}
612
+ onclick={(e) => {
613
+ e.preventDefault();
614
+ forceDownload(String(previewAsset.url.original), previewAsset?.name || "");
615
+ }}
616
+ aria-label={t("download")}
617
+ tooltip={t("download")}
618
+ {...BUTTON_PROPS}
574
619
  >
575
- {@html iconZoomIn({ class: "size-6" })}
576
- </button>
577
- {/if}
620
+ {@html iconDownload({ size: ICON_SIZE })}
621
+ </Button>
578
622
 
579
- {#if typeof onDelete === "function"}
580
- <button
581
- class={twMerge(TOP_BUTTON_CLS, classControls)}
623
+ <Button
624
+ class={twMerge(BUTTON_CLS, classControls)}
625
+ onclick={modal?.close}
626
+ aria-label={t("close")}
582
627
  type="button"
583
- onclick={() => onDelete(previewAsset, previewIdx, { close })}
584
- aria-label={t("delete")}
585
- use:tooltip
586
- >
587
- {@html iconDelete({ class: "size-6" })}
588
- </button>
589
- {/if}
590
-
591
- <button
592
- class={twMerge(TOP_BUTTON_CLS, classControls)}
593
- type="button"
594
- onclick={(e) => {
595
- e.preventDefault();
596
- forceDownload(String(previewAsset.url.original), previewAsset?.name || "");
597
- }}
598
- aria-label={t("download")}
599
- use:tooltip
600
- >
601
- {@html iconDownload({ class: "size-6" })}
602
- </button>
603
-
604
- <button
605
- class={twMerge(TOP_BUTTON_CLS, classControls)}
606
- onclick={modal?.close}
607
- aria-label={t("close")}
608
- type="button"
609
- use:tooltip
610
- >
611
- <X />
612
- </button>
628
+ tooltip={t("close")}
629
+ {...BUTTON_PROPS}
630
+ x
631
+ />
613
632
  </div>
614
633
  </div>
615
634
 
@@ -619,7 +638,7 @@
619
638
  class="absolute bottom-10 left-0 right-0 text-center"
620
639
  transition:fade={{ duration: 100 }}
621
640
  >
622
- <span class="bg-white p-1 rounded opacity/75">
641
+ <span class="stuic-assets-preview-label p-1">
623
642
  {dotTooltip}
624
643
  </span>
625
644
  </div>
@@ -630,8 +649,8 @@
630
649
  <button
631
650
  type="button"
632
651
  class={twMerge(
633
- "size-3 rounded-full transition-colors border border-black/50",
634
- i === previewIdx ? "bg-white" : "bg-white/50 hover:bg-neutral-200"
652
+ "stuic-assets-preview-dot",
653
+ i === previewIdx ? "active" : ""
635
654
  )}
636
655
  onclick={() => {
637
656
  previewIdx = i;
@@ -26,6 +26,7 @@ export interface Props {
26
26
  clampPan?: boolean;
27
27
  }
28
28
  export declare function getAssetIcon(ext?: string): CallableFunction;
29
+ import "./index.css";
29
30
  declare const AssetsPreview: import("svelte").Component<Props, {
30
31
  open: (index?: number) => void;
31
32
  close: () => void;
@@ -0,0 +1,59 @@
1
+ /* ============================================================================
2
+ ASSETS PREVIEW COMPONENT TOKENS
3
+ Override globally: :root { --stuic-assets-preview-control-bg: var(--stuic-color-muted); }
4
+ Override locally: <AssetsPreview style="--stuic-assets-preview-control-bg: red;">
5
+ ============================================================================ */
6
+
7
+ :root {
8
+ /* Control buttons (zoom, download, close, prev/next) */
9
+
10
+ /* Labels (filename, tooltip) */
11
+ --stuic-assets-preview-label-bg: var(--stuic-color-surface);
12
+ --stuic-assets-preview-label-text: var(--stuic-color-surface-foreground);
13
+ --stuic-assets-preview-label-radius: var(--radius-sm);
14
+
15
+ /* Dot indicators */
16
+ --stuic-assets-preview-dot-size: 0.75rem;
17
+ --stuic-assets-preview-dot-bg: color-mix(
18
+ in srgb,
19
+ var(--stuic-color-surface) 50%,
20
+ transparent
21
+ );
22
+ --stuic-assets-preview-dot-bg-active: var(--stuic-color-surface);
23
+ --stuic-assets-preview-dot-bg-hover: var(--stuic-color-surface-hover);
24
+ --stuic-assets-preview-dot-border: var(--stuic-color-border);
25
+
26
+ /* Transition */
27
+ --stuic-assets-preview-transition: 150ms;
28
+ }
29
+
30
+ /* ============================================================================
31
+ LABELS (filename display, tooltip)
32
+ ============================================================================ */
33
+
34
+ .stuic-assets-preview-label {
35
+ background: var(--stuic-assets-preview-label-bg);
36
+ color: var(--stuic-assets-preview-label-text);
37
+ border-radius: var(--stuic-assets-preview-label-radius);
38
+ }
39
+
40
+ /* ============================================================================
41
+ DOT INDICATORS (pagination dots)
42
+ ============================================================================ */
43
+
44
+ .stuic-assets-preview-dot {
45
+ width: var(--stuic-assets-preview-dot-size);
46
+ height: var(--stuic-assets-preview-dot-size);
47
+ border-radius: 9999px;
48
+ background: var(--stuic-assets-preview-dot-bg);
49
+ border: 1px solid var(--stuic-assets-preview-dot-border);
50
+ transition: background var(--stuic-assets-preview-transition);
51
+ }
52
+
53
+ .stuic-assets-preview-dot:hover {
54
+ background: var(--stuic-assets-preview-dot-bg-hover);
55
+ }
56
+
57
+ .stuic-assets-preview-dot.active {
58
+ background: var(--stuic-assets-preview-dot-bg-active);
59
+ }
@@ -39,6 +39,7 @@
39
39
  </script>
40
40
 
41
41
  <script lang="ts">
42
+ import "./index.css";
42
43
  import { twMerge } from "../../utils/tw-merge.js";
43
44
  import { generateAvatarColors } from "../../utils/avatar-colors.js";
44
45
  import { iconUser as defaultIconUser } from "../../icons/index.js";
@@ -60,14 +61,19 @@
60
61
  el = $bindable(),
61
62
  }: Props = $props();
62
63
 
63
- const SIZE_PRESETS: Record<string, { container: string; icon: number }> = {
64
- sm: { container: "size-8 text-xs", icon: 16 },
65
- md: { container: "size-10 text-base", icon: 20 },
66
- lg: { container: "size-12 text-lg", icon: 28 },
67
- xl: { container: "size-14 text-xl", icon: 32 },
68
- "2xl": { container: "size-16 text-2xl", icon: 36 },
64
+ // Icon sizes for preset sizes (visual sizes are handled by CSS)
65
+ const ICON_SIZES: Record<string, number> = {
66
+ sm: 16,
67
+ md: 20,
68
+ lg: 28,
69
+ xl: 32,
70
+ "2xl": 36,
69
71
  };
70
72
 
73
+ // Check if size is a known preset
74
+ const isPresetSize = (s: string): s is "sm" | "md" | "lg" | "xl" | "2xl" =>
75
+ s in ICON_SIZES;
76
+
71
77
  // Extract initials from input string (email, name, or raw initials)
72
78
  function extractInitials(input: string, length: number): string {
73
79
  let _input = (input || "").trim();
@@ -145,8 +151,7 @@
145
151
 
146
152
  // Get icon size based on preset or custom size
147
153
  let iconSize = $derived.by(() => {
148
- const preset = SIZE_PRESETS[size];
149
- if (preset) return preset.icon;
154
+ if (isPresetSize(size)) return ICON_SIZES[size];
150
155
 
151
156
  // For custom sizes, try to parse size-N pattern
152
157
  const match = size?.match(/size-(\d+)/);
@@ -162,25 +167,21 @@
162
167
  autoColor ? generateAvatarColors(hashSource || initialsProp || "") : null
163
168
  );
164
169
 
165
- let sizeClass = $derived(SIZE_PRESETS[size]?.container || size);
166
-
167
170
  let style = $derived(
168
171
  autoColor && colors
169
172
  ? `background-color: ${colors.bg}; color: ${colors.text};`
170
173
  : undefined
171
174
  );
172
175
 
176
+ // Build class string - base class for CSS targeting, allow user overrides via classProp
173
177
  let baseClass = $derived(
174
178
  twMerge(
175
179
  "stuic-avatar",
176
- "inline-flex items-center justify-center",
177
- "rounded-full font-medium shrink-0 overflow-hidden",
178
- !autoColor &&
179
- "bg-neutral-200 text-neutral-700 dark:bg-neutral-700 dark:text-neutral-200",
180
- sizeClass,
180
+ // Custom size classes when not using preset (preset sizes handled by data-size in CSS)
181
+ !isPresetSize(size) && size,
182
+ // User-provided Tailwind overrides for colors (only when not using autoColor)
181
183
  !autoColor && bg,
182
184
  !autoColor && textColor,
183
- onclick && "select-none cursor-pointer",
184
185
  classProp
185
186
  )
186
187
  );
@@ -191,7 +192,15 @@
191
192
  </script>
192
193
 
193
194
  {#if onclick}
194
- <button bind:this={el} type="button" class={baseClass} {style} {onclick}>
195
+ <button
196
+ bind:this={el}
197
+ type="button"
198
+ class={baseClass}
199
+ {style}
200
+ {onclick}
201
+ data-size={isPresetSize(size) ? size : undefined}
202
+ data-interactive="true"
203
+ >
195
204
  {#if renderMode === "photo"}
196
205
  <img {src} {alt} class="size-full object-cover" onerror={handleImageError} />
197
206
  {:else if renderMode === "initials"}
@@ -201,7 +210,12 @@
201
210
  {/if}
202
211
  </button>
203
212
  {:else}
204
- <div bind:this={el} class={baseClass} {style}>
213
+ <div
214
+ bind:this={el}
215
+ class={baseClass}
216
+ {style}
217
+ data-size={isPresetSize(size) ? size : undefined}
218
+ >
205
219
  {#if renderMode === "photo"}
206
220
  <img {src} {alt} class="size-full object-cover" onerror={handleImageError} />
207
221
  {:else if renderMode === "initials"}
@@ -37,6 +37,7 @@ export interface Props {
37
37
  /** Bindable element reference */
38
38
  el?: HTMLDivElement | HTMLButtonElement;
39
39
  }
40
+ import "./index.css";
40
41
  declare const Avatar: import("svelte").Component<Props, {}, "el">;
41
42
  type Avatar = ReturnType<typeof Avatar>;
42
43
  export default Avatar;
@@ -0,0 +1,166 @@
1
+ # Avatar
2
+
3
+ A flexible avatar component that displays user photos, initials, or icons with automatic fallback handling and optional deterministic color generation.
4
+
5
+ ## Props
6
+
7
+ | Prop | Type | Default | Description |
8
+ |------|------|---------|-------------|
9
+ | `src` | `string` | - | Photo URL - when provided, renders in photo mode |
10
+ | `alt` | `string` | - | Alt text for photo mode |
11
+ | `initials` | `string` | - | String to extract initials from. Supports: "AB", "John Doe", or "john.doe@example.com" |
12
+ | `initialsLength` | `number` | `2` | Maximum length of extracted initials |
13
+ | `icon` | `IconFn` | - | Icon function to display - when provided alone, renders in icon mode |
14
+ | `fallback` | `AvatarFallback` | `"icon"` | Fallback when photo fails to load |
15
+ | `hashSource` | `string` | - | String for color hash calculation (e.g., email, user ID). Falls back to `initials` |
16
+ | `size` | `"sm" \| "md" \| "lg" \| "xl" \| "2xl" \| string` | `"md"` | Size preset or custom Tailwind size class |
17
+ | `onclick` | `(event: MouseEvent) => void` | - | Click handler - when provided, renders as a button |
18
+ | `bg` | `string` | - | Background color (Tailwind class). Ignored if `autoColor=true` |
19
+ | `textColor` | `string` | - | Text color (Tailwind class). Ignored if `autoColor=true` |
20
+ | `autoColor` | `boolean` | `false` | Generate deterministic pastel colors from hashSource/initials |
21
+ | `class` | `string` | - | Additional CSS classes |
22
+ | `el` | `HTMLElement` | - | Bindable element reference |
23
+
24
+ ## Usage
25
+
26
+ ### Basic
27
+
28
+ ```svelte
29
+ <script lang="ts">
30
+ import { Avatar } from '@marianmeres/stuic';
31
+ </script>
32
+
33
+ <!-- Photo -->
34
+ <Avatar src="/path/to/photo.jpg" alt="John Doe" />
35
+
36
+ <!-- Initials -->
37
+ <Avatar initials="John Doe" />
38
+
39
+ <!-- Icon (default fallback) -->
40
+ <Avatar />
41
+ ```
42
+
43
+ ### Sizes
44
+
45
+ ```svelte
46
+ <Avatar initials="AB" size="sm" />
47
+ <Avatar initials="AB" size="md" />
48
+ <Avatar initials="AB" size="lg" />
49
+ <Avatar initials="AB" size="xl" />
50
+ <Avatar initials="AB" size="2xl" />
51
+
52
+ <!-- Custom size with Tailwind -->
53
+ <Avatar initials="AB" size="size-20" />
54
+ ```
55
+
56
+ ### Auto Color
57
+
58
+ Generate deterministic pastel colors based on a hash source:
59
+
60
+ ```svelte
61
+ <!-- Same input always produces same color -->
62
+ <Avatar initials="john@example.com" autoColor />
63
+ <Avatar initials="Jane Smith" autoColor hashSource="user-123" />
64
+ ```
65
+
66
+ ### Photo with Fallback
67
+
68
+ ```svelte
69
+ <!-- Falls back to icon on error -->
70
+ <Avatar src="/maybe-broken.jpg" fallback="icon" />
71
+
72
+ <!-- Falls back to initials on error -->
73
+ <Avatar src="/maybe-broken.jpg" fallback="initials" initials="JD" />
74
+
75
+ <!-- Falls back to specific initials -->
76
+ <Avatar src="/maybe-broken.jpg" fallback={{ initials: "AB" }} />
77
+ ```
78
+
79
+ ### As Button
80
+
81
+ ```svelte
82
+ <Avatar
83
+ src="/photo.jpg"
84
+ onclick={() => console.log('clicked')}
85
+ />
86
+ ```
87
+
88
+ ### Custom Colors
89
+
90
+ ```svelte
91
+ <!-- Using Tailwind classes -->
92
+ <Avatar initials="AB" bg="bg-blue-500" textColor="text-white" />
93
+
94
+ <!-- Using inline style for component tokens -->
95
+ <Avatar
96
+ initials="AB"
97
+ style="--stuic-avatar-bg: #3b82f6; --stuic-avatar-fg: white;"
98
+ />
99
+ ```
100
+
101
+ ## CSS Variables
102
+
103
+ ### Component Tokens
104
+
105
+ Override globally in `:root` or locally via `style` prop:
106
+
107
+ | Variable | Default | Description |
108
+ |----------|---------|-------------|
109
+ | `--stuic-avatar-radius` | `9999px` | Border radius (circle by default) |
110
+ | `--stuic-avatar-font-weight` | `--font-weight-medium` | Font weight for initials |
111
+ | `--stuic-avatar-transition` | `150ms` | Transition duration |
112
+ | `--stuic-avatar-bg` | `--stuic-color-muted` | Default background color |
113
+ | `--stuic-avatar-fg` | `--stuic-color-muted-foreground` | Default text/icon color |
114
+ | `--stuic-avatar-ring-width` | `3px` | Focus ring width (button mode) |
115
+ | `--stuic-avatar-ring-color` | `--stuic-color-ring` | Focus ring color |
116
+
117
+ ### Size Tokens
118
+
119
+ Each size preset has corresponding tokens (font sizes use Tailwind CSS variables):
120
+
121
+ | Size | Width/Height | Font Size |
122
+ |------|--------------|-----------|
123
+ | `sm` | `--stuic-avatar-size-sm` (2rem) | `--stuic-avatar-font-size-sm` (`--text-xs`) |
124
+ | `md` | `--stuic-avatar-size-md` (2.5rem) | `--stuic-avatar-font-size-md` (`--text-base`) |
125
+ | `lg` | `--stuic-avatar-size-lg` (3rem) | `--stuic-avatar-font-size-lg` (`--text-lg`) |
126
+ | `xl` | `--stuic-avatar-size-xl` (3.5rem) | `--stuic-avatar-font-size-xl` (`--text-xl`) |
127
+ | `2xl` | `--stuic-avatar-size-2xl` (4rem) | `--stuic-avatar-font-size-2xl` (`--text-2xl`) |
128
+
129
+ ### Customization Examples
130
+
131
+ ```css
132
+ /* Global override - square avatars */
133
+ :root {
134
+ --stuic-avatar-radius: 0.5rem;
135
+ }
136
+
137
+ /* Global override - larger default size */
138
+ :root {
139
+ --stuic-avatar-size-md: 3rem;
140
+ --stuic-avatar-font-size-md: 1.25rem;
141
+ }
142
+ ```
143
+
144
+ ```svelte
145
+ <!-- Local override - square avatar -->
146
+ <Avatar initials="AB" style="--stuic-avatar-radius: 0.25rem;" />
147
+ ```
148
+
149
+ ## Data Attributes
150
+
151
+ The component uses data attributes for CSS styling:
152
+
153
+ | Attribute | Values | Description |
154
+ |-----------|--------|-------------|
155
+ | `data-size` | `sm`, `md`, `lg`, `xl`, `2xl` | Size preset (only for preset sizes) |
156
+ | `data-interactive` | `true` | Present when `onclick` is provided |
157
+
158
+ ## Theming
159
+
160
+ The Avatar component uses theme tokens for default colors:
161
+
162
+ - Background: `--stuic-color-muted`
163
+ - Foreground: `--stuic-color-muted-foreground`
164
+ - Focus ring: `--stuic-color-ring`
165
+
166
+ These automatically adapt to light/dark mode when using the theming system.