Skip to content

Commit

Permalink
feat: Inverted appearance (#28354)
Browse files Browse the repository at this point in the history
* feat: Inverted appearance

Adds the `appearance` prop to `Toast` and adds inverted styles to all
Toast components.

Renames the ToastContext to ToastContainerContext to avoid the clash
with the context that is actually being used by the Toast (i.e.
ToastContext) that holds the appearance value for the component.

* add extra VR tests

* update example

* improve types

* use shared context

* update types

* Update packages/react-components/react-toast/src/components/Toast/renderToast.tsx

Co-authored-by: Oleksandr Fediashov <[email protected]>

---------

Co-authored-by: Oleksandr Fediashov <[email protected]>
  • Loading branch information
ling1726 and layershifter committed Jun 30, 2023
1 parent 0cbd837 commit dde753d
Show file tree
Hide file tree
Showing 22 changed files with 292 additions and 58 deletions.
83 changes: 83 additions & 0 deletions apps/vr-tests-react-components/src/stories/Toast/Toast.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,86 @@ export const TitleOnly = () => {
</>
);
};

export const FullToastInverted = () => {
const { dispatchToast } = useToastController();
const { toastRef, onStatusChange } = useToastScreenshotData();
const dispatchToasts = () => {
for (const intent of toastIntents) {
dispatchToast(
<Toast ref={toastRef} appearance="inverted">
<ToastTitle action={<Link>Action</Link>}>This is a toast</ToastTitle>
<ToastBody subtitle="This is a toast">This is a toast</ToastBody>
<ToastFooter>
<Link>Action</Link>
<Link>Action</Link>
</ToastFooter>
</Toast>,
{
intent,
timeout: -1,
onStatusChange,
},
);
}
};
return (
<>
<button id="dispatch" onClick={dispatchToasts}>
Dispatch toasts
</button>
<Toaster />
</>
);
};

export const WithoutSubtitleInverted = () => {
const { dispatchToast } = useToastController();
const { toastRef, onStatusChange } = useToastScreenshotData();
const dispatchToasts = () => {
for (const intent of toastIntents) {
dispatchToast(
<Toast appearance="inverted" ref={toastRef}>
<ToastTitle action={<Link>Action</Link>}>This is a toast</ToastTitle>
<ToastBody>This is a toast</ToastBody>
<ToastFooter>
<Link>Action</Link>
<Link>Action</Link>
</ToastFooter>
</Toast>,
{ intent, timeout: -1, onStatusChange },
);
}
};
return (
<>
<button id="dispatch" onClick={dispatchToasts}>
Dispatch toasts
</button>
<Toaster />
</>
);
};

export const TitleOnlyInverted = () => {
const { dispatchToast } = useToastController();
const { toastRef, onStatusChange } = useToastScreenshotData();
const dispatchToasts = () => {
for (const intent of toastIntents) {
dispatchToast(
<Toast appearance="inverted" ref={toastRef}>
<ToastTitle action={<Link>Action</Link>}>This is a toast</ToastTitle>
</Toast>,
{ intent, onStatusChange, timeout: -1 },
);
}
};
return (
<>
<button id="dispatch" onClick={dispatchToasts}>
Dispatch toasts
</button>
<Toaster />
</>
);
};
19 changes: 14 additions & 5 deletions packages/react-components/react-toast/etc/react-toast.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import { ARIAButtonResultProps } from '@fluentui/react-aria';
import { ARIAButtonType } from '@fluentui/react-aria';
import { BackgroundAppearanceContextValue } from '@fluentui/react-shared-contexts';
import type { ComponentProps } from '@fluentui/react-utilities';
import type { ComponentState } from '@fluentui/react-utilities';
import type { ForwardRefComponent } from '@fluentui/react-utilities';
Expand All @@ -17,7 +18,7 @@ import type { SlotClassNames } from '@fluentui/react-utilities';
import type { TriggerProps } from '@fluentui/react-utilities';

// @public
export const renderToast_unstable: (state: ToastState) => JSX.Element;
export const renderToast_unstable: (state: ToastState, contextValues: ToastContextValues) => JSX.Element;

// @public
export const renderToastBody_unstable: (state: ToastBodyState) => JSX.Element;
Expand Down Expand Up @@ -50,7 +51,9 @@ export type ToastBodySlots = {
};

// @public
export type ToastBodyState = ComponentState<ToastBodySlots>;
export type ToastBodyState = ComponentState<ToastBodySlots> & {
backgroundAppearance: BackgroundAppearanceContextValue;
};

// @public (undocumented)
export const toastClassNames: SlotClassNames<ToastSlots>;
Expand Down Expand Up @@ -111,15 +114,19 @@ export type ToastPoliteness = 'assertive' | 'polite';
export type ToastPosition = 'top-end' | 'top-start' | 'bottom-end' | 'bottom-start';

// @public
export type ToastProps = ComponentProps<ToastSlots> & {};
export type ToastProps = ComponentProps<ToastSlots> & {
appearance?: BackgroundAppearanceContextValue;
};

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

