@marianmeres/stuic 2.13.0 → 2.14.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.
@@ -0,0 +1,112 @@
1
+ <script lang="ts" module>
2
+ export interface Props {
3
+ /** String to extract initials from. Supports: "AB", "John Doe", or "john.doe@example.com" */
4
+ input: string;
5
+ /** Optional string for color hash calculation (e.g., email, user ID). Falls back to `input` */
6
+ hashSource?: string;
7
+ /** Size preset or custom Tailwind size class */
8
+ size?: "sm" | "md" | "lg" | string;
9
+ /** Click handler - when provided, renders as a button */
10
+ onclick?: (event: MouseEvent) => void;
11
+ /** Background color (Tailwind class). Ignored if autoColor=true */
12
+ bg?: string;
13
+ /** Text color (Tailwind class). Ignored if autoColor=true */
14
+ textColor?: string;
15
+ /** Generate deterministic pastel colors from hashSource/input */
16
+ autoColor?: boolean;
17
+ /** CSS class override */
18
+ class?: string;
19
+ /** Bindable element reference */
20
+ el?: HTMLDivElement | HTMLButtonElement;
21
+ }
22
+ </script>
23
+
24
+ <script lang="ts">
25
+ import { twMerge } from "../../utils/tw-merge.js";
26
+ import { generateAvatarColors } from "../../utils/avatar-colors.js";
27
+
28
+ let {
29
+ input,
30
+ hashSource,
31
+ size = "md",
32
+ onclick,
33
+ bg,
34
+ textColor,
35
+ autoColor = false,
36
+ class: classProp,
37
+ el = $bindable(),
38
+ }: Props = $props();
39
+
40
+ const SIZE_PRESETS: Record<string, string> = {
41
+ sm: "size-8 text-xs",
42
+ md: "size-10 text-sm",
43
+ lg: "size-14 text-base",
44
+ };
45
+
46
+ let initials = $derived.by(() => {
47
+ let _input = (input || "").trim();
48
+
49
+ if (!_input) return "?";
50
+
51
+ // Check if input looks like an email
52
+ if (_input.includes("@")) {
53
+ const username = _input.split("@")[0];
54
+ // Split by common separators (., _, -)
55
+ const parts = username.split(/[._-]/).filter(Boolean);
56
+ if (parts.length > 1) {
57
+ _input = parts.map((p) => p.charAt(0)).join("");
58
+ } else {
59
+ _input = username;
60
+ }
61
+ }
62
+ // Check if input looks like a full name (multiple words)
63
+ else if (_input.length > 2 && /\s/.test(_input)) {
64
+ _input = _input
65
+ .split(/\s/)
66
+ .map((v) => v.trim())
67
+ .filter(Boolean)
68
+ .map((v) => v.charAt(0))
69
+ .join("");
70
+ }
71
+
72
+ // Extract first 2 chars, uppercase
73
+ return _input.slice(0, 2).toUpperCase();
74
+ });
75
+
76
+ let colors = $derived(
77
+ autoColor ? generateAvatarColors(hashSource || input || "") : null
78
+ );
79
+
80
+ let sizeClass = $derived(SIZE_PRESETS[size] || size);
81
+
82
+ let style = $derived(
83
+ autoColor && colors
84
+ ? `background-color: ${colors.bg}; color: ${colors.text};`
85
+ : undefined
86
+ );
87
+
88
+ let baseClass = $derived(
89
+ twMerge(
90
+ "stuic-avatar-initials",
91
+ "inline-flex items-center justify-center",
92
+ "rounded-full font-medium shrink-0",
93
+ !autoColor &&
94
+ "bg-neutral-200 text-neutral-700 dark:bg-neutral-700 dark:text-neutral-200",
95
+ sizeClass,
96
+ !autoColor && bg,
97
+ !autoColor && textColor,
98
+ onclick && "select-none cursor-pointer",
99
+ classProp
100
+ )
101
+ );
102
+ </script>
103
+
104
+ {#if onclick}
105
+ <button bind:this={el} type="button" class={baseClass} {style} {onclick}>
106
+ {initials}
107
+ </button>
108
+ {:else}
109
+ <div bind:this={el} class={baseClass} {style}>
110
+ {initials}
111
+ </div>
112
+ {/if}
@@ -0,0 +1,23 @@
1
+ export interface Props {
2
+ /** String to extract initials from. Supports: "AB", "John Doe", or "john.doe@example.com" */
3
+ input: string;
4
+ /** Optional string for color hash calculation (e.g., email, user ID). Falls back to `input` */
5
+ hashSource?: string;
6
+ /** Size preset or custom Tailwind size class */
7
+ size?: "sm" | "md" | "lg" | string;
8
+ /** Click handler - when provided, renders as a button */
9
+ onclick?: (event: MouseEvent) => void;
10
+ /** Background color (Tailwind class). Ignored if autoColor=true */
11
+ bg?: string;
12
+ /** Text color (Tailwind class). Ignored if autoColor=true */
13
+ textColor?: string;
14
+ /** Generate deterministic pastel colors from hashSource/input */
15
+ autoColor?: boolean;
16
+ /** CSS class override */
17
+ class?: string;
18
+ /** Bindable element reference */
19
+ el?: HTMLDivElement | HTMLButtonElement;
20
+ }
21
+ declare const AvatarInitials: import("svelte").Component<Props, {}, "el">;
22
+ type AvatarInitials = ReturnType<typeof AvatarInitials>;
23
+ export default AvatarInitials;
@@ -0,0 +1 @@
1
+ export { default as AvatarInitials, type Props as AvatarInitialsProps, } from "./AvatarInitials.svelte";
@@ -0,0 +1 @@
1
+ export { default as AvatarInitials, } from "./AvatarInitials.svelte";
package/dist/index.d.ts CHANGED
@@ -24,6 +24,7 @@
24
24
  export * from "./components/AlertConfirmPrompt/index.js";
25
25
  export * from "./components/AnimatedElipsis/index.js";
26
26
  export * from "./components/AppShell/index.js";
27
+ export * from "./components/AvatarInitials/index.js";
27
28
  export * from "./components/Backdrop/index.js";
28
29
  export * from "./components/Button/index.js";
29
30
  export * from "./components/ButtonGroupRadio/index.js";
package/dist/index.js CHANGED
@@ -25,6 +25,7 @@
25
25
  export * from "./components/AlertConfirmPrompt/index.js";
26
26
  export * from "./components/AnimatedElipsis/index.js";
27
27
  export * from "./components/AppShell/index.js";
28
+ export * from "./components/AvatarInitials/index.js";
28
29
  export * from "./components/Backdrop/index.js";
29
30
  export * from "./components/Button/index.js";
30
31
  export * from "./components/ButtonGroupRadio/index.js";
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Generates deterministic, harmonious pastel colors for avatar backgrounds.
3
+ *
4
+ * The colors are derived from a hash of the input string, ensuring the same
5
+ * input always produces the same color combination. The background is a pastel
6
+ * color (high lightness, moderate saturation) and the text is a darker shade
7
+ * of the same hue for visual harmony and good contrast.
8
+ *
9
+ * @param source - The string to hash for color generation (e.g., email, user ID)
10
+ * @returns Object with `bg` and `text` CSS color strings in HSL format
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * generateAvatarColors("user@example.com");
15
+ * // { bg: "hsl(142, 52%, 68%)", text: "hsl(142, 62%, 28%)" }
16
+ *
17
+ * generateAvatarColors("another@example.com");
18
+ * // { bg: "hsl(287, 48%, 71%)", text: "hsl(287, 58%, 28%)" }
19
+ * ```
20
+ */
21
+ export declare function generateAvatarColors(source: string): {
22
+ bg: string;
23
+ text: string;
24
+ };
@@ -0,0 +1,37 @@
1
+ import { strHash } from "./str-hash.js";
2
+ /**
3
+ * Generates deterministic, harmonious pastel colors for avatar backgrounds.
4
+ *
5
+ * The colors are derived from a hash of the input string, ensuring the same
6
+ * input always produces the same color combination. The background is a pastel
7
+ * color (high lightness, moderate saturation) and the text is a darker shade
8
+ * of the same hue for visual harmony and good contrast.
9
+ *
10
+ * @param source - The string to hash for color generation (e.g., email, user ID)
11
+ * @returns Object with `bg` and `text` CSS color strings in HSL format
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * generateAvatarColors("user@example.com");
16
+ * // { bg: "hsl(142, 52%, 68%)", text: "hsl(142, 62%, 28%)" }
17
+ *
18
+ * generateAvatarColors("another@example.com");
19
+ * // { bg: "hsl(287, 48%, 71%)", text: "hsl(287, 58%, 28%)" }
20
+ * ```
21
+ */
22
+ export function generateAvatarColors(source) {
23
+ // Get hash as hex string and convert to number
24
+ const hashHex = strHash(source || "");
25
+ const hash = parseInt(hashHex, 16);
26
+ // Derive hue (0-360) from hash
27
+ const hue = hash % 360;
28
+ // Derive saturation and lightness variations from hash for slight variety
29
+ // Saturation: 40-55% (pastel range - soft, not too vivid)
30
+ const saturation = 40 + (hash % 15);
31
+ // Lightness: 75-82% (high lightness ensures dark text is always readable)
32
+ const lightness = 75 + (hash % 7);
33
+ const bg = `hsl(${hue}, ${saturation}%, ${lightness}%)`;
34
+ // Text: same hue, more saturated, very dark for strong contrast
35
+ const text = `hsl(${hue}, ${saturation + 15}%, 22%)`;
36
+ return { bg, text };
37
+ }
@@ -1,3 +1,4 @@
1
+ export * from "./avatar-colors.js";
1
2
  export * from "./body-scroll-locker.js";
2
3
  export * from "./breakpoint.svelte.js";
3
4
  export * from "./colors.js";
@@ -1,3 +1,4 @@
1
+ export * from "./avatar-colors.js";
1
2
  export * from "./body-scroll-locker.js";
2
3
  export * from "./breakpoint.svelte.js";
3
4
  export * from "./colors.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marianmeres/stuic",
3
- "version": "2.13.0",
3
+ "version": "2.14.0",
4
4
  "files": [
5
5
  "dist",
6
6
  "!dist/**/*.test.*",