@valerius_petrini/corekit-ui 0.1.51 → 0.1.52

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 (60) hide show
  1. package/dist/actions/fbm.d.ts +10 -0
  2. package/dist/actions/fbm.js +199 -0
  3. package/dist/components/Analytics.svelte +28 -0
  4. package/dist/components/Analytics.svelte.d.ts +4 -0
  5. package/dist/components/Button.svelte +81 -0
  6. package/dist/components/Button.svelte.d.ts +4 -0
  7. package/dist/components/Card.svelte +64 -0
  8. package/dist/components/Card.svelte.d.ts +4 -0
  9. package/dist/components/Checkbox.svelte +36 -0
  10. package/dist/components/Checkbox.svelte.d.ts +4 -0
  11. package/dist/components/FloatingInput.svelte +190 -0
  12. package/dist/components/FloatingInput.svelte.d.ts +5 -0
  13. package/dist/components/FloatingSelect.svelte +40 -0
  14. package/dist/components/FloatingSelect.svelte.d.ts +4 -0
  15. package/dist/components/Navbar.svelte +30 -0
  16. package/dist/components/Navbar.svelte.d.ts +4 -0
  17. package/dist/components/NavbarElement.svelte +35 -0
  18. package/dist/components/NavbarElement.svelte.d.ts +4 -0
  19. package/dist/components/NavbarSeparator.svelte +1 -0
  20. package/dist/components/NavbarSeparator.svelte.d.ts +26 -0
  21. package/dist/components/Progress.svelte +69 -0
  22. package/dist/components/Progress.svelte.d.ts +4 -0
  23. package/dist/components/SEO.svelte +43 -0
  24. package/dist/components/SEO.svelte.d.ts +4 -0
  25. package/dist/components/Text.svelte +65 -0
  26. package/dist/components/Text.svelte.d.ts +4 -0
  27. package/dist/components/Typewriter.svelte +86 -0
  28. package/dist/components/Typewriter.svelte.d.ts +4 -0
  29. package/dist/index.d.ts +15 -15
  30. package/dist/index.js +14 -14
  31. package/dist/styles/color.d.ts +4 -0
  32. package/dist/styles/color.js +204 -0
  33. package/dist/styles/layout.css +52 -0
  34. package/dist/styles/size.d.ts +16 -0
  35. package/dist/styles/size.js +146 -0
  36. package/dist/types/Analytics.d.ts +3 -0
  37. package/dist/types/Analytics.js +1 -0
  38. package/dist/types/Button.d.ts +17 -0
  39. package/dist/types/Button.js +1 -0
  40. package/dist/types/Card.d.ts +13 -0
  41. package/dist/types/Card.js +4 -0
  42. package/dist/types/Checkbox.d.ts +10 -0
  43. package/dist/types/Checkbox.js +2 -0
  44. package/dist/types/FloatingInput.d.ts +23 -0
  45. package/dist/types/FloatingInput.js +2 -0
  46. package/dist/types/FloatingSelect.d.ts +14 -0
  47. package/dist/types/FloatingSelect.js +2 -0
  48. package/dist/types/Navbar.d.ts +16 -0
  49. package/dist/types/Navbar.js +2 -0
  50. package/dist/types/Progress.d.ts +20 -0
  51. package/dist/types/Progress.js +2 -0
  52. package/dist/types/SEO.d.ts +6 -0
  53. package/dist/types/SEO.js +1 -0
  54. package/dist/types/Text.d.ts +11 -0
  55. package/dist/types/Text.js +2 -0
  56. package/dist/types/Typewriter.d.ts +33 -0
  57. package/dist/types/Typewriter.js +2 -0
  58. package/dist/utils/link.d.ts +9 -0
  59. package/dist/utils/link.js +9 -0
  60. package/package.json +1 -1
