Skip to content

Commit b1c5c32

Browse files
authored
Move Spinner from lab to core; create Spinner documentation (#1184)
1 parent 3beb5f2 commit b1c5c32

17 files changed

Lines changed: 149 additions & 78 deletions

File tree

.changeset/sharp-terms-doubt.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@salt-ds/core": minor
3+
"@salt-ds/lab": minor
4+
---
5+
6+
Move Spinner from lab to core

packages/ag-grid-theme/stories/examples/InfiniteScroll.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { useEffect } from "react";
22
import { AgGridReact, AgGridReactProps } from "ag-grid-react";
3-
import { StackLayout } from "@salt-ds/core";
4-
import { Spinner } from "@salt-ds/lab";
3+
import { Spinner, StackLayout } from "@salt-ds/core";
54
import dataGridExampleData from "../dependencies/dataGridExampleData";
65
import dataGridInfiniteScrollExampleColumns from "../dependencies/dataGridInfiniteScrollExampleColumns";
76
import { useAgGridHelpers } from "../dependencies/useAgGridHelpers";

packages/ag-grid-theme/stories/examples/LoadingOverlay.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { CSSProperties } from "react";
22
import { AgGridReact, AgGridReactProps } from "ag-grid-react";
3-
import { Card, StackLayout } from "@salt-ds/core";
4-
import { Spinner } from "@salt-ds/lab";
3+
import { Card, Spinner, StackLayout } from "@salt-ds/core";
54
import dataGridExampleData from "../dependencies/dataGridExampleData";
65
import dataGridExampleColumns from "../dependencies/dataGridExampleColumns";
76
import { useAgGridHelpers } from "../dependencies/useAgGridHelpers";

packages/lab/src/__tests__/__e2e__/spinner/Spinner.accessibility.cy.tsx renamed to packages/core/src/__tests__/__e2e__/spinner/Spinner.accessibility.cy.tsx

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import { Spinner } from "@salt-ds/lab";
1+
import { Spinner } from "@salt-ds/core";
2+
import { getContainerEl } from "cypress/react";
3+
import ReactDOM from "react-dom";
24

35
const ariaLabel = "Loading component";
46

@@ -33,31 +35,22 @@ describe("GIVEN an available announcer", () => {
3335

3436
// TODO fix unmount announcement
3537
it.skip("THEN the announcer should be called when the component unmounts", () => {
36-
cy.mount(<Spinner aria-label={ariaLabel} />).then(({ unmount }) => {
37-
unmount().then(() => {
38-
cy.get("body").should("announce", `finished ${ariaLabel}`);
39-
});
40-
});
38+
cy.mount(<Spinner aria-label={ariaLabel} />);
39+
cy.then(() => ReactDOM.unmountComponentAtNode(getContainerEl()));
40+
cy.get("body").should("announce", `finished ${ariaLabel}`);
4141
});
4242

4343
it.skip("THEN nothing should be announced when announcer is disabled", () => {
44-
cy.mount(<Spinner aria-label={ariaLabel} disableAnnouncer />).then(
45-
({ unmount }) => {
46-
cy.findByRole("img").should("not.announce", ariaLabel);
47-
unmount().then(() => {
48-
cy.findByRole("img").should("not.announce", `finished ${ariaLabel}`);
49-
});
50-
}
51-
);
44+
cy.mount(<Spinner aria-label={ariaLabel} disableAnnouncer />);
45+
cy.findByRole("img").should("not.announce", ariaLabel);
46+
cy.then(() => ReactDOM.unmountComponentAtNode(getContainerEl()));
47+
cy.findByRole("img").should("not.announce", `finished ${ariaLabel}`);
5248
});
5349

5450
it.skip("THEN it should not announce completion message when set to null", () => {
55-
cy.mount(
56-
<Spinner aria-label={ariaLabel} completionAnnouncement={null} />
57-
).then(({ unmount }) => {
58-
unmount().then(() => {
59-
cy.findByRole("img").should("not.announce", `finished ${ariaLabel}`);
60-
});
61-
});
51+
cy.mount(<Spinner aria-label={ariaLabel} completionAnnouncement={null} />);
52+
53+
cy.then(() => ReactDOM.unmountComponentAtNode(getContainerEl()));
54+
cy.findByRole("img").should("not.announce", `finished ${ariaLabel}`);
6255
});
6356
});

packages/lab/src/__tests__/__e2e__/spinner/Spinner.cy.tsx renamed to packages/core/src/__tests__/__e2e__/spinner/Spinner.cy.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Spinner } from "@salt-ds/lab";
1+
import { Spinner } from "@salt-ds/core";
22

33
// All previous tests were snapshot tests. These tests are to check svg existence by class
44

