@valerius_petrini/corekit-ui 0.1.62 → 0.1.64

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 (47) hide show
  1. package/dist/components/Analytics.svelte +4 -0
  2. package/dist/components/Button.svelte +10 -5
  3. package/dist/components/Card.svelte +1 -1
  4. package/dist/components/Combobox.svelte +207 -0
  5. package/dist/components/Combobox.svelte.d.ts +4 -0
  6. package/dist/components/Input.svelte +46 -148
  7. package/dist/components/KBD.svelte +23 -0
  8. package/dist/components/KBD.svelte.d.ts +3 -0
  9. package/dist/components/Loader.svelte +35 -0
  10. package/dist/components/Loader.svelte.d.ts +4 -0
  11. package/dist/components/NavbarDropdown.svelte +82 -0
  12. package/dist/components/NavbarDropdown.svelte.d.ts +4 -0
  13. package/dist/components/NavbarSeparator.svelte +11 -1
  14. package/dist/components/NavbarSeparator.svelte.d.ts +3 -25
  15. package/dist/components/SEO.svelte +27 -17
  16. package/dist/components/Select.svelte +63 -23
  17. package/dist/components/Select.svelte.d.ts +1 -1
  18. package/dist/components/Tooltip.svelte +124 -0
  19. package/dist/components/Tooltip.svelte.d.ts +4 -0
  20. package/dist/components/helper/BaseInput.svelte +105 -0
  21. package/dist/components/helper/BaseInput.svelte.d.ts +4 -0
  22. package/dist/components/helper/NumberInput.svelte +79 -0
  23. package/dist/components/helper/NumberInput.svelte.d.ts +11 -0
  24. package/dist/index.d.ts +3 -0
  25. package/dist/index.js +3 -0
  26. package/dist/styles/color.d.ts +1 -1
  27. package/dist/styles/color.js +56 -28
  28. package/dist/styles/layout.css +7 -0
  29. package/dist/styles/posititon.d.ts +1 -0
  30. package/dist/styles/size.d.ts +1 -1
  31. package/dist/styles/size.js +36 -12
  32. package/dist/types/Button.d.ts +1 -0
  33. package/dist/types/Input.d.ts +20 -5
  34. package/dist/types/Input.js +1 -0
  35. package/dist/types/Loader.d.ts +5 -0
  36. package/dist/types/{Select.js → Loader.js} +0 -1
  37. package/dist/types/Navbar.d.ts +14 -1
  38. package/dist/types/Navbar.js +5 -1
  39. package/dist/types/SEO.d.ts +1 -1
  40. package/dist/types/Tooltip.d.ts +7 -0
  41. package/dist/types/Tooltip.js +1 -0
  42. package/dist/utils/debounce.d.ts +1 -0
  43. package/dist/utils/debounce.js +7 -0
  44. package/dist/utils/link.d.ts +2 -2
  45. package/dist/utils/link.js +2 -2
  46. package/package.json +1 -1
  47. package/dist/types/Select.d.ts +0 -12
@@ -1,3 +1,7 @@
1
+ <script module>
2
+ declare function gtag(...args: unknown[]): void;
3
+ </script>
4
+
1
5
  <script lang="ts">
2
6
  import { page } from '$app/state'
3
7
  import type { AnalyticsProps } from '../types/Analytics.js';
@@ -4,6 +4,7 @@
4
4
  import type { ButtonProps } from "../types/Button.js";
5
5
  import { twMerge } from "tailwind-merge";
6
6
  import { getLinkProps } from "../utils/link.js";
7
+ import Loader from "./Loader.svelte";
7
8
 
8
9
  let {
9
10
  children = undefined,
@@ -18,11 +19,12 @@
18
19
  external = false,
19
20
  size = "md",
20
21
  radius = "md",
22
+ loading = false,
21
23
  ...restProps
22
24
  }: ButtonProps = $props();
23
25
 
24
26
  const defaultClass = "inline-flex items-center justify-center gap-2 transition-colors duration-300 ease-in-out text-white whitespace-nowrap";
25
- const disabledClass = $derived(disabled ? "opacity-50 pointer-events-none" : "cursor-pointer");
27
+ const disabledClass = $derived(disabled || loading ? "opacity-50 pointer-events-none" : "cursor-pointer");
26
28
  const iconClass = $derived(icon ? "p-0 flex-none" : "h-fit");
