Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions lang/en/common.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,17 @@
'per_page' => 'Rows per page',
'page' => 'Page',
],
'editor' => [
'bold' => 'Bold',
'italic' => 'Italic',
'heading1' => 'Heading 1',
'heading2' => 'Heading 2',
'bullet_list' => 'Bulleted list',
'numbered_list' => 'Numbered list',
'align_left' => 'Align left',
'align_center' => 'Center',
'align_right' => 'Align right',
'undo' => 'Undo',
'redo' => 'Redo',
],
];
13 changes: 13 additions & 0 deletions lang/fr/common.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,17 @@
'per_page' => 'Lignes par page',
'page' => 'Page',
],
'editor' => [
'bold' => 'Gras',
'italic' => 'Italique',
'heading1' => 'Titre 1',
'heading2' => 'Titre 2',
'bullet_list' => 'Liste à puces',
'numbered_list' => 'Liste numérotée',
'align_left' => 'Aligner à gauche',
'align_center' => 'Centrer',
'align_right' => 'Aligner à droite',
'undo' => 'Annuler',
'redo' => 'Rétablir',
],
];
45 changes: 24 additions & 21 deletions resources/js/components/error-art.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
* utilitaires Tailwind fill et stroke). Une illustration par statut HTTP.
*/

import { useTranslation } from '@/helpers/i18n';

const wrap = 'w-full max-w-[240px] mx-auto h-auto';

