create for series

This commit is contained in:
Nicolas Cantu 2026-01-14 16:30:39 +01:00
parent 53991c7791
commit 95a2019956
11 changed files with 64 additions and 29 deletions

View File

@ -1,3 +1,5 @@
import { Card } from './ui'
interface SeriesStatsProps {
sponsoring: number
purchases: number
@ -17,10 +19,10 @@ export function SeriesStats({ sponsoring, purchases, reviewTips }: SeriesStatsPr
return (
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
{items.map((item) => (
<div key={item.label} className="border rounded-lg p-3 bg-white text-sm">
<Card key={item.label} variant="default" className="bg-white text-sm">
<div className="text-gray-600">{item.label}</div>
<div className="font-semibold text-gray-900">{item.value}</div>
</div>
</Card>
))}
</div>
)

View File

@ -1,5 +1,6 @@
import { ArticleEditorForm } from './ArticleEditorForm'
import type { ArticleDraft } from '@/lib/articlePublisher'
import { Card } from './ui'
interface EditPanelProps {
draft: ArticleDraft | null
@ -24,7 +25,7 @@ export function EditPanel({
return null
}
return (
<div className="border rounded-lg p-4 bg-white space-y-3">
<Card variant="default" className="bg-white space-y-3">
<h3 className="text-lg font-semibold">Edit article</h3>
<ArticleEditorForm
draft={draft}
@ -37,6 +38,6 @@ export function EditPanel({
error={error}
onCancel={onCancel}
/>
</div>
</Card>
)
}

View File

@ -1,7 +1,7 @@
import { useEffect } from 'react'
import { useNostrAuth } from '@/hooks/useNostrAuth'
import { useAuthorPresentation } from '@/hooks/useAuthorPresentation'
import { Button } from '../ui'
import { Button, Card } from '../ui'
import { t } from '@/lib/i18n'
import { NoAccountView } from './NoAccountView'
import { PresentationForm } from './PresentationForm'
@ -10,7 +10,7 @@ import { useAuthorPresentationState } from './useAuthorPresentationState'
function SuccessNotice(params: { pubkey: string | null }): React.ReactElement {
return (
<div className="border border-neon-green/50 rounded-lg p-6 bg-neon-green/10">
<Card variant="default" className="border-neon-green/50 bg-neon-green/10">
<h3 className="text-lg font-semibold text-neon-green mb-2">{t('presentation.success')}</h3>
<p className="text-cyber-accent mb-4">{t('presentation.successMessage')}</p>
{params.pubkey ? (
@ -22,7 +22,7 @@ function SuccessNotice(params: { pubkey: string | null }): React.ReactElement {
</a>
</div>
) : null}
</div>
</Card>
)
}

View File

@ -1,4 +1,4 @@
import { Button, Textarea } from '../ui'
import { Button, Card, Textarea } from '../ui'
import { t } from '@/lib/i18n'
import type { Page } from '@/types/nostr'
@ -39,10 +39,10 @@ function PageEditor(params: {
onImageUpload: (file: File) => Promise<void>
}): React.ReactElement {
return (
<div className="border rounded-lg p-4 space-y-3">
<Card variant="default" className="space-y-3">
<PageEditorHeader page={params.page} onTypeChange={params.onTypeChange} onRemove={params.onRemove} />
<PageEditorBody page={params.page} onContentChange={params.onContentChange} onImageUpload={params.onImageUpload} />
</div>
</Card>
)
}

View File

@ -17,6 +17,7 @@ interface ButtonProps {
'aria-expanded'?: boolean
'aria-haspopup'?: boolean | 'true' | 'false' | 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog'
'aria-selected'?: boolean
'aria-controls'?: string
role?: string
id?: string
}
@ -95,6 +96,17 @@ function ButtonContent({
)
}
function getCombinedClasses(
variant: ButtonVariant,
size: ButtonSize,
className: string
): string {
const variantClasses = getVariantClasses(variant)
const sizeClasses = getSizeClasses(size)
const baseClasses = 'inline-flex items-center justify-center rounded-lg font-medium transition-all border focus:outline-none focus:ring-2 focus:ring-neon-cyan disabled:opacity-50 disabled:cursor-not-allowed'
return `${baseClasses} ${variantClasses} ${sizeClasses} ${className}`.trim()
}
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>((props, ref): React.ReactElement => {
const {
children,
@ -109,14 +121,11 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>((props, r
'aria-expanded': ariaExpanded,
'aria-haspopup': ariaHaspopup,
'aria-selected': ariaSelected,
'aria-controls': ariaControls,
role: roleProp,
id,
} = props
const variantClasses = getVariantClasses(variant)
const sizeClasses = getSizeClasses(size)
const baseClasses = 'inline-flex items-center justify-center rounded-lg font-medium transition-all border focus:outline-none focus:ring-2 focus:ring-neon-cyan disabled:opacity-50 disabled:cursor-not-allowed'
const combinedClasses = `${baseClasses} ${variantClasses} ${sizeClasses} ${className}`.trim()
const combinedClasses = getCombinedClasses(variant, size, className)
return (
<button
@ -131,6 +140,7 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>((props, r
aria-expanded={ariaExpanded}
aria-haspopup={ariaHaspopup}
aria-selected={ariaSelected}
aria-controls={ariaControls}
aria-busy={loading}
>
<ButtonContent loading={loading}>{children}</ButtonContent>

View File

@ -8,6 +8,7 @@ interface CardProps {
className?: string
onClick?: () => void
'aria-label'?: string
role?: string
}
function getVariantClasses(variant: CardVariant, hasOnClick: boolean): string {
@ -40,17 +41,19 @@ export function Card({
className = '',
onClick,
'aria-label': ariaLabel,
role: roleProp,
}: CardProps): React.ReactElement {
const variantClasses = getVariantClasses(variant, onClick !== undefined)
const paddingClasses = getPaddingClasses(variant)
const combinedClasses = `${variantClasses} ${paddingClasses} ${className}`.trim()
const role = roleProp ?? (onClick !== undefined ? 'button' : undefined)
if (onClick) {
return (
<div
onClick={onClick}
className={combinedClasses}
role="button"
role={role}
tabIndex={0}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
@ -66,7 +69,7 @@ export function Card({
}
return (
<div className={combinedClasses} aria-label={ariaLabel}>
<div className={combinedClasses} role={role} aria-label={ariaLabel}>
{children}
</div>
)

View File

@ -1,4 +1,5 @@
import type { ReactNode } from 'react'
import { Card } from './Card'
interface ErrorStateProps {
message: string
@ -27,7 +28,7 @@ function ErrorIcon(): React.ReactElement {
export function ErrorState({ message, action, className = '' }: ErrorStateProps): React.ReactElement {
return (
<div className={`bg-red-900/20 border border-red-500/50 rounded-lg p-4 ${className}`} role="alert">
<Card variant="default" className={`bg-red-900/20 border-red-500/50 ${className}`} role="alert">
<div className="flex items-start gap-3">
<div className="flex-shrink-0 text-red-400">
<ErrorIcon />
@ -37,6 +38,6 @@ export function ErrorState({ message, action, className = '' }: ErrorStateProps)
{action && <div className="mt-3">{action}</div>}
</div>
</div>
</div>
</Card>
)
}

View File

@ -1,5 +1,6 @@
import { useState, useEffect } from 'react'
import type { ReactNode } from 'react'
import { Button } from './Button'
interface MobileMenuProps {
children: ReactNode
@ -130,13 +131,15 @@ function MobileMenuContent({
>
<div className="p-4">
<div className="flex justify-end mb-4">
<button
<Button
type="button"
variant="ghost"
onClick={onClose}
className="text-cyber-accent hover:text-neon-cyan text-2xl transition-colors focus:outline-none focus:ring-2 focus:ring-neon-cyan rounded"
className="text-cyber-accent hover:text-neon-cyan text-2xl p-0"
aria-label="Close menu"
>
×
</button>
</Button>
</div>
<div className="space-y-4">{children}</div>
</div>

View File

@ -1,5 +1,6 @@
import { useEffect, useRef } from 'react'
import type { ReactNode } from 'react'
import { Button } from './Button'
interface ModalProps {
children: ReactNode
@ -28,13 +29,15 @@ function getSizeClasses(size: ModalProps['size']): string {
function CloseButton({ onClose }: { onClose: () => void }): React.ReactElement {
return (
<button
<Button
type="button"
variant="ghost"
onClick={onClose}
className="text-cyber-accent hover:text-neon-cyan text-2xl transition-colors focus:outline-none focus:ring-2 focus:ring-neon-cyan rounded"
className="text-cyber-accent hover:text-neon-cyan text-2xl p-0 w-auto h-auto min-w-0"
aria-label="Close modal"
>
×
</button>
</Button>
)
}

View File

@ -1,5 +1,6 @@
import { useEffect } from 'react'
import type { ReactNode } from 'react'
import { Button } from './Button'
export type ToastVariant = 'info' | 'success' | 'warning' | 'error'
@ -55,13 +56,15 @@ export function Toast({
aria-label={ariaLabel}
>
<div className="flex-1">{children}</div>
<button
<Button
type="button"
variant="ghost"
onClick={onClose}
className="ml-4 text-current hover:opacity-70 transition-opacity focus:outline-none focus:ring-2 focus:ring-current rounded"
className="ml-4 text-current hover:opacity-70 p-0"
aria-label="Close notification"
>
×
</button>
</Button>
</div>
)
}

View File

@ -87,7 +87,16 @@ Aucun composant prioritaire restant. Tous les composants principaux ont été mi
- ✅ `NotificationPanel.tsx` - Migration du conteneur principal vers Card
- ✅ `AuthorFilterButton.tsx` - Migration vers Button avec support forwardRef
- ✅ `AuthorFilterDropdown.tsx` - Migration de AuthorOption et AllAuthorsOption vers Button
- ✅ `components/ui/Button.tsx` - Ajout du support forwardRef, aria-expanded, aria-haspopup, aria-selected, role, id
- ✅ `components/ui/Button.tsx` - Ajout du support forwardRef, aria-expanded, aria-haspopup, aria-selected, aria-controls, role, id
- ✅ `components/ui/MobileMenu.tsx` - Migration des boutons vers Button
- ✅ `components/ui/Toast.tsx` - Migration du bouton de fermeture vers Button
- ✅ `components/ui/ErrorState.tsx` - Migration du conteneur vers Card
- ✅ `components/ui/Card.tsx` - Ajout du support role pour l'accessibilité
- ✅ `components/ui/Modal.tsx` - Migration du bouton de fermeture vers Button
- ✅ `components/SeriesStats.tsx` - Migration des conteneurs de statistiques vers Card
- ✅ `components/UserArticlesEditPanel.tsx` - Migration du conteneur principal vers Card
- ✅ `components/markdownEditorTwoColumns/PagesManager.tsx` - Migration de PageEditor vers Card
- ✅ `components/authorPresentationEditor/AuthorPresentationEditor.tsx` - Migration de SuccessNotice vers Card
## Erreurs corrigées