date-picker + date-range-picker: trigger embedded inside the input

The calendar icon now lives INSIDE the date input as a flush trailing
affordance, not as a separate sibling button. The change is markup +
recipe CSS only — no API change.

- Demo (date-picker): drop the `.picker-control` grid wrapper; put
  `<DatePicker.Trigger />` as the last child of the Input snippet,
  after the segments. Codeblock snippets updated to match.
- Demo (date-range-picker): keep the dual-input + separator layout
  but move `<DateRangePicker.Trigger />` into the END input, since
  the trigger represents the trailing affordance of the whole range.
  Both code snippets updated.
- Recipe CSS (both): add nested rules that detect the embedded case
  (`[data-date-field-input] > [data-date-picker-trigger]`,
  `[data-date-range-field-input] > [data-date-range-picker-trigger]`).
  Embedded trigger drops its own border/background, matches the
  segment height, sits flush at the trailing edge with
  `margin-inline-start: auto`, and uses the segment focus ring on
  hover/focus instead of the standalone field ring.

The standalone (sibling) treatment stays available for consumers who
prefer the icon as a separate control.

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

@ -133,6 +133,41 @@
opacity: var(--date-field-disabled-opacity);
}
/* ── Trigger embedded inside the input ──────────────────────────────────────
* When the consumer places <DatePicker.Trigger /> as a descendant of
* <DatePicker.Input>, the icon becomes a flush affordance at the input's
* trailing edge: no border, no separate background, smaller hit area sized
* to the segment height, and a subtle hover tint that doesn't compete with
* the field's focus ring. The standalone treatment above stays available
* for consumers who place the trigger as a sibling. */
[data-date-field-input] [data-date-picker-trigger] {
flex: 0 0 auto;
inline-size: var(--date-field-segment-height);
block-size: var(--date-field-segment-height);
margin-inline-start: auto;
padding: 0;
border: none;
border-radius: var(--date-field-segment-radius);
background: transparent;
color: var(--date-field-segment-color);
box-shadow: none;
}
[data-date-field-input] [data-date-picker-trigger]:hover {
border: none;
background: var(--_date-field-accent-track);
color: var(--_date-field-accent-text);
}
[data-date-field-input] [data-date-picker-trigger]:focus-visible {
outline: none;
border: none;
background: var(--_date-field-accent-track);
color: var(--_date-field-accent-text);
box-shadow: var(--date-field-segment-focus-shadow);
}
[data-popover-content]:has([data-date-picker-calendar]) {
--_popover-content-width-override: max-content;
--_popover-content-min-width-override: max-content;

@ -286,6 +286,39 @@
opacity: var(--date-field-disabled-opacity);
}
/* ── Trigger embedded inside the input ──────────────────────────────────────
* Same pattern as date-picker: when the consumer places the trigger as a
* descendant of one of the range inputs (usually the `end` input, as the
* trailing affordance of the whole range), the icon button becomes flush
* inside the input chrome. */
[data-date-range-field-input] [data-date-range-picker-trigger] {
flex: 0 0 auto;
inline-size: var(--date-field-segment-height);
block-size: var(--date-field-segment-height);
margin-inline-start: auto;
padding: 0;
border: none;
border-radius: var(--date-field-segment-radius);
background: transparent;
color: var(--date-field-segment-color);
box-shadow: none;
}
[data-date-range-field-input] [data-date-range-picker-trigger]:hover {
border: none;
background: var(--_date-field-accent-track);
color: var(--_date-field-accent-text);
}
[data-date-range-field-input] [data-date-range-picker-trigger]:focus-visible {
outline: none;
border: none;
background: var(--_date-field-accent-track);
color: var(--_date-field-accent-text);
box-shadow: var(--date-field-segment-focus-shadow);
}
[data-popover-content]:has([data-date-range-picker-calendar]) {
--_popover-content-width-override: max-content;
--_popover-content-min-width-override: max-content;

@ -275,9 +275,9 @@
' {#each segments as { part, value }, index (`${part}-${index}`)}',
' <DatePicker.Segment {part}>{value}</DatePicker.Segment>',
' {/each}',
' <DatePicker.Trigger />',
' {/snippet}',
' </DatePicker.Input>',
' <DatePicker.Trigger />',
' <DatePicker.Portal>',
' <DatePicker.Content align="end" sideOffset={10} collisionPadding={12}>',
' <DatePicker.Calendar>',
@ -345,9 +345,9 @@
' {#each segments as { part, value }, index (`${part}-${index}`)}',
' <DatePicker.Segment {part}>{value}</DatePicker.Segment>',
' {/each}',
' <DatePicker.Trigger />',
' {/snippet}',
' </DatePicker.Input>',
' <DatePicker.Trigger />',
' <DatePicker.Portal>',
' <DatePicker.Content align="end" sideOffset={10} collisionPadding={12}>',
' <DatePicker.Calendar>',
@ -429,16 +429,14 @@
weekdayFormat="short"
>
<DatePicker.Label>{labelText}</DatePicker.Label>
<div class="picker-control">
<DatePicker.Input name={inputName} aria-label={labelText}>
{#snippet children({ segments }: DatePickerInputSnippetProps)}
{#each segments as { part, value }, index (`${part}-${index}`)}
<DatePicker.Segment {part}>{value}</DatePicker.Segment>
{/each}
{/snippet}
</DatePicker.Input>
<DatePicker.Trigger />
</div>
<DatePicker.Input name={inputName} aria-label={labelText}>
{#snippet children({ segments }: DatePickerInputSnippetProps)}
{#each segments as { part, value }, index (`${part}-${index}`)}
<DatePicker.Segment {part}>{value}</DatePicker.Segment>
{/each}
<DatePicker.Trigger />
{/snippet}
</DatePicker.Input>
<div class="date-picker-status" data-invalid={boundsMessage || valueMessage !== 'Selected date is valid.' || undefined}>
<span>{boundsMessage || valueMessage}</span>
</div>
@ -1003,13 +1001,6 @@
inline-size: min(32rem, 100%);
}
.picker-control {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
align-items: center;
gap: var(--uix-space-2);
}
.date-picker-status {
display: flex;
align-items: center;

@ -370,8 +370,10 @@
'>',
` <DateRangePicker.Label>${labelText}</DateRangePicker.Label>`,
` <DateRangePicker.Input type="start" name="${startName}">...</DateRangePicker.Input>`,
` <DateRangePicker.Input type="end" name="${endName}">...</DateRangePicker.Input>`,
' <DateRangePicker.Trigger />',
` <DateRangePicker.Input type="end" name="${endName}">`,
' <!-- end segments -->',
' <DateRangePicker.Trigger />',
' </DateRangePicker.Input>',
' <DateRangePicker.Portal>',
' <DateRangePicker.Content align="end" sideOffset={10} collisionPadding={12}>',
' <DateRangePicker.Calendar>...</DateRangePicker.Calendar>',
@ -429,8 +431,10 @@
' <div class="range-field-row">',
` <DateRangePicker.Input type="start" name="${startName}">...</DateRangePicker.Input>`,
' <span aria-hidden="true">to</span>',
` <DateRangePicker.Input type="end" name="${endName}">...</DateRangePicker.Input>`,
' <DateRangePicker.Trigger />',
` <DateRangePicker.Input type="end" name="${endName}">`,
' <!-- end segments -->',
' <DateRangePicker.Trigger />',
' </DateRangePicker.Input>',
' </div>',
' <DateRangePicker.Portal>',
' <DateRangePicker.Content align="end" sideOffset={10} collisionPadding={12}>',
@ -513,9 +517,9 @@
{#each segments as { part, value }, index (`end-${part}-${index}`)}
<DateRangePicker.Segment {part}>{value}</DateRangePicker.Segment>
{/each}
<DateRangePicker.Trigger />
{/snippet}
</DateRangePicker.Input>
<DateRangePicker.Trigger />
</div>
<div class="range-picker-status" data-invalid={statusMessage !== 'Range is valid.' || undefined}>
<span>{statusMessage}</span>

Loading…
Cancel
Save

Powered by TurnKey Linux.