ui: update Input, Select, Button, Sidebar components
This commit is contained in:
parent
5df563198b
commit
f815ca4f30
5 changed files with 108 additions and 86 deletions
|
|
@ -8,7 +8,15 @@
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
/** Button variant style */
|
/** Button variant style */
|
||||||
variant?: 'primary' | 'secondary' | 'ghost' | 'text' | 'destructive' | 'notice' | 'subtle' | undefined
|
variant?:
|
||||||
|
| 'primary'
|
||||||
|
| 'secondary'
|
||||||
|
| 'ghost'
|
||||||
|
| 'text'
|
||||||
|
| 'destructive'
|
||||||
|
| 'notice'
|
||||||
|
| 'subtle'
|
||||||
|
| undefined
|
||||||
/** Button size */
|
/** Button size */
|
||||||
size?: 'small' | 'medium' | 'large' | 'icon' | undefined
|
size?: 'small' | 'medium' | 'large' | 'icon' | undefined
|
||||||
/** Whether button is contained */
|
/** Whether button is contained */
|
||||||
|
|
@ -119,10 +127,10 @@
|
||||||
{@render leftAccessory()}
|
{@render leftAccessory()}
|
||||||
</span>
|
</span>
|
||||||
{:else if hasLeftIcon && !iconOnly && icon}
|
{:else if hasLeftIcon && !iconOnly && icon}
|
||||||
<span class="accessory">
|
<span class="accessory">
|
||||||
<Icon name={icon} size={iconSizes[size]} />
|
<Icon name={icon} size={iconSizes[size]} />
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if children && !iconOnly}
|
{#if children && !iconOnly}
|
||||||
<span class="text">
|
<span class="text">
|
||||||
|
|
@ -137,10 +145,10 @@
|
||||||
{@render rightAccessory()}
|
{@render rightAccessory()}
|
||||||
</span>
|
</span>
|
||||||
{:else if hasRightIcon && !iconOnly && icon}
|
{:else if hasRightIcon && !iconOnly && icon}
|
||||||
<span class="accessory">
|
<span class="accessory">
|
||||||
<Icon name={icon} size={iconSizes[size]} />
|
<Icon name={icon} size={iconSizes[size]} />
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
</ButtonPrimitive.Root>
|
</ButtonPrimitive.Root>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
@ -305,7 +313,7 @@
|
||||||
|
|
||||||
// Sizes
|
// Sizes
|
||||||
:global([data-button-root].small) {
|
:global([data-button-root].small) {
|
||||||
padding: $unit $unit-2x;
|
padding: $unit calc($unit * 1.5);
|
||||||
font-size: $font-small;
|
font-size: $font-small;
|
||||||
min-height: calc($unit * 3.5);
|
min-height: calc($unit * 3.5);
|
||||||
}
|
}
|
||||||
|
|
@ -417,6 +425,7 @@
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
&:hover:not(:disabled) {
|
||||||
background: var(--wind-bg-hover);
|
background: var(--wind-bg-hover);
|
||||||
|
color: white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -426,6 +435,7 @@
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
&:hover:not(:disabled) {
|
||||||
background: var(--fire-bg-hover);
|
background: var(--fire-bg-hover);
|
||||||
|
color: white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -435,6 +445,7 @@
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
&:hover:not(:disabled) {
|
||||||
background: var(--water-bg-hover);
|
background: var(--water-bg-hover);
|
||||||
|
color: white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -444,6 +455,7 @@
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
&:hover:not(:disabled) {
|
||||||
background: var(--earth-bg-hover);
|
background: var(--earth-bg-hover);
|
||||||
|
color: white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -453,6 +465,7 @@
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
&:hover:not(:disabled) {
|
||||||
background: var(--dark-bg-hover);
|
background: var(--dark-bg-hover);
|
||||||
|
color: white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -462,6 +475,7 @@
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
&:hover:not(:disabled) {
|
||||||
background: var(--light-bg-hover);
|
background: var(--light-bg-hover);
|
||||||
|
color: white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -472,6 +486,7 @@
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
&:hover:not(:disabled) {
|
||||||
background: var(--wind-bg-hover);
|
background: var(--wind-bg-hover);
|
||||||
|
color: var(--wind-text-contrast);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -481,6 +496,7 @@
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
&:hover:not(:disabled) {
|
||||||
background: var(--fire-bg-hover);
|
background: var(--fire-bg-hover);
|
||||||
|
color: var(--fire-text-contrast);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -490,6 +506,7 @@
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
&:hover:not(:disabled) {
|
||||||
background: var(--water-bg-hover);
|
background: var(--water-bg-hover);
|
||||||
|
color: var(--water-text-contrast);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -499,6 +516,7 @@
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
&:hover:not(:disabled) {
|
||||||
background: var(--earth-bg-hover);
|
background: var(--earth-bg-hover);
|
||||||
|
color: var(--earth-text-contrast);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -508,6 +526,7 @@
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
&:hover:not(:disabled) {
|
||||||
background: var(--dark-bg-hover);
|
background: var(--dark-bg-hover);
|
||||||
|
color: var(--dark-text-contrast);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -517,6 +536,7 @@
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
&:hover:not(:disabled) {
|
||||||
background: var(--light-bg-hover);
|
background: var(--light-bg-hover);
|
||||||
|
color: var(--light-text-contrast);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -112,16 +112,16 @@
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<input
|
<input
|
||||||
bind:value
|
bind:value
|
||||||
class={inputClasses}
|
class={inputClasses}
|
||||||
{type}
|
{type}
|
||||||
{placeholder}
|
{placeholder}
|
||||||
{disabled}
|
{disabled}
|
||||||
{readonly}
|
{readonly}
|
||||||
{required}
|
{required}
|
||||||
maxlength={maxLength}
|
maxlength={maxLength}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if error}
|
{#if error}
|
||||||
|
|
@ -161,16 +161,16 @@
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<input
|
<input
|
||||||
bind:value
|
bind:value
|
||||||
class={inputClasses}
|
class={inputClasses}
|
||||||
{type}
|
{type}
|
||||||
{placeholder}
|
{placeholder}
|
||||||
{disabled}
|
{disabled}
|
||||||
{readonly}
|
{readonly}
|
||||||
{required}
|
{required}
|
||||||
maxlength={maxLength}
|
maxlength={maxLength}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
@ -235,7 +235,7 @@
|
||||||
@include smooth-transition($duration-quick, background-color);
|
@include smooth-transition($duration-quick, background-color);
|
||||||
|
|
||||||
&:not(.wrapper) {
|
&:not(.wrapper) {
|
||||||
padding: calc($unit * 1.5) $unit-2x;
|
padding: calc($unit * 1.25) $unit-2x;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.fullHeight {
|
&.fullHeight {
|
||||||
|
|
|
||||||
|
|
@ -289,6 +289,7 @@
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
font-weight: $normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
.image {
|
.image {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import SidebarHeader from './SidebarHeader.svelte'
|
import SidebarHeader from './SidebarHeader.svelte'
|
||||||
|
import Button from './Button.svelte'
|
||||||
import { SIDEBAR_WIDTH } from '$lib/stores/sidebar.svelte'
|
import { SIDEBAR_WIDTH } from '$lib/stores/sidebar.svelte'
|
||||||
import type { Snippet } from 'svelte'
|
import type { Snippet } from 'svelte'
|
||||||
|
|
||||||
|
|
@ -14,23 +15,59 @@
|
||||||
onClose?: () => void
|
onClose?: () => void
|
||||||
/** Callback when close is requested (lowercase, deprecated - use onClose) */
|
/** Callback when close is requested (lowercase, deprecated - use onClose) */
|
||||||
onclose?: () => void
|
onclose?: () => void
|
||||||
|
/** Callback when back is requested (shows arrow instead of X) */
|
||||||
|
onback?: () => void
|
||||||
|
/** Callback when save/done is requested */
|
||||||
|
onsave?: () => void
|
||||||
|
/** Label for the save button */
|
||||||
|
saveLabel?: string
|
||||||
|
/** Element for styling the save button */
|
||||||
|
element?: 'wind' | 'fire' | 'water' | 'earth' | 'dark' | 'light'
|
||||||
/** Content to render in the sidebar */
|
/** Content to render in the sidebar */
|
||||||
children?: Snippet
|
children?: Snippet
|
||||||
/** Optional header actions */
|
|
||||||
headerActions?: Snippet
|
|
||||||
/** Whether the sidebar content should scroll. Default true. */
|
/** Whether the sidebar content should scroll. Default true. */
|
||||||
scrollable?: boolean
|
scrollable?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const { open = false, title, onClose, onclose, children, headerActions, scrollable = true }: Props = $props()
|
const { open = false, title, onClose, onclose, onback, onsave, saveLabel = 'Done', element, children, scrollable = true }: Props = $props()
|
||||||
|
|
||||||
// Support both onClose (camelCase) and onclose (lowercase) for backward compatibility
|
// Support both onClose (camelCase) and onclose (lowercase) for backward compatibility
|
||||||
const handleClose = $derived(onClose ?? onclose)
|
const handleClose = $derived(onClose ?? onclose)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
{#snippet leftAccessory()}
|
||||||
|
{#if onback}
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="small"
|
||||||
|
iconOnly
|
||||||
|
icon="arrow-left"
|
||||||
|
onclick={onback}
|
||||||
|
aria-label="Go back"
|
||||||
|
/>
|
||||||
|
{:else if handleClose}
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="small"
|
||||||
|
iconOnly
|
||||||
|
icon="close"
|
||||||
|
onclick={handleClose}
|
||||||
|
aria-label="Close sidebar"
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
{/snippet}
|
||||||
|
|
||||||
|
{#snippet rightAccessory()}
|
||||||
|
{#if onsave}
|
||||||
|
<Button variant="ghost" size="small" {element} elementStyle={!!element} onclick={onsave}>
|
||||||
|
{saveLabel}
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
|
{/snippet}
|
||||||
|
|
||||||
<aside class="sidebar" class:open style:--sidebar-width={SIDEBAR_WIDTH}>
|
<aside class="sidebar" class:open style:--sidebar-width={SIDEBAR_WIDTH}>
|
||||||
{#if title}
|
{#if title}
|
||||||
<SidebarHeader {title} onclose={handleClose} actions={headerActions} />
|
<SidebarHeader {title} {leftAccessory} {rightAccessory} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="sidebar-content" class:scrollable>
|
<div class="sidebar-content" class:scrollable>
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,32 @@
|
||||||
<svelte:options runes={true} />
|
<svelte:options runes={true} />
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Button from './Button.svelte'
|
|
||||||
import closeIcon from '$src/assets/icons/close.svg?raw'
|
|
||||||
import type { Snippet } from 'svelte'
|
import type { Snippet } from 'svelte'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
/** Title for the sidebar header */
|
/** Title for the sidebar header */
|
||||||
title: string
|
title: string
|
||||||
/** Callback when close is requested */
|
/** Left accessory content (e.g., close/back button) */
|
||||||
onclose?: () => void
|
leftAccessory?: Snippet
|
||||||
/** Optional additional actions to render in the header */
|
/** Right accessory content (e.g., save/edit button) */
|
||||||
actions?: Snippet
|
rightAccessory?: Snippet
|
||||||
}
|
}
|
||||||
|
|
||||||
const { title, onclose, actions }: Props = $props()
|
const { title, leftAccessory, rightAccessory }: Props = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="sidebar-header">
|
<div class="sidebar-header">
|
||||||
<div class="header-left">
|
<div class="header-left">
|
||||||
{#if actions}
|
{#if leftAccessory}
|
||||||
{@render actions()}
|
{@render leftAccessory()}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 class="sidebar-title">{title}</h2>
|
<h2 class="sidebar-title">{title}</h2>
|
||||||
|
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
{#if onclose}
|
{#if rightAccessory}
|
||||||
<button onclick={onclose} class="close-button" aria-label="Close sidebar">
|
{@render rightAccessory()}
|
||||||
{@html closeIcon}
|
|
||||||
</button>
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -42,9 +38,9 @@
|
||||||
@use '$src/themes/layout' as *;
|
@use '$src/themes/layout' as *;
|
||||||
|
|
||||||
.sidebar-header {
|
.sidebar-header {
|
||||||
display: flex;
|
display: grid;
|
||||||
|
grid-template-columns: 1fr auto 1fr;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
|
||||||
padding: $unit-2x;
|
padding: $unit-2x;
|
||||||
border-bottom: 1px solid var(--border-primary);
|
border-bottom: 1px solid var(--border-primary);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|
@ -57,11 +53,8 @@
|
||||||
|
|
||||||
.header-left,
|
.header-left,
|
||||||
.header-right {
|
.header-right {
|
||||||
width: 32px; // Same width as close button for balance
|
|
||||||
flex-shrink: 0;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-left {
|
.header-left {
|
||||||
|
|
@ -78,39 +71,10 @@
|
||||||
font-weight: $medium;
|
font-weight: $medium;
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
flex: 1;
|
white-space: nowrap;
|
||||||
}
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
.close-button {
|
padding: 0 $unit;
|
||||||
padding: $unit;
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 4px;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
transition:
|
|
||||||
background-color 0.2s,
|
|
||||||
color 0.2s;
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
|
|
||||||
:global(svg) {
|
|
||||||
width: 14px;
|
|
||||||
height: 14px;
|
|
||||||
fill: currentColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--button-bg);
|
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
transform: translateY(1px);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue