Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[docs-infra] Limit the copy button to the visible code block #42115

Merged
merged 6 commits into from
May 13, 2024
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
30 changes: 27 additions & 3 deletions docs/src/modules/components/Demo.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import copy from 'clipboard-copy';
import { useRouter } from 'next/router';
import { debounce } from '@mui/material/utils';
import { alpha, styled } from '@mui/material/styles';
Expand All @@ -12,6 +13,8 @@ import Box from '@mui/material/Box';
import Collapse from '@mui/material/Collapse';
import NoSsr from '@mui/material/NoSsr';
import { HighlightedCode } from '@mui/docs/HighlightedCode';
import ContentCopyRoundedIcon from '@mui/icons-material/ContentCopyRounded';
import LibraryAddCheckRoundedIcon from '@mui/icons-material/LibraryAddCheckRounded';
import DemoSandbox from 'docs/src/modules/components/DemoSandbox';
import ReactRunner from 'docs/src/modules/components/ReactRunner';
import DemoEditor from 'docs/src/modules/components/DemoEditor';
Expand Down Expand Up @@ -350,9 +353,6 @@ const DemoCodeViewer = styled(HighlightedCode)(() => ({
borderBottomLeftRadius: 12,
borderBottomRightRadius: 12,
},
'& .MuiCode-copy': {
display: 'none',
},
}));

