8000 refactor(free): drop the ak- prefix from classes and Sass helpers by paullaros · Pull Request #159 · adminkit/adminkit · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ Free Bootstrap 5 admin template, published to npm as `@adminkit/core`.

## Motion and loading states

- Tokens: `$ak-transition-duration-fast|base|slow` and `$ak-transition-easing-standard|emphasized` in `src/scss/1-variables/_app.scss`, used as `var(--ak-duration-*)` and `var(--ak-ease-*)`. Write transitions with Bootstrap's `@include transition(...)` so reduced motion turns them off; an animation must not move anything under `prefers-reduced-motion: reduce` (a fade is fine).
- Reveal (`js/modules/reveal.js`, `3-components/_reveal.scss`): every top-level `.card` inside `.ak-reveal-cards` (the layouts put it on `<main>`, which itself fades in once the page scripts have run) and every `.ak-reveal` element fades up when it scrolls into view. The hidden state only applies under `html.js-reveal`, set by `js/head.js`; nothing else is needed on a new page.
- Tokens: `$transition-duration-fast|base|slow` and `$transition-easing-standard|emphasized` in `src/scss/1-variables/_app.scss`, used as `var(--ak-duration-*)` and `var(--ak-ease-*)`. Write transitions with Bootstrap's `@include transition(...)` so reduced motion turns them off; an animation must not move anything under `prefers-reduced-motion: reduce` (a fade is fine).
- Reveal (`js/modules/reveal.js`, `3-components/_reveal.scss`): every top-level `.card` inside `.reveal-cards` (the layouts put it on `<main>`, which itself fades in once the page scripts have run) and every `.reveal` element fades up when it scrolls into view. The hidden state only applies under `html.js-reveal`, set by `js/head.js`; nothing else is needed on a new page.
- Count-up (`js/modules/count-up.js`): `data-count-up` on an element that holds only a number (with any prefix, suffix, `,` separators and decimals) and sits on its own line. The text stays the final value for screen readers and reduced motion.
- Skeletons (`js/modules/skeleton.js`, `3-components/_skeleton.scss`): for content JavaScript draws, put `aria-busy="true"` on the card body and `{{> skeleton }}` inside a `position-relative` element that has the content's final height (a `.chart` or a `min-height`). `adminkit.chartjs()` and `adminkit.vectorMap()` clear it; for anything else call `adminkit.ready(element)`. All of them fire `adminkit:ready`.
- Clickable cards: `a.card` or a card with a `.stretched-link` lifts on hover and focus; nothing to add.
Expand All @@ -75,11 +75,12 @@ Free Bootstrap 5 admin template, published to npm as `@adminkit/core`.

- Target: WCAG 2.2 AA in light and dark mode, on every page including the sidebar and navbar: 4.5:1 for text, 3:1 for large text, icons and controls, no serious or critical axe violations.
- Check with `@axe-core/playwright` on the built pages in both modes.
- A new color that text or a control is drawn in must pass: use `ak-readable($color, $background)` (`src/scss/1-variables/_functions.scss`) and `$theme-text-colors` / `$theme-text-colors-dark` for theme colors as text; fills with white text must reach 4.5:1 (`ak-text-on()` picks white or dark text).
- A new color that text or a control is drawn in must pass: use `readable($color, $background)` (`src/scss/1-variables/_functions.scss`) and `$theme-text-colors` / `$theme-text-colors-dark` for theme colors as text; fills with white text must reach 4.5:1 (`text-on()` picks white or dark text).
- Icon-only links and buttons get an `aria-label`; links that act as buttons get `role="button"`; card titles are `h2.card-title` under the page's `h1`.

## Conventions

- Only CSS custom properties get a prefix: `--ak-*`, like Bootstrap's `--bs-*`. Classes, Sass variables, functions and mixins never do.
- Commits follow Conventional Commits; see `CONTRIBUTING.md` for types, scopes and examples. lefthook enforces them.
- A PR that changes behaviour adds a line under `Unreleased` in `CHANGELOG.md`. Add it, or ask before opening the PR.
- Changelog lines are short, imperative and user-facing, like the released entries: "Add pricing page (/pages-pricing.html)", "Replace Feather icons with Lucide", "Upgrade to Chart.js v4", "Upgrade dependencies". One line per change a template user would notice. No internal tooling (lint, CI, hooks, scripts), no explanations, no implementation details. Notable dependency majors get "Upgrade to X vN"; everything else is "Upgrade dependencies".
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

