mirror of
https://github.com/portainer/portainer.git
synced 2025-07-24 15:59: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
32
app/react/components/NavTabs/NavTabs.module.css
Normal file
32
app/react/components/NavTabs/NavTabs.module.css
Normal file
|
@ -0,0 +1,32 @@
|
|||
.parent {
|
||||
background: var(--blue-4);
|
||||
border-radius: 4px 4px 0 0;
|
||||
padding-top: 5px;
|
||||
margin-top: -5px;
|
||||
}
|
||||
|
||||
.parent a {
|
||||
background-color: initial !important;
|
||||
border: 1px solid transparent !important;
|
||||
cursor: inherit !important;
|
||||
}
|
||||
|
||||
.parent {
|
||||
background-color: var(--blue-4);
|
||||
}
|
||||
:global(:root[theme='dark']) .parent {
|
||||
background-color: var(--grey-40);
|
||||
}
|
||||
:global(:root[theme='highcontrast']) .parent {
|
||||
background-color: var(--white-color);
|
||||
}
|
||||
|
||||
.parent a {
|
||||
color: var(--white-color) !important;
|
||||
}
|
||||
:global([theme='dark']) .parent a {
|
||||
color: var(--black-color) !important;
|
||||
}
|
||||
:global([theme='highcontrast']) .parent a {
|
||||
color: var(--black-color) !important;
|
||||
}
|
31
app/react/components/NavTabs/NavTabs.stories.tsx
Normal file
31
app/react/components/NavTabs/NavTabs.stories.tsx
Normal file
|
@ -0,0 +1,31 @@
|
|||
import { ComponentMeta, Story } from '@storybook/react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { NavTabs, type Option } from './NavTabs';
|
||||
|
||||
export default {
|
||||
title: 'Components/NavTabs',
|
||||
component: NavTabs,
|
||||
} as ComponentMeta<typeof NavTabs>;
|
||||
|
||||
type Args = {
|
||||
options: Option[];
|
||||
};
|
||||
|
||||
function Template({ options = [] }: Args) {
|
||||
const [selected, setSelected] = useState(
|
||||
options.length ? options[0].id : undefined
|
||||
);
|
||||
|
||||
return (
|
||||
<NavTabs options={options} selectedId={selected} onSelect={setSelected} />
|
||||
);
|
||||
}
|
||||
|
||||
export const Example: Story<Args> = Template.bind({});
|
||||
Example.args = {
|
||||
options: [
|
||||
{ children: 'Content 1', id: 'option1', label: 'Option 1' },
|
||||
{ children: 'Content 2', id: 'option2', label: 'Option 2' },
|
||||
],
|
||||
};
|
58
app/react/components/NavTabs/NavTabs.test.tsx
Normal file
58
app/react/components/NavTabs/NavTabs.test.tsx
Normal file
|
@ -0,0 +1,58 @@
|
|||
import { render } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
import { NavTabs, Option } from './NavTabs';
|
||||
|
||||
test('should show titles', async () => {
|
||||
const options = [
|
||||
{ children: 'Content 1', id: 'option1', label: 'Option 1' },
|
||||
{ children: 'Content 2', id: 'option2', label: 'Option 2' },
|
||||
];
|
||||
const { findByText } = renderComponent(options);
|
||||
|
||||
const heading = await findByText(options[0].label);
|
||||
expect(heading).toBeTruthy();
|
||||
|
||||
const heading2 = await findByText(options[1].label);
|
||||
expect(heading2).toBeTruthy();
|
||||
});
|
||||
|
||||
test('should show selected id content', async () => {
|
||||
const options = [
|
||||
{ children: 'Content 1', id: 'option1', label: 'Option 1' },
|
||||
{ children: 'Content 2', id: 'option2', label: 'Option 2' },
|
||||
];
|
||||
|
||||
const selected = options[1];
|
||||
|
||||
const { findByText } = renderComponent(options, selected.id);
|
||||
|
||||
const content = await findByText(selected.children);
|
||||
expect(content).toBeTruthy();
|
||||
});
|
||||
|
||||
test('should call onSelect when clicked with id', async () => {
|
||||
const options = [
|
||||
{ children: 'Content 1', id: 'option1', label: 'Option 1' },
|
||||
{ children: 'Content 2', id: 'option2', label: 'Option 2' },
|
||||
];
|
||||
|
||||
const onSelect = jest.fn();
|
||||
|
||||
const { findByText } = renderComponent(options, options[1].id, onSelect);
|
||||
|
||||
const heading = await findByText(options[0].label);
|
||||
userEvent.click(heading);
|
||||
|
||||
expect(onSelect).toHaveBeenCalledWith(options[0].id);
|
||||
});
|
||||
|
||||
function renderComponent(
|
||||
options: Option[] = [],
|
||||
selectedId?: string | number,
|
||||
onSelect?: (id: string | number) => void
|
||||
) {
|
||||
return render(
|
||||
<NavTabs options={options} selectedId={selectedId} onSelect={onSelect} />
|
||||
);
|
||||
}
|
60
app/react/components/NavTabs/NavTabs.tsx
Normal file
60
app/react/components/NavTabs/NavTabs.tsx
Normal file
|
@ -0,0 +1,60 @@
|
|||
import clsx from 'clsx';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
import styles from './NavTabs.module.css';
|
||||
|
||||
export interface Option {
|
||||
label: string | ReactNode;
|
||||
children?: ReactNode;
|
||||
id: string | number;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
options: Option[];
|
||||
selectedId?: string | number;
|
||||
onSelect?(id: string | number): void;
|
||||
}
|
||||
|
||||
export function NavTabs({ options, selectedId, onSelect = () => {} }: Props) {
|
||||
const selected = options.find((option) => option.id === selectedId);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ul className="nav nav-tabs">
|
||||
{options.map((option) => (
|
||||
<li
|
||||
className={clsx({
|
||||
active: option.id === selectedId,
|
||||
[styles.parent]: !option.children,
|
||||
})}
|
||||
key={option.id}
|
||||
>
|
||||
{/* rule disabled because `nav-tabs` requires an anchor */}
|
||||
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
|
||||
<a
|
||||
onClick={() => handleSelect(option)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
handleSelect(option);
|
||||
}
|
||||
}}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
{option.label}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{selected && selected.children && (
|
||||
<div className="tab-content">{selected.children}</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
function handleSelect(option: Option) {
|
||||
if (option.children) {
|
||||
onSelect(option.id);
|
||||
}
|
||||
}
|
||||
}
|
1
app/react/components/NavTabs/index.ts
Normal file
1
app/react/components/NavTabs/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export { NavTabs } from './NavTabs';
|
Loading…
Add table
Add a link
Reference in a new issue