const AnchorLink = styled('div')({
Expand Down Expand Up @@ -557,6 +557,21 @@ export default function Demo(props) {
demoData.relativeModules,
]);

const [copiedContent, setCopiedContent] = React.useState(false);

const handleCopyClick = async () => {
try {
const activeTabData = tabs[activeTab];
await copy(activeTabData.raw);
setCopiedContent(true);
setTimeout(() => {
setCopiedContent(false);
}, 1000);
} catch (error) {
console.error('Code content not copied', error);
}
};

return (
<Root>
<AnchorLink id={demoName} />
Expand Down Expand Up @@ -596,6 +611,10 @@ export default function Demo(props) {
<DemoToolbar
codeOpen={codeOpen}
codeVariant={codeVariant}
copyIcon={
copiedContent ? <LibraryAddCheckRoundedIcon /> : <ContentCopyRoundedIcon />
}
copyButtonOnClick={handleCopyClick}
hasNonSystemDemos={hasNonSystemDemos}
demo={demo}
demoData={demoData}
Expand Down Expand Up @@ -646,6 +665,11 @@ export default function Demo(props) {
'data-ga-event-label': demo.gaLabel,
'data-ga-event-action': 'copy-click',
}}
sx={{
'& .MuiCode-copy': {
display: 'none',
},
}}
/>
) : (
<DemoEditor
Expand Down
44 changes: 6 additions & 38 deletions docs/src/modules/components/DemoToolbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import Tooltip from '@mui/material/Tooltip';
import Divider from '@mui/material/Divider';
import RefreshRoundedIcon from '@mui/icons-material/RefreshRounded';
import ResetFocusIcon from '@mui/icons-material/CenterFocusWeak';
import ContentCopyRoundedIcon from '@mui/icons-material/ContentCopyRounded';
import { useRouter } from 'next/router';
import { CODE_VARIANTS, CODE_STYLING } from 'docs/src/modules/constants';
import { useSetCodeVariant } from 'docs/src/modules/utils/codeVariant';
Expand Down Expand Up @@ -262,35 +261,12 @@ function useToolbar(controlRefs, options = {}) {
};
}

function copyWithRelativeModules(raw, relativeModules) {
if (relativeModules) {
relativeModules.forEach(({ module, raw: content }) => {
// remove exports from relative module
content = content.replace(/export( )*(default)*( )*\w+;|export default|export/gm, '');
// replace import statement with relative module content
// the module might be imported with or without extension, so we need to cover all cases
// E.g.: /import .* from '(.\/top100Films.js|.\/top100Films)';/
const extensions = ['', '.js', '.jsx', '.ts', '.tsx', '.css', '.json'];
const patterns = extensions
.map((ext) => {
if (module.endsWith(ext)) {
return module.replace(ext, '');
}
return '';
})
.filter(Boolean)
.join('|');
const importPattern = new RegExp(`import .* from '(${patterns})';`);
raw = raw.replace(importPattern, content);
});
}
return copy(raw);
}

export default function DemoToolbar(props) {
const {
codeOpen,
codeVariant,
copyButtonOnClick,
copyIcon,
hasNonSystemDemos,
demo,
demoData,
Expand Down Expand Up @@ -339,16 +315,6 @@ export default function DemoToolbar(props) {
setSnackbarOpen(false);
};

const handleCopyClick = async () => {
try {
await copyWithRelativeModules(demoData.raw, demoData.relativeModules);
setSnackbarMessage(t('copiedSource'));
setSnackbarOpen(true);
} finally {
handleMoreClose();
}
};

const createHandleCodeSourceLink = (anchor, codeVariantParam, stylingSolution) => async () => {
try {
await copy(
Expand Down Expand Up @@ -591,11 +557,11 @@ export default function DemoToolbar(props) {
data-ga-event-category="demo"
data-ga-event-label={demo.gaLabel}
data-ga-event-action="copy"
onClick={handleCopyClick}
onClick={copyButtonOnClick}
{...getControlProps(6)}
sx={{ borderRadius: 1 }}
>
<ContentCopyRoundedIcon />
{copyIcon}
</IconButton>
</DemoTooltip>
<DemoTooltip title={t('resetFocus')} placement="bottom">
Expand Down Expand Up @@ -741,6 +707,8 @@ export default function DemoToolbar(props) {
DemoToolbar.propTypes = {
codeOpen: PropTypes.bool.isRequired,
codeVariant: PropTypes.string.isRequired,
copyButtonOnClick: PropTypes.object.isRequired,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong type fixed in #41994 (comment)

copyIcon: PropTypes.object.isRequired,
demo: PropTypes.object.isRequired,
demoData: PropTypes.object.isRequired,
demoId: PropTypes.string,
Expand Down
6 changes: 2 additions & 4 deletions packages/markdown/parseMarkdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -423,10 +423,8 @@ function createRender(context) {
escaped ? code : escape(code, true)
}</code></pre>${[
'<button data-ga-event-category="code" data-ga-event-action="copy-click" aria-label="Copy the code" class="MuiCode-copy">',
'<svg focusable="false" aria-hidden="true" viewBox="0 0 24 24" data-testid="ContentCopyRoundedIcon">',
'<use class="MuiCode-copy-icon" xlink:href="#copy-icon" />',
'<use class="MuiCode-copied-icon" xlink:href="#copied-icon" />',
'</svg>',
'<span class="MuiCode-copy-label">Copy</span>',
'<span class="MuiCode-copied-label">Copied</span>',
'<span class="MuiCode-copyKeypress"><span>(or</span> $keyC<span>)</span></span></button></div>',
].join('')}\n`;
};
Expand Down
10 changes: 2 additions & 8 deletions packages/mui-docs/src/CodeCopy/CodeCopyButton.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import * as React from 'react';
import ContentCopyRoundedIcon from '@mui/icons-material/ContentCopyRounded';
import LibraryAddCheckRoundedIcon from '@mui/icons-material/LibraryAddCheckRounded';
import useClipboardCopy from './useClipboardCopy';

export interface CodeCopyButtonProps {
Expand All @@ -27,12 +25,8 @@ export function CodeCopyButton(props: CodeCopyButtonProps) {
}}
>
{/* material-ui/no-hardcoded-labels */}
{isCopied ? (
<LibraryAddCheckRoundedIcon sx={{ fontSize: 18 }} />
) : (
<ContentCopyRoundedIcon sx={{ fontSize: 18 }} />
)}
<span className="MuiCode-copyKeypress">
{isCopied ? 'Copied' : 'Copy'}
<span className="MuiCode-copyKeypress" style={{ opacity: isCopied ? 0 : 1 }}>
<span>(or</span> {key}C<span>)</span>
</span>
</button>
Expand Down
56 changes: 24 additions & 32 deletions packages/mui-docs/src/MarkdownElement/MarkdownElement.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -532,44 +532,34 @@ const Root = styled('div')(
top: 0,
},
'& .MuiCode-copy': {
display: 'inline-flex',
flexDirection: 'row-reverse',
alignItems: 'center',
width: 26,
height: 26,
cursor: 'pointer',
position: 'absolute',
top: 12,
right: 12,
display: 'inline-flex',
flexDirection: 'row-reverse',
alignItems: 'center',
height: 24,
padding: theme.spacing(0.5),
fontFamily: 'inherit',
fontWeight: 500,
paddingBottom: '5px', // optical alignment
fontFamily: lightTheme.typography.fontFamily,
fontWeight: lightTheme.typography.fontWeightMedium,
fontSize: lightTheme.typography.pxToRem(12),
borderRadius: 6,
border: 'none',
backgroundColor: 'hsl(210, 35%, 9%)', // using the code block one-off background color (defined in line 23)
color: '#FFF',
border: '1px solid',
borderColor: alpha(lightTheme.palette.primaryDark[600], 0.5),
backgroundColor: alpha(lightTheme.palette.primaryDark[800], 0.5),
color: `var(--muidocs-palette-grey-200, ${lightTheme.palette.grey[200]})`,
transition: theme.transitions.create(['background', 'borderColor', 'display'], {
duration: theme.transitions.duration.shortest,
}),
'& svg': {
userSelect: 'none',
width: theme.typography.pxToRem(16),
height: theme.typography.pxToRem(16),
display: 'inline-block',
fill: 'currentcolor',
flexShrink: 0,
fontSize: '18px',
margin: 'auto',
opacity: 0.5,
},
'& .MuiCode-copied-icon': {
'& .MuiCode-copied-label': {
display: 'none',
},
'&:hover, &:focus': {
backgroundColor: lightTheme.palette.primaryDark[600],
'& svg': {
opacity: 1,
},
borderColor: `var(--muidocs-palette-primaryDark-400, ${lightTheme.palette.primaryDark[400]})`,
backgroundColor: `var(--muidocs-palette-primaryDark-700, ${lightTheme.palette.primaryDark[700]})`,
color: '#FFF',
'& .MuiCode-copyKeypress': {
display: 'block',
// Approximate no hover capabilities with no keyboard
Expand All @@ -582,17 +572,19 @@ const Root = styled('div')(
'& .MuiCode-copyKeypress': {
display: 'none',
position: 'absolute',
right: 26,
right: 34,
},
'&[data-copied]': {
// style of the button when it is in copied state.
borderColor: lightTheme.palette.primary[700],
borderColor: `var(--muidocs-palette-primaryDark-400, ${lightTheme.palette.primaryDark[400]})`,
backgroundColor: `var(--muidocs-palette-primaryDark-700, ${lightTheme.palette.primaryDark[700]})`,
color: '#fff',
backgroundColor: lightTheme.palette.primaryDark[600],
'& .MuiCode-copy-icon': {
'& .MuiCode-copyKeypress': {
opacity: 0,
},
'& .MuiCode-copy-label': {
display: 'none',
},
'& .MuiCode-copied-icon': {
'& .MuiCode-copied-label': {
display: 'block',
},
},
Expand Down
Loading