feat: add dropdown to color picker with 'system' option

This commit is contained in:
Youwen Wu 2024-04-04 16:07:29 -07:00
parent a6c261cb22
commit a40b96329c
Signed by: youwen5
GPG key ID: 865658ED1FE61EC3
16 changed files with 405 additions and 95 deletions

BIN
bun.lockb

Binary file not shown.

View file

@ -1,63 +1,63 @@
{ {
"name": "coredump", "name": "coredump",
"version": "0.0.1", "version": "0.0.1",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite dev", "dev": "vite dev",
"build": "vite build", "build": "vite build",
"preview": "vite preview", "preview": "vite preview",
"test": "playwright test", "test": "playwright test",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --check . && eslint .", "lint": "prettier --check . && eslint .",
"format": "prettier --write ." "format": "prettier --write ."
}, },
"devDependencies": { "devDependencies": {
"@faker-js/faker": "^8.4.1", "@faker-js/faker": "^8.4.1",
"@playwright/test": "^1.28.1", "@playwright/test": "^1.28.1",
"@sveltejs/kit": "^2.0.0", "@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^3.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0",
"@types/eslint": "^8.56.0", "@types/eslint": "^8.56.0",
"@typescript-eslint/eslint-plugin": "^7.0.0", "@typescript-eslint/eslint-plugin": "^7.0.0",
"@typescript-eslint/parser": "^7.0.0", "@typescript-eslint/parser": "^7.0.0",
"autoprefixer": "^10.4.19", "autoprefixer": "^10.4.19",
"eslint": "^8.56.0", "eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.35.1", "eslint-plugin-svelte": "^2.35.1",
"postcss": "^8.4.38", "postcss": "^8.4.38",
"prettier": "^3.1.1", "prettier": "^3.1.1",
"prettier-plugin-svelte": "^3.1.2", "prettier-plugin-svelte": "^3.1.2",
"svelte": "^4.2.7", "svelte": "^4.2.7",
"svelte-check": "^3.6.0", "svelte-check": "^3.6.0",
"svelte-typewriter": "^3.2.3", "svelte-typewriter": "^3.2.3",
"tailwindcss": "^3.4.3", "tailwindcss": "^3.4.3",
"tslib": "^2.4.1", "tslib": "^2.4.1",
"typescript": "^5.0.0", "typescript": "^5.0.0",
"vite": "^5.0.3" "vite": "^5.0.3"
}, },
"type": "module", "type": "module",
"trustedDependencies": [ "trustedDependencies": [
"svelte-preprocess", "svelte-preprocess",
"@sveltejs/kit", "@sveltejs/kit",
"esbuild", "esbuild",
"@sveltejs/adapter-vercel" "@sveltejs/adapter-vercel"
], ],
"dependencies": { "dependencies": {
"@fontsource/geist-mono": "^5.0.2", "@fontsource/geist-mono": "^5.0.2",
"@fontsource/geist-sans": "^5.0.2", "@fontsource/geist-sans": "^5.0.2",
"@fontsource/merriweather": "^5.0.12", "@fontsource/merriweather": "^5.0.12",
"@fontsource/zilla-slab": "^5.0.12", "@fontsource/zilla-slab": "^5.0.12",
"@sveltejs/adapter-vercel": "^5.2.0", "@sveltejs/adapter-vercel": "^5.2.0",
"bits-ui": "^0.21.2", "bits-ui": "^0.21.2",
"clsx": "^2.1.0", "clsx": "^2.1.0",
"marked": "^12.0.1", "marked": "^12.0.1",
"marked-alert": "^2.0.1", "marked-alert": "^2.0.1",
"marked-katex-extension": "^5.0.1", "marked-katex-extension": "^5.0.1",
"mode-watcher": "^0.3.0", "mode-watcher": "^0.3.0",
"svelte-radix": "^1.1.0", "svelte-radix": "^1.1.0",
"svelte-sonner": "^0.3.21", "svelte-sonner": "^0.3.21",
"tailwind-merge": "^2.2.2", "tailwind-merge": "^2.2.2",
"tailwind-variants": "^0.2.1", "tailwind-variants": "^0.2.1",
"vaul-svelte": "^0.3.0" "vaul-svelte": "^0.3.0"
} }
} }

View file

