Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/845 exclude component dropdown fix #848

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,7 @@ function useResourcesManagerChart({
labels: sortByDescendingCosts?.map(item => item.label),
datasets: [
{
data: sortByDescendingCosts?.map(item =>
item.total
) as number[],
data: sortByDescendingCosts?.map(item => item.total) as number[],
backgroundColor: colors,
borderColor: '#FFFFFF',
borderWidth: 3,
Expand Down
130 changes: 130 additions & 0 deletions dashboard/components/select-checkbox/SelectCheckbox.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { render, fireEvent } from '@testing-library/react';
import SelectCheckbox from './SelectCheckbox';

jest.mock('./hooks/useSelectCheckbox', () => ({
__esModule: true,
default: jest.fn(() => ({
listOfExcludableItems: ['Item 1', 'Item 2', 'Item 3'],
error: null
}))
}));

describe('SelectCheckbox', () => {
it('renders the label correctly', () => {
const { getByText } = render(
<SelectCheckbox
label="Test Label"
query="provider"
exclude={[]}
setExclude={() => {}}
/>
);

expect(getByText('Test Label')).toBeInTheDocument();
});

it('opens the dropdown when clicked', () => {
const { getByRole, getByText } = render(
<SelectCheckbox
label="Test Label"
query="provider"
exclude={[]}
setExclude={() => {}}
/>
);

fireEvent.click(getByRole('button'));

expect(getByText('Item 1')).toBeInTheDocument();
expect(getByText('Item 2')).toBeInTheDocument();
expect(getByText('Item 3')).toBeInTheDocument();
});

it('closes the dropdown when clicked outside', () => {
const { getByRole, queryByText, getByTestId } = render(
<SelectCheckbox
label="Test Label"
query="provider"
exclude={[]}
setExclude={() => {}}
/>
);

fireEvent.click(getByRole('button'));
expect(queryByText('Item 1')).toBeInTheDocument();

fireEvent.click(getByTestId('overlay'));
expect(queryByText('Item 1')).toBeNull();
});

it('selects and excludes items', () => {
const setExcludeMock = jest.fn();
const { getByRole, getByText } = render(
<SelectCheckbox
label="Test Label"
query="provider"
exclude={[]}
setExclude={setExcludeMock}
/>
);

fireEvent.click(getByRole('button'));
fireEvent.click(getByRole('checkbox', { name: 'Item 1' }));
fireEvent.click(getByRole('checkbox', { name: 'Item 3' }));
fireEvent.click(getByText('Apply'));

expect(setExcludeMock).toHaveBeenCalledWith(['Item 1', 'Item 3']);
});

it('selects and deselects all items with "Exclude All" checkbox', () => {
const setExcludeMock = jest.fn();
const { getByRole, getByText } = render(
<SelectCheckbox
label="Test Label"
query="provider"
exclude={[]}
setExclude={setExcludeMock}
/>
);

fireEvent.click(getByRole('button'));
fireEvent.click(getByRole('checkbox', { name: 'Exclude All' }));
fireEvent.click(getByText('Apply'));

expect(setExcludeMock).toHaveBeenCalledWith(['Item 1', 'Item 2', 'Item 3']);

fireEvent.click(getByRole('checkbox', { name: 'Exclude All' }));
fireEvent.click(getByText('Apply'));

expect(setExcludeMock).toHaveBeenCalledWith([]);
});

it('filters the list of items based on search input', () => {
const { getByRole, getByPlaceholderText, queryByText } = render(
<SelectCheckbox
label="Test Label"
query="provider"
exclude={[]}
setExclude={() => {}}
/>
);

fireEvent.click(getByRole('button'));
const searchInput = getByPlaceholderText('Search');

fireEvent.change(searchInput, { target: { value: 'Item 1' } });
expect(queryByText('Item 2')).toBeNull();
expect(queryByText('Item 3')).toBeNull();
expect(queryByText('Item 1')).toBeInTheDocument();

fireEvent.change(searchInput, { target: { value: 'Item' } });
expect(queryByText('Item 1')).toBeInTheDocument();
expect(queryByText('Item 2')).toBeInTheDocument();
expect(queryByText('Item 3')).toBeInTheDocument();

fireEvent.change(searchInput, { target: { value: 'Non-matching' } });
expect(queryByText('Item 1')).toBeNull();
expect(queryByText('Item 2')).toBeNull();
expect(queryByText('Item 3')).toBeNull();
});
});
113 changes: 80 additions & 33 deletions dashboard/components/select-checkbox/SelectCheckbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Button from '../button/Button';
import Checkbox from '../checkbox/Checkbox';
import { ResourcesManagerQuery } from '../dashboard/components/resources-manager/hooks/useResourcesManager';
import useSelectCheckbox from './hooks/useSelectCheckbox';
import ChevronDownIcon from '../icons/ChevronDownIcon';

export type SelectCheckboxProps = {
label: string;
Expand Down Expand Up @@ -41,6 +42,14 @@ function SelectCheckbox({
}
}

function handleCheckAll(e: ChangeEvent<HTMLInputElement>) {
if (e.currentTarget.checked) {
setCheckedItems(listOfExcludableItems);
} else {
setCheckedItems([]);
}
}

function submit() {
setExclude(checkedItems);
}
Expand All @@ -55,6 +64,12 @@ function SelectCheckbox({

return (
<div className="relative">
<div
className="pointer-events-none absolute right-4
bottom-[1.15rem] text-black-900 transition-all"
>
<ChevronDownIcon width={24} height={24} />
</div>
<button
onClick={toggle}
className={`h-[60px] w-full overflow-hidden rounded text-left outline hover:outline-black-200 focus:outline-2 focus:outline-primary ${
Expand Down Expand Up @@ -87,37 +102,43 @@ function SelectCheckbox({
{isOpen && (
<>
<div
data-testid="overlay"
onClick={toggle}
className="fixed inset-0 z-20 hidden animate-fade-in bg-transparent opacity-0 sm:block"
></div>
<div className="absolute top-[4.15rem] z-[21] w-full rounded-lg border border-black-200 bg-white shadow-lg">
<div className="relative overflow-hidden rounded-lg rounded-b-none">
<div className="absolute top-[4.15rem] z-[21] w-full rounded-lg border border-black-100 bg-white shadow-lg">
<div className="relative m-4 ">
{!search ? (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
className="absolute top-[1.125rem] left-4"
className="absolute top-[0.5rem] left-3"
>
<path
stroke="currentColor"
d="M17 17L21 21"
stroke="#ababab"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
d="M11.5 21a9.5 9.5 0 100-19 9.5 9.5 0 000 19zM22 22l-2-2"
></path>
/>
<path
d="M19 11C19 15.4183 15.4183 19 11 19C6.58172 19 3 15.4183 3 11C3 6.58172 6.58172 3 11 3C15.4183 3 19 6.58172 19 11Z"
stroke="#ababab"
strokeWidth="2"
/>
</svg>
) : (
<svg
onClick={() => setSearch('')}
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
className="absolute top-[1.175rem] left-4 cursor-pointer"
className="absolute top-[0.5rem] left-3 cursor-pointer"
>
<path
stroke="currentColor"
Expand All @@ -134,7 +155,7 @@ function SelectCheckbox({
onChange={e => setSearch(e.target.value)}
type="text"
placeholder="Search"
className="w-full border-b border-black-200/50 bg-white py-4 pl-10 pr-6 text-sm text-black-900 caret-secondary placeholder:text-black-300 focus:outline-none"
className="h-10 w-full rounded-md border border-black-200/50 bg-white py-4 pl-10 pr-6 text-sm text-black-900 caret-secondary placeholder:text-black-300 focus:outline-none"
autoFocus
/>
</div>
Expand All @@ -145,26 +166,52 @@ function SelectCheckbox({
)}
{!error && (
<>
<div className="flex max-h-[12rem] flex-col gap-3 overflow-auto p-4">
{resources.map((resource, idx) => (
<div key={idx} className="flex items-center gap-2 text-sm">
<Checkbox
id={resource}
onChange={e => handleChange(e, resource)}
checked={
!!checkedItems.find(value => value === resource)
}
/>
<label htmlFor={resource} className="w-full">
{resource}
</label>
</div>
))}
{resources.length === 0 && (
<p className="text-sm text-black-400">
There are no results for {search}
</p>
)}
{!search && (
<div className="m-4 ml-6 flex items-center gap-2 text-sm">
<Checkbox
id="all"
onChange={e => {
handleCheckAll(e);
}}
checked={checkedItems.length === resources.length}
/>
<label
htmlFor="all"
className="w-full text-sm text-black-400"
>
Exclude All
</label>
</div>
)}
<hr className="m-4 mb-0 h-px border-t-0 bg-neutral-100 opacity-100 dark:opacity-50" />
<div className="scrollbar mt-2 mb-2 mr-3 overflow-auto">
<div className="mt-2 flex max-h-[12rem] flex-col gap-3 p-4 pb-4 pt-0 pl-6">
{resources.map((resource, idx) => (
<div
key={idx}
className="flex items-center gap-2 text-sm"
>
<Checkbox
id={resource}
onChange={e => handleChange(e, resource)}
checked={
!!checkedItems.find(value => value === resource)
}
/>
<label
htmlFor={resource}
className="w-full text-black-400"
>
{resource}
</label>
</div>
))}
{resources.length === 0 && (
<p className="text-sm text-black-400">
There are no results for {search}
</p>
)}
</div>
</div>
<div className="flex flex-col border-t border-black-200/50 p-4">
<Button onClick={submit}>Apply</Button>
Expand Down
21 changes: 21 additions & 0 deletions dashboard/styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,24 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer utilities {
.scrollbar::-webkit-scrollbar {
width: 6px;
height: 6px;
}

.scrollbar::-webkit-scrollbar-track {
border-radius: 100vh;
background: #edebee;
}

.scrollbar::-webkit-scrollbar-thumb {
background: #95a3a3;
border-radius: 100vh;
}

.scrollbar::-webkit-scrollbar-thumb:hover {
background: #95a3a3;
}
}