chore: Update Tailwind CSS and dependencies

- Downgraded Tailwind CSS to version 3.4.17 for compatibility.
- Updated various dependencies to resolve version conflicts and
  ensure smooth operation.
- Added missing types for Node.js to enhance type safety.
- Improved the theming system using CSS custom properties (variables)
  for better customization and dark mode support.
- Refactored styles in `app.css` to improve maintainability and
  readability.  Updated the color palette to enhance the user
  experience.
- Updated the PostCSS configuration to use the new Tailwind CSS
  version.
- Updated component styles to utilize the new theming system.
This commit is contained in:
Mahmoud Emad 2025-05-12 13:47:47 +03:00
parent da0ced9b4a
commit f22e9faae2
15 changed files with 1157 additions and 169 deletions

View File

@ -21,7 +21,7 @@
"postcss": "^8.5.3",
"svelte": "^5.28.1",
"svelte-check": "^4.1.6",
"tailwindcss": "^4.1.6",
"tailwindcss": "^3.4.17",
"typescript": "~5.8.3",
"vite": "^6.3.5"
},

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
export default {
plugins: {
'@tailwindcss/postcss': {},
tailwindcss: {},
autoprefixer: {},
},
}

View File

@ -1,11 +1,115 @@
@import "tailwindcss";
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
/* Primary color (blue) */
--color-primary-50: 239 246 255;
/* blue-50 */
--color-primary-100: 219 234 254;
/* blue-100 */
--color-primary-200: 191 219 254;
/* blue-200 */
--color-primary-300: 147 197 253;
/* blue-300 */
--color-primary-400: 96 165 250;
/* blue-400 */
--color-primary-500: 59 130 246;
/* blue-500 */
--color-primary-600: 37 99 235;
/* blue-600 */
--color-primary-700: 29 78 216;
/* blue-700 */
--color-primary-800: 30 64 175;
/* blue-800 */
--color-primary-900: 30 58 138;
/* blue-900 */
/* Background colors */
--color-background: 249 250 251;
/* gray-50 */
--color-background-secondary: 255 255 255;
/* white */
/* Text colors */
--color-text: 17 24 39;
/* gray-900 */
--color-text-secondary: 55 65 81;
/* gray-700 */
--color-text-muted: 107 114 128;
/* gray-500 */
/* Border colors */
--color-border: 229 231 235;
/* gray-200 */
--color-border-secondary: 209 213 219;
/* gray-300 */
}
.dark {
/* Primary color (blue) - slightly adjusted for dark mode */
--color-primary-50: 30 58 138;
/* blue-900 */
--color-primary-100: 30 64 175;
/* blue-800 */
--color-primary-200: 29 78 216;
/* blue-700 */
--color-primary-300: 37 99 235;
/* blue-600 */
--color-primary-400: 59 130 246;
/* blue-500 */
--color-primary-500: 96 165 250;
/* blue-400 */
--color-primary-600: 147 197 253;
/* blue-300 */
--color-primary-700: 191 219 254;
/* blue-200 */
--color-primary-800: 219 234 254;
/* blue-100 */
--color-primary-900: 239 246 255;
/* blue-50 */
/* Background colors */
--color-background: 17 24 39;
/* gray-900 */
--color-background-secondary: 31 41 55;
/* gray-800 */
/* Text colors */
--color-text: 249 250 251;
/* gray-50 */
--color-text-secondary: 229 231 235;
/* gray-200 */
--color-text-muted: 156 163 175;
/* gray-400 */
/* Border colors */
--color-border: 55 65 81;
/* gray-700 */
--color-border-secondary: 75 85 99;
/* gray-600 */
}
body {
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
@apply bg-background text-text;
}
/* Add utility classes for components */
.btn {
@apply inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50;
}
.btn-primary {
@apply bg-primary-600 text-white hover:bg-primary-700;
}
.btn-secondary {
@apply bg-background-secondary text-text border border-border hover:bg-background hover:text-text-secondary;
}
.btn-ghost {
@apply hover:bg-background-secondary hover:text-text-secondary;
}
}

View File