@ -2,10 +2,10 @@
import Name from '$lib/assets/Name.svelte'; import Name from '$lib/assets/Name.svelte';
import { HamburgerMenu } from 'svelte-radix'; import { HamburgerMenu } from 'svelte-radix';
import { Drawer } from 'vaul-svelte'; import { Drawer } from 'vaul-svelte';
import { toggleMode } from 'mode-watcher';
import Separator from '../ui/separator/separator.svelte'; import Separator from '../ui/separator/separator.svelte';
import Button from '../ui/button/button.svelte'; import Button from '../ui/button/button.svelte';
import { Sun, Moon, Home, Person, File, Backpack } from 'svelte-radix'; import { Sun, Moon, Home, Person, File, Backpack } from 'svelte-radix';
import ThemePicker from '../ThemePicker.svelte';
let open: boolean = false; let open: boolean = false;
@ -74,19 +74,13 @@
> >
</Drawer.Close> </Drawer.Close>
<Separator class="h-1 rounded-3xl mt-1 dark:bg-zinc-500 my-2" /> <Separator class="h-1 rounded-3xl mt-1 dark:bg-zinc-500 my-2" />
<Drawer.Close asChild let:builder> <ThemePicker let:builder>
<Button <Button variant="outline" size="lg" builders={[builder]}>
variant="outline"
class="bg-accent dark:border-zinc-500"
size="lg"
on:click={toggleMode}
builders={[builder]}
>
<Sun class="mr-4 dark:hidden" /> <Sun class="mr-4 dark:hidden" />
<Moon class="mr-4 hidden dark:block" /> <Moon class="mr-4 hidden dark:block" />
Toggle Theme Change Theme
</Button> </Button>
</Drawer.Close> </ThemePicker>
</div> </div>
</div> </div>
<div class="mx-auto mb-8 h-1.5 w-12 flex-shrink-0 rounded-full bg-zinc-300" /> <div class="mx-auto mb-8 h-1.5 w-12 flex-shrink-0 rounded-full bg-zinc-300" />

View file

@ -1,14 +1,13 @@
<script lang="ts"> <script lang="ts">
import { Sun, Moon } from 'svelte-radix';
import Button from '../ui/button/button.svelte';
import { toggleMode } from 'mode-watcher';
import Name from '$lib/assets/Name.svelte'; import Name from '$lib/assets/Name.svelte';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { navigating } from '$app/stores'; import { navigating } from '$app/stores';
import Coredump from '$lib/assets/Coredump.svelte'; import Coredump from '$lib/assets/Coredump.svelte';
import Separator from '../ui/separator/separator.svelte'; import Separator from '../ui/separator/separator.svelte';
import * as Tooltip from '$lib/components/ui/tooltip';
import Drawer from './Drawer.svelte'; import Drawer from './Drawer.svelte';
import ThemePicker from '../ThemePicker.svelte';
import Button from '../ui/button/button.svelte';
import { Moon, Sun } from 'svelte-radix';
let current: 'blog' | 'about' | 'home' | 'portfolio' | string; let current: 'blog' | 'about' | 'home' | 'portfolio' | string;
@ -56,25 +55,13 @@
class="text-lg sm:text-xl md:text-2xl font-medium text-primary px-4 py-1 rounded-3xl hover:bg-zinc-200 dark:hover:bg-zinc-800 transition-colors duration-200" class="text-lg sm:text-xl md:text-2xl font-medium text-primary px-4 py-1 rounded-3xl hover:bg-zinc-200 dark:hover:bg-zinc-800 transition-colors duration-200"
class:selected={current === 'blog'}>Blog</a class:selected={current === 'blog'}>Blog</a
> >
<Tooltip.Root openDelay={500}> <ThemePicker let:builder>
<Tooltip.Trigger asChild let:builder> <Button builders={[builder]} variant="outline" size="icon" class="my-1">
<Button <Sun class="dark:hidden" />
builders={[builder]} <Moon class="hidden dark:block" />
on:click={toggleMode} <span class="sr-only">Toggle theme</span>
variant="outline" </Button>
size="icon" </ThemePicker>
class="my-1"
>
<Sun class="dark:hidden" />
<Moon class="hidden dark:block" />
<span class="sr-only">Toggle theme</span>
</Button>
</Tooltip.Trigger>
<Tooltip.Content>
<p class="dark:hidden">Too bright? Switch to dark mode</p>
<p class="hidden dark:block">Too dark? Turn on the lights</p>
</Tooltip.Content>
</Tooltip.Root>
</div> </div>
</div> </div>
<Separator /> <Separator />

View file

