Skip to content
Open
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
95 changes: 66 additions & 29 deletions packages/nextra-theme-docs/src/components/copy-page.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
'use client'

import cn from 'clsx'
import { Button, Select } from 'nextra/components'
import { Anchor, Button, Select } from 'nextra/components'
import { useCopy } from 'nextra/hooks'
import {
ArrowRightIcon,
ChatGPTIcon,
ClaudeIcon,
Copilot365Icon,
CopyCheckIcon,
CopyIcon,
LinkArrowIcon
LinkArrowIcon,
MarkdownIcon
} from 'nextra/icons'
import type { FC, SVGProps } from 'react'

const Item: FC<{
icon: FC<SVGProps<SVGElement>>
title: string
description: string
description?: string
isExternal?: boolean
}> = ({ icon: Icon, title, description, isExternal }) => {
return (
Expand All @@ -24,34 +29,35 @@ const Item: FC<{
{title}
{isExternal && <LinkArrowIcon height="1em" />}
</span>
<span className="x:text-xs">{description}</span>
{!!description && <span className="x:text-xs">{description}</span>}
</div>
</div>
)
}

export const CopyPage: FC<{ sourceCode: string }> = ({ sourceCode }) => {
export const CopyPage: FC<{
sourceCode: string
}> = ({ sourceCode }) => {
const { copy, isCopied } = useCopy()

function handleCopy() {
copy(sourceCode)
}

return (
<div className="x:border x:inline-flex x:rounded-md x:items-stretch nextra-border x:float-end x:overflow-hidden">
<div className="x:border x:inline-flex x:rounded-md nextra-border x:overflow-hidden">
<Button
className={({ hover }) =>
cn(
'x:ps-2 x:pe-1 x:flex x:gap-2 x:text-sm x:font-medium x:items-center',
isCopied && 'x:opacity-70',
hover &&
'x:bg-gray-200 x:text-gray-900 x:dark:bg-primary-100/5 x:dark:text-gray-50'
)
}
onClick={handleCopy}
>
<CopyIcon width="16" />
{isCopied ? 'Copied' : 'Copy page'}
{isCopied ? <CopyCheckIcon width="16" /> : <CopyIcon width="16" />}
Copy page
</Button>
<Select
anchor={{ to: 'bottom end', gap: 10 }}
Expand All @@ -61,7 +67,7 @@ export const CopyPage: FC<{ sourceCode: string }> = ({ sourceCode }) => {
id: 'copy',
name: (
<Item
icon={CopyIcon}
icon={MarkdownIcon}
title="Copy page"
description="Copy page as Markdown for LLMs"
/>
Expand All @@ -70,23 +76,61 @@ export const CopyPage: FC<{ sourceCode: string }> = ({ sourceCode }) => {
{
id: 'chatgpt',
name: (
<Item
icon={ChatGPTIcon}
title="Open in ChatGPT"
description="Ask questions about this page"
isExternal
/>
<Anchor
href={(() => {
if (typeof window === 'undefined') return ''
const query = `Read from ${location.href} so I can ask questions about it.`
return `https://chatgpt.com/?hints=search&prompt=${encodeURIComponent(query)}`
})()}
target="_blank"
>
<Item
icon={ChatGPTIcon}
title="Open in ChatGPT"
description="Ask questions about this page"
isExternal
/>
</Anchor>
)
},
{
id: 'claude',
name: (
<Item
icon={ClaudeIcon}
title="Open in Claude"
description="Ask questions about this page"
isExternal
/>
<Anchor
href={(() => {
if (typeof window === 'undefined') return ''
const query = `Read from ${location.href} so I can ask questions about it.`
return `https://claude.ai/new?q=${encodeURIComponent(query)}`
})()}
target="_blank"
>
<Item
icon={ClaudeIcon}
title="Open in Claude"
description="Ask questions about this page"
isExternal
/>
</Anchor>
)
},
{
id: 'copilot',
name: (
<Anchor
href={(() => {
if (typeof window === 'undefined') return ''
const query = `Read from ${location.href} so I can ask questions about it.`
return `https://copilot.microsoft.com/?q=${encodeURIComponent(query)}`
})()}
target="_blank"
>
<Item
icon={Copilot365Icon}
title="Open in Copilot"
description="Ask questions about this page"
isExternal
/>
</Anchor>
)
}
]}
Expand All @@ -95,14 +139,7 @@ export const CopyPage: FC<{ sourceCode: string }> = ({ sourceCode }) => {
onChange={value => {
if (value === 'copy') {
handleCopy()
return
}
const url =
value === 'chatgpt'
? 'chatgpt.com/?hints=search&prompt'
: 'claude.ai/new?q'
const query = `Read from ${location.href} so I can ask questions about it.`
window.open(`https://${url}=${encodeURIComponent(query)}`, '_blank')
}}
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ export const ClientWrapper: FC<Omit<ComponentProps<MDXWrapper>, 'toc'>> = ({
<Breadcrumb activePath={activePath} />
)}
{themeConfig.copyPageButton && sourceCode && (
<CopyPage sourceCode={sourceCode} />
<div className="x:flex x:justify-end">
<CopyPage sourceCode={sourceCode} />
</div>
)}
{children}
{date ? (
Expand Down
8 changes: 4 additions & 4 deletions packages/nextra/src/client/components/select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ interface MenuOption {
interface MenuProps {
selectedOption: ReactNode
value: string
onChange: Dispatch<string>
onChange?: Dispatch<string>
options: MenuOption[]
title?: string
className?: string
Expand Down Expand Up @@ -60,11 +60,11 @@ export const Select: FC<MenuProps> = ({
as="ul"
transition
anchor={anchor}
className={({ open }) =>
className={() =>
cn(
'x:focus-visible:nextra-focus',
open ? 'x:opacity-100' : 'x:opacity-0',
'x:motion-reduce:transition-none x:transition-opacity x:min-w-(--button-width) x:z-30 x:max-h-64 x:rounded-md x:border x:border-black/5 x:backdrop-blur-md x:bg-nextra-bg/70 x:py-1 x:text-sm x:shadow-lg x:dark:border-white/20'
'x:min-w-(--button-width) x:z-30 x:max-h-64 x:rounded-md x:border x:border-black/5 x:backdrop-blur-md x:bg-nextra-bg/70 x:py-1 x:text-sm x:shadow-lg x:dark:border-white/20',
'x:motion-reduce:transition-none x:origin-top x:transition x:duration-200 x:ease-out x:data-closed:scale-95 x:data-closed:opacity-0'
)
}
>
Expand Down
9 changes: 9 additions & 0 deletions packages/nextra/src/client/icons/copilot-365.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions packages/nextra/src/client/icons/copy-check.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 5 additions & 5 deletions packages/nextra/src/client/icons/copy.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions packages/nextra/src/client/icons/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
export { ReactComponent as ArrowRightIcon } from './arrow-right.svg'
export { ReactComponent as ChatGPTIcon } from './chatgpt.svg'
export { ReactComponent as Copilot365Icon } from './copilot-365.svg'
export { ReactComponent as CheckIcon } from './check.svg'
export { ReactComponent as ClaudeIcon } from './claude.svg'
export { ReactComponent as CopyIcon } from './copy.svg'
export { ReactComponent as CopyCheckIcon } from './copy-check.svg'
export { ReactComponent as DiscordIcon } from './discord.svg'
export { ReactComponent as ExpandIcon } from './expand.svg'
export { ReactComponent as GitHubIcon } from './github.svg'
Expand Down
Loading