Skip to content

Commit

Permalink
feat(MessageBar): Initial implementation (#29313)
Browse files Browse the repository at this point in the history
* feat: Initial implementation

Adds an initial implementation of MessageBar that includes design for
different intents and multiline handling.

* remove TODOs

* fix multiline alignment

* fix slot type misalign

* fix tests

* rename to secondaryActions

* fix warnings

* break out into components

* remove stories

* revert tsconfig changes

* remove outdated keyborg cypress test

* add keyborg test again

* add tsconfig paths to cypress
  • Loading branch information
ling1726 authored Sep 28, 2023
1 parent d701af3 commit 9fd9f70
Show file tree
Hide file tree
Showing 47 changed files with 1,043 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,135 @@
```ts

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

import type { ComponentProps } from '@fluentui/react-utilities';
import type { ComponentState } from '@fluentui/react-utilities';
import type { ForwardRefComponent } from '@fluentui/react-utilities';
import * as React_2 from 'react';
import type { Slot } from '@fluentui/react-utilities';
import type { SlotClassNames } from '@fluentui/react-utilities';

// @public
export const MessageBar: ForwardRefComponent<MessageBarProps>;

// @public
export const MessageBarActions: ForwardRefComponent<MessageBarActionsProps>;

// @public (undocumented)
export const messageBarActionsClassNames: SlotClassNames<MessageBarActionsSlots>;

// @public
export type MessageBarActionsProps = ComponentProps<MessageBarActionsSlots> & {};

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

// @public
export type MessageBarActionsState = ComponentState<MessageBarActionsSlots> & Pick<Required<MessageBarContextValue>, 'layout'>;

// @public
export const MessageBarBody: ForwardRefComponent<MessageBarBodyProps>;

// @public (undocumented)
export const messageBarBodyClassNames: SlotClassNames<MessageBarBodySlots>;

// @public
export type MessageBarBodyProps = ComponentProps<MessageBarBodySlots> & {};

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

// @public
export type MessageBarBodyState = ComponentState<MessageBarBodySlots>;

// @public (undocumented)
export const messageBarClassNames: SlotClassNames<MessageBarSlots>;

// @public (undocumented)
export const MessageBarContextProvider: React_2.Provider<MessageBarContextValue | undefined>;

// @public (undocumented)
export type MessageBarContextValue = {
layout?: 'multiline' | 'singleline';
};

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

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

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

// @public
export const MessageBarTitle: ForwardRefComponent<MessageBarTitleProps>;

// @public (undocumented)
export const messageBarTitleClassNames: SlotClassNames<MessageBarTitleSlots>;

// @public
export type MessageBarTitleProps = ComponentProps<MessageBarTitleSlots> & {};

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

// @public
export type MessageBarTitleState = ComponentState<MessageBarTitleSlots>;

// @public
export const renderMessageBar_unstable: (state: MessageBarState, contexts: MessageBarContextValues) => JSX.Element;

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

// @public
export const renderMessageBarBody_unstable: (state: MessageBarBodyState) => JSX.Element;

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

// @public
export const useMessageBar_unstable: (props: MessageBarProps, ref: React_2.Ref<HTMLElement>) => MessageBarState;

// @public
export const useMessageBarActions_unstable: (props: MessageBarActionsProps, ref: React_2.Ref<HTMLElement>) => MessageBarActionsState;

// @public
export const useMessageBarActionsStyles_unstable: (state: MessageBarActionsState) => MessageBarActionsState;

// @public
export const useMessageBarBody_unstable: (props: MessageBarBodyProps, ref: React_2.Ref<HTMLElement>) => MessageBarBodyState;

// @public
export const useMessageBarBodyStyles_unstable: (state: MessageBarBodyState) => MessageBarBodyState;

// @public (undocumented)
export const useMessageBarContext: () => MessageBarContextValue;

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

// @public
export const useMessageBarTitle_unstable: (props: MessageBarTitleProps, ref: React_2.Ref<HTMLElement>) => MessageBarTitleState;

// @public
export const useMessageBarTitleStyles_unstable: (state: MessageBarTitleState) => MessageBarTitleState;

// (No @packageDocumentation comment for this package)

```
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
"@fluentui/scripts-tasks": "*"
},
"dependencies": {
"@fluentui/react-button": "^9.3.43",
"@fluentui/react-icons": "^2.0.217",
"@fluentui/react-jsx-runtime": "^9.0.12",
"@fluentui/react-theme": "^9.1.14",
"@fluentui/react-utilities": "^9.13.5",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './components/MessageBar/index';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './components/MessageBarActions/index';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './components/MessageBarBody/index';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './components/MessageBarTitle/index';
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import * as React from 'react';
import { render } from '@testing-library/react';
import { isConformant } from '../../testing/isConformant';
import { MessageBar } from './MessageBar';

describe('MessageBar', () => {
isConformant({
Component: MessageBar,
displayName: 'MessageBar',
testOptions: {
'has-static-classnames': [
{
props: {
icon: 'Icon',
},
},
],
},
});

// 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();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import * as React from 'react';
import type { ForwardRefComponent } from '@fluentui/react-utilities';
import { useMessageBar_unstable } from './useMessageBar';
import { renderMessageBar_unstable } from './renderMessageBar';
import { useMessageBarStyles_unstable } from './useMessageBarStyles.styles';
import type { MessageBarProps } from './MessageBar.types';
import { useMessageBarContextValue_unstable } from './useMessageBarContextValues';

/**
* MessageBar component
*/
export const MessageBar: ForwardRefComponent<MessageBarProps> = React.forwardRef((props, ref) => {
const state = useMessageBar_unstable(props, ref);

useMessageBarStyles_unstable(state);
return renderMessageBar_unstable(state, useMessageBarContextValue_unstable(state));
});

MessageBar.displayName = 'MessageBar';
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities';
import { MessageBarContextValue } from '../../contexts/messageBarContext';

export type MessageBarSlots = {
root: Slot<'div'>;
icon?: Slot<'div'>;
};

export type MessageBarContextValues = {
messageBar: MessageBarContextValue;
};

/**
* MessageBar Props
*/
export type MessageBarProps = ComponentProps<MessageBarSlots> &
Pick<MessageBarContextValue, 'layout'> & {
multiline?: boolean;
intent?: 'info' | 'success' | 'warning' | 'error';
};

/**
* State used in rendering MessageBar
*/
export type MessageBarState = ComponentState<MessageBarSlots> & Required<Pick<MessageBarProps, 'layout' | 'intent'>>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`MessageBar renders a default state 1`] = `
<div>
<div
class="fui-MessageBar"
>
<div
class="fui-MessageBar__icon"
>
<svg
aria-hidden="true"
class=""
fill="currentColor"
height="1em"
viewBox="0 0 20 20"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M18 10a8 8 0 1 0-16 0 8 8 0 0 0 16 0ZM9.5 8.91a.5.5 0 0 1 1 0V13.6a.5.5 0 0 1-1 0V8.9Zm-.25-2.16a.75.75 0 1 1 1.5 0 .75.75 0 0 1-1.5 0Z"
fill="currentColor"
/>
</svg>
</div>
Default MessageBar
</div>
</div>
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import * as React from 'react';
import { MessageBarProps } from './MessageBar.types';
import { CheckmarkCircleFilled, InfoFilled, WarningFilled, ErrorCircleFilled } from '@fluentui/react-icons';

export function getIntentIcon(intent: MessageBarProps['intent']) {
switch (intent) {
case 'info':
return <InfoFilled />;
case 'warning':
return <WarningFilled />;
case 'error':
return <ErrorCircleFilled />;
case 'success':
return <CheckmarkCircleFilled />;

default:
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * from './MessageBar';
export * from './MessageBar.types';
export * from './renderMessageBar';
export * from './useMessageBar';
export * from './useMessageBarStyles.styles';
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/** @jsxRuntime automatic */
/** @jsxImportSource @fluentui/react-jsx-runtime */

import { assertSlots } from '@fluentui/react-utilities';
import type { MessageBarState, MessageBarSlots, MessageBarContextValues } from './MessageBar.types';
import { MessageBarContextProvider } from '../../contexts/messageBarContext';

/**
* Render the final JSX of MessageBar
*/
export const renderMessageBar_unstable = (state: MessageBarState, contexts: MessageBarContextValues) => {
assertSlots<MessageBarSlots>(state);

return (
<MessageBarContextProvider value={contexts.messageBar}>
<state.root>
{state.icon && <state.icon />}
{state.root.children}
</state.root>
</MessageBarContextProvider>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import * as React from 'react';
import { getNativeElementProps, slot } from '@fluentui/react-utilities';
import type { MessageBarProps, MessageBarState } from './MessageBar.types';
import { getIntentIcon } from './getIntentIcon';

/**
* Create the state required to render MessageBar.
*
* The returned state can be modified with hooks such as useMessageBarStyles_unstable,
* before being passed to renderMessageBar_unstable.
*
* @param props - props from this instance of MessageBar
* @param ref - reference to root HTMLElement of MessageBar
*/
export const useMessageBar_unstable = (props: MessageBarProps, ref: React.Ref<HTMLElement>): MessageBarState => {
const { layout = 'singleline', intent = 'info' } = props;

return {
components: {
root: 'div',
icon: 'div',
},
root: slot.always(
getNativeElementProps('div', {
ref,
...props,
}),
{ elementType: 'div' },
),

icon: slot.optional(props.icon, {
renderByDefault: true,
elementType: 'div',
defaultProps: { children: getIntentIcon(intent) },
}),
layout,
intent,
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import * as React from 'react';
import { MessageBarContextValues, MessageBarState } from './MessageBar.types';

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

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

return {
messageBar: messageBarContext,
};
}
Loading

0 comments on commit 9fd9f70

Please sign in to comment.