// @public
export type ToastState = ComponentState<ToastSlots>;
export type ToastState = ComponentState<ToastSlots> & {
backgroundAppearance: BackgroundAppearanceContextValue;
};

// @public (undocumented)
export type ToastStatus = 'queued' | 'visible' | 'dismissed' | 'unmounted';
Expand All @@ -141,7 +148,9 @@ export type ToastTitleSlots = {
};

// @public
export type ToastTitleState = ComponentState<ToastTitleSlots> & Pick<ToastContextValue, 'intent'>;
export type ToastTitleState = ComponentState<ToastTitleSlots> & Pick<ToastContainerContextValue, 'intent'> & {
backgroundAppearance: BackgroundAppearanceContextValue;
};

// @public
export const ToastTrigger: React_2.FC<ToastTriggerProps>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { renderToast_unstable } from './renderToast';
import { useToastStyles_unstable } from './useToastStyles.styles';
import type { ToastProps } from './Toast.types';
import type { ForwardRefComponent } from '@fluentui/react-utilities';
import { useToastContextValues_unstable } from './useToastContextValues';

/**
* Toast component
Expand All @@ -12,7 +13,7 @@ export const Toast: ForwardRefComponent<ToastProps> = React.forwardRef((props, r
const state = useToast_unstable(props, ref);

useToastStyles_unstable(state);
return renderToast_unstable(state);
return renderToast_unstable(state, useToastContextValues_unstable(state));
});

Toast.displayName = 'Toast';
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities';
import { BackgroundAppearanceContextValue } from '@fluentui/react-shared-contexts';

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

export type ToastContextValues = {
backgroundAppearance: BackgroundAppearanceContextValue;
};

/**
* Toast Props
*/
export type ToastProps = ComponentProps<ToastSlots> & {};
export type ToastProps = ComponentProps<ToastSlots> & {
appearance?: BackgroundAppearanceContextValue;
};

