Select
Özelleştirilebilir dropdown select bileşenleri. Tek seçim, çoklu seçim ve arama özellikli varyantlar.
Component Code
Ana Select bileşeninin kaynak kodu. Tüm varyantların temelini oluşturur.
'use client'import React, { useState, useRef, useEffect } from 'react'import { motion, AnimatePresence } from 'framer-motion'import { ChevronDown, Check, X } from 'lucide-react'export function Select({options = [],placeholder = "Select an option...",value = null,onChange = () => {},multiple = false,searchable = false,className = "",disabled = false}) {const [isOpen, setIsOpen] = useState(false)const [searchQuery, setSearchQuery] = useState('')const selectRef = useRef(null)// Close dropdown when clicking outsideuseEffect(() => {const handleClickOutside = (event) => {if (selectRef.current && !selectRef.current.contains(event.target)) {setIsOpen(false)setSearchQuery('')}}document.addEventListener('mousedown', handleClickOutside)return () => document.removeEventListener('mousedown', handleClickOutside)}, [])// Filter options based on search queryconst filteredOptions = searchable? options.filter(option =>option.label.toLowerCase().includes(searchQuery.toLowerCase())): optionsconst handleOptionClick = (option) => {if (multiple) {const isSelected = Array.isArray(value) && value.some(v => v.value === option.value)const newValue = isSelected? value.filter(v => v.value !== option.value): [...(value || []), option]onChange(newValue)} else {onChange(option)setIsOpen(false)setSearchQuery('')}}const handleRemoveItem = (removedOption) => {if (multiple && Array.isArray(value)) {const newValue = value.filter(v => v.value !== removedOption.value)onChange(newValue)}}const isSelected = (option) => {if (multiple) {return Array.isArray(value) && value.some(v => v.value === option.value)}return value && value.value === option.value}const getDisplayContent = () => {if (multiple) {if (!value || value.length === 0) {return <span className="text-neutral-400">{placeholder}</span>}return (<div className="flex flex-wrap gap-1">{value.map((item) => (<spankey={item.value}className="inline-flex items-center gap-1 bg-white/5 text-white text-xs px-1.5 py-0.5 rounded">{item.label}<divonClick={(e) => {e.stopPropagation()handleRemoveItem(item)}}className="hover:bg-white/10 rounded p-0.5 cursor-pointer"><X className="w-2.5 h-2.5" /></div></span>))}</div>)}return (<span className={value ? 'text-white' : 'text-neutral-400'}>{value ? value.label : placeholder}</span>)}return (<div ref={selectRef} className={`relative w-full max-w-md ${className}`}>{/* Trigger */}<buttontype="button"onClick={() => !disabled && setIsOpen(!isOpen)}className={`w-full px-3 py-2 bg-neutral-800 border border-white/10 rounded-mdflex items-center justify-between text-left text-sm min-h-[36px]transition-all duration-150${disabled? 'opacity-50 cursor-not-allowed': 'hover:border-white/20 focus:outline-none focus:border-white/30'}`}><div className="flex-1 min-w-0">{getDisplayContent()}</div><motion.divanimate={{ rotate: isOpen ? 180 : 0 }}transition={{ duration: 0.15 }}className="ml-2 flex-shrink-0"><ChevronDown className="w-4 h-4 text-neutral-400" /></motion.div></button>{/* Dropdown */}<AnimatePresence>{isOpen && (<motion.divinitial={{ opacity: 0, scale: 0.95 }}animate={{ opacity: 1, scale: 1 }}exit={{ opacity: 0, scale: 0.95 }}transition={{ duration: 0.1 }}className="absolute z-50 w-full mt-1 bg-neutral-800 border border-white/10 rounded-md shadow-lg max-h-60 overflow-hidden">{/* Search Input */}{searchable && (<div className="p-2 border-b border-white/10"><inputtype="text"placeholder="Search..."value={searchQuery}onChange={(e) => setSearchQuery(e.target.value)}className="w-full px-3 py-1.5 bg-neutral-800 border border-white/10 rounded text-sm text-white placeholder-neutral-400 focus:outline-none focus:border-white/30 transition-all duration-150"/></div>)}{/* Options */}<div className="max-h-48 overflow-y-auto">{filteredOptions.length === 0 ? (<div className="px-3 py-2 text-neutral-400 text-sm">{searchable && searchQuery ? 'No results found' : 'No options available'}</div>) : (filteredOptions.map((option) => (<buttonkey={option.value}type="button"onClick={() => handleOptionClick(option)}className={`w-full px-3 py-1.5 text-left flex items-center justify-between text-smtransition-colors duration-100${isSelected(option)? 'bg-white/5 text-white': 'text-white/70 hover:bg-white/5 hover:text-white'}`}><span>{option.label}</span>{isSelected(option) && (<Check className="w-3 h-3 text-white" />)}</button>)))}</div></motion.div>)}</AnimatePresence></div>)}
Default
Temel select bileşeni. Kullanıcılar listeden tek bir seçenek seçebilir.
'use client'import { useState } from 'react'import { Select } from './Select'export function DefaultSelect() {const [selectedValue, setSelectedValue] = useState(null)const options = [{ value: 'react', label: 'React' },{ value: 'vue', label: 'Vue.js' },{ value: 'angular', label: 'Angular' },{ value: 'svelte', label: 'Svelte' },{ value: 'nextjs', label: 'Next.js' },]return (<Selectoptions={options}placeholder="Choose a framework..."value={selectedValue}onChange={setSelectedValue}/>)}
Multi Select
Çoklu seçim yapılabilen select bileşeni. Seçilen öğeler chip olarak gösterilir ve X butonuyla kaldırılabilir.
'use client'import { useState } from 'react'import { Select } from './Select'export function MultiSelect() {const [selectedValues, setSelectedValues] = useState([])const options = [{ value: 'javascript', label: 'JavaScript' },{ value: 'typescript', label: 'TypeScript' },{ value: 'python', label: 'Python' },{ value: 'java', label: 'Java' },{ value: 'csharp', label: 'C#' },{ value: 'php', label: 'PHP' },{ value: 'ruby', label: 'Ruby' },{ value: 'go', label: 'Go' },]return (<Selectoptions={options}placeholder="Select programming languages..."value={selectedValues}onChange={setSelectedValues}multiple={true}/>)}
Searchable
Arama özellikli select bileşeni. Kullanıcılar yazarak seçenekleri filtreleyebilir.
'use client'import { useState } from 'react'import { Select } from './Select'export function SearchableSelect() {const [selectedValue, setSelectedValue] = useState(null)const options = [{ value: 'afghanistan', label: 'Afghanistan' },{ value: 'albania', label: 'Albania' },{ value: 'algeria', label: 'Algeria' },{ value: 'argentina', label: 'Argentina' },{ value: 'armenia', label: 'Armenia' },{ value: 'australia', label: 'Australia' },{ value: 'austria', label: 'Austria' },{ value: 'bangladesh', label: 'Bangladesh' },{ value: 'belgium', label: 'Belgium' },{ value: 'brazil', label: 'Brazil' },{ value: 'canada', label: 'Canada' },{ value: 'china', label: 'China' },{ value: 'france', label: 'France' },{ value: 'germany', label: 'Germany' },{ value: 'india', label: 'India' },{ value: 'indonesia', label: 'Indonesia' },{ value: 'italy', label: 'Italy' },{ value: 'japan', label: 'Japan' },{ value: 'mexico', label: 'Mexico' },{ value: 'netherlands', label: 'Netherlands' },{ value: 'russia', label: 'Russia' },{ value: 'spain', label: 'Spain' },{ value: 'turkey', label: 'Turkey' },{ value: 'ukraine', label: 'Ukraine' },{ value: 'united-kingdom', label: 'United Kingdom' },{ value: 'united-states', label: 'United States' },]return (<Selectoptions={options}placeholder="Search for a country..."value={selectedValue}onChange={setSelectedValue}searchable={true}/>)}
