From 107e493275c48d94df632522ce01bb3abe5b3adc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Kuna?= <1282324+codecalm@users.noreply.github.com> Date: Sun, 13 Sep 2026 00:56:36 +0200 Subject: [PATCH] Define the color palette in `oklch()` (#3043) --- .agents/skills/core-js/SKILL.md | 2 +- .changeset/palette-oklch.md | 6 ++ core/js/src/tabler.ts | 17 --- core/js/tabler.ts | 3 - core/package.json | 8 +- core/scss/_settings.scss | 4 +- core/scss/_variables-dark.scss | 4 +- core/scss/_variables.scss | 95 ++++++++--------- core/scss/bootstrap/_carousel.scss | 9 +- core/scss/bootstrap/_navbar.scss | 13 ++- core/scss/bootstrap/_reboot.scss | 2 +- core/scss/bootstrap/forms/_form-check.scss | 82 +++++++++------ core/scss/layout/_navbar.scss | 1 + core/scss/marketing/_hero.scss | 11 -- core/scss/mixins/_functions.scss | 109 ++----------------- core/scss/mixins/_mixins.scss | 17 +++ core/scss/tabler-themes.scss | 110 ++++++++++---------- core/scss/tests/_breakpoints.test.scss | 10 +- core/scss/tests/_colors-misc.test.scss | 10 -- core/scss/tests/_css-var.test.scss | 27 +---- core/scss/tests/_escape-svg.test.scss | 5 + core/scss/tests/_maps.test.scss | 41 -------- core/scss/tests/_media-breakpoint.test.scss | 67 ------------ core/scss/ui/forms/_form-colorinput.scss | 4 +- core/scss/ui/forms/_form-imagecheck.scss | 4 +- core/scss/ui/forms/_form-selectgroup.scss | 4 +- docs/content/ui/plugins/color-picker.mdx | 12 +-- shared/ui/Colorpicker.astro | 10 +- 28 files changed, 224 insertions(+), 463 deletions(-) create mode 100644 .changeset/palette-oklch.md delete mode 100644 core/js/src/tabler.ts diff --git a/.agents/skills/core-js/SKILL.md b/.agents/skills/core-js/SKILL.md index 77517bf53..4932bd207 100644 --- a/.agents/skills/core-js/SKILL.md +++ b/.agents/skills/core-js/SKILL.md @@ -50,7 +50,7 @@ switchesTriggerList.map(function (switchTriggerEl: HTMLElement) { - The attribute API stays `data-bs-*`. Renaming it to `data-tblr-*` is a breaking change held for 2.0 — do not start it here. - Keep the accessible state in sync with the class (`aria-pressed`, `aria-expanded`), as above. - A new module must be imported from `js/tabler.ts`, or it never ships. -- Shared helpers go to `js/src/tabler.ts` (`prefix`, `hexToRgba`, `getColor`), which reads theme values through `--tblr-*` custom properties. +- There is no shared colour helper: demo charts resolve `--tblr-*` colours to hex in `shared/components/demo/Chart.astro`. ## 4. Docs markers diff --git a/.changeset/palette-oklch.md b/.changeset/palette-oklch.md new file mode 100644 index 000000000..d583a7866 --- /dev/null +++ b/.changeset/palette-oklch.md @@ -0,0 +1,6 @@ +--- +"@tabler/core": minor +"@tabler/docs": patch +--- + +Updated the color palette to `oklch()`, masked the checkbox, switch, toggler and carousel icons, and removed the `tabler` JS namespace. diff --git a/core/js/src/tabler.ts b/core/js/src/tabler.ts deleted file mode 100644 index 48b60e313..000000000 --- a/core/js/src/tabler.ts +++ /dev/null @@ -1,17 +0,0 @@ -export const prefix: string = 'tblr-' - -export const hexToRgba = (hex: string, opacity: number): string | null => { - const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex) - - return result ? `rgba(${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(result[3], 16)}, ${opacity})` : null -} - -export const getColor = (color: string, opacity: number = 1): string | null => { - const c = getComputedStyle(document.body).getPropertyValue(`--${prefix}${color}`).trim() - - if (opacity !== 1) { - return hexToRgba(c, opacity) - } - - return c -} diff --git a/core/js/tabler.ts b/core/js/tabler.ts index 35d459052..e4822a3f1 100644 --- a/core/js/tabler.ts +++ b/core/js/tabler.ts @@ -12,6 +12,3 @@ import './src/sortable' // Re-export everything from bootstrap.ts (single source of truth) export * from './src/bootstrap' - -// Re-export tabler namespace -export * as tabler from './src/tabler' diff --git a/core/package.json b/core/package.json index 435c80018..6828bfe9a 100644 --- a/core/package.json +++ b/core/package.json @@ -178,19 +178,19 @@ }, { "path": "./dist/css/tabler-themes.css", - "maxSize": "1.7 kB" + "maxSize": "2 kB" }, { "path": "./dist/css/tabler-themes.min.css", - "maxSize": "1.4 kB" + "maxSize": "1.6 kB" }, { "path": "./dist/css/tabler-themes.rtl.css", - "maxSize": "1.7 kB" + "maxSize": "2 kB" }, { "path": "./dist/css/tabler-themes.rtl.min.css", - "maxSize": "1.4 kB" + "maxSize": "1.6 kB" }, { "path": "./dist/css/tabler-vendors.css", diff --git a/core/scss/_settings.scss b/core/scss/_settings.scss index 7613d835e..22989174f 100644 --- a/core/scss/_settings.scss +++ b/core/scss/_settings.scss @@ -9,7 +9,9 @@ // Prefixes // Characters which are escaped by the escape-svg function -$escaped-characters: (('<', '%3c'), ('>', '%3e'), ('#', '%23'), ('(', '%28'), (')', '%29')) !default; +// `%` goes first so the escapes added below are not re-encoded; it shows up in +// `oklch()` lightness values that end up inside svg data uris. +$escaped-characters: (('%', '%25'), ('<', '%3c'), ('>', '%3e'), ('#', '%23'), ('(', '%28'), (')', '%29')) !default; $black: #000 !default; $white: #fff !default; diff --git a/core/scss/_variables-dark.scss b/core/scss/_variables-dark.scss index f9f426839..d4d0c7f1f 100644 --- a/core/scss/_variables-dark.scss +++ b/core/scss/_variables-dark.scss @@ -73,9 +73,7 @@ $mark-bg-dark: mix-color-css(var(--black), var(--yellow), 60%) !default; $form-select-indicator-color-dark: $body-color-dark !default; $form-select-indicator-dark: url("data:image/svg+xml,") !default; -// Compile-time `rgba()` on purpose: url-encoded into the svg below, where `var()` cannot resolve. -$form-switch-color-dark: rgba($white, 0.25) !default; -$form-switch-bg-image-dark: url("data:image/svg+xml,") !default; +$form-switch-color-dark: color-transparent(var(--white), 0.25) !default; // Form validation colors dark $form-valid-color-dark: mix-color-css(var(--white), var(--green), 40%) !default; diff --git a/core/scss/_variables.scss b/core/scss/_variables.scss index 256d72f9e..445f4e0e4 100644 --- a/core/scss/_variables.scss +++ b/core/scss/_variables.scss @@ -34,17 +34,17 @@ $enable-important-utilities: true !default; $color-mode-type: data !default; // Colors -$gray-50: #f9fafb !default; -$gray-100: #f3f4f6 !default; -$gray-200: #e5e7eb !default; -$gray-300: #d1d5db !default; -$gray-400: #9ca3af !default; -$gray-500: #6b7280 !default; -$gray-600: #4b5563 !default; -$gray-700: #374151 !default; -$gray-800: #1f2937 !default; -$gray-900: #111827 !default; -$gray-950: #030712 !default; +$gray-50: oklch(98.46% 0.0017 247.84deg) !default; // #f9fafb +$gray-100: oklch(96.7% 0.0029 264.54deg) !default; // #f3f4f6 +$gray-200: oklch(92.76% 0.0058 264.53deg) !default; // #e5e7eb +$gray-300: oklch(87.17% 0.0093 258.34deg) !default; // #d1d5db +$gray-400: oklch(71.37% 0.0192 261.32deg) !default; // #9ca3af +$gray-500: oklch(55.1% 0.0234 264.36deg) !default; // #6b7280 +$gray-600: oklch(44.61% 0.0263 256.8deg) !default; // #4b5563 +$gray-700: oklch(37.29% 0.0306 259.73deg) !default; // #374151 +$gray-800: oklch(27.81% 0.0296 256.85deg) !default; // #1f2937 +$gray-900: oklch(21.01% 0.0318 264.66deg) !default; // #111827 +$gray-950: oklch(12.96% 0.0274 261.69deg) !default; // #030712 // $black and $white moved to `_settings.scss` @@ -63,18 +63,18 @@ $grays: ( '900': $gray-900, ) !default; -$blue: #066fd1 !default; -$azure: #4299e1 !default; -$indigo: #4263eb !default; -$purple: #ae3ec9 !default; -$pink: #d6336c !default; -$red: #d63939 !default; -$orange: #f76707 !default; -$yellow: #f59f00 !default; -$lime: #74b816 !default; -$green: #2fb344 !default; -$teal: #0ca678 !default; -$cyan: #17a2b8 !default; +$blue: oklch(54.6% 0.1724 254.2deg) !default; // #066fd1 +$azure: oklch(66.26% 0.1358 246.81deg) !default; // #4299e1 +$indigo: oklch(55.66% 0.2082 268.35deg) !default; // #4263eb +$purple: oklch(57.83% 0.2186 319.15deg) !default; // #ae3ec9 +$pink: oklch(58.81% 0.201 5.25deg) !default; // #d6336c +$red: oklch(58.29% 0.1941 25.59deg) !default; // #d63939 +$orange: oklch(68.51% 0.1954 44.57deg) !default; // #f76707 +$yellow: oklch(77.02% 0.1658 71.02deg) !default; // #f59f00 +$lime: oklch(70.99% 0.1897 131.53deg) !default; // #74b816 +$green: oklch(67.38% 0.1885 145.43deg) !default; // #2fb344 +$teal: oklch(64.32% 0.1338 164.7deg) !default; // #0ca678 +$cyan: oklch(65.52% 0.1105 212.17deg) !default; // #17a2b8 $colors: ( 'blue': $blue, @@ -629,17 +629,17 @@ $extra-colors: ( $social-colors: ( 'x': #000, - 'facebook': #1877f2, + 'facebook': #0866ff, 'twitter': #1da1f2, 'linkedin': #0a66c2, - 'google': #dc4e41, + 'google': #4285f4, 'youtube': #f00, 'vimeo': #1ab7ea, 'dribbble': #ea4c89, 'github': #181717, - 'instagram': #e4405f, + 'instagram': #ff0069, 'pinterest': #bd081c, - 'vk': #6383a8, + 'vk': #07f, 'rss': #ffa500, 'flickr': #0063dc, 'bitbucket': #0052cc, @@ -1335,8 +1335,8 @@ $carousel-control-hover-opacity: 0.9 !default; $carousel-control-transition: opacity 0.15s ease !default; $carousel-control-icon-filter: null !default; $carousel-control-icon-width: 1.5rem !default; -$carousel-control-prev-icon-bg: url("data:image/svg+xml,") !default; -$carousel-control-next-icon-bg: url("data:image/svg+xml,") !default; +$carousel-control-prev-icon-bg: url("data:image/svg+xml,") !default; +$carousel-control-next-icon-bg: url("data:image/svg+xml,") !default; $carousel-indicator-width: 30px !default; $carousel-indicator-height: 3px !default; @@ -1593,11 +1593,9 @@ $navbar-light-active-color: var(--body-color) !default; $navbar-light-hover-color: var(--body-color) !default; $navbar-light-disabled-color: var(--disabled-color) !default; $navbar-light-active-bg: color-transparent(var(--primary), 0.08) !default; -// Compile-time `rgba()` on purpose: this value is url-encoded into the svg data -// uri below, and an svg image cannot resolve `var()` or `color-mix()` with -// `var()` — the stroke would be dropped and the icon would disappear. -$navbar-light-icon-color: rgba($body-color, 0.75) !default; -$navbar-light-toggler-icon-bg: url("data:image/svg+xml,") !default; +$navbar-light-icon-color: color-transparent(var(--body-color), 0.75) !default; +// Monochrome: painted with `--navbar-toggler-icon-color` through `mask-icon()`. +$navbar-toggler-icon-bg: url("data:image/svg+xml,") !default; $navbar-light-toggler-border-color: color-transparent(var(--emphasis-color), 0.15) !default; $navbar-light-brand-hover-color: $navbar-light-active-color !default; @@ -1607,9 +1605,7 @@ $navbar-dark-active-bg: color-transparent(var(--white), 0.06) !default; $navbar-dark-brand-color: $white !default; $navbar-dark-active-color: $white !default; $navbar-dark-disabled-color: var(--disabled-color) !default; -// Compile-time `rgba()` on purpose, see `$navbar-light-icon-color`. -$navbar-dark-icon-color: rgba($white, $text-secondary-opacity) !default; -$navbar-dark-toggler-icon-bg: url("data:image/svg+xml,") !default; +$navbar-dark-icon-color: color-transparent(var(--white), $text-secondary-opacity) !default; $navbar-dark-toggler-border-color: color-transparent(var(--white), 0.1) !default; $navbar-dark-brand-hover-color: $navbar-dark-active-color !default; @@ -1946,15 +1942,16 @@ $form-check-input-box-shadow: $input-box-shadow !default; $form-check-input-checked-bg-size: 1.25rem !default; $form-check-input-checked-bg-color: var(--primary) !default; $form-check-input-checked-bg-repeat: repeat !default; -$form-check-input-checked-bg-image: url("data:image/svg+xml,") !default; -$form-check-input-checked-bg-image-dark: url("data:image/svg+xml,") !default; +$form-check-input-checked-color: $white !default; +// Monochrome icons, painted with `--form-check-icon-color` through `mask-icon()`. +$form-check-input-checked-bg-image: url("data:image/svg+xml,") !default; $form-check-input-checked-border-color: $input-border-color-translucent !default; $form-check-input-indeterminate-color: $component-active-color !default; $form-check-input-indeterminate-bg-color: var(--primary) !default; $form-check-input-indeterminate-border-color: $form-check-input-indeterminate-bg-color !default; -$form-check-input-indeterminate-bg-image: url("data:image/svg+xml,") !default; +$form-check-input-indeterminate-bg-image: url("data:image/svg+xml,") !default; -$form-check-radio-checked-bg-image: url("data:image/svg+xml,") !default; +$form-check-radio-checked-bg-image: url("data:image/svg+xml,") !default; $form-check-input-disabled-opacity: 0.5 !default; $form-check-label-disabled-opacity: $text-secondary-opacity; @@ -1997,19 +1994,17 @@ $form-select-font-size-lg: $input-font-size-lg !default; $form-select-border-radius-lg: $input-border-radius-lg !default; $form-select-transition: $input-transition !default; -$form-switch-color: $gray-200 !default; +$form-switch-color: var(--gray-200) !default; $form-switch-width: 2rem !default; $form-switch-height: 1.25rem !default; $form-switch-padding-start: $form-switch-width + 0.5rem !default; -$form-switch-bg-image: url("data:image/svg+xml,") !default; $form-switch-border-radius: $form-switch-width !default; -$form-switch-transition: background-position 0.15s ease-in-out !default; -// Compile-time tint on purpose: url-encoded into the svg below, where `var()` cannot resolve. -$form-switch-focus-color: tint-color($component-active-bg, 50%) !default; -$form-switch-focus-bg-image: url("data:image/svg+xml,") !default; -$form-switch-checked-color: $white !default; -$form-switch-checked-bg-image: url("data:image/svg+xml,") !default; -$form-switch-checked-bg-position: right center !default; +$form-switch-transition: + background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out !default; +$form-switch-knob-transition: inset-inline-start 0.15s ease-in-out !default; +$form-switch-focus-color: mix-color-css(var(--white), var(--primary), 50%) !default; +$form-switch-checked-color: var(--white) !default; // Form range $form-range-track-width: 100% !default; diff --git a/core/scss/bootstrap/_carousel.scss b/core/scss/bootstrap/_carousel.scss index 60df9ce1f..c3f610d4c 100644 --- a/core/scss/bootstrap/_carousel.scss +++ b/core/scss/bootstrap/_carousel.scss @@ -130,16 +130,15 @@ display: inline-block; width: $carousel-control-icon-width; height: $carousel-control-icon-width; - background-repeat: no-repeat; - background-position: 50%; - background-size: 100% 100%; + background-color: currentColor; + @include mask-icon($size: 100% 100%, $position: 50%); } .carousel-control-prev-icon { - background-image: escape-svg($carousel-control-prev-icon-bg) #{'/*rtl:' + escape-svg($carousel-control-next-icon-bg) + '*/'}; + mask-image: escape-svg($carousel-control-prev-icon-bg) #{'/*rtl:' + escape-svg($carousel-control-next-icon-bg) + '*/'}; } .carousel-control-next-icon { - background-image: escape-svg($carousel-control-next-icon-bg) #{'/*rtl:' + escape-svg($carousel-control-prev-icon-bg) + '*/'}; + mask-image: escape-svg($carousel-control-next-icon-bg) #{'/*rtl:' + escape-svg($carousel-control-prev-icon-bg) + '*/'}; } // Optional indicator pips/controls diff --git a/core/scss/bootstrap/_navbar.scss b/core/scss/bootstrap/_navbar.scss index 4e26f6ffe..37456bf09 100644 --- a/core/scss/bootstrap/_navbar.scss +++ b/core/scss/bootstrap/_navbar.scss @@ -24,7 +24,8 @@ --navbar-toggler-padding-y: #{$navbar-toggler-padding-y}; --navbar-toggler-padding-x: #{$navbar-toggler-padding-x}; --navbar-toggler-font-size: #{$navbar-toggler-font-size}; - --navbar-toggler-icon-bg: #{escape-svg($navbar-light-toggler-icon-bg)}; + --navbar-toggler-icon-bg: #{escape-svg($navbar-toggler-icon-bg)}; + --navbar-toggler-icon-color: #{$navbar-light-icon-color}; --navbar-toggler-border-color: #{$navbar-light-toggler-border-color}; --navbar-toggler-border-radius: #{$navbar-toggler-border-radius}; --navbar-toggler-focus-width: #{$navbar-toggler-focus-width}; @@ -175,10 +176,8 @@ width: 1.5em; height: 1.5em; vertical-align: middle; - background-image: var(--navbar-toggler-icon-bg); - background-repeat: no-repeat; - background-position: center; - background-size: 100%; + background-color: var(--navbar-toggler-icon-color); + @include mask-icon(var(--navbar-toggler-icon-bg), $size: 100%); } .navbar-nav-scroll { @@ -278,14 +277,14 @@ --navbar-brand-color: #{$navbar-dark-brand-color}; --navbar-brand-hover-color: #{$navbar-dark-brand-hover-color}; --navbar-toggler-border-color: #{$navbar-dark-toggler-border-color}; - --navbar-toggler-icon-bg: #{escape-svg($navbar-dark-toggler-icon-bg)}; + --navbar-toggler-icon-color: #{$navbar-dark-icon-color}; // scss-docs-end navbar-dark-css-vars } @if $enable-dark-mode { @include color-mode(dark) { .navbar-toggler-icon { - --navbar-toggler-icon-bg: #{escape-svg($navbar-dark-toggler-icon-bg)}; + --navbar-toggler-icon-color: #{$navbar-dark-icon-color}; } } } diff --git a/core/scss/bootstrap/_reboot.scss b/core/scss/bootstrap/_reboot.scss index 77fae89b3..91d6dfd10 100644 --- a/core/scss/bootstrap/_reboot.scss +++ b/core/scss/bootstrap/_reboot.scss @@ -54,7 +54,7 @@ body { text-align: var(--body-text-align); background-color: var(--body-bg); // 2 -webkit-text-size-adjust: 100%; // 3 - -webkit-tap-highlight-color: rgba($black, 0); // 4 + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); // 4 } // scss-docs-end reboot-body-rules diff --git a/core/scss/bootstrap/forms/_form-check.scss b/core/scss/bootstrap/forms/_form-check.scss index 04e6e12a4..606fa122c 100644 --- a/core/scss/bootstrap/forms/_form-check.scss +++ b/core/scss/bootstrap/forms/_form-check.scss @@ -30,7 +30,9 @@ .form-check-input { --form-check-bg: #{$form-check-input-bg}; + --form-check-icon-color: #{$form-check-input-checked-color}; + position: relative; flex-shrink: 0; width: $form-check-input-width; height: $form-check-input-width; @@ -38,19 +40,32 @@ vertical-align: top; appearance: none; background-color: var(--form-check-bg); - background-image: var(--form-check-bg-image); - background-repeat: no-repeat; - background-position: center; - background-size: contain; border: $form-check-input-border; print-color-adjust: exact; // Keep themed appearance for print @include transition($form-check-transition); + // The check, dash or dot is a pseudo-element painted through a CSS mask, so + // its colour comes from `--form-check-icon-color` instead of a colour baked + // into the svg. Hidden until the input is checked or indeterminate. + &::before { + position: absolute; + inset: 0; + pointer-events: none; + content: ''; + background-color: var(--form-check-icon-color); + opacity: 0; + @include mask-icon(var(--form-check-icon)); + } + &[type='checkbox'] { + --form-check-icon: #{escape-svg($form-check-input-checked-bg-image)}; + @include border-radius($form-check-input-border-radius); } &[type='radio'] { + --form-check-icon: #{escape-svg($form-check-radio-checked-bg-image)}; + // stylelint-disable-next-line property-disallowed-list border-radius: $form-check-radio-border-radius; } @@ -69,31 +84,20 @@ background-color: $form-check-input-checked-bg-color; border-color: $form-check-input-checked-border-color; - &[type='checkbox'] { - @if $enable-gradients { - --form-check-bg-image: #{escape-svg($form-check-input-checked-bg-image)}, var(--gradient); - } @else { - --form-check-bg-image: #{escape-svg($form-check-input-checked-bg-image)}; - } - } - - &[type='radio'] { - @if $enable-gradients { - --form-check-bg-image: #{escape-svg($form-check-radio-checked-bg-image)}, var(--gradient); - } @else { - --form-check-bg-image: #{escape-svg($form-check-radio-checked-bg-image)}; - } + &::before { + opacity: 1; } } &[type='checkbox']:indeterminate { + --form-check-icon: #{escape-svg($form-check-input-indeterminate-bg-image)}; + --form-check-icon-color: #{$form-check-input-indeterminate-color}; + background-color: $form-check-input-indeterminate-bg-color; border-color: $form-check-input-indeterminate-border-color; - @if $enable-gradients { - --form-check-bg-image: #{escape-svg($form-check-input-indeterminate-bg-image)}, var(--gradient); - } @else { - --form-check-bg-image: #{escape-svg($form-check-input-indeterminate-bg-image)}; + &::before { + opacity: 1; } } @@ -127,26 +131,40 @@ padding-inline-start: $form-switch-padding-start; .form-check-input { - --form-switch-bg: #{escape-svg($form-switch-bg-image)}; + --form-switch-color: #{$form-switch-color}; + // The knob is a circle three quarters of the track height, inset by an eighth + // — the same geometry the old `viewBox='-4 -4 8 8'` svg had. + --form-switch-knob-size: calc(#{$form-switch-height} * 0.75); + --form-switch-knob-offset: calc(#{$form-switch-height} * 0.125); width: $form-switch-width; margin-inline-start: $form-switch-padding-start * -1; - background-image: var(--form-switch-bg); - background-position: left center; @include border-radius($form-switch-border-radius, 0); @include transition($form-switch-transition); + // The knob replaces the masked check icon: a plain circle that slides. + &::before { + inset: 0 auto 0 var(--form-switch-knob-offset); + width: var(--form-switch-knob-size); + height: var(--form-switch-knob-size); + margin-block: auto; + background-color: var(--form-switch-color); + // stylelint-disable-next-line property-disallowed-list + border-radius: 50%; + opacity: 1; + mask-image: none; + @include transition($form-switch-knob-transition); + } + &:focus { - --form-switch-bg: #{escape-svg($form-switch-focus-bg-image)}; + --form-switch-color: #{$form-switch-focus-color}; } &:checked { - background-position: $form-switch-checked-bg-position; + --form-switch-color: #{$form-switch-checked-color}; - @if $enable-gradients { - --form-switch-bg: #{escape-svg($form-switch-checked-bg-image)}, var(--gradient); - } @else { - --form-switch-bg: #{escape-svg($form-switch-checked-bg-image)}; + &::before { + inset-inline-start: calc(100% - var(--form-switch-knob-size) - var(--form-switch-knob-offset)); } } } @@ -185,7 +203,7 @@ @if $enable-dark-mode { @include color-mode(dark) { .form-switch .form-check-input:not(:checked):not(:focus) { - --form-switch-bg: #{escape-svg($form-switch-bg-image-dark)}; + --form-switch-color: #{$form-switch-color-dark}; } } } diff --git a/core/scss/layout/_navbar.scss b/core/scss/layout/_navbar.scss index 6912f8b1e..7ea1dfd87 100644 --- a/core/scss/layout/_navbar.scss +++ b/core/scss/layout/_navbar.scss @@ -504,6 +504,7 @@ Navbar toggler width: 1.25em; height: 2px; background: currentColor; + mask-image: none; // the bars below replace the masked hamburger from bootstrap/_navbar @include border-radius(var(--border-radius-pill)); @include transition(top $navbar-toggler-animation-time $navbar-toggler-animation-time, bottom $navbar-toggler-animation-time $navbar-toggler-animation-time, transform $navbar-toggler-animation-time, opacity 0s $navbar-toggler-animation-time); diff --git a/core/scss/marketing/_hero.scss b/core/scss/marketing/_hero.scss index 99895c81b..ec2fa5993 100644 --- a/core/scss/marketing/_hero.scss +++ b/core/scss/marketing/_hero.scss @@ -49,8 +49,6 @@ max-width: 65rem; margin: 4rem auto; @include border-radius($border-radius-lg); - //box-shadow: 0 10px 15px -3px rgba($color-text, 0.1), - // 0 4px 6px -2px rgba($color-text, 0.05); img, svg { @@ -60,12 +58,3 @@ height: auto; } } -// -//.hero-img-side { -// img, -// svg { -// max-width: 100%; -// height: auto; -// display: block; -// } -//} diff --git a/core/scss/mixins/_functions.scss b/core/scss/mixins/_functions.scss index 6884b498a..c2ce2ea00 100644 --- a/core/scss/mixins/_functions.scss +++ b/core/scss/mixins/_functions.scss @@ -35,38 +35,6 @@ @return oklch(from #{$color} calc(l #{$sign} #{math.abs($delta)}) c h); } -// str-replace was defined here twice — the canonical copy lives in the -// "Bootstrap functions" section below. - -@mixin media-breakpoint-down-than($name, $breakpoints: $grid-breakpoints) { - $prev: breakpoint-prev($name, $breakpoints); - - @if $prev == null { - @content; - } @else { - $max: breakpoint-max($prev, $breakpoints); - - @if $max { - @media (max-width: $max) { - @content; - } - } @else { - @content; - } - } -} - -@function breakpoint-prev($name, $breakpoints: $grid-breakpoints, $breakpoint-names: map.keys($breakpoints)) { - $n: list.index($breakpoint-names, $name); - @if not $n { - @error "breakpoint `#{$name}` not found in `#{$breakpoints}`"; - } - @return if(sass($n > 1): list.nth($breakpoint-names, $n - 1); else: null); -} - -// escape-svg was defined here twice — the canonical copy lives in the -// "Bootstrap functions" section below. - /** * Converts a given value to a percentage string. * @@ -98,47 +66,18 @@ @return color-mix(in oklab, #{$color} #{to-percentage($alpha)}, $background); } -@function url-svg($svg) { - $svg: str-replace($svg, '#', '%23'); - $svg: str-replace($svg, '= $num { - @warn "Invalid value for #{$map-name}: This map must be in ascending order, but key '#{$key}' has value #{$num} which isn't greater than #{$prev-num}, the value of the previous key '#{$prev-key}' !"; - } - $prev-key: $key; - $prev-num: $num; - } -} - -// Starts at zero -// Used to ensure the min-width of the lowest breakpoint starts at 0. -@mixin _assert-starts-at-zero($map, $map-name: '$grid-breakpoints') { - @if list.length($map) > 0 { - $values: map.values($map); - $first-value: list.nth($values, 1); - @if $first-value != 0 { - @warn "First breakpoint in #{$map-name} must start at 0, but starts at #{$first-value}."; - } - } -} - // Colors @function to-rgb($value) { @return color.channel($value, 'red', $space: rgb), color.channel($value, 'green', $space: rgb), color.channel($value, 'blue', $space: rgb); @@ -177,14 +116,6 @@ } // stylelint-enable scss/dollar-variable-pattern -@function varify($list) { - $result: null; - @each $entry in $list { - $result: list.append($result, var(--#{$entry}), space); - } - @return $result; -} - // Internal Bootstrap function to turn maps into its negative variant. // It prefixes the keys with `n` and makes the value negative. @function negativify-map($map) { @@ -197,32 +128,6 @@ @return $result; } -// Get multiple keys from a sass map -@function map-get-multiple($map, $values) { - $result: (); - @each $key, $value in $map { - @if (list.index($values, $key) != null) { - $result: map.merge( - $result, - ( - $key: $value, - ) - ); - } - } - @return $result; -} - -// Merge multiple maps -@function map-merge-multiple($maps...) { - $merged-maps: (); - - @each $map in $maps { - $merged-maps: map.merge($merged-maps, $map); - } - @return $merged-maps; -} - // Replace `$search` with `$replace` in `$string` // Used on our SVG icon backgrounds for custom forms. // @@ -325,7 +230,7 @@ $_luminance-list: 0.0008 0.001 0.0011 0.0013 0.0015 0.0017 0.002 0.0022 0.0025 0 // Return opaque color // opaque(#fff, rgba(0, 0, 0, .5)) => #808080 @function opaque($background, $foreground) { - @return color.mix(rgba($foreground, 1), $background, color.opacity($foreground) * 100%); + @return color.mix(color.change($foreground, $alpha: 1), $background, color.channel($foreground, 'alpha') * 100%, $method: srgb); } // Tint a color: mix a color with white (in oklab, see `mix-color()`) diff --git a/core/scss/mixins/_mixins.scss b/core/scss/mixins/_mixins.scss index d851ecc1e..c1201bdd2 100644 --- a/core/scss/mixins/_mixins.scss +++ b/core/scss/mixins/_mixins.scss @@ -79,6 +79,23 @@ // // Print styles // +// Renders an svg icon through a CSS mask so the shape is painted with the +// element's `background-color` and follows the theme without a colour baked +// into the data uri. Set `background-color` on the element; pass `null` for +// `$icon` when the mask image is applied per state. +@mixin mask-icon($icon: null, $size: contain, $position: center) { + @if $icon != null { + mask-image: $icon; + } + mask-repeat: no-repeat; + @if $position != null { + mask-position: $position; + } + @if $size != null { + mask-size: $size; + } +} + @mixin media-print { @media print { @content; diff --git a/core/scss/tabler-themes.scss b/core/scss/tabler-themes.scss index ce1ee03c1..b367c2b05 100644 --- a/core/scss/tabler-themes.scss +++ b/core/scss/tabler-themes.scss @@ -2,17 +2,17 @@ [data-bs-theme-base='slate'], [data-theme-base='slate'] { - --gray-50: #f8fafc; - --gray-100: #f1f5f9; - --gray-200: #e2e8f0; - --gray-300: #cbd5e1; - --gray-400: #94a3b8; - --gray-500: #64748b; - --gray-600: #475569; - --gray-700: #334155; - --gray-800: #1e293b; - --gray-900: #0f172a; - --gray-950: #020617; + --gray-50: oklch(98.42% 0.0034 247.86deg); // #f8fafc + --gray-100: oklch(96.83% 0.0069 247.9deg); // #f1f5f9 + --gray-200: oklch(92.88% 0.0126 255.51deg); // #e2e8f0 + --gray-300: oklch(86.9% 0.0198 252.89deg); // #cbd5e1 + --gray-400: oklch(71.07% 0.0351 256.79deg); // #94a3b8 + --gray-500: oklch(55.44% 0.0407 257.42deg); // #64748b + --gray-600: oklch(44.55% 0.0374 257.28deg); // #475569 + --gray-700: oklch(37.17% 0.0392 257.29deg); // #334155 + --gray-800: oklch(27.95% 0.0368 260.03deg); // #1e293b + --gray-900: oklch(20.77% 0.0398 265.75deg); // #0f172a + --gray-950: oklch(12.88% 0.0406 264.7deg); // #020617 } [data-bs-theme-base='gray'], @@ -32,63 +32,63 @@ [data-bs-theme-base='zinc'], [data-theme-base='zinc'] { - --gray-50: #fafafa; - --gray-100: #f4f4f5; - --gray-200: #e4e4e7; - --gray-300: #d4d4d8; - --gray-400: #a1a1aa; - --gray-500: #71717a; - --gray-600: #52525b; - --gray-700: #3f3f46; - --gray-800: #27272a; - --gray-900: #18181b; - --gray-950: #09090b; + --gray-50: oklch(98.51% 0 0deg); // #fafafa + --gray-100: oklch(96.74% 0.0013 286.37deg); // #f4f4f5 + --gray-200: oklch(91.97% 0.004 286.32deg); // #e4e4e7 + --gray-300: oklch(87.11% 0.0055 286.29deg); // #d4d4d8 + --gray-400: oklch(71.18% 0.0129 286.07deg); // #a1a1aa + --gray-500: oklch(55.17% 0.0138 285.94deg); // #71717a + --gray-600: oklch(44.19% 0.0146 285.79deg); // #52525b + --gray-700: oklch(37.03% 0.0119 285.81deg); // #3f3f46 + --gray-800: oklch(27.39% 0.0055 286.03deg); // #27272a + --gray-900: oklch(21.03% 0.0059 285.89deg); // #18181b + --gray-950: oklch(14.08% 0.0044 285.82deg); // #09090b } [data-bs-theme-base='neutral'], [data-theme-base='neutral'] { - --gray-50: #fafafa; - --gray-100: #f5f5f5; - --gray-200: #e5e5e5; - --gray-300: #d4d4d4; - --gray-400: #a3a3a3; - --gray-500: #737373; - --gray-600: #525252; - --gray-700: #404040; - --gray-800: #262626; - --gray-900: #171717; - --gray-950: #0a0a0a; + --gray-50: oklch(98.51% 0 0deg); // #fafafa + --gray-100: oklch(97.02% 0 0deg); // #f5f5f5 + --gray-200: oklch(92.19% 0 0deg); // #e5e5e5 + --gray-300: oklch(86.99% 0 0deg); // #d4d4d4 + --gray-400: oklch(71.55% 0 0deg); // #a3a3a3 + --gray-500: oklch(55.55% 0 0deg); // #737373 + --gray-600: oklch(43.86% 0 0deg); // #525252 + --gray-700: oklch(37.15% 0 0deg); // #404040 + --gray-800: oklch(26.86% 0 0deg); // #262626 + --gray-900: oklch(20.46% 0 0deg); // #171717 + --gray-950: oklch(14.48% 0 0deg); // #0a0a0a } [data-bs-theme-base='stone'], [data-theme-base='stone'] { - --gray-50: #fafaf9; - --gray-100: #f5f5f4; - --gray-200: #e7e5e4; - --gray-300: #d6d3d1; - --gray-400: #a8a29e; - --gray-500: #78716c; - --gray-600: #57534e; - --gray-700: #44403c; - --gray-800: #292524; - --gray-900: #1c1917; - --gray-950: #0c0a09; + --gray-50: oklch(98.48% 0.0013 106.42deg); // #fafaf9 + --gray-100: oklch(96.99% 0.0013 106.42deg); // #f5f5f4 + --gray-200: oklch(92.32% 0.0026 48.72deg); // #e7e5e4 + --gray-300: oklch(86.87% 0.0043 56.37deg); // #d6d3d1 + --gray-400: oklch(71.61% 0.0091 56.26deg); // #a8a29e + --gray-500: oklch(55.34% 0.0116 58.07deg); // #78716c + --gray-600: oklch(44.44% 0.0096 73.64deg); // #57534e + --gray-700: oklch(37.41% 0.0087 67.56deg); // #44403c + --gray-800: oklch(26.85% 0.0063 34.3deg); // #292524 + --gray-900: oklch(21.61% 0.0061 56.04deg); // #1c1917 + --gray-950: oklch(14.69% 0.0041 49.25deg); // #0c0a09 } // Easter egg: not listed in the theme settings panel, enable with `data-bs-theme-base="pink"` or `?theme-base=pink` [data-bs-theme-base='pink'], [data-theme-base='pink'] { - --gray-50: #fdf2f8; - --gray-100: #fce7f3; - --gray-200: #fbcfe8; - --gray-300: #f9a8d4; - --gray-400: #f472b6; - --gray-500: #ec4899; - --gray-600: #db2777; - --gray-700: #be185d; - --gray-800: #9d174d; - --gray-900: #831843; - --gray-950: #500724; + --gray-50: oklch(97.14% 0.0141 343.2deg); // #fdf2f8 + --gray-100: oklch(94.82% 0.0276 342.26deg); // #fce7f3 + --gray-200: oklch(89.94% 0.0589 343.23deg); // #fbcfe8 + --gray-300: oklch(82.28% 0.1095 346.02deg); // #f9a8d4 + --gray-400: oklch(72.53% 0.1752 349.76deg); // #f472b6 + --gray-500: oklch(65.59% 0.2118 354.31deg); // #ec4899 + --gray-600: oklch(59.16% 0.218 0.58deg); // #db2777 + --gray-700: oklch(52.46% 0.199 3.96deg); // #be185d + --gray-800: oklch(45.87% 0.1697 3.82deg); // #9d174d + --gray-900: oklch(40.78% 0.1442 2.43deg); // #831843 + --gray-950: oklch(28.45% 0.1048 3.91deg); // #500724 } @each $name, $value in $extra-colors { diff --git a/core/scss/tests/_breakpoints.test.scss b/core/scss/tests/_breakpoints.test.scss index 53ff9251e..75810d207 100644 --- a/core/scss/tests/_breakpoints.test.scss +++ b/core/scss/tests/_breakpoints.test.scss @@ -2,11 +2,7 @@ // "needs config" tier: they default to the global `$grid-breakpoints`, but every // function also accepts an explicit `$breakpoints` map, so the tests pass a // local fixture map and stay independent of the project variables. -// -// `breakpoint-prev` lives in mixins/_functions.scss; the rest live in -// mixins/bootstrap/_breakpoints.scss. -@use '../mixins/functions' as fn; @use '../mixins/bootstrap/breakpoints' as *; $grid: ( @@ -17,15 +13,13 @@ $grid: ( xl: 1200px, ); -@include describe('breakpoint-next / breakpoint-prev') { +@include describe('breakpoint-next') { @include it('returns the neighbouring breakpoint name') { @include assert-equal(breakpoint-next(md, $grid), lg); - @include assert-equal(fn.breakpoint-prev(md, $grid), sm); } - @include it('returns null past the last / before the first breakpoint') { + @include it('returns null past the last breakpoint') { @include assert-equal(breakpoint-next(xl, $grid), null); - @include assert-equal(fn.breakpoint-prev(xs, $grid), null); } } diff --git a/core/scss/tests/_colors-misc.test.scss b/core/scss/tests/_colors-misc.test.scss index d8cb840af..023035075 100644 --- a/core/scss/tests/_colors-misc.test.scss +++ b/core/scss/tests/_colors-misc.test.scss @@ -67,16 +67,6 @@ } } -@include describe('url-svg') { - @include it("percent-encodes '#' so the data URI stays valid") { - @include assert-equal(meta.inspect(url-svg('#')), 'url("data:image/svg+xml;charset=UTF-8,%23")'); - } - - @include it('injects the SVG xmlns attribute') { - @include assert-equal(meta.inspect(url-svg('')), 'url(\'data:image/svg+xml;charset=UTF-8,\')'); - } -} - @include describe('map-loop') { // Applies a module-level function (resolved by name via meta.get-function) to // every value in the map. `to-percentage` lives in the same module, so it diff --git a/core/scss/tests/_css-var.test.scss b/core/scss/tests/_css-var.test.scss index 3c6fde9ca..f368654bb 100644 --- a/core/scss/tests/_css-var.test.scss +++ b/core/scss/tests/_css-var.test.scss @@ -1,31 +1,10 @@ -// Tests for the CSS custom-property builders in scss/mixins/_functions.scss: -// `varify()` and `rgba-css-var()`. A plain `@use` of the functions module is -// enough — no fixtures. Both emit unprefixed names: the `--tblr-` prefix is -// added later, by the css pipeline (.build/css-var-prefix.ts). +// Tests for `rgba-css-var()` in scss/mixins/_functions.scss. A plain `@use` of +// the functions module is enough — no fixtures. It emits unprefixed names: the +// `--tblr-` prefix is added later, by the css pipeline (.build/css-var-prefix.ts). @use 'sass:meta'; @use '../mixins/functions' as *; -@include describe('varify') { - // `varify` seeds its accumulator with `null`, so the raw list inspects as - // `null var(...) ...`; Sass drops the null when serialising to CSS, so the - // behaviour is asserted through the rendered output rather than the value. - @include it('prefixes each entry as a space-separated var() list') { - @include assert() { - @include output() { - .t { - padding: varify(('gutter-x' 'gutter-y')); - } - } - @include expect() { - .t { - padding: var(--gutter-x) var(--gutter-y); - } - } - } - } -} - @include describe('rgba-css-var') { @include it('builds a color-mix() from the identifier and target opacity') { @include assert-equal(meta.inspect(rgba-css-var('primary', 'bg')), 'color-mix(in oklab, var(--primary) calc(var(--bg-opacity) * 100%), transparent)'); diff --git a/core/scss/tests/_escape-svg.test.scss b/core/scss/tests/_escape-svg.test.scss index f36c7a16f..1830358c5 100644 --- a/core/scss/tests/_escape-svg.test.scss +++ b/core/scss/tests/_escape-svg.test.scss @@ -1,3 +1,4 @@ +@use 'sass:meta'; // Tests for `escape-svg()` from scss/mixins/_functions.scss. // // It iterates the global `$escaped-characters` map, which lives in @@ -8,6 +9,10 @@ @use '../mixins/functions' as *; @include describe('escape-svg') { + @include it('percent-encodes % so oklch() lightness survives inside a data uri') { + @include assert-equal(meta.inspect(escape-svg('url("data:image/svg+xml,")')), 'url("data:image/svg+xml,%3cpath stroke=\'oklch%2854.6%25 0.17 254deg%29\'/%3e")'); + } + @include it('returns a non data-URI string unchanged') { @include assert-equal(escape-svg('no data uri '), 'no data uri '); } diff --git a/core/scss/tests/_maps.test.scss b/core/scss/tests/_maps.test.scss index 0dcc1b337..bc62f6cee 100644 --- a/core/scss/tests/_maps.test.scss +++ b/core/scss/tests/_maps.test.scss @@ -33,44 +33,3 @@ ); } } - -@include describe('map-get-multiple') { - @include it('keeps only the requested keys, preserving map order') { - @include assert-equal( - map-get-multiple( - ( - a: 1, - b: 2, - c: 3, - ), - (a c) - ), - ( - a: 1, - c: 3, - ) - ); - } -} - -@include describe('map-merge-multiple') { - @include it('merges maps left-to-right, later keys winning') { - @include assert-equal( - map-merge-multiple( - ( - a: 1, - ), - ( - b: 2, - ), - ( - a: 3, - ) - ), - ( - a: 3, - b: 2, - ) - ); - } -} diff --git a/core/scss/tests/_media-breakpoint.test.scss b/core/scss/tests/_media-breakpoint.test.scss index 85ddee4e2..f93173bfd 100644 --- a/core/scss/tests/_media-breakpoint.test.scss +++ b/core/scss/tests/_media-breakpoint.test.scss @@ -2,10 +2,6 @@ // functions, every mixin accepts an explicit `$breakpoints` map, so the tests // pass a local fixture map and stay independent of the global `$grid-breakpoints`. // -// `media-breakpoint-{up,down,between,only}` come from -// mixins/bootstrap/_breakpoints.scss; `media-breakpoint-down-than` lives in -// mixins/_functions.scss. Their names don't collide, so both are used as `*`. -// // Max-widths are the breakpoint value minus 0.02px (the min-/max- overlap fix); // the smallest/edge cases emit the content with no media query at all. @@ -137,66 +133,3 @@ $g: ( } } } - -@include describe('media-breakpoint-down-than') { - @include it('targets everything below the previous breakpoint') { - @include assert() { - @include output() { - @include media-breakpoint-down-than(md, $g) { - .t { - color: red; - } - } - } - @include expect() { - @media (max-width: 575.98px) { - .t { - color: red; - } - } - } - } - } - - @include it('emits content directly when there is no previous breakpoint') { - @include assert() { - @include output() { - @include media-breakpoint-down-than(xs, $g) { - .t { - color: red; - } - } - } - @include expect() { - .t { - color: red; - } - } - } - } - - // Names that don't exist in the global $grid-breakpoints at all: this only - // resolves if $breakpoints actually reaches the breakpoint-prev() lookup, - // not just the breakpoint-max() one. - $custom: ( - tablet: 0, - desktop: 900px, - ); - - @include it('resolves the previous breakpoint from a custom $breakpoints map') { - @include assert() { - @include output() { - @include media-breakpoint-down-than(desktop, $custom) { - .t { - color: red; - } - } - } - @include expect() { - .t { - color: red; - } - } - } - } -} diff --git a/core/scss/ui/forms/_form-colorinput.scss b/core/scss/ui/forms/_form-colorinput.scss index aef9d7ae7..14c1420b8 100644 --- a/core/scss/ui/forms/_form-colorinput.scss +++ b/core/scss/ui/forms/_form-colorinput.scss @@ -35,7 +35,7 @@ Color Input height: 100%; content: ''; background: no-repeat center center/$form-check-input-checked-bg-size; - background-image: escape-svg($form-check-input-checked-bg-image); + background-image: escape-svg(svg-icon-color($form-check-input-checked-bg-image, $form-check-input-checked-color)); opacity: 0; @include transition(opacity $transition-time); @@ -51,7 +51,7 @@ Color Input @at-root .form-colorinput-light & { &::before { - background-image: escape-svg($form-check-input-checked-bg-image-dark); + background-image: escape-svg(svg-icon-color($form-check-input-checked-bg-image, $body-color)); } } } diff --git a/core/scss/ui/forms/_form-imagecheck.scss b/core/scss/ui/forms/_form-imagecheck.scss index 3d5ec4d51..b617fe641 100644 --- a/core/scss/ui/forms/_form-imagecheck.scss +++ b/core/scss/ui/forms/_form-imagecheck.scss @@ -54,7 +54,7 @@ Image check .form-imagecheck-input:checked ~ & { background-color: $form-check-input-checked-bg-color; - background-image: escape-svg($form-check-input-checked-bg-image); + background-image: escape-svg(svg-icon-color($form-check-input-checked-bg-image, $form-check-input-checked-color)); background-repeat: $form-check-input-checked-bg-repeat; background-position: center; background-size: $form-check-input-checked-bg-size; @@ -66,7 +66,7 @@ Image check } .form-imagecheck-input[type='radio']:checked ~ & { - background-image: escape-svg($form-check-radio-checked-bg-image); + background-image: escape-svg(svg-icon-color($form-check-radio-checked-bg-image, $form-check-input-checked-color)); } } } diff --git a/core/scss/ui/forms/_form-selectgroup.scss b/core/scss/ui/forms/_form-selectgroup.scss index edeb58c02..85db7aef2 100644 --- a/core/scss/ui/forms/_form-selectgroup.scss +++ b/core/scss/ui/forms/_form-selectgroup.scss @@ -85,11 +85,11 @@ Select group } .form-selectgroup-input[type='checkbox']:checked + .form-selectgroup-label & { - background-image: escape-svg($form-check-input-checked-bg-image); + background-image: escape-svg(svg-icon-color($form-check-input-checked-bg-image, $form-check-input-checked-color)); } .form-selectgroup-input[type='radio']:checked + .form-selectgroup-label & { - background-image: escape-svg($form-check-radio-checked-bg-image); + background-image: escape-svg(svg-icon-color($form-check-radio-checked-bg-image, $form-check-input-checked-color)); } } diff --git a/docs/content/ui/plugins/color-picker.mdx b/docs/content/ui/plugins/color-picker.mdx index 278003ffa..9d75df5aa 100644 --- a/docs/content/ui/plugins/color-picker.mdx +++ b/docs/content/ui/plugins/color-picker.mdx @@ -103,13 +103,7 @@ Coloris({ }) ``` -To read the colors from the theme instead of writing them by hand, take them from the CSS variables: - -```js -const swatches = ['--tblr-blue', '--tblr-azure', '--tblr-indigo'].map(function (prop) { - return getComputedStyle(document.body).getPropertyValue(prop) -}) -``` +Write the swatches as hex values. Tabler's CSS variables such as `--tblr-blue` hold `oklch()` colors, which Coloris cannot parse, so do not read the swatches from them. ### Swatches only @@ -192,9 +186,7 @@ Use the picker like any other form field: a label, the input, and a hint below i window.addEventListener('load', function () { if (typeof Coloris === 'undefined') return; - const swatches = ['--tblr-blue', '--tblr-azure', '--tblr-indigo', '--tblr-purple', '--tblr-pink', '--tblr-red', '--tblr-orange', '--tblr-green'].map(function (prop) { - return getComputedStyle(document.body).getPropertyValue(prop).trim(); - }); + const swatches = ['#066fd1', '#4299e1', '#4263eb', '#ae3ec9', '#d6336c', '#d63939', '#f76707', '#2fb344']; Coloris({ el: '.form-control[id^="colorpicker"]', selectInput: false, format: 'hex', alpha: false, swatchesOnly: false, swatches: swatches }); diff --git a/shared/ui/Colorpicker.astro b/shared/ui/Colorpicker.astro index 74c6ef7bc..f5a28e3c7 100644 --- a/shared/ui/Colorpicker.astro +++ b/shared/ui/Colorpicker.astro @@ -15,8 +15,10 @@ interface Props { const { value, id, format = false, class: className, ariaLabel } = Astro.props const alpha = false -const colors = site.colors as Record -const swatchProps = Object.values(colors).map((color) => color.prop) +// Coloris parses hex, rgb and hsl only, and the CSS tokens are `oklch()`, so the +// swatches use the hex values from site.json. +const colors = site.colors as Record +const swatches = Object.values(colors).map((color) => color.hex) const colorpickerId = `colorpicker-${id}` const colorpickerSelector = `#colorpicker-${id}` @@ -25,12 +27,10 @@ const colorpickerSelector = `#colorpicker-${id}` -