@@ -0,0 +1,10 @@
1
+ export interface FBMBackgroundOptions {
2
+ octaves?: number;
3
+ warps?: number;
4
+ scale?: number;
5
+ seed?: number;
6
+ }
7
+ export declare function fbmBackground(canvas: HTMLCanvasElement, options?: FBMBackgroundOptions): {
8
+ update(newOptions: FBMBackgroundOptions): void;
9
+ destroy(): void;
10
+ } | undefined;
@@ -0,0 +1,199 @@
1
+ const FBM_VERTEX_SHADER_SOURCE = `
2
+ attribute vec4 aVertexPosition;
3
+
4
+ void main() {
5
+ gl_Position = aVertexPosition;
6
+ }`;
7
+ const FBM_FRAGMENT_SHADER_SOURCE = `
8
+ precision mediump float;
9
+
10
+ uniform vec2 u_resolution;
11
+ uniform float u_seed;
12
+ uniform float u_scale;
13
+ uniform int u_octaves;
14
+ uniform int u_warp;
15
+
16
+ const int maxOctaves = 20;
17
+ const int maxWarps = 5;
18
+
19
+ float rand(vec2 n) {
20
+ return fract(sin(dot(n.xy + u_seed, vec2(12.9898, 78.233))) * 43758.5453);
21
+ }
22
+
23
+ float noise(vec2 p){
24
+ vec2 i = floor(p);
25
+ vec2 f = fract(p);
26
+
27
+ // Four corners in 2D of a tile
28
+ float a = rand(i);
29
+ float b = rand(i + vec2(1.0, 0.0));
30
+ float c = rand(i + vec2(0.0, 1.0));
31
+ float d = rand(i + vec2(1.0, 1.0));
32
+
33
+ vec2 u = f * f * f * (f * (f * 6.0 - 15.0) + 10.0);
34
+
35
+ return mix(a, b, u.x) +
36
+ (c - a)* u.y * (1.0 - u.x) +
37
+ (d - b) * u.x * u.y;
38
+ }
39
+
40
+ float fbm( in vec2 x, in float H ) {
41
+ float v = 00.0000;
42
+ float a = 0.5;
43
+ vec2 shift = vec2(100);
44
+ // Rotate to reduce axial bias
45
+ mat2 rot = mat2(0.877, 0.479, -0.479, 0.877);
46
+ for (int i = 0; i < maxOctaves; ++i) {
47
+ v += a * noise(x);
48
+ x = rot * x * 2.0 + shift;
49
+ a *= 0.5;
50
+ if (i > u_octaves) break;
51
+ }
52
+ return v;
53
+ }
54
+
55
+ void main() {
56
+ vec2 uv = gl_FragCoord.xy / u_resolution.xy;
57
+ uv.x *= u_resolution.x / u_resolution.y;
58
+
59
+ float baseNoise = fbm(uv * u_scale, 1.2);
60
+
61
+ vec2 warp = uv;
62
+ for (int i = 0; i < maxWarps; ++i) {
63
+ warp += vec2(fbm(warp * 2.0, 1.0), fbm(warp * 4.0 + vec2(10.2), 1.0));
64
+ if (i > u_warp) break;
65
+ }
66
+
67
+ baseNoise = fbm(warp * u_scale, 1.2);
68
+
69
+ gl_FragColor = vec4(vec3(baseNoise), 1.0);
70
+ }`;
71
+ function loadShaders(gl, type, source) {
72
+ const shader = gl.createShader(type);
73
+ if (!shader) {
74
+ console.error("Unable to create shader");
75
+ return null;
76
+ }
77
+ gl.shaderSource(shader, source);
78
+ gl.compileShader(shader);
79
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
80
+ console.error("An error occurred compiling the shaders: " + gl.getShaderInfoLog(shader));
81
+ gl.deleteShader(shader);
82
+ return null;
83
+ }
84
+ return shader;
85
+ }
86
+ function initializeShaders(gl, vertexSource, fragmentSource) {
87
+ const vertexShader = loadShaders(gl, gl.VERTEX_SHADER, vertexSource);
88
+ const fragmentShader = loadShaders(gl, gl.FRAGMENT_SHADER, fragmentSource);
89
+ if (!vertexShader || !fragmentShader)
90
+ return null;
91
+ const shaderProgram = gl.createProgram();
92
+ if (!shaderProgram) {
93
+ console.error("Unable to create shader program");
94
+ return null;
95
+ }
96
+ gl.attachShader(shaderProgram, vertexShader);
97
+ gl.attachShader(shaderProgram, fragmentShader);
98
+ gl.linkProgram(shaderProgram);
99
+ if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
100
+ console.error("Unable to initialize the shader program: " + gl.getProgramInfoLog(shaderProgram));
101
+ return null;
102
+ }
103
+ return shaderProgram;
104
+ }
105
+ function initBuffers(gl) {
106
+ const positionBuffer = gl.createBuffer();
107
+ gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
108
+ const vertices = new Float32Array([
109
+ -1.0, -1.0,
110
+ 1.0, -1.0,
111
+ -1.0, 1.0,
112
+ 1.0, 1.0,
113
+ ]);
114
+ gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
115
+ return {
116
+ position: positionBuffer,
117
+ };
118
+ }
119
+ export function fbmBackground(canvas, options = {}) {
120
+ let gl = canvas.getContext("webgl", { antialias: true, premultipliedAlpha: false, preserveDrawingBuffer: true });
121
+ if (!gl) {
122
+ console.error("WebGL not supported");
123
+ return;
124
+ }
125
+ let currentOptions = { ...options };
126
+ let internalSeed = options.seed ?? Math.random() * 1000;
127
+ const shaderProgram = initializeShaders(gl, FBM_VERTEX_SHADER_SOURCE, FBM_FRAGMENT_SHADER_SOURCE);
128
+ if (!shaderProgram) {
129
+ console.error("Failed to initialize shaders");
130
+ return;
131
+ }
132
+ const programInfo = {
133
+ program: shaderProgram,
134
+ attribLocations: {
135
+ vertexPosition: gl.getAttribLocation(shaderProgram, "aVertexPosition"),
136
+ },
137
+ uniformLocations: {
138
+ resolution: gl.getUniformLocation(shaderProgram, "u_resolution"),
139
+ seed: gl.getUniformLocation(shaderProgram, "u_seed"),
140
+ scale: gl.getUniformLocation(shaderProgram, "u_scale"),
141
+ octaves: gl.getUniformLocation(shaderProgram, "u_octaves"),
142
+ warp: gl.getUniformLocation(shaderProgram, "u_warp"),
143
+ }
144
+ };
145
+ const buffers = initBuffers(gl);
146
+ function updateCanvasSize() {
147
+ const DPR = window.devicePixelRatio || 1;
148
+ const newWidth = Math.floor(canvas.offsetWidth * DPR);
149
+ const newHeight = Math.floor(canvas.offsetHeight * DPR);
150
+ if (canvas.width !== newWidth || canvas.height !== newHeight) {
151
+ canvas.width = newWidth;
152
+ canvas.height = newHeight;
153
+ if (gl)
154
+ gl.viewport(0, 0, canvas.width, canvas.height);
155
+ }
156
+ }
157
+ function render() {
158
+ updateCanvasSize();
159
+ if (!gl)
160
+ return;
161
+ gl.viewport(0, 0, canvas.width, canvas.height);
162
+ gl.clearColor(0, 0, 0, 1);
163
+ gl.clearDepth(1.0);
164
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
165
+ gl.enable(gl.DEPTH_TEST);
166
+ gl.depthFunc(gl.LEQUAL);
167
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffers.position);
168
+ gl.vertexAttribPointer(programInfo.attribLocations.vertexPosition, 2, gl.FLOAT, false, 0, 0);
169
+ gl.enableVertexAttribArray(programInfo.attribLocations.vertexPosition);
170
+ gl.useProgram(programInfo.program);
171
+ gl.uniform2f(programInfo.uniformLocations.resolution, canvas.width, canvas.height);
172
+ gl.uniform1f(programInfo.uniformLocations.seed, currentOptions.seed ?? internalSeed);
173
+ gl.uniform1i(programInfo.uniformLocations.octaves, currentOptions.octaves ?? 4);
174
+ gl.uniform1i(programInfo.uniformLocations.warp, currentOptions.warps ?? 2);
175
+ gl.uniform1f(programInfo.uniformLocations.scale, currentOptions.scale ?? 2);
176
+ gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
177
+ }
178
+ const resizeObserver = new ResizeObserver(() => {
179
+ requestAnimationFrame(render);
180
+ });
181
+ resizeObserver.observe(canvas);
182
+ render();
183
+ return {
184
+ update(newOptions) {
185
+ const changed = JSON.stringify(currentOptions) !== JSON.stringify(newOptions);
186
+ if (changed) {
187
+ currentOptions = { ...newOptions };
188
+ render();
189
+ }
190
+ },
191
+ destroy() {
192
+ resizeObserver.disconnect();
193
+ gl.bindBuffer(gl.ARRAY_BUFFER, null);
194
+ gl.deleteBuffer(buffers.position);
195
+ gl.deleteProgram(shaderProgram);
196
+ gl.getExtension("WEBGL_lose_context")?.loseContext();
197
+ }
198
+ };
199
+ }
@@ -0,0 +1,28 @@
1
+ <script lang="ts">
2
+ import { page } from '$app/state'
3
+ import type { AnalyticsProps } from '../types/Analytics.js';
4
+
5
+ let { measurementId }: AnalyticsProps = $props();
6
+
7
+ const ANALYTICS_URL = $derived(`https://www.googletagmanager.com/gtag/js?id=${measurementId}`);
8
+
9
+ $effect(() => {
10
+ if (typeof gtag !== 'undefined') {
11
+ gtag('config', measurementId, {
12
+ page_title: document.title,
13
+ page_path: page.url.pathname,
14
+ })
15
+ }
16
+ })
17
+ </script>
18
+
19
+ <svelte:head>
20
+ {#if measurementId}
21
+ <script async src={ANALYTICS_URL}></script>
22
+ <script>
23
+ window.dataLayer = window.dataLayer || [];
24
+ function gtag() { dataLayer.push(arguments); }
25
+ gtag('js', new Date());
26
+ </script>
27
+ {/if}
28
+ </svelte:head>
@@ -0,0 +1,4 @@
1
+ import type { AnalyticsProps } from '../types/Analytics.ts';
2
+ declare const Analytics: import("svelte").Component<AnalyticsProps, {}, "">;
3
+ type Analytics = ReturnType<typeof Analytics>;
4
+ export default Analytics;
@@ -0,0 +1,81 @@
1
+ <script lang="ts">
2
+ import { generateColorStyle } from "../styles/color.js";
3
+ import { sizeStyleParts, type SizeStyleTheme } from "../styles/size.js";
4
+ import type { ButtonProps } from "../types/Button.js";
5
+ import { twMerge } from "tailwind-merge";
6
+ import { getLinkProps } from "../utils/link.js";
7
+
8
+ let {
9
+ children = undefined,
10
+ class: className = "",
11
+ pill = false,
12
+ icon = false,
13
+ square = false,
14
+ href = undefined,
15
+ color = "none",
16
+ variant = "full",
17
+ disabled = false,
18
+ external = false,
19
+ size = "md",
20
+ radius = "md",
21
+ ...restProps
22
+ }: ButtonProps = $props();
23
+
24
+ const sizeClasses = $derived.by(() => {
25
+ const parts = typeof size === "string" ? sizeStyleParts[size as SizeStyleTheme] : null;
26
+ const radiusParts = typeof radius === "string" ? sizeStyleParts[radius as SizeStyleTheme] : null;
27
+
28
+ return twMerge(
29
+ icon ? "p-0 flex-none" : "h-fit",
30
+ parts?.[icon ? "buttonIcon" : "button"],
31
+ radiusParts?.radius,
32
+ pill || icon ? "rounded-full" : "",
33
+ square ? "aspect-square rounded-none" : ""
34
+ );
35
+ });
36
+
37
+ const customStyle = $derived.by(() => {
38
+ const styles: string[] = [];
39
+
40
+ if (typeof size === "number") {
41
+ styles.push(`height: ${size}px`);
42
+ styles.push(icon
43
+ ? `width: ${size}px; padding: ${size / 8}px`
44
+ : `padding: ${size / 4}px ${size / 8}px`
45
+ );
46
+ }
47
+
48
+ if (typeof radius === "number") {
49
+ styles.push(`border-radius: ${radius}px`);
50
+ }
51
+
52
+ return styles.join("; ");
53
+ });
54
+
55
+ const defaultClass = "inline-flex items-center justify-center gap-2 transition-colors duration-300 ease-in-out text-white whitespace-nowrap";
56
+ const disabledClass = $derived(disabled ? "opacity-50 pointer-events-none" : "cursor-pointer");
57
+
58
+ const mergedClass = $derived(twMerge(
59
+ defaultClass,
60
+ generateColorStyle(color, variant),
61
+ disabledClass,
62
+ sizeClasses,
63
+ className
64
+ ));
65
+
66
+ const mergedStyle = $derived([customStyle, restProps.style].filter(Boolean).join("; "));
67
+
68
+ const anchorProps = $derived(getLinkProps(href, external));
69
+ </script>
70
+
71
+ <svelte:element
72
+ this={href ? "a" : "button"}
73
+ class={mergedClass}
74
+ {disabled}
75
+ aria-disabled={disabled}
76
+ type={href ? undefined : (restProps.type || "button")}
77
+ style={mergedStyle}
78
+ {...anchorProps}
79
+ {...restProps}>
80
+ {@render children?.()}
81
+ </svelte:element>
@@ -0,0 +1,4 @@
1
+ import type { ButtonProps } from "../types/Button.ts";
2
+ declare const Button: import("svelte").Component<ButtonProps, {}, "">;
3
+ type Button = ReturnType<typeof Button>;
4
+ export default Button;
@@ -0,0 +1,64 @@
1
+ <script lang="ts">
2
+ import { sizeStyleParts, type SizeStyleTheme } from "../styles/size.js";
3
+ import { cardVariantStyles, type CardProps } from "../types/Card.js";
4
+ import { twMerge } from "tailwind-merge";
5
+
6
+ let {
7
+ children = undefined,
8
+ class: className = "",
9
+ href = undefined,
10
+ external = false,
11
+ variant = "bordered",
12
+ size = "md",
13
+ radius = "md",
14
+ ...restProps
15
+ }: CardProps = $props();
16
+
17
+ const sizeClasses = $derived.by(() => {
18
+ const parts = typeof size === "string" ? sizeStyleParts[size as SizeStyleTheme] : null;
19
+ const radiusParts = typeof radius === "string" ? sizeStyleParts[radius as SizeStyleTheme] : null;
20
+
21
+ return twMerge(
22
+ parts?.card,
23
+ radiusParts?.radius,
24
+ );
25
+ });
26
+
27
+ const customStyle = $derived.by(() => {
28
+ const styles: string[] = [];
29
+
30
+ if (typeof size === "number")
31
+ styles.push(`width: ${size}px`);
32
+
33
+ if (typeof radius === "number")
34
+ styles.push(`border-radius: ${radius}px`);
35
+
36
+ return styles.join("; ");
37
+ });
38
+
39
+ let defaultClass = "text-main-text rounded-lg transition-colors ease-in-out bg-sub-background p-4";
40
+ let linkClass = "hover:bg-sub-background-hover cursor-pointer";
41
+ let combinedClass = $derived(twMerge(
42
+ defaultClass,
43
+ href ? linkClass : "",
44
+ cardVariantStyles[variant],
45
+ sizeClasses,
46
+ className
47
+ ));
48
+
49
+ const anchorProps = $derived(href ? {
50
+ href,
51
+ target: external ? "_blank" : undefined,
52
+ rel: external ? "noopener noreferrer" : undefined,
53
+ } : {});
54
+ </script>
55
+
56
+ <svelte:element
57
+ this={href ? "a" : "div"}
58
+ class={combinedClass}
59
+ style={customStyle}
60
+ {...anchorProps}
61
+ {...restProps}
62
+ >
63
+ {@render children?.()}
64
+ </svelte:element>
@@ -0,0 +1,4 @@
1
+ import { type CardProps } from "../types/Card.ts";
2
+ declare const Card: import("svelte").Component<CardProps, {}, "">;
3
+ type Card = ReturnType<typeof Card>;
4
+ export default Card;
@@ -0,0 +1,36 @@
1
+ <script lang="ts">
2
+ import { twMerge } from "tailwind-merge";
3
+ import Text from "./Text.svelte";
4
+ import type { CheckboxProps } from "../types/Checkbox.js";
5
+
6
+ let {
7
+ children = undefined,
8
+ class: className = "",
9
+ label = "",
10
+ labelClass = "",
11
+ divClass = "",
12
+ checked = $bindable(),
13
+ id = crypto.randomUUID(),
14
+ ...restProps
15
+ }: CheckboxProps = $props();
16
+
17
+ let defaultClass = "z-20 bg-form-input-background text-main-text rounded-lg outline-none focus:ring-2 focus:ring-blue-500 transition-all";
18
+ let defaultLabelClass = "block text-sub-text font-medium";
19
+ let defaultDivClass = "relative flex items-center gap-2";
20
+
21
+ let combinedLabelClass = $derived(twMerge(defaultLabelClass, labelClass));
22
+ let combinedClass = $derived(twMerge(defaultClass, className));
23
+ let combinedDivClass = $derived(twMerge(defaultDivClass, divClass));
24
+ </script>
25
+
26
+ <div class={combinedDivClass}>
27
+ <input
28
+ type="checkbox"
29
+ id={id}
30
+ bind:checked={checked}
31
+ class={combinedClass}
32
+ {...restProps}/>
33
+ <Text tag="label" for={id} class={combinedLabelClass}>
34
+ {label}
35
+ </Text>
36
+ </div>
@@ -0,0 +1,4 @@
1
+ import type { CheckboxProps } from "../types/Checkbox.ts";
2
+ declare const Checkbox: import("svelte").Component<CheckboxProps, {}, "checked">;
3
+ type Checkbox = ReturnType<typeof Checkbox>;
4
+ export default Checkbox;
@@ -0,0 +1,190 @@
1
+ <script lang="ts">
2
+ import type { FloatingInputProps } from "../types/FloatingInput.js";
3
+ import { twMerge } from "tailwind-merge";
4
+ import Text from "./Text.svelte";
5
+ import { sizeStyleParts, type SizeStyleTheme } from "../styles/size.js";
6
+ import { type Component } from "svelte";
7
+ import Button from "./Button.svelte";
8
+
9
+ import Mail from "@lucide/svelte/icons/mail";
10
+ import Lock from "@lucide/svelte/icons/lock";
11
+ import Phone from "@lucide/svelte/icons/phone";
12
+ import Eye from "@lucide/svelte/icons/eye";
13
+ import EyeOff from "@lucide/svelte/icons/eye-off";
14
+
15
+ let {
16
+ children = undefined,
17
+ class: className = "",
18
+ label = "",
19
+ labelClass = "",
20
+ divClass = "",
21
+ icon = undefined,
22
+ variant = "default",
23
+ placeholder = "",
24
+ value = $bindable(),
25
+ onfocus = undefined,
26
+ onblur = undefined,
27
+ required = false,
28
+ disabled = false,
29
+ validInputRegex = undefined,
30
+ size = "md",
31
+ radius = "md",
32
+ id = crypto.randomUUID(),
33
+ ...restProps
34
+ }: FloatingInputProps = $props();
35
+
36
+ const sizeParts = $derived(typeof size === "string" ? sizeStyleParts[size as SizeStyleTheme] : null);
37
+ const radiusParts = $derived(typeof radius === "string" ? sizeStyleParts[radius as SizeStyleTheme] : null);
38
+
39
+ const sizeClasses = $derived.by(() => {
40
+ const parts = typeof size === "string" ? sizeStyleParts[size as SizeStyleTheme] : null;
41
+
42
+ return twMerge(
43
+ parts?.form
44
+ );
45
+ });
46
+
47
+ const labelSizeClass = $derived.by(() => {
48
+ const parts = typeof size === "string" ? sizeStyleParts[size as SizeStyleTheme] : null;
49
+ return parts?.formLabel || "";
50
+ });
51
+
52
+ const divSizeClass = $derived.by(() => {
53
+ const radiusParts = typeof radius === "string" ? sizeStyleParts[radius as SizeStyleTheme] : null;
54
+ return twMerge(
55
+ radiusParts?.radius
56
+ );
57
+ });
58
+
59
+ const selectedLabelSizeClass = $derived.by(() => {
60
+ const parts = typeof size === "string" ? sizeStyleParts[size as SizeStyleTheme] : null;
61
+ return parts?.formLabelSelected || "";
62
+ });
63
+
64
+ const customStyle = $derived.by(() => {
65
+ const styles: string[] = [];
66
+
67
+ if (typeof size === "number")
68
+ styles.push(`width: ${size}px`);
69
+
70
+ if (typeof radius === "number")
71
+ styles.push(`border-radius: ${radius}px`);
72
+
73
+ return styles.join("; ");
74
+ });
75
+
76
+ const customLabelStyle = $derived.by(() => {
77
+ const styles: string[] = [];
78
+
79
+ if (typeof size === "number")
80
+ styles.push(`font-size: ${size / 4}px`);
81
+
82
+ return styles.join("; ");
83
+ });
84
+
85
+ let isFocused = $state(false);
86
+ let touched = $state(false);
87
+
88
+ let canSeePassword = $state(false);
89
+
90
+ const isFloating = $derived(variant === "floating");
91
+ const hasContent = $derived(value !== undefined && value !== null && value.toString().length > 0);
92
+ const isValid = $derived(!touched || !validInputRegex || validInputRegex.test(value || ""));
93
+ const lifted = $derived(isFloating && (isFocused || hasContent));
94
+
95
+ const Icon = $derived(icon ?? ({
96
+ mail: Mail, password: Lock, tel: Phone
97
+ }[restProps.type as string] as Component ?? null));
98
+
99
+ let defaultClass = "text-main-text w-full rounded outline-none px-1.5 w-full";
100
+ let defaultLabelClass = "block text-sub-text rounded-md font-medium mb-1 duration-100 pointer-events-none truncate w-fit";
101
+ let defaultDivClass = "relative *:transition-all flex-center bg-form-background border-[1px] border-form-border focus-within:ring-1 focus-within:ring-blue-500";
102
+ let iconContainerClass = "h-5 aspect-square px-1 py-0!";
103
+ let floatingLabelClass = "absolute w-full";
104
+ let iconClass = "h-full aspect-square text-sub-text";
105
+
106
+ let originalLabelClass = "z-0";
107
+ let originalLabelClassInput = "top-1/2 transform -translate-y-1/2";
108
+ let originalSelectedLabelClass = "z-30";
109
+
110
+ let invalidClass = "border border-red-500 focus:ring-red-500 bg-[#2E1F1F]";
111
+
112
+ let floatingLabelClassFull = $derived(twMerge(originalLabelClassInput, originalLabelClass, floatingLabelClass));
113
+ let divFullClass = $derived(size === "full" ? "w-full" : "");
114
+ let disabledClass = $derived(disabled ? "opacity-50 pointer-events-none" : "cursor-pointer");
115
+
116
+ function handleFocus(e: FocusEvent) {
117
+ isFocused = true;
118
+ onfocus?.(e);
119
+ }
120
+
121
+ function handleBlur(e: FocusEvent) {
122
+ isFocused = false;
123
+ touched = true;
124
+ onblur?.(e);
125
+ }
126
+
127
+ let labelClassIcon = $derived(Icon !== null ? "pl-[32px] pr-2" : "px-1.5");
128
+ let inputClassIcon = $derived(Icon !== null ? "pl-0 pr-1" : "");
129
+
130
+ let defaultInputClassCheck = $derived(variant !== "floating" ? "py-0" : "");
131
+ let floatingLabelClassCheck = $derived(variant === "floating" ? floatingLabelClassFull : "");
132
+ let defaultLabelClassCheck = $derived(variant !== "floating" ? "px-1.5" : "");
133
+ let selectedLabelClass = $derived(twMerge((isFocused || hasContent) && variant === "floating" ? `${originalSelectedLabelClass} ${selectedLabelSizeClass}` : ""));
134
+ let combinedLabelClass = $derived(twMerge(defaultLabelClass, floatingLabelClassCheck, labelSizeClass, selectedLabelClass, labelClassIcon, defaultLabelClassCheck, labelClass));
135
+ let combinedClass = $derived(twMerge(defaultClass, sizeClasses, defaultInputClassCheck, labelSizeClass, inputClassIcon, className, isValid ? "" : invalidClass));
136
+ let combinedDivClass = $derived(twMerge(defaultDivClass, divSizeClass, divFullClass, divClass, disabledClass));
137
+ let combinedOuterDivClass = $derived(twMerge("flex flex-col bg-transparent border-0 p-0", divSizeClass, divFullClass, divClass, disabledClass));
138
+
139
+ let EyeComponent = $derived(canSeePassword ? Eye : EyeOff);
140
+ </script>
141
+
142
+ {#snippet labelElement()}
143
+ <Text tag="label" for={id} class={combinedLabelClass} style={customLabelStyle}>
144
+ {label}
145
+ {#if required}
146
+ <span class="text-[#E05555]">*</span>
147
+ {/if}
148
+ </Text>
149
+ {/snippet}
150
+
151
+ {#snippet innerDivElement()}
152
+ <div class={combinedDivClass}>
153
+ {#if Icon}
154
+ <div class={iconContainerClass}>
155
+ <Icon class={iconClass}></Icon>
156
+ </div>
157
+ {/if}
158
+ {#if variant === "floating"}
159
+ {@render labelElement()}
160
+ {/if}
161
+ <input
162
+ {id}
163
+ bind:value={value}
164
+ onfocus={handleFocus}
165
+ onblur={handleBlur}
166
+ class={combinedClass}
167
+ {required}
168
+ {disabled}
169
+ placeholder={variant === "floating" ? "" : placeholder}
170
+ aria-disabled={disabled}
171
+ style={customStyle}
172
+ {...restProps}
173
+ type={canSeePassword ? "text" : restProps.type}
174
+ />
175
+ {#if restProps.type === "password"}
176
+ <Button class={iconContainerClass} onclick={() => { canSeePassword = !canSeePassword; }}>
177
+ <EyeComponent class={iconClass}></EyeComponent>
178
+ </Button>
179
+ {/if}
180
+ </div>
181
+ {/snippet}
182
+
183
+ {#if variant !== "floating"}
184
+ <div class={combinedOuterDivClass}>
185
+ {@render labelElement()}
186
+ {@render innerDivElement()}
187
+ </div>
188
+ {:else}
189
+ {@render innerDivElement()}
190
+ {/if}
@@ -0,0 +1,5 @@
1
+ import type { FloatingInputProps } from "../types/FloatingInput.ts";
2
+ import { type Component } from "svelte";
3
+ declare const FloatingInput: Component<FloatingInputProps, {}, "value">;
4
+ type FloatingInput = ReturnType<typeof FloatingInput>;
5
+ export default FloatingInput;
@@ -0,0 +1,40 @@
1
+ <script lang="ts">
2
+ import type { FloatingSelectProps } from "../types/FloatingSelect.js";
3
+ import { twMerge } from "tailwind-merge";
4
+ import Text from "./Text.svelte";
5
+
6
+ let {
7
+ children = undefined,
8
+ class: className = "",
9
+ label = "",
10
+ divClass = "",
11
+ optionClass = "",
12
+ value = $bindable(),
13
+ options = [],
14
+ id = crypto.randomUUID(),
15
+ ...restProps
16
+ }: FloatingSelectProps = $props();
17
+
18
+ let defaultSelectClass = "cursor-pointer bg-form-background border-[1px] border-form-border text-main-text z-20 w-full rounded px-1 pt-4 pb-1 text-xs outline-none focus:ring-2 focus:ring-blue-500 transition-all";
19
+ let defaultLabelClass = "block text-sub-text rounded-md text-sm font-medium mb-1 absolute transition-all duration-100 pointer-events-none";
20
+ let defaultDivClass = "relative w-fit";
21
+
22
+ let selectedLabelClass = "left-2 z-30 top-0.5 text-[10px]!";
23
+
24
+ let combinedLabelClass = $derived(twMerge(defaultLabelClass, selectedLabelClass, className));
25
+ let combinedSelectClass = $derived(twMerge(defaultSelectClass, className));
26
+ let combinedDivClass = $derived(twMerge(defaultDivClass, divClass));
27
+ </script>
28
+
29
+ <div class={combinedDivClass}>
30
+ <Text tag="label" for={id} class={combinedLabelClass}>
31
+ {label}
32
+ </Text>
33
+ <select {id} class={combinedSelectClass} {...restProps} bind:value={value}>
34
+ {#each options as option}
35
+ <option value={option.value} class={optionClass}>
36
+ {option.label}
37
+ </option>
38
+ {/each}
39
+ </select>
40
+ </div>