@makolabs/ripple 3.0.2 → 3.0.3

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.
@@ -143,38 +143,6 @@
143
143
  return rgbToHex(r * (1 - amount), g * (1 - amount), b * (1 - amount));
144
144
  }
145
145
 
146
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
147
- function barGradient(baseHex: string, stacked: boolean): any {
148
- if (stacked) {
149
- // Horizontal sheen — same light direction for all segments
150
- return {
151
- type: 'linear',
152
- x: 0,
153
- y: 0,
154
- x2: 1,
155
- y2: 0,
156
- colorStops: [
157
- { offset: 0, color: baseHex },
158
- { offset: 0.5, color: lighten(baseHex, 0.1) },
159
- { offset: 1, color: baseHex }
160
- ]
161
- };
162
- }
163
- // Standalone bars — top-to-bottom depth
164
- return {
165
- type: 'linear',
166
- x: 0,
167
- y: 0,
168
- x2: 0,
169
- y2: 1,
170
- colorStops: [
171
- { offset: 0, color: lighten(baseHex, 0.2) },
172
- { offset: 0.6, color: baseHex },
173
- { offset: 1, color: darken(baseHex, 0.15) }
174
- ]
175
- };
176
- }
177
-
178
146
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
179
147
  function lineGradient(baseHex: string): any {
180
148
  return {
@@ -435,9 +403,10 @@
435
403
  color: getColor(seriesConfig.color),
436
404
  emphasis: seriesConfig.emphasis || {
437
405
  focus: 'series',
438
- blurScope: 'global'
406
+ blurScope: 'global',
407
+ itemStyle: { borderRadius: 0 }
439
408
  },
440
- blur: { itemStyle: { opacity: 0.25 }, lineStyle: { opacity: 0.25 } },
409
+ blur: { itemStyle: { opacity: 0.25, borderRadius: 0 }, lineStyle: { opacity: 0.25 } },
441
410
  animation: true,
442
411
  animationDuration: AnimationDuration,
443
412
  ...(axisMarkLine && { markLine: axisMarkLine }),
@@ -475,8 +444,11 @@
475
444
  barGap: '10%',
476
445
  color: getColor(seriesConfig.color),
477
446
  itemStyle: {
478
- color: barGradient(getColor(seriesConfig.color) ?? '#6366f1', !!seriesConfig.stack),
479
- opacity: seriesConfig.opacity ?? 1
447
+ color: getColor(seriesConfig.color) ?? '#6366f1',
448
+ opacity: seriesConfig.opacity ?? 1,
449
+ borderColor: darken(getColor(seriesConfig.color) ?? '#6366f1', 0.15),
450
+ borderWidth: 1,
451
+ borderRadius: [0, 0, 0, 0]
480
452
  }
481
453
  }),
482
454
 
@@ -37,8 +37,7 @@
37
37
  header: headerVClass,
38
38
  body,
39
39
  footer: footerVClass,
