Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 158 additions & 28 deletions packages/components/src/Menu/Menu.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,38 @@
--menu-space: var(--space-small);
--menu-offset: var(--space-smallest);
z-index: var(--elevation-modal);
min-width: 160px;
box-shadow: var(--shadow-base);
box-sizing: border-box;
padding: var(--menu-space);
padding-bottom: calc(env(safe-area-inset-bottom) + var(--menu-space));
border-radius: var(--radius-base) var(--radius-base) 0 0;
border-radius: var(--radius-base);
outline: none;
overflow-y: scroll;
/* overflow-x: hidden prevents the horizontal scrollbar that appears when
overflow-y is non-visible (CSS spec forces overflow-x to auto as well) */
overflow-x: hidden;
overflow-y: auto;
background-color: var(--color-surface);
-webkit-overflow-scrolling: touch;

@media (--small-screens-and-below) {
/* Mobile sheet — activates at large-mobile and below (≤767px) */
@media (max-width: 767px) {
position: fixed;
right: 0;
bottom: 0;
left: 0;
max-height: 70vh;
max-height: 75vh;
min-width: unset;
padding: var(--space-base);
padding-bottom: calc(env(safe-area-inset-bottom) + var(--space-base));
border-radius: var(--radius-large) var(--radius-large) 0 0;
}

@media (--small-screens-and-up) {
width: calc(var(--base-unit) * 12.5);
padding: var(--menu-space);
/* Desktop panel — medium screens and up (≥768px) */
@media (--medium-screens-and-up) {
/* No fixed width — menu hugs its content; min-width: 160px provides the floor */
max-height: 100vh;
border: var(--border-base) solid var(--color-border);
border-radius: var(--radius-base);
overflow: auto;
}
}

Expand All @@ -61,7 +69,6 @@

.separator {
margin: var(--space-small) 0;
grid-column: 1 / -1;
}

.triggerWrapper {
Expand All @@ -70,49 +77,85 @@
}

.ariaMenu {
display: grid;
display: flex;
flex-direction: column;
max-height: inherit;
grid-template-columns: auto 1fr auto;
}

.ariaItem {
display: grid;
grid-template-columns: subgrid;
grid-column: 1 / -1;
display: flex;
align-items: center;
gap: var(--menu-space);
min-height: 40px;

@media (max-width: 767px) {
min-height: 48px;
}
}

/* Legacy icon slot — order: -1 ensures it renders before the label
even when placed after it in the DOM (backwards compat) */
.ariaItem > [data-menu-slot="icon"] {
grid-column-start: 1;
grid-row-start: 1;
order: -1;
flex-shrink: 0;
}

/* Prefix slot */
.ariaItem > [data-menu-slot="prefix"] {
flex-shrink: 0;
}

/* Label — grows to fill remaining space, pushing suffix/trailing right.
white-space: nowrap keeps text on a single line (menu width hugs content). */
.ariaItem > [data-menu-slot="label"] {
grid-column-start: 2;
flex: 1;
min-width: 0;
white-space: nowrap;
}

/* Consumer suffix slot */
.ariaItem > [data-menu-slot="suffix"] {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: flex-end;
}

/* Auto-trailing icon (checkmark / arrowRight) — always farthest right */
.ariaItem > [data-menu-slot="trailing"] {
flex-shrink: 0;
}

/* Submenu items use tighter right padding to visually hug the arrowRight */
.submenuItem {
padding-right: var(--space-smaller);
}

.ariaSection {
display: grid;
grid-template-columns: subgrid;
grid-column: 1 / -1;
grid-column-start: 1;
display: flex;
flex-direction: column;
}

.ariaMenu .ariaSection:not(:first-child) {
margin-top: var(--space-small);
}

/* When a section follows a separator, avoid doubling vertical spacing.
Grid items don't collapse margins, so the section's top margin would add
to the separator's bottom margin. Remove the section margin in this case. */
/* When a section follows a separator, avoid doubling vertical spacing */
.ariaMenu .separator + .ariaSection {
margin-top: 0;
}

.ariaSectionHeader {
grid-column: 1 / -1;
grid-column-start: 1;
width: 100%;
}

/* GroupLabel: no top margin, subdued appearance — visually distinct from section headers */
.groupLabelSection {
margin-top: 0;
}

.groupLabelSection:not(:first-child) {
margin-top: var(--space-small);
}

.sectionHeader {
Expand All @@ -121,6 +164,14 @@
padding: calc(var(--menu-space) * 1.5) var(--menu-space);
}

/* Must come after .sectionHeader so individual padding values override the shorthand */
.groupLabelHeader {
padding-top: var(--space-small);
padding-bottom: var(--space-smaller);
padding-left: var(--space-small);
padding-right: var(--space-small);
}

.legacyAction {
display: flex;
gap: var(--menu-space);
Expand Down Expand Up @@ -177,6 +228,17 @@
color: var(--color-destructive);
}

/* Destructive item hover / focus — critical surface background */
.action[data-variation="destructive"][data-hovered],
.action[data-variation="destructive"][data-focused] {
background-color: var(--color-critical--surface);
}

/* Destructive prefix — propagates color to SVG icons via currentColor */
.destructivePrefix {
color: var(--color-destructive);
}

.overlay {
position: fixed;
top: 0;
Expand All @@ -186,11 +248,79 @@
z-index: var(--elevation-modal);
background-color: var(--color-overlay);

/* Hide overlay on tablet and desktop — mobile only (≤489px) */
@media (--small-screens-and-up) {
background-color: transparent;
display: none;
}
}

/* ─── Mobile drill-down sheet (submenu on ≤767px) ────────────────────────── */

/* Dims the parent sheet while the drill-down is open */
.drillDownOverlay {
position: fixed;
inset: 0;
/* Sits above the main sheet (--elevation-modal) */
z-index: calc(var(--elevation-modal) + 1);
background-color: var(--color-overlay);
}

/* The drill-down panel itself */
.drillDownSheet {
position: fixed;
right: 0;
bottom: 0;
left: 0;
/* Above the drill-down overlay */
z-index: calc(var(--elevation-modal) + 2);
max-height: 75vh;
box-shadow: var(--shadow-base);
border-radius: var(--radius-large) var(--radius-large) 0 0;
overflow-y: auto;
background-color: var(--color-surface);
-webkit-overflow-scrolling: touch;
}

/* Back button row */
.drillDownHeader {
display: flex;
align-items: center;
gap: var(--space-small);
padding: var(--space-base) var(--space-base) var(--space-small);
}

.drillDownBackButton {
display: flex;
padding: var(--space-smaller);
border: none;
border-radius: var(--radius-base);
color: var(--color-text);
background-color: transparent;
cursor: pointer;
transition: background-color var(--timing-base) ease-out;
align-items: center;
justify-content: center;
flex-shrink: 0;

&:hover {
background-color: var(--color-surface--hover);
}

&:focus-visible {
box-shadow: var(--shadow-focus);
outline: none;
}
}

/* Items list area — matches mobile sheet padding.
Re-declare --menu-space here because the AriaMenu inside the sheet
does not have .menu as an ancestor, so the variable would otherwise be undefined. */
.drillDownItems {
--menu-space: var(--space-small);
padding: 0 var(--space-base);
padding-bottom: calc(env(safe-area-inset-bottom) + var(--space-base));
}

.fullWidth {
width: 100%;
}
Expand Down
9 changes: 9 additions & 0 deletions packages/components/src/Menu/Menu.module.css.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,24 @@ declare const styles: {
readonly "triggerWrapper": string;
readonly "ariaMenu": string;
readonly "ariaItem": string;
readonly "submenuItem": string;
readonly "ariaSection": string;
readonly "ariaSectionHeader": string;
readonly "sectionHeader": string;
readonly "legacyAction": string;
readonly "action": string;
readonly "destructive": string;
readonly "destructivePrefix": string;
readonly "overlay": string;
readonly "drillDownOverlay": string;
readonly "drillDownSheet": string;
readonly "drillDownHeader": string;
readonly "drillDownBackButton": string;
readonly "drillDownItems": string;
readonly "fullWidth": string;
readonly "screenReaderOnly": string;
readonly "groupLabelSection": string;
readonly "groupLabelHeader": string;
};
export = styles;

Loading
Loading