@valerius_petrini/corekit-ui 0.1.60 → 0.1.62

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,8 +1,8 @@
1
1
  <script lang="ts">
2
- import type { InputProps } from "../types/Input.js";
2
+ import type { InputProps, InputRequirements } from "../types/Input.js";
3
3
  import { twMerge } from "tailwind-merge";
4
4
  import Text from "./Text.svelte";
5
- import { sizeStyleParts, type SizeStyleTheme } from "../styles/size.js";
5
+ import { getSizeStyleClass, sizeStyleParts, type SizeStyleTheme } from "../styles/size.js";
6
6
  import { type Component } from "svelte";
7
7
  import Button from "./Button.svelte";
8
8
 
@@ -13,6 +13,9 @@
13
13
  import EyeOff from "@lucide/svelte/icons/eye-off";
14
14
  import ChevronUp from "@lucide/svelte/icons/chevron-up";
15
15
  import ChevronDown from "@lucide/svelte/icons/chevron-down";
16
+ import X from "@lucide/svelte/icons/x";
17
+ import Check from "@lucide/svelte/icons/check";
18
+ import { slide } from "svelte/transition";
16
19
 
17
20
  let {
18
21
  children = undefined,
@@ -29,7 +32,8 @@
29
32
  onblur = undefined,
30
33
  required = false,
31
34
  disabled = false,
32
- validInputRegex = undefined,
35
+ requirements = undefined,
36
+ valid = $bindable(true),
33
37
  size = "md",
34
38
  radius = "md",
35
39
  id = crypto.randomUUID(),
@@ -41,33 +45,10 @@
41
45
 
42
46
  let isHovered = $state(false);
43
47
 
44
- const sizeParts = $derived(typeof size === "string" ? sizeStyleParts[size as SizeStyleTheme] : null);
45
- const radiusParts = $derived(typeof radius === "string" ? sizeStyleParts[radius as SizeStyleTheme] : null);
46
-
47
- const sizeClasses = $derived.by(() => {
48
- const parts = typeof size === "string" ? sizeStyleParts[size as SizeStyleTheme] : null;
49
-
50
- return twMerge(
51
- parts?.form
52
- );
53
- });
54
-
55
- const labelSizeClass = $derived.by(() => {
56
- const parts = typeof size === "string" ? sizeStyleParts[size as SizeStyleTheme] : null;
57
- return parts?.formLabel || "";
58
- });
59
-
60
- const divSizeClass = $derived.by(() => {
61
- const radiusParts = typeof radius === "string" ? sizeStyleParts[radius as SizeStyleTheme] : null;
62
- return twMerge(
63
- radiusParts?.radius
64
- );
65
- });
66
-
67
- const selectedLabelSizeClass = $derived.by(() => {
68
- const parts = typeof size === "string" ? sizeStyleParts[size as SizeStyleTheme] : null;
69
- return parts?.formLabelSelected || "";
70
- });
48
+ const sizeClasses = $derived(getSizeStyleClass(size, "form"));
49
+ const labelSizeClass = $derived(getSizeStyleClass(size, "formLabel"));
50
+ const divSizeClass = $derived(getSizeStyleClass(radius, "radius"));
51
+ const selectedLabelSizeClass = $derived(getSizeStyleClass(size, "formLabelSelected"));
71
52
 
72
53
  const customStyle = $derived.by(() => {
73
54
  const styles: string[] = [];
@@ -97,20 +78,25 @@
97
78
 
98
79
  const isFloating = $derived(variant === "floating");
99
80
  const hasContent = $derived(value !== undefined && value !== null && value.toString().length > 0);
100
- const isValid = $derived(!touched || !validInputRegex || validInputRegex.test(value || ""));
101
81
  const lifted = $derived(isFloating && (isFocused || hasContent));
102
82
 
103
83
  const Icon = $derived(icon ?? ({
104
84
  email: Mail, password: Lock, tel: Phone
105
85
  }[restProps.type as string] as Component ?? null));
106
86
 
107
- let defaultClass = "text-main-text w-full rounded-full outline-none px-1.5 w-full bg-inherit border-0 focus:ring-0 focus-visible:ring-0";
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";
108
88
  let defaultLabelClass = "block text-sub-text font-medium mb-1 duration-100 pointer-events-none truncate w-fit";
109
- let defaultDivClass = "relative *:transition-all flex-center bg-form-background border-[1px] border-form-border focus-within:ring-1 focus-within:ring-blue-500";
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";
110
90
  let iconContainerClass = "h-5 aspect-square px-1 py-0!";
111
91
  let floatingLabelClass = "absolute w-full";
112
92
  let iconClass = "h-full aspect-square text-sub-text";
113
93
 
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
+
114
100
  let originalLabelClass = "z-0";
115
101
  let originalLabelClassInput = "top-1/2 transform -translate-y-1/2";
116
102
  let originalSelectedLabelClass = "z-30";
@@ -123,12 +109,12 @@
123
109
 
124
110
  function handleFocus(e: FocusEvent) {
125
111
  isFocused = true;
112
+ touched = true;
126
113
  onfocus?.(e);
127
114
  }
128
115
 
129
116
  function handleBlur(e: FocusEvent) {
130
117
  isFocused = false;
131
- touched = true;
132
118
  onblur?.(e);
133
119
  }
134
120
 
@@ -140,8 +126,8 @@
140
126
  let defaultLabelClassCheck = $derived(variant !== "floating" ? "px-0" : "");
141
127
  let selectedLabelClass = $derived(twMerge((isFocused || hasContent) && variant === "floating" ? `${originalSelectedLabelClass} ${selectedLabelSizeClass}` : ""));
142
128
  let combinedLabelClass = $derived(twMerge(defaultLabelClass, floatingLabelClassCheck, labelSizeClass, selectedLabelClass, labelClassIcon, defaultLabelClassCheck, labelClass));
143
- let combinedClass = $derived(twMerge(defaultClass, sizeClasses, defaultInputClassCheck, labelSizeClass, inputClassIcon, className, isValid ? "" : invalidClass));
144
- let combinedDivClass = $derived(twMerge(defaultDivClass, divSizeClass, divFullClass, divClass, disabledClass));
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));
145
131
  let combinedOuterDivClass = $derived(twMerge("flex flex-col bg-transparent border-0 p-0", divSizeClass, divFullClass, outerDivClass, disabledClass));
146
132
 
147
133
  let EyeComponent = $derived(canSeePassword ? Eye : EyeOff);
@@ -183,6 +169,25 @@
183
169
  decrementInterval = null;
184
170
  }
