@jsekulowicz/ds-components 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12289,7 +12289,7 @@
12289
12289
  {
12290
12290
  "kind": "variable",
12291
12291
  "name": "dialogStyles",
12292
- "default": "css` /* Interpolatable colors so the scroll-driven keyframes can crossfade the gradient stops instead of jumping between values. */ @property --ds-dialog-body-top-fade { syntax: '<color>'; inherits: false; initial-value: rgb(0 0 0); } @property --ds-dialog-body-bottom-fade { syntax: '<color>'; inherits: false; initial-value: rgb(0 0 0); } :host { display: contents; } dialog { padding: 0; border: 0; background: transparent; color: inherit; width: 100%; max-height: min(90vh, 720px); border-radius: var(--ds-radius-sm); box-shadow: var(--ds-shadow-md); overflow: visible; } /* Scope flex-column to the open state so the UA's display:none keeps the closed dialog out of layout. Without this scope, our display:flex wins unconditionally and the closed dialog renders inline alongside its opener. The flex column itself is needed so ds-card resolves a definite height from the dialog's max-height cap (percentages only resolve against an explicit parent height, not max-height) — otherwise the body never gets a height to scroll against on short viewports. */ dialog[open] { display: flex; flex-direction: column; } :host([size='sm']) dialog { max-width: 400px; } :host([size='md']) dialog { max-width: 560px; } :host([size='lg']) dialog { max-width: 800px; } dialog::backdrop { background: rgb(15 23 42 / 0.55); } ds-card { flex: 1; min-height: 0; min-width: 0; } ds-card::part(card) { height: 100%; max-height: 100%; box-shadow: none; border-color: transparent; gap: var(--ds-space-3); } ds-card::part(body) { flex: 1; min-height: 0; overflow-x: clip; overflow-y: auto; /* Don't chain scroll up to the page when the body reaches its top/bottom boundary or has no overflow. The dialog/drawer is modal — the page behind it shouldn't twitch when the user wheel-scrolls inside the modal. */ overscroll-behavior: contain; padding-inline: var(--ds-space-2); margin-inline: calc(var(--ds-space-2) * -1); /* Hide the native scrollbar. Overflow is indicated by a fade mask at the top and bottom of the scrollport, driven by an actual scroll-progress timeline so the fades only appear when there's content to scroll into / out of view. No padding-block buffer is needed — the top fade stays opaque (no fade) at scroll-top and only switches to transparent (fade visible) once the user has scrolled even one pixel; mirror for the bottom. */ scrollbar-width: none; mask-image: linear-gradient( to bottom, var(--ds-dialog-body-top-fade, rgb(0 0 0)) 0, rgb(0 0 0) var(--ds-space-6), rgb(0 0 0) calc(100% - var(--ds-space-6)), var(--ds-dialog-body-bottom-fade, rgb(0 0 0)) 100% ); animation: ds-dialog-body-scroll-fade linear; animation-timeline: scroll(self); } ds-card::part(body)::-webkit-scrollbar { display: none; } @keyframes ds-dialog-body-scroll-fade { /* At scroll-top: keep the top stop opaque (no fade above), reveal the bottom fade so the user knows there's more below. The two near-instant transitions at 0.001% and 99.999% flip each stop on / off as soon as scroll actually progresses, so the fades are binary (present / absent), not interpolated as the user drags through the range. */ 0% { --ds-dialog-body-top-fade: rgb(0 0 0); --ds-dialog-body-bottom-fade: rgb(0 0 0 / 0); } 0.001%, 99.999% { --ds-dialog-body-top-fade: rgb(0 0 0 / 0); --ds-dialog-body-bottom-fade: rgb(0 0 0 / 0); } 100% { --ds-dialog-body-top-fade: rgb(0 0 0 / 0); --ds-dialog-body-bottom-fade: rgb(0 0 0); } } .title-row { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--ds-space-3); } .title-text { margin: 0; flex: 1; font-family: var(--ds-font-display); font-size: var(--ds-font-size-xl); font-weight: var(--ds-font-weight-semibold); letter-spacing: var(--ds-letter-spacing-display); } /* Slotted heading tags (h1-h6, etc.) carry UA defaults that compound on top of .title-text — a bigger font-size and large vertical margins. Normalise them so 'Foo', '<h2 slot=\"title\">Foo</h2>' and '<span slot=\"title\">Foo</span>' all render identically. */ .title-text ::slotted(*) { font: inherit; margin: 0; letter-spacing: inherit; } /* Pull the close button up and to the right so it sits near the card's top-right corner instead of indented by ds-card's full ds-space-6 padding. Its own visual chrome (size, hover, focus ring, square shape) comes from ds-button. */ .close-btn { margin-block-start: calc(var(--ds-space-3) * -1); margin-inline-end: calc(var(--ds-space-3) * -1); } .footer { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: var(--ds-space-2); } `"
12292
+ "default": "css` /* @property registration so the scroll-driven keyframes can interpolate these as colors. */ @property --ds-dialog-body-top-fade { syntax: '<color>'; inherits: false; initial-value: rgb(0 0 0); } @property --ds-dialog-body-bottom-fade { syntax: '<color>'; inherits: false; initial-value: rgb(0 0 0); } :host { display: contents; } dialog { padding: 0; border: 0; background: transparent; color: inherit; width: calc(100% - var(--ds-space-4)); max-height: min(90vh, 720px); border-radius: var(--ds-radius-sm); box-shadow: var(--ds-shadow-md); overflow: visible; } /* Scope to [open] so closed dialogs stay display:none per UA. */ dialog[open] { display: flex; flex-direction: column; } :host([size='sm']) dialog { max-width: 400px; } :host([size='md']) dialog { max-width: 560px; } :host([size='lg']) dialog { max-width: 800px; } dialog::backdrop { background: rgb(15 23 42 / 0.55); } ds-card { flex: 1; min-height: 0; min-width: 0; } ds-card::part(card) { /* Match the dialog's cap explicitly; percentage heights don't resolve reliably through ds-card's display:block host, so body scroll breaks when content overflows. */ max-height: min(90vh, 720px); box-shadow: none; border-color: transparent; gap: var(--ds-space-3); } ds-card::part(body) { flex: 1; min-height: 0; overflow-x: clip; overflow-y: auto; overscroll-behavior: contain; /* Inline padding + negative margin lets focus rings on full-width children paint outside the body's clip box. */ padding-inline: var(--ds-space-2); margin-inline: calc(var(--ds-space-2) * -1); /* Scrollbar hidden; overflow is signalled by the scroll-driven fade mask defined in the keyframes below. */ scrollbar-width: none; mask-image: linear-gradient( to bottom, var(--ds-dialog-body-top-fade, rgb(0 0 0)) 0, rgb(0 0 0) var(--ds-space-6), rgb(0 0 0) calc(100% - var(--ds-space-6)), var(--ds-dialog-body-bottom-fade, rgb(0 0 0)) 100% ); animation: ds-dialog-body-scroll-fade linear; animation-timeline: scroll(self); } ds-card::part(body)::-webkit-scrollbar { display: none; } /* Top fade hidden at scroll-top, bottom fade hidden at scroll-end. Near-instant flips at 0.001% / 99.999% keep the transitions binary instead of interpolating across the scroll range. */ @keyframes ds-dialog-body-scroll-fade { 0% { --ds-dialog-body-top-fade: rgb(0 0 0); --ds-dialog-body-bottom-fade: rgb(0 0 0 / 0); } 0.001%, 99.999% { --ds-dialog-body-top-fade: rgb(0 0 0 / 0); --ds-dialog-body-bottom-fade: rgb(0 0 0 / 0); } 100% { --ds-dialog-body-top-fade: rgb(0 0 0 / 0); --ds-dialog-body-bottom-fade: rgb(0 0 0); } } .title-row { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--ds-space-3); } .title-text { margin: 0; flex: 1; font-family: var(--ds-font-display); font-size: var(--ds-font-size-xl); font-weight: var(--ds-font-weight-semibold); letter-spacing: var(--ds-letter-spacing-display); } /* Normalise slotted headings (h1-h6) so UA defaults don't compound with .title-text styles. */ .title-text ::slotted(*) { font: inherit; margin: 0; letter-spacing: inherit; } /* Pull the close button toward the card's top-right corner; the card's own padding would otherwise indent it. */ .close-btn { margin-block-start: calc(var(--ds-space-3) * -1); margin-inline-end: calc(var(--ds-space-3) * -1); } .footer { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: var(--ds-space-2); } `"
12293
12293
  }
