From ca9c41024c956a02a97e62222603399119b9badf Mon Sep 17 00:00:00 2001 From: cookesan <6601329+cookesan@users.noreply.github.com> Date: Sun, 28 Jun 2026 08:37:09 -0400 Subject: [PATCH] fix: move mobile copy button outside code block --- __tests__/e2e/markdown-extensions/index.md | 2 +- .../markdown-extensions.test.ts | 77 +++++++++++++------ .../styles/components/vp-doc.css | 43 ++++++++++- 3 files changed, 94 insertions(+), 28 deletions(-) diff --git a/__tests__/e2e/markdown-extensions/index.md b/__tests__/e2e/markdown-extensions/index.md index 3446b4ef..c5202885 100644 --- a/__tests__/e2e/markdown-extensions/index.md +++ b/__tests__/e2e/markdown-extensions/index.md @@ -169,7 +169,7 @@ export default config <<< @/markdown-extensions/foo.md -<<< @/markdown-extensions/foo.md#snippet{1 ts:line-numbers} [snippet with region] +<<< @/markdown-extensions/foo.md#snippet{1 ts:line-numbers} [snippet with region and a long mobile tab label] ::: diff --git a/__tests__/e2e/markdown-extensions/markdown-extensions.test.ts b/__tests__/e2e/markdown-extensions/markdown-extensions.test.ts index 46a1b693..48b1a232 100644 --- a/__tests__/e2e/markdown-extensions/markdown-extensions.test.ts +++ b/__tests__/e2e/markdown-extensions/markdown-extensions.test.ts @@ -7,6 +7,30 @@ const getClassList = async (locator: Locator) => { const trim = (str?: string | null) => str?.replace(/\u200B/g, '').trim() +const expectMobileCopyButtonOutsideCode = async ( + block: Locator, + limit?: Locator +) => { + const button = block.locator('> button.copy') + const firstLine = block.locator('code > span').first() + await block.scrollIntoViewIfNeeded() + + const [buttonBox, firstLineBox, limitBox] = await Promise.all([ + button.boundingBox(), + firstLine.boundingBox(), + (limit ?? block).boundingBox() + ]) + expect(buttonBox).toBeTruthy() + expect(firstLineBox).toBeTruthy() + expect(limitBox).toBeTruthy() + expect(buttonBox!.y + buttonBox!.height).toBeLessThanOrEqual(limitBox!.y) + expect(buttonBox!.y + buttonBox!.height).toBeLessThanOrEqual(firstLineBox!.y) + expect(await button.evaluate((el) => getComputedStyle(el).opacity)).toBe('1') + expect( + await button.evaluate((el) => getComputedStyle(el).pointerEvents) + ).toBe('auto') +} + beforeEach(async () => { await goto('/markdown-extensions/') }) @@ -164,6 +188,17 @@ describe('Custom Containers', () => { 'Click me to view the code' ) }) + + test('copy button stays outside mobile custom container code', async () => { + await page.setViewportSize({ width: 390, height: 844 }) + await goto('/markdown-extensions/') + + const block = page.locator('#custom-title ~ .custom-block').nth(1) + await block.locator('summary').click() + await expectMobileCopyButtonOutsideCode( + block.locator('div[class*="language-"]') + ) + }) }) describe('Line Highlighting in Code Blocks', () => { @@ -172,30 +207,10 @@ describe('Line Highlighting in Code Blocks', () => { await goto('/markdown-extensions/') const block = page.locator('#single-line + div') - const button = block.locator('> button.copy') - const firstLine = block.locator('code > span').first() - await block.scrollIntoViewIfNeeded() - const buttonBox = await button.boundingBox() - const firstLineBox = await firstLine.boundingBox() - expect(buttonBox).toBeTruthy() - expect(firstLineBox).toBeTruthy() - expect(buttonBox!.y + buttonBox!.height).toBeLessThanOrEqual( - firstLineBox!.y - ) + await expectMobileCopyButtonOutsideCode(block) await block.hover() - await page.waitForFunction( - (el) => getComputedStyle(el).opacity === '1', - await button.elementHandle() - ) - - const hoveredButtonBox = await button.boundingBox() - const hoveredFirstLineBox = await firstLine.boundingBox() - expect(hoveredButtonBox).toBeTruthy() - expect(hoveredFirstLineBox).toBeTruthy() - expect(hoveredButtonBox!.y + hoveredButtonBox!.height).toBeLessThanOrEqual( - hoveredFirstLineBox!.y - ) + await expectMobileCopyButtonOutsideCode(block) await page.setViewportSize({ width: 1280, height: 720 }) await goto('/markdown-extensions/') @@ -307,7 +322,10 @@ describe('Code Groups', () => { // tabs const labels = div.locator('.tabs > label') - const labelNames = ['foo.md', 'snippet with region'] + const labelNames = [ + 'foo.md', + 'snippet with region and a long mobile tab label' + ] const count = await labels.count() expect(count).toBe(2) for (let i = 0; i < count; i++) { @@ -325,6 +343,19 @@ describe('Code Groups', () => { await getClassList(blocks.nth(1).locator('code > span').nth(0)) ).toContain('highlighted') }) + + test('copy button stays outside mobile code group tabs', async () => { + await page.setViewportSize({ width: 390, height: 844 }) + await goto('/markdown-extensions/') + + const div = page.locator('#with-other-features-1 + div') + await div.scrollIntoViewIfNeeded() + await div.locator('.tabs > label').nth(1).click() + await expectMobileCopyButtonOutsideCode( + div.locator('.blocks > div.active'), + div.locator('> .tabs') + ) + }) }) describe('Markdown File Inclusion', () => { diff --git a/src/client/theme-default/styles/components/vp-doc.css b/src/client/theme-default/styles/components/vp-doc.css index 11a39b9b..3ad79b61 100644 --- a/src/client/theme-default/styles/components/vp-doc.css +++ b/src/client/theme-default/styles/components/vp-doc.css @@ -474,12 +474,47 @@ } @media (max-width: 639px) { - .vp-doc [class*='language-'] pre { - padding-top: 64px; + .vp-doc div[class*='language-'] { + margin-top: 64px; + overflow: visible; } - .vp-doc div[class*='language-'].line-numbers-mode .line-numbers-wrapper { - padding-top: 64px; + .vp-doc div[class*='language-'] + div[class*='language-'], + .vp-doc div[class$='-api'] + div[class*='language-'], + .vp-doc + div[class*='language-'] + + div[class$='-api'] + > div[class*='language-'] { + margin-top: 64px; + } + + .vp-doc .custom-block div[class*='language-'] { + margin-top: 64px !important; + } + + .vp-doc [class*='language-'] > button.copy { + top: -48px; + /*rtl:ignore*/ + right: 0; + opacity: 1; + pointer-events: auto; + } + + .vp-doc .vp-code-group { + margin-top: 64px; + } + + .vp-doc .custom-block .vp-code-group, + .vp-doc .custom-block [class*='vp-code-block'] { + margin-top: 64px; + } + + .vp-doc .vp-code-group div[class*='language-'] { + margin-top: 0 !important; + } + + .vp-doc .vp-code-group [class*='language-'] > button.copy { + top: -112px; } }