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} {sideOffset}
{collisionPadding} {collisionPadding}
size={resolvedSize} size={resolvedSize}
data-picker-size={resolvedSize}
data-color-picker-content="" data-color-picker-content=""
> >
{#snippet children(snippetProps)} {#snippet children(snippetProps)}

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

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

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

@ -197,7 +197,21 @@
/* ── Year-view + Month-view (kind='year' / 'month') ──────────────────────── */ /* ── Year-view + Month-view (kind='year' / 'month') ──────────────────────── */
/* DatePicker.MonthView and DatePicker.YearView now delegate to the /* DatePicker.MonthView and DatePicker.YearView now delegate to the
standalone MonthGrid / YearGrid primitives, so their visuals come from 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) { @media (prefers-reduced-motion: reduce) {
[data-date-picker-trigger] { [data-date-picker-trigger] {

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

@ -8,11 +8,20 @@
*/ */
[data-picker-footer] { [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; display: flex;
align-items: center; align-items: center;
gap: var(--space-2); gap: var(--_picker-shell-gap);
padding-block-start: var(--space-3); padding-block-start: var(--_picker-shell-row-spacing);
margin-block-start: var(--space-3); margin-block-start: var(--_picker-shell-row-spacing);
border-block-start: var(--border-width) solid var(--color-border-subtle); border-block-start: var(--border-width) solid var(--color-border-subtle);
} }
@ -25,14 +34,14 @@
[data-picker-cancel], [data-picker-cancel],
[data-picker-close] { [data-picker-close] {
inline-size: max-content; inline-size: max-content;
min-block-size: var(--control-height-sm); min-block-size: var(--_picker-shell-control-h);
padding-inline: var(--space-3); padding-inline: var(--_picker-shell-padding-x);
border-radius: var(--radius-md); border-radius: var(--radius-md);
border: var(--border-width) solid transparent; border: var(--border-width) solid transparent;
background: transparent; background: transparent;
color: var(--color-content-primary); color: var(--color-content-primary);
font: inherit; font: inherit;
font-size: var(--font-size-sm); font-size: var(--_picker-shell-font);
line-height: var(--leading-ui); line-height: var(--leading-ui);
cursor: pointer; cursor: pointer;
transition: transition:
@ -41,6 +50,32 @@
color var(--duration-fast) var(--ease-default); 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-clear]:hover,
[data-picker-cancel]:hover { [data-picker-cancel]:hover {
background: var(--color-surface-overlay); background: var(--color-surface-overlay);

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

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

Loading…
Cancel
Save

Powered by TurnKey Linux.