Skip to content
Merged
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
16 changes: 13 additions & 3 deletions examples/ui-showcase/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -653,13 +653,19 @@ const tableOfContentsItems = [
<h2>Admin layout</h2>
<p class="ui-showcase__note">
Regions plus a collapsible sidebar. State lives on the root as <code>data-cm-*</code>, so the Razor adapter and
the runtime controller read the same thing Vue binds.
the runtime controller read the same thing Vue binds. A collapsed sidebar still opens on hover or keyboard
focus, without leaving the collapsed state.
</p>
<div class="ui-showcase__frame">
<CmAdminLayout id="showcase-admin-layout">
<template #brand>CodeMonster</template>
<template #aside>Navigation</template>
<template #header>Workspace</template>
<template #header>
<button type="button" class="cm-button cm-button--ghost cm-button--sm" data-cm-sidebar-toggle>
Collapse
</button>
Workspace
</template>
Layout content
</CmAdminLayout>
</div>
Expand All @@ -679,9 +685,13 @@ const tableOfContentsItems = [

<section id="demo-setup-layout" class="ui-showcase__demo">
<h2>Setup layout</h2>
<p class="ui-showcase__note">A single-task page for onboarding steps.</p>
<p class="ui-showcase__note">
A single-task page for onboarding steps. Given an aside, it sits beside the form rather than above it once
there's room -- <code>aside-position</code> picks which side.
</p>
<div class="ui-showcase__frame">
<CmSetupLayout title="Create your workspace" description="This takes a minute.">
<template #aside>Invite your team once you're set up -- they'll see this workspace immediately.</template>
Workspace name
<template #actions>Continue</template>
</CmSetupLayout>
Expand Down
13 changes: 12 additions & 1 deletion examples/ui-showcase/src/showcase.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@
system, so the fallback colors (#fff/#111) always won regardless of theme. */

.ui-showcase {
/* Wide enough that the layout demos (CmAdminLayout, CmAppShell, ...) clear their own 64rem
container-query breakpoint and render their desktop form -- narrower than that and their
container queries correctly, not incidentally, switch them to the mobile drawer they'd also
show in a real narrow window. */
margin: 0 auto;
max-width: 60rem;
max-width: 76rem;
padding: 2rem 1.5rem 4rem;
}

Expand Down Expand Up @@ -45,6 +49,13 @@
resize: vertical;
border: 1px solid var(--cm-color-border-subtle, #e5e5e5);
border-radius: var(--cm-radius-surface, 8px);

/* Some layouts (CmAdminLayout, CmAdminShell, ...) fix their sidebar/header to the viewport, which
is correct once they're a real page -- but "viewport" for position: fixed is the nearest
ancestor that establishes a containing block, and nothing here did, so those elements escaped
this frame entirely and pinned themselves to the actual browser window instead. `contain: layout
paint` makes this box that ancestor, the same way an iframe's document would be one on its own. */
contain: layout paint;
}

.ui-showcase__inline {
Expand Down
174 changes: 144 additions & 30 deletions packages/css/src/components/admin-layout.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,66 @@
* The layout reads its own state from attributes on the root rather than from classes a framework
* toggles. That is what lets one stylesheet serve both adapters and a server-rendered page that has
* not run JavaScript yet.
*
* The sidebar and header are fixed to the viewport rather than scrolling with the page -- only the
* content area scrolls. Responsiveness is a container query against the layout's own inline size,
* not the viewport: an admin layout embedded in a narrower pane (a preview frame, a split view)
* collapses to its mobile presentation on its own width, the same way it would in a full window.
*/
.cm-admin-layout {
display: grid;
grid-template-columns: auto 1fr;
display: block;
min-block-size: 100dvb;
container: cm-admin-layout / inline-size;
background: var(--cm-color-background-canvas);
}

.cm-admin-layout__aside {
position: fixed;
inset-block: 0;
inset-inline-start: 0;
z-index: var(--cm-z-index-sticky, 10);
display: flex;
flex-direction: column;
gap: var(--cm-space-3);
inline-size: 16rem;
padding: var(--cm-space-3);
border-inline-end: var(--cm-border-width) solid var(--cm-color-border-default);
inline-size: 18rem;
overflow: hidden;
background: var(--cm-color-background-surface);
transition: inline-size var(--cm-motion-duration-fast) var(--cm-motion-ease-standard);
border-inline-end: var(--cm-border-width) solid var(--cm-color-border-default);
transition: inline-size var(--cm-motion-duration-normal) var(--cm-motion-ease-standard);
}

.cm-admin-layout[data-cm-sidebar-collapsed='true'] .cm-admin-layout__aside {
inline-size: 4rem;
inline-size: 4.75rem;
}

/*
* A collapsed sidebar still opens on hover or keyboard focus, wide enough to read again, without
* changing the collapsed state itself -- that state is for the layout at rest, not for whether you
* can currently see it.
*/
.cm-admin-layout[data-cm-sidebar-collapsed='true'] .cm-admin-layout__aside:is(:hover, :has(:focus-visible)) {
z-index: calc(var(--cm-z-index-sticky, 10) + 2);
inline-size: 18rem;
}

.cm-admin-layout__brand {
display: flex;
flex-shrink: 0;
align-items: center;
gap: var(--cm-space-2);
block-size: 4rem;
padding: var(--cm-space-2) var(--cm-space-3);
overflow: hidden;
}

.cm-admin-layout__aside:has(.cm-admin-layout__aside-content) .cm-admin-layout__brand {
border-block-end: var(--cm-border-width) solid var(--cm-color-border-default);
}

.cm-admin-layout__aside-content {
min-block-size: 0;
flex: 1 1 auto;
overflow-y: auto;
padding: var(--cm-space-3);
}

.cm-admin-layout__main {
Expand All @@ -30,11 +70,25 @@
min-inline-size: 0;
}

.cm-admin-layout:has(> .cm-admin-layout__aside) .cm-admin-layout__main {
margin-inline-start: 18rem;
transition: margin-inline-start var(--cm-motion-duration-normal) var(--cm-motion-ease-standard);
}

.cm-admin-layout[data-cm-sidebar-collapsed='true']:has(> .cm-admin-layout__aside) .cm-admin-layout__main {
margin-inline-start: 4.75rem;
}

.cm-admin-layout__header {
position: sticky;
inset-block-start: 0;
z-index: calc(var(--cm-z-index-sticky, 10) + 1);
display: flex;
align-items: center;
gap: var(--cm-space-2);
block-size: 4rem;
padding: var(--cm-space-2) var(--cm-space-3);
background: var(--cm-color-background-surface);
border-block-end: var(--cm-border-width) solid var(--cm-color-border-default);
}

Expand Down Expand Up @@ -101,44 +155,104 @@
}

/*
* Below the breakpoint the sidebar becomes a drawer: off-canvas by default, slid in when the root
* says the drawer is open, with a backdrop that closes it.
* Below the container's own inline-size threshold the sidebar becomes a drawer: off-canvas by
* default, slid in when the root says the drawer is open, with a backdrop that fades in behind it
* and closes it on click. The fade is delayed on the way out and immediate on the way in, so the
* drawer never appears to sit under a backdrop that hasn't shown up yet.
*/
@media (width < 48rem) {
.cm-admin-layout {
grid-template-columns: 1fr;
}

.cm-admin-layout__mobile-toggle,
.cm-admin-layout__mobile-brand {
display: block;
}

@container cm-admin-layout (width < 64rem) {
.cm-admin-layout__aside {
position: fixed;
z-index: 2;
inset-block: 0;
inset-inline-start: 0;
inline-size: 16rem;
inset-block-start: 0;
z-index: calc(var(--cm-z-index-sticky, 10) + 3);
inline-size: min(18rem, 85cqi);
visibility: hidden;
translate: -100% 0;
transition: translate var(--cm-motion-duration-fast) var(--cm-motion-ease-standard);
transition:
translate var(--cm-motion-duration-normal) var(--cm-motion-ease-standard),
visibility 0s var(--cm-motion-duration-normal);
}

[dir='rtl'] .cm-admin-layout__aside {
translate: 100% 0;
}

.cm-admin-layout[data-cm-mobile-sidebar-open='true'] .cm-admin-layout__aside {
visibility: visible;
translate: 0 0;
transition:
translate var(--cm-motion-duration-normal) var(--cm-motion-ease-standard),
visibility 0s;
}

.cm-admin-layout[data-cm-mobile-sidebar-open='true'] .cm-admin-layout__mobile-backdrop {
.cm-admin-layout[data-cm-sidebar-collapsed='true'] .cm-admin-layout__aside {
inline-size: min(18rem, 85cqi);
}

.cm-admin-layout:has(> .cm-admin-layout__aside) .cm-admin-layout__main,
.cm-admin-layout[data-cm-sidebar-collapsed='true']:has(> .cm-admin-layout__aside) .cm-admin-layout__main {
margin-inline-start: 0;
}

.cm-admin-layout__header {
z-index: calc(var(--cm-z-index-sticky, 10) + 3);
display: grid;
grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
}

.cm-admin-layout__mobile-toggle {
display: flex;
justify-self: start;
}

.cm-admin-layout__mobile-brand {
display: flex;
min-inline-size: 0;
align-items: center;
justify-content: center;
grid-column: 2;
}

.cm-admin-layout__header-content {
inline-size: auto;
display: flex;
min-inline-size: 0;
justify-self: end;
grid-column: 3;
}

.cm-admin-layout__mobile-backdrop {
position: fixed;
z-index: 1;
display: block;
inset: 0;
z-index: calc(var(--cm-z-index-sticky, 10) + 2);
display: block;
background: var(--cm-color-background-backdrop);
opacity: 0;
visibility: hidden;
pointer-events: none;
transition:
opacity var(--cm-motion-duration-normal) var(--cm-motion-ease-standard),
visibility 0s var(--cm-motion-duration-normal);
}

.cm-admin-layout[data-cm-mobile-sidebar-open='true'] .cm-admin-layout__mobile-backdrop {
opacity: 1;
visibility: visible;
pointer-events: auto;
transition:
opacity var(--cm-motion-duration-normal) var(--cm-motion-ease-standard),
visibility 0s;
}

.cm-admin-layout[data-cm-sidebar-collapsed='true'] .cm-admin-layout__aside:is(:hover, :has(:focus-visible)) {
z-index: calc(var(--cm-z-index-sticky, 10) + 4);
inline-size: min(18rem, 85cqi);
}
}

@media (prefers-reduced-motion: reduce) {
.cm-admin-layout__aside {
.cm-admin-layout__aside,
.cm-admin-layout__main,
.cm-admin-layout__mobile-backdrop {
transition: none;
}
}
39 changes: 34 additions & 5 deletions packages/css/src/components/admin-shell.css
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
/* A fixed frame: topbar above, sidebar beside the workspace. It has no state to reflect. */
/*
* A fixed frame: topbar above, sidebar beside the workspace. It has no state to reflect. The
* workspace is a distinct, elevated card inset within the shell rather than a plain flow region --
* that's what separates it visually from the sidebar sitting flush beside it.
*/
.cm-admin-shell {
display: flex;
flex-direction: column;
min-block-size: 100dvb;
background: var(--cm-color-background-inverse);
}

.cm-admin-shell__topbar {
position: sticky;
inset-block-start: 0;
z-index: var(--cm-z-index-sticky, 10);
display: flex;
align-items: center;
gap: var(--cm-space-3);
padding: var(--cm-space-2) var(--cm-space-3);
border-block-end: var(--cm-border-width) solid var(--cm-color-border-default);
color: var(--cm-color-text-inverse);
}

.cm-admin-shell__header {
Expand All @@ -19,17 +27,24 @@
}

.cm-admin-shell__body {
position: relative;
display: flex;
flex: 1 1 auto;
min-block-size: 0;
background: var(--cm-color-background-canvas);
border-start-start-radius: var(--cm-radius-surface);
border-start-end-radius: var(--cm-radius-surface);
}

.cm-admin-shell__sidebar {
inline-size: 16rem;
border-inline-end: var(--cm-border-width) solid var(--cm-color-border-default);
}

.cm-admin-shell__sidebar-content {
position: sticky;
inset-block-start: 0;
max-block-size: 100dvb;
overflow-y: auto;
padding: var(--cm-space-3);
}

Expand All @@ -38,6 +53,12 @@
flex: 1 1 auto;
flex-direction: column;
min-inline-size: 0;
margin-block: var(--cm-space-3);
margin-inline-end: var(--cm-space-3);
overflow: hidden;
background: var(--cm-color-background-surface);
border: var(--cm-border-width) solid var(--cm-color-border-default);
border-radius: var(--cm-radius-surface);
}

.cm-admin-shell__content {
Expand All @@ -46,6 +67,7 @@
}

.cm-admin-shell__footer {
margin-block-start: auto;
padding: var(--cm-space-2) var(--cm-space-3);
border-block-start: var(--cm-border-width) solid var(--cm-color-border-default);
}
Expand All @@ -57,7 +79,14 @@

.cm-admin-shell__sidebar {
inline-size: auto;
border-inline-end: 0;
border-block-end: var(--cm-border-width) solid var(--cm-color-border-default);
}

.cm-admin-shell__sidebar-content {
position: static;
max-block-size: none;
}

.cm-admin-shell__workspace {
margin-inline-start: var(--cm-space-3);
}
}
Loading
Loading