@valerius_petrini/corekit-ui 0.1.71 → 0.1.73

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.
@@ -1,3 +1,7 @@
1
+ <script module>
2
+ let count = 0;
3
+ </script>
4
+
1
5
  <script lang="ts">
2
6
  import { twMerge } from "tailwind-merge";
3
7
  import Text from "../../typography/Text/index.svelte";
@@ -11,7 +15,7 @@
11
15
  labelClass = "",
12
16
  divClass = "",
13
17
  checked = $bindable(),
14
- id = crypto.randomUUID(),
18
+ id = `checkbox-${count++}`,
15
19
  ...restProps
16
20
  }: CheckboxProps = $props();
17
21
 
@@ -1,3 +1,7 @@
1
+ <script module>
2
+ let count = 0;
3
+ </script>
4
+
1
5
  <script lang="ts">
2
6
  import { getSizeStyle, getSizeStyleClass } from "../../../styles/size.js";
3
7
  import type { ColorInputProps } from "./types";
@@ -30,7 +34,7 @@
30
34
  size = "md",
31
35
  radius = "md",
32
36
  variant = "full",
33
- id = crypto.randomUUID(),
37
+ id = `color-input-${count++}`,
34
38
  ...restProps
35
39
  }: ColorInputProps = $props();
36
40
 
@@ -263,12 +267,14 @@
263
267
  <svelte:window onmousedown={handleMouseDown}/>
264
268
 
265
269
  <div class={combinedOuterDivClass} bind:this={element}>
