@getmicdrop/svelte-components 5.3.10 → 5.3.12
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/dist/calendar/Calendar/MiniMonthCalendar.svelte +4 -4
- package/dist/calendar/OrderSummary/OrderSummary.svelte +50 -10
- package/dist/calendar/OrderSummary/OrderSummary.svelte.d.ts +2 -0
- package/dist/calendar/OrderSummary/OrderSummary.svelte.d.ts.map +1 -1
- package/dist/primitives/Accordion/AccordionItem.svelte +1 -1
- package/dist/primitives/Button/Button.svelte +12 -3
- package/dist/primitives/Button/Button.svelte.d.ts.map +1 -1
- package/dist/primitives/Checkbox/Checkbox.svelte +8 -8
- package/dist/primitives/Icons/ImageOutline.svelte +19 -0
- package/dist/primitives/Icons/ImageOutline.svelte.d.ts +12 -0
- package/dist/primitives/Icons/ImageOutline.svelte.d.ts.map +1 -0
- package/dist/primitives/Icons/TrashBinOutline.svelte +19 -0
- package/dist/primitives/Icons/TrashBinOutline.svelte.d.ts +12 -0
- package/dist/primitives/Icons/TrashBinOutline.svelte.d.ts.map +1 -0
- package/dist/primitives/Icons/index.d.ts +2 -0
- package/dist/primitives/Icons/index.js +2 -0
- package/dist/primitives/Input/Input.svelte +3 -3
- package/dist/primitives/Input/Select.svelte +1 -1
- package/dist/primitives/Input/Textarea.svelte +1 -1
- package/dist/primitives/Radio/Radio.svelte +7 -7
- package/dist/recipes/ImageUploader/ImageUploader.svelte +59 -18
- package/dist/recipes/ImageUploader/ImageUploader.svelte.d.ts +2 -0
- package/dist/recipes/ImageUploader/ImageUploader.svelte.d.ts.map +1 -1
- package/dist/recipes/fields/FormField.svelte +2 -2
- package/dist/recipes/fields/TextareaField.svelte +1 -1
- package/dist/recipes/inputs/MultiSelect.svelte +1 -1
- package/dist/recipes/modals/ConfirmationModal.svelte +2 -1
- package/package.json +1 -1
|
@@ -322,7 +322,7 @@
|
|
|
322
322
|
}
|
|
323
323
|
|
|
324
324
|
triggerHaptic('medium');
|
|
325
|
-
|
|
325
|
+
onMonthChange?.({ year: currentYear, month: currentMonth });
|
|
326
326
|
|
|
327
327
|
const enterFrom = direction > 0 ? containerWidth : -containerWidth;
|
|
328
328
|
calendarGridElement.style.transition = 'none';
|
|
@@ -615,7 +615,7 @@
|
|
|
615
615
|
|
|
616
616
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
617
617
|
<div
|
|
618
|
-
class="w-full max-w-full mx-auto
|
|
618
|
+
class="w-full max-w-full mx-auto select-none"
|
|
619
619
|
class:max-w-xs={variant === 'display'}
|
|
620
620
|
ontouchstart={handleTouchStart}
|
|
621
621
|
ontouchmove={handleTouchMove}
|
|
@@ -644,7 +644,7 @@
|
|
|
644
644
|
|
|
645
645
|
<div class="flex items-center gap-2">
|
|
646
646
|
<button
|
|
647
|
-
class="p-
|
|
647
|
+
class="p-2 flex items-center justify-center border-0 rounded-lg bg-transparent text-gray-500 dark:text-gray-400 cursor-pointer select-none transition-all duration-100 ease-out hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-gray-900 dark:hover:text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-600 focus-visible:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
648
648
|
class:scale-90={prevPressed}
|
|
649
649
|
class:bg-gray-100={prevPressed}
|
|
650
650
|
class:dark:bg-gray-700={prevPressed}
|
|
@@ -682,7 +682,7 @@
|
|
|
682
682
|
{/if}
|
|
683
683
|
|
|
684
684
|
<button
|
|
685
|
-
class="p-
|
|
685
|
+
class="p-2 flex items-center justify-center border-0 rounded-lg bg-transparent text-gray-500 dark:text-gray-400 cursor-pointer select-none transition-all duration-100 ease-out hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-gray-900 dark:hover:text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-600 focus-visible:ring-offset-2"
|
|
686
686
|
class:scale-90={nextPressed}
|
|
687
687
|
class:bg-gray-100={nextPressed}
|
|
688
688
|
class:dark:bg-gray-700={nextPressed}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
let {
|
|
9
9
|
loading = false,
|
|
10
10
|
quantities = {},
|
|
11
|
+
donationAmounts = {}, // Map of ticketId -> donation amount string
|
|
11
12
|
eventTickets = [],
|
|
12
13
|
checkoutTicket = null,
|
|
13
14
|
isAgreed = true,
|
|
@@ -27,6 +28,24 @@
|
|
|
27
28
|
onPriceUpdate,
|
|
28
29
|
} = $props();
|
|
29
30
|
|
|
31
|
+
// Helper to get effective price for a ticket (handles donation tickets)
|
|
32
|
+
function getEffectivePrice(ticket) {
|
|
33
|
+
// Donation ticket (type 2): use user-entered donation amount
|
|
34
|
+
if (isDonationTicket(ticket)) {
|
|
35
|
+
const donationAmount = donationAmounts[ticket.ID];
|
|
36
|
+
return parseFloat(donationAmount) || 0;
|
|
37
|
+
}
|
|
38
|
+
// Regular ticket: use ticket price
|
|
39
|
+
return parseFloat(ticket.price) || 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Check if ticket is a donation ticket
|
|
43
|
+
// Handle both 'type' and 'ticketType' fields, and coerce to number for comparison
|
|
44
|
+
function isDonationTicket(ticket) {
|
|
45
|
+
const ticketType = ticket.type ?? ticket.ticketType ?? 0;
|
|
46
|
+
return Number(ticketType) === 2;
|
|
47
|
+
}
|
|
48
|
+
|
|
30
49
|
let showOrderSummaryOnMobile = $state(false);
|
|
31
50
|
|
|
32
51
|
function feeFor(price) {
|
|
@@ -76,12 +95,20 @@
|
|
|
76
95
|
let subtotalWithoutDiscount = $derived(Object.keys(quantities).reduce((acc, key) => {
|
|
77
96
|
const ticket = eventTickets.find(t => t.ID == key);
|
|
78
97
|
if (!ticket) return acc;
|
|
79
|
-
|
|
98
|
+
// For donation tickets, use the donation amount; otherwise use ticket price
|
|
99
|
+
const effectivePrice = getEffectivePrice(ticket);
|
|
100
|
+
return acc + quantities[key] * effectivePrice;
|
|
80
101
|
}, 0));
|
|
81
102
|
|
|
82
103
|
let subtotal = $derived(Object.keys(quantities).reduce((acc, key) => {
|
|
83
104
|
const ticket = eventTickets.find(t => t.ID == key);
|
|
84
105
|
if (!ticket) return acc;
|
|
106
|
+
// For donation tickets, use the donation amount (no discounts apply)
|
|
107
|
+
if (isDonationTicket(ticket)) {
|
|
108
|
+
const effectivePrice = getEffectivePrice(ticket);
|
|
109
|
+
return acc + quantities[key] * effectivePrice;
|
|
110
|
+
}
|
|
111
|
+
// For regular tickets, apply discounts as usual
|
|
85
112
|
const discountedPrice = getDiscountedPrice(ticket);
|
|
86
113
|
const priceToUse = discountedPrice !== null ? parseFloat(discountedPrice) : ticket.price;
|
|
87
114
|
return acc + quantities[key] * priceToUse;
|
|
@@ -91,7 +118,8 @@
|
|
|
91
118
|
|
|
92
119
|
let fees = $derived(Object.keys(quantities).reduce((acc, key) => {
|
|
93
120
|
const ticket = eventTickets.find(t => t.ID == key);
|
|
94
|
-
|
|
121
|
+
// Skip fees for: no ticket, free tickets, and donation tickets (type 2)
|
|
122
|
+
if (!ticket || ticket.price == 0 || isDonationTicket(ticket)) return acc;
|
|
95
123
|
const discountedPrice = getDiscountedPrice(ticket);
|
|
96
124
|
const priceToUse = discountedPrice !== null ? parseFloat(discountedPrice) : ticket.price;
|
|
97
125
|
return acc + quantities[key] * feeFor(priceToUse);
|
|
@@ -126,21 +154,27 @@
|
|
|
126
154
|
{#if quantities[key] > 0}
|
|
127
155
|
{#each eventTickets as ticket}
|
|
128
156
|
{#if ticket.ID == key}
|
|
157
|
+
{@const effectivePrice = getEffectivePrice(ticket)}
|
|
158
|
+
{@const isDonation = isDonationTicket(ticket)}
|
|
129
159
|
<div class="flex justify-between py-3 border-b border-gray-200/50 dark:border-gray-600/50">
|
|
130
160
|
<div>
|
|
131
161
|
<p class="{typography.label}">{ticket.name}</p>
|
|
132
162
|
<p class="{typography.smMuted}">
|
|
133
|
-
{#if ticket.price === 0}
|
|
163
|
+
{#if ticket.price === 0 && !isDonation}
|
|
134
164
|
Free x {quantities[key]}
|
|
165
|
+
{:else if isDonation}
|
|
166
|
+
${effectivePrice.toFixed(2)} x {quantities[key]}
|
|
135
167
|
{:else}
|
|
136
168
|
${ticket.price.toFixed(2)} x {quantities[key]}
|
|
137
169
|
{/if}
|
|
138
170
|
</p>
|
|
139
171
|
</div>
|
|
140
172
|
<div class="{typography.label}">
|
|
141
|
-
{ticket.price === 0
|
|
142
|
-
|
|
143
|
-
|
|
173
|
+
{#if ticket.price === 0 && !isDonation}
|
|
174
|
+
Free
|
|
175
|
+
{:else}
|
|
176
|
+
${(effectivePrice * quantities[key]).toFixed(2)}
|
|
177
|
+
{/if}
|
|
144
178
|
</div>
|
|
145
179
|
</div>
|
|
146
180
|
{/if}
|
|
@@ -236,21 +270,27 @@
|
|
|
236
270
|
{#if quantities[key] > 0}
|
|
237
271
|
{#each eventTickets as ticket}
|
|
238
272
|
{#if ticket.ID == key}
|
|
273
|
+
{@const effectivePrice = getEffectivePrice(ticket)}
|
|
274
|
+
{@const isDonation = isDonationTicket(ticket)}
|
|
239
275
|
<div class="flex justify-between py-4 border-b border-gray-200 dark:border-gray-600">
|
|
240
276
|
<div>
|
|
241
277
|
<p class="{typography.h5}">{ticket.name}</p>
|
|
242
278
|
<p class="{typography.smMuted}">
|
|
243
|
-
{#if ticket.price === 0}
|
|
279
|
+
{#if ticket.price === 0 && !isDonation}
|
|
244
280
|
Free x {quantities[key]}
|
|
281
|
+
{:else if isDonation}
|
|
282
|
+
${effectivePrice.toFixed(2)} x {quantities[key]}
|
|
245
283
|
{:else}
|
|
246
284
|
${ticket.price.toFixed(2)} x {quantities[key]}
|
|
247
285
|
{/if}
|
|
248
286
|
</p>
|
|
249
287
|
</div>
|
|
250
288
|
<div class="{typography.h5}">
|
|
251
|
-
{ticket.price === 0
|
|
252
|
-
|
|
253
|
-
|
|
289
|
+
{#if ticket.price === 0 && !isDonation}
|
|
290
|
+
Free
|
|
291
|
+
{:else}
|
|
292
|
+
${(effectivePrice * quantities[key]).toFixed(2)}
|
|
293
|
+
{/if}
|
|
254
294
|
</div>
|
|
255
295
|
</div>
|
|
256
296
|
{/if}
|
|
@@ -6,6 +6,7 @@ type OrderSummary = {
|
|
|
6
6
|
declare const OrderSummary: import("svelte").Component<{
|
|
7
7
|
loading?: boolean;
|
|
8
8
|
quantities?: Record<string, any>;
|
|
9
|
+
donationAmounts?: Record<string, any>;
|
|
9
10
|
eventTickets?: any[];
|
|
10
11
|
checkoutTicket?: any;
|
|
11
12
|
isAgreed?: boolean;
|
|
@@ -21,6 +22,7 @@ declare const OrderSummary: import("svelte").Component<{
|
|
|
21
22
|
type $$ComponentProps = {
|
|
22
23
|
loading?: boolean;
|
|
23
24
|
quantities?: Record<string, any>;
|
|
25
|
+
donationAmounts?: Record<string, any>;
|
|
24
26
|
eventTickets?: any[];
|
|
25
27
|
checkoutTicket?: any;
|
|
26
28
|
isAgreed?: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OrderSummary.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/calendar/OrderSummary/OrderSummary.svelte.js"],"names":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"OrderSummary.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/calendar/OrderSummary/OrderSummary.svelte.js"],"names":[],"mappings":";;;;;AAoVA;cApU+B,OAAO;iBAAe,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;sBAAoB,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;mBAAiB,GAAG,EAAE;qBAAmB,GAAG;eAAa,OAAO;cAAY,MAAM;mBAAiB,OAAO;oBAAkB,MAAM;uBAAqB,GAAG;sBAAoB,GAAG;eAAa,GAAG;yBAAuB,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;mBAAiB,GAAG;WAoUnT;wBApUxC;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAAC,YAAY,CAAC,EAAE,GAAG,EAAE,CAAC;IAAC,cAAc,CAAC,EAAE,GAAG,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,OAAO,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,gBAAgB,CAAC,EAAE,GAAG,CAAC;IAAC,eAAe,CAAC,EAAE,GAAG,CAAC;IAAC,QAAQ,CAAC,EAAE,GAAG,CAAC;IAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAAC,aAAa,EAAE,GAAG,CAAA;CAAE"}
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
typography.smMuted,
|
|
62
62
|
"bg-gray-100 dark:bg-gray-800",
|
|
63
63
|
"hover:bg-gray-200 dark:hover:bg-gray-700",
|
|
64
|
-
"focus:
|
|
64
|
+
"focus:outline-none",
|
|
65
65
|
"gap-3",
|
|
66
66
|
isOpen ? "bg-gray-100 dark:bg-gray-800" : "",
|
|
67
67
|
].join(" "));
|
|
@@ -130,9 +130,9 @@
|
|
|
130
130
|
// Avatar/image trigger - no background, opacity hover
|
|
131
131
|
avatar: "bg-transparent border-transparent hover:opacity-80",
|
|
132
132
|
// Menu items - full width, left-aligned, for dropdowns/sheets and sidebar nav
|
|
133
|
-
"menu-item": "w-full text-left text-gray-900 bg-transparent border-transparent hover:bg-gray-100 dark:text-white dark:hover:bg-gray-600",
|
|
133
|
+
"menu-item": "w-full text-left whitespace-nowrap text-gray-900 bg-transparent border-transparent hover:bg-gray-100 dark:text-white dark:hover:bg-gray-600",
|
|
134
134
|
// Danger menu item - red text version
|
|
135
|
-
"menu-item-danger": "w-full text-left text-red-600 bg-transparent border-transparent hover:bg-red-50 dark:text-red-500 dark:hover:bg-gray-600",
|
|
135
|
+
"menu-item-danger": "w-full text-left whitespace-nowrap text-red-600 bg-transparent border-transparent hover:bg-red-50 dark:text-red-500 dark:hover:bg-gray-600",
|
|
136
136
|
// Selectable card - bordered card-like button for list selections
|
|
137
137
|
card: "w-full text-left text-gray-900 bg-white border border-gray-300 hover:bg-gray-50 dark:bg-gray-800 dark:text-white dark:border-gray-600 dark:hover:bg-gray-700",
|
|
138
138
|
// Search result item - blue hover for search dropdowns
|
|
@@ -155,6 +155,8 @@
|
|
|
155
155
|
|
|
156
156
|
// Disabled classes
|
|
157
157
|
const disabledClasses = "bg-gray-200 border-gray-200 text-gray-400 cursor-not-allowed dark:bg-gray-700 dark:border-gray-700 dark:text-gray-500";
|
|
158
|
+
// Disabled classes for transparent variants (link, ghost) - no background
|
|
159
|
+
const disabledTransparentClasses = "text-gray-400 cursor-not-allowed dark:text-gray-500";
|
|
158
160
|
|
|
159
161
|
let sizeClass = $derived((() => {
|
|
160
162
|
if (resolvedVariant === "icon") return iconSizes[size] || iconSizes.sm;
|
|
@@ -169,9 +171,16 @@
|
|
|
169
171
|
return sizeClasses[size] || sizeClasses.md;
|
|
170
172
|
})());
|
|
171
173
|
|
|
174
|
+
// Variants that should stay transparent when disabled
|
|
175
|
+
const transparentVariants = ["link", "ghost", "ghost-red", "icon"];
|
|
176
|
+
|
|
172
177
|
let variantClass = $derived((() => {
|
|
173
178
|
if (success) return variantClasses.success;
|
|
174
|
-
if (disabled && !loading && !success)
|
|
179
|
+
if (disabled && !loading && !success) {
|
|
180
|
+
return transparentVariants.includes(resolvedVariant)
|
|
181
|
+
? disabledTransparentClasses
|
|
182
|
+
: disabledClasses;
|
|
183
|
+
}
|
|
175
184
|
if (active && activeClasses[resolvedVariant]) {
|
|
176
185
|
// When active, use ONLY active classes (they include all necessary styling)
|
|
177
186
|
return activeClasses[resolvedVariant];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Button.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/primitives/Button/Button.svelte.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"Button.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/primitives/Button/Button.svelte.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AAkRA;;cAfc,MAAM;WACT,MAAM;eACF,OAAO;cACR,OAAO;cACP,OAAO;kBACH,MAAM;aACX,OAAO;WACT,MAAM,GAAG,IAAI;WACb,QAAQ,GAAG,QAAQ,GAAG,OAAO;eACzB,GAAG;eACH,GAAG;YACN,MAAM;cACJ,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI;WAGgB"}
|
|
@@ -42,12 +42,12 @@
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
const colorClasses = {
|
|
45
|
-
blue: "text-blue-600
|
|
46
|
-
red: "text-red-600
|
|
47
|
-
green: "text-green-600
|
|
48
|
-
purple: "text-purple-600
|
|
49
|
-
orange: "text-orange-500
|
|
50
|
-
yellow: "text-yellow-400
|
|
45
|
+
blue: "text-blue-600",
|
|
46
|
+
red: "text-red-600",
|
|
47
|
+
green: "text-green-600",
|
|
48
|
+
purple: "text-purple-600",
|
|
49
|
+
orange: "text-orange-500",
|
|
50
|
+
yellow: "text-yellow-400"
|
|
51
51
|
};
|
|
52
52
|
|
|
53
53
|
let colorClass = $derived(colorClasses[color] || colorClasses.blue);
|
|
@@ -55,8 +55,8 @@
|
|
|
55
55
|
let inputClasses = $derived([
|
|
56
56
|
"w-4 h-4 rounded",
|
|
57
57
|
"bg-gray-100 border-gray-300",
|
|
58
|
-
"dark:bg-gray-700 dark:border-gray-600
|
|
59
|
-
"focus:
|
|
58
|
+
"dark:bg-gray-700 dark:border-gray-600",
|
|
59
|
+
"focus:outline-none",
|
|
60
60
|
colorClass,
|
|
61
61
|
disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer"
|
|
62
62
|
].join(" "));
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
let { class: className = 'w-5 h-5', ...restProps } = $props();
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<svg
|
|
6
|
+
class={className}
|
|
7
|
+
fill="none"
|
|
8
|
+
stroke="currentColor"
|
|
9
|
+
viewBox="0 0 24 24"
|
|
10
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
11
|
+
{...restProps}
|
|
12
|
+
>
|
|
13
|
+
<path
|
|
14
|
+
stroke-linecap="round"
|
|
15
|
+
stroke-linejoin="round"
|
|
16
|
+
stroke-width="2"
|
|
17
|
+
d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
|
|
18
|
+
/>
|
|
19
|
+
</svg>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export default ImageOutline;
|
|
2
|
+
type ImageOutline = {
|
|
3
|
+
$on?(type: string, callback: (e: any) => void): () => void;
|
|
4
|
+
$set?(props: Partial<$$ComponentProps>): void;
|
|
5
|
+
};
|
|
6
|
+
declare const ImageOutline: import("svelte").Component<{
|
|
7
|
+
class?: string;
|
|
8
|
+
} & Record<string, any>, {}, "">;
|
|
9
|
+
type $$ComponentProps = {
|
|
10
|
+
class?: string;
|
|
11
|
+
} & Record<string, any>;
|
|
12
|
+
//# sourceMappingURL=ImageOutline.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ImageOutline.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/primitives/Icons/ImageOutline.svelte.js"],"names":[],"mappings":";;;;;AAaA;YAT6B,MAAM;iCASwB;wBATxC;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
let { class: className = 'w-5 h-5', ...restProps } = $props();
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<svg
|
|
6
|
+
class={className}
|
|
7
|
+
fill="none"
|
|
8
|
+
stroke="currentColor"
|
|
9
|
+
viewBox="0 0 24 24"
|
|
10
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
11
|
+
{...restProps}
|
|
12
|
+
>
|
|
13
|
+
<path
|
|
14
|
+
stroke-linecap="round"
|
|
15
|
+
stroke-linejoin="round"
|
|
16
|
+
stroke-width="2"
|
|
17
|
+
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
|
18
|
+
/>
|
|
19
|
+
</svg>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export default TrashBinOutline;
|
|
2
|
+
type TrashBinOutline = {
|
|
3
|
+
$on?(type: string, callback: (e: any) => void): () => void;
|
|
4
|
+
$set?(props: Partial<$$ComponentProps>): void;
|
|
5
|
+
};
|
|
6
|
+
declare const TrashBinOutline: import("svelte").Component<{
|
|
7
|
+
class?: string;
|
|
8
|
+
} & Record<string, any>, {}, "">;
|
|
9
|
+
type $$ComponentProps = {
|
|
10
|
+
class?: string;
|
|
11
|
+
} & Record<string, any>;
|
|
12
|
+
//# sourceMappingURL=TrashBinOutline.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TrashBinOutline.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/primitives/Icons/TrashBinOutline.svelte.js"],"names":[],"mappings":";;;;;AAaA;YAT6B,MAAM;iCAS2B;wBAT3C;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC"}
|
|
@@ -16,6 +16,8 @@ export { default as PlusOutline } from "./PlusOutline.svelte";
|
|
|
16
16
|
export { default as ExclamationTriangleOutline } from "./ExclamationTriangleOutline.svelte";
|
|
17
17
|
export { default as DotsHorizontalOutline } from "./DotsHorizontalOutline.svelte";
|
|
18
18
|
export { default as FileCopyOutline } from "./FileCopyOutline.svelte";
|
|
19
|
+
export { default as ImageOutline } from "./ImageOutline.svelte";
|
|
20
|
+
export { default as TrashBinOutline } from "./TrashBinOutline.svelte";
|
|
19
21
|
export { default as CheckCircleSolid } from "./CheckCircleSolid.svelte";
|
|
20
22
|
export { default as HomeSolid } from "./HomeSolid.svelte";
|
|
21
23
|
export { default as LogoInstagram } from "./LogoInstagram.svelte";
|
|
@@ -17,6 +17,8 @@ export { default as PlusOutline } from './PlusOutline.svelte';
|
|
|
17
17
|
export { default as ExclamationTriangleOutline } from './ExclamationTriangleOutline.svelte';
|
|
18
18
|
export { default as DotsHorizontalOutline } from './DotsHorizontalOutline.svelte';
|
|
19
19
|
export { default as FileCopyOutline } from './FileCopyOutline.svelte';
|
|
20
|
+
export { default as ImageOutline } from './ImageOutline.svelte';
|
|
21
|
+
export { default as TrashBinOutline } from './TrashBinOutline.svelte';
|
|
20
22
|
|
|
21
23
|
// Solid icons (fill-based)
|
|
22
24
|
export { default as CheckCircleSolid } from './CheckCircleSolid.svelte';
|
|
@@ -309,7 +309,7 @@
|
|
|
309
309
|
onfocus={handleFocus}
|
|
310
310
|
{maxlength}
|
|
311
311
|
{minlength}
|
|
312
|
-
class="{typography.sm} w-full px-3 py-2 bg-gray-50 dark:bg-gray-
|
|
312
|
+
class="{typography.sm} w-full px-3 py-2 bg-gray-50 dark:bg-gray-800 border rounded-lg font-medium placeholder-gray-500 dark:placeholder-gray-400 transition-all focus:outline-none focus:ring-4 focus:ring-blue-300 dark:focus:ring-blue-800 resize-y {hasError ? 'border-red-500' : 'border-gray-300 dark:border-gray-600 hover:border-blue-500 focus:border-blue-500'} {getContentFloatClass()} {getTextareaSizeClass()} {shouldAnimate ? 'focus:scale-[1.01]' : ''}"
|
|
313
313
|
required={false}
|
|
314
314
|
{disabled}
|
|
315
315
|
{readonly}
|
|
@@ -317,7 +317,7 @@
|
|
|
317
317
|
aria-required={required}
|
|
318
318
|
></textarea>
|
|
319
319
|
{:else if type === "password" && showPasswordToggle}
|
|
320
|
-
<div class="flex items-center w-full bg-gray-50 dark:bg-gray-
|
|
320
|
+
<div class="flex items-center w-full bg-gray-50 dark:bg-gray-800 border rounded-lg transition-all outline-none focus-within:ring-4 focus-within:ring-blue-300 dark:focus-within:ring-blue-800 {hasError ? 'border-red-500' : 'border-gray-300 dark:border-gray-600 hover:border-blue-500 focus-within:border-blue-500'} {shouldAnimate ? 'focus-within:scale-[1.01]' : ''}">
|
|
321
321
|
<input
|
|
322
322
|
bind:this={inputElement}
|
|
323
323
|
{id}
|
|
@@ -375,7 +375,7 @@
|
|
|
375
375
|
onkeydown={handleSearchKeyDown}
|
|
376
376
|
{maxlength}
|
|
377
377
|
{minlength}
|
|
378
|
-
class="{typography.body} w-full {sizeClass} bg-gray-50 dark:bg-gray-
|
|
378
|
+
class="{typography.body} w-full {sizeClass} bg-gray-50 dark:bg-gray-800 border rounded-lg font-medium placeholder-gray-500 dark:placeholder-gray-400 transition-all focus:outline-none focus:ring-4 focus:ring-blue-300 dark:focus:ring-blue-800 {hasError ? 'border-red-500' : 'border-gray-300 dark:border-gray-600 hover:border-blue-500 focus:border-blue-500'} {icon || (showClearButton && inputValue) ? 'pr-10' : ''} {leftIcon ? 'pl-10' : ''} {getContentFloatClass()} {shouldAnimate ? 'focus:scale-[1.01]' : ''} {disabled ? 'opacity-50 cursor-not-allowed' : ''}"
|
|
379
379
|
required={false}
|
|
380
380
|
{disabled}
|
|
381
381
|
{readonly}
|
|
@@ -163,7 +163,7 @@
|
|
|
163
163
|
bind:this={triggerElement}
|
|
164
164
|
{id}
|
|
165
165
|
{name}
|
|
166
|
-
class="flex items-center justify-between w-full {sizeClass} bg-gray-50 dark:bg-gray-
|
|
166
|
+
class="flex items-center justify-between w-full {sizeClass} bg-gray-50 dark:bg-gray-800 border rounded-lg cursor-pointer transition-colors text-left focus:outline-none focus:ring-4 focus:ring-blue-300 dark:focus:ring-blue-800 {error ? 'border-red-500 dark:border-red-500' : 'border-gray-300 dark:border-gray-600 hover:border-blue-500 dark:hover:border-blue-500'} {disabled ? 'opacity-50 cursor-not-allowed' : ''} {!selectedItem ? `${typography.textMuted}` : `${typography.body}`}"
|
|
167
167
|
{disabled}
|
|
168
168
|
aria-haspopup="listbox"
|
|
169
169
|
aria-expanded={isOpen}
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
{readonly}
|
|
60
60
|
{maxlength}
|
|
61
61
|
{minlength}
|
|
62
|
-
class="{typography.sm} w-full p-2.5 bg-gray-50 dark:bg-gray-
|
|
62
|
+
class="{typography.sm} w-full p-2.5 bg-gray-50 dark:bg-gray-800 leading-normal border rounded-lg resize-y transition-colors focus:outline-none focus:ring-4 focus:ring-blue-300 dark:focus:ring-blue-800 placeholder-gray-500 dark:placeholder-gray-400 {error ? 'border-red-500' : 'border-gray-300 dark:border-gray-600 hover:border-blue-500 focus:border-blue-500'} {disabled ? 'opacity-50 cursor-not-allowed' : ''} {className}"
|
|
63
63
|
bind:value
|
|
64
64
|
oninput={handleInput}
|
|
65
65
|
onchange={handleChange}
|
|
@@ -35,12 +35,12 @@
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
const colorClasses = {
|
|
38
|
-
blue: 'text-blue-600
|
|
39
|
-
red: 'text-red-600
|
|
40
|
-
green: 'text-green-600
|
|
41
|
-
purple: 'text-purple-600
|
|
42
|
-
orange: 'text-orange-500
|
|
43
|
-
yellow: 'text-yellow-400
|
|
38
|
+
blue: 'text-blue-600',
|
|
39
|
+
red: 'text-red-600',
|
|
40
|
+
green: 'text-green-600',
|
|
41
|
+
purple: 'text-purple-600',
|
|
42
|
+
orange: 'text-orange-500',
|
|
43
|
+
yellow: 'text-yellow-400'
|
|
44
44
|
};
|
|
45
45
|
|
|
46
46
|
let colorClass = $derived(colorClasses[color] || colorClasses.blue);
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
{disabled}
|
|
58
58
|
checked={isChecked}
|
|
59
59
|
onchange={handleChange}
|
|
60
|
-
class="w-4 h-4 bg-gray-100 border-gray-300 focus:
|
|
60
|
+
class="w-4 h-4 bg-gray-100 border-gray-300 focus:outline-none dark:bg-gray-700 dark:border-gray-600 {colorClass}"
|
|
61
61
|
/>
|
|
62
62
|
{#if children}
|
|
63
63
|
<span class="text-sm font-medium text-gray-900 dark:text-gray-300">
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
import Sortable from 'sortablejs';
|
|
24
24
|
import CropImage from '../CropImage/CropImage.svelte';
|
|
25
25
|
import Badge from '../../primitives/Badges/Badge.svelte';
|
|
26
|
-
import
|
|
26
|
+
import Button from '../../primitives/Button/Button.svelte';
|
|
27
|
+
import { CloseOutline, ImageOutline, PlusOutline, TrashBinOutline } from '../../primitives/Icons';
|
|
27
28
|
import { typography } from '../../tokens/typography';
|
|
28
29
|
|
|
29
30
|
// Register FilePond plugins once
|
|
@@ -62,6 +63,8 @@
|
|
|
62
63
|
acceptedTypes?: string[];
|
|
63
64
|
/** Label shown when empty */
|
|
64
65
|
emptyLabel?: string;
|
|
66
|
+
/** Constraints text shown inside dropzone (e.g., "Max 10MB, 2160px") */
|
|
67
|
+
constraintsText?: string;
|
|
65
68
|
/** Helper text shown below dropzone */
|
|
66
69
|
helperText?: string;
|
|
67
70
|
/** Callback when image is uploaded - receives File, optionally returns URL
|
|
@@ -98,6 +101,7 @@
|
|
|
98
101
|
maxFileSize = '20MB',
|
|
99
102
|
acceptedTypes = ['image/jpeg', 'image/png', 'image/webp', 'image/heic', 'image/heif'],
|
|
100
103
|
emptyLabel = 'Drag & drop or click to upload',
|
|
104
|
+
constraintsText = '',
|
|
101
105
|
helperText = '',
|
|
102
106
|
onUpload,
|
|
103
107
|
onRemove,
|
|
@@ -147,6 +151,9 @@
|
|
|
147
151
|
let isModalDragOver = $state(false);
|
|
148
152
|
let modalFileInput: HTMLInputElement;
|
|
149
153
|
|
|
154
|
+
// Single-mode file input for replace functionality
|
|
155
|
+
let singleModeFileInput: HTMLInputElement;
|
|
156
|
+
|
|
150
157
|
// Prevent browser from opening dragged files in new tab
|
|
151
158
|
// This MUST be at document level to intercept before browser default behavior
|
|
152
159
|
$effect(() => {
|
|
@@ -223,11 +230,12 @@
|
|
|
223
230
|
dropOnElement: true,
|
|
224
231
|
labelIdle: `
|
|
225
232
|
<div class="filepond-custom-label">
|
|
226
|
-
<svg class="w-8 h-8 mb-
|
|
233
|
+
<svg class="w-8 h-8 mb-5 text-gray-400 dark:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
227
234
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
|
228
235
|
</svg>
|
|
229
|
-
<span class="text-sm text-gray-
|
|
230
|
-
<span class="text-
|
|
236
|
+
<span class="text-sm font-medium text-gray-600 dark:text-gray-300">${emptyLabel}</span>
|
|
237
|
+
<span class="text-[11px] text-gray-400 dark:text-gray-500 mt-2 block">JPG, PNG, WebP</span>
|
|
238
|
+
${constraintsText ? `<span class="text-[10px] text-gray-400/50 dark:text-gray-500/50 mt-1 block">${constraintsText}</span>` : ''}
|
|
231
239
|
</div>
|
|
232
240
|
`,
|
|
233
241
|
credits: false,
|
|
@@ -460,6 +468,26 @@
|
|
|
460
468
|
input.value = '';
|
|
461
469
|
}
|
|
462
470
|
|
|
471
|
+
// Handle single-mode file input selection (for replace)
|
|
472
|
+
function handleSingleModeFileSelect(e: Event) {
|
|
473
|
+
const input = e.target as HTMLInputElement;
|
|
474
|
+
const file = input?.files?.[0];
|
|
475
|
+
if (!file) return;
|
|
476
|
+
|
|
477
|
+
targetSlotIndex = 0; // Replace the existing image
|
|
478
|
+
|
|
479
|
+
if (enableCrop) {
|
|
480
|
+
const objectUrl = URL.createObjectURL(file);
|
|
481
|
+
imageForCrop = objectUrl;
|
|
482
|
+
showCropModal = true;
|
|
483
|
+
} else {
|
|
484
|
+
handleUpload(file);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Clear input for next selection
|
|
488
|
+
input.value = '';
|
|
489
|
+
}
|
|
490
|
+
|
|
463
491
|
// Initialize sortable for multi-image grid
|
|
464
492
|
function initSortable() {
|
|
465
493
|
if (!browser || !gridContainer || !enableReorder || !isMultiMode) return;
|
|
@@ -543,6 +571,7 @@
|
|
|
543
571
|
});
|
|
544
572
|
</script>
|
|
545
573
|
|
|
574
|
+
|
|
546
575
|
<!-- Single Image Mode -->
|
|
547
576
|
{#if !isMultiMode}
|
|
548
577
|
<div class="image-uploader-single {className}">
|
|
@@ -555,24 +584,32 @@
|
|
|
555
584
|
class="w-full h-full object-cover"
|
|
556
585
|
/>
|
|
557
586
|
{#if !disabled}
|
|
558
|
-
<div class="absolute inset-0 bg-black/
|
|
559
|
-
<
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
onclick={() =>
|
|
563
|
-
aria-label="Replace image"
|
|
587
|
+
<div class="absolute inset-0 bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center gap-3">
|
|
588
|
+
<Button
|
|
589
|
+
variant="default"
|
|
590
|
+
size="sm"
|
|
591
|
+
onclick={() => singleModeFileInput?.click()}
|
|
564
592
|
>
|
|
565
|
-
<
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
593
|
+
<ImageOutline class="w-4 h-4 mr-1.5" />
|
|
594
|
+
Replace
|
|
595
|
+
</Button>
|
|
596
|
+
<Button
|
|
597
|
+
variant="red"
|
|
598
|
+
size="sm"
|
|
570
599
|
onclick={() => handleRemoveImage(0)}
|
|
571
|
-
aria-label="Remove image"
|
|
572
600
|
>
|
|
573
|
-
<
|
|
574
|
-
|
|
601
|
+
<TrashBinOutline class="w-4 h-4 mr-1.5" />
|
|
602
|
+
Delete
|
|
603
|
+
</Button>
|
|
575
604
|
</div>
|
|
605
|
+
<!-- Hidden file input for replace -->
|
|
606
|
+
<input
|
|
607
|
+
bind:this={singleModeFileInput}
|
|
608
|
+
type="file"
|
|
609
|
+
accept={acceptedTypes.join(',')}
|
|
610
|
+
class="hidden"
|
|
611
|
+
onchange={handleSingleModeFileSelect}
|
|
612
|
+
/>
|
|
576
613
|
{/if}
|
|
577
614
|
</div>
|
|
578
615
|
{:else}
|
|
@@ -779,6 +816,10 @@
|
|
|
779
816
|
justify-content: center;
|
|
780
817
|
}
|
|
781
818
|
|
|
819
|
+
/* Note: FilePond dark mode is handled via runtime JS injection
|
|
820
|
+
because Svelte's scoped :global() can't reliably override
|
|
821
|
+
FilePond's CSS that loads from node_modules */
|
|
822
|
+
|
|
782
823
|
:global(.filepond-wrapper .filepond--drop-label label) {
|
|
783
824
|
display: flex;
|
|
784
825
|
flex-direction: column;
|
|
@@ -26,6 +26,8 @@ interface Props {
|
|
|
26
26
|
acceptedTypes?: string[];
|
|
27
27
|
/** Label shown when empty */
|
|
28
28
|
emptyLabel?: string;
|
|
29
|
+
/** Constraints text shown inside dropzone (e.g., "Max 10MB, 2160px") */
|
|
30
|
+
constraintsText?: string;
|
|
29
31
|
/** Helper text shown below dropzone */
|
|
30
32
|
helperText?: string;
|
|
31
33
|
/** Callback when image is uploaded - receives File, optionally returns URL
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ImageUploader.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/recipes/ImageUploader/ImageUploader.svelte.ts"],"names":[],"mappings":"AAoBA,OAAO,gCAAgC,CAAC;AACxC,OAAO,sEAAsE,CAAC;
|
|
1
|
+
{"version":3,"file":"ImageUploader.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/recipes/ImageUploader/ImageUploader.svelte.ts"],"names":[],"mappings":"AAoBA,OAAO,gCAAgC,CAAC;AACxC,OAAO,sEAAsE,CAAC;AAW5E,KAAK,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;AAC/B,KAAK,WAAW,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,CAAC;AAC3D,KAAK,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE/B,UAAU,KAAK;IACb,6CAA6C;IAC7C,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC3B,qDAAqD;IACrD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2BAA2B;IAC3B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,wBAAwB;IACxB,eAAe,CAAC,EAAE,WAAW,CAAC;IAC9B,mDAAmD;IACnD,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,wDAAwD;IACxD,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,gEAAgE;IAChE,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,oDAAoD;IACpD,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,uCAAuC;IACvC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0BAA0B;IAC1B,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,6BAA6B;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,wEAAwE;IACxE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,uCAAuC;IACvC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;sEAEkE;IAClE,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IAChE,qCAAqC;IACrC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,8DAA8D;IAC9D,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACvC,oFAAoF;IACpF,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAChE,4EAA4E;IAC5E,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAC9C,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACtC,wBAAwB;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qBAAqB;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,+BAA+B;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAsoBH,QAAA,MAAM,aAAa,iDAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
|
|
@@ -48,9 +48,9 @@
|
|
|
48
48
|
|
|
49
49
|
{#if !error}
|
|
50
50
|
{#if hintContent}
|
|
51
|
-
<p class={typography.
|
|
51
|
+
<p class={typography.xsMuted}>{@render hintContent()}</p>
|
|
52
52
|
{:else if hint}
|
|
53
|
-
<p class={typography.
|
|
53
|
+
<p class={typography.xsMuted}>{hint}</p>
|
|
54
54
|
{/if}
|
|
55
55
|
{/if}
|
|
56
56
|
|
|
@@ -152,7 +152,7 @@
|
|
|
152
152
|
bind:this={triggerElement}
|
|
153
153
|
{id}
|
|
154
154
|
{name}
|
|
155
|
-
class="flex items-center justify-between gap-2 w-full {sizeClass} bg-gray-50 dark:bg-gray-
|
|
155
|
+
class="flex items-center justify-between gap-2 w-full {sizeClass} bg-gray-50 dark:bg-gray-800 border rounded-lg cursor-pointer text-left transition-all focus:outline-none focus:ring-4 focus:ring-blue-300 dark:focus:ring-blue-800
|
|
156
156
|
{error ? 'border-red-500 dark:border-red-500' : 'border-gray-300 dark:border-gray-600 hover:border-blue-500 dark:hover:border-blue-500 focus:border-blue-500 dark:focus:border-blue-500'}
|
|
157
157
|
{disabled ? 'opacity-50 cursor-not-allowed' : ''}"
|
|
158
158
|
{disabled}
|