@valerius_petrini/corekit-ui 0.1.63 → 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.
- package/dist/components/Analytics.svelte +4 -0
- package/dist/components/Button.svelte +10 -5
- package/dist/components/Card.svelte +1 -1
- package/dist/components/Combobox.svelte +207 -0
- package/dist/components/Combobox.svelte.d.ts +4 -0
- package/dist/components/Input.svelte +46 -148
- package/dist/components/KBD.svelte +23 -0
- package/dist/components/KBD.svelte.d.ts +3 -0
- package/dist/components/Loader.svelte +35 -0
- package/dist/components/Loader.svelte.d.ts +4 -0
- package/dist/components/SEO.svelte +27 -17
- package/dist/components/Select.svelte +63 -23
- package/dist/components/Select.svelte.d.ts +1 -1
- package/dist/components/helper/BaseInput.svelte +105 -0
- package/dist/components/helper/BaseInput.svelte.d.ts +4 -0
- package/dist/components/helper/NumberInput.svelte +79 -0
- package/dist/components/helper/NumberInput.svelte.d.ts +11 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/styles/color.d.ts +1 -1
- package/dist/styles/color.js +56 -28
- package/dist/styles/layout.css +6 -0
- package/dist/styles/size.d.ts +1 -1
- package/dist/styles/size.js +36 -12
- package/dist/types/Button.d.ts +1 -0
- package/dist/types/Input.d.ts +20 -5
- package/dist/types/Input.js +1 -0
- package/dist/types/Loader.d.ts +5 -0
- package/dist/types/{Select.js → Loader.js} +0 -1
- package/dist/types/SEO.d.ts +1 -1
- package/dist/utils/debounce.d.ts +1 -0
- package/dist/utils/debounce.js +7 -0
- package/dist/utils/link.d.ts +2 -2
- package/dist/utils/link.js +2 -2
- package/package.json +1 -1
- package/dist/types/Select.d.ts +0 -12
|
@@ -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 ? "
|
|
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>
|
|
@@ -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, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
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>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type { InputProps
|
|
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
|
|
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
|
-
|
|
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
|
|
126
|
-
let
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
{
|
|
201
|
-
|
|
202
|
-
{
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
<
|
|
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
|
-
|
|
257
|
-
|
|
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,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>
|