Skip to content

Commit

Permalink
Merge pull request #426 from learnhubmk/#386-Add-tags-component
Browse files Browse the repository at this point in the history
#386 add tags component
  • Loading branch information
edichoska authored Aug 18, 2024
2 parents 8c1cacd + e6ca303 commit ac40269
Show file tree
Hide file tree
Showing 6 changed files with 2,034 additions and 1,705 deletions.
39 changes: 39 additions & 0 deletions components/reusable-components/add-skill/AddSkill.module.scss
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;
}
}
}
58 changes: 58 additions & 0 deletions components/reusable-components/add-skill/AddSkills.tsx
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;
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import Button from '../button/Button';
interface ReusableModalProps {
title: string;
description?: string;
// eslint-disable-next-line no-undef
children?: JSX.Element;
isOpen: boolean;
onClose?: () => void;
secondaryButtonLabel?: string;
Expand All @@ -18,6 +20,7 @@ interface ReusableModalProps {
const ReusableModal = ({
title,
description,
children,
isOpen,
secondaryButtonLabel = 'Cancel',
secondaryButtonClass = 'secondaryButton',
Expand Down Expand Up @@ -80,6 +83,7 @@ const ReusableModal = ({
<div className={style.modalContent}>
<h2>{title}</h2>
{description && <p>{description}</p>}
{children}
</div>
<div className={style.modalButtons}>
<Button
Expand Down
56 changes: 56 additions & 0 deletions components/reusable-components/tags-modal/TagsModal.module.scss
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 components/reusable-components/tags-modal/TagsModal.tsx
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;
Loading

0 comments on commit ac40269

Please sign in to comment.