You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/src/lib/_demo/DemoSizes.svelte

298 lines
7.9 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<script lang="ts" module>
/**
* Two visual modes:
*
* - 'square' — for box-like components (Icon, Avatar). Each size
* renders as an actual-size square in a baseline-aligned row, so
* the user sees the proportional difference at a glance: xxs is
* visibly tiny next to xxl.
* - 'bar' — for panel-like components (Dialog, Drawer, Popover).
* Each size is a horizontal bar whose width scales with the size,
* stacked vertically so the user reads it like a histogram.
*/
export type DemoSizesShape = 'square' | 'bar';
export type DemoSize = 'xxs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl' | 'full';
// Absolute sizes for the comparative row. Cap the largest at ~22px
// so that with 1.8rem (~29 px) cell floors the squares always sit
// fully inside their cells — bigger squares would overflow into the
// gap and visually merge with neighbours. The proportional truth is
// still preserved (xxl ≈ 7× xxs).
const SQUARE_PX: Record<DemoSize, number> = {
xxs: 3,
xs: 5,
sm: 8,
md: 12,
lg: 16,
xl: 19,
xxl: 22,
full: 24
};
const BAR_PCT: Record<DemoSize, number> = {
xxs: 12,
xs: 22,
sm: 35,
md: 50,
lg: 65,
xl: 80,
xxl: 95,
full: 100
};
const ORDER: DemoSize[] = ['xxs', 'xs', 'sm', 'md', 'lg', 'xl', 'xxl', 'full'];
</script>
<script lang="ts" generics="T extends DemoSize">
/**
* Size picker for demos. Renders a unified comparative scale (not a
* chip-per-size grid), so the magnitude relationship between values
* is the FIRST thing the reader perceives.
*
* - shape='square' → row of actual-size squares, baseline-aligned;
* active square fills with the accent.
* - shape='bar' → stacked horizontal bars, widths proportional
* to the size; active bar fills + name highlights.
*
* Generic over the size vocabulary so the consumer's narrower type
* (e.g. Drawer's 'sm'|'md'|'lg'|'full') flows through `bind:value`
* without widening casts.
*/
type Props = {
/** Currently selected size. Bindable. */
value: T;
/** Subset of sizes the component supports. Defaults to all eight. */
available?: readonly T[];
/** Visual mode — 'square' for icon-like, 'bar' for panel-like. */
shape?: DemoSizesShape;
/** Optional caption above the scale. */
label?: string;
};
let {
value = $bindable(),
available,
shape = 'square',
label
}: Props = $props();
const visible = $derived(
ORDER.filter((s) =>
(available ?? (ORDER as unknown as readonly T[])).includes(s as unknown as T)
)
);
</script>
<div class="control">
{#if label}
<span class="control-label">{label}</span>
{/if}
{#if shape === 'square'}
<div class="sizes-square-row sizes-box" role="radiogroup" aria-label={label ?? 'Size'}>
{#each visible as size (size)}
<button
type="button"
class="sq-cell"
class:active={(value as string) === size}
role="radio"
aria-checked={(value as string) === size}
onclick={() => (value = size as T)}
>
{#if size === 'full'}
<span class="sq-full" aria-hidden="true">
<span class="sq-full-cap"></span>
<span class="sq-full-bar"></span>
<span class="sq-full-cap"></span>
</span>
{:else}
<span
class="sq-shape"
style="--side: {SQUARE_PX[size]}px;"
aria-hidden="true"
></span>
{/if}
<span class="sq-name">{size}</span>
</button>
{/each}
</div>
{:else}
<div class="sizes-bar-stack sizes-box" role="radiogroup" aria-label={label ?? 'Size'}>
{#each visible as size (size)}
<button
type="button"
class="bar-row"
class:active={(value as string) === size}
role="radio"
aria-checked={(value as string) === size}
onclick={() => (value = size as T)}
>
<span class="bar-name">{size}</span>
<span class="bar-track" aria-hidden="true">
<span class="bar-fill" style="--bar-width: {BAR_PCT[size]}%;"></span>
</span>
</button>
{/each}
</div>
{/if}
</div>
<style>
/* Box wrapping the picker grid/stack — sits below `.control-label`,
* so the label stays outside the box (uniform with DemoText etc). */
.sizes-box {
padding: 0.5rem 0.6rem;
border: 1px solid var(--gray-200);
border-radius: var(--radius-md);
background: var(--gray-0);
}
/* ── Square shape — comparative row, baseline-aligned ───────────── */
.sizes-square-row {
display: flex;
align-items: end;
gap: 0.4rem;
min-inline-size: 0;
overflow-x: auto;
}
.sq-cell {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.4rem;
/* Cells size to their content (label or square — whichever wider)
* + a safety floor that fits the 3-char mono labels comfortably.
* `flex: 0 0 auto` prevents the equal-distribution collapse that
* would compress big-square cells into their neighbours. */
flex: 0 0 auto;
min-inline-size: 1.9rem;
padding: 0.3rem 0.25rem 0.4rem;
border: none;
background: transparent;
font: inherit;
cursor: pointer;
border-radius: var(--radius-sm);
transition: background var(--duration-fast) var(--ease-default);
}
.sq-cell:hover:not(.active) {
background: var(--gray-50);
}
.sq-cell.active {
background: color-mix(in srgb, var(--accent-500) 12%, transparent);
}
.sq-shape {
display: block;
inline-size: var(--side);
block-size: var(--side);
background: var(--gray-400);
border-radius: var(--radius-sm);
transition: background var(--duration-fast) var(--ease-default);
}
.sq-cell.active .sq-shape {
background: var(--accent-600);
}
.sq-cell:hover:not(.active) .sq-shape {
background: var(--gray-600);
}
.sq-name {
font-family: var(--font-mono);
font-size: 0.7rem;
color: var(--gray-500);
letter-spacing: 0.02em;
}
.sq-cell.active .sq-name {
color: var(--accent-700);
font-weight: 600;
}
/* `full` glyph — visually distinct from a fixed square: a horizontal
* track with end caps, conveying "fills its container". */
.sq-full {
display: flex;
align-items: center;
gap: 2px;
inline-size: 36px;
block-size: 8px;
}
.sq-full-cap {
flex: 0 0 3px;
block-size: 100%;
background: var(--gray-400);
border-radius: 2px;
transition: background var(--duration-fast) var(--ease-default);
}
.sq-full-bar {
flex: 1;
block-size: 4px;
background: var(--gray-400);
border-radius: var(--radius-full);
transition: background var(--duration-fast) var(--ease-default);
}
.sq-cell.active .sq-full-cap,
.sq-cell.active .sq-full-bar {
background: var(--accent-600);
}
.sq-cell:hover:not(.active) .sq-full-cap,
.sq-cell:hover:not(.active) .sq-full-bar {
background: var(--gray-600);
}
/* ── Bar shape — stacked histogram ──────────────────────────────── */
.sizes-bar-stack {
display: flex;
flex-direction: column;
gap: 0.18rem;
}
.bar-row {
display: grid;
grid-template-columns: 2.4rem 1fr;
align-items: center;
gap: 0.6rem;
padding: 0.3rem 0.5rem;
border: none;
background: transparent;
font: inherit;
cursor: pointer;
border-radius: var(--radius-sm);
transition: background var(--duration-fast) var(--ease-default);
}
.bar-row:hover:not(.active) {
background: var(--gray-50);
}
.bar-row.active {
background: color-mix(in srgb, var(--accent-500) 12%, transparent);
}
.bar-name {
font-family: var(--font-mono);
font-size: 0.72rem;
color: var(--gray-500);
letter-spacing: 0.02em;
text-align: end;
}
.bar-row.active .bar-name {
color: var(--accent-700);
font-weight: 600;
}
.bar-track {
display: block;
block-size: 10px;
border-radius: var(--radius-full);
background: var(--gray-100);
overflow: hidden;
}
.bar-fill {
display: block;
block-size: 100%;
inline-size: var(--bar-width);
background: var(--gray-400);
transition: background var(--duration-fast) var(--ease-default);
}
.bar-row.active .bar-fill {
background: var(--accent-600);
}
.bar-row:hover:not(.active) .bar-fill {
background: var(--gray-600);
}
</style>

Powered by TurnKey Linux.