Skip to content

Commit dadb8b7

Browse files
committed
feat: script view
1 parent 624283c commit dadb8b7

5 files changed

Lines changed: 173 additions & 38 deletions

File tree

package-lock.json

Lines changed: 45 additions & 37 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@
200200
"scripty": "^3.0.0",
201201
"serve": "^14.2.4",
202202
"shelljs": "^0.8.5",
203-
"shiki": "^3.1.0",
203+
"shiki": "^3.2.1",
204204
"standard": "^17.1.2",
205205
"standard-version": "^9.5.0",
206206
"style-loader": "^4.0.0",
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
'use client'
2+
3+
import React, { useEffect, useState } from 'react'
4+
import { Check, Copy } from 'lucide-react'
5+
import { motion } from 'motion/react'
6+
import clsx from 'clsx'
7+
8+
import styles from './index.module.less'
9+
10+
const ScriptView = ({ showMultiplePackageOptions = true, codeLanguage, commandMap, className }) => {
11+
const packageManagers = Object.keys(commandMap)
12+
const [packageManager, setPackageManager] = useState(packageManagers[0])
13+
const [copied, setCopied] = useState(false)
14+
const [highlightedCode, setHighlightedCode] = useState('')
15+
const command = commandMap[packageManager]
16+
17+
useEffect(() => {
18+
async function loadHighlightedCode() {
19+
try {
20+
const { codeToHtml } = await import('shiki')
21+
const highlighted = await codeToHtml(command, {
22+
lang: codeLanguage,
23+
defaultColor: 'light',
24+
theme: 'github-dark',
25+
wrap: false,
26+
})
27+
setHighlightedCode(highlighted)
28+
} catch (error) {
29+
setHighlightedCode(`<pre>${command}</pre>`)
30+
}
31+
}
32+
33+
loadHighlightedCode()
34+
}, [command, codeLanguage])
35+
36+
const copyToClipboard = () => {
37+
navigator.clipboard.writeText(command)
38+
setCopied(true)
39+
setTimeout(() => setCopied(false), 2000)
40+
}
41+
42+
return (
43+
<div className={clsx('mx-auto flex max-w-md items-center justify-center', className)}>
44+
<div className="w-full space-y-2">
45+
<div className="mb-2 flex items-center justify-between">
46+
{showMultiplePackageOptions && (
47+
<div className="relative">
48+
<div className="border-border inline-flex overflow-hidden rounded-md border text-xs">
49+
{packageManagers.map((pm, index) => (
50+
<div key={pm} className="flex items-center">
51+
{index > 0 && <div className={`h-4 w-px ${styles['bg-border']}`} aria-hidden="true" />}
52+
<span
53+
className={`bg-background hover:bg-background text-primary relative cursor-pointer rounded-none px-2 py-1`}
54+
onClick={() => setPackageManager(pm)}
55+
>
56+
{pm}
57+
{packageManager === pm && (
58+
<motion.div
59+
className={`absolute inset-x-0 bottom-[1px] mx-auto h-0.5 w-[90%] ${styles['bg-primary']}`}
60+
layoutId="activeTab"
61+
initial={false}
62+
transition={{
63+
type: 'spring',
64+
stiffness: 500,
65+
damping: 30,
66+
}}
67+
/>
68+
)}
69+
</span>
70+
</div>
71+
))}
72+
</div>
73+
</div>
74+
)}
75+
</div>
76+
<div className="relative flex items-center">
77+
<div className="min-w-[300px] grow font-mono">
78+
{highlightedCode ? (
79+
<div
80+
className={`${styles.pre} light [&>pre]:overflow-x-auto [&>pre]:rounded-md [&>pre]:p-2 [&>pre]:px-4 [&>pre]:font-mono`}
81+
dangerouslySetInnerHTML={{ __html: highlightedCode }}
82+
/>
83+
) : (
84+
<pre
85+
className={`${styles.pre} border-border rounded-md border bg-white p-2 px-4 font-mono dark:bg-black`}
86+
>
87+
{command}
88+
</pre>
89+
)}
90+
</div>
91+
<span className="relative ml-2 cursor-pointer rounded-md" onClick={copyToClipboard}>
92+
<span className="sr-only">{copied ? 'Copied' : 'Copy'}</span>
93+
<Copy className={`h-4 w-4 transition-all duration-300 ${copied ? 'scale-0' : 'scale-100'}`} />
94+
<Check
95+
className={`absolute inset-0 m-auto h-4 w-4 transition-all duration-300 ${
96+
copied ? 'scale-100' : 'scale-0'
97+
}`}
98+
/>
99+
</span>
100+
</div>
101+
</div>
102+
</div>
103+
)
104+
}
105+
106+
export default ScriptView
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.pre pre {
2+
margin-bottom: 0;
3+
}
4+
5+
.bg-primary {
6+
background-color: #333;
7+
}
8+
9+
.bg-border {
10+
background-color: #eee;
11+
}

src/pages/demo/index.jsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import XuePng from '@assets/images/xue.png'
1717
import { Command, Cannabis, Beer, Mail } from 'lucide-react'
1818

1919
import styles from './index.module.less'
20+
import ScriptView from '@stateless/ScriptView'
2021

2122
const companies = [SpringPng, HePng, SongPng, XuePng]
2223
const columns = [
@@ -86,9 +87,18 @@ const tabs = [
8687
),
8788
},
8889
]
90+
91+
const customCommandMap = {
92+
npm: 'npm run shadcn add button',
93+
yarn: 'yarn shadcn add button',
94+
pnpm: 'pnpm dlx shadcn@latest add button',
95+
bun: 'bun x shadcn@latest add button',
96+
}
97+
8998
const ProDemo = () => {
9099
return (
91100
<FixTabPanel>
101+
<ScriptView showMultiplePackageOptions={true} codeLanguage="shell" commandMap={customCommandMap} />
92102
<StarBack />
93103
<StickyCard cards={[...Array.from({ length: 4 }, () => ({ id: Math.random() }))]} />
94104
<div className="relative w-full overflow-hidden bg-[#0a192f]">

0 commit comments

Comments
 (0)