hamzus-ui 0.0.205 → 0.0.207

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/index.d.ts CHANGED
@@ -62,7 +62,9 @@ export * as Tooltip from "./src/components/hamzus-ui/AdvancedTooltip";
62
62
 
63
63
  // layout
64
64
  export { default as ScrollArea } from "./src/components/hamzus-ui/ScrollArea/ScrollArea.svelte"
65
+ export { default as Scrollbar } from "./src/components/hamzus-ui/Scrollbar/Scrollbar.svelte"
65
66
  export * as Sidebar from "./src/layout/Sidebar"
67
+ export * as SidebarV2 from "./src/components/hamzus-ui/SidebarV2"
66
68
 
67
69
  // special
68
70
  export { default as Portal } from "./src/components/hamzus-ui/Portal/Portal.svelte"
package/index.js CHANGED
@@ -59,7 +59,9 @@ export * as Tooltip from "./src/components/hamzus-ui/AdvancedTooltip"
59
59
 
60
60
  // layout
61
61
  export { default as ScrollArea } from "./src/components/hamzus-ui/ScrollArea/ScrollArea.svelte"
62
+ export { default as Scrollbar } from "./src/components/hamzus-ui/Scrollbar/Scrollbar.svelte"
62
63
  export * as Sidebar from "./src/layout/Sidebar"
64
+ export * as SidebarV2 from "./src/components/hamzus-ui/SidebarV2"
63
65
 
64
66
  // special
65
67
  export { default as Portal } from "./src/components/hamzus-ui/Portal/Portal.svelte"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hamzus-ui",
