urpanels-ui-pack 0.0.2
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 +37 -0
- package/dist/Button/Button.svelte +76 -0
- package/dist/Button/Button.svelte.d.ts +13 -0
- package/dist/Button/index.d.ts +1 -0
- package/dist/Button/index.js +1 -0
- package/dist/InfoCard/InfoCard.svelte +227 -0
- package/dist/InfoCard/InfoCard.svelte.d.ts +4 -0
- package/dist/InfoCard/InfoCard.types.d.ts +56 -0
- package/dist/InfoCard/InfoCard.types.js +1 -0
- package/dist/InfoCard/index.d.ts +2 -0
- package/dist/InfoCard/index.js +1 -0
- package/dist/LoadingSpinner/LoadingSpinner.svelte +3 -0
- package/dist/LoadingSpinner/LoadingSpinner.svelte.d.ts +26 -0
- package/dist/LoadingSpinner/index.d.ts +1 -0
- package/dist/LoadingSpinner/index.js +1 -0
- package/dist/Modal/Modal.svelte +122 -0
- package/dist/Modal/Modal.svelte.d.ts +39 -0
- package/dist/Modal/index.d.ts +1 -0
- package/dist/Modal/index.js +1 -0
- package/dist/PageLayout/ActionButton.svelte +26 -0
- package/dist/PageLayout/ActionButton.svelte.d.ts +8 -0
- package/dist/PageLayout/PageContent.svelte +19 -0
- package/dist/PageLayout/PageContent.svelte.d.ts +8 -0
- package/dist/PageLayout/PageHeader.svelte +29 -0
- package/dist/PageLayout/PageHeader.svelte.d.ts +9 -0
- package/dist/PageLayout/SearchBar.svelte +25 -0
- package/dist/PageLayout/SearchBar.svelte.d.ts +8 -0
- package/dist/PageLayout/ViewToggle.svelte +53 -0
- package/dist/PageLayout/ViewToggle.svelte.d.ts +8 -0
- package/dist/PageLayout/index.d.ts +5 -0
- package/dist/PageLayout/index.js +5 -0
- package/dist/Pagination/Pagination.svelte +96 -0
- package/dist/Pagination/Pagination.svelte.d.ts +4 -0
- package/dist/Pagination/Pagination.types.d.ts +11 -0
- package/dist/Pagination/Pagination.types.js +1 -0
- package/dist/Pagination/index.d.ts +2 -0
- package/dist/Pagination/index.js +1 -0
- package/dist/RichTextEditor/RichTextEditor.svelte +575 -0
- package/dist/RichTextEditor/RichTextEditor.svelte.d.ts +10 -0
- package/dist/RichTextEditor/index.d.ts +1 -0
- package/dist/RichTextEditor/index.js +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +12 -0
- package/dist/sections/ArticlesGrid/ArticlesGrid.svelte +201 -0
- package/dist/sections/ArticlesGrid/ArticlesGrid.svelte.d.ts +31 -0
- package/dist/sections/ArticlesGrid/README.md +229 -0
- package/dist/sections/ArticlesGrid/index.d.ts +2 -0
- package/dist/sections/ArticlesGrid/index.js +1 -0
- package/dist/sections/FeaturedGalleryGrid/FeaturedGalleryGrid.svelte +118 -0
- package/dist/sections/FeaturedGalleryGrid/FeaturedGalleryGrid.svelte.d.ts +21 -0
- package/dist/sections/FeaturedGalleryGrid/README.md +270 -0
- package/dist/sections/FeaturedGalleryGrid/index.d.ts +2 -0
- package/dist/sections/FeaturedGalleryGrid/index.js +1 -0
- package/dist/sections/HeroCarousel/HeroCarousel.svelte +318 -0
- package/dist/sections/HeroCarousel/HeroCarousel.svelte.d.ts +23 -0
- package/dist/sections/HeroCarousel/index.d.ts +1 -0
- package/dist/sections/HeroCarousel/index.js +1 -0
- package/dist/sections/index.d.ts +5 -0
- package/dist/sections/index.js +3 -0
- package/dist/utils/translations.svelte.d.ts +37 -0
- package/dist/utils/translations.svelte.js +67 -0
- package/package.json +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# urpanels-ui-pack
|
|
2
|
+
|
|
3
|
+
Svelte 5 tabanlı urPanels projeleri için tekrar kullanılabilir UI bileşenleri. İçerik şu an PageLayout parçaları, Pagination, LoadingSpinner, InfoCard ve çeviri helper içeriyor.
|
|
4
|
+
|
|
5
|
+
## Kurulum
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install urpanels-ui-pack
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Peer dependency olarak Svelte 5 gerekir.
|
|
12
|
+
|
|
13
|
+
## Kullanım
|
|
14
|
+
|
|
15
|
+
```svelte
|
|
16
|
+
<script lang="ts">
|
|
17
|
+
import { PageHeader, PageContent, SearchBar, ViewToggle, ActionButton, Pagination, LoadingSpinner, InfoCard } from 'urpanels-ui-pack';
|
|
18
|
+
</script>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Geliştirme
|
|
22
|
+
|
|
23
|
+
- Tip kontrolü: `npm run check`
|
|
24
|
+
- Paketleme: `npm run build` (svelte-package ile `dist` altına paketler ve tipleri üretir)
|
|
25
|
+
|
|
26
|
+
## İçerik
|
|
27
|
+
|
|
28
|
+
- PageLayout: PageHeader, PageContent, SearchBar, ViewToggle, ActionButton
|
|
29
|
+
- Pagination
|
|
30
|
+
- LoadingSpinner
|
|
31
|
+
- InfoCard (+ tipleri)
|
|
32
|
+
- utils/translations.svelte.ts (global window üstünden çeviri helper)
|
|
33
|
+
|
|
34
|
+
## Notlar
|
|
35
|
+
|
|
36
|
+
- Yayın öncesi `npm run build` `dist` altında bundle ve `.d.ts` üretir.
|
|
37
|
+
- `package.json` `exports` alanı Svelte + ESM + CJS girişlerini ve tipleri işaret eder.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Button Component - UI'dan bağımsız, loading ve disabled state yöneten buton
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* <Button
|
|
7
|
+
* loading={isLoading}
|
|
8
|
+
* disabled={isDisabled}
|
|
9
|
+
* onclick={handleClick}
|
|
10
|
+
* class="bg-blue-500 text-white px-4 py-2 rounded"
|
|
11
|
+
* >
|
|
12
|
+
* Kaydet
|
|
13
|
+
* </Button>
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
let {
|
|
17
|
+
onclick = undefined,
|
|
18
|
+
disabled = false,
|
|
19
|
+
loading = false,
|
|
20
|
+
type = 'button',
|
|
21
|
+
class: className = '',
|
|
22
|
+
loadingText = undefined,
|
|
23
|
+
children,
|
|
24
|
+
...rest
|
|
25
|
+
}: {
|
|
26
|
+
onclick?: (e: MouseEvent) => void | Promise<void>;
|
|
27
|
+
disabled?: boolean;
|
|
28
|
+
loading?: boolean;
|
|
29
|
+
type?: 'button' | 'submit' | 'reset';
|
|
30
|
+
class?: string;
|
|
31
|
+
loadingText?: string;
|
|
32
|
+
children?: any;
|
|
33
|
+
[key: string]: any;
|
|
34
|
+
} = $props();
|
|
35
|
+
|
|
36
|
+
// Loading durumunda otomatik disable
|
|
37
|
+
const isDisabled = $derived(disabled || loading);
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
<button
|
|
41
|
+
{type}
|
|
42
|
+
disabled={isDisabled}
|
|
43
|
+
onclick={onclick}
|
|
44
|
+
class={className}
|
|
45
|
+
{...rest}
|
|
46
|
+
>
|
|
47
|
+
{#if loading}
|
|
48
|
+
<span class="flex items-center justify-center gap-2">
|
|
49
|
+
<!-- Spinner SVG -->
|
|
50
|
+
<svg
|
|
51
|
+
class="animate-spin h-4 w-4"
|
|
52
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
53
|
+
fill="none"
|
|
54
|
+
viewBox="0 0 24 24"
|
|
55
|
+
>
|
|
56
|
+
<circle
|
|
57
|
+
class="opacity-25"
|
|
58
|
+
cx="12"
|
|
59
|
+
cy="12"
|
|
60
|
+
r="10"
|
|
61
|
+
stroke="currentColor"
|
|
62
|
+
stroke-width="4"
|
|
63
|
+
></circle>
|
|
64
|
+
<path
|
|
65
|
+
class="opacity-75"
|
|
66
|
+
fill="currentColor"
|
|
67
|
+
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
68
|
+
></path>
|
|
69
|
+
</svg>
|
|
70
|
+
<!-- Loading metni veya normal children -->
|
|
71
|
+
<span>{loadingText || ''}{#if !loadingText}{@render children()}{/if}</span>
|
|
72
|
+
</span>
|
|
73
|
+
{:else}
|
|
74
|
+
{@render children()}
|
|
75
|
+
{/if}
|
|
76
|
+
</button>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
onclick?: (e: MouseEvent) => void | Promise<void>;
|
|
3
|
+
disabled?: boolean;
|
|
4
|
+
loading?: boolean;
|
|
5
|
+
type?: 'button' | 'submit' | 'reset';
|
|
6
|
+
class?: string;
|
|
7
|
+
loadingText?: string;
|
|
8
|
+
children?: any;
|
|
9
|
+
[key: string]: any;
|
|
10
|
+
};
|
|
11
|
+
declare const Button: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
12
|
+
type Button = ReturnType<typeof Button>;
|
|
13
|
+
export default Button;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Button } from './Button.svelte';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Button } from './Button.svelte';
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { InfoCardProps } from './InfoCard.types';
|
|
3
|
+
import { t } from '../utils/translations.svelte';
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
title,
|
|
7
|
+
id,
|
|
8
|
+
variant = 'compact',
|
|
9
|
+
actionLayout = 'icons',
|
|
10
|
+
hover = false,
|
|
11
|
+
className = '',
|
|
12
|
+
subtitle,
|
|
13
|
+
subtitleIcon,
|
|
14
|
+
linkedStatus,
|
|
15
|
+
fields = [],
|
|
16
|
+
badges = [],
|
|
17
|
+
relatedItems,
|
|
18
|
+
note,
|
|
19
|
+
onView,
|
|
20
|
+
onEdit,
|
|
21
|
+
onDelete
|
|
22
|
+
}: InfoCardProps = $props();
|
|
23
|
+
|
|
24
|
+
const isCompact = $derived(variant === 'compact');
|
|
25
|
+
const hasActions = $derived(onView || onEdit || onDelete);
|
|
26
|
+
|
|
27
|
+
function truncateText(text: string, maxLength: number): string {
|
|
28
|
+
if (!text) return '';
|
|
29
|
+
return text.length > maxLength ? text.substring(0, maxLength) + '...' : text;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getBadgeColorClasses(color: string): string {
|
|
33
|
+
const colorMap: Record<string, string> = {
|
|
34
|
+
blue: 'bg-blue-100 text-blue-800 border-blue-200',
|
|
35
|
+
purple: 'bg-purple-100 text-purple-800 border-purple-200',
|
|
36
|
+
orange: 'bg-orange-100 text-orange-800 border-orange-200',
|
|
37
|
+
red: 'bg-red-100 text-red-800 border-red-200',
|
|
38
|
+
green: 'bg-green-100 text-green-800 border-green-200',
|
|
39
|
+
gray: 'bg-gray-100 text-gray-800 border-gray-200'
|
|
40
|
+
} as const;
|
|
41
|
+
return colorMap[color] || colorMap.gray;
|
|
42
|
+
}
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
<div
|
|
46
|
+
class="bg-white rounded-lg shadow {isCompact ? 'p-3' : 'p-4'} flex flex-col gap-2 {hover ? 'hover:shadow-md transition-shadow' : ''} {className} relative"
|
|
47
|
+
>
|
|
48
|
+
{#if linkedStatus?.isLinked}
|
|
49
|
+
<div class="absolute -top-1 -right-1 bg-indigo-500 text-white rounded-full w-5 h-5 flex items-center justify-center shadow-sm ring-2 ring-white" title="Kullanıcı hesabına bağlı">
|
|
50
|
+
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
51
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1" />
|
|
52
|
+
</svg>
|
|
53
|
+
</div>
|
|
54
|
+
{/if}
|
|
55
|
+
|
|
56
|
+
<div class="flex items-start justify-between gap-2">
|
|
57
|
+
<div class="flex-1 min-w-0">
|
|
58
|
+
<div class="flex items-center gap-2">
|
|
59
|
+
<h3 class="font-bold {isCompact ? 'text-sm' : 'text-lg'} truncate">
|
|
60
|
+
{title}
|
|
61
|
+
</h3>
|
|
62
|
+
{#if linkedStatus}
|
|
63
|
+
{#if !linkedStatus.isLinked && linkedStatus.hasEmail && linkedStatus.onLink}
|
|
64
|
+
<button
|
|
65
|
+
onclick={linkedStatus.onLink}
|
|
66
|
+
class="text-orange-600 {isCompact ? 'text-[10px] px-1.5 py-0.5' : 'text-xs px-2 py-1'} bg-orange-50 hover:bg-orange-100 rounded border border-orange-200 transition-colors"
|
|
67
|
+
>
|
|
68
|
+
{linkedStatus.linkText || t('common.link')}
|
|
69
|
+
</button>
|
|
70
|
+
{/if}
|
|
71
|
+
{/if}
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
{#if subtitle}
|
|
75
|
+
<div class="{isCompact ? 'text-[10px]' : 'text-xs'} text-gray-600 mt-0.5 truncate">
|
|
76
|
+
{#if subtitleIcon}<span class="mr-1">{subtitleIcon}</span>{/if}
|
|
77
|
+
{subtitle}
|
|
78
|
+
</div>
|
|
79
|
+
{/if}
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
{#if actionLayout === 'icons' && hasActions}
|
|
83
|
+
<div class="flex items-center gap-1 flex-shrink-0">
|
|
84
|
+
{#if onView}
|
|
85
|
+
<button
|
|
86
|
+
onclick={onView}
|
|
87
|
+
class="p-1.5 rounded hover:bg-green-50 text-green-600 transition-colors"
|
|
88
|
+
aria-label={t('common.view')}
|
|
89
|
+
>
|
|
90
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
91
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
|
92
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
|
93
|
+
</svg>
|
|
94
|
+
</button>
|
|
95
|
+
{/if}
|
|
96
|
+
{#if onEdit}
|
|
97
|
+
<button
|
|
98
|
+
onclick={onEdit}
|
|
99
|
+
class="p-1.5 rounded hover:bg-blue-50 text-blue-600 transition-colors"
|
|
100
|
+
aria-label={t('common.edit')}
|
|
101
|
+
>
|
|
102
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
103
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
|
104
|
+
</svg>
|
|
105
|
+
</button>
|
|
106
|
+
{/if}
|
|
107
|
+
{#if onDelete}
|
|
108
|
+
<button
|
|
109
|
+
onclick={onDelete}
|
|
110
|
+
class="p-1.5 rounded hover:bg-red-50 text-red-600 transition-colors"
|
|
111
|
+
aria-label={t('common.delete')}
|
|
112
|
+
>
|
|
113
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
114
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 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" />
|
|
115
|
+
</svg>
|
|
116
|
+
</button>
|
|
117
|
+
{/if}
|
|
118
|
+
</div>
|
|
119
|
+
{/if}
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
{#if fields && fields.length > 0}
|
|
123
|
+
<div class="grid grid-cols-2 gap-2 {isCompact ? 'text-xs' : 'text-sm'}">
|
|
124
|
+
{#each fields as field}
|
|
125
|
+
{#if field.condition !== false}
|
|
126
|
+
<div class="{field.span === 2 ? 'col-span-2' : ''} truncate">
|
|
127
|
+
<span class="font-semibold text-gray-700">{field.label}:</span>
|
|
128
|
+
{#if field.icon}<span class="mx-1">{field.icon}</span>{/if}
|
|
129
|
+
<span class="text-gray-900">{field.value || '-'}</span>
|
|
130
|
+
</div>
|
|
131
|
+
{/if}
|
|
132
|
+
{/each}
|
|
133
|
+
</div>
|
|
134
|
+
{/if}
|
|
135
|
+
|
|
136
|
+
{#if badges && badges.length > 0}
|
|
137
|
+
{#each badges as badgeGroup}
|
|
138
|
+
{#if badgeGroup.items && badgeGroup.items.length > 0}
|
|
139
|
+
{#snippet badgeSection()}
|
|
140
|
+
{@const displayItems = badgeGroup.maxDisplay ? badgeGroup.items.slice(0, badgeGroup.maxDisplay) : badgeGroup.items}
|
|
141
|
+
{@const remaining = badgeGroup.maxDisplay && badgeGroup.items.length > badgeGroup.maxDisplay ? badgeGroup.items.length - badgeGroup.maxDisplay : 0}
|
|
142
|
+
|
|
143
|
+
<div class="mt-1">
|
|
144
|
+
{#if badgeGroup.label}
|
|
145
|
+
<div class="{isCompact ? 'text-[10px]' : 'text-xs'} font-semibold text-gray-600 mb-1">
|
|
146
|
+
{#if badgeGroup.icon}<span class="mr-1">{badgeGroup.icon}</span>{/if}
|
|
147
|
+
{badgeGroup.label}
|
|
148
|
+
</div>
|
|
149
|
+
{/if}
|
|
150
|
+
<div class="flex flex-wrap gap-1">
|
|
151
|
+
{#each displayItems as item}
|
|
152
|
+
<span class="{isCompact ? 'text-[10px] px-1.5 py-0.5' : 'text-xs px-2 py-1'} rounded border {getBadgeColorClasses(badgeGroup.color)}">
|
|
153
|
+
{item}
|
|
154
|
+
</span>
|
|
155
|
+
{/each}
|
|
156
|
+
|
|
157
|
+
{#if remaining > 0}
|
|
158
|
+
<span class="{isCompact ? 'text-[10px] px-1.5 py-0.5' : 'text-xs px-2 py-1'} rounded border {getBadgeColorClasses(badgeGroup.color)}">
|
|
159
|
+
+{remaining}
|
|
160
|
+
</span>
|
|
161
|
+
{/if}
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
{/snippet}
|
|
165
|
+
|
|
166
|
+
{@render badgeSection()}
|
|
167
|
+
{/if}
|
|
168
|
+
{/each}
|
|
169
|
+
{/if}
|
|
170
|
+
|
|
171
|
+
{#if relatedItems && relatedItems.items && relatedItems.items.length > 0}
|
|
172
|
+
<div class="mt-2 border border-gray-200 rounded p-2 bg-gray-50">
|
|
173
|
+
<div class="{isCompact ? 'text-xs' : 'text-sm'} font-semibold text-gray-700 mb-1.5">
|
|
174
|
+
{relatedItems.title}
|
|
175
|
+
</div>
|
|
176
|
+
<div class="space-y-1">
|
|
177
|
+
{#each relatedItems.items as item}
|
|
178
|
+
<div class="{isCompact ? 'text-[10px]' : 'text-xs'} flex items-center justify-between">
|
|
179
|
+
<span class="text-gray-900">{item.primary}</span>
|
|
180
|
+
<div class="flex items-center gap-1">
|
|
181
|
+
{#if item.secondary}
|
|
182
|
+
<span class="text-gray-600">{item.secondary}</span>
|
|
183
|
+
{/if}
|
|
184
|
+
{#if item.badge}
|
|
185
|
+
<span class="px-1.5 py-0.5 bg-blue-100 text-blue-800 rounded text-[10px]">
|
|
186
|
+
{item.badge}
|
|
187
|
+
</span>
|
|
188
|
+
{/if}
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
{/each}
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
{:else if relatedItems && relatedItems.emptyText}
|
|
195
|
+
<div class="mt-2 {isCompact ? 'text-xs' : 'text-sm'} text-gray-500 italic">
|
|
196
|
+
{relatedItems.emptyText}
|
|
197
|
+
</div>
|
|
198
|
+
{/if}
|
|
199
|
+
|
|
200
|
+
{#if note && note.text}
|
|
201
|
+
<div class="mt-1 {isCompact ? 'text-[10px]' : 'text-xs'} text-gray-600">
|
|
202
|
+
{#if note.icon}<span class="mr-1">{note.icon}</span>{/if}
|
|
203
|
+
{note.maxLength ? truncateText(note.text, note.maxLength) : note.text}
|
|
204
|
+
</div>
|
|
205
|
+
{/if}
|
|
206
|
+
|
|
207
|
+
{#if actionLayout === 'buttons' && hasActions}
|
|
208
|
+
<div class="mt-3 flex justify-end gap-2 pt-2 border-t border-gray-100">
|
|
209
|
+
{#if onEdit}
|
|
210
|
+
<button
|
|
211
|
+
onclick={onEdit}
|
|
212
|
+
class="{isCompact ? 'px-2 py-1 text-xs' : 'px-3 py-1.5 text-sm'} bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors font-medium"
|
|
213
|
+
>
|
|
214
|
+
{t('common.edit')}
|
|
215
|
+
</button>
|
|
216
|
+
{/if}
|
|
217
|
+
{#if onDelete}
|
|
218
|
+
<button
|
|
219
|
+
onclick={onDelete}
|
|
220
|
+
class="{isCompact ? 'px-2 py-1 text-xs' : 'px-3 py-1.5 text-sm'} bg-red-500 text-white rounded hover:bg-red-600 transition-colors font-medium"
|
|
221
|
+
>
|
|
222
|
+
{t('common.delete')}
|
|
223
|
+
</button>
|
|
224
|
+
{/if}
|
|
225
|
+
</div>
|
|
226
|
+
{/if}
|
|
227
|
+
</div>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export type InfoCardVariant = 'default' | 'compact';
|
|
2
|
+
export type ActionLayout = 'icons' | 'buttons';
|
|
3
|
+
export type BadgeColor = 'blue' | 'purple' | 'orange' | 'red' | 'green' | 'gray';
|
|
4
|
+
export interface InfoCardField {
|
|
5
|
+
label: string;
|
|
6
|
+
value: string | number;
|
|
7
|
+
icon?: string;
|
|
8
|
+
condition?: boolean;
|
|
9
|
+
span?: 1 | 2;
|
|
10
|
+
}
|
|
11
|
+
export interface InfoCardBadge {
|
|
12
|
+
items: string[];
|
|
13
|
+
color: BadgeColor;
|
|
14
|
+
icon?: string;
|
|
15
|
+
maxDisplay?: number;
|
|
16
|
+
label?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface InfoCardRelatedItem {
|
|
19
|
+
primary: string;
|
|
20
|
+
secondary?: string;
|
|
21
|
+
badge?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface InfoCardRelatedSection {
|
|
24
|
+
title: string;
|
|
25
|
+
items: InfoCardRelatedItem[];
|
|
26
|
+
emptyText?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface InfoCardLinkedStatus {
|
|
29
|
+
isLinked: boolean;
|
|
30
|
+
hasEmail: boolean;
|
|
31
|
+
onLink?: () => void;
|
|
32
|
+
linkText?: string;
|
|
33
|
+
}
|
|
34
|
+
export interface InfoCardNote {
|
|
35
|
+
text: string;
|
|
36
|
+
icon?: string;
|
|
37
|
+
maxLength?: number;
|
|
38
|
+
}
|
|
39
|
+
export interface InfoCardProps {
|
|
40
|
+
title: string;
|
|
41
|
+
id: string;
|
|
42
|
+
variant?: InfoCardVariant;
|
|
43
|
+
actionLayout?: ActionLayout;
|
|
44
|
+
hover?: boolean;
|
|
45
|
+
className?: string;
|
|
46
|
+
subtitle?: string;
|
|
47
|
+
subtitleIcon?: string;
|
|
48
|
+
linkedStatus?: InfoCardLinkedStatus;
|
|
49
|
+
fields?: InfoCardField[];
|
|
50
|
+
badges?: InfoCardBadge[];
|
|
51
|
+
relatedItems?: InfoCardRelatedSection;
|
|
52
|
+
note?: InfoCardNote;
|
|
53
|
+
onView?: () => void;
|
|
54
|
+
onEdit?: () => void;
|
|
55
|
+
onDelete?: () => void;
|
|
56
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as InfoCard } from './InfoCard.svelte';
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export default LoadingSpinner;
|
|
2
|
+
type LoadingSpinner = SvelteComponent<{
|
|
3
|
+
[x: string]: never;
|
|
4
|
+
}, {
|
|
5
|
+
[evt: string]: CustomEvent<any>;
|
|
6
|
+
}, {}> & {
|
|
7
|
+
$$bindings?: string;
|
|
8
|
+
};
|
|
9
|
+
declare const LoadingSpinner: $$__sveltets_2_IsomorphicComponent<{
|
|
10
|
+
[x: string]: never;
|
|
11
|
+
}, {
|
|
12
|
+
[evt: string]: CustomEvent<any>;
|
|
13
|
+
}, {}, {}, string>;
|
|
14
|
+
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> {
|
|
15
|
+
new (options: import("svelte").ComponentConstructorOptions<Props>): import("svelte").SvelteComponent<Props, Events, Slots> & {
|
|
16
|
+
$$bindings?: Bindings;
|
|
17
|
+
} & Exports;
|
|
18
|
+
(internal: unknown, props: {
|
|
19
|
+
$$events?: Events;
|
|
20
|
+
$$slots?: Slots;
|
|
21
|
+
}): Exports & {
|
|
22
|
+
$set?: any;
|
|
23
|
+
$on?: any;
|
|
24
|
+
};
|
|
25
|
+
z_$$bindings?: Bindings;
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as LoadingSpinner } from './LoadingSpinner.svelte';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as LoadingSpinner } from './LoadingSpinner.svelte';
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
open: boolean;
|
|
6
|
+
title: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
icon?: string;
|
|
9
|
+
color?: 'blue' | 'green' | 'amber' | 'red' | 'purple' | 'gray' | 'emerald' | 'teal';
|
|
10
|
+
size?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | 'full';
|
|
11
|
+
showClose?: boolean;
|
|
12
|
+
onClose?: () => void;
|
|
13
|
+
children?: Snippet;
|
|
14
|
+
actions?: Snippet;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let {
|
|
18
|
+
open,
|
|
19
|
+
title,
|
|
20
|
+
description = '',
|
|
21
|
+
icon = '',
|
|
22
|
+
color = 'blue',
|
|
23
|
+
size = 'md',
|
|
24
|
+
showClose = true,
|
|
25
|
+
onClose,
|
|
26
|
+
children,
|
|
27
|
+
actions
|
|
28
|
+
}: Props = $props();
|
|
29
|
+
|
|
30
|
+
// Modal açıkken body scroll'u kapat
|
|
31
|
+
$effect(() => {
|
|
32
|
+
if (open) {
|
|
33
|
+
document.body.style.overflow = 'hidden';
|
|
34
|
+
} else {
|
|
35
|
+
document.body.style.overflow = '';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Cleanup - modal unmount olduğunda
|
|
39
|
+
return () => {
|
|
40
|
+
document.body.style.overflow = '';
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const panelSizes = {
|
|
45
|
+
sm: 'max-w-md',
|
|
46
|
+
md: 'max-w-2xl',
|
|
47
|
+
lg: 'max-w-3xl',
|
|
48
|
+
xl: 'max-w-4xl',
|
|
49
|
+
'2xl': 'max-w-5xl',
|
|
50
|
+
'3xl': 'max-w-6xl',
|
|
51
|
+
full: 'max-w-[95vw]'
|
|
52
|
+
} as const;
|
|
53
|
+
|
|
54
|
+
// Gradient header renkleri (kurs template gibi)
|
|
55
|
+
const gradientColors: Record<string, string> = {
|
|
56
|
+
blue: 'from-blue-600 to-indigo-600',
|
|
57
|
+
green: 'from-green-600 to-emerald-600',
|
|
58
|
+
emerald: 'from-emerald-600 to-teal-600',
|
|
59
|
+
teal: 'from-teal-600 to-cyan-600',
|
|
60
|
+
amber: 'from-amber-500 to-orange-500',
|
|
61
|
+
red: 'from-red-600 to-rose-600',
|
|
62
|
+
purple: 'from-purple-600 to-indigo-600',
|
|
63
|
+
gray: 'from-gray-600 to-slate-600'
|
|
64
|
+
};
|
|
65
|
+
</script>
|
|
66
|
+
|
|
67
|
+
{#if open}
|
|
68
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
69
|
+
<!-- svelte-ignore a11y_interactive_supports_focus -->
|
|
70
|
+
<div
|
|
71
|
+
class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm"
|
|
72
|
+
role="dialog"
|
|
73
|
+
aria-modal="true"
|
|
74
|
+
onclick={(e) => e.target === e.currentTarget && onClose?.()}
|
|
75
|
+
>
|
|
76
|
+
<div
|
|
77
|
+
class={`w-full ${panelSizes[size]} mx-4 bg-white rounded-2xl shadow-2xl overflow-hidden flex flex-col max-h-[90vh]`}
|
|
78
|
+
>
|
|
79
|
+
<!-- Gradient Header (kurs template gibi) -->
|
|
80
|
+
<div class={`bg-gradient-to-r ${gradientColors[color]} px-6 py-5 flex items-center justify-between shrink-0`}>
|
|
81
|
+
<div class="flex items-center gap-3">
|
|
82
|
+
{#if icon}
|
|
83
|
+
<div class="bg-white/20 backdrop-blur-sm p-2.5 rounded-xl">
|
|
84
|
+
<span class="text-xl text-white">{icon}</span>
|
|
85
|
+
</div>
|
|
86
|
+
{/if}
|
|
87
|
+
<div>
|
|
88
|
+
<h2 class="text-xl font-bold text-white">{title}</h2>
|
|
89
|
+
{#if description}
|
|
90
|
+
<p class="text-sm text-white/80 mt-0.5">{description}</p>
|
|
91
|
+
{/if}
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
{#if showClose}
|
|
95
|
+
<button
|
|
96
|
+
class="text-white/80 hover:text-white hover:bg-white/20 p-2 rounded-lg transition-all"
|
|
97
|
+
aria-label="Kapat"
|
|
98
|
+
onclick={() => onClose?.()}
|
|
99
|
+
>
|
|
100
|
+
<svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
101
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
|
102
|
+
</svg>
|
|
103
|
+
</button>
|
|
104
|
+
{/if}
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<!-- Scrollable Content -->
|
|
108
|
+
<div class="flex-1 overflow-y-auto p-6">
|
|
109
|
+
{#if children}
|
|
110
|
+
{@render children()}
|
|
111
|
+
{/if}
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
<!-- Footer Actions -->
|
|
115
|
+
{#if actions}
|
|
116
|
+
<div class="px-6 py-4 bg-gray-50 border-t border-gray-100 shrink-0">
|
|
117
|
+
{@render actions()}
|
|
118
|
+
</div>
|
|
119
|
+
{/if}
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
{/if}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
interface Props {
|
|
3
|
+
open: boolean;
|
|
4
|
+
title: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
icon?: string;
|
|
7
|
+
color?: 'blue' | 'green' | 'amber' | 'red' | 'purple' | 'gray';
|
|
8
|
+
size?: 'sm' | 'md' | 'lg';
|
|
9
|
+
showClose?: boolean;
|
|
10
|
+
onClose?: () => void;
|
|
11
|
+
actions?: Snippet;
|
|
12
|
+
}
|
|
13
|
+
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
|
+
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
|
15
|
+
$$bindings?: Bindings;
|
|
16
|
+
} & Exports;
|
|
17
|
+
(internal: unknown, props: Props & {
|
|
18
|
+
$$events?: Events;
|
|
19
|
+
$$slots?: Slots;
|
|
20
|
+
}): Exports & {
|
|
21
|
+
$set?: any;
|
|
22
|
+
$on?: any;
|
|
23
|
+
};
|
|
24
|
+
z_$$bindings?: Bindings;
|
|
25
|
+
}
|
|
26
|
+
type $$__sveltets_2_PropsWithChildren<Props, Slots> = Props & (Slots extends {
|
|
27
|
+
default: any;
|
|
28
|
+
} ? Props extends Record<string, never> ? any : {
|
|
29
|
+
children?: any;
|
|
30
|
+
} : {});
|
|
31
|
+
declare const Modal: $$__sveltets_2_IsomorphicComponent<$$__sveltets_2_PropsWithChildren<Props, {
|
|
32
|
+
default: {};
|
|
33
|
+
}>, {
|
|
34
|
+
[evt: string]: CustomEvent<any>;
|
|
35
|
+
}, {
|
|
36
|
+
default: {};
|
|
37
|
+
}, {}, "">;
|
|
38
|
+
type Modal = InstanceType<typeof Modal>;
|
|
39
|
+
export default Modal;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Modal } from './Modal.svelte';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Modal } from './Modal.svelte';
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
label: string;
|
|
4
|
+
onclick: () => void;
|
|
5
|
+
color?: 'green' | 'blue' | 'red' | 'gray';
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
let { label, onclick, color = 'green' }: Props = $props();
|
|
9
|
+
|
|
10
|
+
const colorClasses = {
|
|
11
|
+
green: 'bg-green-500 hover:bg-green-600',
|
|
12
|
+
blue: 'bg-blue-500 hover:bg-blue-600',
|
|
13
|
+
red: 'bg-red-500 hover:bg-red-600',
|
|
14
|
+
gray: 'bg-gray-500 hover:bg-gray-600'
|
|
15
|
+
} as const;
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<button
|
|
19
|
+
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
|
+
{onclick}
|
|
21
|
+
>
|
|
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>
|
|
25
|
+
<span class="hidden md:inline text-sm font-semibold">{label}</span>
|
|
26
|
+
</button>
|