style(ViewToggle): elevate visual fidelity to premium standards

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
senke 2026-02-07 08:59:38 +01:00
parent c18771df09
commit 0e9bcb8e99
2 changed files with 44 additions and 19 deletions

View file

@ -6,6 +6,14 @@ const meta = {
title: 'Components/Features/Tracks/ViewToggle',
component: ViewToggle,
tags: ['autodocs'],
parameters: { layout: 'centered' },
decorators: [
(Story) => (
<div className="p-4 bg-background border border-border rounded-[var(--radius-xl)] min-h-layout-story">
<Story />
</div>
),
],
argTypes: {
onChange: { action: 'changed' },
value: {
@ -31,9 +39,26 @@ export const Default: Story = {
};
export const IconOnly: Story = {
render: (args) => {
const [{ value }, updateArgs] = useArgs();
return <ViewToggle {...args} value={value} onChange={(newValue) => updateArgs({ value: newValue })} />;
},
args: {
value: 'grid' as const,
showLabels: false,
onChange: () => { },
}
};
/** Both states to validate tokens and hover/active. */
export const VisualStressTest: Story = {
render: (args) => {
const [{ value }, updateArgs] = useArgs();
return <ViewToggle {...args} value={value} onChange={(newValue) => updateArgs({ value: newValue })} />;
},
args: {
value: 'list' as const,
showLabels: true,
onChange: () => { },
}
};

View file

@ -56,18 +56,26 @@ export function ViewToggle({
setIsTransitioning(true);
onChange(mode);
// Réinitialiser l'état de transition après un court délai
// Réinitialiser l'état de transition après la durée de transition (aligné sur --duration-normal)
setTimeout(() => {
setIsTransitioning(false);
}, 300);
}, 250);
};
const buttonBase = [
'flex items-center gap-2 px-4 py-1.5 rounded-[var(--radius-md)]',
'text-sm font-medium tracking-tight',
'transition-[color,background-color,transform,opacity] duration-[var(--duration-normal)]',
'hover:bg-muted/70 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background',
'disabled:opacity-50 disabled:cursor-not-allowed active:scale-95',
].join(' ');
return (
<div
className={cn(
'inline-flex items-center gap-1 p-1 rounded-lg',
'bg-kodo-void dark:bg-kodo-graphite border border-kodo-steel dark:border-kodo-steel',
'transition-all duration-300',
'inline-flex items-center gap-1 p-1 rounded-[var(--radius-lg)]',
'bg-muted/40 border border-border',
'transition-[opacity,box-shadow] duration-[var(--duration-normal)]',
isTransitioning && 'opacity-75',
className,
)}
@ -78,14 +86,10 @@ export function ViewToggle({
type="button"
onClick={() => handleModeChange('list')}
className={cn(
'flex items-center gap-2 px-4 py-1.5 rounded-md',
'text-sm font-medium transition-all duration-200',
'hover:bg-kodo-slate dark:hover:bg-kodo-steel',
'focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2',
'disabled:opacity-50 disabled:cursor-not-allowed',
buttonBase,
value === 'list'
? 'bg-white dark:bg-kodo-steel text-kodo-cyan dark:text-kodo-cyan shadow-sm'
: 'text-kodo-content-dim dark:text-kodo-content-dim',
? 'bg-primary text-primary-foreground shadow-sm hover:bg-primary/90'
: 'text-muted-foreground/90',
)}
aria-label="Vue liste"
aria-pressed={value === 'list'}
@ -101,14 +105,10 @@ export function ViewToggle({
type="button"
onClick={() => handleModeChange('grid')}
className={cn(
'flex items-center gap-2 px-4 py-1.5 rounded-md',
'text-sm font-medium transition-all duration-200',
'hover:bg-kodo-slate dark:hover:bg-kodo-steel',
'focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2',
'disabled:opacity-50 disabled:cursor-not-allowed',
buttonBase,
value === 'grid'
? 'bg-white dark:bg-kodo-steel text-kodo-cyan dark:text-kodo-cyan shadow-sm'
: 'text-kodo-content-dim dark:text-kodo-content-dim',
? 'bg-primary text-primary-foreground shadow-sm hover:bg-primary/90'
: 'text-muted-foreground/90',
)}
aria-label="Vue grille"
aria-pressed={value === 'grid'}