Skip to content

Commit

Permalink
refactor: Remove asChild from the StudioButton component (#14038)
Browse files Browse the repository at this point in the history
  • Loading branch information
TomasEng authored Nov 18, 2024
1 parent a0d9e99 commit 5b21dae
Show file tree
Hide file tree
Showing 21 changed files with 265 additions and 130 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,16 @@ export const PreviewButton = (): ReactElement => {
const previewLink: string = `${packagesRouter.getPackageNavigationUrl('preview')}${previewLinkQueryParams}`;

return (
<StudioPageHeader.HeaderButton asChild color='dark' variant={variant}>
<a href={previewLink} className={classes.previewLink} aria-label={t('top_menu.preview')}>
<PlayFillIcon className={classes.playIcon} />
{shouldDisplayText && t('top_menu.preview')}
</a>
<StudioPageHeader.HeaderButton
aria-label={t('top_menu.preview')}
as='a'
className={classes.previewLink}
color='dark'
href={previewLink}
variant={variant}
>
<PlayFillIcon className={classes.playIcon} />
{shouldDisplayText && t('top_menu.preview')}
</StudioPageHeader.HeaderButton>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,15 @@ export const AppPreviewSubMenu = () => {

return (
<div className={classes.subHeader}>
<StudioPageHeader.HeaderButton asChild color='dark' variant='preview'>
<a href={backToEditingHref} aria-label={t('top_menu.preview_back_to_editing')}>
<ArrowLeftIcon className={classes.icon} />
{shouldDisplayText && t('top_menu.preview_back_to_editing')}
</a>
<StudioPageHeader.HeaderButton
aria-label={t('top_menu.preview_back_to_editing')}
as='a'
color='dark'
href={backToEditingHref}
variant='preview'
>
<ArrowLeftIcon className={classes.icon} />
{shouldDisplayText && t('top_menu.preview_back_to_editing')}
</StudioPageHeader.HeaderButton>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ describe('StudioButton', () => {
testRefForwarding<HTMLButtonElement>((ref) => renderButton({}, ref), getButton);
});

it('Supports render asChild', () => {
it('Supports polymorphism', () => {
render(
<StudioButton asChild>
<a href='/'>Test</a>
<StudioButton as='a' href='/'>
Test
</StudioButton>,
);
expect(screen.queryByRole('button')).not.toBeInTheDocument();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,35 @@
import { Button } from '@digdir/designsystemet-react';
import type { ButtonProps } from '@digdir/designsystemet-react';
import type { ReactNode } from 'react';
import type { ElementType, ReactNode } from 'react';
import React, { forwardRef } from 'react';
import cn from 'classnames';
import classes from './StudioButton.module.css';
import type { OverridableComponent } from '../../types/OverridableComponent';
import type { IconPlacement } from '../../types/IconPlacement';
import type { OverridableComponentRef } from '../../types/OverridableComponentRef';
import type { OverridableComponentProps } from '../../types/OverridableComponentProps';

export type StudioButtonProps = Omit<ButtonProps, 'icon' | 'color'> & {
export type StudioButtonProps = Omit<ButtonProps, 'icon' | 'color' | 'asChild'> & {
icon?: ReactNode;
iconPlacement?: IconPlacement;
color?: ButtonProps['color'] | 'inverted';
};

const StudioButton: OverridableComponent<StudioButtonProps, HTMLButtonElement> = forwardRef<
HTMLButtonElement,
StudioButtonProps
>(
(
const StudioButton: OverridableComponent<StudioButtonProps, HTMLButtonElement> = forwardRef(
<As extends ElementType = 'button'>(
{
icon,
iconPlacement = 'left',
size = 'small',
as,
children,
className: givenClassName,
color,
fullWidth,
icon,
iconPlacement = 'left',
size = 'small',
variant,
...rest
},
ref,
}: OverridableComponentProps<StudioButtonProps, As>,
ref: OverridableComponentRef<As>,
) => {
const iconComponent = (
<span aria-hidden className={classes.iconWrapper}>
Expand All @@ -43,24 +45,29 @@ const StudioButton: OverridableComponent<StudioButtonProps, HTMLButtonElement> =
});
const selectedColor = color === 'inverted' ? undefined : color;

const Component = as || 'button';

return (
<Button
{...rest}
color={selectedColor}
asChild
className={classNames}
color={selectedColor}
fullWidth={fullWidth}
icon={!children}
size={size}
ref={ref}
variant={variant}
>
{icon ? (
<span className={classes.innerContainer}>
{iconPlacement === 'left' && iconComponent}
{children}
{iconPlacement === 'right' && iconComponent}
</span>
) : (
children
)}
<Component ref={ref} {...rest}>
{icon ? (
<span className={classes.innerContainer}>
{iconPlacement === 'left' && iconComponent}
{children}
{iconPlacement === 'right' && iconComponent}
</span>
) : (
children
)}
</Component>
</Button>
);
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import type { ReactNode } from 'react';
import { useTabProps } from '../hooks/useTabProps';
import { StudioButton } from '@studio/components';
import { Button } from '@digdir/designsystemet-react';

export type StudioContentMenuLinkTabProps<TabId extends string> = {
icon: ReactNode;
Expand All @@ -18,5 +18,9 @@ export function StudioContentMenuLinkTab<TabId extends string>({
}: StudioContentMenuLinkTabProps<TabId>): React.ReactElement {
const props = useTabProps(icon, tabName, tabId);

return <StudioButton asChild>{renderTab(props)}</StudioButton>;
return (
<Button size='sm' asChild>
{renderTab(props)}
</Button>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { render, screen } from '@testing-library/react';
import { StudioDeleteButton } from './StudioDeleteButton';
import type { StudioDeleteButtonProps } from './StudioDeleteButton';
import userEvent from '@testing-library/user-event';
import { StudioButton } from '../StudioButton';
import { testRefForwarding } from '../../test-utils/testRefForwarding';

describe('StudioDeleteButton', () => {
Expand Down Expand Up @@ -61,9 +60,9 @@ describe('StudioDeleteButton', () => {

it('Supports polymorphism', () => {
render(
<StudioButton asChild>
<a href='/'>{buttonLabel}</a>
</StudioButton>,
<StudioDeleteButton {...defaultProps} as='a' href='/'>
{buttonLabel}
</StudioDeleteButton>,
);
expect(screen.getByRole('link')).toBeInTheDocument();
expect(screen.queryByRole('button')).not.toBeInTheDocument();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,39 @@
import React, { forwardRef, type ReactElement } from 'react';
import React, { type ElementType, forwardRef, type ReactElement } from 'react';
import classes from '../common.module.css';
import { StudioButton, type StudioButtonProps } from '../../StudioButton';
import { type StudioPageHeaderColor } from '../types/StudioPageHeaderColor';
import cn from 'classnames';
import { type StudioPageHeaderVariant } from '../types/StudioPageHeaderVariant';
import type { OverridableComponent } from '../../../types/OverridableComponent';
import type { OverridableComponentProps } from '../../../types/OverridableComponentProps';
import type { OverridableComponentRef } from '../../../types/OverridableComponentRef';

export type StudioPageHeaderHeaderButtonProps = {
color: StudioPageHeaderColor;
variant: StudioPageHeaderVariant;
} & Omit<StudioButtonProps, 'color' | 'variant'>;

export const StudioPageHeaderHeaderButton = forwardRef<
HTMLButtonElement,
StudioPageHeaderHeaderButtonProps
>(({ color, variant, className: givenClass, ...rest }, ref): ReactElement => {
return (
<StudioButton
ref={ref}
className={cn(classes.linkOrButton, classes[variant], classes[color], givenClass)}
{...rest}
/>
);
});
export const StudioPageHeaderHeaderButton: OverridableComponent<
StudioPageHeaderHeaderButtonProps,
HTMLButtonElement
> = forwardRef(
<As extends ElementType = 'button'>(
{
color,
variant,
className: givenClass,
...rest
}: OverridableComponentProps<StudioPageHeaderHeaderButtonProps, As>,
ref: OverridableComponentRef<As>,
): ReactElement => {
return (
<StudioButton
ref={ref}
className={cn(classes.linkOrButton, classes[variant], classes[color], givenClass)}
{...rest}
/>
);
},
);

StudioPageHeaderHeaderButton.displayName = 'StudioPageHeader.HeaderButton';
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,8 @@ export const DigdirLogoLink = ({ title, showTitle }: DigdirLogoLinkProps): React

return (
<div className={classes.wrapper}>
<StudioPageHeaderHeaderButton asChild color={color} variant={variant}>
<a href='/' title={title}>
<DigdirLogo />
</a>
<StudioPageHeaderHeaderButton as='a' color={color} variant={variant} href='/' title={title}>
<DigdirLogo />
</StudioPageHeaderHeaderButton>
{showTitle && (
<Paragraph size='large' className={classes.titleText}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ export const StudioTreeViewItem = ({
aria-level={level}
aria-owns={listId}
aria-selected={selected}
as='div'
className={classes.button}
color='first'
icon={<Icon customIcon={icon} hasChildren={hasChildren} open={open} />}
Expand All @@ -115,9 +116,8 @@ export const StudioTreeViewItem = ({
tabIndex={focusable ? 0 : -1}
type='button'
variant='tertiary'
asChild
>
<div className={classes.label}>{label}</div>
<span className={classes.label}>{label}</span>
</StudioButton>
);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import type { RefAttributes, FC, ElementType, ComponentPropsWithRef } from 'react';
import type { RefAttributes, FC, ElementType } from 'react';
import type { OverridableComponentProps } from './OverridableComponentProps';

export type OverridableComponent<ComponentProps, Element extends HTMLElement> = {
(props: ComponentProps & RefAttributes<Element>): ReturnType<FC>;
<As extends ElementType>(
props: {
as?: As;
} & ComponentProps &
Omit<ComponentPropsWithRef<As>, keyof ComponentProps>,
): ReturnType<FC>;
<As extends ElementType>(props: OverridableComponentProps<ComponentProps, As>): ReturnType<FC>;
} & Pick<FC, 'displayName'>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { ComponentPropsWithRef, ElementType } from 'react';

export type OverridableComponentProps<ComponentProps, As extends ElementType> = {
as?: As;
} & ComponentPropsWithRef<As> &
Omit<ComponentProps, keyof ComponentPropsWithRef<As>>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import type { ComponentPropsWithRef, ElementType } from 'react';

export type OverridableComponentRef<As extends ElementType> = ComponentPropsWithRef<As>['ref'];
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { StudioButton, StudioRedirectBox } from '@studio/components';
import { useLocalStorage } from '@studio/components/src/hooks/useLocalStorage';
import { useTranslation } from 'react-i18next';
import { useBpmnApiContext } from '../../../../../contexts/BpmnApiContext';
import { Link } from '@digdir/designsystemet-react';

export const RedirectToCreatePageButton = (): React.ReactElement => {
const { t } = useTranslation();
Expand All @@ -26,14 +25,16 @@ export const RedirectToCreatePageButton = (): React.ReactElement => {
<StudioRedirectBox
title={t('process_editor.configuration_panel_custom_receipt_navigate_to_design_title')}
>
<StudioButton asChild variant='primary' color='second' onClick={handleClick}>
<Link
href={packagesRouter.getPackageNavigationUrl('editorUiEditor')}
className={classes.link}
>
<PencilWritingIcon />
{t('process_editor.configuration_panel_custom_receipt_navigate_to_design_button')}
</Link>
<StudioButton
as='a'
className={classes.link}
color='second'
href={packagesRouter.getPackageNavigationUrl('editorUiEditor')}
icon={<PencilWritingIcon />}
onClick={handleClick}
variant='primary'
>
{t('process_editor.configuration_panel_custom_receipt_navigate_to_design_button')}
</StudioButton>
</StudioRedirectBox>
</div>
Expand Down
Loading

0 comments on commit 5b21dae

Please sign in to comment.