266
- <Text tag="label" for={id} class={combinedLabelClass} style={getSizeStyle(size, "formLabel")}>
267
- {label}
268
- {#if required}
269
- <span class="text-[#E05555]">*</span>
270
- {/if}
271
- </Text>
270
+ {#if label}
271
+ <Text tag="label" for={id} class={combinedLabelClass} style={getSizeStyle(size, "formLabel")}>
272
+ {label}
273
+ {#if required}
274
+ <span class="text-[#E05555]">*</span>
275
+ {/if}
276
+ </Text>
277
+ {/if}
272
278
  <Button color="none" class={combinedDivClass} onclick={handleClick} {disabled}>
273
279
  <input
274
280
  {id}
@@ -288,7 +294,6 @@
288
294
  {value ? value : "Select color"}
289
295
  </Text>
290
296
  </Button>
291
-
292
297
  {#if isOpen}
293
298
  {@const rgb = hexToRgb(value || "#000000")}
294
299
  {@const hsl = hexToHsl(value || "#000000")}
@@ -305,11 +310,11 @@
305
310
  style="background-color: {value || 'transparent'}; left: {thumbX}px; top: {thumbY}px;"
306
311
  ></div>
307
312
  </div>
308
-
313
+
309
314
  <div class="h-36 w-4 hue-slider relative" bind:this={hueEl}>
310
315
  <div class="slider absolute w-5 h-1 border border-white shadow" style="top: {(hue / 360) * 100}%"></div>
311
316
  </div>
312
-
317
+
313
318
  <div class="grow flex flex-col gap-2 min-w-40">
314
319
  <Input
315
320
  type="text"
@@ -321,7 +326,7 @@
321
326
  onkeydown={handleEnter}
322
327
  placeholder="#ffffff"
323
328
  />
324
-
329
+
325
330
  <Input
326
331
  type="text"
327
332
  variant="floating"
@@ -332,7 +337,7 @@
332
337
  onkeydown={handleEnter}
333
338
  placeholder="0, 0, 0"
334
339
  />
335
-
340
+
336
341
  <Input
337
342
  type="text"
338
343
  variant="floating"
@@ -343,7 +348,7 @@
343
348
  onkeydown={handleEnter}
344
349
  placeholder="0, 0, 0"
345
350
  />
346
-
351
+
347
352
  <Input
348
353
  type="text"
349
354
  variant="floating"
@@ -12,5 +12,5 @@ export interface ColorInputProps extends BaseComponentProps {
12
12
  variant?: ColorInputVariant;
13
13
  size?: SizeStyle;
14
14
  radius?: SizeStyle;
15
- id?: `${string}-${string}-${string}-${string}-${string}`;
15
+ id?: string;
16
16
  }
@@ -1,3 +1,7 @@
1
+ <script module>
2
+ let count = 0;
3
+ </script>
4
+
1
5
  <script lang="ts">
2
6
  import type { ComboboxProps } from "./types";
3
7
  import { twMerge } from "tailwind-merge";
@@ -7,8 +11,6 @@
7
11
  import BaseInput from "../helper/BaseInput.svelte";
8
12
  import Text from "../../typography/Text/index.svelte";
9
13
  import { fly } from "svelte/transition";
10
- import { debounce } from "../../../utils/debounce.js";
11
- import { tick } from "svelte";
12
14
 
13
15
  let {
14
16
  children = undefined,
@@ -30,43 +32,28 @@
30
32
  radius = "md",
31
33
  options = [],
32
34
  limit = 10,
33
- id = crypto.randomUUID(),
35
+ id = `combobox-${count++}`,
34
36
  ...restProps
35
37
  }: ComboboxProps = $props();
36
38
 
37
39
  let isFocused = $state(false);
38
40
  let activeIndex = $state(0);
39
41
 
42
+ let floatingEl = $state<HTMLDivElement>();
40
43
  let dropdownX = $state(0);
41
44
  let dropdownY = $state(0);
42
- let floatingEl = $state<HTMLDivElement>();
43
-
44
- let debouncedSearch = $state("");
45
-
46
- const updateSearch = debounce((v: string) => {
47
- debouncedSearch = v;
48
- }, 150);
49
-
50
- $effect(() => {
51
- updateSearch(value ?? "");
52
- });
45
+ let referenceWidth = $state(0);
46
+ let ready = $state(false);
53
47
 
54
48
  const sizeClasses = $derived(getSizeStyleClass(size, "form"));
55
49
  const labelSizeClass = $derived(getSizeStyleClass(size, "formLabel"));
56
50
 
57
51
  let inputElement = $state<HTMLInputElement>();
58
52
 
59
- const customStyle = $derived.by(() => {
60
- const styles: string[] = [];
61
-
62
- if (typeof size === "number")
63
- styles.push(`width: ${size}px`);
64
-
65
- if (typeof radius === "number")
66
- styles.push(`border-radius: ${radius}px`);
67
-
68
- return styles.join("; ");
69
- });
53
+ const customStyle = $derived([
54
+ typeof size === "number" ? `width: ${size}px` : "",
55
+ typeof radius === "number" ? `border-radius: ${radius}px` : "",
56
+ ].filter(Boolean).join("; "));
70
57
 
71
58
  function handleFocus(e: FocusEvent) {
72
59
  isFocused = true;
@@ -80,32 +67,12 @@
80
67
  onblur?.(e);
81
68
  }
82
69
 
83
- async function updateDropdownPosition() {
84
- if (!element || !floatingEl) return;
85
-
86
- referenceWidth = element.offsetWidth;
87
-
88
- const { x, y } = await computePosition(element, floatingEl, {
89
- placement: "bottom-start",
90
- middleware: [
91
- offset(8),
92
- flip(),
93
- shift({ padding: 8 })
94
- ]
95
- });
96
-
97
- dropdownX = x;
98
- dropdownY = y;
99
- }
100
-
101
70
  let defaultClass = "text-main-text w-full outline-none px-1.5 w-full bg-inherit border-0 focus:ring-0 focus-visible:ring-0 rounded-none";
102
71
 
103
72
  let defaultInputClassCheck = $derived(variant !== "floating" ? "py-0" : "");
104
73
  let combinedClass = $derived(twMerge(defaultClass, sizeClasses, defaultInputClassCheck, labelSizeClass, className));
105
74
  let combinedDivClass = $derived(twMerge(divClass));
106
75
 
107
- let referenceWidth = $state(0);
108
-
109
76
  function onClickItem(event: MouseEvent, option: string) {
110
77
  event.preventDefault();
111
78
  value = option;
@@ -113,6 +80,36 @@
113
80
  inputElement?.blur();
114
81
  }
115
82
 
83
+ function initFloating(node: HTMLDivElement) {
84
+ if (!element) return {};
85
+
86
+ let cleanup: (() => void) | null = null;
87
+
88
+ async function updatePosition() {
89
+ referenceWidth = element!.offsetWidth;
90
+
91
+ const { x, y } = await computePosition(element!, node, {
92
+ placement: "bottom-start",
93
+ middleware: [offset(8), flip(), shift({ padding: 8 })]
94
+ });
95
+
96
+ dropdownX = x;
97
+ dropdownY = y;
98
+ ready = true;
99
+ }
100
+
101
+ updatePosition().then(() => {
102
+ cleanup = autoUpdate(element!, node, updatePosition);
103
+ });
104
+
105
+ return {
106
+ destroy() {
107
+ cleanup?.();
108
+ ready = false;
109
+ }
110
+ };
111
+ }
112
+
116
113
  function highlight(label: string, search: string) {
117
114
  const index = label.toLowerCase().indexOf(search.toLowerCase());
118
115
  if (index === -1) return escapeHtml(label);
@@ -130,7 +127,7 @@
130
127
 
131
128
  let filteredOptions = $derived(
132
129
  options.filter(option =>
133
- option.toLowerCase().includes(debouncedSearch.toLowerCase()))
130
+ option.toLowerCase().includes(value?.toLowerCase() ?? ""))
134
131
  );
135
132
 
136
133
  let validOptions = $derived(filteredOptions.slice(0, limit));
@@ -172,17 +169,6 @@
172
169
 
173
170
  let optionsContainerElement = $state<HTMLDivElement>();
174
171
 
175
- $effect(() => {
176
- if (isFocused && element && floatingEl) {
177
- const cleanup = autoUpdate(
178
- element,
179
- floatingEl,
180
- updateDropdownPosition
181
- );
182
- return () => cleanup();
183
- }
184
- });
185
-
186
172
  function handleMouseDown(e: MouseEvent) {
187
173
  if (
188
174
  isFocused &&
@@ -236,24 +222,25 @@
236
222
  {#snippet outerDivElementAfter()}
237
223
  {#if isFocused}
238
224
  <div
239
- bind:this={floatingEl}
240
- transition:fly={{ y: -10, duration: 200 }}
225
+ use:initFloating
241
226
  style="position: fixed; top: {dropdownY}px; left: {dropdownX}px; width: {referenceWidth}px;"
242
- class="z-999999 border-2 overflow-hidden border-blue-500 bg-sub-background {getSizeStyleClass(radius, 'radius')}"
227
+ style:visibility={ready ? "visible" : "hidden"}
228
+ transition:fly={{ y: -10, duration: 200 }}
229
+ class="z-999999 overflow-hidden border border-white/10 bg-sub-background shadow-xl shadow-black/40 {getSizeStyleClass(radius, 'radius')}"
243
230
  >
244
231
  {#if totalMatches > 0 && options.length > limit}
245
- <Text class="text-xs py-0.5 px-1 text-sub-text italic sticky top-0 bg-sub-background w-full">
232
+ <Text class="text-xs py-1.5 px-2.5 text-sub-text italic sticky top-0 bg-sub-background w-full">
246
233
  Showing {limit} of {totalMatches} results for "{value}"
247
234
  </Text>
248
235
  {/if}
249
236
  {#if totalMatches === 0}
250
- <Text class="text-xs py-0.5 px-1 text-sub-text italic sticky top-0 bg-sub-background w-full">
237
+ <Text class="text-xs py-1.5 px-2.5 text-sub-text italic sticky top-0 bg-sub-background w-full">
251
238
  No results found for "{value}"
252
239
  </Text>
253
240
  {/if}
254
241
  <div bind:this={optionsContainerElement} class="overflow-auto max-h-40">
255
242
  {#each validOptions as option, index}
256
- <Text class="text-sm py-0.5 px-1 cursor-pointer hover:bg-sub-background-hover transition-colors {activeIndex === index ? 'bg-sub-background-hover' : ''}" onmousedown={(e: MouseEvent) => onClickItem(e, option)}>
243
+ <Text class="text-sm py-1.5 px-2.5 cursor-pointer hover:bg-white/5 transition-colors {activeIndex === index ? 'bg-white/5' : ''}" onmousedown={(e: MouseEvent) => onClickItem(e, option)}>
257
244
  {@html highlight(option, value ?? "")}
258
245
  </Text>
259
246
  {/each}
@@ -1,3 +1,7 @@
1
+ <script module>
2
+ let count = 0;
3
+ </script>
4
+
1
5
  <script lang="ts">
2
6
  import { getSizeStyle, getSizeStyleClass } from "../../../styles/size.js";
3
7
  import type { FileInputProps } from "./types";
@@ -22,7 +26,7 @@
22
26
  disabled = false,
23
27
  size = "md",
24
28
  radius = "md",
25
- id = crypto.randomUUID(),
29
+ id = `file-input-${count++}`,
26
30
  ...restProps
27
31
  }: FileInputProps = $props();
28
32
 
@@ -77,12 +81,14 @@
77
81
  </script>
78
82
 
79
83
  <div class={combinedOuterDivClass} bind:this={element}>
80
- <Text tag="label" for={id} class={combinedLabelClass} style={getSizeStyle(size, "formLabel")}>
81
- {label}
82
- {#if required}
83
- <span class="text-[#E05555]">*</span>
84
- {/if}
85
- </Text>
84
+ {#if label}
85
+ <Text tag="label" for={id} class={combinedLabelClass} style={getSizeStyle(size, "formLabel")}>
86
+ {label}
87
+ {#if required}
88
+ <span class="text-[#E05555]">*</span>
89
+ {/if}
90
+ </Text>
91
+ {/if}
86
92
  <Button color="none" class={combinedDivClass} onclick={handleClick} {disabled}>
87
93
  <div class={iconContainerClass}>
88
94
  <Icon class={iconClass}></Icon>
@@ -10,5 +10,5 @@ export interface FileInputProps extends BaseComponentProps {
10
10
  disabled?: boolean;
11
11
  size?: SizeStyle;
12
12
  radius?: SizeStyle;
13
- id?: `${string}-${string}-${string}-${string}-${string}`;
13
+ id?: string;
14
14
  }
@@ -1,3 +1,7 @@
1
+ <script module>
2
+ let count = 0;
3
+ </script>
4
+
1
5
  <script lang="ts">
2
6
  import type { InputProps } from "./types";
3
7
  import { twMerge } from "tailwind-merge";
@@ -37,7 +41,7 @@
37
41
  valid = $bindable(true),
38
42
  size = "md",
39
43
  radius = "md",
40
- id = crypto.randomUUID(),
44
+ id = `input-${count++}`,
41
45
  min = undefined,
42
46
  max = undefined,
43
47
  step = undefined,
@@ -85,11 +89,15 @@
85
89
  onblur?.(e);
86
90
  }
87
91
 
92
+ const isValid = $derived(
93
+ (requirements ?? []).every(req => testRequirement(req.requirements))
94
+ );
95
+
88
96
  let inputClassIcon = $derived(Icon !== null ? "pl-0 pr-1" : "");
89
97
 
90
98
  let defaultInputClassCheck = $derived(variant !== "floating" ? "py-0" : "");
91
99
  let combinedClass = $derived(twMerge(defaultClass, sizeClasses, defaultInputClassCheck, labelSizeClass, inputClassIcon, className));
92
- let combinedDivClass = $derived(twMerge(divClass, (!isValidInput() && touched) && invalidClass));
100
+ let combinedDivClass = $derived(twMerge(divClass, (!isValid && touched) && invalidClass));
93
101
 
94
102
  let EyeComponent = $derived(canSeePassword ? Eye : EyeOff);
95
103
 
@@ -101,15 +109,8 @@
101
109
  return true;
102
110
  }
103
111
 
104
- function isValidInput() {
105
- for (const requirement of requirements || [])
106
- if (!testRequirement(requirement.requirements))
107
- return false;
108
- return true;
109
- }
110
-
111
112
  $effect(() => {
112
- valid = isValidInput();
113
+ valid = isValid;
113
114
  });
114
115
  </script>
115
116
 
@@ -1,3 +1,7 @@
1
+ <script module>
2
+ let count = 0;
3
+ </script>
4
+
1
5
  <script lang="ts">
2
6
  import { twMerge } from "tailwind-merge";
3
7
  import { getSizeStyleClass } from "../../../styles/size";
@@ -20,7 +24,7 @@
20
24
  disabled = false,
21
25
  size = "md",
22
26
  radius = "md",
23
- id = crypto.randomUUID(),
27
+ id = `select-${count++}`,
24
28
  ...restProps
25
29
  }: SelectProps = $props();
26
30
 
@@ -1,3 +1,7 @@
1
+ <script module>
2
+ let count = 0;
3
+ </script>
4
+
1
5
  <script lang="ts">
2
6
  import { twMerge } from "tailwind-merge";
3
7
  import type { BaseInputProps } from "../../../types/BaseComponent";
@@ -20,7 +24,7 @@
20
24
  isFocused = false,
21
25
  icon = undefined,
22
26
  wrapper = $bindable(),
23
- id = crypto.randomUUID(),
27
+ id = `base-input-${count++}`,
24
28
 
25
29
  innerDivElement = undefined,
26
30
  outerDivElementAfter = undefined,
@@ -30,14 +34,9 @@
30
34
 
31
35
  const hasContent = $derived(value !== undefined && value !== null && value.toString().length > 0);
32
36
 
33
- const customLabelStyle = $derived.by(() => {
34
- const styles: string[] = [];
35
-
36
- if (typeof size === "number")
37
- styles.push(`font-size: ${size / 4}px`);
38
-
39
- return styles.join("; ");
40
- });
37
+ const customLabelStyle = $derived(
38
+ typeof size === "number" ? `font-size: ${size / 4}px` : ""
39
+ );
41
40
 
42
41
  let labelClassIcon = $derived(icon != null && variant === "floating" ? "pl-[32px] pr-2" : "");
43
42
 
@@ -62,21 +61,21 @@
62
61
  ));
63
62
 
64
63
  const combinedDivClass = $derived(twMerge(
65
- "vpcui-base-input-div-class relative *:transition-all transition-colors flex-center bg-form-background border-[1px] border-form-border focus-within:ring-1 focus-within:ring-blue-500 overflow-hidden",
64
+ "vpcui-base-input-div-class relative *:transition-all transition-colors flex-center bg-form-background border border-form-border focus-within:border-blue-400/40 overflow-hidden",
66
65
  getSizeStyleClass(radius, "radius"),
67
- size === "full" ? "w-full" : "",
68
- divClass,
69
- disabled ? "opacity-50 pointer-events-none" : ""
66
+ divClass
70
67
  ));
71
68
  </script>
72
69
 
73
70
  {#snippet labelElement()}
74
- <Text tag="label" for={id} class={combinedLabelClass} style={customLabelStyle}>
75
- {label}
76
- {#if required}
77
- <span class="text-[#E05555]">*</span>
78
- {/if}
79
- </Text>
71
+ {#if label}
72
+ <Text tag="label" for={id} class={combinedLabelClass} style={customLabelStyle}>
73
+ {label}
74
+ {#if required}
75
+ <span class="text-[#E05555]">*</span>
76
+ {/if}
77
+ </Text>
78
+ {/if}
80
79
  {/snippet}
81
80
 
82
81
  {#snippet innerDivElementWrapper()}
@@ -4,9 +4,11 @@ export { default as Input } from "./Input/index.svelte";
4
4
  export { default as Select } from "./Select/index.svelte";
5
5
  export { default as FileInput } from "./FileInput/index.svelte";
6
6
  export { default as Combobox } from "./Combobox/index.svelte";
7
+ export { default as ColorInput } from "./ColorInput/index.svelte";
7
8
  export type { ButtonProps } from "./Button/types.ts";
8
9
  export type { CheckboxProps } from "./Checkbox/types.ts";
9
10
  export type { InputProps } from "./Input/types.ts";
10
11
  export type { SelectProps } from "./Select/types.ts";
11
12
  export type { FileInputProps } from "./FileInput/types.ts";
12
13
  export type { ComboboxProps } from "./Combobox/types.ts";
14
+ export type { ColorInputProps } from "./ColorInput/types.ts";
@@ -4,3 +4,4 @@ export { default as Input } from "./Input/index.svelte";
4
4
  export { default as Select } from "./Select/index.svelte";
5
5
  export { default as FileInput } from "./FileInput/index.svelte";
6
6
  export { default as Combobox } from "./Combobox/index.svelte";
7
+ export { default as ColorInput } from "./ColorInput/index.svelte";
@@ -1,12 +1,20 @@
1
1
  <script lang="ts">
2
2
  import { fly } from "svelte/transition";
3
3
  import NavbarElement from "./NavbarElement.svelte";
4
- import { tick } from "svelte";
5
4
  import type { NavbarDropdownProps } from "./types.js";
5
+ import {
6
+ computePosition,
7
+ flip,
8
+ shift,
9
+ offset,
10
+ autoUpdate,
11
+ type Placement,
12
+ } from "@floating-ui/dom";
13
+ import { tick } from "svelte";
6
14
 
7
15
  let {
8
- children = undefined,
9
- class: className = "",
16
+ children = undefined,
17
+ class: className = "",
10
18
  element = $bindable(),
11
19
  wrapperClass = "",
12
20
  label = "",
@@ -15,69 +23,92 @@
15
23
  }: NavbarDropdownProps = $props();
16
24
 
17
25
  let open = $state(false);
18
- let dropdownRef: HTMLButtonElement | null = $state(null);
19
- let wrapperRef: HTMLDivElement | null = $state(null);
20
- let offsetX = $state(0);
21
- let flipUp = $state(false);
26
+ let x = $state(0);
27
+ let y = $state(0);
28
+ let resolvedPlacement: Placement = $state("bottom-start");
29
+ let ready = $state(false);
30
+
31
+ const flyParams = $derived(
32
+ resolvedPlacement.startsWith("top")
33
+ ? { y: 5, duration: 150 }
34
+ : { y: -5, duration: 150 }
35
+ );
22
36
 
23
- function calculateOverflow() {
24
- if (!dropdownRef) return;
37
+ function initDropdown(node: HTMLElement) {
38
+ if (!element) return {};
25
39
 
26
- offsetX = 0;
40
+ let cleanup: (() => void) | null = null;
27
41
 
28
- const menuRect = dropdownRef.getBoundingClientRect();
29
- const vw = window.innerWidth;
30
- const padding = 12;
42
+ async function updatePosition() {
43
+ const { x: fx, y: fy, placement } = await computePosition(element!, node, {
44
+ placement: "bottom-start",
45
+ middleware: [offset(4), flip(), shift({ padding: 12 })],
46
+ });
47
+ x = fx;
48
+ y = fy;
49
+ resolvedPlacement = placement;
50
+ ready = true;
51
+ }
31
52
 
32
- flipUp = menuRect.bottom > window.innerHeight - padding;
53
+ updatePosition().then(() => {
54
+ cleanup = autoUpdate(element!, node, updatePosition);
55
+ });
33
56
 
34
- if (menuRect.right > vw - padding)
35
- offsetX = -(menuRect.right - (vw - padding));
36
- else if (menuRect.left < padding)
37
- offsetX = padding - menuRect.left;
38
- else
39
- offsetX = 0;
57
+ return {
58
+ destroy() {
59
+ cleanup?.();
60
+ ready = false;
61
+ }
62
+ };
40
63
  }
41
64
 
42
65
  async function toggle() {
43
- open = !open;
44
66
  if (open) {
45
- await tick();
46
- calculateOverflow();
67
+ open = false;
47
68
  } else {
48
- offsetX = 0;
69
+ ready = false;
70
+ open = true;
49
71
  }
50
72
  }
51
73
 
52
74
  function handleClickOutside(event: MouseEvent) {
53
- if (dropdownRef && !dropdownRef.contains(event.target as Node) && wrapperRef && !wrapperRef.contains(event.target as Node)) {
75
+ const target = event.target as Node;
76
+ if (element && !element.contains(target))
54
77
  open = false;
55
- offsetX = 0;
56
- }
57
78
  }
58
79
  </script>
59
80
 
60
- <svelte:window onclick={handleClickOutside}/>
81
+ <svelte:window onclick={handleClickOutside} />
61
82
 
62
- <div class="relative" bind:this={wrapperRef} bind:this={element}>
63
- <NavbarElement onclick={toggle} class={className} {...restProps} aria-haspopup="true" aria-expanded={open}>
83
+ <div class="relative h-full {wrapperClass}" bind:this={element}>
84
+ <NavbarElement
85
+ onclick={toggle}
86
+ class={className}
87
+ {...restProps}
88
+ aria-haspopup="true"
89
+ aria-expanded={open}
90
+ >
64
91
  {label}
65
92
  {#if navelement}
66
93
  {@render navelement()}
67
94
  {/if}
68
95
  </NavbarElement>
96
+ </div>
69
97
 
70
- {#if open}
71
- <div
72
- class="absolute {flipUp ? 'bottom-full' : 'top-full'} left-0 z-100 mt-1 overflow-hidden shadow-lg"
73
- style="transform: translateX({offsetX}px); visibility: {open ? 'visible' : 'hidden'};">
74
- <button
75
- bind:this={dropdownRef}
76
- transition:fly={{ y: flipUp ? 5 : -5, duration: 200 }}
77
- class="bg-sub-background p-2 min-w-max flex flex-col *:px-0 rounded border-sub-background-hover border"
78
- onclick={() => open = false}>
98
+ {#if open}
99
+ <div
100
+ use:initDropdown
101
+ style="position: fixed; top: {y}px; left: {x}px;"
102
+ class="z-100 shadow-lg"
103
+ style:visibility={ready ? "visible" : "hidden"}
104
+ >
105
+ <div
106
+ transition:fly={flyParams}
107
+ class="bg-sub-background p-2 min-w-max flex flex-col *:px-0 rounded border-sub-background-hover border"
108
+ >
109
+ <button onclick={() => (open = false)} class="contents">
79
110
  {@render children?.()}
80
111
  </button>
81
112
  </div>
82
- {/if}
83
- </div>
113
+ </div>
114
+ {/if}
@@ -2,7 +2,6 @@
2
2
  import { twMerge } from "tailwind-merge";
3
3
  import Button from "../../inputs/Button/index.svelte";
4
4
  import type { NavbarElementProps } from "./types";
5
- import { page } from "$app/state";
6
5
 
7
6
  let {
8
7
  children = undefined,
@@ -10,6 +9,7 @@
10
9
  element = $bindable(),
11
10
  classTop = "",
12
11
  activeClass = "",
12
+ active = false,
13
13
  href = undefined,
14
14
  threshold = 10,
15
15
  ...restProps
@@ -19,18 +19,17 @@
19
19
 
20
20
  let scrollY = $state(0);
21
21
  let isAtTop = $derived(scrollY <= threshold);
22
- let isActive = $derived(page.url.pathname === href);
23
22
 
24
23
  const combinedClass = $derived(twMerge(
25
24
  defaultClass,
26
25
  className,
27
- isActive ? activeClass : "",
26
+ active ? activeClass : "",
28
27
  isAtTop ? classTop : "",
29
28
  ));
30
29
  </script>
31
30
 
32
31
  <svelte:window bind:scrollY={scrollY}/>
33
32
 
34
- <Button bind:element radius="none" {href} color="none" class={combinedClass} {...restProps} aria-current={isActive ? 'page' : undefined}>
33
+ <Button bind:element radius="none" {href} color="sub" class={combinedClass} {...restProps} aria-current={active ? 'page' : undefined}>
35
34
  {@render children?.()}
36
35
  </Button>
@@ -0,0 +1,29 @@
1
+ <script module lang="ts">
2
+ import { defineMeta } from "@storybook/addon-svelte-csf";
3
+ import Navbar from "./index.svelte";
4
+ import NavbarElement from "./NavbarElement.svelte";
5
+ import NavbarSeparator from "./NavbarSeparator.svelte";
6
+ import NavbarDropdown from "./NavbarDropdown.svelte";
7
+
8
+ const { Story } = defineMeta({
9
+ title: "Components/Navigation/Navbar",
10
+ component: Navbar,
11
+ argTypes: {
12
+
13
+ },
14
+ });
15
+ </script>
16
+
17
+ <Story name="Default" args={{ color: "primary", size: "md" }}>
18
+ <NavbarElement href="/">Home</NavbarElement>
19
+ <NavbarElement href="/library">Library</NavbarElement>
20
+ <NavbarElement>Data</NavbarElement>
21
+
22
+ <NavbarSeparator/>
23
+
24
+ <NavbarDropdown label="More">
25
+ <NavbarElement href="/about">About</NavbarElement>
26
+ <NavbarSeparator variant="horizontal"/>
27
+ <NavbarElement href="/contact">Contact</NavbarElement>
28
+ </NavbarDropdown>
29
+ </Story>
@@ -0,0 +1,18 @@
1
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
2
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
3
+ $$bindings?: Bindings;
4
+ } & Exports;
5
+ (internal: unknown, props: {
6
+ $$events?: Events;
7
+ $$slots?: Slots;
8
+ }): Exports & {
9
+ $set?: any;
10
+ $on?: any;
11
+ };
12
+ z_$$bindings?: Bindings;
13
+ }
14
+ declare const Index: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
+ [evt: string]: CustomEvent<any>;
16
+ }, {}, {}, string>;
17
+ type Index = InstanceType<typeof Index>;
18
+ export default Index;
@@ -7,6 +7,7 @@ export interface NavbarProps extends BaseComponentProps {
7
7
  export interface NavbarElementProps extends BaseComponentProps {
8
8
  classTop?: string;
9
9
  activeClass?: string;
10
+ active?: boolean;
10
11
  href?: string;
11
12
  threshold?: number;
12
13
  }
@@ -1,6 +1,6 @@
1
1
  ;
2
2
  export const NavbarSeparatorClasses = {
3
3
  "vertical": "w-0.5 h-full py-3 bg-sub-background-hover bg-clip-content",
4
- "horizontal": "h-0.5 w-full px-3 my-1 bg-sub-background-hover bg-clip-content",
4
+ "horizontal": "h-0.5 w-full my-1 bg-sub-background-hover bg-clip-content",
5
5
  "dynamic": "ml-auto"
6
6
  };
@@ -28,7 +28,7 @@
28
28
  <nav class={combinedClass} {...restProps} onmouseenter={() => expanded = true} onmouseleave={() => expanded = false} bind:this={element}>
29
29
  {#each items as item}
30
30
  {@const isActive = page.url.pathname === item.href}
31
- <Button size="xs" class="{isActive ? 'bg-form-background text-main-text' : 'text-sub-text'} hover:text-main-text py-1 text-nowrap flex justify-start gap-2 overflow-hidden mx-1 w-[calc(100%-16px)] hover:bg-form-background px-1.5" href={item.href}>
31
+ <Button size="xs" color="none" class="{isActive ? 'bg-form-background text-main-text' : 'text-sub-text'} hover:text-main-text py-1 text-nowrap flex justify-start gap-2 overflow-hidden mx-1 w-[calc(100%-16px)] hover:bg-form-background px-1.5" href={item.href}>
32
32
  <item.icon class="w-5 h-5 shrink-0"/>
33
33
  <Text tag="span" class="transition-opacity duration-200 text-sm text-inherit {expanded ? 'w-auto opacity-100 pr-3' : 'w-0 opacity-0'}">
34
34
  {item.label}
@@ -20,5 +20,5 @@ export interface BaseInputProps extends BaseComponentProps {
20
20
  radius?: SizeStyle;
21
21
  icon?: Component;
22
22
  wrapper?: HTMLElement;
23
- id?: `${string}-${string}-${string}-${string}-${string}`;
23
+ id?: string;
24
24
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@valerius_petrini/corekit-ui",
3
- "version": "0.1.71",
3
+ "version": "0.1.73",
4
4
  "description": "Component Library used across all my projects",
5
5
  "author": "Valerius Petrini Jr.",
6
6
  "license": "MIT",