@ -0,0 +1,36 @@
<!--
@component @name ThemePicker
@description - A wrapper for a dropdown menu that allows the user to
change the color theme of the website.
@slot builder - A slot for the button that triggers the dropdown menu.
-->
<script lang="ts">
import * as DropdownMenu from '$lib/components/ui/dropdown-menu/index.js';
import { setMode } from 'mode-watcher';
let modes = [
{ value: 'light', label: 'Light' },
{ value: 'dark', label: 'Dark' },
{ value: 'system', label: 'System' }
];
const changeMode = (mode: string) => {
setMode(mode as 'light' | 'dark' | 'system');
};
</script>
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild let:builder>
<slot {builder} />
</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.Group>
{#each modes as { value, label } (value)}
<DropdownMenu.Item on:click={() => changeMode(value)}>{label}</DropdownMenu.Item>
{/each}
</DropdownMenu.Group>
</DropdownMenu.Content>
</DropdownMenu.Root>

View file

@ -0,0 +1,35 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import Check from "svelte-radix/Check.svelte";
import { cn } from "$lib/utils.js";
type $$Props = DropdownMenuPrimitive.CheckboxItemProps;
type $$Events = DropdownMenuPrimitive.CheckboxItemEvents;
let className: $$Props["class"] = undefined;
export let checked: $$Props["checked"] = undefined;
export { className as class };
</script>
<DropdownMenuPrimitive.CheckboxItem
bind:checked
class={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:opacity-50",
className
)}
{...$$restProps}
on:click
on:keydown
on:focusin
on:focusout
on:pointerdown
on:pointerleave
on:pointermove
>
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.CheckboxIndicator>
<Check class="h-4 w-4" />
</DropdownMenuPrimitive.CheckboxIndicator>
</span>
<slot />
</DropdownMenuPrimitive.CheckboxItem>

View file

@ -0,0 +1,26 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import { cn, flyAndScale } from "$lib/utils.js";
type $$Props = DropdownMenuPrimitive.ContentProps;
let className: $$Props["class"] = undefined;
export let sideOffset: $$Props["sideOffset"] = 4;
export let transition: $$Props["transition"] = flyAndScale;
export let transitionConfig: $$Props["transitionConfig"] = undefined;
export { className as class };
</script>
<DropdownMenuPrimitive.Content
{transition}
{transitionConfig}
{sideOffset}
class={cn(
"z-50 min-w-[8rem] rounded-md border bg-popover p-1 text-popover-foreground shadow-md focus:outline-none",
className
)}
{...$$restProps}
on:keydown
>
<slot />
</DropdownMenuPrimitive.Content>

View file

@ -0,0 +1,31 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
type $$Props = DropdownMenuPrimitive.ItemProps & {
inset?: boolean;
};
type $$Events = DropdownMenuPrimitive.ItemEvents;
let className: $$Props["class"] = undefined;
export let inset: $$Props["inset"] = undefined;
export { className as class };
</script>
<DropdownMenuPrimitive.Item
class={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:opacity-50",
inset && "pl-8",
className
)}
on:click
on:keydown
on:focusin
on:focusout
on:pointerdown
on:pointerleave
on:pointermove
{...$$restProps}
>
<slot />
</DropdownMenuPrimitive.Item>

View file

@ -0,0 +1,19 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
type $$Props = DropdownMenuPrimitive.LabelProps & {
inset?: boolean;
};
let className: $$Props["class"] = undefined;
export let inset: $$Props["inset"] = undefined;
export { className as class };
</script>
<DropdownMenuPrimitive.Label
class={cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className)}
{...$$restProps}
>
<slot />
</DropdownMenuPrimitive.Label>

View file

@ -0,0 +1,11 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
type $$Props = DropdownMenuPrimitive.RadioGroupProps;
export let value: $$Props["value"] = undefined;
</script>
<DropdownMenuPrimitive.RadioGroup {...$$restProps} bind:value>
<slot />
</DropdownMenuPrimitive.RadioGroup>

View file

@ -0,0 +1,35 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import DotFilled from "svelte-radix/DotFilled.svelte";
import { cn } from "$lib/utils.js";
type $$Props = DropdownMenuPrimitive.RadioItemProps;
type $$Events = DropdownMenuPrimitive.RadioItemEvents;
let className: $$Props["class"] = undefined;
export let value: DropdownMenuPrimitive.RadioItemProps["value"];
export { className as class };
</script>
<DropdownMenuPrimitive.RadioItem
class={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:opacity-50",
className
)}
{value}
{...$$restProps}
on:click
on:keydown
on:focusin
on:focusout
on:pointerdown
on:pointerleave
on:pointermove
>
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.RadioIndicator>
<DotFilled class="h-4 w-4 fill-current" />
</DropdownMenuPrimitive.RadioIndicator>
</span>
<slot />
</DropdownMenuPrimitive.RadioItem>

