replace google material icons with phosphor icons

This commit is contained in:
Elijah Duffy
2025-07-10 16:25:27 -07:00
parent 355850d1f2
commit 5f4e50a652
7 changed files with 59 additions and 27 deletions

View File

@@ -13,12 +13,6 @@
rel="stylesheet" rel="stylesheet"
/> />
<!-- Material Design Icons -->
<link
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined"
rel="stylesheet"
/>
%sveltekit.head% %sveltekit.head%
</head> </head>
<body data-sveltekit-preload-data="hover"> <body data-sveltekit-preload-data="hover">

View File

@@ -2,9 +2,11 @@
import type { Snippet } from 'svelte'; import type { Snippet } from 'svelte';
import type { ClassValue, HTMLButtonAttributes, MouseEventHandler } from 'svelte/elements'; import type { ClassValue, HTMLButtonAttributes, MouseEventHandler } from 'svelte/elements';
import Spinner from './Spinner.svelte'; import Spinner from './Spinner.svelte';
import type { IconComponentProps } from 'phosphor-svelte';
import { defaultIconProps, type IconDef } from './util';
interface Props extends Omit<HTMLButtonAttributes, 'class'> { interface Props extends Omit<HTMLButtonAttributes, 'class'> {
icon?: string; icon?: IconDef;
animate?: boolean; animate?: boolean;
loading?: boolean; loading?: boolean;
class?: ClassValue | null | undefined; class?: ClassValue | null | undefined;
@@ -25,7 +27,13 @@
...others ...others
}: Props = $props(); }: Props = $props();
let iconElement = $state<HTMLSpanElement | null>(null); type SVGInHTML = HTMLElement & SVGElement;
let iconElement = $state<SVGInHTML | null>(null);
$effect(() => {
if (icon && ref) {
iconElement = ref.querySelector('svg') as SVGInHTML | null;
}
});
const handleButtonClick: MouseEventHandler<HTMLButtonElement> = (event) => { const handleButtonClick: MouseEventHandler<HTMLButtonElement> = (event) => {
if (animate) { if (animate) {
@@ -41,8 +49,11 @@
const triggerAnimation = (className: string) => { const triggerAnimation = (className: string) => {
if (icon && iconElement) { if (icon && iconElement) {
iconElement.classList.remove(className); iconElement.classList.remove(className);
void iconElement.offsetWidth;
iconElement.classList.add(className); // make sure we have DOM reflow
requestAnimationFrame(() => {
iconElement?.classList.add(className);
});
} }
}; };
@@ -76,7 +87,7 @@
{type} {type}
bind:this={ref} bind:this={ref}
class={[ class={[
'button group relative flex gap-3 overflow-hidden rounded-sm px-5', 'button group relative flex items-center gap-3 overflow-hidden rounded-sm px-5',
'text-sui-background cursor-pointer py-3 font-medium transition-colors', 'text-sui-background cursor-pointer py-3 font-medium transition-colors',
!loading ? ' bg-sui-primary hover:bg-sui-secondary' : 'bg-sui-primary/50 cursor-not-allowed ', !loading ? ' bg-sui-primary hover:bg-sui-secondary' : 'bg-sui-primary/50 cursor-not-allowed ',
classValue classValue
@@ -87,7 +98,7 @@
{@render children()} {@render children()}
{#if icon && !loading} {#if icon && !loading}
<span class="material-symbols-outlined" bind:this={iconElement}>{icon}</span> <icon.component {...icon.props || defaultIconProps} />
{/if} {/if}
{#if loading} {#if loading}
@@ -140,12 +151,12 @@
} }
} }
:global(button span.animate) { :global(button svg.animate) {
animation-name: loop; animation-name: loop;
animation-duration: 0.5s; animation-duration: 0.5s;
} }
:global(button span.bounce) { :global(button svg.bounce) {
animation-name: bounce; animation-name: bounce;
animation-duration: 180ms; animation-duration: 180ms;
animation-timing-function: ease-in-out; animation-timing-function: ease-in-out;

View File

@@ -7,6 +7,7 @@
import { validate } from '@svelte-toolkit/validate'; import { validate } from '@svelte-toolkit/validate';
import { generateIdentifier } from './util'; import { generateIdentifier } from './util';
import type { ClassValue } from 'svelte/elements'; import type { ClassValue } from 'svelte/elements';
import { Check, Minus } from 'phosphor-svelte';
interface Props { interface Props {
name?: string; name?: string;
@@ -31,6 +32,7 @@
let id = $derived(generateIdentifier('checkbox', name)); let id = $derived(generateIdentifier('checkbox', name));
let valid = $state(true); let valid = $state(true);
let Icon = $derived(value === 'indeterminate' ? Minus : value ? Check : undefined);
</script> </script>
<div class={['flex items-center', classValue]}> <div class={['flex items-center', classValue]}>
@@ -69,10 +71,8 @@
onchange?.(value); onchange?.(value);
}} }}
> >
{#if value === 'indeterminate'} {#if Icon}
<span class="material-symbols-outlined">remove</span> <Icon weight="bold" size="1.1em"></Icon>
{:else if value === true}
<span class="material-symbols-outlined">check</span>
{/if} {/if}
</button> </button>

View File

@@ -1,9 +1,10 @@
<script lang="ts"> <script lang="ts">
import type { Snippet } from 'svelte'; import type { Snippet } from 'svelte';
import type { ClassValue, MouseEventHandler } from 'svelte/elements'; import type { ClassValue, MouseEventHandler } from 'svelte/elements';
import { defaultIconProps, type IconDef } from './util';
interface Props { interface Props {
icon?: string; icon?: IconDef;
iconPosition?: 'left' | 'right'; iconPosition?: 'left' | 'right';
disabled?: boolean; disabled?: boolean;
class?: ClassValue | null | undefined; class?: ClassValue | null | undefined;
@@ -22,7 +23,9 @@
</script> </script>
{#snippet iconSnippet()} {#snippet iconSnippet()}
<span class="material-symbols-outlined">{icon}</span> {#if icon}
<icon.component {...icon.props || defaultIconProps} />
{/if}
{/snippet} {/snippet}
<button <button

View File

@@ -13,7 +13,7 @@
| false | false
| { | {
text: string; text: string;
icon: string; icon: IconDef;
onclick?: MouseEventHandler<HTMLButtonElement>; onclick?: MouseEventHandler<HTMLButtonElement>;
}; };
@@ -40,6 +40,8 @@
import { tweened } from 'svelte/motion'; import { tweened } from 'svelte/motion';
import { fade, fly } from 'svelte/transition'; import { fade, fly } from 'svelte/transition';
import { validateForm } from '@svelte-toolkit/validate'; import { validateForm } from '@svelte-toolkit/validate';
import { ArrowLeft, Check } from 'phosphor-svelte';
import type { IconDef } from './util';
let { let {
pages, pages,
@@ -321,7 +323,7 @@
{#if i >= index} {#if i >= index}
<span class="mb-[0.0625rem]">{i + 1}</span> <span class="mb-[0.0625rem]">{i + 1}</span>
{:else} {:else}
<span class="material-symbols-outlined mt-0.5 text-2xl">check_small</span> <Check class="mt-0.5" size="1.5rem" weight="bold" />
{/if} {/if}
</div> </div>
{/each} {/each}
@@ -339,7 +341,7 @@
onclick={() => (index -= 1)} onclick={() => (index -= 1)}
transition:fly={{ x: -200, duration: 200 }} transition:fly={{ x: -200, duration: 200 }}
> >
<span class="material-symbols-outlined text-base">arrow_back</span> <ArrowLeft />
Back Back
</button> </button>
{/if} {/if}

View File

@@ -1,3 +1,22 @@
import type { IconComponentProps } from 'phosphor-svelte';
import type { Component } from 'svelte';
/**
* IconDef is an object that represents an icon element from the phosphor library, alongside an optional set of typed properties.
*/
export type IconDef = {
component: Component;
props?: IconComponentProps;
};
/**
* Defines a set of default properties for icons used in the application.
*/
export const defaultIconProps: IconComponentProps = {
size: '1.2rem',
weight: 'bold'
};
/** /**
* MaybeGetter is a type that can either be a value of type T or a function that returns a value of type T. * MaybeGetter is a type that can either be a value of type T or a function that returns a value of type T.
* This is useful for cases where you might want to pass a value directly or a function that computes the * This is useful for cases where you might want to pass a value directly or a function that computes the

View File

@@ -21,6 +21,7 @@
import { import {
ArrowUUpLeft, ArrowUUpLeft,
ArrowUUpRight, ArrowUUpRight,
Plus,
TextB, TextB,
TextItalic, TextItalic,
TextStrikethrough, TextStrikethrough,
@@ -51,12 +52,14 @@
<div class="component"> <div class="component">
<p class="title">Button, Frameless Button, & Link</p> <p class="title">Button, Frameless Button, & Link</p>
<div class="flex gap-4"> <div class="flex gap-4">
<Button icon="add" loading={false} onclick={() => alert('Button clicked!')}>Click Me</Button> <Button icon={{ component: Plus }} loading={false} onclick={() => alert('Button clicked!')}
<Button icon="add" loading={true} onclick={() => alert('Button clicked!')} >Click Me</Button
>
<Button icon={{ component: Plus }} loading={true} onclick={() => alert('Button clicked!')}
>Loading Button</Button >Loading Button</Button
> >
<FramelessButton icon="add">Click Me</FramelessButton> <FramelessButton icon={{ component: Plus }}>Click Me</FramelessButton>
<Link href="https://svelte.dev">Visit Svelte</Link> <Link href="https://svelte.dev">Visit Svelte</Link>
@@ -136,7 +139,7 @@
<DateInput bind:value={dateInputValue} /> <DateInput bind:value={dateInputValue} />
<div class="shrink-0"> <div class="shrink-0">
<Button <Button
icon="add" icon={{ component: Plus }}
onclick={() => { onclick={() => {
dateInputValue = new Date(); dateInputValue = new Date();
console.log('Dateinput value set to:', dateInputValue); console.log('Dateinput value set to:', dateInputValue);