Skip to content

Commit

Permalink
Merge pull request #848 from MouradGaa/fix/845-exclude-component-drop…
Browse files Browse the repository at this point in the history
…down-fix

Fix/845 exclude component dropdown fix
  • Loading branch information
mlabouardy authored Jun 14, 2023
2 parents 96d8518 + e51d420 commit 8eec5ba
Show file tree
Hide file tree
Showing 4 changed files with 232 additions and 36 deletions.
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;
}
}

0 comments on commit 8eec5ba

Please sign in to comment.