M3 § Date Pickers, Anatomy describes a 14-element full-screen range picker for compact breakpoints; `<x-datepicker range>` always used the 360px modal dialog, which on a phone is cramped for a two-month scroll (plan step 24, audit docs/audits/m3-alignment/inputs.md § Missing). A third presentation, "full", joins docked and modal: the same dialog grown to the screen, an app bar with a close button and Save, the supporting text and the range as the headline over a divider, the weekday labels held at the top, and the months in one vertically scrolling list, each under its own label. The grid now draws from one `rows` getter, which is this month's weeks everywhere else, so the docked and single-date pickers render exactly as before. The list is a window of months grown by the scroll and by the keyboard, since nothing here is lazy. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
596 lines
20 KiB
CSS
596 lines
20 KiB
CSS
/*
|
||
* M3's date pickers — docked, modal and modal input (resources/views/components/datepicker.blade.php).
|
||
*
|
||
* One `<dialog>` wears all three: docked it is a popover hung under the field by CSS anchor
|
||
* positioning (large corner), modal it is opened with `showModal()` over a scrim (extra-large
|
||
* corner, with M3's header: a title, the chosen date as the headline, and the switch between
|
||
* calendar and typing), and full — a `range` picker on a compact window — is that modal dialog
|
||
* grown to the whole screen, M3's full-screen range picker. The first two are 360px of
|
||
* surface-container-high at elevation 3; the third keeps the colour and drops the corner.
|
||
*
|
||
* Values from DatePickerModalTokens and DateInputModalTokens (androidx Compose Material 3, commit
|
||
* 27cf9a7d5788aa0f5f2d8b6699ce279560daf326) and the layout of DatePicker.kt, DateRangePicker.kt and
|
||
* DateInput.kt; the docked picker's from material-web's md-comp-date-picker-docked tokens (v0_192).
|
||
* Apache-2.0.
|
||
*
|
||
* [data-datepicker] the root: the field, its support line, the dialog
|
||
* [data-datepicker-support] the hint, a server error, or what is wrong with the typed date
|
||
* [data-datepicker-picker] the <dialog>; data-presentation="docked" | "modal" | "full"
|
||
* [data-datepicker-surface]
|
||
* [data-datepicker-header] modal only: title, headline, switch
|
||
* [data-datepicker-full] full only: [data-datepicker-app-bar] (close, switch, Save)
|
||
* and [data-datepicker-full-headline] (title, headline)
|
||
* [data-datepicker-calendar] the scroller, full screen
|
||
* [data-datepicker-nav] month and year: one menu button and arrows (modal),
|
||
* or a month and a year stepper (docked, data-docked)
|
||
* [data-datepicker-grid] the weekdays and six weeks of [data-datepicker-day] cells:
|
||
* data-today, data-selected, data-start, data-end,
|
||
* data-between, data-outside, data-blank, aria-disabled —
|
||
* full screen, every month of the window under a
|
||
* [data-datepicker-month-label] row
|
||
* [data-datepicker-years] the modal year grid
|
||
* [data-datepicker-menu] a docked month or year list of [data-datepicker-option]s
|
||
* [data-datepicker-entry] the text field(s) of the modal input
|
||
* [data-datepicker-actions] Cancel and OK
|
||
*
|
||
* Every state layer is the content colour at 8% on hover (only where a pointer can hover) and 10% on
|
||
* focus and press; keyboard focus adds the package's 3px secondary ring.
|
||
*/
|
||
|
||
@layer components {
|
||
/* ---- The support line under the field ------------------------------------------------------ */
|
||
|
||
/* The padding is on the lines, not the box, so a box with nothing to say takes no room. */
|
||
[data-datepicker-support] {
|
||
--datepicker-pad: 1rem;
|
||
|
||
color: var(--md-sys-color-on-surface-variant);
|
||
font: var(--md-sys-typescale-body-sm);
|
||
letter-spacing: var(--md-sys-typescale-body-sm-tracking);
|
||
}
|
||
|
||
[data-datepicker-support] > * {
|
||
padding: 0.25rem var(--datepicker-pad) 0;
|
||
}
|
||
|
||
[data-datepicker-support][data-size="sm"] {
|
||
--datepicker-pad: 0.75rem;
|
||
}
|
||
|
||
[data-datepicker-support][data-size="xs"] {
|
||
--datepicker-pad: 0.625rem;
|
||
}
|
||
|
||
[data-datepicker-error] {
|
||
color: var(--md-sys-color-error);
|
||
}
|
||
|
||
/* ---- The dialog ---------------------------------------------------------------------------- */
|
||
|
||
[data-datepicker-picker] {
|
||
inset: auto;
|
||
width: 22.5rem;
|
||
max-width: calc(100vw - 1rem);
|
||
max-height: calc(100dvh - 1rem);
|
||
margin: 0;
|
||
padding: 0;
|
||
overflow: visible;
|
||
border: 0;
|
||
background: transparent;
|
||
color: var(--md-sys-color-on-surface);
|
||
}
|
||
|
||
[data-datepicker-picker]:popover-open {
|
||
top: anchor(bottom);
|
||
left: anchor(left);
|
||
margin-block: 0.25rem;
|
||
position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;
|
||
opacity: 1;
|
||
transition-property: opacity;
|
||
transition-duration: var(--md-sys-motion-effects-fast-duration);
|
||
transition-timing-function: var(--md-sys-motion-effects-fast);
|
||
}
|
||
|
||
@starting-style {
|
||
[data-datepicker-picker]:popover-open {
|
||
opacity: 0;
|
||
}
|
||
}
|
||
|
||
[data-datepicker-picker]:modal {
|
||
inset: 0;
|
||
margin: auto;
|
||
opacity: 1;
|
||
scale: 1;
|
||
transition-property: opacity, scale;
|
||
transition-duration: var(--md-sys-motion-spatial-fast-duration);
|
||
transition-timing-function: var(--md-sys-motion-spatial-fast);
|
||
}
|
||
|
||
@starting-style {
|
||
[data-datepicker-picker]:modal {
|
||
opacity: 0;
|
||
scale: 0.95;
|
||
}
|
||
}
|
||
|
||
[data-datepicker-picker]:modal::backdrop {
|
||
background-color: color-mix(in srgb, var(--md-sys-color-scrim) 32%, transparent);
|
||
}
|
||
|
||
[data-datepicker-surface] {
|
||
display: flex;
|
||
flex-direction: column;
|
||
max-height: inherit;
|
||
overflow-x: hidden;
|
||
overflow-y: auto;
|
||
border-radius: var(--md-sys-shape-corner-lg);
|
||
background-color: var(--md-sys-color-surface-container-high);
|
||
box-shadow: var(--md-sys-elevation-3);
|
||
outline: none;
|
||
}
|
||
|
||
[data-datepicker-picker][data-presentation="modal"] [data-datepicker-surface] {
|
||
border-radius: var(--md-sys-shape-corner-xl);
|
||
}
|
||
|
||
/* ---- The full-screen range picker (compact) ------------------------------------------------- */
|
||
|
||
/* M3 § Date Pickers: a range picker at a compact breakpoint is a full-screen dialog, "covering
|
||
* the whole screen for readability and touch-target size", with a close (×) icon button and a
|
||
* Save confirmation instead of Cancel and OK. Its anatomy is the modal picker's parts in a
|
||
* different order: an app bar, the supporting text and the range as the headline, a divider, the
|
||
* day-of-week labels, and a vertically scrolling list of months under them, each with its label.
|
||
* Only the months scroll; everything above them stays where it is. */
|
||
[data-datepicker-picker][data-presentation="full"] {
|
||
inset: 0;
|
||
width: 100vw;
|
||
max-width: none;
|
||
height: 100dvh;
|
||
max-height: none;
|
||
margin: 0;
|
||
}
|
||
|
||
[data-datepicker-picker][data-presentation="full"] [data-datepicker-surface] {
|
||
height: 100%;
|
||
max-height: none;
|
||
padding-block: var(--material-safe-top, env(safe-area-inset-top)) var(--material-safe-bottom, env(safe-area-inset-bottom));
|
||
overflow: hidden;
|
||
border-radius: 0;
|
||
box-shadow: none;
|
||
}
|
||
|
||
[data-datepicker-full] {
|
||
flex: none;
|
||
border-bottom: 1px solid var(--md-sys-color-outline-variant);
|
||
color: var(--md-sys-color-on-surface-variant);
|
||
}
|
||
|
||
[data-datepicker-app-bar] {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 0.5rem;
|
||
height: 4rem;
|
||
padding-inline: 0.25rem 0.75rem;
|
||
}
|
||
|
||
[data-datepicker-app-bar-actions] {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.25rem;
|
||
}
|
||
|
||
[data-datepicker-full-headline] {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0.25rem;
|
||
padding: 0 1.5rem 1rem;
|
||
}
|
||
|
||
/* The calendar is the only part that scrolls, with the weekday header held at its top. */
|
||
[data-datepicker-picker][data-presentation="full"] [data-datepicker-calendar] {
|
||
flex: 1 1 auto;
|
||
min-height: 0;
|
||
overflow-y: auto;
|
||
overscroll-behavior: contain;
|
||
}
|
||
|
||
[data-datepicker-picker][data-presentation="full"] [data-datepicker-grid] thead th {
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 1;
|
||
background-color: var(--md-sys-color-surface-container-high);
|
||
}
|
||
|
||
[data-datepicker-picker][data-presentation="full"] [data-datepicker-day] {
|
||
scroll-margin-block: 3rem;
|
||
}
|
||
|
||
[data-datepicker-month-label] {
|
||
padding: 1.25rem 0 0.5rem;
|
||
color: var(--md-sys-color-on-surface-variant);
|
||
font: var(--md-sys-typescale-title-sm);
|
||
letter-spacing: var(--md-sys-typescale-title-sm-tracking);
|
||
text-align: start;
|
||
}
|
||
|
||
/* ---- The modal header ---------------------------------------------------------------------- */
|
||
|
||
[data-datepicker-header] {
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: space-between;
|
||
flex: none;
|
||
min-height: 7.5rem;
|
||
border-bottom: 1px solid var(--md-sys-color-outline-variant);
|
||
color: var(--md-sys-color-on-surface-variant);
|
||
}
|
||
|
||
[data-datepicker-header][data-range] {
|
||
min-height: 8rem;
|
||
}
|
||
|
||
[data-datepicker-title] {
|
||
padding: 1rem 0.75rem 0 1.5rem;
|
||
font: var(--md-sys-typescale-label-lg);
|
||
letter-spacing: var(--md-sys-typescale-label-lg-tracking);
|
||
}
|
||
|
||
[data-datepicker-headline-row] {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 0.5rem;
|
||
padding: 0 0.75rem 0.75rem 1.5rem;
|
||
}
|
||
|
||
[data-datepicker-headline] {
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
font: var(--md-sys-typescale-headline-lg);
|
||
letter-spacing: var(--md-sys-typescale-headline-lg-tracking);
|
||
}
|
||
|
||
[data-datepicker-header][data-range] [data-datepicker-headline] {
|
||
font: var(--md-sys-typescale-title-lg);
|
||
letter-spacing: var(--md-sys-typescale-title-lg-tracking);
|
||
}
|
||
|
||
/* ---- Month and year navigation -------------------------------------------------------------- */
|
||
|
||
[data-datepicker-nav] {
|
||
display: flex;
|
||
flex: none;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
height: 3.5rem;
|
||
padding-inline: 0.75rem;
|
||
color: var(--md-sys-color-on-surface-variant);
|
||
}
|
||
|
||
[data-datepicker-nav][data-docked] {
|
||
height: 4rem;
|
||
padding-inline: 0.25rem;
|
||
}
|
||
|
||
[data-datepicker-stepper],
|
||
[data-datepicker-arrows] {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
/* While a year or month list is open the arrows keep their place but are gone, as androidx hides them. */
|
||
[data-datepicker-arrows][data-concealed] {
|
||
visibility: hidden;
|
||
}
|
||
|
||
[data-datepicker-menu-button] {
|
||
position: relative;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
height: 2.5rem;
|
||
padding-inline: 0.75rem 0.5rem;
|
||
border-radius: var(--md-sys-shape-corner-full);
|
||
color: var(--md-sys-color-on-surface-variant);
|
||
font: var(--md-sys-typescale-label-lg);
|
||
letter-spacing: var(--md-sys-typescale-label-lg-tracking);
|
||
white-space: nowrap;
|
||
cursor: pointer;
|
||
outline: none;
|
||
isolation: isolate;
|
||
}
|
||
|
||
[data-datepicker-nav][data-docked] [data-datepicker-menu-button] {
|
||
padding-inline: 0.5rem 0.25rem;
|
||
gap: 0.25rem;
|
||
}
|
||
|
||
[data-datepicker-menu-arrow] {
|
||
transition: rotate var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast);
|
||
}
|
||
|
||
[data-datepicker-menu-button][aria-expanded="true"] [data-datepicker-menu-arrow] {
|
||
rotate: 180deg;
|
||
}
|
||
|
||
/* ---- State layers shared by days, years, menu buttons and list rows ------------------------ */
|
||
|
||
[data-datepicker-menu-button]::before,
|
||
[data-datepicker-day] > span::before,
|
||
[data-datepicker-year]::before,
|
||
[data-datepicker-option]::before {
|
||
content: "";
|
||
position: absolute;
|
||
inset: 0;
|
||
z-index: -1;
|
||
border-radius: inherit;
|
||
background-color: var(--datepicker-layer, currentColor);
|
||
opacity: 0;
|
||
pointer-events: none;
|
||
transition: opacity var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast);
|
||
}
|
||
|
||
@media (hover: hover) {
|
||
[data-datepicker-menu-button]:hover::before,
|
||
[data-datepicker-day]:not([aria-disabled="true"], [data-blank]):hover > span::before,
|
||
[data-datepicker-year]:hover::before,
|
||
[data-datepicker-option]:not([aria-disabled="true"]):hover::before {
|
||
opacity: 0.08;
|
||
}
|
||
}
|
||
|
||
[data-datepicker-menu-button]:focus-visible::before,
|
||
[data-datepicker-menu-button]:active::before,
|
||
[data-datepicker-day]:focus-visible > span::before,
|
||
[data-datepicker-day]:not([aria-disabled="true"], [data-blank]):active > span::before,
|
||
[data-datepicker-year]:focus-visible::before,
|
||
[data-datepicker-year]:active::before,
|
||
[data-datepicker-option]:focus-visible::before,
|
||
[data-datepicker-option]:not([aria-disabled="true"]):active::before {
|
||
opacity: 0.1;
|
||
}
|
||
|
||
[data-datepicker-menu-button]:focus-visible,
|
||
[data-datepicker-day]:focus-visible > span,
|
||
[data-datepicker-year]:focus-visible {
|
||
outline: 3px solid var(--md-sys-color-secondary);
|
||
outline-offset: 2px;
|
||
}
|
||
|
||
[data-datepicker-option]:focus-visible {
|
||
outline: 3px solid var(--md-sys-color-secondary);
|
||
outline-offset: -3px;
|
||
}
|
||
|
||
/* ---- The days ----------------------------------------------------------------------------- */
|
||
|
||
[data-datepicker-grid] {
|
||
width: calc(100% - 1.5rem);
|
||
margin-inline: 0.75rem;
|
||
border-collapse: collapse;
|
||
table-layout: fixed;
|
||
}
|
||
|
||
[data-datepicker-grid] th {
|
||
height: 3rem;
|
||
padding: 0;
|
||
color: var(--md-sys-color-on-surface);
|
||
font: var(--md-sys-typescale-body-lg);
|
||
letter-spacing: var(--md-sys-typescale-body-lg-tracking);
|
||
text-align: center;
|
||
}
|
||
|
||
[data-datepicker-day] {
|
||
--datepicker-layer: var(--md-sys-color-on-surface-variant);
|
||
|
||
position: relative;
|
||
height: 3rem;
|
||
padding: 0;
|
||
color: var(--md-sys-color-on-surface);
|
||
font: var(--md-sys-typescale-body-lg);
|
||
letter-spacing: var(--md-sys-typescale-body-lg-tracking);
|
||
text-align: center;
|
||
cursor: pointer;
|
||
outline: none;
|
||
isolation: isolate;
|
||
-webkit-tap-highlight-color: transparent;
|
||
}
|
||
|
||
[data-datepicker-day] > span {
|
||
position: relative;
|
||
display: grid;
|
||
place-items: center;
|
||
width: 2.5rem;
|
||
height: 2.5rem;
|
||
margin: auto;
|
||
border-radius: var(--md-sys-shape-corner-full);
|
||
transition: background-color var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast);
|
||
}
|
||
|
||
[data-datepicker-day][data-blank] {
|
||
cursor: default;
|
||
}
|
||
|
||
/* A docked picker shows the neighbouring months' days, quieter. */
|
||
[data-datepicker-day][data-outside] {
|
||
color: var(--md-sys-color-on-surface-variant);
|
||
}
|
||
|
||
[data-datepicker-day][data-today] {
|
||
--datepicker-layer: var(--md-sys-color-primary);
|
||
|
||
color: var(--md-sys-color-primary);
|
||
}
|
||
|
||
[data-datepicker-day][data-today] > span {
|
||
box-shadow: inset 0 0 0 1px var(--md-sys-color-primary);
|
||
}
|
||
|
||
/* A range's band: secondary-container behind the days between its ends, from the middle of each end. */
|
||
[data-datepicker-day][data-between]::before,
|
||
[data-datepicker-day][data-start]::before,
|
||
[data-datepicker-day][data-end]::before {
|
||
content: "";
|
||
position: absolute;
|
||
inset-block: 0.25rem;
|
||
inset-inline: 0;
|
||
z-index: -1;
|
||
background-color: var(--md-sys-color-secondary-container);
|
||
}
|
||
|
||
[data-datepicker-day][data-start]::before {
|
||
inset-inline-start: 50%;
|
||
}
|
||
|
||
[data-datepicker-day][data-end]::before {
|
||
inset-inline-end: 50%;
|
||
}
|
||
|
||
[data-datepicker-day][data-between] {
|
||
--datepicker-layer: var(--md-sys-color-on-secondary-container);
|
||
|
||
color: var(--md-sys-color-on-secondary-container);
|
||
}
|
||
|
||
[data-datepicker-day][data-selected] {
|
||
--datepicker-layer: var(--md-sys-color-on-primary);
|
||
|
||
color: var(--md-sys-color-on-primary);
|
||
}
|
||
|
||
[data-datepicker-day][data-selected] > span {
|
||
background-color: var(--md-sys-color-primary);
|
||
box-shadow: none;
|
||
}
|
||
|
||
[data-datepicker-day][aria-disabled="true"] {
|
||
color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
|
||
cursor: default;
|
||
}
|
||
|
||
[data-datepicker-day][aria-disabled="true"][data-today] > span {
|
||
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
|
||
}
|
||
|
||
/* ---- The modal year grid ----------------------------------------------------------------- */
|
||
|
||
[data-datepicker-years] {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
align-content: start;
|
||
justify-items: center;
|
||
row-gap: 1rem;
|
||
height: 20.9375rem;
|
||
padding: 0.5rem 0.75rem;
|
||
overflow-y: auto;
|
||
overscroll-behavior: contain;
|
||
border-bottom: 1px solid var(--md-sys-color-outline-variant);
|
||
}
|
||
|
||
[data-datepicker-year] {
|
||
--datepicker-layer: var(--md-sys-color-on-surface-variant);
|
||
|
||
position: relative;
|
||
width: 4.5rem;
|
||
height: 2.25rem;
|
||
border-radius: var(--md-sys-shape-corner-full);
|
||
color: var(--md-sys-color-on-surface-variant);
|
||
font: var(--md-sys-typescale-body-lg);
|
||
letter-spacing: var(--md-sys-typescale-body-lg-tracking);
|
||
cursor: pointer;
|
||
outline: none;
|
||
isolation: isolate;
|
||
scroll-margin-block: 0.5rem;
|
||
}
|
||
|
||
[data-datepicker-year][data-current] {
|
||
--datepicker-layer: var(--md-sys-color-primary);
|
||
|
||
color: var(--md-sys-color-primary);
|
||
box-shadow: inset 0 0 0 1px var(--md-sys-color-primary);
|
||
}
|
||
|
||
[data-datepicker-year][aria-selected="true"] {
|
||
--datepicker-layer: var(--md-sys-color-on-primary);
|
||
|
||
background-color: var(--md-sys-color-primary);
|
||
color: var(--md-sys-color-on-primary);
|
||
box-shadow: none;
|
||
}
|
||
|
||
/* ---- The docked month and year lists ------------------------------------------------------ */
|
||
|
||
[data-datepicker-menu] {
|
||
height: 21.5rem;
|
||
padding-block: 0.5rem;
|
||
overflow-y: auto;
|
||
overscroll-behavior: contain;
|
||
border-top: 1px solid var(--md-sys-color-outline-variant);
|
||
}
|
||
|
||
[data-datepicker-option] {
|
||
--datepicker-layer: var(--md-sys-color-on-surface);
|
||
|
||
position: relative;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 1rem;
|
||
width: 100%;
|
||
height: 3rem;
|
||
padding-inline: 1rem;
|
||
color: var(--md-sys-color-on-surface);
|
||
font: var(--md-sys-typescale-body-lg);
|
||
letter-spacing: var(--md-sys-typescale-body-lg-tracking);
|
||
text-align: start;
|
||
cursor: pointer;
|
||
outline: none;
|
||
isolation: isolate;
|
||
}
|
||
|
||
[data-datepicker-option][aria-selected="true"] {
|
||
background-color: var(--md-sys-color-surface-container-highest);
|
||
}
|
||
|
||
[data-datepicker-option][aria-disabled="true"] {
|
||
color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
|
||
cursor: default;
|
||
}
|
||
|
||
[data-datepicker-check] {
|
||
color: var(--md-sys-color-on-surface);
|
||
}
|
||
|
||
[data-datepicker-option]:not([aria-selected="true"]) [data-datepicker-check] {
|
||
visibility: hidden;
|
||
}
|
||
|
||
/* ---- The modal input ------------------------------------------------------------------------ */
|
||
|
||
[data-datepicker-entry] {
|
||
padding: 0.625rem 1.5rem 1rem;
|
||
}
|
||
|
||
[data-datepicker-entry-fields][data-range] {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
|
||
/* ---- Cancel and OK ---------------------------------------------------------------------------- */
|
||
|
||
[data-datepicker-actions] {
|
||
display: flex;
|
||
flex: none;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
justify-content: flex-end;
|
||
gap: 0.5rem;
|
||
padding: 0.25rem 0.375rem 0.5rem 0.75rem;
|
||
}
|
||
}
|