diff --git a/src/components/Icons/IconClipboard.tsx b/src/components/Icons/IconClipboard.tsx new file mode 100644 index 00000000000..a08a8ef7faf --- /dev/null +++ b/src/components/Icons/IconClipboard.tsx @@ -0,0 +1,21 @@ +import { Icon } from '@aws-amplify/ui-react'; + +export const IconClipboard = ({ ...rest }) => { + return ( + + ); +}; diff --git a/src/components/Icons/index.ts b/src/components/Icons/index.ts index a492f2c55c0..d28d62f5d8b 100644 --- a/src/components/Icons/index.ts +++ b/src/components/Icons/index.ts @@ -5,6 +5,7 @@ export { IconAWS } from './IconAWS'; export { IconCheck } from './IconCheck'; export { IconCheckCircle } from './IconCheckCircle'; export { IconChevron } from './IconChevron'; +export { IconClipboard } from './IconClipboard'; export { IconDark } from './IconDark'; export { IconDiscord } from './IconDiscord'; export { IconDoubleChevron } from './IconDoubleChevron'; diff --git a/src/components/MDXComponents/MDXCopyCodeButton.tsx b/src/components/MDXComponents/MDXCopyCodeButton.tsx index 6c1e2429832..ad76cf72a58 100644 --- a/src/components/MDXComponents/MDXCopyCodeButton.tsx +++ b/src/components/MDXComponents/MDXCopyCodeButton.tsx @@ -3,7 +3,7 @@ import { CopyToClipboard } from 'react-copy-to-clipboard'; import { Button, VisuallyHidden } from '@aws-amplify/ui-react'; import { trackCopyClicks } from '@/utils/track'; import { prepareCopyText } from './utils/copy-code'; - +import { IconClipboard } from '@/components/Icons'; interface MDXCopyCodeButtonProps { codeId: string; codeString: string; @@ -38,7 +38,7 @@ export const MDXCopyCodeButton = ({ testId={testId} aria-describedby={title ? undefined : codeId} > - {copied ? 'Copied!' : 'Copy'} + {copied ? 'Copied!' : 'Copy'} {` `} {title} code example diff --git a/src/components/MDXComponents/MDXHighlightedCode.tsx b/src/components/MDXComponents/MDXHighlightedCode.tsx new file mode 100644 index 00000000000..7de2c4217be --- /dev/null +++ b/src/components/MDXComponents/MDXHighlightedCode.tsx @@ -0,0 +1,53 @@ +import { useState, useId } from 'react'; +import { CopyToClipboard } from 'react-copy-to-clipboard'; +import { Button, VisuallyHidden } from '@aws-amplify/ui-react'; +import { trackCopyClicks } from '@/utils/track'; +import { prepareCopyText } from './utils/copy-code'; +import { IconClipboard } from '@/components/Icons'; +interface MDXHighlightedCodeProps { + codeString: string; + testId?: string; + children?: React.ReactNode; +} + +export const MDXHighlightedCode = ({ + codeString, + children +}: MDXHighlightedCodeProps) => { + const [copied, setCopied] = useState(false); + + const copyText = prepareCopyText(codeString); + const highlightCodeId = useId(); + + const copy = () => { + trackCopyClicks(copyText); + setCopied(true); + setTimeout(() => { + setCopied(false); + }, 2000); + }; + return ( + <> + + + + +
+ {children} +
+
+ + ); +}; diff --git a/src/components/MDXComponents/MDXHighlightedCopyCodeButton.tsx b/src/components/MDXComponents/MDXHighlightedCopyCodeButton.tsx deleted file mode 100644 index 7fdf4604f99..00000000000 --- a/src/components/MDXComponents/MDXHighlightedCopyCodeButton.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { useState } from 'react'; -import { CopyToClipboard } from 'react-copy-to-clipboard'; -import { VisuallyHidden } from '@aws-amplify/ui-react'; -import { trackCopyClicks } from '@/utils/track'; -import { prepareCopyText } from './utils/copy-code'; - -interface MDXCopyCodeButtonProps { - codeId: string; - codeString: string; - testId?: string; - title?: string; - children?: React.ReactNode; -} - -export const MDXHighlightedCopyCodeButton = ({ - codeString, - title, - codeId, - children -}: MDXCopyCodeButtonProps) => { - const [copied, setCopied] = useState(false); - - const copyText = prepareCopyText(codeString); - - const copy = () => { - trackCopyClicks(copyText); - setCopied(true); - setTimeout(() => { - setCopied(false); - }, 2000); - }; - return ( - - - - ); -}; diff --git a/src/components/MDXComponents/TokenList.tsx b/src/components/MDXComponents/TokenList.tsx index 9078453bc20..9b3962ff1e5 100644 --- a/src/components/MDXComponents/TokenList.tsx +++ b/src/components/MDXComponents/TokenList.tsx @@ -1,6 +1,6 @@ import type { Token } from 'prism-react-renderer'; import type { TokenListProps } from './types'; -import { MDXHighlightedCopyCodeButton } from './MDXHighlightedCopyCodeButton'; +import { MDXHighlightedCode } from './MDXHighlightedCode'; import classNames from 'classnames'; type ProcessedToken = { @@ -159,8 +159,7 @@ export const TokenList = ({ .join('\n'); return ( - @@ -172,7 +171,7 @@ export const TokenList = ({ showLineNumbers ); })} - + ); } }); diff --git a/src/styles/code.scss b/src/styles/code.scss index 53d05364613..b5699390d3e 100644 --- a/src/styles/code.scss +++ b/src/styles/code.scss @@ -37,7 +37,7 @@ code:not([class]) { } .pre-wrapper { - padding: 0 var(--amplify-space-large) 0 var(--amplify-space-xs); + padding: 0 var(--amplify-space-xs); } .pre-header { @@ -70,13 +70,16 @@ code:not([class]) { -webkit-text-size-adjust: 100%; &:focus-visible { - outline: none; - box-shadow: 0 0 0 2px var(--amplify-colors-white) inset; + outline: 2px solid #fff; + outline-offset: 2px; } } .pre-code { flex: 1; + display: flex; + flex-direction: column; + --highlight-code-hover-background-color: hsl(211, 22%, 19%); } .token-line { @@ -92,7 +95,15 @@ code:not([class]) { } .line-highlight { + &:first-child { + padding-top: var(--amplify-space-xxs); + } + &:last-child { + padding-bottom: var(--amplify-space-xxs); + } &:before { + transition: background-color + var(--amplify-components-button-transition-duration) ease; content: ''; position: absolute; left: 0; @@ -138,45 +149,101 @@ code:not([class]) { width: 1.8rem; } -.highlight-copy-block { - all: unset; - width: 100%; - cursor: pointer; +.highlight-copy-button { + background-color: var(--amplify-colors-neutral-90); + margin-top: var(--amplify-space-xxs); + border-color: transparent; + color: #fff; + align-self: flex-start; + border-end-end-radius: 0; + border-end-start-radius: 0; + margin-inline-start: var(--amplify-space-xxl); position: relative; -} - -.highlight-copy-block-hint { - position: absolute; - top: 0; - right: 1.8rem; - color: white; -} - -.highlight-copy-block .highlight-c4py-block-hint { - display: none; -} - -.highlight-copy-block:focus .highlight-copy-block-hint { - display: block; -} - -.highlight-copy-block:hover .highlight-copy-block-hint { - display: block; -} - -.highlight-copy-block:focus .line-highlight::before { - background-color: var(--amplify-colors-primary-80); - + user-select: none; + gap: var(--amplify-space-xxs); + &:focus { + box-shadow: none; + } + &:focus-visible { + outline: 2px solid #fff; + outline-offset: -2px; + &:after { + content: ''; + position: absolute; + height: 2px; + left: 1px; + right: 1px; + bottom: -1px; + background-color: var(--amplify-colors-neutral-90); + z-index: 2; + } + & + .highlight-code { + outline: 2px solid #fff; + .line-highlight:after { + background-color: #fff; + } + } + } + &:hover { + background-color: var(--highlight-code-hover-background-color); + &:after { + background-color: var(--highlight-code-hover-background-color); + } + & + .highlight-code { + .line-highlight:before { + background-color: var(--highlight-code-hover-background-color); + } + } + } @include darkMode { - background-color: var(--amplify-colors-neutral-40); + background-color: var(--amplify-colors-neutral-20); + &:focus-visible { + &:after { + background-color: var(--amplify-colors-neutral-20); + } + } + &:hover { + background-color: var(--highlight-code-hover-background-color); + &:after { + background-color: var(--highlight-code-hover-background-color); + } + & + .highlight-code { + .line-highlight:before { + background-color: var(--highlight-code-hover-background-color); + } + } + } } } -.highlight-copy-block:hover .line-highlight::before { - background-color: var(--amplify-colors-primary-90); +/* This :has selector allows us to style the .highlight-copy-button +that is preceding the .highlight-code block so that interaction between +them looks "connected", i.e. the hover style on one triggers the +hover style on the other */ +.highlight-copy-button:has(+ .highlight-code:hover) { + background-color: var(--highlight-code-hover-background-color); + &:focus-visible { + &:after { + height: 4px; + bottom: -3px; + background-color: var(--highlight-code-hover-background-color); + } + } + &:hover { + &:after { + background-color: var(--highlight-code-hover-background-color); + } + } +} - @include darkMode { - background-color: var(--amplify-colors-primary-10); +.highlight-code { + position: relative; + margin-bottom: var(--amplify-space-xxs); + &:hover { + cursor: pointer; + .line-highlight:before { + background-color: var(--highlight-code-hover-background-color); + } } } @@ -188,6 +255,7 @@ code:not([class]) { padding-block: var(--amplify-space-xxxs); color: var(--amplify-colors-white); border-radius: var(--amplify-radii-small); + gap: var(--amplify-space-xxs); &:hover { color: var(--amplify-colors-white); background-color: var(--code-copy-hover-background-color); @@ -202,4 +270,4 @@ code:not([class]) { --code-copy-focus-background-color: var(--amplify-colors-neutral-20); --code-copy-focus-box-shadow: var(--amplify-colors-neutral-100); } -} \ No newline at end of file +}