mirror of
https://github.com/XRPLF/xrpl-dev-portal.git
synced 2026-10-10 13:57:58 +00:00
513 lines
13 KiB
SCSS
513 lines
13 KiB
SCSS
// BDS Button Component Styles
|
|
// Brand Design System - Scalable button component
|
|
//
|
|
// Naming Convention: BEM with 'bds' namespace
|
|
// .bds-btn - Base button (shared layout, typography, icon animation)
|
|
// .bds-btn--primary - Primary variant (solid background)
|
|
// .bds-btn--secondary - Secondary variant (outline style)
|
|
// .bds-btn--green - Green color theme (default)
|
|
// .bds-btn--black - Black color theme
|
|
// .bds-btn--tertiary - Tertiary variant (text-only style)
|
|
// .bds-btn__label - Label element
|
|
// .bds-btn__icon - Icon element (inherits color via currentColor)
|
|
// .bds-btn--disabled - Disabled state modifier
|
|
// .bds-btn--no-icon - No icon modifier
|
|
//
|
|
// Colors live on CSS custom properties (--bds-btn-fg, --bds-btn-fill, …).
|
|
// Variants never name a color; the palette table assigns them once.
|
|
// Dark mode remaps green and black onto the same palette.
|
|
//
|
|
// Note: This file is imported within xrpl.scss after Bootstrap and project
|
|
// variables are loaded, so $grid-breakpoints, colors, and mixins are available.
|
|
|
|
// Geometry/variants/mods stay layered so padding mods win without !important.
|
|
// Palette + paint are unlayered: custom properties in a layer can lose to
|
|
// unlayered `a { color }` (invalid var → white Bootstrap link color + underline).
|
|
@layer bds-btn.geometry, bds-btn.variants, bds-btn.mods;
|
|
|
|
// =============================================================================
|
|
// Design tokens
|
|
// =============================================================================
|
|
|
|
$bds-btn-neutral-black: #141414;
|
|
$bds-btn-neutral-black-15: rgba(20, 20, 20, 0.15);
|
|
$bds-btn-black-pressed: #434343;
|
|
$bds-btn-primary-black-bg-hover: #434343;
|
|
|
|
$bds-btn-border-radius: 100px;
|
|
$bds-btn-focus-border-width: 2px;
|
|
$bds-btn-pad-y: 8px;
|
|
$bds-btn-pad-x: 20px;
|
|
$bds-btn-gap: 16px;
|
|
$bds-btn-hover-shift: 6px;
|
|
$bds-btn-pad-x-sm: 16px; // $bds-btn-pad-x - 4px
|
|
$bds-btn-hover-shift-sm: 5px;
|
|
|
|
$bds-btn-transition-duration: 150ms;
|
|
$bds-btn-transition-timing: cubic-bezier(0.98, 0.12, 0.12, 0.98);
|
|
|
|
// =============================================================================
|
|
// Palette maps — assign once; variants consume via custom properties
|
|
// =============================================================================
|
|
|
|
@mixin bds-btn-vars($map) {
|
|
$defaults: (
|
|
fill: transparent,
|
|
fill-hover: transparent,
|
|
fill-disabled: transparent,
|
|
stroke: transparent,
|
|
stroke-hover: transparent,
|
|
stroke-active: transparent,
|
|
stroke-disabled: transparent,
|
|
);
|
|
|
|
@each $name, $value in map-merge($defaults, $map) {
|
|
--bds-btn-#{$name}: #{$value};
|
|
}
|
|
}
|
|
|
|
$bds-btn-primary-green: (
|
|
fg: $bds-btn-neutral-black,
|
|
fg-hover: $bds-btn-neutral-black,
|
|
fg-active: $bds-btn-neutral-black,
|
|
fg-disabled: $gray-500,
|
|
fill: $green-300,
|
|
fill-hover: $green-200,
|
|
fill-disabled: $gray-200,
|
|
);
|
|
|
|
$bds-btn-primary-black: (
|
|
fg: $white,
|
|
fg-hover: $white,
|
|
fg-active: $white,
|
|
fg-disabled: $gray-500,
|
|
fill: $bds-btn-neutral-black,
|
|
fill-hover: $bds-btn-primary-black-bg-hover,
|
|
fill-disabled: $gray-200,
|
|
);
|
|
|
|
$bds-btn-primary-dark: map-merge(
|
|
$bds-btn-primary-green,
|
|
(
|
|
fg-disabled: $gray-300,
|
|
fill-disabled: $gray-500,
|
|
)
|
|
);
|
|
|
|
$bds-btn-secondary-green: (
|
|
fg: $green-400,
|
|
fg-hover: $green-500,
|
|
fg-active: $green-400,
|
|
fg-disabled: $gray-400,
|
|
fill-hover: $green-100,
|
|
stroke: $green-400,
|
|
stroke-hover: $green-500,
|
|
stroke-active: $green-400,
|
|
stroke-disabled: $gray-400,
|
|
);
|
|
|
|
$bds-btn-secondary-black: (
|
|
fg: $bds-btn-neutral-black,
|
|
fg-hover: $bds-btn-neutral-black,
|
|
fg-active: $bds-btn-black-pressed,
|
|
fg-disabled: $gray-500,
|
|
fill-hover: $bds-btn-neutral-black-15,
|
|
stroke: $bds-btn-neutral-black,
|
|
stroke-hover: $bds-btn-neutral-black,
|
|
stroke-active: $bds-btn-black-pressed,
|
|
stroke-disabled: $gray-400,
|
|
);
|
|
|
|
$bds-btn-secondary-dark: (
|
|
fg: $green-300,
|
|
fg-hover: $green-200,
|
|
fg-active: $green-300,
|
|
fg-disabled: $gray-400,
|
|
fill-hover: $green-500,
|
|
stroke: $green-300,
|
|
stroke-hover: $green-200,
|
|
stroke-active: $green-300,
|
|
stroke-disabled: $gray-400,
|
|
);
|
|
|
|
$bds-btn-tertiary-green: (
|
|
fg: $green-400,
|
|
fg-hover: $green-500,
|
|
fg-active: $green-400,
|
|
fg-disabled: $gray-400,
|
|
);
|
|
|
|
$bds-btn-tertiary-black: (
|
|
fg: $bds-btn-neutral-black,
|
|
fg-hover: $bds-btn-neutral-black,
|
|
fg-active: $bds-btn-black-pressed,
|
|
fg-disabled: $gray-500,
|
|
);
|
|
|
|
$bds-btn-tertiary-dark: (
|
|
fg: $green-300,
|
|
fg-hover: $green-200,
|
|
fg-active: $green-300,
|
|
fg-disabled: $gray-400,
|
|
);
|
|
|
|
// =============================================================================
|
|
// Geometry mixin
|
|
// pad-x / pad-y are the border-box intent; border width is subtracted.
|
|
// On hover, $shift moves from padding-inline-end into gap (chevron travel).
|
|
// Primary/secondary rest end-padding is 1px tighter than start (optical).
|
|
// Secondary focus drops the 2px border and adds that width back to padding.
|
|
// =============================================================================
|
|
|
|
@mixin bds-btn-box-at($pad-y, $pad-x, $border, $end-inset, $shift) {
|
|
$y: $pad-y - $border;
|
|
$start: $pad-x - $border;
|
|
$end: $start - $end-inset;
|
|
|
|
padding: $y $end $y $start;
|
|
gap: $bds-btn-gap;
|
|
|
|
&.bds-btn--no-icon {
|
|
padding: $y $start;
|
|
}
|
|
|
|
&:hover:not(:disabled):not(.bds-btn--disabled):not(.bds-btn--no-icon),
|
|
&:focus-visible:not(:disabled):not(.bds-btn--disabled):not(
|
|
.bds-btn--no-icon
|
|
) {
|
|
padding: $y ($end - $shift) $y $start;
|
|
gap: $bds-btn-gap + $shift;
|
|
}
|
|
|
|
@if $border > 0 {
|
|
&:focus-visible:not(:disabled):not(.bds-btn--disabled):not(
|
|
.bds-btn--no-icon
|
|
) {
|
|
border: none;
|
|
padding: $y ($end - $shift + $border) $y ($start + $border);
|
|
gap: $bds-btn-gap + $shift;
|
|
}
|
|
|
|
&:focus-visible:not(:disabled):not(.bds-btn--disabled).bds-btn--no-icon {
|
|
border: none;
|
|
padding: $y ($start + $border);
|
|
}
|
|
}
|
|
}
|
|
|
|
@mixin bds-btn-box($border: 0px, $end-inset: 1px) {
|
|
@include bds-btn-box-at(
|
|
$bds-btn-pad-y,
|
|
$bds-btn-pad-x,
|
|
$border,
|
|
$end-inset,
|
|
$bds-btn-hover-shift
|
|
);
|
|
|
|
@include media-breakpoint-down(xl) {
|
|
@include bds-btn-box-at(
|
|
$bds-btn-pad-y,
|
|
$bds-btn-pad-x-sm,
|
|
$border,
|
|
$end-inset,
|
|
$bds-btn-hover-shift-sm
|
|
);
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Theme: palette table (unlayered so vars always apply)
|
|
// Dark mode: green and black share one palette (black is remapped to green).
|
|
// force-color restores the light black palette and keeps the dark focus ring.
|
|
// =============================================================================
|
|
|
|
.bds-btn {
|
|
--bds-btn-focus: #{$bds-btn-neutral-black};
|
|
@include bds-btn-vars($bds-btn-primary-green);
|
|
}
|
|
|
|
.bds-btn--primary.bds-btn--green {
|
|
@include bds-btn-vars($bds-btn-primary-green);
|
|
}
|
|
|
|
.bds-btn--primary.bds-btn--black {
|
|
@include bds-btn-vars($bds-btn-primary-black);
|
|
}
|
|
|
|
.bds-btn--secondary.bds-btn--green {
|
|
@include bds-btn-vars($bds-btn-secondary-green);
|
|
}
|
|
|
|
.bds-btn--secondary.bds-btn--black {
|
|
@include bds-btn-vars($bds-btn-secondary-black);
|
|
}
|
|
|
|
.bds-btn--tertiary.bds-btn--green {
|
|
@include bds-btn-vars($bds-btn-tertiary-green);
|
|
}
|
|
|
|
.bds-btn--tertiary.bds-btn--black {
|
|
@include bds-btn-vars($bds-btn-tertiary-black);
|
|
}
|
|
|
|
@include bds-theme-mode(dark, $at-root: true) {
|
|
.bds-btn {
|
|
--bds-btn-focus: #{$white};
|
|
}
|
|
|
|
.bds-btn--primary {
|
|
@include bds-btn-vars($bds-btn-primary-dark);
|
|
}
|
|
|
|
.bds-btn--secondary {
|
|
@include bds-btn-vars($bds-btn-secondary-dark);
|
|
}
|
|
|
|
.bds-btn--tertiary {
|
|
@include bds-btn-vars($bds-btn-tertiary-dark);
|
|
}
|
|
|
|
.bds-btn--force-color.bds-btn--black {
|
|
&.bds-btn--primary {
|
|
@include bds-btn-vars($bds-btn-primary-black);
|
|
}
|
|
|
|
&.bds-btn--secondary {
|
|
@include bds-btn-vars($bds-btn-secondary-black);
|
|
}
|
|
|
|
&.bds-btn--tertiary {
|
|
@include bds-btn-vars($bds-btn-tertiary-black);
|
|
}
|
|
|
|
--bds-btn-focus: #{$white};
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Geometry: layout, type, icon motion, padding/gap, state variable swaps
|
|
// =============================================================================
|
|
|
|
@layer bds-btn.geometry {
|
|
.bds-btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
max-height: 40px;
|
|
@include type(label-r);
|
|
white-space: nowrap;
|
|
margin-bottom: 0;
|
|
border: none;
|
|
border-radius: $bds-btn-border-radius;
|
|
cursor: pointer;
|
|
position: relative;
|
|
overflow: hidden;
|
|
z-index: 1;
|
|
transition:
|
|
color $bds-btn-transition-duration $bds-btn-transition-timing,
|
|
border-color $bds-btn-transition-duration $bds-btn-transition-timing,
|
|
padding $bds-btn-transition-duration $bds-btn-transition-timing,
|
|
gap $bds-btn-transition-duration $bds-btn-transition-timing,
|
|
transform $bds-btn-transition-duration $bds-btn-transition-timing;
|
|
|
|
&::before {
|
|
content: '';
|
|
position: absolute;
|
|
inset: 0;
|
|
z-index: -1;
|
|
transform: scaleY(0);
|
|
transform-origin: bottom center;
|
|
transition: transform $bds-btn-transition-duration
|
|
$bds-btn-transition-timing;
|
|
}
|
|
|
|
&:hover:not(:disabled):not(.bds-btn--disabled),
|
|
&:focus-visible:not(:disabled):not(.bds-btn--disabled) {
|
|
--bds-btn-fg: var(--bds-btn-fg-hover);
|
|
--bds-btn-stroke: var(--bds-btn-stroke-hover);
|
|
|
|
&::before {
|
|
transform: scaleY(1);
|
|
}
|
|
|
|
.bds-btn__icon-line {
|
|
transform: scaleX(0);
|
|
}
|
|
}
|
|
|
|
&:active:not(:disabled):not(.bds-btn--disabled) {
|
|
--bds-btn-fg: var(--bds-btn-fg-active);
|
|
--bds-btn-stroke: var(--bds-btn-stroke-active);
|
|
|
|
&::before {
|
|
transform: scaleY(0);
|
|
}
|
|
}
|
|
|
|
&:disabled,
|
|
&.bds-btn--disabled {
|
|
--bds-btn-fg: var(--bds-btn-fg-disabled);
|
|
--bds-btn-fill: var(--bds-btn-fill-disabled);
|
|
--bds-btn-stroke: var(--bds-btn-stroke-disabled);
|
|
cursor: not-allowed;
|
|
pointer-events: none;
|
|
|
|
&::before {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
&:focus-visible:not(:disabled):not(.bds-btn--disabled) {
|
|
outline: $bds-btn-focus-border-width solid var(--bds-btn-focus);
|
|
outline-offset: 2px;
|
|
}
|
|
}
|
|
|
|
.bds-btn__label {
|
|
flex-shrink: 0;
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
.bds-btn__icon {
|
|
width: 15px;
|
|
height: 14px;
|
|
flex-shrink: 0;
|
|
transition: opacity $bds-btn-transition-duration $bds-btn-transition-timing;
|
|
color: currentColor;
|
|
overflow: visible;
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
.bds-btn__icon-line {
|
|
transform-box: fill-box;
|
|
transform-origin: right center;
|
|
transform: scaleX(1);
|
|
transition: transform $bds-btn-transition-duration
|
|
$bds-btn-transition-timing;
|
|
}
|
|
|
|
.bds-btn__icon-chevron {
|
|
transition: transform $bds-btn-transition-duration
|
|
$bds-btn-transition-timing;
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Variants: structure only (no named colors)
|
|
// =============================================================================
|
|
|
|
@layer bds-btn.variants {
|
|
.bds-btn--primary {
|
|
@include bds-btn-box;
|
|
}
|
|
|
|
.bds-btn--secondary {
|
|
border: 2px solid var(--bds-btn-stroke);
|
|
@include bds-btn-box($border: 2px);
|
|
}
|
|
|
|
.bds-btn--tertiary {
|
|
@include type(body-r);
|
|
margin-bottom: 0;
|
|
text-decoration: none;
|
|
@include bds-btn-box($end-inset: 0px);
|
|
|
|
&::before {
|
|
display: none;
|
|
}
|
|
|
|
&:hover:not(:disabled):not(.bds-btn--disabled),
|
|
&:focus-visible:not(:disabled):not(.bds-btn--disabled) {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
&:focus-visible:not(:disabled):not(.bds-btn--disabled) {
|
|
border-radius: 0;
|
|
outline-offset: -2px;
|
|
}
|
|
|
|
&:disabled,
|
|
&.bds-btn--disabled {
|
|
text-decoration: none;
|
|
}
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Mods: later layer wins over geometry without !important
|
|
// =============================================================================
|
|
|
|
@layer bds-btn.mods {
|
|
.bds-btn--no-padding {
|
|
padding: 0;
|
|
justify-content: flex-start;
|
|
|
|
&:hover,
|
|
&:focus-visible,
|
|
&:active {
|
|
padding: 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Unlayered paint — beats Bootstrap / Redocly / html.{light,dark} a rules.
|
|
// Double-class + theme wrapper matches the pre-refactor specificity.
|
|
// Hover fill lives on ::before, not the face fill. Icons follow currentColor.
|
|
// =============================================================================
|
|
|
|
@mixin bds-btn-anchor-paint {
|
|
color: var(--bds-btn-fg);
|
|
background-color: var(--bds-btn-fill);
|
|
border-color: var(--bds-btn-stroke);
|
|
text-decoration: none;
|
|
|
|
&:hover,
|
|
&:focus,
|
|
&:focus-visible,
|
|
&:active,
|
|
&:visited,
|
|
&:link {
|
|
color: var(--bds-btn-fg);
|
|
background-color: var(--bds-btn-fill);
|
|
border-color: var(--bds-btn-stroke);
|
|
text-decoration: none;
|
|
}
|
|
|
|
&.bds-btn--tertiary:hover:not(:disabled):not(.bds-btn--disabled),
|
|
&.bds-btn--tertiary:focus:not(:disabled):not(.bds-btn--disabled),
|
|
&.bds-btn--tertiary:focus-visible:not(:disabled):not(.bds-btn--disabled) {
|
|
text-decoration: underline;
|
|
}
|
|
}
|
|
|
|
.bds-btn {
|
|
color: var(--bds-btn-fg);
|
|
background-color: var(--bds-btn-fill);
|
|
border-color: var(--bds-btn-stroke);
|
|
text-decoration: none;
|
|
|
|
&::before {
|
|
background-color: var(--bds-btn-fill-hover);
|
|
}
|
|
|
|
.bds-btn__icon,
|
|
.bds-btn__icon-line,
|
|
.bds-btn__icon-chevron {
|
|
color: inherit;
|
|
stroke: currentColor;
|
|
}
|
|
}
|
|
|
|
a.bds-btn.bds-btn {
|
|
@include bds-theme-mode(light) {
|
|
@include bds-btn-anchor-paint;
|
|
}
|
|
|
|
@include bds-theme-mode(dark) {
|
|
@include bds-btn-anchor-paint;
|
|
}
|
|
}
|