mirror of
https://github.com/portainer/portainer.git
synced 2025-07-21 22:39:41 +02:00
feat(ui/buttons): introduce Add and Delete buttons [EE-6296] (#10585)
This commit is contained in:
parent
66635ba6b1
commit
1f2f4525e3
6 changed files with 108 additions and 47 deletions
|
@ -1,3 +0,0 @@
|
||||||
.add-button {
|
|
||||||
border: none;
|
|
||||||
}
|
|
|
@ -1,20 +1,21 @@
|
||||||
import { Meta, Story } from '@storybook/react';
|
import { Meta, Story } from '@storybook/react';
|
||||||
|
|
||||||
import { AddButton, Props } from './AddButton';
|
import { AddButton } from './AddButton';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
component: AddButton,
|
component: AddButton,
|
||||||
title: 'Components/Buttons/AddButton',
|
title: 'Components/Buttons/AddButton',
|
||||||
} as Meta;
|
} as Meta;
|
||||||
|
|
||||||
function Template({ label, onClick }: JSX.IntrinsicAttributes & Props) {
|
type Args = {
|
||||||
return <AddButton label={label} onClick={onClick} />;
|
label: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function Template({ label }: Args) {
|
||||||
|
return <AddButton>{label}</AddButton>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Primary: Story<Props> = Template.bind({});
|
export const Primary: Story<Args> = Template.bind({});
|
||||||
Primary.args = {
|
Primary.args = {
|
||||||
label: 'Create new container',
|
label: 'Create new container',
|
||||||
onClick: () => {
|
|
||||||
alert('Hello AddButton!');
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,22 +1,18 @@
|
||||||
import { fireEvent, render } from '@testing-library/react';
|
import { render } from '@/react-tools/test-utils';
|
||||||
|
|
||||||
import { AddButton, Props } from './AddButton';
|
import { AddButton } from './AddButton';
|
||||||
|
|
||||||
function renderDefault({
|
function renderDefault({
|
||||||
label = 'default label',
|
label = 'default label',
|
||||||
onClick = () => {},
|
}: Partial<{ label: string }> = {}) {
|
||||||
}: Partial<Props> = {}) {
|
return render(<AddButton to="">{label}</AddButton>);
|
||||||
return render(<AddButton label={label} onClick={onClick} />);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test('should display a AddButton component and allow onClick', async () => {
|
test('should display a AddButton component', async () => {
|
||||||
const label = 'test label';
|
const label = 'test label';
|
||||||
const onClick = jest.fn();
|
|
||||||
const { findByText } = renderDefault({ label, onClick });
|
const { findByText } = renderDefault({ label });
|
||||||
|
|
||||||
const buttonLabel = await findByText(label);
|
const buttonLabel = await findByText(label);
|
||||||
expect(buttonLabel).toBeTruthy();
|
expect(buttonLabel).toBeTruthy();
|
||||||
|
|
||||||
fireEvent.click(buttonLabel);
|
|
||||||
expect(onClick).toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,35 +1,38 @@
|
||||||
import clsx from 'clsx';
|
import { Plus } from 'lucide-react';
|
||||||
import { PlusCircle } from 'lucide-react';
|
import { ComponentProps, PropsWithChildren } from 'react';
|
||||||
|
|
||||||
import { Icon } from '@/react/components/Icon';
|
import { AutomationTestingProps } from '@/types';
|
||||||
|
|
||||||
import styles from './AddButton.module.css';
|
import { Link } from '@@/Link';
|
||||||
|
|
||||||
export interface Props {
|
import { Button } from './Button';
|
||||||
className?: string;
|
|
||||||
label: string;
|
|
||||||
disabled?: boolean;
|
|
||||||
onClick: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function AddButton({ label, onClick, className, disabled }: Props) {
|
export function AddButton({
|
||||||
|
to = '.new',
|
||||||
|
params,
|
||||||
|
children,
|
||||||
|
color = 'primary',
|
||||||
|
disabled,
|
||||||
|
'data-cy': dataCy,
|
||||||
|
}: PropsWithChildren<
|
||||||
|
{
|
||||||
|
to?: string;
|
||||||
|
params?: object;
|
||||||
|
color?: ComponentProps<typeof Button>['color'];
|
||||||
|
disabled?: boolean;
|
||||||
|
} & AutomationTestingProps
|
||||||
|
>) {
|
||||||
return (
|
return (
|
||||||
<button
|
<Button
|
||||||
className={clsx(
|
as={Link}
|
||||||
className,
|
props={{ to, params }}
|
||||||
'label',
|
icon={Plus}
|
||||||
'label-default',
|
className="!m-0"
|
||||||
'vertical-center',
|
data-cy={dataCy}
|
||||||
'interactive',
|
color={color}
|
||||||
'vertical-center',
|
|
||||||
styles.addButton
|
|
||||||
)}
|
|
||||||
type="button"
|
|
||||||
onClick={onClick}
|
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
<Icon icon={PlusCircle} />
|
{children || 'Add'}
|
||||||
{label}
|
</Button>
|
||||||
</button>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
40
app/react/components/buttons/DeleteButton.tsx
Normal file
40
app/react/components/buttons/DeleteButton.tsx
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import { Trash2 } from 'lucide-react';
|
||||||
|
import { ComponentProps, PropsWithChildren, ReactNode } from 'react';
|
||||||
|
|
||||||
|
import { confirmDelete } from '@@/modals/confirm';
|
||||||
|
|
||||||
|
import { Button } from './Button';
|
||||||
|
|
||||||
|
export function DeleteButton({
|
||||||
|
disabled,
|
||||||
|
confirmMessage,
|
||||||
|
onConfirmed,
|
||||||
|
size,
|
||||||
|
children,
|
||||||
|
}: PropsWithChildren<{
|
||||||
|
size?: ComponentProps<typeof Button>['size'];
|
||||||
|
disabled?: boolean;
|
||||||
|
confirmMessage: ReactNode;
|
||||||
|
onConfirmed(): Promise<void> | void;
|
||||||
|
}>) {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
size={size}
|
||||||
|
color="dangerlight"
|
||||||
|
disabled={disabled}
|
||||||
|
onClick={() => handleClick()}
|
||||||
|
icon={Trash2}
|
||||||
|
className="!m-0"
|
||||||
|
>
|
||||||
|
{children || 'Remove'}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
|
||||||
|
async function handleClick() {
|
||||||
|
if (!(await confirmDelete(confirmMessage))) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return onConfirmed();
|
||||||
|
}
|
||||||
|
}
|
24
app/react/kubernetes/components/CreateFromManifestButton.tsx
Normal file
24
app/react/kubernetes/components/CreateFromManifestButton.tsx
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import { useCurrentStateAndParams } from '@uirouter/react';
|
||||||
|
|
||||||
|
import { AutomationTestingProps } from '@/types';
|
||||||
|
|
||||||
|
import { AddButton } from '@@/buttons';
|
||||||
|
|
||||||
|
export function CreateFromManifestButton({
|
||||||
|
params = {},
|
||||||
|
'data-cy': dataCy,
|
||||||
|
}: { params?: object } & AutomationTestingProps) {
|
||||||
|
const { state } = useCurrentStateAndParams();
|
||||||
|
return (
|
||||||
|
<AddButton
|
||||||
|
to="kubernetes.deploy"
|
||||||
|
params={{
|
||||||
|
referrer: state.name,
|
||||||
|
...params,
|
||||||
|
}}
|
||||||
|
data-cy={dataCy}
|
||||||
|
>
|
||||||
|
Create from manifest
|
||||||
|
</AddButton>
|
||||||
|
);
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue