diff --git a/.build/generate-tokens.ts b/.build/generate-tokens.ts index 1ca60cbd2..98e11dc94 100644 --- a/.build/generate-tokens.ts +++ b/.build/generate-tokens.ts @@ -30,6 +30,7 @@ const TOKENS = [ { name: 'aspectRatios', type: 'AspectRatio', module: 'v', variable: '$aspect-ratios', kind: 'map', source: 'core/scss/_variables.scss' }, { name: 'breakpoints', type: 'Breakpoint', module: 's', variable: '$grid-breakpoints', kind: 'map', source: 'core/scss/_settings.scss' }, { name: 'breadcrumbVariants', type: 'BreadcrumbVariant', module: 'v', variable: '$breadcrumb-variants', kind: 'map', source: 'core/scss/_variables.scss' }, + { name: 'gradients', type: 'Gradient', module: 'v', variable: '$gradients', kind: 'map', source: 'core/scss/_variables.scss' }, { name: 'formValidationStates', type: 'FormValidationState', module: 'v', variable: '$form-validation-states', kind: 'map', source: 'core/scss/_variables.scss' }, { name: 'paymentProviders', type: 'PaymentProvider', module: 'v', variable: '$payment-providers', kind: 'list', source: 'core/scss/_variables.scss' }, { name: 'flagCountries', type: 'FlagCountry', module: 'v', variable: '$flag-countries', kind: 'list', source: 'core/scss/_variables.scss' }, diff --git a/.changeset/btn-outline-gradient.md b/.changeset/btn-outline-gradient.md new file mode 100644 index 000000000..9771f2c93 --- /dev/null +++ b/.changeset/btn-outline-gradient.md @@ -0,0 +1,7 @@ +--- +"@tabler/core": minor +"@tabler/preview": minor +"@tabler/docs": minor +--- + +Added `.btn-outline-gradient` buttons with a gradient border, using the named gradients shared with `.card-gradient`. diff --git a/core/scss/_variables.scss b/core/scss/_variables.scss index caa133799..f32817f7c 100644 --- a/core/scss/_variables.scss +++ b/core/scss/_variables.scss @@ -1207,6 +1207,63 @@ $otp-slot-box-shadow: var(--shadow-input) !default; $otp-separator-color: var(--secondary) !default; // scss-docs-end otp-variables +// Gradients +// Multi-color presets shared by `.card-gradient-*` and `.btn-outline-gradient-*`. +// Each value is a comma-separated list of color stops. +// scss-docs-start gradients +$gradients: ( + 'rainbow': ( + #78c5d6, + #459ba8, + #79c267, + #c5d647, + #f5d63d, + #f08b33, + #e868a2, + #be61a5, + ), + 'sun': ( + #fd1d1d, + #fcb045, + ), + 'snow': ( + #333, + #e9ecef, + ), + 'ocean': ( + #1cb5e0, + #000851, + ), + 'mellow': ( + #f8ff00, + #3ad59f, + ), + 'disco': ( + #fc466b, + #3f5efb, + ), + 'psychedelic': ( + #fcc5e4, + #fda34b, + #ff7882, + #c8699e, + #7046aa, + #0c1db8, + #020f75, + ), + 'love': ( + #f235e6, + #bc0707, + ), + 'gold': ( + #9d4100, + #bf7122, + #f59f00, + #ffd700, + ), +) !default; +// scss-docs-end gradients + // Cards // scss-docs-start card-variables $card-spacer-y: 1.25rem !default; diff --git a/core/scss/tests/_buttons.test.scss b/core/scss/tests/_buttons.test.scss new file mode 100644 index 000000000..854fc2f6a --- /dev/null +++ b/core/scss/tests/_buttons.test.scss @@ -0,0 +1,23 @@ +// `.btn-outline-gradient-*` presets share one selector list with the base +// class, so a preset works without `.btn-outline-gradient` next to it. The +// stops come from the shared `$gradients` map, so a card and a button with the +// same name show the same gradient. +@use 'sass:list'; +@use 'sass:map'; +@use '../config' as *; +@use '../ui/buttons' as buttons; + +@include describe('btn-outline-gradient') { + @include it('shares the named gradient stops with .card-gradient') { + @include assert-equal(map.get($gradients, 'sun'), (#fd1d1d, #fcb045)); + @include assert-true(map.has-key($gradients, 'rainbow')); + } + + @include it('lists the base class and every color and gradient preset') { + $selectors: buttons.btn-gradient-selectors(); + @include assert-equal(list.nth($selectors, 1), '.btn-outline-gradient'); + @include assert-true(list.index($selectors, '.btn-outline-gradient-primary') != null); + @include assert-true(list.index($selectors, '.btn-outline-gradient-rainbow') != null); + @include assert-equal(list.length($selectors), 1 + list.length(map.keys($theme-colors)) + list.length(map.keys($gradients))); + } +} diff --git a/core/scss/ui/_buttons.scss b/core/scss/ui/_buttons.scss index c1f4bce13..698c5b441 100644 --- a/core/scss/ui/_buttons.scss +++ b/core/scss/ui/_buttons.scss @@ -175,6 +175,66 @@ $btn-colors: map.keys(map.merge($theme-colors, $social-colors)); --btn-box-shadow: none; } +// Gradient ring: a surface-colored layer clipped to the padding box sits on a +// gradient layer that fills the border box, so only the border shows the +// gradient — the same trick as `.card-gradient`. Presets share the `.btn-outline-gradient` +// base through one selector list, so a preset class works on its own. +$btn-gradient-names: list.join(map.keys($theme-colors), map.keys($gradients)); + +@function btn-gradient-selectors() { + $selectors: '.btn-outline-gradient'; + @each $name in $btn-gradient-names { + $selectors: list.append($selectors, '.btn-outline-gradient-#{$name}', $separator: comma); + } + @return $selectors; +} + +$btn-gradient-selectors: btn-gradient-selectors(); + +#{$btn-gradient-selectors} { + // scss-docs-start btn-gradient-css-vars + --btn-gradient: var(--primary), var(--primary); + --btn-gradient-direction: 135deg; + // scss-docs-end btn-gradient-css-vars + --btn-border-color: transparent; + --btn-hover-border-color: transparent; + --btn-active-border-color: transparent; + --btn-disabled-border-color: transparent; + --btn-box-shadow: none; + // The hover and active fills are translucent tints, so a solid surface layer + // sits under them — otherwise the gradient would bleed through on hover. + background: + linear-gradient(var(--btn-bg), var(--btn-bg)) padding-box, + linear-gradient(var(--bg-surface), var(--bg-surface)) padding-box, + linear-gradient(var(--btn-gradient-direction), var(--btn-gradient)) border-box; +} + +// `.btn:hover` only sets `background-color`, which sits under the two opaque +// image layers, so the hover and active fills go through the token instead. +// `:is()` keeps the state rules to one selector list each. +:is(#{$btn-gradient-selectors}):hover, +.btn-check + :is(#{$btn-gradient-selectors}):hover { + --btn-bg: var(--btn-hover-bg); +} + +:is(#{$btn-gradient-selectors}):active, +:is(#{$btn-gradient-selectors}).active, +.btn-check:checked + :is(#{$btn-gradient-selectors}) { + --btn-bg: var(--btn-active-bg); +} + +@each $color in map.keys($theme-colors) { + .btn-outline-gradient-#{$color} { + --btn-gradient: var(--#{$color}), var(--#{$color}-darken); + } +} + +@each $name, $stops in $gradients { + .btn-outline-gradient-#{$name} { + --btn-gradient: #{$stops}; + } +} + // // Button sizes // diff --git a/core/scss/ui/_cards.scss b/core/scss/ui/_cards.scss index 13eebfbed..6acb3ab4c 100644 --- a/core/scss/ui/_cards.scss +++ b/core/scss/ui/_cards.scss @@ -734,40 +734,10 @@ Card gradient } } -.card-gradient-rainbow { - --card-gradient: #78c5d6, #459ba8, #79c267, #c5d647, #f5d63d, #f08b33, #e868a2, #be61a5; -} - -.card-gradient-sun { - --card-gradient: #fd1d1d, #fcb045; -} - -.card-gradient-snow { - --card-gradient: #333, #e9ecef; -} - -.card-gradient-ocean { - --card-gradient: #1cb5e0, #000851; -} - -.card-gradient-mellow { - --card-gradient: #f8ff00, #3ad59f; -} - -.card-gradient-disco { - --card-gradient: #fc466b, #3f5efb; -} - -.card-gradient-psychedelic { - --card-gradient: #fcc5e4, #fda34b, #ff7882, #c8699e, #7046aa, #0c1db8, #020f75; -} - -.card-gradient-love { - --card-gradient: #f235e6, #bc0707; -} - -.card-gradient-gold { - --card-gradient: #9d4100, #bf7122, #f59f00, #ffd700; +@each $name, $stops in $gradients { + .card-gradient-#{$name} { + --card-gradient: #{$stops}; + } } .card-gradient-animated { diff --git a/docs/content/ui/components/button.mdx b/docs/content/ui/components/button.mdx index e81f68a72..2b4bf41ff 100644 --- a/docs/content/ui/components/button.mdx +++ b/docs/content/ui/components/button.mdx @@ -25,6 +25,8 @@ classnames: style: - class: btn-outline desc: Transparent fill with a colored border + - class: btn-outline-gradient + desc: Gradient ring on the border; btn-outline-gradient-{color} and the named gradients change the colors - class: btn-ghost desc: No fill and no border until hovered - class: btn-link @@ -187,6 +189,30 @@ The `.btn-outline-*` classes keep the color on the border and the text and leave +## Outline gradient buttons + +`.btn-outline-gradient` keeps the fill empty, like an outline button, but draws the border as a gradient. Add a theme color, such as `.btn-outline-gradient-purple`, or one of the named gradients shared with [card gradients](/ui/components/card-gradient). The fill still follows the theme, so the ring works in dark mode without extra classes. + + +
+ Default + Purple + Rainbow + Sun + Ocean + Disco +
+
+ +Set `--tblr-btn-gradient` on the button to use your own color stops, and `--tblr-btn-gradient-direction` to change the angle. + + +
+ Custom stops + Left to right +
+
+ ## Button sizes Add `.btn-sm`, `.btn-lg` or `.btn-xl` to change the size. A larger button draws the eye to the main action, and a smaller one fits into toolbars and table rows. diff --git a/docs/content/ui/components/card-gradient.mdx b/docs/content/ui/components/card-gradient.mdx index 9e082025c..638f3a689 100644 --- a/docs/content/ui/components/card-gradient.mdx +++ b/docs/content/ui/components/card-gradient.mdx @@ -29,7 +29,7 @@ classnames: --- import Example from '@components/Example.astro' -export const gradients = ['rainbow', 'sun', 'snow', 'ocean', 'mellow', 'disco', 'psychedelic', 'love', 'gold'] +import { gradients } from '@shared/lib/tokens' ## Default markup diff --git a/preview/pages/buttons.astro b/preview/pages/buttons.astro index d2ab01263..15bcd6f43 100644 --- a/preview/pages/buttons.astro +++ b/preview/pages/buttons.astro @@ -9,6 +9,7 @@ import CardBody from '@ui/CardBody.astro' import CardTitle from '@ui/CardTitle.astro' import CardSubtitle from '@ui/CardSubtitle.astro' import site from '@data/site.json' +import { gradients } from '@shared/lib/tokens' import DocsLink from '@ui/DocsLink.astro' type ColorEntry = [string, { title: string; icon?: string }] @@ -170,6 +171,33 @@ const sizes = ['sm', 'md', 'lg', 'xl'] as const +
+ + + Outline gradient + Only the border carries the gradient. Use a theme color or one of the named gradients shared with .card-gradient. + + + { + themeColors.slice(0, 6).map(([name, color]) => ( + + )) + } + + + { + gradients.map((name) => ( + + )) + } + + + +
diff --git a/shared/components/navbar/NavbarSide.astro b/shared/components/navbar/NavbarSide.astro index be69c2431..0e015d85a 100644 --- a/shared/components/navbar/NavbarSide.astro +++ b/shared/components/navbar/NavbarSide.astro @@ -40,18 +40,19 @@ const { } = Astro.props // The All Package link is an ad for tabler.io, so it only renders on the hosted demo -// (Vercel deploys), never in local builds or the packaged template (VERCEL_ENV is unset there). -const isVercelDeploy = !!process.env.VERCEL_ENV +// (Vercel deploys) and on the local dev server, never in a local build or the +// packaged template (VERCEL_ENV is unset and DEV is false there). +const showAllPackage = !!process.env.VERCEL_ENV || import.meta.env.DEV const allPackageUrl = 'https://tabler.io/pricing?ref=tabler-preview-navbar#bundle' ---
{ - !condensed && isVercelDeploy && ( + !condensed && showAllPackage && (
-
) diff --git a/shared/lib/tokens.ts b/shared/lib/tokens.ts index d1d714247..23a5b2e20 100644 --- a/shared/lib/tokens.ts +++ b/shared/lib/tokens.ts @@ -33,6 +33,10 @@ export type Breakpoint = (typeof breakpoints)[number] export const breadcrumbVariants = ['dots', 'arrows', 'bullets'] as const export type BreadcrumbVariant = (typeof breadcrumbVariants)[number] +/** Keys of `$gradients` (core/scss/_variables.scss). */ +export const gradients = ['rainbow', 'sun', 'snow', 'ocean', 'mellow', 'disco', 'psychedelic', 'love', 'gold'] as const +export type Gradient = (typeof gradients)[number] + /** Keys of `$form-validation-states` (core/scss/_variables.scss). */ export const formValidationStates = ['valid', 'invalid'] as const export type FormValidationState = (typeof formValidationStates)[number]