Skip to content

Commit

Permalink
add filter example in the example repo
Browse files Browse the repository at this point in the history
skill tree listens and collapses trees when treeid doesn't match filter query
add memoization to functions for small performance iprovements
update broken type
  • Loading branch information
andrico1234 committed Nov 16, 2019
1 parent 84a16e5 commit f190abe
Show file tree
Hide file tree
Showing 10 changed files with 178 additions and 59 deletions.
18 changes: 18 additions & 0 deletions example/components/FIlterInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as React from 'react';

interface Props {
handleFilter: (query: string) => void;
}

function FilterInput(props: Props) {
const { handleFilter } = props;
return (
<input
style={{ height: '32px' }}
onChange={e => handleFilter(e.target.value)}
placeholder="Search for skill..."
/>
);
}

export default FilterInput;
52 changes: 52 additions & 0 deletions example/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
import './index.css';
import { legsPushData, legsPullData, hpSavedData } from './mockData';
import { ContextStorage } from '../src/models';
import FilterInput from './components/FIlterInput';

function handleSave(
storage: ContextStorage,
Expand All @@ -35,6 +36,7 @@ const App = () => {
skillCount,
selectedSkillCount,
resetSkills,
handleFilter,
}: SkillGroupDataType) => {
const totalSkillCount = skillCount.optional + skillCount.required;
const totalSelectedCount =
Expand All @@ -60,6 +62,56 @@ const App = () => {
Reset
</button>
</div>
<FilterInput handleFilter={handleFilter} />
<SkillTree
treeId="sp"
title="Squat Progression"
description="These are the progressions for squats"
data={legsPushData}
collapsible
/>
<SkillTree
treeId="sp"
title="Squat Progression"
description="These are the progressions for squats"
data={legsPushData}
collapsible
/>
<SkillTree
treeId="sp"
title="Squat Progression"
description="These are the progressions for squats"
data={legsPushData}
collapsible
/>
<SkillTree
treeId="sp"
title="Squat Progression"
description="These are the progressions for squats"
data={legsPushData}
collapsible
/>
<SkillTree
treeId="sp"
title="Squat Progression"
description="These are the progressions for squats"
data={legsPushData}
collapsible
/>
<SkillTree
treeId="sp"
title="Squat Progression"
description="These are the progressions for squats"
data={legsPushData}
collapsible
/>
<SkillTree
treeId="sp"
title="Squat Progression"
description="These are the progressions for squats"
data={legsPushData}
collapsible
/>
<SkillTree
treeId="sp"
title="Squat Progression"
Expand Down
103 changes: 57 additions & 46 deletions src/components/SkillTree.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useContext, useState } from 'react';
import React, { useState, useCallback, useContext } from 'react';
import { Skill, SavedDataType, ContextStorage } from '../models';
import SkillTreeSegment from './SkillTreeSegment';
import HSeparator from './ui/HSeparator';
Expand All @@ -8,7 +8,8 @@ import styled, { BaseThemedCssFunction } from 'styled-components';
import MobileContext from '../context/MobileContext';
import { SkillTheme } from '../theme';
import SkillTreeHeader from './SkillTreeHeader';
import AddToFilterIndex from './AddToFilterIndex';
import AddToFilterIndex from './filter/AddToFilterIndex';
import FilterListener from './filter/FilterListener';

const css: BaseThemedCssFunction<SkillTheme> = require('styled-components').css;

Expand Down Expand Up @@ -46,54 +47,64 @@ function SkillTree({
const { isMobile } = useContext(MobileContext);
const [isVisible, setVisibility] = useState(true);

function toggleVisibility() {
if (!collapsible) return;
const memoizedToggleVisibility = useCallback(
function toggleVisibility() {
if (!collapsible) return;

return setVisibility(!isVisible);
}
return setVisibility(!isVisible);
},
[isVisible]
);

return (
<SkillTreeProvider
treeId={treeId}
savedData={savedData}
handleSave={handleSave}
>
<CalculateNodeCount data={data} />
<React.Fragment>
<AddToFilterIndex treeId={treeId} skills={data} />
<SkillTreeContainer>
<SkillTreeHeader
isVisible={isVisible}
handleClick={toggleVisibility}
collapsible={collapsible}
id={treeId}
description={description}
title={title}
/>
<VisibilityContainer
data-testid="visibility-container"
isVisible={isVisible}
>
<StyledSkillTree isCollapsible={collapsible}>
{data.map((skill, i) => {
const displaySeparator = data.length - 1 !== i && isMobile;

return (
<React.Fragment key={skill.id}>
<SkillTreeSegment
shouldBeUnlocked
skill={skill}
hasParent={false}
parentPosition={0}
parentHasMultipleChildren={false}
/>
<HSeparator display={displaySeparator} />
</React.Fragment>
);
})}
</StyledSkillTree>
</VisibilityContainer>
</SkillTreeContainer>
</SkillTreeProvider>
<FilterListener
isVisible={isVisible}
setVisibility={setVisibility}
treeId={treeId}
/>
<SkillTreeProvider
treeId={treeId}
savedData={savedData}
handleSave={handleSave}
>
<CalculateNodeCount data={data} />
<SkillTreeContainer>
<SkillTreeHeader
isVisible={isVisible}
handleClick={memoizedToggleVisibility}
collapsible={collapsible}
id={treeId}
description={description}
title={title}
/>
<VisibilityContainer
data-testid="visibility-container"
isVisible={isVisible}
>
<StyledSkillTree isCollapsible={collapsible}>
{data.map((skill, i) => {
const displaySeparator = data.length - 1 !== i && isMobile;

return (
<React.Fragment key={skill.id}>
<SkillTreeSegment
shouldBeUnlocked
skill={skill}
hasParent={false}
parentPosition={0}
parentHasMultipleChildren={false}
/>
<HSeparator display={displaySeparator} />
</React.Fragment>
);
})}
</StyledSkillTree>
</VisibilityContainer>
</SkillTreeContainer>
</SkillTreeProvider>
</React.Fragment>
);
}

Expand Down
2 changes: 1 addition & 1 deletion src/components/SkillTreeGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as React from 'react';
import AppContext from '../context/AppContext';
import styled, { ThemeProvider } from 'styled-components';
import defaultTheme from '../theme/index';
import { DeepPartial } from 'models/utils';
import { DeepPartial } from '../models/utils';
import { SkillGroupData } from '../models';
import { MobileProvider } from '../context/MobileContext';
import FilterContext from '../context/FilterContext';
Expand Down
18 changes: 12 additions & 6 deletions src/components/SkillTreeHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useCallback } from 'react';
import styled, { BaseThemedCssFunction } from 'styled-components';
import SkillCountSubtitle from './SkillCountSubtitle';
import { SkillTheme } from '../theme';
Expand Down Expand Up @@ -26,14 +26,20 @@ interface CollapsibleContainerProps {

function SkillTreeHeader(props: Props) {
const { handleClick, collapsible, isVisible, id, title, description } = props;

const memoizedHandleKeyDown = useCallback(
function handleKeyDown(e: React.KeyboardEvent<HTMLDivElement>) {
if (e.keyCode === 13) {
handleClick();
}
},
[handleClick]
);

return (
<StyledSkillTreeHeader
tabIndex={0}
onKeyDown={(e: React.KeyboardEvent<HTMLDivElement>) => {
if (e.keyCode === 13) {
handleClick();
}
}}
onKeyDown={memoizedHandleKeyDown}
onClick={handleClick}
isCollapsible={collapsible}
>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useContext, useEffect } from 'react';
import { Skill, SkillMap } from '../models';
import FilterContext from '../context/FilterContext';
import { Skill, SkillMap } from '../../models';
import FilterContext from '../../context/FilterContext';

interface Props {
treeId: string;
Expand All @@ -16,7 +16,7 @@ function createSkillsTreeMap(treeId: string, skills: Skill[]) {
addSkillToMap(skill.children);
}

skillsTreeMap[skill.id] = treeId;
skillsTreeMap[skill.title.toLowerCase()] = treeId;
});
}

Expand Down
32 changes: 32 additions & 0 deletions src/components/filter/FilterListener.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useEffect, useContext } from 'react';
import FilterContext from '../../context/FilterContext';

