Skip to content

Commit 5a08a70

Browse files
committed
TAL-26 - update talent search input: reuse skill search from shared lib
1 parent 594f796 commit 5a08a70

File tree

9 files changed

+191
-204
lines changed

9 files changed

+191
-204
lines changed

src/apps/talent-search/src/routes/talent-search/TalentSearch.module.scss

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
background-image: url("../../assets/background.png");
77
background-size: cover;
88
background-repeat: no-repeat;
9-
9+
1010
.contentLayout-outer {
1111
width: 100%;
1212

@@ -92,5 +92,5 @@
9292
height: 24px;
9393
color: $black-40;
9494
stroke: $black-40;
95-
margin-right: 24px;
95+
margin-right: 10px;
9696
}

src/apps/talent-search/src/routes/talent-search/TalentSearch.tsx

Lines changed: 53 additions & 169 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,40 @@
1-
/* eslint-disable react/jsx-no-bind */
2-
import {
3-
CSSProperties,
4-
FC,
5-
useState,
6-
} from 'react'
7-
import { components, ControlProps, GroupBase,
8-
MultiValue, Options, SingleValue, StylesConfig } from 'react-select'
9-
import AsyncSelect from 'react-select/async'
10-
import PropTypes from 'prop-types'
1+
import { FC, useMemo, useState } from 'react'
112

12-
import { SearchIcon } from '@heroicons/react/outline'
13-
import { ContentLayout } from '~/libs/ui'
3+
import { ContentLayout, IconOutline, InputMultiselectOption } from '~/libs/ui'
4+
import { EmsiSkill, EmsiSkillSources, InputSkillSelector } from '~/libs/shared'
145
import { Skill } from '@talentSearch/lib/models/'
15-
import MatcherService from '@talentSearch/lib/services/MatcherService'
166

177
import SkillPill from './components/SkillPill'
188
import styles from './TalentSearch.module.scss'
199

20-
function search(skills:Options<Skill>): void {
21-
alert(JSON.stringify(skills))
22-
}
23-
24-
// eslint-disable-next-line react/destructuring-assignment, @typescript-eslint/typedef
25-
const Control: React.FC<ControlProps<Skill, boolean, GroupBase<Skill>>> = ({ children, ...props }) => (
26-
<components.Control {...props}>
27-
{children}
28-
<span
29-
onClick={() => search(props.getValue())}
30-
className={styles.searchIconSpan}
31-
>
32-
<SearchIcon className={styles.searchIcon} />
33-
</span>
34-
</components.Control>
35-
)
36-
37-
Control.propTypes = {
38-
children: PropTypes.node.isRequired,
39-
getValue: PropTypes.func.isRequired,
40-
}
10+
// TODO: Make this configurable, or read from a service. We need to discuss
11+
// how we want to handle this.
12+
const popularSkills:Skill[][] = [
13+
[{ emsiId: 'KS441LF7187KS0CV4B6Y', name: 'Typescript' },
14+
{ emsiId: 'KS1244K6176NLVWV02B6', name: 'Front-End Engineering' },
15+
{ emsiId: 'KS1214R5XG4X4PY7LGY6', name: 'Bootstrap (Front-End Framework)' }],
16+
[{ emsiId: 'KS121F45VPV8C9W3QFYH', name: 'Cascading Style Sheets (CSS)' },
17+
{ emsiId: 'KS1200771D9CR9LB4MWW', name: 'JavaScript (Programming Language)' }],
18+
[{ emsiId: 'KS1200578T5QCYT0Z98G', name: 'HyperText Markup Language (HTML)' },
19+
{ emsiId: 'ES86A20379CD2AD061F3', name: 'IOS Development' },
20+
{ emsiId: 'KS127296VDYS7ZFWVC46', name: 'Node.js' }],
21+
[{ emsiId: 'ES50D03AC9CFC1A0BC93', name: '.NET Development' },
22+
{ emsiId: 'KS1219W70LY1GXZDSKW5', name: 'C++ (Programming Language)' },
23+
{ emsiId: 'KS127SZ60YZR8B5CQKV1', name: 'PHP Development' }],
24+
[{ emsiId: 'KS1206V6K46N1SDVJGBD', name: 'Adobe Illustrator' },
25+
{ emsiId: 'ESD07FEE22E7EC094EB8', name: 'Ruby (Programming Language)' },
26+
{ emsiId: 'KS120076FGP5WGWYMP0F', name: 'Java (Programming Language)' }],
27+
[{ emsiId: 'KSPSGF5MXB6568UIQ4BK', name: 'React Native' },
28+
{ emsiId: 'KS441PL6JPXW200W0GRQ', name: 'User Experience (UX)' }],
29+
]
4130