3
- "version": "0.0.205",
3
+ "version": "0.0.207",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "svelte": "index.js",
@@ -0,0 +1,308 @@
1
+ <script>
2
+ import { onMount, onDestroy } from 'svelte';
3
+ import { browser } from '$app/environment';
4
+
5
+ export let element = null;
6
+ export let thickness = 15;
7
+ export let padding = 1;
8
+
9
+ let thumbHeight = 0;
10
+ let thumbTop = 0;
11
+ let hasScrollY = false;
12
+
13
+ let thumbWidth = 0;
14
+ let thumbLeft = 0;
15
+ let hasScrollX = false;
16
+
17
+ // ── Mesure ──────────────────────────────────────────────────
18
+
19
+ function measure(el) {
20
+ const { clientHeight, scrollHeight, scrollTop,
21
+ clientWidth, scrollWidth, scrollLeft } = el;
22
+
23
+ hasScrollY = scrollHeight > clientHeight;
24
+ thumbHeight = hasScrollY ? (clientHeight / scrollHeight) * clientHeight : clientHeight;
25
+ thumbTop = hasScrollY
26
+ ? (scrollTop / (scrollHeight - clientHeight)) * (clientHeight - thumbHeight)
27
+ : 0;
28
+
29
+ hasScrollX = scrollWidth > clientWidth;
30
+ thumbWidth = hasScrollX ? (clientWidth / scrollWidth) * clientWidth : clientWidth;
31
+ thumbLeft = hasScrollX
32
+ ? (scrollLeft / (scrollWidth - clientWidth)) * (clientWidth - thumbWidth)
33
+ : 0;
34
+ }
35
+
36
+ // ── Press & hold sur la track ───────────────────────────────
37
+
38
+ let trackPressInterval = null;
39
+
40
+ function stopTrackPress() {
41
+ if (trackPressInterval) {
42
+ clearInterval(trackPressInterval);
43
+ trackPressInterval = null;
44
+ }
45
+ }
46
+
47
+ function onTrackYMouseDown(e) {
48
+ if (!element || e.target !== e.currentTarget) return;
49
+ e.preventDefault();
50
+ const target = e.currentTarget;
51
+
52
+ function step() {
53
+ if (!element) return;
54
+ const rect = target.getBoundingClientRect();
55
+ const clickY = e.clientY - rect.top;
56
+ const direction = clickY < thumbTop ? -1 : 1;
57
+ element.scrollTop += direction * element.clientHeight * 0.8;
58
+ measure(element);
59
+ if (direction === -1 && thumbTop <= clickY) stopTrackPress();
60
+ if (direction === 1 && thumbTop + thumbHeight >= clickY) stopTrackPress();
61
+ }
62
+
63
+ step();
64
+ trackPressInterval = setInterval(step, 120);
65
+ window.addEventListener('mouseup', stopTrackPress, { once: true });
66
+ }
67
+
68
+ function onTrackXMouseDown(e) {
69
+ if (!element || e.target !== e.currentTarget) return;
70
+ e.preventDefault();
71
+ const target = e.currentTarget;
72
+
73
+ function step() {
74
+ if (!element) return;
75
+ const rect = target.getBoundingClientRect();
76
+ const clickX = e.clientX - rect.left;
77
+ const direction = clickX < thumbLeft ? -1 : 1;
78
+ element.scrollLeft += direction * element.clientWidth * 0.8;
79
+ measure(element);
80
+ if (direction === -1 && thumbLeft <= clickX) stopTrackPress();
81
+ if (direction === 1 && thumbLeft + thumbWidth >= clickX) stopTrackPress();
82
+ }
83
+
84
+ step();
85
+ trackPressInterval = setInterval(step, 120);
86
+ window.addEventListener('mouseup', stopTrackPress, { once: true });
87
+ }
88
+
89
+ // ── Drag vertical ───────────────────────────────────────────
90
+
91
+ let draggingY = false;
92
+ let dragStartY = 0;
93
+ let dragStartScrollTop = 0;
94
+
95
+ function onThumbYMouseDown(e) {
96
+ if (!element) return;
97
+ e.preventDefault();
98
+ draggingY = true;
99
+ dragStartY = e.clientY;
100
+ dragStartScrollTop = element.scrollTop;
101
+ }
102
+
103
+ function onMouseMoveY(e) {
104
+ if (!draggingY || !element) return;
105
+ const { clientHeight, scrollHeight } = element;
106
+ const thumbH = (clientHeight / scrollHeight) * clientHeight;
107
+ const ratio = (scrollHeight - clientHeight) / (clientHeight - thumbH);
108
+ element.scrollTop = dragStartScrollTop + (e.clientY - dragStartY) * ratio;
109
+ }
110
+
111
+ function onMouseUpY() { draggingY = false; }
112
+
113
+ // ── Drag horizontal ─────────────────────────────────────────
114
+
115
+ let draggingX = false;
116
+ let dragStartX = 0;
117
+ let dragStartScrollLeft = 0;
118
+
119
+ function onThumbXMouseDown(e) {
120
+ if (!element) return;
121
+ e.preventDefault();
122
+ draggingX = true;
123
+ dragStartX = e.clientX;
124
+ dragStartScrollLeft = element.scrollLeft;
125
+ }
126
+
127
+ function onMouseMoveX(e) {
128
+ if (!draggingX || !element) return;
129
+ const { clientWidth, scrollWidth } = element;
130
+ const thumbW = (clientWidth / scrollWidth) * clientWidth;
131
+ const ratio = (scrollWidth - clientWidth) / (clientWidth - thumbW);
132
+ element.scrollLeft = dragStartScrollLeft + (e.clientX - dragStartX) * ratio;
133
+ }
134
+
135
+ function onMouseUpX() { draggingX = false; }
136
+
137
+ // ── Listeners globaux (window) ──────────────────────────────
138
+
139
+ function onWindowMouseMove(e) {
140
+ onMouseMoveY(e);
141
+ onMouseMoveX(e);
142
+ }
143
+
144
+ function onWindowMouseUp() {
145
+ onMouseUpY();
146
+ onMouseUpX();
147
+ }
148
+
149
+ function addWindowListeners() {
150
+ window.addEventListener('mousemove', onWindowMouseMove);
151
+ window.addEventListener('mouseup', onWindowMouseUp);
152
+ }
153
+
154
+ function removeWindowListeners() {
155
+ window.removeEventListener('mousemove', onWindowMouseMove);
156
+ window.removeEventListener('mouseup', onWindowMouseUp);
157
+ }
158
+
159
+ // ── Observer setup ──────────────────────────────────────────
160
+
161
+ let resizeObserver = null;
162
+
163
+ function handleScroll() {
164
+ if (element) measure(element);
165
+ }
166
+
167
+ function setupObserver(el) {
168
+ cleanupObserver();
169
+ measure(el);
170
+
171
+ resizeObserver = new ResizeObserver(() => measure(el));
172
+ resizeObserver.observe(el);
173
+ for (const child of Array.from(el.children)) {
174
+ resizeObserver.observe(child);
175
+ }
176
+ el.addEventListener('scroll', handleScroll);
177
+ }
178
+
179
+ function cleanupObserver() {
180
+ if (element) element.removeEventListener('scroll', handleScroll);
181
+ resizeObserver?.disconnect();
182
+ resizeObserver = null;
183
+ }
184
+
185
+ // $: réactif à `element` — mais uniquement côté client
186
+ $: if (browser && element) {
187
+ setupObserver(element);
188
+ } else if (browser && !element) {
189
+ cleanupObserver();
190
+ stopTrackPress();
191
+ thumbHeight = 0; thumbTop = 0; hasScrollY = false;
192
+ thumbWidth = 0; thumbLeft = 0; hasScrollX = false;
193
+ }
194
+
195
+ // Les listeners window uniquement après le montage (côté client)
196
+ onMount(() => {
197
+ addWindowListeners();
198
+ return () => {
199
+ removeWindowListeners();
200
+ cleanupObserver();
201
+ stopTrackPress();
202
+ };
203
+ });
204
+
205
+ onDestroy(() => {
206
+ // onDestroy peut être appelé côté serveur — ne rien faire si pas browser
207
+ if (!browser) return;
208
+ removeWindowListeners();
209
+ cleanupObserver();
210
+ stopTrackPress();
211
+ });
212
+ </script>
213
+
214
+ <div
215
+ class="scroll-provider"
216
+ style="--track-padding:{padding}px;--track-thickness:{thickness}px;--thumb-width:{thumbWidth}px;--thumb-height:{thumbHeight}px;--thumb-left:{thumbLeft}px;--thumb-top:{thumbTop}px;"
217
+ >
218
+ {#if hasScrollX}
219
+ <div class="track-h" on:mousedown={onTrackXMouseDown}>
220
+ <span
221
+ class="thumb"
222
+ class:dragging={draggingX}
223
+ on:mousedown={onThumbXMouseDown}
224
+ />
225
+ </div>
226
+ {/if}
227
+
228
+ {#if hasScrollY}
229
+ <div class="track-v" on:mousedown={onTrackYMouseDown}>
230
+ <span
231
+ class="thumb"
232
+ class:dragging={draggingY}
233
+ on:mousedown={onThumbYMouseDown}
234
+ />
235
+ </div>
236
+ {/if}
237
+ </div>
238
+
239
+ <style>
240
+ .scroll-provider {
241
+ position: absolute;
242
+ top: 0px;
243
+ left: 0px;
244
+ width: 100%;
245
+ height: 100%;
246
+ pointer-events: none;
247
+ }
248
+
249
+ .track-h,
250
+ .track-v {
251
+ pointer-events: all;
252
+ }
253
+
254
+ .thumb {
255
+ position: absolute;
256
+ display: flex;
257
+ background-color: var(--bg-blur);
258
+ cursor: grab;
259
+ transition: background-color 0.15s;
260
+ }
261
+
262
+ .thumb:hover,
263
+ .thumb.dragging {
264
+ background-color: var(--accent-b);
265
+ cursor: grabbing;
266
+ }
267
+
268
+ .track-h {
269
+ position: absolute;
270
+ bottom: 0px;
271
+ left: 0px;
272
+ width: 100%;
273
+ height: var(--track-thickness);
274
+ }
275
+
276
+ .track-h .thumb {
277
+ width: calc(var(--thumb-width) - var(--track-padding) * 2);
278
+ height: calc(100% - var(--track-padding) * 2);
279
+ left: calc(var(--thumb-left) + var(--track-padding));
280
+ top: calc(0px + var(--track-padding));
281
+ border-radius: var(--radius-s);
282
+ }
283
+
284
+ .track-v {
285
+ position: absolute;
286
+ top: 0px;
287
+ right: 0px;
288
+ height: 100%;
289
+ width: var(--track-thickness);
290
+ }
291
+
292
+ .track-v .thumb {
293
+ height: calc(var(--thumb-height) - var(--track-padding) * 2);
294
+ width: calc(100% - var(--track-padding) * 2);
295
+ top: calc(var(--thumb-top) + 1px);
296
+ left: calc(0px + 1px);
297
+ border-radius: var(--radius-s);
298
+ }
299
+
300
+ @media (max-width:750px) {
301
+ .track-h {
302
+ height: 5px;
303
+ }
304
+ .track-v {
305
+ width: 5px;
306
+ }
307
+ }
308
+ </style>
@@ -0,0 +1,67 @@
1
+ <script>
2
+ import Button from '../Button/Button.svelte';
3
+ import IconButton from '../IconButton/IconButton.svelte';
4
+ import { onMount } from 'svelte';
5
+
6
+ export let goTo = false
7
+ export let goBack = false
8
+
9
+ let isMount = false
10
+
11
+ onMount(()=>{
12
+ isMount = true
13
+ })
14
+ </script>
15
+
16
+ <Button
17
+ {...$$restProps}
18
+ style="width:100%;padding:var(--pad-m);justify-content:space-between;"
19
+ variant="ghost"
20
+ >
21
+ {#if goBack && isMount}
22
+ <IconButton size="24px" variant="secondary">
23
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
24
+ <path d="M14.9993 20.67C14.8093 20.67 14.6193 20.6 14.4693 20.45L7.9493 13.93C6.8893 12.87 6.8893 11.13 7.9493 10.07L14.4693 3.55002C14.7593 3.26002 15.2393 3.26002 15.5293 3.55002C15.8193 3.84002 15.8193 4.32002 15.5293 4.61002L9.0093 11.13C8.5293 11.61 8.5293 12.39 9.0093 12.87L15.5293 19.39C15.8193 19.68 15.8193 20.16 15.5293 20.45C15.3793 20.59 15.1893 20.67 14.9993 20.67Z" fill="#292D32"/>
25
+ </svg>
26
+
27
+ </IconButton>
28
+ {/if}
29
+ <div class="content">
30
+ <slot />
31
+ </div>
32
+ {#if goTo && isMount}
33
+ <IconButton size="24px" variant="secondary">
34
+ <svg
35
+ width="24"
36
+ height="24"
37
+ viewBox="0 0 24 24"
38
+ fill="none"
39
+ xmlns="http://www.w3.org/2000/svg"
40
+ >
41
+ <path
42
+ d="M8.90961 20.67C8.71961 20.67 8.52961 20.6 8.37961 20.45C8.08961 20.16 8.08961 19.68 8.37961 19.39L14.8996 12.87C15.3796 12.39 15.3796 11.61 14.8996 11.13L8.37961 4.61002C8.08961 4.32002 8.08961 3.84002 8.37961 3.55002C8.66961 3.26002 9.14961 3.26002 9.43961 3.55002L15.9596 10.07C16.4696 10.58 16.7596 11.27 16.7596 12C16.7596 12.73 16.4796 13.42 15.9596 13.93L9.43961 20.45C9.28961 20.59 9.09961 20.67 8.90961 20.67Z"
43
+ fill="#292D32"
44
+ />
45
+ </svg>
46
+ </IconButton>
47
+ {/if}
48
+ </Button>
49
+
50
+
51
+ <style>
52
+ .content {
53
+ display: flex;
54
+ align-items: center;
55
+ column-gap: var(--pad-m);
56
+ }
57
+
58
+ .content > :global(svg) {
59
+ width: 24px;
60
+ height: auto;
61
+ }
62
+
63
+ .content > :global(svg path) {
64
+ fill: var(--font-2);
65
+ }
66
+
67
+ </style>
@@ -0,0 +1,23 @@
1
+ <script>
2
+ export let openMenu = 'default'
3
+ export let menu = 'default'
4
+ </script>
5
+
6
+ {#if openMenu === menu}
7
+ <div class="menu">
8
+ <slot/>
9
+ </div>
10
+ {/if}
11
+
12
+ <style>
13
+ .menu {
14
+ max-height: 100%;
15
+ display: flex;
16
+ flex-direction: column;
17
+ flex-shrink: 0;
18
+ flex: 1;
19
+ row-gap: var(--pad-m);
20
+ overflow-y: auto;
21
+ padding: var(--pad-m);
22
+ }
23
+ </style>
@@ -0,0 +1,7 @@
1
+ <script>
2
+ export let openMenu = 'default'
3
+ export const goTo = (val)=>{
4
+ openMenu = val
5
+ }
6
+ </script>
7
+ <slot {openMenu} {goTo}/>
@@ -0,0 +1,197 @@
1
+ <script>
2
+ import IconButton from '../IconButton/IconButton.svelte';
3
+ import { onDestroy, onMount } from 'svelte';
4
+
5
+ export let isOpen = false;
6
+ export let width = 0;
7
+ export let localStorageName = 'hamzus-sidebar-storage';
8
+
9
+ const defaultWidth = 250
10
+ const widthToHide = 249;
11
+ const maxWidth = 350;
12
+ const timeToClose = 500;
13
+
14
+ let startWidth = null;
15
+ let startX = null;
16
+ let endX = null;
17
+
18
+ $: lastWidth = width === 0 ? (lastWidth ?? defaultWidth) : width
19
+ $: console.log(lastWidth);
20
+
21
+ onMount(() => {
22
+ const newWidth = localStorage.getItem(localStorageName)
23
+ if (newWidth) width = parseInt(newWidth)
24
+ else width = defaultWidth
25
+ isOpen = true
26
+ return handleDestroy;
27
+ });
28
+
29
+ function handleMouseDown(event) {
30
+ startWidth = width;
31
+ startX = event.clientX;
32
+
33
+ document.addEventListener('mousemove', handleMouseMove);
34
+ document.addEventListener('mouseup', handleMouseUp);
35
+ }
36
+
37
+ function handleMouseMove(event) {
38
+ endX = event.clientX;
39
+ calcWidth();
40
+ }
41
+ function handleMouseUp(event) {
42
+ endX = event.clientX;
43
+ calcWidth();
44
+
45
+ startWidth = null;
46
+ startX = null;
47
+ endX = null;
48
+
49
+ handleDestroy();
50
+ }
51
+
52
+ function calcWidth() {
53
+ let newWidth = startWidth + endX - startX;
54
+
55
+ if (newWidth > maxWidth) {
56
+ newWidth = maxWidth;
57
+ }
58
+
59
+ if (newWidth < widthToHide) {
60
+ handleClose();
61
+ return;
62
+ }
63
+
64
+
65
+
66
+ localStorage.setItem(localStorageName, width);
67
+
68
+ isOpen = true;
69
+ width = newWidth;
70
+ }
71
+
72
+ function handleClose() {
73
+ isOpen = false;
74
+ width = 0;
75
+ }
76
+
77
+ function handleDestroy() {
78
+ document.removeEventListener('mousemove', handleMouseMove);
79
+ document.removeEventListener('mouseup', handleMouseUp);
80
+ }
81
+
82
+ function handleToggle() {
83
+ isOpen = !isOpen;
84
+ if (!isOpen) {
85
+ width = 0;
86
+ return;
87
+ }
88
+
89
+ width = lastWidth;
90
+ }
91
+ </script>
92
+
93
+ <section
94
+ class="sidebar"
95
+ class:is-changing={startWidth !== null}
96
+ class:open={isOpen}
97
+ style="--sidebar-width:{width}px;--sidebar-min-width:{widthToHide}px;"
98
+ >
99
+ <div class="sidebar-container">
100
+ <slot />
101
+ </div>
102
+ </section>
103
+ <div
104
+ class="trigger"
105
+ class:open={isOpen}
106
+ style="--sidebar-width:{width}px;"
107
+ on:mousedown={handleMouseDown}
108
+ >
109
+ <div class="trigger-btn">
110
+ <IconButton variant="secondary" size="20px" onClick={handleToggle}>
111
+ <svg
112
+ width="24"
113
+ height="24"
114
+ viewBox="0 0 24 24"
115
+ fill="none"
116
+ xmlns="http://www.w3.org/2000/svg"
117
+ >
118
+ <path
119
+ d="M14.9993 20.67C14.8093 20.67 14.6193 20.6 14.4693 20.45L7.9493 13.93C6.8893 12.87 6.8893 11.13 7.9493 10.07L14.4693 3.55002C14.7593 3.26002 15.2393 3.26002 15.5293 3.55002C15.8193 3.84002 15.8193 4.32002 15.5293 4.61002L9.0093 11.13C8.5293 11.61 8.5293 12.39 9.0093 12.87L15.5293 19.39C15.8193 19.68 15.8193 20.16 15.5293 20.45C15.3793 20.59 15.1893 20.67 14.9993 20.67Z"
120
+ fill="#292D32"
121
+ />
122
+ </svg>
123
+ </IconButton>
124
+ </div>
125
+ </div>
126
+
127
+ <style>
128
+ .sidebar {
129
+ position: fixed;
130
+ top: 0px;
131
+ left: 0px;
132
+ width: min(100%, var(--sidebar-width));
133
+ height: 100dvh;
134
+ background-color: var(--bg-1);
135
+ border-right: 1px solid var(--stroke);
136
+ z-index: 10000;
137
+ transition: width 0.1s ease-out;
138
+ overflow: clip;
139
+ }
140
+
141
+ .sidebar:not(.open) {
142
+ width: 0px;
143
+ }
144
+
145
+ :global(:root:has(.sidebar.is-changing)) {
146
+ --default-cursor: col-resize;
147
+ }
148
+
149
+ .sidebar-container {
150
+ height: 100%;
151
+ min-width: var(--sidebar-min-width);
152
+ display: flex;
153
+ flex-direction: column;
154
+ flex-shrink: 0;
155
+ }
156
+
157
+ .trigger {
158
+ cursor: col-resize;
159
+ position: fixed;
160
+ top: 0px;
161
+ left: var(--sidebar-width);
162
+ height: 100dvh;
163
+ z-index: 100000;
164
+ transition: left 0.1s ease-out;
165
+ }
166
+
167
+ .trigger::after {
168
+ content: '';
169
+ position: absolute;
170
+ top: 0px;
171
+ left: 100%;
172
+ width: 25px;
173
+ height: 100%;
174
+ }
175
+
176
+ .trigger-btn {
177
+ position: absolute;
178
+ top: 50%;
179
+ left: 0px;
180
+ z-index: 10;
181
+ transform: translate(-50%, -50%);
182
+ transition: left 0.2s ease-out;
183
+ }
184
+
185
+ .trigger:not(.open) .trigger-btn {
186
+ transform: translate(-50%, -50%) rotate(180deg);
187
+ }
188
+ .trigger:not(.open):hover .trigger-btn {
189
+ left: 15px;
190
+ }
191
+
192
+ @media (max-width: 768px) {
193
+ .trigger {
194
+ display: none;
195
+ }
196
+ }
197
+ </style>
@@ -0,0 +1,4 @@
1
+ export { default as Root } from './Root.svelte';
2
+ export { default as Menu } from './Menu.svelte';
3
+ export { default as MenuCtx } from './MenuCtx.svelte';
4
+ export { default as Button } from './Button.svelte';