Skip to content

Commit df3355d

Browse files
dmytrokirpaclaudekhmakoto
authored
feat(react-headless-components-preview): add Persona component (#36102)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Makoto Morimoto <Humberto.Morimoto@microsoft.com>
1 parent 1012c9d commit df3355d

15 files changed

Lines changed: 285 additions & 1 deletion

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "patch",
3+
"comment": "feat: add Persona component",
4+
"packageName": "@fluentui/react-headless-components-preview",
5+
"email": "dmytrokirpa@microsoft.com",
6+
"dependentChangeType": "patch"
7+
}

packages/react-components/react-headless-components-preview/library/bundle-size/AllComponents.fixture.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import * as Input from '@fluentui/react-headless-components-preview/input';
1414
import * as Link from '@fluentui/react-headless-components-preview/link';
1515
import * as MessageBar from '@fluentui/react-headless-components-preview/message-bar';
1616
import * as ProgressBar from '@fluentui/react-headless-components-preview/progress-bar';
17+
import * as Persona from '@fluentui/react-headless-components-preview/persona';
1718
import * as Popover from '@fluentui/react-headless-components-preview/popover';
1819
import * as Provider from '@fluentui/react-headless-components-preview/provider';
1920
import * as RadioGroup from '@fluentui/react-headless-components-preview/radio-group';
@@ -48,8 +49,9 @@ console.log({
4849
Input,
4950
Link,
5051
MessageBar,
51-
ProgressBar,
52+
Persona,
5253
Popover,
54+
ProgressBar,
5355
Provider,
5456
RadioGroup,
5557
RatingDisplay,
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
## API Report File for "@fluentui/react-headless-components-preview"
2+
3+
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
4+
5+
```ts
6+
7+
import type { AvatarBaseProps } from '@fluentui/react-avatar';
8+
import type { ComponentProps } from '@fluentui/react-utilities';
9+
import type { ComponentState } from '@fluentui/react-utilities';
10+
import type { ForwardRefComponent } from '@fluentui/react-utilities';
11+
import type { JSXElement } from '@fluentui/react-utilities/';
12+
import type { PersonaProps as PersonaProps_2 } from '@fluentui/react-persona';
13+
import type { PersonaSlots as PersonaSlots_2 } from '@fluentui/react-persona';
14+
import type { PersonaState as PersonaState_2 } from '@fluentui/react-persona';
15+
import type * as React_2 from 'react';
16+
import type { Slot } from '@fluentui/react-utilities';
17+
18+
// @public
19+
export const Persona: ForwardRefComponent<PersonaProps>;
20+
21+
// @public
22+
export type PersonaProps = ComponentProps<PersonaSlots> & Pick<PersonaProps_2, 'name' | 'textPosition'>;
23+
24+
// @public (undocumented)
25+
export type PersonaSlots = Omit<PersonaSlots_2, 'avatar' | 'presence'> & {
26+
avatar?: Slot<typeof Avatar>;
27+
};
28+
29+
// @public
30+
export type PersonaState = ComponentState<PersonaSlots> & Pick<PersonaState_2, 'textPosition' | 'numTextLines'>;
31+
32+
// @public (undocumented)
33+
export const renderPersona: (state: PersonaState) => JSXElement;
34+
35+
// @public
36+
export const usePersona: (props: PersonaProps, ref: React_2.Ref<HTMLDivElement>) => PersonaState;
37+
38+
// (No @packageDocumentation comment for this package)
39+
40+
```

packages/react-components/react-headless-components-preview/library/package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,12 @@
159159
"import": "./lib/message-bar.js",
160160
"require": "./lib-commonjs/message-bar.js"
161161
},
162+
"./persona": {
163+
"types": "./dist/persona.d.ts",
164+
"node": "./lib-commonjs/persona.js",
165+
"import": "./lib/persona.js",
166+
"require": "./lib-commonjs/persona.js"
167+
},
162168
"./popover": {
163169
"types": "./dist/popover.d.ts",
164170
"node": "./lib-commonjs/popover.js",
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import * as React from 'react';
2+
import { render } from '@testing-library/react';
3+
import { isConformant } from '../../testing/isConformant';
4+
import { Persona } from './Persona';
5+
6+
describe('Persona', () => {
7+
isConformant({
8+
Component: Persona,
9+
displayName: 'Persona',
10+
});
11+
12+
it('renders a default state', () => {
13+
const { getByRole, getByText } = render(
14+
<Persona name="John Doe" primaryText="John Doe" secondaryText="Software Engineer" />,
15+
);
16+
17+
expect(getByRole('img', { name: 'John Doe' })).toBeInTheDocument();
18+
expect(getByText('John Doe')).toBeInTheDocument();
19+
expect(getByText('Software Engineer')).toBeInTheDocument();
20+
});
21+
22+
it('renders the avatar after the text when textPosition is before', () => {
23+
const { container, getByText, getByRole } = render(
24+
<Persona name="John Doe" primaryText="John Doe" secondaryText="Software Engineer" textPosition="before" />,
25+
);
26+
27+
const root = container.firstElementChild as HTMLElement;
28+
const primaryText = getByText('John Doe');
29+
const avatar = getByRole('img', { name: 'John Doe' });
30+
31+
expect(root.firstElementChild).toBe(primaryText);
32+
expect(root.lastElementChild).toBe(avatar);
33+
});
34+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
'use client';
2+
3+
import * as React from 'react';
4+
import type { ForwardRefComponent } from '@fluentui/react-utilities';
5+
import { usePersona } from './usePersona';
6+
import { renderPersona } from './renderPersona';
7+
import type { PersonaProps } from './Persona.types';
8+
9+
/**
10+
* Represents a person or with an avatar, primary text, and optional secondary text.
11+
*/
12+
export const Persona: ForwardRefComponent<PersonaProps> = React.forwardRef((props, ref) => {
13+
const state = usePersona(props, ref);
14+
15+
return renderPersona(state);
16+
});
17+
18+
Persona.displayName = 'Persona';
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type {
2+
PersonaSlots as PersonaBaseSlots,
3+
PersonaProps as PersonaBaseProps,
4+
PersonaState as PersonaBaseState,
5+
} from '@fluentui/react-persona';
6+
import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities';
7+
import type { Avatar } from '../Avatar';
8+
9+
export type PersonaSlots = Omit<PersonaBaseSlots, 'avatar' | 'presence'> & {
10+
avatar?: Slot<typeof Avatar>;
11+
};
12+
13+
/**
14+
* Persona Props
15+
*/
16+
export type PersonaProps = ComponentProps<PersonaSlots> & Pick<PersonaBaseProps, 'name' | 'textPosition'>;
17+
18+
/**
19+
* State used in rendering Persona
20+
*/
21+
export type PersonaState = ComponentState<PersonaSlots> & Pick<PersonaBaseState, 'textPosition' | 'numTextLines'>;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export { Persona } from './Persona';
2+
export type { PersonaSlots, PersonaProps, PersonaState } from './Persona.types';
3+
export { renderPersona } from './renderPersona';
4+
export { usePersona } from './usePersona';
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { renderPersona_unstable } from '@fluentui/react-persona';
2+
3+
import type { PersonaState } from './Persona.types';
4+
import type { JSXElement } from '@fluentui/react-utilities/';
5+
6+
export const renderPersona = renderPersona_unstable as (state: PersonaState) => JSXElement;
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
'use client';
2+
3+
import type * as React from 'react';
4+
import { usePersonaBase_unstable } from '@fluentui/react-persona';
5+
import { slot } from '@fluentui/react-utilities';
6+
import type { PersonaProps, PersonaState } from './Persona.types';
7+
import { Avatar } from '../Avatar';
8+
9+
/**
10+
* Create the state required to render Persona.
11+
*
12+
* The returned state can be modified with hooks such as usePersonaStyles_unstable,
13+
* before being passed to renderPersona_unstable.
14+
*
15+
* @param props - props from this instance of Persona
16+
* @param ref - reference to root HTMLDivElement of Persona
17+
*/
18+
export const usePersona = (props: PersonaProps, ref: React.Ref<HTMLDivElement>): PersonaState => {
19+
const { textPosition = 'after', ...baseProps } = props;
20+
const baseState = usePersonaBase_unstable(baseProps, ref);
21+
22+
return {
23+
...baseState,
24+
textPosition,
25+
components: {
26+
// eslint-disable-next-line @typescript-eslint/no-deprecated
27+
...baseState.components,
28+
avatar: Avatar,
29+
},
30+
avatar: slot.optional(props.avatar, {
31+
renderByDefault: true,
32+
defaultProps: {
33+
name: props.name,
34+
},
35+
elementType: Avatar,
36+
}),
37+
};
38+
};

0 commit comments

Comments
 (0)