4231
export const TalentSearch: FC = () => {
4332
const [skillsFilter, setSkillsFilter] = useState<Array<Skill>>([])
33+
const emsiSkills: EmsiSkill[] = useMemo(() => skillsFilter.map(s => ({
34+
name: s.name,
35+
skillId: s.emsiId,
36+
skillSources: [EmsiSkillSources.selfPicked],
37+
})), [skillsFilter])
4438

4539
function toggleSkill(skill:Skill): void {
4640
let newFilter: Array<Skill> = []
@@ -63,12 +57,12 @@ export const TalentSearch: FC = () => {
6357
}
6458
}
6559

66-
function onChange(options:MultiValue<Skill> | SingleValue<Skill>): void {
67-
if (Array.isArray(options)) {
68-
setSkillsFilter(options)
69-
} else {
70-
setSkillsFilter([])
71-
}
60+
function onChange(ev: any): void {
61+
const options = (ev.target.value as unknown) as InputMultiselectOption[]
62+
setSkillsFilter(options.map(v => ({
63+
emsiId: v.value,
64+
name: v.label as string,
65+
})))
7266
}
7367

7468
function filteringSkill(skill:Skill): boolean {
@@ -82,105 +76,6 @@ export const TalentSearch: FC = () => {
8276
return result
8377
}
8478

85-
// TODO: Make this configurable, or read from a service. We need to discuss
86-
// how we want to handle this.
87-
const popularSkills:Skill[][] = [
88-
[{ emsiId: 'KS441LF7187KS0CV4B6Y', name: 'Typescript' },
89-
{ emsiId: 'KS1244K6176NLVWV02B6', name: 'Front-End Engineering' },
90-
{ emsiId: 'KS1214R5XG4X4PY7LGY6', name: 'Bootstrap (Front-End Framework)' }],
91-
[{ emsiId: 'KS121F45VPV8C9W3QFYH', name: 'Cascading Style Sheets (CSS)' },
92-
{ emsiId: 'KS1200771D9CR9LB4MWW', name: 'JavaScript (Programming Language)' }],
93-
[{ emsiId: 'KS1200578T5QCYT0Z98G', name: 'HyperText Markup Language (HTML)' },
94-
{ emsiId: 'ES86A20379CD2AD061F3', name: 'IOS Development' },
95-
{ emsiId: 'KS127296VDYS7ZFWVC46', name: 'Node.js' }],
96-
[{ emsiId: 'ES50D03AC9CFC1A0BC93', name: '.NET Development' },
97-
{ emsiId: 'KS1219W70LY1GXZDSKW5', name: 'C++ (Programming Language)' },
98-
{ emsiId: 'KS127SZ60YZR8B5CQKV1', name: 'PHP Development' }],
99-
[{ emsiId: 'KS1206V6K46N1SDVJGBD', name: 'Adobe Illustrator' },
100-
{ emsiId: 'ESD07FEE22E7EC094EB8', name: 'Ruby (Programming Language)' },
101-
{ emsiId: 'KS120076FGP5WGWYMP0F', name: 'Java (Programming Language)' }],
102-
[{ emsiId: 'KSPSGF5MXB6568UIQ4BK', name: 'React Native' },
103-
{ emsiId: 'KS441PL6JPXW200W0GRQ', name: 'User Experience (UX)' }],
104-
]
105-
106-
const controlStyle: CSSProperties = {
107-
borderColor: 'black',
108-
paddingBottom: '10px',
109-
paddingTop: '10px',
110-
}
111-
112-
const placeholderStyle: CSSProperties = {
113-
color: '#2A2A2A',
114-
fontFamily: 'Roboto',
115-
fontSize: '16',
116-
fontWeight: 400,
117-
height: '36px',
118-
paddingTop: '4px',
119-
}
120-
121-
const multiValueStyle: CSSProperties = {
122-
backgroundColor: 'white',
123-
border: '1px solid #d4d4d4',
124-
borderRadius: '24px',
125-
color: '#333',
126-
fontFamily: 'Roboto',
127-
fontSize: '14',
128-
fontWeight: '400',
129-
height: '32px',
130-
marginRight: '10px',
131-
paddingLeft: '8px',
132-
paddingRight: '8px',
133-
}
134-
135-
const multiValueRemoveStyle: CSSProperties = {
136-
backgroundColor: '#d9d9d9',
137-
border: '1px solid #d4d4d4',
138-
borderRadius: '11px',
139-
color: '#333',
140-
fontSize: '12',
141-
height: '12px',
142-
marginBottom: 'auto',
143-
marginLeft: '5px',
144-
marginRight: '5px',
145-
marginTop: 'auto',
146-
padding: '0px',
147-
width: '12px',
148-
}
149-
150-
const hiddenStyle: CSSProperties = {
151-
display: 'none',
152-
}
153-
154-
const selectStyle: StylesConfig<Skill> = {
155-
clearIndicator: provided => ({
156-
...provided,
157-
...hiddenStyle,
158-
}),
159-
control: provided => ({
160-
...provided,
161-
...controlStyle,
162-
}),
163-
dropdownIndicator: provided => ({
164-
...provided,
165-
...hiddenStyle,
166-
}),
167-
indicatorSeparator: provided => ({
168-
...provided,
169-
...hiddenStyle,
170-
}),
171-
multiValue: provided => ({
172-
...provided,
173-
...multiValueStyle,
174-
}),
175-
multiValueRemove: provided => ({
176-
...provided,
177-
...multiValueRemoveStyle,
178-
}),
179-
placeholder: provided => ({
180-
...provided,
181-
...placeholderStyle,
182-
}),
183-
}
18479
return (
18580
<ContentLayout
18681
contentClass={styles.contentLayout}
@@ -197,42 +92,31 @@ export const TalentSearch: FC = () => {
19792
</div>
19893
<div className={styles.searchOptions}>
19994
<span className={styles.searchPrompt}>Search by skills</span>
200-
<AsyncSelect
201-
isMulti
202-
cacheOptions
203-
autoFocus
204-
defaultOptions
95+
<InputSkillSelector
20596
placeholder='Enter skills you are searching for...'
206-
loadOptions={MatcherService.autoCompleteSkills}
207-
name='skills'
208-
styles={selectStyle}
209-
className={styles.searchSelect}
210-
getOptionLabel={(skill: Skill) => skill.name}
211-
getOptionValue={(skill: Skill) => skill.emsiId}
212-
components={{ Control }}
213-
openMenuOnClick={false}
214-
value={skillsFilter}
215-
onChange={(
216-
newValue: MultiValue<Skill> | SingleValue<Skill>,
217-
) => onChange(newValue)}
97+
useWrapper={false}
98+
theme='clear'
99+
dropdownIcon={<IconOutline.SearchIcon className={styles.searchIcon} />}
100+
value={emsiSkills}
101+
onChange={onChange}
218102
/>
219103
</div>
220104
<div className={styles.popularSkillsContainer}>
221105
<span className={styles.popularSkillsTitle}>Popular Skills</span>
222106

223-
{popularSkills.map(
224-
row => (
225-
<div className={styles.pillRow}>
226-
{row.map(skill => (
227-
<SkillPill
228-
skill={skill}
229-
selected={filteringSkill(skill)}
230-
onClick={toggleSkill}
231-
/>
232-
))}
233-
</div>
234-
),
235-
)}
107+
{popularSkills.map((row, i) => (
108+
// eslint-disable-next-line react/no-array-index-key
109+
<div className={styles.pillRow} key={i}>
110+
{row.map(skill => (
111+
<SkillPill
112+
key={skill.emsiId}
113+
skill={skill}
114+
selected={filteringSkill(skill)}
115+
onClick={toggleSkill}
116+
/>
117+
))}
118+
</div>
119+
))}
236120
</div>
237121
</ContentLayout>
238122
)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './contact-support-form'
22
export * from './modals'
3+
export * from './input-skill-selector'
34
export * from './member-skill-editor'

src/libs/shared/lib/components/input-skill-selector/InputSkillSelector.tsx

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { ChangeEvent, FC } from 'react'
1+
import { ChangeEvent, FC, ReactNode } from 'react'
22
import { noop } from 'lodash'
33

4-
import { InputMultiselect, InputMultiselectOption } from '~/libs/ui'
4+
import { InputMultiselect, InputMultiselectOption, InputMultiselectThemes } from '~/libs/ui'
55

66
import { autoCompleteSkills, EmsiSkill, EmsiSkillSources } from '../../services/emsi-skills'
77

@@ -29,21 +29,29 @@ const fetchSkills = (queryTerm: string): Promise<Option[]> => (
2929

3030
interface InputSkillSelectorProps {
3131
readonly limit?: number
32+
readonly label?: string
3233
readonly loading?: boolean
33-
readonly value?: EmsiSkill[]
3434
readonly onChange?: (event: ChangeEvent<HTMLInputElement>) => void
35+
readonly placeholder?: string
36+
readonly value?: EmsiSkill[]
37+
readonly theme?: InputMultiselectThemes
38+
readonly useWrapper?: boolean
39+
readonly dropdownIcon?: ReactNode
3540
}
3641

3742
const InputSkillSelector: FC<InputSkillSelectorProps> = props => (
3843
<InputMultiselect
39-
label='Select Skills'
44+
label={props.label ?? 'Select Skills'}
4045
limit={props.limit}
41-
placeholder='Type to add a skill...'
46+
placeholder={props.placeholder ?? 'Type to add a skill...'}
4247
onFetchOptions={fetchSkills}
4348
name='skills'
4449
onChange={props.onChange ?? noop}
4550
value={props.value?.map(mapEmsiSkillToInputOption)}
4651
loading={props.loading}
52+
theme={props.theme}
53+
useWrapper={props.useWrapper}
54+
dropdownIcon={props.dropdownIcon}
4755
/>
4856
)
4957

src/libs/shared/lib/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export * from './components'
22
export * from './containers'
33
export * from './hooks'
4+
export * from './services'
45
export * from './utils'
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './emsi-skills'

0 commit comments

Comments
 (0)