Skip to content

Commit

Permalink
Merge pull request #117 from codytodonnell/feature/integrate-componen…
Browse files Browse the repository at this point in the history
…t-library

Integrate component library
  • Loading branch information
codytodonnell authored Aug 23, 2024
2 parents 483d245 + 6391321 commit fff9093
Show file tree
Hide file tree
Showing 20 changed files with 359 additions and 278 deletions.
1 change: 1 addition & 0 deletions strudel-components/lib/components/CheckboxList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export const CheckboxList: React.FC<CheckboxListProps> = ({
};

useEffect(() => {
console.log(checkValues)
if (onChange) onChange(checkValues);
}, [checkValues]);

Expand Down
37 changes: 30 additions & 7 deletions strudel-components/lib/components/FilterContext.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,52 @@
import React, { PropsWithChildren, useContext, useEffect, useReducer } from 'react';
import { FilterOperator } from './FilterField';

export interface DataFilter {
field: string;
value: string | any[] | null;
operator: FilterOperator;
}

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

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

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

export type FilterAction =
| { type: 'SET_FILTER', payload: { field: string, value: any } }
| { type: 'SET_ACTIVE_FILTERS', payload:FilterState['activeFilters'] }
| { type: 'SET_FILTER', payload: { field: string, value: any, operator: FilterOperator } }
| { 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': {
const filter = action.payload;
const existingIndex = state.activeFilters.findIndex((f) => f.field === filter.field);
const activeFilters = [...state.activeFilters];
if (existingIndex > -1) {
if (filter.value) {
activeFilters[existingIndex] = filter;
} else {
activeFilters.splice(existingIndex, 1);
}
} else if (filter.value) {
activeFilters.push(filter);
}
return {
...state,
activeFilters: { ...state.activeFilters, [action.payload.field]: action.payload.value }
activeFilters
}
// return {
// ...state,
// activeFilters: { ...state.activeFilters, [action.payload.field]: action.payload.value }
// }
}
case 'SET_ACTIVE_FILTERS': {
return {
Expand All @@ -49,7 +72,7 @@ interface FilterContextProps extends PropsWithChildren {
}

export const FilterContext: React.FC<FilterContextProps> = ({
activeFilters = {},
activeFilters = [],
onChange = (filters) => null,
children
}) => {
Expand All @@ -61,7 +84,7 @@ export const FilterContext: React.FC<FilterContextProps> = ({
*/
useEffect(() => {
if (onChange) onChange(state.activeFilters);
}, [state.activeFilters]);
}, [JSON.stringify(state.activeFilters)]);

/**
* If activeFilters is changed from outside the context (e.g. filters are reset)
Expand Down
33 changes: 21 additions & 12 deletions strudel-components/lib/components/FilterField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,16 @@ import { RangeSlider } from './RangeSlider';
import { DatePicker } from '@mui/x-date-pickers';
import { useFilters } from './FilterContext';

export type FilterOperator = 'contains' | 'contains-one-of' | 'equals' | 'equals-one-of' | 'between-inclusive' | 'between-dates-inclusive';

export type FilterComponent = 'RangeSlider' | 'CheckboxList' | 'DateRange' | 'TextField';

interface FilterFieldProps extends StackProps {
label: string;
field: string;
tooltip?: string;
filterComponent: 'RangeSlider' | 'CheckboxList' | 'DateRange' | 'TextField';
operator: FilterOperator;
filterComponent: FilterComponent;
filterProps?: any;
}

Expand Down Expand Up @@ -46,23 +51,27 @@ export const FilterField: React.FC<FilterFieldProps> = ({
label,
field,
tooltip,
operator,
filterComponent,
filterProps,
...rest
}) => {
const { state, dispatch } = useFilters();
const [value, setValue] = useState<FilterValue<typeof filterComponent>>(null);
const isActive = hasValue(state.activeFilters[field]);
console.log(state.activeFilters);
const currentFilter = state.activeFilters.find((filter) => filter.field === field);
const isActive = hasValue(currentFilter?.value);

/**
* When a filter is canceled, reset its value to the proper
* empty or base state depending on the filter type.
* In the activeFilters variable, empty filters will always be marked as null.
*/
const handleCancelFilter = () => {
console.log('cancel');
switch (filterComponent) {
case 'CheckboxList':
setValue([]);
setValue(null);
break;
case 'RangeSlider':
setValue([filterProps.min, filterProps.max]);
Expand All @@ -77,7 +86,7 @@ export const FilterField: React.FC<FilterFieldProps> = ({
console.log('Unknown filter type');
}

dispatch({ type: 'SET_FILTER', payload: { field: field, value: null } });
dispatch({ type: 'SET_FILTER', payload: { field: field, value: null, operator } });
}

/**
Expand All @@ -94,7 +103,7 @@ export const FilterField: React.FC<FilterFieldProps> = ({
<CheckboxList
values={value as string[] | number[] | null}
options={filterProps.options}
onChange={(values) => dispatch({ type: 'SET_FILTER', payload: { field: field, value: values } })}
onChange={(values) => dispatch({ type: 'SET_FILTER', payload: { field: field, value: values, operator } })}
{...filterProps}
/>
);
Expand All @@ -109,7 +118,7 @@ export const FilterField: React.FC<FilterFieldProps> = ({
if (values[0] === filterProps.min && values[1] === filterProps.max) {
newValues = null
}
dispatch({ type: 'SET_FILTER', payload: { field: field, value: newValues } })
dispatch({ type: 'SET_FILTER', payload: { field: field, value: newValues, operator } })
};

return (
Expand All @@ -126,7 +135,7 @@ export const FilterField: React.FC<FilterFieldProps> = ({
);
}
case 'DateRange': {
const currentDateRange = state.activeFilters[field];
const currentDateRange = state.activeFilters.find((filter) => filter.field === filter.field)?.value;
const hasValue = currentDateRange && Array.isArray(currentDateRange) && currentDateRange.length === 2;
const currentMin = hasValue && Array.isArray(currentDateRange) ? currentDateRange[0] : null;
const currentMax = hasValue && Array.isArray(currentDateRange) ? currentDateRange[1] : null;
Expand All @@ -140,7 +149,7 @@ export const FilterField: React.FC<FilterFieldProps> = ({
actions: ['clear', 'today']
}
}}
onChange={(value) => dispatch({ type: 'SET_FILTER', payload: { field: field, value: [value, currentMax] } })}
onChange={(value) => dispatch({ type: 'SET_FILTER', payload: { field: field, value: [value, currentMax], operator } })}
/>
<DatePicker
label="To"
Expand All @@ -149,7 +158,7 @@ export const FilterField: React.FC<FilterFieldProps> = ({
actions: ['clear', 'today']
}
}}
onChange={(value) => dispatch({ type: 'SET_FILTER', payload: { field: field, value: [currentMin, value] } })}
onChange={(value) => dispatch({ type: 'SET_FILTER', payload: { field: field, value: [currentMin, value], operator } })}
/>
</Stack>
);
Expand All @@ -161,7 +170,7 @@ export const FilterField: React.FC<FilterFieldProps> = ({
*/
useEffect(() => {
const timeout = setTimeout(() => {
dispatch({ type: 'SET_FILTER', payload: { field: field, value: value } })
dispatch({ type: 'SET_FILTER', payload: { field: field, value: value, operator } })
}, 1000);
return () => {
clearTimeout(timeout);
Expand All @@ -186,7 +195,7 @@ export const FilterField: React.FC<FilterFieldProps> = ({
*/
useEffect(() => {
if (isActive) {
setValue(state.activeFilters[field]);
setValue(currentFilter?.value || null);
} else if (filterComponent === 'RangeSlider') {
/** RangeSliders should be considered off if both values are min and max */
if (value && (value[0] !== filterProps.min || value[1] !== filterProps.max)) {
Expand All @@ -195,7 +204,7 @@ export const FilterField: React.FC<FilterFieldProps> = ({
} else if (hasValue(value)) {
handleCancelFilter();
}
},[state.activeFilters]);
},[JSON.stringify(state.activeFilters)]);

return (
<Stack
Expand Down
2 changes: 1 addition & 1 deletion strudel-components/lib/components/FilterGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import ArrowForwardIosSharpIcon from '@mui/icons-material/ArrowForwardIosSharp';
import { Accordion, AccordionDetails, AccordionSummary, Chip, Stack, Typography } from '@mui/material';
import React, { useState } from 'react';
import React from 'react';
import { useFilters } from './FilterContext';
import { hasValue } from './FilterField';

Expand Down
13 changes: 7 additions & 6 deletions strudel-components/lib/components/Filters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import React, { useEffect, useState } from 'react';
import { FilterContext, FilterState } from './FilterContext';
import { hasValue } from './FilterField';

interface FilterPanelProps extends PaperProps {
onChange?: (filters: FilterState['activeFilters']) => void;
interface FilterPanelProps extends Omit<PaperProps, 'onChange'> {
onChange?: (activeFilters: FilterState['activeFilters']) => void;
onClose?: () => any;
config?: any;
header?: React.ReactNode;
Expand All @@ -26,7 +26,7 @@ export const Filters: React.FC<FilterPanelProps> = ({
children,
...rest
}) => {
const [activeFilters, setActiveFilters] = useState<any>({});
const [activeFilters, setActiveFilters] = useState<any>([]);

/**
* Count the number of active filters in this group by using
Expand All @@ -49,7 +49,7 @@ export const Filters: React.FC<FilterPanelProps> = ({
}

const handleReset = () => {
setActiveFilters({});
setActiveFilters([]);
}

useEffect(() => {
Expand All @@ -63,10 +63,11 @@ export const Filters: React.FC<FilterPanelProps> = ({
variant="outlined"
{...rest}
>
<Stack>
<Stack spacing={0}>
{header && (
<Stack
direction="row"
spacing={0}
alignItems="center"
sx={{
borderBottom: '1px solid',
Expand Down Expand Up @@ -100,7 +101,7 @@ export const Filters: React.FC<FilterPanelProps> = ({
</Stack>
)}
{grouped && (
<Stack>
<Stack spacing={0}>
{children}
</Stack>
)}
Expand Down
36 changes: 26 additions & 10 deletions strudel-components/lib/components/SciDataGrid.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Stack, Typography } from '@mui/material';
import { Box, Typography } from '@mui/material';
import { DataGrid, DataGridProps, GridColDef, GridColumnHeaderParams, GridRenderCellParams } from '@mui/x-data-grid';
import React, { ReactNode } from 'react';
import { ArrayWithPopover } from './ArrayWithPopover';
Expand Down Expand Up @@ -40,15 +40,31 @@ const getGridColumns = (columns: SciDataGridColDef[]) => {
...gridColumn
} = column;

/** Render unit label underneath the headerName */
if (units) {
gridColumn.renderHeader = (params: GridColumnHeaderParams) => (
<Stack>
<Typography fontSize="0.875rem" fontWeight="bold">{params.colDef.headerName}</Typography>
<Typography fontSize="small" color="grey.700">{units}</Typography>
</Stack>
)
}
/**
* Style column header and render unit label
* underneath the headerName if units supplied
*/
gridColumn.renderHeader = (params: GridColumnHeaderParams) => (
<Box>
<Typography fontSize="0.875rem" fontWeight="bold">{params.colDef.headerName}</Typography>
{units && (
<Typography
fontSize="small"
color="grey.700"
sx={{
position: 'absolute',
bottom: '4px',
left: params.colDef.type !== 'number' ? 0 : 'auto',
right: params.colDef.type === 'number' ? 0 : 'auto',
transform: 'translate(0, 0)',
zIndex: 1000
}}
>
{units}
</Typography>
)}
</Box>
)

/** Handle value transformation options */
if (!gridColumn.valueFormatter) {
Expand Down
2 changes: 2 additions & 0 deletions strudel-components/lib/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ export { Filters } from './components/Filters'
export { FilterField } from './components/FilterField'
export { FilterGroup } from './components/FilterGroup'
export { FilterContext } from './components/FilterContext'
export type { DataFilter } from './components/FilterContext'
export type { FilterOperator, FilterComponent } from './components/FilterField'
export { SciDataGrid } from './components/SciDataGrid'
export type { SciDataGridColDef } from './components/SciDataGrid'
export { Formula } from './components/Formula'
Expand Down
Loading

0 comments on commit fff9093

Please sign in to comment.