diff --git a/docs/pages/components/button.md b/docs/pages/components/button.md
index 1d62e9b..b0d2df3 100644
--- a/docs/pages/components/button.md
+++ b/docs/pages/components/button.md
@@ -12,21 +12,10 @@ Buttons indicate actions the user can trigger on click. Buttons already have a b
The default button without any class is already utilizing accent coloring. In addition, you can use `neutral`, `positive` and `negative` classes for the corresponding colors.
-
- Send post
-
-
- Return
- .neutral
-
-
- Save profile
- .positive
-
-
- Delete account
- .negative
-
+
Send post
+
Return
+
Save profile
+
Delete account
```html
@@ -36,27 +25,31 @@ The default button without any class is already utilizing accent coloring. In ad
Delete account
```
+Using the `disabled` attribute deactivates buttons. If you're using `onclick` event handlers, make sure this case is handled.
+
+
+ Send post
+ Return
+ Save profile
+ Delete account
+
+
+```html
+Send post
+Return
+Save profile
+Delete account
+```
+
### Secondary button
For less important buttons with outline and transparent backgrounds, the `hollow` class can be used. It can be combined with all other button related classes.
-
- Send post
- .hollow
-
-
- Return
- .hollow.neutral
-
-
- Save profile
- .hollow.positive
-
-
- Delete account
- .hollow.negative
-
+
Send post
+
Return
+
Save profile
+
Delete account
```html
@@ -66,19 +59,29 @@ For less important buttons with outline and transparent backgrounds, the `hollow
Delete account
```
+The `disabled` attribute can be set on secondary buttons too.
+
+
+ Send post
+ Return
+ Save profile
+ Delete account
+
+
+```html
+Send post
+Return
+Save profile
+Delete account
+```
+
### Link button
Use the `button` class to apply all button styles on `` tags. That way you can create button links for navigation.
```html
@@ -91,36 +94,54 @@ Use the `button` class to apply all button styles on ` ` tags. That way you ca
Use the `rounded-full` utility class to give buttons a pill form.
-
- Send post
- .rounded-full
-
-
- Return
- .neutral.rounded-full
-
+
Send post
+
Return
+
Save profile
```html
Send post
Return
+Save profile
```
### Ghost button
-Use the `ghost` class to keep button sizing without background color or border.
+Use the `ghost` class to keep button sizing without background color or border. Works on link buttons too.
-
- GitHub
- .ghost
-
+
Subscribe
+
Docs
+
GitHub
```html
-GitHub
+Subscribe
+Docs
+ GitHub
```
+### Busy button
+
+Use the `busy` class to replace the text of a button with a loader while keeping the button size. Works with secondary buttons, link buttons and pill buttons. Busy buttons are disabled.
+
+
+ Send post
+ Return
+ Save profile
+ Delete account
+ Load settings
+
+
+```html
+Send post
+Return
+Save profile
+Delete account
+Load settings
+```
+
+
### Button group
Use the `button-group` class on an element containing buttons, to group buttons to a single logical unit.
diff --git a/docs/pages/components/loader.md b/docs/pages/components/loader.md
new file mode 100644
index 0000000..8a69fed
--- /dev/null
+++ b/docs/pages/components/loader.md
@@ -0,0 +1,76 @@
+---
+title: 'Components: Loader - Sloth.css'
+description: Loader component of Sloth.css.
+---
+
+## Loader
+
+A loader indicates a busy state, giving visual feedback for users that a process currently takes some time. This reassures the user, that an action got triggered in the first place and is still working or progressing.
+
+### Loader colors
+
+Use the `loader` class to add a loading indicator. The loadinng indicator is in accent colors per default. Use the `neutral`, `positive` and `negative` classes to change the color accordingly.
+
+
+
+```html
+
+
+
+
+```
+
+### Loader sizes
+
+Use the `sm` or `lg` classes to reduce or increase the size of a loader.
+
+
+
+```html
+
+
+
+```
+
+### Double loader
+
+Use the `double` class to make the loading animation more emphasized. Double loaders support all colors and sizes too.
+
+
+
+```html
+
+
+
+```
+
+### Button loader
+
+Loaders are also supported on button elements. Use the `busy` class to replace the text of a button with a loader while keeping the button size.
+
+
+ Send post
+ Return
+ Save profile
+ Delete account
+
+
+```html
+Send post
+Return
+Save profile
+Delete account
+```
diff --git a/eleventy.config.js b/eleventy.config.js
index 05f745e..d4f5034 100644
--- a/eleventy.config.js
+++ b/eleventy.config.js
@@ -31,6 +31,7 @@ module.exports = function(eleventyConfig) {
'breadcrumb',
'button',
'callout',
+ 'loader',
'separator',
'state',
'toast',
diff --git a/src/_components.css b/src/_components.css
index c7d4a4a..f8f3060 100644
--- a/src/_components.css
+++ b/src/_components.css
@@ -5,7 +5,7 @@
@import 'components/_callout.css';
/* @import 'components/_card.css'; */
/* @import 'components/_input.css'; */
-/* @import 'components/_loader.css'; */
+@import 'components/_loader.css';
/* @import 'components/_modal.css'; */
@import 'components/_separator.css';
/* @import 'components/_sequence.css'; */
diff --git a/src/_config.css b/src/_config.css
index b68b5bd..b886a9a 100644
--- a/src/_config.css
+++ b/src/_config.css
@@ -65,5 +65,9 @@
}
@keyframes pulse {
50% { box-shadow: 0 0 0 0 var(--color-bg-accent); }
- 100% { box-shadow: 0 0 0 .5rem rgba(0, 0, 0, 0); }
+ 100% { box-shadow: 0 0 0 .5rem rgba(0, 0, 0, 0); }
+}
+@keyframes rotate {
+ 0% { transform: rotate(0); }
+ 100% { transform: rotate(360deg); }
}
diff --git a/src/components/_button.css b/src/components/_button.css
index d5943ac..0943238 100644
--- a/src/components/_button.css
+++ b/src/components/_button.css
@@ -33,75 +33,79 @@ a.button {
background-color: var(--color-negative);
}
- &:not(:disabled) {
- cursor: pointer;
- }
- &:hover:not(:disabled) {
- background-color: var(--color-accent);
- &.neutral {
- background-color: color-mix(in hsl, var(--color-neutral) 90%, white);
- }
- &.positive {
- background-color: color-mix(in hsl, var(--color-positive) 90%, white);
- }
- &.negative {
- background-color: color-mix(in hsl, var(--color-negative) 90%, white);
- }
- }
- &:focus:not(:disabled), &:focus-within:not(:disabled), &:hover:not(:disabled) {
- box-shadow:
- color-mix(in hsl, var(--color-accent) 90%, black) var(--shadow-border),
- black var(--shadow-float);
- &.neutral {
+ &:not(:disabled):not(.busy):not(.ghost) {
+ &:hover {
+ background-color: var(--color-accent);
+ &.neutral {
+ background-color: color-mix(in hsl, var(--color-neutral) 90%, white);
+ }
+ &.positive {
+ background-color: color-mix(in hsl, var(--color-positive) 90%, white);
+ }
+ &.negative {
+ background-color: color-mix(in hsl, var(--color-negative) 90%, white);
+ }
+ }
+ &:focus, &:focus-within, &:hover {
box-shadow:
- color-mix(in hsl, var(--color-neutral) 90%, black) var(--shadow-border),
+ color-mix(in hsl, var(--color-accent) 90%, black) var(--shadow-border),
black var(--shadow-float);
- }
- &.positive {
+ &.neutral {
+ box-shadow:
+ color-mix(in hsl, var(--color-neutral) 90%, black) var(--shadow-border),
+ black var(--shadow-float);
+ }
+ &.positive {
+ box-shadow:
+ color-mix(in hsl, var(--color-positive) 90%, black) var(--shadow-border),
+ black var(--shadow-float);
+ }
+ &.negative {
+ box-shadow:
+ color-mix(in hsl, var(--color-negative) 90%, black) var(--shadow-border),
+ black var(--shadow-float);
+ }
+ }
+ &:active {
+ outline: 0;
box-shadow:
- color-mix(in hsl, var(--color-positive) 90%, black) var(--shadow-border),
- black var(--shadow-float);
- }
- &.negative {
- box-shadow:
- color-mix(in hsl, var(--color-negative) 90%, black) var(--shadow-border),
- black var(--shadow-float);
+ var(--color-bg-accent) var(--shadow-border),
+ var(--color-bg-accent) var(--shadow-inset);
+ &.neutral {
+ box-shadow:
+ color-mix(in hsl, var(--color-neutral) 80%, black) var(--shadow-border),
+ color-mix(in hsl, var(--color-neutral) 80%, black) var(--shadow-inset);
+ }
+ &.positive {
+ box-shadow:
+ color-mix(in hsl, var(--color-positive) 80%, black) var(--shadow-border),
+ color-mix(in hsl, var(--color-positive) 80%, black) var(--shadow-inset);
+ }
+ &.negative {
+ box-shadow:
+ color-mix(in hsl, var(--color-negative) 80%, black) var(--shadow-border),
+ color-mix(in hsl, var(--color-negative) 80%, black) var(--shadow-inset);
+ }
}
}
- &:focus:not(:disabled), &:focus-within:not(:disabled) {
- outline: 4px solid var(--color-outline);
- &.neutral {
- outline: 4px solid color-mix(in srgb, var(--color-neutral) 35%, transparent);
- }
- &.positive {
- outline: 4px solid color-mix(in srgb, var(--color-positive) 35%, transparent);
- }
- &.negative {
- outline: 4px solid color-mix(in srgb, var(--color-negative) 35%, transparent);
- }
- }
- &:active:not(:disabled) {
- outline: 0;
- box-shadow:
- var(--color-bg-accent) var(--shadow-border),
- var(--color-bg-accent) var(--shadow-inset);
- &.neutral {
- box-shadow:
- color-mix(in hsl, var(--color-neutral) 80%, black) var(--shadow-border),
- color-mix(in hsl, var(--color-neutral) 80%, black) var(--shadow-inset);
- }
- &.positive {
- box-shadow:
- color-mix(in hsl, var(--color-positive) 80%, black) var(--shadow-border),
- color-mix(in hsl, var(--color-positive) 80%, black) var(--shadow-inset);
- }
- &.negative {
- box-shadow:
- color-mix(in hsl, var(--color-negative) 80%, black) var(--shadow-border),
- color-mix(in hsl, var(--color-negative) 80%, black) var(--shadow-inset);
+ &:not(:disabled):not(.busy) {
+ cursor: pointer;
+
+ &:focus, &:focus-within {
+ outline: 4px solid var(--color-outline);
+ &.neutral {
+ outline: 4px solid color-mix(in srgb, var(--color-neutral) 35%, transparent);
+ }
+ &.positive {
+ outline: 4px solid color-mix(in srgb, var(--color-positive) 35%, transparent);
+ }
+ &.negative {
+ outline: 4px solid color-mix(in srgb, var(--color-negative) 35%, transparent);
+ }
}
}
+
&.hollow {
color: light-dark(var(--color-bg-accent), var(--color-accent));
background-color: color-mix(in srgb, var(--color-accent) 10%, transparent);
@@ -143,16 +147,18 @@ a.button {
text-underline-offset: 0;
transition: var(--transition-color), var(--transition-underline);
- &:hover, &:focus, &:focus-within, &:active {
- color: var(--color-link-hover);
- background-color: transparent;
- box-shadow: none;
- text-decoration: underline;
- text-underline-offset: 15%;
+ &:not(:disabled):not(.busy) {
+ &:hover, &:focus, &:focus-within, &:active {
+ color: var(--color-link-hover);
+ background-color: transparent;
+ box-shadow: none;
+ text-decoration: underline;
+ text-underline-offset: 15%;
+ }
}
}
- &:disabled {
+ &:disabled, &.busy {
cursor: not-allowed;
opacity: .5;
}
diff --git a/src/components/_loader.css b/src/components/_loader.css
new file mode 100644
index 0000000..2d54745
--- /dev/null
+++ b/src/components/_loader.css
@@ -0,0 +1,102 @@
+.loader {
+ position: relative;
+ height: 2rem;
+ width: 2rem;
+ border: .25rem solid color-mix(in srgb, var(--color-accent) 10%, transparent);
+ border-radius: 9999px;
+ color: var(--color-accent);
+ border-top-color: currentColor;
+ animation: rotate 1s 0s infinite linear normal;
+ box-sizing: border-box;
+
+ &.neutral {
+ color: var(--color-neutral);
+ border-color: var(--color-bg-muted);
+ border-top-color: currentColor;
+ }
+ &.positive {
+ color: var(--color-positive);
+ border-color: var(--color-bg-positive);
+ border-top-color: currentColor;
+ }
+ &.negative {
+ color: var(--color-negative);
+ border-color: var(--color-bg-negative);
+ border-top-color: currentColor;
+ }
+
+ &.sm {
+ height: 1rem;
+ width: 1rem;
+ border-width: .125rem;
+ }
+ &.lg {
+ height: 4rem;
+ width: 4rem;
+ border-width: .3125rem;
+ }
+
+ &.double::after {
+ content: '';
+ position: absolute;
+ inset: .25rem;
+ display: block;
+ border: inherit;
+ border-radius: inherit;
+ border-top-color: inherit;
+ animation: rotate .5s 0s infinite linear reverse;
+ }
+ &.double.sm::after {
+ border: none;
+ animation: none;
+ background: currentColor;
+ }
+ &.double.lg::after {
+ inset: .5rem;
+ }
+}
+
+input[type="submit"],
+input[type="button"],
+input[type="reset"],
+button,
+a.button {
+ &.busy {
+ color: transparent;
+ position: relative;
+
+ &::before {
+ content: '';
+ position: absolute;
+ display: block;
+ height: 1.5rem;
+ width: 1.5rem;
+ top: calc(50% - .75rem);
+ left: calc(50% - .75rem);
+ border: .25rem solid #ffffff88;
+ border-radius: 9999px;
+ border-top-color: transparent;
+ animation: rotate 1s 0s infinite linear normal;
+ box-sizing: border-box;
+ }
+
+ &.hollow {
+ &, &:hover {
+ color: transparent;
+ }
+
+ &.neutral::before {
+ border-color: var(--color-neutral);
+ border-top-color: transparent;
+ }
+ &.positive::before {
+ border-color: var(--color-positive);
+ border-top-color: transparent;
+ }
+ &.negative::before {
+ border-color: var(--color-negative);
+ border-top-color: transparent;
+ }
+ }
+ }
+}
diff --git a/src/core/_form.css b/src/core/_form.css
index 2459f11..abc71ca 100644
--- a/src/core/_form.css
+++ b/src/core/_form.css
@@ -207,43 +207,44 @@ input[type="reset"],
button {
appearance: none;
background-color: var(--color-bg-accent);
+ border-radius: .25rem;
border: 0;
+ box-shadow: black var(--shadow-float);
+ box-sizing: border-box;
color: white;
display: inline-block;
+ font-family: inherit;
+ font-size: .875rem;
+ padding: .75rem 1rem;
text-align: center;
text-decoration: none;
- user-select: none;
- white-space: nowrap;
- border-radius: .25rem;
- padding: .75rem 1rem;
- font-size: .875rem;
- box-shadow: black var(--shadow-float);
- font-family: inherit;
- box-sizing: border-box;
transition:
var(--transition-outline),
var(--transition-color),
var(--transition-transform);
+ user-select: none;
+ white-space: nowrap;
- &:not(:disabled) {
+ &:not(:disabled):not(.busy) {
cursor: pointer;
- }
- &:hover:not(:disabled) {
- background-color: var(--color-accent);
- }
- &:focus:not(:disabled), &:focus-within:not(:disabled), &:hover:not(:disabled) {
- box-shadow:
- color-mix(in hsl, var(--color-accent) 90%, black) var(--shadow-border),
- black var(--shadow-float);
- }
- &:focus:not(:disabled), &:focus-within:not(:disabled) {
- outline: 4px solid var(--color-outline);
- }
- &:active:not(:disabled) {
- outline: 0;
- box-shadow:
- var(--color-bg-accent) var(--shadow-border),
- var(--color-bg-accent) var(--shadow-inset);
+
+ &:hover {
+ background-color: var(--color-accent);
+ }
+ &:focus, &:focus-within, &:hover {
+ box-shadow:
+ color-mix(in hsl, var(--color-accent) 90%, black) var(--shadow-border),
+ black var(--shadow-float);
+ }
+ &:focus, &:focus-within {
+ outline: 4px solid var(--color-outline);
+ }
+ &:active {
+ outline: 0;
+ box-shadow:
+ var(--color-bg-accent) var(--shadow-border),
+ var(--color-bg-accent) var(--shadow-inset);
+ }
}
&:disabled {
cursor: not-allowed;