diff --git a/change/@fluentui-react-carousel-preview-37035160-0805-40b3-a10a-0d7e332c3442.json b/change/@fluentui-react-carousel-preview-37035160-0805-40b3-a10a-0d7e332c3442.json new file mode 100644 index 0000000000000..2d9cb01009f75 --- /dev/null +++ b/change/@fluentui-react-carousel-preview-37035160-0805-40b3-a10a-0d7e332c3442.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "feat: Handle nav disabled behavior and provide index based aria labels for next/prev buttons", + "packageName": "@fluentui/react-carousel-preview", + "email": "mifraser@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/react-components/react-carousel-preview/library/etc/react-carousel-preview.api.md b/packages/react-components/react-carousel-preview/library/etc/react-carousel-preview.api.md index cb2b770600fc5..a9f52840ca6e6 100644 --- a/packages/react-components/react-carousel-preview/library/etc/react-carousel-preview.api.md +++ b/packages/react-components/react-carousel-preview/library/etc/react-carousel-preview.api.md @@ -29,6 +29,30 @@ import { ToggleButtonState } from '@fluentui/react-button'; // @public export const Carousel: ForwardRefComponent; +// @public +export const CarouselAnnouncer: ForwardRefComponent; + +// @public (undocumented) +export const carouselAnnouncerClassNames: SlotClassNames; + +// @public +export type CarouselAnnouncerProps = Omit>, 'children'> & { + children: AnnouncerIndexRenderFunction; +}; + +// @public (undocumented) +export type CarouselAnnouncerSlots = { + root: Slot<'div'>; +}; + +// @public +export type CarouselAnnouncerState = ComponentState & { + renderAnnouncerChild: AnnouncerIndexRenderFunction; + totalSlides: number; + currentIndex: number; + slideGroupList: number[][]; +}; + // @public export const CarouselAutoplayButton: ForwardRefComponent; @@ -37,7 +61,6 @@ export const carouselAutoplayButtonClassNames: SlotClassNames & { - autoplayAriaLabel?: CarouselAutoplayAriaLabelFunction; onCheckedChange?: EventHandler; }; @@ -102,6 +125,7 @@ export type CarouselContextValue = { selectPageByIndex: (event: React_2.MouseEvent, value: number, jump?: boolean) => void; subscribeForValues: (listener: (data: CarouselUpdateData) => void) => () => void; enableAutoplay: (autoplay: boolean) => void; + containerRef?: React_2.RefObject; }; // @public @@ -246,6 +270,9 @@ export type NavButtonRenderFunction = (index: number) => React_2.ReactNode; // @public export const renderCarousel_unstable: (state: CarouselState, contextValues: CarouselContextValues) => JSX.Element; +// @public +export const renderCarouselAnnouncer_unstable: (state: CarouselAnnouncerState) => JSX.Element; + // @public export const renderCarouselAutoplayButton_unstable: (state: CarouselAutoplayButtonState) => JSX.Element; @@ -273,6 +300,12 @@ export const renderCarouselSlider_unstable: (state: CarouselSliderState, context // @public export function useCarousel_unstable(props: CarouselProps, ref: React_2.Ref): CarouselState; +// @public +export const useCarouselAnnouncer_unstable: (props: CarouselAnnouncerProps, ref: React_2.Ref) => CarouselAnnouncerState; + +// @public +export const useCarouselAnnouncerStyles_unstable: (state: CarouselAnnouncerState) => CarouselAnnouncerState; + // @public export const useCarouselAutoplayButton_unstable: (props: CarouselAutoplayButtonProps, ref: React_2.Ref) => CarouselAutoplayButtonState; diff --git a/packages/react-components/react-carousel-preview/library/src/CarouselAnnouncer.ts b/packages/react-components/react-carousel-preview/library/src/CarouselAnnouncer.ts new file mode 100644 index 0000000000000..8fdef89ad4ae3 --- /dev/null +++ b/packages/react-components/react-carousel-preview/library/src/CarouselAnnouncer.ts @@ -0,0 +1 @@ +export * from './components/CarouselAnnouncer/index'; diff --git a/packages/react-components/react-carousel-preview/library/src/components/Carousel/useCarousel.ts b/packages/react-components/react-carousel-preview/library/src/components/Carousel/useCarousel.ts index 36d2c92b68e6b..0c21b8e08b9e2 100644 --- a/packages/react-components/react-carousel-preview/library/src/components/Carousel/useCarousel.ts +++ b/packages/react-components/react-carousel-preview/library/src/components/Carousel/useCarousel.ts @@ -59,13 +59,15 @@ export function useCarousel_unstable(props: CarouselProps, ref: React.Ref `Slide ${index} of ${totalSlides}`; + +describe('CarouselAnnouncer', () => { + isConformant({ + Component: CarouselAnnouncer, + displayName: 'CarouselAnnouncer', + requiredProps: { + children: renderFunc, + }, + }); + + // TODO add more tests here, and create visual regression tests in /apps/vr-tests + + it('renders a default state', () => { + const result = render({renderFunc}); + expect(result.container).toMatchSnapshot(); + }); +}); diff --git a/packages/react-components/react-carousel-preview/library/src/components/CarouselAnnouncer/CarouselAnnouncer.tsx b/packages/react-components/react-carousel-preview/library/src/components/CarouselAnnouncer/CarouselAnnouncer.tsx new file mode 100644 index 0000000000000..cb77dd7128588 --- /dev/null +++ b/packages/react-components/react-carousel-preview/library/src/components/CarouselAnnouncer/CarouselAnnouncer.tsx @@ -0,0 +1,33 @@ +import * as React from 'react'; +import type { ForwardRefComponent } from '@fluentui/react-utilities'; +import { useCarouselAnnouncer_unstable } from './useCarouselAnnouncer'; +import { renderCarouselAnnouncer_unstable } from './renderCarouselAnnouncer'; +import { useCarouselAnnouncerStyles_unstable } from './useCarouselAnnouncerStyles.styles'; +import type { CarouselAnnouncerProps } from './CarouselAnnouncer.types'; + +/** + * CarouselAnnouncer component - This component will enable context for announcements of carousel page changes. + * + * It is recommended to provide a simple current out of total page index string. + * + * Slide group lists are also provided when multiple cards are present in a single slide. + */ +export const CarouselAnnouncer: ForwardRefComponent = React.forwardRef((props, ref) => { + const state = useCarouselAnnouncer_unstable(props, ref); + + useCarouselAnnouncerStyles_unstable(state); + + /** + * @see https://github.com/microsoft/fluentui/blob/master/docs/react-v9/contributing/rfcs/react-components/convergence/custom-styling.md + * + * TODO: 💡 once package will become stable (PR which will be part of promoting PREVIEW package to STABLE), + * - uncomment this line + * - update types {@link file://./../../../../../../../packages/react-components/react-shared-contexts/library/src/CustomStyleHooksContext/CustomStyleHooksContext.ts#CustomStyleHooksContextValue} + * - verify that custom global style override works for your component + */ + // useCustomStyleHook_unstable('useCarouselAnnouncerStyles_unstable')(state); + + return renderCarouselAnnouncer_unstable(state); +}); + +CarouselAnnouncer.displayName = 'CarouselAnnouncer'; diff --git a/packages/react-components/react-carousel-preview/library/src/components/CarouselAnnouncer/CarouselAnnouncer.types.ts b/packages/react-components/react-carousel-preview/library/src/components/CarouselAnnouncer/CarouselAnnouncer.types.ts new file mode 100644 index 0000000000000..d167c701fd956 --- /dev/null +++ b/packages/react-components/react-carousel-preview/library/src/components/CarouselAnnouncer/CarouselAnnouncer.types.ts @@ -0,0 +1,38 @@ +import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; + +export type CarouselAnnouncerSlots = { + root: Slot<'div'>; +}; + +export type AnnouncerIndexRenderFunction = (index: number, totalSlides: number, slideGroupList: number[][]) => string; +/** + * CarouselAnnouncer Props + */ +export type CarouselAnnouncerProps = Omit>, 'children'> & { + children: AnnouncerIndexRenderFunction; +}; + +/** + * State used in rendering CarouselAnnouncer + */ +export type CarouselAnnouncerState = ComponentState & { + /** + * The function that will render nav items based on total slides and their index. + */ + renderAnnouncerChild: AnnouncerIndexRenderFunction; + + /** + * The total number of slides passed in from carousel context. + */ + totalSlides: number; + + /** + * The current index passed in from carousel context. + */ + currentIndex: number; + + /** + * The list of cards in each slide based on index. + */ + slideGroupList: number[][]; +}; diff --git a/packages/react-components/react-carousel-preview/library/src/components/CarouselAnnouncer/__snapshots__/CarouselAnnouncer.test.tsx.snap b/packages/react-components/react-carousel-preview/library/src/components/CarouselAnnouncer/__snapshots__/CarouselAnnouncer.test.tsx.snap new file mode 100644 index 0000000000000..884d13d5d2952 --- /dev/null +++ b/packages/react-components/react-carousel-preview/library/src/components/CarouselAnnouncer/__snapshots__/CarouselAnnouncer.test.tsx.snap @@ -0,0 +1,12 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`CarouselAnnouncer renders a default state 1`] = ` +
+
+ Slide 0 of 0 +
+
+`; diff --git a/packages/react-components/react-carousel-preview/library/src/components/CarouselAnnouncer/index.ts b/packages/react-components/react-carousel-preview/library/src/components/CarouselAnnouncer/index.ts new file mode 100644 index 0000000000000..b0c5d6461d5b4 --- /dev/null +++ b/packages/react-components/react-carousel-preview/library/src/components/CarouselAnnouncer/index.ts @@ -0,0 +1,5 @@ +export * from './CarouselAnnouncer'; +export * from './CarouselAnnouncer.types'; +export * from './renderCarouselAnnouncer'; +export * from './useCarouselAnnouncer'; +export * from './useCarouselAnnouncerStyles.styles'; diff --git a/packages/react-components/react-carousel-preview/library/src/components/CarouselAnnouncer/renderCarouselAnnouncer.tsx b/packages/react-components/react-carousel-preview/library/src/components/CarouselAnnouncer/renderCarouselAnnouncer.tsx new file mode 100644 index 0000000000000..7ce2e9e72aa46 --- /dev/null +++ b/packages/react-components/react-carousel-preview/library/src/components/CarouselAnnouncer/renderCarouselAnnouncer.tsx @@ -0,0 +1,15 @@ +/** @jsxRuntime automatic */ +/** @jsxImportSource @fluentui/react-jsx-runtime */ + +import { assertSlots } from '@fluentui/react-utilities'; +import type { CarouselAnnouncerState, CarouselAnnouncerSlots } from './CarouselAnnouncer.types'; + +/** + * Render the final JSX of CarouselAnnouncer + */ +export const renderCarouselAnnouncer_unstable = (state: CarouselAnnouncerState) => { + const { renderAnnouncerChild, currentIndex, totalSlides, slideGroupList } = state; + assertSlots(state); + + return {renderAnnouncerChild(currentIndex, totalSlides, slideGroupList)}; +}; diff --git a/packages/react-components/react-carousel-preview/library/src/components/CarouselAnnouncer/useCarouselAnnouncer.ts b/packages/react-components/react-carousel-preview/library/src/components/CarouselAnnouncer/useCarouselAnnouncer.ts new file mode 100644 index 0000000000000..184e7ff5b0417 --- /dev/null +++ b/packages/react-components/react-carousel-preview/library/src/components/CarouselAnnouncer/useCarouselAnnouncer.ts @@ -0,0 +1,57 @@ +import * as React from 'react'; +import { getIntrinsicElementProps, slot, useIsomorphicLayoutEffect } from '@fluentui/react-utilities'; +import type { CarouselAnnouncerProps, CarouselAnnouncerState } from './CarouselAnnouncer.types'; +import { useCarouselContext_unstable as useCarouselContext } from '../CarouselContext'; + +/** + * Create the state required to render CarouselAnnouncer. + * + * The returned state can be modified with hooks such as useCarouselAnnouncerStyles_unstable, + * before being passed to renderCarouselAnnouncer_unstable. + * + * @param props - props from this instance of CarouselAnnouncer + * @param ref - reference to root HTMLDivElement of CarouselAnnouncer + */ +export const useCarouselAnnouncer_unstable = ( + props: CarouselAnnouncerProps, + ref: React.Ref, +): CarouselAnnouncerState => { + const [totalSlides, setTotalSlides] = React.useState(0); + const [slideGroupList, setSlideGroupList] = React.useState([[0]]); + const subscribeForValues = useCarouselContext(ctx => ctx.subscribeForValues); + const currentIndex = useCarouselContext(ctx => ctx.activeIndex); + + useIsomorphicLayoutEffect(() => { + return subscribeForValues(data => { + setTotalSlides(data.navItemsCount); + setSlideGroupList(data.groupIndexList); + }); + }, [subscribeForValues]); + + return { + totalSlides, + currentIndex, + slideGroupList, + renderAnnouncerChild: props.children, + // TODO add appropriate props/defaults + components: { + // TODO add each slot's element type or component + root: 'div', + }, + // TODO add appropriate slots, for example: + // mySlot: resolveShorthand(props.mySlot), + root: slot.always( + getIntrinsicElementProps('div', { + ref, + ...props, + children: null, + }), + { + elementType: 'div', + defaultProps: { + 'aria-live': 'polite', + }, + }, + ), + }; +}; diff --git a/packages/react-components/react-carousel-preview/library/src/components/CarouselAnnouncer/useCarouselAnnouncerStyles.styles.ts b/packages/react-components/react-carousel-preview/library/src/components/CarouselAnnouncer/useCarouselAnnouncerStyles.styles.ts new file mode 100644 index 0000000000000..867f4bb6b2431 --- /dev/null +++ b/packages/react-components/react-carousel-preview/library/src/components/CarouselAnnouncer/useCarouselAnnouncerStyles.styles.ts @@ -0,0 +1,35 @@ +import { makeStyles, mergeClasses } from '@griffel/react'; +import type { SlotClassNames } from '@fluentui/react-utilities'; +import type { CarouselAnnouncerSlots, CarouselAnnouncerState } from './CarouselAnnouncer.types'; + +export const carouselAnnouncerClassNames: SlotClassNames = { + root: 'fui-CarouselAnnouncer', +}; + +/** + * Styles for the root slot + * Hidden according to A11Y compatibility: https://www.a11yproject.com/posts/how-to-hide-content/ + */ +const useStyles = makeStyles({ + root: { + clip: 'rect(0 0 0 0)', + height: '0px', + overflow: 'hidden', + position: 'absolute', + width: '0px', + clipPath: 'inset(50%)', + whiteSpace: 'nowrap', + }, +}); + +/** + * Apply styling to the CarouselAnnouncer slots based on the state + */ +export const useCarouselAnnouncerStyles_unstable = (state: CarouselAnnouncerState): CarouselAnnouncerState => { + 'use no memo'; + + const styles = useStyles(); + state.root.className = mergeClasses(carouselAnnouncerClassNames.root, styles.root, state.root.className); + + return state; +}; diff --git a/packages/react-components/react-carousel-preview/library/src/components/CarouselAutoplayButton/CarouselAutoplayButton.types.ts b/packages/react-components/react-carousel-preview/library/src/components/CarouselAutoplayButton/CarouselAutoplayButton.types.ts index 4f31d889b087c..ae975afd3d075 100644 --- a/packages/react-components/react-carousel-preview/library/src/components/CarouselAutoplayButton/CarouselAutoplayButton.types.ts +++ b/packages/react-components/react-carousel-preview/library/src/components/CarouselAutoplayButton/CarouselAutoplayButton.types.ts @@ -21,11 +21,6 @@ export type CarouselAutoplayAriaLabelFunction = (autoplay: boolean) => string; */ export type CarouselAutoplayButtonProps = ToggleButtonProps & ComponentProps & { - /** - * Override aria label property to provide state - */ - autoplayAriaLabel?: CarouselAutoplayAriaLabelFunction; - /** * Callback that informs the user when internal autoplay value has changed */ diff --git a/packages/react-components/react-carousel-preview/library/src/components/CarouselAutoplayButton/useCarouselAutoplayButton.tsx b/packages/react-components/react-carousel-preview/library/src/components/CarouselAutoplayButton/useCarouselAutoplayButton.tsx index c479c00523d34..ad3a8e838eeeb 100644 --- a/packages/react-components/react-carousel-preview/library/src/components/CarouselAutoplayButton/useCarouselAutoplayButton.tsx +++ b/packages/react-components/react-carousel-preview/library/src/components/CarouselAutoplayButton/useCarouselAutoplayButton.tsx @@ -69,7 +69,6 @@ export const useCarouselAutoplayButton_unstable = ( renderByDefault: true, elementType: 'span', }), - 'aria-label': props.autoplayAriaLabel?.(autoplay), ...props, checked: autoplay, onClick: useEventCallback(mergeCallbacks(handleClick, props.onClick)), diff --git a/packages/react-components/react-carousel-preview/library/src/components/CarouselButton/useCarouselButton.tsx b/packages/react-components/react-carousel-preview/library/src/components/CarouselButton/useCarouselButton.tsx index 4e720279e3b4a..619acf561537b 100644 --- a/packages/react-components/react-carousel-preview/library/src/components/CarouselButton/useCarouselButton.tsx +++ b/packages/react-components/react-carousel-preview/library/src/components/CarouselButton/useCarouselButton.tsx @@ -1,12 +1,20 @@ import { type ARIAButtonElement } from '@fluentui/react-aria'; import { useButton_unstable } from '@fluentui/react-button'; import { ChevronLeftRegular, ChevronRightRegular } from '@fluentui/react-icons'; -import { mergeCallbacks, useEventCallback, slot, useIsomorphicLayoutEffect } from '@fluentui/react-utilities'; +import { + mergeCallbacks, + useEventCallback, + slot, + useIsomorphicLayoutEffect, + useMergedRefs, +} from '@fluentui/react-utilities'; import * as React from 'react'; import { useCarouselContext_unstable as useCarouselContext } from '../CarouselContext'; import type { CarouselButtonProps, CarouselButtonState } from './CarouselButton.types'; import type { CarouselUpdateData } from '../Carousel/Carousel.types'; +import { carouselButtonClassNames } from './useCarouselButtonStyles.styles'; +import { useRef } from 'react'; /** * Create the state required to render CarouselButton. @@ -26,12 +34,14 @@ export const useCarouselButton_unstable = ( // Locally tracks the total number of slides, will only update if this changes. const [totalSlides, setTotalSlides] = React.useState(0); + const buttonRef = useRef(); const circular = useCarouselContext(ctx => ctx.circular); + const containerRef = useCarouselContext(ctx => ctx.containerRef); const selectPageByDirection = useCarouselContext(ctx => ctx.selectPageByDirection); const subscribeForValues = useCarouselContext(ctx => ctx.subscribeForValues); const isTrailing = useCarouselContext(ctx => { - if (ctx.activeIndex === undefined || circular) { + if (circular) { return false; } @@ -47,7 +57,26 @@ export const useCarouselButton_unstable = ( return; } - selectPageByDirection(event, navType); + const nextIndex = selectPageByDirection(event, navType); + + let _trailing = false; + if (navType === 'prev') { + _trailing = nextIndex === 0; + } else { + _trailing = nextIndex === totalSlides - 1; + } + + if (!circular && _trailing && containerRef?.current) { + // Focus non-disabled element + const buttonRefs: NodeListOf = containerRef.current.querySelectorAll( + `.${carouselButtonClassNames.root}`, + ); + buttonRefs.forEach(_buttonRef => { + if (_buttonRef !== buttonRef.current) { + _buttonRef.focus(); + } + }); + } }; useIsomorphicLayoutEffect(() => { @@ -75,7 +104,7 @@ export const useCarouselButton_unstable = ( ...props, onClick: useEventCallback(mergeCallbacks(handleClick, props.onClick)), }, - ref as React.Ref, + useMergedRefs(ref, buttonRef) as React.Ref, ), }; }; diff --git a/packages/react-components/react-carousel-preview/library/src/components/CarouselContext.ts b/packages/react-components/react-carousel-preview/library/src/components/CarouselContext.ts index bd8082ba79bc1..ee039da12af22 100644 --- a/packages/react-components/react-carousel-preview/library/src/components/CarouselContext.ts +++ b/packages/react-components/react-carousel-preview/library/src/components/CarouselContext.ts @@ -19,6 +19,7 @@ export const carouselContextDefaultValue: CarouselContextValue = { /** noop */ }, circular: false, + containerRef: undefined, }; const CarouselContext = createContext(undefined); diff --git a/packages/react-components/react-carousel-preview/library/src/components/CarouselContext.types.ts b/packages/react-components/react-carousel-preview/library/src/components/CarouselContext.types.ts index ddc93081e4afb..934031a58c98b 100644 --- a/packages/react-components/react-carousel-preview/library/src/components/CarouselContext.types.ts +++ b/packages/react-components/react-carousel-preview/library/src/components/CarouselContext.types.ts @@ -26,9 +26,9 @@ export type CarouselContextValue = { value: number, jump?: boolean, ) => void; - subscribeForValues: (listener: (data: CarouselUpdateData) => void) => () => void; enableAutoplay: (autoplay: boolean) => void; + containerRef?: React.RefObject; }; /** diff --git a/packages/react-components/react-carousel-preview/library/src/components/useEmblaCarousel.ts b/packages/react-components/react-carousel-preview/library/src/components/useEmblaCarousel.ts index fef713e927bf4..db78fa2c09466 100644 --- a/packages/react-components/react-carousel-preview/library/src/components/useEmblaCarousel.ts +++ b/packages/react-components/react-carousel-preview/library/src/components/useEmblaCarousel.ts @@ -78,7 +78,7 @@ export function useEmblaCarousel( }; }, []); - const containerRef = React.useMemo(() => { + const containerRef: React.RefObject = React.useMemo(() => { let currentElement: HTMLDivElement | null = null; const handleIndexChange = () => { diff --git a/packages/react-components/react-carousel-preview/library/src/index.ts b/packages/react-components/react-carousel-preview/library/src/index.ts index 4ffbb2c560cff..d37fb9bd31517 100644 --- a/packages/react-components/react-carousel-preview/library/src/index.ts +++ b/packages/react-components/react-carousel-preview/library/src/index.ts @@ -86,3 +86,11 @@ export { } from './CarouselNavContainer'; export { carouselContextDefaultValue, CarouselProvider, useCarouselContext_unstable } from './CarouselContext'; export type { CarouselIndexChangeData, CarouselContextValue, CarouselContextValues } from './CarouselContext'; +export type { CarouselAnnouncerProps, CarouselAnnouncerSlots, CarouselAnnouncerState } from './CarouselAnnouncer'; +export { + CarouselAnnouncer, + carouselAnnouncerClassNames, + renderCarouselAnnouncer_unstable, + useCarouselAnnouncerStyles_unstable, + useCarouselAnnouncer_unstable, +} from './CarouselAnnouncer'; diff --git a/packages/react-components/react-carousel-preview/stories/src/Carousel/CarouselActionCards.stories.tsx b/packages/react-components/react-carousel-preview/stories/src/Carousel/CarouselActionCards.stories.tsx index e35310282e155..fb4e7d2a8d7df 100644 --- a/packages/react-components/react-carousel-preview/stories/src/Carousel/CarouselActionCards.stories.tsx +++ b/packages/react-components/react-carousel-preview/stories/src/Carousel/CarouselActionCards.stories.tsx @@ -12,6 +12,7 @@ import { import { MoreHorizontalRegular, DocumentLinkRegular } from '@fluentui/react-icons'; import { Carousel, + CarouselAnnouncer, CarouselCard, CarouselNav, CarouselNavButton, @@ -139,12 +140,12 @@ const POSTS: Post[] = [ }, ]; -const ActionCard: React.FC = props => { - const { avatarUrl, description, name, text } = props; +const ActionCard: React.FC = props => { + const { avatarUrl, description, name, text, index } = props; const classes = useCardClasses(); return ( - +
diff --git a/packages/react-components/react-carousel-preview/stories/src/Carousel/CarouselAutoplay.stories.tsx b/packages/react-components/react-carousel-preview/stories/src/Carousel/CarouselAutoplay.stories.tsx index c9eb7316565d6..7dcb3db106f97 100644 --- a/packages/react-components/react-carousel-preview/stories/src/Carousel/CarouselAutoplay.stories.tsx +++ b/packages/react-components/react-carousel-preview/stories/src/Carousel/CarouselAutoplay.stories.tsx @@ -1,6 +1,7 @@ import { Button, Field, Image, makeStyles, Switch, tokens, typographyStyles } from '@fluentui/react-components'; import { Carousel, + CarouselAnnouncer, CarouselAutoplayButtonProps, CarouselCard, CarouselNav, @@ -86,14 +87,17 @@ const IMAGES = [ 'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/sea-full-img.jpg', 'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/bridge-full-img.jpg', 'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/park-full-img.jpg', + 'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/sea-full-img.jpg', + 'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/bridge-full-img.jpg', + 'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/park-full-img.jpg', ]; -const BannerCard: React.FC<{ children: React.ReactNode; imageSrc: string }> = props => { - const { children, imageSrc } = props; +const BannerCard: React.FC<{ children: React.ReactNode; imageSrc: string; index: number }> = props => { + const { children, imageSrc, index } = props; const classes = useClasses(); return ( - +
@@ -119,7 +123,7 @@ export const Autoplay = () => { const autoplayProps: CarouselAutoplayButtonProps | undefined = autoplayButton ? { - autoplayAriaLabel: autoplay => (autoplay ? 'Enable Autoplay' : 'Disable Autoplay'), + 'aria-label': 'Enable autoplay', checked: autoplayEnabled, onCheckedChange: (e, data) => { setAutoplayEnabled(data.checked); @@ -140,8 +144,8 @@ export const Autoplay = () => {
- {IMAGES.concat(IMAGES).map((imageSrc, index) => ( - + {IMAGES.map((imageSrc, index) => ( + Card {index + 1} ))} @@ -149,11 +153,16 @@ export const Autoplay = () => { {index => } + + {(currentIndex, totalSlides, _slideGroupList) => { + return `Slide ${currentIndex + 1} of ${totalSlides}`; + }} + ); @@ -163,7 +172,7 @@ Autoplay.parameters = { docs: { description: { story: - 'The Autoplay button must be present to enable autoplay as it is an accessibility requirement. To enable, any valid prop (recommended autoplayAriaLabel) must be passed in, while setting the autoplay prop in CarouselNav to undefined will disable and remove it.', + 'The Autoplay button must be present to enable autoplay as it is an accessibility requirement. To enable, any valid prop (recommended ariaLabel) must be passed in, while setting the autoplay prop in CarouselNav to undefined will disable and remove it.', }, }, }; diff --git a/packages/react-components/react-carousel-preview/stories/src/Carousel/CarouselControlled.stories.tsx b/packages/react-components/react-carousel-preview/stories/src/Carousel/CarouselControlled.stories.tsx index be1efff3b5abe..bad23ec1a6105 100644 --- a/packages/react-components/react-carousel-preview/stories/src/Carousel/CarouselControlled.stories.tsx +++ b/packages/react-components/react-carousel-preview/stories/src/Carousel/CarouselControlled.stories.tsx @@ -126,19 +126,19 @@ export const Controlled = () => {
- + - + - + - + - + diff --git a/packages/react-components/react-carousel-preview/stories/src/Carousel/CarouselDefault.stories.tsx b/packages/react-components/react-carousel-preview/stories/src/Carousel/CarouselDefault.stories.tsx index 81485d38fcee6..47b913e616735 100644 --- a/packages/react-components/react-carousel-preview/stories/src/Carousel/CarouselDefault.stories.tsx +++ b/packages/react-components/react-carousel-preview/stories/src/Carousel/CarouselDefault.stories.tsx @@ -1,6 +1,7 @@ import { Button, Image, makeStyles, tokens, typographyStyles } from '@fluentui/react-components'; import { Carousel, + CarouselAnnouncer, CarouselCard, CarouselNav, CarouselNavButton, @@ -42,14 +43,17 @@ const IMAGES = [ 'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/sea-full-img.jpg', 'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/bridge-full-img.jpg', 'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/park-full-img.jpg', + 'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/sea-full-img.jpg', + 'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/bridge-full-img.jpg', + 'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/park-full-img.jpg', ]; -const BannerCard: React.FC<{ children: React.ReactNode; imageSrc: string }> = props => { - const { children, imageSrc } = props; +const BannerCard: React.FC<{ children: React.ReactNode; imageSrc: string; index: number }> = props => { + const { children, imageSrc, index } = props; const classes = useClasses(); return ( - +
@@ -71,8 +75,8 @@ const BannerCard: React.FC<{ children: React.ReactNode; imageSrc: string }> = pr export const Default = () => ( - {IMAGES.concat(IMAGES).map((imageSrc, index) => ( - + {IMAGES.map((imageSrc, index) => ( + Card {index + 1} ))} @@ -80,12 +84,17 @@ export const Default = () => ( (autoplay ? 'Enable Autoplay' : 'Disable Autoplay'), + 'aria-label': 'Enable autoplay', }} - next={{ 'aria-label': 'Go to next slide' }} - prev={{ 'aria-label': 'Go to prev slide' }} + next={{ 'aria-label': 'go to next' }} + prev={{ 'aria-label': 'go to prev' }} > {index => } + + {(currentIndex, totalSlides, _slideGroupList) => { + return `Slide ${currentIndex + 1} of ${totalSlides}`; + }} + ); diff --git a/packages/react-components/react-carousel-preview/stories/src/Carousel/CarouselImageBox.stories.tsx b/packages/react-components/react-carousel-preview/stories/src/Carousel/CarouselImageBox.stories.tsx index 143b50d98bb7a..ec49623452935 100644 --- a/packages/react-components/react-carousel-preview/stories/src/Carousel/CarouselImageBox.stories.tsx +++ b/packages/react-components/react-carousel-preview/stories/src/Carousel/CarouselImageBox.stories.tsx @@ -1,6 +1,7 @@ import { makeStyles, Image } from '@fluentui/react-components'; import { Carousel, + CarouselAnnouncer, CarouselCard, CarouselNav, CarouselNavContainer, @@ -67,8 +68,8 @@ export const ImageSlideshow = () => { return ( - {IMAGES.map(image => ( - + {IMAGES.map((image, index) => ( + ))} @@ -76,8 +77,8 @@ export const ImageSlideshow = () => { {index => ( @@ -88,6 +89,11 @@ export const ImageSlideshow = () => { )} + + {(currentIndex, totalSlides, _slideGroupList) => { + return `Slide ${currentIndex + 1} of ${totalSlides}`; + }} + ); }; diff --git a/packages/react-components/react-carousel-preview/stories/src/Carousel/CarouselResponsive.stories.tsx b/packages/react-components/react-carousel-preview/stories/src/Carousel/CarouselResponsive.stories.tsx index 4daeb4be15999..4ac7748faca0d 100644 --- a/packages/react-components/react-carousel-preview/stories/src/Carousel/CarouselResponsive.stories.tsx +++ b/packages/react-components/react-carousel-preview/stories/src/Carousel/CarouselResponsive.stories.tsx @@ -1,6 +1,7 @@ import { Body1, Caption1, makeStyles, mergeClasses, tokens, Title1, Subtitle2 } from '@fluentui/react-components'; import { Carousel, + CarouselAnnouncer, CarouselCard, CarouselNav, CarouselNavButton, @@ -86,7 +87,7 @@ export const Responsive = () => { return ( - + Lorem Ipsum @@ -94,37 +95,37 @@ export const Responsive = () => { - + Lorem Ipsum Lorem ipsum... - + Lorem Ipsum Lorem ipsum dolor sit amet... - + Lorem Ipsum Lorem ipsum dolor sit amet... - + Lorem Ipsum Lorem ipsum dolor sit amet... - + Lorem Ipsum Lorem ipsum dolor sit amet... - + Lorem Ipsum Lorem ipsum... @@ -132,13 +133,15 @@ export const Responsive = () => { - + {index => } + + + {(currentIndex, totalSlides, _slideGroupList) => { + return `Slide ${currentIndex + 1} of ${totalSlides}`; + }} + ); };