feat: Add Tailwind CSS and UI components

- Updated project to use Tailwind CSS for styling.
- Added new UI components including Navbar, Sidebar, Footer,
  Accordion, and Button.
- Created markdown content page for documentation.
- Improved overall structure and design of the application.
This commit is contained in:
Mahmoud Emad
2025-05-11 16:10:53 +03:00
parent 373820ec8a
commit 4c6ce31b20
35 changed files with 2986 additions and 132 deletions

View File

@@ -0,0 +1,21 @@
<script lang="ts">
import { cn } from "../../../utils";
import { slide } from "svelte/transition";
let className: string | undefined | null = undefined;
export { className as class };
export let isOpen = false;
</script>
{#if isOpen}
<div
transition:slide={{ duration: 200 }}
class={cn("overflow-hidden text-sm transition-all", className)}
{...$$restProps}
>
<div class="pb-4 pt-0">
<slot />
</div>
</div>
{/if}

View File

@@ -0,0 +1,24 @@
<script lang="ts">
import { cn } from "../../../utils";
import { getAccordionContext } from "./context";
export let value: string;
let className: string | undefined | null = undefined;
export { className as class };
const context = getAccordionContext();
// Compute isOpen based on the current value
$: isOpen = Array.isArray(context.value)
? context.value.includes(value)
: context.value === value;
</script>
<div
data-state={isOpen ? "open" : "closed"}
class={cn("border-b", className)}
{...$$restProps}
>
<slot {isOpen} />
</div>

View File

@@ -0,0 +1,28 @@
<script lang="ts">
import { cn } from "../../../utils";
import { getAccordionContext } from "./context";
export let value: string;
let className: string | undefined | null = undefined;
export { className as class };
const context = getAccordionContext();
function handleClick() {
context.onValueChange(value);
}
</script>
<button
type="button"
aria-expanded={value ? "true" : "false"}
class={cn(
"flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
className,
)}
on:click={handleClick}
{...$$restProps}
>
<slot />
</button>

View File

@@ -0,0 +1,45 @@
<script lang="ts">
import { cn } from "../../../utils";
import { setAccordionContext } from "./context";
export let value: string | undefined = undefined;
export let collapsible = false;
export let multiple = false;
let className: string | undefined | null = undefined;
export { className as class };
$: accordionValue = multiple
? value
? Array.isArray(value)
? value
: [value]
: []
: value;
function onValueChange(itemValue: string) {
if (multiple) {
const newValue = Array.isArray(accordionValue) ? [...accordionValue] : [];
const index = newValue.indexOf(itemValue);
if (index > -1) {
newValue.splice(index, 1);
} else {
newValue.push(itemValue);
}
value = newValue as any; // Type assertion to handle the string[] to string assignment
} else {
value =
accordionValue === itemValue && collapsible ? undefined : itemValue;
}
}
setAccordionContext({
value: accordionValue,
onValueChange,
collapsible,
multiple,
});
</script>
<div class={cn("flex flex-col space-y-1.5", className)} {...$$restProps}>
<slot />
</div>

View File

@@ -0,0 +1,13 @@
import { createContext } from "../../../create-context";
export type AccordionContextValue = {
value: string | string[] | undefined;
onValueChange: (value: string) => void;
collapsible: boolean;
multiple: boolean;
};
export const {
get: getAccordionContext,
set: setAccordionContext
} = createContext<AccordionContextValue>();

View File

@@ -0,0 +1,16 @@
import Root from './accordion.svelte';
import Item from './accordion-item.svelte';
import Content from './accordion-content.svelte';
import Trigger from './accordion-trigger.svelte';
export {
Root,
Item,
Content,
Trigger,
//
Root as Accordion,
Item as AccordionItem,
Content as AccordionContent,
Trigger as AccordionTrigger
};

View File

@@ -0,0 +1,32 @@
<script lang="ts">
import { cn } from "../../../utils";
export let variant: "primary" | "secondary" | "ghost" = "primary";
export let size: "default" | "sm" | "lg" | "icon" = "default";
let className: string | undefined | null = undefined;
export { className as class };
const sizeClasses = {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3 text-xs",
lg: "h-11 rounded-md px-8 text-base",
icon: "h-10 w-10 p-2",
};
</script>
<button
class={cn(
"btn",
variant === "primary"
? "btn-primary"
: variant === "secondary"
? "btn-secondary"
: "btn-ghost",
sizeClasses[size],
className,
)}
{...$$restProps}
>
<slot />
</button>

View File

@@ -0,0 +1,7 @@
import Root from './button.svelte';
export {
Root,
//
Root as Button
};

View File

@@ -0,0 +1,17 @@
import { getContext, hasContext, setContext } from 'svelte';
export function createContext<T>() {
const key = Symbol();
return {
get: () => {
if (!hasContext(key)) {
throw new Error('Context was not set');
}
return getContext<T>(key);
},
set: (context: T) => {
setContext(key, context);
}
};
}

6
sweb/src/lib/utils.ts Normal file
View File

@@ -0,0 +1,6 @@
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}