40
- title: titleVClass,
41
- closeButton
40
+ title: titleVClass
42
41
  } = $derived(
43
42
  drawer({
44
43
  open,
@@ -54,7 +53,6 @@
54
53
  const headerClasses = $derived(cn(headerVClass(), headerClass));
55
54
  const bodyClasses = $derived(cn(body(), bodyClass));
56
55
  const titleClasses = $derived(cn(titleVClass(), titleClass));
57
- const closeButtonClasses = $derived(cn(closeButton(), ''));
58
56
  const footerClasses = $derived(cn(footerVClass(), 'mt-auto', footerClass));
59
57
 
60
58
  function handleBackdropClick(e: MouseEvent) {
@@ -181,41 +179,36 @@
181
179
  data-testid={buildTestId('drawer', 'dialog', testId)}
182
180
  >
183
181
  <div class={contentClasses}>
182
+ <!-- Always-visible close button -->
183
+ <button
184
+ type="button"
185
+ class="bg-default-100 text-default-600 hover:bg-default-200 hover:text-default-900 absolute top-2 right-2 z-10 flex size-8 cursor-pointer items-center justify-center rounded-full transition-colors"
186
+ onclick={handleCloseClick}
187
+ aria-label="Close drawer"
188
+ data-testid={buildTestId('drawer', 'close', testId)}
189
+ >
190
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 12 12">
191
+ <path
192
+ fill="currentColor"
193
+ d="m1.897 2.054l.073-.084a.75.75 0 0 1 .976-.073l.084.073L6 4.939l2.97-2.97a.75.75 0 1 1 1.06 1.061L7.061 6l2.97 2.97a.75.75 0 0 1 .072.976l-.073.084a.75.75 0 0 1-.976.073l-.084-.073L6 7.061l-2.97 2.97A.75.75 0 1 1 1.97 8.97L4.939 6l-2.97-2.97a.75.75 0 0 1-.072-.976l.073-.084z"
194
+ />
195
+ </svg>
196
+ </button>
197
+
184
198
  <!-- Header -->
185
- {#if header || title}
199
+ {#if title}
200
+ <div class={headerClasses}>
201
+ <h3
202
+ id="drawer-title"
203
+ class={titleClasses}
204
+ data-testid={buildTestId('drawer', 'title', testId)}
205
+ >
206
+ {title}
207
+ </h3>
208
+ </div>
209
+ {:else if header}
186
210
  <div class={headerClasses}>
187
- {#if header}
188
- {@render header()}
189
- {:else}
190
- {#if title}
191
- <h3
192
- id="drawer-title"
193
- class={titleClasses}
194
- data-testid={buildTestId('drawer', 'title', testId)}
195
- >
196
- {title}
197
- </h3>
198
- {/if}
199
- <button
200
- type="button"
201
- class={closeButtonClasses}
202
- onclick={handleCloseClick}
203
- aria-label="Close drawer"
204
- data-testid={buildTestId('drawer', 'close', testId)}
205
- >
206
- <svg
207
- xmlns="http://www.w3.org/2000/svg"
208
- width="12"
209
- height="12"
210
- viewBox="0 0 12 12"
211
- >
212
- <path
213
- fill="currentColor"
214
- d="m1.897 2.054l.073-.084a.75.75 0 0 1 .976-.073l.084.073L6 4.939l2.97-2.97a.75.75 0 1 1 1.06 1.061L7.061 6l2.97 2.97a.75.75 0 0 1 .072.976l-.073.084a.75.75 0 0 1-.976.073l-.084-.073L6 7.061l-2.97 2.97A.75.75 0 1 1 1.97 8.97L4.939 6l-2.97-2.97a.75.75 0 0 1-.072-.976l.073-.084z"
215
- />
216
- </svg>
217
- </button>
218
- {/if}
211
+ {@render header()}
219
212
  </div>
220
213
  {/if}
221
214
 
@@ -4,8 +4,8 @@ export const drawer = tv({
4
4
  slots: {
5
5
  base: 'fixed inset-0 z-50 flex overflow-hidden',
6
6
  backdrop: 'fixed inset-0 transition-opacity bg-black/50',
7
- contentWrapper: 'absolute flex flex-col transform transition-transform',
8
- content: 'flex flex-col h-full w-full overflow-y-auto bg-white',
7
+ contentWrapper: 'absolute flex flex-col transform transition-transform max-w-[100vw]',
8
+ content: 'relative flex flex-col h-full w-full overflow-y-auto bg-white',
9
9
  header: 'flex items-center justify-between px-4 py-3 border-b border-default-200',
10
10
  body: 'flex-1 overflow-y-auto p-4',
11
11
  footer: 'flex justify-end border-t border-default-200 p-4',
@@ -97,12 +97,15 @@
97
97
  const v = (e.currentTarget as HTMLInputElement).value;
98
98
  query = v;
99
99
  open = true;
100
+ // Fire onsearch immediately (documented "every keystroke" contract).
101
+ // Only debounce local filtering so rapid typing doesn't churn the
102
+ // DOM with intermediate filter results.
103
+ onsearch?.(v);
100
104
  clearTimeout(debounceTimer);
101
105
  debounceTimer = setTimeout(() => {
102
106
  debouncedQuery = v;
103
107
  highlightedIndex = 0;
104
- onsearch?.(v);
105
- }, 1000);
108
+ }, 300);
106
109
  }
107
110
 
108
111
  $effect(() => {
@@ -206,10 +209,7 @@
206
209
  else openMenu();
207
210
  }
208
211
  }}
209
- class={cn(
210
- 'text-default-400 hover:text-default-700 flex cursor-pointer items-center justify-center rounded',
211
- !disabled && 'hover:text-default-600'
212
- )}
212
+ class="text-default-400 hover:text-default-600 flex cursor-pointer items-center justify-center rounded"
213
213
  aria-label={open ? 'Close suggestions' : 'Open suggestions'}
214
214
  {disabled}
215
215
  >
@@ -216,19 +216,17 @@
216
216
  }
217
217
 
218
218
  function handleClickOutside(event: MouseEvent) {
219
- // Check if the click is inside the portal content
219
+ if (!open) return;
220
+ // On mobile the sheet handles its own close via backdrop.
221
+ if (isMobile) return;
220
222
  const portalContent = document.querySelector('.ripple-portal .portal-content');
221
-
222
- // If the click is inside either the label (trigger) or the portal content, don't close
223
+ const target = event.target as Node;
223
224
  if (
224
- (labelRef && labelRef.contains(event.target as Node)) ||
225
- (portalContent && portalContent.contains(event.target as Node)) ||
226
- !open
225
+ (labelRef && labelRef.contains(target)) ||
226
+ (portalContent && portalContent.contains(target))
227
227
  ) {
228
228
  return;
229
229
  }
230
-
231
- // Otherwise close the dropdown
232
230
  open = false;
233
231
  onclose();
234
232
  }
@@ -236,7 +236,7 @@
236
236
  type="button"
237
237
  class="fixed inset-0 z-[9998] bg-black/40 backdrop-blur-sm"
238
238
  aria-label="Close"
239
- onclick={close}
239
+ onclick={closeOnOutsideClick ? close : undefined}
240
240
  ></button>
241
241
  <div
242
242
  role="dialog"
@@ -333,24 +333,26 @@
333
333
  {/if}
334
334
 
335
335
  {#if isOpen && isMobile}
336
- <button
337
- type="button"
338
- class="fixed inset-0 z-[9998] bg-black/40 backdrop-blur-sm"
339
- aria-label="Close"
340
- onclick={() => (isOpen = false)}
341
- ></button>
342
- <div
343
- class="fixed inset-x-0 bottom-0 z-[9999] flex max-h-[85vh] min-h-48 flex-col overflow-hidden rounded-t-2xl bg-white shadow-2xl"
344
- transition:fly={{ y: 300, duration: 200, easing: quintOut }}
345
- bind:this={calendarRef}
346
- >
347
- <div class="flex justify-center py-2">
348
- <div class="bg-default-300 h-1 w-8 rounded-full"></div>
349
- </div>
350
- <div class="flex-1 cursor-pointer overflow-y-auto p-4">
351
- {@render calendarContent()}
336
+ <Portal target={datePickerRef}>
337
+ <button
338
+ type="button"
339
+ class="fixed inset-0 z-[9998] bg-black/40 backdrop-blur-sm"
340
+ aria-label="Close"
341
+ onclick={() => (isOpen = false)}
342
+ ></button>
343
+ <div
344
+ class="fixed inset-x-0 bottom-0 z-[9999] flex max-h-[85vh] min-h-48 flex-col overflow-hidden rounded-t-2xl bg-white shadow-2xl"
345
+ transition:fly={{ y: 300, duration: 200, easing: quintOut }}
346
+ bind:this={calendarRef}
347
+ >
348
+ <div class="flex justify-center py-2">
349
+ <div class="bg-default-300 h-1 w-8 rounded-full"></div>
350
+ </div>
351
+ <div class="flex-1 cursor-pointer overflow-y-auto p-4">
352
+ {@render calendarContent()}
353
+ </div>
352
354
  </div>
353
- </div>
355
+ </Portal>
354
356
  {/if}
355
357
  </div>
356
358
 
@@ -230,7 +230,6 @@
230
230
  class={cn(
231
231
  'inline-block bg-white select-none',
232
232
  'border-default-200 rounded-lg border shadow-xs',
233
- 'max-sm:w-full max-sm:rounded-none max-sm:border-0 max-sm:shadow-none',
234
233
  density.panel,
235
234
  density.padding,
236
235
  className
@@ -127,7 +127,14 @@
127
127
  </button>
128
128
 
129
129
  {#snippet content()}
130
- <Calendar {value} {minDate} {maxDate} {size} onselect={(d) => handleSelect(d as Date)} />
130
+ <Calendar
131
+ {value}
132
+ {minDate}
133
+ {maxDate}
134
+ {size}
135
+ class="max-sm:w-full max-sm:rounded-none max-sm:border-0 max-sm:shadow-none"
136
+ onselect={(d) => handleSelect(d as Date)}
137
+ />
131
138
  {/snippet}
132
139
  </Popover>
133
140
 
@@ -118,7 +118,10 @@
118
118
 
119
119
  function setThisMonth() {
120
120
  const now = new Date();
121
- value = new Date(now.getFullYear(), now.getMonth(), 1);
121
+ const d = new Date(now.getFullYear(), now.getMonth(), 1);
122
+ if (minDate && d < new Date(minDate.getFullYear(), minDate.getMonth(), 1)) return;
123
+ if (maxDate && d > new Date(maxDate.getFullYear(), maxDate.getMonth(), 1)) return;
124
+ value = d;
122
125
  onselect?.(value);
123
126
  open = false;
124
127
  }
@@ -142,7 +145,11 @@
142
145
  </label>
143
146
  {/if}
144
147
 
145
- <input type="hidden" {name} value={value?.toISOString() ?? ''} />
148
+ <input
149
+ type="hidden"
150
+ {name}
151
+ value={value ? `${value.getFullYear()}-${String(value.getMonth() + 1).padStart(2, '0')}` : ''}
152
+ />
146
153
 
147
154
  <Popover trigger="manual" bind:open placement="bottom" {disabled}>
148
155
  <button
@@ -2,11 +2,11 @@ import { tv } from 'tailwind-variants';
2
2
  import { Size } from '../index.js';
3
3
  export const modal = tv({
4
4
  slots: {
5
- base: 'fixed inset-0 z-50 flex items-center justify-center p-4',
5
+ base: 'fixed inset-0 z-50 flex items-center justify-center p-2 sm:p-4',
6
6
  backdrop: 'fixed inset-0 bg-black/50 backdrop-blur-sm',
7
- container: 'relative w-full flex flex-col bg-white rounded-xl shadow-2xl',
8
- header: 'px-6 py-4 border-b border-default-200 flex items-center justify-between shrink-0',
9
- body: 'flex-1 px-6 py-4 overflow-y-auto',
7
+ container: 'relative w-full flex flex-col bg-white rounded-xl shadow-2xl max-sm:max-w-none max-sm:max-h-[95vh]',
8
+ header: 'px-4 py-3 sm:px-6 sm:py-4 border-b border-default-200 flex items-center justify-between shrink-0',
9
+ body: 'flex-1 px-4 py-3 sm:px-6 sm:py-4 overflow-y-auto',
10
10
  footer: 'bg-default-50/80 rounded-b-xl shrink-0',
11
11
  title: 'text-lg font-semibold text-default-900',
12
12
  description: 'text-sm text-default-600 mt-1',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@makolabs/ripple",
3
- "version": "3.0.2",
3
+ "version": "3.0.3",
4
4
  "description": "Simple Svelte 5 powered component library ✨",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "repository": {