From ebe7f0982c4ae3c963a736f6c3f1837122c3e4b4 Mon Sep 17 00:00:00 2001 From: Alberto Carreras <10593890+AlbertCarreras@users.noreply.github.com> Date: Fri, 31 Jan 2025 20:30:15 +0100 Subject: [PATCH] Spinner, Text, TextUI: add label to Spinner, and id to TextUI and Text #3947 (#3971) --- docs/examples/spinner/dontLabel.tsx | 6 +- .../{variantGrayscale.tsx => grayscale.tsx} | 0 docs/examples/spinner/label.tsx | 16 ++++ .../spinner/{variantWhite.tsx => white.tsx} | 0 docs/pages/web/spinner.tsx | 74 ++++++++++--------- packages/gestalt/src/Icon/InternalIcon.tsx | 12 +-- packages/gestalt/src/Spinner.test.tsx | 5 ++ packages/gestalt/src/Spinner.tsx | 48 +++++++++--- packages/gestalt/src/Spinner/VRSpinner.tsx | 41 ++++++++-- packages/gestalt/src/Text.tsx | 6 ++ packages/gestalt/src/TextUI.tsx | 6 ++ .../src/__snapshots__/Spinner.test.tsx.snap | 58 ++++++++++++++- 12 files changed, 211 insertions(+), 61 deletions(-) rename docs/examples/spinner/{variantGrayscale.tsx => grayscale.tsx} (100%) create mode 100644 docs/examples/spinner/label.tsx rename docs/examples/spinner/{variantWhite.tsx => white.tsx} (100%) diff --git a/docs/examples/spinner/dontLabel.tsx b/docs/examples/spinner/dontLabel.tsx index 518e83f9ed..80d16d4574 100644 --- a/docs/examples/spinner/dontLabel.tsx +++ b/docs/examples/spinner/dontLabel.tsx @@ -1,12 +1,10 @@ -import { Flex, Spinner, Text } from 'gestalt'; +import { Flex, Spinner } from 'gestalt'; export default function Example() { return ( - - - Loading… + ); diff --git a/docs/examples/spinner/variantGrayscale.tsx b/docs/examples/spinner/grayscale.tsx similarity index 100% rename from docs/examples/spinner/variantGrayscale.tsx rename to docs/examples/spinner/grayscale.tsx diff --git a/docs/examples/spinner/label.tsx b/docs/examples/spinner/label.tsx new file mode 100644 index 0000000000..bef394013b --- /dev/null +++ b/docs/examples/spinner/label.tsx @@ -0,0 +1,16 @@ +import { Box, Flex, Spinner, useReducedMotion } from 'gestalt'; + +export default function Example() { + const reduced = useReducedMotion(); + return ( + + + + + + ); +} diff --git a/docs/examples/spinner/variantWhite.tsx b/docs/examples/spinner/white.tsx similarity index 100% rename from docs/examples/spinner/variantWhite.tsx rename to docs/examples/spinner/white.tsx diff --git a/docs/pages/web/spinner.tsx b/docs/pages/web/spinner.tsx index 08c783816b..50172d4128 100644 --- a/docs/pages/web/spinner.tsx +++ b/docs/pages/web/spinner.tsx @@ -1,4 +1,3 @@ -import { useAppContext } from 'docs/docs-components/appContext'; import AccessibilitySection from '../../docs-components/AccessibilitySection'; import docGen, { DocGen } from '../../docs-components/docgen'; import GeneratedPropTable from '../../docs-components/GeneratedPropTable'; @@ -15,15 +14,13 @@ import dontMultiple from '../../examples/spinner/dontMultiple'; import dontWait from '../../examples/spinner/dontWait'; import doOverlay from '../../examples/spinner/doOverlay'; import doWait from '../../examples/spinner/doWait'; +import grayscale from '../../examples/spinner/grayscale'; +import label from '../../examples/spinner/label'; import localizationLabels from '../../examples/spinner/localizationLabels'; import main from '../../examples/spinner/main'; -import variantGrayscale from '../../examples/spinner/variantGrayscale'; -import variantWhite from '../../examples/spinner/variantWhite'; +import white from '../../examples/spinner/white'; export default function DocsPage({ generatedDocGen }: { generatedDocGen: DocGen }) { - const { experiments } = useAppContext(); - const isVREnabled = experiments === 'Tokens'; - return ( @@ -147,7 +144,13 @@ export default function DocsPage({ generatedDocGen }: { generatedDocGen: DocGen @@ -155,10 +158,40 @@ export default function DocsPage({ generatedDocGen }: { generatedDocGen: DocGen + + + } + title="Grayscale" + /> + } + title="White" + /> + + + + } + /> + + } /> - - {isVREnabled && ( - - - } - title="Grayscale" - /> - - } - title="White" - /> - - )} diff --git a/packages/gestalt/src/Icon/InternalIcon.tsx b/packages/gestalt/src/Icon/InternalIcon.tsx index 60de26c722..eb7e34f9e4 100644 --- a/packages/gestalt/src/Icon/InternalIcon.tsx +++ b/packages/gestalt/src/Icon/InternalIcon.tsx @@ -24,6 +24,7 @@ export type IconColor = type IconName = keyof typeof icons | keyof typeof compactIconsVR; type Props = { + accessibilityDescribedby?: string; accessibilityLabel: string; color?: IconColor; dataTestId?: string; @@ -38,16 +39,8 @@ type Props = { // @ts-expect-error - TS2322 - Type 'string[]' is not assignable to type 'readonly ("replace" | "search" | "link" | "text" | "dash" | "3D" | "3D-move" | "360" | "accessibility" | "ad" | "ad-group" | "add" | "add-circle" | "add-layout" | "add-pin" | "add-section" | ... 317 more ... | "wave")[]'. const IconNames: ReadonlyArray = Object.keys(icons); -/** - * [Icons](https://gestalt.pinterest.systems/web/icon) are the symbolic representation of an action or information, providing visual context and improving usability. - * - * See the [Iconography and SVG guidelines](https://gestalt.pinterest.systems/foundations/iconography/library) to explore the full icon library. - * - * ![Icon light mode](https://raw.githubusercontent.com/pinterest/gestalt/master/playwright/visual-test/Icon-list.spec.ts-snapshots/Icon-list-chromium-darwin.png) - * ![Icon dark mode](https://raw.githubusercontent.com/pinterest/gestalt/master/playwright/visual-test/Icon-list-dark.spec.ts-snapshots/Icon-list-dark-chromium-darwin.png) - * - */ function InternalIcon({ + accessibilityDescribedby, accessibilityLabel, color = 'subtle', dangerouslySetSvgPath, @@ -133,6 +126,7 @@ function InternalIcon({ return ( // @ts-expect-error - TS2322 - Type '{ children: Element; "aria-hidden": true | null; "aria-label": string; className: string; height: string | number; role: "img"; viewBox: string; width: string | number; }' is not assignable to type 'SVGProps'. { expect(tree).toMatchSnapshot(); }); +test('Spinner renders label', () => { + const tree = create().toJSON(); + expect(tree).toMatchSnapshot(); +}); + test('Spinner renders with no delay', () => { const tree = create().toJSON(); expect(tree).toMatchSnapshot(); diff --git a/packages/gestalt/src/Spinner.tsx b/packages/gestalt/src/Spinner.tsx index d45c2ee503..6f246aa73c 100644 --- a/packages/gestalt/src/Spinner.tsx +++ b/packages/gestalt/src/Spinner.tsx @@ -1,9 +1,12 @@ +import { useId } from 'react'; import classnames from 'classnames'; import Box from './Box'; import { useDefaultLabelContext } from './contexts/DefaultLabelProvider'; -import Icon from './Icon'; +import Flex from './Flex'; +import InternalIcon from './Icon/InternalIcon'; import styles from './Spinner.css'; import VRSpinner from './Spinner/VRSpinner'; +import TextUI from './TextUI'; import useInExperiment from './useInExperiment'; const SIZE_NAME_TO_PIXEL = { @@ -25,6 +28,10 @@ type Props = { * Whether or not to render with a 300ms delay. The delay is for perceived performance, so you should rarely need to remove it. See the [delay variant](https://gestalt.pinterest.systems/web/spinner#Delay) for more details. */ delay?: boolean; + /** + * Adds a label under the spinning animation. + */ + label?: string; /** * Indicates if Spinner should be visible. Controlling the component with this prop ensures the outro animation is played. If outro animation is not intended, prefer conditional rendering. */ @@ -45,10 +52,12 @@ export default function Spinner({ accessibilityLabel, color = 'subtle', delay = true, + label, show, size = 'md', }: Props) { const { accessibilityLabel: accessibilityLabelDefault } = useDefaultLabelContext('Spinner'); + const id = useId(); const isInVRExperiment = useInExperiment({ webExperimentName: 'web_gestalt_visualrefresh', @@ -59,20 +68,43 @@ export default function Spinner({ return ( ); } - return show ? ( + if (!show) return null; + + return label ? ( + + + +
+ +
+ + + + {label} + + + + + ) : (
-
- ) : ( -
); } diff --git a/packages/gestalt/src/Spinner/VRSpinner.tsx b/packages/gestalt/src/Spinner/VRSpinner.tsx index c7cad75b68..fe97fa1f26 100644 --- a/packages/gestalt/src/Spinner/VRSpinner.tsx +++ b/packages/gestalt/src/Spinner/VRSpinner.tsx @@ -1,9 +1,11 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useId, useState } from 'react'; import classnames from 'classnames'; import vrLightDesignTokens from 'gestalt-design-tokens/dist/json/vr-theme/variables-light.json'; import styles from './VRSpinner.css'; import Box from '../Box'; import { useDefaultLabelContext } from '../contexts/DefaultLabelProvider'; +import Flex from '../Flex'; +import TextUI from '../TextUI'; const SIZE_NAME_TO_PIXEL = { sm: 32, @@ -11,6 +13,7 @@ const SIZE_NAME_TO_PIXEL = { } as const; type SpinnerBodyProps = { + accessibilityDescribedby?: string; accessibilityLabel: string; delay: boolean; show: boolean; @@ -20,6 +23,7 @@ type SpinnerBodyProps = { }; function SpinnerBody({ + accessibilityDescribedby, accessibilityLabel, delay, show, @@ -41,7 +45,6 @@ function SpinnerBody({ return (
- + { if (!showProp) setShow(false); @@ -107,9 +119,28 @@ export default function Spinner({ if (!show) return null; - return ( + return label ? ( + + + + + + {label} + + + + + ) : ( (function Text( children, color = 'default', dataTestId, + id, inline = false, italic = false, lineClamp, @@ -170,6 +175,7 @@ const TextWithForwardRef = forwardRef(function Text( (function Text( overflow = 'breakWord', size = 'md', title, + id, }: Props, ref, ): ReactElement { @@ -148,6 +153,7 @@ const TextUIWithForwardRef = forwardRef(function Text( `; +exports[`Spinner does not render by default 1`] = `null`; + +exports[`Spinner renders label 1`] = ` +
+
+
+
+
+ + + +
+
+
+
+
+
+ Label +
+
+
+
+
+`; exports[`Spinner renders when passed show 1`] = `