interface Props {
setVisibility: (isVisible: boolean) => void;
isVisible: boolean;
treeId: string;
}

function FilterListener({ setVisibility, isVisible, treeId }: Props) {
const { filtersMatches } = useContext(FilterContext);

useEffect(() => {
if (!filtersMatches) {
if (isVisible === true) return;

return setVisibility(true);
}

if (!filtersMatches.has(treeId)) {
if (isVisible === false) return;
return setVisibility(false);
}

if (isVisible === true) return;
return setVisibility(true);
}, [filtersMatches]);

return null;
}

export default FilterListener;
3 changes: 2 additions & 1 deletion src/context/FilterContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ export function FilterProvider(props: Props) {
return setMatches(null);
}

const sanitizedQuery = query.toLowerCase();
const skills = Object.keys(skillMap);
const filteredSkills = skills.filter(key => key.includes(query));
const filteredSkills = skills.filter(key => key.includes(sanitizedQuery));
const treeIds = new Set(filteredSkills.map(skill => skillMap[skill]));

return setMatches(treeIds);
Expand Down
1 change: 0 additions & 1 deletion src/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ export interface SkillGroupData {
skillCount: SkillCount;
selectedSkillCount: SkillCount;
resetSkills: VoidFunction;
filtersMatches: Set<string> | null;
handleFilter: (query: string) => void;
}

Expand Down
2 changes: 1 addition & 1 deletion src/models/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ export type Dictionary<T> = {
export type Nullable<T> = T | null;

export type DeepPartial<T> = {
[P in keyof T]: DeepPartial<T[P]>;
[P in keyof T]?: DeepPartial<T[P]>;
};

0 comments on commit f190abe

Please sign in to comment.