Compare commits
3 Commits
v6-dev
...
v6-stepper
| Author | SHA1 | Date | |
|---|---|---|---|
| b962c3d8f9 | |||
| c609b16099 | |||
| c78afa2bf9 |
@@ -0,0 +1,143 @@
|
||||
@use "sass:map";
|
||||
@use "config" as *;
|
||||
@use "variables" as *;
|
||||
@use "layout/breakpoints" as *;
|
||||
@use "mixins/border-radius" as *;
|
||||
@use "mixins/box-shadow" as *;
|
||||
@use "mixins/gradients" as *;
|
||||
@use "mixins/transition" as *;
|
||||
|
||||
// scss-docs-start stepper-variables
|
||||
$stepper-size: 2rem !default;
|
||||
$stepper-gap: 1rem !default;
|
||||
$stepper-track-size: .25rem !default;
|
||||
$stepper-bg: var(--bg-2) !default;
|
||||
$stepper-active-fg: var(--primary-contrast) !default;
|
||||
$stepper-active-bg: var(--primary-bg) !default;
|
||||
// $stepper-vertical-gap: .5rem !default;
|
||||
// scss-docs-end stepper-variables
|
||||
|
||||
// scss-docs-start stepper-horizontal-mixin
|
||||
@mixin stepper-horizontal() {
|
||||
display: inline-grid;
|
||||
grid-auto-columns: 1fr;
|
||||
grid-auto-flow: column;
|
||||
|
||||
.stepper-item {
|
||||
grid-template-rows: repeat(2, var(--stepper-size));
|
||||
grid-template-columns: auto;
|
||||
justify-items: center;
|
||||
|
||||
&::after {
|
||||
top: calc((var(--stepper-size) * .5) - (var(--stepper-track-size) * .5));
|
||||
right: 0;
|
||||
bottom: auto;
|
||||
left: calc(-50% - var(--stepper-gap));
|
||||
width: auto;
|
||||
height: var(--stepper-track-size);
|
||||
}
|
||||
|
||||
&:last-child::after {
|
||||
right: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
// scss-docs-end stepper-horizontal-mixin
|
||||
|
||||
// scss-docs-start stepper-css
|
||||
.stepper {
|
||||
// scss-docs-start stepper-css-vars
|
||||
--stepper-size: #{$stepper-size};
|
||||
--stepper-gap: #{$stepper-gap};
|
||||
--stepper-bg: #{$stepper-bg};
|
||||
--stepper-track-size: #{$stepper-track-size};
|
||||
--stepper-active-color: #{$stepper-active-fg};
|
||||
--stepper-active-bg: #{$stepper-active-bg};
|
||||
// scss-docs-end stepper-css-vars
|
||||
|
||||
display: grid;
|
||||
grid-auto-rows: 1fr;
|
||||
grid-auto-flow: row;
|
||||
gap: var(--stepper-gap);
|
||||
padding-left: 0;
|
||||
list-style: none;
|
||||
counter-reset: stepper;
|
||||
}
|
||||
|
||||
.stepper-item {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-rows: auto;
|
||||
grid-template-columns: var(--stepper-size) auto;
|
||||
gap: .5rem;
|
||||
place-items: center;
|
||||
justify-items: start;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
|
||||
|
||||
// The counter
|
||||
&::before {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: inline-block;
|
||||
width: var(--stepper-size);
|
||||
height: var(--stepper-size);
|
||||
padding: .5rem;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
content: counter(stepper);
|
||||
counter-increment: stepper;
|
||||
background-color: var(--stepper-bg);
|
||||
@include border-radius(50%);
|
||||
}
|
||||
|
||||
// Connecting lines
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: calc(var(--stepper-gap) * -1);
|
||||
bottom: 100%;
|
||||
left: calc((var(--stepper-size) * .5) - (var(--stepper-track-size) * .5));
|
||||
width: var(--stepper-track-size);
|
||||
content: "";
|
||||
background-color: var(--stepper-bg);
|
||||
}
|
||||
|
||||
// Avoid sibling selector for easier CSS overrides
|
||||
&:first-child::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.active {
|
||||
&::before,
|
||||
&::after {
|
||||
color: var(--theme-contrast, var(--stepper-active-color));
|
||||
background-color: var(--theme-bg, var(--stepper-active-bg));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@each $breakpoint in map.keys($grid-breakpoints) {
|
||||
@include media-breakpoint-up($breakpoint) {
|
||||
$infix: breakpoint-infix($breakpoint, $grid-breakpoints);
|
||||
|
||||
.stepper-horizontal#{$infix} {
|
||||
@include stepper-horizontal();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// scss-docs-start stepper-overflow
|
||||
.stepper-overflow {
|
||||
container-type: inline-size;
|
||||
overflow-x: auto;
|
||||
overscroll-behavior-x: contain;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
|
||||
> .stepper {
|
||||
width: max-content;
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
// scss-docs-end stepper-overflow
|
||||
Vendored
+1
@@ -29,6 +29,7 @@
|
||||
@forward "popover";
|
||||
@forward "progress";
|
||||
@forward "spinner";
|
||||
@forward "stepper";
|
||||
@forward "toasts";
|
||||
@forward "tooltip";
|
||||
@forward "transitions";
|
||||
|
||||
@@ -106,6 +106,7 @@
|
||||
- title: Progress
|
||||
- title: Scrollspy
|
||||
- title: Spinner
|
||||
- title: Stepper
|
||||
- title: Toasts
|
||||
- title: Toggler
|
||||
- title: Tooltip
|
||||
|
||||
@@ -25,7 +25,7 @@ const rounded = ['default', 'pill', 'square']
|
||||
</symbol>
|
||||
</svg>
|
||||
|
||||
<div class="bg-1 p-3 rounded-3">
|
||||
<div class="bg-1 p-3 fs-sm rounded-3">
|
||||
<div class="d-flex flex-wrap gap-3">
|
||||
<div class="vstack gap-1">
|
||||
<label class="form-label fw-semibold mb-0">Color</label>
|
||||
@@ -38,7 +38,7 @@ const rounded = ['default', 'pill', 'square']
|
||||
aria-expanded="false"
|
||||
data-color="primary"
|
||||
>
|
||||
Primary
|
||||
<span>Primary</span>
|
||||
<svg class="bi ms-1" width="16" height="16" aria-hidden="true">
|
||||
<use href="#chevron-expand" />
|
||||
</svg>
|
||||
@@ -89,7 +89,7 @@ const rounded = ['default', 'pill', 'square']
|
||||
aria-expanded="false"
|
||||
data-size=""
|
||||
>
|
||||
Medium
|
||||
<span>Medium</span>
|
||||
<svg class="bi ms-1" width="16" height="16" aria-hidden="true">
|
||||
<use href="#chevron-expand" />
|
||||
</svg>
|
||||
@@ -270,7 +270,8 @@ const rounded = ['default', 'pill', 'square']
|
||||
const colorTitle = item.textContent?.trim() || 'Primary'
|
||||
|
||||
// Update button text and data attribute
|
||||
colorDropdownButton.textContent = colorTitle
|
||||
const labelSpan = colorDropdownButton.querySelector('span')
|
||||
if (labelSpan) labelSpan.textContent = colorTitle
|
||||
colorDropdownButton.dataset.color = colorName
|
||||
|
||||
// Update selected state
|
||||
@@ -301,7 +302,8 @@ const rounded = ['default', 'pill', 'square']
|
||||
const sizeLabel = item.textContent?.trim() || 'Medium'
|
||||
|
||||
// Update button text and data attribute
|
||||
sizeDropdownButton.textContent = sizeLabel
|
||||
const labelSpan = sizeDropdownButton.querySelector('span')
|
||||
if (labelSpan) labelSpan.textContent = sizeLabel
|
||||
sizeDropdownButton.dataset.size = sizeValue
|
||||
|
||||
// Update selected state
|
||||
|
||||
@@ -28,7 +28,7 @@ const logicalPlacements = [
|
||||
]
|
||||
---
|
||||
|
||||
<div class="bg-1 p-3 rounded-3 mb-3">
|
||||
<div class="bg-1 p-3 fs-sm rounded-3 mb-3">
|
||||
<div class="d-flex flex-wrap gap-3 align-items-end">
|
||||
<div class="vstack gap-1">
|
||||
<label class="form-label fw-semibold mb-0">Placement type</label>
|
||||
@@ -67,7 +67,10 @@ const logicalPlacements = [
|
||||
data-placement="bottom-start"
|
||||
style="min-width: 160px;"
|
||||
>
|
||||
bottom-start
|
||||
<span>bottom-start</span>
|
||||
<svg class="bi ms-1" width="16" height="16" aria-hidden="true">
|
||||
<use href="#chevron-expand" />
|
||||
</svg>
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="placement-dropdown">
|
||||
{physicalPlacements.map((p) => (
|
||||
@@ -148,7 +151,8 @@ const logicalPlacements = [
|
||||
if (!placementDropdownButton || !previewToggle) return
|
||||
|
||||
// Update the placement selector button
|
||||
placementDropdownButton.textContent = placement
|
||||
const labelSpan = placementDropdownButton.querySelector('span')
|
||||
if (labelSpan) labelSpan.textContent = placement
|
||||
placementDropdownButton.dataset.placement = placement
|
||||
|
||||
// Update active state in dropdown
|
||||
|
||||
@@ -0,0 +1,380 @@
|
||||
---
|
||||
import { getData } from '@libs/data'
|
||||
import Example from '@components/shortcodes/Example.astro'
|
||||
|
||||
const breakpoints = getData('breakpoints')
|
||||
const orientations = [
|
||||
{ value: 'vertical', label: 'Vertical' },
|
||||
{ value: 'horizontal', label: 'Horizontal' }
|
||||
]
|
||||
const stepCounts = [3, 4, 5, 6]
|
||||
---
|
||||
|
||||
<div class="bg-1 p-3 fs-sm rounded-3">
|
||||
<div class="d-flex flex-wrap gap-3 align-items-end">
|
||||
<div class="vstack gap-1">
|
||||
<label class="form-label fw-semibold mb-0">Orientation</label>
|
||||
<div class="btn-group" role="group" aria-label="Stepper orientation">
|
||||
{orientations.map((orientation) => (
|
||||
<label class="btn-check btn-outline theme-secondary">
|
||||
<input
|
||||
type="radio"
|
||||
name="stepper-orientation"
|
||||
value={orientation.value}
|
||||
checked={orientation.value === 'vertical'}
|
||||
data-orientation={orientation.value}
|
||||
/>
|
||||
{orientation.label}
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="vstack gap-1" id="breakpoint-control">
|
||||
<label class="form-label fw-semibold mb-0">Breakpoint</label>
|
||||
<div class="dropdown">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline theme-secondary dropdown-toggle w-100 justify-content-between"
|
||||
id="stepper-breakpoint-dropdown"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
data-breakpoint=""
|
||||
disabled
|
||||
style="min-width: 120px;"
|
||||
>
|
||||
<span>All sizes</span>
|
||||
<svg class="bi ms-1" width="16" height="16" aria-hidden="true">
|
||||
<use href="#chevron-expand" />
|
||||
</svg>
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="stepper-breakpoint-dropdown">
|
||||
{breakpoints.map((bp) => (
|
||||
<li>
|
||||
<a
|
||||
class:list={['dropdown-item', { 'active': bp.abbr === '' }]}
|
||||
href="#"
|
||||
data-breakpoint={bp.abbr}
|
||||
>
|
||||
{bp.abbr === '' ? 'All sizes' : `${bp.name} (${bp.breakpoint})`}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="vstack gap-1">
|
||||
<label class="form-label fw-semibold mb-0">Steps</label>
|
||||
<div class="dropdown">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline theme-secondary dropdown-toggle w-100 justify-content-between"
|
||||
id="stepper-count-dropdown"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
data-count="4"
|
||||
style="min-width: 80px;"
|
||||
>
|
||||
<span>4</span>
|
||||
<svg class="bi ms-1" width="16" height="16" aria-hidden="true">
|
||||
<use href="#chevron-expand" />
|
||||
</svg>
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="stepper-count-dropdown">
|
||||
{stepCounts.map((count) => (
|
||||
<li>
|
||||
<a
|
||||
class:list={['dropdown-item', { 'active': count === 4 }]}
|
||||
href="#"
|
||||
data-count={count}
|
||||
>
|
||||
{count}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="vstack gap-1">
|
||||
<label class="form-label fw-semibold mb-0">Active step</label>
|
||||
<div class="dropdown">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline theme-secondary dropdown-toggle w-100 justify-content-between"
|
||||
id="stepper-active-dropdown"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
data-active="2"
|
||||
style="min-width: 80px;"
|
||||
>
|
||||
<span>2</span>
|
||||
<svg class="bi ms-1" width="16" height="16" aria-hidden="true">
|
||||
<use href="#chevron-expand" />
|
||||
</svg>
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="stepper-active-dropdown">
|
||||
{stepCounts.map((count) => (
|
||||
<li>
|
||||
<a
|
||||
class:list={['dropdown-item', { 'active': count === 2 }]}
|
||||
href="#"
|
||||
data-active={count}
|
||||
>
|
||||
{count}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="vstack gap-1">
|
||||
<label class="form-label fw-semibold mb-0 user-select-none"> </label>
|
||||
<b-checkgroup class="py-2">
|
||||
<div class="switch">
|
||||
<input type="checkbox" value="" id="stepper-fullwidth" switch>
|
||||
</div>
|
||||
<label for="stepper-fullwidth">Full width</label>
|
||||
</b-checkgroup>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Example
|
||||
code={`<ol class="stepper">
|
||||
<li class="stepper-item active">Create account</li>
|
||||
<li class="stepper-item active">Confirm email</li>
|
||||
<li class="stepper-item">Update profile</li>
|
||||
<li class="stepper-item">Finish</li>
|
||||
</ol>`}
|
||||
id="stepper-preview"
|
||||
/>
|
||||
|
||||
<script>
|
||||
const orientationInputs = document.querySelectorAll('input[name="stepper-orientation"]')
|
||||
const breakpointDropdownButton = document.querySelector('#stepper-breakpoint-dropdown') as HTMLButtonElement
|
||||
const breakpointDropdownItems = document.querySelectorAll('#stepper-breakpoint-dropdown + .dropdown-menu .dropdown-item')
|
||||
const countDropdownButton = document.querySelector('#stepper-count-dropdown') as HTMLButtonElement
|
||||
const countDropdownItems = document.querySelectorAll('#stepper-count-dropdown + .dropdown-menu .dropdown-item')
|
||||
const activeDropdownButton = document.querySelector('#stepper-active-dropdown') as HTMLButtonElement
|
||||
const activeDropdownItems = document.querySelectorAll('#stepper-active-dropdown + .dropdown-menu .dropdown-item')
|
||||
const fullwidthSwitch = document.querySelector('#stepper-fullwidth') as HTMLInputElement
|
||||
const breakpointControl = document.querySelector('#breakpoint-control') as HTMLElement
|
||||
const previewContainer = document.querySelector('#stepper-preview') as HTMLElement
|
||||
const codeSnippet = document.querySelector('#stepper-preview')?.closest('.bd-example-snippet')?.querySelector('.highlight code') as HTMLElement
|
||||
|
||||
const stepLabels = ['Create account', 'Confirm email', 'Update profile', 'Finish', 'Complete', 'Done']
|
||||
|
||||
function getOrientation(): string {
|
||||
return (document.querySelector('input[name="stepper-orientation"]:checked') as HTMLInputElement)?.value || 'vertical'
|
||||
}
|
||||
|
||||
function getBreakpoint(): string {
|
||||
return breakpointDropdownButton?.dataset.breakpoint || ''
|
||||
}
|
||||
|
||||
function getStepCount(): number {
|
||||
return parseInt(countDropdownButton?.dataset.count || '4', 10)
|
||||
}
|
||||
|
||||
function getActiveStep(): number {
|
||||
return parseInt(activeDropdownButton?.dataset.active || '2', 10)
|
||||
}
|
||||
|
||||
function isFullWidth(): boolean {
|
||||
return fullwidthSwitch?.checked || false
|
||||
}
|
||||
|
||||
function buildStepperClass(): string {
|
||||
const classes = ['stepper']
|
||||
const orientation = getOrientation()
|
||||
const breakpoint = getBreakpoint()
|
||||
const fullWidth = isFullWidth()
|
||||
|
||||
if (orientation === 'horizontal') {
|
||||
classes.push(`stepper-horizontal${breakpoint}`)
|
||||
}
|
||||
|
||||
if (fullWidth && orientation === 'horizontal') {
|
||||
classes.push('w-100')
|
||||
}
|
||||
|
||||
return classes.join(' ')
|
||||
}
|
||||
|
||||
function generateHTML(): string {
|
||||
const stepCount = getStepCount()
|
||||
const activeStep = getActiveStep()
|
||||
const stepperClass = buildStepperClass()
|
||||
|
||||
let html = `<ol class="${stepperClass}">\n`
|
||||
|
||||
for (let i = 1; i <= stepCount; i++) {
|
||||
const isActive = i <= activeStep
|
||||
const activeClass = isActive ? ' active' : ''
|
||||
const label = stepLabels[i - 1] || `Step ${i}`
|
||||
html += ` <li class="stepper-item${activeClass}">${label}</li>\n`
|
||||
}
|
||||
|
||||
html += '</ol>'
|
||||
return html
|
||||
}
|
||||
|
||||
function updatePreview() {
|
||||
if (!previewContainer) return
|
||||
|
||||
const stepperElement = previewContainer.querySelector('.stepper')
|
||||
if (!stepperElement) return
|
||||
|
||||
const stepCount = getStepCount()
|
||||
const activeStep = getActiveStep()
|
||||
const stepperClass = buildStepperClass()
|
||||
|
||||
// Update stepper classes
|
||||
stepperElement.className = stepperClass
|
||||
|
||||
// Update step items
|
||||
let stepsHtml = ''
|
||||
for (let i = 1; i <= stepCount; i++) {
|
||||
const isActive = i <= activeStep
|
||||
const activeClass = isActive ? ' active' : ''
|
||||
const label = stepLabels[i - 1] || `Step ${i}`
|
||||
stepsHtml += `<li class="stepper-item${activeClass}">${label}</li>`
|
||||
}
|
||||
stepperElement.innerHTML = stepsHtml
|
||||
}
|
||||
|
||||
function updateCodeSnippet() {
|
||||
if (!codeSnippet) return
|
||||
|
||||
const htmlCode = generateHTML()
|
||||
codeSnippet.className = 'language-html'
|
||||
codeSnippet.textContent = htmlCode
|
||||
|
||||
if (typeof window !== 'undefined' && (window as any).Prism) {
|
||||
(window as any).Prism.highlightElement(codeSnippet)
|
||||
}
|
||||
}
|
||||
|
||||
function updateActiveDropdown() {
|
||||
const stepCount = getStepCount()
|
||||
const currentActive = getActiveStep()
|
||||
|
||||
// Update dropdown items visibility
|
||||
activeDropdownItems.forEach((item) => {
|
||||
const value = parseInt((item as HTMLElement).dataset.active || '0', 10)
|
||||
const li = item.parentElement as HTMLElement
|
||||
if (value > stepCount) {
|
||||
li.classList.add('d-none')
|
||||
} else {
|
||||
li.classList.remove('d-none')
|
||||
}
|
||||
})
|
||||
|
||||
// Reset active step if current is higher than step count
|
||||
if (currentActive > stepCount) {
|
||||
activeDropdownButton.dataset.active = String(stepCount)
|
||||
const labelSpan = activeDropdownButton.querySelector('span')
|
||||
if (labelSpan) labelSpan.textContent = String(stepCount)
|
||||
activeDropdownItems.forEach((item) => {
|
||||
const value = parseInt((item as HTMLElement).dataset.active || '0', 10)
|
||||
item.classList.toggle('active', value === stepCount)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function updateBreakpointState() {
|
||||
const orientation = getOrientation()
|
||||
const isHorizontal = orientation === 'horizontal'
|
||||
|
||||
breakpointDropdownButton.disabled = !isHorizontal
|
||||
breakpointControl.classList.toggle('opacity-50', !isHorizontal)
|
||||
|
||||
// Disable fullwidth when vertical
|
||||
fullwidthSwitch.disabled = !isHorizontal
|
||||
fullwidthSwitch.closest('.vstack')?.classList.toggle('opacity-50', !isHorizontal)
|
||||
}
|
||||
|
||||
function update() {
|
||||
updateBreakpointState()
|
||||
updateActiveDropdown()
|
||||
updatePreview()
|
||||
updateCodeSnippet()
|
||||
}
|
||||
|
||||
// Initialize dropdowns
|
||||
if (breakpointDropdownButton) {
|
||||
const breakpointDropdown = bootstrap.Dropdown.getOrCreateInstance(breakpointDropdownButton)
|
||||
|
||||
breakpointDropdownItems.forEach((item) => {
|
||||
item.addEventListener('click', (e) => {
|
||||
e.preventDefault()
|
||||
const breakpoint = (item as HTMLElement).dataset.breakpoint || ''
|
||||
const label = item.textContent?.trim() || 'All sizes'
|
||||
|
||||
const labelSpan = breakpointDropdownButton.querySelector('span')
|
||||
if (labelSpan) labelSpan.textContent = label
|
||||
breakpointDropdownButton.dataset.breakpoint = breakpoint
|
||||
|
||||
breakpointDropdownItems.forEach((i) => i.classList.remove('active'))
|
||||
item.classList.add('active')
|
||||
|
||||
breakpointDropdown.hide()
|
||||
update()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
if (countDropdownButton) {
|
||||
const countDropdown = bootstrap.Dropdown.getOrCreateInstance(countDropdownButton)
|
||||
|
||||
countDropdownItems.forEach((item) => {
|
||||
item.addEventListener('click', (e) => {
|
||||
e.preventDefault()
|
||||
const count = (item as HTMLElement).dataset.count || '4'
|
||||
|
||||
const labelSpan = countDropdownButton.querySelector('span')
|
||||
if (labelSpan) labelSpan.textContent = count
|
||||
countDropdownButton.dataset.count = count
|
||||
|
||||
countDropdownItems.forEach((i) => i.classList.remove('active'))
|
||||
item.classList.add('active')
|
||||
|
||||
countDropdown.hide()
|
||||
update()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
if (activeDropdownButton) {
|
||||
const activeDropdown = bootstrap.Dropdown.getOrCreateInstance(activeDropdownButton)
|
||||
|
||||
activeDropdownItems.forEach((item) => {
|
||||
item.addEventListener('click', (e) => {
|
||||
e.preventDefault()
|
||||
const active = (item as HTMLElement).dataset.active || '2'
|
||||
|
||||
const labelSpan = activeDropdownButton.querySelector('span')
|
||||
if (labelSpan) labelSpan.textContent = active
|
||||
activeDropdownButton.dataset.active = active
|
||||
|
||||
activeDropdownItems.forEach((i) => i.classList.remove('active'))
|
||||
item.classList.add('active')
|
||||
|
||||
activeDropdown.hide()
|
||||
update()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
orientationInputs.forEach((input) => {
|
||||
input.addEventListener('change', update)
|
||||
})
|
||||
|
||||
fullwidthSwitch?.addEventListener('change', update)
|
||||
|
||||
// Initial update
|
||||
update()
|
||||
</script>
|
||||
@@ -0,0 +1,139 @@
|
||||
---
|
||||
title: Stepper
|
||||
description: Create timelines, wizards, or step-by-step progress bars. Ideal for shopping carts, sign-up forms, and more.
|
||||
toc: true
|
||||
---
|
||||
|
||||
## Examples
|
||||
|
||||
Stepper is built with CSS Grid and `<ol>` elements. By default, steps are displayed vertically. You can transform them into horizontal lists with responsive modifier classes.
|
||||
|
||||
### Basic
|
||||
|
||||
Here's a simple example of a vertical stepper.
|
||||
|
||||
<Example code={`<ol class="stepper">
|
||||
<li class="stepper-item active">Create account</li>
|
||||
<li class="stepper-item active">Confirm email</li>
|
||||
<li class="stepper-item">Update profile</li>
|
||||
<li class="stepper-item">Finish</li>
|
||||
</ol>`} />
|
||||
|
||||
### Responsive
|
||||
|
||||
Steppers sometimes need to be responsive, so you can use the responsive modifier classes to change them from vertical to horizontal at different breakpoints. Responsive modifier classes are available for `sm`, `md`, `lg`, `xl`, and `2xl` breakpoints.
|
||||
|
||||
<Example code={`<ol class="stepper stepper-horizontal-md">
|
||||
<li class="stepper-item active">Create account</li>
|
||||
<li class="stepper-item active">Confirm email</li>
|
||||
<li class="stepper-item">Update profile</li>
|
||||
<li class="stepper-item">Finish</li>
|
||||
</ol>`} />
|
||||
|
||||
### Gap
|
||||
|
||||
Customize the gap with styles that override the `--bs-stepper-gap` CSS variable.
|
||||
|
||||
<Example code={`<ol class="stepper" style="--bs-stepper-gap: 3rem">
|
||||
<li class="stepper-item active">Create account</li>
|
||||
<li class="stepper-item active">Confirm email</li>
|
||||
<li class="stepper-item">Update profile</li>
|
||||
<li class="stepper-item">Finish</li>
|
||||
</ol>`} />
|
||||
|
||||
### Variants
|
||||
|
||||
<Example code={`<ol class="stepper stepper-horizontal">
|
||||
<li class="stepper-item active theme-accent">Create account</li>
|
||||
<li class="stepper-item">Confirm email</li>
|
||||
<li class="stepper-item active theme-success">Update profile</li>
|
||||
<li class="stepper-item active theme-danger">Finish</li>
|
||||
</ol>`} />
|
||||
|
||||
### Overflow
|
||||
|
||||
Wrap your horizontal stepper in a `.stepper-overflow` container to enable horizontal scrolling when the stepper overflows its parent. Uses `container-type: inline-size` for container query support as opposed to a viewport-based media query.
|
||||
|
||||
<Example code={`<div class="stepper-overflow">
|
||||
<ol class="stepper stepper-horizontal">
|
||||
<li class="stepper-item active">Create account</li>
|
||||
<li class="stepper-item active">Verify email address</li>
|
||||
<li class="stepper-item">Complete profile setup</li>
|
||||
<li class="stepper-item">Add payment method</li>
|
||||
<li class="stepper-item">Review and confirm</li>
|
||||
<li class="stepper-item">Finish onboarding</li>
|
||||
</ol>
|
||||
</div>`} />
|
||||
|
||||
## Playground
|
||||
|
||||
Experiment with stepper options including orientation, breakpoints, step count, and more.
|
||||
|
||||
<StepperPlayground />
|
||||
|
||||
### Alignment
|
||||
|
||||
Use [text alignment utilities]([[docsref:/utilities/text-alignment]]) (because we use `display: inline-grid`) to align the steps. The inline grid arrangement allows us to keep the steps equal width and ensures the connecting lines are rendered correctly.
|
||||
|
||||
<Example code={`<ol class="stepper stepper-horizontal">
|
||||
<li class="stepper-item active">Default stepper</li>
|
||||
<li class="stepper-item active">Confirm email</li>
|
||||
<li class="stepper-item">Update profile</li>
|
||||
<li class="stepper-item">Finish</li>
|
||||
</ol>`} />
|
||||
|
||||
<Example class="text-center" code={`<ol class="stepper stepper-horizontal">
|
||||
<li class="stepper-item active">Center stepper</li>
|
||||
<li class="stepper-item active">Confirm email</li>
|
||||
<li class="stepper-item">Update profile</li>
|
||||
<li class="stepper-item">Finish</li>
|
||||
</ol>`} />
|
||||
|
||||
<Example class="text-end" code={`<ol class="stepper stepper-horizontal">
|
||||
<li class="stepper-item active">End stepper</li>
|
||||
<li class="stepper-item active">Confirm email</li>
|
||||
<li class="stepper-item">Update profile</li>
|
||||
<li class="stepper-item">Finish</li>
|
||||
</ol>`} />
|
||||
|
||||
Apply `.w-100` to the stepper to make it full width. Stepper items will be stretched to fill the available space. Alignment doesn't affect full-width steppers.
|
||||
|
||||
<Example code={`<ol class="stepper stepper-horizontal w-100">
|
||||
<li class="stepper-item active">Create account</li>
|
||||
<li class="stepper-item active">Confirm email</li>
|
||||
<li class="stepper-item">Update profile</li>
|
||||
<li class="stepper-item">Finish</li>
|
||||
</ol>`} />
|
||||
|
||||
### With anchors
|
||||
|
||||
Use anchor elements to build your stepper if it links across multiple pages. Add `role="button"` or use `<button>` elements if you're linking across sections in the same document.
|
||||
|
||||
Consider using our [link utilities]([[docsref:/utilities/link]]) for quick color control.
|
||||
|
||||
<Example code={`<div class="stepper">
|
||||
<a href="#" role="button" class="stepper-item link-body-emphasis active">Create account</a>
|
||||
<a href="#" role="button" class="stepper-item link-body-emphasis active">Confirm email</a>
|
||||
<a href="#" role="button" class="stepper-item link-secondary">Update profile</a>
|
||||
<a href="#" role="button" class="stepper-item link-secondary">Finish</a>
|
||||
</div>`} />
|
||||
|
||||
## CSS
|
||||
|
||||
### Variables
|
||||
|
||||
Steppers use [CSS variables]([[docsref:/customize/css-variables]]) for easier customization.
|
||||
|
||||
<ScssDocs name="stepper-css-vars" file="scss/_stepper.scss" />
|
||||
|
||||
### Sass variables
|
||||
|
||||
<ScssDocs name="stepper-variables" file="scss/_stepper.scss" />
|
||||
|
||||
### Sass mixin
|
||||
|
||||
<ScssDocs name="stepper-horizontal-mixin" file="scss/_stepper.scss" />
|
||||
|
||||
### Overflow wrapper
|
||||
|
||||
<ScssDocs name="stepper-overflow" file="scss/_stepper.scss" />
|
||||
Vendored
+1
@@ -21,6 +21,7 @@ export declare global {
|
||||
export const JsDocs: typeof import('@shortcodes/JsDocs.astro').default
|
||||
export const Placeholder: typeof import('@shortcodes/Placeholder.astro').default
|
||||
export const ScssDocs: typeof import('@shortcodes/ScssDocs.astro').default
|
||||
export const StepperPlayground: typeof import('@shortcodes/StepperPlayground.astro').default
|
||||
export const Swatch: typeof import('@shortcodes/Swatch.astro').default
|
||||
export const Table: typeof import('@shortcodes/Table.astro').default
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user