Button component improvements

buttons with a `href` now render as <a> elements, otherwise <button>
This commit is contained in:
ari melody 2025-07-14 01:06:16 +01:00
parent d0163ee094
commit 77665702b7
Signed by: ari
GPG key ID: CF99829C92678188
4 changed files with 64 additions and 72 deletions

View file

@ -5,47 +5,47 @@
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
let className = "";
export { className as class };
export let active = false; export let active = false;
export let filled = false; export let filled = false;
export let disabled = false; export let disabled = false;
export let centered = false; export let centered = false;
export let label = undefined; export let label = undefined;
export let sound = "default"; export let sound = "default";
export let href = false; export let href = undefined;
export let onClick = undefined;
let classes = []; let classes = [];
function click() { function click() {
if (disabled) return; if (disabled) return;
if (href) {
const link = document.createElement('a');
link.href = href;
link.dispatchEvent(new MouseEvent('click', {
bubbles: true,
cancelable: true,
view: window,
ctrlKey: event.ctrlKey,
metaKey: event.metaKey,
shiftKey: event.shiftKey,
altKey: event.altKey,
button: event.button,
}));
return;
}
playSound(sound); playSound(sound);
dispatch('click'); dispatch('click');
} }
afterUpdate(() => { afterUpdate(() => {
classes = []; classes = className.split(' ');
if (active) classes = ["active"]; if (active) classes.push("active");
if (filled) classes = ["filled"]; if (filled) classes.push("filled");
if (disabled) classes = ["disabled"]; if (disabled) classes.push("disabled");
if (centered) classes.push("centered"); if (centered) classes.push("centered");
}); });
</script> </script>
{#if href}
<a
class={classes.join(' ')}
title={label}
aria-label={label}
href={href}
on:click={() => click()}>
<span class="icon">
<slot name="icon" />
</span>
<slot/>
</a>
{:else}
<button <button
type="button" type="button"
class={classes.join(' ')} class={classes.join(' ')}
@ -57,10 +57,10 @@
</span> </span>
<slot/> <slot/>
</button> </button>
{/if}
<style> <style>
button { a, button {
width: 100%;
height: fit-content; height: fit-content;
padding: .7em .8em; padding: .7em .8em;
display: flex; display: flex;
@ -71,6 +71,7 @@
font-size: 1rem; font-size: 1rem;
font-weight: 600; font-weight: 600;
text-align: left; text-align: left;
text-decoration: none;
border-radius: 8px; border-radius: 8px;
border: 2px solid var(--bg-700); border: 2px solid var(--bg-700);
@ -84,22 +85,32 @@
cursor: pointer; cursor: pointer;
} }
a {
width: calc(100% - 1.6em);
}
button {
width: 100%;
}
a.centered,
button.centered { button.centered {
text-align: center; text-align: center;
justify-content: center; justify-content: center;
} }
a:hover,
button:hover { button:hover {
border-color: color-mix(in srgb, var(--bg-700), black 10%); border-color: color-mix(in srgb, var(--bg-700), black 10%);
background-color: color-mix(in srgb, var(--bg-700), black 10%); background-color: color-mix(in srgb, var(--bg-700), black 10%);
} }
a:active,
button:active { button:active {
border-color: color-mix(in srgb, var(--bg-700), black 20%); border-color: color-mix(in srgb, var(--bg-700), black 20%);
background-color: color-mix(in srgb, var(--bg-700), black 20%); background-color: color-mix(in srgb, var(--bg-700), black 20%);
} }
a.active,
button.active { button.active {
background-color: var(--bg-600); background-color: var(--bg-600);
color: var(--accent); color: var(--accent);
@ -107,34 +118,40 @@
text-shadow: 0px 2px 32px var(--accent); text-shadow: 0px 2px 32px var(--accent);
} }
a.active:hover,
button.active:hover { button.active:hover {
color: color-mix(in srgb, var(--accent), var(--bg-1000) 20%); color: color-mix(in srgb, var(--accent), var(--bg-1000) 20%);
border-color: color-mix(in srgb, var(--accent), var(--bg-1000) 20%); border-color: color-mix(in srgb, var(--accent), var(--bg-1000) 20%);
background-color: color-mix(in srgb, var(--bg-600), var(--accent) 10%); background-color: color-mix(in srgb, var(--bg-600), var(--accent) 10%);
} }
a.active:active,
button.active:active { button.active:active {
color: color-mix(in srgb, var(--accent), var(--bg-800) 10%); color: color-mix(in srgb, var(--accent), var(--bg-800) 10%);
border-color: color-mix(in srgb, var(--accent), var(--bg-800) 10%); border-color: color-mix(in srgb, var(--accent), var(--bg-800) 10%);
background-color: color-mix(in srgb, var(--bg-600), var(--bg-800) 10%); background-color: color-mix(in srgb, var(--bg-600), var(--bg-800) 10%);
} }
a.filled,
button.filled { button.filled {
background-color: var(--accent); background-color: var(--accent);
color: var(--bg-800); color: var(--bg-800);
border-color: transparent; border-color: transparent;
} }
a.filled:hover,
button.filled:hover { button.filled:hover {
color: color-mix(in srgb, var(--bg-800), white 10%); color: color-mix(in srgb, var(--bg-800), white 10%);
background-color: color-mix(in srgb, var(--accent), white 20%); background-color: color-mix(in srgb, var(--accent), white 20%);
} }
a.filled:active,
button.filled:active { button.filled:active {
color: color-mix(in srgb, var(--bg-800), black 10%); color: color-mix(in srgb, var(--bg-800), black 10%);
background-color: color-mix(in srgb, var(--accent), black 20%); background-color: color-mix(in srgb, var(--accent), black 20%);
} }
a.disabled,
button.disabled { button.disabled {
color: var(--text); color: var(--text);
opacity: .5; opacity: .5;

View file

@ -32,35 +32,6 @@
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
function handle_btn(name) {
if (!$account) return;
let route;
switch (name) {
case "timeline":
route = "/";
getTimeline(true);
break;
case "notifications":
route = "/notifications";
notifications.set([]);
getNotifications();
break;
case "explore":
case "lists":
case "favourites":
case "bookmarks":
case "hashtags":
default:
return;
}
if (!route) return;
window.scrollTo({
top: 0,
behavior: "smooth"
});
goto(route);
}
function gotoProfile() { function gotoProfile() {
if (!$account) return; if (!$account) return;
playSound(); playSound();
@ -102,7 +73,7 @@
{#if $account} {#if $account}
<div id="nav-items"> <div id="nav-items">
<Button label="Timeline" <Button label="Timeline"
on:click={() => handle_btn("timeline")} href="/")}
active={$page.url.pathname === "/"}> active={$page.url.pathname === "/"}>
<svelte:fragment slot="icon"> <svelte:fragment slot="icon">
<TimelineIcon/> <TimelineIcon/>
@ -110,7 +81,7 @@
{lang.string('navigation.timeline')} {lang.string('navigation.timeline')}
</Button> </Button>
<Button label="Notifications" <Button label="Notifications"
on:click={() => handle_btn("notifications")} href="notifications"}
active={$page.url.pathname === "/notifications"}> active={$page.url.pathname === "/notifications"}>
<svelte:fragment slot="icon"> <svelte:fragment slot="icon">
<NotificationsIcon/> <NotificationsIcon/>
@ -183,7 +154,7 @@
<div id="account-button"> <div id="account-button">
<img src={$account.avatar_url} class="account-avatar" height="64px" alt="" aria-hidden="true" on:click={() => gotoProfile()}> <img src={$account.avatar_url} class="account-avatar" height="64px" alt="" aria-hidden="true" on:click={() => gotoProfile()}>
<div class="account-name" aria-hidden="true"> <div class="account-name" aria-hidden="true">
<a href="/{$server.host}/{$account.username}" class="nickname" title={$account.nickname}>{@html $account.rich_name}</a> <a href="/{$server.host}/@{$account.username}" class="nickname" title={$account.nickname}>{@html $account.rich_name}</a>
<span class="username" title={`@${$account.username}@${$account.host}`}> <span class="username" title={`@${$account.username}@${$account.host}`}>
{$account.fqn} {$account.fqn}
</span> </span>

View file

@ -12,12 +12,12 @@
</script> </script>
<div class={"post-header-container" + (reply ? " reply" : "")}> <div class={"post-header-container" + (reply ? " reply" : "")}>
<a href="/{$server.host}/{post.account.fqn}" class="post-avatar-container" on:mouseup|stopPropagation> <a href="/{$server.host}/@{post.account.fqn}" class="post-avatar-container" on:mouseup|stopPropagation>
<img src={post.account.avatar_url} type={post.account.avatar_type} alt="" width="48" height="48" class="post-avatar" loading="lazy" decoding="async"> <img src={post.account.avatar_url} type={post.account.avatar_type} alt="" width="48" height="48" class="post-avatar" loading="lazy" decoding="async">
</a> </a>
<header class="post-header"> <header class="post-header">
<div class="post-user-info" on:mouseup|stopPropagation> <div class="post-user-info" on:mouseup|stopPropagation>
<a href="/{$server.host}/{post.account.fqn}" class="name">{@html post.account.rich_name}</a> <a href="/{$server.host}/@{post.account.fqn}" class="name">{@html post.account.rich_name}</a>
<span class="username">{post.account.mention}</span> <span class="username">{post.account.mention}</span>
</div> </div>
<div class="post-info" on:mouseup|stopPropagation> <div class="post-info" on:mouseup|stopPropagation>

View file

@ -78,7 +78,7 @@
</ul> </ul>
<div class="profile-actions"> <div class="profile-actions">
{#if $account && profile.fqn !== $account.fqn} {#if $account && profile.fqn !== $account.fqn}
<Button disabled filled label="{lang.string('profile.follow')} {profile.nickname}" class="profile-btn-follow"> <Button filled label="{lang.string('profile.follow')} {profile.nickname}" class="profile-btn-follow">
{lang.string('profile.follow')} {lang.string('profile.follow')}
</Button> </Button>
{/if} {/if}
@ -186,13 +186,17 @@
gap: .5rem; gap: .5rem;
} }
.profile-btn-follow { .profile-actions :global(button.profile-btn-follow) {
padding: 0 32px; padding: 0 32px;
} }
.profile-actions :global(button) { .profile-actions :global(a) {
height: 42px;
width: fit-content; width: fit-content;
height: 16px;
}
.profile-actions :global(button) {
width: fit-content;
height: 42px;
} }
.profile-post-categories { .profile-post-categories {