@ -3,7 +3,7 @@
</script>
<footer
class="bg-gray-100 border-t border-gray-200 py-4 sm:py-6 px-3 sm:px-4 w-full mt-auto"
class="bg-background-secondary border-t border-border py-4 sm:py-6 px-3 sm:px-4 w-full mt-auto"
>
<div class="container mx-auto max-w-6xl">
<div
@ -11,8 +11,10 @@
>
<!-- Logo and tagline -->
<div class="text-center sm:text-left">
<div class="text-lg font-semibold text-blue-800">SecureWeb</div>
<p class="text-gray-600 text-sm mt-1">
<div class="text-lg font-semibold text-primary-600">
SecureWeb
</div>
<p class="text-text-secondary text-sm mt-1">
Secure and reliable web solutions
</p>
</div>
@ -23,22 +25,22 @@
>
<a
href="/"
class="text-gray-600 hover:text-blue-800 transition-colors text-sm sm:text-base"
class="text-text-secondary hover:text-primary-600 transition-colors text-sm sm:text-base"
>Home</a
>
<a
href="/about"
class="text-gray-600 hover:text-blue-800 transition-colors text-sm sm:text-base"
class="text-text-secondary hover:text-primary-600 transition-colors text-sm sm:text-base"
>About</a
>
<a
href="/services"
class="text-gray-600 hover:text-blue-800 transition-colors text-sm sm:text-base"
class="text-text-secondary hover:text-primary-600 transition-colors text-sm sm:text-base"
>Services</a
>
<a
href="/contact"
class="text-gray-600 hover:text-blue-800 transition-colors text-sm sm:text-base"
class="text-text-secondary hover:text-primary-600 transition-colors text-sm sm:text-base"
>Contact</a
>
</div>
@ -48,7 +50,7 @@
<a
href="/twitter"
aria-label="Twitter"
class="text-gray-500 hover:text-blue-800"
class="text-text-muted hover:text-primary-600"
>
<svg
xmlns="http://www.w3.org/2000/svg"
@ -64,7 +66,7 @@
<a
href="/github"
aria-label="GitHub"
class="text-gray-500 hover:text-blue-800"
class="text-text-muted hover:text-primary-600"
>
<svg
xmlns="http://www.w3.org/2000/svg"
@ -80,7 +82,7 @@
<a
href="/linkedin"
aria-label="LinkedIn"
class="text-gray-500 hover:text-blue-800"
class="text-text-muted hover:text-primary-600"
>
<svg
xmlns="http://www.w3.org/2000/svg"
@ -97,7 +99,7 @@
</div>
<div
class="border-t border-gray-200 mt-4 sm:mt-6 pt-4 sm:pt-6 text-center text-gray-500 text-xs sm:text-sm"
class="border-t border-border mt-4 sm:mt-6 pt-4 sm:pt-6 text-center text-text-muted text-xs sm:text-sm"
>
<p>
&copy; {new Date().getFullYear()} SecureWeb. All rights reserved.

View File

