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 6c00a5ed41093a..7fe7c5e7925947 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 = { @@ -59,12 +59,11 @@ export const MessageBarContextProvider: React_2.Provider & Pick & { - multiline?: boolean; intent?: 'info' | 'success' | 'warning' | 'error'; }; @@ -84,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/package.json b/packages/react-components/react-message-bar-preview/package.json index 5f6653aaf116fe..52b330693512a7 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 ca8524d34d7bb5..6a6badfa9ec047 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 9d0bce2e0aa9ec..9a1689458def95 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 b9759360a050d5..c65ec5e43ef920 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,12 @@ 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 { layout = 'auto', intent = 'info' } = props; + + const autoReflow = layout === 'auto'; + const { ref: reflowRef, reflowing } = useMessageBarReflow(autoReflow); + + const computedLayout = autoReflow ? (reflowing ? 'multiline' : 'singleline') : layout; return { components: { @@ -22,7 +28,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 + 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) { + return; + } + + const { inlineSize } = borderBoxSize; + const { target } = entry; + + if (!isHTMLElement(target)) { + return; + } + + 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 + if (reflowingRef.current) { + if (prevInlineSizeRef.current < inlineSize) { + nextReflowing = false; + } + } else { + const scrollWidth = target.scrollWidth; + if (inlineSize < scrollWidth) { + nextReflowing = true; + } + } + + prevInlineSizeRef.current = inlineSize; + if (typeof nextReflowing !== 'undefined' && reflowingRef.current !== nextReflowing) { + reflowingRef.current = nextReflowing; + forceUpdate(); + } + }, + [forceUpdate], + ); + + const ref = React.useCallback( + (el: HTMLElement | null) => { + if (!enabled || !el || !targetDocument?.defaultView) { + return; + } + + resizeObserverRef.current?.disconnect(); + + const win = targetDocument.defaultView; + const resizeObserver = new win.ResizeObserver(handleResize); + resizeObserverRef.current = resizeObserver; + resizeObserver.observe(el, { box: 'border-box' }); + }, + [targetDocument, handleResize, enabled], + ); + + React.useEffect(() => { + return () => { + resizeObserverRef.current?.disconnect(); + }; + }, []); + + 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 974c68afba45f8..5567cbd56c47c1 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', + 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/MessageBarActions.tsx b/packages/react-components/react-message-bar-preview/src/components/MessageBarActions/MessageBarActions.tsx index ca28530fe59397..1e20b5f1f8e83c 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 796972f7bbf8e9..c8c04ee199908c 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/MessageBarActions/useMessageBarActionsStyles.styles.ts b/packages/react-components/react-message-bar-preview/src/components/MessageBarActions/useMessageBarActionsStyles.styles.ts index dead8bcab465b9..b787bcf01471b6 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/components/MessageBarBody/MessageBarBody.types.ts b/packages/react-components/react-message-bar-preview/src/components/MessageBarBody/MessageBarBody.types.ts index f471468f316afb..3d935e94c4003e 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 d966942d2b9fd0..6a36a9db8aa904 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 6b008739d5dcb6..8f5e1978bc2e1e 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 ; }; 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 add68734a62921..59c72989b360d1 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 00000000000000..34c942a80a7add --- /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 87901e5318582a..00000000000000 --- 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 00000000000000..58982e5894df6a --- /dev/null +++ b/packages/react-components/react-message-bar-preview/stories/MessageBar/Reflow.stories.tsx @@ -0,0 +1,59 @@ +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, + }, + }, +}); + +export const Reflow = () => { + const styles = useStyles(); + const [compact, setCompact] = React.useState(true); + return ( + <> + setCompact(checked)} + /> +
+ + + 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 e5af5995d3206a..24d5580dba1ebd 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',