mirror of
https://github.com/portainer/portainer.git
synced 2025-07-24 07:49:41 +02:00
refactor(app): move react components to react codebase [EE-3179] (#6971)
This commit is contained in:
parent
212400c283
commit
18252ab854
346 changed files with 642 additions and 644 deletions
68
app/react/components/form-components/SwitchField/Switch.css
Normal file
68
app/react/components/form-components/SwitchField/Switch.css
Normal file
|
@ -0,0 +1,68 @@
|
|||
/* switch box */
|
||||
|
||||
.switch,
|
||||
.bootbox-checkbox-list > .checkbox > label {
|
||||
--switch-size: 24px;
|
||||
}
|
||||
|
||||
.switch.small {
|
||||
--switch-size: 12px;
|
||||
}
|
||||
|
||||
.switch input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.switch i,
|
||||
.bootbox-form .checkbox i {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
padding-right: var(--switch-size);
|
||||
transition: all ease 0.2s;
|
||||
-webkit-transition: all ease 0.2s;
|
||||
-moz-transition: all ease 0.2s;
|
||||
-o-transition: all ease 0.2s;
|
||||
border-radius: var(--switch-size);
|
||||
box-shadow: inset 0 0 1px 1px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.switch i:before,
|
||||
.bootbox-form .checkbox i:before {
|
||||
display: block;
|
||||
content: '';
|
||||
width: var(--switch-size);
|
||||
height: var(--switch-size);
|
||||
border-radius: var(--switch-size);
|
||||
background: white;
|
||||
box-shadow: 0 0 1px 1px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.switch :checked + i,
|
||||
.bootbox-form .checkbox :checked ~ i {
|
||||
padding-right: 0;
|
||||
padding-left: var(--switch-size);
|
||||
-webkit-box-shadow: inset 0 0 1px rgba(0, 0, 0, 0.5), inset 0 0 40px #337ab7;
|
||||
-moz-box-shadow: inset 0 0 1px rgba(0, 0, 0, 0.5), inset 0 0 40px #337ab7;
|
||||
box-shadow: inset 0 0 1px rgba(0, 0, 0, 0.5), inset 0 0 40px #337ab7;
|
||||
}
|
||||
|
||||
.switch :disabled + i {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.switch.limited {
|
||||
pointer-events: none;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.switch.limited i {
|
||||
opacity: 1;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.switch.business i {
|
||||
background-color: var(--BE-only);
|
||||
box-shadow: inset 0 0 1px rgb(0 0 0 / 50%), inset 0 0 40px var(--BE-only);
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
.root {
|
||||
margin-bottom: 0;
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
import { Meta, Story } from '@storybook/react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { Switch } from './Switch';
|
||||
|
||||
export default {
|
||||
title: 'Components/Form/SwitchField/Switch',
|
||||
} as Meta;
|
||||
|
||||
export function Example() {
|
||||
const [isChecked, setIsChecked] = useState(false);
|
||||
function onChange() {
|
||||
setIsChecked(!isChecked);
|
||||
}
|
||||
|
||||
return <Switch name="name" checked={isChecked} onChange={onChange} id="id" />;
|
||||
}
|
||||
|
||||
interface Args {
|
||||
checked: boolean;
|
||||
}
|
||||
|
||||
function Template({ checked }: Args) {
|
||||
return <Switch name="name" checked={checked} onChange={() => {}} id="id" />;
|
||||
}
|
||||
|
||||
export const Checked: Story<Args> = Template.bind({});
|
||||
Checked.args = {
|
||||
checked: true,
|
||||
};
|
||||
|
||||
export const Unchecked: Story<Args> = Template.bind({});
|
||||
Unchecked.args = {
|
||||
checked: false,
|
||||
};
|
|
@ -0,0 +1,20 @@
|
|||
import { render } from '@testing-library/react';
|
||||
import { PropsWithChildren } from 'react';
|
||||
|
||||
import { Switch, Props } from './Switch';
|
||||
|
||||
function renderDefault({
|
||||
name = 'default name',
|
||||
checked = false,
|
||||
}: Partial<PropsWithChildren<Props>> = {}) {
|
||||
return render(
|
||||
<Switch id="id" name={name} checked={checked} onChange={() => {}} />
|
||||
);
|
||||
}
|
||||
|
||||
test('should display a Switch component', async () => {
|
||||
const { findByRole } = renderDefault();
|
||||
|
||||
const switchElem = await findByRole('checkbox');
|
||||
expect(switchElem).toBeTruthy();
|
||||
});
|
57
app/react/components/form-components/SwitchField/Switch.tsx
Normal file
57
app/react/components/form-components/SwitchField/Switch.tsx
Normal file
|
@ -0,0 +1,57 @@
|
|||
import clsx from 'clsx';
|
||||
|
||||
import { isLimitedToBE } from '@/portainer/feature-flags/feature-flags.service';
|
||||
import { FeatureId } from '@/portainer/feature-flags/enums';
|
||||
|
||||
import { BEFeatureIndicator } from '@@/BEFeatureIndicator';
|
||||
|
||||
import './Switch.css';
|
||||
|
||||
import styles from './Switch.module.css';
|
||||
|
||||
export interface Props {
|
||||
checked: boolean;
|
||||
id: string;
|
||||
name: string;
|
||||
onChange(checked: boolean): void;
|
||||
|
||||
className?: string;
|
||||
dataCy?: string;
|
||||
disabled?: boolean;
|
||||
featureId?: FeatureId;
|
||||
}
|
||||
|
||||
export function Switch({
|
||||
name,
|
||||
checked,
|
||||
id,
|
||||
disabled,
|
||||
dataCy,
|
||||
onChange,
|
||||
featureId,
|
||||
className,
|
||||
}: Props) {
|
||||
const limitedToBE = isLimitedToBE(featureId);
|
||||
|
||||
return (
|
||||
<>
|
||||
<label
|
||||
className={clsx('switch', className, styles.root, {
|
||||
business: limitedToBE,
|
||||
limited: limitedToBE,
|
||||
})}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
name={name}
|
||||
id={id}
|
||||
checked={checked}
|
||||
disabled={disabled || limitedToBE}
|
||||
onChange={({ target: { checked } }) => onChange(checked)}
|
||||
/>
|
||||
<i data-cy={dataCy} />
|
||||
</label>
|
||||
{limitedToBE && <BEFeatureIndicator featureId={featureId} />}
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
.root {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.label {
|
||||
padding: 0;
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
import { Meta, Story } from '@storybook/react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { SwitchField } from './SwitchField';
|
||||
|
||||
export default {
|
||||
title: 'Components/Form/SwitchField',
|
||||
} as Meta;
|
||||
|
||||
export function Example() {
|
||||
const [isChecked, setIsChecked] = useState(false);
|
||||
function onChange() {
|
||||
setIsChecked(!isChecked);
|
||||
}
|
||||
|
||||
return (
|
||||
<SwitchField
|
||||
name="name"
|
||||
checked={isChecked}
|
||||
onChange={onChange}
|
||||
label="Example"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
interface Args {
|
||||
checked: boolean;
|
||||
label: string;
|
||||
labelClass: string;
|
||||
}
|
||||
|
||||
function Template({ checked, label, labelClass }: Args) {
|
||||
return (
|
||||
<SwitchField
|
||||
name="name"
|
||||
checked={checked}
|
||||
onChange={() => {}}
|
||||
label={label}
|
||||
labelClass={labelClass}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const Checked: Story<Args> = Template.bind({});
|
||||
Checked.args = {
|
||||
checked: true,
|
||||
label: 'label',
|
||||
labelClass: 'col-sm-6',
|
||||
};
|
||||
|
||||
export const Unchecked: Story<Args> = Template.bind({});
|
||||
Unchecked.args = {
|
||||
checked: false,
|
||||
label: 'label',
|
||||
labelClass: 'col-sm-6',
|
||||
};
|
|
@ -0,0 +1,37 @@
|
|||
import { render, fireEvent } from '@/react-tools/test-utils';
|
||||
|
||||
import { SwitchField, Props } from './SwitchField';
|
||||
|
||||
function renderDefault({
|
||||
name = 'default name',
|
||||
checked = false,
|
||||
label = 'label',
|
||||
onChange = jest.fn(),
|
||||
}: Partial<Props> = {}) {
|
||||
return render(
|
||||
<SwitchField
|
||||
label={label}
|
||||
name={name}
|
||||
checked={checked}
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
test('should display a Switch component', async () => {
|
||||
const { findByRole } = renderDefault();
|
||||
|
||||
const switchElem = await findByRole('checkbox');
|
||||
expect(switchElem).toBeTruthy();
|
||||
});
|
||||
|
||||
test('clicking should emit on-change with the opposite value', async () => {
|
||||
const onChange = jest.fn();
|
||||
const checked = true;
|
||||
const { findByRole } = renderDefault({ onChange, checked });
|
||||
|
||||
const switchElem = await findByRole('checkbox');
|
||||
fireEvent.click(switchElem);
|
||||
|
||||
expect(onChange).toHaveBeenCalledWith(!checked);
|
||||
});
|
|
@ -0,0 +1,60 @@
|
|||
import clsx from 'clsx';
|
||||
|
||||
import { FeatureId } from '@/portainer/feature-flags/enums';
|
||||
|
||||
import { Tooltip } from '@@/Tip/Tooltip';
|
||||
|
||||
import styles from './SwitchField.module.css';
|
||||
import { Switch } from './Switch';
|
||||
|
||||
export interface Props {
|
||||
label: string;
|
||||
checked: boolean;
|
||||
onChange(value: boolean): void;
|
||||
|
||||
name?: string;
|
||||
tooltip?: string;
|
||||
labelClass?: string;
|
||||
dataCy?: string;
|
||||
disabled?: boolean;
|
||||
featureId?: FeatureId;
|
||||
}
|
||||
|
||||
export function SwitchField({
|
||||
tooltip,
|
||||
checked,
|
||||
label,
|
||||
name,
|
||||
labelClass,
|
||||
dataCy,
|
||||
disabled,
|
||||
onChange,
|
||||
featureId,
|
||||
}: Props) {
|
||||
const toggleName = name ? `toggle_${name}` : '';
|
||||
|
||||
return (
|
||||
<label className={styles.root}>
|
||||
<span
|
||||
className={clsx(
|
||||
'control-label text-left space-right',
|
||||
styles.label,
|
||||
labelClass
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
{tooltip && <Tooltip position="bottom" message={tooltip} />}
|
||||
</span>
|
||||
<Switch
|
||||
className="space-right"
|
||||
name={toggleName}
|
||||
id={toggleName}
|
||||
checked={checked}
|
||||
disabled={disabled}
|
||||
onChange={onChange}
|
||||
featureId={featureId}
|
||||
dataCy={dataCy}
|
||||
/>
|
||||
</label>
|
||||
);
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export { SwitchField } from './SwitchField';
|
Loading…
Add table
Add a link
Reference in a new issue