Skip to content

Commit

Permalink
Add Tooltip component (#2393)
Browse files Browse the repository at this point in the history
  • Loading branch information
connor-baer authored Mar 25, 2024
1 parent 6989d2e commit c5374e0
Show file tree
Hide file tree
Showing 31 changed files with 1,015 additions and 494 deletions.
5 changes: 5 additions & 0 deletions .changeset/beige-bottles-act.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@sumup/circuit-ui": minor
---

Added an experimental Tooltip component.
39 changes: 26 additions & 13 deletions .storybook/components/Icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
type SetStateAction,
useState,
ChangeEvent,
forwardRef,
} from 'react';
import { Unstyled } from '@storybook/addon-docs';
import * as iconComponents from '@sumup/icons';
Expand All @@ -33,17 +34,21 @@ import {
clsx,
utilClasses,
} from '../../packages/circuit-ui/index.js';
import { Tooltip } from '../../packages/circuit-ui/experimental.js';
import { slugify } from '../slugify.js';
import classes from './Icons.module.css';

function groupBy(
icons: IconsManifest['icons'],
key: 'name' | 'category' | 'size',
) {
return icons.reduce((groups, icon) => {
(groups[icon[key]] = groups[icon[key]] || []).push(icon);
return groups;
}, {});
return icons.reduce(
(groups, icon) => {
(groups[icon[key]] = groups[icon[key]] || []).push(icon);
return groups;
},
{} as Record<string, IconsManifest['icons']>,
);
}

function sortBy(
Expand Down Expand Up @@ -172,20 +177,28 @@ const Icons = () => {
}}
/>
</div>
<span id={id} className={classes.label} title={icon.name}>
{icon.name}
<span id={id} className={classes.label}>
{componentName}
{size === 'all' && (
<span className={classes.size}>{icon.size}</span>
)}
</span>
{icon.deprecation && (
<Badge
title={icon.deprecation}
variant="warning"
className={classes.badge}
>
Deprecated
</Badge>
<Tooltip
type="description"
label={icon.deprecation}
component={forwardRef((props, ref) => (
<Badge
{...props}
ref={ref}
tabIndex={0}
variant="danger"
className={classes.badge}
>
Deprecated
</Badge>
))}
/>
)}
</div>
);
Expand Down
1 change: 1 addition & 0 deletions .storybook/components/Stack.module.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.base {
position: relative;
display: flex;
flex-direction: column;
gap: 2rem;
Expand Down
47 changes: 47 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 0 additions & 14 deletions packages/circuit-ui/components/Button/IconButton.module.css
Original file line number Diff line number Diff line change
@@ -1,17 +1,3 @@
/* Visually hide the label */
.base > span:last-child > span:last-child {
/* .hide-visually */
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0 0 0 0);
white-space: nowrap;
border: 0;
}

