Finalized filters & Tags + UI changes

This commit is contained in:
Daniel 2022-05-30 21:14:34 +04:30
parent a4b925cf0d
commit 43251a85d5
9 changed files with 182 additions and 167 deletions

View File

@ -1,26 +1,20 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import './styles/App.css'; import './styles/App.css';
import List from './componets/List'; import List from './componets/List';
import AddModal from './componets/AddModal'; import AddItem from './componets/AddItem';
import config from './config.json'; import config from './config.json';
import Filters from './componets/Filters';
function App() { function App() {
const [data, setData] = useState([]); const [data, setData] = useState([]);
const [isAdding, setIsAdding] = useState(false); const [isAdding, setIsAdding] = useState(false);
const [isFiltering, setIsFiltering] = useState(false);
const [searchQuery, setSearchQuery] = useState(''); const [searchQuery, setSearchQuery] = useState('');
const [numberOfResults, setNumberOfResults] = useState(0); const [numberOfResults, setNumberOfResults] = useState(0);
const [nameChecked, setNameChecked] = useState(true); const [nameChecked, setNameChecked] = useState(true);
const [descriptionChecked, setDescriptionChecked] = useState(true); const [descriptionChecked, setDescriptionChecked] = useState(true);
const [tagsChecked, setTagsChecked] = useState(true); const [tagsChecked, setTagsChecked] = useState(true);
function toggleFilterBtn(e) {
if(e.target.nextSibling.style.display === 'none') {
e.target.nextSibling.style.display = '';
} else if(e.target.nextSibling.style.display === '') {
e.target.nextSibling.style.display = 'none';
}
}
function handleNameCheckbox() { function handleNameCheckbox() {
setNameChecked(!nameChecked); setNameChecked(!nameChecked);
} }
@ -37,6 +31,10 @@ function App() {
setIsAdding(!isAdding); setIsAdding(!isAdding);
} }
function exitFilter() {
setIsFiltering(!isFiltering);
}
function search(e) { function search(e) {
setSearchQuery(e.target.value); setSearchQuery(e.target.value);
} }
@ -68,6 +66,20 @@ function App() {
setData(Data); setData(Data);
} }
const concatTags = () => {
let tags = [];
for (let i = 0; i < data.length; i++) {
tags = tags.concat(data[i].tag)
}
tags = tags.filter((v, i, a) => a.indexOf(v) === i);
return tags;
}
useEffect(() => { useEffect(() => {
fetchData(); fetchData();
}, []); }, []);
@ -82,17 +94,23 @@ function App() {
<input className="search" type="search" placeholder="&#xf002; Search" onChange={search}/> <input className="search" type="search" placeholder="&#xf002; Search" onChange={search}/>
<button className="add-btn" onClick={() => setIsAdding(true)}>&#xf067;</button> <button className="add-btn" onClick={() => setIsAdding(true)}>&#xf067;</button>
</div> </div>
<p className="results">{numberOfResults > 0 ? numberOfResults + ' Bookmarks' : 'No bookmarks.'}</p> <p className="results">{numberOfResults > 0 ? numberOfResults + ' Bookmarks' : 'No bookmarks.'}</p>
<div className='filter'>
<button onClick={(e) => toggleFilterBtn(e)}>&#xf0b0;</button> <button className='filter-button' onClick={() => setIsFiltering(true)}>&#xf0b0;</button>
<div> {isFiltering ? <Filters
<label><input type="checkbox" checked={nameChecked} onChange={handleNameCheckbox} />Name</label> nameChecked={nameChecked}
<label><input type="checkbox" checked={descriptionChecked} onChange={handleDescriptionCheckbox} />Title/Description</label> handleNameCheckbox={handleNameCheckbox}
<label><input type="checkbox" checked={tagsChecked} onChange={handleTagsCheckbox} />Tags</label> descriptionChecked={descriptionChecked}
</div> handleDescriptionCheckbox={handleDescriptionCheckbox}
</div> tagsChecked={tagsChecked}
handleTagsCheckbox={handleTagsCheckbox}
onExit={exitFilter}
/> : null}
<List data={filteredData} reFetch={fetchData} /> <List data={filteredData} reFetch={fetchData} />
{isAdding ? <AddModal onExit={exitAdding} reFetch={fetchData} /> : null}
{isAdding ? <AddItem onExit={exitAdding} reFetch={fetchData} tags={concatTags} /> : null}
</div> </div>
); );
} }

View File

