Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

/// <reference types="react" />

import type { ButtonContextValue } from '@fluentui/react-button';
import type { ComponentProps } from '@fluentui/react-utilities';
import type { ComponentState } from '@fluentui/react-utilities';
import type { ForwardRefComponent } from '@fluentui/react-utilities';
Expand Down Expand Up @@ -59,7 +60,9 @@ export const MessageBarContextProvider: React_2.Provider<MessageBarContextValue

// @public (undocumented)
export type MessageBarContextValue = {
layout?: 'multiline' | 'singleline' | 'auto';
layout: 'multiline' | 'singleline' | 'auto';
actionsRef: React_2.MutableRefObject<HTMLDivElement | null>;
bodyRef: React_2.MutableRefObject<HTMLDivElement | null>;
};

// @public
Expand Down Expand Up @@ -87,8 +90,9 @@ export type MessageBarGroupState = ComponentState<MessageBarGroupSlots> & Pick<M
};

// @public
export type MessageBarProps = ComponentProps<MessageBarSlots> & Pick<MessageBarContextValue, 'layout'> & {
export type MessageBarProps = ComponentProps<MessageBarSlots> & Pick<Partial<MessageBarContextValue>, 'layout'> & {
intent?: 'info' | 'success' | 'warning' | 'error';
politeness?: 'assertive' | 'polite';
};

// @public (undocumented)
Expand All @@ -100,6 +104,8 @@ export type MessageBarSlots = {
// @public
export type MessageBarState = ComponentState<MessageBarSlots> & Required<Pick<MessageBarProps, 'layout' | 'intent'>> & {
transitionClassName: string;
actionsRef: React_2.MutableRefObject<HTMLDivElement | null>;
bodyRef: React_2.MutableRefObject<HTMLDivElement | null>;
};

// @public
Expand All @@ -123,7 +129,7 @@ export type MessageBarTitleState = ComponentState<MessageBarTitleSlots>;
export const renderMessageBar_unstable: (state: MessageBarState, contexts: MessageBarContextValues) => JSX.Element;

// @public
export const renderMessageBarActions_unstable: (state: MessageBarActionsState) => JSX.Element;
export const renderMessageBarActions_unstable: (state: MessageBarActionsState, contexts: MessageBarActionsContextValues) => JSX.Element;

// @public
export const renderMessageBarBody_unstable: (state: MessageBarBodyState) => JSX.Element;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import * as React from 'react';
import { render } from '@testing-library/react';
import { isConformant } from '../../testing/isConformant';
import { MessageBar } from './MessageBar';
import { AnnounceProvider_unstable } from '@fluentui/react-shared-contexts';
import { MessageBarBody } from '../MessageBarBody/MessageBarBody';
import { MessageBarTitle } from '../MessageBarTitle/MessageBarTitle';
import { MessageBarActions } from '../MessageBarActions/MessageBarActions';

describe('MessageBar', () => {
beforeAll(() => {
Expand Down Expand Up @@ -33,10 +37,52 @@ describe('MessageBar', () => {
},
});

// TODO add more tests here, and create visual regression tests in /apps/vr-tests

it('renders a default state', () => {
const result = render(<MessageBar>Default MessageBar</MessageBar>);
expect(result.container).toMatchSnapshot();
});

it.each([
['assertive', 'error'] as const,
['assertive', 'warning'] as const,
['assertive', 'success'] as const,
['polite', 'info'] as const,
])('should announce %s with %s intent', (politeness, intent) => {
const announce = jest.fn();
render(
<AnnounceProvider_unstable value={{ announce }}>
<MessageBar intent={intent}>
<MessageBarBody>
<MessageBarTitle>Title</MessageBarTitle>Body
</MessageBarBody>
</MessageBar>
</AnnounceProvider_unstable>,
);

expect(announce).toHaveBeenCalledTimes(1);
expect(announce).toHaveBeenCalledWith('TitleBody', {
alert: politeness === 'assertive',
polite: politeness === 'polite',
});
});

it('should announce actions', () => {
const announce = jest.fn();
render(
<AnnounceProvider_unstable value={{ announce }}>
<MessageBar>
<MessageBarBody>
<MessageBarTitle>Title</MessageBarTitle>Body
</MessageBarBody>
<MessageBarActions containerAction={<button>Container action</button>}>
<button>Action 1</button>
<button>Action 2</button>
</MessageBarActions>
</MessageBar>
</AnnounceProvider_unstable>,
);

expect(announce).toHaveBeenCalledTimes(1);
expect(announce).toHaveBeenCalledWith('TitleBody,Action 1Action 2', expect.anything());
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities';
import type { MessageBarContextValue } from '../../contexts/messageBarContext';
import * as React from 'react';

export type MessageBarSlots = {
root: Slot<'div'>;
Expand All @@ -14,8 +15,9 @@ export type MessageBarContextValues = {
* MessageBar Props
*/
export type MessageBarProps = ComponentProps<MessageBarSlots> &
Pick<MessageBarContextValue, 'layout'> & {
Pick<Partial<MessageBarContextValue>, 'layout'> & {
intent?: 'info' | 'success' | 'warning' | 'error';
politeness?: 'assertive' | 'polite';
};

/**
Expand All @@ -24,4 +26,6 @@ export type MessageBarProps = ComponentProps<MessageBarSlots> &
export type MessageBarState = ComponentState<MessageBarSlots> &
Required<Pick<MessageBarProps, 'layout' | 'intent'>> & {
transitionClassName: string;
actionsRef: React.MutableRefObject<HTMLDivElement | null>;
bodyRef: React.MutableRefObject<HTMLDivElement | null>;
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as React from 'react';
import { getNativeElementProps, slot, useMergedRefs } from '@fluentui/react-utilities';
import { useAnnounce_unstable } from '@fluentui/react-shared-contexts';
import type { MessageBarProps, MessageBarState } from './MessageBar.types';
import { getIntentIcon } from './getIntentIcon';
import { useMessageBarReflow } from './useMessageBarReflow';
Expand All @@ -15,11 +16,23 @@ import { useMessageBarTransitionContext } from '../../contexts/messageBarTransit
* @param ref - reference to root HTMLElement of MessageBar
*/
export const useMessageBar_unstable = (props: MessageBarProps, ref: React.Ref<HTMLElement>): MessageBarState => {
const { layout = 'auto', intent = 'info' } = props;
const { layout = 'auto', intent = 'info', politeness } = props;
const computedPolitness = politeness ?? intent === 'info' ? 'polite' : 'assertive';
const autoReflow = layout === 'auto';
const { ref: reflowRef, reflowing } = useMessageBarReflow(autoReflow);
const computedLayout = autoReflow ? (reflowing ? 'multiline' : 'singleline') : layout;
const { className: transitionClassName, nodeRef } = useMessageBarTransitionContext();
const actionsRef = React.useRef<HTMLDivElement | null>(null);
const bodyRef = React.useRef<HTMLDivElement | null>(null);
const { announce } = useAnnounce_unstable();

React.useEffect(() => {
const bodyMessage = bodyRef.current?.textContent;
const actionsMessage = actionsRef.current?.textContent;

const message = [bodyMessage, actionsMessage].filter(Boolean).join(',');
announce(message, { polite: computedPolitness === 'polite', alert: computedPolitness === 'assertive' });
}, [bodyRef, actionsRef, announce, computedPolitness]);

return {
components: {
Expand All @@ -42,5 +55,7 @@ export const useMessageBar_unstable = (props: MessageBarProps, ref: React.Ref<HT
layout: computedLayout,
intent,
transitionClassName,
actionsRef,
bodyRef,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import * as React from 'react';
import { MessageBarContextValues, MessageBarState } from './MessageBar.types';

export function useMessageBarContextValue_unstable(state: MessageBarState): MessageBarContextValues {
const { layout } = state;
const { layout, actionsRef, bodyRef } = state;

const messageBarContext = React.useMemo(
() => ({
layout,
actionsRef,
bodyRef,
}),
[layout],
[layout, actionsRef, bodyRef],
);

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useMessageBarActions_unstable } from './useMessageBarActions';
import { renderMessageBarActions_unstable } from './renderMessageBarActions';
import { useMessageBarActionsStyles_unstable } from './useMessageBarActionsStyles.styles';
import type { MessageBarActionsProps } from './MessageBarActions.types';
import { useMessageBarActionsContextValue_unstable } from './useMessageBarActionsContextValues';

/**
* MessageBarActions component
Expand All @@ -12,7 +13,7 @@ export const MessageBarActions: ForwardRefComponent<MessageBarActionsProps> = Re
const state = useMessageBarActions_unstable(props, ref);

useMessageBarActionsStyles_unstable(state);
return renderMessageBarActions_unstable(state);
return renderMessageBarActions_unstable(state, useMessageBarActionsContextValue_unstable());
});

MessageBarActions.displayName = 'MessageBarActions';
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities';
import { MessageBarContextValue } from '../../contexts/messageBarContext';
import type { ButtonContextValue } from '@fluentui/react-button';
import type { MessageBarContextValue } from '../../contexts/messageBarContext';

export type MessageBarActionsSlots = {
root: Slot<'div'>;
containerAction?: Slot<'div'>;
};

export type MessageBarActionsContextValues = {
button: ButtonContextValue;
};

/**
* MessageBarActions Props
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,32 @@
/** @jsxImportSource @fluentui/react-jsx-runtime */

import { assertSlots } from '@fluentui/react-utilities';
import type { MessageBarActionsState, MessageBarActionsSlots } from './MessageBarActions.types';
import type {
MessageBarActionsState,
MessageBarActionsSlots,
MessageBarActionsContextValues,
} from './MessageBarActions.types';
import { ButtonContextProvider } from '@fluentui/react-button';

/**
* Render the final JSX of MessageBarActions
*/
export const renderMessageBarActions_unstable = (state: MessageBarActionsState) => {
export const renderMessageBarActions_unstable = (
state: MessageBarActionsState,
contexts: MessageBarActionsContextValues,
) => {
assertSlots<MessageBarActionsSlots>(state);
if (state.layout === 'multiline') {
return (
<ButtonContextProvider value={{ size: 'small' }}>
<ButtonContextProvider value={contexts.button}>
{state.containerAction && <state.containerAction />}
<state.root />
</ButtonContextProvider>
);
}

return (
<ButtonContextProvider value={{ size: 'small' }}>
<ButtonContextProvider value={contexts.button}>
<state.root />
{state.containerAction && <state.containerAction />}
</ButtonContextProvider>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import { getNativeElementProps, slot } from '@fluentui/react-utilities';
import { getNativeElementProps, slot, useMergedRefs } from '@fluentui/react-utilities';
import type { MessageBarActionsProps, MessageBarActionsState } from './MessageBarActions.types';
import { useMessageBarContext } from '../../contexts/messageBarContext';

Expand All @@ -16,7 +16,7 @@ export const useMessageBarActions_unstable = (
props: MessageBarActionsProps,
ref: React.Ref<HTMLElement>,
): MessageBarActionsState => {
const { layout = 'singleline' } = useMessageBarContext();
const { layout = 'singleline', actionsRef } = useMessageBarContext();
return {
components: {
root: 'div',
Expand All @@ -25,7 +25,7 @@ export const useMessageBarActions_unstable = (
containerAction: slot.optional(props.containerAction, { renderByDefault: false, elementType: 'div' }),
root: slot.always(
getNativeElementProps('div', {
ref,
ref: useMergedRefs(ref, actionsRef),
...props,
}),
{ elementType: 'div' },
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import * as React from 'react';
import { MessageBarActionsContextValues } from './MessageBarActions.types';

export function useMessageBarActionsContextValue_unstable(): MessageBarActionsContextValues {
const buttonContext = React.useMemo(
() => ({
size: 'small' as const,
}),
[],
);

return {
button: buttonContext,
};
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from 'react';
import { getNativeElementProps, slot } from '@fluentui/react-utilities';
import { getNativeElementProps, slot, useMergedRefs } from '@fluentui/react-utilities';
import type { MessageBarBodyProps, MessageBarBodyState } from './MessageBarBody.types';
import { useMessageBarContext } from '../../contexts/messageBarContext';

/**
* Create the state required to render MessageBarBody.
Expand All @@ -15,13 +16,14 @@ export const useMessageBarBody_unstable = (
props: MessageBarBodyProps,
ref: React.Ref<HTMLElement>,
): MessageBarBodyState => {
const { bodyRef } = useMessageBarContext();
return {
components: {
root: 'div',
},
root: slot.always(
getNativeElementProps('div', {
ref,
ref: useMergedRefs(ref, bodyRef),
...props,
}),
{ elementType: 'div' },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import * as React from 'react';

export type MessageBarContextValue = {
layout?: 'multiline' | 'singleline' | 'auto';
layout: 'multiline' | 'singleline' | 'auto';
actionsRef: React.MutableRefObject<HTMLDivElement | null>;
bodyRef: React.MutableRefObject<HTMLDivElement | null>;
};
const messageBarContext = React.createContext<MessageBarContextValue | undefined>(undefined);

export const messageBarContextDefaultValue: MessageBarContextValue = {
layout: 'singleline',
actionsRef: React.createRef(),
bodyRef: React.createRef(),
};

export const MessageBarContextProvider = messageBarContext.Provider;
Expand Down