27
29
  const pillClass = $derived((pill || icon) && "rounded-full");
28
30
  const squareClass = $derived(square && "aspect-square rounded-none");
@@ -40,22 +42,25 @@
40
42
  ));
41
43
 
42
44
  const mergedStyle = $derived([
43
- getSizeStyle(size, icon ? "button" : "button"),
45
+ getSizeStyle(size, icon ? "buttonIcon" : "button"),
44
46
  getSizeStyle(radius, "radius"),
45
47
  restProps.style
46
48
  ].filter(Boolean).join("; "));
47
49
 
48
- const anchorProps = $derived(getLinkProps(href, external));
50
+ const anchorProps = $derived(getLinkProps(href, external, disabled || loading));
49
51
  </script>
50
52
 
51
53
  <svelte:element
52
54
  this={href ? "a" : "button"}
53
55
  class={mergedClass}
54
- {disabled}
55
- aria-disabled={disabled}
56
+ disabled={disabled || loading}
57
+ aria-disabled={disabled || loading}
56
58
  type={href ? undefined : (restProps.type || "button")}
57
59
  style={mergedStyle}
58
60
  {...anchorProps}
59
61
  {...restProps}>
62
+ {#if loading}
63
+ <Loader color="white" class="border-2 border-loader-btn-color {getSizeStyleClass(size, "buttonLoader")}"/>
64
+ {/if}
60
65
  {@render children?.()}
61
66
  </svelte:element>
@@ -32,7 +32,7 @@
32
32
  restProps.style
33
33
  ].filter(Boolean).join("; "));
34
34
 
35
- const anchorProps = $derived(getLinkProps(href, external));
35
+ const anchorProps = $derived(getLinkProps(href, external, false));
36
36
  </script>
37
37
 
38
38
  <svelte:element
