54 changed files with 534 additions and 101 deletions
@ -0,0 +1,67 @@
@@ -0,0 +1,67 @@
|
||||
<script lang="ts"> |
||||
import { onMount } from 'svelte'; |
||||
|
||||
interface Props { |
||||
name: string; |
||||
size?: number | string; |
||||
class?: string; |
||||
} |
||||
|
||||
let { name, size = 16, class: className = '' }: Props = $props(); |
||||
|
||||
let svgContent = $state<string>(''); |
||||
|
||||
const iconPath = $derived(`/icons/${name}.svg`); |
||||
const sizeValue = $derived(typeof size === 'number' ? `${size}px` : size); |
||||
|
||||
onMount(async () => { |
||||
try { |
||||
const path = iconPath; |
||||
const response = await fetch(path); |
||||
if (response.ok) { |
||||
const text = await response.text(); |
||||
svgContent = text; |
||||
} |
||||
} catch (error) { |
||||
console.error(`Failed to load icon: ${iconPath}`, error); |
||||
} |
||||
}); |
||||
</script> |
||||
|
||||
{#if svgContent} |
||||
<div |
||||
class="icon-wrapper {className}" |
||||
style="width: {sizeValue}; height: {sizeValue}; color: currentColor;" |
||||
> |
||||
{@html svgContent} |
||||
</div> |
||||
{:else} |
||||
<div |
||||
class="icon-placeholder {className}" |
||||
style="width: {sizeValue}; height: {sizeValue};" |
||||
aria-label={name} |
||||
></div> |
||||
{/if} |
||||
|
||||
<style> |
||||
.icon-wrapper { |
||||
display: inline-block; |
||||
vertical-align: middle; |
||||
flex-shrink: 0; |
||||
} |
||||
|
||||
.icon-wrapper :global(svg) { |
||||
width: 100%; |
||||
height: 100%; |
||||
display: block; |
||||
} |
||||
|
||||
.icon-placeholder { |
||||
display: inline-block; |
||||
vertical-align: middle; |
||||
flex-shrink: 0; |
||||
background: currentColor; |
||||
opacity: 0.2; |
||||
border-radius: 2px; |
||||
} |
||||
</style> |
||||
@ -0,0 +1,90 @@
@@ -0,0 +1,90 @@
|
||||
<script lang="ts"> |
||||
import Icon from './Icon.svelte'; |
||||
|
||||
interface Props { |
||||
icon: string; |
||||
label: string; |
||||
size?: number | string; |
||||
class?: string; |
||||
active?: boolean; |
||||
disabled?: boolean; |
||||
onclick?: () => void; |
||||
} |
||||
|
||||
let { |
||||
icon, |
||||
label, |
||||
size = 16, |
||||
class: className = '', |
||||
active = false, |
||||
disabled = false, |
||||
onclick |
||||
}: Props = $props(); |
||||
</script> |
||||
|
||||
<button |
||||
class="icon-button {className}" |
||||
class:active={active} |
||||
class:disabled={disabled} |
||||
{onclick} |
||||
{disabled} |
||||
aria-label={label} |
||||
title={label} |
||||
> |
||||
<Icon {name} {size} /> |
||||
</button> |
||||
|
||||
<style> |
||||
.icon-button { |
||||
display: inline-flex; |
||||
align-items: center; |
||||
justify-content: center; |
||||
padding: 0.25rem 0.75rem; |
||||
border: 1px solid var(--fog-border, #e5e7eb); |
||||
border-radius: 0.25rem; |
||||
background: var(--fog-post, #ffffff); |
||||
color: var(--fog-text, #1f2937); |
||||
cursor: pointer; |
||||
transition: all 0.2s; |
||||
font-size: 0.875rem; |
||||
line-height: 1.5; |
||||
gap: 0.375rem; |
||||
} |
||||
|
||||
:global(.dark) .icon-button { |
||||
background: var(--fog-dark-post, #1f2937); |
||||
border-color: var(--fog-dark-border, #374151); |
||||
color: var(--fog-dark-text, #f9fafb); |
||||
} |
||||
|
||||
.icon-button:hover:not(.disabled) { |
||||
background: var(--fog-highlight, #f3f4f6); |
||||
border-color: var(--fog-accent, #64748b); |
||||
} |
||||
|
||||
:global(.dark) .icon-button:hover:not(.disabled) { |
||||
background: var(--fog-dark-highlight, #374151); |
||||
} |
||||
|
||||
.icon-button.active { |
||||
background: var(--fog-accent, #64748b); |
||||
color: var(--fog-text, #ffffff); |
||||
border-color: var(--fog-accent, #64748b); |
||||
} |
||||
|
||||
:global(.dark) .icon-button.active { |
||||
background: var(--fog-dark-accent, #64748b); |
||||
color: var(--fog-dark-text, #ffffff); |
||||
border-color: var(--fog-dark-accent, #64748b); |
||||
} |
||||
|
||||
.icon-button.disabled { |
||||
cursor: not-allowed; |
||||
opacity: 0.5; |
||||
} |
||||
|
||||
.icon-button:focus-visible { |
||||
outline: 2px solid var(--fog-accent, #64748b); |
||||
outline-offset: 2px; |
||||
} |
||||
</style> |
||||
Loading…
Reference in new issue