Skip to content

Commit

Permalink
fixup! fixup! fixup! fixup! fixup! Feat(web-react): Introduce Toast c…
Browse files Browse the repository at this point in the history
…omponent
  • Loading branch information
pavelklibani committed Apr 4, 2024
1 parent 211ebe4 commit 4e99ddc
Show file tree
Hide file tree
Showing 9 changed files with 62 additions and 32 deletions.
6 changes: 2 additions & 4 deletions packages/web-react/src/components/Toast/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,7 @@ once for several reasons:
especially on mobile screens. As of now, there is no automatic stacking of the toast queue items. It is the
responsibility of the developer to ensure that the Toast queue does not overflow the screen.**

⚠️ Please note that scrolling is only available on pointer-equipped devices (mouse, trackpad). Furthermore, scrolling is
only possible when the cursor is placed over the toast message boxes. This way the page content behind the toast
messages can remain accessible.
⚠️ Please note that scrolling is not available on iOS devices due to a limitation in the WebKit engine.

👉 Please note that the initial scroll position is always at the **top** of the queue.

Expand Down Expand Up @@ -188,7 +186,7 @@ to use the **inverted underlined** variant of the link (for all ToastBar colors)

👉 **Do not put any important actions** like "Undo" in the ToastBar component (unless there are other means to perform
said action), as it is very hard (if not impossible) to reach for users with assistive technologies. Read more about
[Toast accessibility](#scott-o-hara-toast) at Scott O'Hara's blog.
[Toast accessibility][scott-o-hara-toast] at Scott O'Hara's blog.

### Colors

Expand Down
5 changes: 3 additions & 2 deletions packages/web-react/src/components/Toast/ToastBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ import { Button } from '../Button';
import { Icon } from '../Icon';
import { VisuallyHidden } from '../VisuallyHidden';
import {
DEFAULT_ICONS,
ICON_BOX_SIZE,
TOAST_BAR_CLOSE_BUTTON_LABEL_DEFAULT,
TRANSITIONING_STYLES,
TRANSITION_DURATION,
} from './constants';
import { useToastBarStyleProps } from './useToastBarStyleProps';
import { useToastIcon } from './useToastIcon';

const ToastBar = (props: SpiritToastBarProps) => {
const {
Expand All @@ -29,6 +29,7 @@ const ToastBar = (props: SpiritToastBarProps) => {
...restProps
} = props;
const rootElementRef: MutableRefObject<HTMLDivElement | null> = useRef(null);
const toastIconName = useToastIcon({ color, iconName });
const { classProps, props: modifiedProps } = useToastBarStyleProps({
...restProps,
color,
Expand All @@ -47,7 +48,7 @@ const ToastBar = (props: SpiritToastBarProps) => {
ref={rootElementRef}
>
<div className={classProps.content}>
{(hasIcon || iconName) && <Icon name={iconName || DEFAULT_ICONS[color]} boxSize={ICON_BOX_SIZE} />}
{(hasIcon || iconName) && <Icon name={toastIconName} boxSize={ICON_BOX_SIZE} />}
<div className={classProps.message}>{children}</div>
</div>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ describe('Toast', () => {
it('should render with responsive alignments', () => {
const dom = render(
<Toast
alignmentX={{ desktop: 'left', tablet: 'center', mobile: 'right' }}
alignmentY={{ desktop: 'top', tablet: 'bottom', mobile: 'top' }}
alignmentX={{ mobile: 'right', tablet: 'center', desktop: 'left' }}
alignmentY={{ mobile: 'top', tablet: 'bottom', desktop: 'top' }}
/>,
);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { renderHook } from '@testing-library/react-hooks';
import { SpiritToastBarProps } from '../../../types';
import { useToastIcon } from '../useToastIcon';

describe('useToastIcon', () => {
it('should return defaults', () => {
const props = {};
const { result } = renderHook(() => useToastIcon(props));

expect(result.current).toBe('info');
});

it.each([
// color, expected icon name
['danger', 'danger'],
['informative', 'info'],
['inverted', 'info'],
['success', 'check-plain'],
['warning', 'warning'],
])('danger alert should return warning icon', (color, iconName) => {
const props = { color } as Partial<SpiritToastBarProps>;
const { result } = renderHook(() => useToastIcon(props));

expect(result.current).toBe(iconName);
});
});
8 changes: 0 additions & 8 deletions packages/web-react/src/components/Toast/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,6 @@ export const TRANSITIONING_STYLES: Record<string, string> = {
exited: 'is-hidden',
};

export const DEFAULT_ICONS: Record<string, string> = {
danger: 'danger',
informative: 'info',
inverted: 'info',
success: 'check-plain',
warning: 'warning',
};

export const ICON_BOX_SIZE = 20;

export const TOAST_BAR_CLOSE_BUTTON_LABEL_DEFAULT = 'Close';
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,6 @@ const ToastAlignment = () => {
<ToastBar
id="third-toastbar"
isOpen={isOpenThird}
// onClose={handleCloseThird}
onClose={() => setIsOpenThird(false)}
color="warning"
hasIcon
Expand Down
18 changes: 18 additions & 0 deletions packages/web-react/src/components/Toast/useToastIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useIconName } from '../../hooks/useIconName';
import { SpiritToastBarProps } from '../../types';

export function useToastIcon({ color, iconName }: Partial<SpiritToastBarProps>) {
const iconNameValue = useIconName(
color as string,
{
danger: 'danger',
informative: 'info',
inverted: 'info',
success: 'check-plain',
warning: 'warning',
},
'inverted',
);

return iconName || iconNameValue;
}
22 changes: 9 additions & 13 deletions packages/web-react/src/components/Toast/useToastStyleProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,20 @@ export function useToastStyleProps(props: SpiritToastProps): ToastStyles<SpiritT
const { alignmentX, alignmentY, ...restProps } = props;

const toastClass = useClassNamePrefix('Toast');
const classes: string[] = [];

function processAlignment(alignment: SpiritToastProps['alignmentX'] | SpiritToastProps['alignmentY']) {
if (typeof alignment === 'object' && alignment !== null) {
Object.keys(alignment).forEach((key) => {
const infix = key === 'mobile' ? '' : `--${key}`;
classes.push(`${toastClass}${infix}--${alignment[key as keyof typeof alignment]}`);
});
} else {
classes.push(`${toastClass}--${alignment}`);
}
return typeof alignment === 'object' && alignment !== null
? Object.keys(alignment).reduce<string[]>((acc, key) => {
const infix = key === 'mobile' ? '' : `--${key}`;

return [...acc, `${toastClass}${infix}--${alignment[key as keyof typeof alignment]}`];
}, [])
: [`${toastClass}--${alignment}`];
}

[alignmentX, alignmentY].forEach((alignment) => {
processAlignment(alignment);
});
const alignmentClasses = [...processAlignment(alignmentX), ...processAlignment(alignmentY)];

const toastRootClass = classNames(toastClass, classes);
const toastRootClass = classNames(toastClass, ...alignmentClasses);
const toastQueueClass = `${toastClass}__queue`;

return {
Expand Down
4 changes: 2 additions & 2 deletions packages/web-react/src/hooks/useIconName.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export function useIconName(key: string | undefined, iconMap: Record<string, string>) {
return key && iconMap[key] ? iconMap[key] : iconMap.default;
export function useIconName(key: string | undefined, iconMap: Record<string, string>, defaultKey: string = 'default') {
return key && iconMap[key] ? iconMap[key] : iconMap[defaultKey];
}

0 comments on commit 4e99ddc

Please sign in to comment.