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

Homepage code example redesign #13844

Merged
merged 23 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
e592c4c
refactor: CodeBlock to accept className
wackerow Sep 10, 2024
24dc6b7
feat: implement code display redesign
wackerow Sep 10, 2024
73ccfe5
deprecate: unused CodeModal
wackerow Sep 10, 2024
d10b974
feat: update desktop code example approach
wackerow Sep 11, 2024
7a9f680
chore: limit mobile codeblock height
wackerow Sep 11, 2024
f1b92e9
feat: add transition to max-width
wackerow Sep 11, 2024
91650dd
fix: skeleton array overflow
wackerow Sep 11, 2024
cceabd2
patch: remove eslint comments
wackerow Sep 11, 2024
60d0763
chore: rm leading whitespace
wackerow Sep 11, 2024
cf8a1c0
chore: highlight active example
wackerow Sep 11, 2024
6b896b4
Revert "deprecate: unused CodeModal"
wackerow Sep 11, 2024
b2a7ed1
Merge branch 'dev' into code-accordion
wackerow Sep 11, 2024
6ba48e4
chore: partially update CodeModal to tw
wackerow Sep 11, 2024
9def832
feat: create usehooks-ts based useClipboard hook
wackerow Sep 11, 2024
9750bdf
feat: add copy button to CodeModal
wackerow Sep 11, 2024
4e89a0d
feat: re-implement desktop code example modal
wackerow Sep 11, 2024
c04f073
feat: add copy button to mobile code examples
wackerow Sep 11, 2024
c06e776
refactor use clipboard
pettinarip Sep 13, 2024
29e2672
Merge pull request #13861 from ethereum/refactor-use-clipboard
wackerow Sep 13, 2024
519882e
Merge branch 'dev' into code-accordion
wackerow Sep 13, 2024
1735b45
fix: mobile copy button positioning
wackerow Sep 13, 2024
fe17054
fix: useClipboard usage
wackerow Sep 17, 2024
148cc69
Merge branch 'dev' into code-accordion
pettinarip Sep 17, 2024
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
88 changes: 46 additions & 42 deletions src/components/CodeModal.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,38 @@
import type { ReactNode } from "react"
import {
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalHeader,
ModalOverlay,
useColorModeValue,
} from "@chakra-ui/react"
import { Children, type ReactElement } from "react"
import { useTranslation } from "next-i18next"
import { IoMdCopy } from "react-icons/io"
import { MdCheck } from "react-icons/md"
import { Modal, ModalBody, ModalContent, ModalOverlay } from "@chakra-ui/react"

import { Button } from "./ui/buttons/Button"

import { useClipboard } from "@/hooks/useClipboard"

type CodeModalProps = {
title: string
children: ReactNode
children?: ReactElement
isOpen: boolean
setIsOpen: (isOpen: boolean) => void
}

