Initalize the project with front-end

This commit is contained in:
Yusuf İpek
2025-11-10 17:40:17 +03:00
commit bb854ea4d0
28 changed files with 6568 additions and 0 deletions
+230
View File
@@ -0,0 +1,230 @@
"use client"
import { useState, useMemo } from 'react'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Checkbox } from '@/components/ui/checkbox'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { mockPackages, categories, licenses } from '@/data/mockData'
import { Package, FilterOptions, Platform } from '@/types'
import { Search, Package as PackageIcon, Terminal, Monitor } from 'lucide-react'
interface PackageBrowserProps {
selectedPlatform: Platform | null
selectedPackages: Package[]
onPackageToggle: (pkg: Package) => void
onFiltersChange: (filters: FilterOptions) => void
}
export function PackageBrowser({
selectedPlatform,
selectedPackages,
onPackageToggle,
onFiltersChange
}: PackageBrowserProps) {
const [searchQuery, setSearchQuery] = useState('')
const [filters, setFilters] = useState<FilterOptions>({
platforms: [],
categories: [],
licenses: [],
types: [],
repositories: [],
searchQuery: ''
})
const filteredPackages = useMemo(() => {
return mockPackages.filter(pkg => {
// Platform filter
if (selectedPlatform && pkg.platform !== selectedPlatform.id) {
return false
}
// Search filter
if (searchQuery && !pkg.name.toLowerCase().includes(searchQuery.toLowerCase()) &&
!pkg.description.toLowerCase().includes(searchQuery.toLowerCase())) {
return false
}
// Category filter
if (filters.categories.length > 0 && !filters.categories.includes(pkg.category)) {
return false
}
// License filter
if (filters.licenses.length > 0 && !filters.licenses.includes(pkg.license)) {
return false
}
// Type filter
if (filters.types.length > 0 && !filters.types.includes(pkg.type)) {
return false
}
// Repository filter
if (filters.repositories.length > 0 && !filters.repositories.includes(pkg.repository)) {
return false
}
return true
})
}, [selectedPlatform, searchQuery, filters])
const handleFilterChange = (key: keyof FilterOptions, value: any) => {
const newFilters = { ...filters, [key]: value }
setFilters(newFilters)
onFiltersChange(newFilters)
}
const isPackageSelected = (pkg: Package) => {
return selectedPackages.some(selected => selected.id === pkg.id)
}
if (!selectedPlatform) {
return (
<Card className="w-full">
<CardContent className="flex items-center justify-center h-64">
<p className="text-muted-foreground">Please select a platform first</p>
</CardContent>
</Card>
)
}
return (
<div className="space-y-6">
{/* Filters */}
<Card>
<CardHeader>
<CardTitle>Filters</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
{/* Search */}
<div className="relative">
<Search className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
<input
type="text"
placeholder="Search packages..."
className="w-full pl-10 pr-4 py-2 border border-input rounded-md bg-background"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
</div>
{/* Category Filter */}
<Select onValueChange={(value) =>
handleFilterChange('categories', value ? [value] : [])
}>
<SelectTrigger>
<SelectValue placeholder="Category" />
</SelectTrigger>
<SelectContent>
{categories.map(category => (
<SelectItem key={category} value={category}>
{category}
</SelectItem>
))}
</SelectContent>
</Select>
{/* Type Filter */}
<Select onValueChange={(value) =>
handleFilterChange('types', value ? [value as 'gui' | 'cli'] : [])
}>
<SelectTrigger>
<SelectValue placeholder="Type" />
</SelectTrigger>
<SelectContent>
<SelectItem value="gui">GUI Applications</SelectItem>
<SelectItem value="cli">CLI Tools</SelectItem>
</SelectContent>
</Select>
{/* Repository Filter */}
<Select onValueChange={(value) =>
handleFilterChange('repositories', value ? [value as 'official' | 'third-party'] : [])
}>
<SelectTrigger>
<SelectValue placeholder="Repository" />
</SelectTrigger>
<SelectContent>
<SelectItem value="official">Official Only</SelectItem>
<SelectItem value="third-party">Third Party</SelectItem>
</SelectContent>
</Select>
</div>
</CardContent>
</Card>
{/* Package List */}
<Card>
<CardHeader>
<CardTitle>Available Packages ({filteredPackages.length})</CardTitle>
<CardDescription>
Select packages to include in your installation script
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
{filteredPackages.map((pkg) => (
<div
key={pkg.id}
className={`p-4 border rounded-lg transition-colors ${
isPackageSelected(pkg)
? 'border-primary bg-primary/5'
: 'border-border hover:bg-secondary/50'
}`}
>
<div className="flex items-start space-x-3">
<Checkbox
checked={isPackageSelected(pkg)}
onCheckedChange={() => onPackageToggle(pkg)}
/>
<div className="flex-1 min-w-0">
<div className="flex items-center space-x-2 mb-1">
<h3 className="font-semibold truncate">{pkg.name}</h3>
{pkg.type === 'gui' ? (
<Monitor className="h-4 w-4 text-muted-foreground" />
) : (
<Terminal className="h-4 w-4 text-muted-foreground" />
)}
{pkg.repository === 'official' && (
<span className="text-xs bg-green-100 text-green-800 px-2 py-1 rounded">
Official
</span>
)}
</div>
<p className="text-sm text-muted-foreground mb-2">{pkg.description}</p>
<div className="flex items-center space-x-4 text-xs text-muted-foreground">
<span>Version: {pkg.version}</span>
<span>License: {pkg.license}</span>
<span>Category: {pkg.category}</span>
{pkg.popularity && (
<span>Popularity: {pkg.popularity}%</span>
)}
</div>
<div className="flex flex-wrap gap-1 mt-2">
{pkg.tags.map(tag => (
<span
key={tag}
className="text-xs bg-secondary text-secondary-foreground px-2 py-1 rounded"
>
{tag}
</span>
))}
</div>
</div>
</div>
</div>
))}
{filteredPackages.length === 0 && (
<div className="text-center py-8">
<PackageIcon className="h-12 w-12 text-muted-foreground mx-auto mb-4" />
<p className="text-muted-foreground">No packages found matching your criteria</p>
</div>
)}
</div>
</CardContent>
</Card>
</div>
)
}
+50
View File
@@ -0,0 +1,50 @@
"use client"
import { useState } from 'react'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { platforms } from '@/data/mockData'
import { Platform } from '@/types'
interface PlatformSelectorProps {
selectedPlatform: Platform | null
onPlatformSelect: (platform: Platform) => void
}
export function PlatformSelector({ selectedPlatform, onPlatformSelect }: PlatformSelectorProps) {
return (
<Card className="w-full">
<CardHeader>
<CardTitle>Select Your Platform</CardTitle>
<CardDescription>
Choose your operating system and package manager to browse available packages
</CardDescription>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{platforms.map((platform) => (
<Button
key={platform.id}
variant={selectedPlatform?.id === platform.id ? "default" : "outline"}
className="h-auto p-4 flex flex-col items-center space-y-2"
onClick={() => onPlatformSelect(platform)}
>
<div className="text-2xl">{platform.icon}</div>
<div className="text-center">
<div className="font-semibold">{platform.name}</div>
<div className="text-sm text-muted-foreground">{platform.packageManager}</div>
</div>
</Button>
))}
</div>
{selectedPlatform && (
<div className="mt-4 p-4 bg-secondary rounded-md">
<p className="text-sm">
<strong>Selected:</strong> {selectedPlatform.name} ({selectedPlatform.packageManager})
</p>
</div>
)}
</CardContent>
</Card>
)
}
+111
View File
@@ -0,0 +1,111 @@
"use client"
import { useState } from 'react'
import { PlatformSelector } from '@/components/PlatformSelector'
import { PackageBrowser } from '@/components/PackageBrowser'
import { SelectionManager } from '@/components/SelectionManager'
import { ScriptPreview } from '@/components/ScriptPreview'
import { generateScript } from '@/lib/scriptGenerator'
import { Platform, Package, SelectedPackage, FilterOptions, GeneratedScript } from '@/types'
export function RepoHubApp() {
const [selectedPlatform, setSelectedPlatform] = useState<Platform | null>(null)
const [selectedPackages, setSelectedPackages] = useState<SelectedPackage[]>([])
const [generatedScript, setGeneratedScript] = useState<GeneratedScript | null>(null)
const handlePlatformSelect = (platform: Platform) => {
setSelectedPlatform(platform)
// Clear selections when platform changes
setSelectedPackages([])
setGeneratedScript(null)
}
const handlePackageToggle = (pkg: Package) => {
setSelectedPackages(prev => {
const exists = prev.some(p => p.id === pkg.id)
if (exists) {
return prev.filter(p => p.id !== pkg.id)
} else {
return [...prev, { ...pkg, selectedAt: new Date().toISOString() }]
}
})
}
const handlePackageRemove = (pkgId: string) => {
setSelectedPackages(prev => prev.filter(p => p.id !== pkgId))
}
const handleClearAll = () => {
setSelectedPackages([])
}
const handleGenerateScript = () => {
if (selectedPlatform && selectedPackages.length > 0) {
const script = generateScript(selectedPackages, selectedPlatform)
setGeneratedScript(script)
}
}
const handleFiltersChange = (filters: FilterOptions) => {
// Filters are handled internally by PackageBrowser
// This could be expanded to handle URL params or state management
}
const handleCloseScriptPreview = () => {
setGeneratedScript(null)
}
return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
<div className="container mx-auto px-4 py-8">
{/* Header */}
<div className="text-center mb-8">
<h1 className="text-4xl font-bold text-gray-900 mb-4">
RepoHub
</h1>
<p className="text-xl text-gray-600 mb-2">
Cross-Platform Package Manager
</p>
<p className="text-lg text-gray-500 max-w-2xl mx-auto">
Simplify software installation across Linux, Windows, and macOS with official repositories
</p>
</div>
{/* Main Content */}
<div className="space-y-8">
{/* Platform Selector */}
<PlatformSelector
selectedPlatform={selectedPlatform}
onPlatformSelect={handlePlatformSelect}
/>
{/* Package Browser */}
<PackageBrowser
selectedPlatform={selectedPlatform}
selectedPackages={selectedPackages}
onPackageToggle={handlePackageToggle}
onFiltersChange={handleFiltersChange}
/>
{/* Selection Manager */}
<SelectionManager
selectedPackages={selectedPackages}
onPackageRemove={handlePackageRemove}
onClearAll={handleClearAll}
onGenerateScript={handleGenerateScript}
/>
</div>
{/* Script Preview Modal */}
{generatedScript && (
<ScriptPreview
generatedScript={generatedScript}
selectedPackages={selectedPackages}
selectedPlatform={selectedPlatform}
onClose={handleCloseScriptPreview}
/>
)}
</div>
</div>
)
}
+174
View File
@@ -0,0 +1,174 @@
"use client"
import { useState } from 'react'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { GeneratedScript, SelectedPackage, Platform } from '@/types'
import { Download, Copy, Check, Terminal, Shield } from 'lucide-react'
interface ScriptPreviewProps {
generatedScript: GeneratedScript | null
selectedPackages: SelectedPackage[]
selectedPlatform: Platform | null
onClose: () => void
}
export function ScriptPreview({
generatedScript,
selectedPackages,
selectedPlatform,
onClose
}: ScriptPreviewProps) {
const [copied, setCopied] = useState(false)
if (!generatedScript || !selectedPlatform) {
return null
}
const handleCopy = async () => {
try {
await navigator.clipboard.writeText(generatedScript.script)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
} catch (err) {
console.error('Failed to copy script:', err)
}
}
const handleDownload = () => {
const blob = new Blob([generatedScript.script], { type: 'text/plain' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = `install-packages-${selectedPlatform.id}.sh`
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
URL.revokeObjectURL(url)
}
const getScriptLanguage = () => {
switch (selectedPlatform.id) {
case 'windows':
return 'powershell'
default:
return 'bash'
}
}
const getScriptExtension = () => {
switch (selectedPlatform.id) {
case 'windows':
return '.ps1'
default:
return '.sh'
}
}
return (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center p-4 z-50">
<Card className="w-full max-w-4xl max-h-[90vh] overflow-hidden">
<CardHeader>
<div className="flex items-center justify-between">
<div>
<CardTitle className="flex items-center space-x-2">
<Terminal className="h-5 w-5" />
<span>Installation Script</span>
</CardTitle>
<CardDescription>
Idempotent script for {selectedPlatform.name} using {selectedPlatform.packageManager}
</CardDescription>
</div>
<Button variant="outline" onClick={onClose}>
Close
</Button>
</div>
</CardHeader>
<CardContent className="space-y-4">
{/* Script Info */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="p-3 bg-secondary rounded-lg">
<div className="flex items-center space-x-2 mb-1">
<Shield className="h-4 w-4 text-green-600" />
<span className="font-medium text-sm">Official Repositories</span>
</div>
<p className="text-xs text-muted-foreground">
All packages from trusted sources
</p>
</div>
<div className="p-3 bg-secondary rounded-lg">
<div className="flex items-center space-x-2 mb-1">
<Terminal className="h-4 w-4 text-blue-600" />
<span className="font-medium text-sm">Idempotent</span>
</div>
<p className="text-xs text-muted-foreground">
Safe to run multiple times
</p>
</div>
<div className="p-3 bg-secondary rounded-lg">
<div className="flex items-center space-x-2 mb-1">
<Download className="h-4 w-4 text-purple-600" />
<span className="font-medium text-sm">{selectedPackages.length} Packages</span>
</div>
<p className="text-xs text-muted-foreground">
Ready for installation
</p>
</div>
</div>
{/* Package List */}
<div>
<h4 className="font-medium mb-2">Included Packages:</h4>
<div className="flex flex-wrap gap-2">
{selectedPackages.map((pkg) => (
<span
key={pkg.id}
className="text-xs bg-primary/10 text-primary px-2 py-1 rounded"
>
{pkg.name} ({pkg.version})
</span>
))}
</div>
</div>
{/* Script Content */}
<div>
<div className="flex items-center justify-between mb-2">
<h4 className="font-medium">Script Content:</h4>
<div className="flex space-x-2">
<Button variant="outline" size="sm" onClick={handleCopy}>
{copied ? (
<Check className="h-4 w-4 mr-2" />
) : (
<Copy className="h-4 w-4 mr-2" />
)}
{copied ? 'Copied!' : 'Copy'}
</Button>
<Button size="sm" onClick={handleDownload}>
<Download className="h-4 w-4 mr-2" />
Download {getScriptExtension()}
</Button>
</div>
</div>
<div className="relative">
<pre className="bg-muted p-4 rounded-lg text-sm overflow-x-auto max-h-64 overflow-y-auto">
<code>{generatedScript.script}</code>
</pre>
</div>
</div>
{/* Usage Instructions */}
<div className="p-4 bg-blue-50 border border-blue-200 rounded-lg">
<h4 className="font-medium text-blue-900 mb-2">How to use:</h4>
<ol className="text-sm text-blue-800 space-y-1 list-decimal list-inside">
<li>Download the script file to your target machine</li>
<li>Make it executable (for Linux/macOS): <code className="bg-blue-100 px-1 rounded">chmod +x install-packages-{selectedPlatform.id}{getScriptExtension()}</code></li>
<li>Run the script with appropriate permissions</li>
<li>The script will automatically handle repository setup and package installation</li>
</ol>
</div>
</CardContent>
</Card>
</div>
)
}
+100
View File
@@ -0,0 +1,100 @@
"use client"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Package, SelectedPackage } from '@/types'
import { X, PackageOpen, Download } from 'lucide-react'
interface SelectionManagerProps {
selectedPackages: SelectedPackage[]
onPackageRemove: (pkgId: string) => void
onClearAll: () => void
onGenerateScript: () => void
}
export function SelectionManager({
selectedPackages,
onPackageRemove,
onClearAll,
onGenerateScript
}: SelectionManagerProps) {
if (selectedPackages.length === 0) {
return (
<Card className="w-full">
<CardContent className="flex items-center justify-center h-32">
<div className="text-center">
<PackageOpen className="h-8 w-8 text-muted-foreground mx-auto mb-2" />
<p className="text-muted-foreground">No packages selected</p>
</div>
</CardContent>
</Card>
)
}
return (
<Card className="w-full">
<CardHeader>
<div className="flex items-center justify-between">
<div>
<CardTitle>Selected Packages ({selectedPackages.length})</CardTitle>
<CardDescription>
These packages will be included in your installation script
</CardDescription>
</div>
<div className="flex space-x-2">
<Button variant="outline" size="sm" onClick={onClearAll}>
Clear All
</Button>
<Button onClick={onGenerateScript}>
<Download className="h-4 w-4 mr-2" />
Generate Script
</Button>
</div>
</div>
</CardHeader>
<CardContent>
<div className="space-y-2 max-h-64 overflow-y-auto">
{selectedPackages.map((pkg) => (
<div
key={pkg.id}
className="flex items-center justify-between p-3 bg-secondary rounded-lg"
>
<div className="flex-1 min-w-0">
<div className="flex items-center space-x-2">
<h4 className="font-medium truncate">{pkg.name}</h4>
<span className="text-xs bg-primary/10 text-primary px-2 py-1 rounded">
{pkg.version}
</span>
</div>
<p className="text-sm text-muted-foreground truncate">
{pkg.description}
</p>
<div className="flex items-center space-x-2 text-xs text-muted-foreground mt-1">
<span>{pkg.platform}</span>
<span></span>
<span>{pkg.category}</span>
<span></span>
<span>{pkg.license}</span>
</div>
</div>
<Button
variant="ghost"
size="sm"
onClick={() => onPackageRemove(pkg.id)}
className="ml-2"
>
<X className="h-4 w-4" />
</Button>
</div>
))}
</div>
<div className="mt-4 p-3 bg-muted rounded-lg">
<p className="text-sm text-muted-foreground">
<strong>Note:</strong> The generated script will use official repositories only
and will be idempotent (safe to run multiple times).
</p>
</div>
</CardContent>
</Card>
)
}
+56
View File
@@ -0,0 +1,56 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }
+79
View File
@@ -0,0 +1,79 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"rounded-lg border bg-card text-card-foreground shadow-sm",
className
)}
{...props}
/>
))
Card.displayName = "Card"
const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex flex-col space-y-1.5 p-6", className)}
{...props}
/>
))
CardHeader.displayName = "CardHeader"
const CardTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h3
ref={ref}
className={cn(
"text-2xl font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
CardTitle.displayName = "CardTitle"
const CardDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<p
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
CardDescription.displayName = "CardDescription"
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
))
CardContent.displayName = "CardContent"
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex items-center p-6 pt-0", className)}
{...props}
/>
))
CardFooter.displayName = "CardFooter"
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
+30
View File
@@ -0,0 +1,30 @@
"use client"
import * as React from "react"
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
import { Check } from "lucide-react"
import { cn } from "@/lib/utils"
const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
>(({ className, ...props }, ref) => (
<CheckboxPrimitive.Root
ref={ref}
className={cn(
"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
className
)}
{...props}
>
<CheckboxPrimitive.Indicator
className={cn("flex items-center justify-center text-current")}
>
<Check className="h-4 w-4" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
))
Checkbox.displayName = CheckboxPrimitive.Root.displayName
export { Checkbox }
+160
View File
@@ -0,0 +1,160 @@
"use client"
import * as React from "react"
import * as SelectPrimitive from "@radix-ui/react-select"
import { Check, ChevronDown, ChevronUp } from "lucide-react"
import { cn } from "@/lib/utils"
const Select = SelectPrimitive.Root
const SelectGroup = SelectPrimitive.Group
const SelectValue = SelectPrimitive.Value
const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Trigger
ref={ref}
className={cn(
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
className
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDown className="h-4 w-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
))
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
const SelectScrollUpButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollUpButton
ref={ref}
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronUp className="h-4 w-4" />
</SelectPrimitive.ScrollUpButton>
))
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
const SelectScrollDownButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollDownButton
ref={ref}
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronDown className="h-4 w-4" />
</SelectPrimitive.ScrollDownButton>
))
SelectScrollDownButton.displayName =
SelectPrimitive.ScrollDownButton.displayName
const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, position = "popper", ...props }, ref) => (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
ref={ref}
className={cn(
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className
)}
position={position}
{...props}
>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
className={cn(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
)}
>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
))
SelectContent.displayName = SelectPrimitive.Content.displayName
const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label
ref={ref}
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
{...props}
/>
))
SelectLabel.displayName = SelectPrimitive.Label.displayName
const SelectItem = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Item
ref={ref}
className={cn(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
))
SelectItem.displayName = SelectPrimitive.Item.displayName
const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
export {
Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator,
SelectScrollUpButton,
SelectScrollDownButton,
}