@sentropic/design-system-svelte 0.11.0 → 0.13.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,161 @@
1
+ <script lang="ts" module>
2
+ export type AvatarSize = "sm" | "md" | "lg" | "xl";
3
+ export type AvatarShape = "circle" | "square";
4
+ export type AvatarTone =
5
+ | "category1"
6
+ | "category2"
7
+ | "category3"
8
+ | "category4"
9
+ | "category5"
10
+ | "category6"
11
+ | "category7"
12
+ | "category8";
13
+ </script>
14
+
15
+ <script lang="ts">
16
+ import type { HTMLAttributes } from "svelte/elements";
17
+ import { deriveInitials } from "./Header.svelte";
18
+
19
+ type AvatarProps = Omit<HTMLAttributes<HTMLSpanElement>, "class"> & {
20
+ /** Nom complet, utilisé pour dériver les initiales et l'étiquette a11y. */
21
+ name: string;
22
+ /** URL de la photo. Si absente, on rend un cercle d'initiales. */
23
+ src?: string;
24
+ /** Texte alternatif de l'image. Par défaut = `name`. */
25
+ alt?: string;
26
+ size?: AvatarSize;
27
+ shape?: AvatarShape;
28
+ /** Catégorie de couleur pour le fond des initiales. */
29
+ tone?: AvatarTone;
30
+ class?: string;
31
+ };
32
+
33
+ let {
34
+ name,
35
+ src,
36
+ alt,
37
+ size = "md",
38
+ shape = "circle",
39
+ tone = "category1",
40
+ class: className,
41
+ ...rest
42
+ }: AvatarProps = $props();
43
+
44
+ const initials = $derived(deriveInitials(name));
45
+
46
+ const classes = $derived(
47
+ [
48
+ "st-avatar",
49
+ `st-avatar--${size}`,
50
+ `st-avatar--${shape}`,
51
+ src ? "st-avatar--image" : `st-avatar--${tone}`,
52
+ className
53
+ ]
54
+ .filter(Boolean)
55
+ .join(" ")
56
+ );
57
+ </script>
58
+
59
+ <span {...rest} class={classes} role="img" aria-label={alt ?? name}>
60
+ {#if src}
61
+ <img class="st-avatar__image" {src} alt={alt ?? name} />
62
+ {:else}
63
+ <span class="st-avatar__initials" aria-hidden="true">{initials}</span>
64
+ {/if}
65
+ </span>
66
+
67
+ <style>
68
+ .st-avatar {
69
+ align-items: center;
70
+ display: inline-flex;
71
+ flex: 0 0 auto;
72
+ justify-content: center;
73
+ overflow: hidden;
74
+ user-select: none;
75
+ vertical-align: middle;
76
+ }
77
+
78
+ .st-avatar--circle {
79
+ border-radius: 50%;
80
+ }
81
+
82
+ .st-avatar--square {
83
+ border-radius: var(--st-radius-sm, 0.375rem);
84
+ }
85
+
86
+ .st-avatar--sm {
87
+ font-size: 0.6875rem;
88
+ height: 1.5rem;
89
+ width: 1.5rem;
90
+ }
91
+
92
+ .st-avatar--md {
93
+ font-size: 0.8125rem;
94
+ height: 2rem;
95
+ width: 2rem;
96
+ }
97
+
98
+ .st-avatar--lg {
99
+ font-size: 1rem;
100
+ height: 2.5rem;
101
+ width: 2.5rem;
102
+ }
103
+
104
+ .st-avatar--xl {
105
+ font-size: 1.25rem;
106
+ height: 3.5rem;
107
+ width: 3.5rem;
108
+ }
109
+
110
+ .st-avatar__image {
111
+ display: block;
112
+ height: 100%;
113
+ object-fit: cover;
114
+ object-position: center;
115
+ width: 100%;
116
+ }
117
+
118
+ .st-avatar__initials {
119
+ font-weight: 700;
120
+ letter-spacing: 0.01em;
121
+ line-height: 1;
122
+ }
123
+
124
+ /* Tone = catégorie : fond teinté, texte coloré (sur le même hue). */
125
+ .st-avatar--category1 {
126
+ background: color-mix(in srgb, var(--st-semantic-data-category1) 16%, white);
127
+ color: var(--st-semantic-data-category1);
128
+ }
129
+ .st-avatar--category2 {
130
+ background: color-mix(in srgb, var(--st-semantic-data-category2) 16%, white);
131
+ color: var(--st-semantic-data-category2);
132
+ }
133
+ .st-avatar--category3 {
134
+ background: color-mix(in srgb, var(--st-semantic-data-category3) 16%, white);
135
+ color: var(--st-semantic-data-category3);
136
+ }
137
+ .st-avatar--category4 {
138
+ background: color-mix(in srgb, var(--st-semantic-data-category4) 16%, white);
139
+ color: var(--st-semantic-data-category4);
140
+ }
141
+ .st-avatar--category5 {
142
+ background: color-mix(in srgb, var(--st-semantic-data-category5) 16%, white);
143
+ color: var(--st-semantic-data-category5);
144
+ }
145
+ .st-avatar--category6 {
146
+ background: color-mix(in srgb, var(--st-semantic-data-category6) 16%, white);
147
+ color: var(--st-semantic-data-category6);
148
+ }
149
+ .st-avatar--category7 {
150
+ background: color-mix(in srgb, var(--st-semantic-data-category7) 16%, white);
151
+ color: var(--st-semantic-data-category7);
152
+ }
153
+ .st-avatar--category8 {
154
+ background: color-mix(in srgb, var(--st-semantic-data-category8) 16%, white);
155
+ color: var(--st-semantic-data-category8);
156
+ }
157
+
158
+ .st-avatar--image {
159
+ background: var(--st-semantic-surface-subtle, #eef2f7);
160
+ }
161
+ </style>
@@ -0,0 +1,21 @@
1
+ export type AvatarSize = "sm" | "md" | "lg" | "xl";
2
+ export type AvatarShape = "circle" | "square";
3
+ export type AvatarTone = "category1" | "category2" | "category3" | "category4" | "category5" | "category6" | "category7" | "category8";
4
+ import type { HTMLAttributes } from "svelte/elements";
5
+ type AvatarProps = Omit<HTMLAttributes<HTMLSpanElement>, "class"> & {
6
+ /** Nom complet, utilisé pour dériver les initiales et l'étiquette a11y. */
7
+ name: string;
8
+ /** URL de la photo. Si absente, on rend un cercle d'initiales. */
9
+ src?: string;
10
+ /** Texte alternatif de l'image. Par défaut = `name`. */
11
+ alt?: string;
12
+ size?: AvatarSize;
13
+ shape?: AvatarShape;
14
+ /** Catégorie de couleur pour le fond des initiales. */
15
+ tone?: AvatarTone;
16
+ class?: string;
17
+ };
18
+ declare const Avatar: import("svelte").Component<AvatarProps, {}, "">;
19
+ type Avatar = ReturnType<typeof Avatar>;
20
+ export default Avatar;
21
+ //# sourceMappingURL=Avatar.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Avatar.svelte.d.ts","sourceRoot":"","sources":["../src/lib/Avatar.svelte.ts"],"names":[],"mappings":"AAGE,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AACnD,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAC9C,MAAM,MAAM,UAAU,GAClB,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,CAAC;AAGlB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAIpD,KAAK,WAAW,GAAG,IAAI,CAAC,cAAc,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC,GAAG;IAClE,2EAA2E;IAC3E,IAAI,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,wDAAwD;IACxD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,uDAAuD;IACvD,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AA8CJ,QAAA,MAAM,MAAM,iDAAwC,CAAC;AACrD,KAAK,MAAM,GAAG,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC;AACxC,eAAe,MAAM,CAAC"}
@@ -0,0 +1,114 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import type { HTMLAttributes } from "svelte/elements";
4
+ import type { AvatarSize } from "./Avatar.svelte";
5
+
6
+ type AvatarGroupProps = Omit<HTMLAttributes<HTMLDivElement>, "class"> & {
7
+ /** Nombre maximum d'avatars visibles. Au-delà, un jeton « +N » est affiché. */
8
+ max?: number;
9
+ /** Taille appliquée au jeton de débordement (doit refléter les Avatar). */
10
+ size?: AvatarSize;
11
+ /** Nombre total réel d'éléments (sert à calculer le « +N » si > max). */
12
+ total?: number;
13
+ class?: string;
14
+ children?: Snippet;
15
+ };
16
+
17
+ let {
18
+ max,
19
+ size = "md",
20
+ total,
21
+ class: className,
22
+ children,
23
+ ...rest
24
+ }: AvatarGroupProps = $props();
25
+
26
+ const overflow = $derived(
27
+ max != null && total != null && total > max ? total - max : 0
28
+ );
29
+
30
+ const classes = $derived(
31
+ ["st-avatarGroup", `st-avatarGroup--${size}`, className].filter(Boolean).join(" ")
32
+ );
33
+ </script>
34
+
35
+ <div {...rest} class={classes} style:--st-avatar-group-max={max ?? ""}>
36
+ {@render children?.()}
37
+ {#if overflow > 0}
38
+ <span class="st-avatarGroup__overflow" aria-label={`+${overflow}`}>+{overflow}</span>
39
+ {/if}
40
+ </div>
41
+
42
+ <style>
43
+ /* Avatars empilés avec recouvrement ; un anneau de surface sépare chaque
44
+ vignette pour la lisibilité. Le recouvrement est porté par les marges
45
+ négatives sur les enfants directs. */
46
+ .st-avatarGroup {
47
+ align-items: center;
48
+ display: inline-flex;
49
+ flex-direction: row;
50
+ }
51
+
52
+ .st-avatarGroup > :global(*) {
53
+ box-shadow: 0 0 0 2px var(--st-semantic-surface-default, #ffffff);
54
+ position: relative;
55
+ }
56
+
57
+ .st-avatarGroup > :global(* + *) {
58
+ margin-inline-start: -0.5rem;
59
+ }
60
+
61
+ .st-avatarGroup--sm > :global(* + *) {
62
+ margin-inline-start: -0.375rem;
63
+ }
64
+
65
+ .st-avatarGroup--lg > :global(* + *) {
66
+ margin-inline-start: -0.625rem;
67
+ }
68
+
69
+ .st-avatarGroup--xl > :global(* + *) {
70
+ margin-inline-start: -0.875rem;
71
+ }
72
+
73
+ .st-avatarGroup__overflow {
74
+ align-items: center;
75
+ background: var(--st-semantic-surface-subtle, #eef2f7);
76
+ border-radius: 50%;
77
+ box-shadow: 0 0 0 2px var(--st-semantic-surface-default, #ffffff);
78
+ color: var(--st-semantic-text-secondary, #64748b);
79
+ display: inline-flex;
80
+ flex: 0 0 auto;
81
+ font-weight: 700;
82
+ justify-content: center;
83
+ margin-inline-start: -0.5rem;
84
+ position: relative;
85
+ user-select: none;
86
+ }
87
+
88
+ .st-avatarGroup--sm .st-avatarGroup__overflow {
89
+ font-size: 0.6875rem;
90
+ height: 1.5rem;
91
+ margin-inline-start: -0.375rem;
92
+ width: 1.5rem;
93
+ }
94
+
95
+ .st-avatarGroup--md .st-avatarGroup__overflow {
96
+ font-size: 0.75rem;
97
+ height: 2rem;
98
+ width: 2rem;
99
+ }
100
+
101
+ .st-avatarGroup--lg .st-avatarGroup__overflow {
102
+ font-size: 0.875rem;
103
+ height: 2.5rem;
104
+ margin-inline-start: -0.625rem;
105
+ width: 2.5rem;
106
+ }
107
+
108
+ .st-avatarGroup--xl .st-avatarGroup__overflow {
109
+ font-size: 1.125rem;
110
+ height: 3.5rem;
111
+ margin-inline-start: -0.875rem;
112
+ width: 3.5rem;
113
+ }
114
+ </style>
@@ -0,0 +1,17 @@
1
+ import type { Snippet } from "svelte";
2
+ import type { HTMLAttributes } from "svelte/elements";
3
+ import type { AvatarSize } from "./Avatar.svelte";
4
+ type AvatarGroupProps = Omit<HTMLAttributes<HTMLDivElement>, "class"> & {
5
+ /** Nombre maximum d'avatars visibles. Au-delà, un jeton « +N » est affiché. */
6
+ max?: number;
7
+ /** Taille appliquée au jeton de débordement (doit refléter les Avatar). */
8
+ size?: AvatarSize;
9
+ /** Nombre total réel d'éléments (sert à calculer le « +N » si > max). */
10
+ total?: number;
11
+ class?: string;
12
+ children?: Snippet;
13
+ };
14
+ declare const AvatarGroup: import("svelte").Component<AvatarGroupProps, {}, "">;
15
+ type AvatarGroup = ReturnType<typeof AvatarGroup>;
16
+ export default AvatarGroup;
17
+ //# sourceMappingURL=AvatarGroup.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AvatarGroup.svelte.d.ts","sourceRoot":"","sources":["../src/lib/AvatarGroup.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAGhD,KAAK,gBAAgB,GAAG,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC,GAAG;IACtE,+EAA+E;IAC/E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,2EAA2E;IAC3E,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,yEAAyE;IACzE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAoCJ,QAAA,MAAM,WAAW,sDAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
@@ -0,0 +1,130 @@
1
+ <script lang="ts" module>
2
+ export type ButtonGroupOrientation = "horizontal" | "vertical";
3
+ export type ButtonGroupSize = "sm" | "md" | "lg";
4
+ </script>
5
+
6
+ <script lang="ts">
7
+ import type { Snippet } from "svelte";
8
+ import type { HTMLAttributes } from "svelte/elements";
9
+
10
+ type ButtonGroupProps = Omit<HTMLAttributes<HTMLDivElement>, "class"> & {
11
+ orientation?: ButtonGroupOrientation;
12
+ /** Look segmenté joint (boutons collés, coins arrondis seulement aux extrémités). */
13
+ attached?: boolean;
14
+ /** Espacement entre boutons (échelle spacing), ignoré quand `attached`. */
15
+ gap?: number;
16
+ /** Taille indicative (transmise via data-attr pour styliser les enfants si besoin). */
17
+ size?: ButtonGroupSize;
18
+ /** Étiquette a11y du groupe. */
19
+ label?: string;
20
+ class?: string;
21
+ children?: Snippet;
22
+ };
23
+
24
+ let {
25
+ orientation = "horizontal",
26
+ attached = false,
27
+ gap,
28
+ size = "md",
29
+ label,
30
+ class: className,
31
+ children,
32
+ ...rest
33
+ }: ButtonGroupProps = $props();
34
+
35
+ const classes = $derived(
36
+ [
37
+ "st-buttonGroup",
38
+ `st-buttonGroup--${orientation}`,
39
+ attached ? "st-buttonGroup--attached" : null,
40
+ className
41
+ ]
42
+ .filter(Boolean)
43
+ .join(" ")
44
+ );
45
+
46
+ const gapValue = $derived(
47
+ attached || gap == null ? null : `var(--st-spacing-${gap}, ${gap * 0.25}rem)`
48
+ );
49
+ </script>
50
+
51
+ <div
52
+ {...rest}
53
+ class={classes}
54
+ role="group"
55
+ aria-label={label}
56
+ data-size={size}
57
+ style:gap={gapValue}
58
+ >
59
+ {@render children?.()}
60
+ </div>
61
+
62
+ <style>
63
+ .st-buttonGroup {
64
+ align-items: stretch;
65
+ display: inline-flex;
66
+ }
67
+
68
+ .st-buttonGroup--horizontal {
69
+ flex-direction: row;
70
+ }
71
+
72
+ .st-buttonGroup--vertical {
73
+ flex-direction: column;
74
+ }
75
+
76
+ /* Default gap when no explicit gap prop and not attached. The inline style
77
+ overrides this when a gap prop is provided. */
78
+ .st-buttonGroup:not(.st-buttonGroup--attached) {
79
+ gap: var(--st-spacing-2, 0.5rem);
80
+ }
81
+
82
+ /* Attached / segmented look: buttons share edges, only the outer corners
83
+ remain rounded. Inner borders are collapsed to a single hairline. */
84
+ .st-buttonGroup--attached {
85
+ gap: 0;
86
+ }
87
+
88
+ .st-buttonGroup--attached.st-buttonGroup--horizontal > :global(* + *) {
89
+ margin-inline-start: -1px;
90
+ }
91
+
92
+ .st-buttonGroup--attached.st-buttonGroup--vertical > :global(* + *) {
93
+ margin-block-start: -1px;
94
+ }
95
+
96
+ .st-buttonGroup--attached.st-buttonGroup--horizontal > :global(*:not(:first-child):not(:last-child)) {
97
+ border-radius: 0;
98
+ }
99
+
100
+ .st-buttonGroup--attached.st-buttonGroup--horizontal > :global(*:first-child) {
101
+ border-end-end-radius: 0;
102
+ border-start-end-radius: 0;
103
+ }
104
+
105
+ .st-buttonGroup--attached.st-buttonGroup--horizontal > :global(*:last-child) {
106
+ border-end-start-radius: 0;
107
+ border-start-start-radius: 0;
108
+ }
109
+
110
+ .st-buttonGroup--attached.st-buttonGroup--vertical > :global(*:not(:first-child):not(:last-child)) {
111
+ border-radius: 0;
112
+ }
113
+
114
+ .st-buttonGroup--attached.st-buttonGroup--vertical > :global(*:first-child) {
115
+ border-end-start-radius: 0;
116
+ border-end-end-radius: 0;
117
+ }
118
+
119
+ .st-buttonGroup--attached.st-buttonGroup--vertical > :global(*:last-child) {
120
+ border-start-start-radius: 0;
121
+ border-start-end-radius: 0;
122
+ }
123
+
124
+ /* Raise the focused/hovered button so its full border is visible over the
125
+ collapsed hairline. */
126
+ .st-buttonGroup--attached > :global(*:hover),
127
+ .st-buttonGroup--attached > :global(*:focus-visible) {
128
+ z-index: 1;
129
+ }
130
+ </style>
@@ -0,0 +1,21 @@
1
+ export type ButtonGroupOrientation = "horizontal" | "vertical";
2
+ export type ButtonGroupSize = "sm" | "md" | "lg";
3
+ import type { Snippet } from "svelte";
4
+ import type { HTMLAttributes } from "svelte/elements";
5
+ type ButtonGroupProps = Omit<HTMLAttributes<HTMLDivElement>, "class"> & {
6
+ orientation?: ButtonGroupOrientation;
7
+ /** Look segmenté joint (boutons collés, coins arrondis seulement aux extrémités). */
8
+ attached?: boolean;
9
+ /** Espacement entre boutons (échelle spacing), ignoré quand `attached`. */
10
+ gap?: number;
11
+ /** Taille indicative (transmise via data-attr pour styliser les enfants si besoin). */
12
+ size?: ButtonGroupSize;
13
+ /** Étiquette a11y du groupe. */
14
+ label?: string;
15
+ class?: string;
16
+ children?: Snippet;
17
+ };
18
+ declare const ButtonGroup: import("svelte").Component<ButtonGroupProps, {}, "">;
19
+ type ButtonGroup = ReturnType<typeof ButtonGroup>;
20
+ export default ButtonGroup;
21
+ //# sourceMappingURL=ButtonGroup.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ButtonGroup.svelte.d.ts","sourceRoot":"","sources":["../src/lib/ButtonGroup.svelte.ts"],"names":[],"mappings":"AAGE,MAAM,MAAM,sBAAsB,GAAG,YAAY,GAAG,UAAU,CAAC;AAC/D,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAGnD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGpD,KAAK,gBAAgB,GAAG,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC,GAAG;IACtE,WAAW,CAAC,EAAE,sBAAsB,CAAC;IACrC,qFAAqF;IACrF,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,2EAA2E;IAC3E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,uFAAuF;IACvF,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB,gCAAgC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AA2CJ,QAAA,MAAM,WAAW,sDAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
@@ -0,0 +1,116 @@
1
+ <script lang="ts" module>
2
+ export interface CheckboxGroupOption {
3
+ label: string;
4
+ value: string;
5
+ disabled?: boolean;
6
+ helperText?: string;
7
+ }
8
+ </script>
9
+
10
+ <script lang="ts">
11
+ import type { Snippet } from "svelte";
12
+ import type { HTMLAttributes } from "svelte/elements";
13
+ import Checkbox from "./Checkbox.svelte";
14
+
15
+ type CheckboxGroupProps = Omit<HTMLAttributes<HTMLFieldSetElement>, "class" | "onchange"> & {
16
+ legend: string;
17
+ /** Valeurs cochées (liste contrôlée). */
18
+ value?: string[];
19
+ onchange?: (value: string[]) => void;
20
+ orientation?: "vertical" | "horizontal";
21
+ /** Nom partagé par les cases (utile pour la soumission de formulaire). */
22
+ name?: string;
23
+ options?: CheckboxGroupOption[];
24
+ /** Description optionnelle affichée sous la légende. */
25
+ helperText?: string;
26
+ /** Marque le groupe comme requis. */
27
+ disabled?: boolean;
28
+ class?: string;
29
+ /** Contenu libre rendu en plus des options. */
30
+ children?: Snippet;
31
+ };
32
+
33
+ let {
34
+ legend,
35
+ value = [],
36
+ onchange,
37
+ orientation = "vertical",
38
+ name,
39
+ options = [],
40
+ helperText,
41
+ disabled = false,
42
+ class: className,
43
+ children,
44
+ ...rest
45
+ }: CheckboxGroupProps = $props();
46
+
47
+ const classes = $derived(
48
+ ["st-checkboxGroup", `st-checkboxGroup--${orientation}`, className].filter(Boolean).join(" ")
49
+ );
50
+
51
+ function toggle(optionValue: string, checked: boolean) {
52
+ const next = checked
53
+ ? [...value, optionValue]
54
+ : value.filter((v) => v !== optionValue);
55
+ onchange?.(next);
56
+ }
57
+ </script>
58
+
59
+ <fieldset {...rest} class={classes} {disabled}>
60
+ <legend class="st-checkboxGroup__legend">{legend}</legend>
61
+ {#if helperText}
62
+ <p class="st-checkboxGroup__help">{helperText}</p>
63
+ {/if}
64
+ <div class="st-checkboxGroup__options">
65
+ {#each options as option (option.value)}
66
+ <Checkbox
67
+ label={option.label}
68
+ helperText={option.helperText}
69
+ {name}
70
+ value={option.value}
71
+ checked={value.includes(option.value)}
72
+ disabled={option.disabled}
73
+ onchange={(event) => toggle(option.value, event.currentTarget.checked)}
74
+ />
75
+ {/each}
76
+ {@render children?.()}
77
+ </div>
78
+ </fieldset>
79
+
80
+ <style>
81
+ .st-checkboxGroup {
82
+ border: 0;
83
+ margin: 0;
84
+ min-width: 0;
85
+ padding: 0;
86
+ }
87
+
88
+ .st-checkboxGroup__legend {
89
+ color: var(--st-component-field-labelText, var(--st-semantic-text-primary));
90
+ font-size: 0.9375rem;
91
+ font-weight: 650;
92
+ line-height: 1.3;
93
+ padding: 0;
94
+ }
95
+
96
+ .st-checkboxGroup__help {
97
+ color: var(--st-component-field-helpText, var(--st-semantic-text-secondary));
98
+ font-size: 0.8125rem;
99
+ margin: 0.25rem 0 0;
100
+ }
101
+
102
+ .st-checkboxGroup__options {
103
+ display: flex;
104
+ gap: var(--st-spacing-3, 0.75rem);
105
+ margin-top: var(--st-spacing-2, 0.5rem);
106
+ }
107
+
108
+ .st-checkboxGroup--vertical .st-checkboxGroup__options {
109
+ flex-direction: column;
110
+ }
111
+
112
+ .st-checkboxGroup--horizontal .st-checkboxGroup__options {
113
+ flex-direction: row;
114
+ flex-wrap: wrap;
115
+ }
116
+ </style>
@@ -0,0 +1,29 @@
1
+ export interface CheckboxGroupOption {
2
+ label: string;
3
+ value: string;
4
+ disabled?: boolean;
5
+ helperText?: string;
6
+ }
7
+ import type { Snippet } from "svelte";
8
+ import type { HTMLAttributes } from "svelte/elements";
9
+ type CheckboxGroupProps = Omit<HTMLAttributes<HTMLFieldSetElement>, "class" | "onchange"> & {
10
+ legend: string;
11
+ /** Valeurs cochées (liste contrôlée). */
12
+ value?: string[];
13
+ onchange?: (value: string[]) => void;
14
+ orientation?: "vertical" | "horizontal";
15
+ /** Nom partagé par les cases (utile pour la soumission de formulaire). */
16
+ name?: string;
17
+ options?: CheckboxGroupOption[];
18
+ /** Description optionnelle affichée sous la légende. */
19
+ helperText?: string;
20
+ /** Marque le groupe comme requis. */
21
+ disabled?: boolean;
22
+ class?: string;
23
+ /** Contenu libre rendu en plus des options. */
24
+ children?: Snippet;
25
+ };
26
+ declare const CheckboxGroup: import("svelte").Component<CheckboxGroupProps, {}, "">;
27
+ type CheckboxGroup = ReturnType<typeof CheckboxGroup>;
28
+ export default CheckboxGroup;
29
+ //# sourceMappingURL=CheckboxGroup.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CheckboxGroup.svelte.d.ts","sourceRoot":"","sources":["../src/lib/CheckboxGroup.svelte.ts"],"names":[],"mappings":"AAGE,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAIpD,KAAK,kBAAkB,GAAG,IAAI,CAAC,cAAc,CAAC,mBAAmB,CAAC,EAAE,OAAO,GAAG,UAAU,CAAC,GAAG;IAC1F,MAAM,EAAE,MAAM,CAAC;IACf,yCAAyC;IACzC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACrC,WAAW,CAAC,EAAE,UAAU,GAAG,YAAY,CAAC;IACxC,0EAA0E;IAC1E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,mBAAmB,EAAE,CAAC;IAChC,wDAAwD;IACxD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qCAAqC;IACrC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAoDJ,QAAA,MAAM,aAAa,wDAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}