@@ -0,0 +1,207 @@
1
+ <script lang="ts">
2
+ import type { ComboboxProps, InputProps } from "../types/Input.js";
3
+ import { twMerge } from "tailwind-merge";
4
+ import { getSizeStyleClass } from "../styles/size.js";
5
+
6
+ import BaseInput from "./helper/BaseInput.svelte";
7
+ import Text from "./Text.svelte";
8
+ import { fly } from "svelte/transition";
9
+ import { debounce } from "../utils/debounce.js";
10
+
11
+ let {
12
+ children = undefined,
13
+ class: className = "",
14
+ label = "",
15
+ labelClass = "",
16
+ divClass = "",
17
+ outerDivClass = "",
18
+ icon = undefined,
19
+ variant = "default",
20
+ placeholder = "",
21
+ value = $bindable(),
22
+ onfocus = undefined,
23
+ onblur = undefined,
24
+ required = false,
25
+ disabled = false,
26
+ size = "md",
27
+ radius = "md",
28
+ options = [],
29
+ limit = 10,
30
+ id = crypto.randomUUID(),
31
+ ...restProps
32
+ }: ComboboxProps = $props();
33
+
34
+ let activeIndex = $state(0);
35
+
36
+ let debouncedSearch = $state("");
37
+
38
+ const updateSearch = debounce((v: string) => {
39
+ debouncedSearch = v;
40
+ }, 150);
41
+
42
+ $effect(() => {
43
+ updateSearch(value ?? "");
44
+ });
45
+
46
+ const sizeClasses = $derived(getSizeStyleClass(size, "form"));
47
+ const labelSizeClass = $derived(getSizeStyleClass(size, "formLabel"));
48
+
49
+ let inputElement = $state<HTMLInputElement>();
50
+
51
+ const customStyle = $derived.by(() => {
52
+ const styles: string[] = [];
53
+
54
+ if (typeof size === "number")
55
+ styles.push(`width: ${size}px`);
56
+
57
+ if (typeof radius === "number")
58
+ styles.push(`border-radius: ${radius}px`);
59
+
60
+ return styles.join("; ");
61
+ });
62
+
63
+ let isFocused = $state(false);
64
+
65
+ function handleFocus(e: FocusEvent) {
66
+ isFocused = true;
67
+ onfocus?.(e);
68
+ }
69
+
70
+ function handleBlur(e: FocusEvent) {
71
+ const match = options.find(o => o.toLowerCase() === (value ?? "").toString().toLowerCase());
72
+ if (!match) value = "";
73
+ isFocused = false;
74
+ onblur?.(e);
75
+ }
76
+
77
+ 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";
78
+
79
+ let defaultInputClassCheck = $derived(variant !== "floating" ? "py-0" : "");
80
+ let combinedClass = $derived(twMerge(defaultClass, sizeClasses, defaultInputClassCheck, labelSizeClass, className));
81
+ let combinedDivClass = $derived(twMerge(divClass));
82
+
83
+ function onClickItem(event: MouseEvent, option: string) {
84
+ event.preventDefault();
85
+ value = option;
86
+ isFocused = false;
87
+ inputElement?.blur();
88
+ }
89
+
90
+ function highlight(label: string, search: string) {
91
+ const index = label.toLowerCase().indexOf(search.toLowerCase());
92
+ if (index === -1) return escapeHtml(label);
93
+
94
+ return [
95
+ label.slice(0, index),
96
+ `<span class="font-bold">${escapeHtml(label.slice(index, index + search.length))}</span>`,
97
+ label.slice(index + search.length)
98
+ ].join("");
99
+ }
100
+
101
+ function escapeHtml(str: string) {
102
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
103
+ }
104
+
105
+ let filteredOptions = $derived(
106
+ options.filter(option =>
107
+ option.toLowerCase().includes(debouncedSearch.toLowerCase()))
108
+ );
109
+
110
+ let validOptions = $derived(filteredOptions.slice(0, limit));
111
+ let totalMatches = $derived(filteredOptions.length);
112
+
113
+ function onKeyDown(event: KeyboardEvent) {
114
+ if (!isFocused) return;
115
+
116
+ if (event.key === "ArrowDown") {
117
+ event.preventDefault();
118
+ activeIndex = (activeIndex + 1) % validOptions.length;
119
+ scrollToActiveElement();
120
+ } else if (event.key === "ArrowUp") {
121
+ event.preventDefault();
122
+ activeIndex = (activeIndex - 1 + validOptions.length) % validOptions.length;
123
+ scrollToActiveElement();
124
+ } else if (event.key === "Enter" || event.key === "Tab") {
125
+ event.preventDefault();
126
+ const option = validOptions[activeIndex];
127
+ if (option) {
128
+ value = option;
129
+ isFocused = false;
130
+ inputElement?.blur();
131
+ }
132
+ activeIndex = 0;
133
+ } else if (event.key === "Escape") {
134
+ event.preventDefault();
135
+ isFocused = false;
136
+ inputElement?.blur();
137
+ } else {
138
+ activeIndex = 0;
139
+ }
140
+ }
141
+
142
+ function scrollToActiveElement() {
143
+ const optionElement = optionsContainerElement?.children[activeIndex] as HTMLElement;
144
+ optionElement?.scrollIntoView({ block: "nearest" });
145
+ }
146
+
147
+ let optionsContainerElement = $state<HTMLDivElement>();
148
+ </script>
149
+
150
+ <BaseInput
151
+ {children}
152
+ {className}
153
+ {label}
154
+ {labelClass}
155
+ divClass={combinedDivClass}
156
+ {outerDivClass}
157
+ {value}
158
+ {required}
159
+ {disabled}
160
+ {variant}
161
+ {size}
162
+ {radius}
163
+ {id}
164
+ {icon}>
165
+
166
+ {#snippet innerDivElement()}
167
+ <input
168
+ {id}
169
+ bind:value={value}
170
+ bind:this={inputElement}
171
+ class={combinedClass}
172
+ {required}
173
+ {disabled}
174
+ onfocus={handleFocus}
175
+ onblur={handleBlur}
176
+ onkeydown={onKeyDown}
177
+ placeholder={variant === "floating" ? "" : placeholder}
178
+ aria-disabled={disabled}
179
+ style={customStyle}
180
+ {...restProps}
181
+ />
182
+ {/snippet}
183
+
184
+ {#snippet outerDivElementAfter()}
185
+ {#if isFocused}
186
+ <div transition:fly={{ y: -10, duration: 200 }} class="absolute top-full left-0 right-0 mt-2 border-2 overflow-hidden border-blue-500 bg-sub-background {getSizeStyleClass(radius, "radius")}">
187
+ {#if totalMatches > limit}
188
+ <Text class="text-xs py-0.5 px-1 text-sub-text italic sticky top-0 bg-sub-background w-full">
189
+ Showing {limit} of {totalMatches} results for "{value}"
190
+ </Text>
191
+ {/if}
192
+ {#if totalMatches === 0}
193
+ <Text class="text-xs py-0.5 px-1 text-sub-text italic sticky top-0 bg-sub-background w-full">
194
+ No results found for "{value}"
195
+ </Text>
196
+ {/if}
197
+ <div bind:this={optionsContainerElement} class="overflow-auto max-h-40">
198
+ {#each validOptions as option, index}
199
+ <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)}>
200
+ {@html highlight(option, value ?? "")}
201
+ </Text>
202
+ {/each}
203
+ </div>
204
+ </div>
205
+ {/if}
206
+ {/snippet}
207
+ </BaseInput>
@@ -0,0 +1,4 @@
1
+ import type { ComboboxProps } from "../types/Input.ts";
2
+ declare const Combobox: import("svelte").Component<ComboboxProps, {}, "value">;
3
+ type Combobox = ReturnType<typeof Combobox>;
4
+ export default Combobox;
@@ -1,8 +1,8 @@
1
1
  <script lang="ts">
2
- import type { InputProps, InputRequirements } from "../types/Input.js";
2
+ import type { InputProps } from "../types/Input.js";
3
3
  import { twMerge } from "tailwind-merge";
4
4
  import Text from "./Text.svelte";
5
- import { getSizeStyleClass, sizeStyleParts, type SizeStyleTheme } from "../styles/size.js";
5
+ import { getSizeStyleClass } from "../styles/size.js";
6
6
  import { type Component } from "svelte";
7
7
  import Button from "./Button.svelte";
8
8
 
@@ -11,11 +11,11 @@
11
11
  import Phone from "@lucide/svelte/icons/phone";
12
12
  import Eye from "@lucide/svelte/icons/eye";
13
13
  import EyeOff from "@lucide/svelte/icons/eye-off";
14
- import ChevronUp from "@lucide/svelte/icons/chevron-up";
15
- import ChevronDown from "@lucide/svelte/icons/chevron-down";
16
14
  import X from "@lucide/svelte/icons/x";
17
15
  import Check from "@lucide/svelte/icons/check";
18
- import { slide } from "svelte/transition";
16
+ import { slide } from "svelte/transition";
17
+ import BaseInput from "./helper/BaseInput.svelte";
18
+ import NumberInput from "./helper/NumberInput.svelte";
19
19
 
20
20
  let {
21
21
  children = undefined,
@@ -47,8 +47,6 @@
47
47
 
48
48
  const sizeClasses = $derived(getSizeStyleClass(size, "form"));
49
49
  const labelSizeClass = $derived(getSizeStyleClass(size, "formLabel"));
50
- const divSizeClass = $derived(getSizeStyleClass(radius, "radius"));
51
- const selectedLabelSizeClass = $derived(getSizeStyleClass(size, "formLabelSelected"));
52
50
 
53
51
  const customStyle = $derived.by(() => {
54
52
  const styles: string[] = [];
@@ -62,51 +60,21 @@
62
60
  return styles.join("; ");
63
61
  });
64
62
 
65
- const customLabelStyle = $derived.by(() => {
66
- const styles: string[] = [];
67
-
68
- if (typeof size === "number")
69
- styles.push(`font-size: ${size / 4}px`);
70
-
71
- return styles.join("; ");
72
- });
73
-
74
63
  let isFocused = $state(false);
75
64
  let touched = $state(false);
76
65
 
77
66
  let canSeePassword = $state(false);
78
67
 
79
- const isFloating = $derived(variant === "floating");
80
- const hasContent = $derived(value !== undefined && value !== null && value.toString().length > 0);
81
- const lifted = $derived(isFloating && (isFocused || hasContent));
82
-
83
68
  const Icon = $derived(icon ?? ({
84
69
  email: Mail, password: Lock, tel: Phone
85
70
  }[restProps.type as string] as Component ?? null));
86
71
 
87
- 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";
88
- let defaultLabelClass = "block text-sub-text font-medium mb-1 duration-100 pointer-events-none truncate w-fit";
89
- let defaultDivClass = "relative *:transition-all transition-colors flex-center bg-form-background border-[1px] border-form-border focus-within:ring-1 focus-within:ring-blue-500";
72
+ 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";
90
73
  let iconContainerClass = "h-5 aspect-square px-1 py-0!";
91
- let floatingLabelClass = "absolute w-full";
92
74
  let iconClass = "h-full aspect-square text-sub-text";
93
75
 
94
- let inputRadius = $derived.by(() => {
95
- if (restProps.type === "password") return "rounded-none";
96
- if (Icon !== null) return "rounded-r-full"
97
- else return "rounded-full";
98
- });
99
-
100
- let originalLabelClass = "z-0";
101
- let originalLabelClassInput = "top-1/2 transform -translate-y-1/2";
102
- let originalSelectedLabelClass = "z-30";
103
-
104
76
  let invalidClass = "border border-red-500 focus:ring-red-500 bg-[#2E1F1F]";
105
77
 
106
- let floatingLabelClassFull = $derived(twMerge(originalLabelClassInput, originalLabelClass, floatingLabelClass));
107
- let divFullClass = $derived(size === "full" ? "w-full" : "");
108
- let disabledClass = $derived(disabled ? "opacity-50 pointer-events-none" : "");
109
-
110
78
  function handleFocus(e: FocusEvent) {
111
79
  isFocused = true;
112
80
  touched = true;
@@ -118,58 +86,14 @@
118
86
  onblur?.(e);
119
87
  }
120
88
 
121
- let labelClassIcon = $derived(Icon !== null ? "pl-[32px] pr-2" : "px-1.5");
122
89
  let inputClassIcon = $derived(Icon !== null ? "pl-0 pr-1" : "");
123
90
 
124
91
  let defaultInputClassCheck = $derived(variant !== "floating" ? "py-0" : "");
125
- let floatingLabelClassCheck = $derived(variant === "floating" ? floatingLabelClassFull : "");
126
- let defaultLabelClassCheck = $derived(variant !== "floating" ? "px-0" : "");
127
- let selectedLabelClass = $derived(twMerge((isFocused || hasContent) && variant === "floating" ? `${originalSelectedLabelClass} ${selectedLabelSizeClass}` : ""));
128
- let combinedLabelClass = $derived(twMerge(defaultLabelClass, floatingLabelClassCheck, labelSizeClass, selectedLabelClass, labelClassIcon, defaultLabelClassCheck, labelClass));
129
- let combinedClass = $derived(twMerge(defaultClass, inputRadius, sizeClasses, defaultInputClassCheck, labelSizeClass, inputClassIcon, className));
130
- let combinedDivClass = $derived(twMerge(defaultDivClass, divSizeClass, divFullClass, divClass, disabledClass, !isValidInput() && touched && invalidClass));
131
- let combinedOuterDivClass = $derived(twMerge("flex flex-col bg-transparent border-0 p-0", divSizeClass, divFullClass, outerDivClass, disabledClass));
92
+ let combinedClass = $derived(twMerge(defaultClass, sizeClasses, defaultInputClassCheck, labelSizeClass, inputClassIcon, className));
93
+ let combinedDivClass = $derived(twMerge(divClass, (!isValidInput() && touched) && invalidClass));
132
94
 
133
95
  let EyeComponent = $derived(canSeePassword ? Eye : EyeOff);
134
96
 
135
- let numberIconClass = $derived(twMerge(iconClass, sizeClasses, "text-sub-text/70 w-fit aspect-auto p-0 flex-center flex-col transition-all duration-150"));
136
- let numberButtonClass = $derived(twMerge(iconContainerClass, "h-1/2 gap-0 px-0.5 hover:bg-form-border aspect-square rounded-none"));
137
-
138
- function increment() {
139
- if (max === undefined || value < max) value = (value || 0) + (step || 1);
140
- }
141
-
142
- function decrement() {
143
- if (min === undefined || value > min) value = (value || 0) - (step || 1);
144
- }
145
-
146
- let incrementInterval: ReturnType<typeof setInterval> | null = null;
147
- let decrementInterval: ReturnType<typeof setInterval> | null = null;
148
-
149
- function startIncrement() {
150
- increment();
151
- incrementInterval = setInterval(increment, 100);
152
- }
153
-
154
- function stopIncrement() {
155
- if (incrementInterval) {
156
- clearInterval(incrementInterval);
157
- incrementInterval = null;
158
- }
159
- }
160
-
161
- function startDecrement() {
162
- decrement();
163
- decrementInterval = setInterval(decrement, 100);
164
- }
165
-
166
- function stopDecrement() {
167
- if (decrementInterval) {
168
- clearInterval(decrementInterval);
169
- decrementInterval = null;
170
- }
171
- }
172
-
173
97
  function testRequirement(requirement: RegExp | ((value: any) => boolean)) {
174
98
  if (typeof requirement === "function")
175
99
  return requirement(value || "");
@@ -190,26 +114,43 @@
190
114
  });
191
115
  </script>
192
116
 
193
- {#snippet labelElement()}
194
- <Text tag="label" for={id} class={combinedLabelClass} style={customLabelStyle}>
195
- {label}
196
- {#if required}
197
- <span class="text-[#E05555]">*</span>
198
- {/if}
199
- </Text>
200
- {/snippet}
201
-
202
- {#snippet innerDivElement()}
203
- <!-- svelte-ignore a11y_no_static_element_interactions -->
204
- <div class={combinedDivClass} onmouseenter={() => isHovered = true} onmouseleave={() => isHovered = false}>
205
- {#if Icon}
206
- <div class={iconContainerClass}>
207
- <Icon class={iconClass}></Icon>
117
+ <BaseInput
118
+ {children}
119
+ {className}
120
+ {label}
121
+ {labelClass}
122
+ divClass={combinedDivClass}
123
+ {outerDivClass}
124
+ {value}
125
+ {required}
126
+ {disabled}
127
+ {variant}
128
+ {size}
129
+ {radius}
130
+ {isFocused}
131
+ {id}
132
+ icon={Icon}
133
+ bind:isHovered
134
+ {...restProps}>
135
+ {#snippet outerDivElementAfter()}
136
+ {#if touched && requirements}
137
+ <div class="mt-1 text-xs" transition:slide={{ duration: 300 }}>
138
+ {#each requirements as req}
139
+ {@const isReqMet = testRequirement(req.requirements)}
140
+ {@const reqClass = isReqMet ? "text-green-500" : "text-red-500"}
141
+ <div class="flex w-full items-center gap-1 transition-colors {reqClass}">
142
+ <div class="relative w-4 h-4">
143
+ <Check class="w-4 h-4 absolute transition-all duration-150 {isReqMet ? 'opacity-100 scale-100' : 'opacity-0 scale-0'}"/>
144
+ <X class="w-4 h-4 absolute transition-all duration-150 {isReqMet ? 'opacity-0 scale-0' : 'opacity-100 scale-100'}"/>
145
+ </div>
146
+ <Text tag="span" class="text-xs text-inherit">{req.label}</Text>
147
+ </div>
148
+ {/each}
208
149
  </div>
209
150
  {/if}
210
- {#if variant === "floating"}
211
- {@render labelElement()}
212
- {/if}
151
+ {/snippet}
152
+
153
+ {#snippet innerDivElement()}
213
154
  <input
214
155
  {id}
215
156
  bind:value={value}
@@ -232,51 +173,8 @@
232
173
  <EyeComponent class={iconClass}></EyeComponent>
233
174
  </Button>
234
175
  {:else if restProps.type === "number"}
235
- <div class={twMerge(numberIconClass, isHovered ? "opacity-100 scale-100" : "opacity-0 scale-75")}>
236
- <Button
237
- size="none" radius="none"
238
- class={numberButtonClass}
239
- onmousedown={startIncrement}
240
- onmouseup={stopIncrement}
241
- onmouseleave={stopIncrement}
242
- disabled={max !== undefined && value >= max}>
243
- <ChevronUp class="w-full h-full"/>
244
- </Button>
245
- <Button
246
- size="none" radius="none"
247
- class={numberButtonClass}
248
- onmousedown={startDecrement}
249
- onmouseup={stopDecrement}
250
- onmouseleave={stopDecrement}
251
- disabled={min !== undefined && value <= min}>
252
- <ChevronDown class="w-full h-full"/>
253
- </Button>
254
- </div>
176
+ <NumberInput {max} {min} {step} bind:value {isHovered} {size}/>
255
177
  {/if}
256
- </div>
257
- {/snippet}
178
+ {/snippet}
179
+ </BaseInput>
258
180
 
259
- <div class={combinedOuterDivClass}>
260
- {#if variant !== "floating"}
261
- {@render labelElement()}
262
- {@render innerDivElement()}
263
- {:else}
264
- {@render innerDivElement()}
265
- {/if}
266
-
267
- {#if touched && requirements}
268
- <div class="mt-1 text-xs" transition:slide={{ duration: 300 }}>
269
- {#each requirements as req}
270
- {@const isReqMet = testRequirement(req.requirements)}
271
- {@const reqClass = isReqMet ? "text-green-500" : "text-red-500"}
272
- <div class="flex w-full items-center gap-1 transition-colors {reqClass}">
273
- <div class="relative w-4 h-4">
274
- <Check class="w-4 h-4 absolute transition-all duration-150 {isReqMet ? 'opacity-100 scale-100' : 'opacity-0 scale-0'}"/>
275
- <X class="w-4 h-4 absolute transition-all duration-150 {isReqMet ? 'opacity-0 scale-0' : 'opacity-100 scale-100'}"/>
276
- </div>
277
- <Text tag="span" class="text-xs text-inherit">{req.label}</Text>
278
- </div>
279
- {/each}
280
- </div>
281
- {/if}
282
- </div>
@@ -0,0 +1,23 @@
1
+ <script lang="ts">
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ let {
5
+ children = undefined,
6
+ class: className = "",
7
+ subtext = undefined,
8
+ ...restProps
9
+ }: any = $props();
10
+
11
+ const defaultClass = "inline-flex flex-col items-center justify-center rounded bg-sub-background px-1.5 py-0.5 text-xs font-mono text-main-text border-sub-background-hover border-1";
12
+
13
+ const combinedClass = $derived(twMerge(defaultClass, className));
14
+ </script>
15
+
16
+ <span class={combinedClass} {...restProps}>
17
+ {@render children?.()}
18
+ {#if subtext}
19
+ <span class="text-sub-text text-[10px] mt-0.5">
20
+ {@render subtext?.()}
21
+ </span>
22
+ {/if}
23
+ </span>
@@ -0,0 +1,3 @@
1
+ declare const KBD: import("svelte").Component<any, {}, "">;
2
+ type KBD = ReturnType<typeof KBD>;
3
+ export default KBD;
@@ -0,0 +1,35 @@
1
+ <script lang="ts">
2
+ import { getSizeStyleClass } from "../styles/size.js";
3
+ import { twMerge } from "tailwind-merge";
4
+ import type { LoaderProps } from "../types/Loader.js";
5
+ import { generateColorStyle } from "../styles/color.js";
6
+
7
+ let {
8
+ class: className = "",
9
+ size = "md",
10
+ color = "blue",
11
+ ...restProps
12
+ }: LoaderProps = $props();
13
+
14
+ const defaultClass = "loader rounded-full border-main-text border-4";
15
+
16
+ let sizeClass = $derived(twMerge(
17
+ defaultClass,
18
+ getSizeStyleClass(size, "loader"),
19
+ className,
20
+ generateColorStyle(color, "loader"),
21
+ ));
22
+ </script>
23
+
24
+ <div class={sizeClass} {...restProps}></div>
25
+
26
+ <style>
27
+ .loader {
28
+ animation: spin 1s linear infinite;
29
+ }
30
+
31
+ @keyframes spin {
32
+ 0% { transform: rotate(0deg); }
33
+ 100% { transform: rotate(360deg); }
34
+ }
35
+ </style>
@@ -0,0 +1,4 @@
1
+ import type { LoaderProps } from "../types/Loader.ts";
2
+ declare const Loader: import("svelte").Component<LoaderProps, {}, "">;
3
+ type Loader = ReturnType<typeof Loader>;
4
+ export default Loader;