fix(picker-shell): footer buttons scale with picker size (xs/sm/md/lg)

The footer buttons (Clear / Cancel / Done) were hardcoded to control-height-sm
+ font-size-sm + space-3 padding regardless of the host picker's size. At
xs the buttons looked oversized next to a compact picker; at lg they
looked undersized.

- picker-shell.css: refactored button rules to consume four `--_picker-shell-*`
  CSS vars (control-h, padding-x, font, gap, row-spacing). Defaults match
  the previous sm sizing. Added explicit xs/md/lg variant blocks keyed off
  `[data-popover-content][data-picker-size='X']`.
- date-picker-content / date-range-picker-content / time-picker-content /
  time-range-picker-content / color-picker-content: forward the picker's
  resolvedSize to the popover via a separate `data-picker-size` attr,
  independent from the popover chrome's clamped `data-size`.

This decouples the popover's own chrome (which still clamps xs → sm because
the popover layer has no xs variant) from the picker-shell button scaling,
which now uses the picker's true size.

Verified at /uix/components/date-picker:
- xs → 22 / 12px (control-height-xxs, font-size-xs)
- sm → 30 / 14px (default)
- md → 36 / 16px
- lg → 44 / 16px

Also fixes the horizontal scroll at xs that surfaced the issue. The
date-picker.css `[data-popover-content]:has(...)` rule lets the popover
grow to max-content when it hosts a calendar / grid.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
active-uix
dev 5 months ago
parent b11ef88bca
commit 826481f1d5