@ -8,10 +8,10 @@
$: actualPath = contentPath || "introduction/introduction";
</script>
<div class="py-4 sm:py-6 md:py-8 px-3 sm:px-4 max-w-7xl mx-auto">
<div class="">
{#if contentPath}
<div
class="bg-white rounded-lg shadow-sm p-4 sm:p-6 mb-6 sm:mb-8 overflow-x-auto"
class="bg-background-secondary shadow-sm p-4 sm:p-6 overflow-x-auto"
>
<MarkdownContent path={actualPath} />
</div>
@ -21,12 +21,14 @@
<div class="md:flex md:items-center md:justify-between">
<div class="md:w-1/2 mb-8 md:mb-0 md:pr-6">
<h1
class="text-3xl sm:text-4xl md:text-5xl font-bold mb-4 sm:mb-6 text-gray-900 leading-tight"
class="text-3xl sm:text-4xl md:text-5xl font-bold mb-4 sm:mb-6 text-text leading-tight"
>
Welcome to <span class="text-blue-600">SecureWeb</span>
Welcome to <span class="text-primary-600"
>SecureWeb</span
>
</h1>
<p
class="text-lg sm:text-xl text-gray-600 max-w-2xl mb-6 sm:mb-8 leading-relaxed"
class="text-lg sm:text-xl text-text-secondary max-w-2xl mb-6 sm:mb-8 leading-relaxed"
>
A comprehensive platform for secure and reliable web
solutions designed for modern businesses.
@ -35,11 +37,11 @@
class="flex flex-col xs:flex-row justify-center md:justify-start gap-3 sm:gap-4"
>
<button
class="bg-blue-600 hover:bg-blue-700 text-white px-6 sm:px-8 py-2.5 sm:py-3 rounded-md font-medium transition-colors duration-200 w-full xs:w-auto"
class="bg-primary-600 hover:bg-primary-700 text-white px-6 sm:px-8 py-2.5 sm:py-3 rounded-md font-medium transition-colors duration-200 w-full xs:w-auto"
>Get Started</button
>
<button
class="bg-gray-100 hover:bg-gray-200 text-gray-800 px-6 sm:px-8 py-2.5 sm:py-3 rounded-md font-medium border border-gray-300 transition-colors duration-200 w-full xs:w-auto"
class="bg-background-secondary hover:bg-background text-text px-6 sm:px-8 py-2.5 sm:py-3 rounded-md font-medium border border-border transition-colors duration-200 w-full xs:w-auto"
>Learn More</button
>
</div>
@ -59,11 +61,13 @@
<section class="mb-12 sm:mb-16 md:mb-20">
<div class="text-center mb-8 sm:mb-12">
<h2
class="text-2xl sm:text-3xl font-bold mb-3 sm:mb-4 text-gray-900"
class="text-2xl sm:text-3xl font-bold mb-3 sm:mb-4 text-text"
>
Our Features
</h2>
<p class="text-lg sm:text-xl text-gray-600 max-w-3xl mx-auto">
<p
class="text-lg sm:text-xl text-text-secondary max-w-3xl mx-auto"
>
Discover what makes SecureWeb the preferred choice for
security-conscious businesses.
</p>
@ -73,20 +77,22 @@
class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6 sm:gap-8 md:gap-10"
>
<div
class="bg-gray-50 p-6 sm:p-8 rounded-xl shadow-md hover:shadow-lg transition-shadow border border-gray-100"
class="bg-background p-6 sm:p-8 rounded-xl shadow-md hover:shadow-lg transition-shadow border border-border"
>
<div
class="bg-blue-100 p-3 rounded-full w-12 h-12 sm:w-14 sm:h-14 flex items-center justify-center mb-4 sm:mb-6"
class="bg-primary-100 p-3 rounded-full w-12 h-12 sm:w-14 sm:h-14 flex items-center justify-center mb-4 sm:mb-6"
>
<Shield class="h-6 w-6 sm:h-7 sm:w-7 text-blue-600" />
<Shield
class="h-6 w-6 sm:h-7 sm:w-7 text-primary-600"
/>
</div>
<h3
class="text-lg sm:text-xl font-bold mb-3 sm:mb-4 text-gray-800"
class="text-lg sm:text-xl font-bold mb-3 sm:mb-4 text-text"
>
Advanced Security
</h3>
<p
class="text-gray-600 leading-relaxed text-sm sm:text-base"
class="text-text-secondary leading-relaxed text-sm sm:text-base"
>
State-of-the-art encryption and security protocols to
keep your data protected from threats.
@ -94,20 +100,20 @@
</div>
<div
class="bg-white p-6 sm:p-8 rounded-xl shadow-md hover:shadow-lg transition-shadow border border-gray-100"
class="bg-background-secondary p-6 sm:p-8 rounded-xl shadow-md hover:shadow-lg transition-shadow border border-border"
>
<div
class="bg-blue-100 p-3 rounded-full w-12 h-12 sm:w-14 sm:h-14 flex items-center justify-center mb-4 sm:mb-6"
class="bg-primary-100 p-3 rounded-full w-12 h-12 sm:w-14 sm:h-14 flex items-center justify-center mb-4 sm:mb-6"
>
<Zap class="h-6 w-6 sm:h-7 sm:w-7 text-blue-600" />
<Zap class="h-6 w-6 sm:h-7 sm:w-7 text-primary-600" />
</div>
<h3
class="text-lg sm:text-xl font-bold mb-3 sm:mb-4 text-gray-800"
class="text-lg sm:text-xl font-bold mb-3 sm:mb-4 text-text"
>
Lightning Performance
</h3>
<p
class="text-gray-600 leading-relaxed text-sm sm:text-base"
class="text-text-secondary leading-relaxed text-sm sm:text-base"
>
Optimized for speed and efficiency, ensuring a smooth
and responsive user experience.
@ -115,22 +121,22 @@
</div>
<div
class="bg-gray-50 p-6 sm:p-8 rounded-xl shadow-md hover:shadow-lg transition-shadow border border-gray-100 sm:col-span-2 md:col-span-1"
class="bg-background p-6 sm:p-8 rounded-xl shadow-md hover:shadow-lg transition-shadow border border-border sm:col-span-2 md:col-span-1"
>
<div
class="bg-blue-100 p-3 rounded-full w-12 h-12 sm:w-14 sm:h-14 flex items-center justify-center mb-4 sm:mb-6"
class="bg-primary-100 p-3 rounded-full w-12 h-12 sm:w-14 sm:h-14 flex items-center justify-center mb-4 sm:mb-6"
>
<Smartphone
class="h-6 w-6 sm:h-7 sm:w-7 text-blue-600"
class="h-6 w-6 sm:h-7 sm:w-7 text-primary-600"
/>
</div>
<h3
class="text-lg sm:text-xl font-bold mb-3 sm:mb-4 text-gray-800"
class="text-lg sm:text-xl font-bold mb-3 sm:mb-4 text-text"
>
Responsive Design
</h3>
<p
class="text-gray-600 leading-relaxed text-sm sm:text-base"
class="text-text-secondary leading-relaxed text-sm sm:text-base"
>
Fully responsive layouts that provide a seamless
experience across all devices and screen sizes.
@ -142,7 +148,7 @@
<!-- CTA Section -->
<section>
<div
class="bg-gradient-to-r from-blue-600 to-blue-800 rounded-xl sm:rounded-2xl p-6 sm:p-8 md:p-10 text-white shadow-lg sm:shadow-xl"
class="bg-gradient-to-r from-primary-600 to-primary-800 rounded-xl sm:rounded-2xl p-6 sm:p-8 md:p-10 text-white shadow-lg sm:shadow-xl"
>
<div class="md:flex md:items-center md:justify-between">
<div class="mb-6 md:mb-0 md:w-2/3 md:pr-6">
@ -150,7 +156,7 @@
Ready to Get Started?
</h2>
<p
class="text-base sm:text-lg text-blue-100 mb-0 max-w-2xl"
class="text-base sm:text-lg text-primary-100 mb-0 max-w-2xl"
>
Join thousands of satisfied users who trust
SecureWeb for their web security needs. Sign up
@ -159,7 +165,7 @@
</div>
<div class="flex justify-center md:justify-end">
<button
class="bg-white text-blue-700 hover:bg-blue-50 px-6 sm:px-8 py-2.5 sm:py-3 rounded-lg font-semibold text-base sm:text-lg shadow-md transition-colors w-full xs:w-auto"
class="bg-background-secondary text-primary-700 hover:bg-primary-50 px-6 sm:px-8 py-2.5 sm:py-3 rounded-lg font-semibold text-base sm:text-lg shadow-md transition-colors w-full xs:w-auto"
>
Sign Up Now
</button>

View File

@ -5,6 +5,7 @@
import NavDataProvider from "./NavDataProvider.svelte";
import Home from "./Home.svelte";
import { onMount } from "svelte";
import ThemeProvider from "../lib/theme/ThemeProvider.svelte";
let sidebarVisible = true;
let isMobile = false;
@ -53,46 +54,43 @@
});
</script>
<div class="flex flex-col min-h-screen bg-gray-50">
<Navbar {toggleSidebar} {isMobile} {sidebarVisible} />
<ThemeProvider>
<div class="flex flex-col min-h-screen bg-background">
<Navbar {toggleSidebar} {isMobile} {sidebarVisible} />
<div class="flex flex-1 pt-16 relative">
<!-- Overlay for mobile sidebar -->
{#if sidebarVisible && isMobile}
<div
class="fixed inset-0 bg-gray-900 bg-opacity-50 z-20 transition-opacity duration-300"
on:click={toggleSidebar}
aria-hidden="true"
></div>
{/if}
<NavDataProvider let:navData>
<!-- Sidebar with improved mobile handling -->
<Sidebar
{navData}
onNavItemClick={handleNavItemClick}
visible={sidebarVisible}
{isMobile}
/>
<main
class={`flex-1 transition-all duration-300 ${
sidebarVisible && !isMobile ? "md:ml-64" : "ml-0"
}`}
>
<div class="flex flex-1 pt-16 relative">
<!-- Overlay for mobile sidebar -->
{#if sidebarVisible && isMobile}
<div
class="container mx-auto px-3 sm:px-4 py-4 sm:py-8 min-h-[calc(100vh-12rem)]"
class="fixed inset-0 bg-text bg-opacity-50 z-20 transition-opacity duration-300"
on:click={toggleSidebar}
aria-hidden="true"
></div>
{/if}
<NavDataProvider let:navData>
<!-- Sidebar with improved mobile handling -->
<Sidebar
{navData}
onNavItemClick={handleNavItemClick}
visible={sidebarVisible}
{isMobile}
/>
<main
class={`flex-1 transition-all duration-300 ${
sidebarVisible && !isMobile ? "md:ml-64" : "ml-0"
}`}
>
{#if selectedContentPath}
<Home contentPath={selectedContentPath} />
{:else}
<slot />
{/if}
</div>
</main>
</NavDataProvider>
</div>
</main>
</NavDataProvider>
</div>
<!-- Footer is now outside the main content area -->
<Footer />
</div>
<Footer />
</div>
</ThemeProvider>

View File

@ -94,13 +94,13 @@
<style>
.markdown-content {
padding: 0.5rem;
/* padding: 0.5rem; */
max-width: 100%;
}
@media (min-width: 640px) {
.markdown-content {
padding: 1rem;
/* padding: 1rem; */
}
}
@ -118,14 +118,14 @@
}
.error {
color: #e53e3e;
color: rgb(229 62 62);
}
.content :global(h1) {
font-size: 1.75rem;
font-weight: 700;
margin-bottom: 1rem;
color: #1e40af;
color: rgb(var(--color-primary-700));
word-break: break-word;
}
@ -134,7 +134,7 @@
font-weight: 600;
margin-top: 1.5rem;
margin-bottom: 0.75rem;
color: #1e3a8a;
color: rgb(var(--color-primary-800));
word-break: break-word;
}
@ -143,7 +143,7 @@
font-weight: 600;
margin-top: 1.25rem;
margin-bottom: 0.5rem;
color: #1e3a8a;
color: rgb(var(--color-primary-800));
word-break: break-word;
}
@ -163,7 +163,7 @@
}
.content :global(a) {
color: #2563eb;
color: rgb(var(--color-primary-600));
text-decoration: none;
word-break: break-word;
}
@ -173,16 +173,16 @@
}
.content :global(blockquote) {
border-left: 4px solid #e5e7eb;
border-left: 4px solid rgb(var(--color-border));
padding-left: 1rem;
margin-left: 0;
margin-right: 0;
font-style: italic;
color: #4b5563;
color: rgb(var(--color-text-secondary));
}
.content :global(code) {
background-color: #f3f4f6;
background-color: rgb(var(--color-background-secondary));
padding: 0.2rem 0.4rem;
border-radius: 0.25rem;
font-family: monospace;
@ -192,7 +192,7 @@
}
.content :global(pre) {
background-color: #f3f4f6;
background-color: rgb(var(--color-background-secondary));
padding: 0.75rem;
border-radius: 0.5rem;
overflow-x: auto;
@ -216,7 +216,7 @@
.content :global(hr) {
border: 0;
border-top: 1px solid #e5e7eb;
border-top: 1px solid rgb(var(--color-border));
margin: 1.5rem 0;
}
@ -230,13 +230,13 @@
.content :global(th),
.content :global(td) {
border: 1px solid #e5e7eb;
border: 1px solid rgb(var(--color-border));
padding: 0.5rem;
min-width: 100px;
}
.content :global(th) {
background-color: #f9fafb;
background-color: rgb(var(--color-background-secondary));
}
@media (min-width: 640px) {

View File

@ -41,15 +41,9 @@
>
<div class="tree-line" style="width: {indentation}px;"></div>
<div class="icon-container">
{#if isExpanded}
<ChevronDown
class="chevron-icon {isExpanded ? 'expanded' : ''}"
/>
{:else}
<ChevronRight
class="chevron-icon {isExpanded ? 'expanded' : ''}"
/>
{/if}
<ChevronRight
class="chevron-icon {isExpanded ? 'expanded' : ''}"
/>
</div>
<span class="label">{item.label}</span>
</button>
@ -101,7 +95,7 @@
position: relative;
border: none;
background: transparent;
color: #4b5563;
color: var(--color-text-secondary);
font-size: 0.9rem;
cursor: pointer;
transition:
@ -110,12 +104,12 @@
}
.nav-button:hover {
background-color: rgba(0, 0, 0, 0.05);
background-color: rgba(var(--color-background), 0.5);
}
.nav-button.active {
background-color: rgba(59, 130, 246, 0.1);
color: #3b82f6;
background-color: rgba(var(--color-primary-500), 0.1);
color: rgb(var(--color-primary-600));
font-weight: 500;
}
@ -134,7 +128,7 @@
top: 0;
bottom: 0;
width: 1px;
background-color: #e5e7eb;
background-color: rgb(var(--color-border));
}
.icon-container {
@ -143,13 +137,13 @@
margin-right: 8px;
}
.chevron-icon {
:global(.chevron-icon) {
width: 16px;
height: 16px;
transition: transform 0.2s;
}
.chevron-icon.expanded {
:global(.chevron-icon.expanded) {
transform: rotate(90deg);
}

View File

@ -1,5 +1,6 @@
<script lang="ts">
import { Menu, Search, User, Bell, X } from "lucide-svelte";
import { Menu, Search, X } from "lucide-svelte";
import ThemeToggle from "../lib/theme/ThemeToggle.svelte";
export let toggleSidebar: () => void = () => {};
export let isMobile: boolean = false;
@ -7,11 +8,11 @@
</script>
<header
class="bg-gray-50 border-b border-gray-200 fixed top-0 left-0 right-0 z-30 h-16 flex items-center justify-between px-3 sm:px-4 shadow-sm"
class="bg-background border-b border-border fixed top-0 left-0 right-0 z-30 h-16 flex items-center justify-between px-3 sm:px-4 shadow-sm"
>
<div class="flex items-center">
<button
class="mr-3 p-2 rounded-md hover:bg-gray-100 text-gray-700"
class="mr-3 p-2 rounded-md hover:bg-background-secondary text-text"
on:click={toggleSidebar}
aria-label={sidebarVisible ? "Close sidebar" : "Open sidebar"}
>
@ -21,13 +22,15 @@
<Menu class="h-5 w-5" />
{/if}
</button>
<div class="text-lg sm:text-xl font-bold text-blue-800">SecureWeb</div>
<div class="text-lg sm:text-xl font-bold text-primary-600">
SecureWeb
</div>
</div>
<div class="flex items-center space-x-2 sm:space-x-4">
<!-- Search button for mobile -->
<button
class="md:hidden p-2 rounded-md hover:bg-gray-100 text-gray-700"
class="md:hidden p-2 rounded-md hover:bg-background-secondary text-text"
>
<Search class="h-5 w-5" />
</button>
@ -35,26 +38,16 @@
<!-- Search bar for desktop -->
<div class="relative hidden md:block">
<Search
class="h-4 w-4 absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"
class="h-4 w-4 absolute left-3 top-1/2 transform -translate-y-1/2 text-text-muted"
/>
<input
type="text"
placeholder="Search..."
class="pl-9 pr-4 py-1.5 text-sm rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent w-40 lg:w-64"
class="pl-9 pr-4 py-1.5 text-sm rounded-md border border-border bg-background text-text focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent w-40 lg:w-64"
/>
</div>
<button
class="p-2 rounded-full hover:bg-gray-100 text-gray-700 relative hidden sm:block"
>
<Bell class="h-5 w-5" />
<span
class="absolute top-1 right-1 w-2 h-2 bg-blue-500 rounded-full"
></span>
</button>
<button class="p-1.5 rounded-full hover:bg-gray-100 text-gray-700">
<User class="h-5 w-5" />
</button>
<!-- Theme toggle -->
<ThemeToggle class="hover:bg-background-secondary" />
</div>
</header>

View File

@ -75,25 +75,11 @@
</script>
<aside
class="sidebar bg-white border-gray-200 h-screen fixed top-0 left-0 pt-16 overflow-y-auto shadow-sm z-20
class="sidebar bg-background-secondary border-border h-screen fixed top-0 left-0 pt-16 overflow-y-auto shadow-sm z-20
{isMobile ? 'w-[85%] max-w-xs' : 'w-64'}
transition-transform duration-300 ease-in-out
{visible ? 'translate-x-0' : '-translate-x-full'}"
>
<div
class="sidebar-header border-gray-200 p-3 flex justify-between items-center"
>
{#if isMobile}
<button
class="p-1 rounded-md hover:bg-gray-100"
on:click={toggleSidebar}
aria-label="Close sidebar"
>
<X class="w-5 h-5 text-gray-500" />
</button>
{/if}
</div>
<nav class="w-full py-2">
{#each navData as item}
<NavItemComponent
@ -110,11 +96,11 @@
{#if isMobile && !visible}
<button
class="fixed top-4 left-4 z-10 p-2 bg-white rounded-md shadow-md hover:bg-gray-100"
class="fixed top-4 left-4 z-10 p-2 bg-background-secondary rounded-md shadow-md hover:bg-background"
on:click={toggleSidebar}
aria-label="Open sidebar"
>
<Menu class="w-5 h-5 text-gray-700" />
<Menu class="w-5 h-5 text-text" />
</button>
{/if}
@ -122,6 +108,7 @@
.sidebar {
scrollbar-width: thin;
scrollbar-color: rgba(156, 163, 175, 0.5) transparent;
border-right: 1px solid rgb(var(--color-border));
}
.sidebar::-webkit-scrollbar {

View File

@ -1,10 +0,0 @@
<script lang="ts">
let count: number = $state(0)
const increment = () => {
count += 1
}
</script>
<button onclick={increment}>
count is {count}
</button>

View File

@ -0,0 +1,104 @@
<script lang="ts" module>
import { createContext } from "../create-context";
// Define theme types
export type Theme = "light" | "dark" | "system";
// Create theme context
export const { get: getThemeContext, set: setThemeContext } =
createContext<{
theme: Theme;
setTheme: (theme: Theme) => void;
subscribe?: (callback: (theme: Theme) => void) => () => void;
}>();
</script>
<script lang="ts">
import { onMount } from "svelte";
// Props
export let initialTheme: Theme = "system";
// State with reactivity
let theme = initialTheme;
let mounted = false;
// Create a custom store for the theme
const themeStore = {
subscribe: (callback: (theme: Theme) => void) => {
// Initial call
callback(theme);
// Setup a MutationObserver to watch for class changes on documentElement
const observer = new MutationObserver(() => {
callback(theme);
});
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ["class"],
});
// Return unsubscribe function
return () => observer.disconnect();
},
};
// Set the theme in localStorage and update the DOM
function setTheme(newTheme: Theme) {
theme = newTheme;
if (mounted) {
updateTheme(theme);
localStorage.setItem("theme", theme);
}
}
// Update the DOM based on the current theme
function updateTheme(currentTheme: Theme) {
const isDark =
currentTheme === "dark" ||
(currentTheme === "system" &&
window.matchMedia("(prefers-color-scheme: dark)").matches);
document.documentElement.classList.toggle("dark", isDark);
}
// Initialize theme on mount
onMount(() => {
// Get stored theme or use system preference
const storedTheme = localStorage.getItem("theme") as Theme | null;
if (storedTheme) {
theme = storedTheme;
}
// Set up system theme change listener
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
const handleChange = () => {
if (theme === "system") {
updateTheme("system");
}
};
mediaQuery.addEventListener("change", handleChange);
// Apply initial theme
updateTheme(theme);
mounted = true;
return () => {
mediaQuery.removeEventListener("change", handleChange);
};
});
// Set the context with the store
setThemeContext({
get theme() {
return theme;
},
setTheme,
subscribe: themeStore.subscribe,
});
</script>
<slot />

View File

@ -0,0 +1,78 @@
<script lang="ts">
import { Sun, Moon } from "lucide-svelte";
import { getThemeContext, type Theme } from "./ThemeProvider.svelte";
import { cn } from "../utils";
import { onMount } from "svelte";
export let size: "sm" | "md" | "lg" = "md";
let className: string | undefined | null = undefined;
export { className as class };
const themeContext = getThemeContext();
// Create a reactive variable to track the current theme
let currentTheme: Theme = themeContext.theme;
// Subscribe to theme changes if the subscribe method is available
onMount(() => {
// Set initial theme
currentTheme = themeContext.theme;
// If subscribe is available, use it to update the theme
if (themeContext.subscribe) {
const unsubscribe = themeContext.subscribe((newTheme) => {
currentTheme = newTheme;
});
return unsubscribe;
}
});
// Size classes for the button
const sizeClasses = {
sm: "h-8 w-8",
md: "h-9 w-9",
lg: "h-10 w-10",
};
// Size classes for the icons
const iconSizeClasses = {
sm: "h-4 w-4",
md: "h-5 w-5",
lg: "h-6 w-6",
};
// Toggle between light and dark modes only
function toggleTheme() {
let newTheme: Theme;
// Just toggle between light and dark
if (currentTheme === "light") {
newTheme = "dark";
} else {
newTheme = "light";
}
themeContext.setTheme(newTheme);
// Update our local state immediately
currentTheme = newTheme;
}
</script>
<button
type="button"
aria-label="Toggle theme"
class={cn(
"rounded-md p-2 transition-colors hover:bg-background-secondary",
sizeClasses[size],
className,
)}
on:click={toggleTheme}
>
{#if currentTheme === "dark"}
<Sun class={cn("text-text", iconSizeClasses[size])} />
{:else}
<Moon class={cn("text-text", iconSizeClasses[size])} />
{/if}
</button>

View File

@ -3,6 +3,7 @@ export default {
content: [
'./src/**/*.{html,js,svelte,ts}',
],
darkMode: 'class',
theme: {
screens: {
'xs': '480px',
@ -14,6 +15,32 @@ export default {
},
extend: {
colors: {
primary: {
50: 'rgb(var(--color-primary-50) / <alpha-value>)',
100: 'rgb(var(--color-primary-100) / <alpha-value>)',
200: 'rgb(var(--color-primary-200) / <alpha-value>)',
300: 'rgb(var(--color-primary-300) / <alpha-value>)',
400: 'rgb(var(--color-primary-400) / <alpha-value>)',
500: 'rgb(var(--color-primary-500) / <alpha-value>)',
600: 'rgb(var(--color-primary-600) / <alpha-value>)',
700: 'rgb(var(--color-primary-700) / <alpha-value>)',
800: 'rgb(var(--color-primary-800) / <alpha-value>)',
900: 'rgb(var(--color-primary-900) / <alpha-value>)',
},
background: {
DEFAULT: 'rgb(var(--color-background) / <alpha-value>)',
secondary: 'rgb(var(--color-background-secondary) / <alpha-value>)',
},
text: {
DEFAULT: 'rgb(var(--color-text) / <alpha-value>)',
secondary: 'rgb(var(--color-text-secondary) / <alpha-value>)',
muted: 'rgb(var(--color-text-muted) / <alpha-value>)',
},
border: {
DEFAULT: 'rgb(var(--color-border) / <alpha-value>)',
secondary: 'rgb(var(--color-border-secondary) / <alpha-value>)',
},
// Keep original colors for backward compatibility
blue: {
600: '#2563eb',
700: '#1d4ed8',