Skip to content

Commit

Permalink
Merge pull request #110 from codytodonnell/feature/filter-components
Browse files Browse the repository at this point in the history
Add and improve filter component in strudel-components
  • Loading branch information
codytodonnell authored Aug 2, 2024
2 parents 7d3854a + 6c37f13 commit 3de42be
Show file tree
Hide file tree
Showing 15 changed files with 1,051 additions and 236 deletions.
40 changes: 27 additions & 13 deletions strudel-components/lib/components/CheckboxList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,49 +9,63 @@ export interface CheckboxOption {
}

interface CheckboxListProps extends Omit<FormGroupProps, 'onChange'> {
values: string[] | number[] | null;
options: CheckboxOption[];
onChange?: (values: CheckboxOptionValue[] | null) => any;
}

export const CheckboxList: React.FC<CheckboxListProps> = ({
options = [],
onChange,
values,
sx,
...rest
}) => {
const [values, setValues] = useState<CheckboxOptionValue[] | null>(null);
const [checkValues, setCheckValues] = useState<CheckboxOptionValue[] | null>(values);

const handleChange = (checked: boolean, value: CheckboxOption['value']) => {
if (values === null && checked) {
setValues([value]);
} else if (values !== null && checked) {
setValues([...values, value]);
} else if (values !== null && !checked) {
const newValues = values.filter((v) => v !== value);
if (checkValues === null && checked) {
setCheckValues([value]);
} else if (checkValues !== null && checked) {
setCheckValues([...checkValues, value]);
} else if (checkValues !== null && !checked) {
const newValues = checkValues.filter((v) => v !== value);
if (newValues.length > 0) {
setValues(newValues);
setCheckValues(newValues);
} else {
setValues(null);
setCheckValues(null);
}
}
};

useEffect(() => {
if (onChange) onChange(values);
if (onChange) onChange(checkValues);
}, [checkValues]);

useEffect(() => {
setCheckValues(values);
}, [values]);

return (
<FormGroup {...rest}>
<FormGroup
sx={{
display: 'inline-flex',
...sx
}}
{...rest}
>
{options.map((option, i) => (
<FormControlLabel
key={`${option}-${i}`}
label={option.label}
control={
<Checkbox
<Checkbox
checked={!!checkValues && checkValues.indexOf(option.value) > -1}
value={option.value}
onChange={(e, checked) => handleChange(checked, option.value)}
sx={{
pr: 1,
pl: 0,
pl: 1,
pb: 0,
pt: 0
}}
Expand Down
87 changes: 87 additions & 0 deletions strudel-components/lib/components/FilterContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import React, { PropsWithChildren, useContext, useEffect, useReducer } from 'react';

export interface FilterState {
activeFilters: { [key: string]: any }
expandedGroup: string | number | boolean;
}

const FilterContextAPI = React.createContext<{state: FilterState; dispatch: React.Dispatch<FilterAction>} | undefined>(undefined);

const initialState: FilterState = {
activeFilters: {},
expandedGroup: false,
}

export type FilterAction =
| { type: 'SET_FILTER', payload: { field: string, value: any } }
| { type: 'SET_ACTIVE_FILTERS', payload:FilterState['activeFilters'] }
| { type: 'SET_EXPANDED_GROUP', payload: FilterState['expandedGroup']; }

function filterReducer(state: FilterState, action: FilterAction): FilterState {
switch (action.type) {
case 'SET_FILTER': {
return {
...state,
activeFilters: { ...state.activeFilters, [action.payload.field]: action.payload.value }
}
}
case 'SET_ACTIVE_FILTERS': {
return {
...state,
activeFilters: action.payload
}
}
case 'SET_EXPANDED_GROUP': {
return {
...state,
expandedGroup: action.payload
}
}
default: {
throw new Error(`Unhandled action type`)
}
}
}

interface FilterContextProps extends PropsWithChildren {
activeFilters?: FilterState['activeFilters'];
onChange?: (filters: FilterState['activeFilters']) => void;
}

export const FilterContext: React.FC<FilterContextProps> = ({
activeFilters = {},
onChange = (filters) => null,
children
}) => {
const [state, dispatch] = useReducer(filterReducer, { ...initialState, activeFilters });
const value = { state, dispatch };

/**
* Emit a change event when state.activeFilters changes
*/
useEffect(() => {
if (onChange) onChange(state.activeFilters);
}, [state.activeFilters]);

/**
* If activeFilters is changed from outside the context (e.g. filters are reset)
* then the new value should be dispatched.
*/
useEffect(() => {
dispatch({ type: 'SET_ACTIVE_FILTERS', payload: activeFilters });
}, [activeFilters]);

return (
<FilterContextAPI.Provider value={value}>
{children}
</FilterContextAPI.Provider>
)
}

export const useFilters = () => {
const context = useContext(FilterContextAPI)
if (context === undefined) {
throw new Error('useFilters must be used within a FilterContext')
}
return context
}
Loading

0 comments on commit 3de42be

Please sign in to comment.