Prechádzať zdrojové kódy

Enhance button and hover animations (#2401)

Paweł Kuna 2 mesiacov pred
rodič
commit
9951fe9b1d

+ 5 - 0
.changeset/forty-pigs-sparkle.md

@@ -0,0 +1,5 @@
+---
+"@tabler/core": patch
+---
+
+Enhance button and hover animations

+ 1 - 0
core/scss/_core.scss

@@ -76,6 +76,7 @@
 @import "utils/opacity";
 @import "utils/shadow";
 @import "utils/text";
+@import "utils/hover";
 
 @import "debug";
 

+ 77 - 47
core/scss/_variables.scss

@@ -26,7 +26,7 @@ $font-icons: () !default;
 $font-family-sans-serif: unquote("#{if($font-local, "#{$font-local}, ", ' ')}#{if($font-google, "#{$font-google}, ", ' ')}") 'Inter Var', Inter, -apple-system, BlinkMacSystemFont, San Francisco, Segoe UI, Roboto, Helvetica Neue, sans-serif !default;
 $font-family-monospace: unquote("#{if($font-google-monospaced, "#{$font-google-monospaced}, ", '')}") Monaco, Consolas, Liberation Mono, Courier New, monospace !default;
 $font-family-serif: "Georgia", "Times New Roman", times, serif !default;
-$font-family-comic: "Comic Sans MS", "Comic Sans", 'Chalkboard SE', 'Comic Neue', sans-serif, cursive !default;
+$font-family-comic: "Comic Sans MS", "Comic Sans", "Chalkboard SE", "Comic Neue", sans-serif, cursive !default;
 
 //Icons
 $icon-stroke-width: 1.5 !default;
@@ -52,9 +52,9 @@ $line-height-700: 3rem !default;
 
 $font-size-base: 0.875rem !default;
 
-$spacing-wide: .04em !default;
+$spacing-wide: 0.04em !default;
 $spacing-normal: 0 !default;
-$spacing-tight: -.04em !default;
+$spacing-tight: -0.04em !default;
 
 $body-letter-spacing: 0 !default;
 
@@ -68,12 +68,12 @@ $headings-font-weight: var(--#{$prefix}font-weight-bold) !default;
 $headings-margin-bottom: var(--#{$prefix}spacer) !default;
 
 $font-weights: (
-  'light': $font-weight-light,
-  'normal': $font-weight-normal,
-  'medium': $font-weight-medium,
-  'bold': $font-weight-bold,
-  'black': $font-weight-black,
-  'headings': $headings-font-weight,
+  "light": $font-weight-light,
+  "normal": $font-weight-normal,
+  "medium": $font-weight-medium,
+  "bold": $font-weight-bold,
+  "black": $font-weight-black,
+  "headings": $headings-font-weight,
 ) !default;
 
 $line-height-base: divide(1.25rem, $font-size-base) !default;
@@ -99,8 +99,8 @@ $h5-line-height: 1rem !default;
 $h6-font-size: 0.625rem !default;
 $h6-line-height: 1rem !default;
 
-$font-size-reative-xs: .71428571em !default;
-$font-size-reative-sm: .85714285em !default;
+$font-size-reative-xs: 0.71428571em !default;
+$font-size-reative-sm: 0.85714285em !default;
 $font-size-reative-md: 1em !default;
 
 $font-sizes: (
@@ -146,7 +146,7 @@ $border-light-opacity: 0.08 !default;
 $border-dark-opacity: 0.24 !default;
 $border-active-opacity: 0.58 !default;
 
-$gray-50:  #f9fafb !default;
+$gray-50: #f9fafb !default;
 $gray-100: #f3f4f6 !default;
 $gray-200: #e5e7eb !default;
 $gray-300: #d1d5db !default;
@@ -210,7 +210,7 @@ $active-border-color: var(--#{$prefix}primary) !default;
 $hover-bg: rgba(var(--#{$prefix}secondary-rgb), 0.08) !default;
 
 $disabled-bg: var(--#{$prefix}bg-surface-secondary) !default;
-$disabled-color: color-transparent(var(--#{$prefix}body-color), .4) !default;
+$disabled-color: color-transparent(var(--#{$prefix}body-color), 0.4) !default;
 
 $primary: $blue !default;
 $secondary: $text-secondary !default;
@@ -341,7 +341,7 @@ $kbd-border-radius: var(--#{$prefix}border-radius) !default;
 
 // Avatars
 $avatar-size: 2.5rem !default;
-$avatar-status-size: .75rem !default;
+$avatar-status-size: 0.75rem !default;
 $avatar-font-size: 1rem !default;
 $avatar-icon-size: 1.5rem !default;
 $avatar-brand-size: 1.25rem !default;
@@ -349,52 +349,52 @@ $avatar-bg: var(--#{$prefix}bg-surface-secondary) !default;
 $avatar-sizes: (
   "xxs": (
     size: 1rem,
-    font-size: .5rem,
-    icon-size: .5rem,
-    status-size: .25rem,
-    brand-size: .5rem
+    font-size: 0.5rem,
+    icon-size: 0.5rem,
+    status-size: 0.25rem,
+    brand-size: 0.5rem,
   ),
   "xs": (
     size: 1.25rem,
     font-size: $h6-font-size,
-    icon-size: .75rem,
-    status-size: .375rem,
-    brand-size: .75rem
+    icon-size: 0.75rem,
+    status-size: 0.375rem,
+    brand-size: 0.75rem,
   ),
   "sm": (
     size: 2rem,
     font-size: $h5-font-size,
     icon-size: 1.5rem,
-    status-size: .5rem,
-    brand-size: 1rem
+    status-size: 0.5rem,
+    brand-size: 1rem,
   ),
   "md": (
     size: 2.5rem,
     font-size: $h4-font-size,
     icon-size: 1.5rem,
-    status-size: .75rem,
-    brand-size: 1.25rem
+    status-size: 0.75rem,
+    brand-size: 1.25rem,
   ),
   "lg": (
     size: 3rem,
     font-size: $h2-font-size,
     icon-size: 2rem,
-    status-size: .75rem,
-    brand-size: 1.25rem
+    status-size: 0.75rem,
+    brand-size: 1.25rem,
   ),
   "xl": (
     size: 5rem,
     font-size: 2rem,
     icon-size: 3rem,
     status-size: 1rem,
-    brand-size: 1.25rem
+    brand-size: 1.25rem,
   ),
   "2xl": (
     size: 7rem,
     font-size: 3rem,
     icon-size: 5rem,
     status-size: 1rem,
-    brand-size: 2rem
+    brand-size: 2rem,
   ),
 ) !default;
 $avatar-border-radius: var(--#{$prefix}border-radius) !default;
@@ -586,41 +586,71 @@ $badge-color: var(--#{$prefix}secondary) !default;
 $badge-bg-color: var(--#{$prefix}bg-surface-secondary) !default;
 
 // Buttons
-$input-btn-line-height: $line-height-base !default;
-$input-btn-font-size: $font-size-base !default;
-$input-btn-font-family: var(--#{$prefix}body-font-face) !default;
-$input-btn-padding-y: 0.5rem - 0.0625rem !default;
-$input-btn-icon-size: $icon-size !default;
+$input-btn-border-width: var(--#{$prefix}border-width) !default;
+$input-btn-font-family: var(--#{$prefix}body-font-family) !default;
+$input-btn-focus-width: 0.25rem !default;
 
+$input-btn-padding-y-sm: 0.3125rem !default;
+$input-btn-padding-x-sm: 0.5rem !default;
 $input-btn-font-size-sm: $h5-font-size !default;
-$input-btn-padding-x-sm: 0.25rem !default;
-$input-btn-padding-y-sm: 0.125rem - 0.0625rem !default;
 $input-btn-line-height-sm: divide(1rem, $input-btn-font-size-sm) !default;
 $input-btn-icon-size-sm: 1rem !default;
 
-$input-btn-font-size-lg: $h2-font-size !default;
+$input-btn-padding-y: 0.5625rem !default;
+$input-btn-padding-x: 1rem !default;
+$input-btn-line-height: 1.25rem !default;
+$input-btn-font-size: $font-size-base !default;
+$input-btn-icon-size: $icon-size !default;
+
+$input-btn-padding-y-lg: 0.6875rem !default;
 $input-btn-padding-x-lg: 1.5rem !default;
-$input-btn-padding-y-lg: 0.75rem - 0.0625rem !default;
-$input-btn-line-height-lg: divide(2rem, $input-btn-font-size-lg) !default;
-$input-btn-icon-size-lg: 2rem !default;
+$input-btn-line-height-lg: 1.5rem !default;
+$input-btn-font-size-lg: $h3-font-size !default;
+$input-btn-icon-size-lg: 1.5rem !default;
+
+$input-btn-padding-y-xl: 0.6875rem !default;
+$input-btn-padding-x-xl: 2rem !default;
+$input-btn-font-size-xl: $h1-font-size !default;
+$input-btn-line-height-xl: divide(2rem, $input-btn-font-size-lg) !default;
+$input-btn-icon-size-xl: 2rem !default;
 
-$input-btn-focus-width: 0.25rem !default;
 
 // Inputs
 $input-height: null !default;
 $input-height-sm: null !default;
 $input-height-lg: null !default;
 $input-border-radius: var(--#{$prefix}border-radius) !default;
+$input-padding-y: $input-btn-padding-y !default;
+$input-padding-x: $input-btn-padding-x !default;
 $input-color: var(--#{$prefix}body-color) !default;
 $input-focus-color: var(--#{$prefix}body-color) !default;
 $input-box-shadow: var(--#{$prefix}shadow-input) !default;
 
+$input-border-width: $input-btn-border-width !default;
+$input-line-height: $input-btn-line-height !default;
+$input-height-border: calc(#{$input-border-width} * 2) !default;
+
+$input-padding-y-sm: $input-btn-padding-y-sm !default;
+$input-padding-x-sm: $input-btn-padding-x-sm !default;
+$input-font-size-sm: $input-btn-font-size-sm !default;
+
+$input-padding-y-lg: $input-btn-padding-y-lg !default;
+$input-padding-x-lg: $input-btn-padding-x-lg !default;
+$input-font-size-lg: $input-btn-font-size-lg !default;
+
+$input-height-inner: add($input-line-height, calc($input-padding-y * 2)) !default;
+$input-height-inner-half: add($input-line-height, $input-padding-y) !default;
+$input-height-inner-quarter: add($input-line-height, calc($input-padding-y * 0.5)) !default;
+
+$input-height: add($input-line-height, add(calc($input-padding-y * 2), $input-height-border, false)) !default;
+$input-height-sm: add($input-line-height, add(calc($input-padding-y-sm * 2), $input-height-border, false)) !default;
+$input-height-lg: add($input-line-height, add(calc($input-padding-y-lg * 2), $input-height-border, false)) !default;
+
 // Buttons
 $btn-border-radius-sm: var(--#{$prefix}border-radius-sm) !default;
 $btn-border-radius-lg: var(--#{$prefix}border-radius-lg) !default;
 
-$btn-disabled-opacity: .4 !default;
-$btn-padding-x: 1rem !default;
+$btn-disabled-opacity: 0.4 !default;
 $btn-font-weight: var(--#{$prefix}font-weight-medium) !default;
 $btn-border-color: var(--#{$prefix}border-color) !default;
 $btn-border-radius: var(--#{$prefix}border-radius) !default;
@@ -811,7 +841,7 @@ $navbar-toggler-focus-width: 0 !default;
 $navbar-overlap-height: 9rem !default;
 
 $navbar-nav-link-padding-x: $nav-link-padding-x !default;
-$navbar-nav-link-hover-bg: rgba(0, 0, 0, .04) !default;
+$navbar-nav-link-hover-bg: rgba(0, 0, 0, 0.04) !default;
 
 $navbar-active-border-color: var(--#{$prefix}primary) !default;
 
@@ -828,8 +858,8 @@ $popover-bg: var(--#{$prefix}bg-surface) !default;
 $popover-header-bg: transparent !default;
 $popover-border-color: var(--#{$prefix}border-color) !default;
 $popover-body-color: inherit !default;
-$popover-body-padding-x: .5rem !default;
-$popover-body-padding-y: .5rem !default;
+$popover-body-padding-x: 0.5rem !default;
+$popover-body-padding-y: 0.5rem !default;
 $popover-box-shadow: var(--#{$prefix}shadow-lg) !default;
 
 // Footer
@@ -896,7 +926,7 @@ $table-sort-desc-bg-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org
 // Toasts
 $toast-border-color: var(--#{$prefix}border-color) !default;
 $toast-header-color: var(--#{$prefix}gray-500) !default;
-$toast-background-color: var(--#{$prefix}bg-surface) !default; 
+$toast-background-color: var(--#{$prefix}bg-surface) !default;
 
 // Tracking
 $tracking-height: 1.5rem !default;

+ 52 - 17
core/scss/layout/_animations.scss

@@ -1,39 +1,50 @@
 @keyframes pulse {
-  from {
-    opacity: 1;
-    transform: scale3d(.8, .8, .8)
+  0% {
+    transform: scale(1);
   }
 
-  50% {
-    transform: scale3d(1, 1, 1);
-    opacity: 1
+  14% {
+    transform: scale(1.25);
   }
 
-  to {
-    opacity: 1;
-    transform: scale3d(.8, .8, .8)
+  28% {
+    transform: scale(1);
+  }
+
+  42% {
+    transform: scale(1.25);
+  }
+
+  70% {
+    transform: scale(1);
   }
 }
 
 @keyframes tada {
   0% {
-    transform: scale3d(1, 1, 1)
+    transform: scale3d(1, 1, 1);
   }
 
-  10%, 5% {
-    transform: scale3d(.9, .9, .9) rotate3d(0, 0, 1, -5deg)
+  10%,
+  5% {
+    transform: scale3d(0.9, 0.9, 0.9) rotate3d(0, 0, 1, -5deg);
   }
 
-  15%, 25%, 35%, 45% {
-    transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 5deg)
+  15%,
+  25%,
+  35%,
+  45% {
+    transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 5deg);
   }
 
-  20%, 30%, 40% {
-    transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -5deg)
+  20%,
+  30%,
+  40% {
+    transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -5deg);
   }
 
   50% {
-    transform: scale3d(1, 1, 1)
+    transform: scale3d(1, 1, 1);
   }
 }
 
@@ -61,3 +72,27 @@
   }
 }
 
+@keyframes shake {
+  0% {
+    transform: scaleX(1);
+  }
+
+  20% {
+    transform: scale3d(0.9, 0.9, 0.9) rotate(-5deg);
+  }
+
+  50%,
+  70%,
+  90% {
+    transform: scale3d(1.25, 1.25, 1.25) rotate(5deg);
+  }
+
+  60%,
+  80% {
+    transform: scale3d(1.25, 1.25, 1.25) rotate(-5deg);
+  }
+
+  to {
+    transform: scaleX(1);
+  }
+}

+ 90 - 19
core/scss/ui/_buttons.scss

@@ -5,12 +5,12 @@
 //
 .btn {
   --#{$prefix}btn-icon-size: #{$input-btn-icon-size};
+  --#{$prefix}btn-icon-color: inherit;
   --#{$prefix}btn-bg: var(--#{$prefix}bg-surface);
   --#{$prefix}btn-color: var(--#{$prefix}body-color);
   --#{$prefix}btn-border-color: #{$btn-border-color};
   --#{$prefix}btn-hover-bg: var(--#{$prefix}btn-bg);
   --#{$prefix}btn-hover-border-color: var(--#{$prefix}border-active-color);
-  --#{$prefix}btn-box-shadow: var(--#{$prefix}shadow-input);
   --#{$prefix}btn-active-color: #{$active-color};
   --#{$prefix}btn-active-bg: #{$active-bg};
   --#{$prefix}btn-active-border-color: #{$active-border-color};
@@ -21,6 +21,9 @@
   white-space: nowrap;
   box-shadow: var(--#{$prefix}btn-box-shadow);
   position: relative;
+  min-width: calc((var(--#{$prefix}btn-line-height) * 1) + (var(--#{$prefix}btn-padding-y) * 2) + (var(--#{$prefix}btn-border-width) * 2));
+  min-height: calc((var(--#{$prefix}btn-line-height) * 1) + (var(--#{$prefix}btn-padding-y) * 2) + (var(--#{$prefix}btn-border-width) * 2));
+  
 
   .icon {
     width: var(--#{$prefix}btn-icon-size);
@@ -29,7 +32,7 @@
     font-size: var(--#{$prefix}btn-icon-size);
     margin: 0 calc(var(--#{$prefix}btn-padding-x) / 2) 0 calc(var(--#{$prefix}btn-padding-x) / -4);
     vertical-align: bottom;
-    color: inherit;
+    color: var(--#{$prefix}btn-icon-color);
   }
 
   .avatar {
@@ -75,7 +78,7 @@
 //
 @each $color, $value in map-merge($theme-colors, $social-colors) {
   .btn-#{$color} {
-    @if $color == 'dark' {
+    @if $color == "dark" {
       --#{$prefix}btn-border-color: var(--#{$prefix}dark-mode-border-color);
       --#{$prefix}btn-hover-border-color: var(--#{$prefix}dark-mode-border-active-color);
       --#{$prefix}btn-active-border-color: var(--#{$prefix}dark-mode-border-active-color);
@@ -96,7 +99,8 @@
     --#{$prefix}btn-box-shadow: var(--#{$prefix}shadow-input);
   }
 
-  .btn-outline-#{$color} {
+  .btn-outline-#{$color},
+  .btn-outline.btn-#{$color} {
     --#{$prefix}btn-color: var(--#{$prefix}#{$color});
     --#{$prefix}btn-bg: transparent;
     --#{$prefix}btn-border-color: var(--#{$prefix}#{$color});
@@ -105,11 +109,13 @@
     --#{$prefix}btn-hover-bg: var(--#{$prefix}#{$color});
     --#{$prefix}btn-active-color: var(--#{$prefix}#{$color}-fg);
     --#{$prefix}btn-active-bg: var(--#{$prefix}#{$color});
+    --#{$prefix}btn-active-border-color: var(--#{$prefix}#{$color});
     --#{$prefix}btn-disabled-color: var(--#{$prefix}#{$color});
     --#{$prefix}btn-disabled-border-color: var(--#{$prefix}#{$color});
   }
 
-  .btn-ghost-#{$color} {
+  .btn-ghost-#{$color},
+  .btn-ghost.btn-#{$color} {
     --#{$prefix}btn-color: var(--#{$prefix}#{$color});
     --#{$prefix}btn-bg: transparent;
     --#{$prefix}btn-border-color: transparent;
@@ -131,16 +137,27 @@
 //
 // Button sizes
 //
-.btn-sm, .btn-group-sm > .btn {
+.btn-sm,
+.btn-group-sm > .btn {
   --#{$prefix}btn-line-height: #{$input-btn-line-height-sm};
   --#{$prefix}btn-icon-size: #{$input-btn-icon-size-sm};
 }
 
-.btn-lg, .btn-group-lg > .btn {
+.btn-lg,
+.btn-group-lg > .btn {
   --#{$prefix}btn-line-height: #{$input-btn-line-height-lg};
   --#{$prefix}btn-icon-size: #{$input-btn-icon-size-lg};
 }
 
+.btn-xl,
+.btn-group-xl > .btn {
+  --#{$prefix}btn-line-height: #{$input-btn-line-height-xl};
+  --#{$prefix}btn-icon-size: #{$input-btn-icon-size-xl};
+  --#{$prefix}btn-padding-y: #{$input-btn-padding-y-xl};
+  --#{$prefix}btn-padding-x: #{$input-btn-padding-x-xl};
+  --#{$prefix}btn-font-size: #{$input-btn-font-size-xl};
+}
+
 //
 // Button shapes
 //
@@ -161,9 +178,8 @@
 //
 // Icon button
 //
-.btn-icon {
-  min-width: calc((var(--#{$prefix}btn-line-height) * var(--#{$prefix}btn-font-size)) + (var(--#{$prefix}btn-padding-y) * 2) + (var(--#{$prefix}btn-border-width) * 2));
-  min-height: calc((var(--#{$prefix}btn-line-height) * var(--#{$prefix}btn-font-size)) + (var(--#{$prefix}btn-padding-y) * 2) + (var(--#{$prefix}btn-border-width) * 2));
+.btn-icon,
+.btn-action {
   padding-left: 0;
   padding-right: 0;
 
@@ -216,7 +232,7 @@
     height: var(--#{$prefix}btn-icon-size);
     left: calc(50% - var(--#{$prefix}btn-icon-size) / 2);
     top: calc(50% - var(--#{$prefix}btn-icon-size) / 2);
-    animation: spinner-border .75s linear infinite;
+    animation: spinner-border 0.75s linear infinite;
   }
 }
 
@@ -225,14 +241,7 @@
 //
 .btn-action {
   --#{$prefix}border-color: transparent;
-  padding: 0;
-  border: 0;
   color: var(--#{$prefix}secondary);
-  display: inline-flex;
-  width: 2rem;
-  height: 2rem;
-  align-items: center;
-  justify-content: center;
   border-radius: var(--#{$prefix}border-radius);
   background: transparent;
   box-shadow: none;
@@ -250,6 +259,7 @@
   &.show {
     color: var(--#{$prefix}body-color);
     background: var(--#{$prefix}active-bg);
+    border-color: transparent;
   }
 
   &.show {
@@ -259,4 +269,65 @@
 
 .btn-actions {
   display: flex;
-}
+}
+
+.btn-animate-icon {
+  .icon {
+    transition: transform 0.3s ease;
+  }
+
+  &:hover,
+  &:focus-visible {
+    .icon {
+      transform: translateX(4px);
+    }
+  }
+
+  &.btn-animate-icon-rotate {
+    &:hover,
+    &:focus-visible {
+      .icon {
+        transform: rotate(90deg);
+      }
+    }
+  }
+
+  &.btn-animate-icon-move-start {
+    &:hover,
+    &:focus-visible {
+      .icon {
+        transform: translateX(-4px);
+      }
+    }
+  }
+
+  &.btn-animate-icon-pulse {
+    &:hover,
+    &:focus-visible {
+      .icon {
+        transform: none;
+        animation: pulse 0.9s;
+      }
+    }
+  }
+
+  &.btn-animate-icon-shake {
+    &:hover,
+    &:focus-visible {
+      .icon {
+        transform: none;
+        animation: shake 0.9s;
+      }
+    }
+  }
+
+  &.btn-animate-icon-tada {
+    &:hover,
+    &:focus-visible {
+      .icon {
+        transform: none;
+        animation: tada 0.9s;
+      }
+    }
+  }
+}

+ 47 - 0
core/scss/utils/_hover.scss

@@ -0,0 +1,47 @@
+%hover-animation {
+  transition: transform 0.3s ease;
+
+  &:hover {
+    will-change: transform;
+  }
+}
+
+.hover-elevate-up {
+  @extend %hover-animation;
+
+  &:hover {
+    transform: translateY(-4px);
+  }
+}
+
+.hover-elevate-down {
+  @extend %hover-animation;
+
+  &:hover {
+    transform: translateY(4px);
+  }
+}
+
+.hover-scale {
+  @extend %hover-animation;
+
+  &:hover {
+    transform: scale(1.1);
+  }
+}
+
+.hover-rotate-end {
+  @extend %hover-animation;
+
+  &:hover {
+    transform: rotate(4deg);
+  }
+}
+
+.hover-rotate-start {
+  @extend %hover-animation;
+  
+  &:hover {
+    transform: rotate(-4deg);
+  }
+}

+ 169 - 0
docs/content/ui/components/buttons.md

@@ -409,6 +409,16 @@ Add the `.btn-loading` class to show a button's loading state, which can be usef
 {%- endcapture -%}
 {%- include "docs/example.html" html=html centered %}
 
+## Full width buttons
+
+Add the `.w-100` class to make buttons span the full width of their container. This is useful for mobile-first designs or when you want buttons to take up the entire available space.
+
+{% capture html -%}
+<a href="#" class="btn btn-primary w-100">Full width button</a>
+<a href="#" class="btn btn-outline-secondary w-100">Full width outline button</a>
+{%- endcapture -%}
+{%- include "docs/example.html" html=html separated %}
+
 ## List of buttons
 
 Create a list of buttons using the `.btn-list` container to display different actions a user can take. If you add additional styling, such as colors, you will be able to focus users' attention on a particular action or suggest the result.
@@ -476,6 +486,26 @@ Use the `.text-center` or the `.text-end` modifiers to change the buttons' align
 {%- endcapture -%}
 {%- include "docs/example.html" html=html %}
 
+## Buttons with badges
+
+Add badges to buttons to display additional information like counts, notifications, or status indicators. Badges automatically position themselves within the button layout.
+
+{% capture html -%}
+<a href="#" class="btn">
+  Notifications
+  <span class="badge ms-2">14</span>
+</a>
+<a href="#" class="btn">
+  Messages
+  <span class="badge ms-2">3</span>
+</a>
+<a href="#" class="btn">
+  Alerts
+  <span class="badge ms-2">7</span>
+</a>
+{%- endcapture -%}
+{%- include "docs/example.html" html=html centered %}
+
 ## Buttons with avatars
 
 Use buttons with avatars to simplify the process of interaction and make your design more personalized. Buttons can contain avatars and labels or only avatars, if displayed on a smaller space.
@@ -508,3 +538,142 @@ Use buttons with avatars to simplify the process of interaction and make your de
 </a>
 {%- endcapture -%}
 {%- include "docs/example.html" html=html centered %}
+
+## Buttons with animations on hover
+
+Add a subtle animation effect to your buttons when users hover over them. This can enhance the interactivity and provide visual feedback to improve the user experience.
+
+{% capture html -%}
+<div class="btn-list">
+  <a class="btn btn-animate-icon">
+    Save {% include "ui/icon.html" icon="arrow-right" class="icon-end" %}
+  </a>
+  <a class="btn btn-animate-icon btn-animate-icon-rotate">
+    {% include "ui/icon.html" icon="plus" %} Add
+  </a>
+  <a class="btn btn-animate-icon btn-animate-icon-shake">
+    {% include "ui/icon.html" icon="bell" %} Notifications
+  </a>
+  <a class="btn btn-animate-icon btn-animate-icon-rotate">
+    {% include "ui/icon.html" icon="settings" %} Settings
+  </a>
+  <a class="btn btn-animate-icon btn-animate-icon-pulse">
+    {% include "ui/icon.html" icon="heart" %} Love
+  </a>
+  <a class="btn btn-animate-icon btn-animate-icon-rotate">
+    {% include "ui/icon.html" icon="x" %} Close
+  </a>
+  <a class="btn btn-animate-icon btn-animate-icon-tada">
+    {% include "ui/icon.html" icon="check" %} Confirm
+  </a>
+  <a class="btn btn-animate-icon">
+    Next {% include "ui/icon.html" icon="chevron-right" class="icon-end" %}
+  </a>
+  <a class="btn btn-animate-icon btn-animate-icon-move-start">
+    {% include "ui/icon.html" icon="chevron-left" %} Previous
+  </a>
+</div>
+{%- endcapture -%} 
+{%- include "docs/example.html" html=html %}
+
+## Button sizes
+
+Use size modifiers to change the size of your buttons. Available sizes: `.btn-xs`, `.btn-sm`, default, `.btn-lg`, `.btn-xl`.
+
+{% capture html -%}
+<button type="button" class="btn btn-sm">Small button</button>
+<button type="button" class="btn">Default button</button>
+<button type="button" class="btn btn-lg">Large button</button>
+<button type="button" class="btn btn-xl">Extra large button</button>
+{%- endcapture -%}
+{%- include "docs/example.html" html=html separated centered vertical %}
+
+## Link buttons
+
+Use the `.btn-link` class to create buttons that look like links but maintain button functionality. These are useful for secondary actions that shouldn't compete with primary buttons for attention.
+
+{% capture html -%}
+<a href="#" class="btn btn-link">Link button</a>
+<button type="button" class="btn btn-link">Link button</button>
+{%- endcapture -%}
+{%- include "docs/example.html" html=html centered %}
+
+## Action buttons
+
+Use the `.btn-action` class to create subtle action buttons that are perfect for card headers, toolbars, or other interface elements where you want minimal visual impact.
+
+{% capture html -%}
+<div class="btn-actions">
+  <a href="#" class="btn btn-action" aria-label="Edit">
+    {%- include "ui/icon.html" icon="edit" -%}
+  </a>
+  <a href="#" class="btn btn-action" aria-label="Copy">
+    {%- include "ui/icon.html" icon="copy" -%}
+  </a>
+  <a href="#" class="btn btn-action" aria-label="Settings">
+    {%- include "ui/icon.html" icon="settings" -%}
+  </a>
+  <a href="#" class="btn btn-action" aria-label="Delete">
+    {%- include "ui/icon.html" icon="trash" -%}
+  </a>
+</div>
+{%- endcapture -%}
+{%- include "docs/example.html" html=html centered %}
+
+## Action button groups
+
+Use the `.btn-actions` container to group multiple action buttons together. This creates a cohesive set of related actions that work well in card headers, toolbars, or other interface elements.
+
+{% capture html -%}
+<div class="btn-actions">
+  <a href="#" class="btn btn-action">
+    {%- include "ui/icon.html" icon="refresh" -%}
+  </a>
+  <a href="#" class="btn btn-action">
+    {%- include "ui/icon.html" icon="chevron-up" -%}
+  </a>
+  <a href="#" class="btn btn-action">
+    {%- include "ui/icon.html" icon="dots-vertical" -%}
+  </a>
+  <a href="#" class="btn btn-action">
+    {%- include "ui/icon.html" icon="x" -%}
+  </a>
+</div>
+{%- endcapture -%}
+{%- include "docs/example.html" html=html centered %}
+
+## Button groups
+
+Use button groups to combine related buttons together. Button groups are perfect for creating toolbars, segmented controls, or any interface where multiple related actions should be visually grouped.
+
+{% capture html -%}
+<div class="btn-group" role="group">
+  <button type="button" class="btn">Left</button>
+  <button type="button" class="btn">Middle</button>
+  <button type="button" class="btn">Right</button>
+</div>
+{%- endcapture -%}
+{%- include "docs/example.html" html=html centered %}
+
+{% capture html -%}
+<div class="btn-group" role="group">
+  <input type="radio" class="btn-check" name="btn-radio" id="btn-radio-1" autocomplete="off" checked>
+  <label class="btn" for="btn-radio-1">Radio 1</label>
+  
+  <input type="radio" class="btn-check" name="btn-radio" id="btn-radio-2" autocomplete="off">
+  <label class="btn" for="btn-radio-2">Radio 2</label>
+  
+  <input type="radio" class="btn-check" name="btn-radio" id="btn-radio-3" autocomplete="off">
+  <label class="btn" for="btn-radio-3">Radio 3</label>
+</div>
+{%- endcapture -%}
+{%- include "docs/example.html" html=html centered %}
+
+{% capture html -%}
+<div class="btn-group-vertical" role="group">
+  <button type="button" class="btn">Top</button>
+  <button type="button" class="btn">Middle</button>
+  <button type="button" class="btn">Bottom</button>
+</div>
+{%- endcapture -%}
+{%- include "docs/example.html" html=html centered %}

+ 110 - 24
preview/pages/buttons.html

@@ -7,91 +7,177 @@ permalink: buttons.html
 ---
 
 <div class="row row-cards">
-	<div class="col-12">
+	<div class="col-md-6">
 		<div class="card">
 			<div class="card-header">
 				<h3 class="card-title">Standard Buttons</h3>
 			</div>
 			<div class="card-body">
-				{% include "parts/buttons-table.html" show-link=true show-states=true %}
+				<div class="btn-list">
+					{% for color in site.themeColors %}
+					<a class="btn btn-{{ color[0] }}">{% include "ui/icon.html" icon=color[1].icon %} {{ color[1].title }}</a>
+					{% endfor %}
+				</div>
 			</div>
 		</div>
 	</div>
-	<div class="col-12">
+	<div class="col-md-6">
 		<div class="card">
 			<div class="card-header">
 				<h3 class="card-title">Outline Buttons</h3>
 			</div>
 			<div class="card-body">
-				<p>Use <code>.btn-outline-*</code> class for outline buttons.
-				</p>
-				{% include "parts/buttons-table.html" prefix="btn-outline-" show-states=true %}
+				<div class="btn-list">
+					{% for color in site.themeColors %}
+					<a class="btn btn-outline btn-{{ color[0] }}">{% include "ui/icon.html"
+						icon=color[1].icon %} {{ color[1].title }}</a>
+					{% endfor %}
+				</div>
 			</div>
 		</div>
 	</div>
-	<div class="col-12">
+	<div class="col-md-6">
 		<div class="card">
 			<div class="card-header">
 				<h3 class="card-title">Ghost Buttons</h3>
 			</div>
 			<div class="card-body">
-				<p>Use <code>.btn-ghost-*</code> class for ghost buttons.
-				</p>
-				{% include "parts/buttons-table.html" prefix="btn-ghost-" show-states=true %}
+				<div class="btn-list">
+					{% for color in site.themeColors %}
+					<a class="btn btn-ghost btn-{{ color[0] }}">{% include "ui/icon.html"
+						icon=color[1].icon %} {{ color[1].title }}</a>
+					{% endfor %}
+				</div>
 			</div>
 		</div>
 	</div>
-	<div class="col-12">
+	<div class="col-md-6">
 		<div class="card">
 			<div class="card-header">
 				<h3 class="card-title">Square Buttons</h3>
 			</div>
 			<div class="card-body">
-				<p>Use <code>.btn-square</code> class for square buttons.
-				</p>
-				{% include "parts/buttons-table.html" class="btn-square" show-states=true %}
+				<div class="btn-list">
+					{% for color in site.themeColors %}
+					<a class="btn btn-square btn-{{ color[0] }}">
+						{% include "ui/icon.html" icon=color[1].icon %} {{ color[1].title }}
+					</a>
+					{% endfor %}
+				</div>
 			</div>
 		</div>
 	</div>
-	<div class="col-12">
+	<div class="col-md-6">
 		<div class="card">
 			<div class="card-header">
 				<h3 class="card-title">Pill Buttons</h3>
 			</div>
 			<div class="card-body">
-				<p>Use <code>.btn-pill</code> class for pill buttons.
-				</p>
-				{% include "parts/buttons-table.html" class="btn-pill" show-states=true %}
+				<div class="btn-list">
+					{% for color in site.themeColors %}
+					<a class="btn btn-pill btn-{{ color[0] }}">
+						{% include "ui/icon.html" icon=color[1].icon %} {{ color[1].title }}
+					</a>
+					{% endfor %}
+				</div>
 			</div>
 		</div>
 	</div>
-	<div class="col-12">
+	<div class="col-md-6">
 		<div class="card">
 			<div class="card-header">
 				<h3 class="card-title">Extra colors</h3>
 			</div>
 			<div class="card-body">
-				{% include "parts/buttons-table.html" variants=site.colors hide-labels=true icon="star" auto-columns=true %}
+				<div class="btn-list">
+					{% for color in site.colors %}
+					<a class="btn btn-{{ color[0] }}">{% include "ui/icon.html"
+						icon=color[1].icon %} {{ color[1].title }}</a>
+					{% endfor %}
+				</div>
 			</div>
 		</div>
 	</div>
-	<div class="col-12">
+	<div class="col-md-6">
 		<div class="card">
 			<div class="card-header">
 				<h3 class="card-title">Icon buttons</h3>
 			</div>
 			<div class="card-body">
-				{% include "parts/buttons-table.html" variants=site.socialColors hide-labels=true icon-only=true auto-columns=true %}
+				<div class="btn-list">
+					{% for app in site.socialColors %}
+					<a class="btn btn-icon btn-{{ app[0] }}">{% include "ui/icon.html" icon=app[1].icon %}</a>
+					{% endfor %}
+				</div>
 			</div>
 		</div>
 	</div>
-	<div class="col-12">
+	<div class="col-md-6">
 		<div class="card">
 			<div class="card-header">
 				<h3 class="card-title">Social colors</h3>
 			</div>
 			<div class="card-body">
-				{% include "parts/buttons-table.html" variants=site.socialColors hide-labels=true icon=true auto-columns=true %}
+				<div class="btn-list">
+					{% for app in site.socialColors %}
+					<a class="btn btn-{{ app[0] }}">{% include "ui/icon.html" icon=app[1].icon %} {{ app[1].title }}</a>
+					{% endfor %}
+				</div>
+			</div>
+		</div>
+	</div>
+	{% assign actions = 'edit,copy,settings,clipboard,x' | split: ',' %}
+	<div class="col-md-6">
+		<div class="card">
+			<div class="card-header">
+				<h3 class="card-title">Action buttons</h3>
+			</div>
+			<div class="card-body">
+				<div class="btn-actions">
+					{% for action in actions %}
+					<a class="btn btn-action">{% include "ui/icon.html" icon=action %}</a>
+					{% endfor %}
+				</div>
+			</div>
+		</div>
+	</div>
+	<div class="col-md-6">
+		<div class="card">
+			<div class="card-header">
+				<h3 class="card-title">Buttons with icon</h3>
+			</div>
+			<div class="card-body">
+				<div class="btn-list">
+					<a class="btn btn-animate-icon">Save {% include "ui/icon.html" icon="arrow-right" class="icon-end" %}</a>
+					<a class="btn btn-animate-icon btn-animate-icon-rotate">{% include "ui/icon.html" icon="plus" %} Add</a>
+					<a class="btn btn-animate-icon btn-animate-icon-shake">{% include "ui/icon.html" icon="bell" %} Notifications</a>
+					<a class="btn btn-animate-icon btn-animate-icon-rotate">{% include "ui/icon.html" icon="settings" %} Settings</a>
+					<a class="btn btn-animate-icon btn-animate-icon-pulse">{% include "ui/icon.html" icon="heart" %} Love</a>
+					<a class="btn btn-animate-icon btn-animate-icon-rotate">{% include "ui/icon.html" icon="x" %} Close</a>
+					<a class="btn btn-animate-icon btn-animate-icon-tada">{% include "ui/icon.html" icon="check" %} Confirm</a>
+					<a class="btn btn-animate-icon">Next {% include "ui/icon.html" icon="chevron-right" class="icon-end" %}</a>
+					<a class="btn btn-animate-icon btn-animate-icon-move-start">{% include "ui/icon.html" icon="chevron-left" %} Previous</a>
+				</div>
+			</div>
+		</div>
+	</div>
+	<div class="col-md-6">
+		<div class="card">
+			<div class="card-header">
+				<h3 class="card-title">Buttons size</h3>
+			</div>
+			<div class="card-body">
+				<div class="space-y">
+					{% assign sizes = 'sm,md,lg,xl' | split: ',' %}
+					{% for size in sizes %}
+					<div class="btn-list">
+						{% include "ui/button.html" size=size text="Button" %}
+						{% include "ui/button.html" size=size icon="star" icon-only %}
+						{% include "ui/button.html" size=size icon="star" text="Button" %}
+						{% include "ui/button.html" size=size icon-end="star" text="Button" %}
+					</div>
+					{% endfor %}
+				</div>
 			</div>
 		</div>
 	</div>

+ 6 - 6
shared/data/sri.json

@@ -1,6 +1,6 @@
 {
-  "css": "sha384-5giQMn6+5ciSHvBDFs0PiYSSxefBFia1in4jshPxFyAW6OgOyFY8MgcpshFmmzIX",
-  "css-rtl": "sha384-bnNXrnBOjBGqF+px2z86UVufg7rLlMXxO/66LoA2Wh2SsR58pS3zgjuJ6HsVeN98",
+  "css": "sha384-LUZmhPrA9DuvV+PENAthWDl8vR3wEHZ2mxCp6V4VEbbCIDjvxx2RlvvYjfSZZ+Fe",
+  "css-rtl": "sha384-R+1hmp3uvF0kkOZyoxDNAnLplv2bfSkdzNMujFi7fbvcu+l5L15NMKg50UT7SHD0",
   "css-flags": "sha384-H7p4Cgh2RM3r9apL63WdIpPu9BtIhbGngx0f3WrqyCpunfY+X7M4apXpQQcZqA5c",
   "css-flags-rtl": "sha384-tzddSAu/YEPdL4FEjR4HwaKan3DhU5kQ+ifJOWa0kR7eb1RUmPKr8PHEx1Hmgx+t",
   "css-marketing": "sha384-jdZv/iHkc5OuAPy1SD9QancYje5FHhs4s1GL7AOvKdb6ZMiC+Zwya8pCGDBb7tIf",
@@ -9,14 +9,14 @@
   "css-payments-rtl": "sha384-gZtxDQ2AdE1j/+cmuiPWY+AJ0jtnsk2C3Z65u+uyzQkqfISW1V7Blezfumdm/mfj",
   "css-socials": "sha384-bujgRtkeNSwK8hFfCUO6SyIyWngJuWblefHIViyjq5XbDAEeCi1YRlIqggw9Vcqu",
   "css-socials-rtl": "sha384-SIYJowkbADF1DNCVS7QnO0WDOPI7xOulE2iUJ5do1VveCIHslxNnE7lYyXbHMqtH",
-  "css-props": "sha384-VeBVCSj9feUGkuG55hL+mt2qeQK5FM8BrDnVpc1LfOydkX5uQXyeb3FFC1Bik301",
-  "css-props-rtl": "sha384-YeIydJO/dmbDv+HHcIbd93qd1wUD1C4oaOdHJVcHB5Z26S4QAmPeNP/400EzB2lu",
+  "css-props": "sha384-eFBhxMcWpvjSKfWwkdzIeMzXUt2j6d0od1+xwAYsUAtAlBK0SK/tZl9xbx624xxx",
+  "css-props-rtl": "sha384-ifUxJ+Cmg0A/0cl51AAiC0TsP2kvHrBMyGxIsHWT3sPmZzu0PGHf0Pc3YwmoUWkT",
   "css-themes": "sha384-EWgzD0j3PnZ9hhq/YFnSNV/Hm54BB0hWYSSCEj6OR1YxP0BVDEM7Enp+UAJDMqBx",
   "css-themes-rtl": "sha384-rzVwsvFoVNK25vTMMkUuO71ndLJIhoMtvnaA9BKDBa2s+lboMuDmnHX/ikLmdgqv",
   "css-vendors-rtl": "sha384-bD4REzS6v/hyJgKgmqVmDd/HSu5y3dsZ4hQYcdYQqEOKQDLUEyqiWAWYq/NObB8B",
   "css-vendors": "sha384-yYQlhpXu43M9F6EsDV1V/GQBPy9gQSKTTkvRiSnKEbUjI3/RhmpARqgI9pUTU6Z9",
-  "js": "sha384-DaKT+KCi9ypTlo8Tfz74XJPqZhkV6luseXpEDIzTeYfy4PIZZW9gV6hAxC4/mGDj",
-  "js-theme": "sha384-6UW/C91tb0VoxDoxl4Fpngc2SX3sK37eWdaDTVBcwA1WkWVYV9V8pZlen7sud5CB",
+  "js": "sha384-2WdYXbwTs+5LXi+OUvcthfGsTS1s8P9lt6uUbrglmYVhAsPSh1ItGCmXkpzSR34u",
+  "js-theme": "sha384-owIUMWnQZ6WJM5sJcZYopxE42XrqqKivlpxysxQYM+RXYbaHGAWVo2aniM4Wyhxq",
   "demo-css": "sha384-BUDq2P684xwRBf0GDlySvob+KJg4ko8y2K7njgvYBscmEuqoVVqJ75zcTDozwkFA",
   "demo-js": "sha384-UcTgbM9IZSOPHHuFa0R9H4TegQWoZkJKpeTjLV5hjem2k0CZ67Q4/bW2rT/Edf4Z"
 }

+ 0 - 34
shared/includes/parts/buttons-table.html

@@ -1,34 +0,0 @@
-{% assign prefix = include.prefix | default: 'btn-' %}
-{% assign show-link = include.show-link | default: false %}
-{% assign show-states = include.show-states | default: false %}
-{% assign hide-labels = include.hide-labels | default: false %}
-{% assign variants = include.variants | default: site.themeColors %}
-
-{% for state in site.buttonStates %}
-   {% if show-states or state.class != 'active' and state.class != 'disabled' %}
-      <div class="row g-2 align-items-center">
-         {% unless hide-labels %}<div class="col-12 col-xl-2 font-weight-semibold">{{ state.title }}</div>{% endunless %}
-         {% for variant in variants %}
-            {% if show-link or variant[0] != 'link' %}
-               <div class="col-6 col-sm-4 col-md-2 col-xl{% if include.auto-columns %}-auto{% endif %}{% if variant[0] == 'light' %} bg-dark{% endif %} py-3">
-                  {% if include.icon or include.icon-only %}
-                     {% assign icon = variant[1].icon %}
-                  {% endif %}
-
-                  {% assign class = prefix | append: variant[0] %}
-                  {% if state.class %}
-                     {% assign class = class | append: ' ' | append: state.class %}
-                  {% endif %}
-                  {% if include.class %}
-                     {% assign class = class | append: ' ' | append: include.class %}
-                  {% endif %}
-
-                  {% assign text = variant[1].title %}
-                  {% assign color = variant[0] %}
-                  {% include "ui/button.html" block=true color=false class=class icon=icon icon-only=include.icon-only text=text %}
-               </div>
-               {% endif %}
-         {% endfor %}
-      </div>
-   {% endif %}
-{% endfor %}