Update Button component to have shape and element

This commit is contained in:
Justin Edmund 2025-09-24 01:53:26 -07:00
parent 6ace86a00e
commit 8db94e37e7

View file

@ -8,7 +8,7 @@
interface Props { interface Props {
/** Button variant style */ /** Button variant style */
variant?: 'primary' | 'secondary' | 'ghost' | 'text' | 'destructive' | 'notice' variant?: 'primary' | 'secondary' | 'ghost' | 'text' | 'destructive' | 'notice' | 'subtle'
/** Button size */ /** Button size */
size?: 'small' | 'medium' | 'large' | 'icon' size?: 'small' | 'medium' | 'large' | 'icon'
/** Whether button is contained */ /** Whether button is contained */
@ -43,6 +43,10 @@
href?: string href?: string
/** Click handler */ /** Click handler */
onclick?: () => void onclick?: () => void
/** Shape of the button corners */
shape?: 'default' | 'circular' | 'circle' | 'pill'
/** Element tag override (for slots/triggers) */
as?: 'button' | 'a' | 'span'
/** Any additional HTML attributes */ /** Any additional HTML attributes */
[key: string]: any [key: string]: any
} }
@ -66,9 +70,15 @@
disabled = false, disabled = false,
href, href,
onclick, onclick,
shape = 'default',
as,
...restProps ...restProps
}: Props = $props() }: Props = $props()
// Normalize shape aliases
const normalizedShape = $derived(shape === 'circle' ? 'circular' : shape)
const renderAs = $derived(as ? as : href ? 'a' : 'button')
const iconSizes = { const iconSizes = {
icon: 16, icon: 16,
small: 14, small: 14,
@ -88,6 +98,7 @@
saved && 'saved', saved && 'saved',
fullWidth && 'full', fullWidth && 'full',
iconOnly && 'iconOnly', iconOnly && 'iconOnly',
normalizedShape !== 'default' && normalizedShape,
className className
] ]
.filter(Boolean) .filter(Boolean)
@ -99,35 +110,74 @@
const hasRightIcon = $derived(icon && iconPosition === 'right') const hasRightIcon = $derived(icon && iconPosition === 'right')
</script> </script>
<ButtonPrimitive.Root class={buttonClass} {disabled} {href} {onclick} {...restProps}> {#if renderAs === 'button'}
{#if leftAccessory} <ButtonPrimitive.Root class={buttonClass} {disabled} {href} {onclick} {...restProps}>
<span class="accessory"> {#if leftAccessory}
{@render leftAccessory()} <span class="accessory">
</span> {@render leftAccessory()}
{:else if hasLeftIcon && !iconOnly} </span>
<span class="accessory"> {:else if hasLeftIcon && !iconOnly}
<Icon name={icon} size={iconSizes[size]} /> <span class="accessory">
</span> <Icon name={icon} size={iconSizes[size]} />
{/if} </span>
{/if}
{#if children && !iconOnly} {#if children && !iconOnly}
<span class="text"> <span class="text">
{@render children()} {@render children()}
</span> </span>
{:else if iconOnly && icon} {:else if iconOnly && icon}
<Icon name={icon} size={iconSizes[size]} />
{/if}
{#if rightAccessory}
<span class="accessory">
{@render rightAccessory()}
</span>
{:else if hasRightIcon && !iconOnly}
<span class="accessory">
<Icon name={icon} size={iconSizes[size]} /> <Icon name={icon} size={iconSizes[size]} />
</span> {/if}
{/if}
</ButtonPrimitive.Root> {#if rightAccessory}
<span class="accessory">
{@render rightAccessory()}
</span>
{:else if hasRightIcon && !iconOnly}
<span class="accessory">
<Icon name={icon} size={iconSizes[size]} />
</span>
{/if}
</ButtonPrimitive.Root>
{:else}
<svelte:element
this={renderAs}
class={buttonClass}
href={renderAs === 'a' ? href : undefined}
aria-disabled={disabled}
on:click={onclick}
{...restProps}
>
{#if leftAccessory}
<span class="accessory">
{@render leftAccessory()}
</span>
{:else if hasLeftIcon && !iconOnly}
<span class="accessory">
<Icon name={icon} size={iconSizes[size]} />
</span>
{/if}
{#if children && !iconOnly}
<span class="text">
{@render children()}
</span>
{:else if iconOnly && icon}
<Icon name={icon} size={iconSizes[size]} />
{/if}
{#if rightAccessory}
<span class="accessory">
{@render rightAccessory()}
</span>
{:else if hasRightIcon && !iconOnly}
<span class="accessory">
<Icon name={icon} size={iconSizes[size]} />
</span>
{/if}
</svelte:element>
{/if}
<style lang="scss"> <style lang="scss">
@use 'sass:color'; @use 'sass:color';
@ -229,6 +279,22 @@
} }
} }
// Subtle variant: card-like with border
:global([data-button-root].subtle) {
background-color: var(--card-bg);
color: var(--text-primary);
border: 1px solid var(--button-bg);
&:hover:not(:disabled) {
background-color: var(--button-bg-hover);
border-color: var(--button-bg-hover);
}
&:focus-visible {
@include focus-ring($blue);
}
}
:global([data-button-root].text) { :global([data-button-root].text) {
background-color: transparent; background-color: transparent;
color: var(--accent-blue); color: var(--accent-blue);
@ -290,6 +356,15 @@
width: calc($unit * 5.5); width: calc($unit * 5.5);
} }
// Shapes
:global([data-button-root].circular) {
border-radius: 999px;
}
:global([data-button-root].pill) {
border-radius: 999px;
}
// Modifiers // Modifiers
:global([data-button-root].contained) { :global([data-button-root].contained) {
background: var(--button-contained-bg); background: var(--button-contained-bg);