12294
12294
  ],
12295
12295
  "exports": [
@@ -12552,7 +12552,7 @@
12552
12552
  {
12553
12553
  "kind": "variable",
12554
12554
  "name": "drawerStyles",
12555
- "default": "css` /* Interpolatable colors so the scroll-driven keyframes can crossfade the gradient stops instead of jumping between values. */ @property --ds-drawer-body-top-fade { syntax: '<color>'; inherits: false; initial-value: rgb(0 0 0); } @property --ds-drawer-body-bottom-fade { syntax: '<color>'; inherits: false; initial-value: rgb(0 0 0); } :host { display: contents; } dialog { padding: 0; border: 0; background: transparent; color: inherit; box-shadow: var(--ds-shadow-lg); overflow: visible; height: 100vh; height: 100dvh; max-height: 100%; /* Slide in on open, slide out on close. allow-discrete lets the display/overlay properties (which can't normally transition) hold their open values for the duration. */ transition: transform var(--ds-duration-slow) var(--ds-easing-standard), display var(--ds-duration-slow) allow-discrete, overlay var(--ds-duration-slow) allow-discrete; } /* Scope flex-column to the open state so the UA's display:none keeps the closed dialog out of layout. Same fix and rationale as ds-dialog — the flex column is needed for the body's height cap to propagate, but unscoped it makes the closed drawer render inline alongside its opener. */ dialog[open] { display: flex; flex-direction: column; } :host([side='start']) dialog { margin: 0; margin-inline-end: auto; transform: translateX(-100%); } :host([side='end']) dialog { margin: 0; margin-inline-start: auto; transform: translateX(100%); } :host([side='start']) dialog[open], :host([side='end']) dialog[open] { transform: translateX(0); } @starting-style { :host([side='start']) dialog[open] { transform: translateX(-100%); } :host([side='end']) dialog[open] { transform: translateX(100%); } } :host([size='sm']) dialog { width: min(20rem, 90vw); } :host([size='md']) dialog { width: min(24rem, 90vw); } :host([size='lg']) dialog { width: min(28rem, 90vw); } dialog::backdrop { background: rgb(15 23 42 / 0.55); } ds-card { flex: 1; min-height: 0; min-width: 0; } ds-card::part(card) { height: 100%; max-height: 100%; box-shadow: none; border-color: transparent; border-radius: 0; gap: var(--ds-space-3); } ds-card::part(body) { flex: 1; min-height: 0; overflow-x: clip; overflow-y: auto; /* Don't chain scroll up to the page when the body reaches its top/bottom boundary or has no overflow. Same rationale as ds-dialog — modal surfaces shouldn't let scroll leak through. */ overscroll-behavior: contain; padding-inline: var(--ds-space-2); margin-inline: calc(var(--ds-space-2) * -1); /* Same scroll-driven fade trick as ds-dialog — see comments there. No padding-block buffer; the fades only kick in once there's actual scroll progress to drive them. */ scrollbar-width: none; mask-image: linear-gradient( to bottom, var(--ds-drawer-body-top-fade, rgb(0 0 0)) 0, rgb(0 0 0) var(--ds-space-6), rgb(0 0 0) calc(100% - var(--ds-space-6)), var(--ds-drawer-body-bottom-fade, rgb(0 0 0)) 100% ); animation: ds-drawer-body-scroll-fade linear; animation-timeline: scroll(self); } ds-card::part(body)::-webkit-scrollbar { display: none; } @keyframes ds-drawer-body-scroll-fade { 0% { --ds-drawer-body-top-fade: rgb(0 0 0); --ds-drawer-body-bottom-fade: rgb(0 0 0 / 0); } 0.001%, 99.999% { --ds-drawer-body-top-fade: rgb(0 0 0 / 0); --ds-drawer-body-bottom-fade: rgb(0 0 0 / 0); } 100% { --ds-drawer-body-top-fade: rgb(0 0 0 / 0); --ds-drawer-body-bottom-fade: rgb(0 0 0); } } .title-row { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--ds-space-3); } .title-text { margin: 0; flex: 1; font-family: var(--ds-font-display); font-size: var(--ds-font-size-xl); font-weight: var(--ds-font-weight-semibold); letter-spacing: var(--ds-letter-spacing-display); } .title-text ::slotted(*) { font: inherit; margin: 0; letter-spacing: inherit; } .close-btn { margin-block-start: calc(var(--ds-space-3) * -1); margin-inline-end: calc(var(--ds-space-3) * -1); } .footer { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: var(--ds-space-2); } `"
12555
+ "default": "css` /* @property registration so the scroll-driven keyframes can interpolate these as colors. */ @property --ds-drawer-body-top-fade { syntax: '<color>'; inherits: false; initial-value: rgb(0 0 0); } @property --ds-drawer-body-bottom-fade { syntax: '<color>'; inherits: false; initial-value: rgb(0 0 0); } :host { display: contents; } dialog { padding: 0; border: 0; background: transparent; color: inherit; box-shadow: var(--ds-shadow-lg); overflow: visible; height: 100vh; height: 100dvh; max-height: 100%; /* allow-discrete lets display/overlay hold their open values for the slide-in / slide-out duration. */ transition: transform var(--ds-duration-slow) var(--ds-easing-standard), display var(--ds-duration-slow) allow-discrete, overlay var(--ds-duration-slow) allow-discrete; } /* Scope to [open] so closed dialogs stay display:none per UA. */ dialog[open] { display: flex; flex-direction: column; } :host([side='start']) dialog { margin: 0; margin-inline-end: auto; transform: translateX(-100%); } :host([side='end']) dialog { margin: 0; margin-inline-start: auto; transform: translateX(100%); } :host([side='start']) dialog[open], :host([side='end']) dialog[open] { transform: translateX(0); } @starting-style { :host([side='start']) dialog[open] { transform: translateX(-100%); } :host([side='end']) dialog[open] { transform: translateX(100%); } } :host([size='sm']) dialog { width: min(20rem, 90vw); } :host([size='md']) dialog { width: min(24rem, 90vw); } :host([size='lg']) dialog { width: min(28rem, 90vw); } dialog::backdrop { background: rgb(15 23 42 / 0.55); } ds-card { flex: 1; min-height: 0; min-width: 0; } ds-card::part(card) { /* Explicit cap; percentage heights don't resolve reliably through ds-card's display:block host. */ max-height: 100vh; max-height: 100dvh; box-shadow: none; border-color: transparent; border-radius: 0; gap: var(--ds-space-3); padding: var(--ds-drawer-card-padding, var(--ds-space-6)); } ds-card::part(body) { flex: 1; min-height: 0; overflow-x: clip; overflow-y: auto; overscroll-behavior: contain; /* Inline padding + negative margin lets focus rings on full-width children paint outside the body's clip box. */ padding-inline: var(--ds-space-2); margin-inline: calc(var(--ds-space-2) * -1); scrollbar-width: none; mask-image: linear-gradient( to bottom, var(--ds-drawer-body-top-fade, rgb(0 0 0)) 0, rgb(0 0 0) var(--ds-space-6), rgb(0 0 0) calc(100% - var(--ds-space-6)), var(--ds-drawer-body-bottom-fade, rgb(0 0 0)) 100% ); animation: ds-drawer-body-scroll-fade linear; animation-timeline: scroll(self); } ds-card::part(body)::-webkit-scrollbar { display: none; } @keyframes ds-drawer-body-scroll-fade { 0% { --ds-drawer-body-top-fade: rgb(0 0 0); --ds-drawer-body-bottom-fade: rgb(0 0 0 / 0); } 0.001%, 99.999% { --ds-drawer-body-top-fade: rgb(0 0 0 / 0); --ds-drawer-body-bottom-fade: rgb(0 0 0 / 0); } 100% { --ds-drawer-body-top-fade: rgb(0 0 0 / 0); --ds-drawer-body-bottom-fade: rgb(0 0 0); } } .title-row { display: flex; align-items: center; justify-content: space-between; gap: var(--ds-space-3); background: var(--ds-drawer-title-bg, transparent); color: var(--ds-drawer-title-fg, inherit); border-bottom: 1px solid var(--ds-drawer-title-border-color, transparent); padding: var(--ds-drawer-title-padding, 0); } .title-text { margin: 0; flex: 1; font-family: var(--ds-font-display); font-size: var(--ds-font-size-xl); font-weight: var(--ds-font-weight-semibold); letter-spacing: var(--ds-letter-spacing-display); } .title-text ::slotted(*) { font: inherit; margin: 0; letter-spacing: inherit; } .close-btn { margin-block-start: calc(var(--ds-space-3) * -1); margin-inline-end: calc(var(--ds-space-3) * -1); } .close-btn::part(button) { color: var(--ds-drawer-title-fg, inherit); } .footer { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: var(--ds-space-2); } `"
12556
12556
  }
12557
12557
  ],
