Skip to content

Commit

Permalink
feat(ToolTip): New + Improved
Browse files Browse the repository at this point in the history
New ToolTips + InfoTips, including adding required InfoTips to IconButtons
  • Loading branch information
dreamwasp authored Apr 4, 2024
1 parent c087bfd commit 6aaf2f8
Show file tree
Hide file tree
Showing 68 changed files with 1,821 additions and 475 deletions.
2 changes: 2 additions & 0 deletions packages/gamut/__tests__/__snapshots__/gamut.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ exports[`Gamut Exported Keys 1`] = `
"CTAButton",
"DataList",
"DataTable",
"DeprecatedToolTip",
"Dialog",
"Drawer",
"ExpandControl",
Expand All @@ -55,6 +56,7 @@ exports[`Gamut Exported Keys 1`] = `
"HiddenText",
"IconButton",
"iFrameWrapper",
"InfoTip",
"Input",
"InputStepper",
"isClickableCrumb",
Expand Down
3 changes: 2 additions & 1 deletion packages/gamut/src/Alert/Alert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -180,11 +180,12 @@ export const Alert: React.FC<AlertProps> = ({
{onClose && (
<IconButton
tabIndex={tabIndex}
aria-label="Close Alert"
variant="secondary"
size="small"
onClick={onClose}
icon={MiniDeleteIcon}
tip="Close alert"
tipProps={{ alignment: 'bottom-center', placement: 'floating' }}
/>
)}
</AlertWrapper>
Expand Down
1 change: 1 addition & 0 deletions packages/gamut/src/Alert/__tests__/Alert.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const mockUseMedia = jest.fn();

// not testing for mobile as default
jest.mock('react-use', () => ({
...jest.requireActual<{}>('react-use'),
get useMedia() {
return mockUseMedia;
},
Expand Down
48 changes: 41 additions & 7 deletions packages/gamut/src/Button/IconButton.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { useId } from '@reach/auto-id';
import { ComponentProps, forwardRef } from 'react';

import { ButtonBaseElements } from '../ButtonBase/ButtonBase';
import { ToolTip, ToolTipProps } from '../Tip';
import {
createButtonComponent,
IconComponentType,
Expand All @@ -15,22 +17,54 @@ const IconButtonBase = createButtonComponent(

export type IconButtonProps = ComponentProps<typeof IconButtonBase> &
IconComponentType & {
'aria-label': string;
'aria-label'?: string;
tip: string;
tipProps?: Omit<ToolTipProps, 'info' | 'id' | 'children' | 'hasLabel'>;
};

export const IconButton = forwardRef<ButtonBaseElements, IconButtonProps>(
({ children, icon: Icon, variant = 'secondary', ...props }, ref) => {
(
{
'aria-label': ariaLabel,
icon: Icon,
tip,
tipProps,
variant = 'secondary',
...props
},
ref
) => {
const tipId = useId();

const firstWord = tip?.split(' ')[0] ?? tip;

const hasRepetitiveLabel =
ariaLabel?.toLowerCase() === firstWord?.toLowerCase() || !ariaLabel;

const trueAriaLabel = ariaLabel ?? firstWord;

return (
<IconButtonBase {...props} variant={variant} ref={ref}>
{Icon && (
<ToolTip
info={tip}
id={tipId}
hasRepetitiveLabel={hasRepetitiveLabel}
{...(tipProps as any)}
l
>
<IconButtonBase
{...props}
variant={variant}
ref={ref}
aria-describedby={tipId}
aria-label={trueAriaLabel}
>
<Icon
width="calc(100% - 14px)"
height="calc(100% - 14px)"
aria-hidden
/>
)}
{children}
</IconButtonBase>
</IconButtonBase>
</ToolTip>
);
}
);
77 changes: 77 additions & 0 deletions packages/gamut/src/Button/__tests__/IconButton.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { StarIcon } from '@codecademy/gamut-icons';
import userEvent from '@testing-library/user-event';
import { setupRtl } from 'component-test-setup';

import { IconButton } from '../IconButton';
import { IconButtonFloatingMock } from './mocks';

const label = 'Click';
const tip = 'Click this button';
const tipText = 'this button';
const uniqueTip = 'I am not repetitive text';

const onClick = jest.fn();
const buttonProps = {
'aria-label': label,
icon: StarIcon,
onClick,
tip,
};

const renderView = setupRtl(IconButton, buttonProps);

const renderFloatingView = setupRtl(IconButtonFloatingMock, buttonProps);

describe('IconButton', () => {
it('renders a clickable button', () => {
const { view } = renderView();

const cta = view.getByRole('button', { name: label });

userEvent.click(cta);

expect(onClick).toHaveBeenCalled();
});
it('renders an icon ', () => {
const { view } = renderView();

view.getByTitle('Star Icon');
});

// TO-DO: When we upgrade jest, we can use `description` in the tests below to make sure they are semantically connected to buttons.
it('renders a tip with repetition removed', async () => {
const { view } = renderView({});

view.getByRole('button', { name: label });
view.getByRole('tooltip', { name: tipText });
});

it('renders a tip with both labels when they are not repetitive', async () => {
const { view } = renderView({ tip: uniqueTip });

view.getByRole('button', { name: label });
view.getByRole('tooltip', { name: uniqueTip });
});

it('renders a true aria-label based on tip when aria-label is not defined', async () => {
const { view } = renderView({ 'aria-label': undefined });

view.getByRole('button', { name: label });
view.getByRole('tooltip', { name: tipText });
});

it('renders a floating tip', async () => {
const { view } = renderFloatingView({});

view.getByRole('tooltip', { name: tipText });
expect(view.queryByText(tip)).toBeNull();

const cta = view.getByRole('button', { name: label });

expect(view.queryByText('tooltip')).toBeNull();

userEvent.hover(cta);

view.getByText(tip);
});
});
9 changes: 2 additions & 7 deletions packages/gamut/src/Button/__tests__/TextButton.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { StarIcon } from '@codecademy/gamut-icons';
import { fireEvent } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { setupRtl } from 'component-test-setup';

import { TextButton } from '../TextButton';
Expand All @@ -17,18 +17,13 @@ describe('TextButton', () => {

const cta = view.getByRole('button', { name: 'Click me!' });

fireEvent.click(cta);
userEvent.click(cta);

expect(onClick).toHaveBeenCalled();
});
it('renders an leading icon when an icon is provided', () => {
const { view } = renderView({ icon: StarIcon });

view.getByTitle('Star Icon');
});
it('renders an icon when an icon is provided', () => {
const { view } = renderView({ icon: StarIcon });

view.getByTitle('Star Icon');
});
});
20 changes: 20 additions & 0 deletions packages/gamut/src/Button/__tests__/mocks.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { ColorMode } from '@codecademy/gamut-styles';
import { MockGamutProvider } from '@codecademy/gamut-tests';
import React, { ComponentProps } from 'react';

import { Box } from '../../Box';
import { IconButton } from '..';

export const IconButtonFloatingMock: React.FC<
ComponentProps<typeof IconButton>
> = (props) => {
return (
<MockGamutProvider>
<ColorMode mode="light">
<Box width="500px">
<IconButton tipProps={{ placement: 'floating' }} {...props} />
</Box>
</ColorMode>
</MockGamutProvider>
);
};
14 changes: 10 additions & 4 deletions packages/gamut/src/ConnectedForm/ConnectedFormGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ import styled from '@emotion/styled';
import { useEffect } from 'react';
import * as React from 'react';

import { FormError, FormGroup, FormGroupLabel, FormGroupProps } from '..';
import {
FormError,
FormGroup,
FormGroupLabel,
FormGroupProps,
InfoTipProps,
} from '..';
import { Anchor } from '../Anchor';
import { HiddenText } from '../HiddenText';
import { Markdown } from '../Markdown';
Expand All @@ -28,7 +34,7 @@ export interface ConnectedFormGroupBaseProps
label: React.ReactNode;
required?: boolean;
showRequired?: boolean;
tooltip?: any;
infotip?: InfoTipProps;
}

export interface ConnectedFormGroupProps<T extends ConnectedField>
Expand All @@ -52,7 +58,7 @@ export function ConnectedFormGroup<T extends ConnectedField>({
name,
labelSize,
spacing = 'fit',
tooltip,
infotip,
}: ConnectedFormGroupProps<T>) {
const {
error,
Expand All @@ -77,7 +83,7 @@ export function ConnectedFormGroup<T extends ConnectedField>({
<FormGroupLabel
disabled={isDisabled}
htmlFor={id || name}
tooltip={tooltip}
infotip={infotip}
showRequired={showRequired && !!validation}
size={labelSize}
>
Expand Down
2 changes: 2 additions & 0 deletions packages/gamut/src/ConnectedForm/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ export const useFormState = () => {
setValue,
watch,
clearErrors,
setFocus,
} = useFormContext();

const {
Expand All @@ -126,6 +127,7 @@ export const useFormState = () => {
isDisabled: (isSubmitting || isSubmitSuccessful) && disableFieldsOnSubmit,
register,
reset,
setFocus,
setError,
setValue,
showRequired,
Expand Down
2 changes: 1 addition & 1 deletion packages/gamut/src/Flyout/__tests__/Flyout.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ describe('Flyout', () => {
const { props, view } = renderView({ expanded: true });

act(() => {
userEvent.click(view.getByLabelText(props.closeLabel));
userEvent.click(view.getByLabelText('Close'));
});

expect(props.onClose).toHaveBeenCalled();
Expand Down
36 changes: 20 additions & 16 deletions packages/gamut/src/Flyout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { MiniDeleteIcon } from '@codecademy/gamut-icons';
import { Background, Colors } from '@codecademy/gamut-styles';
import * as React from 'react';

import { WithChildrenProp } from '..';
import { FlexBox, WithChildrenProp } from '..';
import { IconButton } from '../Button';
import { Drawer } from '../Drawer';
import { Overlay } from '../Overlay';
Expand Down Expand Up @@ -38,7 +38,7 @@ export interface FlyoutProps extends WithChildrenProp {

export const Flyout: React.FC<FlyoutProps> = ({
children,
closeLabel,
closeLabel = 'Close',
expanded,
openFrom = 'left',
onClose,
Expand All @@ -63,25 +63,29 @@ export const Flyout: React.FC<FlyoutProps> = ({
top={0}
{...{ [openFrom]: 0 }}
>
<Text
as="h2"
fontSize={22}
<FlexBox
alignItems="center"
mb={8}
ml={16}
mt={24}
mr={40}
maxWidth="100%"
justifyContent="space-between"
>
{title}
</Text>
<IconButton
aria-label={closeLabel}
icon={MiniDeleteIcon}
onClick={onClose}
position="absolute"
top="1rem"
right="0.5rem"
/>
<Text as="h2" fontSize={22}>
{title}
</Text>
<IconButton
aria-label="Close"
icon={MiniDeleteIcon}
onClick={onClose}
mx={16}
tip={closeLabel}
tipProps={{
alignment: 'bottom-center',
placement: 'floating',
}}
/>
</FlexBox>
{children}
</Drawer>
</Background>
Expand Down
Loading

0 comments on commit 6aaf2f8

Please sign in to comment.