urpanels-ui-pack 0.0.4 → 0.0.10
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/BasePageService/BasePageService.d.ts +121 -0
- package/dist/BasePageService/BasePageService.js +191 -0
- package/dist/BasePageService/index.d.ts +2 -0
- package/dist/BasePageService/index.js +1 -0
- package/dist/Button/Button.svelte +1 -0
- package/dist/InfoCard/InfoCard.svelte +61 -30
- package/dist/InputCheckboxModal/InputCheckboxModal.svelte +235 -0
- package/dist/InputCheckboxModal/InputCheckboxModal.svelte.d.ts +23 -0
- package/dist/InputCheckboxModal/index.d.ts +1 -0
- package/dist/InputCheckboxModal/index.js +1 -0
- package/dist/InputSelectModal/InputSelectModal.svelte +118 -0
- package/dist/InputSelectModal/InputSelectModal.svelte.d.ts +19 -0
- package/dist/InputSelectModal/index.d.ts +1 -0
- package/dist/InputSelectModal/index.js +1 -0
- package/dist/LoadingSpinner/LoadingSpinner.svelte +1 -1
- package/dist/Modal/Modal.svelte +74 -2
- package/dist/Modal/Modal.svelte.d.ts +9 -2
- package/dist/PageLayout/ActionButton.svelte +12 -4
- package/dist/PageLayout/PageContent.svelte +1 -1
- package/dist/PageLayout/PageHeader.svelte +75 -15
- package/dist/PageLayout/PageHeader.svelte.d.ts +10 -1
- package/dist/PageLayout/SearchBar.svelte +1 -1
- package/dist/PageLayout/ViewToggle.svelte +1 -1
- package/dist/Pagination/Pagination.svelte +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +8 -0
- package/dist/inputs/InputNumber/InputNumber.svelte +140 -0
- package/dist/inputs/InputNumber/InputNumber.svelte.d.ts +27 -0
- package/dist/inputs/InputNumber/index.d.ts +2 -0
- package/dist/inputs/InputNumber/index.js +2 -0
- package/dist/inputs/InputSelect/InputSelect.svelte +73 -0
- package/dist/inputs/InputSelect/InputSelect.svelte.d.ts +18 -0
- package/dist/inputs/InputSelect/index.d.ts +2 -0
- package/dist/inputs/InputSelect/index.js +2 -0
- package/dist/inputs/InputText/InputText.svelte +126 -0
- package/dist/inputs/InputText/InputText.svelte.d.ts +49 -0
- package/dist/inputs/InputText/index.d.ts +2 -0
- package/dist/inputs/InputText/index.js +2 -0
- package/dist/inputs/TextArea/TextArea.svelte +113 -0
- package/dist/inputs/TextArea/TextArea.svelte.d.ts +21 -0
- package/dist/inputs/TextArea/index.d.ts +2 -0
- package/dist/inputs/TextArea/index.js +2 -0
- package/dist/inputs/index.d.ts +3 -0
- package/dist/inputs/index.js +4 -0
- package/dist/sections/PreviewSelector/PreviewSelector.svelte +164 -0
- package/dist/sections/PreviewSelector/PreviewSelector.svelte.d.ts +25 -0
- package/dist/sections/PreviewSelector/index.d.ts +2 -0
- package/dist/sections/PreviewSelector/index.js +1 -0
- package/dist/sections/index.d.ts +2 -0
- package/dist/sections/index.js +1 -0
- package/package.json +14 -1
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { tick } from 'svelte';
|
|
3
|
+
import Modal from '../Modal/Modal.svelte';
|
|
4
|
+
import InputText from '../inputs/InputText/InputText.svelte';
|
|
5
|
+
import type { Snippet } from 'svelte';
|
|
6
|
+
|
|
7
|
+
interface Props<T = any> {
|
|
8
|
+
open?: boolean;
|
|
9
|
+
title?: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
items?: T[];
|
|
12
|
+
selectedItems?: T[];
|
|
13
|
+
searchPlaceholder?: string;
|
|
14
|
+
searchLabel?: string;
|
|
15
|
+
emptyMessage?: string;
|
|
16
|
+
onConfirm?: (selectedItems: T[]) => void;
|
|
17
|
+
onClose?: () => void;
|
|
18
|
+
color?: 'blue' | 'emerald' | 'red' | 'amber' | 'gray';
|
|
19
|
+
size?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl';
|
|
20
|
+
renderItem?: Snippet<[T]>;
|
|
21
|
+
searchFunction?: (item: T, searchTerm: string) => boolean;
|
|
22
|
+
getItemId?: (item: T) => string | number;
|
|
23
|
+
confirmText?: string;
|
|
24
|
+
keepOpenOnConfirm?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let {
|
|
28
|
+
open = $bindable(false),
|
|
29
|
+
title = 'Öğe Seç',
|
|
30
|
+
description = 'Listeden bir veya daha fazla öğe seçin',
|
|
31
|
+
items = [],
|
|
32
|
+
selectedItems = $bindable([]),
|
|
33
|
+
searchPlaceholder = 'Ara...',
|
|
34
|
+
searchLabel = 'Ara',
|
|
35
|
+
emptyMessage = 'Öğe bulunamadı',
|
|
36
|
+
onConfirm = () => {},
|
|
37
|
+
onClose = () => {},
|
|
38
|
+
color = 'blue',
|
|
39
|
+
size = '2xl',
|
|
40
|
+
renderItem,
|
|
41
|
+
searchFunction,
|
|
42
|
+
getItemId,
|
|
43
|
+
confirmText = 'Seçilenleri Onayla',
|
|
44
|
+
keepOpenOnConfirm = false
|
|
45
|
+
}: Props = $props();
|
|
46
|
+
|
|
47
|
+
let searchTerm = $state('');
|
|
48
|
+
let tempSelected: any[] = $state([]);
|
|
49
|
+
let isConfirming = $state(false);
|
|
50
|
+
|
|
51
|
+
// Test: keepOpenOnConfirm aktifken kapanmayı engelle
|
|
52
|
+
$effect(() => {
|
|
53
|
+
if (keepOpenOnConfirm && !open) {
|
|
54
|
+
open = true;
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Default ID getter
|
|
59
|
+
const defaultGetItemId = (item: any): string | number => {
|
|
60
|
+
return item?.id ?? item?._id ?? JSON.stringify(item);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const itemIdGetter = getItemId || defaultGetItemId;
|
|
64
|
+
|
|
65
|
+
// Initialize temp selected when modal opens
|
|
66
|
+
$effect(() => {
|
|
67
|
+
if (open) {
|
|
68
|
+
tempSelected = [...(selectedItems || [])];
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const defaultSearchFunction = (item: any, term: string): boolean => {
|
|
73
|
+
if (!term?.trim()) return true;
|
|
74
|
+
const searchLower = term.toLowerCase().trim();
|
|
75
|
+
const searchableFields = Object.values(item).filter(
|
|
76
|
+
(v) => typeof v === 'string'
|
|
77
|
+
);
|
|
78
|
+
return searchableFields.some((field) =>
|
|
79
|
+
(field as string).toLowerCase().includes(searchLower)
|
|
80
|
+
);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const filteredItems = $derived(() => {
|
|
84
|
+
if (!searchTerm?.trim()) return items;
|
|
85
|
+
const filterFn = searchFunction || defaultSearchFunction;
|
|
86
|
+
return items.filter((item) => filterFn(item, searchTerm));
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const isSelected = (item: any): boolean => {
|
|
90
|
+
const itemId = itemIdGetter(item);
|
|
91
|
+
return tempSelected.some(sel => itemIdGetter(sel) === itemId);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const toggleSelection = (item: any) => {
|
|
95
|
+
const itemId = itemIdGetter(item);
|
|
96
|
+
const index = tempSelected.findIndex(sel => itemIdGetter(sel) === itemId);
|
|
97
|
+
|
|
98
|
+
if (index > -1) {
|
|
99
|
+
// Remove from selection
|
|
100
|
+
tempSelected = tempSelected.filter((_, i) => i !== index);
|
|
101
|
+
} else {
|
|
102
|
+
// Add to selection
|
|
103
|
+
tempSelected = [...tempSelected, item];
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const handleConfirm = async () => {
|
|
108
|
+
if (isConfirming) return;
|
|
109
|
+
isConfirming = true;
|
|
110
|
+
try {
|
|
111
|
+
await tick();
|
|
112
|
+
selectedItems = [...tempSelected];
|
|
113
|
+
await onConfirm(tempSelected);
|
|
114
|
+
searchTerm = '';
|
|
115
|
+
if (!keepOpenOnConfirm) {
|
|
116
|
+
open = false;
|
|
117
|
+
}
|
|
118
|
+
} finally {
|
|
119
|
+
isConfirming = false;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const handleClose = () => {
|
|
124
|
+
searchTerm = '';
|
|
125
|
+
tempSelected = [...(selectedItems || [])]; // Reset to original
|
|
126
|
+
onClose();
|
|
127
|
+
open = false;
|
|
128
|
+
};
|
|
129
|
+
</script>
|
|
130
|
+
|
|
131
|
+
<Modal
|
|
132
|
+
{open}
|
|
133
|
+
{title}
|
|
134
|
+
{description}
|
|
135
|
+
onClose={handleClose}
|
|
136
|
+
{size}
|
|
137
|
+
{color}
|
|
138
|
+
cancelText="İptal"
|
|
139
|
+
confirmText={confirmText}
|
|
140
|
+
onCancel={handleClose}
|
|
141
|
+
onConfirm={handleConfirm}
|
|
142
|
+
confirmLoading={isConfirming}
|
|
143
|
+
confirmDisabled={isConfirming}
|
|
144
|
+
>
|
|
145
|
+
{#snippet children()}
|
|
146
|
+
<div class="space-y-4">
|
|
147
|
+
<!-- Search Input -->
|
|
148
|
+
<div>
|
|
149
|
+
<InputText
|
|
150
|
+
label={searchLabel}
|
|
151
|
+
bind:value={searchTerm}
|
|
152
|
+
placeholder={searchPlaceholder}
|
|
153
|
+
/>
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
<!-- Selected Count -->
|
|
157
|
+
{#if tempSelected.length > 0}
|
|
158
|
+
<div class="space-y-3">
|
|
159
|
+
<div class="text-sm text-{color}-600 font-medium">
|
|
160
|
+
{tempSelected.length} öğe seçildi
|
|
161
|
+
</div>
|
|
162
|
+
|
|
163
|
+
<!-- Selected Items Display -->
|
|
164
|
+
<div class="flex flex-wrap gap-2 p-3 bg-{color}-50 rounded-lg border border-{color}-200">
|
|
165
|
+
{#each tempSelected as selectedItem}
|
|
166
|
+
<span class="inline-flex items-center gap-1.5 px-3 py-1.5 bg-{color}-100 text-{color}-700 rounded-full text-sm font-medium">
|
|
167
|
+
{#if renderItem}
|
|
168
|
+
<span class="truncate max-w-32">
|
|
169
|
+
{#if typeof selectedItem === 'object' && selectedItem !== null}
|
|
170
|
+
{selectedItem.username || selectedItem.ad || selectedItem.name || selectedItem.title || 'Öğe'}
|
|
171
|
+
{:else}
|
|
172
|
+
{selectedItem}
|
|
173
|
+
{/if}
|
|
174
|
+
</span>
|
|
175
|
+
{:else}
|
|
176
|
+
<span class="truncate max-w-32">
|
|
177
|
+
{#if typeof selectedItem === 'object' && selectedItem !== null}
|
|
178
|
+
{selectedItem.username || selectedItem.ad || selectedItem.name || selectedItem.title || 'Öğe'}
|
|
179
|
+
{:else}
|
|
180
|
+
{selectedItem}
|
|
181
|
+
{/if}
|
|
182
|
+
</span>
|
|
183
|
+
{/if}
|
|
184
|
+
<button
|
|
185
|
+
type="button"
|
|
186
|
+
onclick={() => toggleSelection(selectedItem)}
|
|
187
|
+
class="ml-1 w-4 h-4 rounded-full bg-{color}-200 hover:bg-{color}-300 flex items-center justify-center transition-colors"
|
|
188
|
+
aria-label="Kaldır"
|
|
189
|
+
>
|
|
190
|
+
<svg class="w-3 h-3 text-{color}-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
191
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
|
192
|
+
</svg>
|
|
193
|
+
</button>
|
|
194
|
+
</span>
|
|
195
|
+
{/each}
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
198
|
+
{/if}
|
|
199
|
+
|
|
200
|
+
<!-- Items List -->
|
|
201
|
+
<div class="max-h-96 overflow-y-auto space-y-2">
|
|
202
|
+
{#if filteredItems().length === 0}
|
|
203
|
+
<div class="text-center py-8 text-gray-500">
|
|
204
|
+
<svg class="w-16 h-16 mx-auto mb-3 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
205
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
206
|
+
</svg>
|
|
207
|
+
<p>{emptyMessage}</p>
|
|
208
|
+
</div>
|
|
209
|
+
{:else}
|
|
210
|
+
{#each filteredItems() as item}
|
|
211
|
+
<label
|
|
212
|
+
class="flex items-start gap-3 p-4 rounded-xl border border-gray-200 hover:border-{color}-500 hover:bg-{color}-50 transition-all cursor-pointer {isSelected(item) ? 'bg-' + color + '-50 border-' + color + '-500' : ''}"
|
|
213
|
+
>
|
|
214
|
+
<input
|
|
215
|
+
type="checkbox"
|
|
216
|
+
checked={isSelected(item)}
|
|
217
|
+
onchange={() => toggleSelection(item)}
|
|
218
|
+
class="mt-1 w-4 h-4 text-{color}-600 border-gray-300 rounded focus:ring-{color}-500"
|
|
219
|
+
/>
|
|
220
|
+
<div class="flex-1 min-w-0">
|
|
221
|
+
{#if renderItem}
|
|
222
|
+
{@render renderItem(item)}
|
|
223
|
+
{:else}
|
|
224
|
+
<div class="text-gray-900 font-medium">
|
|
225
|
+
{JSON.stringify(item)}
|
|
226
|
+
</div>
|
|
227
|
+
{/if}
|
|
228
|
+
</div>
|
|
229
|
+
</label>
|
|
230
|
+
{/each}
|
|
231
|
+
{/if}
|
|
232
|
+
</div>
|
|
233
|
+
</div>
|
|
234
|
+
{/snippet}
|
|
235
|
+
</Modal>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
interface Props<T = any> {
|
|
3
|
+
open?: boolean;
|
|
4
|
+
title?: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
items?: T[];
|
|
7
|
+
selectedItems?: T[];
|
|
8
|
+
searchPlaceholder?: string;
|
|
9
|
+
searchLabel?: string;
|
|
10
|
+
emptyMessage?: string;
|
|
11
|
+
onConfirm?: (selectedItems: T[]) => void;
|
|
12
|
+
onClose?: () => void;
|
|
13
|
+
color?: 'blue' | 'emerald' | 'red' | 'amber' | 'gray';
|
|
14
|
+
size?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl';
|
|
15
|
+
renderItem?: Snippet<[T]>;
|
|
16
|
+
searchFunction?: (item: T, searchTerm: string) => boolean;
|
|
17
|
+
getItemId?: (item: T) => string | number;
|
|
18
|
+
confirmText?: string;
|
|
19
|
+
keepOpenOnConfirm?: boolean;
|
|
20
|
+
}
|
|
21
|
+
declare const InputCheckboxModal: import("svelte").Component<Props<any>, {}, "open" | "selectedItems">;
|
|
22
|
+
type InputCheckboxModal = ReturnType<typeof InputCheckboxModal>;
|
|
23
|
+
export default InputCheckboxModal;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as InputCheckboxModal } from './InputCheckboxModal.svelte';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as InputCheckboxModal } from './InputCheckboxModal.svelte';
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Modal from '../Modal/Modal.svelte';
|
|
3
|
+
import InputText from '../inputs/InputText/InputText.svelte';
|
|
4
|
+
import type { Snippet } from 'svelte';
|
|
5
|
+
|
|
6
|
+
interface Props<T = any> {
|
|
7
|
+
open?: boolean;
|
|
8
|
+
title?: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
items?: T[];
|
|
11
|
+
searchPlaceholder?: string;
|
|
12
|
+
searchLabel?: string;
|
|
13
|
+
emptyMessage?: string;
|
|
14
|
+
onSelect?: (item: T) => void;
|
|
15
|
+
onClose?: () => void;
|
|
16
|
+
color?: 'blue' | 'emerald' | 'red' | 'amber' | 'gray';
|
|
17
|
+
size?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl';
|
|
18
|
+
renderItem?: Snippet<[T]>;
|
|
19
|
+
searchFunction?: (item: T, searchTerm: string) => boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let {
|
|
23
|
+
open = $bindable(false),
|
|
24
|
+
title = 'Öğe Seç',
|
|
25
|
+
description = 'Listeden bir öğe seçin',
|
|
26
|
+
items = [],
|
|
27
|
+
searchPlaceholder = 'Ara...',
|
|
28
|
+
searchLabel = 'Ara',
|
|
29
|
+
emptyMessage = 'Öğe bulunamadı',
|
|
30
|
+
onSelect = () => {},
|
|
31
|
+
onClose = () => {},
|
|
32
|
+
color = 'emerald',
|
|
33
|
+
size = '2xl',
|
|
34
|
+
renderItem,
|
|
35
|
+
searchFunction
|
|
36
|
+
}: Props = $props();
|
|
37
|
+
|
|
38
|
+
let searchTerm = $state('');
|
|
39
|
+
|
|
40
|
+
const defaultSearchFunction = (item: any, term: string): boolean => {
|
|
41
|
+
if (!term?.trim()) return true;
|
|
42
|
+
const searchLower = term.toLowerCase().trim();
|
|
43
|
+
const searchableFields = Object.values(item).filter(
|
|
44
|
+
(v) => typeof v === 'string'
|
|
45
|
+
);
|
|
46
|
+
return searchableFields.some((field) =>
|
|
47
|
+
(field as string).toLowerCase().includes(searchLower)
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const filteredItems = $derived(() => {
|
|
52
|
+
if (!searchTerm?.trim()) return items;
|
|
53
|
+
const filterFn = searchFunction || defaultSearchFunction;
|
|
54
|
+
return items.filter((item) => filterFn(item, searchTerm));
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const handleSelect = (item: any) => {
|
|
58
|
+
onSelect(item);
|
|
59
|
+
searchTerm = '';
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const handleClose = () => {
|
|
63
|
+
searchTerm = '';
|
|
64
|
+
onClose();
|
|
65
|
+
};
|
|
66
|
+
</script>
|
|
67
|
+
|
|
68
|
+
<Modal
|
|
69
|
+
{open}
|
|
70
|
+
{title}
|
|
71
|
+
{description}
|
|
72
|
+
onClose={handleClose}
|
|
73
|
+
{size}
|
|
74
|
+
{color}
|
|
75
|
+
cancelText="Kapat"
|
|
76
|
+
onCancel={handleClose}
|
|
77
|
+
>
|
|
78
|
+
{#snippet children()}
|
|
79
|
+
<div class="space-y-4" data-component-name="InputSelectModal">
|
|
80
|
+
<!-- Search Input -->
|
|
81
|
+
<div>
|
|
82
|
+
<InputText
|
|
83
|
+
label={searchLabel}
|
|
84
|
+
bind:value={searchTerm}
|
|
85
|
+
placeholder={searchPlaceholder}
|
|
86
|
+
/>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<!-- Items List -->
|
|
90
|
+
<div class="max-h-96 overflow-y-auto space-y-2">
|
|
91
|
+
{#if filteredItems().length === 0}
|
|
92
|
+
<div class="text-center py-8 text-gray-500">
|
|
93
|
+
<svg class="w-16 h-16 mx-auto mb-3 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
94
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
95
|
+
</svg>
|
|
96
|
+
<p>{emptyMessage}</p>
|
|
97
|
+
</div>
|
|
98
|
+
{:else}
|
|
99
|
+
{#each filteredItems() as item}
|
|
100
|
+
<button
|
|
101
|
+
type="button"
|
|
102
|
+
onclick={() => handleSelect(item)}
|
|
103
|
+
class="w-full text-left p-4 rounded-xl border border-gray-200 hover:border-{color}-500 hover:bg-{color}-50 transition-all group"
|
|
104
|
+
>
|
|
105
|
+
{#if renderItem}
|
|
106
|
+
{@render renderItem(item)}
|
|
107
|
+
{:else}
|
|
108
|
+
<div class="text-gray-900 font-medium">
|
|
109
|
+
{JSON.stringify(item)}
|
|
110
|
+
</div>
|
|
111
|
+
{/if}
|
|
112
|
+
</button>
|
|
113
|
+
{/each}
|
|
114
|
+
{/if}
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
{/snippet}
|
|
118
|
+
</Modal>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
interface Props<T = any> {
|
|
3
|
+
open?: boolean;
|
|
4
|
+
title?: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
items?: T[];
|
|
7
|
+
searchPlaceholder?: string;
|
|
8
|
+
searchLabel?: string;
|
|
9
|
+
emptyMessage?: string;
|
|
10
|
+
onSelect?: (item: T) => void;
|
|
11
|
+
onClose?: () => void;
|
|
12
|
+
color?: 'blue' | 'emerald' | 'red' | 'amber' | 'gray';
|
|
13
|
+
size?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl';
|
|
14
|
+
renderItem?: Snippet<[T]>;
|
|
15
|
+
searchFunction?: (item: T, searchTerm: string) => boolean;
|
|
16
|
+
}
|
|
17
|
+
declare const InputSelectModal: import("svelte").Component<Props<any>, {}, "open">;
|
|
18
|
+
type InputSelectModal = ReturnType<typeof InputSelectModal>;
|
|
19
|
+
export default InputSelectModal;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as InputSelectModal } from './InputSelectModal.svelte';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as InputSelectModal } from './InputSelectModal.svelte';
|
package/dist/Modal/Modal.svelte
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { tick } from 'svelte';
|
|
2
3
|
import type { Snippet } from 'svelte';
|
|
3
4
|
|
|
4
5
|
interface Props {
|
|
@@ -12,6 +13,13 @@
|
|
|
12
13
|
onClose?: () => void;
|
|
13
14
|
children?: Snippet;
|
|
14
15
|
actions?: Snippet;
|
|
16
|
+
// Otomatik buton props
|
|
17
|
+
cancelText?: string;
|
|
18
|
+
confirmText?: string;
|
|
19
|
+
onCancel?: () => void;
|
|
20
|
+
onConfirm?: () => void;
|
|
21
|
+
confirmDisabled?: boolean;
|
|
22
|
+
confirmLoading?: boolean;
|
|
15
23
|
}
|
|
16
24
|
|
|
17
25
|
let {
|
|
@@ -24,9 +32,31 @@
|
|
|
24
32
|
showClose = true,
|
|
25
33
|
onClose,
|
|
26
34
|
children,
|
|
27
|
-
actions
|
|
35
|
+
actions,
|
|
36
|
+
cancelText,
|
|
37
|
+
confirmText,
|
|
38
|
+
onCancel,
|
|
39
|
+
onConfirm,
|
|
40
|
+
confirmDisabled = false,
|
|
41
|
+
confirmLoading = false
|
|
28
42
|
}: Props = $props();
|
|
29
43
|
|
|
44
|
+
let internalLoading = false;
|
|
45
|
+
|
|
46
|
+
const isBusy = () => confirmLoading || internalLoading;
|
|
47
|
+
|
|
48
|
+
const handleConfirm = async () => {
|
|
49
|
+
if (confirmDisabled || isBusy()) return;
|
|
50
|
+
internalLoading = true;
|
|
51
|
+
try {
|
|
52
|
+
await tick();
|
|
53
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
54
|
+
await onConfirm?.();
|
|
55
|
+
} finally {
|
|
56
|
+
internalLoading = false;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
30
60
|
// Modal açıkken body scroll'u kapat
|
|
31
61
|
$effect(() => {
|
|
32
62
|
if (open) {
|
|
@@ -62,6 +92,18 @@
|
|
|
62
92
|
purple: 'from-purple-600 to-indigo-600',
|
|
63
93
|
gray: 'from-gray-600 to-slate-600'
|
|
64
94
|
};
|
|
95
|
+
|
|
96
|
+
// Buton renkleri (confirm button için)
|
|
97
|
+
const buttonColors: Record<string, string> = {
|
|
98
|
+
blue: 'bg-blue-600 hover:bg-blue-700',
|
|
99
|
+
green: 'bg-green-600 hover:bg-green-700',
|
|
100
|
+
emerald: 'bg-emerald-600 hover:bg-emerald-700',
|
|
101
|
+
teal: 'bg-teal-600 hover:bg-teal-700',
|
|
102
|
+
amber: 'bg-amber-500 hover:bg-amber-600',
|
|
103
|
+
red: 'bg-red-600 hover:bg-red-700',
|
|
104
|
+
purple: 'bg-purple-600 hover:bg-purple-700',
|
|
105
|
+
gray: 'bg-gray-600 hover:bg-gray-700'
|
|
106
|
+
};
|
|
65
107
|
</script>
|
|
66
108
|
|
|
67
109
|
{#if open}
|
|
@@ -71,6 +113,7 @@
|
|
|
71
113
|
class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm"
|
|
72
114
|
role="dialog"
|
|
73
115
|
aria-modal="true"
|
|
116
|
+
data-component-name="Modal"
|
|
74
117
|
onclick={(e) => e.target === e.currentTarget && onClose?.()}
|
|
75
118
|
>
|
|
76
119
|
<div
|
|
@@ -113,9 +156,38 @@
|
|
|
113
156
|
|
|
114
157
|
<!-- Footer Actions -->
|
|
115
158
|
{#if actions}
|
|
116
|
-
<div class="px-6 py-4 bg-gray-50 border-t border-gray-100 shrink-0">
|
|
159
|
+
<div class="px-6 py-4 bg-gray-50 border-t border-gray-100 shrink-0 flex items-center justify-end gap-3">
|
|
117
160
|
{@render actions()}
|
|
118
161
|
</div>
|
|
162
|
+
{:else if cancelText || confirmText}
|
|
163
|
+
<div class="px-6 py-4 bg-gray-50 border-t border-gray-100 shrink-0 flex items-center justify-end gap-3">
|
|
164
|
+
{#if cancelText}
|
|
165
|
+
<button
|
|
166
|
+
type="button"
|
|
167
|
+
onclick={onCancel || onClose}
|
|
168
|
+
disabled={isBusy()}
|
|
169
|
+
class="rounded-lg border border-gray-300 bg-white px-4 py-2 text-gray-700 hover:bg-gray-100 hover:border-gray-400 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200"
|
|
170
|
+
>
|
|
171
|
+
{cancelText}
|
|
172
|
+
</button>
|
|
173
|
+
{/if}
|
|
174
|
+
{#if confirmText}
|
|
175
|
+
<button
|
|
176
|
+
type="button"
|
|
177
|
+
onclick={handleConfirm}
|
|
178
|
+
disabled={confirmDisabled || isBusy()}
|
|
179
|
+
class="rounded-lg {buttonColors[color]} px-4 py-2 text-white disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2 transition-all duration-200 shadow-sm hover:shadow-md active:scale-95"
|
|
180
|
+
>
|
|
181
|
+
{#if isBusy()}
|
|
182
|
+
<svg class="h-4 w-4 animate-spin" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
183
|
+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
184
|
+
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"></path>
|
|
185
|
+
</svg>
|
|
186
|
+
{/if}
|
|
187
|
+
{confirmText}
|
|
188
|
+
</button>
|
|
189
|
+
{/if}
|
|
190
|
+
</div>
|
|
119
191
|
{/if}
|
|
120
192
|
</div>
|
|
121
193
|
</div>
|
|
@@ -4,11 +4,18 @@ interface Props {
|
|
|
4
4
|
title: string;
|
|
5
5
|
description?: string;
|
|
6
6
|
icon?: string;
|
|
7
|
-
color?: 'blue' | 'green' | 'amber' | 'red' | 'purple' | 'gray';
|
|
8
|
-
size?: 'sm' | 'md' | 'lg';
|
|
7
|
+
color?: 'blue' | 'green' | 'amber' | 'red' | 'purple' | 'gray' | 'emerald' | 'teal';
|
|
8
|
+
size?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | 'full';
|
|
9
9
|
showClose?: boolean;
|
|
10
10
|
onClose?: () => void;
|
|
11
|
+
children?: Snippet;
|
|
11
12
|
actions?: Snippet;
|
|
13
|
+
cancelText?: string;
|
|
14
|
+
confirmText?: string;
|
|
15
|
+
onCancel?: () => void;
|
|
16
|
+
onConfirm?: () => void;
|
|
17
|
+
confirmDisabled?: boolean;
|
|
18
|
+
confirmLoading?: boolean;
|
|
12
19
|
}
|
|
13
20
|
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
14
21
|
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
2
4
|
interface Props {
|
|
3
5
|
label: string;
|
|
4
6
|
onclick: () => void;
|
|
5
7
|
color?: 'green' | 'blue' | 'red' | 'gray';
|
|
8
|
+
showDefaultIcon?: boolean;
|
|
9
|
+
icon?: Snippet;
|
|
6
10
|
}
|
|
7
11
|
|
|
8
|
-
let { label, onclick, color = 'green' }: Props = $props();
|
|
12
|
+
let { label, onclick, color = 'green', showDefaultIcon = true, icon }: Props = $props();
|
|
9
13
|
|
|
10
14
|
const colorClasses = {
|
|
11
15
|
green: 'bg-green-500 hover:bg-green-600',
|
|
@@ -19,8 +23,12 @@
|
|
|
19
23
|
class="p-2 md:px-4 md:py-2 {colorClasses[color]} text-white rounded-lg transition-colors flex items-center justify-center gap-2"
|
|
20
24
|
{onclick}
|
|
21
25
|
>
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
{#if icon}
|
|
27
|
+
{@render icon()}
|
|
28
|
+
{:else if showDefaultIcon}
|
|
29
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
|
30
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
|
31
|
+
</svg>
|
|
32
|
+
{/if}
|
|
25
33
|
<span class="hidden md:inline text-sm font-semibold">{label}</span>
|
|
26
34
|
</button>
|