Icon refactor

This commit is contained in:
2026-02-12 14:29:12 -07:00
parent caf763aa1e
commit 1c70626f5a
36 changed files with 329 additions and 607 deletions

27
src/components/Icon.astro Normal file
View File

@@ -0,0 +1,27 @@
---
import { icons, type IconName } from "../config/icons";
interface Props {
name: IconName;
class?: string;
"class:list"?: any;
}
const { name, class: className, "class:list": classList } = Astro.props;
const svg = icons[name];
if (!svg) {
throw new Error(`Icon "${name}" not found in icon registry`);
}
---
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="1em"
height="1em"
fill="none"
class:list={[className, classList]}
aria-hidden="true"
set:html={svg}
/>

30
src/components/Icon.vue Normal file
View File

@@ -0,0 +1,30 @@
<script setup lang="ts">
import { computed } from "vue";
import { icons, type IconName } from "../config/icons";
const props = defineProps<{
name: IconName;
class?: string;
}>();
const svg = computed(() => {
const icon = icons[props.name];
if (!icon) {
throw new Error(`Icon "${props.name}" not found in icon registry`);
}
return icon;
});
</script>
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="1em"
height="1em"
fill="none"
:class="props.class"
aria-hidden="true"
v-html="svg"
/>
</template>

View File

