diff --git a/change/@fluentui-react-avatar-8bdbaa84-a19a-41b4-8e84-12aaaf55bb24.json b/change/@fluentui-react-avatar-8bdbaa84-a19a-41b4-8e84-12aaaf55bb24.json new file mode 100644 index 0000000000000..24b392b1a54ac --- /dev/null +++ b/change/@fluentui-react-avatar-8bdbaa84-a19a-41b4-8e84-12aaaf55bb24.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "feat: Implement avatar context for slot overrides", + "packageName": "@fluentui/react-avatar", + "email": "lingfangao@hotmail.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-table-f485043e-7e31-4a75-844c-3cecdcd998d6.json b/change/@fluentui-react-table-f485043e-7e31-4a75-844c-3cecdcd998d6.json new file mode 100644 index 0000000000000..240cb7d2f3c56 --- /dev/null +++ b/change/@fluentui-react-table-f485043e-7e31-4a75-844c-3cecdcd998d6.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "feat: Use AvatarContext to override avatar size", + "packageName": "@fluentui/react-table", + "email": "lingfangao@hotmail.com", + "dependentChangeType": "patch" +} diff --git a/packages/react-components/react-avatar/etc/react-avatar.api.md b/packages/react-components/react-avatar/etc/react-avatar.api.md index 5c2ada78a5e84..24e29dceca7fc 100644 --- a/packages/react-components/react-avatar/etc/react-avatar.api.md +++ b/packages/react-components/react-avatar/etc/react-avatar.api.md @@ -27,6 +27,15 @@ export const Avatar: ForwardRefComponent; // @public (undocumented) export const avatarClassNames: SlotClassNames; +// @internal (undocumented) +export const AvatarContextProvider: React_2.Provider; + +// @internal (undocumented) +export interface AvatarContextValue { + // (undocumented) + size?: AvatarSizes; +} + // @public export const AvatarGroup: ForwardRefComponent; @@ -178,6 +187,9 @@ export const renderAvatarGroupPopover_unstable: (state: AvatarGroupPopoverState, // @public (undocumented) export const useAvatar_unstable: (props: AvatarProps, ref: React_2.Ref) => AvatarState; +// @internal (undocumented) +export const useAvatarContext: () => AvatarContextValue; + // @public export const useAvatarGroup_unstable: (props: AvatarGroupProps, ref: React_2.Ref) => AvatarGroupState; diff --git a/packages/react-components/react-avatar/src/components/Avatar/Avatar.types.ts b/packages/react-components/react-avatar/src/components/Avatar/Avatar.types.ts index 7e4370ed34050..1474b46dda127 100644 --- a/packages/react-components/react-avatar/src/components/Avatar/Avatar.types.ts +++ b/packages/react-components/react-avatar/src/components/Avatar/Avatar.types.ts @@ -1,6 +1,11 @@ import { PresenceBadge } from '@fluentui/react-badge'; import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; +/** + * Sizes for the avatar + */ +export type AvatarSizes = 16 | 20 | 24 | 28 | 32 | 36 | 40 | 48 | 56 | 64 | 72 | 96 | 120 | 128; + export type AvatarSlots = { root: Slot<'span'>; @@ -69,11 +74,6 @@ export type AvatarNamedColor = | 'platinum' | 'anchor'; -/** - * Sizes that can be used for the Avatar - */ -export type AvatarSizes = 16 | 20 | 24 | 28 | 32 | 36 | 40 | 48 | 56 | 64 | 72 | 96 | 120 | 128; - /** * Properties for Avatar */ diff --git a/packages/react-components/react-avatar/src/components/Avatar/useAvatar.tsx b/packages/react-components/react-avatar/src/components/Avatar/useAvatar.tsx index 96cb7692c5a27..7fda29c056d86 100644 --- a/packages/react-components/react-avatar/src/components/Avatar/useAvatar.tsx +++ b/packages/react-components/react-avatar/src/components/Avatar/useAvatar.tsx @@ -5,6 +5,7 @@ import type { AvatarNamedColor, AvatarProps, AvatarState } from './Avatar.types' import { PersonRegular } from '@fluentui/react-icons'; import { PresenceBadge } from '@fluentui/react-badge'; import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts'; +import { useAvatarContext } from '../../contexts/AvatarContext'; export const DEFAULT_STRINGS = { active: 'active', @@ -13,7 +14,15 @@ export const DEFAULT_STRINGS = { export const useAvatar_unstable = (props: AvatarProps, ref: React.Ref): AvatarState => { const { dir } = useFluent(); - const { name, size = 32, shape = 'circular', active = 'unset', activeAppearance = 'ring', idForColor } = props; + const { size: contextSize } = useAvatarContext(); + const { + name, + size = contextSize ?? (32 as const), + shape = 'circular', + active = 'unset', + activeAppearance = 'ring', + idForColor, + } = props; let { color = 'neutral' } = props; // Resolve 'colorful' to a specific color name diff --git a/packages/react-components/react-avatar/src/contexts/AvatarContext.ts b/packages/react-components/react-avatar/src/contexts/AvatarContext.ts new file mode 100644 index 0000000000000..39bb4d84c93f8 --- /dev/null +++ b/packages/react-components/react-avatar/src/contexts/AvatarContext.ts @@ -0,0 +1,23 @@ +import * as React from 'react'; +import { AvatarSizes } from '../components/Avatar/Avatar.types'; + +const avatarContext = React.createContext(undefined); + +/** + * @internal + */ +export interface AvatarContextValue { + size?: AvatarSizes; +} + +const avatarContextDefaultValue: AvatarContextValue = {}; + +/** + * @internal + */ +export const AvatarContextProvider = avatarContext.Provider; + +/** + * @internal + */ +export const useAvatarContext = () => React.useContext(avatarContext) ?? avatarContextDefaultValue; diff --git a/packages/react-components/react-avatar/src/contexts/index.ts b/packages/react-components/react-avatar/src/contexts/index.ts index 5316e465d3303..9a81f44d0634d 100644 --- a/packages/react-components/react-avatar/src/contexts/index.ts +++ b/packages/react-components/react-avatar/src/contexts/index.ts @@ -1 +1,2 @@ export * from './AvatarGroupContext'; +export * from './AvatarContext'; diff --git a/packages/react-components/react-avatar/src/index.ts b/packages/react-components/react-avatar/src/index.ts index b49aba63355af..0dd61eebe1028 100644 --- a/packages/react-components/react-avatar/src/index.ts +++ b/packages/react-components/react-avatar/src/index.ts @@ -39,4 +39,10 @@ export { useAvatarGroupPopover_unstable, } from './AvatarGroupPopover'; export type { AvatarGroupPopoverProps, AvatarGroupPopoverSlots, AvatarGroupPopoverState } from './AvatarGroupPopover'; -export { AvatarGroupProvider, useAvatarGroupContext_unstable } from './contexts/index'; +export { + AvatarGroupProvider, + useAvatarGroupContext_unstable, + AvatarContextProvider, + useAvatarContext, +} from './contexts/index'; +export type { AvatarContextValue } from './contexts/index'; diff --git a/packages/react-components/react-table/etc/react-table.api.md b/packages/react-components/react-table/etc/react-table.api.md index 32023ee3e96a1..ffe8ca6bf919c 100644 --- a/packages/react-components/react-table/etc/react-table.api.md +++ b/packages/react-components/react-table/etc/react-table.api.md @@ -7,6 +7,7 @@ /// import { ARIAButtonSlotProps } from '@fluentui/react-aria'; +import type { AvatarSizes } from '@fluentui/react-avatar'; import type { Checkbox } from '@fluentui/react-checkbox'; import type { CheckboxProps } from '@fluentui/react-checkbox'; import type { ComponentProps } from '@fluentui/react-utilities'; @@ -40,7 +41,7 @@ export const renderTableCell_unstable: (state: TableCellState) => JSX.Element; export const renderTableCellActions_unstable: (state: TableCellActionsState) => JSX.Element; // @public -export const renderTableCellLayout_unstable: (state: TableCellLayoutState) => JSX.Element; +export const renderTableCellLayout_unstable: (state: TableCellLayoutState, contextValues: TableCellLayoutContextValues) => JSX.Element; // @public export const renderTableHeader_unstable: (state: TableHeaderState) => JSX.Element; @@ -138,7 +139,9 @@ export type TableCellLayoutSlots = { }; // @public -export type TableCellLayoutState = ComponentState & Pick; +export type TableCellLayoutState = ComponentState & Pick & { + avatarSize: AvatarSizes | undefined; +}; // @public export type TableCellProps = ComponentProps & {}; diff --git a/packages/react-components/react-table/package.json b/packages/react-components/react-table/package.json index f34ddcb6a2e02..8760a8c0ef860 100644 --- a/packages/react-components/react-table/package.json +++ b/packages/react-components/react-table/package.json @@ -31,6 +31,7 @@ }, "dependencies": { "@fluentui/react-aria": "^9.2.0", + "@fluentui/react-avatar": "^9.1.1", "@fluentui/react-checkbox": "^9.0.6", "@fluentui/react-icons": "^2.0.175", "@fluentui/react-tabster": "^9.1.1", diff --git a/packages/react-components/react-table/src/components/TableCellLayout/TableCellLayout.tsx b/packages/react-components/react-table/src/components/TableCellLayout/TableCellLayout.tsx index dd3368d98c97e..59c7638e1afc1 100644 --- a/packages/react-components/react-table/src/components/TableCellLayout/TableCellLayout.tsx +++ b/packages/react-components/react-table/src/components/TableCellLayout/TableCellLayout.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { useTableCellLayout_unstable } from './useTableCellLayout'; import { renderTableCellLayout_unstable } from './renderTableCellLayout'; import { useTableCellLayoutStyles_unstable } from './useTableCellLayoutStyles'; +import { useTableCellLayoutContextValues_unstable } from './useTableCellLayoutContextValues'; import type { TableCellLayoutProps } from './TableCellLayout.types'; import type { ForwardRefComponent } from '@fluentui/react-utilities'; @@ -12,7 +13,7 @@ export const TableCellLayout: ForwardRefComponent = React. const state = useTableCellLayout_unstable(props, ref); useTableCellLayoutStyles_unstable(state); - return renderTableCellLayout_unstable(state); + return renderTableCellLayout_unstable(state, useTableCellLayoutContextValues_unstable(state)); }); TableCellLayout.displayName = 'TableCellLayout'; diff --git a/packages/react-components/react-table/src/components/TableCellLayout/TableCellLayout.types.ts b/packages/react-components/react-table/src/components/TableCellLayout/TableCellLayout.types.ts index 0238a65776cdf..1cae8d48f6f9f 100644 --- a/packages/react-components/react-table/src/components/TableCellLayout/TableCellLayout.types.ts +++ b/packages/react-components/react-table/src/components/TableCellLayout/TableCellLayout.types.ts @@ -1,4 +1,11 @@ import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; +import type { AvatarSizes } from '@fluentui/react-avatar'; + +export type TableCellLayoutContextValues = { + avatar: { + size?: AvatarSizes; + }; +}; export type TableCellLayoutSlots = { root: Slot<'div'>; @@ -34,4 +41,5 @@ export type TableCellLayoutProps = ComponentProps> /** * State used in rendering TableCellLayout */ -export type TableCellLayoutState = ComponentState & Pick; +export type TableCellLayoutState = ComponentState & + Pick & { avatarSize: AvatarSizes | undefined }; diff --git a/packages/react-components/react-table/src/components/TableCellLayout/renderTableCellLayout.tsx b/packages/react-components/react-table/src/components/TableCellLayout/renderTableCellLayout.tsx index 26c059caee4af..e3f8d155d7a79 100644 --- a/packages/react-components/react-table/src/components/TableCellLayout/renderTableCellLayout.tsx +++ b/packages/react-components/react-table/src/components/TableCellLayout/renderTableCellLayout.tsx @@ -1,16 +1,24 @@ import * as React from 'react'; import { getSlots } from '@fluentui/react-utilities'; -import type { TableCellLayoutState, TableCellLayoutSlots } from './TableCellLayout.types'; +import { AvatarContextProvider } from '@fluentui/react-avatar'; +import type { TableCellLayoutState, TableCellLayoutSlots, TableCellLayoutContextValues } from './TableCellLayout.types'; /** * Render the final JSX of TableCellLayout */ -export const renderTableCellLayout_unstable = (state: TableCellLayoutState) => { +export const renderTableCellLayout_unstable = ( + state: TableCellLayoutState, + contextValues: TableCellLayoutContextValues, +) => { const { slots, slotProps } = getSlots(state); return ( - {slots.media && } + {slots.media && ( + + + + )} {slots.wrapper && ( {slots.main && {slotProps.root.children}} diff --git a/packages/react-components/react-table/src/components/TableCellLayout/useTableCellLayout.ts b/packages/react-components/react-table/src/components/TableCellLayout/useTableCellLayout.ts index a6a7c8647e134..e8c73c594f1dd 100644 --- a/packages/react-components/react-table/src/components/TableCellLayout/useTableCellLayout.ts +++ b/packages/react-components/react-table/src/components/TableCellLayout/useTableCellLayout.ts @@ -1,6 +1,13 @@ import * as React from 'react'; import { getNativeElementProps, resolveShorthand } from '@fluentui/react-utilities'; import type { TableCellLayoutProps, TableCellLayoutState } from './TableCellLayout.types'; +import { useTableContext } from '../../contexts/tableContext'; + +const tableAvatarSizeMap = { + medium: 32, + small: 24, + smaller: 20, +} as const; /** * Create the state required to render TableCellLayout. @@ -15,6 +22,8 @@ export const useTableCellLayout_unstable = ( props: TableCellLayoutProps, ref: React.Ref, ): TableCellLayoutState => { + const { size } = useTableContext(); + return { components: { root: 'div', @@ -29,5 +38,6 @@ export const useTableCellLayout_unstable = ( media: resolveShorthand(props.media), description: resolveShorthand(props.description), wrapper: resolveShorthand(props.wrapper, { required: !!props.description || !!props.children }), + avatarSize: tableAvatarSizeMap[size], }; }; diff --git a/packages/react-components/react-table/src/components/TableCellLayout/useTableCellLayoutContextValues.ts b/packages/react-components/react-table/src/components/TableCellLayout/useTableCellLayoutContextValues.ts new file mode 100644 index 0000000000000..271c0cbca8b0e --- /dev/null +++ b/packages/react-components/react-table/src/components/TableCellLayout/useTableCellLayoutContextValues.ts @@ -0,0 +1,17 @@ +import * as React from 'react'; +import type { TableCellLayoutState, TableCellLayoutContextValues } from './TableCellLayout.types'; + +export function useTableCellLayoutContextValues_unstable(state: TableCellLayoutState): TableCellLayoutContextValues { + const { avatarSize } = state; + + const avatar = React.useMemo( + () => ({ + size: avatarSize, + }), + [avatarSize], + ); + + return { + avatar, + }; +} diff --git a/packages/react-components/react-table/src/stories/Table/SizeSmall.stories.tsx b/packages/react-components/react-table/src/stories/Table/SizeSmall.stories.tsx index 63c172514a430..7f6558c7465e7 100644 --- a/packages/react-components/react-table/src/stories/Table/SizeSmall.stories.tsx +++ b/packages/react-components/react-table/src/stories/Table/SizeSmall.stories.tsx @@ -8,14 +8,14 @@ import { DocumentPdfRegular, VideoRegular, } from '@fluentui/react-icons'; -import { PresenceBadgeStatus, Avatar } from '@fluentui/react-components'; +import { Avatar } from '@fluentui/react-components'; import { TableBody, TableCell, TableRow, Table, TableHeader, TableHeaderCell } from '../..'; import { TableCellLayout } from '../../components/TableCellLayout/TableCellLayout'; const items = [ { file: { label: 'Meeting notes', icon: }, - author: { label: 'Max Mustermann', status: 'available' }, + author: { label: 'Max Mustermann', status: 'available' as const }, lastUpdated: { label: '7h ago', timestamp: 1 }, lastUpdate: { label: 'You edited this', @@ -24,7 +24,7 @@ const items = [ }, { file: { label: 'Thursday presentation', icon: }, - author: { label: 'Erika Mustermann', status: 'busy' }, + author: { label: 'Erika Mustermann', status: 'busy' as const }, lastUpdated: { label: 'Yesterday at 1:45 PM', timestamp: 2 }, lastUpdate: { label: 'You recently opened this', @@ -33,7 +33,7 @@ const items = [ }, { file: { label: 'Training recording', icon: }, - author: { label: 'John Doe', status: 'away' }, + author: { label: 'John Doe', status: 'away' as const }, lastUpdated: { label: 'Yesterday at 1:45 PM', timestamp: 2 }, lastUpdate: { label: 'You recently opened this', @@ -42,7 +42,7 @@ const items = [ }, { file: { label: 'Purchase order', icon: }, - author: { label: 'Jane Doe', status: 'offline' }, + author: { label: 'Jane Doe', status: 'offline' as const }, lastUpdated: { label: 'Tue at 9:30 AM', timestamp: 3 }, lastUpdate: { label: 'You shared this in a Teams chat', @@ -75,15 +75,7 @@ export const SizeSmall = () => { {item.file.label} - - } - > + }> {item.author.label} diff --git a/packages/react-components/react-table/src/stories/Table/SizeSmaller.stories.tsx b/packages/react-components/react-table/src/stories/Table/SizeSmaller.stories.tsx index 7d431c1cac5a2..1a4daabf1c913 100644 --- a/packages/react-components/react-table/src/stories/Table/SizeSmaller.stories.tsx +++ b/packages/react-components/react-table/src/stories/Table/SizeSmaller.stories.tsx @@ -8,13 +8,13 @@ import { DocumentPdfRegular, VideoRegular, } from '@fluentui/react-icons'; -import { PresenceBadgeStatus, Avatar } from '@fluentui/react-components'; +import { Avatar } from '@fluentui/react-components'; import { TableBody, TableCell, TableRow, Table, TableHeader, TableHeaderCell, TableCellLayout } from '../..'; const items = [ { file: { label: 'Meeting notes', icon: }, - author: { label: 'Max Mustermann', status: 'available' }, + author: { label: 'Max Mustermann', status: 'available' as const }, lastUpdated: { label: '7h ago', timestamp: 1 }, lastUpdate: { label: 'You edited this', @@ -23,7 +23,7 @@ const items = [ }, { file: { label: 'Thursday presentation', icon: }, - author: { label: 'Erika Mustermann', status: 'busy' }, + author: { label: 'Erika Mustermann', status: 'busy' as const }, lastUpdated: { label: 'Yesterday at 1:45 PM', timestamp: 2 }, lastUpdate: { label: 'You recently opened this', @@ -32,7 +32,7 @@ const items = [ }, { file: { label: 'Training recording', icon: }, - author: { label: 'John Doe', status: 'away' }, + author: { label: 'John Doe', status: 'away' as const }, lastUpdated: { label: 'Yesterday at 1:45 PM', timestamp: 2 }, lastUpdate: { label: 'You recently opened this', @@ -41,7 +41,7 @@ const items = [ }, { file: { label: 'Purchase order', icon: }, - author: { label: 'Jane Doe', status: 'offline' }, + author: { label: 'Jane Doe', status: 'offline' as const }, lastUpdated: { label: 'Tue at 9:30 AM', timestamp: 3 }, lastUpdate: { label: 'You shared this in a Teams chat', @@ -74,15 +74,7 @@ export const SizeSmaller = () => { {item.file.label} - - } - > + }> {item.author.label}