185
171
  }
172
+
173
+ function testRequirement(requirement: RegExp | ((value: any) => boolean)) {
174
+ if (typeof requirement === "function")
175
+ return requirement(value || "");
176
+ else if (requirement instanceof RegExp)
177
+ return requirement.test(value || "");
178
+ return true;
179
+ }
180
+
181
+ function isValidInput() {
182
+ for (const requirement of requirements || [])
183
+ if (!testRequirement(requirement.requirements))
184
+ return false;
185
+ return true;
186
+ }
187
+
188
+ $effect(() => {
189
+ valid = isValidInput();
190
+ });
186
191
  </script>
187
192
 
188
193
  {#snippet labelElement()}
@@ -251,11 +256,27 @@
251
256
  </div>
252
257
  {/snippet}
253
258
 
254
- {#if variant !== "floating"}
255
- <div class={combinedOuterDivClass}>
259
+ <div class={combinedOuterDivClass}>
260
+ {#if variant !== "floating"}
256
261
  {@render labelElement()}
257
262
  {@render innerDivElement()}
258
- </div>
259
- {:else}
260
- {@render innerDivElement()}
261
- {/if}
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>
@@ -1,5 +1,5 @@
1
1
  import type { InputProps } from "../types/Input.ts";
2
2
  import { type Component } from "svelte";
3
- declare const Input: Component<InputProps, {}, "value">;
3
+ declare const Input: Component<InputProps, {}, "value" | "valid">;
4
4
  type Input = ReturnType<typeof Input>;
5
5
  export default Input;
@@ -0,0 +1,37 @@
1
+ <script lang="ts">
2
+ import type { SideNavbarProps } from "../types/Navbar.js";
3
+ import { twMerge } from "tailwind-merge";
4
+ import Button from "./Button.svelte";
5
+ import Text from "./Text.svelte";
6
+ import { page } from '$app/state';
7
+
8
+ let {
9
+ children = undefined,
10
+ class: className = "",
11
+ items = [],
12
+ ...restProps
13
+ }: SideNavbarProps = $props();
14
+
15
+ let expanded = $state(false);
16
+
17
+ const defaultClass = "transition-[width] duration-300 overflow-hidden fixed left-0 h-full mt-14 z-[100] py-2 flex flex-col items-center gap-1 bg-sub-background/99 border-r border-box border-r-sub-background-hover";
18
+ const expandedClass = $derived(expanded ? "w-48" : "w-12");
19
+
20
+ const combinedClass = $derived(twMerge(
21
+ defaultClass,
22
+ expandedClass,
23
+ className,
24
+ ));
25
+ </script>
26
+
27
+ <nav class={combinedClass} {...restProps} onmouseenter={() => expanded = true} onmouseleave={() => expanded = false}>
28
+ {#each items as item}
29
+ {@const isActive = page.url.pathname === item.href}
30
+ <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
+ <item.icon class="w-5 h-5 shrink-0"/>
32
+ <Text tag="span" class="transition-opacity duration-200 text-sm text-inherit {expanded ? 'w-auto opacity-100 pr-3' : 'w-0 opacity-0'}">
33
+ {item.label}
34
+ </Text>
35
+ </Button>
36
+ {/each}
37
+ </nav>
@@ -0,0 +1,4 @@
1
+ import type { SideNavbarProps } from "../types/Navbar.ts";
2
+ declare const SideNavbar: import("svelte").Component<SideNavbarProps, {}, "">;
3
+ type SideNavbar = ReturnType<typeof SideNavbar>;
4
+ export default SideNavbar;
package/dist/index.d.ts CHANGED
@@ -16,6 +16,7 @@ export { default as Modal } from "./components/Modal.svelte";
16
16
  export { default as Table } from "./components/Table.svelte";
17
17
  export { default as Toast } from "./components/Toast.svelte";
18
18
  export { default as Toaster } from "./components/Toaster.svelte";
19
+ export { default as SideNavbar } from "./components/SideNavbar.svelte";
19
20
  export { fbmBackground } from "./actions/fbm.ts";
20
21
  export { toast } from "./actions/toast.svelte.ts";
21
22
  export type { TypewriterAction, DisplaySegment } from "./types/Typewriter.ts";
package/dist/index.js CHANGED
@@ -16,5 +16,6 @@ export { default as Modal } from "./components/Modal.svelte";
16
16
  export { default as Table } from "./components/Table.svelte";
17
17
  export { default as Toast } from "./components/Toast.svelte";
18
18
  export { default as Toaster } from "./components/Toaster.svelte";
19
+ export { default as SideNavbar } from "./components/SideNavbar.svelte";
19
20
  export { fbmBackground } from "./actions/fbm.js";
20
21
  export { toast } from "./actions/toast.svelte.js";
@@ -2,6 +2,10 @@ import type { Component } from "svelte";
2
2
  import type { SizeStyle } from "../styles/size.ts";
3
3
  import type { BaseComponentProps } from "./BaseComponent.ts";
4
4
  export type InputVariant = "default" | "floating";
5
+ export interface InputRequirements {
6
+ label: string;
7
+ requirements: RegExp | ((value: any) => boolean);
8
+ }
5
9
  export interface InputProps extends BaseComponentProps {
6
10
  label?: string;
7
11
  labelClass?: string;
@@ -18,7 +22,8 @@ export interface InputProps extends BaseComponentProps {
18
22
  step?: number;
19
23
  onfocus?: (e?: FocusEvent) => void;
20
24
  onblur?: (e?: FocusEvent) => void;
21
- validInputRegex?: RegExp;
25
+ requirements?: InputRequirements[];
26
+ valid?: boolean;
22
27
  size?: SizeStyle;
23
28
  radius?: SizeStyle;
24
29
  id?: `${string}-${string}-${string}-${string}-${string}`;
@@ -1,3 +1,4 @@
1
1
  ;
2
2
  ;
3
+ ;
3
4
  export {};
@@ -1,3 +1,4 @@
1
+ import type { Component } from "svelte";
1
2
  import type { BaseComponentProps } from "./BaseComponent.ts";
2
3
  export interface NavbarProps extends BaseComponentProps {
3
4
  classTop?: string;
@@ -9,3 +10,11 @@ export interface NavbarElementProps extends BaseComponentProps {
9
10
  href?: string;
10
11
  threshold?: number;
11
12
  }
13
+ export interface SideNavbarProps extends BaseComponentProps {
14
+ items?: SideNavbarItem[];
15
+ }
16
+ export interface SideNavbarItem {
17
+ href: string;
18
+ label: string;
19
+ icon: Component;
20
+ }
@@ -1,2 +1,3 @@
1
1
  ;
2
+ ;
2
3
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@valerius_petrini/corekit-ui",
3
- "version": "0.1.60",
3
+ "version": "0.1.62",
4
4
  "description": "Component Library used across all my projects",
5
5
  "author": "Valerius Petrini Jr.",
6
6
  "license": "MIT",