Packages
noora
0.84.0
0.84.1
0.84.0
0.83.2
0.83.1
0.83.0
0.82.7
0.82.6
0.82.5
0.82.4
0.82.3
0.82.2
0.82.1
0.82.0
0.81.4
0.81.3
0.81.2
0.81.1
0.81.0
0.80.1
0.80.0
0.79.1
0.79.0
0.78.1
0.78.0
0.77.4
0.77.3
0.77.2
0.77.1
0.77.0
0.76.0
0.75.0
0.74.0
0.73.0
0.72.0
0.71.0
0.70.0
0.69.1
0.69.0
0.68.0
0.67.0
0.66.0
0.65.0
0.64.2
0.64.1
0.64.0
0.63.2
0.63.1
0.63.0
0.62.0
0.61.0
0.60.0
0.59.0
0.58.0
0.57.0
0.56.1
0.56.0
0.55.0
0.54.0
0.53.1
0.53.0
0.52.1
0.52.0
0.51.0
0.50.1
0.50.0
0.49.0
0.48.0
0.47.0
0.46.0
0.45.0
0.44.3
0.44.2
0.44.1
0.44.0
0.43.0
0.42.0
0.41.0
0.40.6
0.40.5
0.40.4
0.40.3
0.40.2
0.40.1
0.40.0
0.39.1
0.39.0
0.38.0
0.37.0
0.36.0
0.35.0
0.34.0
0.33.0
0.32.1
0.32.0
0.31.0
0.30.0
0.29.2
0.29.1
0.29.0
0.28.5
0.28.4
0.28.3
0.28.2
0.28.1
0.28.0
0.27.0
0.26.1
0.26.0
0.25.0
0.24.0
0.23.1
0.23.0
0.22.1
0.22.0
0.21.0
0.20.0
0.19.0
0.18.0
0.17.0
0.16.0
0.15.0
0.14.0
0.13.0
0.12.1
0.12.0
0.11.2
0.11.1
0.11.0
0.10.0
0.9.0
0.8.0
0.7.0
0.6.1
0.6.0
0.5.1
0.5.0
0.4.0
0.3.2
0.3.1
0.3.0
0.2.2
0.2.0
0.1.0
0.1.0-rc.2
0.1.0-rc.1
0.1.0-alpha.6
0.1.0-alpha.5
0.1.0-alpha.4
0.1.0-alpha.3
0.1.0-alpha.2
0.1.0-alpha.1
A component library for Phoenix LiveView applications
Current section
Files
Jump to
Current section
Files
css/sidebar.css
.noora-sidebar {
display: none;
position: relative;
/* border-box so the rendered box is exactly 228px even with the layout's
1px border-right. */
box-sizing: border-box;
width: 228px;
@media (min-width: 1024px) {
display: block;
}
/* The native scrollbar is hidden and replaced by the overlay scrollbar
below, so it never reserves a gutter and content doesn't shift when it
appears. */
& > [data-part="viewport"] {
display: flex;
flex-direction: column;
align-items: stretch;
gap: var(--noora-spacing-7);
/* border-box so the padding stays inside height: 100% — otherwise the
viewport overflows the wrapper by its vertical padding and the bottom
of the list is clipped. */
box-sizing: border-box;
padding: var(--noora-spacing-9) var(--noora-spacing-5);
height: 100%;
overflow-y: auto;
/* The sidebar is a scroll partition: wheel inside it never chains to the
page scroll, even when its own scroll hits an edge. */
overscroll-behavior: contain;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
}
/* Bottom fade hinting that there is more content to scroll. Driven by the
NooraScrollArea hook via data-scrollable / data-at-end. */
&::after {
content: "";
position: absolute;
right: 0;
bottom: 0;
left: 0;
height: 48px;
background: linear-gradient(
to bottom,
transparent,
var(--noora-surface-background-primary)
);
opacity: 0;
transition: opacity 150ms var(--ease-out-cubic);
pointer-events: none;
}
&[data-scrollable]:not([data-at-end])::after {
opacity: 1;
}
& > [data-part="scrollbar"] {
display: none;
position: absolute;
top: var(--noora-spacing-4);
right: 0;
bottom: var(--noora-spacing-4);
width: 12px;
z-index: 1;
& [data-part="thumb"] {
position: absolute;
top: 0;
right: 2px;
width: 3px;
border-radius: 999px;
background: light-dark(
var(--noora-neutral-light-600),
var(--noora-neutral-dark-600)
);
cursor: default;
transition: width 150ms var(--ease-out-cubic);
}
&:hover [data-part="thumb"],
&[data-dragging] [data-part="thumb"] {
width: 6px;
}
}
@media (pointer: fine) {
&[data-scrollable] > [data-part="scrollbar"] {
display: block;
}
}
& [data-part="group"] {
display: flex;
flex-direction: column;
gap: var(--noora-spacing-4);
& [data-part="group-label"] {
padding: var(--noora-spacing-0) var(--noora-spacing-4);
color: var(--noora-surface-label-secondary);
font: var(--noora-font-weight-medium) var(--noora-font-body-medium);
}
}
& [data-part="collapsible-group"] {
& [data-part="header"] {
gap: var(--noora-spacing-0);
padding: var(--noora-spacing-0);
& [data-part="link"] {
display: flex;
flex-grow: 1;
align-items: center;
gap: var(--noora-spacing-4);
border-radius: var(--noora-radius-3);
padding: var(--noora-spacing-3) var(--noora-spacing-0)
var(--noora-spacing-3) var(--noora-spacing-4);
min-width: 0;
color: inherit;
user-select: none;
text-decoration: unset;
&:focus-visible {
outline: 2px solid var(--noora-surface-label-primary);
outline-offset: -2px;
}
}
& > [data-part="trigger"] {
display: flex;
align-items: center;
margin: var(--noora-spacing-0);
border: none;
background: transparent;
padding: var(--noora-spacing-3) var(--noora-spacing-4);
}
}
& [data-part="trigger"] {
cursor: pointer;
border-radius: var(--noora-radius-3);
user-select: none;
text-decoration: unset;
/* The disclosure-only group renders this trigger as a native <button>;
strip the UA chrome so it matches the div/link tabs while keeping
native button semantics (Space/Enter, focus, disabled). */
appearance: none;
margin: var(--noora-spacing-0);
border: none;
background: transparent;
font: inherit;
text-align: start;
&:focus-visible {
outline: 2px solid var(--noora-surface-label-primary);
outline-offset: -2px;
}
&:disabled {
cursor: default;
opacity: 0.5;
}
& [data-part="label"] {
flex-grow: 1;
font: var(--noora-font-body-medium);
}
& [data-part="indicator"] {
/* Flex blockifies the inline-grid icon-transition child, dropping the
line-box strut that otherwise made the box 21px tall and pushed the
chevron below the row's vertical center. */
display: flex;
align-items: center;
color: var(--noora-surface-label-tertiary);
& svg {
width: 18px;
height: 18px;
display: block;
}
}
}
& .noora-tab-menu-vertical:hover [data-part="indicator"] {
color: var(--noora-surface-label-secondary);
}
& [data-part="root"] {
& [data-part="content"] {
display: flex;
flex-direction: column;
gap: var(--noora-spacing-6);
box-sizing: border-box;
margin-left: 14px;
border-left: 1.5px solid var(--noora-surface-border-primary);
padding-top: 20px;
padding-left: 20px;
overflow: hidden;
&[data-state="open"] {
animation: noora-sidebar-content-expand 250ms var(--ease-out-cubic);
}
&[data-state="closed"] {
animation: noora-sidebar-content-collapse 160ms var(--ease-out-cubic);
}
&[hidden] {
display: none;
}
}
}
}
& [data-part="item"] {
user-select: none;
text-decoration: none;
&:focus-visible {
outline: none;
& .noora-tab-menu-vertical {
outline: 2px solid var(--noora-surface-label-primary);
outline-offset: -2px;
}
}
}
}
/* Expand/collapse animation for collapsible group content. `--height` is
measured and set by the zag.js collapsible (see NooraCollapsible); it keeps
the content mounted until the closing animation ends. */
/* Vertical padding must animate alongside height: with border-box sizing the
box can never be shorter than its padding, so a fixed padding-top would make
the sweep stall at 20px and snap. */
@keyframes noora-sidebar-content-expand {
from {
height: 0;
padding-top: 0;
opacity: 0;
}
to {
height: var(--height);
padding-top: 20px;
opacity: 1;
}
}
/* Fade-dominant close: the items are gone halfway through while the height
finishes sweeping shut, so closing reads as a quick fade-out rather than a
reversed slide. */
@keyframes noora-sidebar-content-collapse {
from {
height: var(--height);
padding-top: 20px;
opacity: 1;
}
50% {
opacity: 0;
}
to {
height: 0;
padding-top: 0;
opacity: 0;
}
}
@media (prefers-reduced-motion: reduce) {
/* zag detects animation-name: none and skips waiting for animationend. */
.noora-sidebar [data-part="collapsible-group"] [data-part="content"] {
animation: none;
}
.noora-sidebar > [data-part="scrollbar"] [data-part="thumb"] {
transition: none;
}
}