@ -24,6 +24,7 @@
{sideOffset}
{collisionPadding}
size={resolvedSize}
data-picker-size={resolvedSize}
data-color-picker-content=""
>
{#snippet children(snippetProps)}

@ -67,6 +67,7 @@
{sideOffset}
{collisionPadding}
size={popoverSize}
data-picker-size={resolvedSize}
style={composedStyle}
>
{#snippet children(snippetProps)}

@ -3,7 +3,8 @@
import { CalendarDate } from '$libs/days';
import { MonthGrid } from '../month-grid';
import type { MonthValue, MonthPlaceholder } from '$soma/components/month-grid';
import type { MonthGridProviderSnippetProps } from '../month-grid';
import type { MonthGridProviderSnippetProps, MonthGridSize } from '../month-grid';
import { getDatePickerVisualContext } from './context.svelte';
import type { HTMLAttributes } from 'svelte/elements';
// Month-grid view for `kind='month'`. Internally renders the standalone
@ -20,6 +21,8 @@
const _props: MonthViewProps = $props();
const provider = DatePickerProvider.require();
const visual = getDatePickerVisualContext();
const size = $derived((visual?.getSize() ?? 'md') as MonthGridSize);
const anchor = $derived(provider.opts.value.current ?? provider.opts.placeholder.current);
@ -53,6 +56,7 @@
placeholder={monthPlaceholder}
{onValueChange}
{onPlaceholderChange}
{size}
locale={provider.opts.locale.current}
disabled={provider.opts.disabled.current}
readonly={provider.opts.readonly.current}

@ -3,7 +3,8 @@
import { CalendarDate } from '$libs/days';
import { YearGrid } from '../year-grid';
import type { YearValue, YearPlaceholder } from '$soma/components/year-grid';
import type { YearGridProviderSnippetProps } from '../year-grid';
import type { YearGridProviderSnippetProps, YearGridSize } from '../year-grid';
import { getDatePickerVisualContext } from './context.svelte';
import type { HTMLAttributes } from 'svelte/elements';
// Year-grid view for `kind='year'`. Internally renders the standalone
@ -20,6 +21,8 @@
const _props: YearViewProps = $props();
const provider = DatePickerProvider.require();
const visual = getDatePickerVisualContext();
const size = $derived((visual?.getSize() ?? 'md') as YearGridSize);
const anchor = $derived(provider.opts.value.current ?? provider.opts.placeholder.current);
@ -52,6 +55,7 @@
placeholder={yearPlaceholder}
{onValueChange}
{onPlaceholderChange}
{size}
locale={provider.opts.locale.current}
disabled={provider.opts.disabled.current}
readonly={provider.opts.readonly.current}

@ -197,7 +197,21 @@
/* ── Year-view + Month-view (kind='year' / 'month') ──────────────────────── */
/* DatePicker.MonthView and DatePicker.YearView now delegate to the
standalone MonthGrid / YearGrid primitives, so their visuals come from
`month-grid.css` and `year-grid.css`. No local rules needed. */
`month-grid.css` and `year-grid.css`. */
/* The popover content uses a size-based base width (popover-min-width-{sm,md,…})
that can be narrower than the picker-shell footer's intrinsic content
(Clear / Cancel / Save buttons + gaps). Let the popover grow to max-content
when it hosts a date-picker calendar / grid so the inner row never overflows
and produces a horizontal scrollbar. */
[data-popover-content]:has(
> [data-date-picker-calendar],
> [data-month-grid],
> [data-year-grid]
) {
inline-size: max-content;
min-inline-size: max-content;
}
@media (prefers-reduced-motion: reduce) {
[data-date-picker-trigger] {

@ -67,6 +67,7 @@
{sideOffset}
{collisionPadding}
size={popoverSize}
data-picker-size={resolvedSize}
style={composedStyle}
>
{#snippet children(snippetProps)}

@ -8,11 +8,20 @@
*/
[data-picker-footer] {
/* Size-scaling vars. Defaults match `sm`. The host picker scopes these
* via `[data-popover-content][data-picker-size='X']` so xs/md/lg tune
* the button row to match the picker. */
--_picker-shell-control-h: var(--control-height-sm);
--_picker-shell-padding-x: var(--space-3);
--_picker-shell-font: var(--font-size-sm);
--_picker-shell-gap: var(--space-2);
--_picker-shell-row-spacing: var(--space-3);
display: flex;
align-items: center;
gap: var(--space-2);
padding-block-start: var(--space-3);
margin-block-start: var(--space-3);
gap: var(--_picker-shell-gap);
padding-block-start: var(--_picker-shell-row-spacing);
margin-block-start: var(--_picker-shell-row-spacing);
border-block-start: var(--border-width) solid var(--color-border-subtle);
}
@ -25,14 +34,14 @@
[data-picker-cancel],
[data-picker-close] {
inline-size: max-content;
min-block-size: var(--control-height-sm);
padding-inline: var(--space-3);
min-block-size: var(--_picker-shell-control-h);
padding-inline: var(--_picker-shell-padding-x);
border-radius: var(--radius-md);
border: var(--border-width) solid transparent;
background: transparent;
color: var(--color-content-primary);
font: inherit;
font-size: var(--font-size-sm);
font-size: var(--_picker-shell-font);
line-height: var(--leading-ui);
cursor: pointer;
transition:
@ -41,6 +50,32 @@
color var(--duration-fast) var(--ease-default);
}
/* Size variants — host pickers emit `data-picker-size` on the popover
* content so portaled descendants can scale. The button row inherits
* the picker's size; popover chrome (padding / min-width) keeps its
* own size variant via [data-size]. */
[data-popover-content][data-picker-size='xs'] [data-picker-footer] {
--_picker-shell-control-h: var(--control-height-xxs);
--_picker-shell-padding-x: var(--space-2);
--_picker-shell-font: var(--font-size-xs);
--_picker-shell-gap: var(--space-1-5);
--_picker-shell-row-spacing: var(--space-2);
}
[data-popover-content][data-picker-size='md'] [data-picker-footer] {
--_picker-shell-control-h: var(--control-height-md);
--_picker-shell-padding-x: var(--space-3-5);
--_picker-shell-font: var(--font-size-md);
}
[data-popover-content][data-picker-size='lg'] [data-picker-footer] {
--_picker-shell-control-h: var(--control-height-lg);
--_picker-shell-padding-x: var(--space-4);
--_picker-shell-font: var(--font-size-md);
--_picker-shell-gap: var(--space-2-5);
--_picker-shell-row-spacing: var(--space-4);
}
[data-picker-clear]:hover,
[data-picker-cancel]:hover {
background: var(--color-surface-overlay);

@ -63,6 +63,7 @@
{sideOffset}
{collisionPadding}
size={resolvedSize}
data-picker-size={resolvedSize}
style={composedStyle}
>
{#snippet children(snippetProps)}

@ -63,6 +63,7 @@
{sideOffset}
{collisionPadding}
size={resolvedSize}
data-picker-size={resolvedSize}
style={composedStyle}
>
{#snippet children(snippetProps)}

Loading…
Cancel
Save

Powered by TurnKey Linux.