From 15bb961ccc6704242c424db3c874635bbcd929a1 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Mon, 25 May 2026 10:19:56 +0300 Subject: [PATCH] refactor(Button): add block-list-row layout variant + adopt design-system tokens MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - New layout prop with values 'inline' (default) and 'block-list-row'. The block-list-row variant bakes in full-width, left-aligned content with trailing icon and text-sm, replacing the ~10-class override duplicated across FilterGroup, FontList, and similar list-row sites. - primary variant's three hard-offset shadows now reference the shadow-stamp-{rest,hover,pressed} tokens; the 0.0625rem translate becomes translate-{x,y}-px. - Base classes use text-label-mono and duration-normal utilities instead of inline 'font-primary font-bold tracking-tight uppercase' and 'duration-200'. - The icon variant's background uses surface-canvas (semantic naming; picks up dark-mode automatically via --color-surface). - text-secondary → text-subtle (avoids collision with the @theme --color-secondary token; see earlier styles commit). New exported type: ButtonLayout. --- src/shared/ui/Button/Button.svelte | 37 ++++++++++++++++++++++-------- src/shared/ui/Button/types.ts | 1 + 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/shared/ui/Button/Button.svelte b/src/shared/ui/Button/Button.svelte index e2b51aa..d533cd2 100644 --- a/src/shared/ui/Button/Button.svelte +++ b/src/shared/ui/Button/Button.svelte @@ -7,6 +7,7 @@ import { cn } from '$shared/lib'; import type { Snippet } from 'svelte'; import type { HTMLButtonAttributes } from 'svelte/elements'; import type { + ButtonLayout, ButtonSize, ButtonVariant, IconPosition, @@ -23,6 +24,14 @@ interface Props extends HTMLButtonAttributes { * @default 'md' */ size?: ButtonSize; + /** + * Layout shape + * - `inline`: default — content-sized, centered. + * - `block-list-row`: full-width row with the content left-aligned and any + * trailing icon pushed to the right (used for filter-group rows, etc). + * @default 'inline' + */ + layout?: ButtonLayout; /** * Icon snippet */ @@ -56,6 +65,7 @@ interface Props extends HTMLButtonAttributes { let { variant = 'secondary', size = 'md', + layout = 'inline', icon, iconPosition = 'left', active = false, @@ -76,10 +86,10 @@ const variantStyles: Record = { 'hover:bg-swiss-red/90', 'active:bg-swiss-red/80', 'border border-swiss-red', - 'shadow-[0.125rem_0.125rem_0_0_rgba(0,0,0,0.1)]', - 'hover:shadow-[0.1875rem_0.1875rem_0_0_rgba(0,0,0,0.15)]', - 'active:shadow-[0.0625rem_0.0625rem_0_0_rgba(0,0,0,0.08)]', - 'active:translate-x-[0.0625rem] active:translate-y-[0.0625rem]', + 'shadow-stamp-rest', + 'hover:shadow-stamp-hover', + 'active:shadow-stamp-pressed', + 'active:translate-x-px active:translate-y-px', 'disabled:bg-neutral-300 dark:disabled:bg-neutral-700', 'disabled:text-neutral-500 dark:disabled:text-neutral-500', 'disabled:border-neutral-300 dark:disabled:border-neutral-700', @@ -111,7 +121,7 @@ const variantStyles: Record = { ), ghost: cn( 'bg-transparent', - 'text-secondary', + 'text-subtle', 'border border-transparent', 'hover:bg-transparent dark:hover:bg-transparent', 'hover:text-brand dark:hover:text-brand', @@ -120,8 +130,8 @@ const variantStyles: Record = { 'disabled:cursor-not-allowed', ), icon: cn( - 'bg-surface dark:bg-dark-bg', - 'text-secondary', + 'surface-canvas', + 'text-subtle', 'border border-transparent', 'hover:bg-paper dark:hover:bg-paper', 'hover:text-brand', @@ -174,12 +184,19 @@ const activeStyles: Partial> = { icon: 'bg-paper dark:bg-paper text-brand border-subtle', }; +const layoutStyles: Record = { + inline: '', + /* List-row buttons act as content labels rather than action buttons, + so they bump to `text-sm` regardless of the size prop's default. */ + 'block-list-row': 'w-full justify-between text-left text-sm', +}; + const classes = $derived(cn( // Base 'inline-flex items-center justify-center', - 'font-primary font-bold tracking-tight uppercase', + 'text-label-mono', 'rounded-none', - 'transition-all duration-200', + 'transition-all duration-normal', 'select-none', 'outline-none', 'cursor-pointer', @@ -190,6 +207,8 @@ const classes = $derived(cn( variantStyles[variant], // Size (square when icon-only) isIconOnly ? iconSizeStyles[size] : sizeStyles[size], + // Layout + layoutStyles[layout], // Animate (CSS tap scale — excluded for primary which uses translate instead) animate && !disabled && variant !== 'primary' && 'active:scale-[0.97]', // Active override diff --git a/src/shared/ui/Button/types.ts b/src/shared/ui/Button/types.ts index 2d42e35..e1e6f5f 100644 --- a/src/shared/ui/Button/types.ts +++ b/src/shared/ui/Button/types.ts @@ -1,3 +1,4 @@ export type ButtonVariant = 'primary' | 'secondary' | 'tertiary' | 'ghost' | 'outline' | 'icon'; export type ButtonSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'; +export type ButtonLayout = 'inline' | 'block-list-row'; export type IconPosition = 'left' | 'right';