mirror of
https://github.com/yusufipk/RepoHub.git
synced 2026-09-11 10:36:07 +00:00
feat: Implement platform locking with shadcn-ui tooltips and remove preset match badge.
This commit is contained in:
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://ui.shadcn.com/schema.json",
|
||||||
|
"style": "default",
|
||||||
|
"rsc": true,
|
||||||
|
"tsx": true,
|
||||||
|
"tailwind": {
|
||||||
|
"config": "tailwind.config.js",
|
||||||
|
"css": "src/app/globals.css",
|
||||||
|
"baseColor": "neutral",
|
||||||
|
"cssVariables": true,
|
||||||
|
"prefix": ""
|
||||||
|
},
|
||||||
|
"iconLibrary": "lucide",
|
||||||
|
"aliases": {
|
||||||
|
"components": "@/components",
|
||||||
|
"utils": "@/lib/utils",
|
||||||
|
"ui": "@/components/ui",
|
||||||
|
"lib": "@/lib",
|
||||||
|
"hooks": "@/hooks"
|
||||||
|
},
|
||||||
|
"registries": {}
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
"@radix-ui/react-select": "^2.0.0",
|
"@radix-ui/react-select": "^2.0.0",
|
||||||
"@radix-ui/react-slot": "^1.0.2",
|
"@radix-ui/react-slot": "^1.0.2",
|
||||||
"@radix-ui/react-tabs": "^1.0.4",
|
"@radix-ui/react-tabs": "^1.0.4",
|
||||||
|
"@radix-ui/react-tooltip": "^1.2.8",
|
||||||
"cheerio": "^1.1.2",
|
"cheerio": "^1.1.2",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
|
|||||||
Generated
+36
@@ -26,6 +26,9 @@ importers:
|
|||||||
'@radix-ui/react-tabs':
|
'@radix-ui/react-tabs':
|
||||||
specifier: ^1.0.4
|
specifier: ^1.0.4
|
||||||
version: 1.1.13(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])
|
version: 1.1.13(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])
|
||||||
|
'@radix-ui/react-tooltip':
|
||||||
|
specifier: ^1.2.8
|
||||||
|
version: 1.2.8(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])
|
||||||
cheerio:
|
cheerio:
|
||||||
specifier: ^1.1.2
|
specifier: ^1.1.2
|
||||||
version: 1.1.2
|
version: 1.1.2
|
||||||
@@ -529,6 +532,19 @@ packages:
|
|||||||
'@types/react-dom':
|
'@types/react-dom':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/[email protected]':
|
||||||
|
resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/[email protected]':
|
'@radix-ui/[email protected]':
|
||||||
resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==}
|
resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -2869,6 +2885,26 @@ snapshots:
|
|||||||
'@types/react': 18.3.26
|
'@types/react': 18.3.26
|
||||||
'@types/react-dom': 18.3.7(@types/[email protected])
|
'@types/react-dom': 18.3.7(@types/[email protected])
|
||||||
|
|
||||||
|
'@radix-ui/[email protected](@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/primitive': 1.1.3
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/[email protected])([email protected])
|
||||||
|
'@radix-ui/react-context': 1.1.2(@types/[email protected])([email protected])
|
||||||
|
'@radix-ui/react-dismissable-layer': 1.1.11(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])
|
||||||
|
'@radix-ui/react-id': 1.1.1(@types/[email protected])([email protected])
|
||||||
|
'@radix-ui/react-popper': 1.2.8(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])
|
||||||
|
'@radix-ui/react-portal': 1.1.9(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])
|
||||||
|
'@radix-ui/react-presence': 1.1.5(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])
|
||||||
|
'@radix-ui/react-slot': 1.2.3(@types/[email protected])([email protected])
|
||||||
|
'@radix-ui/react-use-controllable-state': 1.2.2(@types/[email protected])([email protected])
|
||||||
|
'@radix-ui/react-visually-hidden': 1.2.3(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1([email protected])
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 18.3.26
|
||||||
|
'@types/react-dom': 18.3.7(@types/[email protected])
|
||||||
|
|
||||||
'@radix-ui/[email protected](@types/[email protected])([email protected])':
|
'@radix-ui/[email protected](@types/[email protected])([email protected])':
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
|
|||||||
+21
-10
@@ -10,20 +10,25 @@
|
|||||||
--card-foreground: 222.2 84% 4.9%;
|
--card-foreground: 222.2 84% 4.9%;
|
||||||
--popover: 0 0% 100%;
|
--popover: 0 0% 100%;
|
||||||
--popover-foreground: 222.2 84% 4.9%;
|
--popover-foreground: 222.2 84% 4.9%;
|
||||||
--primary: 221.2 83.2% 53.3%;
|
--primary: 218 63% 38%;
|
||||||
--primary-foreground: 210 40% 98%;
|
--primary-foreground: 210 40% 98%;
|
||||||
--secondary: 210 40% 96%;
|
--secondary: 210 40% 96.1%;
|
||||||
--secondary-foreground: 222.2 84% 4.9%;
|
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||||
--muted: 210 40% 96%;
|
--muted: 210 40% 96.1%;
|
||||||
--muted-foreground: 215.4 16.3% 46.9%;
|
--muted-foreground: 215.4 16.3% 46.9%;
|
||||||
--accent: 210 40% 96%;
|
--accent: 210 40% 96.1%;
|
||||||
--accent-foreground: 222.2 84% 4.9%;
|
--accent-foreground: 222.2 47.4% 11.2%;
|
||||||
--destructive: 0 84.2% 60.2%;
|
--destructive: 0 84.2% 60.2%;
|
||||||
--destructive-foreground: 210 40% 98%;
|
--destructive-foreground: 210 40% 98%;
|
||||||
--border: 214.3 31.8% 91.4%;
|
--border: 214.3 31.8% 91.4%;
|
||||||
--input: 214.3 31.8% 91.4%;
|
--input: 214.3 31.8% 91.4%;
|
||||||
--ring: 221.2 83.2% 53.3%;
|
--ring: 222.2 84% 4.9%;
|
||||||
--radius: 0.5rem;
|
--radius: 0.5rem;
|
||||||
|
--chart-1: 12 76% 61%;
|
||||||
|
--chart-2: 173 58% 39%;
|
||||||
|
--chart-3: 197 37% 24%;
|
||||||
|
--chart-4: 43 74% 66%;
|
||||||
|
--chart-5: 27 87% 67%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
@@ -33,8 +38,8 @@
|
|||||||
--card-foreground: 210 40% 98%;
|
--card-foreground: 210 40% 98%;
|
||||||
--popover: 222.2 84% 4.9%;
|
--popover: 222.2 84% 4.9%;
|
||||||
--popover-foreground: 210 40% 98%;
|
--popover-foreground: 210 40% 98%;
|
||||||
--primary: 217.2 91.2% 59.8%;
|
--primary: 218 63% 38%;
|
||||||
--primary-foreground: 222.2 84% 4.9%;
|
--primary-foreground: 210 40% 98%;
|
||||||
--secondary: 217.2 32.6% 17.5%;
|
--secondary: 217.2 32.6% 17.5%;
|
||||||
--secondary-foreground: 210 40% 98%;
|
--secondary-foreground: 210 40% 98%;
|
||||||
--muted: 217.2 32.6% 17.5%;
|
--muted: 217.2 32.6% 17.5%;
|
||||||
@@ -45,7 +50,12 @@
|
|||||||
--destructive-foreground: 210 40% 98%;
|
--destructive-foreground: 210 40% 98%;
|
||||||
--border: 217.2 32.6% 17.5%;
|
--border: 217.2 32.6% 17.5%;
|
||||||
--input: 217.2 32.6% 17.5%;
|
--input: 217.2 32.6% 17.5%;
|
||||||
--ring: 224.3 76.3% 94.1%;
|
--ring: 212.7 26.8% 83.9%;
|
||||||
|
--chart-1: 220 70% 50%;
|
||||||
|
--chart-2: 160 60% 45%;
|
||||||
|
--chart-3: 30 80% 55%;
|
||||||
|
--chart-4: 280 65% 60%;
|
||||||
|
--chart-5: 340 75% 55%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,6 +63,7 @@
|
|||||||
* {
|
* {
|
||||||
@apply border-border;
|
@apply border-border;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@apply bg-background text-foreground;
|
@apply bg-background text-foreground;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,12 +7,22 @@ import { apiClient } from '@/lib/api/client'
|
|||||||
import { useLocale } from '@/contexts/LocaleContext'
|
import { useLocale } from '@/contexts/LocaleContext'
|
||||||
import { Platform } from '@/types'
|
import { Platform } from '@/types'
|
||||||
|
|
||||||
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
||||||
|
import { Lock } from 'lucide-react'
|
||||||
|
|
||||||
interface PlatformSelectorProps {
|
interface PlatformSelectorProps {
|
||||||
selectedPlatform: Platform | null
|
selectedPlatform: Platform | null
|
||||||
onPlatformSelect: (platform: Platform) => void
|
onPlatformSelect: (platform: Platform) => void
|
||||||
|
isLocked?: boolean
|
||||||
|
lockedMessage?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PlatformSelector({ selectedPlatform, onPlatformSelect }: PlatformSelectorProps) {
|
export function PlatformSelector({
|
||||||
|
selectedPlatform,
|
||||||
|
onPlatformSelect,
|
||||||
|
isLocked = false,
|
||||||
|
lockedMessage = "Platform selection is locked"
|
||||||
|
}: PlatformSelectorProps) {
|
||||||
const { t } = useLocale()
|
const { t } = useLocale()
|
||||||
const [platforms, setPlatforms] = useState<Platform[]>([])
|
const [platforms, setPlatforms] = useState<Platform[]>([])
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
@@ -66,25 +76,36 @@ export function PlatformSelector({ selectedPlatform, onPlatformSelect }: Platfor
|
|||||||
return (
|
return (
|
||||||
<Card className="w-full">
|
<Card className="w-full">
|
||||||
<CardHeader className="pb-4">
|
<CardHeader className="pb-4">
|
||||||
<CardTitle className="text-lg">{t('platform.select')}</CardTitle>
|
<CardTitle className="text-lg flex items-center gap-2">
|
||||||
|
{t('platform.select')}
|
||||||
|
{isLocked && <Lock className="h-4 w-4 text-muted-foreground" />}
|
||||||
|
</CardTitle>
|
||||||
<CardDescription className="text-sm">
|
<CardDescription className="text-sm">
|
||||||
{t('platform.description')}
|
{t('platform.description')}
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="pt-0">
|
<CardContent className="pt-0">
|
||||||
|
<TooltipProvider>
|
||||||
<div className="grid grid-cols-3 sm:grid-cols-4 lg:grid-cols-6 gap-2">
|
<div className="grid grid-cols-3 sm:grid-cols-4 lg:grid-cols-6 gap-2">
|
||||||
{platforms.map((platform) => (
|
{platforms.map((platform) => {
|
||||||
|
const isSelected = selectedPlatform?.id === platform.id
|
||||||
|
const isDisabled = isLocked && !isSelected
|
||||||
|
|
||||||
|
const ButtonContent = (
|
||||||
<Button
|
<Button
|
||||||
key={platform.id}
|
key={platform.id}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className={`h-12 px-2 py-1 flex flex-col items-center justify-center gap-1 rounded-md border transition-colors
|
className={`h-12 px-2 py-1 flex flex-col items-center justify-center gap-1 rounded-md border transition-colors w-full
|
||||||
${selectedPlatform?.id === platform.id
|
${isSelected
|
||||||
? 'border-primary ring-2 ring-primary/60 bg-primary/5'
|
? 'border-primary ring-2 ring-primary/60 bg-primary/5'
|
||||||
: 'border-border hover:bg-secondary/60'}`}
|
: 'border-border hover:bg-secondary/60'}
|
||||||
onClick={() => onPlatformSelect(platform)}
|
${isDisabled ? 'opacity-50 cursor-not-allowed' : ''}
|
||||||
|
`}
|
||||||
|
onClick={() => !isLocked && onPlatformSelect(platform)}
|
||||||
|
disabled={isDisabled}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`h-5 w-5 ${selectedPlatform?.id === platform.id ? 'text-foreground' : 'text-muted-foreground'}`}
|
className={`h-5 w-5 ${isSelected ? 'text-foreground' : 'text-muted-foreground'}`}
|
||||||
style={{
|
style={{
|
||||||
WebkitMaskImage: `url(${iconBase(iconSlug[platform.id] || 'linux')})`,
|
WebkitMaskImage: `url(${iconBase(iconSlug[platform.id] || 'linux')})`,
|
||||||
maskImage: `url(${iconBase(iconSlug[platform.id] || 'linux')})`,
|
maskImage: `url(${iconBase(iconSlug[platform.id] || 'linux')})`,
|
||||||
@@ -101,8 +122,27 @@ export function PlatformSelector({ selectedPlatform, onPlatformSelect }: Platfor
|
|||||||
<div className="font-medium text-xs leading-tight truncate max-w-[90px]">{platform.name}</div>
|
<div className="font-medium text-xs leading-tight truncate max-w-[90px]">{platform.name}</div>
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
))}
|
)
|
||||||
|
|
||||||
|
if (isLocked && !isSelected) {
|
||||||
|
return (
|
||||||
|
<Tooltip key={platform.id}>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<div className="w-full cursor-not-allowed">
|
||||||
|
{ButtonContent}
|
||||||
</div>
|
</div>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>{lockedMessage}</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ButtonContent
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</TooltipProvider>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Package as PackageIcon, Star } from 'lucide-react'
|
import { Package as PackageIcon } from 'lucide-react'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Card, CardContent } from '@/components/ui/card'
|
import { Card, CardContent } from '@/components/ui/card'
|
||||||
import { RecommendedPackage } from '@/types/recommendations'
|
import { RecommendedPackage } from '@/types/recommendations'
|
||||||
@@ -19,14 +19,7 @@ export function RecommendationCard({ pkg, isSelected, onToggle }: Recommendation
|
|||||||
}`}
|
}`}
|
||||||
onClick={() => onToggle(pkg)}
|
onClick={() => onToggle(pkg)}
|
||||||
>
|
>
|
||||||
{pkg.presetMatch && (
|
|
||||||
<div className="absolute top-2 right-2">
|
|
||||||
<span className="inline-flex items-center gap-1 px-2 py-1 rounded-full text-xs font-semibold bg-primary text-primary-foreground">
|
|
||||||
<Star className="h-3 w-3" />
|
|
||||||
{t('recommendations.preset_badge')}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<CardContent className="p-4">
|
<CardContent className="p-4">
|
||||||
<div className="flex items-start gap-3 mb-3">
|
<div className="flex items-start gap-3 mb-3">
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Package as PackageIcon, Star, Info } from 'lucide-react'
|
import { Package as PackageIcon } from 'lucide-react'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { RecommendedPackage } from '@/types/recommendations'
|
import { RecommendedPackage } from '@/types/recommendations'
|
||||||
|
|
||||||
@@ -24,12 +24,6 @@ export function RecommendationListItem({ pkg, isSelected, onToggle }: Recommenda
|
|||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<h4 className="font-semibold text-sm truncate">{pkg.name}</h4>
|
<h4 className="font-semibold text-sm truncate">{pkg.name}</h4>
|
||||||
{pkg.presetMatch && (
|
|
||||||
<span className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-xs font-semibold bg-primary text-primary-foreground">
|
|
||||||
<Star className="h-2.5 w-2.5" />
|
|
||||||
Essential
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
<span className="text-xs text-muted-foreground">{pkg.version}</span>
|
<span className="text-xs text-muted-foreground">{pkg.version}</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-muted-foreground truncate">{pkg.description}</p>
|
<p className="text-xs text-muted-foreground truncate">{pkg.description}</p>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect, useRef } from 'react'
|
||||||
import { LocaleProvider } from '@/contexts/LocaleContext'
|
import { LocaleProvider } from '@/contexts/LocaleContext'
|
||||||
import { Header } from './Header'
|
import { Header } from './Header'
|
||||||
import { PlatformSelector } from './PlatformSelector'
|
import { PlatformSelector } from './PlatformSelector'
|
||||||
@@ -28,7 +28,8 @@ function RepoHubAppContent({ cryptomusEnabled }: { cryptomusEnabled: boolean })
|
|||||||
isLoading: isProfileLoading,
|
isLoading: isProfileLoading,
|
||||||
hasCompletedOnboarding,
|
hasCompletedOnboarding,
|
||||||
saveProfile,
|
saveProfile,
|
||||||
detectedOS
|
detectedOS,
|
||||||
|
getEffectiveOS
|
||||||
} = useRecommendationProfile()
|
} = useRecommendationProfile()
|
||||||
|
|
||||||
const [showOnboarding, setShowOnboarding] = useState(false)
|
const [showOnboarding, setShowOnboarding] = useState(false)
|
||||||
@@ -148,7 +149,24 @@ function RepoHubAppContent({ cryptomusEnabled }: { cryptomusEnabled: boolean })
|
|||||||
const handleCloseScriptPreview = () => {
|
const handleCloseScriptPreview = () => {
|
||||||
setGeneratedScript(null)
|
setGeneratedScript(null)
|
||||||
}
|
}
|
||||||
|
// Track previous effective OS to detect profile changes
|
||||||
|
const prevEffectiveOS = useRef<string | null>(null)
|
||||||
|
|
||||||
|
// Calculate current effective OS
|
||||||
|
const effectiveOS = getEffectiveOS()
|
||||||
|
|
||||||
|
// Sync platform selection with profile changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (hasCompletedOnboarding && availablePlatforms.length > 0) {
|
||||||
|
const platform = availablePlatforms.find(p => p.id === effectiveOS)
|
||||||
|
|
||||||
|
// If profile OS changed, or if no platform is selected yet, update selection
|
||||||
|
if (platform && (effectiveOS !== prevEffectiveOS.current || !selectedPlatform)) {
|
||||||
|
setSelectedPlatform(platform)
|
||||||
|
prevEffectiveOS.current = effectiveOS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [hasCompletedOnboarding, effectiveOS, availablePlatforms, selectedPlatform])
|
||||||
return (
|
return (
|
||||||
<div key={locale} className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 dark:from-gray-900 dark:to-gray-800">
|
<div key={locale} className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 dark:from-gray-900 dark:to-gray-800">
|
||||||
<Header
|
<Header
|
||||||
@@ -183,10 +201,16 @@ function RepoHubAppContent({ cryptomusEnabled }: { cryptomusEnabled: boolean })
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{/* Platform Selector */}
|
{/* Platform Selector */}
|
||||||
<PlatformSelector
|
<PlatformSelector
|
||||||
selectedPlatform={selectedPlatform}
|
selectedPlatform={selectedPlatform}
|
||||||
onPlatformSelect={handlePlatformSelect}
|
onPlatformSelect={handlePlatformSelect}
|
||||||
|
isLocked={selectedPackages.length > 0}
|
||||||
|
lockedMessage="Clear your selection to switch platforms"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Package Browser */}
|
{/* Package Browser */}
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const TooltipProvider = TooltipPrimitive.Provider
|
||||||
|
|
||||||
|
const Tooltip = TooltipPrimitive.Root
|
||||||
|
|
||||||
|
const TooltipTrigger = TooltipPrimitive.Trigger
|
||||||
|
|
||||||
|
const TooltipContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof TooltipPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
||||||
|
>(({ className, sideOffset = 4, ...props }, ref) => (
|
||||||
|
<TooltipPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
sideOffset={sideOffset}
|
||||||
|
className={cn(
|
||||||
|
"z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-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",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
TooltipContent.displayName = TooltipPrimitive.Content.displayName
|
||||||
|
|
||||||
|
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
import { type ClassValue, clsx } from "clsx"
|
import { clsx, type ClassValue } from "clsx"
|
||||||
import { twMerge } from "tailwind-merge"
|
import { twMerge } from "tailwind-merge"
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
export function cn(...inputs: ClassValue[]) {
|
||||||
|
|||||||
+50
-35
@@ -10,67 +10,82 @@ module.exports = {
|
|||||||
theme: {
|
theme: {
|
||||||
container: {
|
container: {
|
||||||
center: true,
|
center: true,
|
||||||
padding: "2rem",
|
padding: '2rem',
|
||||||
screens: {
|
screens: {
|
||||||
"2xl": "1400px",
|
'2xl': '1400px'
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
extend: {
|
extend: {
|
||||||
colors: {
|
colors: {
|
||||||
border: "hsl(var(--border))",
|
border: 'hsl(var(--border))',
|
||||||
input: "hsl(var(--input))",
|
input: 'hsl(var(--input))',
|
||||||
ring: "hsl(var(--ring))",
|
ring: 'hsl(var(--ring))',
|
||||||
background: "hsl(var(--background))",
|
background: 'hsl(var(--background))',
|
||||||
foreground: "hsl(var(--foreground))",
|
foreground: 'hsl(var(--foreground))',
|
||||||
primary: {
|
primary: {
|
||||||
DEFAULT: "hsl(var(--primary))",
|
DEFAULT: 'hsl(var(--primary))',
|
||||||
foreground: "hsl(var(--primary-foreground))",
|
foreground: 'hsl(var(--primary-foreground))'
|
||||||
},
|
},
|
||||||
secondary: {
|
secondary: {
|
||||||
DEFAULT: "hsl(var(--secondary))",
|
DEFAULT: 'hsl(var(--secondary))',
|
||||||
foreground: "hsl(var(--secondary-foreground))",
|
foreground: 'hsl(var(--secondary-foreground))'
|
||||||
},
|
},
|
||||||
destructive: {
|
destructive: {
|
||||||
DEFAULT: "hsl(var(--destructive))",
|
DEFAULT: 'hsl(var(--destructive))',
|
||||||
foreground: "hsl(var(--destructive-foreground))",
|
foreground: 'hsl(var(--destructive-foreground))'
|
||||||
},
|
},
|
||||||
muted: {
|
muted: {
|
||||||
DEFAULT: "hsl(var(--muted))",
|
DEFAULT: 'hsl(var(--muted))',
|
||||||
foreground: "hsl(var(--muted-foreground))",
|
foreground: 'hsl(var(--muted-foreground))'
|
||||||
},
|
},
|
||||||
accent: {
|
accent: {
|
||||||
DEFAULT: "hsl(var(--accent))",
|
DEFAULT: 'hsl(var(--accent))',
|
||||||
foreground: "hsl(var(--accent-foreground))",
|
foreground: 'hsl(var(--accent-foreground))'
|
||||||
},
|
},
|
||||||
popover: {
|
popover: {
|
||||||
DEFAULT: "hsl(var(--popover))",
|
DEFAULT: 'hsl(var(--popover))',
|
||||||
foreground: "hsl(var(--popover-foreground))",
|
foreground: 'hsl(var(--popover-foreground))'
|
||||||
},
|
},
|
||||||
card: {
|
card: {
|
||||||
DEFAULT: "hsl(var(--card))",
|
DEFAULT: 'hsl(var(--card))',
|
||||||
foreground: "hsl(var(--card-foreground))",
|
foreground: 'hsl(var(--card-foreground))'
|
||||||
},
|
},
|
||||||
|
chart: {
|
||||||
|
'1': 'hsl(var(--chart-1))',
|
||||||
|
'2': 'hsl(var(--chart-2))',
|
||||||
|
'3': 'hsl(var(--chart-3))',
|
||||||
|
'4': 'hsl(var(--chart-4))',
|
||||||
|
'5': 'hsl(var(--chart-5))'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
borderRadius: {
|
borderRadius: {
|
||||||
lg: "var(--radius)",
|
lg: 'var(--radius)',
|
||||||
md: "calc(var(--radius) - 2px)",
|
md: 'calc(var(--radius) - 2px)',
|
||||||
sm: "calc(var(--radius) - 4px)",
|
sm: 'calc(var(--radius) - 4px)'
|
||||||
},
|
},
|
||||||
keyframes: {
|
keyframes: {
|
||||||
"accordion-down": {
|
'accordion-down': {
|
||||||
from: { height: 0 },
|
from: {
|
||||||
to: { height: "var(--radix-accordion-content-height)" },
|
height: 0
|
||||||
},
|
},
|
||||||
"accordion-up": {
|
to: {
|
||||||
from: { height: "var(--radix-accordion-content-height)" },
|
height: 'var(--radix-accordion-content-height)'
|
||||||
to: { height: 0 },
|
}
|
||||||
},
|
},
|
||||||
|
'accordion-up': {
|
||||||
|
from: {
|
||||||
|
height: 'var(--radix-accordion-content-height)'
|
||||||
|
},
|
||||||
|
to: {
|
||||||
|
height: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
animation: {
|
animation: {
|
||||||
"accordion-down": "accordion-down 0.2s ease-out",
|
'accordion-down': 'accordion-down 0.2s ease-out',
|
||||||
"accordion-up": "accordion-up 0.2s ease-out",
|
'accordion-up': 'accordion-up 0.2s ease-out'
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
plugins: [require("tailwindcss-animate")],
|
plugins: [require("tailwindcss-animate")],
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user