forked from Cutlery/immich
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| da9e601ed5 | |||
| 88fd46dc9d | |||
| e320e31476 |
@@ -0,0 +1,206 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
mdiBookMultiple,
|
||||
mdiButtonCursor,
|
||||
mdiCardMultipleOutline,
|
||||
mdiFormatHeader1,
|
||||
mdiHeartMultiple,
|
||||
mdiImageMultiple,
|
||||
mdiPlus,
|
||||
mdiSquare,
|
||||
mdiTrashCan,
|
||||
} from '@mdi/js';
|
||||
import Button from '@ui/components/button.svelte';
|
||||
import Card from '@ui/components/card.svelte';
|
||||
import Heading from '@ui/components/heading.svelte';
|
||||
import IconButton from '@ui/components/icon-button.svelte';
|
||||
import Layout from '@ui/components/layout.svelte';
|
||||
import SidebarItem from '@ui/components/sidebar-item.svelte';
|
||||
import Sidebar from '@ui/components/sidebar.svelte';
|
||||
import ThemeSwitcher from '@ui/components/theme-switcher.svelte';
|
||||
import type { Color, Size } from '@ui/types';
|
||||
import { colorTheme } from '../../lib/stores/preferences.store';
|
||||
|
||||
const sizes: Size[] = ['xs', 'sm', 'md', 'lg', 'xl'];
|
||||
const colors: Color[] = ['primary', 'secondary', 'info', 'success', 'warning', 'danger'];
|
||||
|
||||
const scrollTo = (id: string) => {
|
||||
const content = document.getElementById('content');
|
||||
const anchor = document.getElementById(id);
|
||||
if (!anchor || !content) {
|
||||
return;
|
||||
}
|
||||
|
||||
content.scrollTo({ top: anchor.offsetTop - 24, behavior: 'smooth' });
|
||||
};
|
||||
</script>
|
||||
|
||||
<Layout>
|
||||
<!-- <AppBar slot="header" /> -->
|
||||
<div class="h-full" slot="sidebar">
|
||||
<Sidebar>
|
||||
<SidebarItem title="Headings" icon={mdiFormatHeader1} on:click={() => scrollTo('headings')}></SidebarItem>
|
||||
<SidebarItem title="Buttons" icon={mdiButtonCursor} on:click={() => scrollTo('buttons')}></SidebarItem>
|
||||
<SidebarItem title="Cards" icon={mdiCardMultipleOutline} on:click={() => scrollTo('cards')}></SidebarItem>
|
||||
<SidebarItem title="Sidebar" icon={mdiSquare} on:click={() => scrollTo('sidebar')}></SidebarItem>
|
||||
</Sidebar>
|
||||
</div>
|
||||
|
||||
<main class="m-8 pb-16">
|
||||
<div class="mb-6 w-full bg-gray-200 dark:bg-gray-800 p-6 font-bold">
|
||||
<Heading size="xl">IMMICH COMPONENTS</Heading>
|
||||
</div>
|
||||
|
||||
<!-- THEME SWITCHER -->
|
||||
<section class="py-4 flex flex-col gap-2">
|
||||
<Heading size="xl">Theme Switcher</Heading>
|
||||
<ThemeSwitcher on:theme={({ detail }) => ($colorTheme = { value: detail, system: false })}></ThemeSwitcher>
|
||||
</section>
|
||||
|
||||
<hr />
|
||||
|
||||
<!-- HEADING -->
|
||||
<section class="py-4 flex flex-col gap-2">
|
||||
<Heading id="headings" size="xl">Headings</Heading>
|
||||
{#each sizes as size}
|
||||
<Heading {size}>Heading ({size})</Heading>
|
||||
{/each}
|
||||
</section>
|
||||
<hr />
|
||||
|
||||
<!-- BUTTONS -->
|
||||
<section class="py-4 flex flex-col gap-2">
|
||||
<Heading id="buttons" size="xl">Buttons</Heading>
|
||||
|
||||
<Heading size="md">Colors</Heading>
|
||||
<div class="flex gap-2">
|
||||
<Button color="primary">Primary</Button>
|
||||
<Button color="secondary">Secondary</Button>
|
||||
<Button color="success">Success</Button>
|
||||
<Button color="info">Info</Button>
|
||||
<Button color="warning">Warning</Button>
|
||||
<Button color="danger">Danger</Button>
|
||||
</div>
|
||||
|
||||
<Heading size="md">Disabled</Heading>
|
||||
<div class="flex gap-2">
|
||||
<Button disabled color="primary">Primary</Button>
|
||||
<Button disabled color="secondary">Secondary</Button>
|
||||
<Button disabled color="success">Success</Button>
|
||||
<Button disabled color="info">Info</Button>
|
||||
<Button disabled color="warning">Warning</Button>
|
||||
<Button disabled color="danger">Danger</Button>
|
||||
</div>
|
||||
|
||||
<Heading size="md">Rounded</Heading>
|
||||
<div class="flex align-top gap-2">
|
||||
<Button rounded="full" color="secondary">Rounded</Button>
|
||||
<Button rounded="semi" color="warning">Semi Rounded</Button>
|
||||
<Button rounded={false} color="info">None</Button>
|
||||
</div>
|
||||
|
||||
<Heading size="md">Sizes</Heading>
|
||||
<div class="flex gap-2">
|
||||
<Button size="xs">Extra Small</Button>
|
||||
<Button size="sm">Small</Button>
|
||||
<Button size="md">Medium</Button>
|
||||
<Button size="lg">Large</Button>
|
||||
<Button size="xl">Extra Large</Button>
|
||||
</div>
|
||||
|
||||
<Heading size="md">Width</Heading>
|
||||
<div class="flex flex-col gap-2 max-w-[500px]">
|
||||
<Button fullWidth color="primary">Primary</Button>
|
||||
<Button fullWidth color="secondary">Secondary</Button>
|
||||
<Button fullWidth color="success">Success</Button>
|
||||
<Button fullWidth color="info">Info</Button>
|
||||
<Button fullWidth color="warning">Warning</Button>
|
||||
<Button fullWidth color="danger">Danger</Button>
|
||||
</div>
|
||||
|
||||
<Heading size="md">Icon Buttons</Heading>
|
||||
<div class="flex gap-2 max-w-[500px]">
|
||||
{#each colors as color}
|
||||
<IconButton {color} icon={mdiPlus} />
|
||||
{/each}
|
||||
</div>
|
||||
<div class="flex gap-2 max-w-[500px]">
|
||||
{#each colors as color}
|
||||
<IconButton transparent={false} {color} icon={mdiPlus} />
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<Heading size="md">Events</Heading>
|
||||
<div class="flex gap-2">
|
||||
<Button on:click={() => alert('Hello')}>on:click</Button>
|
||||
</div>
|
||||
</section>
|
||||
<hr />
|
||||
|
||||
<!-- CARDS -->
|
||||
<section class="py-4 flex flex-col gap-2">
|
||||
<Heading id="cards" size="xl">Cards</Heading>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
{#each sizes as size}
|
||||
<Card {size}>
|
||||
<Heading size="lg">Card ({size})</Heading>
|
||||
<p class="dark:text-immich-dark-fg text-immich-fg">
|
||||
Lorem ipsum, dolor sit amet consectetur adipisicing elit. Soluta dignissimos cupiditate eum ut nesciunt
|
||||
architecto excepturi veritatis facere exercitationem? Quasi maiores modi voluptatum impedit vero, quae
|
||||
dolorum officia molestiae consequuntur.
|
||||
</p>
|
||||
</Card>
|
||||
{/each}
|
||||
</div>
|
||||
</section>
|
||||
<hr />
|
||||
|
||||
<!-- SIDEBAR -->
|
||||
<section class="py-4 flex flex-col gap-2">
|
||||
<Heading id="sidebar" size="xl">Sidebar</Heading>
|
||||
<Sidebar>
|
||||
<SidebarItem title="Item A" icon={mdiImageMultiple} isSelected></SidebarItem>
|
||||
<SidebarItem title="Item B" icon={mdiHeartMultiple}></SidebarItem>
|
||||
<SidebarItem title="Item C" icon={mdiBookMultiple}></SidebarItem>
|
||||
<SidebarItem title="Item D" icon={mdiTrashCan}></SidebarItem>
|
||||
</Sidebar>
|
||||
</section>
|
||||
<hr />
|
||||
<!-- <section class="py-4 flex flex-col gap-2">
|
||||
<Heading size="lg">Layouts</Heading>
|
||||
</section> -->
|
||||
|
||||
<!-- <section class="py-4 flex flex-col gap-2">
|
||||
<Heading size="lg">Layouts</Heading>
|
||||
|
||||
<div class="max-w-[750px] border border-white">
|
||||
<Layout>
|
||||
<div class="p-2" slot="header">
|
||||
<Heading size="lg">Header</Heading>
|
||||
</div>
|
||||
<div class="p-2 w-full border border-white" slot="sidebar">
|
||||
<Heading size="lg">Sidebar</Heading>
|
||||
</div>
|
||||
<div class="p-2 w-full border border-white h-[500px]">
|
||||
<Heading size="lg">Content</Heading>
|
||||
</div>
|
||||
</Layout>
|
||||
</div>
|
||||
|
||||
<div class="max-w-[750px] border border-white">
|
||||
<Layout title="Content Title">
|
||||
<div class="p-2" slot="header">
|
||||
<Heading size="lg">Header</Heading>
|
||||
</div>
|
||||
<div class="p-2 w-full border border-white" slot="sidebar">
|
||||
<Heading size="lg">Sidebar</Heading>
|
||||
</div>
|
||||
<div class="p-2 w-full border border-white h-[500px]">
|
||||
<Heading size="md">Content</Heading>
|
||||
</div>
|
||||
</Layout>
|
||||
</div>
|
||||
</section> -->
|
||||
</main>
|
||||
</Layout>
|
||||
@@ -0,0 +1,13 @@
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
// export const ssr = false;
|
||||
// export const csr = false;
|
||||
|
||||
export const load = (async () => {
|
||||
return {
|
||||
meta: {
|
||||
title: 'Design',
|
||||
description: 'Immich UI Design',
|
||||
},
|
||||
};
|
||||
}) satisfies PageLoad;
|
||||
@@ -0,0 +1,17 @@
|
||||
<div class="absolute top-0 w-full z-[100] bg-transparent">
|
||||
<div
|
||||
class="grid grid-cols-[10%_80%_10%] justify-between md:grid-cols-[20%_60%_20%] lg:grid-cols-3 mx-2 mt-2 place-items-center rounded-lg p-2 transition-all dark:bg-immich-dark-gray bg-immich-dark-gray text-white"
|
||||
>
|
||||
<div class="flex place-items-center gap-6 justify-self-start dark:text-immich-dark-fg">
|
||||
<slot name="leading" />
|
||||
</div>
|
||||
|
||||
<div class="w-full">
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<div class="mr-4 flex place-items-center gap-1 justify-self-end">
|
||||
<slot name="trailing" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,138 @@
|
||||
<script lang="ts">
|
||||
import type { Color } from '../types';
|
||||
|
||||
export let type: HTMLButtonElement['type'] = 'button';
|
||||
export let disabled = false;
|
||||
export let title = '';
|
||||
export let rounded: 'semi' | 'full' | boolean = true;
|
||||
export let color: Color = 'primary';
|
||||
export let transparent = false;
|
||||
export let size: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'size' | 'icon' = 'md';
|
||||
export let fullWidth = false;
|
||||
|
||||
const getBaseClasses = () => {
|
||||
return 'inline-flex items-center justify-center transition-colors disabled:cursor-not-allowed disabled:opacity-60';
|
||||
};
|
||||
|
||||
const getHoverClasses = () => {
|
||||
if (transparent) {
|
||||
return 'enabled:hover:bg-gray-100 enabled:dark:hover:bg-gray-700';
|
||||
}
|
||||
|
||||
switch (color) {
|
||||
case 'primary':
|
||||
return 'enabled:dark:hover:bg-immich-dark-primary/80 enabled:hover:bg-immich-primary/90';
|
||||
|
||||
case 'secondary':
|
||||
return 'enabled:dark:hover:bg-gray-200/90 enabled:hover:bg-gray-500/90 ';
|
||||
|
||||
case 'info':
|
||||
return 'enabled:hover:bg-blue-300/90';
|
||||
|
||||
case 'success':
|
||||
return 'enabled:hover:bg-green-300/90';
|
||||
|
||||
case 'warning':
|
||||
return 'enabled:hover:bg-yellow-300/90';
|
||||
|
||||
case 'danger':
|
||||
return 'enabled:hover:bg-red-300/90';
|
||||
}
|
||||
};
|
||||
|
||||
const getBackgroundClasses = () => {
|
||||
switch (color) {
|
||||
case 'primary':
|
||||
return 'bg-immich-primary dark:bg-immich-dark-primary';
|
||||
|
||||
case 'secondary':
|
||||
return 'bg-gray-500 dark:bg-gray-200';
|
||||
|
||||
case 'info':
|
||||
return 'bg-blue-500';
|
||||
|
||||
case 'success':
|
||||
return 'bg-green-500';
|
||||
|
||||
case 'warning':
|
||||
return 'bg-yellow-500';
|
||||
|
||||
case 'danger':
|
||||
return 'bg-red-500';
|
||||
}
|
||||
};
|
||||
|
||||
const getColorClasses = () => {
|
||||
switch (color) {
|
||||
case 'primary':
|
||||
return transparent ? 'text-gray-500 dark:text-immich-dark-primary' : 'text-white dark:text-immich-dark-gray';
|
||||
|
||||
case 'secondary':
|
||||
return transparent ? 'text-gray-500 dark:text-immich-dark-gray' : 'text-white dark:text-immich-dark-gray';
|
||||
|
||||
case 'info':
|
||||
return transparent ? 'text-blue-500' : 'text-gray-800';
|
||||
|
||||
case 'success':
|
||||
return transparent ? 'text-green-500' : 'text-gray-800';
|
||||
|
||||
case 'warning':
|
||||
return transparent ? 'text-yellow-500' : 'text-gray-800';
|
||||
|
||||
case 'danger':
|
||||
return transparent ? 'text-red-500' : 'text-white';
|
||||
}
|
||||
};
|
||||
|
||||
const getSizeClasses = () => {
|
||||
switch (size) {
|
||||
case 'xs':
|
||||
return 'p-2 text-xs';
|
||||
case 'sm':
|
||||
return 'px-4 py-2 text-sm';
|
||||
case 'icon':
|
||||
return 'p-2.5';
|
||||
case 'md':
|
||||
return 'px-4 py-2 text-base';
|
||||
case 'lg':
|
||||
return 'px-6 py-3 text-lg';
|
||||
case 'xl':
|
||||
return 'px-8 py-4 text-2xl';
|
||||
}
|
||||
};
|
||||
|
||||
const getWidthClasses = () => {
|
||||
return fullWidth ? 'w-full' : '';
|
||||
};
|
||||
|
||||
const getRoundedClasses = () => {
|
||||
switch (rounded) {
|
||||
case 'full':
|
||||
return 'rounded-full';
|
||||
|
||||
case true:
|
||||
case 'semi':
|
||||
return 'rounded-lg';
|
||||
|
||||
case false:
|
||||
default:
|
||||
return 'rounded-sm';
|
||||
}
|
||||
};
|
||||
|
||||
const className = [
|
||||
transparent ? '' : getBackgroundClasses(),
|
||||
getHoverClasses(),
|
||||
getBaseClasses(),
|
||||
getWidthClasses(),
|
||||
getRoundedClasses(),
|
||||
getColorClasses(),
|
||||
getSizeClasses(),
|
||||
].join(' ');
|
||||
</script>
|
||||
|
||||
<div class={getWidthClasses()}>
|
||||
<button on:click {type} {disabled} {title} class={className}>
|
||||
<slot />
|
||||
</button>
|
||||
</div>
|
||||
@@ -0,0 +1,28 @@
|
||||
<script lang="ts">
|
||||
import type { Size } from '../types';
|
||||
|
||||
export let size: Size | undefined = undefined;
|
||||
|
||||
const getSizeClasses = () => {
|
||||
switch (size) {
|
||||
case 'xs':
|
||||
return 'max-w-64';
|
||||
case 'sm':
|
||||
return 'max-w-96';
|
||||
case 'md':
|
||||
return 'max-w-screen-sm';
|
||||
case 'lg':
|
||||
return 'max-w-screen-md';
|
||||
case 'xl':
|
||||
return 'max-w-screen-lg';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
const className = [getSizeClasses()].join(' ');
|
||||
</script>
|
||||
|
||||
<div class="{className} flex max-w-s flex-col justify-between rounded-3xl bg-immich-gray p-5 dark:bg-immich-dark-gray">
|
||||
<slot />
|
||||
</div>
|
||||
@@ -0,0 +1,43 @@
|
||||
<script lang="ts">
|
||||
import type { Size } from '../types';
|
||||
|
||||
export let id: string | undefined = undefined;
|
||||
export let size: Size = 'xl';
|
||||
export let color: 'primary' = 'primary';
|
||||
|
||||
const getColorClasses = () => {
|
||||
switch (color) {
|
||||
case 'primary':
|
||||
return 'text-immich-primary dark:text-immich-dark-primary';
|
||||
}
|
||||
};
|
||||
|
||||
const getSizeClasses = () => {
|
||||
switch (size) {
|
||||
case 'xl':
|
||||
return 'text-4xl';
|
||||
case 'lg':
|
||||
return 'text-2xl';
|
||||
case 'md':
|
||||
return 'text-xl';
|
||||
case 'sm':
|
||||
return 'text-lg';
|
||||
case 'xs':
|
||||
return 'text-md';
|
||||
}
|
||||
};
|
||||
|
||||
const className = [getColorClasses(), getSizeClasses()].join(' ');
|
||||
</script>
|
||||
|
||||
{#if size === 'xl'}
|
||||
<h1 {id} class={className}><slot /></h1>
|
||||
{:else if size === 'lg'}
|
||||
<h2 {id} class={className}><slot /></h2>
|
||||
{:else if size === 'md'}
|
||||
<h3 {id} class={className}><slot /></h3>
|
||||
{:else if size === 'sm'}
|
||||
<h4 {id} class={className}><slot /></h4>
|
||||
{:else if size === 'xs'}
|
||||
<h5 {id} class={className}><slot /></h5>
|
||||
{/if}
|
||||
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import Icon from './icon.svelte';
|
||||
import Button from './button.svelte';
|
||||
import type { Color } from '../types';
|
||||
|
||||
export let icon = '';
|
||||
export let title = '';
|
||||
export let transparent = true;
|
||||
export let color: Color = 'primary';
|
||||
</script>
|
||||
|
||||
<Button {title} {transparent} {color} size="icon" rounded="full" on:click>
|
||||
<slot>
|
||||
<Icon path={icon} />
|
||||
</slot>
|
||||
</Button>
|
||||
@@ -0,0 +1,36 @@
|
||||
<script lang="ts">
|
||||
import type { AriaRole } from 'svelte/elements';
|
||||
|
||||
export let size: string | number = '1em';
|
||||
export let color = 'currentColor';
|
||||
export let path: string;
|
||||
export let title: string | null = null;
|
||||
export let desc = '';
|
||||
export let flipped = false;
|
||||
let className = '';
|
||||
export { className as class };
|
||||
export let viewBox = '0 0 24 24';
|
||||
export let role: AriaRole = 'img';
|
||||
export let ariaHidden: boolean | undefined = undefined;
|
||||
export let ariaLabel: string | undefined = undefined;
|
||||
export let ariaLabelledby: string | undefined = undefined;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
{viewBox}
|
||||
class="{className} {flipped ? '-scale-x-100' : ''}"
|
||||
{role}
|
||||
aria-label={ariaLabel}
|
||||
aria-hidden={ariaHidden}
|
||||
aria-labelledby={ariaLabelledby}
|
||||
>
|
||||
{#if title}
|
||||
<title>{title}</title>
|
||||
{/if}
|
||||
{#if desc}
|
||||
<desc>{desc}</desc>
|
||||
{/if}
|
||||
<path d={path} fill={color} />
|
||||
</svg>
|
||||
@@ -0,0 +1,35 @@
|
||||
<script lang="ts">
|
||||
import Heading from './heading.svelte';
|
||||
|
||||
export let title = '';
|
||||
|
||||
const titleClass = title ? 'top-16 h-[calc(100%-theme(spacing.16))]' : 'top-0 h-full';
|
||||
const hasHeader = !!$$slots.header;
|
||||
const headerPadding = hasHeader ? 'pt-[var(--navbar-height)]' : '';
|
||||
</script>
|
||||
|
||||
{#if hasHeader}
|
||||
<header class="fixed z-[900] h-[var(--navbar-height)] w-screen">
|
||||
<slot name="header" />
|
||||
</header>
|
||||
{/if}
|
||||
<main
|
||||
class="relative grid h-screen grid-cols-[theme(spacing.18)_auto] overflow-hidden bg-immich-bg {headerPadding} dark:bg-immich-dark-bg md:grid-cols-[theme(spacing.64)_auto]"
|
||||
>
|
||||
<slot name="sidebar" />
|
||||
|
||||
<section class="relative h-full">
|
||||
{#if title}
|
||||
<div
|
||||
class="absolute flex h-16 w-full place-items-center justify-between border-b p-4 dark:border-immich-dark-gray dark:text-immich-dark-fg"
|
||||
>
|
||||
<Heading size="lg">{title}</Heading>
|
||||
<slot name="buttons" />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div id="content" class="immich-scrollbar p-4 pb-8 scrollbar-stable absolute {titleClass} w-full overflow-y-auto">
|
||||
<slot />
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
@@ -0,0 +1,31 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import Icon from './icon.svelte';
|
||||
|
||||
export let title: string;
|
||||
export let icon: string;
|
||||
export let isSelected = false;
|
||||
export let iconFlipped = false;
|
||||
|
||||
const dispatch = createEventDispatcher<{ click: void }>();
|
||||
const onClick = () => dispatch('click');
|
||||
|
||||
const selectedClasses = isSelected
|
||||
? 'bg-immich-primary/10 text-immich-primary hover:bg-immich-primary/25 dark:bg-immich-dark-primary/10 dark:text-immich-dark-primary'
|
||||
: '';
|
||||
</script>
|
||||
|
||||
<button
|
||||
on:click={onClick}
|
||||
on:keydown={onClick}
|
||||
class="{selectedClasses} flex w-full place-items-center justify-between gap-4 rounded-r-full py-3 transition-[padding] delay-100 duration-100 hover:cursor-pointer hover:bg-immich-gray hover:text-immich-primary dark:text-immich-dark-fg dark:hover:bg-immich-dark-gray dark:hover:text-immich-dark-primary pl-5 group-hover:sm:px-5 md:px-5"
|
||||
>
|
||||
<div class="flex w-full place-items-center gap-4 overflow-hidden truncate">
|
||||
<Icon path={icon} size="1.5em" class="shrink-0" flipped={iconFlipped} />
|
||||
<p class="text-sm font-medium">{title}</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="h-0 overflow-hidden transition-[height] delay-1000 duration-100 sm:group-hover:h-auto group-hover:sm:overflow-visible md:h-auto md:overflow-visible"
|
||||
></div>
|
||||
</button>
|
||||
@@ -0,0 +1,5 @@
|
||||
<section
|
||||
class="immich-scrollbar group relative z-10 flex w-18 flex-col gap-1 overflow-y-auto bg-immich-bg pt-8 transition-all duration-200 dark:bg-immich-dark-bg hover:sm:w-64 hover:sm:border-r hover:sm:pr-6 hover:sm:shadow-2xl hover:sm:dark:border-r-immich-dark-gray md:w-64 md:pr-6 hover:md:border-none hover:md:shadow-none"
|
||||
>
|
||||
<slot />
|
||||
</section>
|
||||
@@ -0,0 +1,24 @@
|
||||
<script lang="ts">
|
||||
import { moonPath, moonViewBox, sunPath, sunViewBox } from '$lib/assets/svg-paths';
|
||||
import { Theme } from '$lib/constants';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import IconButton from './icon-button.svelte';
|
||||
import Icon from './icon.svelte';
|
||||
|
||||
export let theme: Theme = Theme.LIGHT;
|
||||
export let title = 'Toggle theme';
|
||||
|
||||
const dispatch = createEventDispatcher<{ theme: Theme }>();
|
||||
const onClick = () => {
|
||||
theme = theme === Theme.LIGHT ? Theme.DARK : Theme.LIGHT;
|
||||
dispatch('theme', theme);
|
||||
};
|
||||
</script>
|
||||
|
||||
<IconButton on:click={onClick} {title}>
|
||||
{#if theme === Theme.LIGHT}
|
||||
<Icon path={moonPath} viewBox={sunViewBox} class="h-6 w-6" />
|
||||
{:else}
|
||||
<Icon path={sunPath} viewBox={moonViewBox} class="h-6 w-6" />
|
||||
{/if}
|
||||
</IconButton>
|
||||
@@ -0,0 +1,9 @@
|
||||
export type Color = 'primary' | 'secondary' | 'warning' | 'success' | 'info' | 'danger';
|
||||
|
||||
export type Size = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
||||
|
||||
// should be the same values as the ones in the app.html
|
||||
export enum Theme {
|
||||
LIGHT = 'light',
|
||||
DARK = 'dark',
|
||||
}
|
||||
@@ -23,6 +23,7 @@ const config = {
|
||||
alias: {
|
||||
$lib: 'src/lib',
|
||||
'$lib/*': 'src/lib/*',
|
||||
'@ui/*': 'src/ui/*',
|
||||
'@api': 'src/api',
|
||||
'@test-data': 'src/test-data',
|
||||
},
|
||||
|
||||
@@ -16,6 +16,7 @@ const config = {
|
||||
'xmlhttprequest-ssl': './node_modules/engine.io-client/lib/xmlhttprequest.js',
|
||||
'@test-data': path.resolve(__dirname, './src/test-data'),
|
||||
'@api': path.resolve('./src/api'),
|
||||
'@ui': path.resolve('./src/ui'),
|
||||
},
|
||||
},
|
||||
server: {
|
||||
|
||||
Reference in New Issue
Block a user