diff --git a/.changeset/docs-css-vars-section.md b/.changeset/docs-css-vars-section.md new file mode 100644 index 000000000..6305435d1 --- /dev/null +++ b/.changeset/docs-css-vars-section.md @@ -0,0 +1,5 @@ +--- +"@tabler/docs": minor +--- + +Added a CSS variables section to component docs pages, replacing the hand-written SCSS variables sections. diff --git a/core/scss/bootstrap/_tables.scss b/core/scss/bootstrap/_tables.scss index 0f9eee480..8035f52e0 100644 --- a/core/scss/bootstrap/_tables.scss +++ b/core/scss/bootstrap/_tables.scss @@ -8,6 +8,7 @@ .table { // Reset needed for nesting tables + // scss-docs-start table-css-vars --table-color-type: initial; --table-bg-type: initial; --table-color-state: initial; @@ -23,6 +24,7 @@ --table-active-bg: #{$table-active-bg}; --table-hover-color: #{$table-hover-color}; --table-hover-bg: #{$table-hover-bg}; + // scss-docs-end table-css-vars width: 100%; margin-bottom: $spacer; diff --git a/core/scss/ui/_accordion.scss b/core/scss/ui/_accordion.scss index 20fef12e6..c88f4ea31 100644 --- a/core/scss/ui/_accordion.scss +++ b/core/scss/ui/_accordion.scss @@ -1,6 +1,7 @@ @use '../config' as *; .accordion { + // scss-docs-start accordion-css-vars --accordion-color: var(--body-color); --accordion-border-color: var(--border-color); --accordion-border-radius: #{$accordion-border-radius}; @@ -19,6 +20,7 @@ --accordion-body-padding-y: #{$accordion-body-padding-y}; --accordion-btn-gap: #{$accordion-btn-gap}; --accordion-header-gap: #{$accordion-header-gap}; + // scss-docs-end accordion-css-vars display: flex; flex-direction: column; diff --git a/core/scss/ui/_alerts.scss b/core/scss/ui/_alerts.scss index 1e8651126..34cb33d7d 100644 --- a/core/scss/ui/_alerts.scss +++ b/core/scss/ui/_alerts.scss @@ -1,6 +1,7 @@ @use '../config' as *; .alert { + // scss-docs-start alert-css-vars --alert-variant-color: var(--body-color); --alert-color: var(--alert-variant-color); --alert-bg: #{color-transparent(var(--alert-variant-color), 0.16, var(--bg-surface))}; @@ -16,6 +17,7 @@ --alert-icon-size: #{$alert-icon-size}; --alert-padding-inline-end: #{$alert-padding-inline-end}; --alert-gap: #{$alert-gap}; + // scss-docs-end alert-css-vars position: relative; display: flex; diff --git a/core/scss/ui/_avatars.scss b/core/scss/ui/_avatars.scss index 111c5ab08..0c981d655 100644 --- a/core/scss/ui/_avatars.scss +++ b/core/scss/ui/_avatars.scss @@ -3,6 +3,7 @@ @use '../config' as *; .avatar { + // scss-docs-start avatar-css-vars --avatar-size: var(--avatar-list-size, #{$avatar-size}); --avatar-status-size: #{$avatar-status-size}; --avatar-bg: #{$avatar-bg}; @@ -12,6 +13,7 @@ --avatar-icon-size: #{$avatar-icon-size}; --avatar-brand-size: #{$avatar-brand-size}; --avatar-border-radius: #{$avatar-border-radius}; + // scss-docs-end avatar-css-vars position: relative; display: inline-flex; align-items: center; diff --git a/core/scss/ui/_badges.scss b/core/scss/ui/_badges.scss index abaee5605..a29128215 100644 --- a/core/scss/ui/_badges.scss +++ b/core/scss/ui/_badges.scss @@ -1,6 +1,7 @@ @use '../config' as *; .badge { + // scss-docs-start badge-css-vars --badge-padding-x: #{$badge-padding-x}; --badge-padding-y: #{$badge-padding-y}; --badge-font-size: #{$badge-font-size}; @@ -10,6 +11,7 @@ --badge-icon-size: 1em; --badge-line-height: 1; --badge-gap: #{$badge-gap}; + // scss-docs-end badge-css-vars display: inline-flex; gap: var(--badge-gap); align-items: center; diff --git a/core/scss/ui/_breadcrumbs.scss b/core/scss/ui/_breadcrumbs.scss index 3a1a96fe6..581510bff 100644 --- a/core/scss/ui/_breadcrumbs.scss +++ b/core/scss/ui/_breadcrumbs.scss @@ -3,6 +3,7 @@ @use '../config' as *; .breadcrumb { + // scss-docs-start breadcrumb-css-vars --breadcrumb-padding-x: #{$breadcrumb-padding-x}; --breadcrumb-padding-y: #{$breadcrumb-padding-y}; --breadcrumb-margin-bottom: #{$breadcrumb-margin-bottom}; @@ -15,6 +16,7 @@ --breadcrumb-item-active-font-weight: #{$breadcrumb-active-font-weight}; --breadcrumb-item-disabled-color: #{$breadcrumb-disabled-color}; --breadcrumb-link-color: #{$breadcrumb-link-color}; + // scss-docs-end breadcrumb-css-vars display: flex; flex-wrap: wrap; diff --git a/core/scss/ui/_cards.scss b/core/scss/ui/_cards.scss index 4698f62f5..f104c44ab 100644 --- a/core/scss/ui/_cards.scss +++ b/core/scss/ui/_cards.scss @@ -712,8 +712,10 @@ Card gradient } .card-gradient { + // scss-docs-start card-gradient-css-vars --card-gradient-direction: 180deg; --card-gradient-opacity: 86%; + // scss-docs-end card-gradient-css-vars --card-gradient: var(--primary), var(--primary); @include card-gradient-background(); diff --git a/core/scss/ui/_chat.scss b/core/scss/ui/_chat.scss index feab01471..d9a1633d6 100644 --- a/core/scss/ui/_chat.scss +++ b/core/scss/ui/_chat.scss @@ -1,9 +1,11 @@ @use '../config' as *; .chat { + // scss-docs-start chat-css-vars --chat-bubbles-gap: #{$chat-bubbles-gap}; --chat-bubble-padding: #{$chat-bubble-padding}; --chat-bubble-title-margin-bottom: #{$chat-bubble-title-margin-bottom}; + // scss-docs-end chat-css-vars } .chat-bubbles { diff --git a/core/scss/ui/_datagrid.scss b/core/scss/ui/_datagrid.scss index 5b9a61d67..7bd5490e2 100644 --- a/core/scss/ui/_datagrid.scss +++ b/core/scss/ui/_datagrid.scss @@ -5,9 +5,11 @@ @use '../config' as *; .datagrid { + // scss-docs-start datagrid-css-vars --datagrid-padding: #{$datagrid-padding}; --datagrid-item-width: #{$datagrid-item-width}; --datagrid-title-margin-bottom: #{$datagrid-title-margin-bottom}; + // scss-docs-end datagrid-css-vars display: grid; grid-template-columns: repeat(auto-fit, minmax(var(--datagrid-item-width), 1fr)); diff --git a/core/scss/ui/_empty.scss b/core/scss/ui/_empty.scss index d9f0a124e..6007cf8fb 100644 --- a/core/scss/ui/_empty.scss +++ b/core/scss/ui/_empty.scss @@ -1,6 +1,7 @@ @use '../config' as *; .empty { + // scss-docs-start empty-css-vars --empty-padding: #{$empty-padding}; --empty-padding-md: #{$empty-padding-md}; --empty-icon-size: #{$empty-icon-size}; @@ -10,6 +11,7 @@ --empty-header-font-size: #{$empty-header-font-size}; --empty-title-margin-bottom: #{$empty-title-margin-bottom}; --empty-action-margin-top: #{$empty-action-margin-top}; + // scss-docs-end empty-css-vars display: flex; flex-direction: column; diff --git a/core/scss/ui/_icons.scss b/core/scss/ui/_icons.scss index b63e7349f..7023ad230 100644 --- a/core/scss/ui/_icons.scss +++ b/core/scss/ui/_icons.scss @@ -4,7 +4,9 @@ @use '../config' as *; .icon { + // scss-docs-start icon-css-vars --icon-size: #{$font-size-base * $line-height-base}; + // scss-docs-end icon-css-vars display: inline-block; width: var(--icon-size); height: var(--icon-size); diff --git a/core/scss/ui/_progress.scss b/core/scss/ui/_progress.scss index d8811200b..0ad899dab 100644 --- a/core/scss/ui/_progress.scss +++ b/core/scss/ui/_progress.scss @@ -128,7 +128,9 @@ Progressbg Progress steps */ .progress-steps { + // scss-docs-start progress-steps-css-vars --progress-steps-gap: #{$progress-steps-gap}; + // scss-docs-end progress-steps-css-vars display: flex; flex-wrap: nowrap; gap: var(--progress-steps-gap); diff --git a/core/scss/ui/_ribbons.scss b/core/scss/ui/_ribbons.scss index 0476c6a3e..a2f0f06be 100644 --- a/core/scss/ui/_ribbons.scss +++ b/core/scss/ui/_ribbons.scss @@ -5,6 +5,7 @@ @use '../config' as *; .ribbon { + // scss-docs-start ribbon-css-vars --ribbon-margin: #{$card-ribbon-margin}; --ribbon-border-radius: #{$card-ribbon-border-radius}; --ribbon-font-size: #{$card-ribbon-font-size}; @@ -17,6 +18,7 @@ --ribbon-top-padding-y: #{$ribbon-top-padding-y}; --ribbon-bookmark-border-size: #{$ribbon-bookmark-border-size}; --ribbon-bookmark-notch-width: #{$ribbon-bookmark-notch-width}; + // scss-docs-end ribbon-css-vars position: absolute; inset-inline-end: calc(-1 * var(--ribbon-margin)); diff --git a/core/scss/ui/_signal.scss b/core/scss/ui/_signal.scss index f42533bf0..f9a3cb19a 100644 --- a/core/scss/ui/_signal.scss +++ b/core/scss/ui/_signal.scss @@ -1,6 +1,7 @@ @use '../config' as *; .signal { + // scss-docs-start signal-css-vars --signal-size: #{$signal-size}; --signal-color: #{$text-secondary}; --signal-bars: 3; @@ -9,6 +10,7 @@ --signal-gap: calc(var(--signal-size) * #{$signal-gap-ratio}); --signal-padding-y: calc(var(--signal-size) * #{$signal-padding-y-ratio}); --signal-padding-x: calc(var(--signal-size) * #{$signal-padding-x-ratio}); + // scss-docs-end signal-css-vars display: inline-flex; flex-shrink: 0; gap: var(--signal-gap); diff --git a/core/scss/ui/_sparkline.scss b/core/scss/ui/_sparkline.scss index 1b043d4be..e420e685f 100644 --- a/core/scss/ui/_sparkline.scss +++ b/core/scss/ui/_sparkline.scss @@ -5,6 +5,7 @@ Sparkline (inline SVG, rendered by js/src/sparkline.ts) @use '../config' as *; .sparkline { + // scss-docs-start sparkline-css-vars --sparkline-width: #{$sparkline-width}; --sparkline-height: #{$sparkline-height}; --sparkline-stroke: #{$sparkline-stroke}; @@ -18,6 +19,7 @@ Sparkline (inline SVG, rendered by js/src/sparkline.ts) --sparkline-spot-size: #{$sparkline-spot-size}; --sparkline-label-font-size: #{$sparkline-label-font-size}; --sparkline-label-color: #{$sparkline-label-color}; + // scss-docs-end sparkline-css-vars position: relative; display: inline-block; diff --git a/core/scss/ui/_stars.scss b/core/scss/ui/_stars.scss index 94945435b..28f2c73a1 100644 --- a/core/scss/ui/_stars.scss +++ b/core/scss/ui/_stars.scss @@ -1,7 +1,9 @@ @use '../config' as *; .stars { + // scss-docs-start stars-css-vars --stars-gap: #{$stars-gap}; + // scss-docs-end stars-css-vars display: inline-flex; font-size: $h5-font-size; diff --git a/core/scss/ui/_status.scss b/core/scss/ui/_status.scss index e46516843..b9d8633a7 100644 --- a/core/scss/ui/_status.scss +++ b/core/scss/ui/_status.scss @@ -44,11 +44,13 @@ // Status // .status { + // scss-docs-start status-css-vars --status-height: #{$status-height}; --status-color: #{$text-secondary}; --status-padding-y: #{$status-padding-y}; --status-padding-x: #{$status-padding-x}; --status-gap: #{$status-gap}; + // scss-docs-end status-css-vars display: inline-flex; gap: var(--status-gap); diff --git a/core/scss/ui/_steps.scss b/core/scss/ui/_steps.scss index 37426d523..9813b5192 100644 --- a/core/scss/ui/_steps.scss +++ b/core/scss/ui/_steps.scss @@ -4,10 +4,12 @@ @use '../config' as *; .steps { + // scss-docs-start steps-css-vars --steps-color: #{$steps-color}; --steps-inactive-color: #{$steps-inactive-color}; --steps-dot-size: #{$steps-dot-size}; --steps-border-width: #{$steps-border-width}; + // scss-docs-end steps-css-vars display: flex; flex-wrap: nowrap; width: 100%; diff --git a/core/scss/ui/_switch-icon.scss b/core/scss/ui/_switch-icon.scss index f211290e5..7cca74d94 100644 --- a/core/scss/ui/_switch-icon.scss +++ b/core/scss/ui/_switch-icon.scss @@ -2,12 +2,14 @@ .switch-icon { @include focus-ring-transition(); + // scss-docs-start switch-icon-css-vars --switch-icon-size: #{$switch-icon-size}; --switch-icon-min-size: #{$switch-icon-min-size}; --switch-icon-disabled-opacity: #{$switch-icon-disabled-opacity}; --switch-icon-loading-size: #{$switch-icon-loading-size}; --switch-icon-loading-border-width: #{$switch-icon-loading-border-width}; --switch-icon-loading-animation-speed: #{$switch-icon-loading-animation-speed}; + // scss-docs-end switch-icon-css-vars position: relative; display: inline-block; width: var(--switch-icon-size); diff --git a/core/scss/ui/_tags.scss b/core/scss/ui/_tags.scss index 10bd09306..76552d3a4 100644 --- a/core/scss/ui/_tags.scss +++ b/core/scss/ui/_tags.scss @@ -1,6 +1,7 @@ @use '../config' as *; .tag { + // scss-docs-start tag-css-vars --tag-height: #{$tag-height}; --tag-padding-x: #{$tag-padding-x}; --tag-gap: #{$tag-gap}; @@ -12,6 +13,7 @@ --tag-icon-margin-end: #{$tag-icon-margin-end}; --tag-icon-size: #{$tag-icon-size}; --tag-check-size: #{$tag-check-size}; + // scss-docs-end tag-css-vars display: inline-flex; gap: var(--tag-gap); align-items: center; diff --git a/core/scss/ui/_timeline.scss b/core/scss/ui/_timeline.scss index 53b975a75..21027d68f 100644 --- a/core/scss/ui/_timeline.scss +++ b/core/scss/ui/_timeline.scss @@ -4,7 +4,9 @@ @use '../config' as *; .timeline { + // scss-docs-start timeline-css-vars --timeline-icon-size: #{$avatar-size}; + // scss-docs-end timeline-css-vars position: relative; padding: 0; list-style: none; diff --git a/core/scss/ui/_tracking.scss b/core/scss/ui/_tracking.scss index fa0a1ed22..74811a723 100644 --- a/core/scss/ui/_tracking.scss +++ b/core/scss/ui/_tracking.scss @@ -1,10 +1,12 @@ @use '../config' as *; .tracking { + // scss-docs-start tracking-css-vars --tracking-height: #{$tracking-height}; --tracking-gap-width: #{$tracking-gap-width}; --tracking-block-border-radius: #{$tracking-border-radius}; --tracking-block-min-width: #{$tracking-block-min-width}; + // scss-docs-end tracking-css-vars display: flex; gap: var(--tracking-gap-width); } diff --git a/core/scss/ui/forms/_form-otp.scss b/core/scss/ui/forms/_form-otp.scss index 293ed50b7..d82bfe2ed 100644 --- a/core/scss/ui/forms/_form-otp.scss +++ b/core/scss/ui/forms/_form-otp.scss @@ -1,6 +1,7 @@ @use '../../config' as *; .otp { + // scss-docs-start otp-css-vars --otp-size: #{$otp-size}; --otp-font-size: #{$otp-font-size}; --otp-gap: #{$otp-gap}; @@ -11,6 +12,7 @@ --otp-slot-border-radius: #{$otp-slot-border-radius}; --otp-slot-box-shadow: #{$otp-slot-box-shadow}; --otp-separator-color: #{$otp-separator-color}; + // scss-docs-end otp-css-vars position: relative; display: inline-flex; gap: var(--otp-gap); diff --git a/docs/components/DocsCssVars.astro b/docs/components/DocsCssVars.astro new file mode 100644 index 000000000..43c55d486 --- /dev/null +++ b/docs/components/DocsCssVars.astro @@ -0,0 +1,64 @@ +--- +// The "CSS" section of a component page, built like the Bootstrap docs: the +// custom-property block and the Sass defaults, both cut from the sources by +// lib/scss-docs.ts. Either part may be missing. +import CodeSnippet from '@components/CodeSnippet.astro' +import { cssSection } from '@lib/scss-docs' +import { site } from '@shared/lib/site' + +interface Props { + /** the custom-property family after `--`, e.g. `badge` for the `badge-css-vars` marker */ + cssVars?: string | undefined + /** the `-variables` marker in `_variables.scss`; defaults to `cssVars` */ + sassVars?: string | undefined +} + +const { cssVars, sassVars } = Astro.props +const { css, sass } = await cssSection(cssVars, sassVars) + +const fileLink = (path: string) => `${site.githubUrl}/blob/dev/${path}` +--- + +{ + (css || sass) && ( + +

CSS

+ + {css && ( + +

Variables

+

+ The component uses local CSS variables + {css.selector && ( + + {' '} + on {css.selector} + + )}{' '} + for real-time customization. Values for the CSS variables are set via Sass, so Sass customization is still supported, too. Source:{' '} + + {css.path} + + . +

+ +
+ )} + + {sass && ( + +

Sass variables

+

+ Override these variables before importing Tabler to change the defaults. Source:{' '} + + {sass.path} + + . +

+ +
+ )} +
+ ) +} + diff --git a/docs/content.config.ts b/docs/content.config.ts index 32756c0c4..c279f3bad 100644 --- a/docs/content.config.ts +++ b/docs/content.config.ts @@ -56,11 +56,24 @@ const docs = defineCollection({ * listed once as `alert-{color}` rather than one row per generated class. */ 'classnames': z.record(z.string(), z.array(classnameEntry)).optional(), + /** + * Custom-property family the component exposes, as the part after + * `--tblr-`: `badge` lists every `--tblr-badge-*` variable. Rendered + * below the class reference by DocsCssVars from the compiled CSS. + */ + 'css-vars': z.string().optional(), + /** + * Sass defaults block: the `-variables` marker in `_variables.scss`. + * Defaults to the `css-vars` name; set it when the marker is named + * differently (`nav` for nav-tabs) or when the component has Sass + * variables but no custom properties of its own. + */ + 'sass-vars': z.string().optional(), }) // strict so a mistyped key fails the build instead of being silently dropped .strict() // kebab-case front matter keys → the camelCase props DocsLayout expects - .transform(({ 'docs-libs': docsLibs, 'css-plugins': cssPlugins, 'hide-pagination': hidePagination, 'added-in': addedIn, ...rest }) => ({ ...rest, docsLibs, cssPlugins, hidePagination, addedIn })), + .transform(({ 'docs-libs': docsLibs, 'css-plugins': cssPlugins, 'hide-pagination': hidePagination, 'added-in': addedIn, 'css-vars': cssVars, 'sass-vars': sassVars, ...rest }) => ({ ...rest, docsLibs, cssPlugins, hidePagination, addedIn, cssVars, sassVars })), }) export const collections = { docs } diff --git a/docs/content/ui/components/accordion.mdx b/docs/content/ui/components/accordion.mdx index 09f7d94ad..f205fea8d 100644 --- a/docs/content/ui/components/accordion.mdx +++ b/docs/content/ui/components/accordion.mdx @@ -4,6 +4,7 @@ summary: Use accordion panels to group related content and show one section at a description: Build collapsible content sections with accordion classes and modifiers. related: [/ui/components/tab] source: core/scss/ui/_accordion.scss +css-vars: accordion classnames: component: - class: accordion @@ -32,7 +33,6 @@ classnames: --- import Example from '@components/Example.astro'; import Icon from '@ui/Icon.astro'; -import CodeDocs from '@components/CodeDocs.astro'; ## Overview @@ -187,9 +187,3 @@ Use `.accordion-inverted` to move the toggle to the start of the button row. Thi - Keep `data-bs-target` and panel `id` values in sync for every toggle. - Keep visible focus styles. The header and button styles include focus handling. - Add meaningful text labels, not only icons. - -## SCSS variables - -Use these SCSS variables to customize the accordion. The default values are: - - diff --git a/docs/content/ui/components/alert.mdx b/docs/content/ui/components/alert.mdx index 17c31fe2d..ceca4b835 100644 --- a/docs/content/ui/components/alert.mdx +++ b/docs/content/ui/components/alert.mdx @@ -4,6 +4,7 @@ summary: Alerts report the result of an action or a problem to fix, in success, description: Show alert messages that inform users about success, info, warning, or danger states, with icons, links, and dismissible options. related: [/ui/components/toast] source: core/scss/ui/_alerts.scss +css-vars: alert classnames: component: - class: alert @@ -33,7 +34,6 @@ classnames: desc: Any base color, for example alert-success, alert-blue or alert-muted --- import Example from '@components/Example.astro'; -import CodeDocs from '@components/CodeDocs.astro'; import Alert from '@ui/Alert.astro'; import Icon from '@ui/Icon.astro' @@ -313,9 +313,3 @@ You're not limited to the 4 default alert colors. You can use any [base color](/ - The icon is decoration, so the alert must still read correctly with the icon ignored. Keep the meaning in the heading and the text. - Colour alone does not say "error". A red alert and a green one must differ in their words too. - After dismissing an alert, move focus somewhere sensible. Focus left on a removed element falls back to the top of the page. - -## SCSS variables - -Use these SCSS variables to customize alerts. The default values are: - - diff --git a/docs/content/ui/components/avatar.mdx b/docs/content/ui/components/avatar.mdx index 3b896afe1..22af4d8ec 100644 --- a/docs/content/ui/components/avatar.mdx +++ b/docs/content/ui/components/avatar.mdx @@ -4,6 +4,7 @@ summary: An avatar shows a user's picture, initials or an icon in a fixed-size c description: Display user avatars with images, initials, or icons. Change sizes, shapes, and colors, or group them into stacked avatar lists. related: [/ui/components/status, /ui/components/badge] source: core/scss/ui/_avatars.scss +css-vars: avatar classnames: component: - class: avatar @@ -38,7 +39,6 @@ classnames: import Example from '@components/Example.astro' import Icon from '@ui/Icon.astro' import AvatarList from '@ui/AvatarList.astro' -import CodeDocs from '@components/CodeDocs.astro' ## Default markup @@ -328,9 +328,3 @@ Add `avatar-list-stacked` to overlap the avatars, which fits a long list of part - Give the upload avatar a clear name. Use the `avatar-upload-text` label, an `aria-label`, or a `form-label` linked with `for`. - Use a `label` with a file input, or a `button`, when the upload avatar does something. A link with `href="#"` is only good for a static demo. - Keep the hidden file input in the markup with the `visually-hidden` class. Do not use `display: none`, because then keyboard users cannot reach it. - -## SCSS variables - -Use these SCSS variables to customize avatars. The default values are: - - diff --git a/docs/content/ui/components/badge.mdx b/docs/content/ui/components/badge.mdx index 207a27b9a..46b5d0df9 100644 --- a/docs/content/ui/components/badge.mdx +++ b/docs/content/ui/components/badge.mdx @@ -4,6 +4,7 @@ summary: A badge is a small label for a count, a status or a tag. It comes in co description: Add badges to show counts, labels, or statuses. Use colors, outline and pill styles, links, and notification dots to add context. related: [/ui/components/status, /ui/components/tag] source: core/scss/ui/_badges.scss +css-vars: badge classnames: component: - class: badge @@ -39,7 +40,6 @@ import BadgeList from '@ui/BadgeList.astro'; import Example from '@components/Example.astro'; import Badge from '@ui/Badge.astro'; import site from '@data/site.json'; -import CodeDocs from '@components/CodeDocs.astro'; export const colors = Object.entries(site.colors); @@ -238,9 +238,3 @@ A badge is text, not a control, so most of the work is in the words you put in i ## More examples More examples are in the [Bootstrap documentation](https://getbootstrap.com/docs/5.3/components/badge/) and on the Tabler [Badges](https://preview.tabler.io/badges.html) preview page. - -## SCSS variables - -Use these SCSS variables to customize badges. The default values are: - - diff --git a/docs/content/ui/components/breadcrumb.mdx b/docs/content/ui/components/breadcrumb.mdx index 58118aa0f..0ae25e035 100644 --- a/docs/content/ui/components/breadcrumb.mdx +++ b/docs/content/ui/components/breadcrumb.mdx @@ -4,6 +4,7 @@ summary: A breadcrumb shows where the current page sits in the site hierarchy an description: A breadcrumb trail for the current page location, with separators, icons and a muted style. related: [/ui/components/pagination] source: core/scss/ui/_breadcrumbs.scss +css-vars: breadcrumb classnames: component: - class: breadcrumb @@ -24,7 +25,6 @@ classnames: import Example from '@components/Example.astro'; import Breadcrumb from '@ui/Breadcrumb.astro'; import Icon from '@ui/Icon.astro'; -import CodeDocs from '@components/CodeDocs.astro'; ## Default markup @@ -72,9 +72,3 @@ A breadcrumb usually sits in the [page header](/ui/layout/page-headers), above t - Mark the last item with `aria-current="page"`. It is the current page, so it should not be a link. - Keep the separator in CSS, as Tabler does. A separator typed into the markup is read out between every item. - Breadcrumbs show where a page sits, not where the user has been. Keep them the same for everyone who lands on that page. - -## SCSS variables - -Use these SCSS variables to customize breadcrumbs. The default values are: - - diff --git a/docs/content/ui/components/button.mdx b/docs/content/ui/components/button.mdx index 5033777aa..e81f68a72 100644 --- a/docs/content/ui/components/button.mdx +++ b/docs/content/ui/components/button.mdx @@ -4,6 +4,7 @@ summary: Buttons in every color, size and shape, with icons, loading states, soc description: Buttons in many colors, sizes and shapes, with icons, loading states and social styles. related: [/ui/components/dropdown] source: core/scss/ui/_buttons.scss +css-vars: btn classnames: component: - class: btn @@ -56,7 +57,6 @@ classnames: import ButtonGroup from '@ui/ButtonGroup.astro'; import Example from '@components/Example.astro'; import Icon from '@ui/Icon.astro'; -import CodeDocs from '@components/CodeDocs.astro'; ## Button tag @@ -572,9 +572,3 @@ Add `btn-floating` to pin a button to the bottom corner of the viewport. It stay - Pair `btn-loading` with `aria-busy="true"` and a disabled state. The class blanks the label with `color: transparent`, so the old text is still what a screen reader reads. - A button that toggles something needs `aria-pressed`; one that opens a panel needs `aria-expanded`. Bootstrap sets `aria-expanded` for you on dropdown and collapse triggers. - Color alone should not carry the meaning. `btn-danger` looks alarming, but the label has to say what happens. - -## SCSS variables - -Use these SCSS variables to customize buttons. The default values are: - - diff --git a/docs/content/ui/components/card-gradient.mdx b/docs/content/ui/components/card-gradient.mdx index baad4ca6e..9e082025c 100644 --- a/docs/content/ui/components/card-gradient.mdx +++ b/docs/content/ui/components/card-gradient.mdx @@ -5,6 +5,7 @@ description: Gradient card backgrounds, with color variants, multi-color themes, added-in: '1.5.0' related: [/ui/components/card] source: core/scss/ui/_cards.scss +css-vars: card-gradient classnames: component: - class: card-gradient diff --git a/docs/content/ui/components/card.mdx b/docs/content/ui/components/card.mdx index bd13804af..201677aeb 100644 --- a/docs/content/ui/components/card.mdx +++ b/docs/content/ui/components/card.mdx @@ -4,6 +4,7 @@ summary: A card groups related content in one box, with an optional header, foot description: Organize content with flexible cards. Combine headers, footers, images, and actions to build sections that work on any screen size. related: [/ui/components/card-gradient, /ui/components/ribbon] source: core/scss/ui/_cards.scss +css-vars: card classnames: component: - class: card @@ -78,7 +79,6 @@ classnames: --- import Example from '@components/Example.astro'; import Icon from '@ui/Icon.astro'; -import CodeDocs from '@components/CodeDocs.astro'; ## Default card @@ -674,9 +674,3 @@ A card is a container, so it has no role of its own. What matters is the content - For a clickable card, wrap the card in a single `a` element (`card-link`) rather than putting a link only on the title. That way the whole card is one tab stop with one accessible name. - `card-status-*` and `card-stamp` are decoration. Never let a color or an icon be the only carrier of meaning - repeat it in text, for example with a [status](/ui/components/status) or a [badge](/ui/components/badge). - Give an image inside a card a real `alt` when it carries information, or an empty `alt=""` when it is decorative. - -## SCSS variables - -Use these SCSS variables to customize cards. The default values are: - - diff --git a/docs/content/ui/components/carousel.mdx b/docs/content/ui/components/carousel.mdx index e824ea058..ad853ba3a 100644 --- a/docs/content/ui/components/carousel.mdx +++ b/docs/content/ui/components/carousel.mdx @@ -4,6 +4,7 @@ summary: A carousel shows several images or slides in the space of one, with arr description: Display multiple images or slides in a compact carousel with indicators, captions, and automatic or manual navigation. related: [/ui/plugins/inline-player] source: core/scss/ui/_carousel.scss +sass-vars: carousel classnames: component: - class: carousel @@ -35,7 +36,6 @@ classnames: desc: Crossfades between slides instead of sliding --- import Example from '@components/Example.astro'; -import CodeDocs from '@components/CodeDocs.astro'; ## Default markup @@ -276,9 +276,3 @@ The captions in this example have the `d-none` class, so they are hidden below t - A carousel that plays on its own moves content out from under the reader. Set `data-bs-ride="false"` or give users a pause control. Bootstrap already pauses on hover and on focus. - Keep the slide images meaningful in `alt` text, or leave `alt=""` when the caption already says the same thing. - Bootstrap does not move focus when a slide changes, so anything focusable inside a hidden slide is still reachable by keyboard. Keep interactive content out of carousel slides. - -## SCSS variables - -Use these SCSS variables to customize the carousel. The default values are: - - diff --git a/docs/content/ui/components/chat.mdx b/docs/content/ui/components/chat.mdx index a961978db..b4af1639a 100644 --- a/docs/content/ui/components/chat.mdx +++ b/docs/content/ui/components/chat.mdx @@ -3,6 +3,7 @@ title: Chat summary: A chat shows a conversation as a column of message bubbles. Use it for direct messages, support conversations, or comments on a record. description: Build a conversation view with message bubbles, sender info, and a typing indicator. source: core/scss/ui/_chat.scss +css-vars: chat classnames: component: - class: chat diff --git a/docs/content/ui/components/datagrid.mdx b/docs/content/ui/components/datagrid.mdx index 054c9bb82..ec9d365c4 100644 --- a/docs/content/ui/components/datagrid.mdx +++ b/docs/content/ui/components/datagrid.mdx @@ -4,6 +4,7 @@ summary: Use the data grid component to display detailed information about your description: Display labeled data in a compact datagrid - a responsive list of title and content pairs, ideal for detail panels and summaries. related: [/ui/components/table] source: core/scss/ui/_datagrid.scss +css-vars: datagrid classnames: component: - class: datagrid @@ -13,7 +14,6 @@ classnames: desc: Label above a value --- import Example from '@components/Example.astro'; -import CodeDocs from '@components/CodeDocs.astro'; import Icon from '@ui/Icon.astro' ## Overview @@ -146,9 +146,3 @@ The pairs are only visually connected, so tell assistive tech how they belong to - -## SCSS variables - -Use these SCSS variables to customize the datagrid. The default values are: - - diff --git a/docs/content/ui/components/dropdown.mdx b/docs/content/ui/components/dropdown.mdx index 50a6b2043..e3f3941f2 100644 --- a/docs/content/ui/components/dropdown.mdx +++ b/docs/content/ui/components/dropdown.mdx @@ -4,6 +4,7 @@ summary: A dropdown is a menu that opens from a button or a link and holds optio description: Build dropdown menus that reveal lists of options or actions. Add icons, dividers, and custom triggers to keep the interface clean. related: [/ui/components/button, /ui/components/popover] source: core/scss/ui/_dropdowns.scss +css-vars: dropdown classnames: component: - class: dropdown @@ -299,9 +300,3 @@ Tabler automatically initializes all elements with `data-bs-toggle="dropdown"` o - An icon-only trigger needs an `aria-label`. The caret is decorative and carries no name. - A dropdown used as a menu of actions should hold `button` items; one used for navigation should hold links. Mixing them makes the keyboard behavior inconsistent. - Use `.dropdown-header` for a group title and `.dropdown-divider` for a separator, rather than styling a plain item to look like one. - -## SCSS variables - -Use these SCSS variables to customize dropdowns. The default values are: - - diff --git a/docs/content/ui/components/empty.mdx b/docs/content/ui/components/empty.mdx index 96e4da5e9..f77d43018 100644 --- a/docs/content/ui/components/empty.mdx +++ b/docs/content/ui/components/empty.mdx @@ -4,6 +4,7 @@ summary: An empty state is the placeholder shown when there is no content yet, o description: Empty states for first use, no results and error screens, with an image, text and an action. related: [/ui/components/placeholder] source: core/scss/ui/_empty.scss +css-vars: empty classnames: component: - class: empty @@ -27,7 +28,6 @@ classnames: --- import Example from '@components/Example.astro'; import Illustration from '@ui/Illustration.astro'; -import CodeDocs from '@components/CodeDocs.astro'; import Icon from '@ui/Icon.astro' ## Default markup @@ -84,9 +84,3 @@ You can also leave out the icon or illustration and show only the text: - The illustration is decoration. Give it `alt=""`, or `aria-hidden="true"` when it is an inline SVG. - The action in `empty-action` should be a real `button` or link, so it can be reached with the keyboard. - When the empty state replaces content after a search, announce it from a live region - otherwise a screen reader user gets no signal that the list changed. - -## SCSS variables - -Use these SCSS variables to customize the empty state. The default values are: - - diff --git a/docs/content/ui/components/icon.mdx b/docs/content/ui/components/icon.mdx index b3274773f..8d44818e4 100644 --- a/docs/content/ui/components/icon.mdx +++ b/docs/content/ui/components/icon.mdx @@ -4,6 +4,7 @@ summary: Tabler Icons are over 5,000 MIT-licensed SVG icons drawn for Tabler. Th description: Use over 5000 free MIT-licensed Tabler icons in your interface. Learn how to embed, size, and animate them in your projects. related: [/ui/components/switch-icon] source: core/scss/ui/_icons.scss +css-vars: icon classnames: component: - class: icon diff --git a/docs/content/ui/components/list-group.mdx b/docs/content/ui/components/list-group.mdx index 37b2377a5..22c47d6ca 100644 --- a/docs/content/ui/components/list-group.mdx +++ b/docs/content/ui/components/list-group.mdx @@ -3,6 +3,7 @@ title: List group summary: A list group shows a series of related items in one block. Use it for simple lists, navigation, settings, and lists of records inside a card. description: Group related items in one list. source: core/scss/ui/_lists.scss +css-vars: list-group classnames: component: - class: list-group diff --git a/docs/content/ui/components/modal.mdx b/docs/content/ui/components/modal.mdx index f1459a754..44657b429 100644 --- a/docs/content/ui/components/modal.mdx +++ b/docs/content/ui/components/modal.mdx @@ -4,6 +4,7 @@ summary: A modal is a dialog shown over the page, for a confirmation, a form or description: Create modal dialogs for notifications, confirmations, and custom content. Adjust sizes, add forms, and control them with JavaScript. related: [/ui/components/offcanvas, /ui/components/toast] source: core/scss/ui/_modals.scss +css-vars: modal classnames: component: - class: modal @@ -45,7 +46,6 @@ classnames: --- import Example from '@components/Example.astro'; import Icon from '@ui/Icon.astro'; -import CodeDocs from '@components/CodeDocs.astro'; Modals are built with HTML, CSS, and JavaScript. They're positioned over everything else in the document and remove scroll from the `` so that modal content scrolls instead. @@ -286,9 +286,3 @@ The Bootstrap size classes cover the smaller steps: `modal-sm`, `modal-lg` and ` - Keep the close button labelled. `btn-close` has no text of its own, so it needs `aria-label="Close"`. - Do not open a modal from inside another modal. Bootstrap supports one dialog at a time and the focus trap follows the last one opened. - A modal that must not be dismissed by accident takes `data-bs-backdrop="static"`, but never remove the close control entirely - Escape is the only way out for many keyboard users. - -## SCSS variables - -Use these SCSS variables to customize modals. The default values are: - - diff --git a/docs/content/ui/components/offcanvas.mdx b/docs/content/ui/components/offcanvas.mdx index 0f0b53849..6f8135411 100644 --- a/docs/content/ui/components/offcanvas.mdx +++ b/docs/content/ui/components/offcanvas.mdx @@ -4,6 +4,7 @@ summary: An offcanvas is a hidden panel that slides in from the edge of the scre description: Sidebar panel that slides in from the edge of the viewport. related: [/ui/components/modal] source: core/scss/ui/_offcanvas.scss +css-vars: offcanvas classnames: component: - class: offcanvas @@ -36,7 +37,6 @@ classnames: desc: Behaves as an offcanvas below the breakpoint and as normal content above it --- import Example from '@components/Example.astro'; -import CodeDocs from '@components/CodeDocs.astro'; An offcanvas is a panel that slides in from the left, right, top or bottom edge of the viewport. A button or a link with `data-bs-toggle="offcanvas"` and `data-bs-target` opens it, and Bootstrap's JavaScript does the rest. @@ -93,9 +93,3 @@ An offcanvas at the bottom edge also works as a cookie banner. It stays visible - The toggle needs `aria-controls` with the panel id. Bootstrap manages `aria-expanded` itself. - Escape closes the panel unless you set `data-bs-keyboard="false"`. If you do turn it off, keep a visible close button. - Content behind the panel stays in the DOM. Use the backdrop, which Bootstrap marks `aria-hidden`, rather than building your own overlay. - -## SCSS variables - -Use these SCSS variables to customize the offcanvas component. The default values are: - - diff --git a/docs/content/ui/components/pagination.mdx b/docs/content/ui/components/pagination.mdx index 643fe16dd..7e7f73339 100644 --- a/docs/content/ui/components/pagination.mdx +++ b/docs/content/ui/components/pagination.mdx @@ -4,6 +4,7 @@ summary: Pagination splits a long list or table into pages and lets the user mov description: Navigate through multi-page content with pagination controls. related: [/ui/components/table] source: core/scss/ui/_pagination.scss +css-vars: pagination classnames: component: - class: pagination @@ -30,7 +31,6 @@ classnames: --- import Example from '@components/Example.astro'; import Pagination from '@ui/Pagination.astro'; -import CodeDocs from '@components/CodeDocs.astro'; ## Basic example @@ -110,9 +110,3 @@ For prose - documentation, a blog, a wizard - name the pages instead of numberin - Previous and next arrows need text. Keep a `visually-hidden` label, or set `aria-label="Previous"` on the link. - A disabled step is still a link. Add `aria-disabled="true"` and `tabindex="-1"` so it is skipped, since the `disabled` class only changes how it looks. - The page numbers alone do not say what they page through. Say it in the `nav` label, for example `aria-label="Search results pages"`. - -## SCSS variables - -Use these SCSS variables to customize pagination. The default values are: - - diff --git a/docs/content/ui/components/placeholder.mdx b/docs/content/ui/components/placeholder.mdx index dad3c3e7f..6b50fc1e4 100644 --- a/docs/content/ui/components/placeholder.mdx +++ b/docs/content/ui/components/placeholder.mdx @@ -4,6 +4,7 @@ summary: Placeholder is used to reserve space for content that will soon appear description: Reserve space for loading content with placeholders. Use lines, headings, and image blocks to build skeleton screens. related: [/ui/components/spinner, /ui/components/empty] source: core/scss/ui/_placeholder.scss +sass-vars: placeholder classnames: component: - class: placeholder @@ -22,7 +23,6 @@ classnames: desc: Large, for a heading line --- import Example from '@components/Example.astro'; -import CodeDocs from '@components/CodeDocs.astro'; ## Placeholder line @@ -254,9 +254,3 @@ See in the following examples how else you can use the placeholder component - Announce the load from one live region, not from the placeholder itself. Otherwise every skeleton block is announced separately. - `placeholder-glow` and `placeholder-wave` animate continuously. Turn them off under `prefers-reduced-motion`. - Keep the layout of the placeholder close to the real content, so nothing jumps when it arrives. - -## SCSS variables - -Use these SCSS variables to customize placeholders. The default values are: - - diff --git a/docs/content/ui/components/popover.mdx b/docs/content/ui/components/popover.mdx index 47506660c..ad5ce12e5 100644 --- a/docs/content/ui/components/popover.mdx +++ b/docs/content/ui/components/popover.mdx @@ -4,6 +4,7 @@ summary: A popover is a small overlay with a title and a body, for information t description: Show extra information in popovers when a tooltip is not enough. Set the placement and trigger to control how they appear. related: [/ui/components/tooltip, /ui/components/dropdown] source: core/scss/ui/_popovers.scss +css-vars: popover classnames: component: - class: popover @@ -61,9 +62,3 @@ Tabler automatically initializes all elements with `data-bs-toggle="popover"` on - Set `data-bs-trigger="focus"` to make a popover dismiss itself when focus leaves, which is the closest thing to Escape behavior. - Tabler enables HTML in a popover only when you set `data-bs-html="true"`. Leave it off for content that comes from users. - Keep the title and body as text. A popover holding a form is a modal in disguise - use a real dialog for that. - -## SCSS variables - -Use these SCSS variables to customize popovers. The default values are: - - diff --git a/docs/content/ui/components/progress-step.mdx b/docs/content/ui/components/progress-step.mdx index 2f231b232..5b58e162d 100644 --- a/docs/content/ui/components/progress-step.mdx +++ b/docs/content/ui/components/progress-step.mdx @@ -5,6 +5,7 @@ description: Use progress steps to display compact onboarding, checkout, and set added-in: '1.5.0' related: [/ui/components/progress, /ui/components/step] source: core/scss/ui/_progress.scss +css-vars: progress-steps classnames: component: - class: progress-steps diff --git a/docs/content/ui/components/progress.mdx b/docs/content/ui/components/progress.mdx index f16b8365d..398ffe375 100644 --- a/docs/content/ui/components/progress.mdx +++ b/docs/content/ui/components/progress.mdx @@ -4,6 +4,7 @@ summary: A progress bar shows how much of a task is done. It comes in sizes and description: Show task progress with progress bars. Set the value, color, and size, or use indeterminate bars for unknown durations. related: [/ui/components/progress-step, /ui/components/spinner] source: core/scss/ui/_progress.scss +css-vars: progress classnames: component: - class: progress @@ -37,7 +38,6 @@ classnames: --- import Example from '@components/Example.astro'; import ProgressBg from '@ui/ProgressBg.astro'; -import CodeDocs from '@components/CodeDocs.astro'; ## Default markup @@ -241,9 +241,3 @@ You can combine progress background with contextual light colors to better separ - An indeterminate bar keeps the role but has no `aria-valuenow`. A missing value is what says "unknown", while `aria-valuenow="0"` says "nothing done yet". - Repeat the number in text next to the bar, or in a `visually-hidden` span inside it. Not every screen reader announces a progress value on its own. - Do not update the value on every frame. Screen readers announce each change, so round to whole percentages and update only when the number really moves. - -## SCSS variables - -Use these SCSS variables to customize progress bars. The default values are: - - diff --git a/docs/content/ui/components/ribbon.mdx b/docs/content/ui/components/ribbon.mdx index f717e398f..223e579e2 100644 --- a/docs/content/ui/components/ribbon.mdx +++ b/docs/content/ui/components/ribbon.mdx @@ -4,6 +4,7 @@ summary: A ribbon is a small label pinned to the edge of a card, for a status su description: Highlight cards and other elements with ribbons. Change the position, color, and shape, or add icons and text to draw attention. related: [/ui/components/card, /ui/components/badge] source: core/scss/ui/_ribbons.scss +css-vars: ribbon classnames: component: - class: ribbon @@ -24,7 +25,6 @@ classnames: --- import Example from '@components/Example.astro'; import Icon from '@ui/Icon.astro'; -import CodeDocs from '@components/CodeDocs.astro'; ## Default markup @@ -94,9 +94,3 @@ Add `.ribbon-bookmark` to give the ribbon a bookmark shape. - Read the ribbon in the order it appears in the markup, not where it lands visually. Put it near the card title so it is announced with it. - An icon-only ribbon needs an `aria-label`, or a `visually-hidden` word next to the icon. - The ribbon sits on top of the card, so check the contrast against whatever is behind it - a photo needs an overlay. - -## SCSS variables - -Use these SCSS variables to customize ribbons. The default values are: - - diff --git a/docs/content/ui/components/segmented-control.mdx b/docs/content/ui/components/segmented-control.mdx index c13f400b6..58c4a64aa 100644 --- a/docs/content/ui/components/segmented-control.mdx +++ b/docs/content/ui/components/segmented-control.mdx @@ -3,6 +3,7 @@ title: Segmented control summary: A segmented control is a row of two or more mutually exclusive options styled as one bar, an alternative to radios or tabs. description: A set of mutually exclusive options displayed as connected buttons. source: core/scss/ui/_segmented.scss +sass-vars: segmented-nav classnames: component: - class: nav-segmented @@ -18,7 +19,6 @@ classnames: --- import Example from '@components/Example.astro'; import Icon from '@ui/Icon.astro'; -import CodeDocs from '@components/CodeDocs.astro'; A segmented control is a `nav` element with the `nav-segmented` class, holding `button` or `a` elements with the `nav-link` class. @@ -123,9 +123,3 @@ Add `nav-sm` or `nav-lg` to the `nav` to change the size. - A segmented control that picks a value is a radio group instead. Use `input type="radio"` with a shared name inside a `fieldset` with a `legend`, so the arrow keys work and the group has a name. - Do not mix the two. Buttons with `aria-pressed` in a row read as separate toggles, which is not what a segmented control looks like. - Every segment needs a text label. An emoji or an icon alone leaves the option unnamed - add an `aria-label` if the design has no room for words. - -## SCSS variables - -Use these SCSS variables to customize the segmented control. The default values are: - - diff --git a/docs/content/ui/components/signal.mdx b/docs/content/ui/components/signal.mdx index 83abfdd7d..404ec8f62 100644 --- a/docs/content/ui/components/signal.mdx +++ b/docs/content/ui/components/signal.mdx @@ -4,6 +4,7 @@ summary: A small row of stepped bars that shows a level, such as priority, stren description: Show priority, strength or any other level with the signal component - a compact row of stepped bars that sits next to a label. related: [/ui/components/status, /ui/components/tracking] source: core/scss/ui/_signal.scss +css-vars: signal classnames: component: - class: signal @@ -19,7 +20,6 @@ classnames: desc: Colors the bars with a theme color --- import Example from '@components/Example.astro'; -import CodeDocs from '@components/CodeDocs.astro'; ## Basic example @@ -155,9 +155,3 @@ A signal is a `1.25rem` square, the same as an extra small [avatar](/ui/componen - Show the level with the number of lit bars, not with color alone. An orange and a green signal with the same bars look identical to many users. - A signal without a text label needs `role="img"` and an `aria-label` that states the level, for example "Priority: urgent". - A signal next to a label that already names the level is decoration. Hide it with `aria-hidden="true"`. - -## SCSS variables - -Use these SCSS variables to customize the signal component. The default values are: - - diff --git a/docs/content/ui/components/sparkline.mdx b/docs/content/ui/components/sparkline.mdx index 6e6668a72..af6177207 100644 --- a/docs/content/ui/components/sparkline.mdx +++ b/docs/content/ui/components/sparkline.mdx @@ -5,6 +5,7 @@ description: Show a trend inline with a sparkline. Line, bar and circle charts r added-in: '1.6.0' related: [/ui/plugins/chart, /ui/components/trending, /ui/components/progress] source: core/scss/ui/_sparkline.scss +css-vars: sparkline classnames: component: - class: sparkline diff --git a/docs/content/ui/components/spinner.mdx b/docs/content/ui/components/spinner.mdx index 2b081c1f2..8f49206dd 100644 --- a/docs/content/ui/components/spinner.mdx +++ b/docs/content/ui/components/spinner.mdx @@ -4,6 +4,7 @@ summary: A spinner is used to show the loading state of a component or page. It description: Show loading states with spinners. Choose between border and growing styles, change colors and sizes, or place them in buttons. related: [/ui/components/progress, /ui/components/placeholder] source: core/scss/ui/_loaders.scss +css-vars: spinner classnames: component: - class: spinner-border @@ -20,7 +21,6 @@ classnames: desc: Small dot --- import Example from '@components/Example.astro'; -import CodeDocs from '@components/CodeDocs.astro'; ## Default markup @@ -121,9 +121,3 @@ Animated dots also fit inside a button while its action runs. - Put the spinner where the content will appear, and set `aria-busy="true"` on that container while it loads. - Do not use a spinner for something that takes under a second. It flashes, which is more distracting than no feedback at all. - For a wait with a known length use a [progress bar](/ui/components/progress) instead - it tells users how much is left. - -## SCSS variables - -Use these SCSS variables to customize spinners. The default values are: - - diff --git a/docs/content/ui/components/star-rating.mdx b/docs/content/ui/components/star-rating.mdx index 9ff59c554..f7e3bdbfa 100644 --- a/docs/content/ui/components/star-rating.mdx +++ b/docs/content/ui/components/star-rating.mdx @@ -4,6 +4,7 @@ summary: Use star rating to show score values with static stars or interactive s description: Build star rating UI with stars classes and data-star-rating state selectors. related: [/ui/components/icon] source: core/scss/ui/_stars.scss +css-vars: stars classnames: component: - class: stars diff --git a/docs/content/ui/components/status.mdx b/docs/content/ui/components/status.mdx index e219db082..fb30b783b 100644 --- a/docs/content/ui/components/status.mdx +++ b/docs/content/ui/components/status.mdx @@ -4,6 +4,7 @@ summary: A status dot is particularly useful when you want to make an interface description: Highlight the state of an element with a small status badge. Add colors, dots, and animations to make statuses easy to scan. related: [/ui/components/badge] source: core/scss/ui/_status.scss +css-vars: status classnames: component: - class: status @@ -28,7 +29,6 @@ classnames: desc: Any base color, for example status-green or status-danger --- import Example from '@components/Example.astro'; -import CodeDocs from '@components/CodeDocs.astro'; ## Default markup @@ -222,9 +222,3 @@ Use the status indicator to show the status of a component or page. The status i - Do not use red and green alone to separate "down" from "up". Pair the color with a word, an icon, or a shape. - An animated dot repeats forever. Keep it for a state that really is live, and respect `prefers-reduced-motion` in your own CSS if you add more motion. - When a status changes while the page is open, announce it from a live region elsewhere rather than making the dot itself one. - -## SCSS variables - -Use these SCSS variables to customize statuses. The default values are: - - diff --git a/docs/content/ui/components/step.mdx b/docs/content/ui/components/step.mdx index 6b1141a1d..9b35bda0b 100644 --- a/docs/content/ui/components/step.mdx +++ b/docs/content/ui/components/step.mdx @@ -4,6 +4,7 @@ summary: "Steps show where the user is in a multi-step process: which steps are description: Guide users through multi-step processes with a steps indicator. Show completed, active, and upcoming steps in a clear layout. related: [/ui/components/progress-step] source: core/scss/ui/_steps.scss +css-vars: steps classnames: component: - class: steps @@ -22,7 +23,6 @@ classnames: desc: Any base color for the completed steps; -lt variants use the soft tint --- import Example from '@components/Example.astro'; -import CodeDocs from '@components/CodeDocs.astro'; ## Default markup @@ -140,9 +140,3 @@ Numbered steps in another color: - Mark the current step with `aria-current="step"`. The `active` class only changes the colors. - A completed step and a pending one must differ in more than color - a checkmark, a label, or the word "done". - If a step is a link, keep it a real link; if it cannot be reached yet, use `aria-disabled="true"` and take it out of the tab order. - -## SCSS variables - -Use these SCSS variables to customize steps. The default values are: - - diff --git a/docs/content/ui/components/switch-icon.mdx b/docs/content/ui/components/switch-icon.mdx index 16e0689da..eee9c1640 100644 --- a/docs/content/ui/components/switch-icon.mdx +++ b/docs/content/ui/components/switch-icon.mdx @@ -4,6 +4,7 @@ summary: The Switch Icon component is used to create a transition between two ic description: Animate a smooth transition between two icons with the switch icon component - useful for likes, toggles, and favorite buttons. related: [/ui/components/icon] source: core/scss/ui/_switch-icon.scss +css-vars: switch-icon classnames: component: - class: switch-icon diff --git a/docs/content/ui/components/tab.mdx b/docs/content/ui/components/tab.mdx index f39301644..dd134dcf5 100644 --- a/docs/content/ui/components/tab.mdx +++ b/docs/content/ui/components/tab.mdx @@ -4,6 +4,8 @@ summary: Tabs switch between related views in the same place on the page. They c description: Split content into tabs so users can switch between related views in the same context. Add icons or style the tabs as pills. related: [/ui/layout/navs-tabs] source: core/scss/ui/_nav.scss +css-vars: nav-tabs +sass-vars: nav classnames: component: - class: nav-tabs diff --git a/docs/content/ui/components/table.mdx b/docs/content/ui/components/table.mdx index 559e7e121..c7d16ce05 100644 --- a/docs/content/ui/components/table.mdx +++ b/docs/content/ui/components/table.mdx @@ -4,6 +4,7 @@ summary: Tables lay out rows of data, with variants for responsive, sortable, se description: Present data in clean, responsive tables. Add hover states, striped rows, sorting, and other options to improve readability. related: [/ui/components/datagrid, /ui/components/pagination] source: core/scss/ui/_tables.scss +css-vars: table classnames: component: - class: table @@ -46,7 +47,6 @@ classnames: desc: Tighter cell padding --- import Example from '@components/Example.astro'; -import CodeDocs from '@components/CodeDocs.astro'; ## Basic table @@ -525,9 +525,3 @@ A table is read cell by cell, so the structure has to say what each cell means. - In a selectable table, give every checkbox its own `aria-label` - "Select task" is enough, and "Select all rows" for the one in the header. - `.table-sort` is a real `button`, so it is reachable by keyboard. Once you wire up sorting, also set `aria-sort` on the `th` to `ascending`, `descending` or `none`. - A stacked `.table-mobile-*` still keeps its header cells in the markup, so the reading order stays correct while the `data-label` text carries the column name visually. - -## SCSS variables - -Use these SCSS variables to customize tables. The default values are: - - diff --git a/docs/content/ui/components/tag.mdx b/docs/content/ui/components/tag.mdx index e23475cee..44a3fb140 100644 --- a/docs/content/ui/components/tag.mdx +++ b/docs/content/ui/components/tag.mdx @@ -5,6 +5,7 @@ description: Build inline tag elements with the tag class API and related tag pa css-plugins: [flags, payments] related: [/ui/components/badge] source: core/scss/ui/_tags.scss +css-vars: tag classnames: component: - class: tag @@ -30,7 +31,6 @@ import Flag from '@ui/Flag.astro'; import Icon from '@ui/Icon.astro'; import Payment from '@ui/Payment.astro'; import TagList from '@ui/TagList.astro'; -import CodeDocs from '@components/CodeDocs.astro'; ## Overview @@ -114,9 +114,3 @@ Use `.tag-list` for groups of tags. It applies consistent spacing and wrapping b - Add `aria-label` to every close button action. - Keep selectable tags keyboard reachable when using `.tag-check`. - Ensure icon or media tags still include readable text. - -## SCSS variables - -Use these SCSS variables to customize tags. The default values are: - - diff --git a/docs/content/ui/components/timeline.mdx b/docs/content/ui/components/timeline.mdx index 611f00553..888d17c0b 100644 --- a/docs/content/ui/components/timeline.mdx +++ b/docs/content/ui/components/timeline.mdx @@ -4,6 +4,7 @@ summary: A timeline is a list of events in order, with an icon or an avatar per description: Visualize events in chronological order with a timeline. Add icons, avatars, and text to present processes and activity feeds. related: [/ui/components/step, /ui/components/status] source: core/scss/ui/_timeline.scss +css-vars: timeline classnames: component: - class: timeline @@ -111,11 +112,3 @@ A timeline is a list of events, so keep the list markup and let the content say - Put the event time in a `time` element with a `datetime` attribute, so "2 hrs ago" also has an exact value. - The icon is decoration on top of the text. Keep the meaning in the heading or the body of the card, never in the icon color alone. - Keep heading levels in order. `h4` inside a card only sets the look - pick the level that fits the page outline. - -## SCSS variables - -The size of the icon marker comes from one custom property, which also sets where the line is drawn: - -| Variable | Description | Default | -|-----------------------------|------------------------------------|----------| -| `--tblr-timeline-icon-size` | Width and height of the event icon | `2.5rem` | diff --git a/docs/content/ui/components/toast.mdx b/docs/content/ui/components/toast.mdx index 34aa59b30..5bdc1a83b 100644 --- a/docs/content/ui/components/toast.mdx +++ b/docs/content/ui/components/toast.mdx @@ -4,6 +4,7 @@ summary: A toast is a small message shown for a few seconds after an action, for description: Display a lightweight alert notification with a toast. related: [/ui/components/alert, /ui/components/modal] source: core/scss/ui/_toasts.scss +css-vars: toast classnames: component: - class: toast @@ -94,9 +95,3 @@ Tabler binds every element with `data-bs-toggle="toast"` to show the toast from - Toasts hide themselves after `data-bs-delay`. That is too fast to read for many people - set `data-bs-autohide="false"` for anything the user has to act on. - A toast does not take focus. Never put the only copy of an action inside one. - Label the close button with `aria-label="Close"`. - -## SCSS variables - -Use these SCSS variables to customize toasts. The default values are: - - diff --git a/docs/content/ui/components/tooltip.mdx b/docs/content/ui/components/tooltip.mdx index 0bef1656a..c301069cf 100644 --- a/docs/content/ui/components/tooltip.mdx +++ b/docs/content/ui/components/tooltip.mdx @@ -4,6 +4,7 @@ summary: A tooltip is a short text label that appears when the user hovers over description: Explain interface elements with tooltips shown on hover. Control placement, add HTML content, and customize how they appear. related: [/ui/components/popover] source: core/scss/bootstrap/_tooltip.scss +css-vars: tooltip classnames: component: - class: tooltip @@ -51,9 +52,3 @@ Tabler automatically initializes all elements with `data-bs-toggle="tooltip"` on - Never put a link, a button or a form field inside a tooltip. It disappears on blur, so nothing in it can be reached. Use a popover for that. - Tabler enables HTML in a tooltip only when you set `data-bs-html="true"`. Leave it off for text that comes from users. - Do not attach a tooltip to a disabled control - a disabled element fires no events. Wrap it in a focusable span instead. - -## SCSS variables - -Use these SCSS variables to customize tooltips. The default values are: - - diff --git a/docs/content/ui/components/tracking.mdx b/docs/content/ui/components/tracking.mdx index 26919bc8f..e5a587fcc 100644 --- a/docs/content/ui/components/tracking.mdx +++ b/docs/content/ui/components/tracking.mdx @@ -4,6 +4,7 @@ summary: A row of colored blocks that shows one status per time period, for upti description: Visualize activity logs and uptime data with the tracking component - a compact row of colored blocks that show status over time. related: [/ui/components/tooltip, /ui/plugins/chart] source: core/scss/ui/_tracking.scss +css-vars: tracking classnames: component: - class: tracking @@ -17,7 +18,6 @@ classnames: --- import Subheader from '@ui/Subheader.astro'; import Example from '@components/Example.astro'; -import CodeDocs from '@components/CodeDocs.astro'; import Icon from '@ui/Icon.astro' ## Basic example @@ -146,9 +146,3 @@ Add `tracking-squares` to draw the blocks as squares of equal size instead of ta - The tooltips on the blocks are mouse-only. Put the same information in a table or a summary line so it can be reached another way. - Green and red blocks look identical to many users. Add a shape, a label, or a legend next to the strip. - Do not make each block focusable. Ninety focus stops for one chart is worse than none. - -## SCSS variables - -Use these SCSS variables to customize the tracking component. The default values are: - - diff --git a/docs/content/ui/forms/elements.mdx b/docs/content/ui/forms/elements.mdx index 4e564e562..fb6c8c86f 100644 --- a/docs/content/ui/forms/elements.mdx +++ b/docs/content/ui/forms/elements.mdx @@ -6,9 +6,9 @@ docs-libs: [nouislider] description: Styled inputs, selects, checkboxes and radios, with states, sizes and layout options. order: 1 related: [/ui/forms/validation, /ui/forms/helpers, /ui/forms/fieldset, /ui/plugins/date-picker, /ui/plugins/advanced-select] +sass-vars: input --- import Example from '@components/Example.astro'; -import CodeDocs from '@components/CodeDocs.astro'; import Icon from '@ui/Icon.astro' ## Classic inputs @@ -482,9 +482,3 @@ Wrap the buttons that close a form in `form-footer`. It only adds the top margin - Group related checkboxes or radios in a `fieldset` with a `legend`. The legend is the only thing that names the group. See [fieldsets](/ui/forms/fieldset). - Keep `input` types honest - `email`, `tel`, `url` and `number` change the on-screen keyboard on phones and let the browser validate for you. - Do not disable a field to make it read-only. `disabled` removes it from the tab order and from the submitted data; `readonly` keeps both. - -## SCSS variables - -Use these SCSS variables to customize form controls. The default values are: - - diff --git a/docs/content/ui/forms/otp-input.mdx b/docs/content/ui/forms/otp-input.mdx index 206cf17f5..a07ffc76e 100644 --- a/docs/content/ui/forms/otp-input.mdx +++ b/docs/content/ui/forms/otp-input.mdx @@ -6,6 +6,7 @@ summary: A single input rendered as separate character slots, so a one-time pass description: Add a one-time password (OTP) input with grouped slots, masking, and keyboard, paste and SMS autofill support. related: [/ui/forms/elements, /ui/forms/validation] source: core/scss/ui/forms/_form-otp.scss +css-vars: otp classnames: component: - class: otp @@ -166,7 +167,3 @@ el.addEventListener('complete.bs.otpInput', (event) => { - `inputmode` and a `pattern` matching the `type` option are set automatically, so mobile keyboards show the right layout. - The mask option only hides the value visually - the input is never switched to `type="password"`, so assistive tech still reads it as plain text. - Keyboard focus lands on the first empty slot, and typing overwrites the active slot rather than inserting, so screen reader and switch-access users always land where they expect. - -## SCSS variables - - diff --git a/docs/content/ui/forms/validation.mdx b/docs/content/ui/forms/validation.mdx index a3ebed5b9..d2bc71228 100644 --- a/docs/content/ui/forms/validation.mdx +++ b/docs/content/ui/forms/validation.mdx @@ -5,9 +5,9 @@ order: 12 summary: Validation states mark a field as valid or invalid and show a message under it, so the user sees what to fix. description: Show valid and invalid states on form fields with feedback messages, so users know exactly what to correct before submitting. related: [/ui/forms/elements, /ui/forms/helpers] +sass-vars: form-validation --- import Example from '@components/Example.astro'; -import CodeDocs from '@components/CodeDocs.astro'; ## Validation states @@ -74,9 +74,3 @@ These variants drop the colored frame and the feedback message, so the state is - Say what to do, not only what failed. "Please provide a valid city" beats "Invalid". - On submit, move focus to the first field that failed. Otherwise a keyboard user has to hunt for it. - Validate when the user leaves a field, not on every keystroke. A message that appears and disappears while typing is announced over and over. - -## SCSS variables - -Use these SCSS variables to customize form validation states. The default values are: - - diff --git a/docs/content/ui/layout/navbars.mdx b/docs/content/ui/layout/navbars.mdx index 61c783cbe..b9d250918 100644 --- a/docs/content/ui/layout/navbars.mdx +++ b/docs/content/ui/layout/navbars.mdx @@ -3,12 +3,12 @@ title: Navbars summary: A navbar is the horizontal bar at the top of the page with the logo, the main links and controls such as search or a user menu. description: Responsive navigation bars, with the markup, the variants and a language selector. related: [/ui/layout/page-layouts, /ui/components/dropdown] +sass-vars: navbar --- import Example from '@components/Example.astro' import Logo from '@shared/components/navbar/Logo.astro' import Icon from '@ui/Icon.astro' -import CodeDocs from '@components/CodeDocs.astro'; A navbar is the horizontal bar at the top of the page. It holds the logo, the main links and often a search field, [buttons](/ui/components/button) or [dropdown menus](/ui/components/dropdown) such as a user menu. @@ -141,9 +141,3 @@ The preview navbar renders it from `shared/data/languages.json` with the `showLa - Mark the current page with `aria-current="page"`. - The brand link should say where it goes. A logo alone needs an `aria-label` or `visually-hidden` text. - A dropdown in the navbar follows the [dropdown](/ui/components/dropdown) rules - the trigger is a `button` or a link with an `href`. - -## SCSS variables - -Use these SCSS variables to customize the navbar. The default values are: - - diff --git a/docs/content/ui/layout/navs-tabs.mdx b/docs/content/ui/layout/navs-tabs.mdx index 4a57bbe1d..4f318e555 100644 --- a/docs/content/ui/layout/navs-tabs.mdx +++ b/docs/content/ui/layout/navs-tabs.mdx @@ -3,9 +3,9 @@ title: Navs and tabs summary: "How to build navs and tabs: horizontal and vertical navs, tabs, pills, the underline style and tabs with a dropdown." description: "Nav styles: tabs, pills, underline, dropdowns and vertical navs." related: [/ui/components/tab] +sass-vars: nav --- import Example from '@components/Example.astro'; -import CodeDocs from '@components/CodeDocs.astro'; A nav is a list of links, and the classes on this page change how that list looks: a horizontal row, a vertical column, tabs, pills or a line under the active link. All of them start from the same `.nav` markup, so you can switch between styles by changing one class. @@ -130,9 +130,3 @@ A tab can open a [dropdown menu](/ui/components/dropdown). Put the `.dropdown` m - A disabled nav link should be an `a` with no `href` plus `aria-disabled="true"`. Without `href` it cannot be focused, which is what you want. - Every link needs text. An icon-only link takes an `aria-label`. - Keep the DOM order the same as the visual order. `nav-fill` and `nav-justified` change the widths, not the order. - -## SCSS variables - -Use these SCSS variables to customize navs and tabs. The default values are: - - diff --git a/docs/content/ui/plugins/chart.mdx b/docs/content/ui/plugins/chart.mdx index 7f6d6027b..6cf19132a 100644 --- a/docs/content/ui/plugins/chart.mdx +++ b/docs/content/ui/plugins/chart.mdx @@ -4,6 +4,7 @@ docs-libs: [apexcharts] summary: Tabler draws charts with ApexCharts and ships a container class, a color palette and theme tokens that make them match the interface. description: Build interactive line, bar, pie, and other charts with ApexCharts. Copy ready-to-use configurations styled to match Tabler. related: [/ui/plugins/countup, /ui/components/trending] +sass-vars: chart --- import Example from '@components/Example.astro' @@ -11,7 +12,6 @@ import Chart from '@shared/components/demo/Chart.astro' import ChartSparkline from '@ui/ChartSparkline.astro' import CdnImportPlugin from '@components/CdnImportPlugin.astro'; import TabsPackage from '@components/TabsPackage.astro'; -import CodeDocs from '@components/CodeDocs.astro'; import { Code } from 'astro:components'; import { site } from '@shared/lib/site.ts'; @@ -403,9 +403,3 @@ A chart is a picture, and a screen reader reads the SVG inside it as a pile of n aria-label="Monthly revenue, January to June 2026" > ``` - -## SCSS variables - -Use these SCSS variables to customize charts. The default values are: - - diff --git a/docs/content/ui/plugins/date-picker.mdx b/docs/content/ui/plugins/date-picker.mdx index 91ab5c107..24f107b17 100644 --- a/docs/content/ui/plugins/date-picker.mdx +++ b/docs/content/ui/plugins/date-picker.mdx @@ -5,6 +5,7 @@ summary: A date picker lets users pick a date from a calendar instead of typing docs-libs: [vanilla-calendar-pro] description: Let users pick a date, a range or several dates with the Datepicker plugin, as a text input, a button or an inline calendar. related: [/ui/forms/elements, /ui/plugins/color-picker, /ui/plugins/fullcalendar] +sass-vars: datepicker --- import Example from '@components/Example.astro'; @@ -287,9 +288,3 @@ Tabler 1.5 and older used [Litepicker](https://github.com/wakirin/Litepicker), w | `numberOfMonths: 2` | `data-bs-display-months-count="2"` | | `format` | `dateFormat` option, as `Intl.DateTimeFormat` options | | `picker.on('selected', …)` | `change.bs.datepicker` event | - -## SCSS variables - -The calendar grid and the popup surface have separate variables. Override the `--tblr-calendar-*` custom properties to restyle every calendar, inline or popup. The `--tblr-datepicker-*` ones only apply to the popup. - - diff --git a/docs/content/ui/plugins/sortable.mdx b/docs/content/ui/plugins/sortable.mdx index a81b622fe..541fdc774 100644 --- a/docs/content/ui/plugins/sortable.mdx +++ b/docs/content/ui/plugins/sortable.mdx @@ -5,6 +5,7 @@ description: Make lists, table rows and cards draggable with the data-sortable a docs-libs: [sortablejs] added-in: '1.6.0' related: [/ui/components/list-group, /ui/components/table] +sass-vars: sortable --- import Example from '@components/Example.astro' @@ -135,7 +136,3 @@ list.sortable.option('disabled', true); - Dragging needs a pointer. SortableJS has no keyboard support, so give keyboard users another way to change the order, such as "Move up" and "Move down" buttons. - Use a drag handle when the items contain links or buttons, so a click on them does not start a drag. - Save the new order only after the drop, and tell the user when saving fails. - -## SCSS variables - - diff --git a/docs/layouts/DocsLayout.astro b/docs/layouts/DocsLayout.astro index 320b8cb1e..05e6f27c5 100644 --- a/docs/layouts/DocsLayout.astro +++ b/docs/layouts/DocsLayout.astro @@ -11,6 +11,7 @@ import DocsNavbar from '@components/DocsNavbar.astro'; import DocsMenu from '@components/DocsMenu.astro'; import DocsSearch from '@components/DocsSearch.astro'; import DocsClassnames from '@components/DocsClassnames.astro'; +import DocsCssVars from '@components/DocsCssVars.astro'; import DocsToc from '@components/DocsToc.astro'; import type { TocItem } from '@components/DocsToc.astro'; import DocsCopyMarkdownLink from '@components/DocsCopyMarkdownLink.astro'; @@ -31,6 +32,10 @@ interface Props { sourcePath?: string | undefined; /** class reference table, rendered above the page body */ classnames?: Record | undefined; + /** front matter `css-vars`: custom-property family (`badge` for `--tblr-badge-*`), rendered below the class reference */ + cssVars?: string | undefined; + /** front matter `sass-vars`: the `-variables` marker in `_variables.scss`, defaults to `cssVars` */ + sassVars?: string | undefined; /** front matter: SEO description */ description?: string | undefined; /** front matter: optional SEO description override */ @@ -65,6 +70,8 @@ const { seoTitle, summary, classnames, + cssVars, + sassVars, sourcePath, description, seoDescription, @@ -409,6 +416,8 @@ const relatedPages = await Promise.all( {classnames && } + {(cssVars || sassVars) && } + {relatedPages.length > 0 && ( diff --git a/docs/lib/scss-docs.ts b/docs/lib/scss-docs.ts new file mode 100644 index 000000000..30ad37038 --- /dev/null +++ b/docs/lib/scss-docs.ts @@ -0,0 +1,87 @@ +// The "CSS" section of a component page, cut from the SCSS sources the way the +// Bootstrap docs do it: the custom-property block (`// scss-docs-start +// -css-vars` in the component's file) and the Sass defaults +// (`-variables` in `_variables.scss`). Shared by the page, which needs +// to know which headings exist for the table of contents, and by DocsCssVars, +// which renders them. +import { extractMarkedSnippet } from '@shared/lib/code-example' + +// Vite inlines the sources at build time — `node:fs` paths would break once +// this module is bundled into dist/.prerender during `astro build`. +const scssSources = import.meta.glob('../../core/scss/**/*.scss', { query: '?raw', import: 'default' }) + +export interface ScssDocsBlock { + /** repo-relative path of the file the block comes from */ + path: string + /** the lines between the markers, minus comment lines, ` !default` and the common indent */ + code: string + /** the rule the block sits in, e.g. `.badge`; undefined when the marker is not directly inside a rule */ + selector: string | undefined +} + +export interface CssSection { + css?: ScssDocsBlock | undefined + sass?: ScssDocsBlock | undefined +} + +let cachedSources: Promise<{ path: string; source: string }[]> | undefined + +function sources() { + cachedSources ??= Promise.all( + Object.entries(scssSources) + .sort(([a], [b]) => a.localeCompare(b)) + .map(async ([key, load]) => ({ path: key.replace('../../', ''), source: (await load()) as string })), + ) + return cachedSources +} + +/** The block between the `` markers in the first file that has it. */ +export async function scssDocsBlock(name: string): Promise { + const file = (await sources()).find(({ source }) => source.includes(`// scss-docs-start ${name}\n`)) + if (!file) return undefined + const snippet = extractMarkedSnippet(file.source.replaceAll(' !default', ''), 'scss-docs', name) + if (snippet === null) throw new Error(`"${name}" has no matching scss-docs-end marker in ${file.path}`) + const code = snippet + .split('\n') + .filter((line) => !/^\s*\/\//.test(line)) + .join('\n') + // the rule the block sits in: the nearest non-comment line above the marker + const above = file.source + .slice(0, file.source.indexOf(`// scss-docs-start ${name}`)) + .split('\n') + .reverse() + const opener = above + .slice(1) + .find((line) => line.trim() && !/^\s*\/\//.test(line)) + ?.trim() + const selector = opener?.endsWith('{') ? opener.slice(0, -1).trim() : undefined + return { path: file.path, code, selector } +} + +/** `spinner` has no `spinner-css-vars`; Bootstrap's marker is `spinner-border-css-vars`. */ +async function familyCssVarsMarker(prefix: string) { + const family = new RegExp(`^\\s*// scss-docs-start (${prefix}-[\\w-]*-css-vars)$`, 'm') + for (const { source } of await sources()) { + const match = source.match(family) + if (match) return match[1]! + } + return undefined +} + +/** + * Resolves a page's `css-vars` and `sass-vars` front matter to the blocks to + * show. A missing `css-vars` marker is an error, since the page asked for it + * by name; a missing `-variables` marker only drops the Sass part, + * because `sass-vars` defaults to `css-vars` and not every component has one. + */ +export async function cssSection(cssVars?: string, sassVars: string | undefined = cssVars): Promise { + let css: ScssDocsBlock | undefined + if (cssVars) { + const marker = (await scssDocsBlock(`${cssVars}-css-vars`)) ? `${cssVars}-css-vars` : await familyCssVarsMarker(cssVars) + css = marker ? await scssDocsBlock(marker) : undefined + if (!css) throw new Error(`css-vars: no "// scss-docs-start ${cssVars}-css-vars" marker in core/scss`) + } + const sass = sassVars ? await scssDocsBlock(`${sassVars}-variables`) : undefined + if (sassVars && !sass && sassVars !== cssVars) throw new Error(`sass-vars: no "// scss-docs-start ${sassVars}-variables" marker in core/scss`) + return { css, sass } +} diff --git a/docs/pages/[...slug].astro b/docs/pages/[...slug].astro index fb6408678..9116e8fa9 100644 --- a/docs/pages/[...slug].astro +++ b/docs/pages/[...slug].astro @@ -8,6 +8,7 @@ import DocsLayout from '@layouts/DocsLayout.astro' import { docsUrlFromId } from '@lib/docs-pages' import { markdownUrlFromDocsUrl } from '@lib/markdown-url' import { callouts } from '@components/callouts/index.ts' +import { cssSection } from '@lib/scss-docs' export const getStaticPaths = (async () => { const entries = await getCollection('docs') @@ -30,6 +31,13 @@ const toc: { level: 2 | 3; text: string; id: string }[] = headings.filter((headi if (data.classnames) { toc.push({ level: 2, text: 'Class reference', id: 'class-reference' }) } +// DocsCssVars too; only the parts that exist in the sources get an entry. +const { css, sass } = await cssSection(data.cssVars, data.sassVars) +if (css || sass) { + toc.push({ level: 2, text: 'CSS', id: 'css' }) + if (css) toc.push({ level: 3, text: 'Variables', id: 'css-variables' }) + if (sass) toc.push({ level: 3, text: 'Sass variables', id: 'sass-variables' }) +} ---