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
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# FeaturedGalleryGrid Component
|
|
2
|
+
|
|
3
|
+
Framework-agnostic featured gallery grid component extracted from turk-cin-dernek-website project.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- ✅ Asymmetric grid layout (2:1 ratio)
|
|
8
|
+
- ✅ Large featured image on left
|
|
9
|
+
- ✅ 3 smaller images stacked on right
|
|
10
|
+
- ✅ Responsive design (stacks on mobile)
|
|
11
|
+
- ✅ Hover scale animations
|
|
12
|
+
- ✅ Image overlays with gradient
|
|
13
|
+
- ✅ Text labels on images
|
|
14
|
+
- ✅ Framework-agnostic click handlers
|
|
15
|
+
- ✅ Custom image resolver support
|
|
16
|
+
- ✅ Placeholder fallback images
|
|
17
|
+
|
|
18
|
+
## Layout Preview
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
┌─────────────────┬────────┐
|
|
22
|
+
│ │ IMG1 │
|
|
23
|
+
│ FEATURED ├────────┤
|
|
24
|
+
│ IMAGE │ IMG2 │
|
|
25
|
+
│ (Large) ├────────┤
|
|
26
|
+
│ │ IMG3 │
|
|
27
|
+
└─────────────────┴────────┘
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
```svelte
|
|
33
|
+
<script>
|
|
34
|
+
import { FeaturedGalleryGrid } from '@urga-panel/urPanels-ui-pack/sections';
|
|
35
|
+
|
|
36
|
+
const featuredItem = {
|
|
37
|
+
image: '/images/featured.jpg',
|
|
38
|
+
alt: 'Featured Event',
|
|
39
|
+
text: 'Ambassador Visit to China',
|
|
40
|
+
href: '/events/ambassador-visit'
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const smallItems = [
|
|
44
|
+
{
|
|
45
|
+
image: '/images/event1.jpg',
|
|
46
|
+
alt: 'Chinese Dance',
|
|
47
|
+
text: 'Chinese Dance Performance',
|
|
48
|
+
href: '/events/dance'
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
image: '/images/event2.jpg',
|
|
52
|
+
alt: 'MADO Representative',
|
|
53
|
+
text: 'MADO Regional Representative',
|
|
54
|
+
href: '/events/mado'
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
image: '/images/event3.jpg',
|
|
58
|
+
alt: 'University Visit',
|
|
59
|
+
text: 'Northwest University',
|
|
60
|
+
href: '/events/university'
|
|
61
|
+
}
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
function handleItemClick(href: string) {
|
|
65
|
+
window.location.href = href;
|
|
66
|
+
}
|
|
67
|
+
</script>
|
|
68
|
+
|
|
69
|
+
<FeaturedGalleryGrid
|
|
70
|
+
{featuredItem}
|
|
71
|
+
{smallItems}
|
|
72
|
+
onItemClick={handleItemClick}
|
|
73
|
+
/>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Props
|
|
77
|
+
|
|
78
|
+
### `featuredItem` (required)
|
|
79
|
+
Type: `GalleryItem`
|
|
80
|
+
|
|
81
|
+
Large featured image on the left side:
|
|
82
|
+
```typescript
|
|
83
|
+
interface GalleryItem {
|
|
84
|
+
image: string; // Image URL
|
|
85
|
+
alt: string; // Alt text for accessibility
|
|
86
|
+
text?: string; // Optional overlay text
|
|
87
|
+
href?: string; // Optional link destination
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### `smallItems` (required)
|
|
92
|
+
Type: `GalleryItem[]`
|
|
93
|
+
|
|
94
|
+
Array of 3 smaller images for the right column. If more than 3 items provided, only first 3 are displayed.
|
|
95
|
+
|
|
96
|
+
### `onItemClick` (optional)
|
|
97
|
+
Type: `(href: string) => void`
|
|
98
|
+
|
|
99
|
+
Click handler for all gallery items. Receives the `href` from the clicked item.
|
|
100
|
+
|
|
101
|
+
### `imageResolver` (optional)
|
|
102
|
+
Type: `(path: string) => string`
|
|
103
|
+
Default: `(path) => path`
|
|
104
|
+
|
|
105
|
+
Function to transform/resolve image paths (e.g., add CDN prefix).
|
|
106
|
+
|
|
107
|
+
### `placeholderImage` (optional)
|
|
108
|
+
Type: `string`
|
|
109
|
+
Default: `'https://placehold.co/800x500'`
|
|
110
|
+
|
|
111
|
+
Fallback image URL when item image is empty or invalid.
|
|
112
|
+
|
|
113
|
+
## Examples
|
|
114
|
+
|
|
115
|
+
### With SvelteKit Navigation
|
|
116
|
+
|
|
117
|
+
```svelte
|
|
118
|
+
<script lang="ts">
|
|
119
|
+
import { FeaturedGalleryGrid } from '@urga-panel/urPanels-ui-pack/sections';
|
|
120
|
+
import { goto } from '$app/navigation';
|
|
121
|
+
import { localizedPath, locale } from '$lib/i18n';
|
|
122
|
+
|
|
123
|
+
export let data;
|
|
124
|
+
|
|
125
|
+
const featuredItem = {
|
|
126
|
+
image: data.indexData?.gallery?.left?.image ?? '',
|
|
127
|
+
alt: data.indexData?.gallery?.left?.alt ?? 'Featured',
|
|
128
|
+
text: data.indexData?.gallery?.left?.text,
|
|
129
|
+
href: data.indexData?.gallery?.left?.href
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const smallItems = data.indexData?.gallery?.right ?? [];
|
|
133
|
+
|
|
134
|
+
function handleItemClick(href: string) {
|
|
135
|
+
if (href.startsWith('http')) {
|
|
136
|
+
window.location.href = href;
|
|
137
|
+
} else {
|
|
138
|
+
goto(localizedPath(href, $locale));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
</script>
|
|
142
|
+
|
|
143
|
+
<FeaturedGalleryGrid
|
|
144
|
+
{featuredItem}
|
|
145
|
+
{smallItems}
|
|
146
|
+
onItemClick={handleItemClick}
|
|
147
|
+
/>
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### With CDN Image Resolver
|
|
151
|
+
|
|
152
|
+
```svelte
|
|
153
|
+
<script>
|
|
154
|
+
const imageResolver = (path) => {
|
|
155
|
+
if (!path) return 'https://placehold.co/800x500';
|
|
156
|
+
if (path.startsWith('http')) return path;
|
|
157
|
+
return `https://cdn.example.com${path}`;
|
|
158
|
+
};
|
|
159
|
+
</script>
|
|
160
|
+
|
|
161
|
+
<FeaturedGalleryGrid
|
|
162
|
+
{featuredItem}
|
|
163
|
+
{smallItems}
|
|
164
|
+
{imageResolver}
|
|
165
|
+
/>
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### With Custom Placeholder
|
|
169
|
+
|
|
170
|
+
```svelte
|
|
171
|
+
<FeaturedGalleryGrid
|
|
172
|
+
{featuredItem}
|
|
173
|
+
{smallItems}
|
|
174
|
+
placeholderImage="/default-event-image.jpg"
|
|
175
|
+
/>
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Static Content (No Click Handlers)
|
|
179
|
+
|
|
180
|
+
```svelte
|
|
181
|
+
<script>
|
|
182
|
+
const featuredItem = {
|
|
183
|
+
image: '/gallery/main.jpg',
|
|
184
|
+
alt: 'Main Gallery Image',
|
|
185
|
+
text: 'Photo Gallery 2024'
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const smallItems = [
|
|
189
|
+
{ image: '/gallery/1.jpg', alt: 'Gallery 1' },
|
|
190
|
+
{ image: '/gallery/2.jpg', alt: 'Gallery 2' },
|
|
191
|
+
{ image: '/gallery/3.jpg', alt: 'Gallery 3' }
|
|
192
|
+
];
|
|
193
|
+
</script>
|
|
194
|
+
|
|
195
|
+
<!-- Without onItemClick, items use default href behavior -->
|
|
196
|
+
<FeaturedGalleryGrid {featuredItem} {smallItems} />
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Styling
|
|
200
|
+
|
|
201
|
+
Component uses Tailwind-like utility classes with scoped CSS fallbacks. Key styles:
|
|
202
|
+
|
|
203
|
+
- **Grid**: 1 column (mobile), 3 columns (md+)
|
|
204
|
+
- **Featured Image**: Spans 2 columns, max-height 600px
|
|
205
|
+
- **Small Images**: Max-height 200px each
|
|
206
|
+
- **Container**: Max-width 1280px
|
|
207
|
+
- **Spacing**: 4px gap on desktop, 16px on mobile
|
|
208
|
+
- **Hover Effects**:
|
|
209
|
+
- Featured: scale(1.01)
|
|
210
|
+
- Small: scale(1.05)
|
|
211
|
+
|
|
212
|
+
## Layout Behavior
|
|
213
|
+
|
|
214
|
+
### Desktop (md+)
|
|
215
|
+
```
|
|
216
|
+
┌──────────────────┬─────────┐
|
|
217
|
+
│ │ Item1 │
|
|
218
|
+
│ Featured ├─────────┤
|
|
219
|
+
│ (col-span-2) │ Item2 │
|
|
220
|
+
│ ├─────────┤
|
|
221
|
+
│ │ Item3 │
|
|
222
|
+
└──────────────────┴─────────┘
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Mobile
|
|
226
|
+
```
|
|
227
|
+
┌──────────────────┐
|
|
228
|
+
│ Featured │
|
|
229
|
+
├──────────────────┤
|
|
230
|
+
│ Item 1 │
|
|
231
|
+
├──────────────────┤
|
|
232
|
+
│ Item 2 │
|
|
233
|
+
├──────────────────┤
|
|
234
|
+
│ Item 3 │
|
|
235
|
+
└──────────────────┘
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Image Overlay
|
|
239
|
+
|
|
240
|
+
All images have:
|
|
241
|
+
- Gradient overlay: `from-black/50 via-black/20 to-transparent`
|
|
242
|
+
- Text positioned at bottom with padding
|
|
243
|
+
- White text with drop-shadow for readability
|
|
244
|
+
- Pointer-events disabled on text to allow image clicks
|
|
245
|
+
|
|
246
|
+
## Accessibility
|
|
247
|
+
|
|
248
|
+
- ✅ Semantic `<a>` elements with proper `href`
|
|
249
|
+
- ✅ Alt text on all images
|
|
250
|
+
- ✅ Keyboard navigation support
|
|
251
|
+
- ✅ Proper heading hierarchy (`<h3>` for featured, `<h4>` for small)
|
|
252
|
+
- ✅ Visual hover feedback
|
|
253
|
+
|
|
254
|
+
## Browser Support
|
|
255
|
+
|
|
256
|
+
Works in all modern browsers with CSS Grid and Flexbox support.
|
|
257
|
+
|
|
258
|
+
## Max Items
|
|
259
|
+
|
|
260
|
+
Component automatically limits `smallItems` to first 3 items using `.slice(0, 3)`.
|
|
261
|
+
|
|
262
|
+
## Performance
|
|
263
|
+
|
|
264
|
+
- Images lazy-load by default (browser native)
|
|
265
|
+
- Transform animations use GPU acceleration
|
|
266
|
+
- Gradient overlays use CSS, not extra DOM elements
|
|
267
|
+
|
|
268
|
+
## Version
|
|
269
|
+
|
|
270
|
+
Component extracted from turk-cin-dernek-website (January 2025).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as FeaturedGalleryGrid } from './FeaturedGalleryGrid.svelte';
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import emblaCarouselSvelte from 'embla-carousel-svelte';
|
|
3
|
+
import Autoplay from 'embla-carousel-autoplay';
|
|
4
|
+
|
|
5
|
+
interface HeroItem {
|
|
6
|
+
id?: string;
|
|
7
|
+
title: string;
|
|
8
|
+
summary?: string;
|
|
9
|
+
slug?: string;
|
|
10
|
+
featuredImage?: string;
|
|
11
|
+
images?: string[];
|
|
12
|
+
type?: string;
|
|
13
|
+
content?: string;
|
|
14
|
+
customStyles?: string; // CSS için ayrı field
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface Props {
|
|
18
|
+
heroItems?: HeroItem[];
|
|
19
|
+
onSlugClick?: (slug: string) => void;
|
|
20
|
+
imageResolver?: (url: string | undefined) => string;
|
|
21
|
+
locale?: string;
|
|
22
|
+
translations?: {
|
|
23
|
+
readMore: string;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let {
|
|
28
|
+
heroItems = [],
|
|
29
|
+
onSlugClick,
|
|
30
|
+
imageResolver = (url) => url || '',
|
|
31
|
+
locale = 'tr',
|
|
32
|
+
translations = { readMore: 'Devamını Oku' }
|
|
33
|
+
}: Props = $props();
|
|
34
|
+
|
|
35
|
+
let emblaApi: any = $state(null);
|
|
36
|
+
let currentSlide = $state(0);
|
|
37
|
+
let totalSlides = $derived(heroItems.length);
|
|
38
|
+
|
|
39
|
+
// Autoplay plugin options
|
|
40
|
+
const autoplayOptions = {
|
|
41
|
+
delay: 4000,
|
|
42
|
+
stopOnInteraction: false,
|
|
43
|
+
stopOnMouseEnter: true,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
function onInit(event: CustomEvent): void {
|
|
47
|
+
emblaApi = event.detail;
|
|
48
|
+
emblaApi.on('select', () => {
|
|
49
|
+
currentSlide = emblaApi.selectedScrollSnap();
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function goToSlide(index: number): void {
|
|
54
|
+
if (emblaApi) {
|
|
55
|
+
emblaApi.scrollTo(index);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function handleSlugClick(slug: string | undefined, e: MouseEvent): void {
|
|
60
|
+
if (slug && onSlugClick) {
|
|
61
|
+
e.preventDefault();
|
|
62
|
+
onSlugClick(slug);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// HTML content'ten style tag'lerini ayıklayıp temizle
|
|
67
|
+
function extractAndCleanHTML(htmlContent: string): { html: string; styles: string } {
|
|
68
|
+
if (!htmlContent) return { html: '', styles: '' };
|
|
69
|
+
|
|
70
|
+
const styleRegex = /<style[^>]*>([\s\S]*?)<\/style>/gi;
|
|
71
|
+
let styles = '';
|
|
72
|
+
let matches;
|
|
73
|
+
|
|
74
|
+
// Tüm style tag'lerini bul ve topla
|
|
75
|
+
while ((matches = styleRegex.exec(htmlContent)) !== null) {
|
|
76
|
+
styles += matches[1] + '\n';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// HTML'den style tag'lerini kaldır
|
|
80
|
+
const cleanHTML = htmlContent.replace(styleRegex, '');
|
|
81
|
+
|
|
82
|
+
return { html: cleanHTML, styles };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Style'ları slide-specific yap (scope'la)
|
|
86
|
+
function scopeStyles(styles: string, slideIndex: number): string {
|
|
87
|
+
if (!styles) return '';
|
|
88
|
+
|
|
89
|
+
const scopeClass = `.hero-slide-${slideIndex}`;
|
|
90
|
+
let result = '';
|
|
91
|
+
|
|
92
|
+
// Media query pattern'i
|
|
93
|
+
const mediaQueryRegex = /@media[^{]+\{([\s\S]+?)\}\s*\}/g;
|
|
94
|
+
let lastIndex = 0;
|
|
95
|
+
let match;
|
|
96
|
+
|
|
97
|
+
// Media query'leri bul ve işle
|
|
98
|
+
while ((match = mediaQueryRegex.exec(styles)) !== null) {
|
|
99
|
+
// Media query'den önceki normal kuralları ekle
|
|
100
|
+
const beforeMedia = styles.substring(lastIndex, match.index);
|
|
101
|
+
if (beforeMedia.trim()) {
|
|
102
|
+
result += scopeNormalCSS(beforeMedia, scopeClass);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Media query'yi işle
|
|
106
|
+
const mediaRule = match[0];
|
|
107
|
+
const mediaCondition = mediaRule.substring(0, mediaRule.indexOf('{') + 1);
|
|
108
|
+
const mediaContent = match[1];
|
|
109
|
+
|
|
110
|
+
// Media query içindeki kuralları scope'la
|
|
111
|
+
const scopedMediaContent = scopeNormalCSS(mediaContent, scopeClass);
|
|
112
|
+
result += `${mediaCondition}\n${scopedMediaContent}\n}\n`;
|
|
113
|
+
|
|
114
|
+
lastIndex = mediaQueryRegex.lastIndex;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Kalan normal kuralları ekle
|
|
118
|
+
const afterMedia = styles.substring(lastIndex);
|
|
119
|
+
if (afterMedia.trim()) {
|
|
120
|
+
result += scopeNormalCSS(afterMedia, scopeClass);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Normal CSS kurallarını scope'la
|
|
127
|
+
function scopeNormalCSS(css: string, scopeClass: string): string {
|
|
128
|
+
if (!css.trim()) return '';
|
|
129
|
+
|
|
130
|
+
// Satır satır işle
|
|
131
|
+
const lines = css.split('\n');
|
|
132
|
+
let result = '';
|
|
133
|
+
let inRule = false;
|
|
134
|
+
let currentSelector = '';
|
|
135
|
+
let currentRules = '';
|
|
136
|
+
|
|
137
|
+
for (let line of lines) {
|
|
138
|
+
const trimmed = line.trim();
|
|
139
|
+
|
|
140
|
+
// Boş satırları atla
|
|
141
|
+
if (!trimmed) continue;
|
|
142
|
+
|
|
143
|
+
// Selector başlangıcı
|
|
144
|
+
if (trimmed.includes('{')) {
|
|
145
|
+
const parts = trimmed.split('{');
|
|
146
|
+
currentSelector = parts[0].trim();
|
|
147
|
+
currentRules = parts[1] || '';
|
|
148
|
+
inRule = true;
|
|
149
|
+
|
|
150
|
+
// @ ile başlamıyorsa scope'la
|
|
151
|
+
if (!currentSelector.startsWith('@')) {
|
|
152
|
+
const scopedSelector = `${scopeClass} ${currentSelector}`;
|
|
153
|
+
result += `${scopedSelector} { ${currentRules}\n`;
|
|
154
|
+
} else {
|
|
155
|
+
result += `${currentSelector} { ${currentRules}\n`;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// Kural devam ediyor
|
|
159
|
+
else if (inRule && !trimmed.includes('}')) {
|
|
160
|
+
result += `${trimmed}\n`;
|
|
161
|
+
}
|
|
162
|
+
// Kural bitiyor
|
|
163
|
+
else if (trimmed.includes('}')) {
|
|
164
|
+
const beforeClose = trimmed.split('}')[0];
|
|
165
|
+
if (beforeClose.trim()) {
|
|
166
|
+
result += `${beforeClose}\n`;
|
|
167
|
+
}
|
|
168
|
+
result += '}\n';
|
|
169
|
+
inRule = false;
|
|
170
|
+
currentSelector = '';
|
|
171
|
+
currentRules = '';
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return result;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Her item için style ve HTML'i hazırla
|
|
179
|
+
let processedItems = $derived(
|
|
180
|
+
heroItems.map((item, index) => {
|
|
181
|
+
if (item.type === 'html' && item.content) {
|
|
182
|
+
const { html, styles } = extractAndCleanHTML(item.content);
|
|
183
|
+
return {
|
|
184
|
+
...item,
|
|
185
|
+
cleanContent: html,
|
|
186
|
+
extractedStyles: scopeStyles(item.customStyles || styles, index)
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
...item,
|
|
191
|
+
cleanContent: item.content,
|
|
192
|
+
extractedStyles: item.customStyles ? scopeStyles(item.customStyles, index) : ''
|
|
193
|
+
};
|
|
194
|
+
})
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
// Tüm slide'ların style'larını birleştir
|
|
198
|
+
let allStyles = $derived(
|
|
199
|
+
processedItems
|
|
200
|
+
.map(item => item.extractedStyles)
|
|
201
|
+
.filter(Boolean)
|
|
202
|
+
.join('\n')
|
|
203
|
+
);
|
|
204
|
+
</script>
|
|
205
|
+
|
|
206
|
+
<!-- Style'ları head'e ekle -->
|
|
207
|
+
<svelte:head>
|
|
208
|
+
{#if allStyles}
|
|
209
|
+
{@html `<style>${allStyles}</style>`}
|
|
210
|
+
{/if}
|
|
211
|
+
</svelte:head>
|
|
212
|
+
|
|
213
|
+
<section class="relative text-white overflow-hidden">
|
|
214
|
+
<div
|
|
215
|
+
class="embla"
|
|
216
|
+
use:emblaCarouselSvelte={{
|
|
217
|
+
options: { loop: true },
|
|
218
|
+
plugins: [Autoplay(autoplayOptions)]
|
|
219
|
+
}}
|
|
220
|
+
onemblaInit={onInit}
|
|
221
|
+
>
|
|
222
|
+
<div class="embla__container">
|
|
223
|
+
<!-- Dynamic Slides from heroItems -->
|
|
224
|
+
{#each processedItems as item, index}
|
|
225
|
+
<div class="embla__slide hero-slide-{index} relative min-h-[600px] flex items-center">
|
|
226
|
+
<div class="absolute inset-0 overflow-hidden">
|
|
227
|
+
{#if item.featuredImage || (item.images && item.images[0])}
|
|
228
|
+
<img
|
|
229
|
+
src={imageResolver(item.featuredImage || item.images?.[0])}
|
|
230
|
+
alt={item.title}
|
|
231
|
+
class="w-full h-full object-cover"
|
|
232
|
+
/>
|
|
233
|
+
{:else}
|
|
234
|
+
<div class="w-full h-full bg-gradient-to-br from-gray-800 to-gray-900"></div>
|
|
235
|
+
{/if}
|
|
236
|
+
<div class="absolute inset-0 bg-gradient-to-b from-gray-900/60 via-gray-900/50 to-gray-900/60"></div>
|
|
237
|
+
</div>
|
|
238
|
+
|
|
239
|
+
{#if item.type === 'html'}
|
|
240
|
+
<!-- HTML Content (full width) -->
|
|
241
|
+
<div class="hero-html-content">
|
|
242
|
+
{@html item.cleanContent}
|
|
243
|
+
</div>
|
|
244
|
+
{:else}
|
|
245
|
+
<!-- Standard Content -->
|
|
246
|
+
<div class="container mx-auto px-4 relative z-10">
|
|
247
|
+
<div class="max-w-4xl mx-auto text-center">
|
|
248
|
+
<h2 class="text-4xl md:text-6xl font-bold mb-6">
|
|
249
|
+
{item.title}
|
|
250
|
+
</h2>
|
|
251
|
+
{#if item.summary}
|
|
252
|
+
<p class="text-xl md:text-2xl text-gray-200 mb-8">
|
|
253
|
+
{item.summary}
|
|
254
|
+
</p>
|
|
255
|
+
{/if}
|
|
256
|
+
{#if item.slug}
|
|
257
|
+
<a
|
|
258
|
+
href={onSlugClick ? '#' : `/makaleler/${item.slug}`}
|
|
259
|
+
onclick={(e) => handleSlugClick(item.slug, e)}
|
|
260
|
+
class="inline-flex items-center bg-white text-primary-900 px-8 py-4 rounded-lg hover:bg-primary-50 transition-colors font-semibold text-lg"
|
|
261
|
+
>
|
|
262
|
+
{translations.readMore}
|
|
263
|
+
<svg class="w-5 h-5 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
264
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
|
265
|
+
</svg>
|
|
266
|
+
</a>
|
|
267
|
+
{/if}
|
|
268
|
+
</div>
|
|
269
|
+
</div>
|
|
270
|
+
{/if}
|
|
271
|
+
</div>
|
|
272
|
+
{/each}
|
|
273
|
+
</div>
|
|
274
|
+
</div>
|
|
275
|
+
|
|
276
|
+
<!-- Navigation Dots -->
|
|
277
|
+
{#if totalSlides > 1}
|
|
278
|
+
<div class="absolute bottom-8 left-1/2 -translate-x-1/2 z-20 flex gap-3">
|
|
279
|
+
{#each Array(totalSlides) as _, index}
|
|
280
|
+
<button
|
|
281
|
+
type="button"
|
|
282
|
+
onclick={() => goToSlide(index)}
|
|
283
|
+
class="w-3 h-3 rounded-full transition-all duration-300 {currentSlide === index
|
|
284
|
+
? 'bg-white scale-125'
|
|
285
|
+
: 'bg-white/50 hover:bg-white/75'}"
|
|
286
|
+
aria-label="Slide {index + 1}'e git"
|
|
287
|
+
></button>
|
|
288
|
+
{/each}
|
|
289
|
+
</div>
|
|
290
|
+
{/if}
|
|
291
|
+
</section>
|
|
292
|
+
|
|
293
|
+
<style>
|
|
294
|
+
.embla {
|
|
295
|
+
overflow: hidden;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.embla__container {
|
|
299
|
+
display: flex;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.embla__slide {
|
|
303
|
+
flex: 0 0 100%;
|
|
304
|
+
min-width: 0;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.hero-html-content :global(*) {
|
|
308
|
+
/* HTML içeriği için global stiller zaten inline gelecek */
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.hero-html-content {
|
|
312
|
+
width: 100%;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.hero-html-content :global(> *) {
|
|
316
|
+
width: 100%;
|
|
317
|
+
}
|
|
318
|
+
</style>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
interface HeroItem {
|
|
2
|
+
id?: string;
|
|
3
|
+
title: string;
|
|
4
|
+
summary?: string;
|
|
5
|
+
slug?: string;
|
|
6
|
+
featuredImage?: string;
|
|
7
|
+
images?: string[];
|
|
8
|
+
type?: string;
|
|
9
|
+
content?: string;
|
|
10
|
+
customStyles?: string;
|
|
11
|
+
}
|
|
12
|
+
interface Props {
|
|
13
|
+
heroItems?: HeroItem[];
|
|
14
|
+
onSlugClick?: (slug: string) => void;
|
|
15
|
+
imageResolver?: (url: string | undefined) => string;
|
|
16
|
+
locale?: string;
|
|
17
|
+
translations?: {
|
|
18
|
+
readMore: string;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
declare const HeroCarousel: import("svelte").Component<Props, {}, "">;
|
|
22
|
+
type HeroCarousel = ReturnType<typeof HeroCarousel>;
|
|
23
|
+
export default HeroCarousel;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as HeroCarousel } from './HeroCarousel.svelte';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as HeroCarousel } from './HeroCarousel.svelte';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Translations Helper for urpanels-template-kurs
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* 1. Import in view: import { t } from '../../utils/translations.svelte';
|
|
6
|
+
* 2. Use t('students.title') to get translations
|
|
7
|
+
*
|
|
8
|
+
* Note: Translations are loaded by layout and stored in window.__urpanels_translations
|
|
9
|
+
* This module reads from window for cross-module compatibility
|
|
10
|
+
*/
|
|
11
|
+
declare global {
|
|
12
|
+
interface Window {
|
|
13
|
+
__urpanels_translations: Record<string, any>;
|
|
14
|
+
__urpanels_language: string;
|
|
15
|
+
__urpanels_translations_loaded: boolean;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Get translation by key
|
|
20
|
+
* Supports nested keys with dot notation (e.g., 'students.title')
|
|
21
|
+
*
|
|
22
|
+
* @param key - Translation key (dot notation for nested)
|
|
23
|
+
* @param fallback - Fallback value if key not found (defaults to key itself)
|
|
24
|
+
* @param params - Optional parameters for interpolation (e.g., { count: 5 })
|
|
25
|
+
* @returns Translated string or fallback
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* t('common.save') // Returns "Kaydet" or "Save"
|
|
29
|
+
* t('students.title') // Returns "Öğrenciler" or "Students"
|
|
30
|
+
* t('validation.minLength', undefined, { min: 3 }) // Returns "En az 3 karakter olmalıdır"
|
|
31
|
+
*/
|
|
32
|
+
export declare function t(key: string, fallback?: string, params?: Record<string, any>): string;
|
|
33
|
+
export declare function getCurrentLanguage(): string;
|
|
34
|
+
export declare function isTranslationsLoaded(): boolean;
|
|
35
|
+
export declare function getAllTranslations(): Record<string, any>;
|
|
36
|
+
export declare function initTranslations(_apiFront?: any, _settings?: any): Promise<void>;
|
|
37
|
+
export declare function changeLanguage(_apiFront?: any, _language?: string): Promise<void>;
|