12558
12558
  "exports": [
@@ -14751,7 +14751,7 @@
14751
14751
  {
14752
14752
  "kind": "variable",
14753
14753
  "name": "pageShellStyles",
14754
- "default": "css` :host { --ds-page-shell-max-width: none; display: flex; flex-direction: column; position: relative; height: 100vh; height: 100dvh; overflow-x: clip; background: var(--ds-color-bg); color: var(--ds-color-fg); font-family: var(--ds-font-body); } header { position: sticky; top: 0; background: color-mix(in oklab, var(--ds-color-bg) 92%, transparent); backdrop-filter: blur(12px); z-index: var(--ds-z-index-sticky); } /* The header composes ds-top-bar; let the top-bar own height, padding, border-bottom, and layout. We just (a) make its background transparent so the sticky header's blurred bg shows through, and (b) constrain its inner brand + actions content to the same max-width as the shell-body below so the bar's brand left-aligns with the aside, and its actions right-align with the aside-end (or the right edge of main when no aside-end is slotted). */ .chrome { --ds-top-bar-bg: transparent; --ds-top-bar-content-max-width: var(--ds-page-shell-max-width); } /* Same treatment for a slotted ds-footer: cap its inner content to the shell-body's max-width so footer content aligns with the column above. Consumers who slot a non-ds-footer custom element can override the property themselves. */ ::slotted(ds-footer) { --ds-footer-content-max-width: var(--ds-page-shell-max-width); } footer { display: block; } .shell-body { flex: 1; width: 100%; max-width: var(--ds-page-shell-max-width); margin-inline: auto; display: grid; grid-template-columns: auto 1fr auto; min-height: 0; } :host([aside-empty]) .shell-body { grid-template-columns: 1fr auto; } :host([aside-end-empty]) .shell-body { grid-template-columns: auto 1fr; } :host([aside-empty][aside-end-empty]) .shell-body { grid-template-columns: 1fr; } aside { display: flex; overflow-x: clip; overflow-y: auto; overflow-clip-margin-inline: var(--ds-space-2); min-height: 0; scrollbar-color: var(--ds-color-fg-subtle) transparent; scrollbar-width: thin; /* No scrollbar-gutter reservation: the aside sits flush with its column edge so the consumer's <main> solely owns the gap. The scrollbar appears on demand when the aside genuinely overflows. */ } :host([aside-empty]) aside[part=\"aside\"], :host([aside-empty]) ds-drawer[part=\"aside\"] { display: none; } :host([aside-end-empty]) aside[part=\"aside-end\"] { display: none; } main { padding: var(--ds-space-5); overflow-x: clip; overflow-y: auto; overflow-clip-margin-inline: var(--ds-space-2); min-width: 0; min-height: 0; scrollbar-color: var(--ds-color-fg-subtle) transparent; scrollbar-width: thin; /* Reserve scrollbar gutters on both inline sides so the inline-start and inline-end visible empty bands stay equal in width whether the vertical scrollbar is present or not. */ scrollbar-gutter: stable both-edges; } @media (max-width: ${belowDesktopBreakpoint}) { main { padding-block: var(--ds-space-4); padding-inline: var(--ds-space-4); } } .menu-toggle { display: none; } .menu-toggle::part(button) { min-width: var(--ds-size-sm); width: var(--ds-size-sm); padding: 0; } :host([mobile-layout]) .menu-toggle { display: inline-flex; } /* In mobile layout the inline-start column collapses — the drawer overlays in the top layer (via ds-drawer's native <dialog>) so it doesn't take grid space — and the inline-end column hides since v1 doesn't surface it in the drawer. */ :host([mobile-layout]) .shell-body { grid-template-columns: 1fr; } :host([mobile-layout]) aside[part=\"aside-end\"] { display: none; } :host([mobile-layout]) ds-drawer[part=\"aside\"] { /* ds-drawer is a top-layer modal; remove it from the grid so it doesn't reserve a column when closed. */ display: contents; } :host([mobile-layout]) ds-drawer[part=\"aside\"] ::slotted(ds-sidenav) { width: 100% !important; max-width: 100% !important; flex: 1 1 auto; min-width: 0; min-height: 0; height: auto !important; } `"
14754
+ "default": "css` :host { --ds-page-shell-max-width: none; display: flex; flex-direction: column; position: relative; height: 100vh; height: 100dvh; overflow-x: clip; background: var(--ds-color-bg); color: var(--ds-color-fg); font-family: var(--ds-font-body); } header { position: sticky; top: 0; background: color-mix(in oklab, var(--ds-color-bg) 92%, transparent); backdrop-filter: blur(12px); z-index: var(--ds-z-index-sticky); } /* The header composes ds-top-bar; let the top-bar own height, padding, border-bottom, and layout. We just (a) make its background transparent so the sticky header's blurred bg shows through, and (b) constrain its inner brand + actions content to the same max-width as the shell-body below so the bar's brand left-aligns with the aside, and its actions right-align with the aside-end (or the right edge of main when no aside-end is slotted). */ .chrome { --ds-top-bar-bg: transparent; --ds-top-bar-content-max-width: var(--ds-page-shell-max-width); } /* Same treatment for a slotted ds-footer: cap its inner content to the shell-body's max-width so footer content aligns with the column above. Consumers who slot a non-ds-footer custom element can override the property themselves. */ ::slotted(ds-footer) { --ds-footer-content-max-width: var(--ds-page-shell-max-width); } footer { display: block; } .shell-body { flex: 1; width: 100%; max-width: var(--ds-page-shell-max-width); margin-inline: auto; display: grid; grid-template-columns: auto 1fr auto; min-height: 0; } :host([aside-empty]) .shell-body { grid-template-columns: 1fr auto; } :host([aside-end-empty]) .shell-body { grid-template-columns: auto 1fr; } :host([aside-empty][aside-end-empty]) .shell-body { grid-template-columns: 1fr; } aside { display: flex; overflow-x: clip; overflow-y: auto; overflow-clip-margin-inline: var(--ds-space-2); min-height: 0; scrollbar-color: var(--ds-color-fg-subtle) transparent; scrollbar-width: thin; /* No scrollbar-gutter reservation: the aside sits flush with its column edge so the consumer's <main> solely owns the gap. The scrollbar appears on demand when the aside genuinely overflows. */ } :host([aside-empty]) aside[part=\"aside\"], :host([aside-empty]) ds-drawer[part=\"aside\"] { display: none; } :host([aside-end-empty]) aside[part=\"aside-end\"] { display: none; } main { padding: var(--ds-space-5); overflow-x: clip; overflow-y: auto; overflow-clip-margin-inline: var(--ds-space-2); min-width: 0; min-height: 0; scrollbar-color: var(--ds-color-fg-subtle) transparent; scrollbar-width: thin; /* Reserve scrollbar gutters on both inline sides so the inline-start and inline-end visible empty bands stay equal in width whether the vertical scrollbar is present or not. */ scrollbar-gutter: stable both-edges; } @media (max-width: ${belowDesktopBreakpoint}) { main { padding-block: var(--ds-space-4); padding-inline: var(--ds-space-4); } } .menu-toggle { display: none; } .menu-toggle::part(button) { min-width: var(--ds-size-sm); width: var(--ds-size-sm); padding: 0; } :host([mobile-layout]) .menu-toggle { display: inline-flex; } :host([mobile-layout]) .shell-body { grid-template-columns: 1fr; } :host([mobile-layout]) aside[part=\"aside-end\"] { display: none; } :host([mobile-layout]) ds-drawer[part=\"aside\"] { /* Top-layer modal; don't reserve a grid column when closed. */ display: contents; --ds-drawer-card-padding: 0; --ds-drawer-title-padding: var(--ds-space-3) var(--ds-space-4); --ds-drawer-title-bg: var(--ds-page-shell-drawer-header-bg, transparent); --ds-drawer-title-fg: var(--ds-page-shell-drawer-header-fg, inherit); --ds-drawer-title-border-color: var(--ds-page-shell-drawer-header-border-color, transparent); } :host([mobile-layout]) ds-drawer[part=\"aside\"] ::slotted(ds-sidenav) { width: 100% !important; max-width: 100% !important; flex: 1 1 auto; min-width: 0; min-height: 0; height: auto !important; } `"
14755
14755
  }
14756
14756
  ],