/**
* State used in rendering Toast
*/
export type ToastState = ComponentState<ToastSlots>;
export type ToastState = ComponentState<ToastSlots> & {
backgroundAppearance: BackgroundAppearanceContextValue;
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@

import { createElement } from '@fluentui/react-jsx-runtime';
import { getSlotsNext } from '@fluentui/react-utilities';
import type { ToastState, ToastSlots } from './Toast.types';
import { BackgroundAppearanceProvider } from '@fluentui/react-shared-contexts';
import type { ToastState, ToastSlots, ToastContextValues } from './Toast.types';

/**
* Render the final JSX of Toast
*/
export const renderToast_unstable = (state: ToastState) => {
export const renderToast_unstable = (state: ToastState, contextValues: ToastContextValues) => {
const { slots, slotProps } = getSlotsNext<ToastSlots>(state);

return <slots.root {...slotProps.root} />;
return (
<BackgroundAppearanceProvider value={contextValues.backgroundAppearance}>
<slots.root {...slotProps.root} />
</BackgroundAppearanceProvider>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@ export const useToast_unstable = (props: ToastProps, ref: React.Ref<HTMLElement>
ref,
...props,
}),
backgroundAppearance: props.appearance,
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { ToastContextValues, ToastState } from './Toast.types';

export function useToastContextValues_unstable(state: ToastState): ToastContextValues {
const { backgroundAppearance } = state;

return {
backgroundAppearance,
};
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { makeResetStyles, mergeClasses, shorthands } from '@griffel/react';
import { makeResetStyles, makeStyles, mergeClasses, shorthands } from '@griffel/react';
import { tokens } from '@fluentui/react-theme';
import type { ToastSlots, ToastState } from './Toast.types';
import type { SlotClassNames } from '@fluentui/react-utilities';
Expand All @@ -21,12 +21,25 @@ const useRootBaseClassName = makeResetStyles({
backgroundColor: tokens.colorNeutralBackground1,
});

const useStyles = makeStyles({
inverted: {
color: tokens.colorNeutralForegroundInverted2,
backgroundColor: tokens.colorNeutralBackgroundInverted,
},
});

/**
* Apply styling to the Toast slots based on the state
*/
export const useToastStyles_unstable = (state: ToastState): ToastState => {
const rootBaseClassName = useRootBaseClassName();
state.root.className = mergeClasses(toastClassNames.root, rootBaseClassName, state.root.className);
const styles = useStyles();
state.root.className = mergeClasses(
toastClassNames.root,
rootBaseClassName,
state.backgroundAppearance === 'inverted' && styles.inverted,
state.root.className,
);

return state;
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities';
import { BackgroundAppearanceContextValue } from '@fluentui/react-shared-contexts';

export type ToastBodySlots = {
root: Slot<'div'>;
Expand All @@ -13,4 +14,6 @@ export type ToastBodyProps = ComponentProps<ToastBodySlots> & {};
/**
* State used in rendering ToastBody
*/
export type ToastBodyState = ComponentState<ToastBodySlots>;
export type ToastBodyState = ComponentState<ToastBodySlots> & {
backgroundAppearance: BackgroundAppearanceContextValue;
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import * as React from 'react';
import { getNativeElementProps, resolveShorthand } from '@fluentui/react-utilities';
import type { ToastBodyProps, ToastBodyState } from './ToastBody.types';
import { useToastContext } from '../../contexts/toastContext';
import { useToastContainerContext } from '../../contexts/toastContainerContext';
import { useBackgroundAppearance } from '@fluentui/react-shared-contexts';

/**
* Create the state required to render ToastBody.
Expand All @@ -13,7 +14,8 @@ import { useToastContext } from '../../contexts/toastContext';
* @param ref - reference to root HTMLElement of ToastBody
*/
export const useToastBody_unstable = (props: ToastBodyProps, ref: React.Ref<HTMLElement>): ToastBodyState => {
const { bodyId } = useToastContext();
const backgroundAppearance = useBackgroundAppearance();
const { bodyId } = useToastContainerContext();
return {
components: {
root: 'div',
Expand All @@ -25,5 +27,6 @@ export const useToastBody_unstable = (props: ToastBodyProps, ref: React.Ref<HTML
id: bodyId,
...props,
}),
backgroundAppearance,
};
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { makeResetStyles, mergeClasses } from '@griffel/react';
import { makeResetStyles, makeStyles, mergeClasses } from '@griffel/react';
import { tokens } from '@fluentui/react-theme';
import type { ToastBodySlots, ToastBodyState } from './ToastBody.types';
import type { SlotClassNames } from '@fluentui/react-utilities';
Expand All @@ -15,6 +15,7 @@ const useRootBaseClassName = makeResetStyles({
fontSize: tokens.fontSizeBase300,
lineHeight: tokens.fontSizeBase300,
fontWeight: tokens.fontWeightRegular,
color: tokens.colorNeutralForeground1,
});

const useSubtitleBaseClassName = makeResetStyles({
Expand All @@ -27,18 +28,34 @@ const useSubtitleBaseClassName = makeResetStyles({
color: tokens.colorNeutralForeground2,
});

const useInvertedStyles = makeStyles({
root: {
color: tokens.colorNeutralForegroundInverted2,
},
subtitle: {
color: tokens.colorNeutralForegroundInverted2,
},
});

/**
* Apply styling to the ToastBody slots based on the state
*/
export const useToastBodyStyles_unstable = (state: ToastBodyState): ToastBodyState => {
const rootBaseClassName = useRootBaseClassName();
const subtitleBaseClassName = useSubtitleBaseClassName();
state.root.className = mergeClasses(toastBodyClassNames.root, rootBaseClassName, state.root.className);
const invertedStyles = useInvertedStyles();
state.root.className = mergeClasses(
toastBodyClassNames.root,
rootBaseClassName,
state.backgroundAppearance === 'inverted' && invertedStyles.root,
state.root.className,
);

if (state.subtitle) {
state.subtitle.className = mergeClasses(
toastBodyClassNames.subtitle,
subtitleBaseClassName,
state.backgroundAppearance === 'inverted' && invertedStyles.subtitle,
state.subtitle.className,
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import * as React from 'react';
import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities';
import { Announce } from '../AriaLive/AriaLive.types';
import { Toast, ToastIntent } from '../../state';
import { ToastContextValue } from '../../contexts/toastContext';
import { ToastContainerContextValue } from '../../contexts/toastContainerContext';
import { TimerProps } from '../Timer/Timer';

export type ToastContainerContextValues = {
toast: ToastContextValue;
toast: ToastContainerContextValue;
};

export type ToastContainerSlots = {
Expand All @@ -30,7 +30,7 @@ export type ToastContainerProps = ComponentProps<Partial<ToastContainerSlots>> &
*/
export type ToastContainerState = ComponentState<ToastContainerSlots> &
Pick<ToastContainerProps, 'remove' | 'close' | 'updateId' | 'visible' | 'intent'> &
Pick<ToastContextValue, 'titleId' | 'bodyId'> & {
Pick<ToastContainerContextValue, 'titleId' | 'bodyId'> & {
transitionTimeout: number;
timerTimeout: number;
running: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { createElement } from '@fluentui/react-jsx-runtime';
import { getSlotsNext } from '@fluentui/react-utilities';
import { Transition } from 'react-transition-group';
import type { ToastContainerState, ToastContainerSlots, ToastContainerContextValues } from './ToastContainer.types';
import { ToastContextProvider } from '../../contexts/toastContext';
import { ToastContainerContextProvider } from '../../contexts/toastContainerContext';

/**
* Render the final JSX of ToastContainer
Expand All @@ -27,10 +27,10 @@ export const renderToastContainer_unstable = (
onEntering={onTransitionEntering}
nodeRef={nodeRef}
>
<ToastContextProvider value={contextValues.toast}>
<ToastContainerContextProvider value={contextValues.toast}>
<slots.root {...slotProps.root} />
<slots.timer {...slotProps.timer} />
</ToastContextProvider>
</ToastContainerContextProvider>
</Transition>
);
};
Loading

0 comments on commit dde753d

Please sign in to comment.