Update Button component to have shape and element
This commit is contained in:
parent
6ace86a00e
commit
8db94e37e7
1 changed files with 103 additions and 28 deletions
|
|
@ -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);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue