From f4ef743caa837a01685f8fca9762e29eaca53a08 Mon Sep 17 00:00:00 2001 From: Shashank RM Date: Mon, 19 May 2025 19:00:13 +0530 Subject: [PATCH 1/3] - Add package manager tab switching component and made it wrap code blocks --- .../src/components/mdx/PackageManagerTabs.tsx | 79 +++++++++++++++++++ site/src/components/mdx/index.tsx | 4 +- site/src/styles/index.css | 78 ++++++++++++++++++ 3 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 site/src/components/mdx/PackageManagerTabs.tsx diff --git a/site/src/components/mdx/PackageManagerTabs.tsx b/site/src/components/mdx/PackageManagerTabs.tsx new file mode 100644 index 0000000..e705f10 --- /dev/null +++ b/site/src/components/mdx/PackageManagerTabs.tsx @@ -0,0 +1,79 @@ +import React, { useState } from 'react'; + +const packageManagers = [ + { id: 'npm', label: 'npm', icon: '๐Ÿ“ฆ', command: 'npm install lovit' }, + { id: 'yarn', label: 'yarn', icon: '๐Ÿงถ', command: 'yarn add lovit' }, + { id: 'pnpm', label: 'pnpm', icon: '๐Ÿ“ฆ', command: 'pnpm add lovit' }, + { id: 'bun', label: 'bun', icon: '๐Ÿž', command: 'bun add lovit' } +]; + +const PackageManagerTabs: React.FC> = (props) => { + const [activeTab, setActiveTab] = useState(packageManagers[0].id); + const [copied, setCopied] = useState(false); + const activeCommand = packageManagers.find((pm) => pm.id === activeTab)?.command || ''; + + const handleCopy = async () => { + try { + await navigator.clipboard.writeText(activeCommand); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } catch (error) { + console.error('Failed to copy:', error); + } + }; + + return ( +
+
+ {packageManagers.map((pm) => ( + + ))} +
+
+
+          {activeCommand}
+        
+ +
+
+ ); +}; + +export default PackageManagerTabs; diff --git a/site/src/components/mdx/index.tsx b/site/src/components/mdx/index.tsx index 7de60f1..43356a0 100644 --- a/site/src/components/mdx/index.tsx +++ b/site/src/components/mdx/index.tsx @@ -2,6 +2,7 @@ import { MDXComponents } from 'mdx/types'; import Callout from './Callout'; import DocLink from './DocLink'; import Heading from './Heading'; +import PackageManagerTabs from './PackageManagerTabs'; import UnorderedList from './UnorderedList'; const components: MDXComponents = { @@ -11,7 +12,8 @@ const components: MDXComponents = { ul: (props) => , a: (props) => , p: (props) =>

, - Callout: (props) => + Callout: (props) => , + pre: (props) => }; export default components; diff --git a/site/src/styles/index.css b/site/src/styles/index.css index f4c1e62..dcd104d 100644 --- a/site/src/styles/index.css +++ b/site/src/styles/index.css @@ -84,3 +84,81 @@ ul:has(a[href='/guide/usage'].active) a[href='/guide'] { :focus-visible { @apply outline-grey-light; } + +.package-manager-tabs { + margin: 1.5rem 0; + border: 1px solid var(--color-black-light-2); + border-radius: 8px; + overflow: hidden; +} + +.package-manager-tabs .tabs { + display: flex; + gap: 0; + background: var(--color-black-light); + border-bottom: 1px solid var(--color-black-light-2); + padding: 0.5rem; +} + +.package-manager-tabs .tab { + padding: 0.5rem 1rem; + border: none; + border-radius: 6px; + background: none; + color: var(--color-grey); + cursor: pointer; + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.875rem; + transition: all 0.2s ease; +} + +.package-manager-tabs .tab:hover { + color: var(--color-grey-light); +} + +.package-manager-tabs .tab.active { + background: var(--color-black); + color: var(--color-grey-light); +} + +.package-manager-tabs .code-block-wrapper { + position: relative; + background: var(--color-black); +} + +.package-manager-tabs .code-block { + margin: 0; + padding: 1rem; + background: var(--color-black); + border-radius: 0; + overflow-x: auto; + color: var(--color-grey-light); + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + font-size: 0.875rem; + line-height: 1.5; +} + +.package-manager-tabs .copy-button { + position: absolute; + top: 0.5rem; + right: 0.5rem; + padding: 0.5rem; + border: none; + border-radius: 4px; + background: var(--color-black-light); + color: var(--color-grey); + cursor: pointer; + opacity: 0; + transition: opacity 0.2s ease; +} + +.package-manager-tabs .code-block-wrapper:hover .copy-button { + opacity: 1; +} + +.package-manager-tabs .copy-button:hover { + background: var(--color-black-light-2); + color: var(--color-grey-light); +} From cbe3b684da162c1d88a348889aa93c3fe4ddafcc Mon Sep 17 00:00:00 2001 From: Shashank RM Date: Mon, 19 May 2025 21:29:17 +0530 Subject: [PATCH 2/3] made the packagemanager component dynamic and added svgs --- site/src/components/mdx/Clipboard.tsx | 5 + .../src/components/mdx/PackageManagerTabs.tsx | 239 +++++++++++++++++- site/src/styles/index.css | 34 ++- 3 files changed, 267 insertions(+), 11 deletions(-) diff --git a/site/src/components/mdx/Clipboard.tsx b/site/src/components/mdx/Clipboard.tsx index d8e91c9..74c4446 100644 --- a/site/src/components/mdx/Clipboard.tsx +++ b/site/src/components/mdx/Clipboard.tsx @@ -6,6 +6,11 @@ function Clipboard({ children }: { children: ReactNode }) { const buttons: { button: HTMLButtonElement; onClick: () => void }[] = []; for (const figure of figures) { + // Skip if it's a package manager tabs code block + if (figure.closest('.package-manager-tabs')) { + continue; + } + if (figure.querySelector('.copy-button')) { continue; } diff --git a/site/src/components/mdx/PackageManagerTabs.tsx b/site/src/components/mdx/PackageManagerTabs.tsx index e705f10..4225370 100644 --- a/site/src/components/mdx/PackageManagerTabs.tsx +++ b/site/src/components/mdx/PackageManagerTabs.tsx @@ -1,16 +1,225 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState, type ReactElement } from 'react'; -const packageManagers = [ - { id: 'npm', label: 'npm', icon: '๐Ÿ“ฆ', command: 'npm install lovit' }, - { id: 'yarn', label: 'yarn', icon: '๐Ÿงถ', command: 'yarn add lovit' }, - { id: 'pnpm', label: 'pnpm', icon: '๐Ÿ“ฆ', command: 'pnpm add lovit' }, - { id: 'bun', label: 'bun', icon: '๐Ÿž', command: 'bun add lovit' } +interface PackageManager { + id: string; + label: string; + icon: ReactElement; + commandTemplate: string; +} + +interface PackageManagerTabsProps extends React.ComponentPropsWithoutRef<'pre'> { + packageName?: string; + customPackageManagers?: PackageManager[]; +} + +const defaultPackageManagers: PackageManager[] = [ + { + id: 'npm', + label: 'npm', + icon: ( + + + + + {' '} + + {' '} + + {' '} + {' '} + + {' '} + {' '} + {' '} + + + ), + commandTemplate: 'npm install {package}' + }, + { + id: 'yarn', + label: 'yarn', + icon: ( + + + + + {' '} + + {' '} + + {' '} + {' '} + + {' '} + {' '} + {' '} + + + ), + commandTemplate: 'yarn add {package}' + }, + { + id: 'pnpm', + label: 'pnpm', + icon: ( + + Pnpm Streamline Icon: https://streamlinehq.com + + + + ), + commandTemplate: 'pnpm add {package}' + }, + { + id: 'bun', + label: 'bun', + icon: ( + + Bun Streamline Icon: https://streamlinehq.com + + + + + + + + + + + + + + + + ), + commandTemplate: 'bun add {package}' + } ]; -const PackageManagerTabs: React.FC> = (props) => { - const [activeTab, setActiveTab] = useState(packageManagers[0].id); +const STORAGE_KEY = 'preferred-package-manager'; + +const PackageManagerTabs: React.FC = ({ + packageName = 'lovit', + customPackageManagers, + ...props +}) => { + const packageManagers = customPackageManagers || defaultPackageManagers; + + const [activeTab, setActiveTab] = useState(() => { + if (globalThis.window !== undefined) { + return globalThis.window.localStorage.getItem(STORAGE_KEY) || packageManagers[0].id; + } + return packageManagers[0].id; + }); const [copied, setCopied] = useState(false); - const activeCommand = packageManagers.find((pm) => pm.id === activeTab)?.command || ''; + + const activePackageManager = packageManagers.find((pm) => pm.id === activeTab); + const activeCommand = activePackageManager + ? activePackageManager.commandTemplate.replace('{package}', packageName) + : ''; + + useEffect(() => { + globalThis.window.localStorage.setItem(STORAGE_KEY, activeTab); + }, [activeTab]); const handleCopy = async () => { try { @@ -22,6 +231,16 @@ const PackageManagerTabs: React.FC> = (pro } }; + const renderCommand = (command: string) => { + const [pkgManager, ...rest] = command.split(' '); + return ( + <> + {pkgManager} + {rest.join(' ')} + + ); + }; + return (

@@ -37,7 +256,7 @@ const PackageManagerTabs: React.FC> = (pro
-          {activeCommand}
+          {renderCommand(activeCommand)}
         
))}