mirror of
https://github.com/tabler/tabler.git
synced 2026-08-05 19:03:18 +04:00
Add accessibility features and improve focus styles
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@tabler/core": patch
|
||||
"@tabler/preview": patch
|
||||
"@tabler/docs": patch
|
||||
---
|
||||
|
||||
Fixed accessibility issues for skip links, keyboard focus, `prefers-reduced-motion`, form labels, and action controls that incorrectly used links instead of `button` elements.
|
||||
@@ -98,6 +98,7 @@
|
||||
@forward 'ui/patterns';
|
||||
|
||||
@forward 'helpers/index';
|
||||
@forward 'helpers/accessibility';
|
||||
|
||||
@forward 'utils/background';
|
||||
@forward 'utils/colors';
|
||||
|
||||
@@ -1007,9 +1007,9 @@ $box-shadow-inset: inset 0 1px 2px rgba($black, 0.075) !default;
|
||||
$component-active-color: $white !default;
|
||||
$component-active-bg: $primary !default;
|
||||
|
||||
// Focus
|
||||
// Focus — opacity high enough for ≥3:1 non-text contrast (WCAG 2.2 SC 1.4.11)
|
||||
$focus-ring-width: 0.25rem !default;
|
||||
$focus-ring-opacity: 0.25 !default;
|
||||
$focus-ring-opacity: 0.75 !default;
|
||||
$focus-ring-color: color-mix(in srgb, var(--#{$prefix}primary) #{math.percentage($focus-ring-opacity)}, transparent) !default;
|
||||
$focus-ring-blur: 0 !default;
|
||||
$focus-ring-box-shadow: 0 0 $focus-ring-blur $focus-ring-width $focus-ring-color !default;
|
||||
@@ -1331,7 +1331,7 @@ $carousel-indicators-vertical-margin-inline-end: 1rem !default;
|
||||
|
||||
// Close
|
||||
$btn-close-width: 1em !default;
|
||||
$btn-close-padding-x: 0.25em !default;
|
||||
$btn-close-padding-x: 0.875em !default;
|
||||
$btn-close-padding-y: $btn-close-padding-x !default;
|
||||
$btn-close-color: $body-color !default;
|
||||
$btn-close-bg: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='#{$btn-close-color}'><path d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414'/></svg>") !default;
|
||||
|
||||
@@ -31,6 +31,10 @@
|
||||
.placeholder-glow {
|
||||
.placeholder {
|
||||
animation: placeholder-glow 2s ease-in-out infinite;
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +48,10 @@
|
||||
mask-image: linear-gradient(130deg, $black 55%, rgba(0, 0, 0, (1 - $placeholder-opacity-min)) 75%, $black 95%);
|
||||
mask-size: 200% 100%;
|
||||
animation: placeholder-wave 2s linear infinite;
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes placeholder-wave {
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
@use '../config' as *;
|
||||
|
||||
//
|
||||
// Skip link — visible only when focused
|
||||
//
|
||||
.skip-link {
|
||||
&:focus,
|
||||
&:focus-visible {
|
||||
position: fixed;
|
||||
top: 0.5rem;
|
||||
inset-inline-start: 0.5rem;
|
||||
z-index: 1080;
|
||||
width: auto;
|
||||
height: auto;
|
||||
padding: 0.5rem 1rem;
|
||||
margin: 0;
|
||||
overflow: visible;
|
||||
clip: auto;
|
||||
white-space: normal;
|
||||
background-color: var(--#{$prefix}bg-surface);
|
||||
color: var(--#{$prefix}primary);
|
||||
border: var(--#{$prefix}border-width) var(--#{$prefix}border-style) var(--#{$prefix}border-color);
|
||||
border-radius: var(--#{$prefix}border-radius);
|
||||
box-shadow: $focus-ring-box-shadow;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Windows High Contrast / forced colors — restore outlines when box-shadow is ignored
|
||||
//
|
||||
@media (forced-colors: active) {
|
||||
:focus-visible,
|
||||
.btn:focus-visible,
|
||||
.btn-action:focus-visible,
|
||||
.btn-close:focus-visible,
|
||||
.nav-link:focus-visible,
|
||||
.dropdown-item:focus-visible,
|
||||
.form-control:focus-visible,
|
||||
.form-select:focus-visible,
|
||||
.form-check-input:focus-visible,
|
||||
.page-link:focus-visible,
|
||||
.switch-icon:focus-visible,
|
||||
.calendar-date:focus-visible,
|
||||
button[data-bs-dismiss='toast']:focus-visible,
|
||||
button[data-dismiss='toast']:focus-visible {
|
||||
outline: 2px solid Highlight;
|
||||
outline-offset: 2px;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
:root,
|
||||
:host {
|
||||
font-size: 16px;
|
||||
font-size: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
@@ -119,6 +119,14 @@
|
||||
opacity: 0.3;
|
||||
animation-delay: -2s;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.wave-1,
|
||||
.wave-2,
|
||||
.wave-3 {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.section-divider-auto {
|
||||
|
||||
@@ -89,6 +89,10 @@
|
||||
|
||||
.badge-blink {
|
||||
animation: blink 2s infinite;
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -279,8 +279,12 @@
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 0;
|
||||
box-shadow: $focus-ring-box-shadow;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
@@ -366,4 +370,17 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
&.btn-animate-icon-pulse,
|
||||
&.btn-animate-icon-shake,
|
||||
&.btn-animate-icon-tada {
|
||||
&:hover,
|
||||
&:focus-visible {
|
||||
.icon {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +68,10 @@
|
||||
background: #fefeff;
|
||||
border-color: var(--#{$prefix}border-color);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
box-shadow: $focus-ring-box-shadow;
|
||||
}
|
||||
}
|
||||
|
||||
.date-today {
|
||||
|
||||
@@ -745,6 +745,10 @@ Card gradient
|
||||
|
||||
.card-gradient-animated {
|
||||
animation: gradient-animation 15s linear infinite;
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
.card-gradient-bottom {
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
--#{$prefix}btn-close-disabled-opacity: #{$btn-close-disabled-opacity};
|
||||
--#{$prefix}btn-close-size: #{$btn-close-width};
|
||||
|
||||
// content-box so padding grows the hit target without shrinking the icon
|
||||
box-sizing: content-box;
|
||||
width: var(--#{$prefix}btn-close-size);
|
||||
height: var(--#{$prefix}btn-close-size);
|
||||
padding: $btn-close-padding-y $btn-close-padding-x;
|
||||
|
||||
@@ -75,3 +75,11 @@
|
||||
animation: rotate-360 3s linear infinite;
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.icon-pulse,
|
||||
.icon-tada,
|
||||
.icon-rotate {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,5 +69,9 @@ Dimmer
|
||||
display: inline-block;
|
||||
content: '...';
|
||||
animation: animated-dots 1.2s steps(4, jump-none) infinite;
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +74,10 @@ Progress bar
|
||||
|
||||
&:before {
|
||||
animation: progress-indeterminate 1.5s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -112,6 +112,11 @@
|
||||
border-radius: inherit;
|
||||
opacity: 0.6;
|
||||
animation: 1s linear 2s backwards infinite status-pulsate-tertiary;
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
animation: none;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,5 +171,9 @@
|
||||
&:nth-child(3) {
|
||||
animation: 2s linear 1s infinite backwards status-pulsate-tertiary;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
animation: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,13 @@
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 0;
|
||||
box-shadow: $focus-ring-box-shadow;
|
||||
border-radius: var(--#{$prefix}border-radius);
|
||||
}
|
||||
|
||||
svg {
|
||||
|
||||
@@ -10,7 +10,14 @@
|
||||
|
||||
button[data-bs-dismiss='toast'],
|
||||
button[data-dismiss='toast'] {
|
||||
outline: none;
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 0;
|
||||
box-shadow: $focus-ring-box-shadow;
|
||||
}
|
||||
}
|
||||
|
||||
@include media-print {
|
||||
|
||||
@@ -48,7 +48,9 @@ const menu = docs.menu as MenuSection[]
|
||||
{level2.title} <span class="nav-link-toggle" />
|
||||
</a>
|
||||
) : (
|
||||
<a class={`nav-link${expanded ? ' active' : ''}`}>{level2.title}</a>
|
||||
<a class={`nav-link${expanded ? ' active' : ''}`} href={level2.url}>
|
||||
{level2.title}
|
||||
</a>
|
||||
)}
|
||||
|
||||
{hasChildren && (
|
||||
|
||||
@@ -182,6 +182,7 @@ const docsLinks = docs.links as DocsLink[];
|
||||
</head>
|
||||
|
||||
<body class="d-flex flex-column" style="background: var(--tblr-bg-surface)">
|
||||
<a href="#content" class="visually-hidden-focusable skip-link">Skip to main content</a>
|
||||
<!-- BEGIN GLOBAL THEME SCRIPT -->
|
||||
<script is:inline src="/dist/js/tabler-theme.js"></script>
|
||||
<!-- END GLOBAL THEME SCRIPT -->
|
||||
|
||||
@@ -10,18 +10,18 @@ import Example from '@components/Example.astro';
|
||||
|
||||
To inform users whether the entered value is correct or not, use either of the validation states. Thanks to that, users will immediately know which form elements they need to correct and, if the state displays as invalid, why the value is incorrect.
|
||||
|
||||
To apply the validation state to the form control, use the `.is-valid` and `.is-invalid` classes.
|
||||
To apply the validation state to the form control, use the `.is-valid` and `.is-invalid` classes. For assistive tech, also set `aria-invalid="true"` on invalid controls.
|
||||
|
||||
<Example>
|
||||
<input type="text" class="form-control is-valid" placeholder="Valid State..." /> <input type="text" class="form-control is-invalid" placeholder="Invalid State..." />
|
||||
<input type="text" class="form-control is-valid" placeholder="Valid State..." aria-invalid="false" /> <input type="text" class="form-control is-invalid" placeholder="Invalid State..." aria-invalid="true" />
|
||||
</Example>
|
||||
|
||||
## Validation feedback
|
||||
|
||||
To provide users with additional information about the validation state, you can use the `.valid-feedback` and `.invalid-feedback` classes.
|
||||
To provide users with additional information about the validation state, you can use the `.valid-feedback` and `.invalid-feedback` classes. Link the message with `aria-describedby` when you can.
|
||||
|
||||
<Example>
|
||||
<div> <label class="form-label required">City</label> <input type="text" class="form-control is-invalid" required /> <div class="invalid-feedback">Please provide a valid city.</div> </div>
|
||||
<div> <label class="form-label required" for="city-invalid">City</label> <input type="text" class="form-control is-invalid" id="city-invalid" required aria-invalid="true" aria-describedby="city-invalid-feedback" /> <div class="invalid-feedback" id="city-invalid-feedback">Please provide a valid city.</div> </div>
|
||||
</Example>
|
||||
|
||||
You can also use the `.valid-feedback` to provide users with positive feedback.
|
||||
|
||||
@@ -226,11 +226,11 @@ function greetUser(name) {
|
||||
<FormGroup label="Textarea">
|
||||
<textarea class="form-control" rows="3" placeholder="Enter message"></textarea>
|
||||
</FormGroup>
|
||||
<FormGroup label="Checkboxes">
|
||||
<FormGroup label="Checkboxes" associate={false}>
|
||||
<Check title="Option 1" type="checkbox" />
|
||||
<Check title="Option 2" type="checkbox" checked />
|
||||
</FormGroup>
|
||||
<FormGroup label="Radio Buttons">
|
||||
<FormGroup label="Radio Buttons" associate={false}>
|
||||
<Check title="Option 1" type="radio" name="radio-demo" />
|
||||
<Check title="Option 2" type="radio" name="radio-demo" checked />
|
||||
</FormGroup>
|
||||
|
||||
@@ -166,7 +166,7 @@ const countries = flags as { name: string }[]
|
||||
</FormGroup>
|
||||
</div>
|
||||
<div>
|
||||
<FormGroup label="Membership" class="">
|
||||
<FormGroup label="Membership" class="" associate={false}>
|
||||
<div class="d-flex flex-row gap-4">
|
||||
<Check title="Free" type="radio" name="membership" checked />
|
||||
<Check title="Paid" type="radio" name="membership" />
|
||||
|
||||
@@ -25,7 +25,7 @@ import FormGroup from '@ui/FormGroup.astro'
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="py-5">
|
||||
<main id="content" class="py-5">
|
||||
<div class="container container-tight">
|
||||
<div class="page-header">
|
||||
<h1 class="page-title">Let's set up your account</h1>
|
||||
@@ -48,7 +48,7 @@ import FormGroup from '@ui/FormGroup.astro'
|
||||
<option value="other">Other</option>
|
||||
</select>
|
||||
</FormGroup>
|
||||
<FormGroup label="Team size" class="">
|
||||
<FormGroup label="Team size" class="" associate={false}>
|
||||
<div class="form-selectgroup">
|
||||
<label class="form-selectgroup-item">
|
||||
<input type="radio" name="team-size" value="1" class="form-selectgroup-input" checked />
|
||||
@@ -68,7 +68,7 @@ import FormGroup from '@ui/FormGroup.astro'
|
||||
</label>
|
||||
</div>
|
||||
</FormGroup>
|
||||
<FormGroup label="What are you planning to use this for?" class="">
|
||||
<FormGroup label="What are you planning to use this for?" class="" associate={false}>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" value="personal" id="use-personal" checked />
|
||||
<label class="form-check-label" for="use-personal"> Personal projects </label>
|
||||
@@ -86,7 +86,7 @@ import FormGroup from '@ui/FormGroup.astro'
|
||||
<label class="form-check-label" for="use-learning"> Learning and experimentation </label>
|
||||
</div>
|
||||
</FormGroup>
|
||||
<FormGroup label="How did you hear about us?" class="">
|
||||
<FormGroup label="How did you hear about us?" class="" associate={false}>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="referral" value="search" id="ref-search" />
|
||||
<label class="form-check-label" for="ref-search"> Search engine </label>
|
||||
|
||||
@@ -46,7 +46,7 @@ import { site } from '@shared/lib/site'
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="page page-center align-items-center">
|
||||
<main id="content" class="page page-center align-items-center">
|
||||
<div class="screenshot d-flex flex-column justify-content-center align-items-center px-12">
|
||||
<div class="screenshot-logo">
|
||||
<NavbarLogo class="brand-gray" />
|
||||
@@ -72,5 +72,5 @@ import { site } from '@shared/lib/site'
|
||||
<a href="?theme=dark" class="nav-link hide-theme-dark">Dark</a>
|
||||
<a href="?theme=dark" class="nav-link active hide-theme-light">Dark</a>
|
||||
</nav>
|
||||
</div>
|
||||
</main>
|
||||
</BaseLayout>
|
||||
|
||||
@@ -8,7 +8,7 @@ import photos from '@data/photos.json'
|
||||
---
|
||||
|
||||
<BaseLayout title="Sign in with cover" bodyClass="d-flex flex-column bg-white">
|
||||
<div class="row g-0 flex-fill">
|
||||
<main id="content" class="row g-0 flex-fill">
|
||||
<div class="col-12 col-lg-6 col-xl-4 border-top-wide border-primary d-flex flex-column justify-content-center">
|
||||
<div class="container container-tight my-5 px-lg-5">
|
||||
<div class="text-center mb-4">
|
||||
@@ -20,12 +20,12 @@ import photos from '@data/photos.json'
|
||||
<SignInForm />
|
||||
|
||||
<div class="text-center text-secondary mt-3">
|
||||
Don't have account yet? <a href="./sign-up.html" tabindex="-1">Sign up</a>
|
||||
Don't have account yet? <a href="./sign-up.html">Sign up</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-lg-6 col-xl-8 d-none d-lg-block">
|
||||
<Photo photo={photos[11]} class="bg-cover h-100 min-vh-100" background />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</BaseLayout>
|
||||
|
||||
@@ -7,7 +7,7 @@ import people from '@data/people.json'
|
||||
const person = people[0]
|
||||
---
|
||||
|
||||
<form class="card card-md" action="./" method="get" autocomplete="off" novalidate>
|
||||
<form class="card card-md" action="./" method="get" novalidate>
|
||||
<div class="card-body text-center">
|
||||
<div class="mb-4">
|
||||
<CardTitle as="h2">Account Locked</CardTitle>
|
||||
@@ -20,7 +20,8 @@ const person = people[0]
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<input type="password" class="form-control" placeholder="Password…" />
|
||||
<label class="visually-hidden" for="lock-password">Password</label>
|
||||
<input type="password" class="form-control" placeholder="Password…" id="lock-password" autocomplete="current-password" required />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
||||
@@ -10,7 +10,7 @@ const years = Array.from({ length: 11 }, (_, i) => 2020 + i)
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<div class="form-label">Card number</div>
|
||||
<input type="text" name="input-mask" class="form-control" data-mask="0000 0000 0000 0000" data-mask-visible="true" placeholder="0000 0000 0000 0000" autocomplete="off" />
|
||||
<input type="text" name="input-mask" class="form-control" data-mask="0000 0000 0000 0000" data-mask-visible="true" placeholder="0000 0000 0000 0000" autocomplete="cc-number" />
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
@@ -20,7 +20,7 @@ const years = Array.from({ length: 11 }, (_, i) => 2020 + i)
|
||||
|
||||
<div class="row">
|
||||
<div class="col-8">
|
||||
<FormGroup label="Expiration date">
|
||||
<FormGroup label="Expiration date" associate={false}>
|
||||
<div class="row g-2">
|
||||
<div class="col">
|
||||
<select class="form-select">
|
||||
|
||||
@@ -4,14 +4,14 @@ import Button from '@ui/Button.astro'
|
||||
import FormGroup from '@ui/FormGroup.astro'
|
||||
---
|
||||
|
||||
<form class="card card-md" action="./" method="get" autocomplete="off" novalidate>
|
||||
<form class="card card-md" action="./" method="get" novalidate>
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-center mb-4">Forgot password</h2>
|
||||
|
||||
<p class="text-secondary mb-4">Enter your email address and your password will be reset and emailed to you.</p>
|
||||
|
||||
<FormGroup label="Email address">
|
||||
<input type="email" class="form-control" placeholder="Enter email" />
|
||||
<FormGroup label="Email address" for="forgot-email">
|
||||
<input type="email" class="form-control" placeholder="Enter email" id="forgot-email" autocomplete="email" required />
|
||||
</FormGroup>
|
||||
<FormFooter>
|
||||
<Button text="Send me new password" block color="primary" icon="mail" />
|
||||
|
||||
@@ -26,5 +26,5 @@ import SignInForm from './SignInForm.astro'
|
||||
</div>
|
||||
|
||||
<div class="text-center text-secondary mt-3">
|
||||
Don't have account yet? <a href="./sign-up.html" tabindex="-1">Sign up</a>
|
||||
Don't have account yet? <a href="./sign-up.html">Sign up</a>
|
||||
</div>
|
||||
|
||||
@@ -4,18 +4,18 @@ import FormGroup from '@ui/FormGroup.astro'
|
||||
import InputGroup from '@ui/InputGroup.astro'
|
||||
---
|
||||
|
||||
<form action="./" method="get" autocomplete="off" novalidate>
|
||||
<FormGroup label="Email address">
|
||||
<input type="email" class="form-control" placeholder="your@email.com" autocomplete="off" />
|
||||
<form action="./" method="get" novalidate>
|
||||
<FormGroup label="Email address" for="signin-email">
|
||||
<input type="email" class="form-control" placeholder="your@email.com" id="signin-email" autocomplete="email" required />
|
||||
</FormGroup>
|
||||
<div class="mb-2">
|
||||
<label class="form-label">
|
||||
<label class="form-label" for="signin-password">
|
||||
Password
|
||||
<span class="form-label-description">
|
||||
<a href="./forgot-password.html">I forgot password</a>
|
||||
</span>
|
||||
</label>
|
||||
<InputGroup type="password" placeholder="Your password" flat appendButton={[{ icon: 'eye', description: 'Show password' }]} />
|
||||
<InputGroup type="password" placeholder="Your password" flat id="signin-password" autocomplete="current-password" required appendButton={[{ icon: 'eye', description: 'Show password' }]} />
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-check">
|
||||
|
||||
@@ -4,23 +4,23 @@ import InputGroup from '@ui/InputGroup.astro'
|
||||
import FormGroup from '@ui/FormGroup.astro'
|
||||
---
|
||||
|
||||
<form class="card card-md" action="./" method="get" autocomplete="off" novalidate>
|
||||
<form class="card card-md" action="./" method="get" novalidate>
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-center mb-4">Create new account</h2>
|
||||
|
||||
<FormGroup label="Name">
|
||||
<input type="text" class="form-control" placeholder="Enter name" />
|
||||
<FormGroup label="Name" for="signup-name">
|
||||
<input type="text" class="form-control" placeholder="Enter name" id="signup-name" autocomplete="name" required />
|
||||
</FormGroup>
|
||||
<FormGroup label="Email address">
|
||||
<input type="email" class="form-control" placeholder="Enter email" />
|
||||
<FormGroup label="Email address" for="signup-email">
|
||||
<input type="email" class="form-control" placeholder="Enter email" id="signup-email" autocomplete="email" required />
|
||||
</FormGroup>
|
||||
<FormGroup label="Password">
|
||||
<InputGroup type="password" appendButton={[{ icon: 'eye', description: 'Show password' }]} flat placeholder="Password" />
|
||||
<FormGroup label="Password" for="signup-password">
|
||||
<InputGroup type="password" appendButton={[{ icon: 'eye', description: 'Show password' }]} flat placeholder="Password" id="signup-password" autocomplete="new-password" required />
|
||||
</FormGroup>
|
||||
<div class="mb-3">
|
||||
<label class="form-check">
|
||||
<input type="checkbox" class="form-check-input" />
|
||||
<span class="form-check-label">Agree the <a href="./terms-of-service.html" tabindex="-1">terms and policy</a>.</span>
|
||||
<span class="form-check-label">Agree the <a href="./terms-of-service.html">terms and policy</a>.</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -31,5 +31,5 @@ import FormGroup from '@ui/FormGroup.astro'
|
||||
</form>
|
||||
|
||||
<div class="text-center text-secondary mt-3">
|
||||
Already have account? <a href="./sign-in.html" tabindex="-1">Sign in</a>
|
||||
Already have account? <a href="./sign-in.html">Sign in</a>
|
||||
</div>
|
||||
|
||||
@@ -67,7 +67,7 @@ const states = (selects as { states: { options: Record<string, { name: string; s
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<FormGroup label="Input group">
|
||||
<FormGroup label="Input group" associate={false}>
|
||||
<div class="input-group mb-2">
|
||||
<input type="text" class="form-control" placeholder="Search for…" />
|
||||
<button class="btn" type="button">Go!</button>
|
||||
@@ -93,7 +93,7 @@ const states = (selects as { states: { options: Record<string, { name: string; s
|
||||
</div>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup label="Icon input">
|
||||
<FormGroup label="Icon input" associate={false}>
|
||||
<div class="input-icon mb-3">
|
||||
<input type="text" value="" class="form-control" placeholder="Search…" />
|
||||
<span class="input-icon-addon">
|
||||
@@ -108,7 +108,7 @@ const states = (selects as { states: { options: Record<string, { name: string; s
|
||||
</div>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup label="Loader input">
|
||||
<FormGroup label="Loader input" associate={false}>
|
||||
<div class="input-icon mb-3">
|
||||
<input type="text" value="" class="form-control" placeholder="Loading…" />
|
||||
<span class="input-icon-addon">
|
||||
|
||||
@@ -3,7 +3,7 @@ import Icon from '@ui/Icon.astro'
|
||||
import FormGroup from '@ui/FormGroup.astro'
|
||||
---
|
||||
|
||||
<FormGroup label="Form control rounded">
|
||||
<FormGroup label="Form control rounded" associate={false}>
|
||||
<input type="text" class="form-control form-control-rounded mb-2" name="Form control rounded" placeholder="Text.." />
|
||||
|
||||
<div class="input-icon">
|
||||
@@ -18,7 +18,7 @@ import FormGroup from '@ui/FormGroup.astro'
|
||||
<input type="text" class="form-control form-control-flush" name="Form control flush" placeholder="Text.." />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup label="Input group">
|
||||
<FormGroup label="Input group" associate={false}>
|
||||
<div class="input-group mb-2">
|
||||
<span class="input-group-text">@</span>
|
||||
<input type="text" class="form-control" placeholder="username" autocomplete="off" />
|
||||
@@ -36,7 +36,7 @@ import FormGroup from '@ui/FormGroup.astro'
|
||||
</div>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup label="Input with checkbox or radios">
|
||||
<FormGroup label="Input with checkbox or radios" associate={false}>
|
||||
<div class="input-group mb-2">
|
||||
<span class="input-group-text">
|
||||
<input class="form-check-input m-0" type="checkbox" checked />
|
||||
@@ -95,7 +95,7 @@ import FormGroup from '@ui/FormGroup.astro'
|
||||
</div>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup label="Floating inputs">
|
||||
<FormGroup label="Floating inputs" associate={false}>
|
||||
<div class="form-floating mb-3">
|
||||
<input type="email" class="form-control" id="floating-input" value="name@example.com" autocomplete="off" />
|
||||
<label for="floating-input">Email address</label>
|
||||
|
||||
@@ -13,7 +13,7 @@ const imageRadios = horizontal.slice(10, 16)
|
||||
const blueHex = (site as { colors: { blue: { hex: string } } }).colors.blue.hex
|
||||
---
|
||||
|
||||
<FormGroup label="Image Check">
|
||||
<FormGroup label="Image Check" associate={false}>
|
||||
<div class="row g-2">
|
||||
{
|
||||
imageChecks.map((photo, i) => {
|
||||
@@ -33,7 +33,7 @@ const blueHex = (site as { colors: { blue: { hex: string } } }).colors.blue.hex
|
||||
</div>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup label="Image Check Radio">
|
||||
<FormGroup label="Image Check Radio" associate={false}>
|
||||
<div class="row g-2">
|
||||
{
|
||||
imageRadios.map((photo, i) => {
|
||||
@@ -62,9 +62,9 @@ const blueHex = (site as { colors: { blue: { hex: string } } }).colors.blue.hex
|
||||
|
||||
{
|
||||
[false, true].map((lite) => (
|
||||
<FormGroup label={`Validation States${lite ? ' (lite)' : ''}`}>
|
||||
<input type="text" class:list={['form-control is-valid', lite && 'is-valid-lite', 'mb-2']} placeholder="Valid State.." />
|
||||
<input type="text" class:list={['form-control is-invalid', lite && 'is-invalid-lite']} placeholder="Invalid State.." />
|
||||
<FormGroup label={`Validation States${lite ? ' (lite)' : ''}`} associate={false}>
|
||||
<input type="text" class:list={['form-control is-valid', lite && 'is-valid-lite', 'mb-2']} placeholder="Valid State.." aria-invalid="false" />
|
||||
<input type="text" class:list={['form-control is-invalid', lite && 'is-invalid-lite']} placeholder="Invalid State.." aria-invalid="true" />
|
||||
{!lite && <div class="invalid-feedback">Invalid feedback</div>}
|
||||
</FormGroup>
|
||||
))
|
||||
@@ -73,16 +73,16 @@ const blueHex = (site as { colors: { blue: { hex: string } } }).colors.blue.hex
|
||||
<label class="form-label">Form fieldset</label>
|
||||
<fieldset class="form-fieldset">
|
||||
<FormGroup label="Full name" required>
|
||||
<input type="text" class="form-control" autocomplete="off" />
|
||||
<input type="text" class="form-control" autocomplete="name" required />
|
||||
</FormGroup>
|
||||
<FormGroup label="Company" required>
|
||||
<input type="text" class="form-control" autocomplete="off" />
|
||||
<input type="text" class="form-control" autocomplete="organization" required />
|
||||
</FormGroup>
|
||||
<FormGroup label="Email" required>
|
||||
<input type="email" class="form-control" autocomplete="off" />
|
||||
<input type="email" class="form-control" autocomplete="email" required />
|
||||
</FormGroup>
|
||||
<FormGroup label="Phone number">
|
||||
<input type="tel" class="form-control" autocomplete="off" />
|
||||
<input type="tel" class="form-control" autocomplete="tel" />
|
||||
</FormGroup>
|
||||
<label class="form-check">
|
||||
<input type="checkbox" class="form-check-input" />
|
||||
|
||||
@@ -5,7 +5,7 @@ import FormGroup from '@ui/FormGroup.astro'
|
||||
import Avatar from '@ui/Avatar.astro'
|
||||
---
|
||||
|
||||
<FormGroup label="Simple selectgroup">
|
||||
<FormGroup label="Simple selectgroup" associate={false}>
|
||||
<div class="form-selectgroup">
|
||||
<label class="form-selectgroup-item">
|
||||
<input type="checkbox" name="name" value="HTML" class="form-selectgroup-input" checked />
|
||||
@@ -26,7 +26,7 @@ import Avatar from '@ui/Avatar.astro'
|
||||
</div>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup label="Icon input">
|
||||
<FormGroup label="Icon input" associate={false}>
|
||||
<div class="form-selectgroup">
|
||||
<label class="form-selectgroup-item">
|
||||
<input type="checkbox" name="name" value="sun" class="form-selectgroup-input" checked />
|
||||
@@ -51,7 +51,7 @@ import Avatar from '@ui/Avatar.astro'
|
||||
</div>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup label="Selectgroup with icons and text">
|
||||
<FormGroup label="Selectgroup with icons and text" associate={false}>
|
||||
<div class="form-selectgroup">
|
||||
<label class="form-selectgroup-item">
|
||||
<input type="radio" name="icons" value="home" class="form-selectgroup-input" checked />
|
||||
@@ -72,7 +72,7 @@ import Avatar from '@ui/Avatar.astro'
|
||||
</div>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup label="Different style">
|
||||
<FormGroup label="Different style" associate={false}>
|
||||
<div class="form-selectgroup form-selectgroup-pills">
|
||||
<label class="form-selectgroup-item">
|
||||
<input type="checkbox" name="name" value="HTML" class="form-selectgroup-input" checked />
|
||||
@@ -93,7 +93,7 @@ import Avatar from '@ui/Avatar.astro'
|
||||
</div>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup label="Payment method">
|
||||
<FormGroup label="Payment method" associate={false}>
|
||||
<div class="form-selectgroup form-selectgroup-boxes d-flex flex-column">
|
||||
<label class="form-selectgroup-item flex-fill">
|
||||
<input type="radio" name="form-payment" value="visa" class="form-selectgroup-input" />
|
||||
@@ -133,7 +133,7 @@ import Avatar from '@ui/Avatar.astro'
|
||||
</div>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup label="Project Manager">
|
||||
<FormGroup label="Project Manager" associate={false}>
|
||||
<div class="form-selectgroup form-selectgroup-boxes d-flex flex-column">
|
||||
<label class="form-selectgroup-item flex-fill">
|
||||
<input type="checkbox" name="form-project-manager[]" value="1" class="form-selectgroup-input" />
|
||||
@@ -213,7 +213,7 @@ import Avatar from '@ui/Avatar.astro'
|
||||
</div>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup label="Buttons group">
|
||||
<FormGroup label="Buttons group" associate={false}>
|
||||
<ButtonGroup class="w-100">
|
||||
<input type="radio" class="btn-check" name="btn-radio-basic" id="btn-radio-basic-1" autocomplete="off" checked />
|
||||
<label for="btn-radio-basic-1" {...{ type: 'button' } as unknown as astroHTML.JSX.LabelHTMLAttributes} class="btn">1 min</label>
|
||||
@@ -226,7 +226,7 @@ import Avatar from '@ui/Avatar.astro'
|
||||
</ButtonGroup>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup label="Buttons group with dropdown">
|
||||
<FormGroup label="Buttons group with dropdown" associate={false}>
|
||||
<ButtonGroup class="w-100">
|
||||
<input type="radio" class="btn-check" name="btn-radio-dropdown" id="btn-radio-dropdown-1" autocomplete="off" checked />
|
||||
<label for="btn-radio-dropdown-1" {...{ type: 'button' } as unknown as astroHTML.JSX.LabelHTMLAttributes} class="btn">Option 1</label>
|
||||
@@ -250,7 +250,7 @@ import Avatar from '@ui/Avatar.astro'
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<FormGroup label="Vertical buttons group">
|
||||
<FormGroup label="Vertical buttons group" associate={false}>
|
||||
<ButtonGroup vertical={true} class="w-100">
|
||||
<input type="radio" class="btn-check" name="btn-radio-vertical" id="btn-radio-vertical-1" autocomplete="off" checked />
|
||||
<label for="btn-radio-vertical-1" {...{ type: 'button' } as unknown as astroHTML.JSX.LabelHTMLAttributes} class="btn">Button 1</label>
|
||||
@@ -266,7 +266,7 @@ import Avatar from '@ui/Avatar.astro'
|
||||
</FormGroup>
|
||||
</div>
|
||||
<div class="col">
|
||||
<FormGroup label="Vertical with dropdown">
|
||||
<FormGroup label="Vertical with dropdown" associate={false}>
|
||||
<ButtonGroup vertical={true} class="w-100">
|
||||
<input type="radio" class="btn-check" name="btn-radio-vertical-dropdown" id="btn-radio-vertical-dropdown-1" autocomplete="off" checked />
|
||||
<label for="btn-radio-vertical-dropdown-1" {...{ type: 'button' } as unknown as astroHTML.JSX.LabelHTMLAttributes} class="btn">Button 1</label>
|
||||
@@ -294,7 +294,7 @@ import Avatar from '@ui/Avatar.astro'
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<FormGroup label="Toolbar">
|
||||
<FormGroup label="Toolbar" associate={false}>
|
||||
<ButtonGroup class="w-100">
|
||||
<input type="radio" class="btn-check" name="btn-radio-toolbar" id="btn-radio-toolbar-1" autocomplete="off" checked />
|
||||
<label for="btn-radio-toolbar-1" class="btn btn-icon"><Icon name="bold" /></label>
|
||||
|
||||
@@ -78,7 +78,7 @@ import FormGroup from '@ui/FormGroup.astro'
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<FormGroup label="Checkboxes with description">
|
||||
<FormGroup label="Checkboxes with description" associate={false}>
|
||||
<label class="form-check">
|
||||
<input class="form-check-input" type="checkbox" />
|
||||
<span class="form-check-label"> Default checkbox </span>
|
||||
@@ -119,7 +119,7 @@ import FormGroup from '@ui/FormGroup.astro'
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<FormGroup label="Notification">
|
||||
<FormGroup label="Notification" associate={false}>
|
||||
<div class="divide-y">
|
||||
<div>
|
||||
<label class="row">
|
||||
@@ -159,7 +159,7 @@ import FormGroup from '@ui/FormGroup.astro'
|
||||
<input type="file" class="form-control" />
|
||||
</div>
|
||||
|
||||
<FormGroup label="Date of birth">
|
||||
<FormGroup label="Date of birth" associate={false}>
|
||||
<div class="row g-2">
|
||||
<div class="col-5">
|
||||
<select name="user[month]" class="form-select">
|
||||
|
||||
@@ -75,7 +75,7 @@ const datalistCountries = (flags as { name: string }[]).slice(0, 10)
|
||||
<Select key="labels" indicator="label" />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup label="Advanced select with validation state">
|
||||
<FormGroup label="Advanced select with validation state" associate={false}>
|
||||
<Select id="countries-valid" key="countries" state="valid" class="mb-3" />
|
||||
<Select id="countries-invalid" key="countries" state="invalid" />
|
||||
</FormGroup>
|
||||
|
||||
@@ -63,7 +63,7 @@ import FormGroup from '@ui/FormGroup.astro'
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<FormGroup label="Social links" class="mb-4">
|
||||
<FormGroup label="Social links" class="mb-4" associate={false}>
|
||||
<div class="row g-2">
|
||||
<div class="col-12">
|
||||
<div class="input-group">
|
||||
|
||||
@@ -6,7 +6,7 @@ import brands from '@data/brands.json'
|
||||
|
||||
<!-- BEGIN APPS -->
|
||||
<div class="nav-item dropdown d-none d-md-flex">
|
||||
<a href="#" class="nav-link px-0" data-bs-toggle="dropdown" tabindex="-1" aria-label="Show app menu" data-bs-auto-close="outside" aria-expanded="false">
|
||||
<a href="#" class="nav-link px-0" data-bs-toggle="dropdown" aria-label="Show app menu" data-bs-auto-close="outside" aria-expanded="false">
|
||||
<Icon name="apps" />
|
||||
</a>
|
||||
<!-- BEGIN NAVBAR APPS -->
|
||||
|
||||
@@ -4,7 +4,7 @@ import languages from '@data/languages.json'
|
||||
|
||||
<!-- BEGIN LANGUAGE SELECTOR -->
|
||||
<div class="nav-item dropdown d-none d-md-flex">
|
||||
<a href="#" class="nav-link px-0" data-bs-toggle="dropdown" tabindex="-1" aria-label="Select language" data-bs-auto-close="outside" aria-expanded="false"> EN </a>
|
||||
<a href="#" class="nav-link px-0" data-bs-toggle="dropdown" aria-label="Select language" data-bs-auto-close="outside" aria-expanded="false"> EN </a>
|
||||
<div class="dropdown-menu dropdown-menu-arrow dropdown-menu-end">
|
||||
{
|
||||
languages.map((language) => (
|
||||
|
||||
@@ -8,7 +8,7 @@ import CardTitle from '@ui/CardTitle.astro'
|
||||
|
||||
<!-- BEGIN NOTIFICATIONS -->
|
||||
<div class="nav-item dropdown d-none d-md-flex">
|
||||
<a href="#" class="nav-link px-0" data-bs-toggle="dropdown" tabindex="-1" aria-label="Show notifications" data-bs-auto-close="outside" aria-expanded="false">
|
||||
<a href="#" class="nav-link px-0" data-bs-toggle="dropdown" aria-label="Show notifications" data-bs-auto-close="outside" aria-expanded="false">
|
||||
<Icon name="bell" />
|
||||
<span class="badge bg-red"></span>
|
||||
</a>
|
||||
|
||||
@@ -16,7 +16,7 @@ const siteColors = Object.keys((site as { colors: Record<string, unknown> }).col
|
||||
const colors = ['dark', 'white', ...siteColors]
|
||||
---
|
||||
|
||||
<FormGroup label={label}>
|
||||
<FormGroup label={label} associate={false}>
|
||||
<div class="row g-2">
|
||||
{
|
||||
colors.map((color, i) => (
|
||||
|
||||
@@ -94,7 +94,7 @@ const libJsFiles = (head: boolean) => pageLibEntries.filter(([, lib]) => Boolean
|
||||
</head>
|
||||
|
||||
<body class={bodyClass}>
|
||||
<a href="#content" class="visually-hidden skip-link">Skip to main content</a>
|
||||
<a href="#content" class="visually-hidden-focusable skip-link">Skip to main content</a>
|
||||
|
||||
<!-- BEGIN GLOBAL THEME SCRIPT -->
|
||||
<script is:inline src={`${base}/dist/js/tabler-theme${min}.js`}></script>
|
||||
|
||||
@@ -26,9 +26,9 @@ const header = error.header ?? 'Oops… You just found an error page'
|
||||
<BaseLayout title={title} bodyClass="border-top-wide border-primary">
|
||||
<!-- BEGIN PAGE BODY -->
|
||||
<div class="page page-center">
|
||||
<div class="container-tight py-4">
|
||||
<main id="content" class="container-tight py-4">
|
||||
<Empty icon="settings" illustration={error.illustration} iconText={error.title != null ? String(error.title) : undefined} title={header} subtitle={error.description} buttonIcon="arrow-left" buttonText="Take me home" />
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<!-- END PAGE BODY -->
|
||||
</BaseLayout>
|
||||
|
||||
@@ -28,8 +28,8 @@ const { title, pageLibs, bodyClass } = Astro.props
|
||||
<!-- END NAVBAR -->
|
||||
|
||||
<!-- BEGIN PAGE BODY -->
|
||||
<div class="page">
|
||||
<main id="content" class="page">
|
||||
<slot />
|
||||
</div>
|
||||
</main>
|
||||
<!-- END PAGE BODY -->
|
||||
</BaseLayout>
|
||||
|
||||
@@ -14,7 +14,7 @@ const { title, containerSize = 'tight', hideLogo } = Astro.props
|
||||
<BaseLayout title={title}>
|
||||
<!-- BEGIN PAGE BODY -->
|
||||
<div class="page page-center">
|
||||
<div class={`container container-${containerSize} py-4`}>
|
||||
<main id="content" class={`container container-${containerSize} py-4`}>
|
||||
{
|
||||
!hideLogo && (
|
||||
<div class="text-center mb-4">
|
||||
@@ -24,7 +24,7 @@ const { title, containerSize = 'tight', hideLogo } = Astro.props
|
||||
}
|
||||
|
||||
<slot />
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<!-- END PAGE BODY -->
|
||||
</BaseLayout>
|
||||
|
||||
@@ -136,16 +136,16 @@ const perPageDefault = perPage[1]
|
||||
</div>
|
||||
<div class="card-footer d-flex align-items-center">
|
||||
<div class="dropdown">
|
||||
<a class="btn dropdown-toggle" data-bs-toggle="dropdown">
|
||||
<button type="button" class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<span id="page-count" class="me-1">{perPage[1]}</span>
|
||||
<span>records</span>
|
||||
</a>
|
||||
</button>
|
||||
<div class="dropdown-menu">
|
||||
{
|
||||
perPage.map((record) => (
|
||||
<a class="dropdown-item" onclick="setPageListItems(event)" data-value={record}>
|
||||
<button type="button" class="dropdown-item" onclick="setPageListItems(event)" data-value={record}>
|
||||
{record} records
|
||||
</a>
|
||||
</button>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
@@ -163,10 +163,12 @@ const perPageDefault = perPage[1]
|
||||
// Astro wraps define:vars scripts in an IIFE, so this needs an explicit window
|
||||
// assignment to stay reachable from the onclick="setPageListItems(event)" markup below.
|
||||
window.setPageListItems = function setPageListItems(e) {
|
||||
window.tabler_list[tableId].page = parseInt(e.target.dataset.value)
|
||||
const item = e.target.closest('[data-value]')
|
||||
if (!item) return
|
||||
window.tabler_list[tableId].page = parseInt(item.dataset.value)
|
||||
window.tabler_list[tableId].update()
|
||||
|
||||
document.querySelector('#page-count').innerHTML = e.target.dataset.value
|
||||
document.querySelector('#page-count').innerHTML = item.dataset.value
|
||||
}
|
||||
|
||||
function initAdvancedTable() {
|
||||
|
||||
@@ -41,10 +41,13 @@ interface Props {
|
||||
dismiss?: boolean
|
||||
}
|
||||
|
||||
const { text = 'Button', href, external, element: Element = 'a', type, id, color, outline, ghost, height, size, block, disabled, square, loading, action, pill, link, class: className, icon, iconColor, iconEnd, iconOnly, dots, modalId, toastId, dismiss } = Astro.props
|
||||
const { text = 'Button', href, external, element, type, id, color, outline, ghost, height, size, block, disabled, square, loading, action, pill, link, class: className, icon, iconColor, iconEnd, iconOnly, dots, modalId, toastId, dismiss } = Astro.props
|
||||
|
||||
// href defaults to '#'; internal hrefs are prefixed with '/'.
|
||||
const resolvedHref = href ? (external ? href : `/${href}`) : '#'
|
||||
// Actions (modal/toast/dismiss) and bare buttons use <button>. Real URLs use <a>.
|
||||
const isAction = Boolean(modalId || toastId || dismiss)
|
||||
const Element = element ?? (href && !isAction ? 'a' : 'button')
|
||||
const resolvedHref = Element === 'a' ? (href ? (external ? href : `/${href}`) : undefined) : undefined
|
||||
const resolvedType = Element === 'button' ? (type ?? 'button') : undefined
|
||||
|
||||
const classes = [
|
||||
'btn',
|
||||
@@ -64,8 +67,8 @@ const classes = [
|
||||
---
|
||||
|
||||
<Element
|
||||
href={Element === 'a' ? resolvedHref : undefined}
|
||||
type={type}
|
||||
href={resolvedHref}
|
||||
type={resolvedType}
|
||||
id={id}
|
||||
class:list={classes}
|
||||
target={external ? '_blank' : undefined}
|
||||
@@ -74,6 +77,7 @@ const classes = [
|
||||
data-bs-target={modalId ? `#modal-${modalId}` : toastId ? `#toast-${toastId}` : undefined}
|
||||
aria-label={iconOnly ? text : undefined}
|
||||
data-bs-dismiss={dismiss ? 'modal' : undefined}
|
||||
disabled={Element === 'button' && disabled ? true : undefined}
|
||||
>
|
||||
{icon && <Icon name={icon} color={iconColor} />}
|
||||
{!iconOnly && text}
|
||||
|
||||
@@ -10,13 +10,13 @@ const optionsArray = options ?? []
|
||||
---
|
||||
|
||||
<div class="dropdown">
|
||||
<a href="#" class="btn dropdown-toggle" data-bs-toggle="dropdown">{mainBtn}</a>
|
||||
<button type="button" class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">{mainBtn}</button>
|
||||
<div class="dropdown-menu">
|
||||
{
|
||||
optionsArray.map((option) => (
|
||||
<a class="dropdown-item" href="#">
|
||||
<button type="button" class="dropdown-item">
|
||||
{option}
|
||||
</a>
|
||||
</button>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -9,10 +9,10 @@ const { id, label = 'Select time range', value = 'Last 7 days' } = Astro.props
|
||||
---
|
||||
|
||||
<div class="dropdown">
|
||||
<a class="dropdown-toggle text-secondary" id={id ? `${id}-dropdown` : undefined} href="#" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" aria-label={label}>{value}</a>
|
||||
<button type="button" class="dropdown-toggle text-secondary" id={id ? `${id}-dropdown` : undefined} data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" aria-label={label}>{value}</button>
|
||||
<div class="dropdown-menu dropdown-menu-end" aria-labelledby={id ? `${id}-dropdown` : undefined}>
|
||||
<a class="dropdown-item active" href="#" aria-current="true">Last 7 days</a>
|
||||
<a class="dropdown-item" href="#">Last 30 days</a>
|
||||
<a class="dropdown-item" href="#">Last 3 months</a>
|
||||
<button type="button" class="dropdown-item active" aria-current="true">Last 7 days</button>
|
||||
<button type="button" class="dropdown-item">Last 30 days</button>
|
||||
<button type="button" class="dropdown-item">Last 3 months</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -43,17 +43,17 @@ const countryItems = (countries as { name: string; flag: string }[]).slice(0, 5)
|
||||
) : item.includes('h:') ? (
|
||||
<h3 class="dropdown-header">{item.split('h:').join('')}</h3>
|
||||
) : item.includes('a:') ? (
|
||||
<a href="#" class="dropdown-item active">
|
||||
<button type="button" class="dropdown-item active">
|
||||
{item.split('a:').join('')}
|
||||
</a>
|
||||
</button>
|
||||
) : item.includes('d:') ? (
|
||||
<a href="#" class="dropdown-item text-danger">
|
||||
<button type="button" class="dropdown-item text-danger">
|
||||
{item.split('d:').join('')}
|
||||
</a>
|
||||
</button>
|
||||
) : (
|
||||
<a href="#" class="dropdown-item">
|
||||
<button type="button" class="dropdown-item">
|
||||
{item}
|
||||
</a>
|
||||
</button>
|
||||
),
|
||||
)
|
||||
) : check || radio ? (
|
||||
@@ -64,20 +64,20 @@ const countryItems = (countries as { name: string; flag: string }[]).slice(0, 5)
|
||||
))
|
||||
) : showPeople ? (
|
||||
peopleItems.map((person) => (
|
||||
<a href="#" class="dropdown-item">
|
||||
<button type="button" class="dropdown-item">
|
||||
<Avatar personId={Number(person.id)} size="xs" /> {person.full_name}
|
||||
</a>
|
||||
</button>
|
||||
))
|
||||
) : showFlags ? (
|
||||
countryItems.map((country) => (
|
||||
<a href="#" class="dropdown-item">
|
||||
<button type="button" class="dropdown-item">
|
||||
<Flag flag={country.flag} size="xs" /> {country.name}
|
||||
</a>
|
||||
</button>
|
||||
))
|
||||
) : type === 'text' ? null : (
|
||||
<>
|
||||
{header && <span class="dropdown-header">Dropdown header</span>}
|
||||
<a class="dropdown-item" href="#">
|
||||
<button type="button" class="dropdown-item">
|
||||
{icons && (
|
||||
<>
|
||||
<Icon name="settings" class="dropdown-item-icon" />{' '}
|
||||
@@ -85,8 +85,8 @@ const countryItems = (countries as { name: string; flag: string }[]).slice(0, 5)
|
||||
)}
|
||||
Action
|
||||
{badge && <span class="badge bg-primary text-primary-fg ms-auto">12</span>}
|
||||
</a>
|
||||
<a class="dropdown-item" href="#">
|
||||
</button>
|
||||
<button type="button" class="dropdown-item">
|
||||
{icons && (
|
||||
<>
|
||||
<Icon name="edit" class="dropdown-item-icon" />{' '}
|
||||
@@ -94,38 +94,38 @@ const countryItems = (countries as { name: string; flag: string }[]).slice(0, 5)
|
||||
)}
|
||||
Another action
|
||||
{badge && <span class="badge bg-green ms-auto" />}
|
||||
</a>
|
||||
</button>
|
||||
{active && (
|
||||
<a class="dropdown-item active" href="#">
|
||||
<button type="button" class="dropdown-item active">
|
||||
{icons && (
|
||||
<>
|
||||
<Icon name="activity" class="dropdown-item-icon" />{' '}
|
||||
</>
|
||||
)}
|
||||
Active action
|
||||
</a>
|
||||
</button>
|
||||
)}
|
||||
{disabled && (
|
||||
<a class="dropdown-item disabled" href="#">
|
||||
<button type="button" class="dropdown-item disabled" disabled>
|
||||
{icons && (
|
||||
<>
|
||||
<Icon name="user-x" class="dropdown-item-icon" />{' '}
|
||||
</>
|
||||
)}
|
||||
Disabled action
|
||||
</a>
|
||||
</button>
|
||||
)}
|
||||
{separated && (
|
||||
<>
|
||||
<div class="dropdown-divider" />
|
||||
<a class="dropdown-item" href="#">
|
||||
<button type="button" class="dropdown-item">
|
||||
{icons && (
|
||||
<>
|
||||
<Icon name="plus" class="dropdown-item-icon" />{' '}
|
||||
</>
|
||||
)}
|
||||
Separated link
|
||||
</a>
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -14,11 +14,11 @@ const peopleItems = (people as { id: number | string; full_name: string }[]).sli
|
||||
|
||||
<div class={`dropdown-menu${show ? ' dropdown-menu-demo' : ''}`}>
|
||||
<h6 class="dropdown-header">Dropdown header</h6>
|
||||
<a href="#" class="dropdown-item"><Icon name="activity" class="dropdown-item-icon" /> Item 1</a>
|
||||
<a href="#" class="dropdown-item"><Icon name="edit" class="dropdown-item-icon" /> Item 2</a>
|
||||
<button type="button" class="dropdown-item"><Icon name="activity" class="dropdown-item-icon" /> Item 1</button>
|
||||
<button type="button" class="dropdown-item"><Icon name="edit" class="dropdown-item-icon" /> Item 2</button>
|
||||
|
||||
<div class="dropdown-item">
|
||||
<a href="#" class="text-reset">My profile</a>
|
||||
<button type="button" class="text-reset border-0 bg-transparent p-0">My profile</button>
|
||||
<label class="form-check m-0 ms-auto">
|
||||
<input type="checkbox" class="form-check-input" />
|
||||
Public
|
||||
@@ -30,22 +30,22 @@ const peopleItems = (people as { id: number | string; full_name: string }[]).sli
|
||||
|
||||
<div class="dropdown-divider"></div>
|
||||
|
||||
<a href="#" class="dropdown-item">Dropdown item 1</a>
|
||||
<a href="#" class="dropdown-item">Dropdown item 2</a>
|
||||
<a href="#" class="dropdown-item disabled">Dropdown disabled</a>
|
||||
<a href="#" class="dropdown-item active">Dropdown active</a>
|
||||
<button type="button" class="dropdown-item">Dropdown item 1</button>
|
||||
<button type="button" class="dropdown-item">Dropdown item 2</button>
|
||||
<button type="button" class="dropdown-item disabled" disabled>Dropdown disabled</button>
|
||||
<button type="button" class="dropdown-item active">Dropdown active</button>
|
||||
|
||||
<div class="dropdown-divider"></div>
|
||||
|
||||
{
|
||||
peopleItems.map((person) => (
|
||||
<a href="#" class="dropdown-item">
|
||||
<button type="button" class="dropdown-item">
|
||||
<Avatar personId={Number(person.id)} class="rounded me-2" size="xs" /> {person.full_name}
|
||||
</a>
|
||||
</button>
|
||||
))
|
||||
}
|
||||
|
||||
<div class="dropdown-divider"></div>
|
||||
|
||||
<a href="#" class="dropdown-item"><Icon name="logout" class="dropdown-item-icon" /> Logout</a>
|
||||
<button type="button" class="dropdown-item"><Icon name="logout" class="dropdown-item-icon" /> Logout</button>
|
||||
</div>
|
||||
|
||||
@@ -1,35 +1,70 @@
|
||||
---
|
||||
// Form group: label (.form-label) + control slot + optional hint (.form-hint).
|
||||
// When `for` is omitted and associate is true (default), the control is nested
|
||||
// inside the label for implicit association. Set associate={false} for groups
|
||||
// with multiple controls.
|
||||
import FormHint from './FormHint.astro'
|
||||
|
||||
interface Props {
|
||||
label?: string
|
||||
/** .required asterisk on the label */
|
||||
/** .required asterisk on the label — also set required on the control */
|
||||
required?: boolean
|
||||
/** .form-label-description at the inline end of the label */
|
||||
description?: string
|
||||
/** .form-hint under the control */
|
||||
hint?: string
|
||||
/** for attribute of the label */
|
||||
/** for attribute of the label — pair with the same id on the control */
|
||||
for?: string
|
||||
/** extra classes on the label (e.g. visually-hidden) */
|
||||
labelClass?: string
|
||||
/** wrapper classes; replaces the default mb-3 */
|
||||
class?: string
|
||||
/** marks the group invalid — pair with aria-invalid and .is-invalid on the control */
|
||||
invalid?: boolean
|
||||
/** error message shown under the control (.invalid-feedback) */
|
||||
error?: string
|
||||
/** nest the control in the label when `for` is omitted (default true) */
|
||||
associate?: boolean
|
||||
}
|
||||
|
||||
const { label, required, description, hint, for: htmlFor, labelClass, class: className = 'mb-3' } = Astro.props
|
||||
const { label, required, description, hint, for: htmlFor, labelClass, class: className = 'mb-3', invalid, error, associate = true } = Astro.props
|
||||
|
||||
const uid = htmlFor ?? `fg-${Math.random().toString(36).slice(2, 9)}`
|
||||
const hintId = hint ? `${uid}-hint` : undefined
|
||||
const errorId = error ? `${uid}-error` : undefined
|
||||
const wrapLabel = Boolean(label && !htmlFor && associate)
|
||||
---
|
||||
|
||||
<div class:list={[className]}>
|
||||
<div class:list={[className, invalid && 'has-validation']}>
|
||||
{
|
||||
label && (
|
||||
label && htmlFor && (
|
||||
<label class:list={['form-label', required && 'required', labelClass]} for={htmlFor}>
|
||||
{label}
|
||||
{description && <span class="form-label-description">{description}</span>}
|
||||
</label>
|
||||
)
|
||||
}
|
||||
<slot />
|
||||
{hint && <FormHint>{hint}</FormHint>}
|
||||
{
|
||||
label && !htmlFor && !associate && (
|
||||
<div class:list={['form-label', required && 'required', labelClass]}>
|
||||
{label}
|
||||
{description && <span class="form-label-description">{description}</span>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
wrapLabel ? (
|
||||
<label class="d-block">
|
||||
<span class:list={['form-label', required && 'required', labelClass]}>
|
||||
{label}
|
||||
{description && <span class="form-label-description">{description}</span>}
|
||||
</span>
|
||||
<slot />
|
||||
</label>
|
||||
) : (
|
||||
<slot />
|
||||
)
|
||||
}
|
||||
{error && <div class="invalid-feedback d-block" id={errorId} role="alert">{error}</div>}
|
||||
{hint && <FormHint id={hintId}>{hint}</FormHint>}
|
||||
</div>
|
||||
|
||||
@@ -5,11 +5,12 @@ interface Props {
|
||||
/** element to render — default div */
|
||||
as?: string
|
||||
class?: string
|
||||
id?: string
|
||||
}
|
||||
|
||||
const { as: Tag = 'div', class: className } = Astro.props
|
||||
const { as: Tag = 'div', class: className, id } = Astro.props
|
||||
---
|
||||
|
||||
<Tag class:list={['form-hint', className]}>
|
||||
<Tag class:list={['form-hint', className]} id={id}>
|
||||
<slot />
|
||||
</Tag>
|
||||
|
||||
@@ -12,24 +12,38 @@ interface Props {
|
||||
prepend?: string
|
||||
append?: string
|
||||
appendButton?: { icon: string; description: string }[]
|
||||
/** HTML autocomplete token; omit unless needed (e.g. email, tel, off) */
|
||||
autocomplete?: string
|
||||
/** mark control invalid for assistive tech */
|
||||
invalid?: boolean
|
||||
required?: boolean
|
||||
}
|
||||
|
||||
const { type = 'text', placeholder, value, flat, class: className, inputClass, id, prepend, append, appendButton } = Astro.props
|
||||
const { type = 'text', placeholder, value, flat, class: className, inputClass, id, prepend, append, appendButton, autocomplete, invalid, required } = Astro.props
|
||||
|
||||
const appendButtons = appendButton ?? []
|
||||
---
|
||||
|
||||
<div class:list={['input-group', flat && 'input-group-flat', className]}>
|
||||
{prepend && <span class="input-group-text">{prepend}</span>}
|
||||
<input type={type} class:list={['form-control', inputClass]} placeholder={placeholder} value={value} autocomplete="off" id={id} />
|
||||
<input
|
||||
type={type}
|
||||
class:list={['form-control', invalid && 'is-invalid', inputClass]}
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
autocomplete={autocomplete}
|
||||
id={id}
|
||||
aria-invalid={invalid ? 'true' : undefined}
|
||||
required={required}
|
||||
/>
|
||||
{append && <span class="input-group-text">{append}</span>}
|
||||
{
|
||||
appendButtons.length > 0 && (
|
||||
<span class="input-group-text">
|
||||
{appendButtons.map(({ icon, description }, index) => (
|
||||
<a href="#" class:list={['link-secondary', index > 0 && 'ms-2']} title={description} data-bs-toggle="tooltip">
|
||||
<button type="button" class:list={['link-secondary border-0 bg-transparent p-0', index > 0 && 'ms-2']} title={description} data-bs-toggle="tooltip" aria-label={description}>
|
||||
<Icon name={icon} />
|
||||
</a>
|
||||
</button>
|
||||
))}
|
||||
</span>
|
||||
)
|
||||
|
||||
+1
-1
@@ -51,5 +51,5 @@ const { text, flag, icon, person, personId, payment, status, legend, checkbox, c
|
||||
{text}
|
||||
{badge && <Badge class="tag-badge" text={String(badge)} />}
|
||||
|
||||
<a href="#" class="btn-close"></a>
|
||||
<button type="button" class="btn-close" aria-label="Remove"></button>
|
||||
</span>
|
||||
|
||||
@@ -16,9 +16,9 @@ const defaults = {
|
||||
|
||||
<!-- BEGIN THEME SETTINGS -->
|
||||
<div class="settings">
|
||||
<a href="#" class="btn btn-floating btn-icon btn-primary" data-bs-toggle="offcanvas" data-bs-target="#offcanvas-settings" aria-controls="offcanvas-settings" aria-label="Theme Settings">
|
||||
<button type="button" class="btn btn-floating btn-icon btn-primary" data-bs-toggle="offcanvas" data-bs-target="#offcanvas-settings" aria-controls="offcanvas-settings" aria-label="Theme Settings">
|
||||
<Icon name="brush" />
|
||||
</a>
|
||||
</button>
|
||||
|
||||
<form class="offcanvas offcanvas-start offcanvas-narrow" tabindex="-1" id="offcanvas-settings" role="dialog" aria-modal="true" aria-labelledby="offcanvas-settings-title">
|
||||
<div class="offcanvas-header">
|
||||
@@ -27,7 +27,7 @@ const defaults = {
|
||||
</div>
|
||||
<div class="offcanvas-body d-flex flex-column">
|
||||
<div>
|
||||
<FormGroup label="Color mode" class="mb-4">
|
||||
<FormGroup label="Color mode" class="mb-4" associate={false}>
|
||||
<FormHint as="p">Choose the color mode for your app.</FormHint>
|
||||
{
|
||||
['light', 'dark', 'auto'].map((mode) => (
|
||||
@@ -41,7 +41,7 @@ const defaults = {
|
||||
}
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup label="Color scheme" class="mb-4">
|
||||
<FormGroup label="Color scheme" class="mb-4" associate={false}>
|
||||
<FormHint as="p">The perfect color mode for your app.</FormHint>
|
||||
|
||||
<div class="row g-2">
|
||||
@@ -64,7 +64,7 @@ const defaults = {
|
||||
</div>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup label="Font family" class="mb-4">
|
||||
<FormGroup label="Font family" class="mb-4" associate={false}>
|
||||
<FormHint as="p">Choose the font family that fits your app.</FormHint>
|
||||
|
||||
<div>
|
||||
@@ -81,7 +81,7 @@ const defaults = {
|
||||
</div>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup label="Theme base" class="mb-4">
|
||||
<FormGroup label="Theme base" class="mb-4" associate={false}>
|
||||
<FormHint as="p">Choose the gray shade for your app.</FormHint>
|
||||
|
||||
<div>
|
||||
@@ -98,7 +98,7 @@ const defaults = {
|
||||
</div>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup label="Corner Radius" class="mb-4">
|
||||
<FormGroup label="Corner Radius" class="mb-4" associate={false}>
|
||||
<FormHint as="p">Choose the border radius factor for your app.</FormHint>
|
||||
|
||||
<div>
|
||||
@@ -121,7 +121,7 @@ const defaults = {
|
||||
<Icon name="rotate" />
|
||||
Reset changes
|
||||
</button>
|
||||
<a href="#" class="btn btn-primary w-100" data-bs-dismiss="offcanvas"> Save </a>
|
||||
<button type="button" class="btn btn-primary w-100" data-bs-dismiss="offcanvas">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
Reference in New Issue
Block a user