@ -1,10 +1,10 @@
import { useState } from 'react'; import { useState } from 'react';
import { nanoid } from 'nanoid' import { nanoid } from 'nanoid'
import '../styles/Modal.css'; import '../styles/AddItem.css';
import config from '../config.json'; import config from '../config.json';
import TagSelection from './TagSelection'; import TagSelection from './TagSelection';
const AddModal = ({onExit, reFetch}) => { const AddItem = ({onExit, reFetch, tags}) => {
const [name, setName] = useState(''); const [name, setName] = useState('');
const [link, setLink] = useState(''); const [link, setLink] = useState('');
const [tag, setTag] = useState([]); const [tag, setTag] = useState([]);
@ -17,9 +17,9 @@ const AddModal = ({onExit, reFetch}) => {
setLink(e.target.value); setLink(e.target.value);
} }
function SetTag(e) { function SetTags(value) {
setTag([e.target.value]); setTag(value);
setTag(e.target.value.split(/(\s+)/).filter( e => e.trim().length > 0).map(e => e.toLowerCase())) setTag(value.map(e => e.value.toLowerCase()));
} }
async function submitBookmark() { async function submitBookmark() {
@ -71,29 +71,27 @@ const AddModal = ({onExit, reFetch}) => {
} }
function abort(e) { function abort(e) {
if (e.target.className === "overlay" || e.target.className === "cancel-btn") { if (e.target.className === "add-overlay") {
onExit(); onExit();
} }
} }
return ( return (
<div className='overlay' onClick={abort}> <>
<div className='add-overlay' onClick={abort}></div>
<div className='box'> <div className='box'>
<div className='modal-content'> <div className='AddItem-content'>
<h2>New Bookmark</h2>
<h3>Name:</h3> <h3>Name:</h3>
<input onChange={SetName} className="modal-input" type="search" placeholder="e.g. Example Tutorial"/> <input onChange={SetName} className="AddItem-input" type="search" placeholder="e.g. Example Tutorial"/>
<h3>Link:</h3> <h3>Link:</h3>
<input onChange={SetLink} className="modal-input" type="search" placeholder="e.g. https://example.com/"/> <input onChange={SetLink} className="AddItem-input" type="search" placeholder="e.g. https://example.com/"/>
<h3>Tags:</h3> <h3>Tags:</h3>
<TagSelection /> <TagSelection setTags={SetTags} tags={tags} />
{/* <input onChange={SetTag} className="modal-input" type="search" placeholder="e.g. Tutorials (Seperate with spaces)"/> */}
<button onClick={submitBookmark} className="upload-btn">Upload &#xf093;</button> <button onClick={submitBookmark} className="upload-btn">Upload &#xf093;</button>
<button className="cancel-btn">Cancel</button>
</div>
</div> </div>
</div> </div>
</>
) )
} }
export default AddModal export default AddItem

22
src/componets/Filters.js Normal file
View File

@ -0,0 +1,22 @@
import '../styles/Filters.css'
const Filters = ({nameChecked, handleNameCheckbox, descriptionChecked, handleDescriptionCheckbox, tagsChecked, handleTagsCheckbox, onExit}) => {
function abort(e) {
if (e.target.className === "filter-overlay") {
onExit();
}
}
return (
<>
<div className='filter-overlay' onClick={abort}></div>
<div className='filter'>
<label><input type="checkbox" checked={nameChecked} onChange={handleNameCheckbox} />Name</label>
<label><input type="checkbox" checked={descriptionChecked} onChange={handleDescriptionCheckbox} />Title/Description</label>
<label><input type="checkbox" checked={tagsChecked} onChange={handleTagsCheckbox} />Tags</label>
</div>
</>
)
}
export default Filters

View File

