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

feat(filter): L3-4114 - create dumb filter component #418

Merged
merged 12 commits into from
Nov 22, 2024
Merged
81 changes: 41 additions & 40 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@
"stylelint-order": "^6.0.4",
"stylelint-scss": "^6.4.1",
"ts-node": "^10.9.2",
"type-fest": "^4.27.0",
"typescript": "^5.5.4",
"vite": "^5.4.6",
"vite-plugin-dts": "^4.2.4",
Expand Down
16 changes: 9 additions & 7 deletions src/componentStyles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@
@use 'components/Tags/tags';
@use 'components/Countdown/countdown';
@use 'components/Countdown/duration';
@use 'components/Detail/detail';
@use 'components/PinchZoom/pinchZoom';
@use 'components/Tabs/tabs';
@use 'components/SeldonImage/seldonImage';
@use 'components/Filter/filter';

// Patterns
@use 'patterns/HeroBanner/heroBanner';
Expand All @@ -49,14 +54,11 @@
@use 'patterns/Subscribe/subscribe';
@use 'patterns/Social/social';
@use 'patterns/SaleHeaderBanner/saleHeaderBanner';
@use 'patterns/ObjectTile/objectTile';
@use 'patterns/BidSnapshot/bidSnapshot';
@use 'patterns/FilterMenu/filterMenu';
@use 'patterns/DetailList/detailList';

// Site Furniture
@use 'site-furniture/Header/header';
@use 'site-furniture/Footer/footer';
@use 'components/Detail/detail';
@use 'patterns/DetailList/detailList';
@use 'components/PinchZoom/pinchZoom';
@use 'components/Tabs/tabs';
@use 'components/SeldonImage/seldonImage';
@use 'patterns/ObjectTile/objectTile';
@use 'patterns/BidSnapshot/bidSnapshot';
6 changes: 3 additions & 3 deletions src/components/DatePicker/DatePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import classnames from 'classnames';
import { noOp, useNormalizedInputProps } from '../../utils';
import Input, { InputProps } from '../Input/Input';

export interface DatePickerProps extends Omit<InputProps, 'defaultValue'>, Record<string, unknown> {
export interface DatePickerProps extends Omit<InputProps, 'defaultValue' | 'onChange'>, Record<string, unknown> {
/**
* Optionally allow manual entry to the date input
*/
Expand Down Expand Up @@ -181,12 +181,12 @@ const DatePicker = React.forwardRef(
};
}, [allowInput, defaultValue, inputProps.disabled, enableTime, id, locale, onChange, ref, type]);

