Skip to content

Commit

Permalink
Make copy button a React component
Browse files Browse the repository at this point in the history
  • Loading branch information
DukeManh authored and nguyenhung15913 committed Mar 29, 2022
1 parent ec5939f commit 3635277
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 60 deletions.
81 changes: 81 additions & 0 deletions src/web/app/src/components/Posts/CopyButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { useState, CSSProperties, MouseEvent } from 'react';
import { createStyles } from '@material-ui/core';
import { makeStyles } from '@material-ui/core/styles';
import Button from '@material-ui/core/IconButton';
import Paper from '@material-ui/core/Paper';
import FileCopyIcon from '@material-ui/icons/FileCopy';
import { Transition } from 'react-transition-group';
import clsx from 'clsx';

const useStyles = makeStyles(() =>
createStyles({
copyButton: {
position: 'absolute',
right: 0,
padding: '1rem',
marginRight: '1.5rem',
transitionDuration: '0.2s',
transitionTimingFunction: 'ease',
cursor: 'pointer',
animation: 'fade-in-out 200ms both',
},
icon: {
fontSize: '2rem',
},
copyBadge: {
position: 'absolute',
top: '50%',
right: 0,
transitionProperty: 'transform, opacity',
transitionDuration: '0.2s',
transitionTimingFunction: 'ease',
borderRadius: '5px',
padding: '3px',
},
})
);

type CopyButtonProps = {
onClick: (e: MouseEvent) => void;
};

const transition: { [state: string]: CSSProperties } = {
entered: { transform: 'translate(-70%, -50%)', opacity: 1 },
entering: { transform: 'translate(0, -50%)', opacity: 0 },
exited: { transform: `translate(0, -50%)`, opacity: 0 },
exiting: { transform: `translate(0, -50%)`, opacity: 0 },
};

const CopyButton = ({ onClick }: CopyButtonProps) => {
const [copied, setCopied] = useState(false);
const classes = useStyles();

const handleClick = (e: MouseEvent) => {
onClick(e);

setCopied(true);
setTimeout(() => {
setCopied(false);
}, 2500);
};
return (
<Button
aria-label="copy"
onClick={handleClick}
className={clsx('copyCodeBtn', classes.copyButton)}
color="inherit"
size="small"
>
<FileCopyIcon className={classes.icon} />
<Transition in={copied} timeout={300} unmountOnExit>
{(state) => (
<Paper elevation={5} style={{ ...transition[state] }} className={classes.copyBadge}>
Copied ✓
</Paper>
)}
</Transition>
</Button>
);
};

export default CopyButton;
45 changes: 18 additions & 27 deletions src/web/app/src/components/Posts/Timeline.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { ReactElement, useEffect } from 'react';
import ReactDOM from 'react-dom';
import { ReactElement, MouseEvent } from 'react';
import { render } from 'react-dom';
import { Container, createStyles } from '@material-ui/core';
import FileCopyIcon from '@material-ui/icons/FileCopy';
import { makeStyles, Theme } from '@material-ui/core/styles';
import PostComponent from './Post';
import { Post } from '../../interfaces';
import LoadAutoScroll from './LoadAutoScroll';
import CopyButton from './CopyButton';

type Props = {
pages: Post[][] | undefined;
Expand Down Expand Up @@ -49,53 +49,40 @@ function isCodeBlock(elem: Element) {
return elem.tagName === 'CODE' && elem.parentElement?.tagName === 'PRE';
}

function createButton(onClick: (e: MouseEvent) => void) {
const elem = document.createElement('button');
elem.className = 'copyCodeBtn';
elem.onclick = onClick;
return elem;
}

function removeButton(parent: HTMLElement) {
parent.querySelectorAll('.copyCodeBtn').forEach((button) => button.remove());
}
function createCopyButton(parent: HTMLElement, onClick: (e: MouseEvent) => void) {
render(<CopyButton onClick={onClick} />, parent);
}

function handleMouseMove(e: MouseEvent) {
// if mouse hovers <code></code>, we call createCopyButton(e)
if (e.target instanceof HTMLElement && isCodeBlock(e.target)) {
e.preventDefault();
const snippet = e.target; // code tag
const parentDiv = snippet.parentElement; // pre tag
const previousNode = snippet.previousElementSibling;

// check if a button has already been added
if (previousNode?.className === 'copyCodeBtn') {
// There is no content to be copied
if (!parentDiv || !snippet.textContent) {
return;
}

// there's nothing to copy
if (!parentDiv || !snippet.textContent) {
// check if a button has already been added
if (parentDiv.querySelector('.copyCodeBtn')) {
return;
}

const copyButton = createButton(() => copyCode(snippet.textContent!));
parentDiv.insertBefore(copyButton, snippet);
ReactDOM.render(<FileCopyIcon />, copyButton); // render JSX icon into the pure HTML button
const button = document.createElement('div');
createCopyButton(button, () => copyCode(snippet.textContent!));
parentDiv.insertBefore(button, snippet);
parentDiv.onmouseleave = () => removeButton(parentDiv);
}
}

const Timeline = ({ pages, totalPosts, nextPage }: Props) => {
const classes = useStyles();

// Listen for mouse move events
useEffect(() => {
window.document.addEventListener('mousemove', handleMouseMove);
return () => {
window.document.removeEventListener('mousemove', handleMouseMove);
};
}, []);

if (!pages) {
return null;
}
Expand Down Expand Up @@ -137,7 +124,11 @@ const Timeline = ({ pages, totalPosts, nextPage }: Props) => {
);
}

return <Container className={classes.root}>{postsTimeline}</Container>;
return (
<div onMouseMove={handleMouseMove}>
<Container className={classes.root}>{postsTimeline}</Container>
</div>
);
};

export default Timeline;
32 changes: 0 additions & 32 deletions src/web/app/src/pages/CopyButton.css

This file was deleted.

1 change: 0 additions & 1 deletion src/web/app/src/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { ThemeContext } from '../components/ThemeProvider';
import '../styles/globals.css';
import '@fontsource/spartan';
import '@fontsource/pt-serif';
import './CopyButton.css';

// Reference: https://github.com/mui-org/material-ui/blob/master/examples/nextjs/pages/_app.js
const App = ({ Component, pageProps }: AppProps) => {
Expand Down
9 changes: 9 additions & 0 deletions src/web/app/src/styles/telescope-post-content.css
Original file line number Diff line number Diff line change
Expand Up @@ -232,3 +232,12 @@
.zoom-out {
animation: scaleFadeOut 0.15s ease-in-out;
}

@keyframes fade-in-out {
from {
opacity: 0;
}
to {
opacity: 1;
}
}

0 comments on commit 3635277

Please sign in to comment.