rune-lab 0.0.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) [2025] [Yrrrrrf]
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,64 @@
1
+ <h1 align="center">
2
+ <img src="./resources/img/rune.png" alt="Rune-Lab Icon" width="128" height="128" description="Some glowing runes">
3
+ <div align="center">Rune Lab</div>
4
+ </h1>
5
+
6
+ <!-- [![JSR Package](https://img.shields.io/badge/JSR-Package-blue?style=for-the-badge&logo=typescript)](https://jsr.io/@yrrrrrf/rune-lab) -->
7
+ [![GitHub](https://img.shields.io/badge/github-rune--lab-blue?style=for-the-badge&logo=github)](https://github.com/Yrrrrrf/rune-lab)
8
+
9
+ ## Overview
10
+
11
+ Rune Lab is a SvelteKit component library **designed for the SvelteHack 2024**. It provides a collection of reusable UI components and utilities built with modern web technologies. The library emphasizes type safety, performance, and developer experience.
12
+
13
+ ## Features
14
+
15
+ - **Svelte 5 Ready**: Built with the latest Svelte features
16
+ - **TypeScript Support**: Full type safety and IDE integration
17
+ - **Tailwind CSS**: Utility-first styling with custom components
18
+ - **Zero Dependencies**: Lightweight and efficient
19
+ - **Hot Reload**: Development-friendly with watch mode
20
+ - **JSR Distribution**: Modern package distribution
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ jsr add @rune-lab
26
+ # jsr add @yrrrrrf/rune-lab
27
+ ```
28
+
29
+ ## Usage
30
+
31
+ ### Components
32
+
33
+ TODO
34
+ TODO
35
+ TODO
36
+ TODO
37
+ TODO
38
+
39
+ ## Development
40
+
41
+ ```bash
42
+ bun install # Install dependencies
43
+ bun watch # Start development watch mode
44
+ bun run build # Build library
45
+ bun test # Run tests
46
+ ```
47
+
48
+ ## Project Structure
49
+
50
+ ```
51
+ rune-lab/
52
+ ├── src/
53
+ │ └── lib/
54
+ │ ├── components/
55
+ │ ├── utils/
56
+ │ └── index.ts
57
+ ├── tests/
58
+ ├── watch.ts
59
+ └── package.json
60
+ ```
61
+
62
+ ## License
63
+
64
+ MIT License - [LICENSE](LICENSE)
@@ -0,0 +1,364 @@
1
+ <script lang="ts">
2
+ import ThemeSelector from "./layout/ThemeSelector.svelte";
3
+
4
+ // State management with Svelte 5 runes
5
+ let activeTab = $state(0);
6
+ let counter = $state(0);
7
+ let modalOpen = $state(false);
8
+ let drawerOpen = $state(false);
9
+ let loading = $state(false);
10
+ let progress = $state(0);
11
+ let rating = $state(3);
12
+ let selected = $state("Option 1");
13
+ let steps = $state(1);
14
+ let toast = $state("");
15
+
16
+ // Color variants for demonstration
17
+ const colors = ['primary', 'secondary', 'accent', 'info', 'success', 'warning', 'error'];
18
+ const sizes = ['xs', 'sm', 'md', 'lg'];
19
+
20
+ // Mock data for examples
21
+ const stats = [
22
+ { title: "Downloads", value: "31K", desc: "Jan 1st - Feb 1st" },
23
+ { title: "New Users", value: "4,200", desc: "↗︎ 400 (22%)" },
24
+ { title: "New Registers", value: "1,200", desc: "↘︎ 90 (14%)" }
25
+ ];
26
+
27
+ // Simulate loading
28
+ function simulateLoading() {
29
+ loading = true;
30
+ setTimeout(() => loading = false, 2000);
31
+ }
32
+
33
+ // Show toast message
34
+ function showToast(message: string) {
35
+ toast = message;
36
+ setTimeout(() => toast = "", 3000);
37
+ }
38
+
39
+ // Progress bar simulation
40
+ $effect(() => {
41
+ const interval = setInterval(() => {
42
+ if (progress < 100) {
43
+ progress += 1;
44
+ } else {
45
+ progress = 0;
46
+ }
47
+ }, 100);
48
+
49
+ return () => clearInterval(interval);
50
+ });
51
+
52
+ const tabs = [
53
+ { title: 'Basic UI', icon: '🎨' },
54
+ { title: 'Components', icon: '🧩' },
55
+ { title: 'Layout', icon: '📐' },
56
+ { title: 'Data Display', icon: '📊' },
57
+ { title: 'Feedback', icon: '💫' }
58
+ ];
59
+ </script>
60
+
61
+ <ThemeSelector />
62
+
63
+ <div class="min-h-screen bg-base-200 p-4">
64
+ <!-- Header with Theme Demo -->
65
+ <div class="text-center mb-8 hero bg-base-100 rounded-box p-8">
66
+ <div class="hero-content text-center">
67
+ <div>
68
+ <h1 class="text-5xl font-bold mb-4">DaisyUI Showcase</h1>
69
+ <p class="text-xl opacity-75 mb-6">A comprehensive demonstration of DaisyUI components with Svelte 5</p>
70
+ <div class="flex justify-center gap-2">
71
+ <button class="btn btn-primary" onclick={() => modalOpen = true}>Open Modal</button>
72
+ <button class="btn btn-secondary" onclick={() => drawerOpen = true}>Open Drawer</button>
73
+ </div>
74
+ </div>
75
+ </div>
76
+ </div>
77
+
78
+ <!-- Navigation Tabs -->
79
+ <div class="tabs tabs-boxed justify-center mb-8">
80
+ {#each tabs as tab, i}
81
+ <button
82
+ class="tab {activeTab === i ? 'tab-active' : ''}"
83
+ onclick={() => activeTab = i}
84
+ >
85
+ <span class="mr-2">{tab.icon}</span>
86
+ {tab.title}
87
+ </button>
88
+ {/each}
89
+ </div>
90
+
91
+ <!-- Main Content -->
92
+ <div class="container mx-auto">
93
+ {#if activeTab === 0}
94
+ <!-- Basic UI Elements -->
95
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
96
+ <!-- Buttons Showcase -->
97
+ <div class="card bg-base-100 shadow-xl">
98
+ <div class="card-body">
99
+ <h2 class="card-title">Buttons</h2>
100
+ <div class="flex flex-wrap gap-2">
101
+ {#each colors as color}
102
+ <button class="btn btn-{color}">{color}</button>
103
+ {/each}
104
+ </div>
105
+ <div class="divider">Sizes</div>
106
+ <div class="flex flex-wrap gap-2 items-center">
107
+ {#each sizes as size}
108
+ <button class="btn btn-{size}">{size}</button>
109
+ {/each}
110
+ </div>
111
+ <div class="divider">States</div>
112
+ <div class="flex flex-wrap gap-2">
113
+ <button class="btn btn-primary loading">Loading</button>
114
+ <button class="btn btn-disabled">Disabled</button>
115
+ <button class="btn btn-outline">Outline</button>
116
+ <button class="btn btn-link">Link</button>
117
+ </div>
118
+ </div>
119
+ </div>
120
+
121
+ <!-- Form Elements -->
122
+ <div class="card bg-base-100 shadow-xl">
123
+ <div class="card-body">
124
+ <h2 class="card-title">Form Elements</h2>
125
+ <div class="form-control gap-4">
126
+ <label class="input-group">
127
+ <span>Email</span>
128
+ <input type="text" placeholder="info@site.com" class="input input-bordered" />
129
+ </label>
130
+
131
+ <select class="select select-bordered w-full" bind:value={selected}>
132
+ <option>Option 1</option>
133
+ <option>Option 2</option>
134
+ <option>Option 3</option>
135
+ </select>
136
+
137
+ <div class="flex gap-4">
138
+ <label class="label cursor-pointer">
139
+ <span class="label-text mr-2">Toggle</span>
140
+ <input type="checkbox" class="toggle toggle-primary" />
141
+ </label>
142
+
143
+ <label class="label cursor-pointer">
144
+ <span class="label-text mr-2">Radio</span>
145
+ <input type="radio" name="radio-10" class="radio radio-primary" />
146
+ </label>
147
+ </div>
148
+
149
+ <input type="range" min="0" max="100" class="range range-primary" />
150
+ </div>
151
+ </div>
152
+ </div>
153
+ </div>
154
+
155
+ {:else if activeTab === 1}
156
+ <!-- Advanced Components -->
157
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
158
+ <!-- Dropdown and Menu -->
159
+ <div class="card bg-base-100 shadow-xl">
160
+ <div class="card-body">
161
+ <h2 class="card-title">Dropdowns & Menus</h2>
162
+ <div class="flex flex-wrap gap-4">
163
+ <div class="dropdown">
164
+ <button class="btn m-1">Dropdown</button>
165
+ <ul class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-52">
166
+ <li><button class="w-full text-left">Item 1</button></li>
167
+ <li><button class="w-full text-left">Item 2</button></li>
168
+ </ul>
169
+ </div>
170
+
171
+ <div class="menu bg-base-200 rounded-box">
172
+ <li><button class="w-full text-left">Menu Item 1</button></li>
173
+ <li><button class="w-full text-left">Menu Item 2</button></li>
174
+ </div>
175
+ </div>
176
+ </div>
177
+ </div>
178
+
179
+ <!-- Steps -->
180
+ <div class="card bg-base-100 shadow-xl">
181
+ <div class="card-body">
182
+ <h2 class="card-title">Steps</h2>
183
+ <ul class="steps steps-vertical lg:steps-horizontal w-full">
184
+ <li class="step step-primary">Register</li>
185
+ <li class="step" class:step-primary={steps >= 2}>Choose plan</li>
186
+ <li class="step" class:step-primary={steps >= 3}>Purchase</li>
187
+ <li class="step" class:step-primary={steps === 4}>Receive Product</li>
188
+ </ul>
189
+ <div class="flex justify-center mt-4">
190
+ <button class="btn btn-primary" onclick={() => steps = steps < 4 ? steps + 1 : 1}>
191
+ Next Step
192
+ </button>
193
+ </div>
194
+ </div>
195
+ </div>
196
+ </div>
197
+
198
+ {:else if activeTab === 2}
199
+ <!-- Layout Elements -->
200
+ <div class="grid grid-cols-1 gap-6">
201
+ <!-- Hero Section -->
202
+ <div class="card bg-base-100 shadow-xl">
203
+ <div class="card-body">
204
+ <h2 class="card-title">Hero Section</h2>
205
+ <div class="hero bg-base-200 rounded-box">
206
+ <div class="hero-content text-center">
207
+ <div class="max-w-md">
208
+ <h1 class="text-5xl font-bold">Hello there</h1>
209
+ <p class="py-6">This is a sample hero section with a button below.</p>
210
+ <button class="btn btn-primary">Get Started</button>
211
+ </div>
212
+ </div>
213
+ </div>
214
+ </div>
215
+ </div>
216
+
217
+ <!-- Grid Layout -->
218
+ <div class="card bg-base-100 shadow-xl">
219
+ <div class="card-body">
220
+ <h2 class="card-title">Grid Layout</h2>
221
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
222
+ {#each Array(3) as _, i}
223
+ <div class="bg-primary text-primary-content p-4 rounded-box text-center">
224
+ Grid Item {i + 1}
225
+ </div>
226
+ {/each}
227
+ </div>
228
+ </div>
229
+ </div>
230
+ </div>
231
+
232
+ {:else if activeTab === 3}
233
+ <!-- Data Display -->
234
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
235
+ <!-- Stats -->
236
+ <div class="card bg-base-100 shadow-xl">
237
+ <div class="card-body">
238
+ <h2 class="card-title">Statistics</h2>
239
+ <div class="stats shadow">
240
+ {#each stats as stat}
241
+ <div class="stat">
242
+ <div class="stat-title">{stat.title}</div>
243
+ <div class="stat-value">{stat.value}</div>
244
+ <div class="stat-desc">{stat.desc}</div>
245
+ </div>
246
+ {/each}
247
+ </div>
248
+ </div>
249
+ </div>
250
+
251
+ <!-- Table -->
252
+ <div class="card bg-base-100 shadow-xl">
253
+ <div class="card-body">
254
+ <h2 class="card-title">Table</h2>
255
+ <div class="overflow-x-auto">
256
+ <table class="table">
257
+ <thead>
258
+ <tr>
259
+ <th>Name</th>
260
+ <th>Job</th>
261
+ <th>Action</th>
262
+ </tr>
263
+ </thead>
264
+ <tbody>
265
+ <tr>
266
+ <td>Cy Ganderton</td>
267
+ <td>Quality Control Specialist</td>
268
+ <td><button class="btn btn-xs">Details</button></td>
269
+ </tr>
270
+ <tr>
271
+ <td>Hart Hagerty</td>
272
+ <td>Desktop Support Technician</td>
273
+ <td><button class="btn btn-xs">Details</button></td>
274
+ </tr>
275
+ </tbody>
276
+ </table>
277
+ </div>
278
+ </div>
279
+ </div>
280
+ </div>
281
+
282
+ {:else if activeTab === 4}
283
+ <!-- Feedback Elements -->
284
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
285
+ <!-- Alerts -->
286
+ <div class="card bg-base-100 shadow-xl">
287
+ <div class="card-body">
288
+ <h2 class="card-title">Alerts</h2>
289
+ <div class="flex flex-col gap-2">
290
+ {#each colors as color}
291
+ <div class="alert alert-{color}">
292
+ <span>This is a {color} alert</span>
293
+ </div>
294
+ {/each}
295
+ </div>
296
+ </div>
297
+ </div>
298
+
299
+ <!-- Progress & Loading -->
300
+ <div class="card bg-base-100 shadow-xl">
301
+ <div class="card-body">
302
+ <h2 class="card-title">Progress & Loading</h2>
303
+ <progress class="progress w-full" value={progress} max="100"></progress>
304
+ <div class="flex gap-2 mt-4">
305
+ <button class="btn" onclick={simulateLoading}>
306
+ {loading ? 'Loading...' : 'Simulate Loading'}
307
+ </button>
308
+ {#if loading}
309
+ <span class="loading loading-spinner loading-lg"></span>
310
+ {/if}
311
+ </div>
312
+ </div>
313
+ </div>
314
+ </div>
315
+ {/if}
316
+ </div>
317
+
318
+ <!-- Modal -->
319
+ {#if modalOpen}
320
+ <div class="modal modal-open">
321
+ <div class="modal-box">
322
+ <h3 class="font-bold text-lg">Modal Title</h3>
323
+ <p class="py-4">This is a sample modal dialog using DaisyUI.</p>
324
+ <div class="modal-action">
325
+ <button class="btn" onclick={() => modalOpen = false}>Close</button>
326
+ </div>
327
+ </div>
328
+ </div>
329
+ {/if}
330
+
331
+ <!-- Drawer -->
332
+ {#if drawerOpen}
333
+ <div class="drawer drawer-end">
334
+ <input id="my-drawer" type="checkbox" class="drawer-toggle" checked />
335
+ <div class="drawer-side">
336
+ <button
337
+ class="drawer-overlay"
338
+ onclick={() => drawerOpen = false}
339
+ onkeydown={(e) => e.key === 'Escape' && (drawerOpen = false)}
340
+ ></button>
341
+ <ul class="menu p-4 w-80 min-h-full bg-base-200 text-base-content">
342
+ <li><button class="w-full text-left">Sidebar Item 1</button></li>
343
+ <li><button class="w-full text-left">Sidebar Item 2</button></li>
344
+ </ul>
345
+ </div>
346
+ </div>
347
+ {/if}
348
+
349
+ <!-- Toast -->
350
+ {#if toast}
351
+ <div class="toast toast-end">
352
+ <div class="alert alert-info">
353
+ <span>{toast}</span>
354
+ </div>
355
+ </div>
356
+ {/if}
357
+
358
+ <!-- Footer -->
359
+ <footer class="footer footer-center p-4 bg-base-300 text-base-content mt-8">
360
+ <div>
361
+ <p>Made with 💝 using Svelte 5 and DaisyUI</p>
362
+ </div>
363
+ </footer>
364
+ </div>
@@ -0,0 +1,3 @@
1
+ declare const UiShowcase: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type UiShowcase = ReturnType<typeof UiShowcase>;
3
+ export default UiShowcase;
@@ -0,0 +1,115 @@
1
+ <!-- src/lib/components/layout/Footer.svelte -->
2
+ <script lang="ts">
3
+ import { appData } from '../../stores/app.svelte.js';
4
+
5
+ // Define footer section types
6
+ interface FooterLink {
7
+ text: string;
8
+ href: string;
9
+ }
10
+
11
+ interface FooterSection {
12
+ title: string;
13
+ links: FooterLink[];
14
+ }
15
+
16
+ // Footer sections configuration
17
+ const footerSections: FooterSection[] = [
18
+ {
19
+ title: "Product",
20
+ links: [
21
+ { text: "Features", href: "/features" },
22
+ { text: "Docs", href: "/docs" },
23
+ { text: "API", href: "/api" },
24
+ { text: "Pricing", href: "/pricing" }
25
+ ]
26
+ },
27
+ {
28
+ title: "Company",
29
+ links: [
30
+ { text: "About", href: "/about" },
31
+ { text: "Blog", href: "/blog" },
32
+ { text: "Careers", href: "/careers" },
33
+ { text: "Contact", href: "/contact" }
34
+ ]
35
+ },
36
+ {
37
+ title: "Resources",
38
+ links: [
39
+ { text: "Community", href: "/community" },
40
+ { text: "Help Center", href: "/help" },
41
+ { text: "Status", href: "/status" },
42
+ { text: "Terms", href: "/terms" }
43
+ ]
44
+ }
45
+ ];
46
+
47
+ // Current year for copyright
48
+ const currentYear = new Date().getFullYear();
49
+ </script>
50
+
51
+ <footer class="footer p-10 bg-base-200 text-base-content">
52
+ <!-- Logo and company info -->
53
+ <aside class="flex flex-col items-start gap-4">
54
+ <img
55
+ src="/favicon.png"
56
+ alt="{appData.name} Logo"
57
+ class="h-12 w-12 rounded-lg hover:animate-pulse"
58
+ />
59
+ <div>
60
+ <h2 class="text-lg font-bold">{appData.name}</h2>
61
+ <p class="text-sm opacity-75">
62
+ Building better software<br/>
63
+ since {currentYear}
64
+ </p>
65
+ </div>
66
+
67
+ <!-- Social media links -->
68
+ <div class="flex gap-4">
69
+ <a
70
+ href="https://github.com/Yrrrrrf"
71
+ aria-label="Visit my GitHub profile"
72
+ class="btn btn-circle btn-ghost btn-sm hover:text-primary"
73
+ >
74
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 24 24">
75
+ <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
76
+ </svg>
77
+ </a>
78
+ <a
79
+ href="https://twitter.com"
80
+ aria-label="Visit my Twitter profile"
81
+ class="btn btn-circle btn-ghost btn-sm hover:text-primary"
82
+ >
83
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 24 24">
84
+ <path d="M24 4.557c-.883.392-1.832.656-2.828.775 1.017-.609 1.798-1.574 2.165-2.724-.951.564-2.005.974-3.127 1.195-.897-.957-2.178-1.555-3.594-1.555-3.179 0-5.515 2.966-4.797 6.045-4.091-.205-7.719-2.165-10.148-5.144-1.29 2.213-.669 5.108 1.523 6.574-.806-.026-1.566-.247-2.229-.616-.054 2.281 1.581 4.415 3.949 4.89-.693.188-1.452.232-2.224.084.626 1.956 2.444 3.379 4.6 3.419-2.07 1.623-4.678 2.348-7.29 2.04 2.179 1.397 4.768 2.212 7.548 2.212 9.142 0 14.307-7.721 13.995-14.646.962-.695 1.797-1.562 2.457-2.549z"/>
85
+ </svg>
86
+ </a>
87
+ <a
88
+ href="https://linkedin.com"
89
+ aria-label="Visit my LinkedIn profile"
90
+ class="btn btn-circle btn-ghost btn-sm hover:text-primary"
91
+ >
92
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 24 24">
93
+ <path d="M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm-11 19h-3v-11h3v11zm-1.5-12.268c-.966 0-1.75-.79-1.75-1.764s.784-1.764 1.75-1.764 1.75.79 1.75 1.764-.783 1.764-1.75 1.764zm13.5 12.268h-3v-5.604c0-3.368-4-3.113-4 0v5.604h-3v-11h3v1.765c1.396-2.586 7-2.777 7 2.476v6.759z"/>
94
+ </svg>
95
+ </a>
96
+ </div>
97
+
98
+ <!-- Navigation sections -->
99
+ {#each footerSections as section}
100
+ <nav>
101
+ <h6 class="footer-title">{section.title}</h6>
102
+ {#each section.links as link}
103
+ <a href={link.href} class="link link-hover">{link.text}</a>
104
+ {/each}
105
+ </nav>
106
+ {/each}
107
+ </footer>
108
+
109
+ <!-- Copyright footer -->
110
+ <footer class="footer footer-center p-4 bg-base-300 text-base-content">
111
+ <aside class="flex flex-col items-center gap-2">
112
+ <p>Copyright © {currentYear} {appData.name} - All rights reserved</p>
113
+ <p class="text-sm opacity-75">Version {appData.version}</p>
114
+ </aside>
115
+ </footer>
@@ -0,0 +1,18 @@
1
+ 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> {
2
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
3
+ $$bindings?: Bindings;
4
+ } & Exports;
5
+ (internal: unknown, props: {
6
+ $$events?: Events;
7
+ $$slots?: Slots;
8
+ }): Exports & {
9
+ $set?: any;
10
+ $on?: any;
11
+ };
12
+ z_$$bindings?: Bindings;
13
+ }
14
+ declare const Footer: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
+ [evt: string]: CustomEvent<any>;
16
+ }, {}, {}, string>;
17
+ type Footer = InstanceType<typeof Footer>;
18
+ export default Footer;
@@ -0,0 +1,78 @@
1
+ <script lang="ts">
2
+ import { themeStore } from '../../stores/theme.svelte.js';
3
+
4
+ // Local state using runes
5
+ let showDropdown = $state(false);
6
+
7
+ // Initialize theme on mount
8
+ themeStore.init();
9
+
10
+ // Get available themes
11
+ const themes = themeStore.getAvailableThemes();
12
+
13
+ function handleThemeSelect(themeValue: string) {
14
+ themeStore.setTheme(themeValue);
15
+ showDropdown = false;
16
+ }
17
+ </script>
18
+
19
+ <div class="theme-selector">
20
+ <button
21
+ class="btn"
22
+ onclick={() => showDropdown = !showDropdown}
23
+ >
24
+ <span>{themeStore.themeConfig.icon}</span>
25
+ </button>
26
+
27
+ {#if showDropdown}
28
+ <ul class="theme-menu">
29
+ {#each themes as theme}
30
+ <li>
31
+ <button
32
+ class="theme-option"
33
+ class:active={themeStore.currentTheme === theme.value}
34
+ onclick={() => handleThemeSelect(theme.value)}
35
+ >
36
+ <span>{theme.icon}</span>
37
+ <span>{theme.name}</span>
38
+ </button>
39
+ </li>
40
+ {/each}
41
+ </ul>
42
+ {/if}
43
+ </div>
44
+
45
+ <style>
46
+ .theme-selector {
47
+ position: relative;
48
+ }
49
+
50
+ .theme-menu {
51
+ position: absolute;
52
+ right: 0;
53
+ top: 100%;
54
+ margin-top: 0.5rem;
55
+ background: var(--background);
56
+ border: 1px solid var(--border);
57
+ border-radius: 0.5rem;
58
+ padding: 0.5rem;
59
+ min-width: 150px;
60
+ }
61
+
62
+ .theme-option {
63
+ display: flex;
64
+ align-items: center;
65
+ gap: 0.5rem;
66
+ padding: 0.5rem;
67
+ width: 100%;
68
+ text-align: left;
69
+ }
70
+
71
+ .theme-option:hover {
72
+ background: var(--hover);
73
+ }
74
+
75
+ .active {
76
+ background: var(--active);
77
+ }
78
+ </style>
@@ -0,0 +1,3 @@
1
+ declare const ThemeSelector: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type ThemeSelector = ReturnType<typeof ThemeSelector>;
3
+ export default ThemeSelector;