packages/core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export * from "./grid-item";
1111
export * from "./grid-layout";
1212
export * from "./link";
1313
export * from "./panel";
14+
export * from "./spinner";
1415
export * from "./stack-layout";
1516
export * from "./status-indicator";
1617
export * from "./text";
Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,26 @@
1-
.saltSpinner-gradientStop {
2-
stop-color: var(--saltSpinner-gradient-color, var(--salt-measured-fill));
3-
}
4-
1+
/* Styles applied to the root element */
52
.saltSpinner {
63
position: relative;
74
}
85

6+
/* Styles applied to the SVG stop elements */
7+
.saltSpinner-gradientStop {
8+
stop-color: var(--saltSpinner-gradient-color, var(--salt-measured-fill));
9+
}
10+
11+
/* Styles applied when `size="default"` */
912
.saltSpinner-default {
1013
height: var(--salt-size-base);
1114
width: var(--salt-size-base);
1215
}
1316

17+
/* Styles applied when `size="large"` */
1418
.saltSpinner-large {
1519
height: calc(var(--salt-size-base) * 2);
1620
width: calc(var(--salt-size-base) * 2);
1721
}
1822

23+
/* Styles applied to the SVG */
1924
.saltSpinner-spinner {
2025
animation: spinner 0.9s linear infinite;
2126
width: 100%;
Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { clsx } from "clsx";
22
import { forwardRef, HTMLAttributes, useEffect } from "react";
3-
import { makePrefixer, useAriaAnnouncer, useId } from "@salt-ds/core";
3+
import { useAriaAnnouncer } from "../aria-announcer";
4+
import { makePrefixer, useId } from "../utils";
45
import { SpinnerSVG } from "./svgSpinners/SpinnerSVG";
56

67
import "./Spinner.css";
@@ -9,12 +10,9 @@ import "./Spinner.css";
910
* Spinner component, provides an indeterminate loading indicator
1011
*
1112
* @example
12-
* <Spinner size="small | medium | large" />
13+
* <Spinner size="default | large" />
1314
*/
1415

15-
// TODO: documentation -- add line about best practices:
16-
// - Improve accessibility by customizing the aria-label to provide additional context about *what* is loading, e.g. `aria-label="loading settings panel"`.
17-
1816
export const SpinnerSizeValues = ["default", "large"] as const;
1917
export type SpinnerSize = typeof SpinnerSizeValues[number];
2018
const withBaseName = makePrefixer("saltSpinner");
@@ -121,7 +119,7 @@ export const Spinner = forwardRef<HTMLDivElement, SpinnerProps>(
121119
role={role}
122120
{...rest}
123121
>
124-
<SpinnerSVG id={id} />
122+
<SpinnerSVG id={id!} />
125123
</div>
126124
);
127125
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { SVGAttributes } from "react";
2+
import { makePrefixer } from "../../utils";
3+
4+
const withBaseName = makePrefixer("saltSpinner");
5+
6+
/* SVG based on MD, do not change viewbox attribute */
7+
export const SpinnerSVG = (props: {
8+
id: string;
9+
rest?: Omit<SVGAttributes<SVGSVGElement>, "id">;
10+
}) => {
11+
const { id, rest } = props;
12+
return (
13+
<svg
14+
className={withBaseName("spinner")}
15+
viewBox="0 0 28 28"
16+
id={id}
17+
{...rest}
18+
>
19+
<defs>
20+
<linearGradient id={`${id}-1`} x1="100%" x2="0%" y1="78%" y2="78%">
21+
<stop className={withBaseName("gradientStop")} offset="0%" />
22+
<stop className={withBaseName("gradientStop")} offset="100%" />
23+
</linearGradient>
24+
<linearGradient id={`${id}-2`} x1="13%" x2="100%" y1="0%" y2="87%">
25+
<stop className={withBaseName("gradientStop")} offset="0%" />
26+
<stop
27+
className={withBaseName("gradientStop")}
28+
offset="100%"
29+
stopOpacity="0"
30+
/>
31+
</linearGradient>
32+
</defs>
33+
<g fill="none" fillRule="evenodd">
34+
<path
35+
d="M28 14H24C24 8.47715 19.5228 4 14 4C8.47715 4 4 8.47715 4 14 H 0 C0 6.26801 6.26801 0 14 0C21.732 0 28 6.26801 28 14Z"
36+
fill={`url(#${id}-1)`}
37+
/>
38+
<path
39+
d="M4 14 C4 19.5228 8.47715 24 14 24V28C6.26801 28 0 21.732 0 14 Z"
40+
fill={`url(#${id}-2)`}
41+
/>
42+
</g>
43+
</svg>
44+
);
45+
};

0 commit comments

Comments
 (0)