mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-07-25 01:15:26 +10:00
Feature: Implement full-screen mode for rich text editor (#2658)
This commit is contained in:
@@ -2,6 +2,8 @@ import { t } from "@lingui/core/macro";
|
||||
import { useLingui } from "@lingui/react";
|
||||
import { Trans } from "@lingui/react/macro";
|
||||
import {
|
||||
ArrowsInSimpleIcon,
|
||||
ArrowsOutSimpleIcon,
|
||||
CodeBlockIcon,
|
||||
CodeSimpleIcon,
|
||||
ColumnsPlusLeftIcon,
|
||||
@@ -48,11 +50,13 @@ import {
|
||||
useEditorState,
|
||||
} from "@tiptap/react";
|
||||
import StarterKit from "@tiptap/starter-kit";
|
||||
import { useMemo } from "react";
|
||||
import { VisuallyHidden } from "radix-ui";
|
||||
import { useMemo, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { match } from "ts-pattern";
|
||||
import z from "zod";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Dialog, DialogContent, DialogDescription, DialogTitle } from "@/components/ui/dialog";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
@@ -103,6 +107,7 @@ type Props = UseEditorOptions & {
|
||||
export function RichInput({ value, onChange, style, className, editorClassName, ...options }: Props) {
|
||||
const { i18n } = useLingui();
|
||||
const textDirection = isRTL(i18n.locale) ? "rtl" : undefined;
|
||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||
|
||||
const editor = useEditor({
|
||||
...options,
|
||||
@@ -115,11 +120,13 @@ export function RichInput({ value, onChange, style, className, editorClassName,
|
||||
attributes: {
|
||||
spellcheck: "false",
|
||||
"data-editor": "true",
|
||||
"data-fullscreen": isFullscreen ? "true" : "false",
|
||||
class: cn(
|
||||
"group/editor",
|
||||
"max-h-[400px] min-h-[100px] overflow-y-auto p-3 pb-0",
|
||||
"group/editor overflow-y-auto p-3 pb-4",
|
||||
"rounded-md rounded-t-none border outline-none focus-visible:border-ring",
|
||||
"[td:has(.selectedCell)]:bg-primary",
|
||||
"data-[fullscreen=false]:max-h-[400px] data-[fullscreen=false]:min-h-[100px]",
|
||||
"data-[fullscreen=true]:max-h-none data-[fullscreen=true]:min-h-full",
|
||||
styles.tiptap_content,
|
||||
styles.editor_content,
|
||||
editorClassName,
|
||||
@@ -135,17 +142,55 @@ export function RichInput({ value, onChange, style, className, editorClassName,
|
||||
|
||||
if (!editor) return null;
|
||||
|
||||
const editorElement = (
|
||||
<div className="relative">
|
||||
<EditorToolbar editor={editor} isFullscreen={isFullscreen} />
|
||||
|
||||
<EditorContent editor={editor} />
|
||||
|
||||
<Button
|
||||
size="icon"
|
||||
variant="secondary"
|
||||
className="absolute right-2 bottom-2 size-7"
|
||||
title={isFullscreen ? t`Exit Fullscreen` : t`Fullscreen`}
|
||||
onClick={() => setIsFullscreen(!isFullscreen)}
|
||||
>
|
||||
{isFullscreen ? <ArrowsInSimpleIcon className="size-4" /> : <ArrowsOutSimpleIcon className="size-4" />}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (isFullscreen) {
|
||||
return (
|
||||
<EditorContext value={providerValue}>
|
||||
<div className={cn("rounded-md", className)} style={style}>
|
||||
{/* Placeholder to maintain layout */}
|
||||
<div className="h-[200px] rounded-md border border-dashed" />
|
||||
</div>
|
||||
|
||||
<Dialog open={isFullscreen} onOpenChange={setIsFullscreen}>
|
||||
<DialogContent className="flex h-[95svh] max-h-none! w-[95svw] max-w-none! flex-col p-4 sm:max-w-none! 2xl:max-w-none!">
|
||||
<VisuallyHidden.Root>
|
||||
<DialogTitle>Fullscreen Editor</DialogTitle>
|
||||
<DialogDescription>Edit content in fullscreen mode</DialogDescription>
|
||||
</VisuallyHidden.Root>
|
||||
{editorElement}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</EditorContext>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EditorContext value={providerValue}>
|
||||
<div className={cn("rounded-md", className)} style={style}>
|
||||
<EditorToolbar editor={editor} />
|
||||
<EditorContent editor={editor} />
|
||||
{editorElement}
|
||||
</div>
|
||||
</EditorContext>
|
||||
);
|
||||
}
|
||||
|
||||
function EditorToolbar({ editor }: { editor: Editor }) {
|
||||
function EditorToolbar({ editor, isFullscreen }: { editor: Editor; isFullscreen: boolean }) {
|
||||
const prompt = usePrompt();
|
||||
|
||||
const state = useEditorState({
|
||||
@@ -313,7 +358,7 @@ function EditorToolbar({ editor }: { editor: Editor }) {
|
||||
return (
|
||||
<div className="flex flex-wrap items-center gap-y-0.5 rounded-md rounded-b-none border border-b-0">
|
||||
<Toggle
|
||||
size="sm"
|
||||
size={isFullscreen ? "lg" : "sm"}
|
||||
tabIndex={-1}
|
||||
className="rounded-none"
|
||||
title={t`Bold`}
|
||||
@@ -325,7 +370,7 @@ function EditorToolbar({ editor }: { editor: Editor }) {
|
||||
</Toggle>
|
||||
|
||||
<Toggle
|
||||
size="sm"
|
||||
size={isFullscreen ? "lg" : "sm"}
|
||||
tabIndex={-1}
|
||||
className="rounded-none"
|
||||
title={t`Italic`}
|
||||
@@ -337,7 +382,7 @@ function EditorToolbar({ editor }: { editor: Editor }) {
|
||||
</Toggle>
|
||||
|
||||
<Toggle
|
||||
size="sm"
|
||||
size={isFullscreen ? "lg" : "sm"}
|
||||
tabIndex={-1}
|
||||
className="rounded-none"
|
||||
title={t`Underline`}
|
||||
@@ -349,7 +394,7 @@ function EditorToolbar({ editor }: { editor: Editor }) {
|
||||
</Toggle>
|
||||
|
||||
<Toggle
|
||||
size="sm"
|
||||
size={isFullscreen ? "lg" : "sm"}
|
||||
tabIndex={-1}
|
||||
className="rounded-none"
|
||||
title={t`Strike`}
|
||||
@@ -361,7 +406,7 @@ function EditorToolbar({ editor }: { editor: Editor }) {
|
||||
</Toggle>
|
||||
|
||||
<Toggle
|
||||
size="sm"
|
||||
size={isFullscreen ? "lg" : "sm"}
|
||||
tabIndex={-1}
|
||||
className="rounded-none"
|
||||
title={t`Highlight`}
|
||||
@@ -376,7 +421,7 @@ function EditorToolbar({ editor }: { editor: Editor }) {
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button size="sm" tabIndex={-1} variant="ghost" className="rounded-none">
|
||||
<Button size={isFullscreen ? "lg" : "sm"} tabIndex={-1} variant="ghost" className="rounded-none">
|
||||
{match(state)
|
||||
.with({ isParagraph: true }, () => <ParagraphIcon className="size-3.5" />)
|
||||
.with({ isHeading1: true }, () => <TextHOneIcon className="size-3.5" />)
|
||||
@@ -446,7 +491,7 @@ function EditorToolbar({ editor }: { editor: Editor }) {
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button size="sm" tabIndex={-1} variant="ghost" className="rounded-none">
|
||||
<Button size={isFullscreen ? "lg" : "sm"} tabIndex={-1} variant="ghost" className="rounded-none">
|
||||
{match(state)
|
||||
.with({ isLeftAlign: true }, () => <TextAlignLeftIcon className="size-3.5" />)
|
||||
.with({ isCenterAlign: true }, () => <TextAlignCenterIcon className="size-3.5" />)
|
||||
@@ -492,7 +537,7 @@ function EditorToolbar({ editor }: { editor: Editor }) {
|
||||
<div className="mx-1 h-5 w-px bg-border" />
|
||||
|
||||
<Toggle
|
||||
size="sm"
|
||||
size={isFullscreen ? "lg" : "sm"}
|
||||
tabIndex={-1}
|
||||
className="rounded-none"
|
||||
title={t`Bullet List`}
|
||||
@@ -504,7 +549,7 @@ function EditorToolbar({ editor }: { editor: Editor }) {
|
||||
</Toggle>
|
||||
|
||||
<Toggle
|
||||
size="sm"
|
||||
size={isFullscreen ? "lg" : "sm"}
|
||||
tabIndex={-1}
|
||||
className="rounded-none"
|
||||
title={t`Ordered List`}
|
||||
@@ -516,7 +561,7 @@ function EditorToolbar({ editor }: { editor: Editor }) {
|
||||
</Toggle>
|
||||
|
||||
<Button
|
||||
size="sm"
|
||||
size={isFullscreen ? "lg" : "sm"}
|
||||
tabIndex={-1}
|
||||
variant="ghost"
|
||||
className="rounded-none"
|
||||
@@ -527,7 +572,7 @@ function EditorToolbar({ editor }: { editor: Editor }) {
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
size="sm"
|
||||
size={isFullscreen ? "lg" : "sm"}
|
||||
tabIndex={-1}
|
||||
variant="ghost"
|
||||
className="rounded-none"
|
||||
@@ -540,17 +585,29 @@ function EditorToolbar({ editor }: { editor: Editor }) {
|
||||
<div className="mx-1 h-5 w-px bg-border" />
|
||||
|
||||
{state.isLink ? (
|
||||
<Button size="sm" tabIndex={-1} variant="ghost" className="rounded-none" onClick={state.unsetLink}>
|
||||
<Button
|
||||
size={isFullscreen ? "lg" : "sm"}
|
||||
tabIndex={-1}
|
||||
variant="ghost"
|
||||
className="rounded-none"
|
||||
onClick={state.unsetLink}
|
||||
>
|
||||
<LinkBreakIcon className="size-3.5" />
|
||||
</Button>
|
||||
) : (
|
||||
<Button size="sm" tabIndex={-1} variant="ghost" className="rounded-none" onClick={state.setLink}>
|
||||
<Button
|
||||
size={isFullscreen ? "lg" : "sm"}
|
||||
tabIndex={-1}
|
||||
variant="ghost"
|
||||
className="rounded-none"
|
||||
onClick={state.setLink}
|
||||
>
|
||||
<LinkIcon className="size-3.5" />
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Toggle
|
||||
size="sm"
|
||||
size={isFullscreen ? "lg" : "sm"}
|
||||
tabIndex={-1}
|
||||
className="rounded-none"
|
||||
title={t`Inline Code`}
|
||||
@@ -562,7 +619,7 @@ function EditorToolbar({ editor }: { editor: Editor }) {
|
||||
</Toggle>
|
||||
|
||||
<Toggle
|
||||
size="sm"
|
||||
size={isFullscreen ? "lg" : "sm"}
|
||||
tabIndex={-1}
|
||||
className="rounded-none"
|
||||
title={t`Code Block`}
|
||||
@@ -575,7 +632,13 @@ function EditorToolbar({ editor }: { editor: Editor }) {
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button size="sm" tabIndex={-1} variant="ghost" className="rounded-none" title={t`Table`}>
|
||||
<Button
|
||||
size={isFullscreen ? "lg" : "sm"}
|
||||
tabIndex={-1}
|
||||
variant="ghost"
|
||||
className="rounded-none"
|
||||
title={t`Table`}
|
||||
>
|
||||
<TableIcon className="size-3.5" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
@@ -621,7 +684,7 @@ function EditorToolbar({ editor }: { editor: Editor }) {
|
||||
</DropdownMenu>
|
||||
|
||||
<Button
|
||||
size="sm"
|
||||
size={isFullscreen ? "lg" : "sm"}
|
||||
tabIndex={-1}
|
||||
variant="ghost"
|
||||
className="rounded-none"
|
||||
@@ -632,7 +695,7 @@ function EditorToolbar({ editor }: { editor: Editor }) {
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
size="sm"
|
||||
size={isFullscreen ? "lg" : "sm"}
|
||||
tabIndex={-1}
|
||||
variant="ghost"
|
||||
className="rounded-none"
|
||||
|
||||
Reference in New Issue
Block a user