mirror of
https://github.com/suitenumerique/docs.git
synced 2026-08-17 21:25:43 +02:00
♻️(frontend) extract reusable presenter slide content component
Pull the BlockNote rendering out of PresenterSlide into PresenterSlideContent so it can be reused by the title slide and the PDF export. Add a PresenterSlideData union (title | content) and a shared PresenterBlock type.
This commit is contained in:
+4
-40
@@ -1,11 +1,8 @@
|
||||
import { BlockNoteView } from '@blocknote/mantine';
|
||||
import { useCreateBlockNote } from '@blocknote/react';
|
||||
import { RefObject, useEffect, useRef } from 'react';
|
||||
import { RefObject, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { css } from 'styled-components';
|
||||
|
||||
import { Box } from '@/components';
|
||||
import { blockNoteSchema } from '@/docs/doc-editor/components/BlockNoteEditor';
|
||||
|
||||
import {
|
||||
PRESENTER_FRAME_PADDING_Y,
|
||||
@@ -14,6 +11,8 @@ import {
|
||||
} from '../constants';
|
||||
import { useFitScale } from '../hooks/useFitScale';
|
||||
|
||||
import { PresenterSlideContent } from './PresenterSlideContent';
|
||||
|
||||
interface PresenterSlideProps {
|
||||
blocks: unknown[];
|
||||
frameRef: RefObject<HTMLDivElement | null>;
|
||||
@@ -47,12 +46,6 @@ const outerCss = css`
|
||||
overflow-x: hidden;
|
||||
background: white;
|
||||
transition: opacity ${PRESENTER_SLIDE_FADE_MS}ms ease;
|
||||
/* Hide editor chrome that may leak through despite editable={false} */
|
||||
.bn-side-menu,
|
||||
.bn-formatting-toolbar,
|
||||
.bn-slash-menu {
|
||||
display: none !important;
|
||||
}
|
||||
`;
|
||||
|
||||
// The stage absorbs the un-scaled inner's layout box. Its explicit height
|
||||
@@ -85,28 +78,6 @@ export const PresenterSlide = ({
|
||||
}: PresenterSlideProps) => {
|
||||
const { t } = useTranslation();
|
||||
const innerRef = useRef<HTMLDivElement>(null);
|
||||
const editor = useCreateBlockNote({
|
||||
initialContent:
|
||||
// BlockNote rejects an empty initialContent array — fall back to one empty paragraph.
|
||||
blocks.length > 0
|
||||
? (blocks as NonNullable<
|
||||
Parameters<typeof useCreateBlockNote>[0]
|
||||
>['initialContent'])
|
||||
: undefined,
|
||||
schema: blockNoteSchema,
|
||||
});
|
||||
|
||||
// ProseMirror adds role="textbox" and contenteditable on its root even
|
||||
// when editable=false, making the SR announce "editing, autocomplete" on
|
||||
// focus. Strip those attributes so the slide reads as plain content.
|
||||
useEffect(() => {
|
||||
const pm = innerRef.current?.querySelector('.ProseMirror');
|
||||
if (pm) {
|
||||
pm.removeAttribute('role');
|
||||
pm.removeAttribute('contenteditable');
|
||||
pm.setAttribute('tabindex', '-1');
|
||||
}
|
||||
}, []);
|
||||
|
||||
const fit = useFitScale(innerRef, frameRef);
|
||||
|
||||
@@ -136,14 +107,7 @@ export const PresenterSlide = ({
|
||||
>
|
||||
<Box $css={stageCss} style={stageStyle}>
|
||||
<Box ref={innerRef} $css={innerCss} style={innerStyle}>
|
||||
<BlockNoteView
|
||||
editor={editor}
|
||||
editable={false}
|
||||
theme="light"
|
||||
formattingToolbar={false}
|
||||
slashMenu={false}
|
||||
comments={false}
|
||||
/>
|
||||
<PresenterSlideContent blocks={blocks} />
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
import { BlockNoteView } from '@blocknote/mantine';
|
||||
import { useCreateBlockNote } from '@blocknote/react';
|
||||
import { CSSProperties, Ref, useEffect, useRef } from 'react';
|
||||
import { css } from 'styled-components';
|
||||
|
||||
import { Box } from '@/components';
|
||||
import { blockNoteSchema } from '@/docs/doc-editor/components/BlockNoteEditor';
|
||||
|
||||
import type { PresenterBlock } from '../types';
|
||||
|
||||
interface PresenterSlideContentProps {
|
||||
blocks: PresenterBlock[];
|
||||
className?: string;
|
||||
innerRef?: Ref<HTMLDivElement>;
|
||||
style?: CSSProperties;
|
||||
}
|
||||
|
||||
const slideContentCss = css`
|
||||
.bn-side-menu,
|
||||
.bn-formatting-toolbar,
|
||||
.bn-slash-menu {
|
||||
display: none !important;
|
||||
}
|
||||
`;
|
||||
|
||||
const setRefValue = (
|
||||
ref: Ref<HTMLDivElement> | undefined,
|
||||
node: HTMLDivElement | null,
|
||||
) => {
|
||||
if (!ref) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof ref === 'function') {
|
||||
ref(node);
|
||||
return;
|
||||
}
|
||||
|
||||
(ref as { current: HTMLDivElement | null }).current = node;
|
||||
};
|
||||
|
||||
export const PresenterSlideContent = ({
|
||||
blocks,
|
||||
className,
|
||||
innerRef,
|
||||
style,
|
||||
}: PresenterSlideContentProps) => {
|
||||
const contentRef = useRef<HTMLDivElement | null>(null);
|
||||
const editor = useCreateBlockNote({
|
||||
initialContent:
|
||||
// BlockNote rejects an empty initialContent array — fall back to one empty paragraph.
|
||||
blocks.length > 0
|
||||
? (blocks as NonNullable<
|
||||
Parameters<typeof useCreateBlockNote>[0]
|
||||
>['initialContent'])
|
||||
: undefined,
|
||||
schema: blockNoteSchema,
|
||||
});
|
||||
|
||||
// Even with `editable={false}`, BlockNote/ProseMirror currently still renders
|
||||
// the editor node with `role="textbox"` and `contenteditable`. For presenter
|
||||
// slides that is wrong - the content is presentation, not an editable field -
|
||||
// and it pollutes the accessibility tree. Strip those, but keep the content
|
||||
// tabbable so keyboard users can reach and scroll long slides. (Observed
|
||||
// BlockNote behaviour, not a guarantee; revisit on upgrades.)
|
||||
useEffect(() => {
|
||||
const pm = contentRef.current?.querySelector('.ProseMirror');
|
||||
if (pm) {
|
||||
pm.removeAttribute('role');
|
||||
pm.removeAttribute('contenteditable');
|
||||
pm.setAttribute('tabindex', '0');
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box
|
||||
ref={(node: HTMLDivElement | null) => {
|
||||
contentRef.current = node;
|
||||
setRefValue(innerRef, node);
|
||||
}}
|
||||
className={className}
|
||||
$css={slideContentCss}
|
||||
style={style}
|
||||
>
|
||||
<BlockNoteView
|
||||
editor={editor}
|
||||
editable={false}
|
||||
theme="light"
|
||||
formattingToolbar={false}
|
||||
slashMenu={false}
|
||||
comments={false}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user