Skip to content

Commit

Permalink
Remove aschild from StudioPopover
Browse files Browse the repository at this point in the history
  • Loading branch information
TomasEng committed Nov 27, 2024
1 parent 4a7d1e2 commit 35dad9d
Show file tree
Hide file tree
Showing 21 changed files with 252 additions and 164 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useState } from 'react';
import classes from './DeployDropdown.module.css';
import { AltinnConfirmDialog } from 'app-shared/components';
import { StudioButton, StudioError, StudioSpinner } from '@studio/components';
import { StudioError, StudioSpinner } from '@studio/components';
import { Combobox, Spinner } from '@digdir/designsystemet-react';
import type { ImageOption } from './ImageOption';
import { useTranslation } from 'react-i18next';
Expand Down Expand Up @@ -83,22 +83,23 @@ export const DeployDropdown = ({
onConfirm={startDeploy}
onClose={() => setIsConfirmDeployDialogOpen(false)}
placement='right'
trigger={
<StudioButton
disabled={!selectedImageTag || disabled}
onClick={() => setIsConfirmDeployDialogOpen((prevState) => !prevState)}
>
{isPending && (
<Spinner
variant='interaction'
title=''
size='xsmall'
data-testid='spinner-test-id'
/>
)}
{t('app_deployment.btn_deploy_new_version')}
</StudioButton>
}
triggerProps={{
disabled: !selectedImageTag || disabled,
onClick: () => setIsConfirmDeployDialogOpen((prevState) => !prevState),
children: (
<>
{isPending && (
<Spinner
variant='interaction'
title=''
size='xsmall'
data-testid='spinner-test-id'
/>
)}
{t('app_deployment.btn_deploy_new_version')}
</>
),
}}
>
<p>
{appDeployedVersion
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { StudioButton } from '@studio/components';
import { TrashIcon } from '@studio/icons';
import { useDeleteDataModelMutation } from '../../../../hooks/mutations';
import type { MetadataOption } from '../../../../types/MetadataOption';
Expand Down Expand Up @@ -40,18 +39,15 @@ export function DeleteWrapper({ selectedOption }: DeleteWrapperProps) {
confirmText={t('schema_editor.confirm_deletion')}
onConfirm={onDeleteConfirmClick}
onClose={() => setDialogOpen(false)}
trigger={
<StudioButton
id='delete-model-button'
disabled={!schemaName}
onClick={onDeleteClick}
color='danger'
icon={<TrashIcon />}
variant='tertiary'
>
{t('schema_editor.delete_data_model')}
</StudioButton>
}
triggerProps={{
id: 'delete-model-button',
disabled: !schemaName,
onClick: onDeleteClick,
color: 'danger',
icon: <TrashIcon />,
variant: 'tertiary',
children: t('schema_editor.delete_data_model'),
}}
>
<p>
<Trans
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import type { ForwardedRef } from 'react';
import React from 'react';
import type { RenderResult } from '@testing-library/react';
import { render, screen } from '@testing-library/react';
import { StudioPageHeader } from '../';
import { StudioPopover } from '../../';
import userEvent from '@testing-library/user-event';
import { testRefForwarding } from '../../../test-utils/testRefForwarding';
import type { StudioPageHeaderPopoverTriggerProps } from './StudioPageHeaderPopoverTrigger';
import { testRootClassNameAppending } from '../../../test-utils/testRootClassNameAppending';
import { testCustomAttributes } from '../../../test-utils/testCustomAttributes';

// Test data:
const triggerText = 'Trigger';
const contentText = 'Content';
const defaultProps: StudioPageHeaderPopoverTriggerProps = {
children: triggerText,
};

describe('StudioPageHeader.PopoverTrigger', () => {
it('Renders the trigger button', () => {
renderPopover();
expect(screen.getByRole('button', { name: triggerText })).toBeInTheDocument();
});

it('Does not display the popover by default', () => {
renderPopover();
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
});

it('Opens the popover when the user clicks the trigger', async () => {
const user = userEvent.setup();
renderPopover();
await user.click(screen.getByRole('button', { name: triggerText }));
expect(screen.getByRole('dialog')).toBeInTheDocument();
expect(screen.getByRole('dialog')).toHaveTextContent(contentText);
});

it('Forwards the ref to the trigger button', () => {
testRefForwarding<HTMLButtonElement>((ref) => renderPopoverTrigger({}, ref));
});

it('Appends the given className to the trigger button', () => {
testRootClassNameAppending((className) => renderPopoverTrigger({ className }));
});

it('Accepts custom attributes', () => {
testCustomAttributes(renderPopoverTrigger);
});
});

function renderPopover(): RenderResult {
return render(
<StudioPopover>
<StudioPageHeader.PopoverTrigger {...defaultProps} />
<StudioPopover.Content>{contentText}</StudioPopover.Content>
</StudioPopover>,
);
}

function renderPopoverTrigger(
props: StudioPageHeaderPopoverTriggerProps,
ref?: ForwardedRef<HTMLButtonElement>,
): RenderResult {
return render(<StudioPageHeader.PopoverTrigger {...defaultProps} {...props} ref={ref} />);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React, { forwardRef } from 'react';
import type { StudioPopoverTriggerProps } from '../../StudioPopover';
import { StudioPopover } from '../../StudioPopover';
import cn from 'classnames';
import classes from '../common.module.css';

import type { StudioPageHeaderColor } from '../types/StudioPageHeaderColor';
import type { StudioPageHeaderVariant } from '../types/StudioPageHeaderVariant';

export type StudioPageHeaderPopoverTriggerProps = {
color?: StudioPageHeaderColor;
variant?: StudioPageHeaderVariant;
} & Omit<StudioPopoverTriggerProps, 'color' | 'variant'>;

export const StudioPageHeaderPopoverTrigger = forwardRef<
HTMLButtonElement,
StudioPageHeaderPopoverTriggerProps
>(({ color = 'dark', variant = 'regular', className: givenClass, ...rest }, ref) => {
const className = cn(classes.linkOrButton, classes[variant], classes[color], givenClass);
return <StudioPopover.Trigger className={className} {...rest} ref={ref} />;
});

StudioPageHeaderPopoverTrigger.displayName = 'StudioPageHeader.PopoverTrigger';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './StudioPageHeaderPopoverTrigger';
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ import { StudioPageHeaderMain } from './StudioPageHeaderMain';
import { StudioPageHeaderRight } from './StudioPageHeaderRight';
import { StudioPageHeaderSub } from './StudioPageHeaderSub';
import { StudioPageHeaderHeaderLink } from './StudioPageHeaderHeaderLink';
import { StudioPageHeaderPopoverTrigger } from './StudioPageHeaderPopoverTrigger';

type StudioPageHeaderComponent = typeof StudioPageHeaderParent & {
Main: typeof StudioPageHeaderMain;
Left: typeof StudioPageHeaderLeft;
Center: typeof StudioPageHeaderCenter;
Right: typeof StudioPageHeaderRight;
PopoverTrigger: typeof StudioPageHeaderPopoverTrigger;
Sub: typeof StudioPageHeaderSub;
HeaderButton: typeof StudioPageHeaderHeaderButton;
HeaderLink: typeof StudioPageHeaderHeaderLink;
Expand All @@ -32,6 +34,7 @@ StudioPageHeader.Main = StudioPageHeaderMain;
StudioPageHeader.Left = StudioPageHeaderLeft;
StudioPageHeader.Center = StudioPageHeaderCenter;
StudioPageHeader.Right = StudioPageHeaderRight;
StudioPageHeader.PopoverTrigger = StudioPageHeaderPopoverTrigger;
StudioPageHeader.Sub = StudioPageHeaderSub;
StudioPageHeader.HeaderButton = StudioPageHeaderHeaderButton;
StudioPageHeader.HeaderLink = StudioPageHeaderHeaderLink;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,34 @@
import React from 'react';
import {
type PopoverProps,
Popover,
type PopoverTriggerProps,
type PopoverContentProps,
} from '@digdir/designsystemet-react';

const StudioPopoverTrigger = ({ ...rest }: PopoverTriggerProps): React.ReactElement => {
return <Popover.Trigger {...rest} />;
};
import React, { forwardRef } from 'react';
import { type PopoverProps, Popover, type PopoverContentProps } from '@digdir/designsystemet-react';
import type { WithoutAsChild } from '../../types/WithoutAsChild';
import type { StudioButtonProps } from '../StudioButton';
import { StudioButton } from '../StudioButton';

const StudioPopoverContent = ({ ...rest }: PopoverContentProps): React.ReactElement => {
return <Popover.Content {...rest} />;
};
export type StudioPopoverTriggerProps = StudioButtonProps;

export type StudioPopoverProps = PopoverProps;
const StudioPopoverTrigger = forwardRef<HTMLButtonElement, StudioPopoverTriggerProps>(
(props, ref): React.ReactElement => (
<Popover.Trigger asChild>
<StudioButton {...props} ref={ref} />
</Popover.Trigger>
),
);

const StudioPopoverRoot = ({ ...rest }: StudioPopoverProps): React.ReactElement => {
return <Popover {...rest} />;
};
StudioPopoverTrigger.displayName = 'StudioPopover.Trigger';

export type StudioPopoverContentProps = WithoutAsChild<PopoverContentProps>;

const StudioPopoverContent = forwardRef<HTMLDivElement, StudioPopoverContentProps>(
(props, ref): React.ReactElement => <Popover.Content {...props} ref={ref} />,
);

StudioPopoverContent.displayName = 'StudioPopover.Content';

export type StudioPopoverProps = WithoutAsChild<PopoverProps>;

function StudioPopoverRoot(props: StudioPopoverProps): React.ReactElement {
return <Popover {...props} />;
}

type StudioPopoverComponent = typeof StudioPopoverRoot & {
Trigger: typeof StudioPopoverTrigger;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export { StudioPopover } from './StudioPopover';
export type { StudioPopoverProps } from './StudioPopover';
export * from './StudioPopover';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type WithoutAsChild<Props> = Omit<Props, 'asChild'>;
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Switch } from '@digdir/designsystemet-react';
import { AltinnConfirmDialog } from 'app-shared/components';
import { useTranslation } from 'react-i18next';
import { TrashIcon } from '@studio/icons';
import { StudioButton, StudioCenter } from '@studio/components';
import { StudioCenter } from '@studio/components';
import { nameFieldClass } from '@altinn/schema-editor/components/SchemaInspector/ItemFieldsTab/domUtils';
import { ItemFieldType } from './ItemFieldType';

Expand Down Expand Up @@ -96,15 +96,13 @@ export const ItemFieldsTableRow = ({
confirmText={t('schema_editor.data_model_field_deletion_confirm')}
onConfirm={deleteHandler}
onClose={() => setIsConfirmDeleteDialogOpen(false)}
trigger={
<StudioButton
title={t('schema_editor.delete_field')}
icon={<TrashIcon />}
onClick={() => setIsConfirmDeleteDialogOpen((prevState) => !prevState)}
color='danger'
variant='tertiary'
/>
}
triggerProps={{
title: t('schema_editor.delete_field'),
icon: <TrashIcon />,
onClick: () => setIsConfirmDeleteDialogOpen((prevState) => !prevState),
color: 'danger',
variant: 'tertiary',
}}
>
<p>{t('schema_editor.data_model_field_deletion_text')}</p>
<p>{t('schema_editor.data_model_field_deletion_info')}</p>
Expand Down
13 changes: 9 additions & 4 deletions frontend/packages/shared/src/components/AltinnConfirmDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,20 @@ import classes from './AltinnConfirmDialog.module.css';
import { useTranslation } from 'react-i18next';
import cn from 'classnames';
import { StudioButton, StudioPopover } from '@studio/components';
import type { StudioButtonProps, StudioPopoverProps } from '@studio/components';
import type {
StudioButtonProps,
StudioPopoverProps,
StudioPopoverTriggerProps,
} from '@studio/components';
import type { WithDataAttributes } from 'app-shared/types/WithDataAttributes';

export type AltinnConfirmDialogProps = {
confirmText?: string;
confirmColor?: StudioButtonProps['color'];
cancelText?: string;
onConfirm: (event: React.MouseEvent<HTMLButtonElement>) => void;
onClose: (event: React.MouseEvent<HTMLButtonElement> | MouseEvent) => void;
trigger?: React.ReactNode;
triggerProps?: WithDataAttributes<StudioPopoverTriggerProps>;
className?: string;
} & Partial<Pick<StudioPopoverProps, 'open' | 'placement' | 'children'>>;

Expand All @@ -23,7 +28,7 @@ export function AltinnConfirmDialog({
onClose,
placement,
children,
trigger = <div />,
triggerProps,
open = false,
className,
}: AltinnConfirmDialogProps) {
Expand All @@ -48,7 +53,7 @@ export function AltinnConfirmDialog({
return (
<div ref={dialogRef}>
<StudioPopover variant='warning' placement={placement} open={open}>
<StudioPopover.Trigger asChild>{trigger}</StudioPopover.Trigger>
<StudioPopover.Trigger {...triggerProps} />
<StudioPopover.Content className={cn(className, classes.popover)}>
{children}
<div className={classes.buttonContainer}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,12 @@ export const ThreeDotsMenu = ({ isClonePossible = false }: ThreeDotsMenuProps) =

return (
<StudioPopover>
<StudioPopover.Trigger asChild>
<StudioPageHeader.HeaderButton
icon={<MenuElipsisVerticalIcon />}
title={t('sync_header.gitea_menu')}
color='light'
variant='regular'
/>
</StudioPopover.Trigger>
<StudioPageHeader.PopoverTrigger
icon={<MenuElipsisVerticalIcon />}
title={t('sync_header.gitea_menu')}
color='light'
variant='regular'
/>
<StudioPopover.Content className={classes.popover}>
<ul className={classes.menuItems}>
{isClonePossible && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,19 +57,17 @@ export const FetchChangesPopover = (): React.ReactElement => {

return (
<StudioPopover open={popoverOpen} onClose={handleClosePopover} placement='bottom-end'>
<StudioPopover.Trigger asChild>
<StudioPageHeader.HeaderButton
onClick={handleOpenPopover}
disabled={hasMergeConflict}
icon={<DownloadIcon />}
color='light'
variant='regular'
aria-label={t('sync_header.fetch_changes')}
>
{shouldDisplayText && t('sync_header.fetch_changes')}
{displayNotification && <Notification numChanges={repoStatus?.behindBy ?? 0} />}
</StudioPageHeader.HeaderButton>
</StudioPopover.Trigger>
<StudioPageHeader.PopoverTrigger
onClick={handleOpenPopover}
disabled={hasMergeConflict}
icon={<DownloadIcon />}
color='light'
variant='regular'
aria-label={t('sync_header.fetch_changes')}
>
{shouldDisplayText && t('sync_header.fetch_changes')}
{displayNotification && <Notification numChanges={repoStatus?.behindBy ?? 0} />}
</StudioPageHeader.PopoverTrigger>
<StudioPopover.Content className={classes.popoverContent}>
{isLoading && <SyncLoadingIndicator heading={t('sync_header.fetching_latest_version')} />}
{!isLoading && <GiteaFetchCompleted heading={t('sync_header.service_updated_to_latest')} />}
Expand Down
Loading

0 comments on commit 35dad9d

Please sign in to comment.