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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const MessageBarActions: ForwardRefComponent<MessageBarActionsProps>;
export const messageBarActionsClassNames: SlotClassNames<MessageBarActionsSlots>;

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

// @public (undocumented)
export type MessageBarActionsSlots = {
Expand All @@ -41,7 +41,7 @@ export const MessageBarBody: ForwardRefComponent<MessageBarBodyProps>;
export const messageBarBodyClassNames: SlotClassNames<MessageBarBodySlots>;

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

// @public (undocumented)
export type MessageBarBodySlots = {
Expand All @@ -59,12 +59,11 @@ export const MessageBarContextProvider: React_2.Provider<MessageBarContextValue

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

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

Expand All @@ -84,7 +83,7 @@ export const MessageBarTitle: ForwardRefComponent<MessageBarTitleProps>;
export const messageBarTitleClassNames: SlotClassNames<MessageBarTitleSlots>;

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

// @public (undocumented)
export type MessageBarTitleSlots = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ export type MessageBarContextValues = {
*/
export type MessageBarProps = ComponentProps<MessageBarSlots> &
Pick<MessageBarContextValue, 'layout'> & {
multiline?: boolean;
intent?: 'info' | 'success' | 'warning' | 'error';
};

Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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<HTMLElement>): 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: {
Expand All @@ -22,7 +28,7 @@ export const useMessageBar_unstable = (props: MessageBarProps, ref: React.Ref<HT
},
root: slot.always(
getNativeElementProps('div', {
ref,
ref: useMergedRefs(ref, reflowRef),
...props,
}),
{ elementType: 'div' },
Expand All @@ -33,7 +39,7 @@ export const useMessageBar_unstable = (props: MessageBarProps, ref: React.Ref<HT
elementType: 'div',
defaultProps: { children: getIntentIcon(intent) },
}),
layout,
layout: computedLayout,
intent,
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import * as React from 'react';
import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts';
import { isHTMLElement } from '@fluentui/react-utilities';

export function useMessageBarReflow(enabled: boolean = false) {
const { targetDocument } = useFluent();
const forceUpdate = React.useReducer(() => ({}), {})[1];
const reflowingRef = React.useRef(false);
const resizeObserverRef = React.useRef<ResizeObserver | null>(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];
Comment thread
ling1726 marked this conversation as resolved.
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();
}
Comment thread
ling1726 marked this conversation as resolved.
},
[forceUpdate],
);

const ref = React.useCallback(
(el: HTMLElement | null) => {
if (!enabled || !el || !targetDocument?.defaultView) {
return;
}

Comment thread
ling1726 marked this conversation as resolved.
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],
);

Comment thread
ling1726 marked this conversation as resolved.
React.useEffect(() => {
return () => {
resizeObserverRef.current?.disconnect();
};
}, []);

return { ref, reflowing: reflowingRef.current };
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ export const messageBarClassNames: SlotClassNames<MessageBarSlots> = {
};

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),
Expand All @@ -30,6 +32,8 @@ const useIconBaseStyles = makeResetStyles({

const useMultilineStyles = makeStyles({
rootMultiline: {
whiteSpace: 'normal',
alignItems: 'start',
paddingTop: tokens.spacingVerticalMNudge,
gridTemplateColumns: 'auto 1fr auto',
gridTemplateAreas: `
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<MessageBarActionsProps> = React.forwardRef((props, ref) => {
const state = useMessageBarActions_unstable(props, ref);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export type MessageBarActionsSlots = {
/**
* MessageBarActions Props
*/
export type MessageBarActionsProps = ComponentProps<MessageBarActionsSlots> & {};
export type MessageBarActionsProps = ComponentProps<MessageBarActionsSlots>;

/**
* State used in rendering MessageBarActions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ export const messageBarActionsClassNames: SlotClassNames<MessageBarActionsSlots>
const useRootBaseStyles = makeResetStyles({
...shorthands.gridArea('secondaryActions'),
display: 'flex',
alignItems: 'center',
columnGap: tokens.spacingHorizontalM,
marginRight: tokens.spacingHorizontalM,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export type MessageBarBodySlots = {
/**
* MessageBarBody Props
*/
export type MessageBarBodyProps = ComponentProps<MessageBarBodySlots> & {};
export type MessageBarBodyProps = ComponentProps<MessageBarBodySlots>;

/**
* State used in rendering MessageBarBody
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export type MessageBarTitleSlots = {
/**
* MessageBarTitle Props
*/
export type MessageBarTitleProps = ComponentProps<MessageBarTitleSlots> & {};
export type MessageBarTitleProps = ComponentProps<MessageBarTitleSlots>;

/**
* State used in rendering MessageBarTitle
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,5 @@ import type { MessageBarTitleState, MessageBarTitleSlots } from './MessageBarTit
export const renderMessageBarTitle_unstable = (state: MessageBarTitleState) => {
assertSlots<MessageBarTitleSlots>(state);

// TODO Add additional slots in the appropriate place
return (
<>
<state.root />
</>
);
return <state.root />;
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as React from 'react';

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

Expand Down
Original file line number Diff line number Diff line change
@@ -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 (
<>
<Switch
label={single ? 'Single line layout' : 'Multi line layout'}
checked={single}
onChange={(_, { checked }) => setSingle(checked)}
/>
<div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
{intents.map(intent => (
<MessageBar key={intent} layout={single ? 'singleline' : 'multiline'} intent={intent}>
Comment thread
ling1726 marked this conversation as resolved.
<MessageBarBody>
<MessageBarTitle>Descriptive title</MessageBarTitle>
Message providing information to the user with actionable insights. <Link>Link</Link>
</MessageBarBody>
<MessageBarActions containerAction={<Button appearance="transparent" icon={<DismissRegular />} />}>
<Button>Action</Button>
<Button>Action</Button>
</MessageBarActions>
</MessageBar>
))}
</div>
</>
);
};

This file was deleted.

Loading