@ -8,12 +8,6 @@ const options = [
]; ];
const customStyles = { const customStyles = {
container: (provided) => ({
...provided,
marginLeft: '20%',
marginRight: '20%',
}),
placeholder: (provided) => ({ placeholder: (provided) => ({
...provided, ...provided,
color: '#a9a9a9', color: '#a9a9a9',
@ -27,11 +21,10 @@ const customStyles = {
menu: (provided) => ({ menu: (provided) => ({
...provided, ...provided,
padding: '5px', padding: '5px',
borderRadius: '10px',
opacity: '90%', opacity: '90%',
color: 'gray', color: 'gray',
background: '#273949', background: '#273949',
boxShadow: 'rgba(0, 0, 0, 0.4) 0px 2px 4px, rgba(0, 0, 0, 0.3) 0px 7px 13px -3px, rgba(0, 0, 0, 0.2) 0px -3px 0px inset', boxShadow: 'rgba(0, 0, 0, 0.16) 0px 3px 6px, rgba(0, 0, 0, 0.23) 0px 3px 6px',
}), }),
input: (provided) => ({ input: (provided) => ({
@ -39,25 +32,25 @@ const customStyles = {
color: 'white', color: 'white',
}), }),
control: (provided) => ({ control: (provided, state) => ({
...provided, ...provided,
background: '#273949', background: '#273949',
border: 'none', border: 'none',
borderRadius: '10px', boxShadow: state.isFocused ? 'rgba(0, 0, 0, 0.16) 0px 3px 6px, rgba(0, 0, 0, 0.23) 0px 3px 6px' : 'rgba(0, 0, 0, 0.4) 0px 2px 4px, rgba(0, 0, 0, 0.3) 0px 7px 13px -3px, rgba(0, 0, 0, 0.2) 0px -3px 0px inset',
boxShadow: 'rgba(0, 0, 0, 0.4) 0px 2px 4px, rgba(0, 0, 0, 0.3) 0px 7px 13px -3px, rgba(0, 0, 0, 0.2) 0px -3px 0px inset',
}), }),
} }
export default function TagSelection() { export default function TagSelection({setTags, tags}) {
const [selectedOption, setSelectedOption] = useState(null); const data = tags().map((e) => {
return { value: e, label: e }
})
return ( return (
<CreatableSelect <CreatableSelect
styles={customStyles} styles={customStyles}
isMulti isMulti
defaultValue={selectedOption} onChange={setTags}
onChange={setSelectedOption} options={data}
options={options}
/> />
); );
} }

65
src/styles/AddItem.css Normal file
View File

@ -0,0 +1,65 @@
.add-overlay {
position: fixed;
top: 0;
left: 0;
bottom: 0;
width: 100vw;
}
.box {
box-shadow: rgba(0, 0, 0, 0.16) 0px 3px 6px, rgba(0, 0, 0, 0.23) 0px 3px 6px;
position: absolute;
z-index: 2;
top: 70px;
right: 20px;
background-color: #1f2c38;
width: 40%;
overflow-x: hidden;
overflow-y: auto;
}
.box h3 {
font-weight: 300;
}
.AddItem-content {
padding: 20px;
}
.AddItem-input {
font-size: 1rem;
padding: 10px;
border: none;
width: 100%;
color: white;
background-color:#273949;
box-shadow: rgba(0, 0, 0, 0.4) 0px 2px 4px, rgba(0, 0, 0, 0.3) 0px 7px 13px -3px, rgba(0, 0, 0, 0.2) 0px -3px 0px inset;
}
.AddItem-input:focus {
box-shadow: rgba(0, 0, 0, 0.16) 0px 3px 6px, rgba(0, 0, 0, 0.23) 0px 3px 6px;
}
.upload-btn {
font-family: 'Font Awesome 5 Free';
font-size: 1.1rem;
padding: 5px;
cursor: pointer;
box-shadow: rgba(0, 0, 0, 0.4) 0px 2px 4px, rgba(0, 0, 0, 0.3) 0px 7px 13px -3px, rgba(0, 0, 0, 0.2) 0px -3px 0px inset;
color: #ffffffb6;
background-color:#273949;
border: none;
margin-top: 20px;
display: block;
margin-left: auto;
margin-right: auto;
transition: background-color 0.1s;
}
.upload-btn:hover {
background-color: rgb(76, 117, 170);
}
.upload-btn:active {
box-shadow: rgba(0, 0, 0, 0.16) 0px 3px 6px, rgba(0, 0, 0, 0.23) 0px 3px 6px;
}

View File

@ -9,7 +9,6 @@
} }
.search { .search {
border-radius: 10px;
margin: 20px 20px 0px 20px; margin: 20px 20px 0px 20px;
padding: 10px; padding: 10px;
font-family: 'Font Awesome 5 Free'; font-family: 'Font Awesome 5 Free';
@ -32,7 +31,7 @@
} }
.add-btn { .add-btn {
border-radius: 10px; border-radius: 100%;
margin: 20px 20px 0px auto; margin: 20px 20px 0px auto;
font-family: 'Font Awesome 5 Free'; font-family: 'Font Awesome 5 Free';
padding: 10px; padding: 10px;
@ -58,12 +57,8 @@ textarea:focus, input:focus{
display: inline-block; display: inline-block;
} }
.filter { .filter-button {
display: inline-block; border-radius: 100%;
}
.filter button {
border-radius: 10px;
font-family: 'Font Awesome 5 Free'; font-family: 'Font Awesome 5 Free';
padding: 10px; padding: 10px;
font-size: 1rem; font-size: 1rem;
@ -75,18 +70,10 @@ textarea:focus, input:focus{
transition: background-color 0.1s; transition: background-color 0.1s;
} }
.filter button:hover { .filter-button:active {
box-shadow: rgba(0, 0, 0, 0.16) 0px 3px 6px, rgba(0, 0, 0, 0.23) 0px 3px 6px;
}
.filter-button:hover {
background-color: rgb(76, 117, 170); background-color: rgb(76, 117, 170);
} }
.display {
visibility: visible;
}
.filter div {
position: absolute;
margin: -30px 0 0 50px;
-moz-user-select: none;
-webkit-user-select: none;
user-select: none;
}

26
src/styles/Filters.css Normal file
View File

@ -0,0 +1,26 @@
.filter {
display: flex;
flex-direction: column;
box-shadow: rgba(0, 0, 0, 0.16) 0px 3px 6px, rgba(0, 0, 0, 0.23) 0px 3px 6px;
z-index: 2;
background-color: #273949;
padding: 10px;
left: 150px;
position: absolute;
margin-top: 4px;
-moz-user-select: none;
-webkit-user-select: none;
user-select: none;
}
.filter label {
margin: 10px;
}
.filter-overlay {
position: fixed;
top: 0;
left: 0;
bottom: 0;
width: 100vw;
}

View File

@ -24,7 +24,6 @@
} }
.list-row { .list-row {
border-radius: 10px;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
@ -77,7 +76,7 @@
width: fit-content; width: fit-content;
height: fit-content; height: fit-content;
margin: 10px; margin: 10px;
border-radius: 10px; border-radius: 100%;
} }
.delete:hover { .delete:hover {

View File

@ -1,93 +0,0 @@
.overlay {
position: fixed;
top: 0;
left: 0;
background-color: rgba(39, 60, 78, 0.781);
width: 100vw;
height: 100vh;
}
.box {
border-radius: 10px;
box-shadow: rgba(0, 0, 0, 0.4) 0px 2px 4px, rgba(0, 0, 0, 0.3) 0px 7px 13px -3px, rgba(0, 0, 0, 0.2) 0px -3px 0px inset;
position: absolute;
top: 20%;
left: 20%;
background-color: #1b2e3f;
width: 60%;
height: 60%;
overflow-x: hidden;
overflow-y: auto;
}
.box h2 {
margin-top: -1px;
margin-bottom: 30px;
text-align: center;
}
.box h3 {text-align: center;}
.modal-content {
padding: 20px;
}
.modal-input {
border-radius: 10px;
font-size: 1.3rem;
padding: 10px;
border: none;
width: 100%;
color: white;
background-color:#273949;
box-shadow: rgba(0, 0, 0, 0.4) 0px 2px 4px, rgba(0, 0, 0, 0.3) 0px 7px 13px -3px, rgba(0, 0, 0, 0.2) 0px -3px 0px inset;
}
.modal-input:focus {
box-shadow: rgba(0, 0, 0, 0.16) 0px 3px 6px, rgba(0, 0, 0, 0.23) 0px 3px 6px;
}
.upload-btn {
border-radius: 10px;
font-family: 'Font Awesome 5 Free';
font-size: 2rem;
padding: 10px;
cursor: pointer;
box-shadow: rgba(0, 0, 0, 0.4) 0px 2px 4px, rgba(0, 0, 0, 0.3) 0px 7px 13px -3px, rgba(0, 0, 0, 0.2) 0px -3px 0px inset;
color: #ffffffb6;
background-color:#273949;
border: none;
margin-top: 50px;
display: block;
margin-left: auto;
margin-right: auto;
transition: background-color 0.1s;
}
.upload-btn:hover {
background-color: rgb(76, 117, 170);
}
.cancel-btn {
border-radius: 10px;
padding: 5px;
cursor: pointer;
box-shadow: rgba(0, 0, 0, 0.4) 0px 2px 4px, rgba(0, 0, 0, 0.3) 0px 7px 13px -3px, rgba(0, 0, 0, 0.2) 0px -3px 0px inset;
color: #ffffffb6;
background-color:#273949;
border: none;
margin-top: 5px;
display: block;
margin-left: auto;
margin-right: auto;
transition: background-color 0.1s;
font-weight: 900;
}
.cancel-btn:hover {
background-color: rgb(255, 123, 123);
}
.upload-btn:active, .cancel-btn:active {
box-shadow: rgba(0, 0, 0, 0.16) 0px 3px 6px, rgba(0, 0, 0, 0.23) 0px 3px 6px;
}