@makolabs/ripple 0.0.1-dev.8 → 0.0.1-dev.81
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/README.md +1 -1
- package/dist/adapters/storage/BaseAdapter.d.ts +20 -0
- package/dist/adapters/storage/BaseAdapter.js +171 -0
- package/dist/adapters/storage/S3Adapter.d.ts +21 -0
- package/dist/adapters/storage/S3Adapter.js +194 -0
- package/dist/adapters/storage/index.d.ts +3 -0
- package/dist/adapters/storage/index.js +3 -0
- package/dist/adapters/storage/types.d.ts +102 -0
- package/dist/adapters/storage/types.js +4 -0
- package/dist/charts/Chart.svelte +59 -47
- package/dist/charts/Chart.svelte.d.ts +1 -1
- package/dist/drawer/drawer.js +3 -3
- package/dist/elements/accordion/Accordion.svelte +98 -0
- package/dist/elements/accordion/Accordion.svelte.d.ts +4 -0
- package/dist/elements/accordion/accordion.d.ts +227 -0
- package/dist/elements/accordion/accordion.js +138 -0
- package/dist/elements/alert/Alert.svelte +7 -3
- package/dist/elements/dropdown/Dropdown.svelte +74 -107
- package/dist/elements/dropdown/Select.svelte +81 -62
- package/dist/elements/dropdown/dropdown.js +1 -1
- package/dist/elements/dropdown/select.js +8 -8
- package/dist/elements/file-upload/FileUpload.svelte +17 -95
- package/dist/elements/file-upload/FilesPreview.svelte +93 -0
- package/dist/elements/file-upload/FilesPreview.svelte.d.ts +4 -0
- package/dist/elements/progress/Progress.svelte +83 -25
- package/dist/file-browser/FileBrowser.svelte +837 -0
- package/dist/file-browser/FileBrowser.svelte.d.ts +14 -0
- package/dist/file-browser/index.d.ts +1 -0
- package/dist/file-browser/index.js +1 -0
- package/dist/filters/CompactFilters.svelte +147 -0
- package/dist/filters/CompactFilters.svelte.d.ts +4 -0
- package/dist/filters/index.d.ts +1 -0
- package/dist/filters/index.js +1 -0
- package/dist/forms/Checkbox.svelte +2 -2
- package/dist/forms/DateRange.svelte +21 -21
- package/dist/forms/Input.svelte +3 -3
- package/dist/forms/NumberInput.svelte +1 -1
- package/dist/forms/RadioInputs.svelte +3 -3
- package/dist/forms/Tags.svelte +5 -5
- package/dist/forms/Toggle.svelte +3 -3
- package/dist/forms/slider.js +4 -4
- package/dist/header/PageHeader.svelte +49 -11
- package/dist/index.d.ts +256 -143
- package/dist/index.js +19 -2
- package/dist/layout/card/MetricCard.svelte +64 -0
- package/dist/layout/card/MetricCard.svelte.d.ts +4 -0
- package/dist/layout/card/StatsCard.svelte +4 -3
- package/dist/layout/card/StatsCard.svelte.d.ts +1 -1
- package/dist/layout/card/metric-card.d.ts +49 -0
- package/dist/layout/card/metric-card.js +10 -0
- package/dist/layout/card/stats-card.d.ts +0 -15
- package/dist/layout/card/stats-card.js +1 -1
- package/dist/layout/sidebar/NavGroup.svelte +8 -9
- package/dist/layout/sidebar/NavItem.svelte +2 -2
- package/dist/layout/sidebar/Sidebar.svelte +102 -49
- package/dist/layout/table/Table.svelte +464 -87
- package/dist/layout/table/Table.svelte.d.ts +1 -1
- package/dist/layout/table/table.d.ts +0 -47
- package/dist/layout/table/table.js +0 -8
- package/dist/layout/tabs/Tab.svelte +9 -6
- package/dist/layout/tabs/Tab.svelte.d.ts +1 -1
- package/dist/layout/tabs/TabContent.svelte +1 -2
- package/dist/layout/tabs/TabContent.svelte.d.ts +1 -1
- package/dist/layout/tabs/TabGroup.svelte +10 -5
- package/dist/layout/tabs/TabGroup.svelte.d.ts +2 -2
- package/dist/layout/tabs/tabs.d.ts +61 -76
- package/dist/layout/tabs/tabs.js +170 -28
- package/dist/modal/Modal.svelte +3 -3
- package/dist/modal/modal.js +3 -3
- package/dist/utils/Portal.svelte +108 -0
- package/dist/utils/Portal.svelte.d.ts +8 -0
- package/dist/utils/dateUtils.d.ts +7 -0
- package/dist/utils/dateUtils.js +26 -0
- package/dist/variants.d.ts +11 -1
- package/dist/variants.js +17 -0
- package/package.json +2 -2
- package/dist/header/pageheaders.d.ts +0 -10
- package/dist/header/pageheaders.js +0 -1
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { dropdownMenu } from '../../index.js';
|
|
6
6
|
import { onMount, onDestroy } from 'svelte';
|
|
7
7
|
import { Size } from '../../variants.js';
|
|
8
|
+
import Portal from '../../utils/Portal.svelte';
|
|
8
9
|
|
|
9
10
|
let {
|
|
10
11
|
sections = [],
|
|
@@ -26,7 +27,6 @@
|
|
|
26
27
|
|
|
27
28
|
let dropdownRef = $state<HTMLDivElement | undefined>();
|
|
28
29
|
let triggerRef = $state<HTMLDivElement | undefined>();
|
|
29
|
-
let portalEl = $state<HTMLDivElement | undefined>();
|
|
30
30
|
let triggerRect = $state<DOMRect | null>(null);
|
|
31
31
|
|
|
32
32
|
const {
|
|
@@ -90,10 +90,10 @@
|
|
|
90
90
|
posStyles += `left: ${right - triggerWidth}px;`;
|
|
91
91
|
}
|
|
92
92
|
} else {
|
|
93
|
-
const centeredLeft = left +
|
|
94
|
-
if (centeredLeft +
|
|
93
|
+
const centeredLeft = left + triggerWidth / 2;
|
|
94
|
+
if (centeredLeft + dropdownWidthPx / 2 > viewportWidth - 20) {
|
|
95
95
|
posStyles += `right: 20px; left: auto;`;
|
|
96
|
-
} else if (centeredLeft -
|
|
96
|
+
} else if (centeredLeft - dropdownWidthPx / 2 < 20) {
|
|
97
97
|
posStyles += `left: 20px; right: auto;`;
|
|
98
98
|
} else {
|
|
99
99
|
posStyles += `left: ${centeredLeft}px; transform: translateX(-50%);`;
|
|
@@ -111,18 +111,16 @@
|
|
|
111
111
|
function handleToggle() {
|
|
112
112
|
if (disabled) return;
|
|
113
113
|
isOpen = !isOpen;
|
|
114
|
-
|
|
115
|
-
if (isOpen) {
|
|
116
|
-
setTimeout(updatePosition, 0); // Use setTimeout to ensure DOM is updated
|
|
117
|
-
}
|
|
118
114
|
}
|
|
119
115
|
|
|
120
116
|
function handleClickOutside(event: MouseEvent) {
|
|
121
|
-
if (
|
|
117
|
+
if (
|
|
118
|
+
isOpen &&
|
|
122
119
|
dropdownRef &&
|
|
123
120
|
!dropdownRef.contains(event.target as Node) &&
|
|
124
121
|
triggerRef &&
|
|
125
|
-
!triggerRef.contains(event.target as Node)
|
|
122
|
+
!triggerRef.contains(event.target as Node)
|
|
123
|
+
) {
|
|
126
124
|
isOpen = false;
|
|
127
125
|
}
|
|
128
126
|
}
|
|
@@ -131,49 +129,6 @@
|
|
|
131
129
|
if (item.onclick) item.onclick();
|
|
132
130
|
isOpen = false;
|
|
133
131
|
}
|
|
134
|
-
|
|
135
|
-
function updatePosition() {
|
|
136
|
-
if (triggerRef) {
|
|
137
|
-
triggerRect = triggerRef.getBoundingClientRect();
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function renderPortalDropdown() {
|
|
142
|
-
if (!portalEl || !document.body.contains(portalEl)) {
|
|
143
|
-
portalEl = document.createElement('div');
|
|
144
|
-
portalEl.id = 'dropdown-portal';
|
|
145
|
-
portalEl.style.position = 'fixed';
|
|
146
|
-
portalEl.style.top = '0';
|
|
147
|
-
portalEl.style.left = '0';
|
|
148
|
-
portalEl.style.width = '100%';
|
|
149
|
-
portalEl.style.height = '100%';
|
|
150
|
-
portalEl.style.pointerEvents = 'none';
|
|
151
|
-
portalEl.style.zIndex = '9999';
|
|
152
|
-
document.body.appendChild(portalEl);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
onMount(() => {
|
|
157
|
-
renderPortalDropdown();
|
|
158
|
-
window.addEventListener('scroll', updatePosition, true);
|
|
159
|
-
window.addEventListener('resize', updatePosition);
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
onDestroy(() => {
|
|
163
|
-
if (!triggerRef) return;
|
|
164
|
-
window.removeEventListener('scroll', updatePosition, true);
|
|
165
|
-
window.removeEventListener('resize', updatePosition);
|
|
166
|
-
if (portalEl && document.body.contains(portalEl)) {
|
|
167
|
-
document.body.removeChild(portalEl);
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
$effect(() => {
|
|
172
|
-
if (isOpen) {
|
|
173
|
-
renderPortalDropdown();
|
|
174
|
-
updatePosition();
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
132
|
</script>
|
|
178
133
|
|
|
179
134
|
<svelte:window onclick={handleClickOutside} />
|
|
@@ -194,68 +149,80 @@
|
|
|
194
149
|
{/if}
|
|
195
150
|
|
|
196
151
|
{#if Icon}
|
|
197
|
-
<Icon class="
|
|
152
|
+
<Icon class="text-default-400 size-5" />
|
|
198
153
|
{:else if label}
|
|
199
|
-
<svg
|
|
200
|
-
|
|
201
|
-
|
|
154
|
+
<svg
|
|
155
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
156
|
+
width="28"
|
|
157
|
+
height="28"
|
|
158
|
+
viewBox="0 0 28 28"
|
|
159
|
+
class="size-5"
|
|
160
|
+
>
|
|
161
|
+
<path
|
|
162
|
+
fill="currentColor"
|
|
163
|
+
d="M4.22 9.47a.75.75 0 0 1 1.06 0L14 18.19l8.72-8.72a.75.75 0 1 1 1.06 1.06l-9.25 9.25a.75.75 0 0 1-1.06 0l-9.25-9.25a.75.75 0 0 1 0-1.06"
|
|
164
|
+
/>
|
|
202
165
|
</svg>
|
|
203
166
|
{/if}
|
|
204
167
|
</button>
|
|
205
168
|
</div>
|
|
206
169
|
</div>
|
|
207
170
|
|
|
208
|
-
{#if isOpen
|
|
209
|
-
<
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
{
|
|
221
|
-
{
|
|
222
|
-
|
|
223
|
-
{#if header.title}
|
|
224
|
-
<span class={headerTitleClass}>{header.title}</span>
|
|
225
|
-
{/if}
|
|
226
|
-
{#if header.subtitle}
|
|
227
|
-
<span class={headerSubtitleClass}>{header.subtitle}</span>
|
|
228
|
-
{/if}
|
|
229
|
-
{/if}
|
|
230
|
-
</button>
|
|
231
|
-
{/if}
|
|
232
|
-
|
|
233
|
-
{#each sections as section_, sectionIndex (sectionIndex)}
|
|
234
|
-
<div class={sectionClass}>
|
|
235
|
-
{#each section_.items as menuItem, itemIndex (itemIndex)}
|
|
236
|
-
{@const itemProps = {
|
|
237
|
-
class: itemClass_,
|
|
238
|
-
role: 'menuitem',
|
|
239
|
-
tabindex: -1,
|
|
240
|
-
id: `menu-item-${sectionIndex}-${itemIndex}`,
|
|
241
|
-
'data-active': menuItem.active
|
|
242
|
-
}}
|
|
243
|
-
{#if menuItem.href}
|
|
244
|
-
<a href={menuItem.href} {...itemProps}>
|
|
245
|
-
{@render DropItemContent(menuItem)}
|
|
246
|
-
</a>
|
|
171
|
+
{#if isOpen}
|
|
172
|
+
<Portal target={triggerRef}>
|
|
173
|
+
<div
|
|
174
|
+
bind:this={dropdownRef}
|
|
175
|
+
class={containerClass_}
|
|
176
|
+
role="menu"
|
|
177
|
+
aria-orientation="vertical"
|
|
178
|
+
aria-labelledby="menu-button"
|
|
179
|
+
style={dropdownStyles}
|
|
180
|
+
transition:fly={{ duration: 150, y: 5, opacity: 0 }}
|
|
181
|
+
>
|
|
182
|
+
{#if header}
|
|
183
|
+
<button class={headerClass_} onclick={header.onclick} aria-label="Header Actions">
|
|
184
|
+
{#if header.content}
|
|
185
|
+
{@render header.content()}
|
|
247
186
|
{:else}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
{
|
|
253
|
-
|
|
187
|
+
{#if header.title}
|
|
188
|
+
<span class={headerTitleClass}>{header.title}</span>
|
|
189
|
+
{/if}
|
|
190
|
+
{#if header.subtitle}
|
|
191
|
+
<span class={headerSubtitleClass}>{header.subtitle}</span>
|
|
192
|
+
{/if}
|
|
254
193
|
{/if}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
194
|
+
</button>
|
|
195
|
+
{/if}
|
|
196
|
+
|
|
197
|
+
{#each sections as section_, sectionIndex (sectionIndex)}
|
|
198
|
+
<div class={sectionClass}>
|
|
199
|
+
{#each section_.items as menuItem, itemIndex (itemIndex)}
|
|
200
|
+
{@const itemProps = {
|
|
201
|
+
class: itemClass_,
|
|
202
|
+
role: 'menuitem',
|
|
203
|
+
tabindex: -1,
|
|
204
|
+
id: `menu-item-${sectionIndex}-${itemIndex}`,
|
|
205
|
+
'data-active': menuItem.active
|
|
206
|
+
}}
|
|
207
|
+
{#if menuItem.href}
|
|
208
|
+
<a href={menuItem.href} {...itemProps}>
|
|
209
|
+
{@render DropItemContent(menuItem)}
|
|
210
|
+
</a>
|
|
211
|
+
{:else}
|
|
212
|
+
<button
|
|
213
|
+
type="button"
|
|
214
|
+
onclick={() => handleItemClick(menuItem)}
|
|
215
|
+
disabled={!menuItem.onclick}
|
|
216
|
+
{...itemProps}
|
|
217
|
+
>
|
|
218
|
+
{@render DropItemContent(menuItem)}
|
|
219
|
+
</button>
|
|
220
|
+
{/if}
|
|
221
|
+
{/each}
|
|
222
|
+
</div>
|
|
223
|
+
{/each}
|
|
224
|
+
</div>
|
|
225
|
+
</Portal>
|
|
259
226
|
{/if}
|
|
260
227
|
|
|
261
228
|
{#snippet DropItemContent(menuItem: DropdownItem)}
|
|
@@ -264,4 +231,4 @@
|
|
|
264
231
|
<ItemIcon class={iconClass} />
|
|
265
232
|
{/if}
|
|
266
233
|
<span class="truncate">{menuItem.label}</span>
|
|
267
|
-
{/snippet}
|
|
234
|
+
{/snippet}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import type { SelectItem, SelectProps } from '../../index.js';
|
|
6
6
|
import Badge from '../badge/Badge.svelte';
|
|
7
7
|
import { Size } from '../../variants.js';
|
|
8
|
+
import Portal from '../../utils/Portal.svelte';
|
|
8
9
|
|
|
9
10
|
let {
|
|
10
11
|
items = [],
|
|
@@ -29,7 +30,7 @@
|
|
|
29
30
|
|
|
30
31
|
let open = $state(false);
|
|
31
32
|
let searchQuery = $state('');
|
|
32
|
-
let
|
|
33
|
+
let labelRef = $state<HTMLLabelElement | null>(null);
|
|
33
34
|
let searchInputRef = $state<HTMLInputElement | null>(null);
|
|
34
35
|
let highlightedIndex = $state(-1);
|
|
35
36
|
|
|
@@ -50,7 +51,7 @@
|
|
|
50
51
|
);
|
|
51
52
|
|
|
52
53
|
const baseClass = $derived(cn(base(), className));
|
|
53
|
-
const triggerClass_ = $derived(cn(trigger(), triggerClass));
|
|
54
|
+
const triggerClass_ = $derived(cn(trigger(), triggerClass, baseClass));
|
|
54
55
|
const triggerIconClass = $derived(cn(triggerIcon(), iconClass));
|
|
55
56
|
const containerClass_ = $derived(cn(container(), containerClass));
|
|
56
57
|
const searchInputClass_ = $derived(cn(searchInput(), searchInputClass));
|
|
@@ -122,15 +123,26 @@
|
|
|
122
123
|
}
|
|
123
124
|
|
|
124
125
|
function handleClickOutside(event: MouseEvent) {
|
|
125
|
-
if
|
|
126
|
-
|
|
127
|
-
|
|
126
|
+
// Check if the click is inside the portal content
|
|
127
|
+
const portalContent = document.querySelector('.ripple-portal .portal-content');
|
|
128
|
+
|
|
129
|
+
// If the click is inside either the label (trigger) or the portal content, don't close
|
|
130
|
+
if (
|
|
131
|
+
(labelRef && labelRef.contains(event.target as Node)) ||
|
|
132
|
+
(portalContent && portalContent.contains(event.target as Node)) ||
|
|
133
|
+
!open
|
|
134
|
+
) {
|
|
135
|
+
return;
|
|
128
136
|
}
|
|
137
|
+
|
|
138
|
+
// Otherwise close the dropdown
|
|
139
|
+
open = false;
|
|
140
|
+
onclose();
|
|
129
141
|
}
|
|
130
142
|
|
|
131
143
|
function handleKeydown(event: KeyboardEvent) {
|
|
132
144
|
// check if the event is fired from the select
|
|
133
|
-
if (!
|
|
145
|
+
if (!labelRef || !labelRef.contains(event.target as Node)) return;
|
|
134
146
|
|
|
135
147
|
if (!open) {
|
|
136
148
|
if (event.key === 'Enter' || event.key === ' ' || event.key === 'ArrowDown') {
|
|
@@ -182,59 +194,63 @@
|
|
|
182
194
|
|
|
183
195
|
<svelte:window onclick={handleClickOutside} onkeydown={handleKeydown} />
|
|
184
196
|
|
|
185
|
-
<
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
{
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
197
|
+
<label
|
|
198
|
+
bind:this={labelRef}
|
|
199
|
+
class={triggerClass_}
|
|
200
|
+
aria-disabled={disabled}
|
|
201
|
+
aria-haspopup="listbox"
|
|
202
|
+
aria-labelledby="select-label"
|
|
203
|
+
data-state={open ? 'open' : 'closed'}
|
|
204
|
+
>
|
|
205
|
+
<button
|
|
206
|
+
type="button"
|
|
207
|
+
aria-label="Toggle dropdown"
|
|
208
|
+
{disabled}
|
|
209
|
+
aria-expanded={open}
|
|
210
|
+
onclick={handleToggle}
|
|
211
|
+
class="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
|
|
212
|
+
tabindex={disabled ? -1 : 0}
|
|
213
|
+
></button>
|
|
214
|
+
<span class="flex min-h-[1.5rem] flex-1 flex-wrap items-center gap-1 overflow-hidden">
|
|
215
|
+
{#if multiple && selectedItems.length > 0}
|
|
216
|
+
{#each selectedItems as item (item.value)}
|
|
217
|
+
<Badge {size} color="info" onclose={() => removeItem(item.value)}>
|
|
218
|
+
{item.value}
|
|
219
|
+
</Badge>
|
|
220
|
+
{/each}
|
|
221
|
+
{:else if !multiple && selectedItem}
|
|
222
|
+
<span id="select-label" class="flex-1 truncate text-left">
|
|
223
|
+
{selectedItem.label}
|
|
224
|
+
</span>
|
|
225
|
+
{:else}
|
|
226
|
+
<span id="select-label" class="text-default-500 px-1">
|
|
227
|
+
{placeholder}
|
|
228
|
+
</span>
|
|
229
|
+
{/if}
|
|
230
|
+
</span>
|
|
231
|
+
|
|
232
|
+
<span class="ml-auto flex flex-shrink-0 items-center pl-2">
|
|
233
|
+
{#if Icon}
|
|
234
|
+
<Icon class={triggerIconClass} />
|
|
235
|
+
{:else}
|
|
236
|
+
<svg
|
|
237
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
238
|
+
viewBox="0 0 20 20"
|
|
239
|
+
fill="currentColor"
|
|
240
|
+
class={cn(triggerIconClass, open && 'rotate-180 transform')}
|
|
241
|
+
>
|
|
242
|
+
<path
|
|
243
|
+
fill-rule="evenodd"
|
|
244
|
+
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
|
|
245
|
+
clip-rule="evenodd"
|
|
246
|
+
/>
|
|
247
|
+
</svg>
|
|
248
|
+
{/if}
|
|
249
|
+
</span>
|
|
250
|
+
</label>
|
|
251
|
+
|
|
252
|
+
{#if open}
|
|
253
|
+
<Portal target={labelRef}>
|
|
238
254
|
<div class={containerClass_} role="listbox" aria-labelledby="select-label">
|
|
239
255
|
{#if searchable}
|
|
240
256
|
<div class={searchInputClass_}>
|
|
@@ -269,7 +285,10 @@
|
|
|
269
285
|
<li>
|
|
270
286
|
<button
|
|
271
287
|
type="button"
|
|
272
|
-
onclick={() =>
|
|
288
|
+
onclick={(event) => {
|
|
289
|
+
handleSelect(item);
|
|
290
|
+
event.preventDefault();
|
|
291
|
+
}}
|
|
273
292
|
disabled={item.disabled}
|
|
274
293
|
class={itemClass_}
|
|
275
294
|
role="option"
|
|
@@ -310,5 +329,5 @@
|
|
|
310
329
|
</ul>
|
|
311
330
|
{/if}
|
|
312
331
|
</div>
|
|
313
|
-
|
|
314
|
-
|
|
332
|
+
</Portal>
|
|
333
|
+
{/if}
|
|
@@ -2,7 +2,7 @@ import { tv } from '../../helper/cls.js';
|
|
|
2
2
|
import { Size } from '../../variants.js';
|
|
3
3
|
export const dropdownMenu = tv({
|
|
4
4
|
slots: {
|
|
5
|
-
base: '
|
|
5
|
+
base: 'inline-block text-left',
|
|
6
6
|
trigger: 'inline-flex w-full justify-center items-center gap-x-1.5 rounded-md bg-white px-3 py-2 text-sm font-semibold text-default-900 shadow-xs ring-1 ring-inset ring-default-300 hover:bg-default-50 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed',
|
|
7
7
|
container: 'absolute z-50 mt-2 origin-top-right divide-y divide-default-100 rounded-md bg-white ring-1 ring-black/5 shadow-lg focus:outline-none',
|
|
8
8
|
section: 'py-1',
|
|
@@ -2,11 +2,11 @@ import { tv } from 'tailwind-variants';
|
|
|
2
2
|
import { Size } from '../../variants.js';
|
|
3
3
|
export const selectTV = tv({
|
|
4
4
|
slots: {
|
|
5
|
-
base: '
|
|
6
|
-
trigger: `flex items-center justify-between w-full text-left bg-white border
|
|
7
|
-
border-default-
|
|
5
|
+
base: '',
|
|
6
|
+
trigger: `relative flex items-center justify-between w-full text-left bg-white border
|
|
7
|
+
border-default-300 text-sm focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2 focus-within:border-primary-500 focus-within:ring-primary-500 rounded-lg shadow-xs cursor-pointer transition-colors hover:border-default-400`,
|
|
8
8
|
triggerIcon: 'transition-transform duration-200 text-default-500',
|
|
9
|
-
container: 'absolute z-50 w-full mt-1 bg-white overflow-clip border border-default-200 rounded-md shadow-
|
|
9
|
+
container: 'absolute z-50 w-full mt-1 bg-white overflow-clip border border-default-200 rounded-md shadow-sm origin-top-left top-full left-0 mt-2',
|
|
10
10
|
searchInput: 'flex items-center gap-x-3 w-full outline-none px-2 h-10 border-b border-b-default-200',
|
|
11
11
|
list: 'py-1 max-h-60 overflow-x-clip overflow-y-auto h-full',
|
|
12
12
|
item: `w-full px-3 py-2 text-sm text-left
|
|
@@ -26,21 +26,21 @@ export const selectTV = tv({
|
|
|
26
26
|
base: 'w-24'
|
|
27
27
|
},
|
|
28
28
|
[Size.SM]: {
|
|
29
|
-
trigger: 'h-8 px-
|
|
29
|
+
trigger: 'h-8 px-3 py-2 text-sm gap-1.5',
|
|
30
30
|
triggerIcon: 'h-3.5 w-3.5',
|
|
31
31
|
container: 'max-h-48',
|
|
32
32
|
item: 'px-2.5 py-1.5 text-xs',
|
|
33
33
|
base: 'w-32'
|
|
34
34
|
},
|
|
35
35
|
[Size.BASE]: {
|
|
36
|
-
trigger: 'h-10 px-3 py-2 text-
|
|
36
|
+
trigger: 'h-10 px-3 py-2 text-base gap-2',
|
|
37
37
|
triggerIcon: 'h-4 w-4',
|
|
38
38
|
container: 'max-h-60',
|
|
39
39
|
item: 'px-3 py-2 text-sm',
|
|
40
40
|
base: 'w-40'
|
|
41
41
|
},
|
|
42
42
|
[Size.LG]: {
|
|
43
|
-
trigger: 'h-
|
|
43
|
+
trigger: 'h-12 px-3 py-2 text-lg gap-2.5',
|
|
44
44
|
triggerIcon: 'h-5 w-5',
|
|
45
45
|
container: 'max-h-72',
|
|
46
46
|
item: 'px-4 py-2.5 text-base',
|
|
@@ -63,7 +63,7 @@ export const selectTV = tv({
|
|
|
63
63
|
},
|
|
64
64
|
disabled: {
|
|
65
65
|
true: {
|
|
66
|
-
trigger: 'opacity-50 cursor-not-allowed hover:border-default-
|
|
66
|
+
trigger: 'opacity-50 cursor-not-allowed hover:border-default-300',
|
|
67
67
|
container: 'pointer-events-none',
|
|
68
68
|
item: 'disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-transparent'
|
|
69
69
|
}
|
|
@@ -8,11 +8,9 @@
|
|
|
8
8
|
maxSize,
|
|
9
9
|
class: className = '',
|
|
10
10
|
dropzoneClass = '',
|
|
11
|
-
previewClass = '',
|
|
12
11
|
id = 'file-upload',
|
|
13
|
-
onfiles
|
|
14
|
-
|
|
15
|
-
files = []
|
|
12
|
+
onfiles,
|
|
13
|
+
uploadContent
|
|
16
14
|
}: FileUploadProps = $props();
|
|
17
15
|
|
|
18
16
|
const disabled = $derived(maxFiles <= 0);
|
|
@@ -25,10 +23,6 @@
|
|
|
25
23
|
if (onfiles) onfiles(newFiles);
|
|
26
24
|
}
|
|
27
25
|
|
|
28
|
-
function handleDeleteFile(fileId: string, index: number) {
|
|
29
|
-
if (ondelete) ondelete(fileId, index);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
26
|
function handleDragEnter(e: DragEvent) {
|
|
33
27
|
e.preventDefault();
|
|
34
28
|
e.stopPropagation();
|
|
@@ -77,8 +71,12 @@
|
|
|
77
71
|
<label
|
|
78
72
|
class={cn(
|
|
79
73
|
'group relative block rounded-[32px] border-2 border-dashed p-12 text-center transition-colors',
|
|
80
|
-
|
|
81
|
-
|
|
74
|
+
{
|
|
75
|
+
'border-primary-400 bg-primary-50': isDragging,
|
|
76
|
+
'border-default-200 bg-white': !isDragging,
|
|
77
|
+
'cursor-not-allowed opacity-50': disabled,
|
|
78
|
+
'hover:bg-default-50 cursor-pointer': !disabled
|
|
79
|
+
},
|
|
82
80
|
dropzoneClass
|
|
83
81
|
)}
|
|
84
82
|
ondragenter={handleDragEnter}
|
|
@@ -114,13 +112,17 @@
|
|
|
114
112
|
</div>
|
|
115
113
|
|
|
116
114
|
<!-- Upload Text -->
|
|
117
|
-
|
|
118
|
-
<
|
|
119
|
-
|
|
120
|
-
|
|
115
|
+
{#if !uploadContent}
|
|
116
|
+
<div class="text-sm">
|
|
117
|
+
<span class="text-primary-500 font-medium">Click here</span>
|
|
118
|
+
<span class="text-default-600"> to upload your file or drag and drop.</span>
|
|
119
|
+
</div>
|
|
120
|
+
{:else}
|
|
121
|
+
{@render uploadContent()}
|
|
122
|
+
{/if}
|
|
121
123
|
|
|
122
124
|
<!-- File Type Info -->
|
|
123
|
-
<div class="text-default-500 text-
|
|
125
|
+
<div class="text-default-500 text-xs">
|
|
124
126
|
Supported Format: {allowedMimeTypes.length ? allowedMimeTypes.join(', ') : 'SVG, JPG, PNG'}
|
|
125
127
|
{#if maxSize}
|
|
126
128
|
({formatFileSize(maxSize)} each)
|
|
@@ -130,84 +132,4 @@
|
|
|
130
132
|
</div>
|
|
131
133
|
</div>
|
|
132
134
|
</label>
|
|
133
|
-
|
|
134
|
-
<!-- File List -->
|
|
135
|
-
{#if files.length > 0}
|
|
136
|
-
<ul class={cn('mt-6 space-y-3', previewClass)}>
|
|
137
|
-
{#each files as file, i (i)}
|
|
138
|
-
<li
|
|
139
|
-
class={cn('flex items-center justify-between rounded-xl p-4 ring-4', {
|
|
140
|
-
'bg-danger-50 ring-danger-100': file.status === 'error',
|
|
141
|
-
'bg-success-50 ring-success-100': file.status === 'success',
|
|
142
|
-
'ring-primary-100/30 bg-white': file.status === 'uploading'
|
|
143
|
-
})}
|
|
144
|
-
>
|
|
145
|
-
<div class="flex flex-1 items-center gap-3">
|
|
146
|
-
<!-- File Icon -->
|
|
147
|
-
<div
|
|
148
|
-
class={cn('flex size-10 items-center justify-center rounded-full', {
|
|
149
|
-
'bg-danger-100 text-danger-500': file.status === 'error',
|
|
150
|
-
'bg-success-100 text-success-500': file.status === 'success',
|
|
151
|
-
'bg-primary-100 text-primary-500': file.status === 'uploading'
|
|
152
|
-
})}
|
|
153
|
-
>
|
|
154
|
-
<svg xmlns="http://www.w3.org/2000/svg" class="size-5" viewBox="0 0 24 24">
|
|
155
|
-
<path
|
|
156
|
-
fill="currentColor"
|
|
157
|
-
d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6z"
|
|
158
|
-
/>
|
|
159
|
-
</svg>
|
|
160
|
-
</div>
|
|
161
|
-
|
|
162
|
-
<div class="flex-1">
|
|
163
|
-
<!-- Filename and Size -->
|
|
164
|
-
<div class="mb-1 flex items-center justify-between">
|
|
165
|
-
<span class="text-default-700 font-medium">{file.OriginalFilename}</span>
|
|
166
|
-
<span class="text-default-500 text-sm">{formatFileSize(file.Size)}</span>
|
|
167
|
-
</div>
|
|
168
|
-
|
|
169
|
-
<!-- Status Information -->
|
|
170
|
-
{#if file.status === 'uploading'}
|
|
171
|
-
<div class="bg-default-100 h-1.5 w-full rounded-full">
|
|
172
|
-
<div
|
|
173
|
-
class="bg-primary-500 h-1.5 rounded-full transition-all duration-300"
|
|
174
|
-
style="width: {file.progress || 0}%"
|
|
175
|
-
></div>
|
|
176
|
-
</div>
|
|
177
|
-
<span class="text-default-500 mt-1 text-sm">{file.progress || 0}%</span>
|
|
178
|
-
{:else if file.status === 'error'}
|
|
179
|
-
<div class="flex items-center justify-between">
|
|
180
|
-
<span class="text-danger-500 text-sm">Upload failed!</span>
|
|
181
|
-
<span class="text-danger-600 cursor-help text-sm font-medium"
|
|
182
|
-
>Please try again!</span
|
|
183
|
-
>
|
|
184
|
-
</div>
|
|
185
|
-
{:else if file.status === 'success'}
|
|
186
|
-
<p class="text-success-500 text-sm">Upload Successful!</p>
|
|
187
|
-
{/if}
|
|
188
|
-
</div>
|
|
189
|
-
</div>
|
|
190
|
-
|
|
191
|
-
<button
|
|
192
|
-
type="button"
|
|
193
|
-
aria-label="Delete file"
|
|
194
|
-
class={cn('ml-4 flex size-6 cursor-pointer items-center justify-center rounded-lg', {
|
|
195
|
-
'bg-danger-100 text-danger-500': file.status === 'error',
|
|
196
|
-
'bg-success-100 text-success-500': file.status === 'success',
|
|
197
|
-
'bg-default-100 text-default-500': file.status === 'uploading'
|
|
198
|
-
})}
|
|
199
|
-
onclick={() => (file.FileID ? handleDeleteFile(file.FileID, i) : ondelete?.('', i))}
|
|
200
|
-
disabled={file.status === 'uploading'}
|
|
201
|
-
>
|
|
202
|
-
<svg xmlns="http://www.w3.org/2000/svg" class="size-4" viewBox="0 0 24 24">
|
|
203
|
-
<path
|
|
204
|
-
fill="currentColor"
|
|
205
|
-
d="M19 6.41L17.59 5L12 10.59L6.41 5L5 6.41L10.59 12L5 17.59L6.41 19L12 13.41L17.59 19L19 17.59L13.41 12L19 6.41z"
|
|
206
|
-
/>
|
|
207
|
-
</svg>
|
|
208
|
-
</button>
|
|
209
|
-
</li>
|
|
210
|
-
{/each}
|
|
211
|
-
</ul>
|
|
212
|
-
{/if}
|
|
213
135
|
</div>
|