Skip to content

Commit d821b75

Browse files
authored
Merge pull request #13844 from ethereum/code-accordion
Homepage code example redesign
2 parents 71137cf + 148cc69 commit d821b75

File tree

7 files changed

+169
-80
lines changed

7 files changed

+169
-80
lines changed

src/components/CodeModal.tsx

Lines changed: 44 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,36 @@
1-
import type { ReactNode } from "react"
2-
import {
3-
Modal,
4-
ModalBody,
5-
ModalCloseButton,
6-
ModalContent,
7-
ModalHeader,
8-
ModalOverlay,
9-
useColorModeValue,
10-
} from "@chakra-ui/react"
1+
import { Children, type ReactElement } from "react"
2+
import { useTranslation } from "next-i18next"
3+
import { IoMdCopy } from "react-icons/io"
4+
import { MdCheck } from "react-icons/md"
5+
import { Modal, ModalBody, ModalContent, ModalOverlay } from "@chakra-ui/react"
6+
7+
import { Button } from "./ui/buttons/Button"
8+
9+
import { useClipboard } from "@/hooks/useClipboard"
1110

1211
type CodeModalProps = {
1312
title: string
14-
children: ReactNode
13+
children?: ReactElement
1514
isOpen: boolean
1615
setIsOpen: (isOpen: boolean) => void
1716
}
1817

