diff --git a/change/@fluentui-react-card-b42d28bf-e2ec-4352-8b18-5052df2d5aa2.json b/change/@fluentui-react-card-b42d28bf-e2ec-4352-8b18-5052df2d5aa2.json new file mode 100644 index 0000000000000..dddeaa19ea376 --- /dev/null +++ b/change/@fluentui-react-card-b42d28bf-e2ec-4352-8b18-5052df2d5aa2.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Add keyboard focus interactions", + "packageName": "@fluentui/react-card", + "email": "andredias@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-tabster-29b2dc96-c72a-490d-af5c-fa0e1e956863.json b/change/@fluentui-react-tabster-29b2dc96-c72a-490d-af5c-fa0e1e956863.json new file mode 100644 index 0000000000000..cabc686fe236a --- /dev/null +++ b/change/@fluentui-react-tabster-29b2dc96-c72a-490d-af5c-fa0e1e956863.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Add useFocusableGroup hook", + "packageName": "@fluentui/react-tabster", + "email": "andredias@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/react-card/package.json b/packages/react-card/package.json index 9c4361c6f0535..3c3863910d942 100644 --- a/packages/react-card/package.json +++ b/packages/react-card/package.json @@ -48,6 +48,7 @@ "dependencies": { "@fluentui/react-make-styles": "9.0.0-beta.1", "@fluentui/react-utilities": "9.0.0-beta.1", + "@fluentui/react-tabster": "9.0.0-beta.1", "tslib": "^2.1.0" }, "peerDependencies": { diff --git a/packages/react-card/src/components/Card/__snapshots__/Card.test.tsx.snap b/packages/react-card/src/components/Card/__snapshots__/Card.test.tsx.snap index 019836bae171a..ed3a5ce2a5e89 100644 --- a/packages/react-card/src/components/Card/__snapshots__/Card.test.tsx.snap +++ b/packages/react-card/src/components/Card/__snapshots__/Card.test.tsx.snap @@ -4,6 +4,7 @@ exports[`Card renders a default state 1`] = `
Default Card diff --git a/packages/react-card/src/components/Card/useCard.ts b/packages/react-card/src/components/Card/useCard.ts index 425dfb30261b2..47a21bd5f72aa 100644 --- a/packages/react-card/src/components/Card/useCard.ts +++ b/packages/react-card/src/components/Card/useCard.ts @@ -1,6 +1,7 @@ import * as React from 'react'; import { getNativeElementProps } from '@fluentui/react-utilities'; import type { CardProps, CardState } from './Card.types'; +import { useFocusableGroup, FocusableGroupTabBehavior } from '@fluentui/react-tabster'; /** * Create the state required to render Card. @@ -12,12 +13,17 @@ import type { CardProps, CardState } from './Card.types'; * @param ref - reference to root HTMLElement of Card */ export const useCard = (props: CardProps, ref: React.Ref): CardState => { + const groupperAttrs = useFocusableGroup({ + tabBehavior: FocusableGroupTabBehavior.LimitedTrapFocus, + }); + return { components: { root: 'div' }, root: getNativeElementProps(props.as || 'div', { ref, role: 'group', + ...groupperAttrs, ...props, }), }; diff --git a/packages/react-card/src/stories/Card.stories.tsx b/packages/react-card/src/stories/Card.stories.tsx index ff2a10608fb3d..8e39bcda3030a 100644 --- a/packages/react-card/src/stories/Card.stories.tsx +++ b/packages/react-card/src/stories/Card.stories.tsx @@ -4,15 +4,25 @@ import { Button } from '@fluentui/react-button'; import { Open16Regular, ArrowReply16Regular, - DismissSquare20Regular, MoreHorizontal16Regular, MoreVertical20Regular, + Share16Regular, } from '@fluentui/react-icons'; import { makeStyles } from '@fluentui/react-make-styles'; import { Card, CardFooter, CardHeader, CardPreview } from '../index'; const useStyles = makeStyles({ - root: { + actionCard: { + minWidth: '368px', + }, + gridViewCard: { + minWidth: '254px', + maxWidth: '368px', + }, + gray: { + color: 'gray', + }, + logo: { background: 'white', padding: '6px', width: '20px', @@ -27,90 +37,94 @@ const useStyles = makeStyles({ const LogoBackground = (props: React.HTMLAttributes) => { const styles = useStyles(); - return
{props.children}
; + return
{props.children}
; }; -export const ActionCard = () => ( - <> - console.log('Test action')}> - } - header={ - - Elvia Atkins mentioned you - - } - description={5h ago · About us - Overview} - /> +export const ActionCard = () => { + const styles = useStyles(); - - Microsoft Word logo - - } - > - Preview of a Word document - + return ( + <> + + } + header={ + + Elvia Atkins mentioned you + + } + description={5h ago · About us - Overview} + /> - - - - + + Microsoft Word logo + + } + > + Preview of a Word document + + + + + + + -
+
- + console.log('Test action')}> + } + header={ + + Mauricio August + 7 others edited + + } + description={Artificial Intelligence Deck} + action={ + + + + ); +}; + +export const GridviewCard = () => { + const styles = useStyles(); + + return ( + + + Preview of a sales slide deck + } + image={Microsoft PowerPoint logo} header={ - Mauricio August + 7 others edited + Sales Analysis } - description={Artificial Intelligence Deck} - action={} + description={Elvia replied to a comment} + action={ - - -); - -export const GridviewCard = () => ( - - - Preview of a sales slide deck - - } - header={ - - Sales Analysis - - } - description={Elvia replied to a comment} - action={} - /> - -); + ); +}; export default { title: 'Components/OfficeCard', diff --git a/packages/react-tabster/etc/react-tabster.api.md b/packages/react-tabster/etc/react-tabster.api.md index 4326d93868ba7..7d6f7ec888aac 100644 --- a/packages/react-tabster/etc/react-tabster.api.md +++ b/packages/react-tabster/etc/react-tabster.api.md @@ -24,6 +24,13 @@ export const createFocusOutlineStyle: (theme: Theme, options?: { style: Partial; } & CreateFocusIndicatorStyleRuleOptions) => MakeStyles; +// @public (undocumented) +export enum FocusableGroupTabBehavior { + Limited, + LimitedTrapFocus, + Unlimited +} + // @public (undocumented) export type FocusOutlineOffset = Record<'top' | 'bottom' | 'left' | 'right', string>; @@ -45,6 +52,14 @@ export interface UseArrowNavigationGroupOptions { memorizeCurrent?: boolean; } +// @public +export const useFocusableGroup: (options?: UseFocusableGroupOptions | undefined) => Types.TabsterDOMAttribute; + +// @public (undocumented) +export interface UseFocusableGroupOptions { + tabBehavior?: FocusableGroupTabBehavior; +} + // @public export const useFocusFinders: () => { findAllFocusable: (container: HTMLElement, acceptCondition: (el: HTMLElement) => boolean) => HTMLElement[]; diff --git a/packages/react-tabster/src/hooks/index.ts b/packages/react-tabster/src/hooks/index.ts index f8271c1854741..72f74c994b2f7 100644 --- a/packages/react-tabster/src/hooks/index.ts +++ b/packages/react-tabster/src/hooks/index.ts @@ -4,3 +4,4 @@ export * from './useModalAttributes'; export * from './useTabsterAttributes'; export * from './useFocusIndicatorStyle'; export * from './useKeyboardNavAttribute'; +export * from './useFocusableGroup'; diff --git a/packages/react-tabster/src/hooks/useFocusableGroup.ts b/packages/react-tabster/src/hooks/useFocusableGroup.ts new file mode 100644 index 0000000000000..5542050b08acb --- /dev/null +++ b/packages/react-tabster/src/hooks/useFocusableGroup.ts @@ -0,0 +1,43 @@ +import { Types, getGroupper } from 'tabster'; +import { useTabsterAttributes } from './useTabsterAttributes'; +import { useTabster } from './useTabster'; + +export enum FocusableGroupTabBehavior { + /** + * Tab will cycle into and out of the groupper content. + */ + Unlimited = Types.GroupperTabbabilities.Unlimited, + /** + * Tab will cycle out of the container, but not into it. + */ + Limited = Types.GroupperTabbabilities.Limited, + /** + * Tab only cycles the inner elements. + */ + LimitedTrapFocus = Types.GroupperTabbabilities.LimitedTrapFocus, +} + +export interface UseFocusableGroupOptions { + /** + * Type of TAB key interaction. + */ + tabBehavior?: FocusableGroupTabBehavior; +} + +/** + * A hook that returns the necessary tabster attributes to support groupping. + * @param options - Options to configure keyboard navigation + */ +export const useFocusableGroup = (options?: UseFocusableGroupOptions) => { + const tabster = useTabster(); + + if (tabster) { + getGroupper(tabster); + } + + return useTabsterAttributes({ + groupper: { + tabbability: options?.tabBehavior as Types.GroupperTabbability, + }, + }); +};