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();
-
-
-
- }
- >
-
-
+ return (
+ <>
+
+ }
+ header={
+
+ Elvia Atkins mentioned you
+
+ }
+ description={5h ago · About us - Overview}
+ />
-
- }>Reply
-
-
+
+
+
+ }
+ >
+
+
+
+
+ }>Reply
+ }>Share
+
+
-
+
-
+ console.log('Test action')}>
+ }
+ header={
+
+ Mauricio August + 7 others edited
+
+ }
+ description={Artificial Intelligence Deck}
+ action={} />}
+ />
+
+
+
+
+ }
+ >
+
+
+
+
+ }>View changes
+
+
+ >
+ );
+};
+
+export const GridviewCard = () => {
+ const styles = useStyles();
+
+ return (
+
+
+
+
}
+ image={
}
header={
- Mauricio August + 7 others edited
+ Sales Analysis
}
- description={Artificial Intelligence Deck}
- action={}
+ description={Elvia replied to a comment}
+ action={} />}
/>
-
-
-
-
- }
- >
-
-
-
- } />}>
- }>View changes
-
- >
-);
-
-export const GridviewCard = () => (
-
-
-
-
- }
- 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,
+ },
+ });
+};