|
| 1 | +/* eslint-disable react/destructuring-assignment */ |
| 2 | +/* eslint-disable @typescript-eslint/no-unused-vars */ |
| 3 | +/* eslint-disable arrow-body-style */ |
| 4 | +/* eslint-disable @typescript-eslint/typedef */ |
| 5 | +/* eslint-disable indent */ |
| 6 | +/* eslint-disable sort-keys */ |
1 | 7 | /* eslint-disable ordered-imports/ordered-imports */ |
2 | 8 | /* eslint-disable react/jsx-no-bind */ |
3 | 9 | import { |
4 | 10 | FC, |
5 | 11 | useState, |
| 12 | + CSSProperties |
6 | 13 | } from 'react' |
7 | 14 |
|
| 15 | +import { components, ControlProps, Options, GroupBase, MultiValue, SingleValue, StylesConfig, ActionMeta } from 'react-select' |
8 | 16 | import AsyncSelect from 'react-select/async' |
| 17 | + |
9 | 18 | import { ContentLayout } from '~/libs/ui' |
10 | | -import MatcherService from '@talentSearch/lib/services/MatcherService' |
11 | 19 | import { Skill } from '@talentSearch/lib/models/' |
12 | | -import SkillSearchResults from './components/skill-search-results/SkillSearchResults' |
13 | | - |
| 20 | +import MatcherService from '@talentSearch/lib/services/MatcherService' |
| 21 | +import { SearchIcon } from '@heroicons/react/outline' |
| 22 | +import SkillPill from './components/SkillPill' |
14 | 23 | import styles from './TalentSearch.module.scss' |
15 | 24 |
|
| 25 | +function search(skills:Options<Skill>): void { |
| 26 | + alert("Searching skills: " + JSON.stringify(skills)) |
| 27 | +} |
| 28 | + |
| 29 | +const Control: React.FC<ControlProps<Skill, boolean, GroupBase<Skill>>> = ({children, ...props }) => ( |
| 30 | + <components.Control {...props}> |
| 31 | + {children} |
| 32 | + <span onClick={() => search(props.getValue())} className={styles.searchIconSpan}><SearchIcon className={styles.searchIcon} /></span> |
| 33 | + </components.Control> |
| 34 | +) |
| 35 | + |
16 | 36 | export const TalentSearch: FC = () => { |
17 | | - const [skillsFilter, setSkillsFilter] = useState<ReadonlyArray<Skill>>([]) |
| 37 | + const [skillsFilter, setSkillsFilter] = useState<Array<Skill>>([]) |
| 38 | + |
| 39 | + function search(): void { |
| 40 | + alert("Searching for skills: " + JSON.stringify(skillsFilter)) |
| 41 | + } |
| 42 | + |
| 43 | + function toggleSkill(skill:Skill, pill:SkillPill): void { |
| 44 | + let newFilter: Array<Skill> = [] |
| 45 | + let deleted: boolean = false |
| 46 | + if (skillsFilter) { |
| 47 | + // Either delete the value from the list, if we're toggling one that's already in the list |
| 48 | + // Or add the new item to the list |
| 49 | + skillsFilter.forEach((filterSkill, index) => { |
| 50 | + if (filterSkill.emsiId === skill.emsiId) { |
| 51 | + deleted = true |
| 52 | + } |
| 53 | + else { |
| 54 | + newFilter.push(filterSkill) |
| 55 | + } |
| 56 | + }) |
| 57 | + if (deleted === false) { |
| 58 | + newFilter = skillsFilter.concat(skill) |
| 59 | + } |
| 60 | + |
| 61 | + setSkillsFilter(newFilter) |
| 62 | + } |
| 63 | + } |
| 64 | + |
| 65 | + function onChange(options:MultiValue<Skill> | SingleValue<Skill>, meta:ActionMeta<Skill>): void { |
| 66 | + if (Array.isArray(options)) { |
| 67 | + setSkillsFilter(options) |
| 68 | + } |
| 69 | + else { |
| 70 | + setSkillsFilter([]) |
| 71 | + } |
| 72 | + } |
| 73 | + |
| 74 | + function filteringSkill(skill:Skill): boolean { |
| 75 | + let result: boolean = false |
| 76 | + skillsFilter.forEach((filterSkill, index) => { |
| 77 | + if (filterSkill.emsiId === skill.emsiId) { |
| 78 | + result = true |
| 79 | + } |
| 80 | + }) |
18 | 81 |
|
| 82 | + return result |
| 83 | + } |
| 84 | + |
| 85 | + const popularSkills:Skill[][] = [ |
| 86 | + [{ name: 'Typescript', emsiId: 'KS441LF7187KS0CV4B6Y' }, |
| 87 | + { name: 'Front-End Engineering', emsiId: 'KS1244K6176NLVWV02B6' }, |
| 88 | + { name: 'Bootstrap (Front-End Framework)', emsiId: 'KS1214R5XG4X4PY7LGY6' }], |
| 89 | + [{ name: 'Cascading Style Sheets (CSS)', emsiId: 'KS121F45VPV8C9W3QFYH' }, |
| 90 | + { name: 'JavaScript (Programming Language)', emsiId: 'KS1200771D9CR9LB4MWW' }], |
| 91 | + [{ name: 'HyperText Markup Language (HTML)', emsiId: 'KS1200578T5QCYT0Z98G' }, |
| 92 | + { name: 'IOS Development', emsiId: 'ES86A20379CD2AD061F3' }, |
| 93 | + { name: 'Node.js', emsiId: 'KS127296VDYS7ZFWVC46' }], |
| 94 | + [{ name: '.NET Development', emsiId: 'ES50D03AC9CFC1A0BC93' }, |
| 95 | + { name: 'C++ (Programming Language)', emsiId: 'KS1219W70LY1GXZDSKW5' }, |
| 96 | + { name: 'PHP Development', emsiId: 'KS127SZ60YZR8B5CQKV1' }], |
| 97 | + [{ name: 'Adobe Illustrator', emsiId: 'KS1206V6K46N1SDVJGBD' }, |
| 98 | + { name: 'Ruby (Programming Language)', emsiId: 'ESD07FEE22E7EC094EB8' }, |
| 99 | + { name: 'Java (Programming Language)', emsiId: 'KS120076FGP5WGWYMP0F' }], |
| 100 | + [{ name: 'React Native', emsiId: 'KSPSGF5MXB6568UIQ4BK' }, |
| 101 | + { name: 'User Experience (UX)', emsiId: 'KS441PL6JPXW200W0GRQ' }], |
| 102 | + ] |
| 103 | + |
| 104 | + const controlStyle: CSSProperties = { |
| 105 | + borderColor: 'black', |
| 106 | + paddingTop: '10px', |
| 107 | + paddingBottom: '10px', |
| 108 | + } |
| 109 | + |
| 110 | + const placeholderStyle: CSSProperties = { |
| 111 | + height: '36px', |
| 112 | + paddingTop: '4px', |
| 113 | + color: '#2A2A2A', |
| 114 | + fontSize: '16', |
| 115 | + fontFamily: 'Roboto', |
| 116 | + fontWeight: 400, |
| 117 | + } |
| 118 | + |
| 119 | + |
| 120 | + const multiValueStyle: CSSProperties = { |
| 121 | + backgroundColor: 'white', |
| 122 | + border: '1px solid #d4d4d4', |
| 123 | + color: '#333', |
| 124 | + borderRadius: '24px', |
| 125 | + height: '32px', |
| 126 | + fontFamily: 'Roboto', |
| 127 | + fontWeight: '400', |
| 128 | + fontSize: '14', |
| 129 | + paddingRight: '8px', |
| 130 | + paddingLeft: '8px', |
| 131 | + marginRight: '10px', |
| 132 | + } |
| 133 | + |
| 134 | + const multiValueRemoveStyle: CSSProperties = { |
| 135 | + width: '12px', |
| 136 | + height: '12px', |
| 137 | + backgroundColor: '#d9d9d9', |
| 138 | + color: '#333', |
| 139 | + marginTop: 'auto', |
| 140 | + marginBottom: 'auto', |
| 141 | + marginRight: '5px', |
| 142 | + marginLeft: '5px', |
| 143 | + borderRadius: '11px', |
| 144 | + border: '1px solid #d4d4d4', |
| 145 | + fontSize: '12', |
| 146 | + padding: '0px', |
| 147 | + } |
| 148 | + |
| 149 | + const hiddenStyle: CSSProperties = { |
| 150 | + display: 'none', |
| 151 | + } |
| 152 | + |
| 153 | + const selectStyle: StylesConfig<Skill> = { |
| 154 | + control: (provided, state) => { |
| 155 | + return { |
| 156 | + ...provided, |
| 157 | + ...controlStyle, |
| 158 | + } |
| 159 | + }, |
| 160 | + multiValue: (provided, state) => { |
| 161 | + return { |
| 162 | + ...provided, |
| 163 | + ...multiValueStyle, |
| 164 | + } |
| 165 | + }, |
| 166 | + multiValueRemove: (provided, state) => { |
| 167 | + return { |
| 168 | + ...provided, |
| 169 | + ...multiValueRemoveStyle, |
| 170 | + } |
| 171 | + }, |
| 172 | + clearIndicator: (provided, state) => { |
| 173 | + return { |
| 174 | + ...provided, |
| 175 | + ...hiddenStyle, |
| 176 | + } |
| 177 | + }, |
| 178 | + dropdownIndicator: (provided, state) => { |
| 179 | + return { |
| 180 | + ...provided, |
| 181 | + ...hiddenStyle, |
| 182 | + } |
| 183 | + }, |
| 184 | + indicatorSeparator: (provided, state) => { |
| 185 | + return { |
| 186 | + ...provided, |
| 187 | + ...hiddenStyle, |
| 188 | + } |
| 189 | + }, |
| 190 | + placeholder: (provided, state) => { |
| 191 | + return { |
| 192 | + ...provided, |
| 193 | + ...placeholderStyle, |
| 194 | + } |
| 195 | + }, |
| 196 | + } |
19 | 197 | return ( |
20 | 198 | <ContentLayout |
21 | 199 | contentClass={styles.contentLayout} |
22 | 200 | outerClass={styles['contentLayout-outer']} |
23 | 201 | innerClass={styles['contentLayout-inner']} |
24 | 202 | > |
25 | | - <div className={styles.header}> |
26 | | - <h2>Talent search</h2> |
| 203 | + <div className={styles.searchHeader}> |
| 204 | + <span className={styles.searchHeaderText}>Looking for a technology expert?</span> |
| 205 | + </div> |
| 206 | + <div className={styles.subHeader}> |
| 207 | + <span className={styles.subHeaderText}> |
| 208 | + Search thousands of skills to match with our global experts. |
| 209 | + </span> |
27 | 210 | </div> |
28 | | - <div className={styles.options}> |
29 | | - <h4>Select Skills:</h4> |
| 211 | + <div className={styles.searchOptions}> |
| 212 | + <span className={styles.searchPrompt}>Search by skills</span> |
30 | 213 | <AsyncSelect |
31 | 214 | isMulti |
32 | 215 | cacheOptions |
33 | 216 | autoFocus |
34 | 217 | defaultOptions |
35 | | - placeholder='Start typing to autocomplete available EMSI skills' |
| 218 | + placeholder='Enter skills you are searching for...' |
36 | 219 | loadOptions={MatcherService.autoCompleteSkills} |
37 | 220 | name='skills' |
38 | | - className='basic-multi-select' |
39 | | - classNamePrefix='select' |
| 221 | + styles={selectStyle} |
| 222 | + className={styles.searchSelect} |
40 | 223 | getOptionLabel={(skill: Skill) => skill.name} |
41 | 224 | getOptionValue={(skill: Skill) => skill.emsiId} |
42 | | - onChange={(option: readonly Skill[]) => { |
43 | | - setSkillsFilter(option) |
44 | | - }} |
| 225 | + components={{ Control }} |
| 226 | + openMenuOnClick={false} |
| 227 | + value={skillsFilter} |
| 228 | + onChange={(newValue: MultiValue<Skill> | SingleValue<Skill>, actionMeta: ActionMeta<Skill>) => |
| 229 | + onChange(newValue, actionMeta)} |
45 | 230 | /> |
46 | 231 | </div> |
| 232 | + <div className={styles.popularSkillsContainer}> |
| 233 | + <span className={styles.popularSkillsTitle}>Popular Skills</span> |
47 | 234 |
|
48 | | - <h2>Search Results</h2> |
49 | | - <hr /> |
50 | | - <SkillSearchResults |
51 | | - skillsFilter={skillsFilter} |
52 | | - /> |
53 | | - |
| 235 | + {popularSkills.map(row => |
| 236 | + <div className={styles.pillRow}> |
| 237 | + {row.map(skill => |
| 238 | + <SkillPill skill={skill} |
| 239 | + selected={ filteringSkill(skill) } |
| 240 | + onClick={toggleSkill} |
| 241 | + /> )} |
| 242 | + </div> |
| 243 | + )} |
| 244 | + </div> |
54 | 245 | </ContentLayout> |
55 | 246 | ) |
56 | 247 | } |
|
0 commit comments