From 7c696049df9f1ad24cfab46e199a82f2fef573b4 Mon Sep 17 00:00:00 2001 From: Michael Winter Date: Fri, 20 Jun 2025 14:00:40 +1000 Subject: [PATCH 01/14] update VideoPlayer to allow for always visible button --- .../BrandMomentCaptureIntro.tsx | 2 + .../_docs/BrandMomentCaptureIntro.mdx | 6 +++ .../_docs/BrandMomentCaptureIntro.stories.tsx | 41 +++++++++++++++++++ .../subcomponents/Base/Base.module.scss | 23 +++++------ .../subcomponents/VideoPlayer/VideoPlayer.tsx | 26 +++++++++--- .../src/Illustration/utils/usePausePlay.tsx | 18 +++++++- 6 files changed, 95 insertions(+), 21 deletions(-) diff --git a/packages/components/src/Illustration/Scene/BrandMomentCaptureIntro/BrandMomentCaptureIntro.tsx b/packages/components/src/Illustration/Scene/BrandMomentCaptureIntro/BrandMomentCaptureIntro.tsx index 2ca1da82f98..48860a69478 100644 --- a/packages/components/src/Illustration/Scene/BrandMomentCaptureIntro/BrandMomentCaptureIntro.tsx +++ b/packages/components/src/Illustration/Scene/BrandMomentCaptureIntro/BrandMomentCaptureIntro.tsx @@ -32,6 +32,7 @@ export const BrandMomentCaptureIntro = ({ return ( setFirstAnimationComplete(true)} diff --git a/packages/components/src/Illustration/Scene/BrandMomentCaptureIntro/_docs/BrandMomentCaptureIntro.mdx b/packages/components/src/Illustration/Scene/BrandMomentCaptureIntro/_docs/BrandMomentCaptureIntro.mdx index e6fcdd98c9f..e14c4d2a8d7 100644 --- a/packages/components/src/Illustration/Scene/BrandMomentCaptureIntro/_docs/BrandMomentCaptureIntro.mdx +++ b/packages/components/src/Illustration/Scene/BrandMomentCaptureIntro/_docs/BrandMomentCaptureIntro.mdx @@ -45,3 +45,9 @@ Will render the a looped animation. Should be used with `isAnimated` to render t If false will render the animation paused. This can be re-enabled clicking the pause sign on hover. + +## Example + +This is not an exact template, rather an example of the component in use with a looping animation. + + diff --git a/packages/components/src/Illustration/Scene/BrandMomentCaptureIntro/_docs/BrandMomentCaptureIntro.stories.tsx b/packages/components/src/Illustration/Scene/BrandMomentCaptureIntro/_docs/BrandMomentCaptureIntro.stories.tsx index 0917f224399..be2fdf40e34 100644 --- a/packages/components/src/Illustration/Scene/BrandMomentCaptureIntro/_docs/BrandMomentCaptureIntro.stories.tsx +++ b/packages/components/src/Illustration/Scene/BrandMomentCaptureIntro/_docs/BrandMomentCaptureIntro.stories.tsx @@ -1,4 +1,9 @@ +import React from 'react' import { type Meta, type StoryObj } from '@storybook/react' +import { Heading } from '~components/Heading' +import { Link } from '~components/Link' +import { Text } from '~components/Text' +import { Button, Icon } from '~components/__next__' import { BrandMomentCaptureIntro } from '../index' const meta = { @@ -41,3 +46,39 @@ export const Autoplay: Story = { autoplay: false, }, } + +// This is an example that provides a closer representation of how the component is used in the product. +export const CaptureExample: Story = { + args: { + isAnimated: true, + loop: true, + autoplay: true, + }, + render: (args) => ( +
+
+ +
+
+ + Survey Title + + + You have been asked to provide feedback for Demonstration Employee. + + + The setting for this survey control how your responses can be used by Hooli. + + + Your information will be stored and processed in accordance with Culture Amp’s{' '} + Privacy Policy. More on managing information. + +
+ +
+
+
+ ), +} diff --git a/packages/components/src/Illustration/subcomponents/Base/Base.module.scss b/packages/components/src/Illustration/subcomponents/Base/Base.module.scss index 509eef1c0c4..0560111225c 100644 --- a/packages/components/src/Illustration/subcomponents/Base/Base.module.scss +++ b/packages/components/src/Illustration/subcomponents/Base/Base.module.scss @@ -1,6 +1,3 @@ -@import '~@kaizen/design-tokens/sass/color'; -@import '~@kaizen/design-tokens/sass/animation'; - @layer kz-components { .wrapper { width: 100%; @@ -18,24 +15,16 @@ position: absolute; right: 1rem; bottom: 1rem; - /* stylelint-disable declaration-no-important */ - background-color: $color-white !important; - border: 1px solid $color-gray-400; - transition: all $animation-duration-immediate; + border-width: 1px; + transition: opacity var(--animation-duration-immediate); @media (hover: none) and (pointer: coarse) { opacity: 100%; } - svg { - color: $color-purple-800; - opacity: 70%; - } - &:hover, &:focus { opacity: 100%; - background-color: $color-gray-200 !important; svg { opacity: 100%; @@ -43,6 +32,14 @@ } } + .figure .pausePlayButton.hasVisiblePlayButton { + opacity: 100%; + + svg { + opacity: 100%; + } + } + .figure:hover { .pausePlayButton { opacity: 100%; diff --git a/packages/components/src/Illustration/subcomponents/VideoPlayer/VideoPlayer.tsx b/packages/components/src/Illustration/subcomponents/VideoPlayer/VideoPlayer.tsx index 33c4478ceb7..65a151dedab 100644 --- a/packages/components/src/Illustration/subcomponents/VideoPlayer/VideoPlayer.tsx +++ b/packages/components/src/Illustration/subcomponents/VideoPlayer/VideoPlayer.tsx @@ -1,6 +1,7 @@ import React, { useEffect, useRef } from 'react' import classnames from 'classnames' -import { IconButton } from '~components/Button' +import { VisuallyHidden } from '~components/VisuallyHidden' +import { Button } from '~components/__next__' import { assetUrl } from '~components/utils/hostedAssets' import { canPlayWebm } from '../../utils/canPlayWebm' import { usePausePlay } from '../../utils/usePausePlay' @@ -37,9 +38,15 @@ export type VideoPlayerProps = { */ aspectRatio?: 'landscape' | 'portrait' | 'square' + /** + * This render the play/pause button with 100% visibility, instead of relying on hover/focus interactions. This is useful for satisfying the a11y requirements for animations that last longer than 5 seconds. @default false. + */ + hasVisiblePlayButton?: boolean onEnded?: () => void } +// hasVisiblePlayButton is an interim solution to resolve an a11y issue with the animation player only showing the play/pause button on hover/focus. This ideally will be resolved through a design solution or updated pattern. + export const VideoPlayer = ({ autoplay = true, loop = false, @@ -47,6 +54,7 @@ export const VideoPlayer = ({ source, aspectRatio, onEnded, + hasVisiblePlayButton = false, // }: VideoPlayerProps): JSX.Element => { const videoRef = useRef(null) const [prefersReducedMotion, setPrefersReducedMotion] = React.useState(true) @@ -181,12 +189,18 @@ export const VideoPlayer = ({ {isWebmCompatible && } - pausePlay.toggle()} + ) } diff --git a/packages/components/src/Illustration/utils/usePausePlay.tsx b/packages/components/src/Illustration/utils/usePausePlay.tsx index 9a1b3e36f66..00228909393 100644 --- a/packages/components/src/Illustration/utils/usePausePlay.tsx +++ b/packages/components/src/Illustration/utils/usePausePlay.tsx @@ -1,4 +1,5 @@ import React, { useState, type RefObject } from 'react' +import { useIntl } from '@cultureamp/i18n-react-intl' import { Icon } from '~components/__next__/Icon' export type usePausePlayHook = { @@ -8,8 +9,21 @@ export type usePausePlayHook = { } export const usePausePlay = (videoRef: RefObject): usePausePlayHook => { + const { formatMessage } = useIntl() const [isPaused, setPaused] = useState(false) + const playAnimationLabel = formatMessage({ + id: 'videoPlayer.pausePlayBtn.playLabel', + defaultMessage: 'Play animation', + description: 'Label for the starting / playing an animation', + }) + + const pauseAnimationLabel = formatMessage({ + id: 'videoPlayer.pausePlayBtn.pauseLabel', + defaultMessage: 'Play animation', + description: 'Label for the pausing / stopping an animation', + }) + return { toggle: (): void => { if (!videoRef.current) return @@ -22,7 +36,7 @@ export const usePausePlay = (videoRef: RefObject): usePausePla videoRef.current.pause() } }, - icon: , - label: isPaused ? 'Play animation' : 'Pause animation', + icon: , + label: isPaused ? playAnimationLabel : pauseAnimationLabel, } } From b4c263071e37096b8e3969c0700516f125898b83 Mon Sep 17 00:00:00 2001 From: Michael Winter Date: Fri, 20 Jun 2025 14:02:00 +1000 Subject: [PATCH 02/14] update scene documentation to showcase animatable scenes --- .../src/Illustration/Scene/_docs/Scene.mdx | 6 +++ .../Scene/_docs/Scene.stories.tsx | 46 ++++++++++++++++++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/packages/components/src/Illustration/Scene/_docs/Scene.mdx b/packages/components/src/Illustration/Scene/_docs/Scene.mdx index 5f24a922fdb..09acb28b5a5 100644 --- a/packages/components/src/Illustration/Scene/_docs/Scene.mdx +++ b/packages/components/src/Illustration/Scene/_docs/Scene.mdx @@ -20,3 +20,9 @@ import * as SceneStories from './Scene.stories' + +### Animated scenes + +Some Scene illustrations have animated versions. Looped animation should be used sparingly as it can be distracting for users, especially those with motion sensitivity. Consider also the WCAG spec for [non-essential animations](https://www.w3.org/WAI/WCAG22/Understanding/animation-from-interactions.html). + + diff --git a/packages/components/src/Illustration/Scene/_docs/Scene.stories.tsx b/packages/components/src/Illustration/Scene/_docs/Scene.stories.tsx index b30b737a7df..47cb4d22c64 100644 --- a/packages/components/src/Illustration/Scene/_docs/Scene.stories.tsx +++ b/packages/components/src/Illustration/Scene/_docs/Scene.stories.tsx @@ -1,6 +1,16 @@ +import React from 'react' import { type Meta, type StoryObj } from '@storybook/react' -import { BrandMomentPositiveOutro } from '../index' +import { + BrandMomentError, + BrandMomentLogin, + BrandMomentPositiveOutro, + EmptyStatesAction, + EmptyStatesInformative, + EmptyStatesNegative, + EmptyStatesNeutral, + EmptyStatesPositive, +} from '../index' const meta = { title: 'Components/Illustrations/Scene', @@ -20,3 +30,37 @@ export const Playground: Story = { }, }, } + +export const AnimatedScenes: Story = { + parameters: { + docs: { + canvas: { + sourceState: 'shown', + }, + }, + }, + args: { + isAnimated: true, + loop: true, + autoplay: true, + }, + render: (args) => ( + <> + + + + + + + + + + ), + decorators: [ + (Story) => ( +
+ +
+ ), + ], +} From 3a9e2ba80907f5068d0ee884c635b6dc30b5d15b Mon Sep 17 00:00:00 2001 From: Michael Winter Date: Fri, 20 Jun 2025 14:14:19 +1000 Subject: [PATCH 03/14] rename prop to hasVisibleAnimationToggle --- .../BrandMomentCaptureIntro/BrandMomentCaptureIntro.tsx | 4 ++-- .../src/Illustration/subcomponents/Base/Base.module.scss | 2 +- .../subcomponents/VideoPlayer/VideoPlayer.tsx | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/components/src/Illustration/Scene/BrandMomentCaptureIntro/BrandMomentCaptureIntro.tsx b/packages/components/src/Illustration/Scene/BrandMomentCaptureIntro/BrandMomentCaptureIntro.tsx index 48860a69478..3e3768837ca 100644 --- a/packages/components/src/Illustration/Scene/BrandMomentCaptureIntro/BrandMomentCaptureIntro.tsx +++ b/packages/components/src/Illustration/Scene/BrandMomentCaptureIntro/BrandMomentCaptureIntro.tsx @@ -32,7 +32,7 @@ export const BrandMomentCaptureIntro = ({ return ( setFirstAnimationComplete(true)} diff --git a/packages/components/src/Illustration/subcomponents/Base/Base.module.scss b/packages/components/src/Illustration/subcomponents/Base/Base.module.scss index 0560111225c..e78d29a78ee 100644 --- a/packages/components/src/Illustration/subcomponents/Base/Base.module.scss +++ b/packages/components/src/Illustration/subcomponents/Base/Base.module.scss @@ -32,7 +32,7 @@ } } - .figure .pausePlayButton.hasVisiblePlayButton { + .figure .pausePlayButton.hasVisibleAnimationToggle { opacity: 100%; svg { diff --git a/packages/components/src/Illustration/subcomponents/VideoPlayer/VideoPlayer.tsx b/packages/components/src/Illustration/subcomponents/VideoPlayer/VideoPlayer.tsx index 65a151dedab..22d7db40208 100644 --- a/packages/components/src/Illustration/subcomponents/VideoPlayer/VideoPlayer.tsx +++ b/packages/components/src/Illustration/subcomponents/VideoPlayer/VideoPlayer.tsx @@ -41,11 +41,11 @@ export type VideoPlayerProps = { /** * This render the play/pause button with 100% visibility, instead of relying on hover/focus interactions. This is useful for satisfying the a11y requirements for animations that last longer than 5 seconds. @default false. */ - hasVisiblePlayButton?: boolean + hasVisibleAnimationToggle?: boolean onEnded?: () => void } -// hasVisiblePlayButton is an interim solution to resolve an a11y issue with the animation player only showing the play/pause button on hover/focus. This ideally will be resolved through a design solution or updated pattern. +// hasVisibleAnimationToggle is an interim solution to resolve an a11y issue with the animation player only showing the play/pause button on hover/focus. This ideally will be resolved through a design solution or updated pattern. export const VideoPlayer = ({ autoplay = true, @@ -54,7 +54,7 @@ export const VideoPlayer = ({ source, aspectRatio, onEnded, - hasVisiblePlayButton = false, // + hasVisibleAnimationToggle = false, }: VideoPlayerProps): JSX.Element => { const videoRef = useRef(null) const [prefersReducedMotion, setPrefersReducedMotion] = React.useState(true) @@ -192,7 +192,7 @@ export const VideoPlayer = ({