- Drop the `ak-` prefix from classes, Sass variables and functions (`.ak-reveal` → `.reveal`); the old names work until v5
- Upgrade dependencies

## [4.0.0] - 2026-10-28
Expand Down
16 changes: 9 additions & 7 deletions src/js/modules/reveal.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
// Cards fade up as they scroll into view: every top-level .card inside .ak-reveal-cards (the
// Cards fade up as they scroll into view: every top-level .card inside .reveal-cards (the
// layouts put it on <main>, which itself fades in once the page scripts have run) and any
// element with .ak-reveal. Elements that come into view together are staggered through
// element with .reveal. Elements that come into view together are staggered through
// --ak-reveal-index, capped at maxStagger steps. The hidden state is CSS under
// html.js-reveal, which js/head.js sets; without JavaScript nothing is hidden. Styles:
// scss/3-components/_reveal.scss.
// scss/3-components/_reveal.scss. .ak-reveal and .ak-reveal-cards are v4 aliases, remove
// them in v5.
import { mount } from "./mount";

const selector = ".ak-reveal, .ak-reveal-cards .card:not(.card .card)";
const cards = ".reveal-cards, .ak-reveal-cards";
const selector = `.reveal, .ak-reveal, :is(${cards}) .card:not(.card .card)`;
const maxStagger = 8;

const reveal = (elements) => {
elements.forEach((element, index) => {
const => {
if (event.target === element && event.propertyName === "opacity") {
element.removeEventListener("transitionend", onTransitionEnd);
element.classList.remove("ak-revealing");
element.classList.remove("revealing");
element.style.removeProperty("--ak-reveal-index");
}
};

element.addEventListener("transitionend", onTransitionEnd);
element.style.setProperty("--ak-reveal-index", Math.min(index, maxStagger));
element.classList.add("ak-revealed", "ak-revealing");
element.classList.add("revealed", "revealing");
});
};