14757
14757
  "exports": [
@@ -1 +1 @@
1
- {"version":3,"file":"dialog.styles.d.ts","sourceRoot":"","sources":["../../../src/molecules/dialog/dialog.styles.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,YAAY,yBAyJxB,CAAC"}
1
+ {"version":3,"file":"dialog.styles.d.ts","sourceRoot":"","sources":["../../../src/molecules/dialog/dialog.styles.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,YAAY,yBAsIxB,CAAC"}
@@ -1,7 +1,7 @@
1
1
  import { css } from 'lit';
2
2
  export const dialogStyles = css `
3
- /* Interpolatable colors so the scroll-driven keyframes can crossfade
4
- the gradient stops instead of jumping between values. */
3
+ /* @property registration so the scroll-driven keyframes can
4
+ interpolate these as colors. */
5
5
  @property --ds-dialog-body-top-fade {
6
6
  syntax: '<color>';
7
7
  inherits: false;
@@ -20,20 +20,13 @@ export const dialogStyles = css `
20
20
  border: 0;
21
21
  background: transparent;
22
22
  color: inherit;
23
- width: 100%;
23
+ width: calc(100% - var(--ds-space-4));
24
24
  max-height: min(90vh, 720px);
25
25
  border-radius: var(--ds-radius-sm);
26
26
  box-shadow: var(--ds-shadow-md);
27
27
  overflow: visible;
28
28
  }
29
- /* Scope flex-column to the open state so the UA's display:none
30
- keeps the closed dialog out of layout. Without this scope, our
31
- display:flex wins unconditionally and the closed dialog renders
32
- inline alongside its opener. The flex column itself is needed so
33
- ds-card resolves a definite height from the dialog's max-height
34
- cap (percentages only resolve against an explicit parent height,
35
- not max-height) — otherwise the body never gets a height to
36
- scroll against on short viewports. */
29
+ /* Scope to [open] so closed dialogs stay display:none per UA. */
37
30
  dialog[open] {
38
31
  display: flex;
39
32
  flex-direction: column;
@@ -56,8 +49,10 @@ export const dialogStyles = css `
56
49
  min-width: 0;
57
50
  }
58
51
  ds-card::part(card) {
59
- height: 100%;
60
- max-height: 100%;
52
+ /* Match the dialog's cap explicitly; percentage heights don't
53
+ resolve reliably through ds-card's display:block host, so body
54
+ scroll breaks when content overflows. */
55
+ max-height: min(90vh, 720px);
61
56
  box-shadow: none;
62
57
  border-color: transparent;
63
58
  gap: var(--ds-space-3);
@@ -67,20 +62,13 @@ export const dialogStyles = css `
67
62
  min-height: 0;
68
63
  overflow-x: clip;
69
64
  overflow-y: auto;
70
- /* Don't chain scroll up to the page when the body reaches its
71
- top/bottom boundary or has no overflow. The dialog/drawer is
72
- modal — the page behind it shouldn't twitch when the user
73
- wheel-scrolls inside the modal. */
74
65
  overscroll-behavior: contain;
66
+ /* Inline padding + negative margin lets focus rings on full-width
67
+ children paint outside the body's clip box. */
75
68
  padding-inline: var(--ds-space-2);
76
69
  margin-inline: calc(var(--ds-space-2) * -1);
77
- /* Hide the native scrollbar. Overflow is indicated by a fade mask
78
- at the top and bottom of the scrollport, driven by an actual
79
- scroll-progress timeline so the fades only appear when there's
80
- content to scroll into / out of view. No padding-block buffer
81
- is needed — the top fade stays opaque (no fade) at scroll-top
82
- and only switches to transparent (fade visible) once the user
83
- has scrolled even one pixel; mirror for the bottom. */
70
+ /* Scrollbar hidden; overflow is signalled by the scroll-driven
71
+ fade mask defined in the keyframes below. */
84
72
  scrollbar-width: none;
85
73
  mask-image: linear-gradient(
86
74
  to bottom,
@@ -95,13 +83,10 @@ export const dialogStyles = css `
95
83
  ds-card::part(body)::-webkit-scrollbar {
96
84
  display: none;
97
85
  }
86
+ /* Top fade hidden at scroll-top, bottom fade hidden at scroll-end.
87
+ Near-instant flips at 0.001% / 99.999% keep the transitions
88
+ binary instead of interpolating across the scroll range. */
98
89
  @keyframes ds-dialog-body-scroll-fade {
99
- /* At scroll-top: keep the top stop opaque (no fade above), reveal
100
- the bottom fade so the user knows there's more below. The two
101
- near-instant transitions at 0.001% and 99.999% flip each stop
102
- on / off as soon as scroll actually progresses, so the fades
103
- are binary (present / absent), not interpolated as the user
104
- drags through the range. */
105
90
  0% {
106
91
  --ds-dialog-body-top-fade: rgb(0 0 0);
107
92
  --ds-dialog-body-bottom-fade: rgb(0 0 0 / 0);
@@ -129,19 +114,15 @@ export const dialogStyles = css `
129
114
  font-weight: var(--ds-font-weight-semibold);
130
115
  letter-spacing: var(--ds-letter-spacing-display);
131
116
  }
132
- /* Slotted heading tags (h1-h6, etc.) carry UA defaults that
133
- compound on top of .title-text a bigger font-size and large
134
- vertical margins. Normalise them so 'Foo', '<h2 slot="title">Foo</h2>'
135
- and '<span slot="title">Foo</span>' all render identically. */
117
+ /* Normalise slotted headings (h1-h6) so UA defaults don't compound
118
+ with .title-text styles. */
136
119
  .title-text ::slotted(*) {
137
120
  font: inherit;
138
121
  margin: 0;
139
122
  letter-spacing: inherit;
140
123
  }
141
- /* Pull the close button up and to the right so it sits near the
142
- card's top-right corner instead of indented by ds-card's full
143
- ds-space-6 padding. Its own visual chrome (size, hover, focus
144
- ring, square shape) comes from ds-button. */
124
+ /* Pull the close button toward the card's top-right corner; the
125
+ card's own padding would otherwise indent it. */
145
126
  .close-btn {
146
127
  margin-block-start: calc(var(--ds-space-3) * -1);
147
128
  margin-inline-end: calc(var(--ds-space-3) * -1);
@@ -1 +1 @@
1
- {"version":3,"file":"dialog.styles.js","sourceRoot":"","sources":["../../../src/molecules/dialog/dialog.styles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAE1B,MAAM,CAAC,MAAM,YAAY,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyJ9B,CAAC"}
1
+ {"version":3,"file":"dialog.styles.js","sourceRoot":"","sources":["../../../src/molecules/dialog/dialog.styles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAE1B,MAAM,CAAC,MAAM,YAAY,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsI9B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"drawer.styles.d.ts","sourceRoot":"","sources":["../../../src/molecules/drawer/drawer.styles.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,YAAY,yBAiKxB,CAAC"}
1
+ {"version":3,"file":"drawer.styles.d.ts","sourceRoot":"","sources":["../../../src/molecules/drawer/drawer.styles.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,YAAY,yBAkKxB,CAAC"}
@@ -1,7 +1,7 @@
1
1
  import { css } from 'lit';
2
2
  export const drawerStyles = css `
3
- /* Interpolatable colors so the scroll-driven keyframes can crossfade
4
- the gradient stops instead of jumping between values. */
3
+ /* @property registration so the scroll-driven keyframes can
4
+ interpolate these as colors. */
5
5
  @property --ds-drawer-body-top-fade {
6
6
  syntax: '<color>';
7
7
  inherits: false;
@@ -25,19 +25,14 @@ export const drawerStyles = css `
25
25
  height: 100vh;
26
26
  height: 100dvh;
27
27
  max-height: 100%;
28
- /* Slide in on open, slide out on close. allow-discrete lets the
29
- display/overlay properties (which can't normally transition)
30
- hold their open values for the duration. */
28
+ /* allow-discrete lets display/overlay hold their open values for
29
+ the slide-in / slide-out duration. */
31
30
  transition:
32
31
  transform var(--ds-duration-slow) var(--ds-easing-standard),
33
32
  display var(--ds-duration-slow) allow-discrete,
34
33
  overlay var(--ds-duration-slow) allow-discrete;
35
34
  }
36
- /* Scope flex-column to the open state so the UA's display:none
37
- keeps the closed dialog out of layout. Same fix and rationale as
38
- ds-dialog — the flex column is needed for the body's height cap to
39
- propagate, but unscoped it makes the closed drawer render inline
40
- alongside its opener. */
35
+ /* Scope to [open] so closed dialogs stay display:none per UA. */
41
36
  dialog[open] {
42
37
  display: flex;
43
38
  flex-direction: column;
@@ -82,27 +77,26 @@ export const drawerStyles = css `
82
77
  min-width: 0;
83
78
  }
84
79
  ds-card::part(card) {
85
- height: 100%;
86
- max-height: 100%;
80
+ /* Explicit cap; percentage heights don't resolve reliably through
81
+ ds-card's display:block host. */
82
+ max-height: 100vh;
83
+ max-height: 100dvh;
87
84
  box-shadow: none;
88
85
  border-color: transparent;
89
86
  border-radius: 0;
90
87
  gap: var(--ds-space-3);
88
+ padding: var(--ds-drawer-card-padding, var(--ds-space-6));
91
89
  }
92
90
  ds-card::part(body) {
93
91
  flex: 1;
94
92
  min-height: 0;
95
93
  overflow-x: clip;
96
94
  overflow-y: auto;
97
- /* Don't chain scroll up to the page when the body reaches its
98
- top/bottom boundary or has no overflow. Same rationale as
99
- ds-dialog — modal surfaces shouldn't let scroll leak through. */
100
95
  overscroll-behavior: contain;
96
+ /* Inline padding + negative margin lets focus rings on full-width
97
+ children paint outside the body's clip box. */
101
98
  padding-inline: var(--ds-space-2);
102
99
  margin-inline: calc(var(--ds-space-2) * -1);
103
- /* Same scroll-driven fade trick as ds-dialog — see comments
104
- there. No padding-block buffer; the fades only kick in once
105
- there's actual scroll progress to drive them. */
106
100
  scrollbar-width: none;
107
101
  mask-image: linear-gradient(
108
102
  to bottom,
@@ -133,9 +127,13 @@ export const drawerStyles = css `
133
127
  }
134
128
  .title-row {
135
129
  display: flex;
136
- align-items: flex-start;
130
+ align-items: center;
137
131
  justify-content: space-between;
138
132
  gap: var(--ds-space-3);
133
+ background: var(--ds-drawer-title-bg, transparent);
134
+ color: var(--ds-drawer-title-fg, inherit);
135
+ border-bottom: 1px solid var(--ds-drawer-title-border-color, transparent);
136
+ padding: var(--ds-drawer-title-padding, 0);
139
137
  }
140
138
  .title-text {
141
139
  margin: 0;
@@ -154,6 +152,9 @@ export const drawerStyles = css `
154
152
  margin-block-start: calc(var(--ds-space-3) * -1);
155
153
  margin-inline-end: calc(var(--ds-space-3) * -1);
156
154
  }
155
+ .close-btn::part(button) {
156
+ color: var(--ds-drawer-title-fg, inherit);
157
+ }
157
158
  .footer {
158
159
  display: flex;
159
160
  flex-wrap: wrap;
@@ -1 +1 @@
1
- {"version":3,"file":"drawer.styles.js","sourceRoot":"","sources":["../../../src/molecules/drawer/drawer.styles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAE1B,MAAM,CAAC,MAAM,YAAY,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiK9B,CAAC"}
1
+ {"version":3,"file":"drawer.styles.js","sourceRoot":"","sources":["../../../src/molecules/drawer/drawer.styles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAE1B,MAAM,CAAC,MAAM,YAAY,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkK9B,CAAC"}
@@ -126,10 +126,6 @@ export const pageShellStyles = css `
126
126
  display: inline-flex;
127
127
  }
128
128
 
129
- /* In mobile layout the inline-start column collapses — the drawer
130
- overlays in the top layer (via ds-drawer's native <dialog>) so it
131
- doesn't take grid space — and the inline-end column hides since
132
- v1 doesn't surface it in the drawer. */
133
129
  :host([mobile-layout]) .shell-body {
134
130
  grid-template-columns: 1fr;
135
131
  }
@@ -137,9 +133,13 @@ export const pageShellStyles = css `
137
133
  display: none;
138
134
  }
139
135
  :host([mobile-layout]) ds-drawer[part="aside"] {
140
- /* ds-drawer is a top-layer modal; remove it from the grid so it
141
- doesn't reserve a column when closed. */
136
+ /* Top-layer modal; don't reserve a grid column when closed. */
142
137
  display: contents;
138
+ --ds-drawer-card-padding: 0;
139
+ --ds-drawer-title-padding: var(--ds-space-3) var(--ds-space-4);
140
+ --ds-drawer-title-bg: var(--ds-page-shell-drawer-header-bg, transparent);
141
+ --ds-drawer-title-fg: var(--ds-page-shell-drawer-header-fg, inherit);
142
+ --ds-drawer-title-border-color: var(--ds-page-shell-drawer-header-border-color, transparent);
143
143
  }
144
144
  :host([mobile-layout]) ds-drawer[part="aside"] ::slotted(ds-sidenav) {
145
145
  width: 100% !important;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsekulowicz/ds-components",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "Lit web components for the Design System Library (atoms, molecules, organisms, templates, pages).",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -321,8 +321,8 @@
321
321
  ],
322
322
  "dependencies": {
323
323
  "lit": "^3.2.1",
324
- "@jsekulowicz/ds-tokens": "0.5.0",
325
- "@jsekulowicz/ds-core": "0.5.0"
324
+ "@jsekulowicz/ds-core": "0.5.0",
325
+ "@jsekulowicz/ds-tokens": "0.5.0"
326
326
  },
327
327
  "devDependencies": {
328
328
  "heroicons": "2.2.0",