function NotFound() {
function NotFound({ label }: Readonly<{ label: string }>) {
return (
<svg viewBox="0 0 220 170" className={wrap} role="img" aria-label="Page introuvable" fill="none">
<svg viewBox="0 0 220 170" className={wrap} role="img" aria-label={label} fill="none">
<ellipse cx="110" cy="150" rx="72" ry="10" className="fill-blue-100 dark:fill-blue-950/50" />
<circle cx="104" cy="70" r="54" className="fill-blue-50 dark:fill-blue-950/40" />
<circle cx="96" cy="66" r="34" className="fill-white dark:fill-[#161615] stroke-blue-500 dark:stroke-blue-400" strokeWidth="7" />
Expand All @@ -17,9 +19,9 @@ function NotFound() {
);
}

function Forbidden() {
function Forbidden({ label }: Readonly<{ label: string }>) {
return (
<svg viewBox="0 0 220 170" className={wrap} role="img" aria-label="Accès refusé" fill="none">
<svg viewBox="0 0 220 170" className={wrap} role="img" aria-label={label} fill="none">
<ellipse cx="110" cy="150" rx="72" ry="10" className="fill-amber-100 dark:fill-amber-950/50" />
<circle cx="110" cy="70" r="54" className="fill-amber-50 dark:fill-amber-950/40" />
<path d="M92 66 v-13 a18 18 0 0 1 36 0 v13" className="fill-none stroke-amber-500 dark:stroke-amber-400" strokeWidth="8" strokeLinecap="round" />
Expand All @@ -30,9 +32,9 @@ function Forbidden() {
);
}

function Expired() {
function Expired({ label }: Readonly<{ label: string }>) {
return (
<svg viewBox="0 0 220 170" className={wrap} role="img" aria-label="Session expirée" fill="none">
<svg viewBox="0 0 220 170" className={wrap} role="img" aria-label={label} fill="none">
<ellipse cx="110" cy="150" rx="72" ry="10" className="fill-violet-100 dark:fill-violet-950/50" />
<circle cx="110" cy="72" r="54" className="fill-violet-50 dark:fill-violet-950/40" />
<circle cx="110" cy="72" r="40" className="fill-white dark:fill-[#161615] stroke-violet-500 dark:stroke-violet-400" strokeWidth="7" />
Expand All @@ -43,9 +45,9 @@ function Expired() {
);
}

function TooMany() {
function TooMany({ label }: Readonly<{ label: string }>) {
return (
<svg viewBox="0 0 220 170" className={wrap} role="img" aria-label="Trop de requêtes" fill="none">
<svg viewBox="0 0 220 170" className={wrap} role="img" aria-label={label} fill="none">
<ellipse cx="110" cy="150" rx="72" ry="10" className="fill-orange-100 dark:fill-orange-950/50" />
<circle cx="110" cy="72" r="54" className="fill-orange-50 dark:fill-orange-950/40" />
<line x1="70" y1="66" x2="150" y2="66" className="stroke-orange-300 dark:stroke-orange-800" strokeWidth="3" strokeDasharray="4 6" />
Expand All @@ -56,9 +58,9 @@ function TooMany() {
);
}

function ServerError() {
function ServerError({ label }: Readonly<{ label: string }>) {
return (
<svg viewBox="0 0 220 170" className={wrap} role="img" aria-label="Erreur serveur" fill="none">
<svg viewBox="0 0 220 170" className={wrap} role="img" aria-label={label} fill="none">
<ellipse cx="110" cy="150" rx="72" ry="10" className="fill-red-100 dark:fill-red-950/50" />
<circle cx="104" cy="72" r="54" className="fill-red-50 dark:fill-red-950/40" />
<rect x="70" y="44" width="76" height="34" rx="7" className="fill-white dark:fill-[#161615] stroke-red-500 dark:stroke-red-400" strokeWidth="5" />
Expand All @@ -74,9 +76,9 @@ function ServerError() {
);
}

function Maintenance() {
function Maintenance({ label }: Readonly<{ label: string }>) {
return (
<svg viewBox="0 0 220 170" className={wrap} role="img" aria-label="Maintenance" fill="none">
<svg viewBox="0 0 220 170" className={wrap} role="img" aria-label={label} fill="none">
<ellipse cx="110" cy="150" rx="72" ry="10" className="fill-teal-100 dark:fill-teal-950/50" />
<circle cx="110" cy="72" r="54" className="fill-teal-50 dark:fill-teal-950/40" />
<g className="fill-teal-500 dark:fill-teal-400">
Expand All @@ -90,9 +92,9 @@ function Maintenance() {
);
}

function Generic() {
function Generic({ label }: Readonly<{ label: string }>) {
return (
<svg viewBox="0 0 220 170" className={wrap} role="img" aria-label="Erreur" fill="none">
<svg viewBox="0 0 220 170" className={wrap} role="img" aria-label={label} fill="none">
<ellipse cx="110" cy="150" rx="72" ry="10" className="fill-gray-200 dark:fill-gray-800" />
<circle cx="110" cy="72" r="54" className="fill-gray-100 dark:fill-gray-800/60" />
<path d="M110 38 l40 70 h-80 z" className="fill-none stroke-gray-500 dark:stroke-gray-400" strokeWidth="7" strokeLinejoin="round" />
Expand All @@ -103,13 +105,14 @@ function Generic() {
}

export function ErrorArt({ status }: Readonly<{ status: number }>) {
const { t } = useTranslation();
switch (status) {
case 403: return <Forbidden />;
case 404: return <NotFound />;
case 419: return <Expired />;
case 429: return <TooMany />;
case 500: return <ServerError />;
case 503: return <Maintenance />;
default: return <Generic />;
case 403: return <Forbidden label={t('errors.403.title')} />;
case 404: return <NotFound label={t('errors.404.title')} />;
case 419: return <Expired label={t('errors.419.title')} />;
case 429: return <TooMany label={t('errors.429.title')} />;
case 500: return <ServerError label={t('errors.500.title')} />;
case 503: return <Maintenance label={t('errors.503.title')} />;
default: return <Generic label={t('errors.fallback.title')} />;
}
}
25 changes: 14 additions & 11 deletions resources/js/components/rich-text-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
Italic, List, ListOrdered, Redo, Undo,
} from 'lucide-react';
import { useEffect } from 'react';
import { useTranslation } from '@/helpers/i18n';

interface Props {
value: string;
Expand All @@ -32,6 +33,8 @@ export function RichTextEditor({ value, onChange, onReady }: Readonly<Props>) {
// cette directive désactive sa mémoïsation pour ce composant.
'use no memo';

const { t } = useTranslation();

const editor = useEditor({
extensions: [
StarterKit,
Expand Down Expand Up @@ -65,21 +68,21 @@ export function RichTextEditor({ value, onChange, onReady }: Readonly<Props>) {
return (
<div className="rounded-xl ring-1 ring-gray-200 bg-white overflow-hidden">
<div className="flex flex-wrap items-center gap-1 border-b border-gray-100 bg-gray-50/60 px-2 py-1.5">
<ToolbarButton title="Gras" active={editor.isActive('bold')} onClick={() => editor.chain().focus().toggleBold().run()}><Bold className="w-4 h-4" /></ToolbarButton>
<ToolbarButton title="Italique" active={editor.isActive('italic')} onClick={() => editor.chain().focus().toggleItalic().run()}><Italic className="w-4 h-4" /></ToolbarButton>
<ToolbarButton title={t('common.editor.bold')} active={editor.isActive('bold')} onClick={() => editor.chain().focus().toggleBold().run()}><Bold className="w-4 h-4" /></ToolbarButton>
<ToolbarButton title={t('common.editor.italic')} active={editor.isActive('italic')} onClick={() => editor.chain().focus().toggleItalic().run()}><Italic className="w-4 h-4" /></ToolbarButton>
<div className="w-px h-5 bg-gray-200 mx-1" />
<ToolbarButton title="Titre 1" active={editor.isActive('heading', { level: 1 })} onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}><Heading1 className="w-4 h-4" /></ToolbarButton>
<ToolbarButton title="Titre 2" active={editor.isActive('heading', { level: 2 })} onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}><Heading2 className="w-4 h-4" /></ToolbarButton>
<ToolbarButton title={t('common.editor.heading1')} active={editor.isActive('heading', { level: 1 })} onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}><Heading1 className="w-4 h-4" /></ToolbarButton>
<ToolbarButton title={t('common.editor.heading2')} active={editor.isActive('heading', { level: 2 })} onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}><Heading2 className="w-4 h-4" /></ToolbarButton>
<div className="w-px h-5 bg-gray-200 mx-1" />
<ToolbarButton title="Liste à puces" active={editor.isActive('bulletList')} onClick={() => editor.chain().focus().toggleBulletList().run()}><List className="w-4 h-4" /></ToolbarButton>
<ToolbarButton title="Liste numérotée" active={editor.isActive('orderedList')} onClick={() => editor.chain().focus().toggleOrderedList().run()}><ListOrdered className="w-4 h-4" /></ToolbarButton>
<ToolbarButton title={t('common.editor.bullet_list')} active={editor.isActive('bulletList')} onClick={() => editor.chain().focus().toggleBulletList().run()}><List className="w-4 h-4" /></ToolbarButton>
<ToolbarButton title={t('common.editor.numbered_list')} active={editor.isActive('orderedList')} onClick={() => editor.chain().focus().toggleOrderedList().run()}><ListOrdered className="w-4 h-4" /></ToolbarButton>
<div className="w-px h-5 bg-gray-200 mx-1" />
<ToolbarButton title="Aligner à gauche" active={editor.isActive({ textAlign: 'left' })} onClick={() => editor.chain().focus().setTextAlign('left').run()}><AlignLeft className="w-4 h-4" /></ToolbarButton>
<ToolbarButton title="Centrer" active={editor.isActive({ textAlign: 'center' })} onClick={() => editor.chain().focus().setTextAlign('center').run()}><AlignCenter className="w-4 h-4" /></ToolbarButton>
<ToolbarButton title="Aligner à droite" active={editor.isActive({ textAlign: 'right' })} onClick={() => editor.chain().focus().setTextAlign('right').run()}><AlignRight className="w-4 h-4" /></ToolbarButton>
<ToolbarButton title={t('common.editor.align_left')} active={editor.isActive({ textAlign: 'left' })} onClick={() => editor.chain().focus().setTextAlign('left').run()}><AlignLeft className="w-4 h-4" /></ToolbarButton>
<ToolbarButton title={t('common.editor.align_center')} active={editor.isActive({ textAlign: 'center' })} onClick={() => editor.chain().focus().setTextAlign('center').run()}><AlignCenter className="w-4 h-4" /></ToolbarButton>
<ToolbarButton title={t('common.editor.align_right')} active={editor.isActive({ textAlign: 'right' })} onClick={() => editor.chain().focus().setTextAlign('right').run()}><AlignRight className="w-4 h-4" /></ToolbarButton>
<div className="w-px h-5 bg-gray-200 mx-1" />
<ToolbarButton title="Annuler" onClick={() => editor.chain().focus().undo().run()}><Undo className="w-4 h-4" /></ToolbarButton>
<ToolbarButton title="Rétablir" onClick={() => editor.chain().focus().redo().run()}><Redo className="w-4 h-4" /></ToolbarButton>
<ToolbarButton title={t('common.editor.undo')} onClick={() => editor.chain().focus().undo().run()}><Undo className="w-4 h-4" /></ToolbarButton>
<ToolbarButton title={t('common.editor.redo')} onClick={() => editor.chain().focus().redo().run()}><Redo className="w-4 h-4" /></ToolbarButton>
</div>
<EditorContent editor={editor} />
</div>
Expand Down
Loading