Expand All @@ -42,6 +44,6 @@ root.classList.add("js-reveal-ready");
// mount() starts after the page scripts that build charts and tables on DOMContentLoaded, so
// their layout is in place before the cards become visible
if (root.classList.contains("js-reveal")) {
mount(".ak-reveal-cards", (element) => element.classList.add("ak-revealed"));
mount(cards, (element) => element.classList.add("revealed"));
mount(selector, (element) => observer.observe(element));
}
6 changes: 3 additions & 3 deletions src/js/modules/skeleton.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
// Loading placeholders for content that JavaScript draws (charts, maps, tables). The markup
// shows an .ak-skeleton inside an element marked aria-busy="true"; markReady(element)
// shows a .skeleton inside an element marked aria-busy="true"; markReady(element)
// removes the placeholders around element, sets aria-busy="false" and fires
// adminkit:ready on element. The chart, map and table helpers call it when they have
// drawn; call adminkit.ready(element) for your own content. Styles:
// scss/3-components/_skeleton.scss.
// scss/3-components/_skeleton.scss. .ak-skeleton is a v4 alias, remove it in v5.
export const markReady = (element) => {
const busy = element.closest('[aria-busy="true"]');

if (busy) {
busy.querySelectorAll(".ak-skeleton").forEach((skeleton) => {
busy.querySelectorAll(".skeleton, .ak-skeleton").forEach((skeleton) => {
skeleton.remove();
});
busy.setAttribute("aria-busy", "false");
Expand Down
2 changes: 1 addition & 1 deletion src/layouts/auth.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
{{> site-meta }}
</head>
<body>
<main class="d-flex w-100 ak-reveal-cards">
<main class="d-flex w-100 reveal-cards">
<div class="container d-flex flex-column">
<div class="row gx-0 vh-100">
<div class="col-sm-10 col-md-8 col-lg-6 col-xl-5 mx-auto d-table h-100">
Expand Down
2 changes: 1 addition & 1 deletion src/layouts/default.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<div class="main">
{{> site-navbar }}

<main class="content ak-reveal-cards">
<main class="content reveal-cards">
<div class="container-fluid p-0">
{{> @partial-block }}
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/partials/site-footer.hbs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<footer class="footer ak-reveal">
<footer class="footer reveal">
<div class="container-fluid">
<div class="row text-body-secondary">
<div class="col-6 text-start">
Expand Down
2 changes: 1 addition & 1 deletion src/partials/skeleton.hbs
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<div class="ak-skeleton placeholder-glow" aria-hidden="true"><span class="placeholder"></span></div>
<div class="skeleton placeholder-glow" aria-hidden="true"><span class="placeholder"></span></div>
2 changes: 1 addition & 1 deletion src/partials/upgrade-screenshot.hbs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{{!-- One color mode of an upgrade page screenshot; the other mode's picture is hidden and,
being lazy, never loads. imgClass and sizes default to a card's top image in a
two-column grid --}}
<picture class="ak-{{mode}}-only">
<picture class="{{mode}}-only">
<img class="{{#if imgClass}}{{imgClass}}{{else}}card-img-top{{/if}}" src="/img/screenshots/pro/{{name}}-{{mode}}.webp" srcset="/img/screenshots/pro/{{name}}-{{mode}}-800.webp 800w, /img/screenshots/pro/{{name}}-{{mode}}.webp 1600w" sizes="{{#if sizes}}{{sizes}}{{else}}(min-width: 992px) 45vw, 100vw{{/if}}" width="1600" height="1000" loading="lazy" decoding="async" alt="AdminKit PRO: {{title}}{{#compare mode "===" "dark"}}, dark mode{{/compare}}">
</picture>
10BC8
21 changes: 14 additions & 7 deletions src/scss/1-variables/_app.scss
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,18 @@ $enable-dark-mode: true !default; // Dark mode values live in 1-variables/_dark.
// --ak-ease-standard|emphasized, set on :root in 3-components/_root.scss) so they can be
// tuned at runtime. Under prefers-reduced-motion: reduce, Bootstrap's transition() mixin
// drops the transitions and AdminKit's own animations only fade.
$ak-transition-duration-fast: 0.15s !default;
$ak-transition-duration-base: 0.25s !default;
$ak-transition-duration-slow: 0.35s !default;
$ak-transition-easing-standard: cubic-bezier(0.4, 0, 0.2, 1) !default;
$ak-transition-easing-emphasized: cubic-bezier(0.2, 0, 0, 1) !default;
$transition-duration-fast: 0.15s !default;
$transition-duration-base: 0.25s !default;
$transition-duration-slow: 0.35s !default;
$transition-easing-standard: cubic-bezier(0.4, 0, 0.2, 1) !default;
$transition-easing-emphasized: cubic-bezier(0.2, 0, 0, 1) !default;

// v4 aliases, remove in v5
$ak-transition-duration-fast: $transition-duration-fast;
$ak-transition-duration-base: $transition-duration-base;
$ak-transition-duration-slow: $transition-duration-slow;
$ak-transition-easing-standard: $transition-easing-standard;
$ak-transition-easing-emphasized: $transition-easing-emphasized;

// Bootstrap transitions on the motion tokens
$transition-base: all var(--ak-duration-slow) var(--ak-ease-standard) !default;
Expand Down Expand Up @@ -175,13 +182,13 @@ $theme-text-colors: () !default;
$theme-text-colors: map.merge(
$theme-text-colors,
(
$name: ak-readable($value, $body-bg),
$name: readable($value, $body-bg),
)
);
}
}

$code-color: ak-readable($pink, $body-bg) !default;
$code-color: readable($pink, $body-bg) !default;

// Links
$link-decoration: none !default;
Expand Down
8 changes: 4 additions & 4 deletions src/scss/1-variables/_dark.scss
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ $grays-dark: (

// The theme colours that differ in dark mode; the others keep their light value
// Dark enough for white text on it; as text, see $theme-text-colors-dark
$secondary-dark: ak-readable($gray-400-dark, #fff) !default;
$secondary-dark: readable($gray-400-dark, #fff) !default;
$light-dark: color.mix(#fff, $dark-theme-base, 5%) !default;
$dark-dark: $gray-900-dark !default;

Expand All @@ -54,7 +54,7 @@ $theme-text-colors-dark: () !default;
$theme-text-colors-dark: map.merge(
$theme-text-colors-dark,
(
$name: ak-readable($value, $surface-bg-dark),
$name: readable($value, $surface-bg-dark),
)
);
}
Expand All @@ -73,9 +73,9 @@ $border-color-dark: $gray-200-dark !default;
$border-color-translucent-dark: rgba(#fff, 0.175) !default;

// Links, code and marks keep their light colours
$link-color-dark: ak-readable($primary, $surface-bg-dark) !default;
$link-color-dark: readable($primary, $surface-bg-dark) !default;
$link-hover-color-dark: color.mix(#fff, $link-color-dark, 20%) !default;
$code-color-dark: ak-readable($pink, $surface-bg-dark) !default;
$code-color-dark: readable($pink, $surface-bg-dark) !default;
$mark-color-dark: $gray-900 !default;
$mark-bg-dark: color.mix(#fff, $yellow, 80%) !default;

Expand Down
15 changes: 12 additions & 3 deletions src/scss/1-variables/_functions.scss
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
@use "sass:color";

// WCAG 2.2 AA asks for 4.5:1 for text, 3:1 for large text, icons and other UI parts.
// ak-readable() returns $color, mixed with black (on a light $background) or white (on a
// readable() returns $color, mixed with black (on a light $background) or white (on a
// dark one) in 1% steps until it reaches $min contrast with $background; a colour that
// passes comes back unchanged. Use it for every colour that text or a control is drawn in.
// contrast-ratio() and luminance() come from Bootstrap's functions.
@function ak-readable($color, $background, $min: 4.5) {
@function readable($color, $background, $min: 4.5) {
$towards: #000;

@if luminance($background) < 0.18 {
Expand All @@ -24,10 +24,19 @@
}

// White, or the darkest gray when white does not reach 4.5:1 on $background
@function ak-text-on($background) {
@function text-on($background) {
@if contrast-ratio($background, #fff) >= 4.5 {
@return #fff;
}

@return #212529;
}

// v4 aliases, remove in v5
@function ak-readable($color, $background, $min: 4.5) {
@return readable($color, $background, $min);
}

@function ak-text-on($background) {
@return text-on($background);
}
6 changes: 3 additions & 3 deletions src/scss/2-mixins/_modal.scss
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
@mixin modal-variant($color) {
.modal-content {
background: $color;
color: ak-text-on($color); 10BC8
color: text-on($color);
}

// The close button follows the text: white, or the default dark one in both modes
.btn-close {
@if ak-text-on($color) == #fff {
@if text-on($color) == #fff {
filter: $btn-close-filter-dark;
} @else {
filter: none;
Expand All @@ -25,6 +25,6 @@
.h4,
.h5,
.h6 {
color: ak-text-on($color);
color: text-on($color);
}
}
2 changes: 1 addition & 1 deletion src/scss/3-components/_alert.scss
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
@each $color, $value in $theme-colors {
&.alert-#{$color} .alert-icon {
background-color: var(--#{$prefix}#{$color});
color: ak-text-on($value);
color: text-on($value);
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/scss/3-components/_badges.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
.badge-#{$color}-light {
@include badge-variant(
rgba($value, 0.15),
ak-readable($value, color.mix($value, $surface-bg, 15%))
readable($value, color.mix($value, $surface-bg, 15%))
);
}
}
Expand All @@ -17,7 +17,7 @@
.badge-#{$color}-light {
@include badge-variant(
rgba($value, 0.15),
ak-readable($value, color.mix($value, $surface-bg-dark, 15%))
readable($value, color.mix($value, $surface-bg-dark, 15%))
);
}
}
Expand Down
14 changes: 7 additions & 7 deletions src/scss/3-components/_buttons.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
// White text, or dark text where white does not reach 4.5:1 on the colour (warning)
@each $color, $value in $theme-colors {
.btn-#{$color} {
@include button-color(ak-text-on($value));
@include button-color-hover(ak-text-on($value));
@include button-color(text-on($value));
@include button-color-hover(text-on($value));
}
}

Expand All @@ -22,7 +22,7 @@

// Brand colours, darkened where white text does not reach 4.5:1 on them
@each $color, $value in $social-colors {
$bg: ak-readable($value, $white);
$bg: readable($value, $white);

.btn-#{$color} {
@include button-variant($bg, $bg);
Expand All @@ -33,9 +33,9 @@

// Dark text on the badge colours where white does not reach 4.5:1 (warning)
@each $color, $value in $theme-colors {
@if $color != "light" and ak-text-on($value) != #fff {
@if $color != "light" and text-on($value) != #fff {
.badge.bg-#{$color} {
color: ak-text-on($value);
color: text-on($value);
}
}
}
Expand All @@ -50,8 +50,8 @@
@each $color, $value in $theme-colors-dark {
.btn-#{$color} {
@include button-variant($value, $value);
@include button-color(ak-text-on($value));
@include button-color-hover(ak-text-on($value));
@include button-color(text-on($value));
@include button-color-hover(text-on($value));
}

.btn-outline-#{$color} {
Expand Down
6 changes: 4 additions & 2 deletions src/scss/3-components/_print.scss
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@
box-shadow: none;
}

.js-reveal .ak-reveal-cards,
.js-reveal :is(.ak-reveal, .ak-reveal-cards .card) {
// .ak-reveal and .ak-reveal-cards: v4 aliases, remove in v5
.js-reveal :is(.reveal-cards, .ak-reveal-cards),
.js-reveal
:is(.reveal, .ak-reveal, .reveal-cards .card, .ak-reveal-cards .card) {
opacity: 1 !important;
transform: none !important;
}
Expand Down
20 changes: 12 additions & 8 deletions src/scss/3-components/_reveal.scss
Original file line number Diff line number Diff line change
@@ -1,38 +1,42 @@
// Cards fade up as they scroll into view (js/modules/reveal.js): the top-level cards in
// .ak-reveal-cards and any .ak-reveal element. The hidden state only applies under
// .reveal-cards and any .reveal element. The hidden state only applies under
// html.js-reveal, which js/head.js sets, so without JavaScript nothing is hidden. Reduced
// motion keeps the fade and drops the movement.
$reveal-items: ".ak-reveal, .ak-reveal-cards .card:not(.card .card)";
// v4 aliases, remove in v5: .ak-reveal, .ak-reveal-cards, .ak-revealed, .ak-revealing
$reveal-cards: ".reveal-cards, .ak-reveal-cards";
$reveal-items: ".reveal, .ak-reveal, :is(#{$reveal-cards}) .card:not(.card .card)";
$revealed: ".revealed, .ak-revealed";
$revealing: ".revealing, .ak-revealing";

.js-reveal :is(#{$reveal-items}):not(.ak-revealed) {
.js-reveal :is(#{$reveal-items}):not(#{$revealed}) {
opacity: 0;
transform: translateY(0.75rem);
}

// The rest of the content (page title, columns) waits for the page scripts too, so plugins
// that change the layout on DOMContentLoaded do so before anything is seen
.js-reveal .ak-reveal-cards {
.js-reveal :is(#{$reveal-cards}) {
opacity: 0;

&.ak-revealed {
&:is(#{$revealed}) {
opacity: 1;
transition: opacity var(--ak-duration-fast) var(--ak-ease-standard);
}
}

.js-reveal .ak-revealing {
.js-reveal :is(#{$revealing}) {
transition:
opacity var(--ak-duration-slow) var(--ak-ease-standard),
transform var(--ak-duration-slow) var(--ak-ease-emphasized);
transition-delay: calc(var(--ak-reveal-index, 0) * 60ms);
}

@media (prefers-reduced-motion: reduce) {
.js-reveal :is(#{$reveal-items}):not(.ak-revealed) {
.js-reveal :is(#{$reveal-items}):not(#{$revealed}) {
transform: none;
}

.js-reveal .ak-revealing {
.js-reveal :is(#{$revealing}) {
transition-property: opacity;
}
}
Loading
Loading
0