Skip to content

Commit

Permalink
feat: improve custom filter/sortby/groupby actions
Browse files Browse the repository at this point in the history
  • Loading branch information
sgratzl committed May 25, 2021
1 parent ba66de7 commit c5ff024
Show file tree
Hide file tree
Showing 18 changed files with 395 additions and 183 deletions.
213 changes: 147 additions & 66 deletions packages/_playground/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable jsx-a11y/label-has-associated-control */
/**
* @lineup-lite/example-basic
* https://github.com/sgratzl/lineup-lite
Expand All @@ -10,23 +11,23 @@ import '@lineup-lite/components/src/style.css';
import '@lineup-lite/hooks/src/style.css';
import LineUpLite, {
actionIconsRemixicon,
asTextColumn,
ActionLineUpProps,
asDivergingNumberColumn,
asTextColumn,
featureFilterColumns,
featureFlexLayout,
featureRowRank,
featureRowSelect,
featureSortAndGroupBy,
LineUpLiteColumn,
LineUpLitePanel,
LineUpLiteStateContextProvider,
ColumnInstance,
LineUpLiteFilterAction,
UseFiltersColumnProps,
featureFlexLayout,
featureFilterColumns,
featureRowRank,
featureSortAndGroupBy,
featureRowSelect,
ActionIcons,
ColumnInstance,
} from '@lineup-lite/table';
import type { TextGroupByOptions } from '@lineup-lite/hooks';
import '@lineup-lite/table/src/style.css';
import React, { useMemo, useCallback, useState } from 'react';
import React, { useCallback, useMemo, useState } from 'react';
import { data, Row } from './data';
import './styles.css';

Expand All @@ -35,59 +36,138 @@ function MyCheckBox({ indeterminate, ...rest }: any) {
return <input type="checkbox" {...rest} style={{ color: 'blue' }} />;
}

function MyFilterAction(props: { col: ColumnInstance<Row>; icons: ActionIcons }) {
const col = props.col as unknown as ColumnInstance<Row> & UseFiltersColumnProps<Row>;
function useVisibleHelper() {
const [visible, setVisible] = useState([] as string[]);

const [visible, setVisible] = useState(false);
const showFilterDialog = useCallback(() => {
setVisible(!visible);
}, [visible, setVisible]);
const toggleVisibility = useCallback(
(col: { id: string }) => {
setVisible((v) => (v.indexOf(col.id) >= 0 ? v.filter((d) => d !== col.id) : [...v, col.id]));
},
[setVisible]
);
const isVisible = useCallback((col: { id: string }) => visible.indexOf(col.id) >= 0, [visible]);
return { toggleVisibility, isVisible };
}

return (
<>
<LineUpLiteFilterAction
column={col}
iconFilter={props.icons.filterColumn}
toggleFilterColumn={showFilterDialog}
/>
{visible && (
<div className="filter-dialog">
{col.render('Summary')}
<button onClick={showFilterDialog} type="button">
Close
</button>
</div>
)}
</>
function useFilterAction() {
const { toggleVisibility, isVisible } = useVisibleHelper();
const r: ActionLineUpProps<Row>['actionFilter'] = useCallback(
(col: ColumnInstance<Row> & UseFiltersColumnProps<Row>) => {
if (!col.canFilter) {
return undefined;
}
const handler = () => toggleVisibility(col);
return {
handler,
children: isVisible(col) ? (
<div className="filter-dialog">
{col.render('Summary')}
<button onClick={handler} type="button">
Close
</button>
</div>
) : undefined,
};
},
[isVisible, toggleVisibility]
);
return r;
}

// function MyGroupingOptionAction(props: { col: ColumnInstance<Row>; icons: ActionIcons }) {
// const col = props.col as unknown as ColumnInstance<Row> & UseFiltersColumnProps<Row>;
function TextGroupByOptionsBlock({
setGroupingOptions,
groupingOptions,
onClose,
}: {
groupingOptions?: TextGroupByOptions;
setGroupingOptions(v: TextGroupByOptions | undefined): void;
onClose(): void;
}) {
const values = (groupingOptions?.values ?? []) as (RegExp | string)[];

const onSubmit = useCallback(
(e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const fData = new FormData(e.currentTarget);
const type = fData.get('type') as TextGroupByOptions['type'];
if (!type) {
setGroupingOptions(undefined);
onClose();
return;
}

const currentValues = (fData.get('values') ?? '')
.toString()
.split('\n')
.map((d) => d.trim())
.filter(Boolean);
if (type === 'regex') {
setGroupingOptions({
type,
values: currentValues.map((d) => new RegExp(d)),
});
} else {
setGroupingOptions({
type,
values: currentValues,
});
}
onClose();
},
[onClose, setGroupingOptions]
);

// const [visible, setVisible] = useState(false);
// const showGroupingDialog = useCallback(() => {
// setVisible(!visible);
// }, [visible, setVisible]);
return (
<form className="groupby-dialog" onSubmit={onSubmit}>
<label>
<input type="radio" name="type" value="value" defaultChecked={groupingOptions?.type === 'value'} />
By Value
</label>
<label>
<input type="radio" name="type" value="regex" defaultChecked={groupingOptions?.type === 'regex'} />
By Regex
</label>
<label>
<input type="radio" name="type" value="startsWith" defaultChecked={groupingOptions?.type === 'startsWith'} />
By Starts With
</label>
<textarea
required
name="values"
defaultValue={values.map((d) => (d instanceof RegExp ? d.source : d)).join('\n')}
rows={5}
style={{ height: '5em' }}
/>
<button type="submit">Close</button>
</form>
);
}

// return (
// <>
// <LineUpLiteFilterAction
// column={col}
// iconFilter={props.icons.filterColumn}
// toggleFilterColumn={showGroupingDialog}
// />
// {visible && (
// <div className="filter-dialog">
// {col.render('Summary')}
// <button onClick={showGroupingDialog} type="button">
// Close
// </button>
// </div>
// )}
// </>
// );
// }
function useGroupByAction() {
const { toggleVisibility, isVisible } = useVisibleHelper();
return useMemo(() => {
const f: ActionLineUpProps<Row>['actionGroupBy'] = (col, helper) => {
if (!col.canGroupBy || !col.setGroupingOptions || col.id !== 'name' || col.isGrouped) {
return undefined;
}
const handler = () => toggleVisibility(col);
return {
handler,
children: isVisible(col) ? (
<TextGroupByOptionsBlock
groupingOptions={col.groupingOptions}
setGroupingOptions={col.setGroupingOptions}
onClose={() => {
helper.toggleGroupBy();
handler();
}}
/>
) : undefined,
};
};
return f;
}, [isVisible, toggleVisibility]);
}

function Table({ isDarkTheme }: { isDarkTheme: boolean }) {
const columns: LineUpLiteColumn<Row>[] = useMemo(
Expand All @@ -114,13 +194,8 @@ function Table({ isDarkTheme }: { isDarkTheme: boolean }) {
);
const icons = useMemo(() => actionIconsRemixicon(), []);

const filterAction = useCallback((col: ColumnInstance<Row>, givenIcons: ActionIcons) => {
return (
<>
<MyFilterAction col={col} icons={givenIcons} />
</>
);
}, []);
const actionFilter = useFilterAction();
const actionGroupBy = useGroupByAction();

return (
<LineUpLiteStateContextProvider>
Expand All @@ -133,11 +208,17 @@ function Table({ isDarkTheme }: { isDarkTheme: boolean }) {
icons={icons}
dark={isDarkTheme}
selectCheckboxComponent={MyCheckBox}
actions={filterAction}
// onStateChange={setInstance}
actionFilter={actionFilter}
actionGroupBy={actionGroupBy}
/>
</div>
<LineUpLitePanel className="side-panel" icons={icons} dark={isDarkTheme} actions={filterAction} />
<LineUpLitePanel
className="side-panel"
icons={icons}
dark={isDarkTheme}
actionFilter={actionFilter}
actionGroupBy={actionGroupBy}
/>
</div>
</LineUpLiteStateContextProvider>
);
Expand Down
16 changes: 15 additions & 1 deletion packages/_playground/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ code {

@media (prefers-color-scheme: dark) {
body {
background: #333; color: white;
background: #333; color: white;
}
}

Expand Down Expand Up @@ -68,3 +68,17 @@ code {
margin-top: 0.5em;
flex-grow: 1;
}


.groupby-dialog {
position: absolute;
width: 12em;
background: rgba(255, 255, 255, 0.95);
border: 1px solid currentColor;
border-radius: 3px;
z-index: 100;
top: 100%;
right: 0;
display: flex;
flex-direction: column
}
22 changes: 16 additions & 6 deletions packages/hooks/src/grouping/baseGroupBy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,24 @@
import type { ColumnInstance, Row } from 'react-table';
import type { AnyObject, UnknownObject } from '../interfaces';
import { compareAsc } from '../sort/internal';
import { MISSING_GROUP } from './histGroupBy';
import { MISSING_GROUP, OTHERS_GROUPS } from './histGroupBy';

export function compareMissing(a: string, b: string): number {
if (a === MISSING_GROUP) {
return b === MISSING_GROUP ? 0 : 1;
function toSpecialIndex(v: string) {
if (v === MISSING_GROUP) {
return 2;
}
if (v === OTHERS_GROUPS) {
return 1;
}
if (b === MISSING_GROUP) {
return -1;
return 0;
}

export function compareMissing(a: string, b: string): number {
const aIndex = toSpecialIndex(a);
const bIndex = toSpecialIndex(b);

if (aIndex !== bIndex) {
return aIndex - bIndex;
}
return compareAsc(a, b);
}
Expand Down
6 changes: 5 additions & 1 deletion packages/hooks/src/grouping/columnSpecificGroupByFn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ export default function columnSpecificGroupByFn<D extends AnyObject = UnknownObj
}
const cell: Cell<D, AnyObject> | undefined = rows[0].allCells.find((d) => d.column.id === columnId);
if (hasGroupByFunction<D>(cell)) {
return cell.column.groupBy(rows, cell.column, (cell as Partial<UseGroupingOptionsColumnProps>).groupingOptions);
return cell.column.groupBy(
rows,
cell.column,
(cell.column as Partial<UseGroupingOptionsColumnProps>).groupingOptions
);
}
return defaultGroupByFn(rows, columnId);
}
2 changes: 1 addition & 1 deletion packages/hooks/src/grouping/histGroupBy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import type { AnyObject, UnknownObject } from '../interfaces';
import { computeArrayNumberStats } from '../stats';

export const MISSING_GROUP = 'Missing Values';

export const OTHERS_GROUPS = 'Other Values';
/**
* group by histogram
* @param rows
Expand Down
10 changes: 5 additions & 5 deletions packages/hooks/src/grouping/textGroupBy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import type { ColumnInstance, Row } from 'react-table';
import type { AnyObject, UnknownObject } from '../interfaces';
import baseGroupBy, { sortGroups } from './baseGroupBy';
import { MISSING_GROUP } from './histGroupBy';
import { MISSING_GROUP, OTHERS_GROUPS } from './histGroupBy';

export interface TextGroupByValueOptions {
type: 'value';
Expand Down Expand Up @@ -38,19 +38,19 @@ export default function textGroupBy<D extends AnyObject = UnknownObject>(
let toGroup = (v: string | null) => v || MISSING_GROUP;
switch (options.type) {
case 'regex':
toGroup = (v) => options.values.find((d) => v != null && d.test(v))?.source ?? MISSING_GROUP;
toGroup = (v) => options.values.find((d) => v != null && d.test(v))?.source ?? OTHERS_GROUPS;
break;
case 'value':
toGroup = (v) => options.values.find((d) => v != null && d === v) ?? MISSING_GROUP;
toGroup = (v) => options.values.find((d) => v != null && d === v) ?? OTHERS_GROUPS;
break;
case 'startsWith':
toGroup = (v) => options.values.find((d) => v != null && v.startsWith(d)) ?? MISSING_GROUP;
toGroup = (v) => options.values.find((d) => v != null && v.startsWith(d)) ?? OTHERS_GROUPS;
break;
}

for (const row of rows) {
const v = row.values[column.id] ?? null;
const g = toGroup(v);
const g = v != null ? toGroup(v) : MISSING_GROUP;
const rValue = r[g];
if (!rValue) {
r[g] = [row];
Expand Down
2 changes: 1 addition & 1 deletion packages/hooks/src/hooks/useGroupingOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ function reducer<D extends AnyObject = UnknownObject>(
};
}

if (action.type === actions.setRowState) {
if (action.type === actions.setGroupingOptions) {
const { columnId, groupingOptions } = action as unknown as {
columnId: string;
groupingOptions: Record<string, unknown>;
Expand Down
2 changes: 1 addition & 1 deletion packages/hooks/src/hooks/useSortingOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ function reducer<D extends AnyObject = UnknownObject>(
};
}

if (action.type === actions.setRowState) {
if (action.type === actions.setSortingOptions) {
const { columnId, sortingOptions } = action as unknown as {
columnId: string;
sortingOptions: Record<string, unknown>;
Expand Down
Loading

0 comments on commit c5ff024

Please sign in to comment.