@wakastellar/ui 2.1.1 → 2.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,541 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cn } from "../../utils"
5
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../../components/card"
6
+ import { Button } from "../../components/button"
7
+ import { Badge } from "../../components/badge"
8
+ import { Progress } from "../../components/progress"
9
+ import { Avatar, AvatarFallback, AvatarImage } from "../../components/avatar"
10
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "../../components/tabs"
11
+ import { Separator } from "../../components/separator"
12
+ import {
13
+ Trophy,
14
+ Star,
15
+ Zap,
16
+ Target,
17
+ Medal,
18
+ Crown,
19
+ Flame,
20
+ TrendingUp,
21
+ Award,
22
+ Shield,
23
+ Swords,
24
+ Heart,
25
+ Clock,
26
+ Calendar,
27
+ Users,
28
+ Gamepad2,
29
+ } from "lucide-react"
30
+
31
+ // ============================================
32
+ // TYPES
33
+ // ============================================
34
+
35
+ export interface PlayerStats {
36
+ label: string
37
+ value: string | number
38
+ icon?: React.ReactNode
39
+ color?: "default" | "blue" | "green" | "yellow" | "red" | "purple"
40
+ }
41
+
42
+ export interface Achievement {
43
+ id: string
44
+ name: string
45
+ description: string
46
+ icon?: React.ReactNode
47
+ rarity?: "common" | "rare" | "epic" | "legendary"
48
+ unlockedAt?: Date | string
49
+ progress?: number
50
+ maxProgress?: number
51
+ }
52
+
53
+ export interface PlayerBadge {
54
+ id: string
55
+ name: string
56
+ icon?: React.ReactNode
57
+ color?: string
58
+ }
59
+
60
+ export interface MatchHistory {
61
+ id: string
62
+ result: "win" | "loss" | "draw"
63
+ score?: string
64
+ opponent?: string
65
+ mode?: string
66
+ date: Date | string
67
+ xpGained?: number
68
+ }
69
+
70
+ export interface PlayerProfileProps {
71
+ /** Player name */
72
+ name: string
73
+ /** Player avatar URL */
74
+ avatar?: string
75
+ /** Player title/rank */
76
+ title?: string
77
+ /** Current level */
78
+ level: number
79
+ /** Current XP */
80
+ currentXP: number
81
+ /** XP needed for next level */
82
+ maxXP: number
83
+ /** Total XP earned */
84
+ totalXP?: number
85
+ /** Current rank */
86
+ rank?: string
87
+ /** Rank icon */
88
+ rankIcon?: React.ReactNode
89
+ /** Player stats */
90
+ stats?: PlayerStats[]
91
+ /** Achievements */
92
+ achievements?: Achievement[]
93
+ /** Badges */
94
+ badges?: PlayerBadge[]
95
+ /** Match history */
96
+ matchHistory?: MatchHistory[]
97
+ /** Streak count */
98
+ streak?: number
99
+ /** Join date */
100
+ joinDate?: Date | string
101
+ /** Total playtime */
102
+ playtime?: string
103
+ /** Friends count */
104
+ friendsCount?: number
105
+ /** Is online */
106
+ isOnline?: boolean
107
+ /** Custom className */
108
+ className?: string
109
+ }
110
+
111
+ // ============================================
112
+ // SUBCOMPONENTS
113
+ // ============================================
114
+
115
+ const rarityColors = {
116
+ common: "bg-slate-500/20 text-slate-400 border-slate-500/30",
117
+ rare: "bg-blue-500/20 text-blue-400 border-blue-500/30",
118
+ epic: "bg-purple-500/20 text-purple-400 border-purple-500/30",
119
+ legendary: "bg-amber-500/20 text-amber-400 border-amber-500/30",
120
+ }
121
+
122
+ const rarityGlow = {
123
+ common: "",
124
+ rare: "shadow-blue-500/20",
125
+ epic: "shadow-purple-500/20",
126
+ legendary: "shadow-amber-500/30 shadow-lg",
127
+ }
128
+
129
+ function XPBar({ current, max, level }: { current: number; max: number; level: number }) {
130
+ const percentage = (current / max) * 100
131
+
132
+ return (
133
+ <div className="space-y-2">
134
+ <div className="flex items-center justify-between text-sm">
135
+ <div className="flex items-center gap-2">
136
+ <div className="h-8 w-8 rounded-full bg-primary flex items-center justify-center text-primary-foreground font-bold">
137
+ {level}
138
+ </div>
139
+ <span className="text-muted-foreground">Level {level}</span>
140
+ </div>
141
+ <span className="text-muted-foreground">
142
+ {current.toLocaleString()} / {max.toLocaleString()} XP
143
+ </span>
144
+ </div>
145
+ <div className="relative h-3 rounded-full bg-muted overflow-hidden">
146
+ <div
147
+ className="absolute inset-y-0 left-0 bg-gradient-to-r from-primary to-primary/80 rounded-full transition-all duration-500"
148
+ style={{ width: `${percentage}%` }}
149
+ />
150
+ <div
151
+ className="absolute inset-y-0 left-0 bg-gradient-to-r from-white/20 to-transparent rounded-full"
152
+ style={{ width: `${percentage}%` }}
153
+ />
154
+ </div>
155
+ </div>
156
+ )
157
+ }
158
+
159
+ function StatCard({ stat }: { stat: PlayerStats }) {
160
+ const colorClasses = {
161
+ default: "text-foreground",
162
+ blue: "text-blue-500",
163
+ green: "text-green-500",
164
+ yellow: "text-yellow-500",
165
+ red: "text-red-500",
166
+ purple: "text-purple-500",
167
+ }
168
+
169
+ return (
170
+ <div className="flex items-center gap-3 p-3 rounded-lg bg-muted/50">
171
+ {stat.icon && (
172
+ <div className={cn("h-10 w-10 rounded-lg bg-background flex items-center justify-center", colorClasses[stat.color || "default"])}>
173
+ {stat.icon}
174
+ </div>
175
+ )}
176
+ <div>
177
+ <p className="text-2xl font-bold">{stat.value}</p>
178
+ <p className="text-sm text-muted-foreground">{stat.label}</p>
179
+ </div>
180
+ </div>
181
+ )
182
+ }
183
+
184
+ function AchievementCard({ achievement }: { achievement: Achievement }) {
185
+ const isUnlocked = achievement.unlockedAt || (achievement.progress !== undefined && achievement.maxProgress !== undefined && achievement.progress >= achievement.maxProgress)
186
+
187
+ return (
188
+ <div
189
+ className={cn(
190
+ "p-4 rounded-lg border transition-all",
191
+ isUnlocked
192
+ ? cn(rarityColors[achievement.rarity || "common"], rarityGlow[achievement.rarity || "common"])
193
+ : "bg-muted/30 border-dashed opacity-60"
194
+ )}
195
+ >
196
+ <div className="flex items-start gap-3">
197
+ <div
198
+ className={cn(
199
+ "h-12 w-12 rounded-lg flex items-center justify-center",
200
+ isUnlocked ? "bg-background/50" : "bg-muted"
201
+ )}
202
+ >
203
+ {achievement.icon || <Trophy className="h-6 w-6" />}
204
+ </div>
205
+ <div className="flex-1 min-w-0">
206
+ <div className="flex items-center gap-2">
207
+ <h4 className="font-medium truncate">{achievement.name}</h4>
208
+ {achievement.rarity && (
209
+ <Badge variant="outline" className="text-xs capitalize">
210
+ {achievement.rarity}
211
+ </Badge>
212
+ )}
213
+ </div>
214
+ <p className="text-sm text-muted-foreground mt-1">{achievement.description}</p>
215
+ {achievement.progress !== undefined && achievement.maxProgress !== undefined && !isUnlocked && (
216
+ <div className="mt-2">
217
+ <Progress value={(achievement.progress / achievement.maxProgress) * 100} className="h-1.5" />
218
+ <p className="text-xs text-muted-foreground mt-1">
219
+ {achievement.progress} / {achievement.maxProgress}
220
+ </p>
221
+ </div>
222
+ )}
223
+ {achievement.unlockedAt && (
224
+ <p className="text-xs text-muted-foreground mt-2">
225
+ Unlocked {typeof achievement.unlockedAt === "string" ? achievement.unlockedAt : achievement.unlockedAt.toLocaleDateString()}
226
+ </p>
227
+ )}
228
+ </div>
229
+ </div>
230
+ </div>
231
+ )
232
+ }
233
+
234
+ function MatchHistoryItem({ match }: { match: MatchHistory }) {
235
+ const resultColors = {
236
+ win: "text-green-500 bg-green-500/10",
237
+ loss: "text-red-500 bg-red-500/10",
238
+ draw: "text-yellow-500 bg-yellow-500/10",
239
+ }
240
+
241
+ const resultIcons = {
242
+ win: <Trophy className="h-4 w-4" />,
243
+ loss: <Swords className="h-4 w-4" />,
244
+ draw: <Shield className="h-4 w-4" />,
245
+ }
246
+
247
+ return (
248
+ <div className="flex items-center gap-4 p-3 rounded-lg bg-muted/50">
249
+ <div className={cn("h-10 w-10 rounded-lg flex items-center justify-center", resultColors[match.result])}>
250
+ {resultIcons[match.result]}
251
+ </div>
252
+ <div className="flex-1 min-w-0">
253
+ <div className="flex items-center gap-2">
254
+ <span className="font-medium capitalize">{match.result}</span>
255
+ {match.score && <span className="text-muted-foreground">{match.score}</span>}
256
+ </div>
257
+ <div className="flex items-center gap-2 text-sm text-muted-foreground">
258
+ {match.opponent && <span>vs {match.opponent}</span>}
259
+ {match.mode && <Badge variant="outline" className="text-xs">{match.mode}</Badge>}
260
+ </div>
261
+ </div>
262
+ <div className="text-right">
263
+ {match.xpGained && (
264
+ <p className="text-sm font-medium text-green-500">+{match.xpGained} XP</p>
265
+ )}
266
+ <p className="text-xs text-muted-foreground">
267
+ {typeof match.date === "string" ? match.date : match.date.toLocaleDateString()}
268
+ </p>
269
+ </div>
270
+ </div>
271
+ )
272
+ }
273
+
274
+ // ============================================
275
+ // MAIN COMPONENT
276
+ // ============================================
277
+
278
+ export function PlayerProfile({
279
+ name,
280
+ avatar,
281
+ title,
282
+ level,
283
+ currentXP,
284
+ maxXP,
285
+ totalXP,
286
+ rank,
287
+ rankIcon,
288
+ stats,
289
+ achievements,
290
+ badges,
291
+ matchHistory,
292
+ streak,
293
+ joinDate,
294
+ playtime,
295
+ friendsCount,
296
+ isOnline = false,
297
+ className,
298
+ }: PlayerProfileProps) {
299
+ return (
300
+ <div className={cn("space-y-6", className)}>
301
+ {/* Header Card */}
302
+ <Card>
303
+ <CardContent className="pt-6">
304
+ <div className="flex flex-col md:flex-row gap-6 items-start">
305
+ {/* Avatar & Basic Info */}
306
+ <div className="flex items-center gap-4">
307
+ <div className="relative">
308
+ <Avatar className="h-24 w-24 border-4 border-primary/20">
309
+ <AvatarImage src={avatar} alt={name} />
310
+ <AvatarFallback className="text-2xl">{name.slice(0, 2).toUpperCase()}</AvatarFallback>
311
+ </Avatar>
312
+ {isOnline && (
313
+ <div className="absolute bottom-1 right-1 h-5 w-5 rounded-full bg-green-500 border-2 border-background" />
314
+ )}
315
+ <div className="absolute -top-2 -right-2 h-8 w-8 rounded-full bg-primary flex items-center justify-center text-primary-foreground font-bold text-sm">
316
+ {level}
317
+ </div>
318
+ </div>
319
+ <div>
320
+ <h2 className="text-2xl font-bold">{name}</h2>
321
+ {title && <p className="text-muted-foreground">{title}</p>}
322
+ {rank && (
323
+ <div className="flex items-center gap-2 mt-2">
324
+ {rankIcon || <Medal className="h-5 w-5 text-yellow-500" />}
325
+ <span className="font-medium">{rank}</span>
326
+ </div>
327
+ )}
328
+ </div>
329
+ </div>
330
+
331
+ {/* Quick Stats */}
332
+ <div className="flex-1 grid grid-cols-2 md:grid-cols-4 gap-4">
333
+ {streak !== undefined && (
334
+ <div className="text-center p-3 rounded-lg bg-orange-500/10">
335
+ <Flame className="h-6 w-6 text-orange-500 mx-auto" />
336
+ <p className="text-2xl font-bold mt-1">{streak}</p>
337
+ <p className="text-xs text-muted-foreground">Day Streak</p>
338
+ </div>
339
+ )}
340
+ {totalXP !== undefined && (
341
+ <div className="text-center p-3 rounded-lg bg-purple-500/10">
342
+ <Zap className="h-6 w-6 text-purple-500 mx-auto" />
343
+ <p className="text-2xl font-bold mt-1">{totalXP.toLocaleString()}</p>
344
+ <p className="text-xs text-muted-foreground">Total XP</p>
345
+ </div>
346
+ )}
347
+ {playtime && (
348
+ <div className="text-center p-3 rounded-lg bg-blue-500/10">
349
+ <Clock className="h-6 w-6 text-blue-500 mx-auto" />
350
+ <p className="text-2xl font-bold mt-1">{playtime}</p>
351
+ <p className="text-xs text-muted-foreground">Playtime</p>
352
+ </div>
353
+ )}
354
+ {friendsCount !== undefined && (
355
+ <div className="text-center p-3 rounded-lg bg-green-500/10">
356
+ <Users className="h-6 w-6 text-green-500 mx-auto" />
357
+ <p className="text-2xl font-bold mt-1">{friendsCount}</p>
358
+ <p className="text-xs text-muted-foreground">Friends</p>
359
+ </div>
360
+ )}
361
+ </div>
362
+ </div>
363
+
364
+ {/* XP Bar */}
365
+ <div className="mt-6">
366
+ <XPBar current={currentXP} max={maxXP} level={level} />
367
+ </div>
368
+
369
+ {/* Badges */}
370
+ {badges && badges.length > 0 && (
371
+ <div className="mt-6">
372
+ <h3 className="text-sm font-medium text-muted-foreground mb-3">Badges</h3>
373
+ <div className="flex flex-wrap gap-2">
374
+ {badges.map((badge) => (
375
+ <div
376
+ key={badge.id}
377
+ className="flex items-center gap-2 px-3 py-1.5 rounded-full bg-muted text-sm"
378
+ style={badge.color ? { backgroundColor: `${badge.color}20`, color: badge.color } : undefined}
379
+ >
380
+ {badge.icon}
381
+ <span>{badge.name}</span>
382
+ </div>
383
+ ))}
384
+ </div>
385
+ </div>
386
+ )}
387
+ </CardContent>
388
+ </Card>
389
+
390
+ {/* Tabs Section */}
391
+ <Tabs defaultValue="stats" className="w-full">
392
+ <TabsList className="w-full justify-start">
393
+ <TabsTrigger value="stats">Stats</TabsTrigger>
394
+ <TabsTrigger value="achievements">Achievements</TabsTrigger>
395
+ <TabsTrigger value="history">Match History</TabsTrigger>
396
+ </TabsList>
397
+
398
+ {/* Stats Tab */}
399
+ <TabsContent value="stats" className="mt-4">
400
+ <Card>
401
+ <CardHeader>
402
+ <CardTitle>Player Statistics</CardTitle>
403
+ <CardDescription>Your performance metrics</CardDescription>
404
+ </CardHeader>
405
+ <CardContent>
406
+ {stats && stats.length > 0 ? (
407
+ <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
408
+ {stats.map((stat, index) => (
409
+ <StatCard key={index} stat={stat} />
410
+ ))}
411
+ </div>
412
+ ) : (
413
+ <p className="text-muted-foreground text-center py-8">No statistics available</p>
414
+ )}
415
+ </CardContent>
416
+ </Card>
417
+ </TabsContent>
418
+
419
+ {/* Achievements Tab */}
420
+ <TabsContent value="achievements" className="mt-4">
421
+ <Card>
422
+ <CardHeader>
423
+ <CardTitle>Achievements</CardTitle>
424
+ <CardDescription>
425
+ {achievements?.filter((a) => a.unlockedAt).length || 0} / {achievements?.length || 0} unlocked
426
+ </CardDescription>
427
+ </CardHeader>
428
+ <CardContent>
429
+ {achievements && achievements.length > 0 ? (
430
+ <div className="grid gap-4 sm:grid-cols-2">
431
+ {achievements.map((achievement) => (
432
+ <AchievementCard key={achievement.id} achievement={achievement} />
433
+ ))}
434
+ </div>
435
+ ) : (
436
+ <p className="text-muted-foreground text-center py-8">No achievements yet</p>
437
+ )}
438
+ </CardContent>
439
+ </Card>
440
+ </TabsContent>
441
+
442
+ {/* Match History Tab */}
443
+ <TabsContent value="history" className="mt-4">
444
+ <Card>
445
+ <CardHeader>
446
+ <CardTitle>Match History</CardTitle>
447
+ <CardDescription>Your recent games</CardDescription>
448
+ </CardHeader>
449
+ <CardContent>
450
+ {matchHistory && matchHistory.length > 0 ? (
451
+ <div className="space-y-3">
452
+ {matchHistory.map((match) => (
453
+ <MatchHistoryItem key={match.id} match={match} />
454
+ ))}
455
+ </div>
456
+ ) : (
457
+ <p className="text-muted-foreground text-center py-8">No matches played yet</p>
458
+ )}
459
+ </CardContent>
460
+ </Card>
461
+ </TabsContent>
462
+ </Tabs>
463
+
464
+ {/* Footer Info */}
465
+ {joinDate && (
466
+ <div className="flex items-center justify-center gap-2 text-sm text-muted-foreground">
467
+ <Calendar className="h-4 w-4" />
468
+ <span>
469
+ Member since {typeof joinDate === "string" ? joinDate : joinDate.toLocaleDateString()}
470
+ </span>
471
+ </div>
472
+ )}
473
+ </div>
474
+ )
475
+ }
476
+
477
+ // ============================================
478
+ // PRESET DATA
479
+ // ============================================
480
+
481
+ export const defaultPlayerStats: PlayerStats[] = [
482
+ { label: "Matches Played", value: 342, icon: <Gamepad2 className="h-5 w-5" />, color: "blue" },
483
+ { label: "Wins", value: 189, icon: <Trophy className="h-5 w-5" />, color: "green" },
484
+ { label: "Win Rate", value: "55.3%", icon: <TrendingUp className="h-5 w-5" />, color: "yellow" },
485
+ { label: "Best Score", value: "12,450", icon: <Star className="h-5 w-5" />, color: "purple" },
486
+ { label: "Kills", value: "1,234", icon: <Target className="h-5 w-5" />, color: "red" },
487
+ { label: "Assists", value: 876, icon: <Heart className="h-5 w-5" />, color: "default" },
488
+ ]
489
+
490
+ export const defaultAchievements: Achievement[] = [
491
+ {
492
+ id: "first-win",
493
+ name: "First Victory",
494
+ description: "Win your first match",
495
+ rarity: "common",
496
+ unlockedAt: new Date("2024-01-15"),
497
+ icon: <Trophy className="h-6 w-6 text-yellow-500" />,
498
+ },
499
+ {
500
+ id: "streak-master",
501
+ name: "Streak Master",
502
+ description: "Achieve a 10-day login streak",
503
+ rarity: "rare",
504
+ unlockedAt: new Date("2024-02-20"),
505
+ icon: <Flame className="h-6 w-6 text-orange-500" />,
506
+ },
507
+ {
508
+ id: "legendary-player",
509
+ name: "Legendary Player",
510
+ description: "Reach level 50",
511
+ rarity: "legendary",
512
+ progress: 42,
513
+ maxProgress: 50,
514
+ icon: <Crown className="h-6 w-6 text-amber-500" />,
515
+ },
516
+ {
517
+ id: "social-butterfly",
518
+ name: "Social Butterfly",
519
+ description: "Add 25 friends",
520
+ rarity: "epic",
521
+ progress: 18,
522
+ maxProgress: 25,
523
+ icon: <Users className="h-6 w-6 text-purple-500" />,
524
+ },
525
+ ]
526
+
527
+ export const defaultBadges: PlayerBadge[] = [
528
+ { id: "beta", name: "Beta Tester", icon: <Shield className="h-4 w-4" />, color: "#3b82f6" },
529
+ { id: "premium", name: "Premium", icon: <Crown className="h-4 w-4" />, color: "#f59e0b" },
530
+ { id: "verified", name: "Verified", icon: <Award className="h-4 w-4" />, color: "#10b981" },
531
+ ]
532
+
533
+ export const defaultMatchHistory: MatchHistory[] = [
534
+ { id: "1", result: "win", score: "3-1", opponent: "xShadowx", mode: "Ranked", date: new Date("2024-03-10"), xpGained: 150 },
535
+ { id: "2", result: "win", score: "2-0", opponent: "ProGamer99", mode: "Ranked", date: new Date("2024-03-10"), xpGained: 175 },
536
+ { id: "3", result: "loss", score: "1-3", opponent: "NightHawk", mode: "Ranked", date: new Date("2024-03-09"), xpGained: 50 },
537
+ { id: "4", result: "draw", score: "2-2", opponent: "StormRider", mode: "Casual", date: new Date("2024-03-09"), xpGained: 75 },
538
+ { id: "5", result: "win", score: "5-2", opponent: "BlazeMaster", mode: "Ranked", date: new Date("2024-03-08"), xpGained: 200 },
539
+ ]
540
+
541
+ export default PlayerProfile
@@ -240,10 +240,16 @@ export function LanguageSelector({
240
240
  }
241
241
 
242
242
  // Variant compact - Bouton avec drapeau et chevron avec dropdown
243
+ // En mode compact sans labels, on n'affiche que le drapeau (ou l'icône globe)
244
+ const isIconOnly = !showLabels
245
+
243
246
  const dropdownContent = isOpen && mounted ? (
244
- <div
247
+ <div
245
248
  ref={dropdownRef}
246
- className="fixed z-[9999] min-w-[180px] rounded-md border border-border bg-popover text-popover-foreground p-2 shadow-lg"
249
+ className={cn(
250
+ "fixed z-[9999] rounded-md border border-border bg-popover text-popover-foreground p-2 shadow-lg",
251
+ isIconOnly ? "min-w-[50px]" : "min-w-[180px]"
252
+ )}
247
253
  style={{
248
254
  top: `${dropdownPosition.top}px`,
249
255
  left: `${dropdownPosition.left}px`,
@@ -260,23 +266,25 @@ export function LanguageSelector({
260
266
  handleLanguageChange(language.code)
261
267
  }}
262
268
  type="button"
269
+ title={language.label}
263
270
  className={cn(
264
271
  "flex items-center gap-2 rounded-sm px-2 py-2 text-sm hover:bg-accent hover:text-accent-foreground transition-colors cursor-pointer",
265
- value === language.code && "bg-accent"
272
+ value === language.code && "bg-accent",
273
+ isIconOnly && "justify-center"
266
274
  )}
267
275
  >
268
276
  {showFlags && language.flagEmoji && (
269
277
  <span className="text-lg">{language.flagEmoji}</span>
270
278
  )}
271
279
  {showFlags && language.flag && !language.flagEmoji && (
272
- <img
273
- src={language.flag}
280
+ <img
281
+ src={language.flag}
274
282
  alt={language.label}
275
283
  className="h-4 w-6 object-cover rounded"
276
284
  />
277
285
  )}
278
- <span className="flex-1 text-left">{language.label}</span>
279
- {value === language.code && (
286
+ {!isIconOnly && <span className="flex-1 text-left">{language.label}</span>}
287
+ {!isIconOnly && value === language.code && (
280
288
  <Check className="h-4 w-4 shrink-0" />
281
289
  )}
282
290
  </button>
@@ -297,20 +305,22 @@ export function LanguageSelector({
297
305
  }
298
306
  }}
299
307
  className={cn(
300
- "inline-flex items-center gap-2 rounded-md border border-input bg-transparent px-3 py-2 text-sm hover:bg-accent hover:text-accent-foreground transition-colors",
308
+ "inline-flex items-center justify-center rounded-md border border-input bg-transparent text-sm hover:bg-accent hover:text-accent-foreground transition-colors",
309
+ isIconOnly ? "h-9 w-9 p-0" : "gap-2 px-3 py-2",
301
310
  isLoading && "opacity-50 cursor-not-allowed",
302
311
  className
303
312
  )}
304
313
  disabled={isLoading}
305
314
  type="button"
306
315
  aria-label={ariaLabel}
316
+ title={selectedLanguage?.label || ariaLabel}
307
317
  >
308
318
  {showFlags && selectedLanguage?.flagEmoji && (
309
319
  <span className="text-lg">{selectedLanguage.flagEmoji}</span>
310
320
  )}
311
321
  {showFlags && selectedLanguage?.flag && !selectedLanguage?.flagEmoji && (
312
- <img
313
- src={selectedLanguage.flag}
322
+ <img
323
+ src={selectedLanguage.flag}
314
324
  alt={selectedLanguage.label}
315
325
  className="h-4 w-6 object-cover rounded"
316
326
  />
@@ -319,7 +329,7 @@ export function LanguageSelector({
319
329
  {showLabels && (
320
330
  <span>{selectedLanguage?.label || "Langue"}</span>
321
331
  )}
322
- <ChevronDown className="h-4 w-4" />
332
+ {!isIconOnly && <ChevronDown className="h-4 w-4" />}
323
333
  </button>
324
334
 
325
335
  {mounted && typeof document !== "undefined" && dropdownContent && createPortal(