const handleKeyPress = (e: KeyboardEvent) => {
const handleKeyPress: React.KeyboardEventHandler<HTMLInputElement> = (e) => {
if (allowInput && /[0-9-/:]+/g.test(e.key)) {
manualValue.current = fp?.current?.selectedDates;
}
};
const handOnBlur = (e: FocusEvent) => {
const handOnBlur: React.FocusEventHandler<HTMLInputElement> = (e) => {
if (
!allowInput ||
(e.relatedTarget instanceof HTMLElement && e.relatedTarget?.classList.value.includes('flatpickr'))
Expand Down
2 changes: 1 addition & 1 deletion src/components/ErrorBoundary/ErrorBoundary.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const VolatileComponent = ({ throwError }: WrappedProps) => {
throwError();
}
}, [throwError]);
return <Input {...reqProps} defaultValue={['2023-06-01T08:30', '2023-06-05T08:30']} />;
return <Input {...reqProps} defaultValue="2023-06-01T08:30" />;
};

describe('An ErrorBoundary', () => {
Expand Down
99 changes: 99 additions & 0 deletions src/components/Filter/Filter.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { Meta } from '@storybook/react';
import Filter from './Filter';
import FilterHeader from './FilterHeader';
import FilterInput from './FilterInput';
import { useState } from 'react';

// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction
const meta = {
title: 'Components/Filter',
component: Filter,
} satisfies Meta<typeof Filter>;

export default meta;

type FilterDimension = { label: string; disabled?: boolean | undefined; checked: boolean };

type FilterType = {
label: string;
id: string;
filterDimensions: FilterDimension[];
};

type PropTypes = {
filter: FilterType;
viewAllLimit: number;
isViewingAll: boolean;
};

const filter: FilterType = {
label: 'Artists & Makers',
id: 'makers',
filterDimensions: [
{ label: 'Jimmy', checked: true },
{ label: 'Bob', checked: false },
{ label: 'Alan', checked: false },
{ label: 'Nick', checked: false },
{ label: 'Joe', checked: false },
{ label: 'Fred', checked: false },
{ label: 'Rob', checked: false },
{ label: 'Roy', checked: false },
{ label: 'disabled', disabled: true, checked: false },
],
};

const FilterComponent = (props: PropTypes) => {
const { filter: intialFilters, viewAllLimit, isViewingAll } = props;
const [filter, setFilter] = useState(intialFilters);

const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
const { checked, name } = e.target as HTMLInputElement;
const updatedFilterDimensions = filter.filterDimensions.map((dimension) => {
if (dimension.label === name) {
return {
...dimension,
checked,
};
}
return dimension;
});

const updatedFilter = {
...filter,
filterDimensions: updatedFilterDimensions,
};

setFilter(updatedFilter);
};

return (
<Filter key={filter.label} name={filter.label} viewAllLimit={viewAllLimit} isViewingAll={isViewingAll}>
<FilterHeader heading={filter.label} />
{filter.filterDimensions.map((value: FilterDimension) => (
<FilterInput
id={value.label}
key={value.label}
labelText={value.label}
onChange={handleChange}
type="checkbox"
disabled={value?.disabled}
name={value.label}
checked={value.checked}
/>
))}
</Filter>
);
};

export const Playground = (props: PropTypes) => {
return <FilterComponent {...props} />;
};

// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args
Playground.args = {
filter,
viewAllLimit: 10,
isViewingAll: false,
};

Playground.argTypes = {};
74 changes: 74 additions & 0 deletions src/components/Filter/Filter.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import Filter from './Filter';
import { runCommonTests } from '../../utils/testUtils';
import { render, screen } from '@testing-library/react';
import FilterHeader from './FilterHeader';
import FilterInput from './FilterInput';

describe('Filter', () => {
runCommonTests(Filter, 'Filter');

it('renders the different input types of filter values', () => {
const handleChange = vi.fn();

render(
<Filter name="test">
<FilterInput
type="checkbox"
onChange={handleChange}
id="test[test1]"
labelText="Filter 1"
name="test[test1]"
checked={true}
/>
<FilterInput
type="radio"
onChange={handleChange}
id="test[test2]"
labelText="Filter 2"
name="test[test2]"
checked={true}
/>
</Filter>,
);

expect(screen.getAllByText('Filter 1')[0]).toBeInTheDocument();
expect(screen.getAllByText('Filter 2')[0]).toBeInTheDocument();

const checkbox = screen.getByRole('checkbox');
const radio = screen.getByRole('radio');

expect(checkbox).toHaveAttribute('type', 'checkbox');
expect(radio).toHaveAttribute('type', 'radio');
});

it('renders a filter header', () => {
render(
<Filter name="test">
<FilterHeader heading="Filter Header 1" />
</Filter>,
);

expect(screen.getByText('Filter Header 1')).toBeInTheDocument();
});

it('should disable filters when disabled prop is passed', () => {
const handleChange = vi.fn();

render(
<Filter name="test">
<FilterInput
type="checkbox"
onChange={handleChange}
id="test[test1]"
labelText="Filter 1"
name="test[test1]"
checked={true}
disabled={true}
/>
</Filter>,
);

const checkbox = screen.getByRole('checkbox');
expect(checkbox).toBeDisabled();
});
});
Loading
Loading