@@ -1,5 +1,6 @@
<script setup lang="ts">
import { ref } from "vue";
import Icon from "./Icon.vue";
const props = defineProps<{
clients: { id: string; name: string }[];
@@ -164,37 +165,13 @@ function clearForm() {
<!-- Success Message -->
<div v-if="success" class="alert alert-success">
<svg
xmlns="http://www.w3.org/2000/svg"
class="stroke-current shrink-0 h-6 w-6"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<Icon name="check-circle" class="stroke-current shrink-0 h-6 w-6" />
<span>Manual time entry created successfully!</span>
</div>
<!-- Error Message -->
<div v-if="error" class="alert alert-error">
<svg
xmlns="http://www.w3.org/2000/svg"
class="stroke-current shrink-0 h-6 w-6"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<Icon name="x-circle" class="stroke-current shrink-0 h-6 w-6" />
<span>{{ error }}</span>
</div>

View File

@@ -1,5 +1,5 @@
---
import { Icon } from 'astro-icon/components';
import Icon from './Icon.astro';
interface Props {
title: string;

View File

@@ -33,7 +33,7 @@ const chartData = computed(() => ({
{
data: props.tags.map((t) => t.totalTime / (1000 * 60)), // Convert to minutes
backgroundColor: props.tags.map((t) => t.color),
borderColor: "#1e293b", // Matches typical dark mode bg
borderColor: "#1e293b",
borderWidth: 2,
},
],

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { Icon } from '@iconify/vue';
import Icon from './Icon.vue';
const theme = ref('macchiato');
@@ -27,7 +27,7 @@ function toggleTheme() {
aria-label="Toggle Theme"
>
<Icon
:icon="theme === 'macchiato' ? 'heroicons:moon' : 'heroicons:sun'"
:name="theme === 'macchiato' ? 'moon' : 'sun'"
class="w-5 h-5"
/>
</button>

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from "vue";
import { Icon } from "@iconify/vue";
import Icon from "./Icon.vue";
const props = defineProps<{
initialRunningEntry: {
@@ -188,7 +188,7 @@ async function stopTimer() {
@click="startTimer"
class="btn btn-primary btn-lg min-w-40 shadow-lg shadow-primary/20 hover:shadow-xl hover:shadow-primary/30 transition-all"
>
<Icon icon="heroicons:play" class="w-5 h-5" />
<Icon name="play" class="w-5 h-5" />
Start Timer
</button>
<button
@@ -196,7 +196,7 @@ async function stopTimer() {
@click="stopTimer"
class="btn btn-error btn-lg min-w-40 shadow-lg shadow-error/20 hover:shadow-xl hover:shadow-error/30 transition-all"
>
<Icon icon="heroicons:stop" class="w-5 h-5" />
<Icon name="stop" class="w-5 h-5" />
Stop Timer
</button>
</div>

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import { ref } from "vue";
import { Icon } from "@iconify/vue";
import Icon from "../Icon.vue";
import { startAuthentication } from "@simplewebauthn/browser";
const loading = ref(false);
@@ -60,12 +60,12 @@ async function handlePasskeyLogin() {
:disabled="loading"
>
<span v-if="loading" class="loading loading-spinner loading-sm"></span>
<Icon v-else icon="heroicons:finger-print" class="w-5 h-5 mr-2" />
<Icon v-else name="finger-print" class="w-5 h-5 mr-2" />
Sign in with Passkey
</button>
<div v-if="error" role="alert" class="alert alert-error mt-4">
<Icon icon="heroicons:exclamation-circle" class="w-6 h-6" />
<Icon name="exclamation-circle" class="w-6 h-6" />
<span>{{ error }}</span>
</div>
</div>

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import { ref, onMounted } from "vue";
import { Icon } from "@iconify/vue";
import Icon from "../Icon.vue";
interface ApiToken {
id: string;
@@ -112,7 +112,7 @@ function closeShowTokenModal() {
<div class="flex justify-between items-center mb-6">
<h2 class="card-title text-lg sm:text-xl">
<Icon
icon="heroicons:code-bracket-square"
name="code-bracket-square"
class="w-5 h-5 sm:w-6 sm:h-6"
/>
API Tokens
@@ -121,7 +121,7 @@ function closeShowTokenModal() {
class="btn btn-primary btn-sm"
@click="createModalOpen = true"
>
<Icon icon="heroicons:plus" class="w-4 h-4" />
<Icon name="plus" class="w-4 h-4" />
Create Token
</button>
</div>
@@ -161,7 +161,7 @@ function closeShowTokenModal() {
class="btn btn-ghost btn-xs text-error"
@click="deleteToken(token.id)"
>
<Icon icon="heroicons:trash" class="w-4 h-4" />
<Icon name="trash" class="w-4 h-4" />
</button>
</td>
</tr>
@@ -222,7 +222,7 @@ function closeShowTokenModal() {
<dialog class="modal" :class="{ 'modal-open': showTokenModalOpen }">
<div class="modal-box">
<h3 class="font-bold text-lg text-success flex items-center gap-2">
<Icon icon="heroicons:check-circle" class="w-6 h-6" />
<Icon name="check-circle" class="w-6 h-6" />
Token Created
</h3>
<p class="py-4">
@@ -239,7 +239,7 @@ function closeShowTokenModal() {
@click="copyToken"
title="Copy to clipboard"
>
<Icon icon="heroicons:clipboard" class="w-4 h-4" />
<Icon name="clipboard" class="w-4 h-4" />
</button>
</div>

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import { ref, onMounted } from "vue";
import { Icon } from "@iconify/vue";
import Icon from "../Icon.vue";
import { startRegistration } from "@simplewebauthn/browser";
interface Passkey {
@@ -98,7 +98,7 @@ async function deletePasskey(id: string) {
<div class="card-body p-4 sm:p-6">
<div class="flex justify-between items-center mb-6">
<h2 class="card-title text-lg sm:text-xl">
<Icon icon="heroicons:finger-print" class="w-5 h-5 sm:w-6 sm:h-6" />
<Icon name="finger-print" class="w-5 h-5 sm:w-6 sm:h-6" />
Passkeys
</h2>
<button
@@ -110,7 +110,7 @@ async function deletePasskey(id: string) {
v-if="loading"
class="loading loading-spinner loading-xs"
></span>
<Icon v-else icon="heroicons:plus" class="w-4 h-4" />
<Icon v-else name="plus" class="w-4 h-4" />
Add Passkey
</button>
</div>
@@ -157,7 +157,7 @@ async function deletePasskey(id: string) {
class="btn btn-ghost btn-xs text-error"
@click="deletePasskey(pk.id)"
>
<Icon icon="heroicons:trash" class="w-4 h-4" />
<Icon name="trash" class="w-4 h-4" />
</button>
</td>
</tr>

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import { ref } from "vue";
import { Icon } from "@iconify/vue";
import Icon from "../Icon.vue";
const currentPassword = ref("");
const newPassword = ref("");
@@ -76,10 +76,10 @@ async function changePassword() {
]"
>
<Icon
:icon="
:name="
message.type === 'success'
? 'heroicons:check-circle'
: 'heroicons:exclamation-circle'
? 'check-circle'
: 'exclamation-circle'
"
class="w-6 h-6 shrink-0"
/>
@@ -89,7 +89,7 @@ async function changePassword() {
<div class="card bg-base-100 shadow-xl border border-base-200 mb-6">
<div class="card-body p-4 sm:p-6">
<h2 class="card-title mb-6 text-lg sm:text-xl">
<Icon icon="heroicons:key" class="w-5 h-5 sm:w-6 sm:h-6" />
<Icon name="key" class="w-5 h-5 sm:w-6 sm:h-6" />
Change Password
</h2>
@@ -163,7 +163,7 @@ async function changePassword() {
v-if="loading"
class="loading loading-spinner loading-sm"
></span>
<Icon v-else icon="heroicons:lock-closed" class="w-5 h-5" />
<Icon v-else name="lock-closed" class="w-5 h-5" />
Update Password
</button>
</div>

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import { ref } from "vue";
import { Icon } from "@iconify/vue";
import Icon from "../Icon.vue";
const props = defineProps<{
user: {
@@ -61,10 +61,10 @@ async function updateProfile() {
]"
>
<Icon
:icon="
:name="
message.type === 'success'
? 'heroicons:check-circle'
: 'heroicons:exclamation-circle'
? 'check-circle'
: 'exclamation-circle'
"
class="w-6 h-6 shrink-0"
/>
@@ -74,7 +74,7 @@ async function updateProfile() {
<div class="card bg-base-100 shadow-xl border border-base-200 mb-6">
<div class="card-body p-4 sm:p-6">
<h2 class="card-title mb-6 text-lg sm:text-xl">
<Icon icon="heroicons:user-circle" class="w-5 h-5 sm:w-6 sm:h-6" />
<Icon name="user-circle" class="w-5 h-5 sm:w-6 sm:h-6" />
Profile Information
</h2>
@@ -128,7 +128,7 @@ async function updateProfile() {
v-if="loading"
class="loading loading-spinner loading-sm"
></span>
<Icon v-else icon="heroicons:check" class="w-5 h-5" />
<Icon v-else name="check" class="w-5 h-5" />
Save Changes
</button>
</div>