urpanels-ui-pack 0.0.3 → 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.
Files changed (51) hide show
  1. package/dist/BasePageService/BasePageService.d.ts +121 -0
  2. package/dist/BasePageService/BasePageService.js +191 -0
  3. package/dist/BasePageService/index.d.ts +2 -0
  4. package/dist/BasePageService/index.js +1 -0
  5. package/dist/Button/Button.svelte +1 -0
  6. package/dist/InfoCard/InfoCard.svelte +61 -30
  7. package/dist/InputCheckboxModal/InputCheckboxModal.svelte +235 -0
  8. package/dist/InputCheckboxModal/InputCheckboxModal.svelte.d.ts +23 -0
  9. package/dist/InputCheckboxModal/index.d.ts +1 -0
  10. package/dist/InputCheckboxModal/index.js +1 -0
  11. package/dist/InputSelectModal/InputSelectModal.svelte +118 -0
  12. package/dist/InputSelectModal/InputSelectModal.svelte.d.ts +19 -0
  13. package/dist/InputSelectModal/index.d.ts +1 -0
  14. package/dist/InputSelectModal/index.js +1 -0
  15. package/dist/LoadingSpinner/LoadingSpinner.svelte +1 -1
  16. package/dist/Modal/Modal.svelte +74 -2
  17. package/dist/Modal/Modal.svelte.d.ts +9 -2
  18. package/dist/PageLayout/ActionButton.svelte +12 -4
  19. package/dist/PageLayout/PageContent.svelte +1 -1
  20. package/dist/PageLayout/PageHeader.svelte +75 -15
  21. package/dist/PageLayout/PageHeader.svelte.d.ts +10 -1
  22. package/dist/PageLayout/SearchBar.svelte +1 -1
  23. package/dist/PageLayout/ViewToggle.svelte +1 -1
  24. package/dist/Pagination/Pagination.svelte +1 -1
  25. package/dist/index.d.ts +1 -0
  26. package/dist/index.js +8 -0
  27. package/dist/inputs/InputNumber/InputNumber.svelte +140 -0
  28. package/dist/inputs/InputNumber/InputNumber.svelte.d.ts +27 -0
  29. package/dist/inputs/InputNumber/index.d.ts +2 -0
  30. package/dist/inputs/InputNumber/index.js +2 -0
  31. package/dist/inputs/InputSelect/InputSelect.svelte +73 -0
  32. package/dist/inputs/InputSelect/InputSelect.svelte.d.ts +18 -0
  33. package/dist/inputs/InputSelect/index.d.ts +2 -0
  34. package/dist/inputs/InputSelect/index.js +2 -0
  35. package/dist/inputs/InputText/InputText.svelte +126 -0
  36. package/dist/inputs/InputText/InputText.svelte.d.ts +49 -0
  37. package/dist/inputs/InputText/index.d.ts +2 -0
  38. package/dist/inputs/InputText/index.js +2 -0
  39. package/dist/inputs/TextArea/TextArea.svelte +113 -0
  40. package/dist/inputs/TextArea/TextArea.svelte.d.ts +21 -0
  41. package/dist/inputs/TextArea/index.d.ts +2 -0
  42. package/dist/inputs/TextArea/index.js +2 -0
  43. package/dist/inputs/index.d.ts +3 -0
  44. package/dist/inputs/index.js +4 -0
  45. package/dist/sections/PreviewSelector/PreviewSelector.svelte +164 -0
  46. package/dist/sections/PreviewSelector/PreviewSelector.svelte.d.ts +25 -0
  47. package/dist/sections/PreviewSelector/index.d.ts +2 -0
  48. package/dist/sections/PreviewSelector/index.js +1 -0
  49. package/dist/sections/index.d.ts +2 -0
  50. package/dist/sections/index.js +1 -0
  51. 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';
@@ -1,3 +1,3 @@
1
- <div class="flex items-center justify-center py-12">
1
+ <div class="flex items-center justify-center py-12" data-component-name="LoadingSpinner">
2
2
  <div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
3
3
  </div>
@@ -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
- <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
23
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
24
- </svg>
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>
@@ -10,7 +10,7 @@
10
10
  let { children, isLoading = false }: Props = $props();
11
11
  </script>
12
12
 
13
- <div class="px-4 sm:px-6 pb-6">
13
+ <div class="px-4 sm:px-6 pb-6" data-component-name="PageContent">
14
14
  {#if isLoading}
15
15
  <LoadingSpinner />
16
16
  {:else}