@rkosafo/cai.components 0.0.34 → 0.0.36

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.
@@ -0,0 +1,68 @@
1
+ <script lang="ts">
2
+ import { IconifyIcon, key, Label } from '../../index.js';
3
+ import type { FormClEditorProps } from '../../types/index.js';
4
+ import Editor from 'cl-editor/src/Editor.svelte';
5
+ import { nanoid } from 'nanoid';
6
+ import { getContext, onMount } from 'svelte';
7
+
8
+ let {
9
+ name = '',
10
+ label = '',
11
+ required,
12
+ readonly,
13
+ contextKey = null,
14
+ type = 'text',
15
+ pattern = null,
16
+ placeholder,
17
+ value,
18
+ height,
19
+ ...otherProps
20
+ }: FormClEditorProps = $props();
21
+ const { touched, errors, data, setData }: any = getContext(contextKey || key);
22
+ const hasError = $derived($touched[name] && $errors[name]?.length);
23
+ const error = $derived($errors[name]?.join(', '));
24
+ const isSuccess = $derived(!readonly && !hasError && $touched[name]);
25
+ let editorValue = $derived($data[name] || value || '');
26
+ let id = nanoid();
27
+
28
+ function onChange({ detail: e }: CustomEvent) {
29
+ setData({ ...$data, [name]: e });
30
+ }
31
+
32
+ function onBlur({ detail: e }: CustomEvent) {
33
+ console.log({ e });
34
+ // setData({ ...$data, [name]: e });
35
+ }
36
+ // $effect(() => {
37
+ // if (value !== undefined && value !== $data[name]) {
38
+ // setData({ ...$data, [name]: value });
39
+ // }
40
+ // });
41
+ </script>
42
+
43
+ <div class="relative space-y-1">
44
+ <Label
45
+ >{label}
46
+ {#if required}
47
+ <span class="pl-1 text-red-500">*</span>
48
+ {/if}
49
+ </Label>
50
+
51
+ <Editor contentId={id} on:change={onChange} bind:html={editorValue} {height} />
52
+
53
+ {#if hasError}
54
+ <Label
55
+ class="v-error-container absolute top-9 right-2 flex items-center gap-1 text-sm {hasError &&
56
+ 'text-red-600'}"
57
+ >
58
+ <span class="v-error-message hidden backdrop-blur-sm">
59
+ {error}
60
+ </span>
61
+ <IconifyIcon
62
+ icon="solar:danger-circle-bold-duotone"
63
+ class="v-error-svg ml-auto cursor-pointer text-red-500 select-none hover:text-red-600"
64
+ style="font-size: 18px;"
65
+ />
66
+ </Label>
67
+ {/if}
68
+ </div>
@@ -0,0 +1,4 @@
1
+ import type { FormClEditorProps } from '../../types/index.js';
2
+ declare const ClEdito: import("svelte").Component<FormClEditorProps, {}, "">;
3
+ type ClEdito = ReturnType<typeof ClEdito>;
4
+ export default ClEdito;
@@ -0,0 +1 @@
1
+ export { default as FormClEditor } from './ClEdito.svelte';
@@ -0,0 +1 @@
1
+ export { default as FormClEditor } from './ClEdito.svelte';
package/dist/index.d.ts CHANGED
@@ -40,6 +40,7 @@ export * from './forms/FormSelect/index.js';
40
40
  export * from './forms/FormTextarea/index.js';
41
41
  export * from './forms/FormCheckbox/index.js';
42
42
  export * from './forms/FormRadio/index.js';
43
+ export * from './forms/FormClEditor/index.js';
43
44
  export * from './builders/filters/index.js';
44
45
  export * from './types/index.js';
45
46
  export * from './utils/index.js';
package/dist/index.js CHANGED
@@ -41,6 +41,7 @@ export * from './forms/FormSelect/index.js';
41
41
  export * from './forms/FormTextarea/index.js';
42
42
  export * from './forms/FormCheckbox/index.js';
43
43
  export * from './forms/FormRadio/index.js';
44
+ export * from './forms/FormClEditor/index.js';
44
45
  export * from './builders/filters/index.js';
45
46
  export * from './types/index.js';
46
47
  export * from './utils/index.js';
@@ -1,8 +1,8 @@
1
1
  <script lang="ts">
2
2
  import { cn } from '../../../utils/index.js';
3
3
  import { page } from '$app/state';
4
- import type { Snippet } from 'svelte';
5
- import type { IMenuItem, TFSidebarProps } from '../../../index.js';
4
+ import { IconifyIcon, type IMenuItem, type TFSidebarProps } from '../../../index.js';
5
+ import { slide } from 'svelte/transition';
6
6
 
7
7
  let {
8
8
  homeUrl,
@@ -16,22 +16,113 @@
16
16
  }: TFSidebarProps = $props();
17
17
 
18
18
  let activeUrl = $state('');
19
+ let openNestedIndexes = $state<Set<number>>(new Set());
20
+
19
21
  $effect(() => {
20
22
  activeUrl = page.url.pathname;
21
23
  });
24
+
25
+ // Function to toggle nested menu by index
26
+ function toggleNestedMenu(index: number) {
27
+ handleMenuItemClick();
28
+ const newOpenIndexes = new Set(openNestedIndexes);
29
+
30
+ if (newOpenIndexes.has(index)) {
31
+ // If already open, close it
32
+ newOpenIndexes.delete(index);
33
+ } else {
34
+ // If opening new one, just add it (don't clear others)
35
+ newOpenIndexes.add(index);
36
+ }
37
+ openNestedIndexes = newOpenIndexes;
38
+ }
39
+
40
+ // Function to handle regular menu item clicks (non-nested)
41
+ function handleMenuItemClick() {
42
+ // Close all nested menus when clicking a regular menu item
43
+ openNestedIndexes = new Set();
44
+ }
45
+
46
+ // Function to check if a menu item or any of its children is active
47
+ function isItemOrChildActive(item: IMenuItem): boolean {
48
+ if (isActiveFunction) {
49
+ return isActiveFunction(item);
50
+ }
51
+
52
+ if (item.path === activeUrl) {
53
+ return true;
54
+ }
55
+
56
+ // Check if any child is active
57
+ if (item.items) {
58
+ return item.items.some((child) => child.path === activeUrl);
59
+ }
60
+
61
+ return false;
62
+ }
22
63
  </script>
23
64
 
24
65
  {#snippet menuItemSnippet(item: IMenuItem, active: boolean)}
25
66
  <li class:active class="relative">
26
- <a class="space-x-3 pl-3" href={item.path}>
27
- <iconify-icon icon={item.icon}></iconify-icon>
28
-
29
- <span class="text hidden md:block">{item.title}</span>
67
+ <a
68
+ class="space-x-3 pl-3 {active ? 'text-[#3C91E6]' : ''}"
69
+ href={item.path}
70
+ onclick={() => handleMenuItemClick()}
71
+ >
72
+ <IconifyIcon icon={item.icon} class={active ? 'text-[#3C91E6]' : ''} />
73
+ <span class="text hidden md:block {active ? 'text-[#3C91E6]' : ''}">{item.title}</span>
30
74
  </a>
31
75
  </li>
32
76
  {/snippet}
77
+
78
+ {#snippet nestedItemSnippet(item: IMenuItem, index: number)}
79
+ {@const isActive = isItemOrChildActive(item)}
80
+ {@const isOpen = openNestedIndexes.has(index)}
81
+
82
+ <div class="mx-1 rounded-[5px] px-1.5 py-2 {isActive ? 'bg-[#cfe8ff79] text-[#3C91E6]' : ''}">
83
+ <button
84
+ class="flex w-full items-center space-x-3 pl-3"
85
+ onclick={() => toggleNestedMenu(index)}
86
+ type="button"
87
+ >
88
+ <IconifyIcon icon={item.icon} class={isActive ? 'text-[#3C91E6]' : ''} />
89
+ <span class="text hidden md:block {isActive ? 'text-[#3C91E6]' : ''}">{item.title}</span>
90
+ <IconifyIcon
91
+ icon="ri:arrow-down-s-line"
92
+ class="ml-auto transition-transform duration-200 {isOpen ? 'rotate-180' : ''}"
93
+ />
94
+ </button>
95
+ {#if isOpen}
96
+ <ul transition:slide class="sub-menu space-y-0 py-1">
97
+ {#each item.items! as child}
98
+ {@const isChildActive = isActiveFunction
99
+ ? isActiveFunction(child)
100
+ : activeUrl === child.path}
101
+ <div class="pl-4">
102
+ <li class="relative">
103
+ <a
104
+ class="flex items-center space-x-3 rounded transition-colors hover:bg-[#cfe8ff] {isChildActive
105
+ ? 'bg-[#cfe8ff] text-[#3C91E6]'
106
+ : ''}"
107
+ href={child.path}
108
+ >
109
+ {#if child.icon}
110
+ <IconifyIcon icon={child.icon} class={isChildActive ? 'text-[#3C91E6]' : ''} />
111
+ {/if}
112
+ <span class="text hidden md:block {isChildActive ? 'text-[#3C91E6]' : ''}"
113
+ >{child.title}</span
114
+ >
115
+ </a>
116
+ </li>
117
+ </div>
118
+ {/each}
119
+ </ul>
120
+ {/if}
121
+ </div>
122
+ {/snippet}
123
+
33
124
  <section id="tf-sidebar" class={cn('relative')} class:hide={hideSidebar}>
34
- <a href={homeUrl} class="brand flex flex-col pt-4">
125
+ <a href={homeUrl} class="brand flex flex-col pt-4" onclick={() => handleMenuItemClick()}>
35
126
  {#if logo}
36
127
  {@render logo()}
37
128
  {:else}
@@ -44,9 +135,13 @@
44
135
  </a>
45
136
  {#if menuItems.length > 0}
46
137
  <ul class="side-menu top relative pt-4">
47
- {#each menuItems as item}
138
+ {#each menuItems as item, index}
48
139
  {@const active = isActiveFunction ? isActiveFunction(item) : activeUrl === item.path}
49
- {@render menuItemSnippet(item, active)}
140
+ {#if item.items}
141
+ {@render nestedItemSnippet(item, index)}
142
+ {:else}
143
+ {@render menuItemSnippet(item, active)}
144
+ {/if}
50
145
  {/each}
51
146
  </ul>
52
147
  {/if}
@@ -1,4 +1,4 @@
1
- import type { TFSidebarProps } from '../../../index.js';
1
+ import { type TFSidebarProps } from '../../../index.js';
2
2
  declare const Sidebar: import("svelte").Component<TFSidebarProps, {}, "">;
3
3
  type Sidebar = ReturnType<typeof Sidebar>;
4
4
  export default Sidebar;
@@ -543,6 +543,9 @@ export interface FormTextareaProps extends Omit<TextareaProps, 'name'>, IFormPro
543
543
  }
544
544
  export interface FormRadioProps<T> extends Omit<RadioProps<T>, 'name'>, IFormProps {
545
545
  }
546
+ export interface FormClEditorProps extends Omit<InputProps, 'name'>, IFormProps {
547
+ height?: string;
548
+ }
546
549
  export interface FormCheckboxProps extends Omit<CheckboxProps, 'name'>, IFormProps {
547
550
  onChange?: (val: any) => void;
548
551
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rkosafo/cai.components",
3
- "version": "0.0.34",
3
+ "version": "0.0.36",
4
4
  "files": [
5
5
  "dist",
6
6
  "!dist/**/*.test.*",
@@ -54,6 +54,7 @@
54
54
  "@tiptap/extension-text-style": "^3.0.7",
55
55
  "@tiptap/pm": "^3.0.7",
56
56
  "@tiptap/starter-kit": "^3.0.7",
57
+ "cl-editor": "^2.3.0",
57
58
  "clsx": "^2.1.1",
58
59
  "date-fns": "^4.1.0",
59
60
  "felte": "^1.3.0",