From 102759cf8a2a9a49b14e847be2d21f19a70fc76c Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Wed, 27 Sep 2023 09:06:54 +0000 Subject: [PATCH 01/17] feat: Initial implementation Adds an initial implementation of MessageBar that includes design for different intents and multiline handling. --- .../etc/react-message-bar-preview.api.md | 61 ++++++++ .../react-message-bar-preview/package.json | 2 + .../src/MessageBar.ts | 1 + .../src/MessageBarTitle.ts | 1 + .../components/MessageBar/MessageBar.test.tsx | 18 +++ .../src/components/MessageBar/MessageBar.tsx | 18 +++ .../components/MessageBar/MessageBar.types.ts | 23 +++ .../components/MessageBar/getIntentIcon.tsx | 19 +++ .../src/components/MessageBar/index.ts | 5 + .../MessageBar/renderMessageBar.tsx | 34 ++++ .../components/MessageBar/useMessageBar.ts | 45 ++++++ .../MessageBar/useMessageBarStyles.styles.ts | 146 ++++++++++++++++++ .../MessageBarTitle/MessageBarTitle.test.tsx | 18 +++ .../MessageBarTitle/MessageBarTitle.tsx | 18 +++ .../MessageBarTitle/MessageBarTitle.types.ts | 17 ++ .../src/components/MessageBarTitle/index.ts | 5 + .../MessageBarTitle/renderMessageBarTitle.tsx | 19 +++ .../MessageBarTitle/useMessageBarTitle.ts | 30 ++++ .../useMessageBarTitleStyles.styles.ts | 28 ++++ .../react-message-bar-preview/src/index.ts | 18 ++- .../stories/MessageBar/Default.stories.tsx | 30 ++++ .../MessageBar/MessageBarBestPractices.md | 5 + .../MessageBar/MessageBarDescription.md | 0 .../stories/MessageBar/Multiline.stories.tsx | 31 ++++ .../stories/MessageBar/index.stories.tsx | 19 +++ 25 files changed, 610 insertions(+), 1 deletion(-) create mode 100644 packages/react-components/react-message-bar-preview/src/MessageBar.ts create mode 100644 packages/react-components/react-message-bar-preview/src/MessageBarTitle.ts create mode 100644 packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.test.tsx create mode 100644 packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.tsx create mode 100644 packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.types.ts create mode 100644 packages/react-components/react-message-bar-preview/src/components/MessageBar/getIntentIcon.tsx create mode 100644 packages/react-components/react-message-bar-preview/src/components/MessageBar/index.ts create mode 100644 packages/react-components/react-message-bar-preview/src/components/MessageBar/renderMessageBar.tsx create mode 100644 packages/react-components/react-message-bar-preview/src/components/MessageBar/useMessageBar.ts create mode 100644 packages/react-components/react-message-bar-preview/src/components/MessageBar/useMessageBarStyles.styles.ts create mode 100644 packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/MessageBarTitle.test.tsx create mode 100644 packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/MessageBarTitle.tsx create mode 100644 packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/MessageBarTitle.types.ts create mode 100644 packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/index.ts create mode 100644 packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/renderMessageBarTitle.tsx create mode 100644 packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/useMessageBarTitle.ts create mode 100644 packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/useMessageBarTitleStyles.styles.ts create mode 100644 packages/react-components/react-message-bar-preview/stories/MessageBar/Default.stories.tsx create mode 100644 packages/react-components/react-message-bar-preview/stories/MessageBar/MessageBarBestPractices.md create mode 100644 packages/react-components/react-message-bar-preview/stories/MessageBar/MessageBarDescription.md create mode 100644 packages/react-components/react-message-bar-preview/stories/MessageBar/Multiline.stories.tsx create mode 100644 packages/react-components/react-message-bar-preview/stories/MessageBar/index.stories.tsx diff --git a/packages/react-components/react-message-bar-preview/etc/react-message-bar-preview.api.md b/packages/react-components/react-message-bar-preview/etc/react-message-bar-preview.api.md index e4cc48f2a1684..3ce4d5291f10b 100644 --- a/packages/react-components/react-message-bar-preview/etc/react-message-bar-preview.api.md +++ b/packages/react-components/react-message-bar-preview/etc/react-message-bar-preview.api.md @@ -4,6 +4,67 @@ ```ts +/// + +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'; + +// @public +export const MessageBar: ForwardRefComponent; + +// @public +export type MessageBarProps = ComponentProps & { + multiline?: boolean; + intent?: 'info' | 'success' | 'warning' | 'error'; +}; + +// @public (undocumented) +export type MessageBarSlots = { + root: Slot<'div'>; + icon?: Slot<'div'>; + action?: Slot<'div'>; + actions?: Slot<'div'>; + body?: Slot<'div'>; +}; + +// @public +export type MessageBarState = ComponentState & Required>; + +// @public +export const MessageBarTitle: ForwardRefComponent; + +// @public +export type MessageBarTitleProps = ComponentProps & {}; + +// @public (undocumented) +export type MessageBarTitleSlots = { + root: Slot<'span'>; +}; + +// @public +export type MessageBarTitleState = ComponentState; + +// @public +export const renderMessageBar_unstable: (state: MessageBarState) => JSX.Element; + +// @public +export const renderMessageBarTitle_unstable: (state: MessageBarTitleState) => JSX.Element; + +// @public +export const useMessageBar_unstable: (props: MessageBarProps, ref: React_2.Ref) => MessageBarState; + +// @public +export const useMessageBarStyles_unstable: (state: MessageBarState) => MessageBarState; + +// @public +export const useMessageBarTitle_unstable: (props: MessageBarTitleProps, ref: React_2.Ref) => MessageBarTitleState; + +// @public +export const useMessageBarTitleStyles_unstable: (state: MessageBarTitleState) => MessageBarTitleState; + // (No @packageDocumentation comment for this package) ``` diff --git a/packages/react-components/react-message-bar-preview/package.json b/packages/react-components/react-message-bar-preview/package.json index b851ab73ce444..f5b63f244d716 100644 --- a/packages/react-components/react-message-bar-preview/package.json +++ b/packages/react-components/react-message-bar-preview/package.json @@ -31,6 +31,8 @@ "@fluentui/scripts-tasks": "*" }, "dependencies": { + "@fluentui/react-button": "^9.3.41", + "@fluentui/react-icons": "^2.0.217", "@fluentui/react-jsx-runtime": "^9.0.10", "@fluentui/react-theme": "^9.1.12", "@fluentui/react-utilities": "^9.13.3", diff --git a/packages/react-components/react-message-bar-preview/src/MessageBar.ts b/packages/react-components/react-message-bar-preview/src/MessageBar.ts new file mode 100644 index 0000000000000..380da827afeaa --- /dev/null +++ b/packages/react-components/react-message-bar-preview/src/MessageBar.ts @@ -0,0 +1 @@ +export * from './components/MessageBar/index'; diff --git a/packages/react-components/react-message-bar-preview/src/MessageBarTitle.ts b/packages/react-components/react-message-bar-preview/src/MessageBarTitle.ts new file mode 100644 index 0000000000000..e570c6fb916df --- /dev/null +++ b/packages/react-components/react-message-bar-preview/src/MessageBarTitle.ts @@ -0,0 +1 @@ +export * from './components/MessageBarTitle/index'; diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.test.tsx b/packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.test.tsx new file mode 100644 index 0000000000000..f38c1f1c33ea8 --- /dev/null +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.test.tsx @@ -0,0 +1,18 @@ +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', + }); + + // TODO add more tests here, and create visual regression tests in /apps/vr-tests + + it('renders a default state', () => { + const result = render(Default MessageBar); + expect(result.container).toMatchSnapshot(); + }); +}); diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.tsx b/packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.tsx new file mode 100644 index 0000000000000..c6e3220bc668a --- /dev/null +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.tsx @@ -0,0 +1,18 @@ +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'; + +/** + * MessageBar component - TODO: add more docs + */ +export const MessageBar: ForwardRefComponent = React.forwardRef((props, ref) => { + const state = useMessageBar_unstable(props, ref); + + useMessageBarStyles_unstable(state); + return renderMessageBar_unstable(state); +}); + +MessageBar.displayName = 'MessageBar'; diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.types.ts b/packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.types.ts new file mode 100644 index 0000000000000..91c379989667d --- /dev/null +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.types.ts @@ -0,0 +1,23 @@ +import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; + +export type MessageBarSlots = { + root: Slot<'div'>; + icon?: Slot<'div'>; + action?: Slot<'div'>; + actions?: Slot<'div'>; + body?: Slot<'div'>; +}; + +/** + * MessageBar Props + */ +export type MessageBarProps = ComponentProps & { + multiline?: boolean; + intent?: 'info' | 'success' | 'warning' | 'error'; +}; + +/** + * State used in rendering MessageBar + */ +export type MessageBarState = ComponentState & + Required>; diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBar/getIntentIcon.tsx b/packages/react-components/react-message-bar-preview/src/components/MessageBar/getIntentIcon.tsx new file mode 100644 index 0000000000000..8b8f345ce332f --- /dev/null +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBar/getIntentIcon.tsx @@ -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 ; + case 'warning': + return ; + case 'error': + return ; + case 'success': + return ; + + default: + return null; + } +} diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBar/index.ts b/packages/react-components/react-message-bar-preview/src/components/MessageBar/index.ts new file mode 100644 index 0000000000000..1b87b685202c5 --- /dev/null +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBar/index.ts @@ -0,0 +1,5 @@ +export * from './MessageBar'; +export * from './MessageBar.types'; +export * from './renderMessageBar'; +export * from './useMessageBar'; +export * from './useMessageBarStyles.styles'; diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBar/renderMessageBar.tsx b/packages/react-components/react-message-bar-preview/src/components/MessageBar/renderMessageBar.tsx new file mode 100644 index 0000000000000..f2a797861eae6 --- /dev/null +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBar/renderMessageBar.tsx @@ -0,0 +1,34 @@ +/** @jsxRuntime automatic */ +/** @jsxImportSource @fluentui/react-jsx-runtime */ + +import { assertSlots } from '@fluentui/react-utilities'; +import type { MessageBarState, MessageBarSlots } from './MessageBar.types'; +import { ButtonContextProvider } from '@fluentui/react-button'; + +/** + * Render the final JSX of MessageBar + */ +export const renderMessageBar_unstable = (state: MessageBarState) => { + assertSlots(state); + + return ( + + {state.icon && } + + + {state.multiline ? ( + <> + {state.action && } + {state.actions && } + + ) : ( + <> + {state.actions && } + {state.actions && } + {state.action && } + + )} + + + ); +}; diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBar/useMessageBar.ts b/packages/react-components/react-message-bar-preview/src/components/MessageBar/useMessageBar.ts new file mode 100644 index 0000000000000..7554edad43e2b --- /dev/null +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBar/useMessageBar.ts @@ -0,0 +1,45 @@ +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): MessageBarState => { + const { multiline = false, intent = 'info' } = props; + + return { + components: { + root: 'div', + icon: 'div', + action: 'div', + actions: 'div', + body: 'div', + }, + root: slot.always( + getNativeElementProps('div', { + ref, + ...props, + }), + { elementType: 'div' }, + ), + + body: slot.always(props.body ?? {}, { elementType: 'div', defaultProps: { children: props.children } }), + icon: slot.optional(props.icon, { + renderByDefault: true, + elementType: 'div', + defaultProps: { children: getIntentIcon(intent) }, + }), + action: slot.optional(props.action, { renderByDefault: false, elementType: 'div' }), + actions: slot.optional(props.actions, { renderByDefault: false, elementType: 'div' }), + multiline, + intent, + }; +}; diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBar/useMessageBarStyles.styles.ts b/packages/react-components/react-message-bar-preview/src/components/MessageBar/useMessageBarStyles.styles.ts new file mode 100644 index 0000000000000..caa767c1847f4 --- /dev/null +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBar/useMessageBarStyles.styles.ts @@ -0,0 +1,146 @@ +import { makeResetStyles, makeStyles, mergeClasses, shorthands } from '@griffel/react'; +import { tokens, typographyStyles } from '@fluentui/react-theme'; +import type { SlotClassNames } from '@fluentui/react-utilities'; +import type { MessageBarSlots, MessageBarState } from './MessageBar.types'; + +export const messageBarClassNames: SlotClassNames = { + root: 'fui-MessageBar', + icon: 'fui-MessageBar__icon', + action: 'fui-MessageBar__action', + actions: 'fui-MessageBar__actions', + body: 'fui-MessageBar__body', +}; + +const useRootBaseStyles = makeResetStyles({ + display: 'grid', + gridTemplateColumns: 'auto 1fr auto auto', + gridTemplateAreas: '"icon body actions action"', + ...shorthands.padding('0', tokens.spacingHorizontalM), + ...shorthands.border(tokens.strokeWidthThin, 'solid', tokens.colorNeutralStroke1), + ...shorthands.borderRadius(tokens.borderRadiusMedium), + alignItems: 'center', + minHeight: '36px', + boxSizing: 'border-box', + backgroundColor: tokens.colorNeutralBackground3, +}); + +const useIconBaseStyles = makeResetStyles({ + ...shorthands.gridArea('icon'), + fontSize: tokens.fontSizeBase500, + marginRight: tokens.spacingHorizontalS, + color: tokens.colorNeutralForeground3, +}); + +const useActionBaseStyles = makeResetStyles({ + ...shorthands.gridArea('action'), +}); + +const useActionsBaseStyles = makeResetStyles({ + ...shorthands.gridArea('actions'), + display: 'flex', + alignItems: 'center', + columnGap: tokens.spacingHorizontalM, + marginRight: tokens.spacingHorizontalM, +}); + +const useBodyBaseStyles = makeResetStyles({ + ...typographyStyles.body1, + ...shorthands.gridArea('body'), +}); + +const useMultilineStyles = makeStyles({ + rootMultiline: { + paddingTop: tokens.spacingVerticalMNudge, + gridTemplateColumns: 'auto 1fr auto', + gridTemplateAreas: ` + "icon body action" + "actions actions actions" + `, + }, + + actionsMultiline: { + justifyContent: 'end', + marginTop: tokens.spacingVerticalMNudge, + marginBottom: tokens.spacingVerticalS, + }, +}); + +const useIconIntentStyles = makeStyles({ + info: { + /** already in base reset styles */ + }, + error: { + color: tokens.colorStatusDangerForeground1, + }, + warning: { + color: tokens.colorStatusWarningForeground3, + }, + success: { + color: tokens.colorStatusSuccessForeground1, + }, +}); + +const useRootIntentStyles = makeStyles({ + info: { + /** already in base reset styles */ + }, + error: { + backgroundColor: tokens.colorStatusDangerBackground1, + ...shorthands.borderColor(tokens.colorStatusDangerBorder1), + }, + warning: { + backgroundColor: tokens.colorStatusWarningBackground1, + ...shorthands.borderColor(tokens.colorStatusWarningBorder1), + }, + success: { + backgroundColor: tokens.colorStatusSuccessBackground1, + ...shorthands.borderColor(tokens.colorStatusSuccessBorder1), + }, +}); + +/** + * Apply styling to the MessageBar slots based on the state + */ +export const useMessageBarStyles_unstable = (state: MessageBarState): MessageBarState => { + const rootBaseStyles = useRootBaseStyles(); + const iconBaseStyles = useIconBaseStyles(); + const actionBaseStyles = useActionBaseStyles(); + const actionsBaseStyles = useActionsBaseStyles(); + const bodyBaseStyles = useBodyBaseStyles(); + const multilineStyles = useMultilineStyles(); + const iconIntentStyles = useIconIntentStyles(); + const rootIntntStyles = useRootIntentStyles(); + state.root.className = mergeClasses( + messageBarClassNames.root, + rootBaseStyles, + state.multiline && multilineStyles.rootMultiline, + rootIntntStyles[state.intent], + state.root.className, + ); + + state.body.className = mergeClasses(messageBarClassNames.body, bodyBaseStyles, state.body.className); + + if (state.icon) { + state.icon.className = mergeClasses( + messageBarClassNames.icon, + iconBaseStyles, + iconIntentStyles[state.intent], + state.icon.className, + ); + } + + if (state.action) { + state.action.className = mergeClasses(messageBarClassNames.actions, actionBaseStyles, state.action.className); + } + + if (state.actions) { + state.actions.className = mergeClasses( + messageBarClassNames.action, + actionsBaseStyles, + state.multiline && multilineStyles.actionsMultiline, + state.actions.className, + ); + } + + return state; +}; diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/MessageBarTitle.test.tsx b/packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/MessageBarTitle.test.tsx new file mode 100644 index 0000000000000..c34b6a2446dbc --- /dev/null +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/MessageBarTitle.test.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; +import { render } from '@testing-library/react'; +import { isConformant } from '../../testing/isConformant'; +import { MessageBarTitle } from './MessageBarTitle'; + +describe('MessageBarTitle', () => { + isConformant({ + Component: MessageBarTitle, + displayName: 'MessageBarTitle', + }); + + // TODO add more tests here, and create visual regression tests in /apps/vr-tests + + it('renders a default state', () => { + const result = render(Default MessageBarTitle); + expect(result.container).toMatchSnapshot(); + }); +}); diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/MessageBarTitle.tsx b/packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/MessageBarTitle.tsx new file mode 100644 index 0000000000000..a6a12e84462ae --- /dev/null +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/MessageBarTitle.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; +import type { ForwardRefComponent } from '@fluentui/react-utilities'; +import { useMessageBarTitle_unstable } from './useMessageBarTitle'; +import { renderMessageBarTitle_unstable } from './renderMessageBarTitle'; +import { useMessageBarTitleStyles_unstable } from './useMessageBarTitleStyles.styles'; +import type { MessageBarTitleProps } from './MessageBarTitle.types'; + +/** + * MessageBarTitle component - TODO: add more docs + */ +export const MessageBarTitle: ForwardRefComponent = React.forwardRef((props, ref) => { + const state = useMessageBarTitle_unstable(props, ref); + + useMessageBarTitleStyles_unstable(state); + return renderMessageBarTitle_unstable(state); +}); + +MessageBarTitle.displayName = 'MessageBarTitle'; diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/MessageBarTitle.types.ts b/packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/MessageBarTitle.types.ts new file mode 100644 index 0000000000000..89818edb55afc --- /dev/null +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/MessageBarTitle.types.ts @@ -0,0 +1,17 @@ +import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; + +export type MessageBarTitleSlots = { + root: Slot<'span'>; +}; + +/** + * MessageBarTitle Props + */ +export type MessageBarTitleProps = ComponentProps & {}; + +/** + * State used in rendering MessageBarTitle + */ +export type MessageBarTitleState = ComponentState; +// TODO: Remove semicolon from previous line, uncomment next line, and provide union of props to pick from MessageBarTitleProps. +// & Required> diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/index.ts b/packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/index.ts new file mode 100644 index 0000000000000..4bcb0e7df960b --- /dev/null +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/index.ts @@ -0,0 +1,5 @@ +export * from './MessageBarTitle'; +export * from './MessageBarTitle.types'; +export * from './renderMessageBarTitle'; +export * from './useMessageBarTitle'; +export * from './useMessageBarTitleStyles.styles'; diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/renderMessageBarTitle.tsx b/packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/renderMessageBarTitle.tsx new file mode 100644 index 0000000000000..6b008739d5dcb --- /dev/null +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/renderMessageBarTitle.tsx @@ -0,0 +1,19 @@ +/** @jsxRuntime automatic */ +/** @jsxImportSource @fluentui/react-jsx-runtime */ + +import { assertSlots } from '@fluentui/react-utilities'; +import type { MessageBarTitleState, MessageBarTitleSlots } from './MessageBarTitle.types'; + +/** + * Render the final JSX of MessageBarTitle + */ +export const renderMessageBarTitle_unstable = (state: MessageBarTitleState) => { + assertSlots(state); + + // TODO Add additional slots in the appropriate place + return ( + <> + + + ); +}; diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/useMessageBarTitle.ts b/packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/useMessageBarTitle.ts new file mode 100644 index 0000000000000..ffa1883ffdb0c --- /dev/null +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/useMessageBarTitle.ts @@ -0,0 +1,30 @@ +import * as React from 'react'; +import { getNativeElementProps, slot } from '@fluentui/react-utilities'; +import type { MessageBarTitleProps, MessageBarTitleState } from './MessageBarTitle.types'; + +/** + * Create the state required to render MessageBarTitle. + * + * The returned state can be modified with hooks such as useMessageBarTitleStyles_unstable, + * before being passed to renderMessageBarTitle_unstable. + * + * @param props - props from this instance of MessageBarTitle + * @param ref - reference to root HTMLElement of MessageBarTitle + */ +export const useMessageBarTitle_unstable = ( + props: MessageBarTitleProps, + ref: React.Ref, +): MessageBarTitleState => { + return { + components: { + root: 'span', + }, + root: slot.always( + getNativeElementProps('span', { + ref, + ...props, + }), + { elementType: 'div' }, + ), + }; +}; diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/useMessageBarTitleStyles.styles.ts b/packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/useMessageBarTitleStyles.styles.ts new file mode 100644 index 0000000000000..7bc54ff127ac8 --- /dev/null +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/useMessageBarTitleStyles.styles.ts @@ -0,0 +1,28 @@ +import { makeResetStyles, mergeClasses } from '@griffel/react'; +import { typographyStyles } from '@fluentui/react-theme'; +import type { SlotClassNames } from '@fluentui/react-utilities'; +import type { MessageBarTitleSlots, MessageBarTitleState } from './MessageBarTitle.types'; + +export const messageBarTitleClassNames: SlotClassNames = { + root: 'fui-MessageBarTitle', +}; + +/** + * Styles for the root slot + */ +const useRootBaseStyles = makeResetStyles({ + ...typographyStyles.body1Strong, + '::after': { + content: '" "', + }, +}); + +/** + * Apply styling to the MessageBarTitle slots based on the state + */ +export const useMessageBarTitleStyles_unstable = (state: MessageBarTitleState): MessageBarTitleState => { + const rootBaseStyles = useRootBaseStyles(); + state.root.className = mergeClasses(messageBarTitleClassNames.root, rootBaseStyles, state.root.className); + + return state; +}; diff --git a/packages/react-components/react-message-bar-preview/src/index.ts b/packages/react-components/react-message-bar-preview/src/index.ts index cb0ff5c3b541f..7ad8a189cf2ad 100644 --- a/packages/react-components/react-message-bar-preview/src/index.ts +++ b/packages/react-components/react-message-bar-preview/src/index.ts @@ -1 +1,17 @@ -export {}; +export { + MessageBar, + useMessageBarStyles_unstable, + useMessageBar_unstable, + renderMessageBar_unstable, +} from './MessageBar'; + +export type { MessageBarProps, MessageBarSlots, MessageBarState } from './MessageBar'; + +export { + MessageBarTitle, + useMessageBarTitleStyles_unstable, + useMessageBarTitle_unstable, + renderMessageBarTitle_unstable, +} from './MessageBarTitle'; + +export type { MessageBarTitleProps, MessageBarTitleSlots, MessageBarTitleState } from './MessageBarTitle'; diff --git a/packages/react-components/react-message-bar-preview/stories/MessageBar/Default.stories.tsx b/packages/react-components/react-message-bar-preview/stories/MessageBar/Default.stories.tsx new file mode 100644 index 0000000000000..3d242f4143391 --- /dev/null +++ b/packages/react-components/react-message-bar-preview/stories/MessageBar/Default.stories.tsx @@ -0,0 +1,30 @@ +import * as React from 'react'; +import { Button, Link } from '@fluentui/react-components'; +import { DismissRegular } from '@fluentui/react-icons'; +import { MessageBar, MessageBarTitle } from '@fluentui/react-message-bar-preview'; + +const intents = ['info', 'warning', 'error', 'success'] as const; + +export const Default = () => ( + <> + {intents.map(intent => ( + <> + } appearance="transparent" />} + actions={ + <> + + + + } + > + Descriptive title + Message providing information to the user with actionable insights. Link + +
+ + ))} + +); diff --git a/packages/react-components/react-message-bar-preview/stories/MessageBar/MessageBarBestPractices.md b/packages/react-components/react-message-bar-preview/stories/MessageBar/MessageBarBestPractices.md new file mode 100644 index 0000000000000..08ff8ddeeb5f8 --- /dev/null +++ b/packages/react-components/react-message-bar-preview/stories/MessageBar/MessageBarBestPractices.md @@ -0,0 +1,5 @@ +## Best practices + +### Do + +### Don't diff --git a/packages/react-components/react-message-bar-preview/stories/MessageBar/MessageBarDescription.md b/packages/react-components/react-message-bar-preview/stories/MessageBar/MessageBarDescription.md new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/packages/react-components/react-message-bar-preview/stories/MessageBar/Multiline.stories.tsx b/packages/react-components/react-message-bar-preview/stories/MessageBar/Multiline.stories.tsx new file mode 100644 index 0000000000000..a5c6a4b711c5c --- /dev/null +++ b/packages/react-components/react-message-bar-preview/stories/MessageBar/Multiline.stories.tsx @@ -0,0 +1,31 @@ +import * as React from 'react'; +import { Button, Link } from '@fluentui/react-components'; +import { DismissRegular } from '@fluentui/react-icons'; +import { MessageBar, MessageBarTitle } from '@fluentui/react-message-bar-preview'; + +const intents = ['info', 'warning', 'error', 'success'] as const; + +export const Multiline = () => ( + <> + {intents.map(intent => ( + <> + } appearance="transparent" />} + actions={ + <> + + + + } + > + Descriptive title + Message providing information to the user with actionable insights. Link + +
+ + ))} + +); diff --git a/packages/react-components/react-message-bar-preview/stories/MessageBar/index.stories.tsx b/packages/react-components/react-message-bar-preview/stories/MessageBar/index.stories.tsx new file mode 100644 index 0000000000000..e5af5995d3206 --- /dev/null +++ b/packages/react-components/react-message-bar-preview/stories/MessageBar/index.stories.tsx @@ -0,0 +1,19 @@ +import { MessageBar } from '@fluentui/react-message-bar-preview'; + +import descriptionMd from './MessageBarDescription.md'; +import bestPracticesMd from './MessageBarBestPractices.md'; + +export { Default } from './Default.stories'; +export { Multiline } from './Multiline.stories'; + +export default { + title: 'Preview Components/MessageBar', + component: MessageBar, + parameters: { + docs: { + description: { + component: [descriptionMd, bestPracticesMd].join('\n'), + }, + }, + }, +}; From a75a53607e1445e797be7ad82b0b45fab348c062 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Wed, 27 Sep 2023 09:10:33 +0000 Subject: [PATCH 02/17] remove TODOs --- .../src/components/MessageBar/MessageBar.tsx | 2 +- .../src/components/MessageBarTitle/MessageBarTitle.tsx | 2 +- .../src/components/MessageBarTitle/MessageBarTitle.types.ts | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.tsx b/packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.tsx index c6e3220bc668a..b5904b078f1ed 100644 --- a/packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.tsx +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.tsx @@ -6,7 +6,7 @@ import { useMessageBarStyles_unstable } from './useMessageBarStyles.styles'; import type { MessageBarProps } from './MessageBar.types'; /** - * MessageBar component - TODO: add more docs + * MessageBar component */ export const MessageBar: ForwardRefComponent = React.forwardRef((props, ref) => { const state = useMessageBar_unstable(props, ref); diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/MessageBarTitle.tsx b/packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/MessageBarTitle.tsx index a6a12e84462ae..6ce8533ca2ec6 100644 --- a/packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/MessageBarTitle.tsx +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/MessageBarTitle.tsx @@ -6,7 +6,7 @@ import { useMessageBarTitleStyles_unstable } from './useMessageBarTitleStyles.st import type { MessageBarTitleProps } from './MessageBarTitle.types'; /** - * MessageBarTitle component - TODO: add more docs + * MessageBarTitle component */ export const MessageBarTitle: ForwardRefComponent = React.forwardRef((props, ref) => { const state = useMessageBarTitle_unstable(props, ref); diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/MessageBarTitle.types.ts b/packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/MessageBarTitle.types.ts index 89818edb55afc..d966942d2b9fd 100644 --- a/packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/MessageBarTitle.types.ts +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/MessageBarTitle.types.ts @@ -13,5 +13,3 @@ export type MessageBarTitleProps = ComponentProps & {}; * State used in rendering MessageBarTitle */ export type MessageBarTitleState = ComponentState; -// TODO: Remove semicolon from previous line, uncomment next line, and provide union of props to pick from MessageBarTitleProps. -// & Required> From aeacd98f784cf81b88edac769a1ad8f8f4603a7d Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Wed, 27 Sep 2023 10:19:21 +0000 Subject: [PATCH 03/17] fix multiline alignment --- .../src/components/MessageBar/useMessageBarStyles.styles.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBar/useMessageBarStyles.styles.ts b/packages/react-components/react-message-bar-preview/src/components/MessageBar/useMessageBarStyles.styles.ts index caa767c1847f4..2ee6d443d6f27 100644 --- a/packages/react-components/react-message-bar-preview/src/components/MessageBar/useMessageBarStyles.styles.ts +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBar/useMessageBarStyles.styles.ts @@ -62,6 +62,7 @@ const useMultilineStyles = makeStyles({ justifyContent: 'end', marginTop: tokens.spacingVerticalMNudge, marginBottom: tokens.spacingVerticalS, + marginRight: '0px', }, }); From cf9c216f439c053088169ac4c8b23add1246493d Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Wed, 27 Sep 2023 10:20:44 +0000 Subject: [PATCH 04/17] fix slot type misalign --- .../__snapshots__/MessageBar.test.tsx.snap | 33 +++++++++++++++++++ .../MessageBarTitle.test.tsx.snap | 11 +++++++ .../MessageBarTitle/useMessageBarTitle.ts | 2 +- 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 packages/react-components/react-message-bar-preview/src/components/MessageBar/__snapshots__/MessageBar.test.tsx.snap create mode 100644 packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/__snapshots__/MessageBarTitle.test.tsx.snap diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBar/__snapshots__/MessageBar.test.tsx.snap b/packages/react-components/react-message-bar-preview/src/components/MessageBar/__snapshots__/MessageBar.test.tsx.snap new file mode 100644 index 0000000000000..8faf50e88bf66 --- /dev/null +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBar/__snapshots__/MessageBar.test.tsx.snap @@ -0,0 +1,33 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`MessageBar renders a default state 1`] = ` +
+
+
+ +
+
+ Default MessageBar +
+
+
+`; diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/__snapshots__/MessageBarTitle.test.tsx.snap b/packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/__snapshots__/MessageBarTitle.test.tsx.snap new file mode 100644 index 0000000000000..8d96636aac25e --- /dev/null +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/__snapshots__/MessageBarTitle.test.tsx.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`MessageBarTitle renders a default state 1`] = ` +
+ + Default MessageBarTitle + +
+`; diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/useMessageBarTitle.ts b/packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/useMessageBarTitle.ts index ffa1883ffdb0c..87329b8eba488 100644 --- a/packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/useMessageBarTitle.ts +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/useMessageBarTitle.ts @@ -24,7 +24,7 @@ export const useMessageBarTitle_unstable = ( ref, ...props, }), - { elementType: 'div' }, + { elementType: 'span' }, ), }; }; From b48228337759b8f49acbdb17293e0f38d3bb4ab9 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Wed, 27 Sep 2023 10:22:50 +0000 Subject: [PATCH 05/17] fix tests --- .../src/components/MessageBar/MessageBar.test.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.test.tsx b/packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.test.tsx index f38c1f1c33ea8..3e25240e5f343 100644 --- a/packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.test.tsx +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.test.tsx @@ -7,6 +7,17 @@ describe('MessageBar', () => { isConformant({ Component: MessageBar, displayName: 'MessageBar', + testOptions: { + 'has-static-classnames': [ + { + props: { + icon: 'Icon', + actions: 'Actions', + action: 'Action', + }, + }, + ], + }, }); // TODO add more tests here, and create visual regression tests in /apps/vr-tests From 91ffc5e975dc101f18415f8b5625b9a637963493 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Wed, 27 Sep 2023 10:29:47 +0000 Subject: [PATCH 06/17] rename to secondaryActions --- .../etc/react-message-bar-preview.api.md | 9 ++++- .../components/MessageBar/MessageBar.test.tsx | 2 +- .../components/MessageBar/MessageBar.types.ts | 2 +- .../MessageBar/renderMessageBar.tsx | 6 ++-- .../components/MessageBar/useMessageBar.ts | 4 +-- .../MessageBar/useMessageBarStyles.styles.ts | 36 ++++++++++--------- .../react-message-bar-preview/src/index.ts | 2 ++ .../stories/MessageBar/Default.stories.tsx | 4 +-- .../stories/MessageBar/Multiline.stories.tsx | 4 +-- 9 files changed, 41 insertions(+), 28 deletions(-) diff --git a/packages/react-components/react-message-bar-preview/etc/react-message-bar-preview.api.md b/packages/react-components/react-message-bar-preview/etc/react-message-bar-preview.api.md index 3ce4d5291f10b..c173c5e695e47 100644 --- a/packages/react-components/react-message-bar-preview/etc/react-message-bar-preview.api.md +++ b/packages/react-components/react-message-bar-preview/etc/react-message-bar-preview.api.md @@ -11,10 +11,14 @@ 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; +// @public (undocumented) +export const messageBarClassNames: SlotClassNames; + // @public export type MessageBarProps = ComponentProps & { multiline?: boolean; @@ -25,8 +29,8 @@ export type MessageBarProps = ComponentProps & { export type MessageBarSlots = { root: Slot<'div'>; icon?: Slot<'div'>; - action?: Slot<'div'>; actions?: Slot<'div'>; + secondaryActions?: Slot<'div'>; body?: Slot<'div'>; }; @@ -36,6 +40,9 @@ export type MessageBarState = ComponentState & Required; +// @public (undocumented) +export const messageBarTitleClassNames: SlotClassNames; + // @public export type MessageBarTitleProps = ComponentProps & {}; diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.test.tsx b/packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.test.tsx index 3e25240e5f343..8cbf1e49df380 100644 --- a/packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.test.tsx +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.test.tsx @@ -13,7 +13,7 @@ describe('MessageBar', () => { props: { icon: 'Icon', actions: 'Actions', - action: 'Action', + secondaryActions: 'Actions', }, }, ], diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.types.ts b/packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.types.ts index 91c379989667d..403e7d401b775 100644 --- a/packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.types.ts +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.types.ts @@ -3,8 +3,8 @@ import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utili export type MessageBarSlots = { root: Slot<'div'>; icon?: Slot<'div'>; - action?: Slot<'div'>; actions?: Slot<'div'>; + secondaryActions?: Slot<'div'>; body?: Slot<'div'>; }; diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBar/renderMessageBar.tsx b/packages/react-components/react-message-bar-preview/src/components/MessageBar/renderMessageBar.tsx index f2a797861eae6..54aef7d271c07 100644 --- a/packages/react-components/react-message-bar-preview/src/components/MessageBar/renderMessageBar.tsx +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBar/renderMessageBar.tsx @@ -18,14 +18,14 @@ export const renderMessageBar_unstable = (state: MessageBarState) => { {state.multiline ? ( <> - {state.action && } {state.actions && } + {state.secondaryActions && } ) : ( <> + {state.secondaryActions && } + {state.secondaryActions && } {state.actions && } - {state.actions && } - {state.action && } )} diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBar/useMessageBar.ts b/packages/react-components/react-message-bar-preview/src/components/MessageBar/useMessageBar.ts index 7554edad43e2b..371b266875748 100644 --- a/packages/react-components/react-message-bar-preview/src/components/MessageBar/useMessageBar.ts +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBar/useMessageBar.ts @@ -19,8 +19,8 @@ export const useMessageBar_unstable = (props: MessageBarProps, ref: React.Ref = { root: 'fui-MessageBar', icon: 'fui-MessageBar__icon', - action: 'fui-MessageBar__action', actions: 'fui-MessageBar__actions', + secondaryActions: 'fui-MessageBar__secondaryActions', body: 'fui-MessageBar__body', }; const useRootBaseStyles = makeResetStyles({ display: 'grid', gridTemplateColumns: 'auto 1fr auto auto', - gridTemplateAreas: '"icon body actions action"', + gridTemplateAreas: '"icon body secondaryActions actions"', ...shorthands.padding('0', tokens.spacingHorizontalM), ...shorthands.border(tokens.strokeWidthThin, 'solid', tokens.colorNeutralStroke1), ...shorthands.borderRadius(tokens.borderRadiusMedium), @@ -32,11 +32,11 @@ const useIconBaseStyles = makeResetStyles({ }); const useActionBaseStyles = makeResetStyles({ - ...shorthands.gridArea('action'), + ...shorthands.gridArea('actions'), }); -const useActionsBaseStyles = makeResetStyles({ - ...shorthands.gridArea('actions'), +const useSecondaryActionsBaseStyles = makeResetStyles({ + ...shorthands.gridArea('secondaryActions'), display: 'flex', alignItems: 'center', columnGap: tokens.spacingHorizontalM, @@ -53,12 +53,12 @@ const useMultilineStyles = makeStyles({ paddingTop: tokens.spacingVerticalMNudge, gridTemplateColumns: 'auto 1fr auto', gridTemplateAreas: ` - "icon body action" - "actions actions actions" + "icon body actions" + "secondaryActions secondaryActions secondaryActions" `, }, - actionsMultiline: { + secondaryActionsMultiline: { justifyContent: 'end', marginTop: tokens.spacingVerticalMNudge, marginBottom: tokens.spacingVerticalS, @@ -105,8 +105,8 @@ const useRootIntentStyles = makeStyles({ export const useMessageBarStyles_unstable = (state: MessageBarState): MessageBarState => { const rootBaseStyles = useRootBaseStyles(); const iconBaseStyles = useIconBaseStyles(); - const actionBaseStyles = useActionBaseStyles(); - const actionsBaseStyles = useActionsBaseStyles(); + const actionsBaseStyles = useActionBaseStyles(); + const secondaryActionsBaseStyles = useSecondaryActionsBaseStyles(); const bodyBaseStyles = useBodyBaseStyles(); const multilineStyles = useMultilineStyles(); const iconIntentStyles = useIconIntentStyles(); @@ -130,18 +130,22 @@ export const useMessageBarStyles_unstable = (state: MessageBarState): MessageBar ); } - if (state.action) { - state.action.className = mergeClasses(messageBarClassNames.actions, actionBaseStyles, state.action.className); - } - if (state.actions) { state.actions.className = mergeClasses( - messageBarClassNames.action, + messageBarClassNames.secondaryActions, actionsBaseStyles, - state.multiline && multilineStyles.actionsMultiline, state.actions.className, ); } + if (state.secondaryActions) { + state.secondaryActions.className = mergeClasses( + messageBarClassNames.actions, + secondaryActionsBaseStyles, + state.multiline && multilineStyles.secondaryActionsMultiline, + state.secondaryActions.className, + ); + } + return state; }; diff --git a/packages/react-components/react-message-bar-preview/src/index.ts b/packages/react-components/react-message-bar-preview/src/index.ts index 7ad8a189cf2ad..63270882f450b 100644 --- a/packages/react-components/react-message-bar-preview/src/index.ts +++ b/packages/react-components/react-message-bar-preview/src/index.ts @@ -3,6 +3,7 @@ export { useMessageBarStyles_unstable, useMessageBar_unstable, renderMessageBar_unstable, + messageBarClassNames, } from './MessageBar'; export type { MessageBarProps, MessageBarSlots, MessageBarState } from './MessageBar'; @@ -12,6 +13,7 @@ export { useMessageBarTitleStyles_unstable, useMessageBarTitle_unstable, renderMessageBarTitle_unstable, + messageBarTitleClassNames, } from './MessageBarTitle'; export type { MessageBarTitleProps, MessageBarTitleSlots, MessageBarTitleState } from './MessageBarTitle'; diff --git a/packages/react-components/react-message-bar-preview/stories/MessageBar/Default.stories.tsx b/packages/react-components/react-message-bar-preview/stories/MessageBar/Default.stories.tsx index 3d242f4143391..d9836f0372718 100644 --- a/packages/react-components/react-message-bar-preview/stories/MessageBar/Default.stories.tsx +++ b/packages/react-components/react-message-bar-preview/stories/MessageBar/Default.stories.tsx @@ -12,8 +12,8 @@ export const Default = () => ( } appearance="transparent" />} - actions={ + actions={ diff --git a/packages/react-components/react-message-bar-preview/stories/MessageBar/Multiline.stories.tsx b/packages/react-components/react-message-bar-preview/stories/MessageBar/Multiline.stories.tsx index a5c6a4b711c5c..02947f4c8b40b 100644 --- a/packages/react-components/react-message-bar-preview/stories/MessageBar/Multiline.stories.tsx +++ b/packages/react-components/react-message-bar-preview/stories/MessageBar/Multiline.stories.tsx @@ -13,8 +13,8 @@ export const Multiline = () => ( key={intent} multiline intent={intent} - action={ From 824404fc8bd8e6a66544664e4fb45131eda95fe3 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Wed, 27 Sep 2023 10:57:53 +0000 Subject: [PATCH 07/17] fix warnings --- .../stories/MessageBar/Default.stories.tsx | 35 ++++++++---------- .../stories/MessageBar/Multiline.stories.tsx | 37 +++++++++---------- 2 files changed, 33 insertions(+), 39 deletions(-) diff --git a/packages/react-components/react-message-bar-preview/stories/MessageBar/Default.stories.tsx b/packages/react-components/react-message-bar-preview/stories/MessageBar/Default.stories.tsx index d9836f0372718..496bddf697502 100644 --- a/packages/react-components/react-message-bar-preview/stories/MessageBar/Default.stories.tsx +++ b/packages/react-components/react-message-bar-preview/stories/MessageBar/Default.stories.tsx @@ -6,25 +6,22 @@ import { MessageBar, MessageBarTitle } from '@fluentui/react-message-bar-preview const intents = ['info', 'warning', 'error', 'success'] as const; export const Default = () => ( - <> +
{intents.map(intent => ( - <> - } appearance="transparent" />} - secondaryActions={ - <> - - - - } - > - Descriptive title - Message providing information to the user with actionable insights. Link - -
- + } appearance="transparent" />} + secondaryActions={ + <> + + + + } + > + Descriptive title + Message providing information to the user with actionable insights. Link + ))} - +
); diff --git a/packages/react-components/react-message-bar-preview/stories/MessageBar/Multiline.stories.tsx b/packages/react-components/react-message-bar-preview/stories/MessageBar/Multiline.stories.tsx index 02947f4c8b40b..4e448316ff2a4 100644 --- a/packages/react-components/react-message-bar-preview/stories/MessageBar/Multiline.stories.tsx +++ b/packages/react-components/react-message-bar-preview/stories/MessageBar/Multiline.stories.tsx @@ -6,26 +6,23 @@ import { MessageBar, MessageBarTitle } from '@fluentui/react-message-bar-preview const intents = ['info', 'warning', 'error', 'success'] as const; export const Multiline = () => ( - <> +
{intents.map(intent => ( - <> - } appearance="transparent" />} - secondaryActions={ - <> - - - - } - > - Descriptive title - Message providing information to the user with actionable insights. Link - -
- + } appearance="transparent" />} + secondaryActions={ + <> + + + + } + > + Descriptive title + Message providing information to the user with actionable insights. Link + ))} - +
); From dbc31451b8c28b195d33e53066ec373fa8bd1543 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Wed, 27 Sep 2023 15:47:09 +0000 Subject: [PATCH 08/17] break out into components --- .../etc/react-message-bar-preview.api.md | 73 +++++++++++++++++-- .../src/MessageBarActions.ts | 1 + .../src/MessageBarBody.ts | 1 + .../components/MessageBar/MessageBar.test.tsx | 2 - .../src/components/MessageBar/MessageBar.tsx | 3 +- .../components/MessageBar/MessageBar.types.ts | 20 ++--- .../__snapshots__/MessageBar.test.tsx.snap | 6 +- .../MessageBar/renderMessageBar.tsx | 30 +++----- .../components/MessageBar/useMessageBar.ts | 10 +-- .../MessageBar/useMessageBarContextValues.ts | 17 +++++ .../MessageBar/useMessageBarStyles.styles.ts | 46 +----------- .../MessageBarActions.test.tsx | 23 ++++++ .../MessageBarActions/MessageBarActions.tsx | 18 +++++ .../MessageBarActions.types.ts | 18 +++++ .../MessageBarActions.test.tsx.snap | 11 +++ .../src/components/MessageBarActions/index.ts | 5 ++ .../renderMessageBarActions.tsx | 28 +++++++ .../MessageBarActions/useMessageBarActions.ts | 35 +++++++++ .../useMessageBarActionsStyles.styles.ts | 58 +++++++++++++++ .../MessageBarBody/MessageBarBody.test.tsx | 16 ++++ .../MessageBarBody/MessageBarBody.tsx | 18 +++++ .../MessageBarBody/MessageBarBody.types.ts | 15 ++++ .../MessageBarBody.test.tsx.snap | 11 +++ .../src/components/MessageBarBody/index.ts | 5 ++ .../MessageBarBody/renderMessageBarBody.tsx | 14 ++++ .../MessageBarBody/useMessageBarBody.ts | 30 ++++++++ .../useMessageBarBodyStyles.styles.ts | 23 ++++++ .../src/contexts/messageBarContext.ts | 13 ++++ .../react-message-bar-preview/src/index.ts | 23 ++++++ .../stories/MessageBar/Default.stories.tsx | 24 +++--- .../stories/MessageBar/Multiline.stories.tsx | 25 +++---- .../MessageBarActionsBestPractices.md | 5 ++ .../MessageBarActionsDefault.stories.tsx | 4 + .../MessageBarActionsDescription.md | 0 .../MessageBarActions/index.stories.tsx | 18 +++++ .../MessageBarBodyBestPractices.md | 5 ++ .../MessageBarBodyDefault.stories.tsx | 4 + .../MessageBarBodyDescription.md | 0 .../stories/MessageBarBody/index.stories.tsx | 18 +++++ .../tsconfig.lib.json | 2 +- 40 files changed, 552 insertions(+), 126 deletions(-) create mode 100644 packages/react-components/react-message-bar-preview/src/MessageBarActions.ts create mode 100644 packages/react-components/react-message-bar-preview/src/MessageBarBody.ts create mode 100644 packages/react-components/react-message-bar-preview/src/components/MessageBar/useMessageBarContextValues.ts create mode 100644 packages/react-components/react-message-bar-preview/src/components/MessageBarActions/MessageBarActions.test.tsx create mode 100644 packages/react-components/react-message-bar-preview/src/components/MessageBarActions/MessageBarActions.tsx create mode 100644 packages/react-components/react-message-bar-preview/src/components/MessageBarActions/MessageBarActions.types.ts create mode 100644 packages/react-components/react-message-bar-preview/src/components/MessageBarActions/__snapshots__/MessageBarActions.test.tsx.snap create mode 100644 packages/react-components/react-message-bar-preview/src/components/MessageBarActions/index.ts create mode 100644 packages/react-components/react-message-bar-preview/src/components/MessageBarActions/renderMessageBarActions.tsx create mode 100644 packages/react-components/react-message-bar-preview/src/components/MessageBarActions/useMessageBarActions.ts create mode 100644 packages/react-components/react-message-bar-preview/src/components/MessageBarActions/useMessageBarActionsStyles.styles.ts create mode 100644 packages/react-components/react-message-bar-preview/src/components/MessageBarBody/MessageBarBody.test.tsx create mode 100644 packages/react-components/react-message-bar-preview/src/components/MessageBarBody/MessageBarBody.tsx create mode 100644 packages/react-components/react-message-bar-preview/src/components/MessageBarBody/MessageBarBody.types.ts create mode 100644 packages/react-components/react-message-bar-preview/src/components/MessageBarBody/__snapshots__/MessageBarBody.test.tsx.snap create mode 100644 packages/react-components/react-message-bar-preview/src/components/MessageBarBody/index.ts create mode 100644 packages/react-components/react-message-bar-preview/src/components/MessageBarBody/renderMessageBarBody.tsx create mode 100644 packages/react-components/react-message-bar-preview/src/components/MessageBarBody/useMessageBarBody.ts create mode 100644 packages/react-components/react-message-bar-preview/src/components/MessageBarBody/useMessageBarBodyStyles.styles.ts create mode 100644 packages/react-components/react-message-bar-preview/src/contexts/messageBarContext.ts create mode 100644 packages/react-components/react-message-bar-preview/stories/MessageBarActions/MessageBarActionsBestPractices.md create mode 100644 packages/react-components/react-message-bar-preview/stories/MessageBarActions/MessageBarActionsDefault.stories.tsx create mode 100644 packages/react-components/react-message-bar-preview/stories/MessageBarActions/MessageBarActionsDescription.md create mode 100644 packages/react-components/react-message-bar-preview/stories/MessageBarActions/index.stories.tsx create mode 100644 packages/react-components/react-message-bar-preview/stories/MessageBarBody/MessageBarBodyBestPractices.md create mode 100644 packages/react-components/react-message-bar-preview/stories/MessageBarBody/MessageBarBodyDefault.stories.tsx create mode 100644 packages/react-components/react-message-bar-preview/stories/MessageBarBody/MessageBarBodyDescription.md create mode 100644 packages/react-components/react-message-bar-preview/stories/MessageBarBody/index.stories.tsx diff --git a/packages/react-components/react-message-bar-preview/etc/react-message-bar-preview.api.md b/packages/react-components/react-message-bar-preview/etc/react-message-bar-preview.api.md index c173c5e695e47..6c00a5ed41093 100644 --- a/packages/react-components/react-message-bar-preview/etc/react-message-bar-preview.api.md +++ b/packages/react-components/react-message-bar-preview/etc/react-message-bar-preview.api.md @@ -16,11 +16,54 @@ import type { SlotClassNames } from '@fluentui/react-utilities'; // @public export const MessageBar: ForwardRefComponent; +// @public +export const MessageBarActions: ForwardRefComponent; + +// @public (undocumented) +export const messageBarActionsClassNames: SlotClassNames; + +// @public +export type MessageBarActionsProps = ComponentProps & {}; + +// @public (undocumented) +export type MessageBarActionsSlots = { + root: Slot<'div'>; + containerAction?: Slot<'div'>; +}; + +// @public +export type MessageBarActionsState = ComponentState & Pick, 'layout'>; + +// @public +export const MessageBarBody: ForwardRefComponent; + +// @public (undocumented) +export const messageBarBodyClassNames: SlotClassNames; + +// @public +export type MessageBarBodyProps = ComponentProps & {}; + +// @public (undocumented) +export type MessageBarBodySlots = { + root: Slot<'div'>; +}; + +// @public +export type MessageBarBodyState = ComponentState; + // @public (undocumented) export const messageBarClassNames: SlotClassNames; +// @public (undocumented) +export const MessageBarContextProvider: React_2.Provider; + +// @public (undocumented) +export type MessageBarContextValue = { + layout?: 'multiline' | 'singleline'; +}; + // @public -export type MessageBarProps = ComponentProps & { +export type MessageBarProps = ComponentProps & Pick & { multiline?: boolean; intent?: 'info' | 'success' | 'warning' | 'error'; }; @@ -29,13 +72,10 @@ export type MessageBarProps = ComponentProps & { export type MessageBarSlots = { root: Slot<'div'>; icon?: Slot<'div'>; - actions?: Slot<'div'>; - secondaryActions?: Slot<'div'>; - body?: Slot<'div'>; }; // @public -export type MessageBarState = ComponentState & Required>; +export type MessageBarState = ComponentState & Required>; // @public export const MessageBarTitle: ForwardRefComponent; @@ -55,7 +95,13 @@ export type MessageBarTitleSlots = { export type MessageBarTitleState = ComponentState; // @public -export const renderMessageBar_unstable: (state: MessageBarState) => JSX.Element; +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; @@ -63,6 +109,21 @@ export const renderMessageBarTitle_unstable: (state: MessageBarTitleState) => JS // @public export const useMessageBar_unstable: (props: MessageBarProps, ref: React_2.Ref) => MessageBarState; +// @public +export const useMessageBarActions_unstable: (props: MessageBarActionsProps, ref: React_2.Ref) => MessageBarActionsState; + +// @public +export const useMessageBarActionsStyles_unstable: (state: MessageBarActionsState) => MessageBarActionsState; + +// @public +export const useMessageBarBody_unstable: (props: MessageBarBodyProps, ref: React_2.Ref) => MessageBarBodyState; + +// @public +export const useMessageBarBodyStyles_unstable: (state: MessageBarBodyState) => MessageBarBodyState; + +// @public (undocumented) +export const useMessageBarContext: () => MessageBarContextValue; + // @public export const useMessageBarStyles_unstable: (state: MessageBarState) => MessageBarState; diff --git a/packages/react-components/react-message-bar-preview/src/MessageBarActions.ts b/packages/react-components/react-message-bar-preview/src/MessageBarActions.ts new file mode 100644 index 0000000000000..7ab9044a3ee73 --- /dev/null +++ b/packages/react-components/react-message-bar-preview/src/MessageBarActions.ts @@ -0,0 +1 @@ +export * from './components/MessageBarActions/index'; diff --git a/packages/react-components/react-message-bar-preview/src/MessageBarBody.ts b/packages/react-components/react-message-bar-preview/src/MessageBarBody.ts new file mode 100644 index 0000000000000..dc59e16a3e414 --- /dev/null +++ b/packages/react-components/react-message-bar-preview/src/MessageBarBody.ts @@ -0,0 +1 @@ +export * from './components/MessageBarBody/index'; diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.test.tsx b/packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.test.tsx index 8cbf1e49df380..ca8524d34d7bb 100644 --- a/packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.test.tsx +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.test.tsx @@ -12,8 +12,6 @@ describe('MessageBar', () => { { props: { icon: 'Icon', - actions: 'Actions', - secondaryActions: 'Actions', }, }, ], diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.tsx b/packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.tsx index b5904b078f1ed..27f443e9d07bf 100644 --- a/packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.tsx +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.tsx @@ -4,6 +4,7 @@ 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 @@ -12,7 +13,7 @@ export const MessageBar: ForwardRefComponent = React.forwardRef const state = useMessageBar_unstable(props, ref); useMessageBarStyles_unstable(state); - return renderMessageBar_unstable(state); + return renderMessageBar_unstable(state, useMessageBarContextValue_unstable(state)); }); MessageBar.displayName = 'MessageBar'; diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.types.ts b/packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.types.ts index 403e7d401b775..9d0bce2e0aa9e 100644 --- a/packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.types.ts +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.types.ts @@ -1,23 +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'>; - actions?: Slot<'div'>; - secondaryActions?: Slot<'div'>; - body?: Slot<'div'>; +}; + +export type MessageBarContextValues = { + messageBar: MessageBarContextValue; }; /** * MessageBar Props */ -export type MessageBarProps = ComponentProps & { - multiline?: boolean; - intent?: 'info' | 'success' | 'warning' | 'error'; -}; +export type MessageBarProps = ComponentProps & + Pick & { + multiline?: boolean; + intent?: 'info' | 'success' | 'warning' | 'error'; + }; /** * State used in rendering MessageBar */ -export type MessageBarState = ComponentState & - Required>; +export type MessageBarState = ComponentState & Required>; diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBar/__snapshots__/MessageBar.test.tsx.snap b/packages/react-components/react-message-bar-preview/src/components/MessageBar/__snapshots__/MessageBar.test.tsx.snap index 8faf50e88bf66..17d8f7e24e3ea 100644 --- a/packages/react-components/react-message-bar-preview/src/components/MessageBar/__snapshots__/MessageBar.test.tsx.snap +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBar/__snapshots__/MessageBar.test.tsx.snap @@ -23,11 +23,7 @@ exports[`MessageBar renders a default state 1`] = ` /> -
- Default MessageBar -
+ Default MessageBar `; diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBar/renderMessageBar.tsx b/packages/react-components/react-message-bar-preview/src/components/MessageBar/renderMessageBar.tsx index 54aef7d271c07..83aa749e3d1ef 100644 --- a/packages/react-components/react-message-bar-preview/src/components/MessageBar/renderMessageBar.tsx +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBar/renderMessageBar.tsx @@ -2,33 +2,21 @@ /** @jsxImportSource @fluentui/react-jsx-runtime */ import { assertSlots } from '@fluentui/react-utilities'; -import type { MessageBarState, MessageBarSlots } from './MessageBar.types'; -import { ButtonContextProvider } from '@fluentui/react-button'; +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) => { +export const renderMessageBar_unstable = (state: MessageBarState, contexts: MessageBarContextValues) => { assertSlots(state); return ( - - {state.icon && } - - - {state.multiline ? ( - <> - {state.actions && } - {state.secondaryActions && } - - ) : ( - <> - {state.secondaryActions && } - {state.secondaryActions && } - {state.actions && } - - )} - - + + + {state.icon && } + {state.root.children} + + ); }; diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBar/useMessageBar.ts b/packages/react-components/react-message-bar-preview/src/components/MessageBar/useMessageBar.ts index 371b266875748..b9759360a050d 100644 --- a/packages/react-components/react-message-bar-preview/src/components/MessageBar/useMessageBar.ts +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBar/useMessageBar.ts @@ -13,15 +13,12 @@ import { getIntentIcon } from './getIntentIcon'; * @param ref - reference to root HTMLElement of MessageBar */ export const useMessageBar_unstable = (props: MessageBarProps, ref: React.Ref): MessageBarState => { - const { multiline = false, intent = 'info' } = props; + const { layout = 'singleline', intent = 'info' } = props; return { components: { root: 'div', icon: 'div', - actions: 'div', - secondaryActions: 'div', - body: 'div', }, root: slot.always( getNativeElementProps('div', { @@ -31,15 +28,12 @@ export const useMessageBar_unstable = (props: MessageBarProps, ref: React.Ref ({ + layout, + }), + [layout], + ); + + return { + messageBar: messageBarContext, + }; +} diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBar/useMessageBarStyles.styles.ts b/packages/react-components/react-message-bar-preview/src/components/MessageBar/useMessageBarStyles.styles.ts index 520265b62825d..974c68afba45f 100644 --- a/packages/react-components/react-message-bar-preview/src/components/MessageBar/useMessageBarStyles.styles.ts +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBar/useMessageBarStyles.styles.ts @@ -1,14 +1,11 @@ import { makeResetStyles, makeStyles, mergeClasses, shorthands } from '@griffel/react'; -import { tokens, typographyStyles } from '@fluentui/react-theme'; +import { tokens } from '@fluentui/react-theme'; import type { SlotClassNames } from '@fluentui/react-utilities'; import type { MessageBarSlots, MessageBarState } from './MessageBar.types'; export const messageBarClassNames: SlotClassNames = { root: 'fui-MessageBar', icon: 'fui-MessageBar__icon', - actions: 'fui-MessageBar__actions', - secondaryActions: 'fui-MessageBar__secondaryActions', - body: 'fui-MessageBar__body', }; const useRootBaseStyles = makeResetStyles({ @@ -31,23 +28,6 @@ const useIconBaseStyles = makeResetStyles({ color: tokens.colorNeutralForeground3, }); -const useActionBaseStyles = makeResetStyles({ - ...shorthands.gridArea('actions'), -}); - -const useSecondaryActionsBaseStyles = makeResetStyles({ - ...shorthands.gridArea('secondaryActions'), - display: 'flex', - alignItems: 'center', - columnGap: tokens.spacingHorizontalM, - marginRight: tokens.spacingHorizontalM, -}); - -const useBodyBaseStyles = makeResetStyles({ - ...typographyStyles.body1, - ...shorthands.gridArea('body'), -}); - const useMultilineStyles = makeStyles({ rootMultiline: { paddingTop: tokens.spacingVerticalMNudge, @@ -105,22 +85,17 @@ const useRootIntentStyles = makeStyles({ export const useMessageBarStyles_unstable = (state: MessageBarState): MessageBarState => { const rootBaseStyles = useRootBaseStyles(); const iconBaseStyles = useIconBaseStyles(); - const actionsBaseStyles = useActionBaseStyles(); - const secondaryActionsBaseStyles = useSecondaryActionsBaseStyles(); - const bodyBaseStyles = useBodyBaseStyles(); const multilineStyles = useMultilineStyles(); const iconIntentStyles = useIconIntentStyles(); const rootIntntStyles = useRootIntentStyles(); state.root.className = mergeClasses( messageBarClassNames.root, rootBaseStyles, - state.multiline && multilineStyles.rootMultiline, + state.layout === 'multiline' && multilineStyles.rootMultiline, rootIntntStyles[state.intent], state.root.className, ); - state.body.className = mergeClasses(messageBarClassNames.body, bodyBaseStyles, state.body.className); - if (state.icon) { state.icon.className = mergeClasses( messageBarClassNames.icon, @@ -130,22 +105,5 @@ export const useMessageBarStyles_unstable = (state: MessageBarState): MessageBar ); } - if (state.actions) { - state.actions.className = mergeClasses( - messageBarClassNames.secondaryActions, - actionsBaseStyles, - state.actions.className, - ); - } - - if (state.secondaryActions) { - state.secondaryActions.className = mergeClasses( - messageBarClassNames.actions, - secondaryActionsBaseStyles, - state.multiline && multilineStyles.secondaryActionsMultiline, - state.secondaryActions.className, - ); - } - return state; }; diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBarActions/MessageBarActions.test.tsx b/packages/react-components/react-message-bar-preview/src/components/MessageBarActions/MessageBarActions.test.tsx new file mode 100644 index 0000000000000..d62960938f7ba --- /dev/null +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBarActions/MessageBarActions.test.tsx @@ -0,0 +1,23 @@ +import * as React from 'react'; +import { render } from '@testing-library/react'; +import { isConformant } from '../../testing/isConformant'; +import { MessageBarActions } from './MessageBarActions'; +import { MessageBarActionsProps } from './MessageBarActions.types'; + +describe('MessageBarActions', () => { + isConformant({ + Component: MessageBarActions, + displayName: 'MessageBarActions', + disabledTests: [ + // TODO: having problems due to the fact root of DialogTitle is Fragment + 'component-has-static-classnames-object', + ], + }); + + // TODO add more tests here, and create visual regression tests in /apps/vr-tests + + it('renders a default state', () => { + const result = render(Default MessageBarActions); + expect(result.container).toMatchSnapshot(); + }); +}); diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBarActions/MessageBarActions.tsx b/packages/react-components/react-message-bar-preview/src/components/MessageBarActions/MessageBarActions.tsx new file mode 100644 index 0000000000000..ca28530fe5939 --- /dev/null +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBarActions/MessageBarActions.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; +import type { ForwardRefComponent } from '@fluentui/react-utilities'; +import { useMessageBarActions_unstable } from './useMessageBarActions'; +import { renderMessageBarActions_unstable } from './renderMessageBarActions'; +import { useMessageBarActionsStyles_unstable } from './useMessageBarActionsStyles.styles'; +import type { MessageBarActionsProps } from './MessageBarActions.types'; + +/** + * MessageBarActions component - TODO: add more docs + */ +export const MessageBarActions: ForwardRefComponent = React.forwardRef((props, ref) => { + const state = useMessageBarActions_unstable(props, ref); + + useMessageBarActionsStyles_unstable(state); + return renderMessageBarActions_unstable(state); +}); + +MessageBarActions.displayName = 'MessageBarActions'; diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBarActions/MessageBarActions.types.ts b/packages/react-components/react-message-bar-preview/src/components/MessageBarActions/MessageBarActions.types.ts new file mode 100644 index 0000000000000..796972f7bbf8e --- /dev/null +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBarActions/MessageBarActions.types.ts @@ -0,0 +1,18 @@ +import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; +import { MessageBarContextValue } from '../../contexts/messageBarContext'; + +export type MessageBarActionsSlots = { + root: Slot<'div'>; + containerAction?: Slot<'div'>; +}; + +/** + * MessageBarActions Props + */ +export type MessageBarActionsProps = ComponentProps & {}; + +/** + * State used in rendering MessageBarActions + */ +export type MessageBarActionsState = ComponentState & + Pick, 'layout'>; diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBarActions/__snapshots__/MessageBarActions.test.tsx.snap b/packages/react-components/react-message-bar-preview/src/components/MessageBarActions/__snapshots__/MessageBarActions.test.tsx.snap new file mode 100644 index 0000000000000..aa2eebdf434aa --- /dev/null +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBarActions/__snapshots__/MessageBarActions.test.tsx.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`MessageBarActions renders a default state 1`] = ` +
+
+ Default MessageBarActions +
+
+`; diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBarActions/index.ts b/packages/react-components/react-message-bar-preview/src/components/MessageBarActions/index.ts new file mode 100644 index 0000000000000..d8ba24f39657b --- /dev/null +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBarActions/index.ts @@ -0,0 +1,5 @@ +export * from './MessageBarActions'; +export * from './MessageBarActions.types'; +export * from './renderMessageBarActions'; +export * from './useMessageBarActions'; +export * from './useMessageBarActionsStyles.styles'; diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBarActions/renderMessageBarActions.tsx b/packages/react-components/react-message-bar-preview/src/components/MessageBarActions/renderMessageBarActions.tsx new file mode 100644 index 0000000000000..15ddc4bf084e3 --- /dev/null +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBarActions/renderMessageBarActions.tsx @@ -0,0 +1,28 @@ +/** @jsxRuntime automatic */ +/** @jsxImportSource @fluentui/react-jsx-runtime */ + +import { assertSlots } from '@fluentui/react-utilities'; +import type { MessageBarActionsState, MessageBarActionsSlots } from './MessageBarActions.types'; +import { ButtonContextProvider } from '@fluentui/react-button'; + +/** + * Render the final JSX of MessageBarActions + */ +export const renderMessageBarActions_unstable = (state: MessageBarActionsState) => { + assertSlots(state); + if (state.layout === 'multiline') { + return ( + + {state.containerAction && } + + + ); + } + + return ( + + + {state.containerAction && } + + ); +}; diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBarActions/useMessageBarActions.ts b/packages/react-components/react-message-bar-preview/src/components/MessageBarActions/useMessageBarActions.ts new file mode 100644 index 0000000000000..5ccf97a36a718 --- /dev/null +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBarActions/useMessageBarActions.ts @@ -0,0 +1,35 @@ +import * as React from 'react'; +import { getNativeElementProps, slot } from '@fluentui/react-utilities'; +import type { MessageBarActionsProps, MessageBarActionsState } from './MessageBarActions.types'; +import { useMessageBarContext } from '../../contexts/messageBarContext'; + +/** + * Create the state required to render MessageBarActions. + * + * The returned state can be modified with hooks such as useMessageBarActionsStyles_unstable, + * before being passed to renderMessageBarActions_unstable. + * + * @param props - props from this instance of MessageBarActions + * @param ref - reference to root HTMLElement of MessageBarActions + */ +export const useMessageBarActions_unstable = ( + props: MessageBarActionsProps, + ref: React.Ref, +): MessageBarActionsState => { + const { layout = 'singleline' } = useMessageBarContext(); + return { + components: { + root: 'div', + containerAction: 'div', + }, + containerAction: slot.optional(props.containerAction, { renderByDefault: false, elementType: 'div' }), + root: slot.always( + getNativeElementProps('div', { + ref, + ...props, + }), + { elementType: 'div' }, + ), + layout, + }; +}; diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBarActions/useMessageBarActionsStyles.styles.ts b/packages/react-components/react-message-bar-preview/src/components/MessageBarActions/useMessageBarActionsStyles.styles.ts new file mode 100644 index 0000000000000..dead8bcab465b --- /dev/null +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBarActions/useMessageBarActionsStyles.styles.ts @@ -0,0 +1,58 @@ +import { makeResetStyles, makeStyles, mergeClasses, shorthands } from '@griffel/react'; +import type { SlotClassNames } from '@fluentui/react-utilities'; +import { tokens } from '@fluentui/react-theme'; +import type { MessageBarActionsSlots, MessageBarActionsState } from './MessageBarActions.types'; + +export const messageBarActionsClassNames: SlotClassNames = { + root: 'fui-MessageBarActions', + containerAction: 'fui-MessageBarActions__containerAction', +}; + +/** + * Styles for the root slot + */ +const useRootBaseStyles = makeResetStyles({ + ...shorthands.gridArea('secondaryActions'), + display: 'flex', + alignItems: 'center', + columnGap: tokens.spacingHorizontalM, + marginRight: tokens.spacingHorizontalM, +}); + +const useContainerActionBaseStyles = makeResetStyles({ + ...shorthands.gridArea('actions'), +}); + +const useMultilineStyles = makeStyles({ + root: { + justifyContent: 'end', + marginTop: tokens.spacingVerticalMNudge, + marginBottom: tokens.spacingVerticalS, + marginRight: '0px', + }, +}); + +/** + * Apply styling to the MessageBarActions slots based on the state + */ +export const useMessageBarActionsStyles_unstable = (state: MessageBarActionsState): MessageBarActionsState => { + const rootBaseStyles = useRootBaseStyles(); + const containerActionBaseStyles = useContainerActionBaseStyles(); + const multilineStyles = useMultilineStyles(); + state.root.className = mergeClasses( + messageBarActionsClassNames.root, + rootBaseStyles, + state.layout === 'multiline' && multilineStyles.root, + state.root.className, + ); + + if (state.containerAction) { + state.containerAction.className = mergeClasses( + messageBarActionsClassNames.containerAction, + containerActionBaseStyles, + state.containerAction.className, + ); + } + + return state; +}; diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBarBody/MessageBarBody.test.tsx b/packages/react-components/react-message-bar-preview/src/components/MessageBarBody/MessageBarBody.test.tsx new file mode 100644 index 0000000000000..b0bc0a9968aa1 --- /dev/null +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBarBody/MessageBarBody.test.tsx @@ -0,0 +1,16 @@ +import * as React from 'react'; +import { render } from '@testing-library/react'; +import { isConformant } from '../../testing/isConformant'; +import { MessageBarBody } from './MessageBarBody'; + +describe('MessageBarBody', () => { + isConformant({ + Component: MessageBarBody, + displayName: 'MessageBarBody', + }); + + it('renders a default state', () => { + const result = render(Default MessageBarBody); + expect(result.container).toMatchSnapshot(); + }); +}); diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBarBody/MessageBarBody.tsx b/packages/react-components/react-message-bar-preview/src/components/MessageBarBody/MessageBarBody.tsx new file mode 100644 index 0000000000000..21e15ad108048 --- /dev/null +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBarBody/MessageBarBody.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; +import type { ForwardRefComponent } from '@fluentui/react-utilities'; +import { useMessageBarBody_unstable } from './useMessageBarBody'; +import { renderMessageBarBody_unstable } from './renderMessageBarBody'; +import { useMessageBarBodyStyles_unstable } from './useMessageBarBodyStyles.styles'; +import type { MessageBarBodyProps } from './MessageBarBody.types'; + +/** + * MessageBarBody component + */ +export const MessageBarBody: ForwardRefComponent = React.forwardRef((props, ref) => { + const state = useMessageBarBody_unstable(props, ref); + + useMessageBarBodyStyles_unstable(state); + return renderMessageBarBody_unstable(state); +}); + +MessageBarBody.displayName = 'MessageBarBody'; diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBarBody/MessageBarBody.types.ts b/packages/react-components/react-message-bar-preview/src/components/MessageBarBody/MessageBarBody.types.ts new file mode 100644 index 0000000000000..f471468f316af --- /dev/null +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBarBody/MessageBarBody.types.ts @@ -0,0 +1,15 @@ +import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; + +export type MessageBarBodySlots = { + root: Slot<'div'>; +}; + +/** + * MessageBarBody Props + */ +export type MessageBarBodyProps = ComponentProps & {}; + +/** + * State used in rendering MessageBarBody + */ +export type MessageBarBodyState = ComponentState; diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBarBody/__snapshots__/MessageBarBody.test.tsx.snap b/packages/react-components/react-message-bar-preview/src/components/MessageBarBody/__snapshots__/MessageBarBody.test.tsx.snap new file mode 100644 index 0000000000000..4c73379056bad --- /dev/null +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBarBody/__snapshots__/MessageBarBody.test.tsx.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`MessageBarBody renders a default state 1`] = ` +
+
+ Default MessageBarBody +
+
+`; diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBarBody/index.ts b/packages/react-components/react-message-bar-preview/src/components/MessageBarBody/index.ts new file mode 100644 index 0000000000000..360550329c1df --- /dev/null +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBarBody/index.ts @@ -0,0 +1,5 @@ +export * from './MessageBarBody'; +export * from './MessageBarBody.types'; +export * from './renderMessageBarBody'; +export * from './useMessageBarBody'; +export * from './useMessageBarBodyStyles.styles'; diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBarBody/renderMessageBarBody.tsx b/packages/react-components/react-message-bar-preview/src/components/MessageBarBody/renderMessageBarBody.tsx new file mode 100644 index 0000000000000..fa5e80f1b3da2 --- /dev/null +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBarBody/renderMessageBarBody.tsx @@ -0,0 +1,14 @@ +/** @jsxRuntime automatic */ +/** @jsxImportSource @fluentui/react-jsx-runtime */ + +import { assertSlots } from '@fluentui/react-utilities'; +import type { MessageBarBodyState, MessageBarBodySlots } from './MessageBarBody.types'; + +/** + * Render the final JSX of MessageBarBody + */ +export const renderMessageBarBody_unstable = (state: MessageBarBodyState) => { + assertSlots(state); + + return ; +}; diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBarBody/useMessageBarBody.ts b/packages/react-components/react-message-bar-preview/src/components/MessageBarBody/useMessageBarBody.ts new file mode 100644 index 0000000000000..ed99cf2f18a9b --- /dev/null +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBarBody/useMessageBarBody.ts @@ -0,0 +1,30 @@ +import * as React from 'react'; +import { getNativeElementProps, slot } from '@fluentui/react-utilities'; +import type { MessageBarBodyProps, MessageBarBodyState } from './MessageBarBody.types'; + +/** + * Create the state required to render MessageBarBody. + * + * The returned state can be modified with hooks such as useMessageBarBodyStyles_unstable, + * before being passed to renderMessageBarBody_unstable. + * + * @param props - props from this instance of MessageBarBody + * @param ref - reference to root HTMLElement of MessageBarBody + */ +export const useMessageBarBody_unstable = ( + props: MessageBarBodyProps, + ref: React.Ref, +): MessageBarBodyState => { + return { + components: { + root: 'div', + }, + root: slot.always( + getNativeElementProps('div', { + ref, + ...props, + }), + { elementType: 'div' }, + ), + }; +}; diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBarBody/useMessageBarBodyStyles.styles.ts b/packages/react-components/react-message-bar-preview/src/components/MessageBarBody/useMessageBarBodyStyles.styles.ts new file mode 100644 index 0000000000000..1e470a13b0ba1 --- /dev/null +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBarBody/useMessageBarBodyStyles.styles.ts @@ -0,0 +1,23 @@ +import { makeResetStyles, mergeClasses, shorthands } from '@griffel/react'; +import type { SlotClassNames } from '@fluentui/react-utilities'; +import type { MessageBarBodySlots, MessageBarBodyState } from './MessageBarBody.types'; +import { typographyStyles } from '@fluentui/react-theme'; + +export const messageBarBodyClassNames: SlotClassNames = { + root: 'fui-MessageBarBody', +}; + +const useRootBaseStyles = makeResetStyles({ + ...typographyStyles.body1, + ...shorthands.gridArea('body'), +}); + +/** + * Apply styling to the MessageBarBody slots based on the state + */ +export const useMessageBarBodyStyles_unstable = (state: MessageBarBodyState): MessageBarBodyState => { + const rootBaseStyles = useRootBaseStyles(); + state.root.className = mergeClasses(messageBarBodyClassNames.root, rootBaseStyles, state.root.className); + + return state; +}; diff --git a/packages/react-components/react-message-bar-preview/src/contexts/messageBarContext.ts b/packages/react-components/react-message-bar-preview/src/contexts/messageBarContext.ts new file mode 100644 index 0000000000000..add68734a6292 --- /dev/null +++ b/packages/react-components/react-message-bar-preview/src/contexts/messageBarContext.ts @@ -0,0 +1,13 @@ +import * as React from 'react'; + +export type MessageBarContextValue = { + layout?: 'multiline' | 'singleline'; +}; +const messageBarContext = React.createContext(undefined); + +export const messageBarContextDefaultValue: MessageBarContextValue = { + layout: 'singleline', +}; + +export const MessageBarContextProvider = messageBarContext.Provider; +export const useMessageBarContext = () => React.useContext(messageBarContext) ?? messageBarContextDefaultValue; diff --git a/packages/react-components/react-message-bar-preview/src/index.ts b/packages/react-components/react-message-bar-preview/src/index.ts index 63270882f450b..e4d035efe8584 100644 --- a/packages/react-components/react-message-bar-preview/src/index.ts +++ b/packages/react-components/react-message-bar-preview/src/index.ts @@ -17,3 +17,26 @@ export { } from './MessageBarTitle'; export type { MessageBarTitleProps, MessageBarTitleSlots, MessageBarTitleState } from './MessageBarTitle'; + +export { + MessageBarActions, + useMessageBarActionsStyles_unstable, + useMessageBarActions_unstable, + renderMessageBarActions_unstable, + messageBarActionsClassNames, +} from './MessageBarActions'; + +export type { MessageBarActionsProps, MessageBarActionsSlots, MessageBarActionsState } from './MessageBarActions'; + +export { + MessageBarBody, + useMessageBarBodyStyles_unstable, + useMessageBarBody_unstable, + renderMessageBarBody_unstable, + messageBarBodyClassNames, +} from './MessageBarBody'; + +export type { MessageBarBodyProps, MessageBarBodySlots, MessageBarBodyState } from './MessageBarBody'; + +export { MessageBarContextProvider, useMessageBarContext } from './contexts/messageBarContext'; +export type { MessageBarContextValue } from './contexts/messageBarContext'; diff --git a/packages/react-components/react-message-bar-preview/stories/MessageBar/Default.stories.tsx b/packages/react-components/react-message-bar-preview/stories/MessageBar/Default.stories.tsx index 496bddf697502..c6c7034034318 100644 --- a/packages/react-components/react-message-bar-preview/stories/MessageBar/Default.stories.tsx +++ b/packages/react-components/react-message-bar-preview/stories/MessageBar/Default.stories.tsx @@ -1,26 +1,22 @@ import * as React from 'react'; import { Button, Link } from '@fluentui/react-components'; import { DismissRegular } from '@fluentui/react-icons'; -import { MessageBar, MessageBarTitle } from '@fluentui/react-message-bar-preview'; +import { MessageBar, MessageBarActions, MessageBarTitle, MessageBarBody } from '@fluentui/react-message-bar-preview'; const intents = ['info', 'warning', 'error', 'success'] as const; export const Default = () => (
{intents.map(intent => ( - } appearance="transparent" />} - secondaryActions={ - <> - - - - } - > - Descriptive title - Message providing information to the user with actionable insights. Link + + + Descriptive title + Message providing information to the user with actionable insights. Link + + } />}> + + + ))}
diff --git a/packages/react-components/react-message-bar-preview/stories/MessageBar/Multiline.stories.tsx b/packages/react-components/react-message-bar-preview/stories/MessageBar/Multiline.stories.tsx index 4e448316ff2a4..87901e5318582 100644 --- a/packages/react-components/react-message-bar-preview/stories/MessageBar/Multiline.stories.tsx +++ b/packages/react-components/react-message-bar-preview/stories/MessageBar/Multiline.stories.tsx @@ -1,27 +1,22 @@ import * as React from 'react'; import { Button, Link } from '@fluentui/react-components'; import { DismissRegular } from '@fluentui/react-icons'; -import { MessageBar, MessageBarTitle } from '@fluentui/react-message-bar-preview'; +import { MessageBar, MessageBarActions, MessageBarBody, MessageBarTitle } from '@fluentui/react-message-bar-preview'; const intents = ['info', 'warning', 'error', 'success'] as const; export const Multiline = () => (
{intents.map(intent => ( - } appearance="transparent" />} - secondaryActions={ - <> - - - - } - > - Descriptive title - Message providing information to the user with actionable insights. Link + + + Descriptive title + Message providing information to the user with actionable insights. Link + + } />}> + + + ))}
diff --git a/packages/react-components/react-message-bar-preview/stories/MessageBarActions/MessageBarActionsBestPractices.md b/packages/react-components/react-message-bar-preview/stories/MessageBarActions/MessageBarActionsBestPractices.md new file mode 100644 index 0000000000000..08ff8ddeeb5f8 --- /dev/null +++ b/packages/react-components/react-message-bar-preview/stories/MessageBarActions/MessageBarActionsBestPractices.md @@ -0,0 +1,5 @@ +## Best practices + +### Do + +### Don't diff --git a/packages/react-components/react-message-bar-preview/stories/MessageBarActions/MessageBarActionsDefault.stories.tsx b/packages/react-components/react-message-bar-preview/stories/MessageBarActions/MessageBarActionsDefault.stories.tsx new file mode 100644 index 0000000000000..6146b89d62279 --- /dev/null +++ b/packages/react-components/react-message-bar-preview/stories/MessageBarActions/MessageBarActionsDefault.stories.tsx @@ -0,0 +1,4 @@ +import * as React from 'react'; +import { MessageBarActions, MessageBarActionsProps } from '@fluentui/react-message-bar-preview'; + +export const Default = (props: Partial) => ; diff --git a/packages/react-components/react-message-bar-preview/stories/MessageBarActions/MessageBarActionsDescription.md b/packages/react-components/react-message-bar-preview/stories/MessageBarActions/MessageBarActionsDescription.md new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/packages/react-components/react-message-bar-preview/stories/MessageBarActions/index.stories.tsx b/packages/react-components/react-message-bar-preview/stories/MessageBarActions/index.stories.tsx new file mode 100644 index 0000000000000..148376427ded4 --- /dev/null +++ b/packages/react-components/react-message-bar-preview/stories/MessageBarActions/index.stories.tsx @@ -0,0 +1,18 @@ +import { MessageBarActions } from '@fluentui/react-message-bar-preview'; + +import descriptionMd from './MessageBarActionsDescription.md'; +import bestPracticesMd from './MessageBarActionsBestPractices.md'; + +export { Default } from './MessageBarActionsDefault.stories'; + +export default { + title: 'Preview Components/MessageBarActions', + component: MessageBarActions, + parameters: { + docs: { + description: { + component: [descriptionMd, bestPracticesMd].join('\n'), + }, + }, + }, +}; diff --git a/packages/react-components/react-message-bar-preview/stories/MessageBarBody/MessageBarBodyBestPractices.md b/packages/react-components/react-message-bar-preview/stories/MessageBarBody/MessageBarBodyBestPractices.md new file mode 100644 index 0000000000000..08ff8ddeeb5f8 --- /dev/null +++ b/packages/react-components/react-message-bar-preview/stories/MessageBarBody/MessageBarBodyBestPractices.md @@ -0,0 +1,5 @@ +## Best practices + +### Do + +### Don't diff --git a/packages/react-components/react-message-bar-preview/stories/MessageBarBody/MessageBarBodyDefault.stories.tsx b/packages/react-components/react-message-bar-preview/stories/MessageBarBody/MessageBarBodyDefault.stories.tsx new file mode 100644 index 0000000000000..60e2f1aab57a0 --- /dev/null +++ b/packages/react-components/react-message-bar-preview/stories/MessageBarBody/MessageBarBodyDefault.stories.tsx @@ -0,0 +1,4 @@ +import * as React from 'react'; +import { MessageBarBody, MessageBarBodyProps } from '@fluentui/react-message-bar-preview'; + +export const Default = (props: Partial) => ; diff --git a/packages/react-components/react-message-bar-preview/stories/MessageBarBody/MessageBarBodyDescription.md b/packages/react-components/react-message-bar-preview/stories/MessageBarBody/MessageBarBodyDescription.md new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/packages/react-components/react-message-bar-preview/stories/MessageBarBody/index.stories.tsx b/packages/react-components/react-message-bar-preview/stories/MessageBarBody/index.stories.tsx new file mode 100644 index 0000000000000..f21a9ae4e96ca --- /dev/null +++ b/packages/react-components/react-message-bar-preview/stories/MessageBarBody/index.stories.tsx @@ -0,0 +1,18 @@ +import { MessageBarBody } from '@fluentui/react-message-bar-preview'; + +import descriptionMd from './MessageBarBodyDescription.md'; +import bestPracticesMd from './MessageBarBodyBestPractices.md'; + +export { Default } from './MessageBarBodyDefault.stories'; + +export default { + title: 'Preview Components/MessageBarBody', + component: MessageBarBody, + parameters: { + docs: { + description: { + component: [descriptionMd, bestPracticesMd].join('\n'), + }, + }, + }, +}; diff --git a/packages/react-components/react-message-bar-preview/tsconfig.lib.json b/packages/react-components/react-message-bar-preview/tsconfig.lib.json index 6f90cf95c005b..8debc43c62f97 100644 --- a/packages/react-components/react-message-bar-preview/tsconfig.lib.json +++ b/packages/react-components/react-message-bar-preview/tsconfig.lib.json @@ -18,5 +18,5 @@ "**/*.stories.ts", "**/*.stories.tsx" ], - "include": ["./src/**/*.ts", "./src/**/*.tsx"] + "include": ["./src/**/*.ts", "./src/**/*.tsx", "src/contexts"] } From 0c03bfaada3eb6232a6c5fb6958f4e894d665bd9 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Wed, 27 Sep 2023 15:48:03 +0000 Subject: [PATCH 09/17] remove stories --- .../MessageBarActionsBestPractices.md | 5 ----- .../MessageBarActionsDefault.stories.tsx | 4 ---- .../MessageBarActionsDescription.md | 0 .../MessageBarActions/index.stories.tsx | 18 ------------------ .../MessageBarBodyBestPractices.md | 5 ----- .../MessageBarBodyDefault.stories.tsx | 4 ---- .../MessageBarBodyDescription.md | 0 .../stories/MessageBarBody/index.stories.tsx | 18 ------------------ 8 files changed, 54 deletions(-) delete mode 100644 packages/react-components/react-message-bar-preview/stories/MessageBarActions/MessageBarActionsBestPractices.md delete mode 100644 packages/react-components/react-message-bar-preview/stories/MessageBarActions/MessageBarActionsDefault.stories.tsx delete mode 100644 packages/react-components/react-message-bar-preview/stories/MessageBarActions/MessageBarActionsDescription.md delete mode 100644 packages/react-components/react-message-bar-preview/stories/MessageBarActions/index.stories.tsx delete mode 100644 packages/react-components/react-message-bar-preview/stories/MessageBarBody/MessageBarBodyBestPractices.md delete mode 100644 packages/react-components/react-message-bar-preview/stories/MessageBarBody/MessageBarBodyDefault.stories.tsx delete mode 100644 packages/react-components/react-message-bar-preview/stories/MessageBarBody/MessageBarBodyDescription.md delete mode 100644 packages/react-components/react-message-bar-preview/stories/MessageBarBody/index.stories.tsx diff --git a/packages/react-components/react-message-bar-preview/stories/MessageBarActions/MessageBarActionsBestPractices.md b/packages/react-components/react-message-bar-preview/stories/MessageBarActions/MessageBarActionsBestPractices.md deleted file mode 100644 index 08ff8ddeeb5f8..0000000000000 --- a/packages/react-components/react-message-bar-preview/stories/MessageBarActions/MessageBarActionsBestPractices.md +++ /dev/null @@ -1,5 +0,0 @@ -## Best practices - -### Do - -### Don't diff --git a/packages/react-components/react-message-bar-preview/stories/MessageBarActions/MessageBarActionsDefault.stories.tsx b/packages/react-components/react-message-bar-preview/stories/MessageBarActions/MessageBarActionsDefault.stories.tsx deleted file mode 100644 index 6146b89d62279..0000000000000 --- a/packages/react-components/react-message-bar-preview/stories/MessageBarActions/MessageBarActionsDefault.stories.tsx +++ /dev/null @@ -1,4 +0,0 @@ -import * as React from 'react'; -import { MessageBarActions, MessageBarActionsProps } from '@fluentui/react-message-bar-preview'; - -export const Default = (props: Partial) => ; diff --git a/packages/react-components/react-message-bar-preview/stories/MessageBarActions/MessageBarActionsDescription.md b/packages/react-components/react-message-bar-preview/stories/MessageBarActions/MessageBarActionsDescription.md deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/packages/react-components/react-message-bar-preview/stories/MessageBarActions/index.stories.tsx b/packages/react-components/react-message-bar-preview/stories/MessageBarActions/index.stories.tsx deleted file mode 100644 index 148376427ded4..0000000000000 --- a/packages/react-components/react-message-bar-preview/stories/MessageBarActions/index.stories.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { MessageBarActions } from '@fluentui/react-message-bar-preview'; - -import descriptionMd from './MessageBarActionsDescription.md'; -import bestPracticesMd from './MessageBarActionsBestPractices.md'; - -export { Default } from './MessageBarActionsDefault.stories'; - -export default { - title: 'Preview Components/MessageBarActions', - component: MessageBarActions, - parameters: { - docs: { - description: { - component: [descriptionMd, bestPracticesMd].join('\n'), - }, - }, - }, -}; diff --git a/packages/react-components/react-message-bar-preview/stories/MessageBarBody/MessageBarBodyBestPractices.md b/packages/react-components/react-message-bar-preview/stories/MessageBarBody/MessageBarBodyBestPractices.md deleted file mode 100644 index 08ff8ddeeb5f8..0000000000000 --- a/packages/react-components/react-message-bar-preview/stories/MessageBarBody/MessageBarBodyBestPractices.md +++ /dev/null @@ -1,5 +0,0 @@ -## Best practices - -### Do - -### Don't diff --git a/packages/react-components/react-message-bar-preview/stories/MessageBarBody/MessageBarBodyDefault.stories.tsx b/packages/react-components/react-message-bar-preview/stories/MessageBarBody/MessageBarBodyDefault.stories.tsx deleted file mode 100644 index 60e2f1aab57a0..0000000000000 --- a/packages/react-components/react-message-bar-preview/stories/MessageBarBody/MessageBarBodyDefault.stories.tsx +++ /dev/null @@ -1,4 +0,0 @@ -import * as React from 'react'; -import { MessageBarBody, MessageBarBodyProps } from '@fluentui/react-message-bar-preview'; - -export const Default = (props: Partial) => ; diff --git a/packages/react-components/react-message-bar-preview/stories/MessageBarBody/MessageBarBodyDescription.md b/packages/react-components/react-message-bar-preview/stories/MessageBarBody/MessageBarBodyDescription.md deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/packages/react-components/react-message-bar-preview/stories/MessageBarBody/index.stories.tsx b/packages/react-components/react-message-bar-preview/stories/MessageBarBody/index.stories.tsx deleted file mode 100644 index f21a9ae4e96ca..0000000000000 --- a/packages/react-components/react-message-bar-preview/stories/MessageBarBody/index.stories.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { MessageBarBody } from '@fluentui/react-message-bar-preview'; - -import descriptionMd from './MessageBarBodyDescription.md'; -import bestPracticesMd from './MessageBarBodyBestPractices.md'; - -export { Default } from './MessageBarBodyDefault.stories'; - -export default { - title: 'Preview Components/MessageBarBody', - component: MessageBarBody, - parameters: { - docs: { - description: { - component: [descriptionMd, bestPracticesMd].join('\n'), - }, - }, - }, -}; From 038781d326851faf841bc6a0e12e74042067635c Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Wed, 27 Sep 2023 15:49:55 +0000 Subject: [PATCH 10/17] revert tsconfig changes --- .../react-message-bar-preview/tsconfig.lib.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-components/react-message-bar-preview/tsconfig.lib.json b/packages/react-components/react-message-bar-preview/tsconfig.lib.json index 8debc43c62f97..6f90cf95c005b 100644 --- a/packages/react-components/react-message-bar-preview/tsconfig.lib.json +++ b/packages/react-components/react-message-bar-preview/tsconfig.lib.json @@ -18,5 +18,5 @@ "**/*.stories.ts", "**/*.stories.tsx" ], - "include": ["./src/**/*.ts", "./src/**/*.tsx", "src/contexts"] + "include": ["./src/**/*.ts", "./src/**/*.tsx"] } From c58ee70c888b800e6ab997c59881a43b6d3bd663 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Thu, 28 Sep 2023 14:02:39 +0000 Subject: [PATCH 11/17] remove outdated keyborg cypress test --- .../react-tabster/src/useKeyborg.cy.tsx | 25 ------------------- 1 file changed, 25 deletions(-) delete mode 100644 packages/react-components/react-tabster/src/useKeyborg.cy.tsx diff --git a/packages/react-components/react-tabster/src/useKeyborg.cy.tsx b/packages/react-components/react-tabster/src/useKeyborg.cy.tsx deleted file mode 100644 index a027be8a0b4cb..0000000000000 --- a/packages/react-components/react-tabster/src/useKeyborg.cy.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import * as React from 'react'; -import { KEYBOARD_NAV_ATTRIBUTE } from './focus/constants'; -import { useKeyboardNavAttribute } from '@fluentui/react-tabster'; -import { mount as mountBase } from '@cypress/react'; -import { FluentProvider } from '@fluentui/react-provider'; -import { teamsLightTheme } from '@fluentui/react-theme'; - -const mount = (element: JSX.Element) => { - mountBase({element}); -}; - -describe('Keyborg', () => { - const Example = () => ( -
- - -
- ); - - it('should open when clicked', () => { - mount(); - cy.contains('Start').focus().realPress('Tab'); - cy.contains('Finish').should('be.focused').parent().should('have.attr', KEYBOARD_NAV_ATTRIBUTE); - }); -}); From 823248dae3a861d23b4aee8b28104169a80b506c Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Thu, 28 Sep 2023 14:07:27 +0000 Subject: [PATCH 12/17] add keyborg test again --- .../react-tabster/src/useKeyborg.cy.tsx | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 packages/react-components/react-tabster/src/useKeyborg.cy.tsx diff --git a/packages/react-components/react-tabster/src/useKeyborg.cy.tsx b/packages/react-components/react-tabster/src/useKeyborg.cy.tsx new file mode 100644 index 0000000000000..a027be8a0b4cb --- /dev/null +++ b/packages/react-components/react-tabster/src/useKeyborg.cy.tsx @@ -0,0 +1,25 @@ +import * as React from 'react'; +import { KEYBOARD_NAV_ATTRIBUTE } from './focus/constants'; +import { useKeyboardNavAttribute } from '@fluentui/react-tabster'; +import { mount as mountBase } from '@cypress/react'; +import { FluentProvider } from '@fluentui/react-provider'; +import { teamsLightTheme } from '@fluentui/react-theme'; + +const mount = (element: JSX.Element) => { + mountBase({element}); +}; + +describe('Keyborg', () => { + const Example = () => ( +
+ + +
+ ); + + it('should open when clicked', () => { + mount(); + cy.contains('Start').focus().realPress('Tab'); + cy.contains('Finish').should('be.focused').parent().should('have.attr', KEYBOARD_NAV_ATTRIBUTE); + }); +}); From e328ff0d6b1073a60517aafa1f6f2c0289ecc896 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Thu, 28 Sep 2023 14:11:08 +0000 Subject: [PATCH 13/17] add tsconfig paths to cypress --- scripts/cypress/src/base.config.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/scripts/cypress/src/base.config.ts b/scripts/cypress/src/base.config.ts index b38813a4c3de8..d3a646e0d07a5 100644 --- a/scripts/cypress/src/base.config.ts +++ b/scripts/cypress/src/base.config.ts @@ -1,6 +1,7 @@ import * as path from 'path'; import { defineConfig } from 'cypress'; +import { TsconfigPathsPlugin } from 'tsconfig-paths-webpack-plugin'; import type { Configuration } from 'webpack'; /** @@ -32,6 +33,14 @@ const cypressWebpackConfig = (): Configuration => { }); } + baseWebpackConfig.resolve ??= {}; + baseWebpackConfig.resolve.plugins ??= []; + baseWebpackConfig.resolve.plugins.push( + new TsconfigPathsPlugin({ + configFile: path.resolve(__dirname, '../../../tsconfig.base.json'), + }), + ); + return baseWebpackConfig; }; From 076d08fa9d15a34315c822aee3017ce3bf89800a Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Thu, 28 Sep 2023 14:15:30 +0000 Subject: [PATCH 14/17] feat: Implement auto reflow for MessageBar layouts --- .../react-message-bar-preview/package.json | 1 + .../components/MessageBar/MessageBar.test.tsx | 15 ++++ .../components/MessageBar/MessageBar.types.ts | 1 - .../components/MessageBar/useMessageBar.ts | 15 +++- .../MessageBar/useMessageBarReflow.ts | 72 +++++++++++++++++++ .../MessageBar/useMessageBarStyles.styles.ts | 6 +- .../useMessageBarActionsStyles.styles.ts | 1 - .../src/contexts/messageBarContext.ts | 2 +- .../MessageBar/ManualLayout.stories.tsx | 32 +++++++++ .../stories/MessageBar/Multiline.stories.tsx | 23 ------ .../stories/MessageBar/Reflow.stories.tsx | 62 ++++++++++++++++ .../stories/MessageBar/index.stories.tsx | 3 +- 12 files changed, 202 insertions(+), 31 deletions(-) create mode 100644 packages/react-components/react-message-bar-preview/src/components/MessageBar/useMessageBarReflow.ts create mode 100644 packages/react-components/react-message-bar-preview/stories/MessageBar/ManualLayout.stories.tsx delete mode 100644 packages/react-components/react-message-bar-preview/stories/MessageBar/Multiline.stories.tsx create mode 100644 packages/react-components/react-message-bar-preview/stories/MessageBar/Reflow.stories.tsx diff --git a/packages/react-components/react-message-bar-preview/package.json b/packages/react-components/react-message-bar-preview/package.json index 5f6653aaf116f..52b330693512a 100644 --- a/packages/react-components/react-message-bar-preview/package.json +++ b/packages/react-components/react-message-bar-preview/package.json @@ -34,6 +34,7 @@ "@fluentui/react-button": "^9.3.43", "@fluentui/react-icons": "^2.0.217", "@fluentui/react-jsx-runtime": "^9.0.12", + "@fluentui/react-shared-contexts": "^9.9.1", "@fluentui/react-theme": "^9.1.14", "@fluentui/react-utilities": "^9.13.5", "@griffel/react": "^1.5.14", diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.test.tsx b/packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.test.tsx index ca8524d34d7bb..6a6badfa9ec04 100644 --- a/packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.test.tsx +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.test.tsx @@ -4,6 +4,21 @@ import { isConformant } from '../../testing/isConformant'; import { MessageBar } from './MessageBar'; describe('MessageBar', () => { + beforeAll(() => { + // https://github.com/jsdom/jsdom/issues/3368 + global.ResizeObserver = class ResizeObserver { + public observe() { + // do nothing + } + public unobserve() { + // do nothing + } + public disconnect() { + // do nothing + } + }; + }); + isConformant({ Component: MessageBar, displayName: 'MessageBar', diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.types.ts b/packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.types.ts index 9d0bce2e0aa9e..9a1689458def9 100644 --- a/packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.types.ts +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBar/MessageBar.types.ts @@ -15,7 +15,6 @@ export type MessageBarContextValues = { */ export type MessageBarProps = ComponentProps & Pick & { - multiline?: boolean; intent?: 'info' | 'success' | 'warning' | 'error'; }; diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBar/useMessageBar.ts b/packages/react-components/react-message-bar-preview/src/components/MessageBar/useMessageBar.ts index b9759360a050d..90fd2d55e5bce 100644 --- a/packages/react-components/react-message-bar-preview/src/components/MessageBar/useMessageBar.ts +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBar/useMessageBar.ts @@ -1,7 +1,8 @@ import * as React from 'react'; -import { getNativeElementProps, slot } from '@fluentui/react-utilities'; +import { getNativeElementProps, slot, useMergedRefs } from '@fluentui/react-utilities'; import type { MessageBarProps, MessageBarState } from './MessageBar.types'; import { getIntentIcon } from './getIntentIcon'; +import { useMessageBarReflow } from './useMessageBarReflow'; /** * Create the state required to render MessageBar. @@ -13,7 +14,15 @@ import { getIntentIcon } from './getIntentIcon'; * @param ref - reference to root HTMLElement of MessageBar */ export const useMessageBar_unstable = (props: MessageBarProps, ref: React.Ref): MessageBarState => { - const { layout = 'singleline', intent = 'info' } = props; + const { intent = 'info' } = props; + let { layout = 'auto' } = props; + + const autoReflow = layout === 'auto'; + const { ref: reflowRef, reflowing } = useMessageBarReflow(autoReflow); + + if (autoReflow) { + layout = reflowing ? 'multiline' : 'singleline'; + } return { components: { @@ -22,7 +31,7 @@ export const useMessageBar_unstable = (props: MessageBarProps, ref: React.Ref ({}), {})[1]; + const reflowingRef = React.useRef(false); + const resizeObserverRef = React.useRef(null); + const prevInlineSizeRef = React.useRef(-1); + + const handleResize: ResizeObserverCallback = React.useCallback( + entries => { + // Resize observer is only owned by this component - one resize observer entry expected + // No need to support mutliple fragments - one border box entry expected + const entry = entries[0]; + const borderBoxSize = entry?.borderBoxSize[0]; + if (!borderBoxSize || !entry) { + return; + } + + const { inlineSize } = borderBoxSize; + const { target } = entry; + + if (!isHTMLElement(target)) { + return; + } + + let nextReflowing = reflowingRef.current; + + if (!reflowingRef.current) { + const scrollWidth = target.scrollWidth; + if (inlineSize < scrollWidth) { + nextReflowing = true; + } + } + + // No easy way to really determine when the single line layout will fit + // Just keep try to set single line layout as long as the size is growing + // Will cause flickering when size is being adjusted gradually (i.e drag) - but this should not be a common case + if (reflowingRef.current) { + if (prevInlineSizeRef.current < inlineSize) { + nextReflowing = false; + } + } + + if (reflowingRef.current !== nextReflowing) { + reflowingRef.current = nextReflowing; + forceUpdate(); + } + + prevInlineSizeRef.current = inlineSize; + }, + [forceUpdate], + ); + + const ref = React.useCallback( + (el: HTMLElement | null) => { + if (!enabled || !el || !targetDocument?.defaultView) { + return; + } + + const win = targetDocument.defaultView; + const resizeObserver = new win.ResizeObserver(handleResize); + resizeObserverRef.current = resizeObserver; + resizeObserver.observe(el, { box: 'border-box' }); + }, + [targetDocument, handleResize, enabled], + ); + + return { ref, reflowing: reflowingRef.current }; +} diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBar/useMessageBarStyles.styles.ts b/packages/react-components/react-message-bar-preview/src/components/MessageBar/useMessageBarStyles.styles.ts index 974c68afba45f..84aee427f6785 100644 --- a/packages/react-components/react-message-bar-preview/src/components/MessageBar/useMessageBarStyles.styles.ts +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBar/useMessageBarStyles.styles.ts @@ -9,8 +9,10 @@ export const messageBarClassNames: SlotClassNames = { }; const useRootBaseStyles = makeResetStyles({ + whiteSpace: 'nowrap', display: 'grid', - gridTemplateColumns: 'auto 1fr auto auto', + gridTemplateColumns: 'auto 2fr auto auto', + gridTemplateRows: '1fr', gridTemplateAreas: '"icon body secondaryActions actions"', ...shorthands.padding('0', tokens.spacingHorizontalM), ...shorthands.border(tokens.strokeWidthThin, 'solid', tokens.colorNeutralStroke1), @@ -30,6 +32,8 @@ const useIconBaseStyles = makeResetStyles({ const useMultilineStyles = makeStyles({ rootMultiline: { + whiteSpace: 'normal', + alignItems: 'start', paddingTop: tokens.spacingVerticalMNudge, gridTemplateColumns: 'auto 1fr auto', gridTemplateAreas: ` diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBarActions/useMessageBarActionsStyles.styles.ts b/packages/react-components/react-message-bar-preview/src/components/MessageBarActions/useMessageBarActionsStyles.styles.ts index dead8bcab465b..b787bcf01471b 100644 --- a/packages/react-components/react-message-bar-preview/src/components/MessageBarActions/useMessageBarActionsStyles.styles.ts +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBarActions/useMessageBarActionsStyles.styles.ts @@ -14,7 +14,6 @@ export const messageBarActionsClassNames: SlotClassNames const useRootBaseStyles = makeResetStyles({ ...shorthands.gridArea('secondaryActions'), display: 'flex', - alignItems: 'center', columnGap: tokens.spacingHorizontalM, marginRight: tokens.spacingHorizontalM, }); diff --git a/packages/react-components/react-message-bar-preview/src/contexts/messageBarContext.ts b/packages/react-components/react-message-bar-preview/src/contexts/messageBarContext.ts index add68734a6292..59c72989b360d 100644 --- a/packages/react-components/react-message-bar-preview/src/contexts/messageBarContext.ts +++ b/packages/react-components/react-message-bar-preview/src/contexts/messageBarContext.ts @@ -1,7 +1,7 @@ import * as React from 'react'; export type MessageBarContextValue = { - layout?: 'multiline' | 'singleline'; + layout?: 'multiline' | 'singleline' | 'auto'; }; const messageBarContext = React.createContext(undefined); diff --git a/packages/react-components/react-message-bar-preview/stories/MessageBar/ManualLayout.stories.tsx b/packages/react-components/react-message-bar-preview/stories/MessageBar/ManualLayout.stories.tsx new file mode 100644 index 0000000000000..34c942a80a7ad --- /dev/null +++ b/packages/react-components/react-message-bar-preview/stories/MessageBar/ManualLayout.stories.tsx @@ -0,0 +1,32 @@ +import * as React from 'react'; +import { Button, Link, Switch } from '@fluentui/react-components'; +import { DismissRegular } from '@fluentui/react-icons'; +import { MessageBar, MessageBarActions, MessageBarBody, MessageBarTitle } from '@fluentui/react-message-bar-preview'; + +const intents = ['info', 'warning', 'error', 'success'] as const; +export const ManualLayout = () => { + const [single, setSingle] = React.useState(true); + return ( + <> + setSingle(checked)} + /> +
+ {intents.map(intent => ( + + + Descriptive title + Message providing information to the user with actionable insights. Link + + } />}> + + + + + ))} +
+ + ); +}; diff --git a/packages/react-components/react-message-bar-preview/stories/MessageBar/Multiline.stories.tsx b/packages/react-components/react-message-bar-preview/stories/MessageBar/Multiline.stories.tsx deleted file mode 100644 index 87901e5318582..0000000000000 --- a/packages/react-components/react-message-bar-preview/stories/MessageBar/Multiline.stories.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import * as React from 'react'; -import { Button, Link } from '@fluentui/react-components'; -import { DismissRegular } from '@fluentui/react-icons'; -import { MessageBar, MessageBarActions, MessageBarBody, MessageBarTitle } from '@fluentui/react-message-bar-preview'; - -const intents = ['info', 'warning', 'error', 'success'] as const; - -export const Multiline = () => ( -
- {intents.map(intent => ( - - - Descriptive title - Message providing information to the user with actionable insights. Link - - } />}> - - - - - ))} -
-); diff --git a/packages/react-components/react-message-bar-preview/stories/MessageBar/Reflow.stories.tsx b/packages/react-components/react-message-bar-preview/stories/MessageBar/Reflow.stories.tsx new file mode 100644 index 0000000000000..2c19f885373a0 --- /dev/null +++ b/packages/react-components/react-message-bar-preview/stories/MessageBar/Reflow.stories.tsx @@ -0,0 +1,62 @@ +import * as React from 'react'; +import { Button, Link, makeStyles, shorthands, tokens, Switch, mergeClasses } from '@fluentui/react-components'; +import { DismissRegular } from '@fluentui/react-icons'; +import { MessageBar, MessageBarActions, MessageBarBody, MessageBarTitle } from '@fluentui/react-message-bar-preview'; + +const useStyles = makeStyles({ + compact: { + width: '600px', + }, + resizableArea: { + display: 'flex', + flexDirection: 'column', + ...shorthands.padding('30px', '10px'), + ...shorthands.gap('10px'), + ...shorthands.border('2px', 'solid', tokens.colorBrandBackground), + position: 'relative', + ...shorthands.overflow('hidden'), + '::after': { + content: `'Resizable Area'`, + position: 'absolute', + ...shorthands.padding('1px', '4px', '1px'), + top: '-2px', + left: '-2px', + fontFamily: 'monospace', + fontSize: '15px', + fontWeight: 900, + lineHeight: 1, + letterSpacing: '1px', + color: tokens.colorNeutralForegroundOnBrand, + backgroundColor: tokens.colorBrandBackground, + }, + }, +}); + +const intents = ['info', 'success', 'warning', 'error'] as const; +export const Reflow = () => { + const styles = useStyles(); + const [compact, setCompact] = React.useState(true); + return ( + <> + setCompact(checked)} + /> +
+ {intents.map(intent => ( + + + Descriptive title + Message providing information to the user with actionable insights. Link + + } />}> + + + + + ))} +
+ + ); +}; diff --git a/packages/react-components/react-message-bar-preview/stories/MessageBar/index.stories.tsx b/packages/react-components/react-message-bar-preview/stories/MessageBar/index.stories.tsx index e5af5995d3206..24d5580dba1eb 100644 --- a/packages/react-components/react-message-bar-preview/stories/MessageBar/index.stories.tsx +++ b/packages/react-components/react-message-bar-preview/stories/MessageBar/index.stories.tsx @@ -4,7 +4,8 @@ import descriptionMd from './MessageBarDescription.md'; import bestPracticesMd from './MessageBarBestPractices.md'; export { Default } from './Default.stories'; -export { Multiline } from './Multiline.stories'; +export { Reflow } from './Reflow.stories'; +export { ManualLayout } from './ManualLayout.stories'; export default { title: 'Preview Components/MessageBar', From 922672596383605e317bbf6ea3c0e9ad9e8db98c Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Thu, 28 Sep 2023 20:27:01 +0000 Subject: [PATCH 15/17] update api --- .../etc/react-message-bar-preview.api.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/react-components/react-message-bar-preview/etc/react-message-bar-preview.api.md b/packages/react-components/react-message-bar-preview/etc/react-message-bar-preview.api.md index 6c00a5ed41093..6c4bfcb1fd78e 100644 --- a/packages/react-components/react-message-bar-preview/etc/react-message-bar-preview.api.md +++ b/packages/react-components/react-message-bar-preview/etc/react-message-bar-preview.api.md @@ -59,12 +59,11 @@ export const MessageBarContextProvider: React_2.Provider & Pick & { - multiline?: boolean; intent?: 'info' | 'success' | 'warning' | 'error'; }; From be973b40b5d595181d497ad7a5f0a340c98f2fe3 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Fri, 29 Sep 2023 12:30:44 +0000 Subject: [PATCH 16/17] pr comments --- .../components/MessageBar/useMessageBar.ts | 9 ++-- .../MessageBar/useMessageBarReflow.ts | 42 +++++++++++++------ .../stories/MessageBar/Reflow.stories.tsx | 23 +++++----- 3 files changed, 42 insertions(+), 32 deletions(-) diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBar/useMessageBar.ts b/packages/react-components/react-message-bar-preview/src/components/MessageBar/useMessageBar.ts index 90fd2d55e5bce..c65ec5e43ef92 100644 --- a/packages/react-components/react-message-bar-preview/src/components/MessageBar/useMessageBar.ts +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBar/useMessageBar.ts @@ -14,15 +14,12 @@ import { useMessageBarReflow } from './useMessageBarReflow'; * @param ref - reference to root HTMLElement of MessageBar */ export const useMessageBar_unstable = (props: MessageBarProps, ref: React.Ref): MessageBarState => { - const { intent = 'info' } = props; - let { layout = 'auto' } = props; + const { layout = 'auto', intent = 'info' } = props; const autoReflow = layout === 'auto'; const { ref: reflowRef, reflowing } = useMessageBarReflow(autoReflow); - if (autoReflow) { - layout = reflowing ? 'multiline' : 'singleline'; - } + const computedLayout = autoReflow ? (reflowing ? 'multiline' : 'singleline') : layout; return { components: { @@ -42,7 +39,7 @@ export const useMessageBar_unstable = (props: MessageBarProps, ref: React.Ref { // Resize observer is only owned by this component - one resize observer entry expected - // No need to support mutliple fragments - one border box entry expected + // No need to support mutliple fragments - one border box entry expected + if (process.env.NODE_ENV !== 'production' && entries.length > 1) { + // eslint-disable-next-line no-console + console.error( + [ + 'useMessageBarReflow: Resize observer should only have one entry. ', + 'If multiple entries are observed, the first entry will be used.', + 'This is a bug, please report it to the Fluent UI team.', + ].join(' '), + ); + } + const entry = entries[0]; const borderBoxSize = entry?.borderBoxSize[0]; if (!borderBoxSize || !entry) { @@ -26,30 +37,27 @@ export function useMessageBarReflow(enabled: boolean = false) { return; } - let nextReflowing = reflowingRef.current; - - if (!reflowingRef.current) { - const scrollWidth = target.scrollWidth; - if (inlineSize < scrollWidth) { - nextReflowing = true; - } - } + let nextReflowing: boolean | undefined; // No easy way to really determine when the single line layout will fit // Just keep try to set single line layout as long as the size is growing - // Will cause flickering when size is being adjusted gradually (i.e drag) - but this should not be a common case + // Will cause flickering when size is being adjusted gradually (i.e. drag) - but this should not be a common case if (reflowingRef.current) { if (prevInlineSizeRef.current < inlineSize) { nextReflowing = false; } + } else { + const scrollWidth = target.scrollWidth; + if (inlineSize < scrollWidth) { + nextReflowing = true; + } } - if (reflowingRef.current !== nextReflowing) { + prevInlineSizeRef.current = inlineSize; + if (typeof nextReflowing !== 'undefined' && reflowingRef.current !== nextReflowing) { reflowingRef.current = nextReflowing; forceUpdate(); } - - prevInlineSizeRef.current = inlineSize; }, [forceUpdate], ); @@ -60,6 +68,8 @@ export function useMessageBarReflow(enabled: boolean = false) { return; } + resizeObserverRef.current?.disconnect(); + const win = targetDocument.defaultView; const resizeObserver = new win.ResizeObserver(handleResize); resizeObserverRef.current = resizeObserver; @@ -68,5 +78,11 @@ export function useMessageBarReflow(enabled: boolean = false) { [targetDocument, handleResize, enabled], ); + React.useEffect(() => { + return () => { + resizeObserverRef.current?.disconnect(); + }; + }, []); + return { ref, reflowing: reflowingRef.current }; } diff --git a/packages/react-components/react-message-bar-preview/stories/MessageBar/Reflow.stories.tsx b/packages/react-components/react-message-bar-preview/stories/MessageBar/Reflow.stories.tsx index 2c19f885373a0..58982e5894df6 100644 --- a/packages/react-components/react-message-bar-preview/stories/MessageBar/Reflow.stories.tsx +++ b/packages/react-components/react-message-bar-preview/stories/MessageBar/Reflow.stories.tsx @@ -32,7 +32,6 @@ const useStyles = makeStyles({ }, }); -const intents = ['info', 'success', 'warning', 'error'] as const; export const Reflow = () => { const styles = useStyles(); const [compact, setCompact] = React.useState(true); @@ -44,18 +43,16 @@ export const Reflow = () => { onChange={(_, { checked }) => setCompact(checked)} />
- {intents.map(intent => ( - - - Descriptive title - Message providing information to the user with actionable insights. Link - - } />}> - - - - - ))} + + + Descriptive title + Message providing information to the user with actionable insights. Link + + } />}> + + + +
); From 7ccaa4f89786f7187eb6f31e2eea439b454a823a Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Fri, 29 Sep 2023 12:46:06 +0000 Subject: [PATCH 17/17] cleanup --- .../etc/react-message-bar-preview.api.md | 6 +++--- .../src/components/MessageBarActions/MessageBarActions.tsx | 2 +- .../MessageBarActions/MessageBarActions.types.ts | 2 +- .../src/components/MessageBarBody/MessageBarBody.types.ts | 2 +- .../components/MessageBarTitle/MessageBarTitle.types.ts | 2 +- .../components/MessageBarTitle/renderMessageBarTitle.tsx | 7 +------ 6 files changed, 8 insertions(+), 13 deletions(-) diff --git a/packages/react-components/react-message-bar-preview/etc/react-message-bar-preview.api.md b/packages/react-components/react-message-bar-preview/etc/react-message-bar-preview.api.md index 6c4bfcb1fd78e..7fe7c5e792594 100644 --- a/packages/react-components/react-message-bar-preview/etc/react-message-bar-preview.api.md +++ b/packages/react-components/react-message-bar-preview/etc/react-message-bar-preview.api.md @@ -23,7 +23,7 @@ export const MessageBarActions: ForwardRefComponent; export const messageBarActionsClassNames: SlotClassNames; // @public -export type MessageBarActionsProps = ComponentProps & {}; +export type MessageBarActionsProps = ComponentProps; // @public (undocumented) export type MessageBarActionsSlots = { @@ -41,7 +41,7 @@ export const MessageBarBody: ForwardRefComponent; export const messageBarBodyClassNames: SlotClassNames; // @public -export type MessageBarBodyProps = ComponentProps & {}; +export type MessageBarBodyProps = ComponentProps; // @public (undocumented) export type MessageBarBodySlots = { @@ -83,7 +83,7 @@ export const MessageBarTitle: ForwardRefComponent; export const messageBarTitleClassNames: SlotClassNames; // @public -export type MessageBarTitleProps = ComponentProps & {}; +export type MessageBarTitleProps = ComponentProps; // @public (undocumented) export type MessageBarTitleSlots = { diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBarActions/MessageBarActions.tsx b/packages/react-components/react-message-bar-preview/src/components/MessageBarActions/MessageBarActions.tsx index ca28530fe5939..1e20b5f1f8e83 100644 --- a/packages/react-components/react-message-bar-preview/src/components/MessageBarActions/MessageBarActions.tsx +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBarActions/MessageBarActions.tsx @@ -6,7 +6,7 @@ import { useMessageBarActionsStyles_unstable } from './useMessageBarActionsStyle import type { MessageBarActionsProps } from './MessageBarActions.types'; /** - * MessageBarActions component - TODO: add more docs + * MessageBarActions component */ export const MessageBarActions: ForwardRefComponent = React.forwardRef((props, ref) => { const state = useMessageBarActions_unstable(props, ref); diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBarActions/MessageBarActions.types.ts b/packages/react-components/react-message-bar-preview/src/components/MessageBarActions/MessageBarActions.types.ts index 796972f7bbf8e..c8c04ee199908 100644 --- a/packages/react-components/react-message-bar-preview/src/components/MessageBarActions/MessageBarActions.types.ts +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBarActions/MessageBarActions.types.ts @@ -9,7 +9,7 @@ export type MessageBarActionsSlots = { /** * MessageBarActions Props */ -export type MessageBarActionsProps = ComponentProps & {}; +export type MessageBarActionsProps = ComponentProps; /** * State used in rendering MessageBarActions diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBarBody/MessageBarBody.types.ts b/packages/react-components/react-message-bar-preview/src/components/MessageBarBody/MessageBarBody.types.ts index f471468f316af..3d935e94c4003 100644 --- a/packages/react-components/react-message-bar-preview/src/components/MessageBarBody/MessageBarBody.types.ts +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBarBody/MessageBarBody.types.ts @@ -7,7 +7,7 @@ export type MessageBarBodySlots = { /** * MessageBarBody Props */ -export type MessageBarBodyProps = ComponentProps & {}; +export type MessageBarBodyProps = ComponentProps; /** * State used in rendering MessageBarBody diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/MessageBarTitle.types.ts b/packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/MessageBarTitle.types.ts index d966942d2b9fd..6a36a9db8aa90 100644 --- a/packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/MessageBarTitle.types.ts +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/MessageBarTitle.types.ts @@ -7,7 +7,7 @@ export type MessageBarTitleSlots = { /** * MessageBarTitle Props */ -export type MessageBarTitleProps = ComponentProps & {}; +export type MessageBarTitleProps = ComponentProps; /** * State used in rendering MessageBarTitle diff --git a/packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/renderMessageBarTitle.tsx b/packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/renderMessageBarTitle.tsx index 6b008739d5dcb..8f5e1978bc2e1 100644 --- a/packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/renderMessageBarTitle.tsx +++ b/packages/react-components/react-message-bar-preview/src/components/MessageBarTitle/renderMessageBarTitle.tsx @@ -10,10 +10,5 @@ import type { MessageBarTitleState, MessageBarTitleSlots } from './MessageBarTit export const renderMessageBarTitle_unstable = (state: MessageBarTitleState) => { assertSlots(state); - // TODO Add additional slots in the appropriate place - return ( - <> - - - ); + return ; };