Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions src/components/FilterDropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { useState, useRef, useEffect } from 'react';
import * as Icons from 'lucide-react';

interface FilterDropdownProps {
label: string;
options: string[];
value: string;
onChange: (value: string) => void;
}

export function FilterDropdown({
label,
options,
value,
onChange,
}: FilterDropdownProps) {
const [open, setOpen] = useState(false);
const ref = useRef<HTMLDivElement>(null);

// Close on outside click
useEffect(() => {
function handleClickOutside(e: MouseEvent) {
if (ref.current && !ref.current.contains(e.target as Node)) {
setOpen(false);
}
}
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);

return (
<div ref={ref} className="relative">
{/* Button */}
<button
onClick={() => setOpen(!open)}
className="
inline-flex items-center gap-2
rounded-xl border border-gray-200
bg-white px-4 py-3
text-sm font-medium
shadow-sm transition
hover:bg-gray-50
dark:border-neutral-800 dark:bg-neutral-900 dark:hover:bg-neutral-800
"
>
{label}
<span className="text-gray-500">{value}</span>
<Icons.ChevronDown size={16} />
</button>

{/* Dropdown */}
{open && (
<div className="
absolute right-0 z-20 mt-2 w-56
overflow-hidden rounded-xl
border border-gray-200 bg-white
shadow-lg
dark:border-neutral-800 dark:bg-neutral-900
">
{options.map((option) => {
const isActive = option === value;

return (
<button
key={option}
onClick={() => {
onChange(option);
setOpen(false);
}}
className={`
flex w-full items-center px-4 py-2 text-sm
transition-colors
${
isActive
? 'bg-blue-600 text-white'
: 'hover:bg-gray-100 dark:hover:bg-neutral-800'
}
`}
>
{option}
</button>
);
})}
</div>
)}
</div>
);
}
71 changes: 71 additions & 0 deletions src/components/SearchFilter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import * as Icons from 'lucide-react';
import { FilterDropdown } from './FilterDropdown';

interface SearchAndFiltersProps {
search: string;
onSearchChange: (value: string) => void;
categories: string[];
activeCategory: string;
onCategoryChange: (value: string) => void;
resultCount: number;
}

export function SearchFilter({
search,
onSearchChange,
categories,
activeCategory,
onCategoryChange,
resultCount,
}: SearchAndFiltersProps) {
return (
<div className="mb-10 flex flex-col gap-4 sm:flex-row sm:items-center">
{/* Search */}
<div className="relative flex-1">
<label
htmlFor="profile-search"
className="sr-only"
>
Search platforms and social media handles
</label>

<Icons.Search
size={18}
aria-hidden="true"
className="absolute left-4 top-1/2 -translate-y-1/2 text-gray-400"
/>

<input
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this element is missing an accessible name or label. That makes it hard for people using screen readers or voice control to use the control.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👨‍💻

id="profile-search"
type="text"
role="searchbox"
value={search}
onChange={(e) => onSearchChange(e.target.value)}
placeholder="Search platforms, handles..."
className="
w-full rounded-xl bg-gray-100 px-11 py-3
text-sm outline-none
focus:ring-2 focus:ring-blue-500/40
dark:bg-neutral-800 dark:text-white
"
/>
</div>

{/* Dropdown Filter */}
<FilterDropdown
label="Category:"
options={categories}
value={activeCategory}
onChange={onCategoryChange}
/>

{/* Result Count */}
<span
aria-live="polite"
className="text-xs text-gray-500 dark:text-neutral-500"
>
{resultCount} results
</span>
</div>
);
}
102 changes: 0 additions & 102 deletions src/css/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -105,108 +105,6 @@ mark {
border-radius: 0.2em;
}

/*
.footer {
background-image: linear-gradient(#272329aa, #2d2c2d50, transparent), url("../../static/img/use-svg-as-background-image-particle-strokes.svg");
color: #f2f2f2;
padding: 20px;
}

.footer .container {
justify-content: space-around
}

.footer_info--container {
width: 400px;
}

.footer_info--container img {
width: 150px;
border-radius: 8px;
}

@media screen and (max-width: 420px) {
.footer_info--container {
width: 100%;
}
}

.hero__title {
animation: fadeIn 2s;
animation-fill-mode: forwards;
}

@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}

.hero__subtitle {
transition: transform 0.5s;
animation: fadeIn 4s;
animation-fill-mode: forwards;
}

.hero__subtitle:hover {
transform: scale(1.1);
}

.typing_text {
animation: typing 3s steps(40, end);
overflow: hidden;
white-space: nowrap;
border-right: 2px solid;
margin: auto auto 20px auto;
font-size: 1.5em;
animation-fill-mode: forwards;
}

@keyframes typing {
from {
width: 0;
}
to {
width: 300px;
}
}

.footer .container a {
text-decoration: none;
}

.footer__copyright a {
color: #F0DB4F;
text-decoration: none;
position: relative;
transition: color 0.3s;
}

.footer__copyright a::after {
content: '';
position: absolute;
width: 100%;
height: 2px;
bottom: 0;
left: 0;
background-color: #F0DB4F;
visibility: hidden;
transform: scaleX(0);
transition: all 0.3s ease-in-out 0s;
}

.footer__copyright a:hover::after {
visibility: visible;
transform: scaleX(1);
}

.footer__bottom .margin-bottom--sm img {
border-radius: 8px;
} */

.solution-author-wrapper {
text-align: center;
border-top: 1px solid #4e8da0db;
Expand Down
Loading