-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
#386 add tags component
- Loading branch information
Showing
6 changed files
with
2,034 additions
and
1,705 deletions.
There are no files selected for viewing
39 changes: 39 additions & 0 deletions
39
components/reusable-components/add-skill/AddSkill.module.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
.addSkillWrapper { | ||
position: relative; | ||
} | ||
|
||
.addButton { | ||
padding: 8px 16px; | ||
background: #212f42; | ||
color: #aad6ff; | ||
border: 3px solid #aad6ff; | ||
border-radius: 20px; | ||
cursor: pointer; | ||
font-weight: 700; | ||
font-size: 18px; | ||
margin-top: 20px; | ||
} | ||
|
||
.selectedTags { | ||
display: flex; | ||
flex-wrap: wrap; | ||
margin-top: 16px; | ||
|
||
.selectedTag { | ||
background: white; | ||
color: #212f42; | ||
border: 1px solid #212f42; | ||
padding: 4px 8px; | ||
border-radius: 10px; | ||
margin: 4px; | ||
display: flex; | ||
align-items: center; | ||
gap: 3px; | ||
|
||
.removeTag { | ||
cursor: pointer; | ||
background-color: transparent; | ||
border: none; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
'use client'; | ||
|
||
import { useState } from 'react'; | ||
import styles from './AddSkill.module.scss'; | ||
import TagsModal from '../tags-modal/TagsModal'; | ||
|
||
const existingTags = [ | ||
'JavaScript', | ||
'React', | ||
'Next.js', | ||
'CSS', | ||
'HTML', | ||
'Node.js', | ||
'Java', | ||
'C#', | ||
'PHP', | ||
]; | ||
|
||
const AddSkill = () => { | ||
const [showModal, setShowModal] = useState(false); | ||
const [selectedTags, setSelectedTags] = useState<string[]>([]); | ||
const openModal = () => setShowModal(true); | ||
const closeModal = () => setShowModal(false); | ||
|
||
return ( | ||
<div className={styles.addSkillWrapper}> | ||
<button type="button" onClick={openModal} className={styles.addButton}> | ||
+ Add Skill | ||
</button> | ||
{showModal && ( | ||
<TagsModal | ||
existingTags={existingTags} | ||
selectedTags={selectedTags} | ||
setSelectedTags={setSelectedTags} | ||
onClose={closeModal} | ||
isOpen | ||
/> | ||
)} | ||
<div className={styles.selectedTags}> | ||
{selectedTags.map((tag) => ( | ||
<div key={tag} className={styles.selectedTag}> | ||
<button | ||
aria-label="Remove" | ||
className={styles.removeTag} | ||
type="button" | ||
onClick={() => setSelectedTags(selectedTags.filter((item) => item !== tag))} | ||
> | ||
<i className="bi bi-x" /> | ||
</button>{' '} | ||
{tag} | ||
</div> | ||
))} | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
export default AddSkill; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
56 changes: 56 additions & 0 deletions
56
components/reusable-components/tags-modal/TagsModal.module.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
.modalContent { | ||
width: 100%; | ||
position: relative; | ||
} | ||
|
||
.searchInput { | ||
width: 100%; | ||
padding: 8px; | ||
border: 1px solid #ccc; | ||
border-radius: 4px; | ||
} | ||
|
||
.dropdown { | ||
position: absolute; | ||
width: 100%; | ||
max-height: 200px; | ||
overflow-y: auto; | ||
background: white; | ||
color: #121d36; | ||
border: 1px solid #ccc; | ||
border-radius: 4px; | ||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); | ||
z-index: 1000; | ||
|
||
.tagItem { | ||
padding: 8px; | ||
cursor: pointer; | ||
|
||
&:hover { | ||
background: #121d36cb; | ||
color: rgb(255, 255, 255); | ||
} | ||
} | ||
} | ||
|
||
.selectedTags { | ||
display: flex; | ||
flex-wrap: wrap; | ||
margin-top: 8px; | ||
|
||
.tag { | ||
background: transparent; | ||
color: #121d36; | ||
padding: 4px 8px; | ||
border: 1px solid #121d36; | ||
border-radius: 10px; | ||
margin: 4px; | ||
display: flex; | ||
align-items: center; | ||
gap: 3px; | ||
|
||
.removeTag { | ||
cursor: pointer; | ||
} | ||
} | ||
} |
118 changes: 118 additions & 0 deletions
118
components/reusable-components/tags-modal/TagsModal.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
/* eslint-disable jsx-a11y/no-static-element-interactions */ | ||
/* eslint-disable jsx-a11y/click-events-have-key-events */ | ||
|
||
'use client'; | ||
|
||
import React, { ChangeEvent, useEffect, useState } from 'react'; | ||
import styles from './TagsModal.module.scss'; | ||
import ReusableModal from '../reusable-modal/ReusableModal'; | ||
|
||
interface TagSearchProps { | ||
existingTags: string[]; | ||
selectedTags: string[]; | ||
setSelectedTags: (items: string[]) => void; | ||
onClose: () => void; | ||
isOpen: boolean; | ||
} | ||
|
||
const TagsModal = ({ | ||
existingTags, | ||
selectedTags, | ||
setSelectedTags, | ||
onClose, | ||
isOpen, | ||
}: TagSearchProps) => { | ||
const [selectedSkills, setSelectedSkills] = useState<string[]>(selectedTags); | ||
const [searchTag, setSearchTag] = useState<string>(''); | ||
const [filteredTags, setFilteredTags] = useState<string[]>([]); | ||
|
||
useEffect(() => { | ||
if (isOpen) { | ||
setSelectedSkills(selectedTags); | ||
} | ||
}, [isOpen, selectedTags]); | ||
|
||
const handleSearchChange = (e: ChangeEvent<HTMLInputElement>) => { | ||
const query = e.target.value; | ||
setSearchTag(query); | ||
|
||
if (query) { | ||
const filtered = existingTags.filter((tag) => | ||
tag.toLowerCase().includes(query.toLowerCase()) | ||
); | ||
setFilteredTags(filtered); | ||
} else { | ||
setFilteredTags([]); | ||
} | ||
}; | ||
|
||
const addTag = (tag: string) => { | ||
if (!selectedSkills.includes(tag)) { | ||
setSelectedSkills([...selectedSkills, tag]); | ||
} | ||
setSearchTag(''); | ||
setFilteredTags([]); | ||
}; | ||
|
||
const removeTag = (tag: string) => { | ||
setSelectedSkills(selectedSkills.filter((item) => item !== tag)); | ||
}; | ||
|
||
const handleSave = () => { | ||
setSelectedTags(selectedSkills); | ||
onClose(); | ||
}; | ||
|
||
const handleCancel = () => { | ||
onClose(); | ||
}; | ||
|
||
return ( | ||
<ReusableModal | ||
title="Add Skill" | ||
isOpen={isOpen} | ||
onClose={handleCancel} | ||
onPrimaryButtonClick={handleSave} | ||
onSecondaryButtonClick={handleCancel} | ||
> | ||
<div className={styles.modalContent}> | ||
<input | ||
id="searchTagInput" | ||
type="text" | ||
value={searchTag} | ||
onChange={handleSearchChange} | ||
placeholder="Search or add a tag" | ||
className={styles.searchInput} | ||
name="searchTagInput" | ||
/> | ||
{searchTag && ( | ||
<div className={styles.dropdown}> | ||
{filteredTags.length > 0 ? ( | ||
filteredTags.map((tag) => ( | ||
<div key={tag} onClick={() => addTag(tag)} className={styles.tagItem}> | ||
{tag} | ||
</div> | ||
)) | ||
) : ( | ||
<div onClick={() => addTag(searchTag)} className={styles.tagItem}> | ||
Add {searchTag} | ||
</div> | ||
)} | ||
</div> | ||
)} | ||
<div className={styles.selectedTags}> | ||
{selectedSkills.map((skill) => ( | ||
<span key={skill} className={styles.tag}> | ||
<span className={styles.removeTag} onClick={() => removeTag(skill)}> | ||
<i className="bi bi-x" /> | ||
</span> | ||
{skill} | ||
</span> | ||
))} | ||
</div> | ||
</div> | ||
</ReusableModal> | ||
); | ||
}; | ||
|
||
export default TagsModal; |
Oops, something went wrong.