diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md index 55da23ce4c..07e41f69fb 100755 --- a/MIGRATION_GUIDE.md +++ b/MIGRATION_GUIDE.md @@ -69,6 +69,7 @@ #### Loader - `Spinner` component replaced with new `Loader` component +- `xs` and `xl` sized have been removed, leaving only the default, `sm`, and `lg` sizes #### Menu diff --git a/packages/stacks-classic/lib/components/loader/loader.a11y.test.ts b/packages/stacks-classic/lib/components/loader/loader.a11y.test.ts index 456b79bd4b..498d9404a7 100644 --- a/packages/stacks-classic/lib/components/loader/loader.a11y.test.ts +++ b/packages/stacks-classic/lib/components/loader/loader.a11y.test.ts @@ -1,9 +1,9 @@ import { runA11yTests } from "../../test/a11y-test-utils"; import "../../index"; -describe("loading", () => { +describe("loader", () => { runA11yTests({ - baseClass: "s-loading", + baseClass: "s-loader", modifiers: { primary: ["sm", "lg"], }, diff --git a/packages/stacks-classic/lib/components/loader/loader.less b/packages/stacks-classic/lib/components/loader/loader.less index c39109cc46..6faf984997 100644 --- a/packages/stacks-classic/lib/components/loader/loader.less +++ b/packages/stacks-classic/lib/components/loader/loader.less @@ -1,61 +1,78 @@ -.s-loader--block { - // BASE COMPONENT-SPECIFIC CUSTOM PROPERTIES - --_ld-size: var(--su-static24); - --_ld-block-size: calc(var(--su-static8) - var(--su-static1)); - --_ld-gap: calc(var(--su-static2) + var(--su-static1)); - --_ld-start-offset: var(--su-static2); +.s-loader { + --_ld-color: var(--black-600); + --_ld-gap: calc(var(--_ld-size) / 2); + --_ld-size: calc(var(--su4) + var(--su1)); // 5px + --_ld-offset: calc(calc(var(--_ld-size) / 8) * -5); // -5/8ths of the size // MODIFIERS - &&__sm { - --_ld-size: var(--su-static16); - --_ld-gap: var(--su-static2); - --_ld-start-offset: calc(var(--su-static2) + var(--su-static1)); + &__sm { + --_ld-size: calc(calc(var(--su8) - var(--su1)) / 2); // 3.5px + margin-left: var(--su1); + margin-right: var(--su1); } - &&__lg { - --_ld-size: var(--su-static48); - --_ld-block-size: calc(var(--su-static4) + var(--su-static4)); - --_ld-start-offset: var(--su-static1); + + &__lg { + --_ld-size: var(--su8); + } + + // CHILD ELEMENTS + & &--block, + &:before, + &:after { + background-color: currentColor; + content: ""; + display: block; + height: var(--_ld-size); + width: var(--_ld-size); + + animation: loader-animation .8s cubic-bezier(1, 1, 0, 1) infinite; + } + + & &--block { + animation-delay: .25s; + } + + &:after { + animation-delay: .5s; } - align-items: center; display: flex; - height: var(--_ld-size); - justify-content: center; - position: relative; - width: var(--_ld-size); - - // Style the spot loading SVG to match container size - .svg-spot { - height: auto; - max-height: 100%; - max-width: 100%; - width: auto; - - rect { - height: var(--_ld-block-size); - width: var(--_ld-block-size); - } + gap: var(--_ld-gap); + margin-top: var(--_ld-gap); +} - rect:nth-child(2) { - x: var(--_ld-start-offset); - } - - rect:nth-child(3) { - x: calc(var(--_ld-start-offset) + var(--_ld-block-size) + var(--_ld-gap)); - } - - rect:nth-child(4) { - x: calc(var(--_ld-start-offset) + var(--_ld-block-size) * 2 + var(--_ld-gap) * 2); +@media (prefers-reduced-motion:reduce){ + .s-loader { + & &--block, + &:before, + &:after { + animation: loader-animation-reduced-motion 2s ease-in-out infinite; } } - - // Inherit text color when inside a button - .s-btn & { - color: var(--_bu-fc); - .svg-spot { - rect { - fill: var(--_bu-fc) !important; - } - } +} + +@keyframes loader-animation { + 0%,1%,99%,to{ + opacity: 0.2; + transform: translateY(0); + } + 49%,50%{ + opacity: 1; + transform: translateY(var(--_ld-offset)); + } + 51%{ + opacity: 0.2; + transform: translateY(var(--_ld-offset)); + } +} + +@keyframes loader-animation-reduced-motion { + 0%,to{ + opacity: 0.3; + transform: none; + } + 50%{ + opacity: 1; + transform: none; } -} \ No newline at end of file +} diff --git a/packages/stacks-classic/lib/components/loader/loader.visual.test.ts b/packages/stacks-classic/lib/components/loader/loader.visual.test.ts index 73827b5c3b..9eb4972e48 100644 --- a/packages/stacks-classic/lib/components/loader/loader.visual.test.ts +++ b/packages/stacks-classic/lib/components/loader/loader.visual.test.ts @@ -6,10 +6,10 @@ import "../../index"; const template = ({ component, testid }: any) => html`
${component}
`; -describe("loading", () => { +describe("loader", () => { // default, sizes runVisualTests({ - baseClass: "s-loading", + baseClass: "s-loader", modifiers: { primary: ["sm", "lg"], }, @@ -20,7 +20,7 @@ describe("loading", () => { }); // applied font color runVisualTests({ - baseClass: "s-loading", + baseClass: "s-loader", modifiers: { global: ["fc-theme-primary"], }, diff --git a/packages/stacks-docs/_data/loader.json b/packages/stacks-docs/_data/loader.json index 2381db5a31..0c37314c0e 100644 --- a/packages/stacks-docs/_data/loader.json +++ b/packages/stacks-docs/_data/loader.json @@ -1,19 +1,25 @@ { - "loader": [ + "classes": [ { - "class": ".s-loader--block", + "class": ".s-loader", "applies": "N/A", - "description": "Base block loading style that displays three animated squares" + "description": "Base class for the loader component" + }, + { + "class": ".s-loader--block", + "applies": ".s-loader", + "description": "Child necessary to render the center loader block" }, { - "class": ".s-loader--block__sm", - "applies": ".s-loader--block", - "description": "A small style for compact layouts" + "class": ".s-loader__sm", + "applies": ".s-loader", + "description": "A small variant of the loader component" }, { - "class": ".s-loader--block__lg", - "applies": ".s-loader--block", - "description": "A large style for the largest layouts" + "class": ".s-loader__lg", + "applies": ".s-loader", + "description": "A large variant of the loader component" } - ] + ], + "sizes": ["s-loader__sm", "", "s-loader__lg"] } diff --git a/packages/stacks-docs/_data/site-navigation.json b/packages/stacks-docs/_data/site-navigation.json index 84ca8c3f3c..1c9a41bfc7 100644 --- a/packages/stacks-docs/_data/site-navigation.json +++ b/packages/stacks-docs/_data/site-navigation.json @@ -288,7 +288,8 @@ }, { "title": "Loader", - "url": "/product/components/loader/" + "url": "/product/components/loader/", + "new": true }, { "title": "Menus", diff --git a/packages/stacks-docs/product/components/buttons.html b/packages/stacks-docs/product/components/buttons.html index 77fa04c86b..50fd1096b8 100644 --- a/packages/stacks-docs/product/components/buttons.html +++ b/packages/stacks-docs/product/components/buttons.html @@ -268,13 +268,13 @@
{% header "h2", "Loading" %} -

Any button can have a loading state applied by adding the .s-loader--block .s-loader--block__sm state class.

+

Indicate a loading state by adding a .s-loader component to a button.

{% highlight html %} - @@ -291,6 +291,7 @@ + {% for btn in buttons.variants %} {{ btn.title }} diff --git a/packages/stacks-docs/product/components/loader.html b/packages/stacks-docs/product/components/loader.html index ea9bfad66e..b6bca8c51f 100644 --- a/packages/stacks-docs/product/components/loader.html +++ b/packages/stacks-docs/product/components/loader.html @@ -2,7 +2,7 @@ layout: page title: Loader svelte: https://beta.svelte.stackoverflow.design/?path=/docs/components-loader--docs -description: "The Loader indicates an active wait state for a page, section, or interactive element." +description: "The loader component indicates an active wait state for a page, section, or interactive element." tags: components ---
@@ -17,7 +17,7 @@ - {% for item in loader.loader %} + {% for item in loader.classes %} {{ item.class }} {% if item.applies == "N/A" %}{{ item.applies }}{% else %}{{ item.applies }}{% endif %} @@ -30,44 +30,65 @@
{% header "h2", "Examples" %} - {% header "h3", "Blocks" %} -

Use the Blocks variant as the standard loader for general UI states. This is the most common style and utilizes a monochrome black and gray palette.

+ {% header "h3", "Base" %} +

The base loader component displays three animated squares.

{% highlight html %} -
+
+
Loading…
- @Svg.Spot.Loading.With("native") -
-
-
Loading…
- @Svg.Spot.Loading.With("native") -
-
-
Loading…
- @Svg.Spot.Loading.With("native")
{% endhighlight %}
-
-
-
Loading…
- {% spot "Loading", "native" %} -
-
-
-
-
Loading…
- {% spot "Loading", "native" %} -
-
-
-
-
Loading…
- {% spot "Loading", "native" %} -
+
+
+
Loading…
+ +
+ {% header "h3", "Sizes" %} + + + + + + + + + + {% for size in loader.sizes %} + + + + + + {% endfor %} + +
ClassApplied toExample
+ + {% if size == "" %} + .s-loader + {% else %} + .{{ size }} + {% endif %} + + + + {% if size == "" %} + N/A + {% else %} + .s-loader + {% endif %} + + +
+
+
Loading…
+
+
+
\ No newline at end of file diff --git a/packages/stacks-svelte/src/components/Button/Button.stories.svelte b/packages/stacks-svelte/src/components/Button/Button.stories.svelte index 8aafcd1e6f..baa83a44fe 100644 --- a/packages/stacks-svelte/src/components/Button/Button.stories.svelte +++ b/packages/stacks-svelte/src/components/Button/Button.stories.svelte @@ -4,7 +4,6 @@ import type { Brand, Size, Variant, Weight } from "./Button.svelte"; import Icon from "../Icon/Icon.svelte"; import { IconTrash } from "@stackoverflow/stacks-icons/icons"; - import Loader from "../Loader/Loader.svelte"; const ButtonBrands: Brand[] = ["", "facebook", "github", "google"]; const ButtonSizes: Size[] = ["", "xs", "sm", "lg"]; @@ -115,9 +114,6 @@ {#each ButtonVariants as variant (variant)} {#each ButtonWeights as weight (weight)} {#if !(weight === "clear" && (variant === "featured" || variant === "tonal"))} - {#snippet loader()} - - {/snippet} {titleCase(variant || "secondary")} @@ -128,7 +124,7 @@ {#each [null, "selected", "disabled"] as state (state)}
- - -
- - - - - - - - - - {#each LoaderVariants as variant (variant)} - - - - - {/each} - -
VariantsExample
- {variant} - -
- -
-
-
-
diff --git a/packages/stacks-svelte/src/components/Loader/Loader.svelte b/packages/stacks-svelte/src/components/Loader/Loader.svelte index ad8785b5b8..dfb6d880b0 100644 --- a/packages/stacks-svelte/src/components/Loader/Loader.svelte +++ b/packages/stacks-svelte/src/components/Loader/Loader.svelte @@ -1,6 +1,5 @@
+
{label}
-
diff --git a/packages/stacks-svelte/src/components/Loader/Loader.test.ts b/packages/stacks-svelte/src/components/Loader/Loader.test.ts index fd956c5842..5eb1e187e6 100644 --- a/packages/stacks-svelte/src/components/Loader/Loader.test.ts +++ b/packages/stacks-svelte/src/components/Loader/Loader.test.ts @@ -14,31 +14,23 @@ describe("Loader", () => { expect(screen.getByText("Please wait...")).to.exist; }); - it("should render the loader with the block variant class when variant is provided", () => { - render(Loader, { variant: "block" }); - const loader = screen.getByText("Loading…").closest(".s-loader--block"); - expect(loader).to.exist; - }); - it("should render the loader without size modifier class when size is undefined", () => { - render(Loader, { size: undefined }); - const loader = screen.getByText("Loading…").closest(".s-loader--block"); + render(Loader); + const loader = screen.getByText("Loading…").closest(".s-loader"); expect(loader).to.exist; - expect(loader).not.to.have.class("s-loader--block__sm"); - expect(loader).not.to.have.class("s-loader--block__lg"); }); it("should render the loader with the small size class", () => { render(Loader, { size: "sm" }); - const loader = screen.getByText("Loading…").closest(".s-loader--block"); + const loader = screen.getByText("Loading…").closest(".s-loader"); expect(loader).to.exist; - expect(loader).to.have.class("s-loader--block__sm"); + expect(loader).to.have.class("s-loader__sm"); }); it("should render the loader with the large size class", () => { render(Loader, { size: "lg" }); - const loader = screen.getByText("Loading…").closest(".s-loader--block"); + const loader = screen.getByText("Loading…").closest(".s-loader"); expect(loader).to.exist; - expect(loader).to.have.class("s-loader--block__lg"); + expect(loader).to.have.class("s-loader__lg"); }); });