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 @@ -62,6 +62,30 @@ export type MessageBarContextValue = {
layout?: 'multiline' | 'singleline' | 'auto';
};

// @public
export const MessageBarGroup: ForwardRefComponent<MessageBarGroupProps>;

// @public (undocumented)
export const messageBarGroupClassNames: SlotClassNames<MessageBarGroupSlots>;

// @public
export type MessageBarGroupProps = ComponentProps<MessageBarGroupSlots> & {
children: React_2.ReactElement[] | React_2.ReactElement;
animate?: 'exit-only' | 'both';
};

// @public (undocumented)
export type MessageBarGroupSlots = {
root: Slot<'div'>;
};

// @public
export type MessageBarGroupState = ComponentState<MessageBarGroupSlots> & Pick<MessageBarGroupProps, 'animate'> & {
enterStyles: string;
exitStyles: string;
children: React_2.ReactElement[];
};

// @public
export type MessageBarProps = ComponentProps<MessageBarSlots> & Pick<MessageBarContextValue, 'layout'> & {
intent?: 'info' | 'success' | 'warning' | 'error';
Expand All @@ -74,7 +98,9 @@ export type MessageBarSlots = {
};

// @public
export type MessageBarState = ComponentState<MessageBarSlots> & Required<Pick<MessageBarProps, 'layout' | 'intent'>>;
export type MessageBarState = ComponentState<MessageBarSlots> & Required<Pick<MessageBarProps, 'layout' | 'intent'>> & {
transitionClassName: string;
};

// @public
export const MessageBarTitle: ForwardRefComponent<MessageBarTitleProps>;
Expand Down Expand Up @@ -102,6 +128,9 @@ export const renderMessageBarActions_unstable: (state: MessageBarActionsState) =
// @public
export const renderMessageBarBody_unstable: (state: MessageBarBodyState) => JSX.Element;

// @public
export const renderMessageBarGroup_unstable: (state: MessageBarGroupState) => JSX.Element;

// @public
export const renderMessageBarTitle_unstable: (state: MessageBarTitleState) => JSX.Element;

Expand All @@ -123,6 +152,12 @@ export const useMessageBarBodyStyles_unstable: (state: MessageBarBodyState) => M
// @public (undocumented)
export const useMessageBarContext: () => MessageBarContextValue;

// @public
export const useMessageBarGroup_unstable: (props: MessageBarGroupProps, ref: React_2.Ref<HTMLElement>) => MessageBarGroupState;

// @public
export const useMessageBarGroupStyles_unstable: (state: MessageBarGroupState) => MessageBarGroupState;

// @public
export const useMessageBarStyles_unstable: (state: MessageBarState) => MessageBarState;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
"@fluentui/react-theme": "^9.1.14",
"@fluentui/react-utilities": "^9.13.5",
"@griffel/react": "^1.5.14",
"@swc/helpers": "^0.5.1"
"@swc/helpers": "^0.5.1",
"react-transition-group": "^4.4.1"
},
"peerDependencies": {
"@types/react": ">=16.8.0 <19.0.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './components/MessageBarGroup/index';
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities';
import { MessageBarContextValue } from '../../contexts/messageBarContext';
import type { MessageBarContextValue } from '../../contexts/messageBarContext';

export type MessageBarSlots = {
root: Slot<'div'>;
Expand All @@ -21,4 +21,7 @@ export type MessageBarProps = ComponentProps<MessageBarSlots> &
/**
* State used in rendering MessageBar
*/
export type MessageBarState = ComponentState<MessageBarSlots> & Required<Pick<MessageBarProps, 'layout' | 'intent'>>;
export type MessageBarState = ComponentState<MessageBarSlots> &
Required<Pick<MessageBarProps, 'layout' | 'intent'>> & {
transitionClassName: string;
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { getNativeElementProps, slot, useMergedRefs } from '@fluentui/react-util
import type { MessageBarProps, MessageBarState } from './MessageBar.types';
import { getIntentIcon } from './getIntentIcon';
import { useMessageBarReflow } from './useMessageBarReflow';
import { useMessageBarTransitionContext } from '../../contexts/messageBarTransitionContext';

/**
* Create the state required to render MessageBar.
Expand All @@ -15,11 +16,10 @@ import { useMessageBarReflow } from './useMessageBarReflow';
*/
export const useMessageBar_unstable = (props: MessageBarProps, ref: React.Ref<HTMLElement>): MessageBarState => {
const { layout = 'auto', intent = 'info' } = props;

const autoReflow = layout === 'auto';
const { ref: reflowRef, reflowing } = useMessageBarReflow(autoReflow);

const computedLayout = autoReflow ? (reflowing ? 'multiline' : 'singleline') : layout;
const { className: transitionClassName, nodeRef } = useMessageBarTransitionContext();

return {
components: {
Expand All @@ -28,7 +28,7 @@ export const useMessageBar_unstable = (props: MessageBarProps, ref: React.Ref<HT
},
root: slot.always(
getNativeElementProps('div', {
ref: useMergedRefs(ref, reflowRef),
ref: useMergedRefs(ref, reflowRef, nodeRef),
...props,
}),
{ elementType: 'div' },
Expand All @@ -41,5 +41,6 @@ export const useMessageBar_unstable = (props: MessageBarProps, ref: React.Ref<HT
}),
layout: computedLayout,
intent,
transitionClassName,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,14 @@ export const useMessageBarStyles_unstable = (state: MessageBarState): MessageBar
const iconBaseStyles = useIconBaseStyles();
const multilineStyles = useMultilineStyles();
const iconIntentStyles = useIconIntentStyles();
const rootIntntStyles = useRootIntentStyles();
const rootIntentStyles = useRootIntentStyles();

state.root.className = mergeClasses(
messageBarClassNames.root,
rootBaseStyles,
state.layout === 'multiline' && multilineStyles.rootMultiline,
rootIntntStyles[state.intent],
rootIntentStyles[state.intent],
state.transitionClassName,
state.root.className,
);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import * as React from 'react';
import { render } from '@testing-library/react';
import { isConformant } from '../../testing/isConformant';
import { MessageBarGroup } from './MessageBarGroup';

describe('MessageBarGroup', () => {
isConformant({
Component: MessageBarGroup,
displayName: 'MessageBarGroup',
});

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

it('renders a default state', () => {
const result = render(
<MessageBarGroup>
<span>Default MessageBarGroup</span>
</MessageBarGroup>,
);
expect(result.container).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as React from 'react';
import type { ForwardRefComponent } from '@fluentui/react-utilities';
import { useMessageBarGroup_unstable } from './useMessageBarGroup';
import { renderMessageBarGroup_unstable } from './renderMessageBarGroup';
import { useMessageBarGroupStyles_unstable } from './useMessageBarGroupStyles.styles';
import type { MessageBarGroupProps } from './MessageBarGroup.types';

/**
* MessageBarGroup component
*/
export const MessageBarGroup: ForwardRefComponent<MessageBarGroupProps> = React.forwardRef((props, ref) => {
const state = useMessageBarGroup_unstable(props, ref);

useMessageBarGroupStyles_unstable(state);
return renderMessageBarGroup_unstable(state);
});

MessageBarGroup.displayName = 'MessageBarGroup';
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities';
import * as React from 'react';

export type MessageBarGroupSlots = {
root: Slot<'div'>;
};

/**
* MessageBarGroup Props
*/
export type MessageBarGroupProps = ComponentProps<MessageBarGroupSlots> & {
children: React.ReactElement[] | React.ReactElement;
animate?: 'exit-only' | 'both';
};

/**
* State used in rendering MessageBarGroup
*/
export type MessageBarGroupState = ComponentState<MessageBarGroupSlots> &
Pick<MessageBarGroupProps, 'animate'> & {
enterStyles: string;
exitStyles: string;
children: React.ReactElement[];
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import * as React from 'react';
import { Transition, TransitionStatus } from 'react-transition-group';
import { MessageBarTransitionContextProvider } from '../../contexts/messageBarTransitionContext';
import { MessageBarGroupProps } from './MessageBarGroup.types';

const getClassName = (
status: TransitionStatus,
enterClassName: string,
exitClassName: string,
animate: MessageBarGroupProps['animate'],
) => {
switch (status) {
case 'entering':
case 'entered':
return animate === 'both' ? enterClassName : '';
case 'exiting':
case 'exited':
return exitClassName;
default:
return '';
}
};

/**
* Internal component that controls the animation transition for MessageBar components
* @internal
*/
export const MessageBarTransition: React.FC<{
children: React.ReactElement;
enterClassName: string;
exitClassName: string;
animate: MessageBarGroupProps['animate'];
}> = ({ children, enterClassName, exitClassName, animate, ...rest }) => {
const nodeRef = React.useRef<HTMLDivElement>(null);

return (
<Transition timeout={250} nodeRef={nodeRef} {...rest}>
{state => (
<MessageBarTransitionInner
animate={animate}
enterClassName={enterClassName}
exitClassName={exitClassName}
nodeRef={nodeRef}
state={state}
>
{children}
</MessageBarTransitionInner>
)}
</Transition>
);
};

const MessageBarTransitionInner: React.FC<{
children: React.ReactElement;
enterClassName: string;
exitClassName: string;
animate: MessageBarGroupProps['animate'];
nodeRef: React.Ref<HTMLDivElement | null>;
state: TransitionStatus;
}> = ({ children, state, enterClassName, exitClassName, animate, nodeRef }) => {
const className = getClassName(state, enterClassName, exitClassName, animate);
const context = React.useMemo(
() => ({
className,
nodeRef,
}),
[className, nodeRef],
);

return <MessageBarTransitionContextProvider value={context}>{children}</MessageBarTransitionContextProvider>;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`MessageBarGroup renders a default state 1`] = `
<div>
<div
class="fui-MessageBarGroup"
>
<span>
Default MessageBarGroup
</span>
</div>
</div>
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * from './MessageBarGroup';
export * from './MessageBarGroup.types';
export * from './renderMessageBarGroup';
export * from './useMessageBarGroup';
export * from './useMessageBarGroupStyles.styles';
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/** @jsxRuntime automatic */
/** @jsxImportSource @fluentui/react-jsx-runtime */

import { assertSlots } from '@fluentui/react-utilities';
import type { MessageBarGroupState, MessageBarGroupSlots } from './MessageBarGroup.types';
import { TransitionGroup } from 'react-transition-group';
import { MessageBarTransition } from './MessageBarTransition';

/**
* Render the final JSX of MessageBarGroup
*/
export const renderMessageBarGroup_unstable = (state: MessageBarGroupState) => {
assertSlots<MessageBarGroupSlots>(state);

return (
<state.root>
<TransitionGroup component={null}>
{state.children.map(child => (
<MessageBarTransition
animate={state.animate}
key={child.key}
enterClassName={state.enterStyles}
exitClassName={state.exitStyles}
>
{child}
</MessageBarTransition>
))}
</TransitionGroup>
</state.root>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import * as React from 'react';
import type { MessageBarGroupProps, MessageBarGroupState } from './MessageBarGroup.types';
import { getNativeElementProps, slot } from '@fluentui/react-utilities';

/**
* Create the state required to render MessageBarGroup.
*
* The returned state can be modified with hooks such as useMessageBarGroupStyles_unstable,
* before being passed to renderMessageBarGroup_unstable.
*
* @param props - props from this instance of MessageBarGroup
* @param ref - reference to root HTMLElement of MessageBarGroup
*/
export const useMessageBarGroup_unstable = (
props: MessageBarGroupProps,
ref: React.Ref<HTMLElement>,
): MessageBarGroupState => {
if (process.env.NODE_ENV !== 'production') {
React.Children.forEach(props.children, c => {
if (!React.isValidElement(c) || c.type === React.Fragment) {
throw new Error(
"MessageBarGroup: children must be valid MessageBar components. Please ensure you're not using fragments. ",
);
}
});
}

const children = React.Children.map(props.children ?? [], c =>
React.isValidElement(c) && c.type !== React.Fragment ? c : null,
).filter(Boolean);

return {
components: {
root: 'div',
},

root: slot.always(
getNativeElementProps('div', {
ref,
...props,
}),
{ elementType: 'div' },
),
children,
animate: props.animate ?? 'exit-only',
enterStyles: '',
exitStyles: '',
};
};
Loading