zabi-components 5.0.20 → 5.0.21

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,88 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import type { ButtonVariant, SizeVariant } from "../../types/variants.js";
4
+
5
+ interface Props {
6
+ /** Visual style variant. */
7
+ variant?: ButtonVariant;
8
+ /** Size variant controlling padding and radius. */
9
+ size?: SizeVariant;
10
+ /** Whether the button is disabled. */
11
+ disabled?: boolean;
12
+ /** Button type attribute. */
13
+ type?: "button" | "submit" | "reset";
14
+ /** Accessible label for icon-only buttons. */
15
+ label?: string;
16
+ /** Click handler for native click events. */
17
+ onclick?: (event: MouseEvent) => void;
18
+ /** Icon content. */
19
+ children?: Snippet;
20
+ }
21
+
22
+ let {
23
+ variant = "primary",
24
+ size = "md",
25
+ disabled = false,
26
+ type = "button",
27
+ label = "",
28
+ onclick,
29
+ children,
30
+ ...restProps
31
+ }: Props = $props();
32
+
33
+ const sizeClass = $derived(() => {
34
+ if (size === "sm") {
35
+ return {
36
+ padding: "p-2",
37
+ radius: "rounded-md",
38
+ };
39
+ } else if (size === "lg") {
40
+ return {
41
+ padding: "p-3",
42
+ radius: "rounded-xl",
43
+ };
44
+ } else {
45
+ return {
46
+ padding: "p-2.5",
47
+ radius: "rounded-lg",
48
+ };
49
+ }
50
+ });
51
+
52
+ const variantClass = $derived(() => {
53
+ return variant === "primary"
54
+ ? "bg-action-primary text-action-primary hover:bg-action-primary-hover active:bg-action-primary-active focus:ring-2 focus:ring-brand-500 focus:ring-offset-2"
55
+ : variant === "secondary"
56
+ ? "bg-action-secondary text-headline hover:bg-action-secondary-hover active:bg-action-secondary-active focus:ring-2 focus:ring-brand-500 focus:ring-offset-2"
57
+ : variant === "danger"
58
+ ? "bg-action-danger text-inverse hover:bg-action-danger-hover active:bg-action-danger-active focus:ring-2 focus:ring-red-500 focus:ring-offset-2"
59
+ : variant === "ghost"
60
+ ? "bg-transparent text-headline hover:bg-base-100 active:bg-base-200 focus:ring-2 focus:ring-base-500 focus:ring-offset-2 disabled:text-disabled"
61
+ : variant === "outline"
62
+ ? "bg-transparent border-2 border-action-primary text-headline hover:bg-action-secondary hover:text-action-primary active:bg-action-primary-active active:text-action-primary focus:ring-2 focus:ring-brand-500 focus:ring-offset-2 disabled:border-disabled disabled:text-disabled"
63
+ : variant === "link"
64
+ ? "text-brand-700 active:text-brand-700 underline-offset-4 hover:underline focus:ring-2 focus:ring-brand-500 focus:ring-offset-2 disabled:text-disabled disabled:no-underline"
65
+ : "bg-action-primary text-action-primary hover:bg-action-primary-hover active:bg-action-primary-active focus:ring-2 focus:ring-brand-500 focus:ring-offset-2";
66
+ });
67
+
68
+ const buttonClasses = $derived(() => {
69
+ const sizeStyles = sizeClass();
70
+ const baseClasses =
71
+ "inline-flex items-center justify-center transition-all duration-200 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed focus:outline-none";
72
+
73
+ return `${baseClasses} ${sizeStyles.padding} ${sizeStyles.radius} ${variantClass()}`.trim();
74
+ });
75
+ </script>
76
+
77
+ <button
78
+ {type}
79
+ class={buttonClasses()}
80
+ {disabled}
81
+ {onclick}
82
+ aria-label={label || undefined}
83
+ {...restProps}
84
+ >
85
+ {#if children}
86
+ {@render children()}
87
+ {/if}
88
+ </button>
@@ -0,0 +1,21 @@
1
+ import type { Snippet } from "svelte";
2
+ import type { ButtonVariant, SizeVariant } from "../../types/variants.js";
3
+ interface Props {
4
+ /** Visual style variant. */
5
+ variant?: ButtonVariant;
6
+ /** Size variant controlling padding and radius. */
7
+ size?: SizeVariant;
8
+ /** Whether the button is disabled. */
9
+ disabled?: boolean;
10
+ /** Button type attribute. */
11
+ type?: "button" | "submit" | "reset";
12
+ /** Accessible label for icon-only buttons. */
13
+ label?: string;
14
+ /** Click handler for native click events. */
15
+ onclick?: (event: MouseEvent) => void;
16
+ /** Icon content. */
17
+ children?: Snippet;
18
+ }
19
+ declare const IconButton: import("svelte").Component<Props, {}, "">;
20
+ type IconButton = ReturnType<typeof IconButton>;
21
+ export default IconButton;
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import Dropdown from "../molecules/Dropdown.svelte";
3
+ import Button from "./Button.svelte";
3
4
  import {
4
5
  ChevronDown,
5
6
  CheckCircle,
@@ -22,6 +23,16 @@
22
23
  label: string;
23
24
  disabled?: boolean;
24
25
  }>;
26
+ /** Enables the search input inside the dropdown. */
27
+ searchable?: boolean;
28
+ /** Placeholder for the search input. */
29
+ searchPlaceholder?: string;
30
+ /** Max height for the options list (supports any valid CSS length). */
31
+ maxMenuHeight?: string;
32
+ /** Width for the dropdown menu (supports any valid CSS length). */
33
+ menuWidth?: string;
34
+ /** Message shown when no options match the search. */
35
+ noResultsText?: string;
25
36
  placeholder?: string;
26
37
  label?: string;
27
38
  disabled?: boolean;
@@ -34,6 +45,11 @@
34
45
  let {
35
46
  value = undefined,
36
47
  options = [],
48
+ searchable = true,
49
+ searchPlaceholder = "Search options",
50
+ maxMenuHeight = "60vh",
51
+ menuWidth = "100%",
52
+ noResultsText = "No results found",
37
53
  placeholder = "Select an option",
38
54
  label = "",
39
55
  disabled = false,
@@ -46,6 +62,7 @@
46
62
 
47
63
  let isOpen = $state(false);
48
64
  let selectContainer: HTMLDivElement;
65
+ let searchQuery = $state("");
49
66
 
50
67
  const sizeClass = $derived(() => {
51
68
  if (size === "sm") {
@@ -93,13 +110,13 @@
93
110
 
94
111
  const messageClasses = $derived(() => {
95
112
  if (variant === "error") {
96
- return "text-error text-sm mt-1 flex items-center gap-1.5";
113
+ return "text-error text-sm mt-1 flex items-center gap-1.5 w-full";
97
114
  } else if (variant === "success") {
98
- return "text-success text-sm mt-1 flex items-center gap-1.5";
115
+ return "text-success text-sm mt-1 flex items-center gap-1.5 w-full";
99
116
  } else if (variant === "warning") {
100
- return "text-warning text-sm mt-1 flex items-center gap-1.5";
117
+ return "text-warning text-sm mt-1 flex items-center gap-1.5 w-full";
101
118
  }
102
- return "text-description text-sm mt-1 flex items-center gap-1.5";
119
+ return "text-description text-sm mt-1 flex items-center gap-1.5 w-full";
103
120
  });
104
121
 
105
122
  const getIcon = $derived(() => {
@@ -125,6 +142,33 @@
125
142
  return value === undefined || value === null || value === "";
126
143
  });
127
144
 
145
+ const normalizedOptions = $derived(() => {
146
+ const fallbackOptions = (restProps as { options?: Props["options"] })
147
+ .options;
148
+ return Array.from(options ?? fallbackOptions ?? []);
149
+ });
150
+
151
+ const hasSearchQuery = $derived(() => {
152
+ return searchQuery.trim().length > 0;
153
+ });
154
+
155
+ const filteredOptions = $derived(() => {
156
+ const query = searchQuery.trim().toLowerCase();
157
+ const availableOptions = normalizedOptions();
158
+ if (!searchable || query.length === 0) {
159
+ return availableOptions;
160
+ }
161
+ return availableOptions.filter((option) =>
162
+ option.label.toLowerCase().includes(query),
163
+ );
164
+ });
165
+
166
+ $effect(() => {
167
+ if (!isOpen && searchQuery.length > 0) {
168
+ searchQuery = "";
169
+ }
170
+ });
171
+
128
172
  function handleOptionClick(optionValue: string | number) {
129
173
  if (disabled) return;
130
174
  value = optionValue;
@@ -179,8 +223,8 @@
179
223
  {isOpen}
180
224
  placement="bottom-start"
181
225
  selectedValue={value}
182
- {options}
183
226
  onOptionClick={handleOptionClick}
227
+ ariaLabel="Select options"
184
228
  >
185
229
  {#snippet trigger()}
186
230
  <button
@@ -194,7 +238,7 @@
194
238
  aria-describedby={message ? `${selectId}-message` : undefined}
195
239
  >
196
240
  <span
197
- class="text-left flex-1 {isEmpty()
241
+ class="text-left flex-1 truncate {isEmpty()
198
242
  ? 'text-description'
199
243
  : 'text-body'}"
200
244
  >
@@ -211,6 +255,52 @@
211
255
  />
212
256
  </button>
213
257
  {/snippet}
258
+ {#snippet children()}
259
+ <div class="px-2 pb-2 pt-1" style:width={menuWidth}>
260
+ {#if searchable}
261
+ <div class="px-1 pb-2">
262
+ <input
263
+ type="text"
264
+ class="w-full rounded-md bg-input hover:bg-input-hover focus:bg-input-focus text-body placeholder:text-description px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-brand-500 disabled:opacity-50 disabled:cursor-not-allowed"
265
+ placeholder={searchPlaceholder}
266
+ bind:value={searchQuery}
267
+ aria-label={searchPlaceholder}
268
+ {disabled}
269
+ />
270
+ </div>
271
+ {/if}
272
+ <div
273
+ class="overflow-y-auto px-1"
274
+ style:max-height={maxMenuHeight}
275
+ >
276
+ {#if filteredOptions().length > 0}
277
+ {#each filteredOptions() as option (option.value)}
278
+ {@const buttonRestProps = {
279
+ "data-value": String(option.value),
280
+ } as Record<string, string>}
281
+ <div class="w-full my-0.5">
282
+ <Button
283
+ variant={value === option.value
284
+ ? "outline"
285
+ : "ghost"}
286
+ size="sm"
287
+ isFullWidth={true}
288
+ disabled={option.disabled}
289
+ onclick={() => handleOptionClick(option.value)}
290
+ {...buttonRestProps}
291
+ >
292
+ {option.label}
293
+ </Button>
294
+ </div>
295
+ {/each}
296
+ {:else if hasSearchQuery()}
297
+ <div class="px-3 py-2 text-sm text-description">
298
+ {noResultsText}
299
+ </div>
300
+ {/if}
301
+ </div>
302
+ </div>
303
+ {/snippet}
214
304
  </Dropdown>
215
305
  {#if message && variant !== "default"}
216
306
  <p id={`${selectId}-message`} class={messageClasses()} role="alert">
@@ -5,6 +5,16 @@ interface Props {
5
5
  label: string;
6
6
  disabled?: boolean;
7
7
  }>;
8
+ /** Enables the search input inside the dropdown. */
9
+ searchable?: boolean;
10
+ /** Placeholder for the search input. */
11
+ searchPlaceholder?: string;
12
+ /** Max height for the options list (supports any valid CSS length). */
13
+ maxMenuHeight?: string;
14
+ /** Width for the dropdown menu (supports any valid CSS length). */
15
+ menuWidth?: string;
16
+ /** Message shown when no options match the search. */
17
+ noResultsText?: string;
8
18
  placeholder?: string;
9
19
  label?: string;
10
20
  disabled?: boolean;
@@ -1,6 +1,7 @@
1
1
  export { default as Toggle } from './Toggle.svelte';
2
2
  export { default as Badge } from './Badge.svelte';
3
3
  export { default as Button } from './Button.svelte';
4
+ export { default as IconButton } from './IconButton.svelte';
4
5
  export { default as Card } from './Card.svelte';
5
6
  export { default as CardHeader } from './CardHeader.svelte';
6
7
  export { default as CardContent } from './CardContent.svelte';
@@ -1,6 +1,7 @@
1
1
  export { default as Toggle } from './Toggle.svelte';
2
2
  export { default as Badge } from './Badge.svelte';
3
3
  export { default as Button } from './Button.svelte';
4
+ export { default as IconButton } from './IconButton.svelte';
4
5
  export { default as Card } from './Card.svelte';
5
6
  export { default as CardHeader } from './CardHeader.svelte';
6
7
  export { default as CardContent } from './CardContent.svelte';
@@ -1,6 +1,7 @@
1
1
  export { default as Toggle } from './Toggle.svelte';
2
2
  export { default as Badge } from './Badge.svelte';
3
3
  export { default as Button } from './Button.svelte';
4
+ export { default as IconButton } from './IconButton.svelte';
4
5
  export { default as Card } from './Card.svelte';
5
6
  export { default as CardHeader } from './CardHeader.svelte';
6
7
  export { default as CardContent } from './CardContent.svelte';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/atoms/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/atoms/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC"}
@@ -11,6 +11,7 @@ export { default as NavigationMenuItem } from './NavigationMenuItem.svelte';
11
11
  export { default as NavigationMenuTrigger } from './NavigationMenuTrigger.svelte';
12
12
  export { default as NavigationMenuContent } from './NavigationMenuContent.svelte';
13
13
  export { default as NavigationMenuLink } from './NavigationMenuLink.svelte';
14
+ export { default as Section } from './Section.svelte';
14
15
  export { default as Sidebar } from './Sidebar.svelte';
15
16
  export { default as SlideUp } from './SlideUp.svelte';
16
17
  export { default as Tabs } from './Tabs.svelte';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/molecules/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAClF,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAClF,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/molecules/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAClF,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAClF,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,158 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import type { SizeVariant } from "../../types/variants.js";
4
+ import Heading from "../atoms/Heading.svelte";
5
+
6
+ interface Props {
7
+ /** Section title */
8
+ title?: string;
9
+ /** Section description/subtitle */
10
+ description?: string;
11
+ /** Heading level for title (1-6) */
12
+ headingLevel?: number;
13
+ /** Section size variant */
14
+ size?: SizeVariant;
15
+ /** Maximum width of the section container */
16
+ maxWidth?: "sm" | "md" | "lg" | "xl" | "2xl" | "full" | "none";
17
+ /** Background variant */
18
+ background?: "default" | "muted" | "accent" | "transparent";
19
+ /** Padding variant */
20
+ padding?: "none" | "sm" | "md" | "lg" | "xl";
21
+ /** Whether to center the content */
22
+ centered?: boolean;
23
+ /** Additional CSS classes */
24
+ className?: string;
25
+ /** Section content */
26
+ children?: Snippet;
27
+ }
28
+
29
+ let {
30
+ title = "",
31
+ description = "",
32
+ headingLevel = 2,
33
+ size = "md",
34
+ maxWidth = "xl",
35
+ background = "default",
36
+ padding = "lg",
37
+ centered = false,
38
+ className = "",
39
+ children,
40
+ ...restProps
41
+ }: Props = $props();
42
+
43
+ const sizeClasses = $derived(() => {
44
+ switch (size) {
45
+ case "sm":
46
+ return "text-sm";
47
+ case "lg":
48
+ return "text-lg";
49
+ default:
50
+ return "text-base";
51
+ }
52
+ });
53
+
54
+ const maxWidthClasses = $derived(() => {
55
+ switch (maxWidth) {
56
+ case "sm":
57
+ return "max-w-screen-sm";
58
+ case "md":
59
+ return "max-w-screen-md";
60
+ case "lg":
61
+ return "max-w-screen-lg";
62
+ case "xl":
63
+ return "max-w-screen-xl";
64
+ case "2xl":
65
+ return "max-w-screen-2xl";
66
+ case "full":
67
+ return "max-w-full";
68
+ case "none":
69
+ return "";
70
+ default:
71
+ return "max-w-screen-xl";
72
+ }
73
+ });
74
+
75
+ const backgroundClasses = $derived(() => {
76
+ switch (background) {
77
+ case "muted":
78
+ return "bg-base-50";
79
+ case "accent":
80
+ return "bg-brand-50 dark:bg-brand-950";
81
+ case "transparent":
82
+ return "bg-transparent";
83
+ default:
84
+ return "bg-background";
85
+ }
86
+ });
87
+
88
+ const paddingClasses = $derived(() => {
89
+ switch (padding) {
90
+ case "none":
91
+ return "";
92
+ case "sm":
93
+ return "py-8 sm:py-12";
94
+ case "md":
95
+ return "py-12 sm:py-16";
96
+ case "lg":
97
+ return "py-16 sm:py-24";
98
+ case "xl":
99
+ return "py-24 sm:py-32";
100
+ default:
101
+ return "py-16 sm:py-24";
102
+ }
103
+ });
104
+
105
+ const containerClasses = $derived(() => {
106
+ const base = "w-full";
107
+ const maxWidthClass = maxWidthClasses();
108
+ const centeredClass = centered ? "mx-auto" : "";
109
+ return `${base} ${maxWidthClass} ${centeredClass}`.trim();
110
+ });
111
+
112
+ const contentClasses = $derived(() => {
113
+ const base = "px-4 sm:px-6 lg:px-8";
114
+ const centeredClass = centered ? "text-center" : "";
115
+ return `${base} ${centeredClass}`.trim();
116
+ });
117
+ </script>
118
+
119
+ <section
120
+ class="section {backgroundClasses()} {paddingClasses()} {className}"
121
+ {...restProps}
122
+ >
123
+ <div class={containerClasses()}>
124
+ <div class={contentClasses()}>
125
+ {#if title}
126
+ <div class="mb-6 {centered ? 'mx-auto' : ''}">
127
+ <Heading level={headingLevel} text={title} />
128
+ {#if description}
129
+ <p
130
+ class="mt-4 {sizeClasses()} text-description {centered
131
+ ? 'mx-auto max-w-2xl'
132
+ : ''}"
133
+ >
134
+ {description}
135
+ </p>
136
+ {/if}
137
+ </div>
138
+ {/if}
139
+
140
+ {#if children}
141
+ <div class="section-content {centered ? 'mx-auto' : ''}">
142
+ {@render children()}
143
+ </div>
144
+ {/if}
145
+ </div>
146
+ </div>
147
+ </section>
148
+
149
+ <style>
150
+ .section {
151
+ position: relative;
152
+ width: 100%;
153
+ }
154
+
155
+ .section-content {
156
+ width: 100%;
157
+ }
158
+ </style>
@@ -0,0 +1,27 @@
1
+ import type { Snippet } from "svelte";
2
+ import type { SizeVariant } from "../../types/variants.js";
3
+ interface Props {
4
+ /** Section title */
5
+ title?: string;
6
+ /** Section description/subtitle */
7
+ description?: string;
8
+ /** Heading level for title (1-6) */
9
+ headingLevel?: number;
10
+ /** Section size variant */
11
+ size?: SizeVariant;
12
+ /** Maximum width of the section container */
13
+ maxWidth?: "sm" | "md" | "lg" | "xl" | "2xl" | "full" | "none";
14
+ /** Background variant */
15
+ background?: "default" | "muted" | "accent" | "transparent";
16
+ /** Padding variant */
17
+ padding?: "none" | "sm" | "md" | "lg" | "xl";
18
+ /** Whether to center the content */
19
+ centered?: boolean;
20
+ /** Additional CSS classes */
21
+ className?: string;
22
+ /** Section content */
23
+ children?: Snippet;
24
+ }
25
+ declare const Section: import("svelte").Component<Props, {}, "">;
26
+ type Section = ReturnType<typeof Section>;
27
+ export default Section;
@@ -11,6 +11,7 @@ export { default as NavigationMenuItem } from './NavigationMenuItem.svelte';
11
11
  export { default as NavigationMenuTrigger } from './NavigationMenuTrigger.svelte';
12
12
  export { default as NavigationMenuContent } from './NavigationMenuContent.svelte';
13
13
  export { default as NavigationMenuLink } from './NavigationMenuLink.svelte';
14
+ export { default as Section } from './Section.svelte';
14
15
  export { default as Sidebar } from './Sidebar.svelte';
15
16
  export { default as SlideUp } from './SlideUp.svelte';
16
17
  export { default as Tabs } from './Tabs.svelte';
@@ -11,6 +11,7 @@ export { default as NavigationMenuItem } from './NavigationMenuItem.svelte';
11
11
  export { default as NavigationMenuTrigger } from './NavigationMenuTrigger.svelte';
12
12
  export { default as NavigationMenuContent } from './NavigationMenuContent.svelte';
13
13
  export { default as NavigationMenuLink } from './NavigationMenuLink.svelte';
14
+ export { default as Section } from './Section.svelte';
14
15
  export { default as Sidebar } from './Sidebar.svelte';
15
16
  export { default as SlideUp } from './SlideUp.svelte';
16
17
  export { default as Tabs } from './Tabs.svelte';
@@ -35,11 +35,11 @@
35
35
  const ulClasses = $derived(() => {
36
36
  return variant === "sidebar"
37
37
  ? "flex flex-col gap-1"
38
- : "flex items-center justify-between gap-1";
38
+ : "flex flex-col gap-1 md:flex-row md:items-center md:justify-between";
39
39
  });
40
40
 
41
41
  function getNavItemClasses(itemHref: string): string {
42
- const baseClasses = "flex flex-col gap-1 grow h-full items-center justify-center min-h-0 min-w-0 relative shrink-0 cursor-pointer";
42
+ const baseClasses = "flex flex-col gap-1 grow h-full items-center justify-center min-h-0 min-w-0 relative shrink-0 cursor-pointer w-full md:w-auto";
43
43
 
44
44
  return baseClasses;
45
45
  }