const CodeModal = ({ children, isOpen, setIsOpen, title }: CodeModalProps) => {
const bgColor = useColorModeValue("rgb(247, 247, 247)", "rgb(25, 25, 25)")
const borderColor = useColorModeValue("rgb(51, 51, 51)", "rgb(242, 242, 242)")
const { t } = useTranslation()
const codeSnippet = (Children.toArray(children)[0] as ReactElement).props
.children.props.children

const { onCopy, hasCopied } = useClipboard(codeSnippet, {
timeout: 1500,
})

return (
<Modal
isOpen={isOpen}
scrollBehavior="inside"
onClose={() => setIsOpen(false)}
>
<ModalOverlay />
<ModalOverlay hideBelow="md" />
<ModalContent
hideBelow="md"
maxW="100vw"
marginTop="auto"
marginBottom="0"
Expand All @@ -36,35 +41,34 @@ const CodeModal = ({ children, isOpen, setIsOpen, title }: CodeModalProps) => {
p={{ base: "0", md: "0" }}
gap="0"
>
<ModalHeader
bg={bgColor}
borderColor={borderColor}
borderTop="1px solid"
borderBottom="1px solid"
textTransform="uppercase"
fontWeight="normal"
fontSize="md"
fontFamily="monospace"
px="6"
py="4"
me="0"
>
{title}
</ModalHeader>
<ModalCloseButton
position="absolute"
padding="0"
width="24px"
height="24px"
borderRadius="0"
color="rgb(178, 178, 178)"
fontSize="sm"
margin="0"
top="4"
insetInlineEnd="4"
bottom="4"
/>
<div className="flex items-center border-y bg-background px-6 py-3 font-monospace uppercase">
<h2 className="text-md font-normal">{title}</h2>
<Button
variant="ghost"
className="ms-auto text-sm"
size="sm"
isSecondary
onClick={() => setIsOpen(false)}
>
{t("close")}
</Button>
</div>
<ModalBody p="0">{children}</ModalBody>
<Button
variant="outline"
onClick={onCopy}
className="absolute end-4 top-20"
>
{hasCopied ? (
<>
<MdCheck /> {t("copied")}
</>
) : (
<>
<IoMdCopy /> {t("copy")}
</>
)}
</Button>
</ModalContent>
</Modal>
)
Expand Down
19 changes: 8 additions & 11 deletions src/components/Codeblock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -202,18 +202,18 @@ const getValidChildrenForCodeblock = (child) => {
}
}

export type CodeblockProps = {
export type CodeblockProps = React.HTMLAttributes<HTMLDivElement> & {
allowCollapse?: boolean
codeLanguage: string
fromHomepage?: boolean
children: React.ReactNode
}

const Codeblock = ({
children,
allowCollapse = true,
codeLanguage,
fromHomepage = false,
className,
}: CodeblockProps) => {
const { t } = useTranslation("common")
const selectedTheme = useColorModeValue(codeTheme.light, codeTheme.dark)
Expand All @@ -227,14 +227,14 @@ const Codeblock = ({

const [isCollapsed, setIsCollapsed] = useState(allowCollapse)

let className: string
let langClass: string
if (React.isValidElement(children)) {
className = children?.props?.className
langClass = children?.props?.className
} else {
className = codeLanguage || ""
langClass = codeLanguage || ""
}

const matches = className?.match(/language-(.*)/)
const matches = langClass?.match(/language-(.*)/)
const language = matches?.[1] || ""

const shouldShowCopyWidget = ["js", "json", "python", "solidity"].includes(
Expand All @@ -249,12 +249,9 @@ const Codeblock = ({
return (
/* Overwrites codeblocks inheriting RTL styling in Right-To-Left script languages (e.g. Arabic) */
/* Context: https://github.com/ethereum/ethereum-org-website/issues/6202 */
<div className="relative" dir="ltr">
<div className={cn("relative", className)} dir="ltr">
<div
className={cn(
"mb-4 overflow-scroll rounded",
fromHomepage && "mb-0 border"
)}
className="mb-4 overflow-scroll rounded"
style={{
maxHeight: isCollapsed
? `calc((1.2rem * ${LINES_BEFORE_COLLAPSABLE}) + 4.185rem)`
Expand Down
5 changes: 4 additions & 1 deletion src/components/ui/skeleton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ const SkeletonLines = ({
{Array(noOfLines)
.fill(0)
.map((_, idx) => (
<Skeleton key={idx} className={cn("h-3", widths[idx])} />
<Skeleton
key={idx}
className={cn("h-3", widths[idx % widths.length])}
/>
))}
</div>
)
Expand Down
3 changes: 0 additions & 3 deletions src/data/CreateWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,9 @@ const mnemonic =
const walletMnemonic = ethers.Wallet.fromMnemonic(mnemonic)

// ...or from a private key
// eslint-disable-next-line unused-imports/no-unused-vars
pettinarip marked this conversation as resolved.
Show resolved Hide resolved
const walletPrivateKey = new ethers.Wallet(walletMnemonic.privateKey)

// ...or create a wallet from a random private key
// eslint-disable-next-line unused-imports/no-unused-vars
const randomWallet = ethers.Wallet.createRandom()

walletMnemonic.address
Expand All @@ -32,7 +30,6 @@ walletMnemonic.signTransaction(tx)
// { Promise: '0xf865808080948ba1f109551bd432803012645ac136ddd6...dfc' }

// Connect to the Ethereum network using a provider
// eslint-disable-next-line no-undef
const wallet = walletMnemonic.connect(provider)

// Query the network
Expand Down
2 changes: 1 addition & 1 deletion src/data/SimpleDomainRegistry.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-License-Identifier: MIT
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.1;

// This is a smart contract - a program that can be deployed to the Ethereum blockchain.
Expand Down
29 changes: 29 additions & 0 deletions src/hooks/useClipboard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { useState } from "react"
import { useCopyToClipboard } from "usehooks-ts"

export type UseClipboardOptions = {
/**
* timeout delay (in ms) to switch back to initial state once copied.
*/
timeout?: number
}

export const useClipboard = ({ timeout = 1500 }: UseClipboardOptions = {}) => {
const [hasCopied, setHasCopied] = useState(false)
const [_, copy] = useCopyToClipboard()

const onCopy = async (value: string) => {
try {
await copy(value)

setHasCopied(true)
setTimeout(() => {
setHasCopied(false)
}, timeout)
} catch (error) {
console.error("Failed to copy!", error)
}
}

return { onCopy, hasCopied }
}
103 changes: 81 additions & 22 deletions src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { Fragment, lazy, Suspense } from "react"
import type { GetStaticProps, InferGetStaticPropsType } from "next"
import { serverSideTranslations } from "next-i18next/serverSideTranslations"
import { FaDiscord, FaGithub } from "react-icons/fa6"
import { IoMdCopy } from "react-icons/io"
import { MdCheck } from "react-icons/md"

import type {
AllMetricData,
Expand All @@ -25,7 +27,7 @@ import MainArticle from "@/components/MainArticle"
import PageMetadata from "@/components/PageMetadata"
import Swiper from "@/components/Swiper"
import { TranslatathonBanner } from "@/components/Translatathon/TranslatathonBanner"
import { ButtonLink } from "@/components/ui/buttons/Button"
import { Button, ButtonLink } from "@/components/ui/buttons/Button"
import {
Card,
CardBanner,
Expand Down Expand Up @@ -64,6 +66,14 @@ import {
RSS_DISPLAY_COUNT,
} from "@/lib/constants"

import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "../../tailwind/ui/accordion"

import { useClipboard } from "@/hooks/useClipboard"
import { fetchCommunityEvents } from "@/lib/api/calendarEvents"
import { fetchEthPrice } from "@/lib/api/fetchEthPrice"
import { fetchGrowThePie } from "@/lib/api/fetchGrowThePie"
Expand Down Expand Up @@ -181,6 +191,8 @@ const HomePage = ({
bentoItems,
} = useHome()

const { onCopy, hasCopied } = useClipboard()

return (
<MainArticle className="flex w-full flex-col items-center" dir={dir}>
<PageMetadata
Expand Down Expand Up @@ -349,7 +361,7 @@ const HomePage = ({

{/* Builders - Blockchain's biggest builder community */}
<Section id="builders" variant="responsiveFlex">
<SectionBanner>
<SectionBanner className="relative">
<TwImage src={BuildersImage} alt="" />
</SectionBanner>

Expand Down Expand Up @@ -381,10 +393,16 @@ const HomePage = ({
title={t("page-index:page-index-developers-code-examples")}
Svg={AngleBrackets}
>
{/* Desktop */}
{codeExamples.map(({ title, description }, idx) => (
<button
key={title}
className="flex flex-col gap-y-0.5 border-t px-6 py-4 hover:bg-background-highlight"
className={cn(
"flex flex-col gap-y-0.5 border-t px-6 py-4 hover:bg-background-highlight max-md:hidden",
isModalOpen &&
idx === activeCode &&
"bg-background-highlight"
)}
onClick={() => toggleCodeExample(idx)}
>
<p className="font-bold">{title}</p>
Expand All @@ -393,27 +411,68 @@ const HomePage = ({
</p>
</button>
))}
{/* Mobile */}
<Accordion type="single" collapsible className="md:hidden">
{codeExamples.map(
({ title, description, code, codeLanguage }) => (
<AccordionItem
key={title}
value={title}
className="relative"
>
<AccordionTrigger className="flex border-t px-6 py-4 hover:bg-background-highlight">
<div className="flex flex-col items-start gap-y-0.5">
<p className="text-md font-bold text-body">
{title}
</p>
<p className="text-start text-sm text-body-medium">
{description}
</p>
</div>
</AccordionTrigger>
<AccordionContent className="max-h-[50vh] overflow-auto border-t">
<Suspense fallback={<SkeletonLines noOfLines={16} />}>
<Codeblock
codeLanguage={codeLanguage}
allowCollapse={false}
className="[&>div]:-m-2 [&>div]:rounded-none [&_*]:!text-xs [&_pre]:p-4"
fromHomepage
>
{code}
</Codeblock>
<Button
onClick={() => onCopy(code)}
className="absolute end-4 top-24"
>
{hasCopied ? <MdCheck /> : <IoMdCopy />}
</Button>
</Suspense>
</AccordionContent>
</AccordionItem>
)
)}
</Accordion>
</WindowBox>
{isModalOpen && (
// TODO: Migrate CodeModal, CodeBlock from Chakra-UI to tailwind/shad-cn
<CodeModal
isOpen={isModalOpen}
setIsOpen={setModalOpen}
title={codeExamples[activeCode].title}
>
<Suspense fallback={<SkeletonLines noOfLines={16} />}>
<Codeblock
codeLanguage={codeExamples[activeCode].codeLanguage}
allowCollapse={false}
className="[&_pre]:p-6"
fromHomepage
>
{codeExamples[activeCode].code}
</Codeblock>
</Suspense>
</CodeModal>
)}
</div>

{isModalOpen && (
// TODO: Migrate CodeModal, CodeBlock from Chakra-UI to tailwind/shad-cn
<CodeModal
isOpen={isModalOpen}
setIsOpen={setModalOpen}
title={codeExamples[activeCode].title}
>
<Suspense fallback={<SkeletonLines noOfLines={16} />}>
<Codeblock
codeLanguage={codeExamples[activeCode].codeLanguage}
allowCollapse={false}
fromHomepage
>
{codeExamples[activeCode].code}
</Codeblock>
</Suspense>
</CodeModal>
)}
</SectionContent>
</Section>

Expand Down
Loading