create for series
This commit is contained in:
parent
53991c7791
commit
95a2019956
@ -1,3 +1,5 @@
|
|||||||
|
import { Card } from './ui'
|
||||||
|
|
||||||
interface SeriesStatsProps {
|
interface SeriesStatsProps {
|
||||||
sponsoring: number
|
sponsoring: number
|
||||||
purchases: number
|
purchases: number
|
||||||
@ -17,10 +19,10 @@ export function SeriesStats({ sponsoring, purchases, reviewTips }: SeriesStatsPr
|
|||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
|
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
|
||||||
{items.map((item) => (
|
{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="text-gray-600">{item.label}</div>
|
||||||
<div className="font-semibold text-gray-900">{item.value}</div>
|
<div className="font-semibold text-gray-900">{item.value}</div>
|
||||||
</div>
|
</Card>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { ArticleEditorForm } from './ArticleEditorForm'
|
import { ArticleEditorForm } from './ArticleEditorForm'
|
||||||
import type { ArticleDraft } from '@/lib/articlePublisher'
|
import type { ArticleDraft } from '@/lib/articlePublisher'
|
||||||
|
import { Card } from './ui'
|
||||||
|
|
||||||
interface EditPanelProps {
|
interface EditPanelProps {
|
||||||
draft: ArticleDraft | null
|
draft: ArticleDraft | null
|
||||||
@ -24,7 +25,7 @@ export function EditPanel({
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return (
|
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>
|
<h3 className="text-lg font-semibold">Edit article</h3>
|
||||||
<ArticleEditorForm
|
<ArticleEditorForm
|
||||||
draft={draft}
|
draft={draft}
|
||||||
@ -37,6 +38,6 @@ export function EditPanel({
|
|||||||
error={error}
|
error={error}
|
||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { useNostrAuth } from '@/hooks/useNostrAuth'
|
import { useNostrAuth } from '@/hooks/useNostrAuth'
|
||||||
import { useAuthorPresentation } from '@/hooks/useAuthorPresentation'
|
import { useAuthorPresentation } from '@/hooks/useAuthorPresentation'
|
||||||
import { Button } from '../ui'
|
import { Button, Card } from '../ui'
|
||||||
import { t } from '@/lib/i18n'
|
import { t } from '@/lib/i18n'
|
||||||
import { NoAccountView } from './NoAccountView'
|
import { NoAccountView } from './NoAccountView'
|
||||||
import { PresentationForm } from './PresentationForm'
|
import { PresentationForm } from './PresentationForm'
|
||||||
@ -10,7 +10,7 @@ import { useAuthorPresentationState } from './useAuthorPresentationState'
|
|||||||
|
|
||||||
function SuccessNotice(params: { pubkey: string | null }): React.ReactElement {
|
function SuccessNotice(params: { pubkey: string | null }): React.ReactElement {
|
||||||
return (
|
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>
|
<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>
|
<p className="text-cyber-accent mb-4">{t('presentation.successMessage')}</p>
|
||||||
{params.pubkey ? (
|
{params.pubkey ? (
|
||||||
@ -22,7 +22,7 @@ function SuccessNotice(params: { pubkey: string | null }): React.ReactElement {
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Button, Textarea } from '../ui'
|
import { Button, Card, Textarea } from '../ui'
|
||||||
import { t } from '@/lib/i18n'
|
import { t } from '@/lib/i18n'
|
||||||
import type { Page } from '@/types/nostr'
|
import type { Page } from '@/types/nostr'
|
||||||
|
|
||||||
@ -39,10 +39,10 @@ function PageEditor(params: {
|
|||||||
onImageUpload: (file: File) => Promise<void>
|
onImageUpload: (file: File) => Promise<void>
|
||||||
}): React.ReactElement {
|
}): React.ReactElement {
|
||||||
return (
|
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} />
|
<PageEditorHeader page={params.page} onTypeChange={params.onTypeChange} onRemove={params.onRemove} />
|
||||||
<PageEditorBody page={params.page} onContentChange={params.onContentChange} onImageUpload={params.onImageUpload} />
|
<PageEditorBody page={params.page} onContentChange={params.onContentChange} onImageUpload={params.onImageUpload} />
|
||||||
</div>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,6 +17,7 @@ interface ButtonProps {
|
|||||||
'aria-expanded'?: boolean
|
'aria-expanded'?: boolean
|
||||||
'aria-haspopup'?: boolean | 'true' | 'false' | 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog'
|
'aria-haspopup'?: boolean | 'true' | 'false' | 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog'
|
||||||
'aria-selected'?: boolean
|
'aria-selected'?: boolean
|
||||||
|
'aria-controls'?: string
|
||||||
role?: string
|
role?: string
|
||||||
id?: 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 => {
|
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>((props, ref): React.ReactElement => {
|
||||||
const {
|
const {
|
||||||
children,
|
children,
|
||||||
@ -109,14 +121,11 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>((props, r
|
|||||||
'aria-expanded': ariaExpanded,
|
'aria-expanded': ariaExpanded,
|
||||||
'aria-haspopup': ariaHaspopup,
|
'aria-haspopup': ariaHaspopup,
|
||||||
'aria-selected': ariaSelected,
|
'aria-selected': ariaSelected,
|
||||||
|
'aria-controls': ariaControls,
|
||||||
role: roleProp,
|
role: roleProp,
|
||||||
id,
|
id,
|
||||||
} = props
|
} = props
|
||||||
const variantClasses = getVariantClasses(variant)
|
const combinedClasses = getCombinedClasses(variant, size, className)
|
||||||
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()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
@ -131,6 +140,7 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>((props, r
|
|||||||
aria-expanded={ariaExpanded}
|
aria-expanded={ariaExpanded}
|
||||||
aria-haspopup={ariaHaspopup}
|
aria-haspopup={ariaHaspopup}
|
||||||
aria-selected={ariaSelected}
|
aria-selected={ariaSelected}
|
||||||
|
aria-controls={ariaControls}
|
||||||
aria-busy={loading}
|
aria-busy={loading}
|
||||||
>
|
>
|
||||||
<ButtonContent loading={loading}>{children}</ButtonContent>
|
<ButtonContent loading={loading}>{children}</ButtonContent>
|
||||||
|
|||||||
@ -8,6 +8,7 @@ interface CardProps {
|
|||||||
className?: string
|
className?: string
|
||||||
onClick?: () => void
|
onClick?: () => void
|
||||||
'aria-label'?: string
|
'aria-label'?: string
|
||||||
|
role?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
function getVariantClasses(variant: CardVariant, hasOnClick: boolean): string {
|
function getVariantClasses(variant: CardVariant, hasOnClick: boolean): string {
|
||||||
@ -40,17 +41,19 @@ export function Card({
|
|||||||
className = '',
|
className = '',
|
||||||
onClick,
|
onClick,
|
||||||
'aria-label': ariaLabel,
|
'aria-label': ariaLabel,
|
||||||
|
role: roleProp,
|
||||||
}: CardProps): React.ReactElement {
|
}: CardProps): React.ReactElement {
|
||||||
const variantClasses = getVariantClasses(variant, onClick !== undefined)
|
const variantClasses = getVariantClasses(variant, onClick !== undefined)
|
||||||
const paddingClasses = getPaddingClasses(variant)
|
const paddingClasses = getPaddingClasses(variant)
|
||||||
const combinedClasses = `${variantClasses} ${paddingClasses} ${className}`.trim()
|
const combinedClasses = `${variantClasses} ${paddingClasses} ${className}`.trim()
|
||||||
|
const role = roleProp ?? (onClick !== undefined ? 'button' : undefined)
|
||||||
|
|
||||||
if (onClick) {
|
if (onClick) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className={combinedClasses}
|
className={combinedClasses}
|
||||||
role="button"
|
role={role}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === 'Enter' || e.key === ' ') {
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
@ -66,7 +69,7 @@ export function Card({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={combinedClasses} aria-label={ariaLabel}>
|
<div className={combinedClasses} role={role} aria-label={ariaLabel}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import type { ReactNode } from 'react'
|
import type { ReactNode } from 'react'
|
||||||
|
import { Card } from './Card'
|
||||||
|
|
||||||
interface ErrorStateProps {
|
interface ErrorStateProps {
|
||||||
message: string
|
message: string
|
||||||
@ -27,7 +28,7 @@ function ErrorIcon(): React.ReactElement {
|
|||||||
|
|
||||||
export function ErrorState({ message, action, className = '' }: ErrorStateProps): React.ReactElement {
|
export function ErrorState({ message, action, className = '' }: ErrorStateProps): React.ReactElement {
|
||||||
return (
|
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 items-start gap-3">
|
||||||
<div className="flex-shrink-0 text-red-400">
|
<div className="flex-shrink-0 text-red-400">
|
||||||
<ErrorIcon />
|
<ErrorIcon />
|
||||||
@ -37,6 +38,6 @@ export function ErrorState({ message, action, className = '' }: ErrorStateProps)
|
|||||||
{action && <div className="mt-3">{action}</div>}
|
{action && <div className="mt-3">{action}</div>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import type { ReactNode } from 'react'
|
import type { ReactNode } from 'react'
|
||||||
|
import { Button } from './Button'
|
||||||
|
|
||||||
interface MobileMenuProps {
|
interface MobileMenuProps {
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
@ -130,13 +131,15 @@ function MobileMenuContent({
|
|||||||
>
|
>
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<div className="flex justify-end mb-4">
|
<div className="flex justify-end mb-4">
|
||||||
<button
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
onClick={onClose}
|
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"
|
aria-label="Close menu"
|
||||||
>
|
>
|
||||||
×
|
×
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-4">{children}</div>
|
<div className="space-y-4">{children}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { useEffect, useRef } from 'react'
|
import { useEffect, useRef } from 'react'
|
||||||
import type { ReactNode } from 'react'
|
import type { ReactNode } from 'react'
|
||||||
|
import { Button } from './Button'
|
||||||
|
|
||||||
interface ModalProps {
|
interface ModalProps {
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
@ -28,13 +29,15 @@ function getSizeClasses(size: ModalProps['size']): string {
|
|||||||
|
|
||||||
function CloseButton({ onClose }: { onClose: () => void }): React.ReactElement {
|
function CloseButton({ onClose }: { onClose: () => void }): React.ReactElement {
|
||||||
return (
|
return (
|
||||||
<button
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
onClick={onClose}
|
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"
|
aria-label="Close modal"
|
||||||
>
|
>
|
||||||
×
|
×
|
||||||
</button>
|
</Button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import type { ReactNode } from 'react'
|
import type { ReactNode } from 'react'
|
||||||
|
import { Button } from './Button'
|
||||||
|
|
||||||
export type ToastVariant = 'info' | 'success' | 'warning' | 'error'
|
export type ToastVariant = 'info' | 'success' | 'warning' | 'error'
|
||||||
|
|
||||||
@ -55,13 +56,15 @@ export function Toast({
|
|||||||
aria-label={ariaLabel}
|
aria-label={ariaLabel}
|
||||||
>
|
>
|
||||||
<div className="flex-1">{children}</div>
|
<div className="flex-1">{children}</div>
|
||||||
<button
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
onClick={onClose}
|
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"
|
aria-label="Close notification"
|
||||||
>
|
>
|
||||||
×
|
×
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -87,7 +87,16 @@ Aucun composant prioritaire restant. Tous les composants principaux ont été mi
|
|||||||
- ✅ `NotificationPanel.tsx` - Migration du conteneur principal vers Card
|
- ✅ `NotificationPanel.tsx` - Migration du conteneur principal vers Card
|
||||||
- ✅ `AuthorFilterButton.tsx` - Migration vers Button avec support forwardRef
|
- ✅ `AuthorFilterButton.tsx` - Migration vers Button avec support forwardRef
|
||||||
- ✅ `AuthorFilterDropdown.tsx` - Migration de AuthorOption et AllAuthorsOption vers Button
|
- ✅ `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
|
## Erreurs corrigées
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user