refactor: tailwind tier 1 — border-subtle/text-secondary/focus-ring utilities + Input config extraction

This commit is contained in:
Ilia Mashkov
2026-04-16 16:32:41 +03:00
parent aa1379c15b
commit 816d4b89ce
15 changed files with 78 additions and 55 deletions

View File

@@ -265,6 +265,21 @@
} }
} }
@layer utilities {
/* 21× border-black/5 dark:border-white/10 → single token */
.border-subtle {
@apply border-black/5 dark:border-white/10;
}
/* Secondary text pair */
.text-secondary {
@apply text-neutral-500 dark:text-neutral-400;
}
/* Standard focus ring */
.focus-ring {
@apply focus-visible:ring-2 focus-visible:ring-brand focus-visible:ring-offset-2;
}
}
/* Global utility - useful across your app */ /* Global utility - useful across your app */
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
* { * {

View File

@@ -44,7 +44,7 @@ function createButtonText(item: BreadcrumbItem) {
flex items-center justify-between flex items-center justify-between
z-40 z-40
bg-surface/90 dark:bg-dark-bg/90 backdrop-blur-md bg-surface/90 dark:bg-dark-bg/90 backdrop-blur-md
border-b border-black/5 dark:border-white/10 border-b border-subtle
" "
> >
<div class="max-w-8xl px-4 sm:px-6 h-full w-full flex items-center justify-between gap-2 sm:gap-4"> <div class="max-w-8xl px-4 sm:px-6 h-full w-full flex items-center justify-between gap-2 sm:gap-4">

View File

@@ -59,7 +59,7 @@ const stats = $derived([
group relative group relative
w-full h-full w-full h-full
bg-paper dark:bg-dark-card bg-paper dark:bg-dark-card
border border-black/5 dark:border-white/10 border border-subtle
hover:border-brand dark:hover:border-brand hover:border-brand dark:hover:border-brand
hover:shadow-brand/10 hover:shadow-brand/10
hover:shadow-[5px_5px_0px_0px] hover:shadow-[5px_5px_0px_0px]
@@ -76,7 +76,7 @@ const stats = $derived([
class=" class="
flex items-center justify-between flex items-center justify-between
px-4 sm:px-5 md:px-6 py-3 sm:py-4 px-4 sm:px-5 md:px-6 py-3 sm:py-4
border-b border-black/5 dark:border-white/10 border-b border-subtle
bg-paper dark:bg-dark-card bg-paper dark:bg-dark-card
" "
> >
@@ -145,7 +145,7 @@ const stats = $derived([
</div> </div>
<!-- ── Mobile stats footer (md:hidden — header stats take over above) --> <!-- ── Mobile stats footer (md:hidden — header stats take over above) -->
<div class="md:hidden px-4 sm:px-5 py-1.5 sm:py-2 border-t border-black/5 dark:border-white/10 flex gap-2 sm:gap-4 bg-paper dark:bg-dark-card mt-auto"> <div class="md:hidden px-4 sm:px-5 py-1.5 sm:py-2 border-t border-subtle flex gap-2 sm:gap-4 bg-paper dark:bg-dark-card mt-auto">
{#each stats as stat, i} {#each stats as stat, i}
<Footnote class="text-[0.4375rem] sm:text-[0.5rem] tracking-wider {i === 0 ? 'ml-auto' : ''}"> <Footnote class="text-[0.4375rem] sm:text-[0.5rem] tracking-wider {i === 0 ? 'ml-auto' : ''}">
{stat.label}:{stat.value} {stat.label}:{stat.value}

View File

@@ -79,7 +79,7 @@ $effect(() => {
'transition-colors duration-150', 'transition-colors duration-150',
'hover:bg-white/50 dark:hover:bg-white/5', 'hover:bg-white/50 dark:hover:bg-white/5',
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand/30', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand/30',
isOpen && 'bg-paper dark:bg-dark-card border-black/5 dark:border-white/10 shadow-sm', isOpen && 'bg-paper dark:bg-dark-card border-subtle shadow-sm',
className, className,
)} )}
> >
@@ -96,7 +96,7 @@ $effect(() => {
class={cn( class={cn(
'z-50 w-72', 'z-50 w-72',
'bg-surface dark:bg-dark-card', 'bg-surface dark:bg-dark-card',
'border border-black/5 dark:border-white/10', 'border border-subtle',
'shadow-[0_20px_40px_-10px_rgba(0,0,0,0.15)]', 'shadow-[0_20px_40px_-10px_rgba(0,0,0,0.15)]',
'rounded-none p-4', 'rounded-none p-4',
'data-[state=open]:animate-in data-[state=closed]:animate-out', 'data-[state=open]:animate-in data-[state=closed]:animate-out',
@@ -109,7 +109,7 @@ $effect(() => {
escapeKeydownBehavior="close" escapeKeydownBehavior="close"
> >
<!-- Header --> <!-- Header -->
<div class="flex items-center justify-between mb-3 pb-3 border-b border-black/5 dark:border-white/10"> <div class="flex items-center justify-between mb-3 pb-3 border-b border-subtle">
<div class="flex items-center gap-1.5"> <div class="flex items-center gap-1.5">
<Settings2Icon size={12} class="text-swiss-red" /> <Settings2Icon size={12} class="text-swiss-red" />
<span <span
@@ -154,13 +154,13 @@ $effect(() => {
class={cn( class={cn(
'flex items-center gap-1 md:gap-2 p-1.5 md:p-2', 'flex items-center gap-1 md:gap-2 p-1.5 md:p-2',
'bg-surface/95 dark:bg-dark-bg/95 backdrop-blur-xl', 'bg-surface/95 dark:bg-dark-bg/95 backdrop-blur-xl',
'border border-black/5 dark:border-white/10', 'border border-subtle',
'shadow-[0_20px_40px_-10px_rgba(0,0,0,0.1)]', 'shadow-[0_20px_40px_-10px_rgba(0,0,0,0.1)]',
'rounded-none ring-1 ring-black/5 dark:ring-white/5', 'rounded-none ring-1 ring-black/5 dark:ring-white/5',
)} )}
> >
<!-- Header: icon + label --> <!-- Header: icon + label -->
<div class="px-2 md:px-3 flex items-center gap-1.5 md:gap-2 border-r border-black/5 dark:border-white/10 mr-1 text-swiss-black dark:text-neutral-200 shrink-0"> <div class="px-2 md:px-3 flex items-center gap-1.5 md:gap-2 border-r border-subtle mr-1 text-swiss-black dark:text-neutral-200 shrink-0">
<Settings2Icon <Settings2Icon
size={14} size={14}
class="text-swiss-red" class="text-swiss-red"

View File

@@ -111,7 +111,7 @@ const variantStyles: Record<ButtonVariant, string> = {
), ),
ghost: cn( ghost: cn(
'bg-transparent', 'bg-transparent',
'text-neutral-500 dark:text-neutral-400', 'text-secondary',
'border border-transparent', 'border border-transparent',
'hover:bg-transparent dark:hover:bg-transparent', 'hover:bg-transparent dark:hover:bg-transparent',
'hover:text-brand dark:hover:text-brand', 'hover:text-brand dark:hover:text-brand',
@@ -121,7 +121,7 @@ const variantStyles: Record<ButtonVariant, string> = {
), ),
icon: cn( icon: cn(
'bg-surface dark:bg-dark-bg', 'bg-surface dark:bg-dark-bg',
'text-neutral-500 dark:text-neutral-400', 'text-secondary',
'border border-transparent', 'border border-transparent',
'hover:bg-paper dark:hover:bg-paper', 'hover:bg-paper dark:hover:bg-paper',
'hover:text-brand', 'hover:text-brand',
@@ -172,7 +172,7 @@ const activeStyles: Partial<Record<ButtonVariant, string>> = {
'bg-paper dark:bg-dark-card border-black/10 dark:border-white/10 shadow-sm text-neutral-900 dark:text-neutral-100', 'bg-paper dark:bg-dark-card border-black/10 dark:border-white/10 shadow-sm text-neutral-900 dark:text-neutral-100',
ghost: 'bg-transparent dark:bg-transparent text-brand dark:text-brand', ghost: 'bg-transparent dark:bg-transparent text-brand dark:text-brand',
outline: 'bg-surface dark:bg-paper border-brand', outline: 'bg-surface dark:bg-paper border-brand',
icon: 'bg-paper dark:bg-paper text-brand border-black/5 dark:border-white/10', icon: 'bg-paper dark:bg-paper text-brand border-subtle',
}; };
const classes = $derived(cn( const classes = $derived(cn(
@@ -184,7 +184,7 @@ const classes = $derived(cn(
'select-none', 'select-none',
'outline-none', 'outline-none',
'cursor-pointer', 'cursor-pointer',
'focus-visible:ring-2 focus-visible:ring-brand focus-visible:ring-offset-2', 'focus-ring',
'focus-visible:ring-offset-surface dark:focus-visible:ring-offset-dark-bg', 'focus-visible:ring-offset-surface dark:focus-visible:ring-offset-dark-bg',
'disabled:cursor-not-allowed disabled:pointer-events-none', 'disabled:cursor-not-allowed disabled:pointer-events-none',
// Variant // Variant

View File

@@ -26,7 +26,7 @@ let { children, class: className, ...rest }: Props = $props();
class={cn( class={cn(
'flex items-center gap-1 p-1', 'flex items-center gap-1 p-1',
'bg-surface dark:bg-dark-bg', 'bg-surface dark:bg-dark-bg',
'border border-black/5 dark:border-white/10', 'border border-subtle',
'rounded-none', 'rounded-none',
'transition-colors duration-500', 'transition-colors duration-500',
className, className,

View File

@@ -93,9 +93,7 @@ const displayLabel = $derived(label ?? controlLabel ?? '');
step={control.step} step={control.step}
orientation="horizontal" orientation="horizontal"
/> />
<span <span class="font-mono text-[0.6875rem] text-secondary tabular-nums w-10 text-right shrink-0">
class="font-mono text-[0.6875rem] text-neutral-500 dark:text-neutral-400 tabular-nums w-10 text-right shrink-0"
>
{formattedValue()} {formattedValue()}
</span> </span>
</div> </div>
@@ -129,7 +127,7 @@ const displayLabel = $derived(label ?? controlLabel ?? '');
'border border-transparent', 'border border-transparent',
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand/30', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand/30',
open open
? 'bg-paper dark:bg-dark-card shadow-sm border-black/5 dark:border-white/10' ? 'bg-paper dark:bg-dark-card shadow-sm border-subtle'
: 'hover:bg-paper/50 dark:hover:bg-dark-card/50', : 'hover:bg-paper/50 dark:hover:bg-dark-card/50',
)} )}
aria-label={controlLabel} aria-label={controlLabel}
@@ -157,7 +155,7 @@ const displayLabel = $derived(label ?? controlLabel ?? '');
<!-- Vertical slider popover --> <!-- Vertical slider popover -->
<PopoverContent <PopoverContent
class="w-auto py-4 px-3 h-64 flex items-center justify-center rounded-none border border-black/5 dark:border-white/10 shadow-sm bg-paper dark:bg-dark-card" class="w-auto py-4 px-3 h-64 flex items-center justify-center rounded-none border border-subtle shadow-sm bg-paper dark:bg-dark-card"
align="center" align="center"
side="top" side="top"
> >

View File

@@ -24,7 +24,7 @@ interface Props {
const { label, children, class: className }: Props = $props(); const { label, children, class: className }: Props = $props();
</script> </script>
<div class={cn('flex flex-col gap-3 py-6 border-b border-black/5 dark:border-white/10 last:border-0', className)}> <div class={cn('flex flex-col gap-3 py-6 border-b border-subtle last:border-0', className)}>
<div class="flex justify-between items-center text-[0.6875rem] font-primary font-bold tracking-tight text-neutral-900 dark:text-neutral-100 uppercase leading-none"> <div class="flex justify-between items-center text-[0.6875rem] font-primary font-bold tracking-tight text-neutral-900 dark:text-neutral-100 uppercase leading-none">
{label} {label}
</div> </div>

View File

@@ -9,6 +9,10 @@ import type { Snippet } from 'svelte';
import { cubicOut } from 'svelte/easing'; import { cubicOut } from 'svelte/easing';
import type { HTMLInputAttributes } from 'svelte/elements'; import type { HTMLInputAttributes } from 'svelte/elements';
import { scale } from 'svelte/transition'; import { scale } from 'svelte/transition';
import {
inputSizeConfig,
inputVariantConfig,
} from './config';
import type { import type {
InputSize, InputSize,
InputVariant, InputVariant,
@@ -80,36 +84,11 @@ let {
...rest ...rest
}: Props = $props(); }: Props = $props();
const sizeConfig: Record<InputSize, { input: string; text: string; height: string; clearIcon: number }> = {
sm: { input: 'px-3 py-1.5', text: 'text-sm', height: 'h-8', clearIcon: 12 },
md: { input: 'px-4 py-2', text: 'text-base', height: 'h-10', clearIcon: 14 },
lg: { input: 'px-4 py-3', text: 'text-lg md:text-xl', height: 'h-12', clearIcon: 16 },
xl: { input: 'px-4 py-3', text: 'text-xl md:text-2xl', height: 'h-14', clearIcon: 18 },
};
const variantConfig: Record<InputVariant, { base: string; focus: string; error: string }> = {
default: {
base: 'bg-paper dark:bg-paper border border-black/5 dark:border-white/10',
focus: 'focus:border-brand focus:ring-1 focus:ring-brand/20',
error: 'border-brand ring-1 ring-brand/20',
},
underline: {
base: 'bg-transparent border-0 border-b border-neutral-300 dark:border-neutral-700',
focus: 'focus:border-brand',
error: 'border-brand',
},
filled: {
base: 'bg-surface dark:bg-paper border border-transparent',
focus: 'focus:border-brand focus:ring-1 focus:ring-brand/20',
error: 'border-brand ring-1 ring-brand/20',
},
};
const hasValue = $derived(value !== undefined && value !== ''); const hasValue = $derived(value !== undefined && value !== '');
const showClear = $derived(showClearButton && hasValue && !!onclear); const showClear = $derived(showClearButton && hasValue && !!onclear);
const hasRightSlot = $derived(!!rightIcon || showClearButton); const hasRightSlot = $derived(!!rightIcon || showClearButton);
const cfg = $derived(sizeConfig[size]); const cfg = $derived(inputSizeConfig[size]);
const styles = $derived(variantConfig[variant]); const styles = $derived(inputVariantConfig[variant]);
const inputClasses = $derived(cn( const inputClasses = $derived(cn(
'font-primary rounded-none outline-none transition-all duration-200', 'font-primary rounded-none outline-none transition-all duration-200',
@@ -170,7 +149,7 @@ const inputClasses = $derived(cn(
<span <span
class={cn( class={cn(
'text-[0.625rem] font-mono tracking-wide px-1', 'text-[0.625rem] font-mono tracking-wide px-1',
error ? 'text-brand ' : 'text-neutral-500 dark:text-neutral-400', error ? 'text-brand ' : 'text-secondary',
)} )}
> >
{helperText} {helperText}

View File

@@ -0,0 +1,31 @@
import type {
InputSize,
InputVariant,
} from './types';
/** Size-specific layout classes: padding, text size, height, and clear-icon pixel size. */
export const inputSizeConfig: Record<InputSize, { input: string; text: string; height: string; clearIcon: number }> = {
sm: { input: 'px-3 py-1.5', text: 'text-sm', height: 'h-8', clearIcon: 12 },
md: { input: 'px-4 py-2', text: 'text-base', height: 'h-10', clearIcon: 14 },
lg: { input: 'px-4 py-3', text: 'text-lg md:text-xl', height: 'h-12', clearIcon: 16 },
xl: { input: 'px-4 py-3', text: 'text-xl md:text-2xl', height: 'h-14', clearIcon: 18 },
};
/** Variant-specific classes: base background/border, focus ring, and error state. */
export const inputVariantConfig: Record<InputVariant, { base: string; focus: string; error: string }> = {
default: {
base: 'bg-paper dark:bg-paper border border-subtle',
focus: 'focus:border-brand focus:ring-1 focus:ring-brand/20',
error: 'border-brand ring-1 ring-brand/20',
},
underline: {
base: 'bg-transparent border-0 border-b border-neutral-300 dark:border-neutral-700',
focus: 'focus:border-brand',
error: 'border-brand',
},
filled: {
base: 'bg-surface dark:bg-paper border border-transparent',
focus: 'focus:border-brand focus:ring-1 focus:ring-brand/20',
error: 'border-brand ring-1 ring-brand/20',
},
};

View File

@@ -84,7 +84,7 @@ function close() {
'overflow-hidden', 'overflow-hidden',
'will-change-[width]', 'will-change-[width]',
'transition-[width] duration-300 ease-out', 'transition-[width] duration-300 ease-out',
'border-r border-black/5 dark:border-white/10', 'border-r border-subtle',
'bg-surface dark:bg-dark-bg', 'bg-surface dark:bg-dark-bg',
isOpen ? 'w-80 opacity-100' : 'w-0 opacity-0', isOpen ? 'w-80 opacity-100' : 'w-0 opacity-0',
'transition-[width,opacity] duration-300 ease-out', 'transition-[width,opacity] duration-300 ease-out',

View File

@@ -70,7 +70,7 @@ let {
const isVertical = $derived(orientation === 'vertical'); const isVertical = $derived(orientation === 'vertical');
const labelClasses = `font-mono text-[0.625rem] tabular-nums shrink-0 const labelClasses = `font-mono text-[0.625rem] tabular-nums shrink-0
text-neutral-500 dark:text-neutral-400 text-secondary
group-hover:text-neutral-700 dark:group-hover:text-neutral-300 group-hover:text-neutral-700 dark:group-hover:text-neutral-300
transition-colors`; transition-colors`;

View File

@@ -71,7 +71,7 @@ $effect(() => {
<div class="flex-1 min-h-0 h-full"> <div class="flex-1 min-h-0 h-full">
<div class="py-2 relative flex flex-col min-h-0 h-full"> <div class="py-2 relative flex flex-col min-h-0 h-full">
<div class="py-2 mx-6 sticky border-b border-black/5 dark:border-white/10"> <div class="py-2 mx-6 sticky border-b border-subtle">
<Label class="font-primary text-neutral-400" bold variant="default" size="sm" uppercase> <Label class="font-primary text-neutral-400" bold variant="default" size="sm" uppercase>
Typeface Selection Typeface Selection
</Label> </Label>

View File

@@ -53,7 +53,7 @@ const fontBName = $derived(comparisonStore.fontB?.name ?? '');
'flex items-center justify-between', 'flex items-center justify-between',
'px-4 md:px-8 py-4 md:py-6', 'px-4 md:px-8 py-4 md:py-6',
'h-16 md:h-20 z-20', 'h-16 md:h-20 z-20',
'border-b border-black/5 dark:border-white/10', 'border-b border-subtle',
'bg-surface dark:bg-dark-bg', 'bg-surface dark:bg-dark-bg',
className, className,
)} )}

View File

@@ -44,7 +44,7 @@ let {
'flex flex-col h-full', 'flex flex-col h-full',
'w-80', 'w-80',
'bg-surface dark:bg-dark-bg', 'bg-surface dark:bg-dark-bg',
'border-r border-black/5 dark:border-white/10', 'border-r border-subtle',
'transition-colors duration-500', 'transition-colors duration-500',
className, className,
)} )}
@@ -53,7 +53,7 @@ let {
<div <div
class=" class="
p-6 shrink-0 p-6 shrink-0
border-b border-black/5 dark:border-white/10 border-b border-subtle
bg-surface dark:bg-dark-bg bg-surface dark:bg-dark-bg
" "
> >
@@ -100,7 +100,7 @@ let {
class=" class="
shrink-0 p-6 shrink-0 p-6
bg-surface dark:bg-dark-bg bg-surface dark:bg-dark-bg
border-t border-black/5 dark:border-white/10 border-t border-subtle
z-10 z-10
" "
> >