/* Sizes */
.s {
padding: calc(var(--cui-spacings-bit) - var(--cui-border-width-kilo));
Expand Down
93 changes: 55 additions & 38 deletions packages/circuit-ui/components/Button/IconButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@
* limitations under the License.
*/

'use client';

import {
Children,
cloneElement,
forwardRef,
type ForwardRefExoticComponent,
type PropsWithoutRef,
type ReactElement,
Expand All @@ -27,9 +30,12 @@ import { clsx } from '../../styles/clsx.js';
import { CircuitError } from '../../util/errors.js';
import { deprecate } from '../../util/logger.js';
import { isString } from '../../util/type-check.js';
import Tooltip from '../Tooltip/index.js';
import { applyMultipleRefs } from '../../util/refs.js';

import {
createButtonComponent,
CreateButtonComponentProps,
legacyButtonSizeMap,
type SharedButtonProps,
} from './base.js';
Expand All @@ -56,22 +62,30 @@ export type IconButtonProps = SharedButtonProps & {
icon?: IconComponentType;
};

// TODO: This factory function doesn't make much sense for the IconButton anymore. Refactor?
const InnerIconButton = createButtonComponent<CreateButtonComponentProps>(
'IconButton',
(props) => props,
);

/**
* The IconButton component enables the user to perform an action or navigate
* to a different screen.
*/
export const IconButton: ForwardRefExoticComponent<
PropsWithoutRef<IconButtonProps> & RefAttributes<any>
> = createButtonComponent<IconButtonProps>(
'IconButton',
({
className,
icon: Icon,
label,
children,
size: legacySize = 'm',
...props
}) => {
> = forwardRef<HTMLButtonElement, IconButtonProps>(
(
{
className,
icon: Icon,
size: legacySize = 'm',
label,
children,
...props
},
ref,
) => {
const size = legacyButtonSizeMap[legacySize] || legacySize;

if (
Expand All @@ -96,33 +110,36 @@ export const IconButton: ForwardRefExoticComponent<
);
}

if (
process.env.NODE_ENV !== 'production' &&
legacyButtonSizeMap[legacySize]
) {
deprecate(
'IconButton',
`The \`${legacySize}\` size has been deprecated. Use the \`${legacyButtonSizeMap[legacySize]}\` size instead.`,
);
}

return {
className: clsx(classes.base, classes[size], className),
icon: (iconProps) => {
if (Icon) {
return <Icon {...iconProps} />;
}
const child = Children.only(children)!;
// TODO: Remove with the next major
if (isString(child)) {
return null;
}
return cloneElement(child, iconProps);
},
size,
children: isString(children) ? children : label,
title: isString(children) ? children : label,
...props,
};
return (
<Tooltip
type="label"
label={isString(children) ? children : (label as string)}
component={forwardRef((tooltipProps, tooltipRef) => (
<InnerIconButton
{...props}
{...tooltipProps}
size={size}
className={clsx(
classes.base,
classes[size],
tooltipProps.className,
className,
)}
icon={(iconProps) => {
if (Icon) {
return <Icon {...iconProps} />;
}
const child = Children.only(children)!;
// TODO: Remove with the next major
if (isString(child)) {
return null;
}
return cloneElement(child, iconProps);
}}
ref={applyMultipleRefs(ref, tooltipRef)}
/>
))}
/>
);
},
);
6 changes: 3 additions & 3 deletions packages/circuit-ui/components/Button/base.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export type CreateButtonComponentProps = SharedButtonProps & {
* with the button. Use one strong, clear imperative verb and follow with a
* one-word object if needed to clarify.
*/
children: ReactNode;
children?: ReactNode;
/**
* Choose from 2 sizes. Default: 'm'.
*/
Expand All @@ -124,7 +124,7 @@ export const legacyButtonSizeMap: Record<string, 's' | 'm'> = {

export function createButtonComponent<Props>(
componentName: string,
// TODO: Refactor to `mapClassName` once the deprecations have been removed.
// TODO: Refactor to `mapClassName` once the deprecations have been removed?
mapProps: (props: Props) => CreateButtonComponentProps,
) {
const Button = forwardRef<any, Props>((props, ref) => {
Expand Down Expand Up @@ -222,7 +222,7 @@ export function createButtonComponent<Props>(
height={leadingIconSize}
/>
)}
<span className={classes.label}>{children}</span>
{children && <span className={classes.label}>{children}</span>}
{TrailingIcon && (
<TrailingIcon
aria-hidden="true"
Expand Down
4 changes: 3 additions & 1 deletion packages/circuit-ui/components/ImageInput/ImageInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,9 @@ export const ImageInput = ({
disabled={isLoading || disabled}
className={clsx(classes.button, classes.add)}
icon={Plus}
/>
>
+
</IconButton>
)}
<Spinner
className={clsx(classes.spinner, isLoading && classes.loading)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ describe('NotificationToast', () => {
expect(screen.getByText('This is a toast message')).toBeVisible();
});

const closeButton = screen.getByText('Close');
const closeButton = screen.getByLabelText('Close');

await userEvent.click(closeButton);

Expand Down
Loading

0 comments on commit c5374e0

Please sign in to comment.