Line,
Menu,
PopOut,
+ Scroll,
Text,
Tooltip,
TooltipProvider,
} from 'folds';
import React, { ReactNode, useState } from 'react';
import { ReactEditor, useSlate } from 'slate-react';
-import { isBlockActive, isMarkActive, toggleBlock, toggleMark } from './common';
+import {
+ isAnyMarkActive,
+ isBlockActive,
+ isMarkActive,
+ removeAllMark,
+ toggleBlock,
+ toggleMark,
+} from './common';
import * as css from './Editor.css';
import { BlockType, MarkType } from './Elements';
import { HeadingLevel } from './slate';
type MarkButtonProps = { format: MarkType; icon: IconSrc; tooltip: ReactNode };
export function MarkButton({ format, icon, tooltip }: MarkButtonProps) {
const editor = useSlate();
+ const disableInline = isBlockActive(editor, BlockType.CodeBlock);
+
+ if (disableInline) {
+ removeAllMark(editor);
+ }
const handleClick = () => {
toggleMark(editor, format);
variant="SurfaceVariant"
onClick={handleClick}
aria-pressed={isMarkActive(editor, format)}
- size="300"
+ size="400"
radii="300"
+ disabled={disableInline}
>
- <Icon size="50" src={icon} />
+ <Icon size="200" src={icon} />
</IconButton>
)}
</TooltipProvider>
variant="SurfaceVariant"
onClick={handleClick}
aria-pressed={isBlockActive(editor, format)}
- size="300"
+ size="400"
radii="300"
>
- <Icon size="50" src={icon} />
+ <Icon size="200" src={icon} />
</IconButton>
)}
</TooltipProvider>
return (
<PopOut
open={open}
+ offset={5}
align="Start"
position="Top"
content={
>
<Menu style={{ padding: config.space.S100 }}>
<Box gap="100">
- <IconButton onClick={() => handleMenuSelect(1)} size="300" radii="300">
- <Icon size="100" src={Icons.Heading1} />
+ <IconButton onClick={() => handleMenuSelect(1)} size="400" radii="300">
+ <Icon size="200" src={Icons.Heading1} />
</IconButton>
- <IconButton onClick={() => handleMenuSelect(2)} size="300" radii="300">
- <Icon size="100" src={Icons.Heading2} />
+ <IconButton onClick={() => handleMenuSelect(2)} size="400" radii="300">
+ <Icon size="200" src={Icons.Heading2} />
</IconButton>
- <IconButton onClick={() => handleMenuSelect(3)} size="300" radii="300">
- <Icon size="100" src={Icons.Heading3} />
+ <IconButton onClick={() => handleMenuSelect(3)} size="400" radii="300">
+ <Icon size="200" src={Icons.Heading3} />
</IconButton>
</Box>
</Menu>
variant="SurfaceVariant"
onClick={() => (isActive ? toggleBlock(editor, BlockType.Heading) : setOpen(!open))}
aria-pressed={isActive}
- size="300"
+ size="400"
radii="300"
>
- <Icon size="50" src={Icons[`Heading${level}`]} />
- <Icon size="50" src={isActive ? Icons.Cross : Icons.ChevronBottom} />
+ <Icon size="200" src={Icons[`Heading${level}`]} />
+ <Icon size="200" src={isActive ? Icons.Cross : Icons.ChevronBottom} />
</IconButton>
)}
</PopOut>
);
}
+type ExitFormattingProps = { tooltip: ReactNode };
+export function ExitFormatting({ tooltip }: ExitFormattingProps) {
+ const editor = useSlate();
+
+ const handleClick = () => {
+ if (isAnyMarkActive(editor)) {
+ removeAllMark(editor);
+ } else if (!isBlockActive(editor, BlockType.Paragraph)) {
+ toggleBlock(editor, BlockType.Paragraph);
+ }
+ ReactEditor.focus(editor);
+ };
+
+ return (
+ <TooltipProvider tooltip={tooltip} delay={500}>
+ {(triggerRef) => (
+ <IconButton
+ ref={triggerRef}
+ variant="SurfaceVariant"
+ onClick={handleClick}
+ size="400"
+ radii="300"
+ >
+ <Text size="B400">{`Exit ${KeySymbol.Hyper}`}</Text>
+ </IconButton>
+ )}
+ </TooltipProvider>
+ );
+}
+
export function Toolbar() {
const editor = useSlate();
- const allowInline = !isBlockActive(editor, BlockType.CodeBlock);
const modKey = isMacOS() ? KeySymbol.Command : 'Ctrl';
+ const canEscape = isAnyMarkActive(editor) || !isBlockActive(editor, BlockType.Paragraph);
+
return (
- <Box className={css.EditorToolbar} alignItems="Center" gap="300">
- <Box gap="100">
- <HeadingBlockButton />
- <BlockButton
- format={BlockType.OrderedList}
- icon={Icons.OrderList}
- tooltip={
- <BtnTooltip text="Ordered List" shortCode={`${modKey} + ${KeySymbol.Shift} + 0`} />
- }
- />
- <BlockButton
- format={BlockType.UnorderedList}
- icon={Icons.UnorderList}
- tooltip={
- <BtnTooltip text="Unordered List" shortCode={`${modKey} + ${KeySymbol.Shift} + 8`} />
- }
- />
- <BlockButton
- format={BlockType.BlockQuote}
- icon={Icons.BlockQuote}
- tooltip={
- <BtnTooltip text="Block Quote" shortCode={`${modKey} + ${KeySymbol.Shift} + '`} />
- }
- />
- <BlockButton
- format={BlockType.CodeBlock}
- icon={Icons.BlockCode}
- tooltip={
- <BtnTooltip text="Block Code" shortCode={`${modKey} + ${KeySymbol.Shift} + ;`} />
- }
- />
- </Box>
- {allowInline && (
- <>
- <Line variant="SurfaceVariant" direction="Vertical" style={{ height: toRem(12) }} />
- <Box gap="100">
- <MarkButton
- format={MarkType.Bold}
- icon={Icons.Bold}
- tooltip={<BtnTooltip text="Bold" shortCode={`${modKey} + B`} />}
+ <Box className={css.EditorToolbarBase}>
+ <Scroll direction="Horizontal" size="0">
+ <Box className={css.EditorToolbar} alignItems="Center" gap="300">
+ <>
+ <Box shrink="No" gap="100">
+ <MarkButton
+ format={MarkType.Bold}
+ icon={Icons.Bold}
+ tooltip={<BtnTooltip text="Bold" shortCode={`${modKey} + B`} />}
+ />
+ <MarkButton
+ format={MarkType.Italic}
+ icon={Icons.Italic}
+ tooltip={<BtnTooltip text="Italic" shortCode={`${modKey} + I`} />}
+ />
+ <MarkButton
+ format={MarkType.Underline}
+ icon={Icons.Underline}
+ tooltip={<BtnTooltip text="Underline" shortCode={`${modKey} + U`} />}
+ />
+ <MarkButton
+ format={MarkType.StrikeThrough}
+ icon={Icons.Strike}
+ tooltip={
+ <BtnTooltip
+ text="Strike Through"
+ shortCode={`${modKey} + ${KeySymbol.Shift} + U`}
+ />
+ }
+ />
+ <MarkButton
+ format={MarkType.Code}
+ icon={Icons.Code}
+ tooltip={<BtnTooltip text="Inline Code" shortCode={`${modKey} + [`} />}
+ />
+ <MarkButton
+ format={MarkType.Spoiler}
+ icon={Icons.EyeBlind}
+ tooltip={<BtnTooltip text="Spoiler" shortCode={`${modKey} + H`} />}
+ />
+ </Box>
+ <Line variant="SurfaceVariant" direction="Vertical" style={{ height: toRem(12) }} />
+ </>
+ <Box shrink="No" gap="100">
+ <BlockButton
+ format={BlockType.BlockQuote}
+ icon={Icons.BlockQuote}
+ tooltip={
+ <BtnTooltip text="Block Quote" shortCode={`${modKey} + ${KeySymbol.Shift} + '`} />
+ }
/>
- <MarkButton
- format={MarkType.Italic}
- icon={Icons.Italic}
- tooltip={<BtnTooltip text="Italic" shortCode={`${modKey} + I`} />}
+ <BlockButton
+ format={BlockType.CodeBlock}
+ icon={Icons.BlockCode}
+ tooltip={
+ <BtnTooltip text="Block Code" shortCode={`${modKey} + ${KeySymbol.Shift} + ;`} />
+ }
/>
- <MarkButton
- format={MarkType.Underline}
- icon={Icons.Underline}
- tooltip={<BtnTooltip text="Underline" shortCode={`${modKey} + U`} />}
+ <BlockButton
+ format={BlockType.OrderedList}
+ icon={Icons.OrderList}
+ tooltip={
+ <BtnTooltip text="Ordered List" shortCode={`${modKey} + ${KeySymbol.Shift} + 7`} />
+ }
/>
- <MarkButton
- format={MarkType.StrikeThrough}
- icon={Icons.Strike}
+ <BlockButton
+ format={BlockType.UnorderedList}
+ icon={Icons.UnorderList}
tooltip={
<BtnTooltip
- text="Strike Through"
- shortCode={`${modKey} + ${KeySymbol.Shift} + U`}
+ text="Unordered List"
+ shortCode={`${modKey} + ${KeySymbol.Shift} + 8`}
/>
}
/>
- <MarkButton
- format={MarkType.Code}
- icon={Icons.Code}
- tooltip={<BtnTooltip text="Inline Code" shortCode={`${modKey} + [`} />}
- />
- <MarkButton
- format={MarkType.Spoiler}
- icon={Icons.EyeBlind}
- tooltip={<BtnTooltip text="Spoiler" shortCode={`${modKey} + H`} />}
- />
+ <HeadingBlockButton />
</Box>
- </>
- )}
+ {canEscape && (
+ <>
+ <Line variant="SurfaceVariant" direction="Vertical" style={{ height: toRem(12) }} />
+ <Box shrink="No" gap="100">
+ <ExitFormatting
+ tooltip={<BtnTooltip text="Exit Formatting" shortCode={`${modKey} + E`} />}
+ />
+ </Box>
+ </>
+ )}
+ </Box>
+ </Scroll>
</Box>
);
}
Icon,
IconButton,
Icons,
+ Line,
Overlay,
OverlayBackdrop,
OverlayCenter,
import colorMXID from '../../../util/colorMXID';
import { parseReplyBody, parseReplyFormattedBody } from '../../utils/room';
import { sanitizeText } from '../../utils/sanitize';
+import { getResizeObserverEntry, useResizeObserver } from '../../hooks/useResizeObserver';
interface RoomInputProps {
roomViewRef: RefObject<HTMLElement>;
const handlePaste = useFilePasteHandler(handleFiles);
const dropZoneVisible = useFileDropZone(roomViewRef, handleFiles);
+ const [mobile, setMobile] = useState(document.body.clientWidth < 500);
+ useResizeObserver(
+ document.body,
+ useCallback((entries) => {
+ const bodyEntry = getResizeObserverEntry(document.body, entries);
+ if (bodyEntry && bodyEntry.contentRect.width < 500) setMobile(true);
+ else setMobile(false);
+ }, [])
+ );
+
useEffect(() => {
Transforms.insertFragment(editor, msgDraft);
}, [editor, msgDraft]);
>
{(anchorRef) => (
<>
- <IconButton
- aria-pressed={emojiBoardTab === EmojiBoardTab.Sticker}
- onClick={() => setEmojiBoardTab(EmojiBoardTab.Sticker)}
- variant="SurfaceVariant"
- size="300"
- radii="300"
- >
- <Icon
- src={Icons.Sticker}
- filled={emojiBoardTab === EmojiBoardTab.Sticker}
- />
- </IconButton>
+ {!mobile && (
+ <IconButton
+ aria-pressed={emojiBoardTab === EmojiBoardTab.Sticker}
+ onClick={() => setEmojiBoardTab(EmojiBoardTab.Sticker)}
+ variant="SurfaceVariant"
+ size="300"
+ radii="300"
+ >
+ <Icon
+ src={Icons.Sticker}
+ filled={emojiBoardTab === EmojiBoardTab.Sticker}
+ />
+ </IconButton>
+ )}
<IconButton
ref={anchorRef}
- aria-pressed={emojiBoardTab === EmojiBoardTab.Emoji}
+ aria-pressed={
+ mobile ? !!emojiBoardTab : emojiBoardTab === EmojiBoardTab.Emoji
+ }
onClick={() => setEmojiBoardTab(EmojiBoardTab.Emoji)}
variant="SurfaceVariant"
size="300"
radii="300"
>
- <Icon src={Icons.Smile} filled={emojiBoardTab === EmojiBoardTab.Emoji} />
+ <Icon
+ src={Icons.Smile}
+ filled={
+ mobile ? !!emojiBoardTab : emojiBoardTab === EmojiBoardTab.Emoji
+ }
+ />
</IconButton>
</>
)}
</IconButton>
</>
}
- bottom={toolbar && <Toolbar />}
+ bottom={
+ toolbar && (
+ <div>
+ <Line variant="SurfaceVariant" size="300" />
+ <Toolbar />
+ </div>
+ )
+ }
/>
</div>
);