1918
const CodeModal = ({ children, isOpen, setIsOpen, title }: CodeModalProps) => {
20-
const bgColor = useColorModeValue("rgb(247, 247, 247)", "rgb(25, 25, 25)")
21-
const borderColor = useColorModeValue("rgb(51, 51, 51)", "rgb(242, 242, 242)")
19+
const { t } = useTranslation()
20+
const codeSnippet = (Children.toArray(children)[0] as ReactElement).props
21+
.children.props.children
22+
23+
const { onCopy, hasCopied } = useClipboard()
2224

2325
return (
2426
<Modal
2527
isOpen={isOpen}
2628
scrollBehavior="inside"
2729
onClose={() => setIsOpen(false)}
2830
>
29-
<ModalOverlay />
31+
<ModalOverlay hideBelow="md" />
3032
<ModalContent
33+
hideBelow="md"
3134
maxW="100vw"
3235
marginTop="auto"
3336
marginBottom="0"
@@ -36,35 +39,34 @@ const CodeModal = ({ children, isOpen, setIsOpen, title }: CodeModalProps) => {
3639
p={{ base: "0", md: "0" }}
3740
gap="0"
3841
>
39-
<ModalHeader
40-
bg={bgColor}
41-
borderColor={borderColor}
42-
borderTop="1px solid"
43-
borderBottom="1px solid"
44-
textTransform="uppercase"
45-
fontWeight="normal"
46-
fontSize="md"
47-
fontFamily="monospace"
48-
px="6"
49-
py="4"
50-
me="0"
51-
>
52-
{title}
53-
</ModalHeader>
54-
<ModalCloseButton
55-
position="absolute"
56-
padding="0"
57-
width="24px"
58-
height="24px"
59-
borderRadius="0"
60-
color="rgb(178, 178, 178)"
61-
fontSize="sm"
62-
margin="0"
63-
top="4"
64-
insetInlineEnd="4"
65-
bottom="4"
66-
/>
42+
<div className="flex items-center border-y bg-background px-6 py-3 font-monospace uppercase">
43+
<h2 className="text-md font-normal">{title}</h2>
44+
<Button
45+
variant="ghost"
46+
className="ms-auto text-sm"
47+
size="sm"
48+
isSecondary
49+
onClick={() => setIsOpen(false)}
50+
>
51+
{t("close")}
52+
</Button>
53+
</div>
6754
<ModalBody p="0">{children}</ModalBody>
55+
<Button
56+
variant="outline"
57+
onClick={() => onCopy(codeSnippet)}
58+
className="absolute end-4 top-20"
59+
>
60+
{hasCopied ? (
61+
<>
62+
<MdCheck /> {t("copied")}
63+
</>
64+
) : (
65+
<>
66+
<IoMdCopy /> {t("copy")}
67+
</>
68+
)}
69+
</Button>
6870
</ModalContent>
6971
</Modal>
7072
)

src/components/Codeblock.tsx

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -202,18 +202,18 @@ const getValidChildrenForCodeblock = (child) => {
202202
}
203203
}
204204

205-
export type CodeblockProps = {
205+
export type CodeblockProps = React.HTMLAttributes<HTMLDivElement> & {
206206
allowCollapse?: boolean
207207
codeLanguage: string
208208
fromHomepage?: boolean
209-
children: React.ReactNode
210209
}
211210

212211
const Codeblock = ({
213212
children,
214213
allowCollapse = true,
215214
codeLanguage,
216215
fromHomepage = false,
216+
className,
217217
}: CodeblockProps) => {
218218
const { t } = useTranslation("common")
219219
const selectedTheme = useColorModeValue(codeTheme.light, codeTheme.dark)
@@ -227,14 +227,14 @@ const Codeblock = ({
227227

228228
const [isCollapsed, setIsCollapsed] = useState(allowCollapse)
229229

230-
let className: string
230+
let langClass: string
231231
if (React.isValidElement(children)) {
232-
className = children?.props?.className
232+
langClass = children?.props?.className
233233
} else {
234-
className = codeLanguage || ""
234+
langClass = codeLanguage || ""
235235
}
236236

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

240240
const shouldShowCopyWidget = ["js", "json", "python", "solidity"].includes(
@@ -249,12 +249,9 @@ const Codeblock = ({
249249
return (
250250
/* Overwrites codeblocks inheriting RTL styling in Right-To-Left script languages (e.g. Arabic) */
251251
/* Context: https://github.com/ethereum/ethereum-org-website/issues/6202 */
252-
<div className="relative" dir="ltr">
252+
<div className={cn("relative", className)} dir="ltr">
253253
<div
254-
className={cn(
255-
"mb-4 overflow-scroll rounded",
256-
fromHomepage && "mb-0 border"
257-
)}
254+
className="overflow-scroll rounded"
258255
style={{
259256
maxHeight: isCollapsed
260257
? `calc((1.2rem * ${LINES_BEFORE_COLLAPSABLE}) + 4.185rem)`

src/components/ui/skeleton.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,10 @@ const SkeletonLines = ({
4848
{Array(noOfLines)
4949
.fill(0)
5050
.map((_, idx) => (
51-
<Skeleton key={idx} className={cn("h-3", widths[idx])} />
51+
<Skeleton
52+
key={idx}
53+
className={cn("h-3", widths[idx % widths.length])}
54+
/>
5255
))}
5356
</div>
5457
)

src/data/CreateWallet.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,9 @@ const mnemonic =
66
const walletMnemonic = ethers.Wallet.fromMnemonic(mnemonic)
77
88
// ...or from a private key
9-
// eslint-disable-next-line unused-imports/no-unused-vars
109
const walletPrivateKey = new ethers.Wallet(walletMnemonic.privateKey)
1110
1211
// ...or create a wallet from a random private key
13-
// eslint-disable-next-line unused-imports/no-unused-vars
1412
const randomWallet = ethers.Wallet.createRandom()
1513
1614
walletMnemonic.address
@@ -32,7 +30,6 @@ walletMnemonic.signTransaction(tx)
3230
// { Promise: '0xf865808080948ba1f109551bd432803012645ac136ddd6...dfc' }
3331
3432
// Connect to the Ethereum network using a provider
35-
// eslint-disable-next-line no-undef
3633
const wallet = walletMnemonic.connect(provider)
3734
3835
// Query the network

src/data/SimpleDomainRegistry.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// SPDX-License-Identifier: MIT
1+
// SPDX-License-Identifier: MIT
22
pragma solidity ^0.8.1;
33

44
// This is a smart contract - a program that can be deployed to the Ethereum blockchain.

src/hooks/useClipboard.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { useState } from "react"
2+
import { useCopyToClipboard } from "usehooks-ts"
3+
4+
export type UseClipboardOptions = {
5+
/**
6+
* timeout delay (in ms) to switch back to initial state once copied.
7+
*/
8+
timeout?: number
9+
}
10+
11+
export const useClipboard = ({ timeout = 1500 }: UseClipboardOptions = {}) => {
12+
const [hasCopied, setHasCopied] = useState(false)
13+
const [_, copy] = useCopyToClipboard()
14+
15+
const onCopy = async (value: string) => {
16+
try {
17+
await copy(value)
18+
19+
setHasCopied(true)
20+
setTimeout(() => {
21+
setHasCopied(false)
22+
}, timeout)
23+
} catch (error) {
24+
console.error("Failed to copy!", error)
25+
}
26+
}
27+
28+
return { onCopy, hasCopied }
29+
}

src/pages/index.tsx

Lines changed: 83 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { Fragment, lazy, Suspense } from "react"
22
import type { GetStaticProps, InferGetStaticPropsType } from "next"
33
import { serverSideTranslations } from "next-i18next/serverSideTranslations"
44
import { FaDiscord, FaGithub } from "react-icons/fa6"
5+
import { IoMdCopy } from "react-icons/io"
6+
import { MdCheck } from "react-icons/md"
57

68
import type {
79
AllMetricData,
@@ -25,7 +27,7 @@ import MainArticle from "@/components/MainArticle"
2527
import PageMetadata from "@/components/PageMetadata"
2628
import Swiper from "@/components/Swiper"
2729
import { TranslatathonBanner } from "@/components/Translatathon/TranslatathonBanner"
28-
import { ButtonLink } from "@/components/ui/buttons/Button"
30+
import { Button, ButtonLink } from "@/components/ui/buttons/Button"
2931
import {
3032
Card,
3133
CardBanner,
@@ -64,6 +66,14 @@ import {
6466
RSS_DISPLAY_COUNT,
6567
} from "@/lib/constants"
6668

69+
import {
70+
Accordion,
71+
AccordionContent,
72+
AccordionItem,
73+
AccordionTrigger,
74+
} from "../../tailwind/ui/accordion"
75+
76+
import { useClipboard } from "@/hooks/useClipboard"
6777
import { fetchCommunityEvents } from "@/lib/api/calendarEvents"
6878
import { fetchEthPrice } from "@/lib/api/fetchEthPrice"
6979
import { fetchGrowThePie } from "@/lib/api/fetchGrowThePie"
@@ -181,6 +191,8 @@ const HomePage = ({
181191
bentoItems,
182192
} = useHome()
183193

194+
const { onCopy, hasCopied } = useClipboard()
195+
184196
return (
185197
<MainArticle className="flex w-full flex-col items-center" dir={dir}>
186198
<PageMetadata
@@ -350,7 +362,7 @@ const HomePage = ({
350362

351363
{/* Builders - Blockchain's biggest builder community */}
352364
<Section id="builders" variant="responsiveFlex">
353-
<SectionBanner>
365+
<SectionBanner className="relative">
354366
<TwImage src={BuildersImage} alt="" />
355367
</SectionBanner>
356368

@@ -382,10 +394,16 @@ const HomePage = ({
382394
title={t("page-index:page-index-developers-code-examples")}
383395
Svg={AngleBrackets}
384396
>
397+
{/* Desktop */}
385398
{codeExamples.map(({ title, description }, idx) => (
386399
<button
387400
key={title}
388-
className="flex flex-col gap-y-0.5 border-t px-6 py-4 hover:bg-background-highlight"
401+
className={cn(
402+
"flex flex-col gap-y-0.5 border-t px-6 py-4 hover:bg-background-highlight max-md:hidden",
403+
isModalOpen &&
404+
idx === activeCode &&
405+
"bg-background-highlight"
406+
)}
389407
onClick={() => toggleCodeExample(idx)}
390408
>
391409
<p className="font-bold">{title}</p>
@@ -394,27 +412,70 @@ const HomePage = ({
394412
</p>
395413
</button>
396414
))}
415+
{/* Mobile */}
416+
<Accordion type="single" collapsible className="md:hidden">
417+
{codeExamples.map(
418+
({ title, description, code, codeLanguage }) => (
419+
<AccordionItem
420+
key={title}
421+
value={title}
422+
className="relative"
423+
>
424+
<AccordionTrigger className="flex border-t px-6 py-4 hover:bg-background-highlight">
425+
<div className="flex flex-col items-start gap-y-0.5">
426+
<p className="text-md font-bold text-body">
427+
{title}
428+
</p>
429+
<p className="text-start text-sm text-body-medium">
430+
{description}
431+
</p>
432+
</div>
433+
</AccordionTrigger>
434+
<AccordionContent className="relative border-t">
435+
<Suspense fallback={<SkeletonLines noOfLines={16} />}>
436+
<div className="-m-2 max-h-[50vh] overflow-auto">
437+
<Codeblock
438+
codeLanguage={codeLanguage}
439+
allowCollapse={false}
440+
className="[&>div]:-m-//2 [&>div]:rounded-none [&_*]:!text-xs [&_pre]:p-4"
441+
fromHomepage
442+
>
443+
{code}
444+
</Codeblock>
445+
<Button
446+
onClick={() => onCopy(code)}
447+
className="absolute end-4 top-4"
448+
>
449+
{hasCopied ? <MdCheck /> : <IoMdCopy />}
450+
</Button>
451+
</div>
452+
</Suspense>
453+
</AccordionContent>
454+
</AccordionItem>
455+
)
456+
)}
457+
</Accordion>
397458
</WindowBox>
459+
{isModalOpen && (
460+
// TODO: Migrate CodeModal, CodeBlock from Chakra-UI to tailwind/shad-cn
461+
<CodeModal
462+
isOpen={isModalOpen}
463+
setIsOpen={setModalOpen}
464+
title={codeExamples[activeCode].title}
465+
>
466+
<Suspense fallback={<SkeletonLines noOfLines={16} />}>
467+
<Codeblock
468+
codeLanguage={codeExamples[activeCode].codeLanguage}
469+
allowCollapse={false}
470+
className="[&_pre]:p-6"
471+
fromHomepage
472+
>
473+
{codeExamples[activeCode].code}
474+
</Codeblock>
475+
</Suspense>
476+
</CodeModal>
477+
)}
398478
</div>
399-
400-
{isModalOpen && (
401-
// TODO: Migrate CodeModal, CodeBlock from Chakra-UI to tailwind/shad-cn
402-
<CodeModal
403-
isOpen={isModalOpen}
404-
setIsOpen={setModalOpen}
405-
title={codeExamples[activeCode].title}
406-
>
407-
<Suspense fallback={<SkeletonLines noOfLines={16} />}>
408-
<Codeblock
409-
codeLanguage={codeExamples[activeCode].codeLanguage}
410-
allowCollapse={false}
411-
fromHomepage
412-
>
413-
{codeExamples[activeCode].code}
414-
</Codeblock>
415-
</Suspense>
416-
</CodeModal>
417-
)}
418479
</SectionContent>
419480
</Section>
420481

0 commit comments

Comments
 (0)