Skip to content

Commit

Permalink
Add property button component (#12428)
Browse files Browse the repository at this point in the history
  • Loading branch information
TomasEng authored Mar 4, 2024
1 parent 390f7e6 commit 27e4603
Show file tree
Hide file tree
Showing 13 changed files with 171 additions and 65 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
.propertyButton {
border-radius: 0;
display: flex;
padding: var(--studio-property-button-vertical-spacing) var(--fds-spacing-5);
justify-content: flex-start;
overflow: hidden;
}

.propertyButton.withValue .content {
color: var(--fds-semantic-text-neutral-default);
text-align: left;
}

.propertyButton.withValue .property {
font-weight: bold;
}

.content {
overflow: hidden;
white-space: nowrap;
}

.property,
.value {
display: block;
overflow: hidden;
text-overflow: ellipsis;
}

.editIconWrapper {
flex: 1;
text-align: right;
display: none;
}

.propertyButton:hover .editIconWrapper,
.propertyButton:focus .editIconWrapper {
display: inline-block;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import type { RefObject } from 'react';
import React from 'react';
import type { StudioPropertyButtonProps } from './StudioPropertyButton';
import { StudioPropertyButton } from './StudioPropertyButton';
import { act, render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

// Test data:
const property = 'Test property';
const defaultProps: StudioPropertyButtonProps = {
property,
};

describe('StudioPropertyButton', () => {
it('Renders a button with the property name', () => {
renderButton();
expect(screen.getByRole('button', { name: property })).toHaveTextContent(property);
});

it('Renders both the property and the value when a value is given', () => {
const value = 'Test value';
renderButton({ value });
expect(screen.getByRole('button', { name: property })).toHaveTextContent(property);
expect(screen.getByRole('button', { name: property })).toHaveTextContent(value);
});

it('Overrides the icon when a custom icon is given', () => {
const iconTestId = 'custom-icon';
const icon = <svg data-testid={iconTestId} />;
renderButton({ icon });
expect(screen.getByTestId(iconTestId)).toBeInTheDocument();
});

it('Appends the given class name', () => {
const className = 'test-class';
renderButton({ className });
expect(screen.getByRole('button', { name: property })).toHaveClass(className);
});

it('Forwards a ref to the button', () => {
const ref = React.createRef<HTMLButtonElement>();
renderButton({}, ref);
expect(ref.current).toBe(screen.getByRole('button'));
});

it('Calls the onClick function when the button is clicked', async () => {
const user = userEvent.setup();
const onClick = jest.fn();
renderButton({ onClick });
await act(() => user.click(screen.getByRole('button')));
expect(onClick).toHaveBeenCalled();
});
});

const renderButton = (
props: Partial<StudioPropertyButtonProps> = {},
ref?: RefObject<HTMLButtonElement>,
) => render(<StudioPropertyButton {...defaultProps} {...props} ref={ref} />);
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import type { ReactNode } from 'react';
import React, { forwardRef } from 'react';
import type { StudioButtonProps } from '../StudioButton';
import { StudioButton } from '../StudioButton';
import classes from './StudioPropertyButton.module.css';
import { PlusCircleIcon, PencilIcon } from '@studio/icons';
import cn from 'classnames';

export type StudioPropertyButtonProps = {
property: string;
value?: ReactNode;
} & Omit<StudioButtonProps, 'children' | 'value'>;

const StudioPropertyButton = forwardRef<HTMLButtonElement, StudioPropertyButtonProps>(
({ property, value, icon: givenIcon, className: givenClass, ...rest }, ref) => {
const hasValue = !!value;

const icon = hasValue || givenIcon ? givenIcon : <PlusCircleIcon />;

const className = cn(classes.propertyButton, hasValue && classes.withValue, givenClass);

return (
<StudioButton
aria-label={property}
className={className}
fullWidth
icon={icon}
ref={ref}
size='small'
title={property}
variant='tertiary'
{...rest}
>
<span className={classes.content}>
<span className={classes.property}>{property}</span>
<span className={classes.value}>{value}</span>
</span>
{hasValue && (
<span className={classes.editIconWrapper}>
<PencilIcon />
</span>
)}
</StudioButton>
);
},
);

StudioPropertyButton.displayName = 'StudioPropertyButton';

export { StudioPropertyButton };
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { StudioPropertyButton } from './StudioPropertyButton';
export type { StudioPropertyButtonProps } from './StudioPropertyButton';
5 changes: 3 additions & 2 deletions frontend/libs/studio-components/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ export * from './StudioDeleteButton';
export * from './StudioDropdownMenu';
export * from './StudioExpression';
export * from './StudioGridSelector';
export * from './StudioIconTextfield';
export * from './StudioLabelWrapper';
export * from './StudioModal';
export * from './StudioNotFoundPage';
export * from './StudioPageSpinner';
export * from './StudioPropertyButton';
export * from './StudioSpinner';
export * from './StudioTextarea';
export * from './StudioTextfield';
export * from './StudioTreeView';
export * from './StudioToggleableTextfield';
export * from './StudioIconTextfield';
export * from './StudioToggleableTextfieldSchema';
export * from './StudioTreeView';
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@
--studio-treeitem-vertical-line-colour: var(--fds-semantic-border-divider-default);
--studio-treeitem-vertical-line-width: 2px;
--studio-expression-spacing: var(--fds-spacing-3);
--studio-property-button-vertical-spacing: var(--fds-spacing-2);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
.container {
display: grid;
gap: 1rem;
}

.switch {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
}

.datamodelBindings {
padding: var(--fds-spacing-5) 0;
--vertical-padding: calc(var(--fds-spacing-5) - var(--studio-property-button-vertical-spacing));
padding: var(--vertical-padding) 0;
}
Original file line number Diff line number Diff line change
@@ -1,29 +1,5 @@
.definedBinding {
border-radius: 0;
padding-left: var(--fds-spacing-5);
padding-right: var(--fds-spacing-5);
}

.mainContent {
align-items: flex-start;
color: var(--fds-semantic-text-neutral-default);
display: flex;
flex-direction: column;
}

.selectedOption {
align-items: center;
display: flex;
gap: var(--fds-spacing-1);
}

.pencilIcon {
visibility: hidden;
flex: 1;
text-align: right;
}

.definedBinding:hover .pencilIcon,
.definedBinding:focus .pencilIcon {
visibility: visible;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { StudioButton } from '@studio/components';
import { LinkIcon, PencilIcon } from '@studio/icons';
import { StudioPropertyButton } from '@studio/components';
import { LinkIcon } from '@studio/icons';
import classes from './DefinedBinding.module.css';
import { useTranslation } from 'react-i18next';

Expand All @@ -13,25 +13,20 @@ export type DefinedBindingProps = {
export const DefinedBinding = ({ onClick, label, selectedOption }: DefinedBindingProps) => {
const { t } = useTranslation();
const title = t('right_menu.dataModelBindings_edit', { binding: label });

const value = (
<span className={classes.selectedOption}>
<LinkIcon /> {selectedOption}
</span>
);

return (
<StudioButton
<StudioPropertyButton
aria-label={title}
className={classes.definedBinding}
fullWidth
onClick={onClick}
size='small'
property={label}
title={title}
variant='tertiary'
>
<span className={classes.mainContent}>
<strong>{label}</strong>
<span className={classes.selectedOption}>
<LinkIcon /> {selectedOption}
</span>
</span>
<span className={classes.pencilIcon}>
<PencilIcon />
</span>
</StudioButton>
value={value}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
display: flex;
flex-direction: column;
gap: var(--datamodel-binding-spacing);
margin: 0 var(--fds-spacing-5);
margin: var(--studio-property-button-vertical-spacing) var(--fds-spacing-5);
padding: var(--datamodel-binding-spacing);
}

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import classes from './UndefinedBinding.module.css';
import React from 'react';
import { StudioButton } from '@studio/components';
import { StudioPropertyButton } from '@studio/components';
import { LinkIcon } from '@studio/icons';

export type UndefinedBindingProps = {
Expand All @@ -9,14 +8,5 @@ export type UndefinedBindingProps = {
};

export const UndefinedBinding = ({ onClick, label }: UndefinedBindingProps) => (
<StudioButton
className={classes.undefinedBinding}
onClick={onClick}
variant='tertiary'
size='small'
fullWidth
icon={<LinkIcon />}
>
{label}
</StudioButton>
<StudioPropertyButton icon={<LinkIcon />} onClick={onClick} property={label} />
);

0 comments on commit 27e4603

Please sign in to comment.