View file

@ -0,0 +1,14 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
type $$Props = DropdownMenuPrimitive.SeparatorProps;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<DropdownMenuPrimitive.Separator
class={cn("-mx-1 my-1 h-px bg-muted", className)}
{...$$restProps}
/>

View file

@ -0,0 +1,13 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import { cn } from "$lib/utils.js";
type $$Props = HTMLAttributes<HTMLSpanElement>;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<span class={cn("ml-auto text-xs tracking-widest opacity-60", className)} {...$$restProps}>
<slot />
</span>

View file

@ -0,0 +1,29 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import { cn, flyAndScale } from "$lib/utils.js";
type $$Props = DropdownMenuPrimitive.SubContentProps;
let className: $$Props["class"] = undefined;
export let transition: $$Props["transition"] = flyAndScale;
export let transitionConfig: $$Props["transitionConfig"] = {
x: -10,
y: 0,
};
export { className as class };
</script>
<DropdownMenuPrimitive.SubContent
{transition}
{transitionConfig}
class={cn(
"z-50 min-w-[8rem] rounded-md border bg-popover p-1 text-popover-foreground shadow-lg focus:outline-none",
className
)}
{...$$restProps}
on:keydown
on:focusout
on:pointermove
>
<slot />
</DropdownMenuPrimitive.SubContent>

View file

@ -0,0 +1,32 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import ChevronRight from "svelte-radix/ChevronRight.svelte";
import { cn } from "$lib/utils.js";
type $$Props = DropdownMenuPrimitive.SubTriggerProps & {
inset?: boolean;
};
type $$Events = DropdownMenuPrimitive.SubTriggerEvents;
let className: $$Props["class"] = undefined;
export let inset: $$Props["inset"] = undefined;
export { className as class };
</script>
<DropdownMenuPrimitive.SubTrigger
class={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[highlighted]:bg-accent data-[state=open]:bg-accent data-[highlighted]:text-accent-foreground data-[state=open]:text-accent-foreground",
inset && "pl-8",
className
)}
{...$$restProps}
on:click
on:keydown
on:focusin
on:focusout
on:pointerleave
on:pointermove
>
<slot />
<ChevronRight class="ml-auto h-4 w-4" />
</DropdownMenuPrimitive.SubTrigger>

View file

@ -0,0 +1,48 @@
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import Item from "./dropdown-menu-item.svelte";
import Label from "./dropdown-menu-label.svelte";
import Content from "./dropdown-menu-content.svelte";
import Shortcut from "./dropdown-menu-shortcut.svelte";
import RadioItem from "./dropdown-menu-radio-item.svelte";
import Separator from "./dropdown-menu-separator.svelte";
import RadioGroup from "./dropdown-menu-radio-group.svelte";
import SubContent from "./dropdown-menu-sub-content.svelte";
import SubTrigger from "./dropdown-menu-sub-trigger.svelte";
import CheckboxItem from "./dropdown-menu-checkbox-item.svelte";
const Sub = DropdownMenuPrimitive.Sub;
const Root = DropdownMenuPrimitive.Root;
const Trigger = DropdownMenuPrimitive.Trigger;
const Group = DropdownMenuPrimitive.Group;
export {
Sub,
Root,
Item,
Label,
Group,
Trigger,
Content,
Shortcut,
Separator,
RadioItem,
SubContent,
SubTrigger,
RadioGroup,
CheckboxItem,
//
Root as DropdownMenu,
Sub as DropdownMenuSub,
Item as DropdownMenuItem,
Label as DropdownMenuLabel,
Group as DropdownMenuGroup,
Content as DropdownMenuContent,
Trigger as DropdownMenuTrigger,
Shortcut as DropdownMenuShortcut,
RadioItem as DropdownMenuRadioItem,
Separator as DropdownMenuSeparator,
RadioGroup as DropdownMenuRadioGroup,
SubContent as DropdownMenuSubContent,
SubTrigger as DropdownMenuSubTrigger,
CheckboxItem as DropdownMenuCheckboxItem,
};