Files
tabler/docs/scss/docs.scss
T

616 lines
17 KiB
SCSS

// Docs-only stylesheet, built by the `css` script through the shared css
// pipeline (.build/build-css.ts, with --no-prefix) into tmp-assets/css/;
// copy-assets serves it as /css/docs.css. Tabler variables are written with
// their public `--tblr-` prefix here: the inlined DocSearch stylesheet below
// has variables its JS sets at runtime, so the build must not rename any.
@use '@docsearch/css/dist/style.css';
// DocSearch ships its own palette — an Algolia blue plus lavender greys — and
// switches themes on `html[data-theme=dark]`, which Tabler never sets. Mapping
// every colour onto a Tabler token fixes both: the modal matches the docs, and
// it follows the color mode through the `light-dark()` tokens.
:root {
--docsearch-primary-color: var(--tblr-primary);
--docsearch-highlight-color: var(--tblr-primary);
--docsearch-text-color: var(--tblr-body-color);
--docsearch-muted-color: var(--tblr-secondary);
--docsearch-secondary-text-color: var(--tblr-secondary);
--docsearch-icon-color: var(--tblr-secondary);
--docsearch-subtle-color: var(--tblr-border-color);
--docsearch-container-background: color-mix(in srgb, var(--tblr-backdrop-bg), transparent calc(100% - 32%));
--docsearch-modal-background: var(--tblr-bg-surface);
--docsearch-modal-shadow: var(--tblr-shadow-dropdown);
--docsearch-searchbox-background: var(--tblr-bg-surface);
--docsearch-searchbox-focus-background: var(--tblr-bg-surface);
--docsearch-hit-background: var(--tblr-bg-surface-secondary);
--docsearch-hit-color: var(--tblr-body-color);
--docsearch-hit-shadow: none;
--docsearch-footer-background: var(--tblr-bg-surface);
--docsearch-key-background: var(--tblr-bg-surface-secondary);
--docsearch-key-color: var(--tblr-secondary);
--docsearch-key-pressed-shadow: 0 0 0 1px var(--tblr-border-color);
}
.DocSearch-Button {
width: 100%;
box-shadow: 0 0 0 1px var(--tblr-border-color);
font-weight: var(--tblr-font-weight-normal);
transition: all 0.2s ease-in-out;
}
.DocSearch-SearchBar {
display: flex;
padding: var(--docsearch-spacing) var(--docsearch-spacing) 0;
background-color: var(--tblr-bg-surface) !important;
}
.DocSearch-Hit-source {
background: none !important;
}
.DocSearch-Dropdown {
background-color: var(--tblr-bg-surface) !important;
}
.DocSearch-Hit a {
background-color: var(--tblr-bg-surface-secondary) !important;
border: 1px solid var(--tblr-border-color) !important;
color: var(--tblr-body-color) !important;
box-shadow: none !important;
&:hover {
background-color: var(--tblr-bg-surface-tertiary) !important;
}
}
.DocSearch-Hit-Container {
color: var(--tblr-body-color) !important;
}
.DocSearch-Hit-title {
color: var(--tblr-body-color) !important;
}
.DocSearch-Hit-path {
color: var(--tblr-muted) !important;
}
// The selected hit is tinted like the active page in the sidebar rather than
// filled with the primary color: DocSearch colors the matched words itself, and
// on a solid fill they came out blue on blue.
.DocSearch-Hit[aria-selected='true'] a {
background-color: color-mix(in srgb, var(--tblr-primary) 8%, transparent) !important;
border-color: var(--tblr-primary) !important;
}
.DocSearch-Modal {
background-color: var(--tblr-bg-surface) !important;
box-shadow: var(--tblr-shadow-dropdown) !important;
}
.DocSearch-Form {
background-color: var(--tblr-bg-surface-secondary) !important;
border: 1px solid var(--tblr-border-color) !important;
}
.DocSearch-Input {
color: var(--tblr-body-color) !important;
&::placeholder {
color: var(--tblr-muted) !important;
}
}
.DocSearch-Footer {
background-color: var(--tblr-bg-surface) !important;
border-top: 1px solid var(--tblr-border-color) !important;
box-shadow: none !important;
}
.DocSearch-Button-Key {
top: 0;
}
.DocSearch-Container {
z-index: 10000;
}
.no-transition {
* {
transition: none !important;
}
}
.example > .modal,
.example > .offcanvas {
display: block !important;
position: relative !important;
}
.example > .offcanvas-backdrop {
position: absolute !important;
}
// Highlighted code. Shiki writes the theme's own background and foreground
// into a style attribute — `.shiki` comes from the docs components, and
// `.astro-code` from markdown fences. Both follow Tabler's surface instead,
// so only the token colors come from the shiki theme.
.shiki,
.astro-code {
background: var(--tblr-bg-surface-dark) !important;
color: var(--tblr-gray-300) !important;
}
code {
::selection {
background: var(--tblr-primary);
}
}
// -----------------------------------------------------------------------------
// Docs layout: muted page canvas, elevated article panel, compact sidebar
// navigation, "On this page" rail and joined example/code groups.
// -----------------------------------------------------------------------------
// Top navbar reads as a distinct bar on the muted canvas. Tabler's navbar
// already draws its edge with an inset box-shadow, so an explicit border
// would double the line.
header > .navbar {
background-color: var(--tblr-bg-surface);
// Quiet text links: secondary until hovered, like the sidebar navigation.
.docs-navbar-link {
padding: 0.25rem 0.625rem;
border-radius: var(--tblr-border-radius);
color: var(--tblr-secondary);
&:hover,
&:focus-visible {
color: var(--tblr-body-color);
background-color: color-mix(in srgb, var(--tblr-body-color) 6%, transparent);
}
}
}
// Article panel: elevated surface holding the page content.
.docs-article {
background-color: var(--tblr-bg-surface);
border: 1px solid var(--tblr-border-color);
border-radius: var(--tblr-border-radius-lg);
box-shadow: var(--tblr-shadow-xs);
margin-block: 1rem;
@media (min-width: 992px) {
margin: 1.5rem;
}
}
// Page footer below the article panel, aligned with the panel content.
.docs-footer {
margin-block-start: 2rem;
padding: 0.5rem 0 2rem;
@media (min-width: 992px) {
margin-block-start: 4rem;
margin-inline: 1.5rem;
padding: 0.5rem 0 2.5rem;
}
}
// Footer theme switcher: a segmented control whose active segment follows the
// stored choice — light, dark, or auto (see the data-docs-theme inline script
// in DocsLayout.astro; tabler-theme.js resolves auto to the system scheme).
:root[data-docs-theme='light'] .docs-theme-toggle-light,
:root[data-docs-theme='dark'] .docs-theme-toggle-dark,
:root[data-docs-theme='auto'] .docs-theme-toggle-auto {
color: var(--tblr-body-color);
background: var(--tblr-bg-surface);
border-color: var(--tblr-border-color);
}
// Sidebar navigation: compact rounded links.
.col-docs {
width: 15rem;
// Section subheaders line up with the link text, not the pill edge.
.subheader {
padding-inline: 0.625rem;
}
.nav-link {
border-radius: var(--tblr-border-radius);
padding: 0.375rem 0.625rem;
color: var(--tblr-body-color);
&:hover,
&:focus-visible {
background-color: color-mix(in srgb, var(--tblr-body-color) 6%, transparent);
}
// The sidebar links had no visible focus ring at all — `.nav-link` only
// resets the outline. Match the ring the buttons use.
&:focus-visible {
outline: 0;
box-shadow: 0 0 0 var(--tblr-focus-ring-width, 0.25rem) var(--tblr-focus-ring-color);
}
}
// Every row stays one line: a wrapped label turns the current-page pill into
// a two-line block and breaks the rhythm of the list.
.nav-link-title {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.nav-link-toggle {
flex-shrink: 0;
}
// Hierarchy comes from weight, not size: a group that opens a section reads
// stronger than the pages inside it.
#menu .nav-link[data-bs-toggle='collapse'] {
font-weight: var(--tblr-font-weight-medium);
}
.nav-vertical .nav {
padding-inline-start: 0.5rem;
margin-inline-start: 0.625rem;
}
// Expanded groups only switch weight; the tinted pill marks the current page.
#menu .nav-link.active {
color: var(--tblr-body-color);
background-color: transparent;
&:hover,
&:focus-visible {
background-color: color-mix(in srgb, var(--tblr-body-color) 6%, transparent);
}
}
#menu .nav-link[aria-current='page'] {
&,
&:hover,
&:focus-visible {
color: var(--tblr-primary);
background-color: color-mix(in srgb, var(--tblr-primary) 8%, transparent);
font-weight: var(--tblr-font-weight-medium);
}
}
}
// Article typography. Docs pages are long-form reading, so the prose gets a
// larger heading scale and more breathing room than the product defaults in
// core's _markdown.scss. Everything targets direct children of .prose — the
// markdown output — so markup inside <Example> demos keeps the product styles.
.docs-page-title {
font-size: 1.75rem;
text-wrap: balance;
}
.docs-article .prose {
// Heading scale: the defaults (h2 20px, h3 16px) leave h3 the same size as
// body text, so subsections disappear. Margin below grows from 8px, which
// glued the heading to its section while 40px separated it from the last.
> h2 {
margin-bottom: 1rem;
font-size: 1.375rem;
}
> h3 {
margin-bottom: 0.75rem;
font-size: 1.125rem;
}
> p,
> ul,
> ol {
margin-bottom: 1.25rem;
}
> ul li + li,
> ol li + li {
margin-top: 0.25em;
}
// Prose links keep the primary color but gain a quiet underline, so they
// are findable when scanning and do not rely on color alone (WCAG 1.4.1).
> :is(p, ul, ol, table) a:not(.btn) {
text-decoration: underline;
text-decoration-color: color-mix(in srgb, var(--tblr-primary) 40%, transparent);
text-underline-offset: 3px;
&:hover {
text-decoration-color: var(--tblr-primary);
}
}
// `pretty` only optimises the tail of a block, so it stays cheap on long
// text; `balance` evens out all lines and suits short headings.
> :is(p, li, blockquote, dd),
> :is(ul, ol) li {
text-wrap: pretty;
}
> :is(h2, h3, h4, h5, h6) {
text-wrap: balance;
}
}
// Table of contents: rail with a scrollspy indicator.
#toc {
border-inline-start: 1px solid var(--tblr-border-color);
.nav-link {
padding: 0.25rem 0.75rem;
margin-inline-start: -1px;
border-inline-start: 2px solid transparent;
border-radius: 0;
color: var(--tblr-secondary);
font-size: 0.875rem;
&:hover,
&:focus-visible {
color: var(--tblr-body-color);
background-color: transparent;
}
&.active {
color: var(--tblr-primary);
border-inline-start-color: var(--tblr-primary);
font-weight: var(--tblr-font-weight-medium);
background-color: transparent;
}
&.docs-toc-sub {
padding-inline-start: 1.75rem;
}
}
}
// Page tools under the table of contents: source, edit. One shape for both
// links (copying the page as markdown moved to the "Copy to LLM" button above
// the title, see DocsCopyMarkdownLink).
.docs-page-links {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 0.5rem;
}
.docs-page-link {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0;
font-size: 0.875rem;
color: var(--tblr-secondary);
text-decoration: none;
background: none;
border: 0;
.icon {
width: 1rem;
height: 1rem;
flex-shrink: 0;
}
&:hover,
&:focus-visible {
color: var(--tblr-body-color);
}
&:focus-visible {
outline: 0;
border-radius: var(--tblr-border-radius);
box-shadow: 0 0 0 var(--tblr-focus-ring-width, 0.25rem) var(--tblr-focus-ring-color);
}
}
// Previous/next page navigation: a pair of cards on the page canvas below the
// article panel, aligned with it and with the footer.
.docs-pagination {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
margin-block-end: 1.5rem;
@media (min-width: 992px) {
margin-inline: 1.5rem;
}
.docs-pagination-link {
display: flex;
flex-direction: column;
gap: 0.25rem;
padding: 1rem 1.25rem;
border-radius: var(--tblr-border-radius-lg);
background-color: transparent;
text-decoration: none;
transition: background-color 0.15s ease-in-out;
&:hover,
&:focus-visible {
background-color: color-mix(in srgb, var(--tblr-body-color) 4%, transparent);
}
}
.docs-pagination-next {
grid-column: 2;
align-items: flex-end;
text-align: end;
}
// Direction reads as an eyebrow label, so the page title carries the line.
.docs-pagination-label {
display: inline-flex;
align-items: center;
gap: 0.25rem;
font-size: 0.6875rem;
font-weight: var(--tblr-font-weight-medium);
text-transform: uppercase;
letter-spacing: 0.04em;
color: var(--tblr-secondary);
}
.docs-pagination-title {
font-weight: var(--tblr-font-weight-medium);
color: var(--tblr-body-color);
transition: color 0.15s ease-in-out;
}
// Side by side leaves no room for a longer page title on a phone. Last in the
// block on purpose: it has to outrank the `grid-column: 2` rule above.
@media (max-width: 575.98px) {
grid-template-columns: 1fr;
.docs-pagination-next {
grid-column: 1;
}
}
}
// Related pages use the same card language as prev/next: a quiet bordered
// surface that takes the primary color on hover, and a title that leads.
.docs-related {
.card {
border-radius: var(--tblr-border-radius-lg);
box-shadow: none;
transition:
border-color 0.15s ease-in-out,
box-shadow 0.15s ease-in-out;
&:hover,
&:focus-visible {
border-color: var(--tblr-primary);
box-shadow: var(--tblr-shadow-xs);
.h3 {
color: var(--tblr-primary);
}
}
}
.card-body {
padding: 1rem 1.25rem;
}
.h3 {
margin-bottom: 0.25rem;
font-size: 1rem;
font-weight: var(--tblr-font-weight-medium);
color: var(--tblr-body-color);
transition: color 0.15s ease-in-out;
}
p {
font-size: 0.875rem;
}
}
// Example blocks: the demo and its code panel form one rounded group.
.docs-example .example {
margin: 0;
}
.docs-example-code .shiki {
margin: 0;
border-radius: var(--tblr-border-radius);
}
.docs-example-code-attached .shiki {
border-start-start-radius: 0;
border-start-end-radius: 0;
}
// -----------------------------------------------------------------------------
// Callouts: a tinted aside for a note, a tip or a warning inside prose. The icon
// carries the state color while the text stays body color, so the block reads as
// part of the page — and so a pale state like yellow never has to carry text.
// -----------------------------------------------------------------------------
.callout {
display: flex;
gap: 0.75rem;
padding: 1rem 1.25rem;
margin-block: 1.5rem;
font-size: 0.875rem;
line-height: 1.5;
color: var(--tblr-body-color);
background: var(--tblr-bg-surface-secondary);
border: 1px solid var(--tblr-border-color);
border-radius: var(--tblr-border-radius-lg);
}
.callout-icon {
flex: none;
margin-top: 0.0625rem;
color: var(--tblr-secondary);
}
.callout-title {
margin-bottom: 0.25rem;
font-weight: var(--tblr-font-weight-medium);
}
// The slot holds markdown, so the trailing paragraph or list brings its own
// bottom margin — which would pad the callout unevenly.
.callout-body > :last-child {
margin-bottom: 0;
}
// The prose link treatment targets direct children of .prose, so it stops at the
// callout. Links inside one get the same quiet underline (WCAG 1.4.1).
.callout a:not(.btn) {
text-decoration: underline;
text-decoration-color: color-mix(in sRGB, var(--tblr-primary) 40%, transparent);
text-underline-offset: 3px;
&:hover {
text-decoration-color: var(--tblr-primary);
}
}
@each $state, $color in ('info': 'azure', 'tip': 'green', 'warning': 'yellow', 'danger': 'red') {
.callout-#{$state} {
background: var(--tblr-#{$color}-lt);
border-color: color-mix(in srgb, var(--tblr-#{$color}) 24%, transparent);
.callout-icon {
color: var(--tblr-#{$color});
}
}
}
//
// Sticky header
//
// `position: sticky` only travels inside its containing block. The docs shell
// has no `.page` wrapper - the header is a child of `body` - and core's
// `body { height: 100% }` makes that block exactly one viewport tall, so the
// header would unstick after the first screen (#2332). Let the docs body grow
// with its content instead; `.page` does the same thing for the app layouts.
body {
height: auto;
min-height: 100%;
}
:root {
// Matches `$navbar-height` in core - the height of the docs navbar.
--docs-header-height: 3.5rem;
}
// Anchors from the table of contents would otherwise land under the header.
html {
scroll-padding-top: var(--docs-header-height);
}
// The table of contents sticks too, so it has to clear the header (#2421).
// A long list must not run past the viewport either: the rail is capped at the
// space below the header and scrolls on its own (#2965).
.docs-toc-sticky {
top: var(--docs-header-height);
max-height: calc(100vh - var(--docs-header-height));
overflow-y: auto;
}