2025-12-03 21:56:50 +00:00
|
|
|
import { useMemo } from 'react';
|
|
|
|
|
import { cn } from '@/lib/utils';
|
|
|
|
|
|
|
|
|
|
export interface ProgressProps {
|
|
|
|
|
value: number; // 0-100
|
|
|
|
|
max?: number;
|
|
|
|
|
variant?: 'linear' | 'circular';
|
|
|
|
|
showLabel?: boolean;
|
|
|
|
|
label?: string;
|
|
|
|
|
color?: string;
|
|
|
|
|
className?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Composant Progress pour afficher la progression d'une opération.
|
|
|
|
|
*/
|
|
|
|
|
export function Progress({
|
|
|
|
|
value,
|
|
|
|
|
max = 100,
|
|
|
|
|
variant = 'linear',
|
|
|
|
|
showLabel = false,
|
|
|
|
|
label,
|
|
|
|
|
color,
|
|
|
|
|
className,
|
|
|
|
|
}: ProgressProps) {
|
|
|
|
|
const percentage = useMemo(() => {
|
|
|
|
|
const clampedValue = Math.max(0, Math.min(value, max));
|
|
|
|
|
return Math.round((clampedValue / max) * 100);
|
|
|
|
|
}, [value, max]);
|
|
|
|
|
|
|
|
|
|
if (variant === 'circular') {
|
|
|
|
|
const radius = 20;
|
|
|
|
|
const circumference = 2 * Math.PI * radius;
|
|
|
|
|
const strokeDashoffset = circumference - (percentage / 100) * circumference;
|
|
|
|
|
|
|
|
|
|
return (
|
2025-12-13 02:34:34 +00:00
|
|
|
<div
|
|
|
|
|
className={cn(
|
|
|
|
|
'relative inline-flex items-center justify-center',
|
|
|
|
|
className,
|
|
|
|
|
)}
|
|
|
|
|
>
|
2025-12-03 21:56:50 +00:00
|
|
|
<svg
|
|
|
|
|
className="w-16 h-16 transform -rotate-90"
|
|
|
|
|
viewBox="0 0 50 50"
|
|
|
|
|
role="progressbar"
|
|
|
|
|
aria-valuenow={value}
|
|
|
|
|
aria-valuemin={0}
|
|
|
|
|
aria-valuemax={max}
|
|
|
|
|
>
|
|
|
|
|
{/* Background circle */}
|
|
|
|
|
<circle
|
|
|
|
|
cx="25"
|
|
|
|
|
cy="25"
|
|
|
|
|
r={radius}
|
|
|
|
|
fill="none"
|
|
|
|
|
stroke="currentColor"
|
|
|
|
|
strokeWidth="4"
|
|
|
|
|
className="text-muted"
|
|
|
|
|
/>
|
|
|
|
|
{/* Progress circle */}
|
|
|
|
|
<circle
|
|
|
|
|
cx="25"
|
|
|
|
|
cy="25"
|
|
|
|
|
r={radius}
|
|
|
|
|
fill="none"
|
|
|
|
|
stroke={color || 'currentColor'}
|
|
|
|
|
strokeWidth="4"
|
|
|
|
|
strokeDasharray={circumference}
|
|
|
|
|
strokeDashoffset={strokeDashoffset}
|
|
|
|
|
strokeLinecap="round"
|
|
|
|
|
className={cn(
|
feat(web): UI premium Discord/Spotify-like — tokens, shadows, focus, layout
Plan UI premium 6–8 semaines (design system, shell, Storybook, a11y):
- Design system: DESIGN_TOKENS.md, APP_SHELL.md, FULL_LAYOUT_PAGE.md. Single source
for layout/shell (index.css), shadows (design-system.css), durations/easing.
- Tokens: shadow-cover-depth, shadow-gold-glow, shadow-fab-glow; layout max-height
(max-h-layout-drawer, max-h-layout-panel, max-h-layout-list). All duration-200/300/500
replaced by --duration-fast/normal/slow. Arbitrary shadows replaced by token classes.
- Shell & player: Sidebar, Header, GlobalPlayer, MiniPlayer, PlayerQueue, PlayerControls,
AudioPlayer use tokens; focus-visible on Sidebar, PlayerQueue, DropdownMenuTrigger/Item,
TabsTrigger. Typography: text-[10px]/[9px] → text-xs where applicable.
- ESLint: no-restricted-syntax (warn) for w-/h-/rounded-/shadow-/text-/spacing arbitrary.
- Scripts: report-arbitrary-values.mjs, capture/compare/generate visual; visual-complete.spec.ts.
- Stories full layout: Dashboard, Playlists, Library, Settings, Profile in DashboardLayout.stories.
- .cursorrules + README: DESIGN_TOKENS, APP_SHELL, visual commands, no arbitrary without justification.
- apps/web/.gitignore: e2e test artifacts (test-results-visual, playwright-report-visual).
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 16:15:58 +00:00
|
|
|
'transition-all duration-[var(--duration-normal)] ease-in-out',
|
2025-12-13 02:34:34 +00:00
|
|
|
!color && 'text-primary',
|
2025-12-03 21:56:50 +00:00
|
|
|
)}
|
|
|
|
|
style={color ? { stroke: color } : undefined}
|
|
|
|
|
/>
|
|
|
|
|
</svg>
|
|
|
|
|
{showLabel && (
|
|
|
|
|
<span className="absolute inset-0 flex items-center justify-center text-sm font-medium">
|
|
|
|
|
{percentage}%
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className={cn('w-full', className)}>
|
|
|
|
|
{(showLabel || label) && (
|
|
|
|
|
<div className="flex justify-between mb-1 text-sm">
|
|
|
|
|
{label && <span className="text-muted-foreground">{label}</span>}
|
|
|
|
|
{showLabel && (
|
2025-12-13 02:34:34 +00:00
|
|
|
<span className="text-muted-foreground font-medium">
|
|
|
|
|
{percentage}%
|
|
|
|
|
</span>
|
2025-12-03 21:56:50 +00:00
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
<div
|
|
|
|
|
className="w-full bg-muted rounded-full h-2 overflow-hidden"
|
|
|
|
|
role="progressbar"
|
|
|
|
|
aria-valuenow={value}
|
|
|
|
|
aria-valuemin={0}
|
|
|
|
|
aria-valuemax={max}
|
2025-12-13 02:34:34 +00:00
|
|
|
aria-label={
|
|
|
|
|
label ? `${label}: ${percentage}%` : `Progress: ${percentage}%`
|
|
|
|
|
}
|
2025-12-03 21:56:50 +00:00
|
|
|
>
|
|
|
|
|
<div
|
|
|
|
|
className={cn(
|
feat(web): UI premium Discord/Spotify-like — tokens, shadows, focus, layout
Plan UI premium 6–8 semaines (design system, shell, Storybook, a11y):
- Design system: DESIGN_TOKENS.md, APP_SHELL.md, FULL_LAYOUT_PAGE.md. Single source
for layout/shell (index.css), shadows (design-system.css), durations/easing.
- Tokens: shadow-cover-depth, shadow-gold-glow, shadow-fab-glow; layout max-height
(max-h-layout-drawer, max-h-layout-panel, max-h-layout-list). All duration-200/300/500
replaced by --duration-fast/normal/slow. Arbitrary shadows replaced by token classes.
- Shell & player: Sidebar, Header, GlobalPlayer, MiniPlayer, PlayerQueue, PlayerControls,
AudioPlayer use tokens; focus-visible on Sidebar, PlayerQueue, DropdownMenuTrigger/Item,
TabsTrigger. Typography: text-[10px]/[9px] → text-xs where applicable.
- ESLint: no-restricted-syntax (warn) for w-/h-/rounded-/shadow-/text-/spacing arbitrary.
- Scripts: report-arbitrary-values.mjs, capture/compare/generate visual; visual-complete.spec.ts.
- Stories full layout: Dashboard, Playlists, Library, Settings, Profile in DashboardLayout.stories.
- .cursorrules + README: DESIGN_TOKENS, APP_SHELL, visual commands, no arbitrary without justification.
- apps/web/.gitignore: e2e test artifacts (test-results-visual, playwright-report-visual).
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 16:15:58 +00:00
|
|
|
'h-full rounded-full transition-all duration-[var(--duration-normal)] ease-in-out',
|
2025-12-13 02:34:34 +00:00
|
|
|
!color && 'bg-primary',
|
2025-12-03 21:56:50 +00:00
|
|
|
)}
|
|
|
|
|
style={{
|
|
|
|
|
width: `${percentage}%`,
|
|
|
|
|
backgroundColor: color,
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|